@pmate/chat-sdk 0.12.0-next.43 → 0.12.0-next.44

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.
@@ -10,6 +10,10 @@ export type OutgoingMessage = {
10
10
  confirmed?: boolean;
11
11
  accessVersion?: number;
12
12
  voiceBlob?: Blob;
13
+ voiceData?: {
14
+ bytes: ArrayBuffer;
15
+ type: string;
16
+ };
13
17
  };
14
18
  export type SendMessageInput = {
15
19
  to: string;
@@ -205,7 +205,7 @@ export class DurableChatSender {
205
205
  if (!(input.voiceBlob instanceof Blob) || input.voiceBlob.size === 0 || input.voiceBlob.size > 16 * 1024 * 1024 || body.kind !== "message" || !validVoice(body.content.voice))
206
206
  throw new Error("INVALID_VOICE_ATTACHMENT");
207
207
  }
208
- const record = { ...(input.voiceBlob ? { voiceBlob: input.voiceBlob } : {}), scope: this.scope, threadId, hash: message.hash, message, status: "sending", accessVersion: this.version };
208
+ const record = { ...(input.voiceBlob ? { voiceData: { bytes: await input.voiceBlob.arrayBuffer(), type: input.voiceBlob.type } } : {}), scope: this.scope, threadId, hash: message.hash, message, status: "sending", accessVersion: this.version };
209
209
  await this.withEpoch("rw", async () => { await this.outbox.put(record); });
210
210
  await this.checkAccess();
211
211
  this.values.set(record.hash, record);
@@ -244,7 +244,7 @@ export class DurableChatSender {
244
244
  this.emit();
245
245
  }
246
246
  try {
247
- if (record.voiceBlob) {
247
+ if (hasVoice(record)) {
248
248
  record = await this.prepareVoice(record);
249
249
  if (record.confirmed || record.status === "sent")
250
250
  return record.message;
@@ -289,18 +289,19 @@ export class DurableChatSender {
289
289
  releaseUrls() { for (const hash of this.localUrls.keys())
290
290
  this.releaseUrl(hash); }
291
291
  localView(record) {
292
- if (!record.voiceBlob || typeof URL.createObjectURL !== "function")
292
+ if (!hasVoice(record) || typeof URL.createObjectURL !== "function")
293
293
  return record;
294
294
  let cached = this.localViews.get(record);
295
295
  if (cached)
296
296
  return cached;
297
+ const blob = voiceBlob(record);
297
298
  let local = this.localUrls.get(record.hash);
298
- if (!local || local.blob !== record.voiceBlob) {
299
+ if (!local || local.blob !== blob) {
299
300
  this.releaseUrl(record.hash);
300
- local = { blob: record.voiceBlob, url: URL.createObjectURL(record.voiceBlob) };
301
+ local = { blob, url: URL.createObjectURL(blob) };
301
302
  this.localUrls.set(record.hash, local);
302
303
  }
303
- cached = { ...record, message: withVoiceUrl(record.message, local.url) };
304
+ cached = { ...record, voiceBlob: blob, message: withVoiceUrl(record.message, local.url) };
304
305
  this.localViews.set(record, cached);
305
306
  return cached;
306
307
  }
@@ -319,7 +320,7 @@ export class DurableChatSender {
319
320
  const latest = await this.withEpoch("r", () => this.outbox.get([this.scope, record.hash]));
320
321
  if (!latest)
321
322
  throw new Error("OUTGOING_MESSAGE_UNAVAILABLE");
322
- if (!latest.voiceBlob || latest.confirmed || latest.status === "sent")
323
+ if (!hasVoice(latest) || latest.confirmed || latest.status === "sent")
323
324
  return latest;
324
325
  if (!this.options.uploadVoice)
325
326
  throw new Error("VOICE_UPLOAD_UNAVAILABLE");
@@ -328,7 +329,7 @@ export class DurableChatSender {
328
329
  const timeout = setTimeout(() => controller.abort(), 30_000);
329
330
  let url;
330
331
  try {
331
- url = await this.options.uploadVoice(latest.voiceBlob, controller.signal);
332
+ url = await this.options.uploadVoice(voiceBlob(latest), controller.signal);
332
333
  }
333
334
  finally {
334
335
  clearTimeout(timeout);
@@ -340,10 +341,10 @@ export class DurableChatSender {
340
341
  await this.checkAccess();
341
342
  if (this.closing)
342
343
  throw new Error("CHAT_SENDER_CLOSED");
343
- const { voiceBlob: _blob, ...uploaded } = latest;
344
+ const { voiceBlob: _blob, voiceData: _data, ...uploaded } = latest;
344
345
  const committed = await this.withEpoch("rw", async () => {
345
346
  const current = await this.outbox.get([this.scope, record.hash]);
346
- if (!current || current.confirmed || current.status === "sent" || !current.voiceBlob)
347
+ if (!current || current.confirmed || current.status === "sent" || !hasVoice(current))
347
348
  return current;
348
349
  const next = { ...uploaded, message: withVoiceUrl(latest.message, url) };
349
350
  await this.outbox.put(next);
@@ -366,7 +367,7 @@ export class DurableChatSender {
366
367
  if (!previous || previous.confirmed || previous.status === "sent")
367
368
  return previous;
368
369
  // A second document may already have durably replaced the Blob with its URL.
369
- if (record.voiceBlob && !previous.voiceBlob)
370
+ if (hasVoice(record) && !hasVoice(previous))
370
371
  return previous;
371
372
  await this.outbox.put(record);
372
373
  return record;
@@ -381,7 +382,7 @@ export class DurableChatSender {
381
382
  await this.ready;
382
383
  await this.withEpoch("rw", async () => {
383
384
  const previous = await this.outbox.bulkGet(hashes.map(hash => [this.scope, hash]));
384
- const confirmed = previous.filter((record) => Boolean(record && !record.confirmed)).map(record => ({ ...record, voiceBlob: undefined, status: "sent", confirmed: true }));
385
+ const confirmed = previous.filter((record) => Boolean(record && !record.confirmed)).map(record => ({ ...record, voiceBlob: undefined, voiceData: undefined, status: "sent", confirmed: true }));
385
386
  if (confirmed.length)
386
387
  await this.outbox.bulkPut(confirmed);
387
388
  });
@@ -435,3 +436,5 @@ function withVoiceUrl(message, url) {
435
436
  const body = message.body;
436
437
  return { ...message, body: { ...body, content: { ...body.content, voice: { ...body.content.voice, url } } } };
437
438
  }
439
+ function hasVoice(record) { return Boolean(record.voiceBlob || record.voiceData); }
440
+ function voiceBlob(record) { return record.voiceBlob ?? new Blob([record.voiceData.bytes], { type: record.voiceData.type }); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/chat-sdk",
3
- "version": "0.12.0-next.43",
3
+ "version": "0.12.0-next.44",
4
4
  "description": "Cross-platform PMate chat input components, voice input primitives, and ASR helpers",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",