@dxos/client-services 0.10.0 → 0.11.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 (304) hide show
  1. package/dist/lib/chunk-client-services-layer.mjs +11425 -0
  2. package/dist/lib/chunk-client-services-layer.mjs.map +1 -0
  3. package/dist/lib/chunk-decorate.mjs +9 -0
  4. package/dist/lib/index.mjs +731 -0
  5. package/dist/lib/index.mjs.map +1 -0
  6. package/dist/lib/packlets/diagnostics/browser-diagnostics-broadcast.mjs +72 -0
  7. package/dist/lib/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +1 -0
  8. package/dist/lib/packlets/diagnostics/diagnostics-broadcast.mjs +14 -0
  9. package/dist/lib/packlets/diagnostics/diagnostics-broadcast.mjs.map +1 -0
  10. package/dist/lib/packlets/locks/browser.mjs +105 -0
  11. package/dist/lib/packlets/locks/browser.mjs.map +1 -0
  12. package/dist/lib/packlets/locks/node.mjs +53 -0
  13. package/dist/lib/packlets/locks/node.mjs.map +1 -0
  14. package/dist/lib/testing.mjs +570 -0
  15. package/dist/lib/testing.mjs.map +1 -0
  16. package/dist/types/src/index.d.ts +2 -0
  17. package/dist/types/src/index.d.ts.map +1 -1
  18. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts +21 -4
  19. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts.map +1 -1
  20. package/dist/types/src/packlets/agents/edge-agent-service.d.ts +8 -7
  21. package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -1
  22. package/dist/types/src/packlets/devices/devices-service.d.ts +7 -5
  23. package/dist/types/src/packlets/devices/devices-service.d.ts.map +1 -1
  24. package/dist/types/src/packlets/devtools/devtools.d.ts +30 -29
  25. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  26. package/dist/types/src/packlets/devtools/feeds.d.ts +1 -1
  27. package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
  28. package/dist/types/src/packlets/devtools/network.d.ts +2 -1
  29. package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
  30. package/dist/types/src/packlets/devtools/spaces.d.ts.map +1 -1
  31. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts +2 -2
  32. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
  33. package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -1
  34. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts +1 -8
  35. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -1
  36. package/dist/types/src/packlets/identity/authenticator.d.ts +1 -1
  37. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  38. package/dist/types/src/packlets/identity/contacts-service.d.ts +9 -6
  39. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  40. package/dist/types/src/packlets/identity/identity-manager.d.ts +32 -3
  41. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  42. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +23 -4
  43. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
  44. package/dist/types/src/packlets/identity/identity-service.d.ts +15 -20
  45. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  46. package/dist/types/src/packlets/identity/identity.d.ts +2 -2
  47. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  48. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -1
  49. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +17 -0
  50. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  51. package/dist/types/src/packlets/invitations/invitations-manager.d.ts +25 -4
  52. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
  53. package/dist/types/src/packlets/invitations/invitations-service.d.ts +11 -11
  54. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  55. package/dist/types/src/packlets/logging/logging-service.d.ts +14 -12
  56. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  57. package/dist/types/src/packlets/metadata/index.d.ts +3 -0
  58. package/dist/types/src/packlets/metadata/index.d.ts.map +1 -0
  59. package/dist/types/src/packlets/metadata/metadata-store.d.ts +107 -0
  60. package/dist/types/src/packlets/metadata/metadata-store.d.ts.map +1 -0
  61. package/dist/types/src/packlets/metadata/metadata-store.test.d.ts +2 -0
  62. package/dist/types/src/packlets/metadata/metadata-store.test.d.ts.map +1 -0
  63. package/dist/types/src/packlets/metadata/sqlite-metadata-store.d.ts +65 -0
  64. package/dist/types/src/packlets/metadata/sqlite-metadata-store.d.ts.map +1 -0
  65. package/dist/types/src/packlets/network/network-service.d.ts +15 -14
  66. package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
  67. package/dist/types/src/packlets/pipeline/codec.d.ts +11 -0
  68. package/dist/types/src/packlets/pipeline/codec.d.ts.map +1 -0
  69. package/dist/types/src/packlets/pipeline/feeds.d.ts +7 -0
  70. package/dist/types/src/packlets/pipeline/feeds.d.ts.map +1 -0
  71. package/dist/types/src/packlets/pipeline/index.d.ts +5 -0
  72. package/dist/types/src/packlets/pipeline/index.d.ts.map +1 -0
  73. package/dist/types/src/packlets/pipeline/message-selector.d.ts +11 -0
  74. package/dist/types/src/packlets/pipeline/message-selector.d.ts.map +1 -0
  75. package/dist/types/src/packlets/pipeline/pipeline-stress.test.d.ts +2 -0
  76. package/dist/types/src/packlets/pipeline/pipeline-stress.test.d.ts.map +1 -0
  77. package/dist/types/src/packlets/pipeline/pipeline.d.ts +131 -0
  78. package/dist/types/src/packlets/pipeline/pipeline.d.ts.map +1 -0
  79. package/dist/types/src/packlets/pipeline/pipeline.test.d.ts +2 -0
  80. package/dist/types/src/packlets/pipeline/pipeline.test.d.ts.map +1 -0
  81. package/dist/types/src/packlets/pipeline/testing/index.d.ts +2 -0
  82. package/dist/types/src/packlets/pipeline/testing/index.d.ts.map +1 -0
  83. package/dist/types/src/packlets/pipeline/testing/test-feed-builder.d.ts +9 -0
  84. package/dist/types/src/packlets/pipeline/testing/test-feed-builder.d.ts.map +1 -0
  85. package/dist/types/src/packlets/pipeline/timeframe-clock.d.ts +31 -0
  86. package/dist/types/src/packlets/pipeline/timeframe-clock.d.ts.map +1 -0
  87. package/dist/types/src/packlets/services/client-services-layer.d.ts +21 -0
  88. package/dist/types/src/packlets/services/client-services-layer.d.ts.map +1 -0
  89. package/dist/types/src/packlets/services/cross-device-space-synchronizer.d.ts +28 -0
  90. package/dist/types/src/packlets/services/cross-device-space-synchronizer.d.ts.map +1 -0
  91. package/dist/types/src/packlets/services/effect-rpc.test.d.ts +2 -0
  92. package/dist/types/src/packlets/services/effect-rpc.test.d.ts.map +1 -0
  93. package/dist/types/src/packlets/services/feed-syncer.d.ts +22 -2
  94. package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -1
  95. package/dist/types/src/packlets/services/index.d.ts +2 -2
  96. package/dist/types/src/packlets/services/index.d.ts.map +1 -1
  97. package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
  98. package/dist/types/src/packlets/services/service-context.d.ts +38 -57
  99. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  100. package/dist/types/src/packlets/services/service-host.d.ts +82 -31
  101. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  102. package/dist/types/src/packlets/services/sqlite-storage.d.ts +29 -0
  103. package/dist/types/src/packlets/services/sqlite-storage.d.ts.map +1 -1
  104. package/dist/types/src/packlets/services/sqlite-storage.test.d.ts +2 -0
  105. package/dist/types/src/packlets/services/sqlite-storage.test.d.ts.map +1 -0
  106. package/dist/types/src/packlets/space/admission-discovery-extension.d.ts +30 -0
  107. package/dist/types/src/packlets/space/admission-discovery-extension.d.ts.map +1 -0
  108. package/dist/types/src/packlets/space/auth.d.ts +24 -0
  109. package/dist/types/src/packlets/space/auth.d.ts.map +1 -0
  110. package/dist/types/src/packlets/space/control-pipeline.d.ts +46 -0
  111. package/dist/types/src/packlets/space/control-pipeline.d.ts.map +1 -0
  112. package/dist/types/src/packlets/space/control-pipeline.test.d.ts +2 -0
  113. package/dist/types/src/packlets/space/control-pipeline.test.d.ts.map +1 -0
  114. package/dist/types/src/packlets/space/index.d.ts +6 -0
  115. package/dist/types/src/packlets/space/index.d.ts.map +1 -0
  116. package/dist/types/src/packlets/space/replication.browser.test.d.ts +2 -0
  117. package/dist/types/src/packlets/space/replication.browser.test.d.ts.map +1 -0
  118. package/dist/types/src/packlets/space/space-manager.browser.test.d.ts +2 -0
  119. package/dist/types/src/packlets/space/space-manager.browser.test.d.ts.map +1 -0
  120. package/dist/types/src/packlets/space/space-manager.d.ts +72 -0
  121. package/dist/types/src/packlets/space/space-manager.d.ts.map +1 -0
  122. package/dist/types/src/packlets/space/space-protocol.browser.test.d.ts +2 -0
  123. package/dist/types/src/packlets/space/space-protocol.browser.test.d.ts.map +1 -0
  124. package/dist/types/src/packlets/space/space-protocol.d.ts +99 -0
  125. package/dist/types/src/packlets/space/space-protocol.d.ts.map +1 -0
  126. package/dist/types/src/packlets/space/space-protocol.test.d.ts +2 -0
  127. package/dist/types/src/packlets/space/space-protocol.test.d.ts.map +1 -0
  128. package/dist/types/src/packlets/space/space.d.ts +65 -0
  129. package/dist/types/src/packlets/space/space.d.ts.map +1 -0
  130. package/dist/types/src/packlets/space/space.test.d.ts +2 -0
  131. package/dist/types/src/packlets/space/space.test.d.ts.map +1 -0
  132. package/dist/types/src/packlets/space/testing/change-metadata.d.ts +8 -0
  133. package/dist/types/src/packlets/space/testing/change-metadata.d.ts.map +1 -0
  134. package/dist/types/src/packlets/space/testing/index.d.ts +4 -0
  135. package/dist/types/src/packlets/space/testing/index.d.ts.map +1 -0
  136. package/dist/types/src/packlets/space/testing/test-agent-builder.d.ts +62 -0
  137. package/dist/types/src/packlets/space/testing/test-agent-builder.d.ts.map +1 -0
  138. package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts +1 -1
  139. package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts.map +1 -1
  140. package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts.map +1 -1
  141. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +40 -7
  142. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  143. package/dist/types/src/packlets/spaces/data-space.d.ts +3 -1
  144. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  145. package/dist/types/src/packlets/spaces/genesis.d.ts +1 -1
  146. package/dist/types/src/packlets/spaces/genesis.d.ts.map +1 -1
  147. package/dist/types/src/packlets/spaces/spaces-service.d.ts +25 -23
  148. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  149. package/dist/types/src/packlets/storage/level.d.ts.map +1 -1
  150. package/dist/types/src/packlets/system/system-service.d.ts +18 -18
  151. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  152. package/dist/types/src/packlets/testing/invitation-utils.d.ts +1 -1
  153. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  154. package/dist/types/src/packlets/testing/test-builder.d.ts +7 -6
  155. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  156. package/dist/types/src/packlets/worker/worker-runtime.d.ts +41 -58
  157. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  158. package/dist/types/src/packlets/worker/worker-session.d.ts +21 -8
  159. package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -1
  160. package/dist/types/src/testing/index.d.ts +1 -0
  161. package/dist/types/src/testing/index.d.ts.map +1 -1
  162. package/dist/types/src/version.d.ts +1 -1
  163. package/dist/types/tsconfig.tsbuildinfo +1 -1
  164. package/package.json +52 -48
  165. package/src/index.ts +2 -0
  166. package/src/packlets/agents/edge-agent-manager.ts +56 -15
  167. package/src/packlets/agents/edge-agent-service.ts +25 -15
  168. package/src/packlets/devices/devices-service.test.ts +20 -19
  169. package/src/packlets/devices/devices-service.ts +17 -17
  170. package/src/packlets/devtools/devtools.ts +133 -75
  171. package/src/packlets/devtools/feeds.ts +2 -1
  172. package/src/packlets/devtools/network.ts +42 -12
  173. package/src/packlets/devtools/spaces.ts +1 -1
  174. package/src/packlets/diagnostics/diagnostics-broadcast.ts +4 -2
  175. package/src/packlets/diagnostics/diagnostics-collector.ts +5 -18
  176. package/src/packlets/diagnostics/diagnostics.ts +0 -11
  177. package/src/packlets/identity/authenticator.ts +2 -1
  178. package/src/packlets/identity/contacts-service.ts +46 -37
  179. package/src/packlets/identity/identity-manager.test.ts +3 -1
  180. package/src/packlets/identity/identity-manager.ts +73 -5
  181. package/src/packlets/identity/identity-recovery-manager.ts +56 -7
  182. package/src/packlets/identity/identity-service.test.ts +27 -18
  183. package/src/packlets/identity/identity-service.ts +114 -71
  184. package/src/packlets/identity/identity.test.ts +4 -9
  185. package/src/packlets/identity/identity.ts +1 -2
  186. package/src/packlets/invitations/edge-invitation-handler.ts +0 -1
  187. package/src/packlets/invitations/invitations-handler.ts +39 -3
  188. package/src/packlets/invitations/invitations-manager.ts +52 -4
  189. package/src/packlets/invitations/invitations-service.ts +54 -28
  190. package/src/packlets/logging/logging-service.ts +41 -29
  191. package/src/packlets/logging/logging.test.ts +28 -15
  192. package/src/packlets/metadata/index.ts +6 -0
  193. package/src/packlets/metadata/metadata-store.test.ts +49 -0
  194. package/src/packlets/metadata/metadata-store.ts +446 -0
  195. package/src/packlets/metadata/sqlite-metadata-store.ts +409 -0
  196. package/src/packlets/network/network-service.test.ts +19 -12
  197. package/src/packlets/network/network-service.ts +96 -29
  198. package/src/packlets/pipeline/codec.ts +18 -0
  199. package/src/packlets/pipeline/feeds.ts +22 -0
  200. package/src/packlets/pipeline/index.ts +8 -0
  201. package/src/packlets/pipeline/message-selector.ts +35 -0
  202. package/src/packlets/pipeline/pipeline-stress.test.ts +230 -0
  203. package/src/packlets/pipeline/pipeline.test.ts +140 -0
  204. package/src/packlets/pipeline/pipeline.ts +439 -0
  205. package/src/packlets/pipeline/testing/index.ts +5 -0
  206. package/src/packlets/pipeline/testing/test-feed-builder.ts +19 -0
  207. package/src/packlets/pipeline/timeframe-clock.ts +81 -0
  208. package/src/packlets/services/client-services-layer.ts +186 -0
  209. package/src/packlets/services/cross-device-space-synchronizer.ts +132 -0
  210. package/src/packlets/services/effect-rpc.test.ts +314 -0
  211. package/src/packlets/services/feed-syncer.ts +57 -3
  212. package/src/packlets/services/index.ts +2 -2
  213. package/src/packlets/services/platform.ts +0 -6
  214. package/src/packlets/services/service-context.ts +219 -522
  215. package/src/packlets/services/service-host.test.ts +34 -11
  216. package/src/packlets/services/service-host.ts +693 -213
  217. package/src/packlets/services/spec.md +106 -0
  218. package/src/packlets/services/sqlite-storage.test.ts +48 -0
  219. package/src/packlets/services/sqlite-storage.ts +117 -20
  220. package/src/packlets/space/admission-discovery-extension.ts +91 -0
  221. package/src/packlets/space/auth.ts +92 -0
  222. package/src/packlets/space/control-pipeline.test.ts +165 -0
  223. package/src/packlets/space/control-pipeline.ts +229 -0
  224. package/src/packlets/space/index.ts +9 -0
  225. package/src/packlets/space/replication.browser.test.ts +58 -0
  226. package/src/packlets/space/space-manager.browser.test.ts +40 -0
  227. package/src/packlets/space/space-manager.ts +224 -0
  228. package/src/packlets/space/space-protocol.browser.test.ts +129 -0
  229. package/src/packlets/space/space-protocol.test.ts +158 -0
  230. package/src/packlets/space/space-protocol.ts +308 -0
  231. package/src/packlets/space/space.test.ts +181 -0
  232. package/src/packlets/space/space.ts +196 -0
  233. package/src/packlets/space/testing/change-metadata.ts +27 -0
  234. package/src/packlets/space/testing/index.ts +7 -0
  235. package/src/packlets/space/testing/test-agent-builder.ts +252 -0
  236. package/src/packlets/space-export/serialized-space-reader.ts +3 -1
  237. package/src/packlets/space-export/serialized-space-writer.ts +53 -30
  238. package/src/packlets/space-export/space-archive.test.ts +30 -0
  239. package/src/packlets/spaces/data-space-manager.test.ts +1 -1
  240. package/src/packlets/spaces/data-space-manager.ts +148 -33
  241. package/src/packlets/spaces/data-space.ts +29 -18
  242. package/src/packlets/spaces/edge-feed-replicator.test.ts +1 -1
  243. package/src/packlets/spaces/genesis.ts +1 -1
  244. package/src/packlets/spaces/spaces-service.test.ts +35 -22
  245. package/src/packlets/spaces/spaces-service.ts +259 -180
  246. package/src/packlets/storage/level.ts +3 -1
  247. package/src/packlets/system/system-service.test.ts +31 -13
  248. package/src/packlets/system/system-service.ts +65 -37
  249. package/src/packlets/testing/invitation-utils.ts +3 -3
  250. package/src/packlets/testing/test-builder.ts +35 -20
  251. package/src/packlets/worker/worker-runtime.ts +222 -260
  252. package/src/packlets/worker/worker-session.ts +108 -91
  253. package/src/testing/index.ts +1 -0
  254. package/src/version.ts +1 -1
  255. package/dist/lib/browser/chunk-QCWEHHJW.mjs +0 -24
  256. package/dist/lib/browser/chunk-QCWEHHJW.mjs.map +0 -7
  257. package/dist/lib/browser/chunk-XJRPB3GA.mjs +0 -22
  258. package/dist/lib/browser/chunk-XJRPB3GA.mjs.map +0 -7
  259. package/dist/lib/browser/chunk-XU5R2BMF.mjs +0 -8837
  260. package/dist/lib/browser/chunk-XU5R2BMF.mjs.map +0 -7
  261. package/dist/lib/browser/index.mjs +0 -876
  262. package/dist/lib/browser/index.mjs.map +0 -7
  263. package/dist/lib/browser/meta.json +0 -1
  264. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs +0 -88
  265. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +0 -7
  266. package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs +0 -11
  267. package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs.map +0 -7
  268. package/dist/lib/browser/packlets/locks/browser.mjs +0 -86
  269. package/dist/lib/browser/packlets/locks/browser.mjs.map +0 -7
  270. package/dist/lib/browser/packlets/locks/node.mjs +0 -48
  271. package/dist/lib/browser/packlets/locks/node.mjs.map +0 -7
  272. package/dist/lib/browser/testing/index.mjs +0 -436
  273. package/dist/lib/browser/testing/index.mjs.map +0 -7
  274. package/dist/lib/node-esm/chunk-2DT3MZRL.mjs +0 -22
  275. package/dist/lib/node-esm/chunk-2DT3MZRL.mjs.map +0 -7
  276. package/dist/lib/node-esm/chunk-2SZHAWBN.mjs +0 -24
  277. package/dist/lib/node-esm/chunk-2SZHAWBN.mjs.map +0 -7
  278. package/dist/lib/node-esm/chunk-H5YTUD35.mjs +0 -8837
  279. package/dist/lib/node-esm/chunk-H5YTUD35.mjs.map +0 -7
  280. package/dist/lib/node-esm/index.mjs +0 -876
  281. package/dist/lib/node-esm/index.mjs.map +0 -7
  282. package/dist/lib/node-esm/meta.json +0 -1
  283. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs +0 -88
  284. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +0 -7
  285. package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs +0 -11
  286. package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs.map +0 -7
  287. package/dist/lib/node-esm/packlets/locks/browser.mjs +0 -86
  288. package/dist/lib/node-esm/packlets/locks/browser.mjs.map +0 -7
  289. package/dist/lib/node-esm/packlets/locks/node.mjs +0 -48
  290. package/dist/lib/node-esm/packlets/locks/node.mjs.map +0 -7
  291. package/dist/lib/node-esm/testing/index.mjs +0 -436
  292. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  293. package/dist/types/src/packlets/services/client-rpc-server.d.ts +0 -24
  294. package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +0 -1
  295. package/dist/types/src/packlets/services/service-registry.d.ts +0 -15
  296. package/dist/types/src/packlets/services/service-registry.d.ts.map +0 -1
  297. package/dist/types/src/packlets/services/service-registry.test.d.ts +0 -2
  298. package/dist/types/src/packlets/services/service-registry.test.d.ts.map +0 -1
  299. package/dist/types/src/testing/setup.d.ts +0 -3
  300. package/dist/types/src/testing/setup.d.ts.map +0 -1
  301. package/src/packlets/services/client-rpc-server.ts +0 -111
  302. package/src/packlets/services/service-registry.test.ts +0 -78
  303. package/src/packlets/services/service-registry.ts +0 -36
  304. package/src/testing/setup.ts +0 -11
