@freqhole/haruspex 0.0.0

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 (250) hide show
  1. package/dist/flows/add-peer/machine.d.ts +36 -0
  2. package/dist/flows/add-peer/machine.d.ts.map +1 -0
  3. package/dist/flows/add-peer/machine.js +500 -0
  4. package/dist/flows/add-peer/machine.test.d.ts +2 -0
  5. package/dist/flows/add-peer/machine.test.d.ts.map +1 -0
  6. package/dist/flows/add-peer/machine.test.js +421 -0
  7. package/dist/flows/add-peer/runner.d.ts +16 -0
  8. package/dist/flows/add-peer/runner.d.ts.map +1 -0
  9. package/dist/flows/add-peer/runner.js +349 -0
  10. package/dist/flows/add-peer/runner.test.d.ts +2 -0
  11. package/dist/flows/add-peer/runner.test.d.ts.map +1 -0
  12. package/dist/flows/add-peer/runner.test.js +205 -0
  13. package/dist/flows/add-peer/types.d.ts +271 -0
  14. package/dist/flows/add-peer/types.d.ts.map +1 -0
  15. package/dist/flows/add-peer/types.js +10 -0
  16. package/dist/flows/index.d.ts +6 -0
  17. package/dist/flows/index.d.ts.map +1 -0
  18. package/dist/flows/index.js +6 -0
  19. package/dist/identity/idbStore.d.ts +51 -0
  20. package/dist/identity/idbStore.d.ts.map +1 -0
  21. package/dist/identity/idbStore.js +183 -0
  22. package/dist/identity/idbStore.test.d.ts +2 -0
  23. package/dist/identity/idbStore.test.d.ts.map +1 -0
  24. package/dist/identity/idbStore.test.js +103 -0
  25. package/dist/identity/index.d.ts +8 -0
  26. package/dist/identity/index.d.ts.map +1 -0
  27. package/dist/identity/index.js +3 -0
  28. package/dist/identity/resolve.d.ts +31 -0
  29. package/dist/identity/resolve.d.ts.map +1 -0
  30. package/dist/identity/resolve.js +37 -0
  31. package/dist/identity/resolve.test.d.ts +2 -0
  32. package/dist/identity/resolve.test.d.ts.map +1 -0
  33. package/dist/identity/resolve.test.js +105 -0
  34. package/dist/identity/types.d.ts +21 -0
  35. package/dist/identity/types.d.ts.map +1 -0
  36. package/dist/identity/types.js +1 -0
  37. package/dist/identity/webLocks.d.ts +22 -0
  38. package/dist/identity/webLocks.d.ts.map +1 -0
  39. package/dist/identity/webLocks.js +67 -0
  40. package/dist/identity/webLocks.test.d.ts +2 -0
  41. package/dist/identity/webLocks.test.d.ts.map +1 -0
  42. package/dist/identity/webLocks.test.js +125 -0
  43. package/dist/index.d.ts +2 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +1 -0
  46. package/dist/index.test.d.ts +2 -0
  47. package/dist/index.test.d.ts.map +1 -0
  48. package/dist/index.test.js +7 -0
  49. package/dist/knock/index.d.ts +10 -0
  50. package/dist/knock/index.d.ts.map +1 -0
  51. package/dist/knock/index.js +5 -0
  52. package/dist/knock/pending-checker.d.ts +19 -0
  53. package/dist/knock/pending-checker.d.ts.map +1 -0
  54. package/dist/knock/pending-checker.js +27 -0
  55. package/dist/knock/pending-checker.test.d.ts +2 -0
  56. package/dist/knock/pending-checker.test.d.ts.map +1 -0
  57. package/dist/knock/pending-checker.test.js +93 -0
  58. package/dist/knock/requester.d.ts +26 -0
  59. package/dist/knock/requester.d.ts.map +1 -0
  60. package/dist/knock/requester.js +72 -0
  61. package/dist/knock/requester.test.d.ts +2 -0
  62. package/dist/knock/requester.test.d.ts.map +1 -0
  63. package/dist/knock/requester.test.js +108 -0
  64. package/dist/knock/responder.d.ts +13 -0
  65. package/dist/knock/responder.d.ts.map +1 -0
  66. package/dist/knock/responder.js +40 -0
  67. package/dist/knock/responder.test.d.ts +2 -0
  68. package/dist/knock/responder.test.d.ts.map +1 -0
  69. package/dist/knock/responder.test.js +89 -0
  70. package/dist/knock/store.d.ts +11 -0
  71. package/dist/knock/store.d.ts.map +1 -0
  72. package/dist/knock/store.js +211 -0
  73. package/dist/knock/store.test.d.ts +2 -0
  74. package/dist/knock/store.test.d.ts.map +1 -0
  75. package/dist/knock/store.test.js +195 -0
  76. package/dist/knock/types.d.ts +144 -0
  77. package/dist/knock/types.d.ts.map +1 -0
  78. package/dist/knock/types.js +26 -0
  79. package/dist/permissions/access.d.ts +54 -0
  80. package/dist/permissions/access.d.ts.map +1 -0
  81. package/dist/permissions/access.js +56 -0
  82. package/dist/permissions/access.test.d.ts +2 -0
  83. package/dist/permissions/access.test.d.ts.map +1 -0
  84. package/dist/permissions/access.test.js +72 -0
  85. package/dist/permissions/index.d.ts +5 -0
  86. package/dist/permissions/index.d.ts.map +1 -0
  87. package/dist/permissions/index.js +2 -0
  88. package/dist/permissions/role-table.d.ts +11 -0
  89. package/dist/permissions/role-table.d.ts.map +1 -0
  90. package/dist/permissions/role-table.js +16 -0
  91. package/dist/protocol/client.d.ts +67 -0
  92. package/dist/protocol/client.d.ts.map +1 -0
  93. package/dist/protocol/client.js +195 -0
  94. package/dist/protocol/client.test.d.ts +2 -0
  95. package/dist/protocol/client.test.d.ts.map +1 -0
  96. package/dist/protocol/client.test.js +282 -0
  97. package/dist/protocol/codec.d.ts +45 -0
  98. package/dist/protocol/codec.d.ts.map +1 -0
  99. package/dist/protocol/codec.js +111 -0
  100. package/dist/protocol/codec.test.d.ts +2 -0
  101. package/dist/protocol/codec.test.d.ts.map +1 -0
  102. package/dist/protocol/codec.test.js +93 -0
  103. package/dist/protocol/extensions.d.ts +33 -0
  104. package/dist/protocol/extensions.d.ts.map +1 -0
  105. package/dist/protocol/extensions.js +36 -0
  106. package/dist/protocol/extensions.test.d.ts +2 -0
  107. package/dist/protocol/extensions.test.d.ts.map +1 -0
  108. package/dist/protocol/extensions.test.js +65 -0
  109. package/dist/protocol/fixtures.test.d.ts +2 -0
  110. package/dist/protocol/fixtures.test.d.ts.map +1 -0
  111. package/dist/protocol/fixtures.test.js +51 -0
  112. package/dist/protocol/index.d.ts +9 -0
  113. package/dist/protocol/index.d.ts.map +1 -0
  114. package/dist/protocol/index.js +4 -0
  115. package/dist/protocol/messages.d.ts +480 -0
  116. package/dist/protocol/messages.d.ts.map +1 -0
  117. package/dist/protocol/messages.js +255 -0
  118. package/dist/protocol/messages.test.d.ts +2 -0
  119. package/dist/protocol/messages.test.d.ts.map +1 -0
  120. package/dist/protocol/messages.test.js +75 -0
  121. package/dist/share/codec.d.ts +58 -0
  122. package/dist/share/codec.d.ts.map +1 -0
  123. package/dist/share/codec.js +222 -0
  124. package/dist/share/codec.test.d.ts +2 -0
  125. package/dist/share/codec.test.d.ts.map +1 -0
  126. package/dist/share/codec.test.js +150 -0
  127. package/dist/share/detect.d.ts +24 -0
  128. package/dist/share/detect.d.ts.map +1 -0
  129. package/dist/share/detect.js +25 -0
  130. package/dist/share/detect.test.d.ts +2 -0
  131. package/dist/share/detect.test.d.ts.map +1 -0
  132. package/dist/share/detect.test.js +31 -0
  133. package/dist/share/index.d.ts +7 -0
  134. package/dist/share/index.d.ts.map +1 -0
  135. package/dist/share/index.js +3 -0
  136. package/dist/share/peer-addr.d.ts +32 -0
  137. package/dist/share/peer-addr.d.ts.map +1 -0
  138. package/dist/share/peer-addr.js +72 -0
  139. package/dist/share/peer-addr.test.d.ts +2 -0
  140. package/dist/share/peer-addr.test.d.ts.map +1 -0
  141. package/dist/share/peer-addr.test.js +58 -0
  142. package/dist/solid/auth-form.d.ts +43 -0
  143. package/dist/solid/auth-form.d.ts.map +1 -0
  144. package/dist/solid/auth-form.js +55 -0
  145. package/dist/solid/auth-form.test.d.ts +2 -0
  146. package/dist/solid/auth-form.test.d.ts.map +1 -0
  147. package/dist/solid/auth-form.test.js +131 -0
  148. package/dist/solid/auth-status.d.ts +21 -0
  149. package/dist/solid/auth-status.d.ts.map +1 -0
  150. package/dist/solid/auth-status.js +58 -0
  151. package/dist/solid/auth-status.test.d.ts +2 -0
  152. package/dist/solid/auth-status.test.d.ts.map +1 -0
  153. package/dist/solid/auth-status.test.js +60 -0
  154. package/dist/solid/index.d.ts +5 -0
  155. package/dist/solid/index.d.ts.map +1 -0
  156. package/dist/solid/index.js +10 -0
  157. package/dist/solid/knock-inbox.d.ts +61 -0
  158. package/dist/solid/knock-inbox.d.ts.map +1 -0
  159. package/dist/solid/knock-inbox.js +123 -0
  160. package/dist/solid/knock-inbox.test.d.ts +2 -0
  161. package/dist/solid/knock-inbox.test.d.ts.map +1 -0
  162. package/dist/solid/knock-inbox.test.js +153 -0
  163. package/dist/solid/reauth-flow.d.ts +26 -0
  164. package/dist/solid/reauth-flow.d.ts.map +1 -0
  165. package/dist/solid/reauth-flow.js +34 -0
  166. package/dist/solid/reauth-flow.test.d.ts +2 -0
  167. package/dist/solid/reauth-flow.test.d.ts.map +1 -0
  168. package/dist/solid/reauth-flow.test.js +91 -0
  169. package/dist/state/endpoint-control.d.ts +22 -0
  170. package/dist/state/endpoint-control.d.ts.map +1 -0
  171. package/dist/state/endpoint-control.js +34 -0
  172. package/dist/state/endpoint-control.test.d.ts +2 -0
  173. package/dist/state/endpoint-control.test.d.ts.map +1 -0
  174. package/dist/state/endpoint-control.test.js +63 -0
  175. package/dist/state/friend-directory.d.ts +37 -0
  176. package/dist/state/friend-directory.d.ts.map +1 -0
  177. package/dist/state/friend-directory.js +44 -0
  178. package/dist/state/friend-directory.test.d.ts +2 -0
  179. package/dist/state/friend-directory.test.d.ts.map +1 -0
  180. package/dist/state/friend-directory.test.js +63 -0
  181. package/dist/state/index.d.ts +6 -0
  182. package/dist/state/index.d.ts.map +1 -0
  183. package/dist/state/index.js +3 -0
  184. package/dist/state/peer-names.d.ts +7 -0
  185. package/dist/state/peer-names.d.ts.map +1 -0
  186. package/dist/state/peer-names.js +21 -0
  187. package/dist/state/peer-names.test.d.ts +2 -0
  188. package/dist/state/peer-names.test.d.ts.map +1 -0
  189. package/dist/state/peer-names.test.js +29 -0
  190. package/dist/testing/idb-harness.d.ts +9 -0
  191. package/dist/testing/idb-harness.d.ts.map +1 -0
  192. package/dist/testing/idb-harness.js +19 -0
  193. package/dist/testing/idb-harness.test.d.ts +2 -0
  194. package/dist/testing/idb-harness.test.d.ts.map +1 -0
  195. package/dist/testing/idb-harness.test.js +18 -0
  196. package/dist/testing/identity-fixtures.d.ts +10 -0
  197. package/dist/testing/identity-fixtures.d.ts.map +1 -0
  198. package/dist/testing/identity-fixtures.js +21 -0
  199. package/dist/testing/identity-fixtures.test.d.ts +2 -0
  200. package/dist/testing/identity-fixtures.test.d.ts.map +1 -0
  201. package/dist/testing/identity-fixtures.test.js +28 -0
  202. package/dist/testing/index.d.ts +7 -0
  203. package/dist/testing/index.d.ts.map +1 -0
  204. package/dist/testing/index.js +11 -0
  205. package/dist/testing/knock-fixtures.d.ts +13 -0
  206. package/dist/testing/knock-fixtures.d.ts.map +1 -0
  207. package/dist/testing/knock-fixtures.js +45 -0
  208. package/dist/testing/knock-fixtures.test.d.ts +2 -0
  209. package/dist/testing/knock-fixtures.test.d.ts.map +1 -0
  210. package/dist/testing/knock-fixtures.test.js +65 -0
  211. package/dist/testing/protocol-fixtures.d.ts +7 -0
  212. package/dist/testing/protocol-fixtures.d.ts.map +1 -0
  213. package/dist/testing/protocol-fixtures.js +28 -0
  214. package/dist/testing/protocol-fixtures.test.d.ts +2 -0
  215. package/dist/testing/protocol-fixtures.test.d.ts.map +1 -0
  216. package/dist/testing/protocol-fixtures.test.js +19 -0
  217. package/dist/testing/state-fixtures.d.ts +33 -0
  218. package/dist/testing/state-fixtures.d.ts.map +1 -0
  219. package/dist/testing/state-fixtures.js +67 -0
  220. package/dist/testing/state-fixtures.test.d.ts +2 -0
  221. package/dist/testing/state-fixtures.test.d.ts.map +1 -0
  222. package/dist/testing/state-fixtures.test.js +68 -0
  223. package/dist/testing/webauthn-mocks.d.ts +27 -0
  224. package/dist/testing/webauthn-mocks.d.ts.map +1 -0
  225. package/dist/testing/webauthn-mocks.js +78 -0
  226. package/dist/testing/webauthn-mocks.test.d.ts +2 -0
  227. package/dist/testing/webauthn-mocks.test.d.ts.map +1 -0
  228. package/dist/testing/webauthn-mocks.test.js +40 -0
  229. package/dist/webauthn/ceremony.d.ts +76 -0
  230. package/dist/webauthn/ceremony.d.ts.map +1 -0
  231. package/dist/webauthn/ceremony.js +136 -0
  232. package/dist/webauthn/ceremony.test.d.ts +2 -0
  233. package/dist/webauthn/ceremony.test.d.ts.map +1 -0
  234. package/dist/webauthn/ceremony.test.js +245 -0
  235. package/dist/webauthn/codec.d.ts +39 -0
  236. package/dist/webauthn/codec.d.ts.map +1 -0
  237. package/dist/webauthn/codec.js +118 -0
  238. package/dist/webauthn/codec.test.d.ts +2 -0
  239. package/dist/webauthn/codec.test.d.ts.map +1 -0
  240. package/dist/webauthn/codec.test.js +126 -0
  241. package/dist/webauthn/index.d.ts +7 -0
  242. package/dist/webauthn/index.d.ts.map +1 -0
  243. package/dist/webauthn/index.js +3 -0
  244. package/dist/webauthn/transport.d.ts +58 -0
  245. package/dist/webauthn/transport.d.ts.map +1 -0
  246. package/dist/webauthn/transport.js +6 -0
  247. package/dist/webauthn/types.d.ts +78 -0
  248. package/dist/webauthn/types.d.ts.map +1 -0
  249. package/dist/webauthn/types.js +29 -0
  250. package/package.json +83 -0
