gitspace 0.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. package/.claude/settings.local.json +21 -0
  2. package/.gitspace/bundle.json +50 -0
  3. package/.gitspace/select/01-status.sh +40 -0
  4. package/.gitspace/setup/01-install-deps.sh +12 -0
  5. package/.gitspace/setup/02-typecheck.sh +16 -0
  6. package/AGENTS.md +439 -0
  7. package/CLAUDE.md +1 -0
  8. package/LICENSE +25 -0
  9. package/README.md +607 -0
  10. package/bin/gssh +62 -0
  11. package/bun.lock +647 -0
  12. package/docs/CONNECTION.md +623 -0
  13. package/docs/GATEWAY-WORKER.md +319 -0
  14. package/docs/GETTING-STARTED.md +448 -0
  15. package/docs/GITSPACE-PLATFORM.md +1819 -0
  16. package/docs/INFRASTRUCTURE.md +1347 -0
  17. package/docs/PROTOCOL.md +619 -0
  18. package/docs/QUICKSTART.md +174 -0
  19. package/docs/RELAY.md +327 -0
  20. package/docs/REMOTE-DESIGN.md +549 -0
  21. package/docs/ROADMAP.md +564 -0
  22. package/docs/SITE_DOCS_FIGMA_MAKE.md +1167 -0
  23. package/docs/STACK-DESIGN.md +588 -0
  24. package/docs/UNIFIED_ARCHITECTURE.md +292 -0
  25. package/experiments/pty-benchmark.ts +148 -0
  26. package/experiments/pty-latency.ts +100 -0
  27. package/experiments/router/client.ts +199 -0
  28. package/experiments/router/protocol.ts +74 -0
  29. package/experiments/router/router.ts +217 -0
  30. package/experiments/router/session.ts +180 -0
  31. package/experiments/router/test.ts +133 -0
  32. package/experiments/socket-bandwidth.ts +77 -0
  33. package/homebrew/gitspace.rb +45 -0
  34. package/landing-page/ATTRIBUTIONS.md +3 -0
  35. package/landing-page/README.md +11 -0
  36. package/landing-page/bun.lock +801 -0
  37. package/landing-page/guidelines/Guidelines.md +61 -0
  38. package/landing-page/index.html +37 -0
  39. package/landing-page/package.json +90 -0
  40. package/landing-page/postcss.config.mjs +15 -0
  41. package/landing-page/public/_redirects +1 -0
  42. package/landing-page/public/favicon.png +0 -0
  43. package/landing-page/src/app/App.tsx +53 -0
  44. package/landing-page/src/app/components/figma/ImageWithFallback.tsx +27 -0
  45. package/landing-page/src/app/components/ui/accordion.tsx +66 -0
  46. package/landing-page/src/app/components/ui/alert-dialog.tsx +157 -0
  47. package/landing-page/src/app/components/ui/alert.tsx +66 -0
  48. package/landing-page/src/app/components/ui/aspect-ratio.tsx +11 -0
  49. package/landing-page/src/app/components/ui/avatar.tsx +53 -0
  50. package/landing-page/src/app/components/ui/badge.tsx +46 -0
  51. package/landing-page/src/app/components/ui/breadcrumb.tsx +109 -0
  52. package/landing-page/src/app/components/ui/button.tsx +57 -0
  53. package/landing-page/src/app/components/ui/calendar.tsx +75 -0
  54. package/landing-page/src/app/components/ui/card.tsx +92 -0
  55. package/landing-page/src/app/components/ui/carousel.tsx +241 -0
  56. package/landing-page/src/app/components/ui/chart.tsx +353 -0
  57. package/landing-page/src/app/components/ui/checkbox.tsx +32 -0
  58. package/landing-page/src/app/components/ui/collapsible.tsx +33 -0
  59. package/landing-page/src/app/components/ui/command.tsx +177 -0
  60. package/landing-page/src/app/components/ui/context-menu.tsx +252 -0
  61. package/landing-page/src/app/components/ui/dialog.tsx +135 -0
  62. package/landing-page/src/app/components/ui/drawer.tsx +132 -0
  63. package/landing-page/src/app/components/ui/dropdown-menu.tsx +257 -0
  64. package/landing-page/src/app/components/ui/form.tsx +168 -0
  65. package/landing-page/src/app/components/ui/hover-card.tsx +44 -0
  66. package/landing-page/src/app/components/ui/input-otp.tsx +77 -0
  67. package/landing-page/src/app/components/ui/input.tsx +21 -0
  68. package/landing-page/src/app/components/ui/label.tsx +24 -0
  69. package/landing-page/src/app/components/ui/menubar.tsx +276 -0
  70. package/landing-page/src/app/components/ui/navigation-menu.tsx +168 -0
  71. package/landing-page/src/app/components/ui/pagination.tsx +127 -0
  72. package/landing-page/src/app/components/ui/popover.tsx +48 -0
  73. package/landing-page/src/app/components/ui/progress.tsx +31 -0
  74. package/landing-page/src/app/components/ui/radio-group.tsx +45 -0
  75. package/landing-page/src/app/components/ui/resizable.tsx +56 -0
  76. package/landing-page/src/app/components/ui/scroll-area.tsx +58 -0
  77. package/landing-page/src/app/components/ui/select.tsx +189 -0
  78. package/landing-page/src/app/components/ui/separator.tsx +28 -0
  79. package/landing-page/src/app/components/ui/sheet.tsx +139 -0
  80. package/landing-page/src/app/components/ui/sidebar.tsx +726 -0
  81. package/landing-page/src/app/components/ui/skeleton.tsx +13 -0
  82. package/landing-page/src/app/components/ui/slider.tsx +63 -0
  83. package/landing-page/src/app/components/ui/sonner.tsx +25 -0
  84. package/landing-page/src/app/components/ui/switch.tsx +31 -0
  85. package/landing-page/src/app/components/ui/table.tsx +116 -0
  86. package/landing-page/src/app/components/ui/tabs.tsx +66 -0
  87. package/landing-page/src/app/components/ui/textarea.tsx +18 -0
  88. package/landing-page/src/app/components/ui/toggle-group.tsx +73 -0
  89. package/landing-page/src/app/components/ui/toggle.tsx +47 -0
  90. package/landing-page/src/app/components/ui/tooltip.tsx +61 -0
  91. package/landing-page/src/app/components/ui/use-mobile.ts +21 -0
  92. package/landing-page/src/app/components/ui/utils.ts +6 -0
  93. package/landing-page/src/components/docs/DocsContent.tsx +718 -0
  94. package/landing-page/src/components/docs/DocsSidebar.tsx +84 -0
  95. package/landing-page/src/components/landing/CTA.tsx +59 -0
  96. package/landing-page/src/components/landing/Comparison.tsx +84 -0
  97. package/landing-page/src/components/landing/FaultyTerminal.tsx +424 -0
  98. package/landing-page/src/components/landing/Features.tsx +201 -0
  99. package/landing-page/src/components/landing/Hero.tsx +142 -0
  100. package/landing-page/src/components/landing/Pricing.tsx +140 -0
  101. package/landing-page/src/components/landing/Roadmap.tsx +86 -0
  102. package/landing-page/src/components/landing/Security.tsx +81 -0
  103. package/landing-page/src/components/landing/TerminalWindow.tsx +27 -0
  104. package/landing-page/src/components/landing/UseCases.tsx +55 -0
  105. package/landing-page/src/components/landing/Workflow.tsx +101 -0
  106. package/landing-page/src/components/layout/DashboardNavbar.tsx +37 -0
  107. package/landing-page/src/components/layout/Footer.tsx +55 -0
  108. package/landing-page/src/components/layout/LandingNavbar.tsx +82 -0
  109. package/landing-page/src/components/ui/badge.tsx +39 -0
  110. package/landing-page/src/components/ui/breadcrumb.tsx +115 -0
  111. package/landing-page/src/components/ui/button.tsx +57 -0
  112. package/landing-page/src/components/ui/card.tsx +79 -0
  113. package/landing-page/src/components/ui/mock-terminal.tsx +68 -0
  114. package/landing-page/src/components/ui/separator.tsx +28 -0
  115. package/landing-page/src/lib/utils.ts +6 -0
  116. package/landing-page/src/main.tsx +10 -0
  117. package/landing-page/src/pages/Dashboard.tsx +133 -0
  118. package/landing-page/src/pages/DocsPage.tsx +79 -0
  119. package/landing-page/src/pages/LandingPage.tsx +31 -0
  120. package/landing-page/src/pages/TerminalView.tsx +106 -0
  121. package/landing-page/src/styles/fonts.css +0 -0
  122. package/landing-page/src/styles/index.css +3 -0
  123. package/landing-page/src/styles/tailwind.css +4 -0
  124. package/landing-page/src/styles/theme.css +181 -0
  125. package/landing-page/vite.config.ts +19 -0
  126. package/npm/darwin-arm64/bin/gssh +0 -0
  127. package/npm/darwin-arm64/package.json +20 -0
  128. package/package.json +74 -0
  129. package/scripts/build.ts +284 -0
  130. package/scripts/release.ts +140 -0
  131. package/src/__tests__/test-utils.ts +298 -0
  132. package/src/commands/__tests__/serve-messages.test.ts +190 -0
  133. package/src/commands/access.ts +298 -0
  134. package/src/commands/add.ts +452 -0
  135. package/src/commands/auth.ts +364 -0
  136. package/src/commands/connect.ts +287 -0
  137. package/src/commands/directory.ts +16 -0
  138. package/src/commands/host.ts +396 -0
  139. package/src/commands/identity.ts +184 -0
  140. package/src/commands/list.ts +200 -0
  141. package/src/commands/relay.ts +315 -0
  142. package/src/commands/remove.ts +241 -0
  143. package/src/commands/serve.ts +1493 -0
  144. package/src/commands/share.ts +456 -0
  145. package/src/commands/status.ts +125 -0
  146. package/src/commands/switch.ts +353 -0
  147. package/src/commands/tmux.ts +317 -0
  148. package/src/core/__tests__/access.test.ts +240 -0
  149. package/src/core/access.ts +277 -0
  150. package/src/core/bundle.ts +342 -0
  151. package/src/core/config.ts +510 -0
  152. package/src/core/git.ts +317 -0
  153. package/src/core/github.ts +151 -0
  154. package/src/core/identity.ts +631 -0
  155. package/src/core/linear.ts +225 -0
  156. package/src/core/shell.ts +161 -0
  157. package/src/core/trusted-relays.ts +315 -0
  158. package/src/index.ts +821 -0
  159. package/src/lib/remote-session/index.ts +7 -0
  160. package/src/lib/remote-session/protocol.ts +267 -0
  161. package/src/lib/remote-session/session-handler.ts +581 -0
  162. package/src/lib/remote-session/workspace-scanner.ts +167 -0
  163. package/src/lib/tmux-lite/README.md +81 -0
  164. package/src/lib/tmux-lite/cli.ts +796 -0
  165. package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +349 -0
  166. package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +291 -0
  167. package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +142 -0
  168. package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +339 -0
  169. package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +477 -0
  170. package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +499 -0
  171. package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +371 -0
  172. package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +573 -0
  173. package/src/lib/tmux-lite/crypto/access-control.test.ts +512 -0
  174. package/src/lib/tmux-lite/crypto/access-control.ts +320 -0
  175. package/src/lib/tmux-lite/crypto/frames.test.ts +262 -0
  176. package/src/lib/tmux-lite/crypto/frames.ts +141 -0
  177. package/src/lib/tmux-lite/crypto/handshake.ts +894 -0
  178. package/src/lib/tmux-lite/crypto/identity.test.ts +220 -0
  179. package/src/lib/tmux-lite/crypto/identity.ts +286 -0
  180. package/src/lib/tmux-lite/crypto/index.ts +51 -0
  181. package/src/lib/tmux-lite/crypto/invites.test.ts +381 -0
  182. package/src/lib/tmux-lite/crypto/invites.ts +215 -0
  183. package/src/lib/tmux-lite/crypto/keyexchange.ts +435 -0
  184. package/src/lib/tmux-lite/crypto/keys.test.ts +58 -0
  185. package/src/lib/tmux-lite/crypto/keys.ts +47 -0
  186. package/src/lib/tmux-lite/crypto/secretbox.test.ts +169 -0
  187. package/src/lib/tmux-lite/crypto/secretbox.ts +124 -0
  188. package/src/lib/tmux-lite/handshake-handler.ts +451 -0
  189. package/src/lib/tmux-lite/protocol.test.ts +307 -0
  190. package/src/lib/tmux-lite/protocol.ts +266 -0
  191. package/src/lib/tmux-lite/relay-client.ts +506 -0
  192. package/src/lib/tmux-lite/server.ts +1250 -0
  193. package/src/lib/tmux-lite/shell-integration.sh +37 -0
  194. package/src/lib/tmux-lite/terminal-queries.test.ts +54 -0
  195. package/src/lib/tmux-lite/terminal-queries.ts +49 -0
  196. package/src/relay/__tests__/e2e-flow.test.ts +1284 -0
  197. package/src/relay/__tests__/helpers/auth.ts +354 -0
  198. package/src/relay/__tests__/helpers/ports.ts +51 -0
  199. package/src/relay/__tests__/protocol-validation.test.ts +265 -0
  200. package/src/relay/authorization.ts +303 -0
  201. package/src/relay/embedded-assets.generated.d.ts +15 -0
  202. package/src/relay/identity.ts +352 -0
  203. package/src/relay/index.ts +57 -0
  204. package/src/relay/pipes.test.ts +427 -0
  205. package/src/relay/pipes.ts +195 -0
  206. package/src/relay/protocol.ts +804 -0
  207. package/src/relay/registries.test.ts +437 -0
  208. package/src/relay/registries.ts +593 -0
  209. package/src/relay/server.test.ts +1323 -0
  210. package/src/relay/server.ts +1092 -0
  211. package/src/relay/signing.ts +238 -0
  212. package/src/relay/types.ts +69 -0
  213. package/src/serve/client-session-manager.ts +622 -0
  214. package/src/serve/daemon.ts +497 -0
  215. package/src/serve/pty-session.ts +236 -0
  216. package/src/serve/types.ts +169 -0
  217. package/src/shared/components/Flow.tsx +453 -0
  218. package/src/shared/components/Flow.tui.tsx +343 -0
  219. package/src/shared/components/Flow.web.tsx +442 -0
  220. package/src/shared/components/Inbox.tsx +446 -0
  221. package/src/shared/components/Inbox.tui.tsx +262 -0
  222. package/src/shared/components/Inbox.web.tsx +329 -0
  223. package/src/shared/components/MachineList.tsx +187 -0
  224. package/src/shared/components/MachineList.tui.tsx +161 -0
  225. package/src/shared/components/MachineList.web.tsx +210 -0
  226. package/src/shared/components/ProjectList.tsx +176 -0
  227. package/src/shared/components/ProjectList.tui.tsx +109 -0
  228. package/src/shared/components/ProjectList.web.tsx +143 -0
  229. package/src/shared/components/SpacesBrowser.tsx +332 -0
  230. package/src/shared/components/SpacesBrowser.tui.tsx +163 -0
  231. package/src/shared/components/SpacesBrowser.web.tsx +221 -0
  232. package/src/shared/components/index.ts +103 -0
  233. package/src/shared/hooks/index.ts +16 -0
  234. package/src/shared/hooks/useNavigation.ts +226 -0
  235. package/src/shared/index.ts +122 -0
  236. package/src/shared/providers/LocalMachineProvider.ts +425 -0
  237. package/src/shared/providers/MachineProvider.ts +165 -0
  238. package/src/shared/providers/RemoteMachineProvider.ts +444 -0
  239. package/src/shared/providers/index.ts +26 -0
  240. package/src/shared/types.ts +145 -0
  241. package/src/tui/adapters.ts +120 -0
  242. package/src/tui/app.tsx +1816 -0
  243. package/src/tui/components/Terminal.tsx +580 -0
  244. package/src/tui/hooks/index.ts +35 -0
  245. package/src/tui/hooks/useAppState.ts +314 -0
  246. package/src/tui/hooks/useDaemonStatus.ts +174 -0
  247. package/src/tui/hooks/useInboxTUI.ts +113 -0
  248. package/src/tui/hooks/useRemoteMachines.ts +209 -0
  249. package/src/tui/index.ts +24 -0
  250. package/src/tui/state.ts +299 -0
  251. package/src/tui/terminal-bracketed-paste.test.ts +45 -0
  252. package/src/tui/terminal-bracketed-paste.ts +47 -0
  253. package/src/types/bundle.ts +112 -0
  254. package/src/types/config.ts +89 -0
  255. package/src/types/errors.ts +206 -0
  256. package/src/types/identity.ts +284 -0
  257. package/src/types/workspace-fuzzy.ts +49 -0
  258. package/src/types/workspace.ts +151 -0
  259. package/src/utils/bun-socket-writer.ts +80 -0
  260. package/src/utils/deps.ts +127 -0
  261. package/src/utils/fuzzy-match.ts +125 -0
  262. package/src/utils/logger.ts +127 -0
  263. package/src/utils/markdown.ts +254 -0
  264. package/src/utils/onboarding.ts +229 -0
  265. package/src/utils/prompts.ts +114 -0
  266. package/src/utils/run-commands.ts +112 -0
  267. package/src/utils/run-scripts.ts +142 -0
  268. package/src/utils/sanitize.ts +98 -0
  269. package/src/utils/secrets.ts +122 -0
  270. package/src/utils/shell-escape.ts +40 -0
  271. package/src/utils/utf8.ts +79 -0
  272. package/src/utils/workspace-state.ts +47 -0
  273. package/src/web/README.md +73 -0
  274. package/src/web/bun.lock +575 -0
  275. package/src/web/eslint.config.js +23 -0
  276. package/src/web/index.html +16 -0
  277. package/src/web/package.json +37 -0
  278. package/src/web/public/vite.svg +1 -0
  279. package/src/web/src/App.tsx +604 -0
  280. package/src/web/src/assets/react.svg +1 -0
  281. package/src/web/src/components/Terminal.tsx +207 -0
  282. package/src/web/src/hooks/useRelayConnection.ts +224 -0
  283. package/src/web/src/hooks/useTerminal.ts +699 -0
  284. package/src/web/src/index.css +55 -0
  285. package/src/web/src/lib/crypto/__tests__/web-terminal.test.ts +1158 -0
  286. package/src/web/src/lib/crypto/frames.ts +205 -0
  287. package/src/web/src/lib/crypto/handshake.ts +396 -0
  288. package/src/web/src/lib/crypto/identity.ts +128 -0
  289. package/src/web/src/lib/crypto/keyexchange.ts +246 -0
  290. package/src/web/src/lib/crypto/relay-signing.ts +53 -0
  291. package/src/web/src/lib/invite.ts +58 -0
  292. package/src/web/src/lib/storage/identity-store.ts +94 -0
  293. package/src/web/src/main.tsx +10 -0
  294. package/src/web/src/types/identity.ts +45 -0
  295. package/src/web/tsconfig.app.json +28 -0
  296. package/src/web/tsconfig.json +7 -0
  297. package/src/web/tsconfig.node.json +26 -0
  298. package/src/web/vite.config.ts +31 -0
  299. package/todo-security.md +92 -0
  300. package/tsconfig.json +23 -0
  301. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite +0 -0
  302. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-shm +0 -0
  303. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-wal +0 -0
  304. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite +0 -0
  305. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-shm +0 -0
  306. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-wal +0 -0
  307. package/worker/bun.lock +237 -0
  308. package/worker/package.json +22 -0
  309. package/worker/schema.sql +96 -0
  310. package/worker/src/handlers/auth.ts +451 -0
  311. package/worker/src/handlers/subdomains.ts +376 -0
  312. package/worker/src/handlers/user.ts +98 -0
  313. package/worker/src/index.ts +70 -0
  314. package/worker/src/middleware/auth.ts +152 -0
  315. package/worker/src/services/cloudflare.ts +609 -0
  316. package/worker/src/types.ts +96 -0
  317. package/worker/tsconfig.json +15 -0
  318. package/worker/wrangler.toml +26 -0
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Integration tests for X3DH handshake flow
3
+ *
4
+ * Tests the complete 4-message handshake exchange between client and machine.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach } from "bun:test";
8
+ import {
9
+ createClientHello,
10
+ processServerHello,
11
+ createClientAuth,
12
+ processServerAuth,
13
+ } from "../../handshake.js";
14
+ import {
15
+ HandshakeHandler,
16
+ type HandshakeMessage,
17
+ } from "../../../handshake-handler.js";
18
+ import { AccessControlList } from "../../access-control.js";
19
+ import { createInviteToken } from "../../invites.js";
20
+ import {
21
+ createTestIdentityPair,
22
+ toPublicIdentity,
23
+ createIdentityFixtures,
24
+ } from "../helpers/test-identities.js";
25
+ import {
26
+ runCompleteHandshake,
27
+ verifyKeyPairing,
28
+ verifyKeysUnique,
29
+ } from "../helpers/handshake-runner.js";
30
+ import type {
31
+ X3DHResponseMessage,
32
+ X3DHResultMessage,
33
+ } from "../../../../../types/identity.js";
34
+
35
+ describe("X3DH Handshake Integration", () => {
36
+ describe("complete 4-message exchange", () => {
37
+ it("should complete handshake with access list authorization", async () => {
38
+ const { client, machine } = createTestIdentityPair();
39
+ const accessList = new AccessControlList();
40
+
41
+ // Add client to access list with full access
42
+ accessList.addEntry(toPublicIdentity(client), 'full');
43
+
44
+ const result = await runCompleteHandshake(
45
+ client,
46
+ machine,
47
+ accessList,
48
+ { type: "access_list" }
49
+ );
50
+
51
+ expect(result.success).toBe(true);
52
+ expect(result.clientKeys).toBeDefined();
53
+ expect(result.machineSession).toBeDefined();
54
+ expect(result.messageCount).toBe(4);
55
+ });
56
+
57
+ it("should complete handshake with invite token authorization", async () => {
58
+ const { client, machine } = createTestIdentityPair();
59
+ const accessList = new AccessControlList();
60
+
61
+ // Note: Client is NOT in access list - using invite instead
62
+ const result = await runCompleteHandshake(
63
+ client,
64
+ machine,
65
+ accessList,
66
+ {
67
+ type: "invite",
68
+ accessType: 'full',
69
+ }
70
+ );
71
+
72
+ expect(result.success).toBe(true);
73
+ expect(result.clientKeys).toBeDefined();
74
+ expect(result.machineSession).toBeDefined();
75
+ });
76
+
77
+ it("should derive matching session keys for both parties", async () => {
78
+ const { client, machine } = createTestIdentityPair();
79
+ const accessList = new AccessControlList();
80
+ accessList.addEntry(toPublicIdentity(client));
81
+
82
+ const result = await runCompleteHandshake(
83
+ client,
84
+ machine,
85
+ accessList,
86
+ { type: "access_list" }
87
+ );
88
+
89
+ expect(result.success).toBe(true);
90
+ expect(result.clientKeys).toBeDefined();
91
+ expect(result.machineSession).toBeDefined();
92
+
93
+ // Verify keys are correctly paired
94
+ const paired = verifyKeyPairing(
95
+ result.clientKeys!,
96
+ result.machineSession!.sessionKeys
97
+ );
98
+ expect(paired).toBe(true);
99
+ });
100
+
101
+ it("should generate unique keys for each session (forward secrecy)", async () => {
102
+ const { client, machine } = createTestIdentityPair();
103
+ const accessList = new AccessControlList();
104
+ accessList.addEntry(toPublicIdentity(client));
105
+
106
+ // Run multiple handshakes
107
+ const results = await Promise.all([
108
+ runCompleteHandshake(client, machine, accessList, { type: "access_list" }),
109
+ runCompleteHandshake(client, machine, accessList, { type: "access_list" }),
110
+ runCompleteHandshake(client, machine, accessList, { type: "access_list" }),
111
+ ]);
112
+
113
+ // All should succeed
114
+ for (const result of results) {
115
+ expect(result.success).toBe(true);
116
+ }
117
+
118
+ // All keys should be unique
119
+ const clientKeysSets = results.map((r) => r.clientKeys!);
120
+ expect(verifyKeysUnique(clientKeysSets)).toBe(true);
121
+ });
122
+ });
123
+
124
+ describe("step-by-step handshake", () => {
125
+ it("should complete ClientHello -> ServerHello exchange", async () => {
126
+ const { client, machine } = createTestIdentityPair();
127
+ const accessList = new AccessControlList();
128
+ const handler = new HandshakeHandler({
129
+ identity: machine,
130
+ accessList,
131
+ });
132
+
133
+ // Client creates ClientHello
134
+ const { state: clientState, message: clientHello } = createClientHello(
135
+ machine.id
136
+ );
137
+
138
+ expect(clientState.phase).toBe("awaiting_server_hello");
139
+ expect(clientHello.version).toBe(1);
140
+ expect(clientHello.ephemeralKey).toBeDefined();
141
+
142
+ // Machine processes ClientHello
143
+ const result = await handler.processMessage("conn-1", {
144
+ type: "handshake",
145
+ phase: "client_hello",
146
+ data: clientHello,
147
+ });
148
+
149
+ expect(result.type).toBe("reply");
150
+ if (result.type === "reply") {
151
+ expect(result.message.phase).toBe("server_hello");
152
+
153
+ const serverHello = result.message.data as X3DHResponseMessage;
154
+ expect(serverHello.version).toBe(1);
155
+ expect(serverHello.identityKey).toBeDefined();
156
+ expect(serverHello.ephemeralKey).toBeDefined();
157
+ expect(serverHello.signedPreKey).toBeDefined();
158
+ expect(serverHello.preKeySignature).toBeDefined();
159
+ expect(serverHello.serverNonce).toBeDefined();
160
+ }
161
+ });
162
+
163
+ it("should complete full manual handshake", async () => {
164
+ const { client, machine } = createTestIdentityPair();
165
+ const accessList = new AccessControlList();
166
+ accessList.addEntry(toPublicIdentity(client));
167
+
168
+ const handler = new HandshakeHandler({
169
+ identity: machine,
170
+ accessList,
171
+ });
172
+
173
+ // Step 1: Client creates ClientHello
174
+ const { state: clientState1, message: clientHello } = createClientHello();
175
+
176
+ // Step 2: Machine processes ClientHello, returns ServerHello
177
+ const result1 = await handler.processMessage("conn-1", {
178
+ type: "handshake",
179
+ phase: "client_hello",
180
+ data: clientHello,
181
+ });
182
+
183
+ expect(result1.type).toBe("reply");
184
+ const serverHello = (result1 as { type: "reply"; message: HandshakeMessage })
185
+ .message.data as X3DHResponseMessage;
186
+
187
+ // Step 3: Client processes ServerHello
188
+ const clientState2 = processServerHello(clientState1, serverHello);
189
+ expect(clientState2).not.toBeNull();
190
+ expect(clientState2!.phase).toBe("awaiting_server_auth");
191
+
192
+ // Step 4: Client creates ClientAuth
193
+ const { message: clientAuth, sessionKeys: clientSessionKeys } = createClientAuth(
194
+ clientState2!,
195
+ client,
196
+ { type: "access_list" }
197
+ );
198
+
199
+ // Step 5: Machine processes ClientAuth
200
+ const result2 = await handler.processMessage("conn-1", {
201
+ type: "handshake",
202
+ phase: "client_auth",
203
+ data: clientAuth,
204
+ });
205
+
206
+ // Should get established session
207
+ expect(result2.type).toBe("established");
208
+ if (result2.type === "established") {
209
+ expect(result2.session.peerIdentityId).toBe(client.id);
210
+ expect(result2.session.accessType).toBe('full');
211
+
212
+ // Verify keys are paired
213
+ const paired = verifyKeyPairing(
214
+ clientSessionKeys,
215
+ result2.session.sessionKeys
216
+ );
217
+ expect(paired).toBe(true);
218
+ }
219
+ });
220
+ });
221
+
222
+ describe("session information", () => {
223
+ it("should include correct peer identity ID", async () => {
224
+ const { client, machine } = createTestIdentityPair();
225
+ const accessList = new AccessControlList();
226
+ accessList.addEntry(toPublicIdentity(client));
227
+
228
+ const result = await runCompleteHandshake(
229
+ client,
230
+ machine,
231
+ accessList,
232
+ { type: "access_list" }
233
+ );
234
+
235
+ expect(result.success).toBe(true);
236
+ expect(result.machineSession?.peerIdentityId).toBe(client.id);
237
+ });
238
+
239
+ it("should include correct access type from access list", async () => {
240
+ const { client, machine } = createTestIdentityPair();
241
+ const accessList = new AccessControlList();
242
+ accessList.addEntry(toPublicIdentity(client), 'session-invite', 'test-session');
243
+
244
+ const result = await runCompleteHandshake(
245
+ client,
246
+ machine,
247
+ accessList,
248
+ { type: "access_list" }
249
+ );
250
+
251
+ expect(result.success).toBe(true);
252
+ expect(result.machineSession?.accessType).toBe('session-invite');
253
+ });
254
+
255
+ it("should include correct access type from invite token", async () => {
256
+ const { client, machine } = createTestIdentityPair();
257
+ const accessList = new AccessControlList();
258
+
259
+ const result = await runCompleteHandshake(
260
+ client,
261
+ machine,
262
+ accessList,
263
+ {
264
+ type: "invite",
265
+ accessType: 'full',
266
+ }
267
+ );
268
+
269
+ expect(result.success).toBe(true);
270
+ expect(result.machineSession?.accessType).toBe('full');
271
+ });
272
+
273
+ it("should have valid session timestamps", async () => {
274
+ const before = Date.now();
275
+
276
+ const { client, machine } = createTestIdentityPair();
277
+ const accessList = new AccessControlList();
278
+ accessList.addEntry(toPublicIdentity(client));
279
+
280
+ const result = await runCompleteHandshake(
281
+ client,
282
+ machine,
283
+ accessList,
284
+ { type: "access_list" }
285
+ );
286
+
287
+ const after = Date.now();
288
+
289
+ expect(result.success).toBe(true);
290
+ expect(result.machineSession?.establishedAt).toBeGreaterThanOrEqual(before);
291
+ expect(result.machineSession?.establishedAt).toBeLessThanOrEqual(after);
292
+ });
293
+ });
294
+
295
+ describe("concurrent handshakes", () => {
296
+ it("should handle multiple concurrent connections", async () => {
297
+ const fixtures = createIdentityFixtures();
298
+ const accessList = new AccessControlList();
299
+ accessList.addEntry(fixtures.alicePublic);
300
+ accessList.addEntry(fixtures.bobPublic);
301
+
302
+ // Run concurrent handshakes with different clients
303
+ const [result1, result2] = await Promise.all([
304
+ runCompleteHandshake(
305
+ fixtures.alice,
306
+ fixtures.machine,
307
+ accessList,
308
+ { type: "access_list" }
309
+ ),
310
+ runCompleteHandshake(
311
+ fixtures.bob,
312
+ fixtures.machine,
313
+ accessList,
314
+ { type: "access_list" }
315
+ ),
316
+ ]);
317
+
318
+ expect(result1.success).toBe(true);
319
+ expect(result2.success).toBe(true);
320
+
321
+ // Sessions should have different peer IDs
322
+ expect(result1.machineSession?.peerIdentityId).toBe(fixtures.alice.id);
323
+ expect(result2.machineSession?.peerIdentityId).toBe(fixtures.bob.id);
324
+
325
+ // Keys should be unique
326
+ expect(
327
+ verifyKeysUnique([result1.clientKeys!, result2.clientKeys!])
328
+ ).toBe(true);
329
+ });
330
+
331
+ it("should isolate handshake state per connection", async () => {
332
+ const { client, machine } = createTestIdentityPair();
333
+ const accessList = new AccessControlList();
334
+ accessList.addEntry(toPublicIdentity(client));
335
+
336
+ const handler = new HandshakeHandler({
337
+ identity: machine,
338
+ accessList,
339
+ });
340
+
341
+ // Start two handshakes
342
+ const { message: clientHello1 } = createClientHello();
343
+ const { message: clientHello2 } = createClientHello();
344
+
345
+ // Process both ClientHellos
346
+ await handler.processMessage("conn-1", {
347
+ type: "handshake",
348
+ phase: "client_hello",
349
+ data: clientHello1,
350
+ });
351
+
352
+ await handler.processMessage("conn-2", {
353
+ type: "handshake",
354
+ phase: "client_hello",
355
+ data: clientHello2,
356
+ });
357
+
358
+ // Both connections should have active handshakes
359
+ expect(handler.hasActiveHandshake("conn-1")).toBe(true);
360
+ expect(handler.hasActiveHandshake("conn-2")).toBe(true);
361
+ expect(handler.activeHandshakes).toBe(2);
362
+
363
+ // Cleanup one connection
364
+ handler.cleanup("conn-1");
365
+
366
+ expect(handler.hasActiveHandshake("conn-1")).toBe(false);
367
+ expect(handler.hasActiveHandshake("conn-2")).toBe(true);
368
+ expect(handler.activeHandshakes).toBe(1);
369
+ });
370
+ });
371
+ });