@@ -0,0 +1,106 @@
1
+ # ServiceContext → Effect Layers
2
+
3
+ Replace `ServiceContext` (monolithic `Resource`) with a composed stack of Effect layers.
4
+
5
+ ## Conventions
6
+
7
+ | Item | Rule |
8
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
9
+ | **Tag name** | Prefer interface name + `Service` (`IMetadataStore` → `IMetadataStoreService`). No interface → impl name + `Service` (`SpaceManager` → `SpaceManagerService`). |
10
+ | **Tag placement** | Top of defining file: after interface/type, before impl class. |
11
+ | **Layer placement** | Bottom of defining file. |
12
+ | **Layer options** | Value-based config (flags, callbacks, runtime props) go in the layer constructor `options` object. |
13
+ | **Layer requirements** | Injected components go in the layer's `Requirements` type (resolved via `yield*` on sibling tags). |
14
+
15
+ ```ts
16
+ // Tag (after interface)
17
+ export class IMetadataStoreService extends EffectContext.Tag('...')<
18
+ IMetadataStoreService,
19
+ IMetadataStore
20
+ >() {}
21
+
22
+ // Layer (bottom of file)
23
+ export const SqliteMetadataStoreLayer = (): Layer.Layer<
24
+ IMetadataStoreService,
25
+ never,
26
+ SqlClient.SqlClient | SqlTransactionTag
27
+ > => Layer.effect(IMetadataStoreService, Effect.gen(function* () { ... }));
28
+ ```
29
+
30
+ ## Component map
31
+
32
+ Status: `done` | `todo`
33
+
34
+ ### Storage / persistence
35
+
36
+ | Component | Tag | Layer | Options | Requirements | Status |
37
+ | ---------------------------------------- | ----------------------- | ------------------------------- | ------- | ----------------------------- | ------ |
38
+ | `IMetadataStore` / `SqliteMetadataStore` | `IMetadataStoreService` | `SqliteMetadataStoreLayer()` | — | `SqlClient \| SqlTransaction` | done |
39
+ | `BlobStoreApi` / `SqliteBlobStore` | `BlobStoreApiService` | `SqliteBlobStoreLayer()` | — | `SqlClient \| SqlTransaction` | done |
40
+ | `KeyringApi` / `SqliteKeyring` | `KeyringApiService` | `SqliteKeyringLayer()` | — | `SqlClient \| SqlTransaction` | done |
41
+ | `SqliteStorage` | `SqliteStorageService` | `SqliteStorageLayer({ path? })` | `path?` | `SqlClient \| SqlTransaction` | done |
42
+
43
+ ### Feeds
44
+
45
+ | Component | Tag | Layer | Options | Requirements | Status |
46
+ | ------------------------------ | ----------------------------- | ------------------------------------- | ------------ | -------------------------------------------------- | ------ |
47
+ | `FeedFactory` | `FeedFactoryService` | `FeedFactoryLayer({ hypercore? })` | `hypercore?` | `KeyringApiService`, `FeedStorageDirectoryService` | done |
48
+ | `FeedStore` (@dxos/feed-store) | `FeedStoreService` | `FeedStoreLayer()` | — | `FeedFactoryService` | done |
49
+ | `FeedStorageDirectory` | `FeedStorageDirectoryService` | `FeedStorageDirectoryLayer({ sub? })` | `sub?` | `SqliteStorageService` | done |
50
+
51
+ ### Spaces / echo
52
+
53
+ | Component | Tag | Layer | Options | Requirements | Status |
54
+ | ------------------------------ | -------------------------------- | ------------------------------------------------------------------------ | ------------------------ | ------------------------------------------------------------------------------------------------ | ------ |
55
+ | `SpaceManager` | `SpaceManagerService` | `SpaceManagerLayer({ disableP2pReplication? })` | `disableP2pReplication?` | `FeedStoreService`, `SwarmNetworkManagerService`, `IMetadataStoreService`, `BlobStoreApiService` | done |
56
+ | `EchoHost` | `EchoHostService` | `EchoHostLayer({ ...callbacks, useSubduction?, assignQueuePositions? })` | callbacks, flags | `SqlClient \| SqlTransaction` | done |
57
+ | `MeshEchoReplicator` | `AutomergeReplicatorService` | `MeshEchoReplicatorLayer()` | — | — | done |
58
+ | `EchoEdgeReplicator` | `EdgeAutomergeReplicatorService` | `EchoEdgeReplicatorLayer({ disableSharePolicy? })` | `disableSharePolicy?` | `EdgeConnectionService`, `EdgeHttpClientService` | done |
59
+ | `EchoEdgeSubductionReplicator` | `EdgeAutomergeReplicatorService` | `EchoEdgeSubductionReplicatorLayer({ disableSharePolicy? })` | `disableSharePolicy?` | `EdgeConnectionService`, `EdgeHttpClientService` | done |
60
+
61
+ ### Identity / invitations / spaces (client-services)
62
+
63
+ | Component | Tag | Layer | Options | Requirements | Status |
64
+ | ------------------------------ | ------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
65
+ | `IdentityManager` | `IdentityManagerService` | `IdentityManagerLayer({ devicePresence*, edgeFeatures? })` | presence intervals, edge features | `IMetadataStoreService`, `KeyringApiService`, `FeedStoreService`, `SpaceManagerService`, `EdgeConnectionService?` | done |
66
+ | `EdgeIdentityRecoveryManager` | `EdgeIdentityRecoveryManagerService` | `EdgeIdentityRecoveryManagerLayer()` | — (wire `setAcceptRecoveredIdentity` after stack compose) | `KeyringApiService`, `EdgeHttpClientService?`, `IdentityManagerService` | done |
67
+ | `InvitationsHandler` | `InvitationsHandlerService` | `InvitationsHandlerLayer({ connectionProps? })` | `connectionProps?` | `SwarmNetworkManagerService`, `EdgeHttpClientService?` | done |
68
+ | `InvitationsManager` | `InvitationsManagerService` | `InvitationsManagerLayer({ getHandler })` | `getHandler` callback | `InvitationsHandlerService`, `IMetadataStoreService` | done |
69
+ | `DataSpaceManager` | `DataSpaceManagerService` | `DataSpaceManagerLayer({ runtimeProps?, edgeFeatures? })` | runtime props, edge features | `SpaceManagerService`, `IMetadataStoreService`, `KeyringApiService`, `SigningContextProviderService`, `FeedStoreService`, `EchoHostService`, `InvitationsManagerService`, edge/replicator deps (optional) | done |
70
+ | `SigningContextProvider` | `SigningContextProviderService` | `SigningContextProviderLayer` | — | `IdentityProviderService` | done |
71
+ | `EdgeAgentManager` | `EdgeAgentManagerService` | `EdgeAgentManagerLayer({ edgeFeatures? })` | edge features | `DataSpaceManagerService`, `IdentityProviderService`, `EdgeHttpClientService?` | done |
72
+ | `CrossDeviceSpaceSynchronizer` | `CrossDeviceSpaceSynchronizerService` | `CrossDeviceSpaceSynchronizerLayer` | — | `DataSpaceManagerService` | done |
73
+ | `IdentityProvider` | `IdentityProviderService` | supplied at runtime (e.g. `identityProviderFromManager`) | — | — | done |
74
+ | `FeedSyncer` | `FeedSyncerService` | `FeedSyncerLayer({ peerId, syncNamespaces, getSpaceIds, ...tuning })` | peer/sync config | `SqlClient \| SqlTransaction`, `FeedStoreService` (@dxos/feed), `EdgeConnectionService` | done |
75
+
76
+ ### External inputs (provided by host, not constructed inside ServiceContext)
77
+
78
+ | Component | Tag | Notes |
79
+ | --------------------- | ------------------------------------------------- | -------------------------- |
80
+ | `SwarmNetworkManager` | `SwarmNetworkManagerService` | host-provided |
81
+ | `SignalManager` | `SignalManagerService` | todo |
82
+ | `EdgeConnection` | `EdgeConnectionService` | host-provided |
83
+ | `EdgeHttpClient` | `EdgeHttpClientService` | host-provided |
84
+ | `RuntimeProvider` | ambient via `SqlClient` + `SqlTransaction` layers | not a ServiceContext field |
85
+
86
+ ## Target layer stack (sketch)
87
+
88
+ ```ts
89
+ const ServiceContextLive = Layer.mergeAll(
90
+ SqliteMetadataStoreLayer(),
91
+ SqliteBlobStoreLayer(),
92
+ SqliteKeyringLayer(),
93
+ SqliteStorageLayer(),
94
+ FeedFactoryLayer({ hypercore: { valueEncoding, stats: true } }),
95
+ FeedStoreLayer(),
96
+ SpaceManagerLayer({ disableP2pReplication }),
97
+ EchoHostLayer({ peerIdProvider, getSpaceKeyByRootDocumentId, syncFeed, getSyncState, useSubduction }),
98
+ // ...identity, invitations, data-space layers
99
+ ).pipe(Layer.provideMerge(SqlTransaction.layer), Layer.provideMerge(sqliteLayer));
100
+ ```
101
+
102
+ ## Open questions
103
+
104
+ 1. **Lifecycle** — identity-bound services (`DataSpaceManager`, `EdgeAgentManager`, `CrossDeviceSpaceSynchronizer`) are constructed dormant by their layers; `open`/`close` is called explicitly after identity is ready (e.g. in `_initialize`).
105
+ 2. **Provider wiring** — `SigningContextProviderLayer` and `IdentityProviderService` must be provided before `DataSpaceManagerLayer` / `EdgeAgentManagerLayer` can run.
106
+ 3. **Callbacks** — `EchoHost` and `InvitationsManager` need callbacks that close over other services; pass as layer options or resolve via `Effect.gen` at a higher layer.
@@ -0,0 +1,48 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { describe, expect, test } from 'vitest';
6
+
7
+ import { isClosedConnectionError } from './sqlite-storage';
8
+
9
+ // `SqliteRandomAccessFile._loadFromDb` swallows a read that races client teardown only when the
10
+ // error signals a closed connection. Getting this predicate wrong reintroduces the flake where a
11
+ // background load rejects unhandled after the SQL connection closed, crashing the whole test
12
+ // worker (exit 1 with no failing test).
13
+ describe('isClosedConnectionError', () => {
14
+ test('matches the raw sqlite closed-connection error', () => {
15
+ expect(isClosedConnectionError(new TypeError('The database connection is not open'))).toBe(true);
16
+ });
17
+
18
+ test('matches when wrapped in an effect SqlError cause chain', () => {
19
+ const wrapped = Object.assign(new Error('Failed to execute statement'), {
20
+ _tag: 'SqlError',
21
+ cause: new TypeError('The database connection is not open'),
22
+ });
23
+ expect(isClosedConnectionError(wrapped)).toBe(true);
24
+ });
25
+
26
+ test('matches a plain string message', () => {
27
+ expect(isClosedConnectionError('SqliteError: the database connection is not open')).toBe(true);
28
+ });
29
+
30
+ test('does not match a genuine query error against a live connection', () => {
31
+ const real = Object.assign(new Error('Failed to execute statement'), {
32
+ cause: new Error('no such table: hypercore_files'),
33
+ });
34
+ expect(isClosedConnectionError(real)).toBe(false);
35
+ });
36
+
37
+ test('is safe on null / undefined / non-error values', () => {
38
+ expect(isClosedConnectionError(null)).toBe(false);
39
+ expect(isClosedConnectionError(undefined)).toBe(false);
40
+ expect(isClosedConnectionError(42)).toBe(false);
41
+ });
42
+
43
+ test('terminates on a self-referential cause chain', () => {
44
+ const cyclic: any = new Error('boom');
45
+ cyclic.cause = cyclic;
46
+ expect(isClosedConnectionError(cyclic)).toBe(false);
47
+ });
48
+ });
@@ -4,10 +4,13 @@
4
4
 