@@ -0,0 +1,150 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { decodeShareToken, encodeShareToken, extractShareToken, shareFragment, } from "./codec.js";
3
+ const NODE_ID = "ab".repeat(32);
4
+ const OTHER_NODE_ID = "cd".repeat(32);
5
+ describe("encodeShareToken / decodeShareToken (current wire)", () => {
6
+ it("round-trips a node payload", () => {
7
+ const payload = { kind: "node", nodeId: NODE_ID, title: "my node" };
8
+ const token = encodeShareToken(payload);
9
+ expect(decodeShareToken(token)).toEqual(payload);
10
+ });
11
+ it("round-trips a doc payload with mode + title", () => {
12
+ const payload = {
13
+ kind: "doc",
14
+ nodeId: NODE_ID,
15
+ docId: "automerge:abc123",
16
+ title: "shared playlist",
17
+ mode: "knock",
18
+ };
19
+ const token = encodeShareToken(payload);
20
+ expect(decodeShareToken(token)).toEqual(payload);
21
+ });
22
+ it("round-trips a doc payload with no optional fields", () => {
23
+ const payload = { kind: "doc", nodeId: NODE_ID, docId: "automerge:xyz" };
24
+ const token = encodeShareToken(payload);
25
+ expect(decodeShareToken(token)).toEqual(payload);
26
+ });
27
+ it("round-trips an entity payload", () => {
28
+ const payload = {
29
+ kind: "entity",
30
+ source: { nodeId: NODE_ID, httpOrigin: "https://music.example.com" },
31
+ entityKind: "album",
32
+ entityId: "album-1",
33
+ parentId: "artist-1",
34
+ title: "a great album",
35
+ artist: "someone",
36
+ album: "a great album",
37
+ };
38
+ const token = encodeShareToken(payload);
39
+ expect(decodeShareToken(token)).toEqual(payload);
40
+ });
41
+ it("round-trips unicode titles", () => {
42
+ const payload = {
43
+ kind: "doc",
44
+ nodeId: NODE_ID,
45
+ docId: "automerge:abc",
46
+ title: "\u65e5\u672c\u8a9e \ud83c\udfb5",
47
+ };
48
+ const token = encodeShareToken(payload);
49
+ expect(decodeShareToken(token)).toEqual(payload);
50
+ });
51
+ it("wraps the token in a #share/ fragment and decodes it back", () => {
52
+ const payload = { kind: "node", nodeId: NODE_ID };
53
+ const fragment = shareFragment(payload);
54
+ expect(fragment.startsWith("#share/")).toBe(true);
55
+ expect(decodeShareToken(fragment)).toEqual(payload);
56
+ });
57
+ it("decodes a token embedded in a full url", () => {
58
+ const payload = { kind: "node", nodeId: NODE_ID };
59
+ const token = encodeShareToken(payload);
60
+ const url = `https://example.com/app/${shareFragment(payload)}`;
61
+ expect(decodeShareToken(url)).toEqual(payload);
62
+ expect(extractShareToken(url)).toBe(token);
63
+ });
64
+ });
65
+ describe("decodeShareToken (legacy shapes)", () => {
66
+ it("decodes the legacy versioned doc shape {v:1,n,d,t,m}", () => {
67
+ const legacy = { v: 1, n: NODE_ID, d: "automerge:legacy", t: "old link", m: "public" };
68
+ const token = btoa(JSON.stringify(legacy))
69
+ .replace(/\+/g, "-")
70
+ .replace(/\//g, "_")
71
+ .replace(/=+$/, "");
72
+ expect(decodeShareToken(token)).toEqual({
73
+ kind: "doc",
74
+ nodeId: NODE_ID,
75
+ docId: "automerge:legacy",
76
+ title: "old link",
77
+ mode: "public",
78
+ });
79
+ });
80
+ it("decodes the bare, unversioned doc shape {n,d}", () => {
81
+ const legacy = { n: NODE_ID, d: "automerge:bare" };
82
+ const token = btoa(JSON.stringify(legacy));
83
+ expect(decodeShareToken(token)).toEqual({
84
+ kind: "doc",
85
+ nodeId: NODE_ID,
86
+ docId: "automerge:bare",
87
+ });
88
+ });
89
+ it("decodes the legacy entity permalink shape {v:1,s:{n,h},k,i,...}", () => {
90
+ const legacy = {
91
+ v: 1,
92
+ s: { n: NODE_ID, h: "https://spume.example.com" },
93
+ k: "song",
94
+ i: "song-1",
95
+ p: "album-1",
96
+ t: "a song",
97
+ a: "an artist",
98
+ al: "an album",
99
+ };
100
+ const json = JSON.stringify(legacy);
101
+ let bin = "";
102
+ for (const byte of new TextEncoder().encode(json))
103
+ bin += String.fromCharCode(byte);
104
+ const token = btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
105
+ expect(decodeShareToken(token)).toEqual({
106
+ kind: "entity",
107
+ source: { nodeId: NODE_ID, httpOrigin: "https://spume.example.com" },
108
+ entityKind: "song",
109
+ entityId: "song-1",
110
+ parentId: "album-1",
111
+ title: "a song",
112
+ artist: "an artist",
113
+ album: "an album",
114
+ });
115
+ });
116
+ it("decodes a legacy token behind #share/ and share/ prefixes", () => {
117
+ const legacy = { n: NODE_ID, d: OTHER_NODE_ID };
118
+ const token = btoa(JSON.stringify(legacy));
119
+ expect(decodeShareToken(`#share/${token}`)).toEqual({
120
+ kind: "doc",
121
+ nodeId: NODE_ID,
122
+ docId: OTHER_NODE_ID,
123
+ });
124
+ expect(decodeShareToken(`share/${token}`)).toEqual({
125
+ kind: "doc",
126
+ nodeId: NODE_ID,
127
+ docId: OTHER_NODE_ID,
128
+ });
129
+ });
130
+ });
131
+ describe("decodeShareToken (invalid input)", () => {
132
+ it("returns null for empty input", () => {
133
+ expect(decodeShareToken("")).toBeNull();
134
+ expect(decodeShareToken(" ")).toBeNull();
135
+ });
136
+ it("returns null for non-base64 garbage", () => {
137
+ expect(decodeShareToken("not a token!!! ###")).toBeNull();
138
+ });
139
+ it("returns null for valid base64 that isn't json", () => {
140
+ expect(decodeShareToken(btoa("just a string"))).toBeNull();
141
+ });
142
+ it("returns null for json that matches no known shape", () => {
143
+ const token = btoa(JSON.stringify({ foo: "bar" }));
144
+ expect(decodeShareToken(token)).toBeNull();
145
+ });
146
+ it("returns null for a wire payload with an unsupported version", () => {
147
+ const token = btoa(JSON.stringify({ v: 99, k: "node", n: NODE_ID }));
148
+ expect(decodeShareToken(token)).toBeNull();
149
+ });
150
+ });
@@ -0,0 +1,24 @@
1
+ import type { ShareTokenPayload } from "./codec.js";
2
+ /**
3
+ * classification of a raw string: a bare node id, a share token
4
+ * (optionally embedded in a url fragment), or neither.
5
+ */
6
+ export type ShareInputDetection = {
7
+ kind: "node_id";
8
+ nodeId: string;
9
+ } | {
10
+ kind: "share_token";
11
+ token: string;
12
+ payload: ShareTokenPayload;
13
+ } | {
14
+ kind: "invalid";
15
+ };
16
+ /**
17
+ * classify raw input as a node id, a share token, or neither. node id
18
+ * detection reuses extractNodeIdStrict so this never diverges from
19
+ * parsePeerAddress's own notion of a valid peer address; share-token
20
+ * detection reuses decodeShareToken/extractShareToken so a full url with
21
+ * the token in its fragment classifies the same way a bare token does.
22
+ */
23
+ export declare function detectShareInput(input: string): ShareInputDetection;
24
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/share/detect.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGpD;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,GAClE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAExB;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,CAcnE"}
@@ -0,0 +1,25 @@
1
+ // classification of raw strings a user might paste or type into a
2
+ // peer-address / share-link input box.
3
+ import { decodeShareToken, extractShareToken } from "./codec.js";
4
+ import { extractNodeIdStrict } from "./peer-addr.js";
5
+ /**
6
+ * classify raw input as a node id, a share token, or neither. node id
7
+ * detection reuses extractNodeIdStrict so this never diverges from
8
+ * parsePeerAddress's own notion of a valid peer address; share-token
9
+ * detection reuses decodeShareToken/extractShareToken so a full url with
10
+ * the token in its fragment classifies the same way a bare token does.
11
+ */
12
+ export function detectShareInput(input) {
13
+ const trimmed = input.trim();
14
+ if (!trimmed)
15
+ return { kind: "invalid" };
16
+ const nodeId = extractNodeIdStrict(trimmed);
17
+ if (nodeId) {
18
+ return { kind: "node_id", nodeId };
19
+ }
20
+ const token = extractShareToken(trimmed);
21
+ const payload = decodeShareToken(token);
22
+ if (!payload)
23
+ return { kind: "invalid" };
24
+ return { kind: "share_token", token, payload };
25
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=detect.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.test.d.ts","sourceRoot":"","sources":["../../src/share/detect.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { detectShareInput } from "./detect.js";
3
+ import { encodeShareToken, shareFragment } from "./codec.js";
4
+ const NODE_ID = "ab".repeat(32);
5
+ describe("detectShareInput", () => {
6
+ it("classifies a bare 64-hex node id", () => {
7
+ expect(detectShareInput(NODE_ID)).toEqual({ kind: "node_id", nodeId: NODE_ID });
8
+ });
9
+ it("classifies a json endpoint blob as a node id", () => {
10
+ const blob = JSON.stringify({ node_id: NODE_ID, relay_url: "https://relay.example" });
11
+ expect(detectShareInput(blob)).toEqual({ kind: "node_id", nodeId: NODE_ID });
12
+ });
13
+ it("classifies a bare share token", () => {
14
+ const payload = { kind: "doc", nodeId: NODE_ID, docId: "automerge:abc" };
15
+ const token = encodeShareToken(payload);
16
+ expect(detectShareInput(token)).toEqual({ kind: "share_token", token, payload });
17
+ });
18
+ it("classifies a url with the token in its fragment", () => {
19
+ const payload = { kind: "doc", nodeId: NODE_ID, docId: "automerge:xyz" };
20
+ const token = encodeShareToken(payload);
21
+ const url = `https://example.com/app/${shareFragment(payload)}`;
22
+ expect(detectShareInput(url)).toEqual({ kind: "share_token", token, payload });
23
+ });
24
+ it("classifies empty input as invalid", () => {
25
+ expect(detectShareInput("")).toEqual({ kind: "invalid" });
26
+ expect(detectShareInput(" ")).toEqual({ kind: "invalid" });
27
+ });
28
+ it("classifies garbage as invalid", () => {
29
+ expect(detectShareInput("not a node id or a token")).toEqual({ kind: "invalid" });
30
+ });
31
+ });
@@ -0,0 +1,7 @@
1
+ export { extractNodeId, extractNodeIdStrict, isValidNodeId, parsePeerAddress, } from "./peer-addr.js";
2
+ export type { PeerTarget } from "./peer-addr.js";
3
+ export type { DocSharePayload, EntitySharePayload, NodeSharePayload, ShareTokenPayload, } from "./codec.js";
4
+ export { decodeShareToken, encodeShareToken, extractShareToken, shareFragment, } from "./codec.js";
5
+ export type { ShareInputDetection } from "./detect.js";
6
+ export { detectShareInput } from "./detect.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/share/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { extractNodeId, extractNodeIdStrict, isValidNodeId, parsePeerAddress, } from "./peer-addr.js";
2
+ export { decodeShareToken, encodeShareToken, extractShareToken, shareFragment, } from "./codec.js";
3
+ export { detectShareInput } from "./detect.js";
@@ -0,0 +1,32 @@
1
+ /** true when `value` is a bare 64-hex node id. */
2
+ export declare function isValidNodeId(value: string): boolean;
3
+ /**
4
+ * extract the 64-hex node id from a `peer_addr` string (bare id or json
5
+ * endpoint blob). falls back to returning the raw value when it cannot
6
+ * be parsed.
7
+ */
8
+ export declare function extractNodeId(peerAddr: string): string;
9
+ /** strict variant: null when a 64-hex node id could not be derived. */
10
+ export declare function extractNodeIdStrict(peerAddr: string): string | null;
11
+ /** a classified peer target: p2p (addressed by node id / endpoint json)
12
+ * or http (addressed by base url). */
13
+ export type PeerTarget = {
14
+ type: "p2p";
15
+ peerAddr: string;
16
+ } | {
17
+ type: "http";
18
+ url: string;
19
+ };
20
+ /**
21
+ * classify raw user input as a p2p peer address or an http url.
22
+ *
23
+ * - a bare 64-hex string is a node id (p2p)
24
+ * - a json blob with a string `id` or `node_id` field is a full endpoint (p2p)
25
+ * - anything else is treated as an http url, scheme-prefixed with
26
+ * `defaultScheme` when missing and trailing-slash trimmed
27
+ *
28
+ * returns null for empty input. url VALIDITY is not checked here - the
29
+ * caller decides how to validate/normalize further.
30
+ */
31
+ export declare function parsePeerAddress(input: string, defaultScheme?: "http" | "https"): PeerTarget | null;
32
+ //# sourceMappingURL=peer-addr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-addr.d.ts","sourceRoot":"","sources":["../../src/share/peer-addr.ts"],"names":[],"mappings":"AAQA,kDAAkD;AAClD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWtD;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGnE;AAED;uCACuC;AACvC,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3F;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,aAAa,GAAE,MAAM,GAAG,OAAiB,GACxC,UAAU,GAAG,IAAI,CAyBnB"}
@@ -0,0 +1,72 @@
1
+ // peer-address helpers: classify and normalize the strings apps use to
2
+ // address a peer. a `peer_addr` may be a bare 64-hex iroh node id, or a
3
+ // json endpoint blob carrying the node id under `node_id` (full endpoint
4
+ // dumps) or `id` (compact share forms); anything else is treated as an
5
+ // http url.
6
+ const NODE_ID_RE = /^[0-9a-f]{64}$/i;
7
+ /** true when `value` is a bare 64-hex node id. */
8
+ export function isValidNodeId(value) {
9
+ return NODE_ID_RE.test(value.trim());
10
+ }
11
+ /**
12
+ * extract the 64-hex node id from a `peer_addr` string (bare id or json
13
+ * endpoint blob). falls back to returning the raw value when it cannot
14
+ * be parsed.
15
+ */
16
+ export function extractNodeId(peerAddr) {
17
+ const trimmed = peerAddr.trim();
18
+ if (NODE_ID_RE.test(trimmed))
19
+ return trimmed;
20
+ try {
21
+ const parsed = JSON.parse(trimmed);
22
+ if (typeof parsed?.node_id === "string")
23
+ return parsed.node_id;
24
+ if (typeof parsed?.id === "string")
25
+ return parsed.id;
26
+ }
27
+ catch {
28
+ // not json
29
+ }
30
+ return peerAddr;
31
+ }
32
+ /** strict variant: null when a 64-hex node id could not be derived. */
33
+ export function extractNodeIdStrict(peerAddr) {
34
+ const id = extractNodeId(peerAddr);
35
+ return NODE_ID_RE.test(id) ? id : null;
36
+ }
37
+ /**
38
+ * classify raw user input as a p2p peer address or an http url.
39
+ *
40
+ * - a bare 64-hex string is a node id (p2p)
41
+ * - a json blob with a string `id` or `node_id` field is a full endpoint (p2p)
42
+ * - anything else is treated as an http url, scheme-prefixed with
43
+ * `defaultScheme` when missing and trailing-slash trimmed
44
+ *
45
+ * returns null for empty input. url VALIDITY is not checked here - the
46
+ * caller decides how to validate/normalize further.
47
+ */
48
+ export function parsePeerAddress(input, defaultScheme = "https") {
49
+ const trimmed = input.trim();
50
+ if (!trimmed)
51
+ return null;
52
+ if (NODE_ID_RE.test(trimmed)) {
53
+ return { type: "p2p", peerAddr: trimmed };
54
+ }
55
+ if (trimmed.startsWith("{")) {
56
+ try {
57
+ const parsed = JSON.parse(trimmed);
58
+ if (typeof parsed?.id === "string" || typeof parsed?.node_id === "string") {
59
+ return { type: "p2p", peerAddr: trimmed };
60
+ }
61
+ }
62
+ catch {
63
+ // not valid json, fall through to url handling
64
+ }
65
+ }
66
+ let url = trimmed;
67
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
68
+ url = `${defaultScheme}://${url}`;
69
+ }
70
+ url = url.replace(/\/+$/, "");
71
+ return { type: "http", url };
72
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=peer-addr.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-addr.test.d.ts","sourceRoot":"","sources":["../../src/share/peer-addr.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,58 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { extractNodeId, extractNodeIdStrict, isValidNodeId, parsePeerAddress, } from "./peer-addr.js";
3
+ const NODE_ID = "ab".repeat(32);
4
+ describe("isValidNodeId", () => {
5
+ it("accepts a 64-hex id (either case, surrounding whitespace ok)", () => {
6
+ expect(isValidNodeId(NODE_ID)).toBe(true);
7
+ expect(isValidNodeId(` ${NODE_ID.toUpperCase()} `)).toBe(true);
8
+ });
9
+ it("rejects wrong lengths and non-hex", () => {
10
+ expect(isValidNodeId(NODE_ID.slice(1))).toBe(false);
11
+ expect(isValidNodeId("z".repeat(64))).toBe(false);
12
+ expect(isValidNodeId("")).toBe(false);
13
+ });
14
+ });
15
+ describe("extractNodeId", () => {
16
+ it("passes a bare id through", () => {
17
+ expect(extractNodeId(NODE_ID)).toBe(NODE_ID);
18
+ });
19
+ it("reads node_id or id out of a json endpoint blob", () => {
20
+ expect(extractNodeId(JSON.stringify({ node_id: NODE_ID, relay_url: "x" }))).toBe(NODE_ID);
21
+ expect(extractNodeId(JSON.stringify({ id: NODE_ID }))).toBe(NODE_ID);
22
+ });
23
+ it("falls back to the raw value when unparseable", () => {
24
+ expect(extractNodeId("not json")).toBe("not json");
25
+ expect(extractNodeIdStrict("not json")).toBeNull();
26
+ expect(extractNodeIdStrict(JSON.stringify({ id: NODE_ID }))).toBe(NODE_ID);
27
+ });
28
+ });
29
+ describe("parsePeerAddress", () => {
30
+ it("classifies a bare node id as p2p", () => {
31
+ expect(parsePeerAddress(` ${NODE_ID} `)).toEqual({ type: "p2p", peerAddr: NODE_ID });
32
+ });
33
+ it("classifies a json endpoint blob as p2p, keeping the blob intact", () => {
34
+ const blob = JSON.stringify({ id: NODE_ID, relay_url: "https://r.example" });
35
+ expect(parsePeerAddress(blob)).toEqual({ type: "p2p", peerAddr: blob });
36
+ });
37
+ it("json without an id field falls through to http", () => {
38
+ const result = parsePeerAddress('{"foo": 1}');
39
+ expect(result?.type).toBe("http");
40
+ });
41
+ it("normalizes http input: default scheme + trailing slash trim", () => {
42
+ expect(parsePeerAddress("music.example.com/")).toEqual({
43
+ type: "http",
44
+ url: "https://music.example.com",
45
+ });
46
+ expect(parsePeerAddress("music.example.com", "http")).toEqual({
47
+ type: "http",
48
+ url: "http://music.example.com",
49
+ });
50
+ expect(parsePeerAddress("https://music.example.com//")).toEqual({
51
+ type: "http",
52
+ url: "https://music.example.com",
53
+ });
54
+ });
55
+ it("returns null for empty input", () => {
56
+ expect(parsePeerAddress(" ")).toBeNull();
57
+ });
58
+ });
@@ -0,0 +1,43 @@
1
+ import { type Accessor, type Setter } from "solid-js";
2
+ export type AuthMode = "login" | "register";
3
+ export interface AuthFormData {
4
+ username: string;
5
+ inviteCode?: string;
6
+ mode: AuthMode;
7
+ }
8
+ export interface AuthFormDeps {
9
+ /** starting mode. defaults to "login". */
10
+ initialMode?: AuthMode;
11
+ /** reactive read of the current loading state (e.g. `() => isLoading()`).
12
+ * while true, submit and passkey handlers no-op instead of firing
13
+ * another request. defaults to `() => false`. */
14
+ loading?: () => boolean;
15
+ onSubmit?: (data: AuthFormData) => void | Promise<void>;
16
+ onPasskeyClick?: (data: AuthFormData) => void | Promise<void>;
17
+ onModeChange?: (mode: AuthMode) => void;
18
+ }
19
+ export interface AuthFormState {
20
+ mode: Accessor<AuthMode>;
21
+ username: Accessor<string>;
22
+ setUsername: Setter<string>;
23
+ inviteCode: Accessor<string>;
24
+ setInviteCode: Setter<string>;
25
+ /** true when the current field values don't satisfy submission
26
+ * (blank username, or register mode with a blank invite code) or the
27
+ * form is loading. */
28
+ isSubmitDisabled: Accessor<boolean>;
29
+ /** flips between login and register, notifying `onModeChange`. */
30
+ switchMode(): void;
31
+ /** builds the current `AuthFormData` and calls `onSubmit`. no-ops while
32
+ * loading. `event?.preventDefault()` is called if an event is passed,
33
+ * so this can be wired directly to a `<form onSubmit>`. */
34
+ handleSubmit(event?: {
35
+ preventDefault(): void;
36
+ }): Promise<void>;
37
+ /** same shape as `handleSubmit`, but calls `onPasskeyClick`. */
38
+ handlePasskeyClick(event?: {
39
+ preventDefault(): void;
40
+ }): Promise<void>;
41
+ }
42
+ export declare function createAuthForm(deps?: AuthFormDeps): AuthFormState;
43
+ //# sourceMappingURL=auth-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-form.d.ts","sourceRoot":"","sources":["../../src/solid/auth-form.ts"],"names":[],"mappings":"AAKA,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAEpE,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAE5C,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB;;sDAEkD;IAClD,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACzB,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5B,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9B;;2BAEuB;IACvB,gBAAgB,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACpC,kEAAkE;IAClE,UAAU,IAAI,IAAI,CAAC;IACnB;;gEAE4D;IAC5D,YAAY,CAAC,KAAK,CAAC,EAAE;QAAE,cAAc,IAAI,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,gEAAgE;IAChE,kBAAkB,CAAC,KAAK,CAAC,EAAE;QAAE,cAAc,IAAI,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED,wBAAgB,cAAc,CAAC,IAAI,GAAE,YAAiB,GAAG,aAAa,CAkDrE"}
@@ -0,0 +1,55 @@
1
+ // headless auth-form state: mode switching (login/register), field
2
+ // signals, submit-disabled logic, and the submit/passkey handlers - all
3
+ // with zero markup or styling opinions. consumers render whatever inputs/
4
+ // buttons they like and wire them to the returned signals/handlers.
5
+ import { createSignal } from "solid-js";
6
+ export function createAuthForm(deps = {}) {
7
+ const isLoading = deps.loading ?? (() => false);
8
+ const [mode, setMode] = createSignal(deps.initialMode ?? "login");
9
+ const [username, setUsername] = createSignal("");
10
+ const [inviteCode, setInviteCode] = createSignal("");
11
+ function currentData() {
12
+ return {
13
+ username: username(),
14
+ inviteCode: mode() === "register" ? inviteCode() : undefined,
15
+ mode: mode(),
16
+ };
17
+ }
18
+ function isSubmitDisabled() {
19
+ if (isLoading())
20
+ return true;
21
+ if (!username().trim())
22
+ return true;
23
+ if (mode() === "register" && !inviteCode().trim())
24
+ return true;
25
+ return false;
26
+ }
27
+ function switchMode() {
28
+ const next = mode() === "login" ? "register" : "login";
29
+ setMode(next);
30
+ deps.onModeChange?.(next);
31
+ }
32
+ async function handleSubmit(event) {
33
+ event?.preventDefault();
34
+ if (isLoading())
35
+ return;
36
+ await deps.onSubmit?.(currentData());
37
+ }
38
+ async function handlePasskeyClick(event) {
39
+ event?.preventDefault();
40
+ if (isLoading())
41
+ return;
42
+ await deps.onPasskeyClick?.(currentData());
43
+ }
44
+ return {
45
+ mode,
46
+ username,
47
+ setUsername,
48
+ inviteCode,
49
+ setInviteCode,
50
+ isSubmitDisabled,
51
+ switchMode,
52
+ handleSubmit,
53
+ handlePasskeyClick,
54
+ };
55
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth-form.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-form.test.d.ts","sourceRoot":"","sources":["../../src/solid/auth-form.test.ts"],"names":[],"mappings":""}