@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.
@@ -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
- // delete if value equals default value
228
- if ((0, lodash_1.isEqual)(rec.value.value, defaultValue)) {
229
- if (rec.persistentVarId) {
230
- if (name === 'colorModePreference') {
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
- else {
238
- try {
239
- rec = await table.save(rec);
240
- if (name === 'colorModePreference') {
241
- console.log(`Saved var ${name} to db:`, rec.value.value);
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
- catch (err) {
245
- const errMsg = err?.message || String(err) || '';
246
- if (errMsg.includes('UNIQUE constraint failed')) {
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
- // WebRTC SCTP has ~256KB max message size, use 64KB to be safe
136
- const WRTC_MAX_CHUNK = 64 * 1024;
137
- // Backpressure thresholds
138
- const HIGH_WATER_MARK = transportType === 'wrtc' ? 64 * 1024 : 4 * 1024 * 1024;
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(payload, 3 + streamIdBytes.length);
169
- await waitForDrain();
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
- // Handle RPC stream with reassembly (large RPC messages are length-prefixed)
201
+ // Check if this is an RPC message sent via raw bytes (for large payloads)
249
202
  if (streamId === RPC_STREAM_ID) {
250
- // Append to buffer
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 (small, sent directly)
211
+ // Handle RPC messages
294
212
  if (protocolType === PROTOCOL_RPC) {
295
213
  const rpcBytes = bytes.subarray(1);
296
214
  handleRPCMessage(rpcBytes, handlers, callbacks, sendRPCData);
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.8.10",
3
+ "version": "0.8.12",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"