5
5
  import * as SqlClient from '@effect/sql/SqlClient';
6
6
  import type * as SqlError from '@effect/sql/SqlError';
7
+ import * as EffectContext from 'effect/Context';
7
8
  import * as Effect from 'effect/Effect';
9
+ import * as Layer from 'effect/Layer';
8
10
  import type { Callback, FileStat, RandomAccessStorage } from 'random-access-storage';
9
11
 
10
12
  import { RuntimeProvider } from '@dxos/effect';
13
+ import { FeedStorageDirectoryService } from '@dxos/feed-store';
11
14
  import { log } from '@dxos/log';
12
15
  import { Directory, type File, type Storage, StorageType, wrapFile } from '@dxos/random-access-storage';
13
16
  import { SqlTransaction } from '@dxos/sql-sqlite';
@@ -15,10 +18,33 @@ import { SqlTransaction } from '@dxos/sql-sqlite';
15
18
  // SqlTransaction.SqlTransaction is the Tag class exported from the SqlTransaction namespace.
16
19
  type SqlTransactionTag = SqlTransaction.SqlTransaction;
17
20
 
21
+ /**
22
+ * True when a rejected SQL op failed because its connection was already closed — the message
23
+ * `The database connection is not open` (raised by `@effect/sql-sqlite-node`), which arrives
24
+ * wrapped as an effect `SqlError`, so walk the `cause` chain. Signals teardown, not a real fault.
25
+ */
26
+ export const isClosedConnectionError = (err: unknown): boolean => {
27
+ for (let cur: any = err, depth = 0; cur != null && depth < 5; cur = cur.cause, depth++) {
28
+ const message = typeof cur === 'string' ? cur : cur.message;
29
+ if (typeof message === 'string' && /connection is not open/i.test(message)) {
30
+ return true;
31
+ }
32
+ }
33
+ return false;
34
+ };
35
+
18
36
  export type SqliteStorageOptions = {
19
37
  runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransactionTag>;
20
38
  };
