@peers-app/peers-sdk 0.23.13 → 0.24.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/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/users.js +4 -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/rpc-types.d.ts +5 -4
- package/dist/rpc-types.js +2 -1
- package/dist/system-ids.d.ts +22 -0
- package/dist/system-ids.js +23 -1
- package/dist/types/peer-device.d.ts +15 -1
- package/dist/types/peer-device.js +15 -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,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const group_member_roles_1 = require("../data/group-member-roles");
|
|
4
|
+
const keys_1 = require("../keys");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
const invite_messages_1 = require("./invite-messages");
|
|
7
|
+
const invite_token_1 = require("./invite-token");
|
|
8
|
+
function identity(keys = (0, keys_1.newKeys)()) {
|
|
9
|
+
return {
|
|
10
|
+
userId: (0, utils_1.newid)(),
|
|
11
|
+
publicKey: keys.publicKey,
|
|
12
|
+
publicBoxKey: keys.publicBoxKey,
|
|
13
|
+
displayName: "Alice",
|
|
14
|
+
secretKey: keys.secretKey,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function contactToken(inviter = identity(), expiresAt = new Date(Date.now() + 60_000)) {
|
|
18
|
+
const { secretKey, ...publicIdentity } = inviter;
|
|
19
|
+
return (0, invite_token_1.createInviteToken)({
|
|
20
|
+
type: "contact",
|
|
21
|
+
inviter: publicIdentity,
|
|
22
|
+
expiresAt: expiresAt.toISOString(),
|
|
23
|
+
nonce: (0, invite_token_1.newInviteNonce)(),
|
|
24
|
+
epoch: 0,
|
|
25
|
+
uses: 1,
|
|
26
|
+
}, secretKey);
|
|
27
|
+
}
|
|
28
|
+
describe("PeersInvite tokens", () => {
|
|
29
|
+
it("round-trips through every shareable encoding and verifies the signature", () => {
|
|
30
|
+
const token = contactToken();
|
|
31
|
+
const encoded = (0, invite_token_1.encodeInviteToken)(token);
|
|
32
|
+
expect((0, invite_token_1.parseInviteToken)(encoded)).toEqual(token);
|
|
33
|
+
expect((0, invite_token_1.parseInviteToken)((0, invite_token_1.inviteUrl)(token))).toEqual(token);
|
|
34
|
+
expect((0, invite_token_1.parseInviteToken)((0, invite_token_1.inviteDeepLink)(token))).toEqual(token);
|
|
35
|
+
expect((0, invite_token_1.parseInviteToken)(`https://peers.app/?app#invite=${encoded}`)).toEqual(token);
|
|
36
|
+
expect((0, invite_token_1.parseInviteToken)(` ${encoded}\n`)).toEqual(token);
|
|
37
|
+
expect((0, invite_token_1.extractInviteTokenText)((0, invite_token_1.inviteUrl)(token))).toBe(encoded);
|
|
38
|
+
});
|
|
39
|
+
it("rejects tampered contents without echoing the input", () => {
|
|
40
|
+
const token = contactToken();
|
|
41
|
+
const tampered = (0, invite_token_1.encodeInviteToken)({ ...token, epoch: token.epoch + 1 });
|
|
42
|
+
expect(() => (0, invite_token_1.parseInviteToken)(tampered)).toThrow("Invite signature is not valid");
|
|
43
|
+
expect(() => (0, invite_token_1.parseInviteToken)("not a token")).toThrow("not a valid Peers invite");
|
|
44
|
+
});
|
|
45
|
+
it("refuses to sign with a key that is not the inviter's", () => {
|
|
46
|
+
const inviter = identity();
|
|
47
|
+
const { secretKey: _ignored, ...publicIdentity } = inviter;
|
|
48
|
+
expect(() => (0, invite_token_1.createInviteToken)({
|
|
49
|
+
type: "contact",
|
|
50
|
+
inviter: publicIdentity,
|
|
51
|
+
expiresAt: new Date(Date.now() + 1000).toISOString(),
|
|
52
|
+
nonce: (0, invite_token_1.newInviteNonce)(),
|
|
53
|
+
epoch: 0,
|
|
54
|
+
uses: 1,
|
|
55
|
+
}, (0, keys_1.newKeys)().secretKey)).toThrow("inviter's own key");
|
|
56
|
+
});
|
|
57
|
+
it("enforces expiry and type expectations", () => {
|
|
58
|
+
const expired = contactToken(identity(), new Date(Date.now() - 1000));
|
|
59
|
+
const encoded = (0, invite_token_1.encodeInviteToken)(expired);
|
|
60
|
+
expect(() => (0, invite_token_1.parseInviteToken)(encoded)).toThrow("expired");
|
|
61
|
+
expect((0, invite_token_1.parseInviteToken)(encoded, { allowExpired: true })).toEqual(expired);
|
|
62
|
+
expect(() => (0, invite_token_1.parseInviteToken)(encoded, { allowExpired: true, expectType: "group" })).toThrow("Expected a group invite");
|
|
63
|
+
});
|
|
64
|
+
it("requires group facts exactly when the type is group", () => {
|
|
65
|
+
const inviter = identity();
|
|
66
|
+
const { secretKey, ...publicIdentity } = inviter;
|
|
67
|
+
const base = {
|
|
68
|
+
inviter: publicIdentity,
|
|
69
|
+
expiresAt: new Date(Date.now() + 1000).toISOString(),
|
|
70
|
+
nonce: (0, invite_token_1.newInviteNonce)(),
|
|
71
|
+
epoch: 0,
|
|
72
|
+
uses: "unlimited",
|
|
73
|
+
};
|
|
74
|
+
expect(() => (0, invite_token_1.createInviteToken)({ ...base, type: "group" }, secretKey)).toThrow();
|
|
75
|
+
const groupKeys = (0, keys_1.newKeys)();
|
|
76
|
+
const token = (0, invite_token_1.createInviteToken)({
|
|
77
|
+
...base,
|
|
78
|
+
type: "group",
|
|
79
|
+
group: {
|
|
80
|
+
groupId: (0, utils_1.newid)(),
|
|
81
|
+
name: "Family",
|
|
82
|
+
publicKey: groupKeys.publicKey,
|
|
83
|
+
publicBoxKey: groupKeys.publicBoxKey,
|
|
84
|
+
role: group_member_roles_1.GroupMemberRole.Reader,
|
|
85
|
+
mode: "open",
|
|
86
|
+
},
|
|
87
|
+
}, secretKey);
|
|
88
|
+
expect((0, invite_token_1.parseInviteToken)((0, invite_token_1.encodeInviteToken)(token), { expectType: "group" }).group?.mode).toBe("open");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe("invite envelopes", () => {
|
|
92
|
+
it("seals to the recipient and exposes the verified sender keys", () => {
|
|
93
|
+
const sender = identity();
|
|
94
|
+
const recipient = identity();
|
|
95
|
+
const message = invite_messages_1.contactReplySchema.parse({
|
|
96
|
+
kind: "contact-reply",
|
|
97
|
+
inviteNonce: (0, invite_token_1.newInviteNonce)(),
|
|
98
|
+
inviteEpoch: 0,
|
|
99
|
+
responder: {
|
|
100
|
+
userId: sender.userId,
|
|
101
|
+
publicKey: sender.publicKey,
|
|
102
|
+
publicBoxKey: sender.publicBoxKey,
|
|
103
|
+
},
|
|
104
|
+
responderDeviceId: (0, utils_1.newid)(),
|
|
105
|
+
});
|
|
106
|
+
const envelope = (0, invite_messages_1.sealInviteEnvelope)(message, {
|
|
107
|
+
publicKey: sender.publicKey,
|
|
108
|
+
publicBoxKey: sender.publicBoxKey,
|
|
109
|
+
secretKey: sender.secretKey,
|
|
110
|
+
}, { userId: recipient.userId, publicBoxKey: recipient.publicBoxKey });
|
|
111
|
+
expect(envelope.to).toBe(recipient.userId);
|
|
112
|
+
expect(JSON.stringify(envelope)).not.toContain(sender.userId);
|
|
113
|
+
const opened = (0, invite_messages_1.openInviteEnvelope)(envelope, {
|
|
114
|
+
userId: recipient.userId,
|
|
115
|
+
secretKey: recipient.secretKey,
|
|
116
|
+
});
|
|
117
|
+
expect(opened.message).toEqual(message);
|
|
118
|
+
expect(opened.senderPublicKey).toBe(sender.publicKey);
|
|
119
|
+
expect(opened.senderPublicBoxKey).toBe(sender.publicBoxKey);
|
|
120
|
+
expect(() => (0, invite_messages_1.openInviteEnvelope)(envelope, { userId: (0, utils_1.newid)(), secretKey: recipient.secretKey })).toThrow("different user");
|
|
121
|
+
expect(() => (0, invite_messages_1.openInviteEnvelope)(envelope, { userId: recipient.userId, secretKey: (0, keys_1.newKeys)().secretKey })).toThrow();
|
|
122
|
+
});
|
|
123
|
+
});
|
package/dist/rpc-types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IDeviceDatabaseQueryResult, IDeviceOperationStatus, IDeviceToolRunInput } from "./contracts/system/device-operations-contract";
|
|
2
2
|
import type { ITool } from "./data/tools";
|
|
3
|
-
import type { DevicePairingCeremonyAction, DevicePairingStartDestinationInput,
|
|
3
|
+
import type { DevicePairingCeremonyAction, DevicePairingStartDestinationInput, DevicePairingStartSourceInput, DevicePairingState } from "./device-pairing";
|
|
4
4
|
import type { IConsoleLog } from "./logging/console-logs.table";
|
|
5
5
|
import type { IAddOrUpdatePackageResult } from "./package-installer/types";
|
|
6
6
|
import type { IAppNav } from "./types/app-nav";
|
|
@@ -66,13 +66,14 @@ export declare const rpcServerCalls: {
|
|
|
66
66
|
seedBundledPeersCore: (dataContextId?: string) => Promise<boolean>;
|
|
67
67
|
setUserIdAndSecretKey: (userId: string, secretKey: string) => Promise<void>;
|
|
68
68
|
getUserId: () => Promise<string | undefined>;
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
/** Signed-in side: joins the room named by a typed or scanned pairing code. */
|
|
70
|
+
devicePairingStartSource: (input: DevicePairingStartSourceInput) => Promise<DevicePairingState>;
|
|
71
|
+
/** Signed-out side: creates a room and returns state whose `code` the UI shows as text and QR. */
|
|
72
|
+
devicePairingStartDestination: (input?: DevicePairingStartDestinationInput) => Promise<DevicePairingState>;
|
|
71
73
|
devicePairingApprove: (input: DevicePairingCeremonyAction) => Promise<DevicePairingState>;
|
|
72
74
|
devicePairingReject: (input: DevicePairingCeremonyAction) => Promise<DevicePairingState>;
|
|
73
75
|
devicePairingCancel: (input: DevicePairingCeremonyAction) => Promise<DevicePairingState>;
|
|
74
76
|
devicePairingGetState: () => Promise<DevicePairingState>;
|
|
75
|
-
devicePairingGetInvitationUrl: () => Promise<string | undefined>;
|
|
76
77
|
encryptData: (value: string, groupId?: string) => Promise<string>;
|
|
77
78
|
/**
|
|
78
79
|
* Decrypt (if secret) and copy a persistent variable's value to the system clipboard
|
package/dist/rpc-types.js
CHANGED
|
@@ -21,13 +21,14 @@ exports.rpcServerCalls = {
|
|
|
21
21
|
seedBundledPeersCore: rpcStub("seedBundledPeersCore"),
|
|
22
22
|
setUserIdAndSecretKey: rpcStub("setUserIdAndSecretKey"),
|
|
23
23
|
getUserId: rpcStub("getUserId"),
|
|
24
|
+
/** Signed-in side: joins the room named by a typed or scanned pairing code. */
|
|
24
25
|
devicePairingStartSource: rpcStub("devicePairingStartSource"),
|
|
26
|
+
/** Signed-out side: creates a room and returns state whose `code` the UI shows as text and QR. */
|
|
25
27
|
devicePairingStartDestination: rpcStub("devicePairingStartDestination"),
|
|
26
28
|
devicePairingApprove: rpcStub("devicePairingApprove"),
|
|
27
29
|
devicePairingReject: rpcStub("devicePairingReject"),
|
|
28
30
|
devicePairingCancel: rpcStub("devicePairingCancel"),
|
|
29
31
|
devicePairingGetState: rpcStub("devicePairingGetState"),
|
|
30
|
-
devicePairingGetInvitationUrl: rpcStub("devicePairingGetInvitationUrl"),
|
|
31
32
|
encryptData: rpcStub("encryptData"),
|
|
32
33
|
/**
|
|
33
34
|
* Decrypt (if secret) and copy a persistent variable's value to the system clipboard
|
package/dist/system-ids.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare const runWorkflowToolId = "000peers0tool0runworkflow";
|
|
|
17
17
|
* Distinct from Device Operations contract `queryDatabase`.
|
|
18
18
|
*/
|
|
19
19
|
export declare const queryDatabaseToolId = "000peers0tool00querydb001";
|
|
20
|
+
/** Stable ID of the `create-group` system tool (creates a group founded by the current user). */
|
|
21
|
+
export declare const createGroupToolId = "000peers0tool0creategroup";
|
|
20
22
|
/** Stable package ID of the bundled peers-core package. */
|
|
21
23
|
export declare const peersCorePackageId = "00mh0wlipjixk2gqmurbwee0o";
|
|
22
24
|
/** Canonical source repository for peers-core. */
|
|
@@ -54,3 +56,23 @@ export declare const toolAccessControlToolIds: {
|
|
|
54
56
|
readonly reject: "000peers0tacreject0000001";
|
|
55
57
|
readonly listPending: "000peers0taclist000000001";
|
|
56
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* Contract id for the local-only contact and group invite surface (System Invites).
|
|
61
|
+
* Not available over device-to-device connections.
|
|
62
|
+
*/
|
|
63
|
+
export declare const invitesContractId = "00mu8uuzvo2zux1wfb9f1r48l";
|
|
64
|
+
/** Stable IDs for tools in System Invites v1. */
|
|
65
|
+
export declare const invitesToolIds: {
|
|
66
|
+
readonly createContactInvite: "00mu8uuzvqbn5o23ya563wmp5";
|
|
67
|
+
readonly createGroupInvite: "00mu8uuzvqz17wedq1iqmh9i4";
|
|
68
|
+
readonly inviteContactToGroup: "00mu8uuzvqhbk5avjpew7w9ng";
|
|
69
|
+
readonly previewInvite: "00mu8uuzvqwbo180wgz4ctslm";
|
|
70
|
+
readonly acceptInvite: "00mu8uuzvq8i1boc4ml1ywk30";
|
|
71
|
+
readonly declineInvite: "00mu8uuzvq97veint1977p2ey";
|
|
72
|
+
readonly approveJoinRequest: "00mu8uuzvqisozhlfbe1oeo0e";
|
|
73
|
+
readonly denyJoinRequest: "00mu8uuzvqdars4o466eansi2";
|
|
74
|
+
readonly revokeInvite: "00mu8uuzvqndg6qdh74dob9j1";
|
|
75
|
+
readonly regenerateProfileInvite: "00mu8uuzvqbu5ri9octpk64oo";
|
|
76
|
+
readonly syncMailbox: "00mu8uuzvq4xlaiqrn5notj1j";
|
|
77
|
+
readonly listInvites: "00mu8uuzvqfego6lyjjkgr4s7";
|
|
78
|
+
};
|
package/dist/system-ids.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toolAccessControlToolIds = exports.toolAccessControlContractId = exports.networkControlContractId = exports.networkDiagnosticsContractId = exports.deviceOperationsContractId = exports.logsContractId = exports.peersSystemPackageId = exports.peersCorePublishPublicKey = exports.peersCorePackageRepoUrl = exports.peersCorePackageId = exports.queryDatabaseToolId = exports.runWorkflowToolId = exports.defaultSendMessageToolId = exports.defaultAssistantRunnerToolId = exports.adminAssistantId = exports.openCodeAssistantId = exports.defaultAssistantId = exports.peersRootUserId = void 0;
|
|
3
|
+
exports.invitesToolIds = exports.invitesContractId = exports.toolAccessControlToolIds = exports.toolAccessControlContractId = exports.networkControlContractId = exports.networkDiagnosticsContractId = exports.deviceOperationsContractId = exports.logsContractId = exports.peersSystemPackageId = exports.peersCorePublishPublicKey = exports.peersCorePackageRepoUrl = exports.peersCorePackageId = exports.createGroupToolId = exports.queryDatabaseToolId = exports.runWorkflowToolId = exports.defaultSendMessageToolId = exports.defaultAssistantRunnerToolId = exports.adminAssistantId = exports.openCodeAssistantId = exports.defaultAssistantId = exports.peersRootUserId = void 0;
|
|
4
4
|
/** Stable user ID that owns built-in Peers system records. */
|
|
5
5
|
exports.peersRootUserId = "000peers0user000000000001";
|
|
6
6
|
/** Default assistant used when no user-selected assistant is available. */
|
|
@@ -21,6 +21,8 @@ exports.runWorkflowToolId = "000peers0tool0runworkflow";
|
|
|
21
21
|
* Distinct from Device Operations contract `queryDatabase`.
|
|
22
22
|
*/
|
|
23
23
|
exports.queryDatabaseToolId = "000peers0tool00querydb001";
|
|
24
|
+
/** Stable ID of the `create-group` system tool (creates a group founded by the current user). */
|
|
25
|
+
exports.createGroupToolId = "000peers0tool0creategroup";
|
|
24
26
|
/** Stable package ID of the bundled peers-core package. */
|
|
25
27
|
exports.peersCorePackageId = "00mh0wlipjixk2gqmurbwee0o";
|
|
26
28
|
/** Canonical source repository for peers-core. */
|
|
@@ -58,3 +60,23 @@ exports.toolAccessControlToolIds = {
|
|
|
58
60
|
reject: "000peers0tacreject0000001",
|
|
59
61
|
listPending: "000peers0taclist000000001",
|
|
60
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* Contract id for the local-only contact and group invite surface (System Invites).
|
|
65
|
+
* Not available over device-to-device connections.
|
|
66
|
+
*/
|
|
67
|
+
exports.invitesContractId = "00mu8uuzvo2zux1wfb9f1r48l";
|
|
68
|
+
/** Stable IDs for tools in System Invites v1. */
|
|
69
|
+
exports.invitesToolIds = {
|
|
70
|
+
createContactInvite: "00mu8uuzvqbn5o23ya563wmp5",
|
|
71
|
+
createGroupInvite: "00mu8uuzvqz17wedq1iqmh9i4",
|
|
72
|
+
inviteContactToGroup: "00mu8uuzvqhbk5avjpew7w9ng",
|
|
73
|
+
previewInvite: "00mu8uuzvqwbo180wgz4ctslm",
|
|
74
|
+
acceptInvite: "00mu8uuzvq8i1boc4ml1ywk30",
|
|
75
|
+
declineInvite: "00mu8uuzvq97veint1977p2ey",
|
|
76
|
+
approveJoinRequest: "00mu8uuzvqisozhlfbe1oeo0e",
|
|
77
|
+
denyJoinRequest: "00mu8uuzvqdars4o466eansi2",
|
|
78
|
+
revokeInvite: "00mu8uuzvqndg6qdh74dob9j1",
|
|
79
|
+
regenerateProfileInvite: "00mu8uuzvqbu5ri9octpk64oo",
|
|
80
|
+
syncMailbox: "00mu8uuzvq4xlaiqrn5notj1j",
|
|
81
|
+
listInvites: "00mu8uuzvqfego6lyjjkgr4s7",
|
|
82
|
+
};
|
|
@@ -74,7 +74,21 @@ export interface IDeviceConnection {
|
|
|
74
74
|
/** Shared tuning constants for device synchronization and routing. */
|
|
75
75
|
export declare const PeerDeviceConsts: Readonly<{
|
|
76
76
|
readonly MAX_CONNECTIONS: 30;
|
|
77
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Period of the safety-net election + pull from preferred connections.
|
|
79
|
+
* Each tick is a handful of tiny `getNetworkInfo` RPCs per group and a
|
|
80
|
+
* watermark check, so this is cheap; it bounds how long any missed
|
|
81
|
+
* push can go unnoticed.
|
|
82
|
+
*/
|
|
83
|
+
readonly RESYNC_INTERVAL: 60000;
|
|
84
|
+
/**
|
|
85
|
+
* Delays (ms after a new connection's first election) at which the election
|
|
86
|
+
* is re-run so both sides see each other's settled `preferredDeviceIds`.
|
|
87
|
+
* Two peers that connect at the same time each read the other's network info
|
|
88
|
+
* before the other has finished electing, which otherwise leaves a one-way
|
|
89
|
+
* preferred edge until the next `RESYNC_INTERVAL` tick.
|
|
90
|
+
*/
|
|
91
|
+
readonly ELECTION_SETTLE_DELAYS_MS: readonly number[];
|
|
78
92
|
readonly NOTIFY_CHANGE_DELAY: 100;
|
|
79
93
|
readonly CHANGES_PAGE_SIZE: 100;
|
|
80
94
|
readonly RETRY_COUNT: 2;
|
|
@@ -5,7 +5,21 @@ exports.PeerDeviceConsts = void 0;
|
|
|
5
5
|
exports.PeerDeviceConsts = Object.freeze({
|
|
6
6
|
// TODO set this based on device type (desktops and servers should be able to handle 100 connections)
|
|
7
7
|
MAX_CONNECTIONS: 30,
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Period of the safety-net election + pull from preferred connections.
|
|
10
|
+
* Each tick is a handful of tiny `getNetworkInfo` RPCs per group and a
|
|
11
|
+
* watermark check, so this is cheap; it bounds how long any missed
|
|
12
|
+
* push can go unnoticed.
|
|
13
|
+
*/
|
|
14
|
+
RESYNC_INTERVAL: 60_000,
|
|
15
|
+
/**
|
|
16
|
+
* Delays (ms after a new connection's first election) at which the election
|
|
17
|
+
* is re-run so both sides see each other's settled `preferredDeviceIds`.
|
|
18
|
+
* Two peers that connect at the same time each read the other's network info
|
|
19
|
+
* before the other has finished electing, which otherwise leaves a one-way
|
|
20
|
+
* preferred edge until the next `RESYNC_INTERVAL` tick.
|
|
21
|
+
*/
|
|
22
|
+
ELECTION_SETTLE_DELAYS_MS: [10_000, 30_000],
|
|
9
23
|
NOTIFY_CHANGE_DELAY: 100,
|
|
10
24
|
// CHANGES_PAGE_SIZE: 2_000,
|
|
11
25
|
CHANGES_PAGE_SIZE: 100,
|
package/package.json
CHANGED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { type DevicePairingInvitation } from "./types";
|
|
2
|
-
/** Creates a five-minute, high-entropy invitation for a signaling service origin. */
|
|
3
|
-
export declare function createDevicePairingInvitation({ serviceOrigin, now, ttlMs, }: {
|
|
4
|
-
serviceOrigin: string;
|
|
5
|
-
now?: number;
|
|
6
|
-
ttlMs?: number;
|
|
7
|
-
}): DevicePairingInvitation;
|
|
8
|
-
/** Encodes an invitation as a versioned token suitable for a URL fragment or manual entry. */
|
|
9
|
-
export declare function encodeDevicePairingInvitation(invitation: DevicePairingInvitation): string;
|
|
10
|
-
/** Parses and validates a versioned invitation token or fragment URL. */
|
|
11
|
-
export declare function parseDevicePairingInvitation(input: string, now?: number): DevicePairingInvitation;
|
|
12
|
-
/** Places the invitation in a URL fragment so it is excluded from HTTP requests and access logs. */
|
|
13
|
-
export declare function createDevicePairingInvitationUrl(invitationBaseUrl: string, invitation: DevicePairingInvitation): string;
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createDevicePairingInvitation = createDevicePairingInvitation;
|
|
4
|
-
exports.encodeDevicePairingInvitation = encodeDevicePairingInvitation;
|
|
5
|
-
exports.parseDevicePairingInvitation = parseDevicePairingInvitation;
|
|
6
|
-
exports.createDevicePairingInvitationUrl = createDevicePairingInvitationUrl;
|
|
7
|
-
const keys_1 = require("../keys");
|
|
8
|
-
const constants_1 = require("./constants");
|
|
9
|
-
const types_1 = require("./types");
|
|
10
|
-
const INVITATION_TOKEN_PREFIX = "peers-pairing-v1.";
|
|
11
|
-
const textDecoder = new TextDecoder();
|
|
12
|
-
const textEncoder = new TextEncoder();
|
|
13
|
-
function normalizeServiceOrigin(value) {
|
|
14
|
-
const url = new URL(value);
|
|
15
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
16
|
-
throw new Error("Pairing service must use HTTP or HTTPS");
|
|
17
|
-
}
|
|
18
|
-
return url.origin;
|
|
19
|
-
}
|
|
20
|
-
function extractInvitationToken(input) {
|
|
21
|
-
const trimmed = input.trim();
|
|
22
|
-
if (trimmed.startsWith(INVITATION_TOKEN_PREFIX)) {
|
|
23
|
-
return trimmed;
|
|
24
|
-
}
|
|
25
|
-
try {
|
|
26
|
-
const url = new URL(trimmed);
|
|
27
|
-
const fragment = url.hash.slice(1);
|
|
28
|
-
if (fragment.startsWith("pair=")) {
|
|
29
|
-
return decodeURIComponent(fragment.slice("pair=".length));
|
|
30
|
-
}
|
|
31
|
-
if (fragment.startsWith("pair?")) {
|
|
32
|
-
const token = new URLSearchParams(fragment.slice("pair?".length)).get("token");
|
|
33
|
-
if (token) {
|
|
34
|
-
return token;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
// The generic error below deliberately avoids echoing invitation input.
|
|
40
|
-
}
|
|
41
|
-
throw new Error("Invalid pairing invitation");
|
|
42
|
-
}
|
|
43
|
-
/** Creates a five-minute, high-entropy invitation for a signaling service origin. */
|
|
44
|
-
function createDevicePairingInvitation({ serviceOrigin, now = Date.now(), ttlMs = constants_1.DEVICE_PAIRING_TTL_MS, }) {
|
|
45
|
-
if (!Number.isSafeInteger(now) || ttlMs <= 0 || ttlMs > constants_1.DEVICE_PAIRING_TTL_MS) {
|
|
46
|
-
throw new Error("Invalid pairing invitation lifetime");
|
|
47
|
-
}
|
|
48
|
-
return types_1.devicePairingInvitationSchema.parse({
|
|
49
|
-
version: constants_1.DEVICE_PAIRING_PROTOCOL_VERSION,
|
|
50
|
-
roomId: (0, keys_1.newToken)(16),
|
|
51
|
-
roomSecret: (0, keys_1.newToken)(32),
|
|
52
|
-
expiresAt: now + ttlMs,
|
|
53
|
-
serviceOrigin: normalizeServiceOrigin(serviceOrigin),
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
/** Encodes an invitation as a versioned token suitable for a URL fragment or manual entry. */
|
|
57
|
-
function encodeDevicePairingInvitation(invitation) {
|
|
58
|
-
const parsed = types_1.devicePairingInvitationSchema.parse(invitation);
|
|
59
|
-
return `${INVITATION_TOKEN_PREFIX}${(0, keys_1.encodeBase64)(textEncoder.encode(JSON.stringify(parsed)))}`;
|
|
60
|
-
}
|
|
61
|
-
/** Parses and validates a versioned invitation token or fragment URL. */
|
|
62
|
-
function parseDevicePairingInvitation(input, now = Date.now()) {
|
|
63
|
-
try {
|
|
64
|
-
const token = extractInvitationToken(input);
|
|
65
|
-
if (!token.startsWith(INVITATION_TOKEN_PREFIX)) {
|
|
66
|
-
throw new Error("unsupported");
|
|
67
|
-
}
|
|
68
|
-
const encoded = token.slice(INVITATION_TOKEN_PREFIX.length);
|
|
69
|
-
const parsed = types_1.devicePairingInvitationSchema.parse(JSON.parse(textDecoder.decode((0, keys_1.decodeBase64)(encoded))));
|
|
70
|
-
if (parsed.expiresAt <= now ||
|
|
71
|
-
parsed.expiresAt > now + constants_1.DEVICE_PAIRING_TTL_MS + constants_1.DEVICE_PAIRING_CLOCK_SKEW_MS) {
|
|
72
|
-
throw new Error("expired");
|
|
73
|
-
}
|
|
74
|
-
return parsed;
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
throw new Error("Invalid or expired pairing invitation");
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
/** Places the invitation in a URL fragment so it is excluded from HTTP requests and access logs. */
|
|
81
|
-
function createDevicePairingInvitationUrl(invitationBaseUrl, invitation) {
|
|
82
|
-
const url = new URL(invitationBaseUrl);
|
|
83
|
-
url.hash = `pair=${encodeURIComponent(encodeDevicePairingInvitation(invitation))}`;
|
|
84
|
-
return url.toString();
|
|
85
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utilities for group invitation flow.
|
|
3
|
-
*
|
|
4
|
-
* Uses a simple password that users can edit or customize.
|
|
5
|
-
* The password is hashed to create an alias for network discovery.
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Generate a random memorable passphrase.
|
|
9
|
-
* Format: "word-word-word-word" (4 words separated by dashes)
|
|
10
|
-
*
|
|
11
|
-
* @param wordCount Number of words to use (default 4)
|
|
12
|
-
* @returns A memorable passphrase
|
|
13
|
-
*/
|
|
14
|
-
export declare function generateInvitePassword(wordCount?: number): string;
|
|
15
|
-
/**
|
|
16
|
-
* Derive a network alias from a password.
|
|
17
|
-
* Uses SHA-256 hash, takes first 8 characters as hex.
|
|
18
|
-
*
|
|
19
|
-
* @param password The password to derive from
|
|
20
|
-
* @returns An 8-character hex alias
|
|
21
|
-
*/
|
|
22
|
-
export declare function deriveAliasFromPassword(password: string): string;
|
|
23
|
-
/**
|
|
24
|
-
* Encrypt data using the invite password.
|
|
25
|
-
* Uses NaCl secretbox with a key derived from the password.
|
|
26
|
-
*
|
|
27
|
-
* @param data Data to encrypt
|
|
28
|
-
* @param password The invitation password
|
|
29
|
-
* @returns Base64-encoded encrypted data with nonce
|
|
30
|
-
*/
|
|
31
|
-
export declare function encryptWithPassword(data: any, password: string): string;
|
|
32
|
-
/**
|
|
33
|
-
* Decrypt data using the invite password.
|
|
34
|
-
*
|
|
35
|
-
* @param encrypted Base64-encoded encrypted data
|
|
36
|
-
* @param password The invitation password
|
|
37
|
-
* @returns Decrypted data
|
|
38
|
-
*/
|
|
39
|
-
export declare function decryptWithPassword(encrypted: string, password: string): any;
|
|
40
|
-
/**
|
|
41
|
-
* Validate a password format.
|
|
42
|
-
* Passwords should be at least 4 characters.
|
|
43
|
-
*
|
|
44
|
-
* @param password Password to validate
|
|
45
|
-
* @returns true if valid
|
|
46
|
-
*/
|
|
47
|
-
export declare function isValidInvitePassword(password: string): boolean;
|
|
48
|
-
/**
|
|
49
|
-
* Generate a unique request ID for tracking join requests.
|
|
50
|
-
*
|
|
51
|
-
* @returns A unique request ID
|
|
52
|
-
*/
|
|
53
|
-
export declare function generateRequestId(): string;
|