@peers-app/peers-sdk 0.8.10 → 0.8.12
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/data/persistent-vars.d.ts +2 -0
- package/dist/data/persistent-vars.js +32 -25
- package/dist/device/binary-peer-connection.js +25 -107
- package/dist/rpc-types.d.ts +13 -0
- package/dist/rpc-types.js +12 -0
- package/package.json +1 -1
|
@@ -60,6 +60,8 @@ export declare class PersistentVarsTable extends Table<IPersistentVar> {
|
|
|
60
60
|
export declare function PersistentVars(dataContext?: DataContext): PersistentVarsTable;
|
|
61
61
|
export type PersistentVar<T> = Observable<T> & {
|
|
62
62
|
loadingPromise: Promise<PersistentVar<T>>;
|
|
63
|
+
/** Delete the persistent variable from the database and reset to default value */
|
|
64
|
+
delete: () => Promise<void>;
|
|
63
65
|
};
|
|
64
66
|
export declare function getPersistentVar(name: string, dataContext?: DataContext): Promise<IPersistentVar | undefined>;
|
|
65
67
|
interface IPersistentVarOptionsBase {
|
|
@@ -155,6 +155,14 @@ function persistentVarFactory(name, opts) {
|
|
|
155
155
|
pvarCache.set(cacheKey, persistentVar);
|
|
156
156
|
let isSecret = opts.isSecret;
|
|
157
157
|
let rec = undefined;
|
|
158
|
+
let deleteVarImpl = undefined;
|
|
159
|
+
// Assign the delete method - it will wait for loading and then call the implementation
|
|
160
|
+
persistentVar.delete = async () => {
|
|
161
|
+
await persistentVar.loadingPromise;
|
|
162
|
+
if (deleteVarImpl) {
|
|
163
|
+
await deleteVarImpl();
|
|
164
|
+
}
|
|
165
|
+
};
|
|
158
166
|
persistentVar.loadingPromise = new Promise(async (resolve) => {
|
|
159
167
|
try {
|
|
160
168
|
const userContext = opts?.userContext || await (0, user_context_singleton_1.getUserContext)();
|
|
@@ -224,37 +232,36 @@ function persistentVarFactory(name, opts) {
|
|
|
224
232
|
rec.value.value = value;
|
|
225
233
|
const dc = getDataContext();
|
|
226
234
|
const table = PersistentVars(dc);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
console.log(`deleted persistent var ${name} from db:`, rec.value.value);
|
|
232
|
-
}
|
|
233
|
-
await table.delete(rec);
|
|
235
|
+
try {
|
|
236
|
+
rec = await table.save(rec);
|
|
237
|
+
if (name === 'colorModePreference') {
|
|
238
|
+
console.log(`Saved var ${name} to db:`, rec.value.value);
|
|
234
239
|
}
|
|
235
|
-
rec = undefined;
|
|
236
240
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
241
|
+
catch (err) {
|
|
242
|
+
const errMsg = err?.message || String(err) || '';
|
|
243
|
+
if (errMsg.includes('UNIQUE constraint failed')) {
|
|
244
|
+
console.warn(`Detected UNIQUE constraint failed error when saving persistent var, reloading and retrying: ${name}`);
|
|
245
|
+
rec = await loadRecFromDb();
|
|
246
|
+
persistentVar(rec.value.value);
|
|
243
247
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
console.warn(`Detected UNIQUE constraint failed error when saving persistent var, reloading and retrying: ${name}`);
|
|
248
|
-
rec = await loadRecFromDb();
|
|
249
|
-
persistentVar(rec.value.value);
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
console.error('Error saving persistent var', { name, value, rec, err });
|
|
253
|
-
throw err;
|
|
254
|
-
}
|
|
248
|
+
else {
|
|
249
|
+
console.error('Error saving persistent var', { name, value, rec, err });
|
|
250
|
+
throw err;
|
|
255
251
|
}
|
|
256
252
|
}
|
|
257
253
|
}
|
|
254
|
+
deleteVarImpl = async () => {
|
|
255
|
+
const dc = getDataContext();
|
|
256
|
+
const table = PersistentVars(dc);
|
|
257
|
+
if (rec?.persistentVarId) {
|
|
258
|
+
await table.delete(rec);
|
|
259
|
+
rec = undefined;
|
|
260
|
+
}
|
|
261
|
+
if (defaultValue !== undefined) {
|
|
262
|
+
persistentVar(defaultValue);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
258
265
|
// subscribe to db changes
|
|
259
266
|
userContext.subscribeToDataChangedAcrossAllGroups(exports.persistentVarsMetaData.name, async (evt) => {
|
|
260
267
|
const dbRec = evt.data.dataObject;
|
|
@@ -132,80 +132,36 @@ function createBinaryPeerSocket(connectionId, peer, protocol = 'binary') {
|
|
|
132
132
|
const transportType = protocol === 'wrtc' ? 'wrtc' :
|
|
133
133
|
protocol === 'libp2p' ? 'libp2p' :
|
|
134
134
|
protocol === 'ws' ? 'ws' : 'unknown';
|
|
135
|
-
//
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const LOW_WATER_MARK = transportType === 'wrtc' ? 16 * 1024 : 2 * 1024 * 1024;
|
|
140
|
-
// Helper to wait for buffer to drain
|
|
141
|
-
const waitForDrain = async () => {
|
|
142
|
-
if (!peer.getBufferedAmount)
|
|
143
|
-
return;
|
|
144
|
-
const buffered = peer.getBufferedAmount();
|
|
145
|
-
if (buffered <= HIGH_WATER_MARK)
|
|
146
|
-
return;
|
|
147
|
-
await new Promise(resolve => {
|
|
148
|
-
const checkDrain = () => {
|
|
149
|
-
const currentBuffered = peer.getBufferedAmount ? peer.getBufferedAmount() : 0;
|
|
150
|
-
if (!peer.getBufferedAmount || currentBuffered < LOW_WATER_MARK) {
|
|
151
|
-
resolve();
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
setTimeout(checkDrain, 5);
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
peer.on('drain', resolve);
|
|
158
|
-
checkDrain();
|
|
159
|
-
});
|
|
160
|
-
};
|
|
161
|
-
// Helper to send a single raw bytes packet
|
|
162
|
-
const sendRawBytesPacket = async (streamIdBytes, payload) => {
|
|
163
|
-
const packet = new Uint8Array(1 + 2 + streamIdBytes.length + payload.length);
|
|
135
|
+
// Core sendRawBytes implementation - defined early so sendRPCData can use it
|
|
136
|
+
const sendRawBytesImpl = async (streamId, data) => {
|
|
137
|
+
const streamIdBytes = new TextEncoder().encode(streamId);
|
|
138
|
+
const packet = new Uint8Array(1 + 2 + streamIdBytes.length + data.length);
|
|
164
139
|
packet[0] = PROTOCOL_RAW_BYTES;
|
|
165
140
|
const view = new DataView(packet.buffer, packet.byteOffset, packet.byteLength);
|
|
166
141
|
view.setUint16(1, streamIdBytes.length, false);
|
|
167
142
|
packet.set(streamIdBytes, 3);
|
|
168
|
-
packet.set(
|
|
169
|
-
|
|
143
|
+
packet.set(data, 3 + streamIdBytes.length);
|
|
144
|
+
// Use transport-specific thresholds for backpressure
|
|
145
|
+
const HIGH_WATER_MARK = transportType === 'wrtc' ? 128 * 1024 : 4 * 1024 * 1024;
|
|
146
|
+
const LOW_WATER_MARK = transportType === 'wrtc' ? 32 * 1024 : 2 * 1024 * 1024;
|
|
147
|
+
if (peer.getBufferedAmount && peer.getBufferedAmount() > HIGH_WATER_MARK) {
|
|
148
|
+
await new Promise(resolve => {
|
|
149
|
+
const checkDrain = () => {
|
|
150
|
+
if (!peer.getBufferedAmount || peer.getBufferedAmount() < LOW_WATER_MARK) {
|
|
151
|
+
resolve();
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
setTimeout(checkDrain, 5);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
peer.on('drain', resolve);
|
|
158
|
+
checkDrain();
|
|
159
|
+
});
|
|
160
|
+
}
|
|
170
161
|
stats.bytesSent += packet.length;
|
|
171
162
|
stats.messagesSent++;
|
|
172
163
|
peer.send(packet);
|
|
173
164
|
};
|
|
174
|
-
// Core sendRawBytes implementation
|
|
175
|
-
// For WebRTC: chunks large data to fit under SCTP message size limit
|
|
176
|
-
// For RPC_STREAM_ID: also prepends 4-byte length for reassembly
|
|
177
|
-
const sendRawBytesImpl = async (streamId, data) => {
|
|
178
|
-
const streamIdBytes = new TextEncoder().encode(streamId);
|
|
179
|
-
const headerOverhead = 1 + 2 + streamIdBytes.length; // PROTOCOL + streamIdLen + streamId
|
|
180
|
-
const maxPayloadSize = WRTC_MAX_CHUNK - headerOverhead;
|
|
181
|
-
// For WebRTC with large data
|
|
182
|
-
if (transportType === 'wrtc' && data.length > maxPayloadSize) {
|
|
183
|
-
// RPC_STREAM_ID needs length prefix for reassembly (receiver must reconstruct complete message)
|
|
184
|
-
if (streamId === RPC_STREAM_ID) {
|
|
185
|
-
// Prepend 4-byte total length to data
|
|
186
|
-
const withLength = new Uint8Array(4 + data.length);
|
|
187
|
-
new DataView(withLength.buffer).setUint32(0, data.length, false);
|
|
188
|
-
withLength.set(data, 4);
|
|
189
|
-
// Send in chunks
|
|
190
|
-
for (let offset = 0; offset < withLength.length; offset += maxPayloadSize) {
|
|
191
|
-
const chunk = withLength.subarray(offset, Math.min(offset + maxPayloadSize, withLength.length));
|
|
192
|
-
await sendRawBytesPacket(streamIdBytes, chunk);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
// Other streams: just chunk without length prefix
|
|
197
|
-
// Handlers receive smaller pieces but that's OK (e.g., file transfers stream to disk)
|
|
198
|
-
for (let offset = 0; offset < data.length; offset += maxPayloadSize) {
|
|
199
|
-
const chunk = data.subarray(offset, Math.min(offset + maxPayloadSize, data.length));
|
|
200
|
-
await sendRawBytesPacket(streamIdBytes, chunk);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
// Small data or non-WebRTC: send directly
|
|
206
|
-
await sendRawBytesPacket(streamIdBytes, data);
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
165
|
// Helper to send RPC messages - routes large messages through raw bytes streaming
|
|
210
166
|
const sendRPCData = async (encoded) => {
|
|
211
167
|
if (encoded.length > LARGE_MESSAGE_THRESHOLD) {
|
|
@@ -222,9 +178,6 @@ function createBinaryPeerSocket(connectionId, peer, protocol = 'binary') {
|
|
|
222
178
|
peer.send(withProtocol);
|
|
223
179
|
}
|
|
224
180
|
};
|
|
225
|
-
// Buffer for RPC stream reassembly (large RPC messages sent via raw bytes are length-prefixed)
|
|
226
|
-
let rpcStreamBuffer = null;
|
|
227
|
-
let rpcStreamExpectedLength = null;
|
|
228
181
|
// Handle incoming data
|
|
229
182
|
peer.on('data', async (data) => {
|
|
230
183
|
try {
|
|
@@ -245,52 +198,17 @@ function createBinaryPeerSocket(connectionId, peer, protocol = 'binary') {
|
|
|
245
198
|
const streamIdBytes = bytes.subarray(3, 3 + streamIdLength);
|
|
246
199
|
const streamId = new TextDecoder().decode(streamIdBytes);
|
|
247
200
|
const payload = bytes.subarray(3 + streamIdLength);
|
|
248
|
-
//
|
|
201
|
+
// Check if this is an RPC message sent via raw bytes (for large payloads)
|
|
249
202
|
if (streamId === RPC_STREAM_ID) {
|
|
250
|
-
|
|
251
|
-
if (rpcStreamBuffer === null) {
|
|
252
|
-
rpcStreamBuffer = payload;
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
const combined = new Uint8Array(rpcStreamBuffer.length + payload.length);
|
|
256
|
-
combined.set(rpcStreamBuffer, 0);
|
|
257
|
-
combined.set(payload, rpcStreamBuffer.length);
|
|
258
|
-
rpcStreamBuffer = combined;
|
|
259
|
-
}
|
|
260
|
-
// Process complete messages from buffer
|
|
261
|
-
while (rpcStreamBuffer && rpcStreamBuffer.length >= 4) {
|
|
262
|
-
// Read expected length if not yet known
|
|
263
|
-
if (rpcStreamExpectedLength === null) {
|
|
264
|
-
const lenView = new DataView(rpcStreamBuffer.buffer, rpcStreamBuffer.byteOffset, rpcStreamBuffer.byteLength);
|
|
265
|
-
rpcStreamExpectedLength = lenView.getUint32(0, false);
|
|
266
|
-
}
|
|
267
|
-
// Check if we have the complete message
|
|
268
|
-
if (rpcStreamBuffer.length >= 4 + rpcStreamExpectedLength) {
|
|
269
|
-
const rpcData = rpcStreamBuffer.subarray(4, 4 + rpcStreamExpectedLength);
|
|
270
|
-
handleRPCMessage(rpcData, handlers, callbacks, sendRPCData);
|
|
271
|
-
// Remove processed data from buffer
|
|
272
|
-
if (rpcStreamBuffer.length === 4 + rpcStreamExpectedLength) {
|
|
273
|
-
rpcStreamBuffer = null;
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
rpcStreamBuffer = rpcStreamBuffer.subarray(4 + rpcStreamExpectedLength);
|
|
277
|
-
}
|
|
278
|
-
rpcStreamExpectedLength = null;
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
// Need more data
|
|
282
|
-
break;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
203
|
+
handleRPCMessage(payload, handlers, callbacks, sendRPCData);
|
|
285
204
|
return;
|
|
286
205
|
}
|
|
287
|
-
// Other streams: pass through to handler
|
|
288
206
|
const handler = rawBytesHandlers[streamId];
|
|
289
207
|
if (handler)
|
|
290
208
|
handler(payload);
|
|
291
209
|
return;
|
|
292
210
|
}
|
|
293
|
-
// Handle RPC messages
|
|
211
|
+
// Handle RPC messages
|
|
294
212
|
if (protocolType === PROTOCOL_RPC) {
|
|
295
213
|
const rpcBytes = bytes.subarray(1);
|
|
296
214
|
handleRPCMessage(rpcBytes, handlers, callbacks, sendRPCData);
|
package/dist/rpc-types.d.ts
CHANGED
|
@@ -93,6 +93,19 @@ export declare const rpcServerCalls: {
|
|
|
93
93
|
success: boolean;
|
|
94
94
|
error?: string;
|
|
95
95
|
}>);
|
|
96
|
+
voiceGetState: (() => Promise<{
|
|
97
|
+
state: "disabled" | "idle" | "listening" | "recording" | "processing" | "speaking";
|
|
98
|
+
}>);
|
|
99
|
+
voiceStartRecording: (() => Promise<void>);
|
|
100
|
+
voiceStopRecording: (() => Promise<void>);
|
|
101
|
+
voiceSetTargetChannel: ((channelId: string) => Promise<void>);
|
|
102
|
+
voiceGetAudioDevices: (() => Promise<string[]>);
|
|
103
|
+
voiceTestTTS: ((text: string) => Promise<void>);
|
|
104
|
+
voiceNotifyPlaybackComplete: (() => Promise<void>);
|
|
105
|
+
voiceStopPlayback: (() => Promise<void>);
|
|
106
|
+
voiceGetThreadId: (() => Promise<string | null>);
|
|
107
|
+
voiceSetThreadId: ((threadId: string | null) => Promise<void>);
|
|
108
|
+
voiceNotifyTextActivity: (() => Promise<void>);
|
|
96
109
|
};
|
|
97
110
|
export declare const rpcClientCalls: {
|
|
98
111
|
ping: (msg: string) => Promise<string>;
|
package/dist/rpc-types.js
CHANGED
|
@@ -39,6 +39,18 @@ exports.rpcServerCalls = {
|
|
|
39
39
|
uiClick: rpcStub('uiClick'),
|
|
40
40
|
uiSet: rpcStub('uiSet'),
|
|
41
41
|
uiScroll: rpcStub('uiScroll'),
|
|
42
|
+
// Voice control (settings managed via voiceSettings pvar, keys via secret pvars)
|
|
43
|
+
voiceGetState: rpcStub('voiceGetState'),
|
|
44
|
+
voiceStartRecording: rpcStub('voiceStartRecording'),
|
|
45
|
+
voiceStopRecording: rpcStub('voiceStopRecording'),
|
|
46
|
+
voiceSetTargetChannel: rpcStub('voiceSetTargetChannel'),
|
|
47
|
+
voiceGetAudioDevices: rpcStub('voiceGetAudioDevices'),
|
|
48
|
+
voiceTestTTS: rpcStub('voiceTestTTS'),
|
|
49
|
+
voiceNotifyPlaybackComplete: rpcStub('voiceNotifyPlaybackComplete'),
|
|
50
|
+
voiceStopPlayback: rpcStub('voiceStopPlayback'),
|
|
51
|
+
voiceGetThreadId: rpcStub('voiceGetThreadId'),
|
|
52
|
+
voiceSetThreadId: rpcStub('voiceSetThreadId'),
|
|
53
|
+
voiceNotifyTextActivity: rpcStub('voiceNotifyTextActivity'),
|
|
42
54
|
// TODO try to get rid of this and rely on the client-side table and server-side table individually emitting events
|
|
43
55
|
// TODO TODO before deleting this, check if we can stop client-side tables from emitting events and rely solely on server-side tables
|
|
44
56
|
// propagating events with rpcClientCalls.emitEvent. It's very likely we're currently seeing two events for every one write originating from the UI
|