21
39
 
40
+ /**
41
+ * Effect service tag for {@link SqliteStorage}.
42
+ */
43
+ export class SqliteStorageService extends EffectContext.Tag('@dxos/client-services/SqliteStorage')<
44
+ SqliteStorageService,
45
+ SqliteStorage
46
+ >() {}
47
+
22
48
  /** Minimal cross-platform EventEmitter needed by the RandomAccessStorage contract. */
23
49
  class BaseEventEmitter {
24
50
  readonly #events: Record<string, ((...args: unknown[]) => void)[]> = Object.create(null);
@@ -135,7 +161,11 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
135
161
 
136
162
  #buffer: Buffer = Buffer.alloc(0);
137
163
  #loaded = false;
164
+ // The one-shot initial DB read (populates `#buffer`); set once, on first access.
138
165
  #loading: Promise<void> | null = null;
166
+ // The latest in-flight DB write (from `write`/`del`); replaced on each save, so it always
167
+ // tracks the most recent one. `close()` drains both before reporting closed.
168
+ #saving: Promise<void> | null = null;
139
169
 
140
170
  constructor(
141
171
  private readonly filePath: string,
@@ -161,6 +191,11 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
161
191
  }
162
192
  if (!this.#loading) {
163
193
  this.#loading = this._loadFromDb();
194
+ // Mark the cached load promise as handled so that if it rejects after every awaiter is gone
195
+ // (e.g. the SQL runtime is disposed at teardown without close() being called on this file),
196
+ // the rejection is never surfaced as an unhandled rejection. Legitimate awaiters still observe
197
+ // the error through their own `.then`/`.catch` on the same promise returned below.
198
+ this.#loading.catch(() => {});
164
199
  }
165
200
  return this.#loading;
166
201
  }
