@peers-app/peers-sdk 0.23.14 → 0.25.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.
- package/dist/context/user-context.d.ts +8 -0
- package/dist/context/user-context.js +9 -1
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +4 -1
- package/dist/contracts/system/index.d.ts +2 -0
- package/dist/contracts/system/index.js +5 -1
- package/dist/contracts/system/invites-contract.d.ts +121 -0
- package/dist/contracts/system/invites-contract.js +150 -0
- package/dist/data/create-group.d.ts +24 -0
- package/dist/data/create-group.js +61 -0
- package/dist/data/group-secret.d.ts +10 -0
- package/dist/data/group-secret.js +26 -0
- package/dist/data/group-share.d.ts +0 -20
- package/dist/data/group-share.js +0 -35
- package/dist/data/index.d.ts +2 -0
- package/dist/data/index.js +2 -0
- package/dist/data/invites.d.ts +350 -0
- package/dist/data/invites.js +99 -0
- package/dist/data/orm/data-query.d.ts +1 -0
- package/dist/data/orm/sql.data-source.d.ts +3 -2
- package/dist/data/orm/sql.data-source.js +5 -3
- package/dist/data/orm/subscribable.data-source.d.ts +3 -2
- package/dist/data/orm/subscribable.data-source.js +7 -11
- package/dist/data/users.d.ts +11 -0
- package/dist/data/users.js +17 -0
- package/dist/device/device-election.d.ts +23 -3
- package/dist/device/device-election.js +15 -0
- package/dist/device/device-election.test.js +73 -0
- package/dist/device-pairing/constants.d.ts +27 -9
- package/dist/device-pairing/constants.js +28 -10
- package/dist/device-pairing/cpace.d.ts +46 -0
- package/dist/device-pairing/cpace.js +173 -0
- package/dist/device-pairing/cpace.test.js +116 -0
- package/dist/device-pairing/crypto.d.ts +40 -15
- package/dist/device-pairing/crypto.js +74 -33
- package/dist/device-pairing/device-pairing.test.js +156 -119
- package/dist/device-pairing/index.d.ts +3 -1
- package/dist/device-pairing/index.js +3 -1
- package/dist/device-pairing/pairing-code.d.ts +27 -0
- package/dist/device-pairing/pairing-code.js +84 -0
- package/dist/device-pairing/pgp-word-list.d.ts +14 -0
- package/dist/device-pairing/pgp-word-list.js +531 -0
- package/dist/device-pairing/signaling.d.ts +17 -6
- package/dist/device-pairing/signaling.js +31 -8
- package/dist/device-pairing/transcript.d.ts +3 -3
- package/dist/device-pairing/transcript.js +6 -13
- package/dist/device-pairing/types.d.ts +870 -224
- package/dist/device-pairing/types.js +186 -108
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/invites/data-box-schema.d.ts +15 -0
- package/dist/invites/data-box-schema.js +15 -0
- package/dist/invites/group-approval.d.ts +43 -0
- package/dist/invites/group-approval.js +155 -0
- package/dist/invites/group-approval.test.js +221 -0
- package/dist/invites/index.d.ts +5 -0
- package/dist/{group-invite → invites}/index.js +5 -3
- package/dist/invites/invite-epoch.d.ts +18 -0
- package/dist/invites/invite-epoch.js +35 -0
- package/dist/invites/invite-messages.d.ts +1354 -0
- package/dist/invites/invite-messages.js +145 -0
- package/dist/invites/invite-token.d.ts +334 -0
- package/dist/invites/invite-token.js +178 -0
- package/dist/invites/invite-token.test.d.ts +1 -0
- package/dist/invites/invite-token.test.js +123 -0
- package/dist/keys.d.ts +17 -1
- package/dist/keys.js +58 -5
- package/dist/keys.test.js +113 -0
- package/dist/rpc-types.d.ts +5 -4
- package/dist/rpc-types.js +2 -1
- package/dist/serial-json.d.ts +4 -1
- package/dist/serial-json.js +4 -1
- package/dist/system-ids.d.ts +22 -0
- package/dist/system-ids.js +23 -1
- package/package.json +1 -1
- package/dist/device-pairing/invitation.d.ts +0 -13
- package/dist/device-pairing/invitation.js +0 -85
- package/dist/group-invite/group-invite.d.ts +0 -53
- package/dist/group-invite/group-invite.js +0 -231
- package/dist/group-invite/group-invite.pvars.d.ts +0 -94
- package/dist/group-invite/group-invite.pvars.js +0 -139
- package/dist/group-invite/group-invite.types.d.ts +0 -124
- package/dist/group-invite/group-invite.types.js +0 -8
- package/dist/group-invite/index.d.ts +0 -3
- package/dist/user-connect/connection-code.d.ts +0 -67
- package/dist/user-connect/connection-code.js +0 -176
- package/dist/user-connect/connection-code.test.js +0 -213
- package/dist/user-connect/index.d.ts +0 -3
- package/dist/user-connect/index.js +0 -19
- package/dist/user-connect/user-connect.pvars.d.ts +0 -20
- package/dist/user-connect/user-connect.pvars.js +0 -63
- package/dist/user-connect/user-connect.pvars.test.js +0 -46
- package/dist/user-connect/user-connect.types.d.ts +0 -94
- package/dist/user-connect/user-connect.types.js +0 -68
- /package/dist/{user-connect/connection-code.test.d.ts → device-pairing/cpace.test.d.ts} +0 -0
- /package/dist/{user-connect/user-connect.pvars.test.d.ts → invites/group-approval.test.d.ts} +0 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { DataContext } from "../context/data-context";
|
|
3
|
+
import { GroupMemberRole } from "./group-member-roles";
|
|
4
|
+
/** Lifecycle of an invite row. */
|
|
5
|
+
export declare const inviteStatusSchema: z.ZodEnum<["pending", "accepted", "declined", "expired", "revoked"]>;
|
|
6
|
+
/** Lifecycle of an invite row. */
|
|
7
|
+
export type InviteStatus = z.infer<typeof inviteStatusSchema>;
|
|
8
|
+
/**
|
|
9
|
+
* How the invite was issued:
|
|
10
|
+
* - `direct`: a known contact was invited to a group (3a); the payload is the approval.
|
|
11
|
+
* - `approve`: group link whose join requests the inviter approves (3b).
|
|
12
|
+
* - `open`: group link any Admin+ member auto-approves (3c).
|
|
13
|
+
* - `profile`: the reusable contact QR on the Account screen.
|
|
14
|
+
* - `contact`: a one-off contact token.
|
|
15
|
+
*/
|
|
16
|
+
export declare const inviteModeSchema: z.ZodEnum<["direct", "approve", "open", "profile", "contact"]>;
|
|
17
|
+
/** How the invite was issued. */
|
|
18
|
+
export type InviteMode = z.infer<typeof inviteModeSchema>;
|
|
19
|
+
/** Outbound delivery progress; `queued` rows are retried when connectivity changes. */
|
|
20
|
+
export declare const inviteDeliveryStateSchema: z.ZodEnum<["queued", "sent"]>;
|
|
21
|
+
/**
|
|
22
|
+
* One pending or historical invite, synced across the user's devices.
|
|
23
|
+
*
|
|
24
|
+
* Boxed payloads are encrypted to the user's box key, which every one of their devices
|
|
25
|
+
* holds, so storing them in this personal-context table is safe. Rows move to a terminal
|
|
26
|
+
* status rather than being deleted; terminal rows older than 30 days are purged.
|
|
27
|
+
*/
|
|
28
|
+
export declare const inviteSchema: z.ZodObject<{
|
|
29
|
+
inviteId: z.ZodEffects<z.ZodString, string, string>;
|
|
30
|
+
direction: z.ZodEnum<["in", "out"]>;
|
|
31
|
+
type: z.ZodEnum<["contact", "group"]>;
|
|
32
|
+
status: z.ZodDefault<z.ZodEnum<["pending", "accepted", "declined", "expired", "revoked"]>>;
|
|
33
|
+
mode: z.ZodEnum<["direct", "approve", "open", "profile", "contact"]>;
|
|
34
|
+
/** The other party, once known (unknown for outstanding link invites). */
|
|
35
|
+
counterpart: z.ZodOptional<z.ZodObject<{
|
|
36
|
+
userId: z.ZodEffects<z.ZodString, string, string>;
|
|
37
|
+
publicKey: z.ZodString;
|
|
38
|
+
publicBoxKey: z.ZodString;
|
|
39
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
40
|
+
}, "strict", z.ZodTypeAny, {
|
|
41
|
+
publicKey: string;
|
|
42
|
+
publicBoxKey: string;
|
|
43
|
+
userId: string;
|
|
44
|
+
displayName?: string | undefined;
|
|
45
|
+
}, {
|
|
46
|
+
publicKey: string;
|
|
47
|
+
publicBoxKey: string;
|
|
48
|
+
userId: string;
|
|
49
|
+
displayName?: string | undefined;
|
|
50
|
+
}>>;
|
|
51
|
+
/** Device the counterpart used, when they told us; lets the network layer connect early. */
|
|
52
|
+
counterpartDeviceId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
53
|
+
/**
|
|
54
|
+
* Correlates replies with this row: the token nonce for link invites, a fresh nonce for
|
|
55
|
+
* direct invites.
|
|
56
|
+
*/
|
|
57
|
+
nonce: z.ZodOptional<z.ZodString>;
|
|
58
|
+
groupId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
59
|
+
groupName: z.ZodOptional<z.ZodString>;
|
|
60
|
+
role: z.ZodOptional<z.ZodNativeEnum<typeof GroupMemberRole>>;
|
|
61
|
+
/** The signed token: what I issued (out) or what I received/used (in). */
|
|
62
|
+
token: z.ZodOptional<z.ZodObject<{
|
|
63
|
+
v: z.ZodLiteral<1>;
|
|
64
|
+
type: z.ZodEnum<["contact", "group"]>;
|
|
65
|
+
inviter: z.ZodObject<{
|
|
66
|
+
userId: z.ZodEffects<z.ZodString, string, string>;
|
|
67
|
+
publicKey: z.ZodString;
|
|
68
|
+
publicBoxKey: z.ZodString;
|
|
69
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
70
|
+
}, "strict", z.ZodTypeAny, {
|
|
71
|
+
publicKey: string;
|
|
72
|
+
publicBoxKey: string;
|
|
73
|
+
userId: string;
|
|
74
|
+
displayName?: string | undefined;
|
|
75
|
+
}, {
|
|
76
|
+
publicKey: string;
|
|
77
|
+
publicBoxKey: string;
|
|
78
|
+
userId: string;
|
|
79
|
+
displayName?: string | undefined;
|
|
80
|
+
}>;
|
|
81
|
+
expiresAt: z.ZodString;
|
|
82
|
+
nonce: z.ZodString;
|
|
83
|
+
epoch: z.ZodNumber;
|
|
84
|
+
uses: z.ZodUnion<[z.ZodLiteral<1>, z.ZodLiteral<"unlimited">]>;
|
|
85
|
+
group: z.ZodOptional<z.ZodObject<{
|
|
86
|
+
groupId: z.ZodEffects<z.ZodString, string, string>;
|
|
87
|
+
name: z.ZodString;
|
|
88
|
+
publicKey: z.ZodString;
|
|
89
|
+
publicBoxKey: z.ZodString;
|
|
90
|
+
role: z.ZodNativeEnum<typeof GroupMemberRole>;
|
|
91
|
+
mode: z.ZodEnum<["approve", "open"]>;
|
|
92
|
+
}, "strict", z.ZodTypeAny, {
|
|
93
|
+
name: string;
|
|
94
|
+
role: GroupMemberRole;
|
|
95
|
+
publicKey: string;
|
|
96
|
+
groupId: string;
|
|
97
|
+
publicBoxKey: string;
|
|
98
|
+
mode: "open" | "approve";
|
|
99
|
+
}, {
|
|
100
|
+
name: string;
|
|
101
|
+
role: GroupMemberRole;
|
|
102
|
+
publicKey: string;
|
|
103
|
+
groupId: string;
|
|
104
|
+
publicBoxKey: string;
|
|
105
|
+
mode: "open" | "approve";
|
|
106
|
+
}>>;
|
|
107
|
+
signature: z.ZodString;
|
|
108
|
+
}, "strict", z.ZodTypeAny, {
|
|
109
|
+
type: "group" | "contact";
|
|
110
|
+
nonce: string;
|
|
111
|
+
signature: string;
|
|
112
|
+
expiresAt: string;
|
|
113
|
+
v: 1;
|
|
114
|
+
inviter: {
|
|
115
|
+
publicKey: string;
|
|
116
|
+
publicBoxKey: string;
|
|
117
|
+
userId: string;
|
|
118
|
+
displayName?: string | undefined;
|
|
119
|
+
};
|
|
120
|
+
epoch: number;
|
|
121
|
+
uses: 1 | "unlimited";
|
|
122
|
+
group?: {
|
|
123
|
+
name: string;
|
|
124
|
+
role: GroupMemberRole;
|
|
125
|
+
publicKey: string;
|
|
126
|
+
groupId: string;
|
|
127
|
+
publicBoxKey: string;
|
|
128
|
+
mode: "open" | "approve";
|
|
129
|
+
} | undefined;
|
|
130
|
+
}, {
|
|
131
|
+
type: "group" | "contact";
|
|
132
|
+
nonce: string;
|
|
133
|
+
signature: string;
|
|
134
|
+
expiresAt: string;
|
|
135
|
+
v: 1;
|
|
136
|
+
inviter: {
|
|
137
|
+
publicKey: string;
|
|
138
|
+
publicBoxKey: string;
|
|
139
|
+
userId: string;
|
|
140
|
+
displayName?: string | undefined;
|
|
141
|
+
};
|
|
142
|
+
epoch: number;
|
|
143
|
+
uses: 1 | "unlimited";
|
|
144
|
+
group?: {
|
|
145
|
+
name: string;
|
|
146
|
+
role: GroupMemberRole;
|
|
147
|
+
publicKey: string;
|
|
148
|
+
groupId: string;
|
|
149
|
+
publicBoxKey: string;
|
|
150
|
+
mode: "open" | "approve";
|
|
151
|
+
} | undefined;
|
|
152
|
+
}>>;
|
|
153
|
+
/**
|
|
154
|
+
* For `in` direct invites: the GroupApproval boxed to my own user box key. For rows whose
|
|
155
|
+
* `deliveryState` is `queued`: the outbound box (already sealed to `recipientUserId`).
|
|
156
|
+
*/
|
|
157
|
+
payload: z.ZodOptional<z.ZodObject<{
|
|
158
|
+
contents: z.ZodString;
|
|
159
|
+
nonce: z.ZodString;
|
|
160
|
+
fromPublicKey: z.ZodString;
|
|
161
|
+
}, "strict", z.ZodTypeAny, {
|
|
162
|
+
nonce: string;
|
|
163
|
+
contents: string;
|
|
164
|
+
fromPublicKey: string;
|
|
165
|
+
}, {
|
|
166
|
+
nonce: string;
|
|
167
|
+
contents: string;
|
|
168
|
+
fromPublicKey: string;
|
|
169
|
+
}>>;
|
|
170
|
+
/** Inviter's epoch at issue time. */
|
|
171
|
+
epoch: z.ZodNumber;
|
|
172
|
+
/** For `in` rows spawned by one of my reusable `out` tokens. */
|
|
173
|
+
parentInviteId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
174
|
+
deliveryState: z.ZodOptional<z.ZodEnum<["queued", "sent"]>>;
|
|
175
|
+
/** Outbound recipient for `direct` invites and replies that are still queued. */
|
|
176
|
+
recipientUserId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
177
|
+
createdAt: z.ZodDefault<z.ZodDate>;
|
|
178
|
+
expiresAt: z.ZodDate;
|
|
179
|
+
resolvedAt: z.ZodOptional<z.ZodDate>;
|
|
180
|
+
note: z.ZodOptional<z.ZodString>;
|
|
181
|
+
}, "strip", z.ZodTypeAny, {
|
|
182
|
+
type: "group" | "contact";
|
|
183
|
+
direction: "in" | "out";
|
|
184
|
+
status: "pending" | "expired" | "accepted" | "declined" | "revoked";
|
|
185
|
+
expiresAt: Date;
|
|
186
|
+
createdAt: Date;
|
|
187
|
+
mode: "direct" | "open" | "approve" | "contact" | "profile";
|
|
188
|
+
epoch: number;
|
|
189
|
+
inviteId: string;
|
|
190
|
+
role?: GroupMemberRole | undefined;
|
|
191
|
+
nonce?: string | undefined;
|
|
192
|
+
groupId?: string | undefined;
|
|
193
|
+
counterpart?: {
|
|
194
|
+
publicKey: string;
|
|
195
|
+
publicBoxKey: string;
|
|
196
|
+
userId: string;
|
|
197
|
+
displayName?: string | undefined;
|
|
198
|
+
} | undefined;
|
|
199
|
+
counterpartDeviceId?: string | undefined;
|
|
200
|
+
groupName?: string | undefined;
|
|
201
|
+
token?: {
|
|
202
|
+
type: "group" | "contact";
|
|
203
|
+
nonce: string;
|
|
204
|
+
signature: string;
|
|
205
|
+
expiresAt: string;
|
|
206
|
+
v: 1;
|
|
207
|
+
inviter: {
|
|
208
|
+
publicKey: string;
|
|
209
|
+
publicBoxKey: string;
|
|
210
|
+
userId: string;
|
|
211
|
+
displayName?: string | undefined;
|
|
212
|
+
};
|
|
213
|
+
epoch: number;
|
|
214
|
+
uses: 1 | "unlimited";
|
|
215
|
+
group?: {
|
|
216
|
+
name: string;
|
|
217
|
+
role: GroupMemberRole;
|
|
218
|
+
publicKey: string;
|
|
219
|
+
groupId: string;
|
|
220
|
+
publicBoxKey: string;
|
|
221
|
+
mode: "open" | "approve";
|
|
222
|
+
} | undefined;
|
|
223
|
+
} | undefined;
|
|
224
|
+
payload?: {
|
|
225
|
+
nonce: string;
|
|
226
|
+
contents: string;
|
|
227
|
+
fromPublicKey: string;
|
|
228
|
+
} | undefined;
|
|
229
|
+
parentInviteId?: string | undefined;
|
|
230
|
+
deliveryState?: "queued" | "sent" | undefined;
|
|
231
|
+
recipientUserId?: string | undefined;
|
|
232
|
+
resolvedAt?: Date | undefined;
|
|
233
|
+
note?: string | undefined;
|
|
234
|
+
}, {
|
|
235
|
+
type: "group" | "contact";
|
|
236
|
+
direction: "in" | "out";
|
|
237
|
+
expiresAt: Date;
|
|
238
|
+
mode: "direct" | "open" | "approve" | "contact" | "profile";
|
|
239
|
+
epoch: number;
|
|
240
|
+
inviteId: string;
|
|
241
|
+
role?: GroupMemberRole | undefined;
|
|
242
|
+
nonce?: string | undefined;
|
|
243
|
+
status?: "pending" | "expired" | "accepted" | "declined" | "revoked" | undefined;
|
|
244
|
+
groupId?: string | undefined;
|
|
245
|
+
createdAt?: Date | undefined;
|
|
246
|
+
counterpart?: {
|
|
247
|
+
publicKey: string;
|
|
248
|
+
publicBoxKey: string;
|
|
249
|
+
userId: string;
|
|
250
|
+
displayName?: string | undefined;
|
|
251
|
+
} | undefined;
|
|
252
|
+
counterpartDeviceId?: string | undefined;
|
|
253
|
+
groupName?: string | undefined;
|
|
254
|
+
token?: {
|
|
255
|
+
type: "group" | "contact";
|
|
256
|
+
nonce: string;
|
|
257
|
+
signature: string;
|
|
258
|
+
expiresAt: string;
|
|
259
|
+
v: 1;
|
|
260
|
+
inviter: {
|
|
261
|
+
publicKey: string;
|
|
262
|
+
publicBoxKey: string;
|
|
263
|
+
userId: string;
|
|
264
|
+
displayName?: string | undefined;
|
|
265
|
+
};
|
|
266
|
+
epoch: number;
|
|
267
|
+
uses: 1 | "unlimited";
|
|
268
|
+
group?: {
|
|
269
|
+
name: string;
|
|
270
|
+
role: GroupMemberRole;
|
|
271
|
+
publicKey: string;
|
|
272
|
+
groupId: string;
|
|
273
|
+
publicBoxKey: string;
|
|
274
|
+
mode: "open" | "approve";
|
|
275
|
+
} | undefined;
|
|
276
|
+
} | undefined;
|
|
277
|
+
payload?: {
|
|
278
|
+
nonce: string;
|
|
279
|
+
contents: string;
|
|
280
|
+
fromPublicKey: string;
|
|
281
|
+
} | undefined;
|
|
282
|
+
parentInviteId?: string | undefined;
|
|
283
|
+
deliveryState?: "queued" | "sent" | undefined;
|
|
284
|
+
recipientUserId?: string | undefined;
|
|
285
|
+
resolvedAt?: Date | undefined;
|
|
286
|
+
note?: string | undefined;
|
|
287
|
+
}>;
|
|
288
|
+
/** One pending or historical invite. */
|
|
289
|
+
export type IInvite = z.infer<typeof inviteSchema>;
|
|
290
|
+
/**
|
|
291
|
+
* Accessor for the personal-context {@link Invites} table.
|
|
292
|
+
*
|
|
293
|
+
* @param dataContext - Optional data context; defaults to the current singleton.
|
|
294
|
+
*/
|
|
295
|
+
export declare function Invites(dataContext?: DataContext): import("..").Table<{
|
|
296
|
+
type: "group" | "contact";
|
|
297
|
+
direction: "in" | "out";
|
|
298
|
+
status: "pending" | "expired" | "accepted" | "declined" | "revoked";
|
|
299
|
+
expiresAt: Date;
|
|
300
|
+
createdAt: Date;
|
|
301
|
+
mode: "direct" | "open" | "approve" | "contact" | "profile";
|
|
302
|
+
epoch: number;
|
|
303
|
+
inviteId: string;
|
|
304
|
+
role?: GroupMemberRole | undefined;
|
|
305
|
+
nonce?: string | undefined;
|
|
306
|
+
groupId?: string | undefined;
|
|
307
|
+
counterpart?: {
|
|
308
|
+
publicKey: string;
|
|
309
|
+
publicBoxKey: string;
|
|
310
|
+
userId: string;
|
|
311
|
+
displayName?: string | undefined;
|
|
312
|
+
} | undefined;
|
|
313
|
+
counterpartDeviceId?: string | undefined;
|
|
314
|
+
groupName?: string | undefined;
|
|
315
|
+
token?: {
|
|
316
|
+
type: "group" | "contact";
|
|
317
|
+
nonce: string;
|
|
318
|
+
signature: string;
|
|
319
|
+
expiresAt: string;
|
|
320
|
+
v: 1;
|
|
321
|
+
inviter: {
|
|
322
|
+
publicKey: string;
|
|
323
|
+
publicBoxKey: string;
|
|
324
|
+
userId: string;
|
|
325
|
+
displayName?: string | undefined;
|
|
326
|
+
};
|
|
327
|
+
epoch: number;
|
|
328
|
+
uses: 1 | "unlimited";
|
|
329
|
+
group?: {
|
|
330
|
+
name: string;
|
|
331
|
+
role: GroupMemberRole;
|
|
332
|
+
publicKey: string;
|
|
333
|
+
groupId: string;
|
|
334
|
+
publicBoxKey: string;
|
|
335
|
+
mode: "open" | "approve";
|
|
336
|
+
} | undefined;
|
|
337
|
+
} | undefined;
|
|
338
|
+
payload?: {
|
|
339
|
+
nonce: string;
|
|
340
|
+
contents: string;
|
|
341
|
+
fromPublicKey: string;
|
|
342
|
+
} | undefined;
|
|
343
|
+
parentInviteId?: string | undefined;
|
|
344
|
+
deliveryState?: "queued" | "sent" | undefined;
|
|
345
|
+
recipientUserId?: string | undefined;
|
|
346
|
+
resolvedAt?: Date | undefined;
|
|
347
|
+
note?: string | undefined;
|
|
348
|
+
}>;
|
|
349
|
+
/** Age after which terminal rows are purged. */
|
|
350
|
+
export declare const INVITE_HISTORY_RETENTION_MS: number;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.INVITE_HISTORY_RETENTION_MS = exports.inviteSchema = exports.inviteDeliveryStateSchema = exports.inviteModeSchema = exports.inviteStatusSchema = void 0;
|
|
4
|
+
exports.Invites = Invites;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
7
|
+
const data_box_schema_1 = require("../invites/data-box-schema");
|
|
8
|
+
const invite_token_1 = require("../invites/invite-token");
|
|
9
|
+
const zod_types_1 = require("../types/zod-types");
|
|
10
|
+
const group_member_roles_1 = require("./group-member-roles");
|
|
11
|
+
const table_definitions_system_1 = require("./orm/table-definitions.system");
|
|
12
|
+
const types_1 = require("./orm/types");
|
|
13
|
+
/** Lifecycle of an invite row. */
|
|
14
|
+
exports.inviteStatusSchema = zod_1.z.enum(["pending", "accepted", "declined", "expired", "revoked"]);
|
|
15
|
+
/**
|
|
16
|
+
* How the invite was issued:
|
|
17
|
+
* - `direct`: a known contact was invited to a group (3a); the payload is the approval.
|
|
18
|
+
* - `approve`: group link whose join requests the inviter approves (3b).
|
|
19
|
+
* - `open`: group link any Admin+ member auto-approves (3c).
|
|
20
|
+
* - `profile`: the reusable contact QR on the Account screen.
|
|
21
|
+
* - `contact`: a one-off contact token.
|
|
22
|
+
*/
|
|
23
|
+
exports.inviteModeSchema = zod_1.z.enum(["direct", "approve", "open", "profile", "contact"]);
|
|
24
|
+
/** Outbound delivery progress; `queued` rows are retried when connectivity changes. */
|
|
25
|
+
exports.inviteDeliveryStateSchema = zod_1.z.enum(["queued", "sent"]);
|
|
26
|
+
/**
|
|
27
|
+
* One pending or historical invite, synced across the user's devices.
|
|
28
|
+
*
|
|
29
|
+
* Boxed payloads are encrypted to the user's box key, which every one of their devices
|
|
30
|
+
* holds, so storing them in this personal-context table is safe. Rows move to a terminal
|
|
31
|
+
* status rather than being deleted; terminal rows older than 30 days are purged.
|
|
32
|
+
*/
|
|
33
|
+
exports.inviteSchema = zod_1.z.object({
|
|
34
|
+
inviteId: zod_types_1.zodPeerId,
|
|
35
|
+
direction: zod_1.z.enum(["in", "out"]).describe("Whether I issued (out) or received (in) this item"),
|
|
36
|
+
type: zod_1.z.enum(["contact", "group"]),
|
|
37
|
+
status: exports.inviteStatusSchema.default("pending"),
|
|
38
|
+
mode: exports.inviteModeSchema,
|
|
39
|
+
/** The other party, once known (unknown for outstanding link invites). */
|
|
40
|
+
counterpart: invite_token_1.inviteIdentitySchema.optional(),
|
|
41
|
+
/** Device the counterpart used, when they told us; lets the network layer connect early. */
|
|
42
|
+
counterpartDeviceId: zod_types_1.zodPeerId.optional(),
|
|
43
|
+
/**
|
|
44
|
+
* Correlates replies with this row: the token nonce for link invites, a fresh nonce for
|
|
45
|
+
* direct invites.
|
|
46
|
+
*/
|
|
47
|
+
nonce: zod_1.z
|
|
48
|
+
.string()
|
|
49
|
+
.regex(/^[A-Za-z0-9_-]+$/)
|
|
50
|
+
.length(22)
|
|
51
|
+
.optional(),
|
|
52
|
+
groupId: zod_types_1.zodPeerId.optional(),
|
|
53
|
+
groupName: zod_1.z.string().optional(),
|
|
54
|
+
role: zod_1.z.nativeEnum(group_member_roles_1.GroupMemberRole).optional().describe("Granted role for group invites"),
|
|
55
|
+
/** The signed token: what I issued (out) or what I received/used (in). */
|
|
56
|
+
token: invite_token_1.peersInviteSchema.optional(),
|
|
57
|
+
/**
|
|
58
|
+
* For `in` direct invites: the GroupApproval boxed to my own user box key. For rows whose
|
|
59
|
+
* `deliveryState` is `queued`: the outbound box (already sealed to `recipientUserId`).
|
|
60
|
+
*/
|
|
61
|
+
payload: data_box_schema_1.dataBoxSchema.optional(),
|
|
62
|
+
/** Inviter's epoch at issue time. */
|
|
63
|
+
epoch: zod_1.z.number().int().min(0),
|
|
64
|
+
/** For `in` rows spawned by one of my reusable `out` tokens. */
|
|
65
|
+
parentInviteId: zod_types_1.zodPeerId.optional(),
|
|
66
|
+
deliveryState: exports.inviteDeliveryStateSchema.optional(),
|
|
67
|
+
/** Outbound recipient for `direct` invites and replies that are still queued. */
|
|
68
|
+
recipientUserId: zod_types_1.zodPeerId.optional(),
|
|
69
|
+
createdAt: zod_1.z.date().default(() => new Date()),
|
|
70
|
+
expiresAt: zod_1.z.date(),
|
|
71
|
+
resolvedAt: zod_1.z.date().optional(),
|
|
72
|
+
note: zod_1.z.string().max(500).optional().describe("Sanitized status text for the UI"),
|
|
73
|
+
});
|
|
74
|
+
const metaData = {
|
|
75
|
+
name: "Invites",
|
|
76
|
+
description: "Pending and historical contact and group invites, synced across my devices.",
|
|
77
|
+
primaryKeyName: "inviteId",
|
|
78
|
+
fields: (0, types_1.schemaToFields)(exports.inviteSchema),
|
|
79
|
+
iconClassName: "bi bi-envelope-paper",
|
|
80
|
+
indexes: [
|
|
81
|
+
{ fields: ["status"] },
|
|
82
|
+
{ fields: ["direction"] },
|
|
83
|
+
{ fields: ["groupId"] },
|
|
84
|
+
{ fields: ["parentInviteId"] },
|
|
85
|
+
{ fields: ["nonce"] },
|
|
86
|
+
{ fields: ["deliveryState"] },
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
(0, table_definitions_system_1.registerSystemTableDefinition)(metaData, exports.inviteSchema);
|
|
90
|
+
/**
|
|
91
|
+
* Accessor for the personal-context {@link Invites} table.
|
|
92
|
+
*
|
|
93
|
+
* @param dataContext - Optional data context; defaults to the current singleton.
|
|
94
|
+
*/
|
|
95
|
+
function Invites(dataContext) {
|
|
96
|
+
return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(metaData, exports.inviteSchema);
|
|
97
|
+
}
|
|
98
|
+
/** Age after which terminal rows are purged. */
|
|
99
|
+
exports.INVITE_HISTORY_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
2
|
import type { Emitter } from "../../events";
|
|
3
3
|
import type { FieldType, IField } from "../../types/field-type";
|
|
4
|
-
import { type DataFilter, type ICursorIterable, type IDataQueryParams, type IDataSource } from "./data-query";
|
|
4
|
+
import { type DataFilter, type ICursorIterable, type IDataQueryParams, type IDataSource, type ISaveOptions } from "./data-query";
|
|
5
5
|
import type { ISqlDb } from "./sql-db";
|
|
6
6
|
import type { IDataChangedEvent } from "./subscribable.data-source";
|
|
7
7
|
import { type ITableMetaData } from "./types";
|
|
@@ -27,7 +27,8 @@ export declare class SQLDataSource<T extends {
|
|
|
27
27
|
findOne(filter?: DataFilter<T>, opts?: {
|
|
28
28
|
enforceUnique?: boolean;
|
|
29
29
|
}): Promise<T | undefined>;
|
|
30
|
-
|
|
30
|
+
/** Insert or update by primary key. Honors `insertOnly` (leave an existing row untouched). */
|
|
31
|
+
save(record: T, opts?: ISaveOptions): Promise<T>;
|
|
31
32
|
sqlRecordToJsRecord(record: any): T;
|
|
32
33
|
insert(record: T): Promise<T>;
|
|
33
34
|
update(record: T): Promise<T>;
|
|
@@ -208,7 +208,8 @@ class SQLDataSource {
|
|
|
208
208
|
}
|
|
209
209
|
return records[0];
|
|
210
210
|
}
|
|
211
|
-
|
|
211
|
+
/** Insert or update by primary key. Honors `insertOnly` (leave an existing row untouched). */
|
|
212
|
+
async save(record, opts) {
|
|
212
213
|
await this.initTable();
|
|
213
214
|
let newRecord = true;
|
|
214
215
|
const primaryKey = record[this.metaData.primaryKeyName];
|
|
@@ -221,9 +222,10 @@ class SQLDataSource {
|
|
|
221
222
|
if (newRecord) {
|
|
222
223
|
return await this.insert(record);
|
|
223
224
|
}
|
|
224
|
-
|
|
225
|
-
return await this.
|
|
225
|
+
if (opts?.insertOnly) {
|
|
226
|
+
return (await this.get(primaryKey));
|
|
226
227
|
}
|
|
228
|
+
return await this.update(record);
|
|
227
229
|
}
|
|
228
230
|
sqlRecordToJsRecord(record) {
|
|
229
231
|
if (record) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Emitter, type Event } from "../../events";
|
|
2
|
-
import { type DataFilter, type ICursorIterable, type IDataQueryParams, type IDataSource } from "./data-query";
|
|
2
|
+
import { type DataFilter, type ICursorIterable, type IDataQueryParams, type IDataSource, type ISaveOptions } from "./data-query";
|
|
3
3
|
export interface IDataChangedEvent<T> {
|
|
4
4
|
dataObject: T;
|
|
5
5
|
op: "insert" | "update" | "delete";
|
|
@@ -18,7 +18,8 @@ export declare class SubscribableDataSource<T extends {
|
|
|
18
18
|
list(filter?: DataFilter<T>, opts?: IDataQueryParams<T>): Promise<T[]>;
|
|
19
19
|
count(filter?: DataFilter<T>): Promise<number>;
|
|
20
20
|
cursor(filter?: DataFilter<T>, opts?: IDataQueryParams<T>): ICursorIterable<T>;
|
|
21
|
-
|
|
21
|
+
/** Insert or update by primary key. Honors `insertOnly` (leave an existing row untouched). */
|
|
22
|
+
save(record: T, opts?: ISaveOptions): Promise<T>;
|
|
22
23
|
insert(record: T): Promise<T>;
|
|
23
24
|
update(record: T): Promise<T>;
|
|
24
25
|
delete(idOrRecord: string | T): Promise<void>;
|
|
@@ -33,21 +33,17 @@ class SubscribableDataSource {
|
|
|
33
33
|
cursor(filter = {}, opts = {}) {
|
|
34
34
|
return (0, data_query_1.dataSourceCursor)(this.dataSource || this, filter, opts);
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
/** Insert or update by primary key. Honors `insertOnly` (leave an existing row untouched). */
|
|
37
|
+
async save(record, opts) {
|
|
38
38
|
const primaryKey = record[this.primaryKeyName];
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (existingRecord) {
|
|
42
|
-
newRecord = false;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (newRecord) {
|
|
39
|
+
const existingRecord = primaryKey ? await this.dataSource.get(primaryKey) : undefined;
|
|
40
|
+
if (!existingRecord) {
|
|
46
41
|
return await this.insert(record);
|
|
47
42
|
}
|
|
48
|
-
|
|
49
|
-
return
|
|
43
|
+
if (opts?.insertOnly) {
|
|
44
|
+
return existingRecord;
|
|
50
45
|
}
|
|
46
|
+
return await this.update(record);
|
|
51
47
|
}
|
|
52
48
|
async insert(record) {
|
|
53
49
|
record = await this.dataSource.insert(record);
|
package/dist/data/users.d.ts
CHANGED
|
@@ -27,6 +27,17 @@ export type IUser = z.infer<typeof userSchema>;
|
|
|
27
27
|
/** User table with owner-signature enforcement outside the personal context. */
|
|
28
28
|
export declare class UsersTable extends Table<IUser> {
|
|
29
29
|
static isPassthrough: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Save a user row. Outside the personal context the write must carry a valid owner
|
|
32
|
+
* signature (or be an unsigned stub where nothing signed exists yet).
|
|
33
|
+
*
|
|
34
|
+
* An unsigned `weakInsert` is a discovery placeholder (connection handshake, invite
|
|
35
|
+
* identity, join approval). It is written `insertOnly`: seed the row when the table has
|
|
36
|
+
* nothing for that user, otherwise leave whatever is stored alone. Checking here and then
|
|
37
|
+
* writing is not enough — the owner's signed row (via sync or `syncMyUserToGroup`) can land
|
|
38
|
+
* between the two, and a plain update would replace it with an unsigned snapshot carrying a
|
|
39
|
+
* fresh timestamp that then wins last-write-wins on every device.
|
|
40
|
+
*/
|
|
30
41
|
save(user: IUser, opts?: ISaveOptions): Promise<IUser>;
|
|
31
42
|
signAndSave(user: IUser, opts?: ISaveOptions): Promise<IUser>;
|
|
32
43
|
private static addSignatureToUser;
|
package/dist/data/users.js
CHANGED
|
@@ -79,9 +79,23 @@ let UsersTable = (() => {
|
|
|
79
79
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
80
80
|
}
|
|
81
81
|
static isPassthrough = false;
|
|
82
|
+
/**
|
|
83
|
+
* Save a user row. Outside the personal context the write must carry a valid owner
|
|
84
|
+
* signature (or be an unsigned stub where nothing signed exists yet).
|
|
85
|
+
*
|
|
86
|
+
* An unsigned `weakInsert` is a discovery placeholder (connection handshake, invite
|
|
87
|
+
* identity, join approval). It is written `insertOnly`: seed the row when the table has
|
|
88
|
+
* nothing for that user, otherwise leave whatever is stored alone. Checking here and then
|
|
89
|
+
* writing is not enough — the owner's signed row (via sync or `syncMyUserToGroup`) can land
|
|
90
|
+
* between the two, and a plain update would replace it with an unsigned snapshot carrying a
|
|
91
|
+
* fresh timestamp that then wins last-write-wins on every device.
|
|
92
|
+
*/
|
|
82
93
|
async save(user, opts) {
|
|
83
94
|
opts = opts || {};
|
|
84
95
|
opts = { saveAsSnapshot: true, ...opts };
|
|
96
|
+
if (opts.weakInsert && !user.signature) {
|
|
97
|
+
opts = { ...opts, insertOnly: true };
|
|
98
|
+
}
|
|
85
99
|
if (UsersTable.isPassthrough) {
|
|
86
100
|
return super.save(user, opts);
|
|
87
101
|
}
|
|
@@ -93,6 +107,9 @@ let UsersTable = (() => {
|
|
|
93
107
|
}
|
|
94
108
|
// Get the old user record if it exists
|
|
95
109
|
const oldUser = await this.get(user.userId);
|
|
110
|
+
if (opts.insertOnly && oldUser) {
|
|
111
|
+
return oldUser;
|
|
112
|
+
}
|
|
96
113
|
await (0, user_permissions_1.verifyUserSignature)(user, oldUser);
|
|
97
114
|
return super.save(user, opts);
|
|
98
115
|
}
|
|
@@ -18,11 +18,31 @@ export interface ISortConnectionsArgs {
|
|
|
18
18
|
unconnectedDeviceIds: Set<string>;
|
|
19
19
|
connections: ISortableConnection[];
|
|
20
20
|
}
|
|
21
|
+
/** A direct connection annotated with the shared groups it serves, as seen by the shedding logic. */
|
|
22
|
+
export interface IShedCandidateConnection extends IDeviceConnection {
|
|
23
|
+
/** Data-context ids this connection is a member of on both sides. */
|
|
24
|
+
groups: string[];
|
|
25
|
+
/**
|
|
26
|
+
* How many other devices the remote is known to be connected to (from its last network
|
|
27
|
+
* snapshot). `undefined` when the remote's topology is not known yet. A device whose only
|
|
28
|
+
* known route into the mesh is this connection has `0` alternatives.
|
|
29
|
+
*/
|
|
30
|
+
alternativeRoutes?: number;
|
|
31
|
+
}
|
|
32
|
+
/** Inputs for {@link getLeastPreferredConnection}. */
|
|
21
33
|
export interface IGetLeastPreferredConnectionArgs {
|
|
22
|
-
connections:
|
|
23
|
-
groups: string[];
|
|
24
|
-
})[];
|
|
34
|
+
connections: IShedCandidateConnection[];
|
|
25
35
|
preferredDeviceIds: string[];
|
|
26
36
|
preferredByDeviceIds: string[];
|
|
27
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Choose the connection to drop when this device is at its connection cap.
|
|
40
|
+
*
|
|
41
|
+
* Preference order: connections that are neither preferred by us nor prefer us, then
|
|
42
|
+
* connections we do not depend on, then connections that do not depend on us, then anything.
|
|
43
|
+
* Within the chosen tier, a remote whose only known route into the mesh is this connection is
|
|
44
|
+
* never shed while some other candidate is known to have alternative routes; shedding it would
|
|
45
|
+
* isolate it and it would only redial us and force another shed. Remaining ties go to the
|
|
46
|
+
* connection serving the fewest groups, then to the worst latency x error rate.
|
|
47
|
+
*/
|
|
28
48
|
export declare function getLeastPreferredConnection({ connections, preferredDeviceIds, preferredByDeviceIds, }: IGetLeastPreferredConnectionArgs): IDeviceConnection | undefined;
|
|
@@ -104,6 +104,16 @@ function sortConnections({ myDeviceId, connections, unconnectedDeviceIds }) {
|
|
|
104
104
|
});
|
|
105
105
|
return connections;
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Choose the connection to drop when this device is at its connection cap.
|
|
109
|
+
*
|
|
110
|
+
* Preference order: connections that are neither preferred by us nor prefer us, then
|
|
111
|
+
* connections we do not depend on, then connections that do not depend on us, then anything.
|
|
112
|
+
* Within the chosen tier, a remote whose only known route into the mesh is this connection is
|
|
113
|
+
* never shed while some other candidate is known to have alternative routes; shedding it would
|
|
114
|
+
* isolate it and it would only redial us and force another shed. Remaining ties go to the
|
|
115
|
+
* connection serving the fewest groups, then to the worst latency x error rate.
|
|
116
|
+
*/
|
|
107
117
|
function getLeastPreferredConnection({ connections, preferredDeviceIds, preferredByDeviceIds, }) {
|
|
108
118
|
// first consider connections that are not critical to me and I'm not critical to them
|
|
109
119
|
let candidates = connections.filter((c) => !(preferredByDeviceIds.includes(c.deviceId) || preferredDeviceIds.includes(c.deviceId)));
|
|
@@ -119,6 +129,11 @@ function getLeastPreferredConnection({ connections, preferredDeviceIds, preferre
|
|
|
119
129
|
// if none, then just consider all connections
|
|
120
130
|
candidates = [...connections];
|
|
121
131
|
}
|
|
132
|
+
// never strand a device: if anyone here is known to have another way into the mesh, leave
|
|
133
|
+
// the candidates whose only known route is through us alone
|
|
134
|
+
if (candidates.some((c) => (c.alternativeRoutes ?? 0) > 0)) {
|
|
135
|
+
candidates = candidates.filter((c) => c.alternativeRoutes !== 0);
|
|
136
|
+
}
|
|
122
137
|
// start with random order to avoid biasing the selection to newer connections
|
|
123
138
|
candidates = (0, lodash_1.shuffle)(candidates);
|
|
124
139
|
// sort by latency * error rate (smaller is better)
|