@peers-app/peers-sdk 0.23.14 → 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.
Files changed (80) hide show
  1. package/dist/contracts/index.d.ts +1 -0
  2. package/dist/contracts/index.js +4 -1
  3. package/dist/contracts/system/index.d.ts +2 -0
  4. package/dist/contracts/system/index.js +5 -1
  5. package/dist/contracts/system/invites-contract.d.ts +121 -0
  6. package/dist/contracts/system/invites-contract.js +150 -0
  7. package/dist/data/create-group.d.ts +24 -0
  8. package/dist/data/create-group.js +61 -0
  9. package/dist/data/group-secret.d.ts +10 -0
  10. package/dist/data/group-secret.js +26 -0
  11. package/dist/data/group-share.d.ts +0 -20
  12. package/dist/data/group-share.js +0 -35
  13. package/dist/data/index.d.ts +2 -0
  14. package/dist/data/index.js +2 -0
  15. package/dist/data/invites.d.ts +350 -0
  16. package/dist/data/invites.js +99 -0
  17. package/dist/data/users.js +4 -0
  18. package/dist/device-pairing/constants.d.ts +27 -9
  19. package/dist/device-pairing/constants.js +28 -10
  20. package/dist/device-pairing/cpace.d.ts +46 -0
  21. package/dist/device-pairing/cpace.js +173 -0
  22. package/dist/device-pairing/cpace.test.js +116 -0
  23. package/dist/device-pairing/crypto.d.ts +40 -15
  24. package/dist/device-pairing/crypto.js +74 -33
  25. package/dist/device-pairing/device-pairing.test.js +156 -119
  26. package/dist/device-pairing/index.d.ts +3 -1
  27. package/dist/device-pairing/index.js +3 -1
  28. package/dist/device-pairing/pairing-code.d.ts +27 -0
  29. package/dist/device-pairing/pairing-code.js +84 -0
  30. package/dist/device-pairing/pgp-word-list.d.ts +14 -0
  31. package/dist/device-pairing/pgp-word-list.js +531 -0
  32. package/dist/device-pairing/signaling.d.ts +17 -6
  33. package/dist/device-pairing/signaling.js +31 -8
  34. package/dist/device-pairing/transcript.d.ts +3 -3
  35. package/dist/device-pairing/transcript.js +6 -13
  36. package/dist/device-pairing/types.d.ts +870 -224
  37. package/dist/device-pairing/types.js +186 -108
  38. package/dist/index.d.ts +1 -2
  39. package/dist/index.js +1 -2
  40. package/dist/invites/data-box-schema.d.ts +15 -0
  41. package/dist/invites/data-box-schema.js +15 -0
  42. package/dist/invites/group-approval.d.ts +43 -0
  43. package/dist/invites/group-approval.js +155 -0
  44. package/dist/invites/group-approval.test.js +221 -0
  45. package/dist/invites/index.d.ts +5 -0
  46. package/dist/{group-invite → invites}/index.js +5 -3
  47. package/dist/invites/invite-epoch.d.ts +18 -0
  48. package/dist/invites/invite-epoch.js +35 -0
  49. package/dist/invites/invite-messages.d.ts +1354 -0
  50. package/dist/invites/invite-messages.js +145 -0
  51. package/dist/invites/invite-token.d.ts +334 -0
  52. package/dist/invites/invite-token.js +178 -0
  53. package/dist/invites/invite-token.test.d.ts +1 -0
  54. package/dist/invites/invite-token.test.js +123 -0
  55. package/dist/rpc-types.d.ts +5 -4
  56. package/dist/rpc-types.js +2 -1
  57. package/dist/system-ids.d.ts +22 -0
  58. package/dist/system-ids.js +23 -1
  59. package/package.json +1 -1
  60. package/dist/device-pairing/invitation.d.ts +0 -13
  61. package/dist/device-pairing/invitation.js +0 -85
  62. package/dist/group-invite/group-invite.d.ts +0 -53
  63. package/dist/group-invite/group-invite.js +0 -231
  64. package/dist/group-invite/group-invite.pvars.d.ts +0 -94
  65. package/dist/group-invite/group-invite.pvars.js +0 -139
  66. package/dist/group-invite/group-invite.types.d.ts +0 -124
  67. package/dist/group-invite/group-invite.types.js +0 -8
  68. package/dist/group-invite/index.d.ts +0 -3
  69. package/dist/user-connect/connection-code.d.ts +0 -67
  70. package/dist/user-connect/connection-code.js +0 -176
  71. package/dist/user-connect/connection-code.test.js +0 -213
  72. package/dist/user-connect/index.d.ts +0 -3
  73. package/dist/user-connect/index.js +0 -19
  74. package/dist/user-connect/user-connect.pvars.d.ts +0 -20
  75. package/dist/user-connect/user-connect.pvars.js +0 -63
  76. package/dist/user-connect/user-connect.pvars.test.js +0 -46
  77. package/dist/user-connect/user-connect.types.d.ts +0 -94
  78. package/dist/user-connect/user-connect.types.js +0 -68
  79. /package/dist/{user-connect/connection-code.test.d.ts → device-pairing/cpace.test.d.ts} +0 -0
  80. /package/dist/{user-connect/user-connect.pvars.test.d.ts → invites/group-approval.test.d.ts} +0 -0