@@ -176,11 +211,14 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
176
211
  );
177
212
  this.#buffer = rows.length > 0 ? Buffer.from(rows[0].data) : Buffer.alloc(0);
178
213
  } catch (err) {
179
- // The SQL connection may close mid-read during teardown (the `#closed` guard in `_ensureLoaded`
180
- // only catches reads that have not yet started). A read racing teardown rejects with
181
- // "database connection is not open"; swallow it once closed and fall back to the empty buffer,
182
- // otherwise surface the genuine error.
183
- if (!this.#closed) {
214
+ // A read racing teardown rejects with "database connection is not open". The `#closed` guard
215
+ // in `_ensureLoaded` only catches reads not yet started, and `#closed` covers only this file
216
+ // being closed during client disposal the shared SQL connection can be torn down while this
217
+ // file's `#closed` is still false (the container closed the connection without closing every
218
+ // file first). In either case a closed connection means there is nothing to load, so fall back
219
+ // to the empty `#buffer`; rethrow only a genuine error against a live connection. Swallowing an
220
+ // unhandled rejection here otherwise crashes the whole test worker (exit 1, no test failure).
221
+ if (!this.#closed && !isClosedConnectionError(err)) {
184
222
  throw err;
185
223
  }
186
224
  }
@@ -190,16 +228,27 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
190
228
  private async _saveToDb(): Promise<void> {
191
229
  const filePath = this.filePath;
192
230
  const data = this.#buffer;
193
- await RuntimeProvider.runPromise(this.runtime)(
194
- Effect.gen(function* () {
195
- const sql = yield* SqlClient.SqlClient;
196
- yield* sql`INSERT OR REPLACE INTO hypercore_files (path, data) VALUES (${filePath}, ${data})`;
197
- }),
198
- );
231
+ try {
232
+ await RuntimeProvider.runPromise(this.runtime)(
233
+ Effect.gen(function* () {
234
+ const sql = yield* SqlClient.SqlClient;
235
+ yield* sql`INSERT OR REPLACE INTO hypercore_files (path, data) VALUES (${filePath}, ${data})`;
236
+ }),
237
+ );
238
+ } catch (err) {
239
+ // Symmetric with `_loadFromDb`: a write racing teardown rejects (as `SqlError: Failed to
240
+ // execute statement` wrapping "connection is not open") when the shared SQL connection is
241
+ // torn down before this file's `#closed` flips. Persisting to a dead connection is moot
242
+ // during disposal, so swallow it; rethrow only a genuine failure against a live connection.
243
+ // An unswallowed rejection here surfaces as an unhandled rejection that fails the test worker.
244
+ if (!this.#closed && !isClosedConnectionError(err)) {
245
+ throw err;
246
+ }
247
+ }
199
248
  }
200
249
 
201
250
  write(offset: number, data: Buffer, cb: Callback<any>): void {
202
- this._ensureLoaded()
251
+ const saving = this._ensureLoaded()
203
252
  .then(() => {
204
253
  const end = offset + data.length;
205
254
  if (end > this.#buffer.length) {
@@ -210,8 +259,14 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
210
259
  data.copy(this.#buffer, offset);
211
260
  return this._saveToDb();
212
261
  })
213
- .then(() => cb(null))
214
- .catch((err) => cb(err));
262
+ .then(
263
+ () => cb(null),
264
+ (err) => cb(err),
265
+ );
266
+ // Track the in-flight save so `close()` can drain it before reporting closed — otherwise a
267
+ // write racing `close()` (e.g. a background sync write racing `client.reset()`'s teardown)
268
+ // keeps running against a connection the container may tear down immediately after.
269
+ this.#saving = saving.catch(() => {});
215
270
  }
216
271
 
217
272
  read(offset: number, size: number, cb: Callback<Buffer>): void {
@@ -231,7 +286,7 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
231
286
  }
232
287
 
233
288
  del(offset: number, size: number, cb: Callback<any>): void {
234
- this._ensureLoaded()
289
+ const saving = this._ensureLoaded()
235
290
  .then(() => {
236
291
  const end = Math.min(offset + size, this.#buffer.length);
237
292
  if (offset < end) {
@@ -239,8 +294,12 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
239
294
  }
240
295
  return this._saveToDb();
241
296
  })
242
- .then(() => cb(null))
243
- .catch((err) => cb(err));
297
+ .then(
298
+ () => cb(null),
299
+ (err) => cb(err),
300
+ );
301
+ // See `write()`: track the in-flight save so `close()` can drain it before reporting closed.
302
+ this.#saving = saving.catch(() => {});
244
303
  }
245
304
 
246
305
  stat(cb: Callback<FileStat>): void {
@@ -253,9 +312,11 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
253
312
 
254
313
  close(cb: Callback<Error>): void {
255
314
  this.#closed = true;
256
- // Drain any in-flight initial load before reporting closed, so a read never races a
257
- // torn-down SQL connection (avoids "database connection is not open" unhandled rejections).
258
- (this.#loading ?? Promise.resolve()).then(
315
+ // Drain any in-flight initial load and any in-flight write/del before reporting closed, so
316
+ // neither races a torn-down SQL connection (avoids "database connection is not open" errors,
317
+ // and the resulting stall in callers — e.g. `client.reset()`'s feed teardown — that wait on
318
+ // this file's `close()` to settle before tearing down the connection itself).
319
+ Promise.all([this.#loading ?? Promise.resolve(), this.#saving ?? Promise.resolve()]).then(
259
320
  () => cb(null),
260
321
  () => cb(null),
261
322
  );
@@ -398,3 +459,39 @@ export class SqliteStorage implements Storage {
398
459
  this.#nativeFiles.clear();
399
460
  }
400
461
  }
462
+
463
+ export type SqliteStorageLayerOptions = {
464
+ path?: string;
465
+ };
466
+
467
+ /**
468
+ * Effect Layer constructing a {@link SqliteStorage} from the ambient SQL runtime.
469
+ */
470
+ export const SqliteStorageLayer = (
471
+ options: SqliteStorageLayerOptions = {},
472
+ ): Layer.Layer<SqliteStorageService, never, SqlClient.SqlClient | SqlTransactionTag> =>
473
+ Layer.effect(
474
+ SqliteStorageService,
475
+ Effect.gen(function* () {
476
+ const runtime = yield* RuntimeProvider.currentRuntime<SqlClient.SqlClient | SqlTransactionTag>();
477
+ return new SqliteStorage({ runtime }, options.path);
478
+ }),
479
+ );
480
+
481
+ export type FeedStorageDirectoryLayerOptions = {
482
+ sub?: string;
483
+ };
484
+
485
+ /**
486
+ * Effect Layer providing the hypercore feeds root directory from {@link SqliteStorage}.
487
+ */
488
+ export const FeedStorageDirectoryLayer = (
489
+ options: FeedStorageDirectoryLayerOptions = {},
490
+ ): Layer.Layer<FeedStorageDirectoryService, never, SqliteStorageService> =>
491
+ Layer.effect(
492
+ FeedStorageDirectoryService,
493
+ Effect.gen(function* () {
494
+ const storage = yield* SqliteStorageService;
495
+ return storage.createDirectory(options.sub ?? 'feeds');
496
+ }),
497
+ );
@@ -0,0 +1,91 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Trigger, scheduleTask } from '@dxos/async';
6
+ import { Context } from '@dxos/context';
7
+ import { ProtocolError } from '@dxos/protocols';
8
+ import { schema } from '@dxos/protocols/proto';
9
+ import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
10
+ import {
11
+ type AdmissionDiscoveryService,
12
+ type GetAdmissionCredentialRequest,
13
+ type GetAdmissionCredentialResponse,
14
+ } from '@dxos/protocols/proto/dxos/mesh/teleport';
15
+ import { type ExtensionContext, RpcExtension } from '@dxos/teleport';
16
+
17
+ import { type Space } from './space';
18
+
19
+ /**
20
+ * Guest's side for a connection to a concrete peer in p2p network during invitation.
21
+ */
22
+ export class CredentialRetrieverExtension extends RpcExtension<
23
+ { AdmissionDiscoveryService: AdmissionDiscoveryService },
24
+ {}
25
+ > {
26
+ private _ctx = new Context();
27
+
28
+ constructor(
29
+ private readonly _request: GetAdmissionCredentialRequest,
30
+ private readonly _onResult: Trigger<Credential>,
31
+ ) {
32
+ super({
33
+ requested: {
34
+ AdmissionDiscoveryService: schema.getService('dxos.mesh.teleport.AdmissionDiscoveryService'),
35
+ },
36
+ });
37
+ }
38
+
39
+ protected override async getHandlers(): Promise<{}> {
40
+ return {};
41
+ }
42
+
43
+ override async onOpen(context: ExtensionContext): Promise<void> {
44
+ await super.onOpen(context);
45
+ scheduleTask(this._ctx, async () => {
46
+ try {
47
+ const result = await this.rpc.AdmissionDiscoveryService.getAdmissionCredential(this._request);
48
+ this._onResult.wake(result.admissionCredential);
49
+ } catch (err: any) {
50
+ context.close(err);
51
+ }
52
+ });
53
+ }
54
+
55
+ override async onClose(): Promise<void> {
56
+ await this._ctx.dispose();
57
+ }
58
+
59
+ override async onAbort(): Promise<void> {
60
+ await this._ctx.dispose();
61
+ }
62
+ }
63
+
64
+ export class CredentialServerExtension extends RpcExtension<
65
+ {},
66
+ { AdmissionDiscoveryService: AdmissionDiscoveryService }
67
+ > {
68
+ constructor(private readonly _space: Space) {
69
+ super({
70
+ exposed: {
71
+ AdmissionDiscoveryService: schema.getService('dxos.mesh.teleport.AdmissionDiscoveryService'),
72
+ },
73
+ });
74
+ }
75
+
76
+ protected override async getHandlers(): Promise<{ AdmissionDiscoveryService: AdmissionDiscoveryService }> {
77
+ return {
78
+ AdmissionDiscoveryService: {
79
+ getAdmissionCredential: async (
80
+ request: GetAdmissionCredentialRequest,
81
+ ): Promise<GetAdmissionCredentialResponse> => {
82
+ const memberInfo = this._space.spaceState.members.get(request.memberKey);
83
+ if (!memberInfo?.credential) {
84
+ throw new ProtocolError({ message: 'Space member not found.', context: { ...request } });
85
+ }
86
+ return { admissionCredential: memberInfo.credential };
87
+ },
88
+ },
89
+ };
90
+ }
91
+ }
@@ -0,0 +1,92 @@
1
+ //
2
+ // Copyright 2019 DXOS.org
3
+ //
4
+
5
+ import { runInContext, scheduleTask } from '@dxos/async';
6
+ import { Context } from '@dxos/context';
7
+ import { randomBytes } from '@dxos/crypto';
8
+ import { invariant } from '@dxos/invariant';
9
+ import { log } from '@dxos/log';
10
+ import { schema } from '@dxos/protocols/proto';
11
+ import { type AuthService } from '@dxos/protocols/proto/dxos/mesh/teleport/auth';
12
+ import { type ExtensionContext, RpcExtension } from '@dxos/teleport';
13
+
14
+ export type AuthProvider = (nonce: Uint8Array) => Promise<Uint8Array | undefined>;
15
+
16
+ export type AuthVerifier = (nonce: Uint8Array, credential: Uint8Array) => Promise<boolean>;
17
+
18
+ export type AuthExtensionProps = {
19
+ provider: AuthProvider;
20
+ verifier: AuthVerifier;
21
+ onAuthSuccess: () => void;
22
+ onAuthFailure: () => void;
23
+ };
24
+
25
+ export class AuthExtension extends RpcExtension<Services, Services> {
26
+ private readonly _ctx = new Context({
27
+ onError: (err) => {
28
+ log.catch(err);
29
+ },
30
+ });
31
+
32
+ constructor(private readonly _authProps: AuthExtensionProps) {
33
+ super({
34
+ requested: {
35
+ AuthService: schema.getService('dxos.mesh.teleport.auth.AuthService'),
36
+ },
37
+ exposed: {
38
+ AuthService: schema.getService('dxos.mesh.teleport.auth.AuthService'),
39
+ },
40
+ timeout: 60 * 1000, // Long timeout because auth can wait for sync in certain cases.
41
+ });
42
+ }
43
+
44
+ protected async getHandlers(): Promise<Services> {
45
+ return {
46
+ AuthService: {
47
+ authenticate: async ({ challenge }) => {
48
+ try {
49
+ const credential = await this._authProps.provider(challenge);
50
+ if (!credential) {
51
+ throw new Error('auth rejected');
52
+ }
53
+ return { credential };
54
+ } catch (err) {
55
+ log.error('failed to generate auth credentials', err);
56
+ throw new Error('auth rejected');
57
+ }
58
+ },
59
+ },
60
+ };
61
+ }
62
+
63
+ override async onOpen(context: ExtensionContext): Promise<void> {
64
+ await super.onOpen(context);
65
+ scheduleTask(this._ctx, async () => {
66
+ try {
67
+ const challenge = randomBytes(32);
68
+ const { credential } = await this.rpc.AuthService.authenticate({ challenge });
69
+ invariant(credential?.length > 0, 'invalid credential');
70
+ const success = await this._authProps.verifier(challenge, credential);
71
+ invariant(success, 'credential not verified');
72
+ runInContext(this._ctx, () => this._authProps.onAuthSuccess());
73
+ } catch (err) {
74
+ log('auth failed', err);
75
+ this.close();
76
+ this._authProps.onAuthFailure();
77
+ }
78
+ });
79
+ }
80
+
81
+ override async onClose(): Promise<void> {
82
+ await this._ctx.dispose();
83
+ await super.onClose();
84
+ }
85
+
86
+ override async onAbort(): Promise<void> {
87
+ await this._ctx.dispose();
88
+ await super.onAbort();
89
+ }
90
+ }
91
+
92
+ type Services = { AuthService: AuthService };