@peers-app/peers-sdk 0.25.13 → 0.25.15

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 (79) hide show
  1. package/dist/context/user-context-identity.test.d.ts +1 -0
  2. package/dist/context/user-context-identity.test.js +220 -0
  3. package/dist/context/user-context.d.ts +5 -1
  4. package/dist/context/user-context.js +63 -8
  5. package/dist/contracts/index.d.ts +1 -1
  6. package/dist/contracts/system/identity-contract.d.ts +156 -0
  7. package/dist/contracts/system/identity-contract.js +172 -0
  8. package/dist/contracts/system/index.d.ts +1 -1
  9. package/dist/data/assistants.d.ts +4 -4
  10. package/dist/data/devices.d.ts +70 -4
  11. package/dist/data/devices.js +3 -0
  12. package/dist/data/group-members.d.ts +2 -2
  13. package/dist/data/groups.d.ts +2 -2
  14. package/dist/data/invites.d.ts +93 -25
  15. package/dist/data/persistent-vars.d.ts +8 -0
  16. package/dist/data/persistent-vars.js +16 -0
  17. package/dist/data/tool-access-approvals.d.ts +2 -2
  18. package/dist/data/user-permissions.d.ts +10 -2
  19. package/dist/data/user-permissions.js +35 -4
  20. package/dist/data/user-permissions.test.js +26 -0
  21. package/dist/data/users.d.ts +48 -4
  22. package/dist/data/users.js +61 -5
  23. package/dist/data/workflows.d.ts +1 -1
  24. package/dist/device/connection.d.ts +7 -1
  25. package/dist/device/connection.js +38 -6
  26. package/dist/device/connection.test.js +33 -4
  27. package/dist/device/device.d.ts +2 -2
  28. package/dist/device/device.js +4 -2
  29. package/dist/device/get-trust-level-fn.d.ts +1 -0
  30. package/dist/device/get-trust-level-fn.js +28 -4
  31. package/dist/device/get-trust-level-fn.test.js +95 -0
  32. package/dist/device-pairing/types.d.ts +26 -26
  33. package/dist/host-credentials.d.ts +78 -0
  34. package/dist/host-credentials.js +128 -0
  35. package/dist/host-credentials.test.d.ts +1 -0
  36. package/dist/host-credentials.test.js +88 -0
  37. package/dist/identity/account-email.d.ts +59 -0
  38. package/dist/identity/account-email.js +87 -0
  39. package/dist/identity/account-email.test.d.ts +1 -0
  40. package/dist/identity/account-email.test.js +59 -0
  41. package/dist/identity/anchor-document.d.ts +205 -0
  42. package/dist/identity/anchor-document.js +119 -0
  43. package/dist/identity/anchor-document.test.d.ts +1 -0
  44. package/dist/identity/anchor-document.test.js +16 -0
  45. package/dist/identity/anchor-resolution.d.ts +61 -0
  46. package/dist/identity/anchor-resolution.js +117 -0
  47. package/dist/identity/anchor-resolution.test.d.ts +1 -0
  48. package/dist/identity/anchor-resolution.test.js +130 -0
  49. package/dist/identity/key-change.d.ts +142 -0
  50. package/dist/identity/key-change.js +472 -0
  51. package/dist/identity/key-change.test.d.ts +1 -0
  52. package/dist/identity/key-change.test.js +811 -0
  53. package/dist/identity/key-contest.d.ts +91 -0
  54. package/dist/identity/key-contest.js +55 -0
  55. package/dist/identity/key-resolutions.d.ts +55 -0
  56. package/dist/identity/key-resolutions.js +42 -0
  57. package/dist/identity/passkey.d.ts +22 -0
  58. package/dist/identity/passkey.js +33 -0
  59. package/dist/identity/user-row-change.d.ts +45 -0
  60. package/dist/identity/user-row-change.js +81 -0
  61. package/dist/index.d.ts +9 -0
  62. package/dist/index.js +9 -0
  63. package/dist/invites/invite-messages.d.ts +512 -116
  64. package/dist/invites/invite-token.d.ts +114 -24
  65. package/dist/invites/invite-token.js +6 -0
  66. package/dist/keys.d.ts +7 -0
  67. package/dist/keys.js +20 -0
  68. package/dist/package-installer/package-author-signing.test.js +11 -0
  69. package/dist/package-installer/package-publisher.d.ts +5 -1
  70. package/dist/package-installer/package-publisher.js +1 -1
  71. package/dist/package-installer/package-publisher.test.js +11 -0
  72. package/dist/package-installer/package-remote-checker.js +3 -0
  73. package/dist/system-ids.d.ts +16 -0
  74. package/dist/system-ids.js +16 -0
  75. package/dist/types/workflow.d.ts +2 -2
  76. package/dist/utils.d.ts +10 -0
  77. package/dist/utils.js +43 -0
  78. package/dist/utils.test.js +23 -0
  79. package/package.json +1 -1