@@ -1,94 +0,0 @@
1
- /**
2
- * Persistent variables for group invitation state.
3
- *
4
- * These device-level variables track:
5
- * - Active invitation listeners (admin side)
6
- * - Pending join requests (admin side)
7
- * - Status messages for UI feedback
8
- * - Action triggers (UI sets these, device layer reacts)
9
- */
10
- import type { GroupMemberRole } from "../data/group-member-roles";
11
- import type { IGroupInviteListener, IGroupInviteListenerInfo, IGroupJoinRequest, IGroupJoinResult } from "./group-invite.types";
12
- /**
13
- * Active group invitation listeners.
14
- * Key is groupId, value contains the password and alias being listened for.
15
- *
16
- * Used by admins who are waiting for join requests.
17
- */
18
- export declare const groupInviteListeners: import("../data/persistent-vars").PersistentVar<Record<string, IGroupInviteListener>>;
19
- /**
20
- * Pending join requests that need admin approval.
21
- * Array of requests waiting to be approved or denied.
22
- *
23
- * These are shown to the admin in the group details UI.
24
- */
25
- export declare const groupInviteRequests: import("../data/persistent-vars").PersistentVar<IGroupJoinRequest[]>;
26
- /**
27
- * Status message for the group invite UI (admin side).
28
- * Used to show success/error messages to the user.
29
- */
30
- export declare const groupInviteStatus: import("../data/persistent-vars").PersistentVar<string>;
31
- /**
32
- * Status message for the join group UI (joiner side).
33
- * Used to show discovery/join status to the user.
34
- */
35
- export declare const groupJoinStatus: import("../data/persistent-vars").PersistentVar<string>;
36
- /**
37
- * Results from discovering group listeners.
38
- * Updated by the device layer after discovery completes.
39
- */
40
- export declare const groupInviteDiscoveryResult: import("../data/persistent-vars").PersistentVar<IGroupInviteListenerInfo[]>;
41
- /**
42
- * Result of joining a group.
43
- * Updated by the device layer after join completes.
44
- */
45
- export declare const groupInviteJoinResult: import("../data/persistent-vars").PersistentVar<IGroupJoinResult | null>;
46
- /**
47
- * Responses to join requests, keyed by requestId.
48
- * Written by processJoinRequest, read by handleJoinRequest.
49
- * Entries are cleaned up after being read.
50
- */
51
- export declare const groupInviteResponses: import("../data/persistent-vars").PersistentVar<Record<string, {
52
- statusCode: number;
53
- payload: string;
54
- }>>;
55
- /**
56
- * Trigger to start listening for invitations.
57
- * Set by UI: { groupId, password }
58
- * Device layer clears after processing.
59
- */
60
- export declare const groupInviteStartListening: import("../data/persistent-vars").PersistentVar<{
61
- groupId: string;
62
- password: string;
63
- } | null>;
64
- /**
65
- * Trigger to stop listening for invitations.
66
- * Set by UI: groupId
67
- * Device layer clears after processing.
68
- */
69
- export declare const groupInviteStopListening: import("../data/persistent-vars").PersistentVar<string | null>;
70
- /**
71
- * Trigger to discover groups with a password.
72
- * Set by UI: password
73
- * Device layer updates groupInviteDiscoveryResult and clears this.
74
- */
75
- export declare const groupInviteDiscover: import("../data/persistent-vars").PersistentVar<string | null>;
76
- /**
77
- * Trigger to send a join request.
78
- * Set by UI: { listener, password }
79
- * Device layer updates groupInviteJoinResult and clears this.
80
- */
81
- export declare const groupInviteSendRequest: import("../data/persistent-vars").PersistentVar<{
82
- listener: IGroupInviteListenerInfo;
83
- password: string;
84
- } | null>;
85
- /**
86
- * Trigger to process (approve/deny) a join request.
87
- * Set by UI: { requestId, approved, role }
88
- * Device layer processes and clears this.
89
- */
90
- export declare const groupInviteProcessRequest: import("../data/persistent-vars").PersistentVar<{
91
- requestId: string;
92
- approved: boolean;
93
- role: GroupMemberRole;
94
- } | null>;
@@ -1,139 +0,0 @@
1
- "use strict";
2
- /**
3
- * Persistent variables for group invitation state.
4
- *
5
- * These device-level variables track:
6
- * - Active invitation listeners (admin side)
7
- * - Pending join requests (admin side)
8
- * - Status messages for UI feedback
9
- * - Action triggers (UI sets these, device layer reacts)
10
- */
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.groupInviteProcessRequest = exports.groupInviteSendRequest = exports.groupInviteDiscover = exports.groupInviteStopListening = exports.groupInviteStartListening = exports.groupInviteResponses = exports.groupInviteJoinResult = exports.groupInviteDiscoveryResult = exports.groupJoinStatus = exports.groupInviteStatus = exports.groupInviteRequests = exports.groupInviteListeners = void 0;
13
- const persistent_vars_1 = require("../data/persistent-vars");
14
- // ============================================================================
15
- // STATE VARIABLES (read by both UI and device layer)
16
- // ============================================================================
17
- /**
18
- * Active group invitation listeners.
19
- * Key is groupId, value contains the password and alias being listened for.
20
- *
21
- * Used by admins who are waiting for join requests.
22
- */
23
- exports.groupInviteListeners = (0, persistent_vars_1.deviceVar)("groupInviteListeners", { defaultValue: {} });
24
- /**
25
- * Pending join requests that need admin approval.
26
- * Array of requests waiting to be approved or denied.
27
- *
28
- * These are shown to the admin in the group details UI.
29
- */
30
- exports.groupInviteRequests = (0, persistent_vars_1.deviceVar)("groupInviteRequests", {
31
- defaultValue: [],
32
- });
33
- /**
34
- * Status message for the group invite UI (admin side).
35
- * Used to show success/error messages to the user.
36
- */
37
- exports.groupInviteStatus = (0, persistent_vars_1.deviceVar)("groupInviteStatus", { defaultValue: "" });
38
- /**
39
- * Status message for the join group UI (joiner side).
40
- * Used to show discovery/join status to the user.
41
- */
42
- exports.groupJoinStatus = (0, persistent_vars_1.deviceVar)("groupJoinStatus", { defaultValue: "" });
43
- /**
44
- * Results from discovering group listeners.
45
- * Updated by the device layer after discovery completes.
46
- */
47
- exports.groupInviteDiscoveryResult = (0, persistent_vars_1.deviceVar)("groupInviteDiscoveryResult", { defaultValue: [] });
48
- /**
49
- * Result of joining a group.
50
- * Updated by the device layer after join completes.
51
- */
52
- exports.groupInviteJoinResult = (0, persistent_vars_1.deviceVar)("groupInviteJoinResult", {
53
- defaultValue: null,
54
- });
55
- /**
56
- * Responses to join requests, keyed by requestId.
57
- * Written by processJoinRequest, read by handleJoinRequest.
58
- * Entries are cleaned up after being read.
59
- */
60
- exports.groupInviteResponses = (0, persistent_vars_1.deviceVar)("groupInviteResponses", { defaultValue: {} });
61
- // ============================================================================
62
- // ACTION TRIGGERS (UI sets these, device layer reacts)
63
- // ============================================================================
64
- /**
65
- * Trigger to start listening for invitations.
66
- * Set by UI: { groupId, password }
67
- * Device layer clears after processing.
68
- */
69
- exports.groupInviteStartListening = (0, persistent_vars_1.deviceVar)("groupInviteStartListening", { defaultValue: null });
70
- /**
71
- * Trigger to stop listening for invitations.
72
- * Set by UI: groupId
73
- * Device layer clears after processing.
74
- */
75
- exports.groupInviteStopListening = (0, persistent_vars_1.deviceVar)("groupInviteStopListening", {
76
- defaultValue: null,
77
- });
78
- /**
79
- * Trigger to discover groups with a password.
80
- * Set by UI: password
81
- * Device layer updates groupInviteDiscoveryResult and clears this.
82
- */
83
- exports.groupInviteDiscover = (0, persistent_vars_1.deviceVar)("groupInviteDiscover", {
84
- defaultValue: null,
85
- });
86
- /**
87
- * Trigger to send a join request.
88
- * Set by UI: { listener, password }
89
- * Device layer updates groupInviteJoinResult and clears this.
90
- */
91
- exports.groupInviteSendRequest = (0, persistent_vars_1.deviceVar)("groupInviteSendRequest", { defaultValue: null });
92
- /**
93
- * Trigger to process (approve/deny) a join request.
94
- * Set by UI: { requestId, approved, role }
95
- * Device layer processes and clears this.
96
- */
97
- exports.groupInviteProcessRequest = (0, persistent_vars_1.deviceVar)("groupInviteProcessRequest", { defaultValue: null });
98
- // ============================================================================
99
- // AUTO-CLEANUP
100
- // ============================================================================
101
- // Auto-cleanup of expired listeners (10 minute expiry)
102
- exports.groupInviteListeners.loadingPromise.then(() => {
103
- // Check every minute for expired listeners
104
- setInterval(() => {
105
- const listeners = (0, exports.groupInviteListeners)();
106
- const now = Date.now();
107
- const expiredGroupIds = [];
108
- for (const [groupId, listener] of Object.entries(listeners)) {
109
- const startedAt = new Date(listener.startedAt).getTime();
110
- const elapsed = now - startedAt;
111
- // Expire after 10 minutes
112
- if (elapsed > 600_000) {
113
- expiredGroupIds.push(groupId);
114
- }
115
- }
116
- if (expiredGroupIds.length > 0) {
117
- const updated = { ...listeners };
118
- for (const groupId of expiredGroupIds) {
119
- delete updated[groupId];
120
- }
121
- (0, exports.groupInviteListeners)(updated);
122
- }
123
- }, 60_000);
124
- });
125
- // Clean up old requests after 10 minutes
126
- exports.groupInviteRequests.loadingPromise.then(() => {
127
- setInterval(() => {
128
- const requests = (0, exports.groupInviteRequests)();
129
- const now = Date.now();
130
- const validRequests = requests.filter((req) => {
131
- const receivedAt = new Date(req.receivedAt).getTime();
132
- const elapsed = now - receivedAt;
133
- return elapsed < 600_000; // Keep for 10 minutes
134
- });
135
- if (validRequests.length !== requests.length) {
136
- (0, exports.groupInviteRequests)(validRequests);
137
- }
138
- }, 60_000);
139
- });
@@ -1,124 +0,0 @@
1
- /**
2
- * Types for the group invitation flow.
3
- *
4
- * This flow allows admins to invite new members to a group using a simple
5
- * password. Multiple admins can listen for the same password simultaneously.
6
- */
7
- import type { GroupMemberRole } from "../data/group-member-roles";
8
- import type { IGroupMember } from "../data/group-members";
9
- import type { IGroupShare } from "../data/group-share";
10
- /**
11
- * Information about a group shared during the invitation flow.
12
- */
13
- export interface IGroupInviteGroupInfo {
14
- groupId: string;
15
- name: string;
16
- description?: string;
17
- iconClassName?: string;
18
- }
19
- /**
20
- * Information about the inviting admin shared during discovery.
21
- */
22
- export interface IGroupInviteListenerInfo {
23
- /** The admin's user ID */
24
- userId: string;
25
- /** The admin's display name */
26
- name: string;
27
- /** The admin's device ID */
28
- deviceId: string;
29
- /** Group information */
30
- group: IGroupInviteGroupInfo;
31
- /** The alias being listened on */
32
- alias: string;
33
- }
34
- /**
35
- * Information sent by a user requesting to join a group.
36
- */
37
- export interface IGroupJoinRequestInfo {
38
- /** Requesting user's ID */
39
- userId: string;
40
- /** Requesting user's display name */
41
- name?: string;
42
- /** Requesting user's public signing key */
43
- publicKey: string;
44
- /** Requesting user's public box key for encryption */
45
- publicBoxKey: string;
46
- /** Requesting user's device ID */
47
- deviceId: string;
48
- }
49
- /**
50
- * A pending join request shown to the admin.
51
- */
52
- export interface IGroupJoinRequest {
53
- /** Unique request ID */
54
- requestId: string;
55
- /** The group being joined */
56
- groupId: string;
57
- /** The alias this request came in on */
58
- alias: string;
59
- /** The password used (for encryption) */
60
- password: string;
61
- /** Information about the requesting user */
62
- requester: IGroupJoinRequestInfo;
63
- /** When the request was received */
64
- receivedAt: Date;
65
- }
66
- /**
67
- * Response sent by admin when approving a join request.
68
- */
69
- export interface IGroupJoinApprovalInfo {
70
- /** Whether the request was approved */
71
- approved: boolean;
72
- /** The role assigned to the new member (if approved) */
73
- role?: GroupMemberRole;
74
- /** Canonical group record plus Admin+ seed memberships */
75
- groupShare?: IGroupShare;
76
- /** Newly signed membership for the joiner, including Reader roles */
77
- newMember?: IGroupMember;
78
- /** Admin's user info for adding to contacts */
79
- adminInfo?: IGroupJoinRequestInfo;
80
- /** Error message if rejected */
81
- rejectionReason?: string;
82
- }
83
- /**
84
- * Signal that a joiner finished importing approval data and can connect to the admin.
85
- */
86
- export interface IGroupInviteJoinFinished {
87
- /** Group the joiner just imported */
88
- groupId: string;
89
- /** Approving admin device that should be contacted in that group context */
90
- adminDeviceId: string;
91
- }
92
- /**
93
- * Active invitation listener state.
94
- */
95
- export interface IGroupInviteListener {
96
- /** The group being invited to */
97
- groupId: string;
98
- /** The password being listened for */
99
- password: string;
100
- /** The derived alias for network discovery */
101
- alias: string;
102
- /** When listening started */
103
- startedAt: Date;
104
- }
105
- /**
106
- * Result of discovering group invite listeners.
107
- */
108
- export interface IGroupInviteDiscoveryResult {
109
- /** List of listeners found */
110
- listeners: IGroupInviteListenerInfo[];
111
- /** Any error that occurred */
112
- error?: string;
113
- }
114
- /**
115
- * Result of a join request.
116
- */
117
- export interface IGroupJoinResult {
118
- /** Whether the join was successful */
119
- success: boolean;
120
- /** The group ID if successful */
121
- groupId?: string;
122
- /** Error message if failed */
123
- error?: string;
124
- }
@@ -1,8 +0,0 @@
1
- "use strict";
2
- /**
3
- * Types for the group invitation flow.
4
- *
5
- * This flow allows admins to invite new members to a group using a simple
6
- * password. Multiple admins can listen for the same password simultaneously.
7
- */
8
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,3 +0,0 @@
1
- export * from "./group-invite";
2
- export * from "./group-invite.pvars";
3
- export * from "./group-invite.types";
@@ -1,67 +0,0 @@
1
- /**
2
- * Utilities for generating and handling connection codes.
3
- *
4
- * Connection codes are 12 characters of Crockford Base32:
5
- * - First 4 chars: device alias (rendezvous point)
6
- * - Last 8 chars: shared secret for encryption
7
- *
8
- * Displayed format: XXXX-YYYY-ZZZZ
9
- */
10
- import type { IUserConnectInfo } from "./user-connect.types";
11
- export interface IConnectionCode {
12
- /** Full 12-character code */
13
- code: string;
14
- /** First 4 characters - device alias for rendezvous */
15
- alias: string;
16
- /** Last 8 characters - shared secret for encryption */
17
- secret: string;
18
- }
19
- /**
20
- * Generate a new connection code.
21
- * @returns Object with full code, alias, and secret
22
- */
23
- export declare function generateConnectionCode(): IConnectionCode;
24
- /**
25
- * Format a connection code for display.
26
- * @param code 12-character code
27
- * @returns Formatted as "XXXX-YYYY-ZZZZ"
28
- */
29
- export declare function formatConnectionCode(code: string): string;
30
- /**
31
- * Parse a formatted connection code.
32
- * @param formatted Code in any format (with or without dashes)
33
- * @returns Object with alias and secret
34
- */
35
- export declare function parseConnectionCode(formatted: string): {
36
- code: string;
37
- alias: string;
38
- secret: string;
39
- };
40
- /**
41
- * Encrypt data using the shared secret.
42
- * @param data Data to encrypt
43
- * @param secret 8-character shared secret
44
- * @returns Base64-encoded encrypted data with nonce
45
- */
46
- export declare function encryptWithSecret(data: any, secret: string): string;
47
- /**
48
- * Decrypt data using the shared secret.
49
- * @param encrypted Base64-encoded encrypted data
50
- * @param secret 8-character shared secret
51
- * @returns Decrypted data
52
- */
53
- export declare function decryptWithSecret(encrypted: string, secret: string): any;
54
- /**
55
- * Generate a confirmation hash from both users' information.
56
- * Both parties should see the same hash if the exchange was successful.
57
- * @param userA First user's info
58
- * @param userB Second user's info
59
- * @returns 4-character confirmation code
60
- */
61
- export declare function generateConfirmationHash(userA: IUserConnectInfo, userB: IUserConnectInfo): string;
62
- /**
63
- * Validate that a string is a valid connection code format.
64
- * @param code Code to validate (with or without dashes)
65
- * @returns true if valid
66
- */
67
- export declare function isValidConnectionCode(code: string): boolean;
@@ -1,176 +0,0 @@
1
- "use strict";
2
- /**
3
- * Utilities for generating and handling connection codes.
4
- *
5
- * Connection codes are 12 characters of Crockford Base32:
6
- * - First 4 chars: device alias (rendezvous point)
7
- * - Last 8 chars: shared secret for encryption
8
- *
9
- * Displayed format: XXXX-YYYY-ZZZZ
10
- */
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.generateConnectionCode = generateConnectionCode;
13
- exports.formatConnectionCode = formatConnectionCode;
14
- exports.parseConnectionCode = parseConnectionCode;
15
- exports.encryptWithSecret = encryptWithSecret;
16
- exports.decryptWithSecret = decryptWithSecret;
17
- exports.generateConfirmationHash = generateConfirmationHash;
18
- exports.isValidConnectionCode = isValidConnectionCode;
19
- const sha2_1 = require("@noble/hashes/sha2");
20
- const nacl = require("tweetnacl");
21
- const tx_encoding_1 = require("../device/tx-encoding");
22
- const keys_1 = require("../keys");
23
- /**
24
- * Crockford Base32 alphabet.
25
- * Excludes I, L, O, U to avoid confusion with 1, 1, 0, V respectively.
26
- */
27
- const CROCKFORD_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
28
- /**
29
- * Generate random bytes using nacl.
30
- */
31
- function randomBytes(length) {
32
- return nacl.randomBytes(length);
33
- }
34
- /**
35
- * Encode bytes to Crockford Base32 string.
36
- */
37
- function toCrockfordBase32(bytes, length) {
38
- let result = "";
39
- let i = 0;
40
- while (result.length < length) {
41
- if (i >= bytes.length) {
42
- // Need more bytes
43
- const moreBytes = randomBytes(length - result.length);
44
- for (const b of moreBytes) {
45
- result += CROCKFORD_ALPHABET[b % 32];
46
- if (result.length >= length)
47
- break;
48
- }
49
- break;
50
- }
51
- // Use 5 bits at a time for base32
52
- result += CROCKFORD_ALPHABET[bytes[i] % 32];
53
- i++;
54
- }
55
- return result.substring(0, length);
56
- }
57
- /**
58
- * Derive a 32-byte encryption key from the shared secret.
59
- */
60
- function deriveKeyFromSecret(secret) {
61
- // Use SHA-256 to derive a proper 32-byte key from the secret
62
- const secretBytes = new TextEncoder().encode(secret);
63
- return (0, sha2_1.sha256)(secretBytes);
64
- }
65
- /**
66
- * Generate a new connection code.
67
- * @returns Object with full code, alias, and secret
68
- */
69
- function generateConnectionCode() {
70
- const bytes = randomBytes(16);
71
- const alias = toCrockfordBase32(bytes.slice(0, 4), 4);
72
- const secret = toCrockfordBase32(bytes.slice(4), 8);
73
- return {
74
- code: alias + secret,
75
- alias,
76
- secret,
77
- };
78
- }
79
- /**
80
- * Format a connection code for display.
81
- * @param code 12-character code
82
- * @returns Formatted as "XXXX-YYYY-ZZZZ"
83
- */
84
- function formatConnectionCode(code) {
85
- const normalized = code.toUpperCase().replace(/[^0-9A-Z]/g, "");
86
- if (normalized.length !== 12) {
87
- throw new Error(`Connection code must be 12 characters, got ${normalized.length}`);
88
- }
89
- return `${normalized.slice(0, 4)}-${normalized.slice(4, 8)}-${normalized.slice(8, 12)}`;
90
- }
91
- /**
92
- * Parse a formatted connection code.
93
- * @param formatted Code in any format (with or without dashes)
94
- * @returns Object with alias and secret
95
- */
96
- function parseConnectionCode(formatted) {
97
- const normalized = formatted.toUpperCase().replace(/[^0-9A-Z]/g, "");
98
- if (normalized.length !== 12) {
99
- throw new Error(`Connection code must be 12 characters, got ${normalized.length}`);
100
- }
101
- return {
102
- code: normalized,
103
- alias: normalized.slice(0, 4),
104
- secret: normalized.slice(4, 12),
105
- };
106
- }
107
- /**
108
- * Encrypt data using the shared secret.
109
- * @param data Data to encrypt
110
- * @param secret 8-character shared secret
111
- * @returns Base64-encoded encrypted data with nonce
112
- */
113
- function encryptWithSecret(data, secret) {
114
- const key = deriveKeyFromSecret(secret);
115
- const nonce = nacl.randomBytes(24);
116
- const dataBytes = (0, tx_encoding_1.txEncode)(data);
117
- const encrypted = nacl.secretbox(dataBytes, nonce, key);
118
- // Combine nonce + encrypted data
119
- const combined = new Uint8Array(nonce.length + encrypted.length);
120
- combined.set(nonce);
121
- combined.set(encrypted, nonce.length);
122
- return (0, keys_1.encodeBase64)(combined);
123
- }
124
- /**
125
- * Decrypt data using the shared secret.
126
- * @param encrypted Base64-encoded encrypted data
127
- * @param secret 8-character shared secret
128
- * @returns Decrypted data
129
- */
130
- function decryptWithSecret(encrypted, secret) {
131
- const key = deriveKeyFromSecret(secret);
132
- const combined = (0, keys_1.decodeBase64)(encrypted);
133
- const nonce = combined.slice(0, 24);
134
- const ciphertext = combined.slice(24);
135
- const decrypted = nacl.secretbox.open(ciphertext, nonce, key);
136
- if (!decrypted) {
137
- throw new Error("Decryption failed - invalid secret or corrupted data");
138
- }
139
- return (0, tx_encoding_1.txDecode)(decrypted);
140
- }
141
- /**
142
- * Generate a confirmation hash from both users' information.
143
- * Both parties should see the same hash if the exchange was successful.
144
- * @param userA First user's info
145
- * @param userB Second user's info
146
- * @returns 4-character confirmation code
147
- */
148
- function generateConfirmationHash(userA, userB) {
149
- // Sort by userId to ensure consistent ordering regardless of who is A or B
150
- const sorted = [userA, userB].sort((a, b) => a.userId.localeCompare(b.userId));
151
- const combined = JSON.stringify(sorted);
152
- const hash = (0, sha2_1.sha256)(new TextEncoder().encode(combined));
153
- // Take first 4 characters of the hash encoded in Crockford Base32
154
- return toCrockfordBase32(hash.slice(0, 4), 4);
155
- }
156
- /**
157
- * Validate that a string is a valid connection code format.
158
- * @param code Code to validate (with or without dashes)
159
- * @returns true if valid
160
- */
161
- function isValidConnectionCode(code) {
162
- try {
163
- const normalized = code.toUpperCase().replace(/[^0-9A-Z]/g, "");
164
- if (normalized.length !== 12)
165
- return false;
166
- // Check all characters are valid Crockford Base32
167
- for (const char of normalized) {
168
- if (!CROCKFORD_ALPHABET.includes(char))
169
- return false;
170
- }
171
- return true;
172
- }
173
- catch {
174
- return false;
175
- }
176
- }