@peers-app/peers-sdk 0.16.4 → 0.16.5
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.js +6 -0
- package/dist/data/persistent-vars.js +14 -2
- package/dist/events.d.ts +13 -0
- package/dist/events.js +218 -34
- package/dist/rpc-types.d.ts +4 -0
- package/dist/rpc-types.js +5 -4
- package/dist/utils.d.ts +15 -0
- package/dist/utils.js +39 -0
- package/package.json +1 -1
|
@@ -238,6 +238,7 @@ class UserContext {
|
|
|
238
238
|
subscribeToDataChangedAcrossAllGroups(table, handler) {
|
|
239
239
|
const tableName = typeof table === "string" ? table : table.tableName;
|
|
240
240
|
const tableEventPrefix = `${tableName}_DataChanged_`;
|
|
241
|
+
const unsubscribePrefix = (0, events_1.subscribePrefix)(tableEventPrefix);
|
|
241
242
|
const subscription = (0, events_1.subscribe)((evt) => evt.name.startsWith(tableEventPrefix), async (evt) => {
|
|
242
243
|
const dataContextId = evt.name.endsWith("_") ? this.userId : evt.name.split("_").pop();
|
|
243
244
|
const dataContext = this.getDataContext(dataContextId);
|
|
@@ -248,6 +249,11 @@ class UserContext {
|
|
|
248
249
|
data: evt.data,
|
|
249
250
|
});
|
|
250
251
|
});
|
|
252
|
+
const originalUnsubscribe = subscription.unsubscribe;
|
|
253
|
+
subscription.unsubscribe = () => {
|
|
254
|
+
unsubscribePrefix();
|
|
255
|
+
return originalUnsubscribe();
|
|
256
|
+
};
|
|
251
257
|
return subscription;
|
|
252
258
|
}
|
|
253
259
|
dispose() {
|
|
@@ -58,10 +58,12 @@ class PersistentVarsTable extends table_1.Table {
|
|
|
58
58
|
}
|
|
59
59
|
async save(persistentVar, opts) {
|
|
60
60
|
if (!persistentVar.persistentVarId) {
|
|
61
|
-
persistentVar.persistentVarId = (0, utils_1.
|
|
61
|
+
persistentVar.persistentVarId = (0, utils_1.deterministicPvarId)(persistentVar.name);
|
|
62
62
|
}
|
|
63
63
|
const dbVar = await this.get(persistentVar.persistentVarId);
|
|
64
|
-
if (persistentVar.isSecret &&
|
|
64
|
+
if (persistentVar.isSecret &&
|
|
65
|
+
persistentVar.value.value !== undefined &&
|
|
66
|
+
(!rpc_types_1.isClient || (0, rpc_types_1.isSingleProcessClient)())) {
|
|
65
67
|
if (!dbVar ||
|
|
66
68
|
dbVar.value.value !== persistentVar.value.value ||
|
|
67
69
|
dbVar.isSecret !== persistentVar.isSecret) {
|
|
@@ -204,6 +206,12 @@ function persistentVarFactory(name, opts) {
|
|
|
204
206
|
dbRec = undefined;
|
|
205
207
|
}
|
|
206
208
|
}
|
|
209
|
+
if (dbRec && !(0, utils_1.isDeterministicPvarId)(dbRec.persistentVarId)) {
|
|
210
|
+
const newId = (0, utils_1.deterministicPvarId)(name);
|
|
211
|
+
await table.delete(dbRec);
|
|
212
|
+
dbRec.persistentVarId = newId;
|
|
213
|
+
dbRec = await table.save(dbRec);
|
|
214
|
+
}
|
|
207
215
|
if (!dbRec) {
|
|
208
216
|
dbRec = {
|
|
209
217
|
persistentVarId: "",
|
|
@@ -271,6 +279,10 @@ function persistentVarFactory(name, opts) {
|
|
|
271
279
|
};
|
|
272
280
|
// subscribe to db changes
|
|
273
281
|
userContext.subscribeToDataChangedAcrossAllGroups(exports.persistentVarsMetaData.name, async (evt) => {
|
|
282
|
+
const dc = getDataContext();
|
|
283
|
+
if (dc && evt.dataContext && evt.dataContext.dataContextId !== dc.dataContextId) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
274
286
|
const dbRec = evt.data.dataObject;
|
|
275
287
|
const dbName = getVarNameInDb();
|
|
276
288
|
if (!rec?.persistentVarId && dbRec.name === dbName) {
|
package/dist/events.d.ts
CHANGED
|
@@ -4,10 +4,23 @@ export type IEventHandler<T = any> = (eventData: IEventData<T>) => boolean | voi
|
|
|
4
4
|
export interface ISubscription {
|
|
5
5
|
filter: IEventFilter;
|
|
6
6
|
handler: IEventHandler;
|
|
7
|
+
/** When set, this subscription matches only events with this exact name (fast-path via Map lookup). */
|
|
8
|
+
eventName?: string;
|
|
7
9
|
}
|
|
8
10
|
export interface ISubscriptionResult {
|
|
9
11
|
unsubscribe: () => boolean;
|
|
10
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Notify the events system that the client connection is ready.
|
|
15
|
+
* Flushes any subscriptions that were registered before the connection.
|
|
16
|
+
*/
|
|
17
|
+
export declare function notifyClientConnected(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Register a prefix-based event subscription with the server.
|
|
20
|
+
* Events whose name starts with this prefix will be forwarded to the client.
|
|
21
|
+
* Returns an unsubscribe function that decrements the refcount.
|
|
22
|
+
*/
|
|
23
|
+
export declare function subscribePrefix(prefix: string): () => void;
|
|
11
24
|
export declare function subscribe<T = any>(nameOrFilter: string | IEventFilter, handler: IEventHandler<T>): ISubscriptionResult;
|
|
12
25
|
export declare function subscribeDebounce<T = any>(nameOrFilter: string | IEventFilter, handler: IEventHandler<T>, debounceMs: number): ISubscriptionResult;
|
|
13
26
|
export declare function emit(event: IEventData, dontPropagate?: boolean): Promise<boolean>;
|
package/dist/events.js
CHANGED
|
@@ -1,37 +1,195 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Emitter = exports.Event = void 0;
|
|
4
|
+
exports.notifyClientConnected = notifyClientConnected;
|
|
5
|
+
exports.subscribePrefix = subscribePrefix;
|
|
4
6
|
exports.subscribe = subscribe;
|
|
5
7
|
exports.subscribeDebounce = subscribeDebounce;
|
|
6
8
|
exports.emit = emit;
|
|
7
9
|
exports.unionEvents = unionEvents;
|
|
8
10
|
const rpc_types_1 = require("./rpc-types");
|
|
9
11
|
const unitTests = process.env.NODE_ENV === "test";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
// ─── Subscription Storage ────────────────────────────────────────────────────
|
|
13
|
+
/** Subscriptions indexed by exact event name for O(1) dispatch. */
|
|
14
|
+
const nameIndex = new Map();
|
|
15
|
+
/** Subscriptions that use custom filter functions (checked on every emit). */
|
|
16
|
+
const wildcardSubscriptions = new Set();
|
|
17
|
+
function addSubscription(subscription) {
|
|
18
|
+
if (subscription.eventName) {
|
|
19
|
+
let bucket = nameIndex.get(subscription.eventName);
|
|
20
|
+
if (!bucket) {
|
|
21
|
+
bucket = new Set();
|
|
22
|
+
nameIndex.set(subscription.eventName, bucket);
|
|
23
|
+
}
|
|
24
|
+
bucket.add(subscription);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
wildcardSubscriptions.add(subscription);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function removeSubscription(subscription) {
|
|
31
|
+
if (subscription.eventName) {
|
|
32
|
+
const bucket = nameIndex.get(subscription.eventName);
|
|
33
|
+
if (bucket) {
|
|
34
|
+
const removed = bucket.delete(subscription);
|
|
35
|
+
if (bucket.size === 0) {
|
|
36
|
+
nameIndex.delete(subscription.eventName);
|
|
37
|
+
}
|
|
38
|
+
return removed;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return wildcardSubscriptions.delete(subscription);
|
|
43
|
+
}
|
|
44
|
+
// ─── Client → Server Subscription Registration ──────────────────────────────
|
|
45
|
+
// In multi-process mode (Electron), the client tells the server which event
|
|
46
|
+
// names/prefixes it cares about so the server can skip forwarding unneeded events.
|
|
47
|
+
/** Reference counts for event names registered with the server. */
|
|
48
|
+
const clientNameRefCounts = new Map();
|
|
49
|
+
/** Reference counts for event prefixes registered with the server. */
|
|
50
|
+
const clientPrefixRefCounts = new Map();
|
|
51
|
+
/** Whether the client-server connection is established (for deferred registration). */
|
|
52
|
+
let clientConnected = false;
|
|
53
|
+
/** Names pending registration (accumulated before connection is ready). */
|
|
54
|
+
const pendingNameSubscriptions = new Set();
|
|
55
|
+
/** Prefixes pending registration (accumulated before connection is ready). */
|
|
56
|
+
const pendingPrefixSubscriptions = new Set();
|
|
57
|
+
/**
|
|
58
|
+
* Notify the events system that the client connection is ready.
|
|
59
|
+
* Flushes any subscriptions that were registered before the connection.
|
|
60
|
+
*/
|
|
61
|
+
function notifyClientConnected() {
|
|
62
|
+
clientConnected = true;
|
|
63
|
+
if (pendingNameSubscriptions.size > 0) {
|
|
64
|
+
const names = [...pendingNameSubscriptions];
|
|
65
|
+
pendingNameSubscriptions.clear();
|
|
66
|
+
rpc_types_1.rpcServerCalls.subscribeEvents(names).catch((err) => {
|
|
67
|
+
console.error("Failed to register pending event subscriptions with server", err);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (pendingPrefixSubscriptions.size > 0) {
|
|
71
|
+
const prefixes = [...pendingPrefixSubscriptions];
|
|
72
|
+
pendingPrefixSubscriptions.clear();
|
|
73
|
+
rpc_types_1.rpcServerCalls.subscribePrefixes(prefixes).catch((err) => {
|
|
74
|
+
console.error("Failed to register pending prefix subscriptions with server", err);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function clientRegisterName(name) {
|
|
79
|
+
if (unitTests || (0, rpc_types_1.isSingleProcessClient)())
|
|
80
|
+
return;
|
|
81
|
+
const count = clientNameRefCounts.get(name) || 0;
|
|
82
|
+
clientNameRefCounts.set(name, count + 1);
|
|
83
|
+
if (count === 0) {
|
|
84
|
+
if (clientConnected) {
|
|
85
|
+
rpc_types_1.rpcServerCalls.subscribeEvents([name]).catch((err) => {
|
|
86
|
+
console.error(`Failed to register event subscription with server: ${name}`, err);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
pendingNameSubscriptions.add(name);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function clientUnregisterName(name) {
|
|
95
|
+
if (unitTests || (0, rpc_types_1.isSingleProcessClient)())
|
|
96
|
+
return;
|
|
97
|
+
const count = clientNameRefCounts.get(name) || 0;
|
|
98
|
+
if (count <= 1) {
|
|
99
|
+
clientNameRefCounts.delete(name);
|
|
100
|
+
pendingNameSubscriptions.delete(name);
|
|
101
|
+
if (clientConnected) {
|
|
102
|
+
rpc_types_1.rpcServerCalls.unsubscribeEvents([name]).catch((err) => {
|
|
103
|
+
console.error(`Failed to unregister event subscription with server: ${name}`, err);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
clientNameRefCounts.set(name, count - 1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Register a prefix-based event subscription with the server.
|
|
113
|
+
* Events whose name starts with this prefix will be forwarded to the client.
|
|
114
|
+
* Returns an unsubscribe function that decrements the refcount.
|
|
115
|
+
*/
|
|
116
|
+
function subscribePrefix(prefix) {
|
|
117
|
+
if (!rpc_types_1.isClient || unitTests || (0, rpc_types_1.isSingleProcessClient)()) {
|
|
118
|
+
return () => { };
|
|
119
|
+
}
|
|
120
|
+
const count = clientPrefixRefCounts.get(prefix) || 0;
|
|
121
|
+
clientPrefixRefCounts.set(prefix, count + 1);
|
|
122
|
+
if (count === 0) {
|
|
123
|
+
if (clientConnected) {
|
|
124
|
+
rpc_types_1.rpcServerCalls.subscribePrefixes([prefix]).catch((err) => {
|
|
125
|
+
console.error(`Failed to register prefix subscription with server: ${prefix}`, err);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
pendingPrefixSubscriptions.add(prefix);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return () => {
|
|
133
|
+
const current = clientPrefixRefCounts.get(prefix) || 0;
|
|
134
|
+
if (current <= 1) {
|
|
135
|
+
clientPrefixRefCounts.delete(prefix);
|
|
136
|
+
pendingPrefixSubscriptions.delete(prefix);
|
|
137
|
+
if (clientConnected) {
|
|
138
|
+
rpc_types_1.rpcServerCalls.unsubscribePrefixes([prefix]).catch((err) => {
|
|
139
|
+
console.error(`Failed to unregister prefix subscription with server: ${prefix}`, err);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
clientPrefixRefCounts.set(prefix, current - 1);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// ─── Server-Side Event Forwarding Gate ───────────────────────────────────────
|
|
149
|
+
// The server uses this to decide whether to forward an event to the client.
|
|
150
|
+
/** Event names the client has subscribed to. Server-side only. */
|
|
151
|
+
const serverClientNames = new Set();
|
|
152
|
+
/** Event prefixes the client has subscribed to. Server-side only. */
|
|
153
|
+
const serverClientPrefixes = new Set();
|
|
154
|
+
/**
|
|
155
|
+
* Check whether the client wants this event.
|
|
156
|
+
* Used by the server to gate `rpcClientCalls.emitEvent`.
|
|
157
|
+
*/
|
|
158
|
+
function clientWantsEvent(event) {
|
|
159
|
+
if (serverClientNames.has(event.name))
|
|
160
|
+
return true;
|
|
161
|
+
for (const prefix of serverClientPrefixes) {
|
|
162
|
+
if (event.name.startsWith(prefix))
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
12
168
|
function subscribe(nameOrFilter, handler) {
|
|
169
|
+
let eventName;
|
|
170
|
+
let filter;
|
|
13
171
|
if (typeof nameOrFilter === "string") {
|
|
14
|
-
|
|
15
|
-
|
|
172
|
+
eventName = nameOrFilter;
|
|
173
|
+
filter = (evt) => evt.name === eventName;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
filter = nameOrFilter;
|
|
177
|
+
}
|
|
178
|
+
const subscription = { filter, handler, eventName };
|
|
179
|
+
addSubscription(subscription);
|
|
180
|
+
if (rpc_types_1.isClient && eventName) {
|
|
181
|
+
clientRegisterName(eventName);
|
|
16
182
|
}
|
|
17
|
-
const filter = nameOrFilter;
|
|
18
|
-
const subscription = {
|
|
19
|
-
filter,
|
|
20
|
-
handler,
|
|
21
|
-
};
|
|
22
|
-
subscriptions.push(subscription);
|
|
23
183
|
return {
|
|
24
184
|
unsubscribe: () => {
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
return true;
|
|
185
|
+
const removed = removeSubscription(subscription);
|
|
186
|
+
if (removed && rpc_types_1.isClient && eventName) {
|
|
187
|
+
clientUnregisterName(eventName);
|
|
29
188
|
}
|
|
30
|
-
return
|
|
189
|
+
return removed;
|
|
31
190
|
},
|
|
32
191
|
};
|
|
33
192
|
}
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
193
|
function subscribeDebounce(nameOrFilter, handler, debounceMs) {
|
|
36
194
|
let pid = 0;
|
|
37
195
|
const handlerDebounced = (evt) => {
|
|
@@ -46,37 +204,64 @@ function subscribeDebounce(nameOrFilter, handler, debounceMs) {
|
|
|
46
204
|
return subscribe(nameOrFilter, handlerDebounced);
|
|
47
205
|
}
|
|
48
206
|
async function emit(event, dontPropagate) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
207
|
+
const matched = [];
|
|
208
|
+
const bucket = nameIndex.get(event.name);
|
|
209
|
+
if (bucket) {
|
|
210
|
+
for (const sub of bucket) {
|
|
211
|
+
matched.push(sub);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const sub of wildcardSubscriptions) {
|
|
215
|
+
if (sub.filter(event)) {
|
|
216
|
+
matched.push(sub);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const handlerPromises = matched.map(async (subscription) => {
|
|
52
220
|
try {
|
|
53
221
|
return await subscription.handler(event);
|
|
54
222
|
}
|
|
55
223
|
catch (err) {
|
|
56
|
-
console.error(`An unhandled error occurred in a handler while processing event: ${JSON.stringify({ event
|
|
224
|
+
console.error(`An unhandled error occurred in a handler while processing event: ${JSON.stringify({ event })}`, err);
|
|
57
225
|
return false;
|
|
58
226
|
}
|
|
59
227
|
});
|
|
60
|
-
// if (!(dontPropagate || unitTests)) {
|
|
61
|
-
// matchedHandlerPromises.push(
|
|
62
|
-
// isClient
|
|
63
|
-
// ? rpcServerCalls.emitEvent(event).catch((err) => { console.error(`Error while propagating event to server`, err); return false; })
|
|
64
|
-
// : rpcClientCalls.emitEvent(event).catch((err) => { console.error(`Error while propagating event to client`, err); return false; })
|
|
65
|
-
// );
|
|
66
|
-
// }
|
|
67
228
|
if (!(dontPropagate || unitTests || (0, rpc_types_1.isSingleProcessClient)())) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
229
|
+
if (clientWantsEvent(event)) {
|
|
230
|
+
handlerPromises.push(rpc_types_1.rpcClientCalls.emitEvent(event).catch((err) => {
|
|
231
|
+
console.error(`Error while propagating event to client`, err);
|
|
232
|
+
return false;
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
72
235
|
}
|
|
73
|
-
const results = await Promise.all(
|
|
74
|
-
// if any handlers returned false (or errored), return false, otherwise return true
|
|
236
|
+
const results = await Promise.all(handlerPromises);
|
|
75
237
|
return !results.some((r) => r === false);
|
|
76
238
|
}
|
|
77
239
|
if (rpc_types_1.isClient) {
|
|
78
240
|
rpc_types_1.rpcClientCalls.emitEvent = (event) => emit(event, true);
|
|
79
241
|
}
|
|
242
|
+
// Server-side: register RPC handlers for client subscription management
|
|
243
|
+
if (!rpc_types_1.isClient) {
|
|
244
|
+
rpc_types_1.rpcServerCalls.subscribeEvents = async (names) => {
|
|
245
|
+
for (const name of names) {
|
|
246
|
+
serverClientNames.add(name);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
rpc_types_1.rpcServerCalls.unsubscribeEvents = async (names) => {
|
|
250
|
+
for (const name of names) {
|
|
251
|
+
serverClientNames.delete(name);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
rpc_types_1.rpcServerCalls.subscribePrefixes = async (prefixes) => {
|
|
255
|
+
for (const prefix of prefixes) {
|
|
256
|
+
serverClientPrefixes.add(prefix);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
rpc_types_1.rpcServerCalls.unsubscribePrefixes = async (prefixes) => {
|
|
260
|
+
for (const prefix of prefixes) {
|
|
261
|
+
serverClientPrefixes.delete(prefix);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
80
265
|
class Event {
|
|
81
266
|
_eventName;
|
|
82
267
|
constructor(_eventName) {
|
|
@@ -119,7 +304,6 @@ class Emitter {
|
|
|
119
304
|
}
|
|
120
305
|
}
|
|
121
306
|
exports.Emitter = Emitter;
|
|
122
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
307
|
function unionEvents(...events) {
|
|
124
308
|
const eventName = events.map((s) => s.eventName()).join("|");
|
|
125
309
|
return {
|
package/dist/rpc-types.d.ts
CHANGED
|
@@ -116,6 +116,10 @@ export declare const rpcServerCalls: {
|
|
|
116
116
|
voiceDisable: () => Promise<void>;
|
|
117
117
|
voiceEnable: () => Promise<void>;
|
|
118
118
|
flushDatabases: () => Promise<void>;
|
|
119
|
+
subscribeEvents: (names: string[]) => Promise<void>;
|
|
120
|
+
unsubscribeEvents: (names: string[]) => Promise<void>;
|
|
121
|
+
subscribePrefixes: (prefixes: string[]) => Promise<void>;
|
|
122
|
+
unsubscribePrefixes: (prefixes: string[]) => Promise<void>;
|
|
119
123
|
};
|
|
120
124
|
export declare const rpcClientCalls: {
|
|
121
125
|
ping: (msg: string) => Promise<string>;
|
package/dist/rpc-types.js
CHANGED
|
@@ -67,10 +67,11 @@ exports.rpcServerCalls = {
|
|
|
67
67
|
// Flush all in-memory database snapshots to durable storage (IndexedDB in PWA).
|
|
68
68
|
// No-op on Electron where better-sqlite3 writes are synchronous to disk.
|
|
69
69
|
flushDatabases: (async () => { }),
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
// Event subscription management (for selective proxying)
|
|
71
|
+
subscribeEvents: rpcStub("subscribeEvents"),
|
|
72
|
+
unsubscribeEvents: rpcStub("unsubscribeEvents"),
|
|
73
|
+
subscribePrefixes: rpcStub("subscribePrefixes"),
|
|
74
|
+
unsubscribePrefixes: rpcStub("unsubscribePrefixes"),
|
|
74
75
|
};
|
|
75
76
|
exports.rpcClientCalls = {
|
|
76
77
|
ping: async (msg) => `pong: ${msg}`,
|
package/dist/utils.d.ts
CHANGED
|
@@ -31,6 +31,21 @@ export declare function simpleHash(str: string): number;
|
|
|
31
31
|
export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
32
32
|
/** `simpleHash` of JSON-stable stringification of an object. */
|
|
33
33
|
export declare function simpleObjectHash(obj: any): number;
|
|
34
|
+
/**
|
|
35
|
+
* Generates a deterministic 25-character peer ID for a persistent variable.
|
|
36
|
+
* The ID has the fixed prefix `000pvar0` (8 chars) followed by 17 base-36 characters
|
|
37
|
+
* derived from a SHA-512 hash of the input name. Two calls with the same name always
|
|
38
|
+
* return the same ID.
|
|
39
|
+
*
|
|
40
|
+
* @param name The persistent variable name as stored in the database
|
|
41
|
+
* (already includes group suffixes for groupDevice/groupUser scopes)
|
|
42
|
+
*/
|
|
43
|
+
export declare function deterministicPvarId(name: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Returns whether a peer ID was generated by {@link deterministicPvarId}
|
|
46
|
+
* (starts with the `000pvar0` prefix).
|
|
47
|
+
*/
|
|
48
|
+
export declare function isDeterministicPvarId(id: string): boolean;
|
|
34
49
|
/** Resolves after `ms` milliseconds (0 yields a microtask-yield on most runtimes). */
|
|
35
50
|
export declare function sleep(ms?: number): Promise<void>;
|
|
36
51
|
/** Turns `fooBar` into `Foo Bar` for labels (also replaces `_` with space). */
|
package/dist/utils.js
CHANGED
|
@@ -9,6 +9,8 @@ exports.idDate = idDate;
|
|
|
9
9
|
exports.idRandNums = idRandNums;
|
|
10
10
|
exports.simpleHash = simpleHash;
|
|
11
11
|
exports.simpleObjectHash = simpleObjectHash;
|
|
12
|
+
exports.deterministicPvarId = deterministicPvarId;
|
|
13
|
+
exports.isDeterministicPvarId = isDeterministicPvarId;
|
|
12
14
|
exports.sleep = sleep;
|
|
13
15
|
exports.camelCaseToSpaces = camelCaseToSpaces;
|
|
14
16
|
exports.camelCaseToHyphens = camelCaseToHyphens;
|
|
@@ -119,6 +121,43 @@ function simpleHash(str) {
|
|
|
119
121
|
function simpleObjectHash(obj) {
|
|
120
122
|
return simpleHash(stableStringify(obj));
|
|
121
123
|
}
|
|
124
|
+
/** Well-known prefix for deterministic persistent variable IDs. */
|
|
125
|
+
const DETERMINISTIC_PVAR_PREFIX = "000pvar0";
|
|
126
|
+
/**
|
|
127
|
+
* Converts a byte array to a base-36 string of the requested length, using unbiased modular reduction.
|
|
128
|
+
* Bytes >= 252 are skipped to avoid bias (same technique as {@link cryptoRandomString}).
|
|
129
|
+
*/
|
|
130
|
+
function bytesToBase36(bytes, length) {
|
|
131
|
+
let s = "";
|
|
132
|
+
for (let i = 0; i < bytes.length && s.length < length; i++) {
|
|
133
|
+
const n = bytes[i];
|
|
134
|
+
if (n >= 252)
|
|
135
|
+
continue;
|
|
136
|
+
s += (n % 36).toString(36);
|
|
137
|
+
}
|
|
138
|
+
return s.substring(0, length);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Generates a deterministic 25-character peer ID for a persistent variable.
|
|
142
|
+
* The ID has the fixed prefix `000pvar0` (8 chars) followed by 17 base-36 characters
|
|
143
|
+
* derived from a SHA-512 hash of the input name. Two calls with the same name always
|
|
144
|
+
* return the same ID.
|
|
145
|
+
*
|
|
146
|
+
* @param name The persistent variable name as stored in the database
|
|
147
|
+
* (already includes group suffixes for groupDevice/groupUser scopes)
|
|
148
|
+
*/
|
|
149
|
+
function deterministicPvarId(name) {
|
|
150
|
+
const hashSuffixLength = 25 - DETERMINISTIC_PVAR_PREFIX.length; // 17
|
|
151
|
+
const hash = nacl.hash(new TextEncoder().encode(name)); // 64-byte SHA-512
|
|
152
|
+
return DETERMINISTIC_PVAR_PREFIX + bytesToBase36(hash, hashSuffixLength);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Returns whether a peer ID was generated by {@link deterministicPvarId}
|
|
156
|
+
* (starts with the `000pvar0` prefix).
|
|
157
|
+
*/
|
|
158
|
+
function isDeterministicPvarId(id) {
|
|
159
|
+
return id.startsWith(DETERMINISTIC_PVAR_PREFIX);
|
|
160
|
+
}
|
|
122
161
|
/** Resolves after `ms` milliseconds (0 yields a microtask-yield on most runtimes). */
|
|
123
162
|
function sleep(ms = 0) {
|
|
124
163
|
return new Promise((resolve) => setTimeout(resolve, ms));
|