@@ -21,15 +21,37 @@ export declare const inviteIdentitySchema: z.ZodObject<{
21
21
  publicKey: z.ZodString;
22
22
  publicBoxKey: z.ZodString;
23
23
  displayName: z.ZodOptional<z.ZodString>;
24
+ /**
25
+ * Anchor list to store on first contact. A peer that already has a list
26
+ * keeps it; this is only the bootstrap that arrives with the invitation.
27
+ */
28
+ anchors: z.ZodOptional<z.ZodArray<z.ZodObject<{
29
+ type: z.ZodEnum<["peers-services", "https"]>;
30
+ url: z.ZodString;
31
+ }, "strip", z.ZodTypeAny, {
32
+ type: "peers-services" | "https";
33
+ url: string;
34
+ }, {
35
+ type: "peers-services" | "https";
36
+ url: string;
37
+ }>, "many">>;
24
38
  }, "strict", z.ZodTypeAny, {
25
39
  publicKey: string;
26
- publicBoxKey: string;
27
40
  userId: string;
41
+ publicBoxKey: string;
42
+ anchors?: {
43
+ type: "peers-services" | "https";
44
+ url: string;
45
+ }[] | undefined;
28
46
  displayName?: string | undefined;
29
47
  }, {
30
48
  publicKey: string;
31
- publicBoxKey: string;
32
49
  userId: string;
50
+ publicBoxKey: string;
51
+ anchors?: {
52
+ type: "peers-services" | "https";
53
+ url: string;
54
+ }[] | undefined;
33
55
  displayName?: string | undefined;
34
56
  }>;
35
57
  /** Public identity of a party to an invite. */
@@ -50,15 +72,15 @@ export declare const inviteGroupSchema: z.ZodObject<{
50
72
  name: string;
51
73
  role: GroupMemberRole;
52
74
  publicKey: string;
53
- groupId: string;
54
75
  publicBoxKey: string;
76
+ groupId: string;
55
77
  mode: "open" | "approve";
56
78
  }, {
57
79
  name: string;
58
80
  role: GroupMemberRole;
59
81
  publicKey: string;
60
- groupId: string;
61
82
  publicBoxKey: string;
83
+ groupId: string;
62
84
  mode: "open" | "approve";
63
85
  }>;
64
86
  /** Group facts embedded in a group token. */
@@ -74,15 +96,37 @@ export declare const peersInviteContentsSchema: z.ZodEffects<z.ZodObject<{
74
96
  publicKey: z.ZodString;
75
97
  publicBoxKey: z.ZodString;
76
98
  displayName: z.ZodOptional<z.ZodString>;
99
+ /**
100
+ * Anchor list to store on first contact. A peer that already has a list
101
+ * keeps it; this is only the bootstrap that arrives with the invitation.
102
+ */
103
+ anchors: z.ZodOptional<z.ZodArray<z.ZodObject<{
104
+ type: z.ZodEnum<["peers-services", "https"]>;
105
+ url: z.ZodString;
106
+ }, "strip", z.ZodTypeAny, {
107
+ type: "peers-services" | "https";
108
+ url: string;
109
+ }, {
110
+ type: "peers-services" | "https";
111
+ url: string;
112
+ }>, "many">>;
77
113
  }, "strict", z.ZodTypeAny, {
78
114
  publicKey: string;
79
- publicBoxKey: string;
80
115
  userId: string;
116
+ publicBoxKey: string;
117
+ anchors?: {
118
+ type: "peers-services" | "https";
119
+ url: string;
120
+ }[] | undefined;
81
121
  displayName?: string | undefined;
82
122
  }, {
83
123
  publicKey: string;
84
- publicBoxKey: string;
85
124
  userId: string;
125
+ publicBoxKey: string;
126
+ anchors?: {
127
+ type: "peers-services" | "https";
128
+ url: string;
129
+ }[] | undefined;
86
130
  displayName?: string | undefined;
87
131
  }>;
88
132
  /** ISO 8601 instant after which the token is invalid. */
@@ -103,15 +147,15 @@ export declare const peersInviteContentsSchema: z.ZodEffects<z.ZodObject<{
103
147
  name: string;
104
148
  role: GroupMemberRole;
105
149
  publicKey: string;
106
- groupId: string;
107
150
  publicBoxKey: string;
151
+ groupId: string;
108
152
  mode: "open" | "approve";
109
153
  }, {
110
154
  name: string;
111
155
  role: GroupMemberRole;
112
156
  publicKey: string;
113
- groupId: string;
114
157
  publicBoxKey: string;
158
+ groupId: string;
115
159
  mode: "open" | "approve";
116
160
  }>>;
117
161
  }, "strict", z.ZodTypeAny, {
@@ -121,8 +165,12 @@ export declare const peersInviteContentsSchema: z.ZodEffects<z.ZodObject<{
121
165
  v: 1;
122
166
  inviter: {
123
167
  publicKey: string;
124
- publicBoxKey: string;
125
168
  userId: string;
169
+ publicBoxKey: string;
170
+ anchors?: {
171
+ type: "peers-services" | "https";
172
+ url: string;
173
+ }[] | undefined;
126
174
  displayName?: string | undefined;
127
175
  };
128
176
  epoch: number;
@@ -131,8 +179,8 @@ export declare const peersInviteContentsSchema: z.ZodEffects<z.ZodObject<{
131
179
  name: string;
132
180
  role: GroupMemberRole;
133
181
  publicKey: string;
134
- groupId: string;
135
182
  publicBoxKey: string;
183
+ groupId: string;
136
184
  mode: "open" | "approve";
137
185
  } | undefined;
138
186
  }, {
@@ -142,8 +190,12 @@ export declare const peersInviteContentsSchema: z.ZodEffects<z.ZodObject<{
142
190
  v: 1;
143
191
  inviter: {
144
192
  publicKey: string;
145
- publicBoxKey: string;
146
193
  userId: string;
194
+ publicBoxKey: string;
195
+ anchors?: {
196
+ type: "peers-services" | "https";
197
+ url: string;
198
+ }[] | undefined;
147
199
  displayName?: string | undefined;
148
200
  };
149
201
  epoch: number;
@@ -152,8 +204,8 @@ export declare const peersInviteContentsSchema: z.ZodEffects<z.ZodObject<{
152
204
  name: string;
153
205
  role: GroupMemberRole;
154
206
  publicKey: string;
155
- groupId: string;
156
207
  publicBoxKey: string;
208
+ groupId: string;
157
209
  mode: "open" | "approve";
158
210
  } | undefined;
159
211
  }>, {
@@ -163,8 +215,12 @@ export declare const peersInviteContentsSchema: z.ZodEffects<z.ZodObject<{
163
215
  v: 1;
164
216
  inviter: {
165
217
  publicKey: string;
166
- publicBoxKey: string;
167
218
  userId: string;
219
+ publicBoxKey: string;
220
+ anchors?: {
221
+ type: "peers-services" | "https";
222
+ url: string;
223
+ }[] | undefined;
168
224
  displayName?: string | undefined;
169
225
  };
170
226
  epoch: number;
@@ -173,8 +229,8 @@ export declare const peersInviteContentsSchema: z.ZodEffects<z.ZodObject<{
173
229
  name: string;
174
230
  role: GroupMemberRole;
175
231
  publicKey: string;
176
- groupId: string;
177
232
  publicBoxKey: string;
233
+ groupId: string;
178
234
  mode: "open" | "approve";
179
235
  } | undefined;
180
236
  }, {
@@ -184,8 +240,12 @@ export declare const peersInviteContentsSchema: z.ZodEffects<z.ZodObject<{
184
240
  v: 1;
185
241
  inviter: {
186
242
  publicKey: string;
187
- publicBoxKey: string;
188
243
  userId: string;
244
+ publicBoxKey: string;
245
+ anchors?: {
246
+ type: "peers-services" | "https";
247
+ url: string;
248
+ }[] | undefined;
189
249
  displayName?: string | undefined;
190
250
  };
191
251
  epoch: number;
@@ -194,8 +254,8 @@ export declare const peersInviteContentsSchema: z.ZodEffects<z.ZodObject<{
194
254
  name: string;
195
255
  role: GroupMemberRole;
196
256
  publicKey: string;
197
- groupId: string;
198
257
  publicBoxKey: string;
258
+ groupId: string;
199
259
  mode: "open" | "approve";
200
260
  } | undefined;
201
261
  }>;
@@ -210,15 +270,37 @@ export declare const peersInviteSchema: z.ZodObject<{
210
270
  publicKey: z.ZodString;
211
271
  publicBoxKey: z.ZodString;
212
272
  displayName: z.ZodOptional<z.ZodString>;
273
+ /**
274
+ * Anchor list to store on first contact. A peer that already has a list
275
+ * keeps it; this is only the bootstrap that arrives with the invitation.
276
+ */
277
+ anchors: z.ZodOptional<z.ZodArray<z.ZodObject<{
278
+ type: z.ZodEnum<["peers-services", "https"]>;
279
+ url: z.ZodString;
280
+ }, "strip", z.ZodTypeAny, {
281
+ type: "peers-services" | "https";
282
+ url: string;
283
+ }, {
284
+ type: "peers-services" | "https";
285
+ url: string;
286
+ }>, "many">>;
213
287
  }, "strict", z.ZodTypeAny, {
214
288
  publicKey: string;
215
- publicBoxKey: string;
216
289
  userId: string;
290
+ publicBoxKey: string;
291
+ anchors?: {
292
+ type: "peers-services" | "https";
293
+ url: string;
294
+ }[] | undefined;
217
295
  displayName?: string | undefined;
218
296
  }, {
219
297
  publicKey: string;
220
- publicBoxKey: string;
221
298
  userId: string;
299
+ publicBoxKey: string;
300
+ anchors?: {
301
+ type: "peers-services" | "https";
302
+ url: string;
303
+ }[] | undefined;
222
304
  displayName?: string | undefined;
223
305
  }>;
224
306
  expiresAt: z.ZodString;
@@ -236,15 +318,15 @@ export declare const peersInviteSchema: z.ZodObject<{
236
318
  name: string;
237
319
  role: GroupMemberRole;
238
320
  publicKey: string;
239
- groupId: string;
240
321
  publicBoxKey: string;
322
+ groupId: string;
241
323
  mode: "open" | "approve";
242
324
  }, {
243
325
  name: string;
244
326
  role: GroupMemberRole;
245
327
  publicKey: string;
246
- groupId: string;
247
328
  publicBoxKey: string;
329
+ groupId: string;
248
330
  mode: "open" | "approve";
249
331
  }>>;
250
332
  signature: z.ZodString;
@@ -256,8 +338,12 @@ export declare const peersInviteSchema: z.ZodObject<{
256
338
  v: 1;
257
339
  inviter: {
258
340
  publicKey: string;
259
- publicBoxKey: string;
260
341
  userId: string;
342
+ publicBoxKey: string;
343
+ anchors?: {
344
+ type: "peers-services" | "https";
345
+ url: string;
346
+ }[] | undefined;
261
347
  displayName?: string | undefined;
262
348
  };
263
349
  epoch: number;
@@ -266,8 +352,8 @@ export declare const peersInviteSchema: z.ZodObject<{
266
352
  name: string;
267
353
  role: GroupMemberRole;
268
354
  publicKey: string;
269
- groupId: string;
270
355
  publicBoxKey: string;
356
+ groupId: string;
271
357
  mode: "open" | "approve";
272
358
  } | undefined;
273
359
  }, {
@@ -278,8 +364,12 @@ export declare const peersInviteSchema: z.ZodObject<{
278
364
  v: 1;
279
365
  inviter: {
280
366
  publicKey: string;
281
- publicBoxKey: string;
282
367
  userId: string;
368
+ publicBoxKey: string;
369
+ anchors?: {
370
+ type: "peers-services" | "https";
371
+ url: string;
372
+ }[] | undefined;
283
373
  displayName?: string | undefined;
284
374
  };
285
375
  epoch: number;
@@ -288,8 +378,8 @@ export declare const peersInviteSchema: z.ZodObject<{
288
378
  name: string;
289
379
  role: GroupMemberRole;
290
380
  publicKey: string;
291
- groupId: string;
292
381
  publicBoxKey: string;
382
+ groupId: string;
293
383
  mode: "open" | "approve";
294
384
  } | undefined;
295
385
  }>;
@@ -12,6 +12,7 @@ exports.inviteDeepLink = inviteDeepLink;
12
12
  exports.clampInviteExpiry = clampInviteExpiry;
13
13
  const zod_1 = require("zod");
14
14
  const group_member_roles_1 = require("../data/group-member-roles");
15
+ const anchor_document_1 = require("../identity/anchor-document");
15
16
  const keys_1 = require("../keys");
16
17
  const zod_types_1 = require("../types/zod-types");
17
18
  const base64UrlSchema = zod_1.z.string().regex(/^[A-Za-z0-9_-]+$/);
@@ -38,6 +39,11 @@ exports.inviteIdentitySchema = zod_1.z
38
39
  publicKey: base64UrlSchema.length(43),
39
40
  publicBoxKey: base64UrlSchema.length(43),
40
41
  displayName: zod_1.z.string().max(100).optional(),
42
+ /**
43
+ * Anchor list to store on first contact. A peer that already has a list
44
+ * keeps it; this is only the bootstrap that arrives with the invitation.
45
+ */
46
+ anchors: zod_1.z.array(anchor_document_1.anchorRefSchema).max(8).optional(),
41
47
  })
42
48
  .strict();
43
49
  /** How joiners are admitted by a group token. */
package/dist/keys.d.ts CHANGED
@@ -55,6 +55,13 @@ export interface IDataBox {
55
55
  export declare function newKeys(): IPublicPrivateKeys;
56
56
  /** Re-derives public and box keys from an Ed25519 `secretKey` (same encoding as {@link newKeys}). */
57
57
  export declare function hydrateKeys(secretKey: string): IPublicPrivateKeys;
58
+ /**
59
+ * Derives the X25519 box public key for an Ed25519 verification key, without the secret side.
60
+ * Produces the same `publicBoxKey` that {@link newKeys} / {@link hydrateKeys} compute from the
61
+ * secret, so a peer can check that an advertised box key really belongs to a signing key.
62
+ * @returns base64url box key, or `null` when `publicKey` is not a decodable 32-byte Ed25519 point
63
+ */
64
+ export declare function derivePublicBoxKey(publicKey: string): string | null;
58
65
  /** Signs a UTF-8 message with Ed25519; returns base64url signed message (NaCl `sign` combined format). */
59
66
  export declare function signMessageWithSecretKey(msg: string, secretKey: string): string;
60
67
  /**
package/dist/keys.js CHANGED
@@ -11,6 +11,7 @@ exports.decodeBase64 = decodeBase64;
11
11
  exports.newToken = newToken;
12
12
  exports.newKeys = newKeys;
13
13
  exports.hydrateKeys = hydrateKeys;
14
+ exports.derivePublicBoxKey = derivePublicBoxKey;
14
15
  exports.signMessageWithSecretKey = signMessageWithSecretKey;
15
16
  exports.openMessageWithPublicKey = openMessageWithPublicKey;
16
17
  exports.signObjectWithSecretKey = signObjectWithSecretKey;
@@ -157,6 +158,25 @@ function hydrateKeys(secretKey) {
157
158
  publicBoxKey: encodeBase64(box.publicKey),
158
159
  };
159
160
  }
161
+ /**
162
+ * Derives the X25519 box public key for an Ed25519 verification key, without the secret side.
163
+ * Produces the same `publicBoxKey` that {@link newKeys} / {@link hydrateKeys} compute from the
164
+ * secret, so a peer can check that an advertised box key really belongs to a signing key.
165
+ * @returns base64url box key, or `null` when `publicKey` is not a decodable 32-byte Ed25519 point
166
+ */
167
+ function derivePublicBoxKey(publicKey) {
168
+ let bytes;
169
+ try {
170
+ bytes = decodeBase64(publicKey);
171
+ }
172
+ catch {
173
+ return null;
174
+ }
175
+ if (bytes.length !== 32)
176
+ return null;
177
+ const converted = ed2curve.convertPublicKey(bytes);
178
+ return converted ? encodeBase64(converted) : null;
179
+ }
160
180
  /** Signs a UTF-8 message with Ed25519; returns base64url signed message (NaCl `sign` combined format). */
161
181
  function signMessageWithSecretKey(msg, secretKey) {
162
182
  const _secretKey = decodeBase64(secretKey);
@@ -128,6 +128,17 @@ describe("package-author-signing", () => {
128
128
  const result = (0, package_author_signing_1.verifyPackageAuthorSignature)(tampered, keys.publicKey);
129
129
  expect(result.valid).toBe(false);
130
130
  });
131
+ it("fails when a signed createdBy is changed", () => {
132
+ const signed = makePV({ createdBy: "00author0000000000000000a" });
133
+ const signedSignature = (0, package_author_signing_1.signPackageAuthor)(signed, keys.secretKey);
134
+ const tampered = {
135
+ ...signed,
136
+ createdBy: "00attacker000000000000000a",
137
+ packageAuthorSignature: signedSignature,
138
+ };
139
+ const result = (0, package_author_signing_1.verifyPackageAuthorSignature)(tampered, keys.publicKey);
140
+ expect(result.valid).toBe(false);
141
+ });
131
142
  });
132
143
  describe("verification fails with wrong key", () => {
133
144
  it("rejects when verified against a different key", () => {
@@ -33,7 +33,11 @@ export interface IBuildPublishArtifactsOpts {
33
33
  packageVersionId?: string;
34
34
  /** App navigation items to include in the tarball metadata. */
35
35
  appNavs?: IAppNav[];
36
- /** Peer ID of the publisher. Recorded as `createdBy` on the PV. */
36
+ /**
37
+ * Peer ID recorded as `createdBy` and covered by the author signature.
38
+ * Defaults to `packageId`. The remote installer persists this field, and
39
+ * clients reject the version when the stored value is not the one that was signed.
40
+ */
37
41
  createdBy?: string;
38
42
  }
39
43
  /** Artifacts produced by {@link buildPublishArtifacts}. */
@@ -42,7 +42,7 @@ async function buildPublishArtifacts(opts) {
42
42
  routesBundleFileHash,
43
43
  uiBundleFileHash,
44
44
  appNavs: opts.appNavs,
45
- createdBy: opts.createdBy,
45
+ createdBy: opts.createdBy || packageId,
46
46
  createdAt: now,
47
47
  };
48
48
  const payload = (0, package_author_signing_1.buildAuthorSignedPayload)(pvFields, publicKey);
@@ -93,6 +93,17 @@ describe("package-publisher", () => {
93
93
  const sigResult = (0, package_author_signing_1.verifyPackageAuthorSignature)(result.payload, wrongKeys.publicKey);
94
94
  expect(sigResult.valid).toBe(false);
95
95
  });
96
+ it("signs the package id as createdBy when the caller omits it", async () => {
97
+ const keys = (0, keys_1.newKeys)();
98
+ const opts = makeOpts({
99
+ publicKey: keys.publicKey,
100
+ sign: (payload) => (0, package_author_signing_1.signPackageAuthor)(payload, keys.secretKey),
101
+ });
102
+ const result = await (0, package_publisher_1.buildPublishArtifacts)(opts);
103
+ const unpacked = await (0, package_tarball_1.unpackPeersTarball)(result.tarball);
104
+ expect(unpacked.payload.createdBy).toBe(opts.packageId);
105
+ expect((0, package_author_signing_1.verifyPackageAuthorSignature)({ ...unpacked.payload, createdBy: opts.packageId }, keys.publicKey).valid).toBe(true);
106
+ });
96
107
  it("defaults versionTag to stable", async () => {
97
108
  const result = await (0, package_publisher_1.buildPublishArtifacts)(makeOpts());
98
109
  expect(result.pointer.versionTag).toBe("stable");
@@ -172,6 +172,9 @@ async function installRemotePackageVersion(dataContext, result) {
172
172
  appNavs: unpacked.payload.appNavs,
173
173
  packageAuthorSignature: unpacked.payload.packageAuthorSignature,
174
174
  signature: "",
175
+ // New publications include createdBy in the signed payload. Legacy
176
+ // tarballs omitted it, so their required database value defaults to the
177
+ // package id but does not pass later record-level author verification.
175
178
  createdBy: unpacked.payload.createdBy ?? packageId,
176
179
  createdAt: unpacked.payload.createdAt ?? new Date().toISOString(),
177
180
  };
@@ -112,6 +112,22 @@ export declare const identityToolIds: {
112
112
  readonly signIn: "00muc9mlzx456ecfmzp1xn0db";
113
113
  readonly signOut: "00muc9mlzxwdm2yj5p5wkjvz7";
114
114
  readonly registerWithPeersServices: "00muc9mlzx07q98l3f9x49o10";
115
+ readonly getKeyInfo: "00mus1g5sfmu8vhazy7grwtjz";
116
+ readonly rotateKey: "00mus1g5sfrifcnula9crst8s";
117
+ readonly getRecoveryEmail: "00mustp0jrvoxpgion9dfgizc";
118
+ readonly bindEmailStart: "00mustox69twakb9sss2cto4g";
119
+ readonly bindEmailVerify: "00mustox6brb044r1ms5gylw5";
120
+ readonly unbindEmail: "00mustox6b3afftshfac8vc04";
121
+ readonly startAnchorSession: "00mustox6bjv7mo0jtywuzehm";
122
+ readonly confirmAnchorSession: "00mustox6bvmiuzzq4t7kg18q";
123
+ readonly contestRecovery: "00musvcrtk1bkhivlo3d9e51z";
124
+ readonly acceptRecovery: "00musvcrtlpgic1r6w2boo4k4";
125
+ readonly recoverKey: "00musvcrtl6ruaysm9f8he39x";
126
+ readonly setAnchors: "00musw0he8emme6dxl16ks885";
127
+ readonly passkeyRegisterStart: "00musw0he9s4v0lz8bn4qpk7w";
128
+ readonly passkeyRegisterFinish: "00musw0he9w4smpnzer0gqy22";
129
+ readonly passkeyAuthStart: "00musw0he9ad5zggw924sqd05";
130
+ readonly passkeyAuthFinish: "00musw0he9yq03hf7qyk3b8dn";
115
131
  };
116
132
  /**
117
133
  * Contract id for the local-only System Local Folders surface (`system.fs`).
@@ -119,6 +119,22 @@ exports.identityToolIds = {
119
119
  signIn: "00muc9mlzx456ecfmzp1xn0db",
120
120
  signOut: "00muc9mlzxwdm2yj5p5wkjvz7",
121
121
  registerWithPeersServices: "00muc9mlzx07q98l3f9x49o10",
122
+ getKeyInfo: "00mus1g5sfmu8vhazy7grwtjz",
123
+ rotateKey: "00mus1g5sfrifcnula9crst8s",
124
+ getRecoveryEmail: "00mustp0jrvoxpgion9dfgizc",
125
+ bindEmailStart: "00mustox69twakb9sss2cto4g",
126
+ bindEmailVerify: "00mustox6brb044r1ms5gylw5",
127
+ unbindEmail: "00mustox6b3afftshfac8vc04",
128
+ startAnchorSession: "00mustox6bjv7mo0jtywuzehm",
129
+ confirmAnchorSession: "00mustox6bvmiuzzq4t7kg18q",
130
+ contestRecovery: "00musvcrtk1bkhivlo3d9e51z",
131
+ acceptRecovery: "00musvcrtlpgic1r6w2boo4k4",
132
+ recoverKey: "00musvcrtl6ruaysm9f8he39x",
133
+ setAnchors: "00musw0he8emme6dxl16ks885",
134
+ passkeyRegisterStart: "00musw0he9s4v0lz8bn4qpk7w",
135
+ passkeyRegisterFinish: "00musw0he9w4smpnzer0gqy22",
136
+ passkeyAuthStart: "00musw0he9ad5zggw924sqd05",
137
+ passkeyAuthFinish: "00musw0he9yq03hf7qyk3b8dn",
122
138
  };
123
139
  /**
124
140
  * Contract id for the local-only System Local Folders surface (`system.fs`).
@@ -44,8 +44,8 @@ export declare const workflowSchema: z.ZodObject<{
44
44
  }, "strip", z.ZodTypeAny, {
45
45
  name: string;
46
46
  description: string;
47
- createdAt: Date;
48
47
  updatedAt: Date;
48
+ createdAt: Date;
49
49
  workflowId: string;
50
50
  defaultAssistantId: string;
51
51
  instructions: {
@@ -62,9 +62,9 @@ export declare const workflowSchema: z.ZodObject<{
62
62
  workflowId: string;
63
63
  defaultAssistantId: string;
64
64
  createdBy: string;
65
+ updatedAt?: Date | undefined;
65
66
  packageId?: string | undefined;
66
67
  createdAt?: Date | undefined;
67
- updatedAt?: Date | undefined;
68
68
  instructions?: {
69
69
  markdown?: string | undefined;
70
70
  onError?: string | undefined;
package/dist/utils.d.ts CHANGED
@@ -128,6 +128,16 @@ export declare function retryOnErrorOrTimeout<T>({ fn, connection, opts, }: {
128
128
  retriesOnTimeout?: number;
129
129
  };
130
130
  }): Promise<T>;
131
+ /**
132
+ * Read a fetch response body as text, giving up once it exceeds `maxBytes`.
133
+ *
134
+ * The body is streamed and the connection is cancelled as soon as the cap is passed, so a
135
+ * hostile server cannot make the caller download an arbitrarily large response before the
136
+ * size is checked. Falls back to `text()` when the body is not streamable (test doubles).
137
+ *
138
+ * @returns the decoded text, or `undefined` when the body is larger than `maxBytes`
139
+ */
140
+ export declare function readResponseTextCapped(response: Response, maxBytes: number): Promise<string | undefined>;
131
141
  /**
132
142
  * Return a table's physical storage name.
133
143
  *
package/dist/utils.js CHANGED
@@ -21,6 +21,7 @@ exports.memoizePromise = memoizePromise;
21
21
  exports.debounceByArgs = debounceByArgs;
22
22
  exports.getTimestamp = getTimestamp;
23
23
  exports.retryOnErrorOrTimeout = retryOnErrorOrTimeout;
24
+ exports.readResponseTextCapped = readResponseTextCapped;
24
25
  exports.getFullTableName = getFullTableName;
25
26
  const _ = require("lodash");
26
27
  const nacl = require("tweetnacl");
@@ -389,6 +390,48 @@ async function retryOnErrorOrTimeout({ fn, connection, opts = {}, }) {
389
390
  throw new Error(`Timeout after ${timeoutCount} retries waiting for ${timeout}ms`);
390
391
  }
391
392
  }
393
+ /**
394
+ * Read a fetch response body as text, giving up once it exceeds `maxBytes`.
395
+ *
396
+ * The body is streamed and the connection is cancelled as soon as the cap is passed, so a
397
+ * hostile server cannot make the caller download an arbitrarily large response before the
398
+ * size is checked. Falls back to `text()` when the body is not streamable (test doubles).
399
+ *
400
+ * @returns the decoded text, or `undefined` when the body is larger than `maxBytes`
401
+ */
402
+ async function readResponseTextCapped(response, maxBytes) {
403
+ const body = response.body;
404
+ if (!body || typeof body.getReader !== "function") {
405
+ const text = await response.text();
406
+ return new TextEncoder().encode(text).byteLength > maxBytes ? undefined : text;
407
+ }
408
+ const reader = body.getReader();
409
+ const chunks = [];
410
+ let received = 0;
411
+ try {
412
+ while (true) {
413
+ const { done, value } = await reader.read();
414
+ if (done)
415
+ break;
416
+ received += value.byteLength;
417
+ if (received > maxBytes) {
418
+ await reader.cancel().catch(() => undefined);
419
+ return undefined;
420
+ }
421
+ chunks.push(value);
422
+ }
423
+ }
424
+ finally {
425
+ reader.releaseLock();
426
+ }
427
+ const joined = new Uint8Array(received);
428
+ let offset = 0;
429
+ for (const chunk of chunks) {
430
+ joined.set(chunk, offset);
431
+ offset += chunk.byteLength;
432
+ }
433
+ return new TextDecoder().decode(joined);
434
+ }
392
435
  /**
393
436
  * Return a table's physical storage name.
394
437
  *
@@ -2,6 +2,29 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const lodash_1 = require("lodash");
4
4
  const utils_1 = require("./utils");
5
+ describe("readResponseTextCapped", () => {
6
+ it("returns a body within the cap and refuses one over it", async () => {
7
+ await expect((0, utils_1.readResponseTextCapped)(new Response("hello"), 5)).resolves.toBe("hello");
8
+ await expect((0, utils_1.readResponseTextCapped)(new Response("hello!"), 5)).resolves.toBeUndefined();
9
+ });
10
+ it("stops reading a streamed body as soon as the cap is passed", async () => {
11
+ let pulled = 0;
12
+ let cancelled = false;
13
+ const stream = new ReadableStream({
14
+ pull(controller) {
15
+ pulled++;
16
+ controller.enqueue(new Uint8Array(1024));
17
+ },
18
+ cancel() {
19
+ cancelled = true;
20
+ },
21
+ });
22
+ await expect((0, utils_1.readResponseTextCapped)(new Response(stream), 4096)).resolves.toBeUndefined();
23
+ expect(cancelled).toBe(true);
24
+ // Five chunks pass the cap; an unbounded reader would never stop.
25
+ expect(pulled).toBeLessThanOrEqual(8);
26
+ });
27
+ });
5
28
  describe("getTimestamp", () => {
6
29
  afterEach(() => {
7
30
  jest.restoreAllMocks();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.25.13",
3
+ "version": "0.25.15",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"