@memberjunction/realtime-runtime 6.2.0-edge.4 → 6.2.0-edge.6
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/hosts/IRealtimeMediaHost.d.ts +25 -30
- package/dist/hosts/IRealtimeMediaHost.d.ts.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/session/RealtimeSessionRuntime.d.ts +372 -73
- package/dist/session/RealtimeSessionRuntime.d.ts.map +1 -1
- package/dist/session/RealtimeSessionRuntime.js +919 -212
- package/dist/session/RealtimeSessionRuntime.js.map +1 -1
- package/dist/session/RecordingShardDrain.d.ts +59 -0
- package/dist/session/RecordingShardDrain.d.ts.map +1 -0
- package/dist/session/RecordingShardDrain.js +135 -0
- package/dist/session/RecordingShardDrain.js.map +1 -0
- package/dist/session/RecordingShardQueue.d.ts +64 -38
- package/dist/session/RecordingShardQueue.d.ts.map +1 -1
- package/dist/session/RecordingShardQueue.js +106 -84
- package/dist/session/RecordingShardQueue.js.map +1 -1
- package/dist/session/RecordingShardStore.d.ts +57 -0
- package/dist/session/RecordingShardStore.d.ts.map +1 -0
- package/dist/session/RecordingShardStore.js +184 -0
- package/dist/session/RecordingShardStore.js.map +1 -0
- package/dist/session/RecordingShardWindow.d.ts +42 -0
- package/dist/session/RecordingShardWindow.d.ts.map +1 -0
- package/dist/session/RecordingShardWindow.js +88 -0
- package/dist/session/RecordingShardWindow.js.map +1 -0
- package/dist/session/RecordingTransport.d.ts +67 -0
- package/dist/session/RecordingTransport.d.ts.map +1 -0
- package/dist/session/RecordingTransport.js +61 -0
- package/dist/session/RecordingTransport.js.map +1 -0
- package/package.json +10 -9
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { CreateRecordingShardStore } from './RecordingShardStore.js';
|
|
2
|
+
import { FetchRecordingTransport, RecordingBaseURLFromGraphQLURL } from './RecordingTransport.js';
|
|
3
|
+
/** Most stored shards one drain pass re-uploads; the rest wait for the next pass or load. */
|
|
4
|
+
export const MAX_DRAINED_SHARDS_PER_LOAD = 50;
|
|
5
|
+
function describeFailure(failure) {
|
|
6
|
+
return failure instanceof Error ? failure.message : String(failure);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The default recording transport for a provider: MJAPI's REST recording routes, derived from the
|
|
10
|
+
* provider's GraphQL URL, with credentials read per request so a refreshed token is used. Null when
|
|
11
|
+
* the provider has no URL (a test double, a provider that is not GraphQL, or one not configured yet).
|
|
12
|
+
*/
|
|
13
|
+
export function CreateRecordingTransportForProvider(provider) {
|
|
14
|
+
// Typed as GraphQL, but read defensively: a test double or a non-GraphQL provider has no ConfigData.
|
|
15
|
+
const graphQL = provider;
|
|
16
|
+
const url = graphQL?.ConfigData?.URL;
|
|
17
|
+
if (!graphQL || !url) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return new FetchRecordingTransport({
|
|
21
|
+
BaseURL: RecordingBaseURLFromGraphQLURL(url),
|
|
22
|
+
Headers: () => {
|
|
23
|
+
const config = graphQL.ConfigData;
|
|
24
|
+
const headers = {};
|
|
25
|
+
if (config?.Token)
|
|
26
|
+
headers.authorization = `Bearer ${config.Token}`;
|
|
27
|
+
if (config?.MJAPIKey)
|
|
28
|
+
headers['x-mj-api-key'] = config.MJAPIKey;
|
|
29
|
+
if (config?.UserAPIKey)
|
|
30
|
+
headers['x-api-key'] = config.UserAPIKey;
|
|
31
|
+
return headers;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* One drain pass over `userID`'s stored shards, oldest first, each under its own session and index. A
|
|
37
|
+
* shard the server acknowledges or refuses for good (see {@link RecordingUploadOutcome}) leaves the
|
|
38
|
+
* store; one that may still succeed stays for the next pass or load. Never rejects for an upload or
|
|
39
|
+
* delete failure (each is logged); a store whose listing throws rejects.
|
|
40
|
+
*/
|
|
41
|
+
export async function DrainStoredRecordingShardsOnce(store, transport, userID) {
|
|
42
|
+
const pending = await store.ListOldest(MAX_DRAINED_SHARDS_PER_LOAD, userID);
|
|
43
|
+
let delivered = 0;
|
|
44
|
+
let refused = 0;
|
|
45
|
+
for (const shard of pending) {
|
|
46
|
+
let outcome = 'Retry';
|
|
47
|
+
try {
|
|
48
|
+
outcome = await transport.UploadShard(shard.SessionID, shard);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.warn(`[RealtimeSession] Re-uploading stored recording shard ${shard.SessionID}/${shard.Index} failed: ${describeFailure(error)}; it stays stored for the next load.`, error);
|
|
52
|
+
}
|
|
53
|
+
if (outcome === 'Acked') {
|
|
54
|
+
await DeleteStoredRecordingShard(store, shard.SessionID, shard.Index);
|
|
55
|
+
delivered++;
|
|
56
|
+
}
|
|
57
|
+
else if (outcome === 'Refused') {
|
|
58
|
+
console.warn(`[RealtimeSession] The server refused stored recording shard ${shard.SessionID}/${shard.Index} for good; deleting it so it no longer holds back the shards behind it.`);
|
|
59
|
+
await DeleteStoredRecordingShard(store, shard.SessionID, shard.Index);
|
|
60
|
+
refused++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (pending.length > 0) {
|
|
64
|
+
console.log(`[RealtimeSession] Re-uploaded ${delivered}/${pending.length} stored recording shard(s); ${refused} refused and deleted.`);
|
|
65
|
+
}
|
|
66
|
+
return { Listed: pending.length, Delivered: delivered, Refused: refused };
|
|
67
|
+
}
|
|
68
|
+
/** Removes one shard from the store. Never rejects: a failed delete only costs a duplicate re-upload. */
|
|
69
|
+
export async function DeleteStoredRecordingShard(store, sessionId, index) {
|
|
70
|
+
try {
|
|
71
|
+
await store.Delete(sessionId, index);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
// Harmless beyond a wasted re-upload on the next load: the server acks a duplicate.
|
|
75
|
+
console.warn(`[RealtimeSession] Failed to delete recording shard ${sessionId}/${index} from the local store:`, error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Most drain passes a logout runs before it deletes what is left (each pass is itself bounded). */
|
|
79
|
+
const MAX_LOGOUT_DRAIN_PASSES = 3;
|
|
80
|
+
/**
|
|
81
|
+
* For logout: tries to deliver `userID`'s stored shards within `timeoutMs`, then deletes every shard
|
|
82
|
+
* of that user that is still stored, so their call audio neither stays on a shared browser's disk nor
|
|
83
|
+
* waits for a later login. Other users' shards are untouched. Never rejects and never takes longer
|
|
84
|
+
* than `timeoutMs` plus the delete.
|
|
85
|
+
*/
|
|
86
|
+
export async function ReleaseUserRecordingShards(store, transport, userID, timeoutMs) {
|
|
87
|
+
let delivered = 0;
|
|
88
|
+
let timedOut = false;
|
|
89
|
+
if (transport) {
|
|
90
|
+
let timer;
|
|
91
|
+
const deadline = new Promise((resolve) => {
|
|
92
|
+
timer = setTimeout(() => resolve('timeout'), timeoutMs);
|
|
93
|
+
});
|
|
94
|
+
const attempt = (async () => {
|
|
95
|
+
for (let pass = 1; pass <= MAX_LOGOUT_DRAIN_PASSES; pass++) {
|
|
96
|
+
const result = await DrainStoredRecordingShardsOnce(store, transport, userID);
|
|
97
|
+
delivered += result.Delivered;
|
|
98
|
+
// Another pass only reaches shards beyond this pass's page, and only when this one made room;
|
|
99
|
+
// a shard that answered Retry is not retried within the logout.
|
|
100
|
+
if (result.Listed < MAX_DRAINED_SHARDS_PER_LOAD || result.Delivered + result.Refused === 0) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
})().catch((error) => {
|
|
105
|
+
console.warn(`[RealtimeSession] Sending stored recording shards at logout failed for user ${userID ?? '(none)'}: ${describeFailure(error)}; deleting them.`, error);
|
|
106
|
+
});
|
|
107
|
+
try {
|
|
108
|
+
timedOut = (await Promise.race([attempt, deadline])) === 'timeout';
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
}
|
|
113
|
+
if (timedOut) {
|
|
114
|
+
console.warn(`[RealtimeSession] Sending stored recording shards at logout took over ${timeoutMs} ms for user ${userID ?? '(none)'}; deleting what is left.`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
await store.DeleteForUser(userID);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.warn(`[RealtimeSession] Failed to delete the stored recording shards of user ${userID ?? '(none)'} at logout:`, error);
|
|
122
|
+
}
|
|
123
|
+
return { Delivered: delivered, TimedOut: timedOut };
|
|
124
|
+
}
|
|
125
|
+
/** How long a logout waits for stored shards to upload before it deletes them. */
|
|
126
|
+
export const RECORDING_LOGOUT_SEND_TIMEOUT_MS = 3000;
|
|
127
|
+
/**
|
|
128
|
+
* A host's logout step: {@link ReleaseUserRecordingShards} for the provider's signed-in user, with the
|
|
129
|
+
* browser's IndexedDB store and the provider's recording transport. Call it while the provider is
|
|
130
|
+
* still authenticated (before its token is dropped). Never rejects.
|
|
131
|
+
*/
|
|
132
|
+
export function ReleaseRecordingShardsAtLogout(provider, timeoutMs = RECORDING_LOGOUT_SEND_TIMEOUT_MS) {
|
|
133
|
+
return ReleaseUserRecordingShards(CreateRecordingShardStore(globalThis.indexedDB), CreateRecordingTransportForProvider(provider), provider?.CurrentUser?.ID ?? null, timeoutMs);
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=RecordingShardDrain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecordingShardDrain.js","sourceRoot":"","sources":["../../src/session/RecordingShardDrain.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,76 +1,102 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Retry queue for
|
|
2
|
+
* @fileoverview Retry queue for indexed recording shards.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
* single transient error left a permanent gap in the recovered
|
|
7
|
-
* failed shards and resends them, oldest first, on later
|
|
8
|
-
* grow memory without limit.
|
|
4
|
+
* The recorder cuts the call's Opus stream into already-indexed `MJOPUS1` shards (see
|
|
5
|
+
* {@link RecordingShardWindow}) and each one is uploaded as it seals. Before this queue a failed
|
|
6
|
+
* upload was never retried, so a single transient error left a permanent gap in the recovered
|
|
7
|
+
* recording (#5197). The queue keeps failed shards and resends them, oldest first, on later
|
|
8
|
+
* flushes — bounded so a dead server cannot grow memory without limit. A shard the queue gives up on
|
|
9
|
+
* is not lost: it stays in the IndexedDB store, and the owner is told so it can re-send it from there
|
|
10
|
+
* once uploads succeed again (or a later load drains it).
|
|
11
|
+
*
|
|
12
|
+
* The queue is told why an upload failed, not only that it did (#6012): a `Retry` outcome (dead
|
|
13
|
+
* network, 5xx, expired token) is retried, while a `Refused` outcome is final and the shard is given
|
|
14
|
+
* up on at once — the transport's contract says re-sending it is pointless.
|
|
9
15
|
*
|
|
10
16
|
* @module @memberjunction/realtime-runtime
|
|
11
17
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
|
|
18
|
+
import type { RecordingShard } from './RecordingShardWindow.js';
|
|
19
|
+
import type { RecordingUploadOutcome } from './RecordingTransport.js';
|
|
20
|
+
/**
|
|
21
|
+
* Sends one shard and resolves the transport's outcome: `Acked` (stored), `Retry` (may succeed later;
|
|
22
|
+
* retried up to {@link MAX_SHARD_UPLOAD_ATTEMPTS}), or `Refused` (final; given up on after this one
|
|
23
|
+
* attempt). A throw counts as `Retry`, with the thrown error as the logged reason.
|
|
24
|
+
*/
|
|
25
|
+
export type ShardUpload = (shard: RecordingShard) => Promise<RecordingUploadOutcome>;
|
|
26
|
+
/**
|
|
27
|
+
* Told about shards the queue stops retrying while the recording is live (attempts used up, the
|
|
28
|
+
* server refused the shard for good, or the retry list overflowed). They are still in the store; the owner re-sends them once uploads work.
|
|
29
|
+
*/
|
|
30
|
+
export type ShardsGivenUp = (shards: RecordingShard[]) => void;
|
|
16
31
|
/** Total upload attempts per shard (the first try plus retries) before it is dropped. */
|
|
17
32
|
export declare const MAX_SHARD_UPLOAD_ATTEMPTS = 3;
|
|
18
33
|
/**
|
|
19
|
-
* Flushes allowed in flight at once.
|
|
20
|
-
* slower than a tick; an unbounded pile-up on a dead network would
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* so the next capture is one larger window (bounded by the upload timeout x attempts); if that
|
|
24
|
-
* shard is later dropped, recovery fills a correspondingly longer gap with silence.
|
|
34
|
+
* Flushes allowed in flight at once. Flushes are ~5 s apart, so overlap only happens when an upload
|
|
35
|
+
* is slower than a tick; an unbounded pile-up on a dead network would start a new batch of uploads
|
|
36
|
+
* every tick. A flush that arrives at capacity does not upload, but it does not lose its shards
|
|
37
|
+
* either: they are retained and the next flush that has a free slot sends them, oldest first.
|
|
25
38
|
*/
|
|
26
39
|
export declare const MAX_CONCURRENT_FLUSHES = 2;
|
|
27
40
|
/**
|
|
28
41
|
* Per-attempt upload deadline. A hung request would otherwise pin one of the
|
|
29
42
|
* {@link MAX_CONCURRENT_FLUSHES} slots forever and silently stop all later shards. A timeout counts
|
|
30
43
|
* as a failed attempt; if the request later succeeds anyway that is harmless, because the server
|
|
31
|
-
* key is per-index and a resend is an idempotent overwrite. The timeout is
|
|
32
|
-
*
|
|
33
|
-
* stay rate-bounded regardless (at most {@link MAX_CONCURRENT_FLUSHES} flushes, each
|
|
34
|
-
* most one batch, per timeout window)
|
|
44
|
+
* key is per-index and a resend of the same bytes is an idempotent overwrite. The timeout is
|
|
45
|
+
* logical only: the transport may accept no AbortSignal, so a timed-out request may still be on the
|
|
46
|
+
* wire. Requests stay rate-bounded regardless (at most {@link MAX_CONCURRENT_FLUSHES} flushes, each
|
|
47
|
+
* sending at most one batch, per timeout window).
|
|
35
48
|
*/
|
|
36
49
|
export declare const SHARD_UPLOAD_TIMEOUT_MS = 60000;
|
|
37
50
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
51
|
+
* Cap on shards held for retry: 10 minutes of 5 s shards. A dead network with flushes at capacity
|
|
52
|
+
* would otherwise grow the retained list (and its bytes) without bound. Overflow drops the oldest;
|
|
53
|
+
* dropped shards remain in the IndexedDB store for the next-load drain.
|
|
54
|
+
*/
|
|
55
|
+
export declare const MAX_RETAINED_SHARDS = 120;
|
|
56
|
+
/**
|
|
57
|
+
* Uploads indexed shards and retains failures for retry on later flushes. A retry keeps its shard's
|
|
58
|
+
* index and bytes. Safe to call {@link Flush} from an overlapping timer.
|
|
40
59
|
*/
|
|
41
60
|
export declare class RecordingShardQueue {
|
|
42
|
-
private
|
|
61
|
+
private readonly upload;
|
|
62
|
+
private readonly onGivenUp;
|
|
43
63
|
private retained;
|
|
64
|
+
/** Every shard handed to the queue and not yet acknowledged or given up on, by index. */
|
|
65
|
+
private readonly outstanding;
|
|
44
66
|
private inFlight;
|
|
45
67
|
/** True once a capacity stall has been logged; cleared when a flush actually runs. */
|
|
46
68
|
private capacityWarned;
|
|
47
|
-
/** Set by {@link Close} when the recording ends; nothing is
|
|
69
|
+
/** Set by {@link Close} when the recording ends; nothing is sent or retained after it. */
|
|
48
70
|
private closed;
|
|
49
71
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* order and give newer audio an older index. Never rejected: the link catches internally.
|
|
72
|
+
* @param upload sends one shard
|
|
73
|
+
* @param onGivenUp told about every shard dropped while the recording is live (not on {@link Close})
|
|
53
74
|
*/
|
|
54
|
-
|
|
75
|
+
constructor(upload: ShardUpload, onGivenUp?: ShardsGivenUp);
|
|
55
76
|
/** Shards awaiting a retry (for tests and diagnostics). */
|
|
56
77
|
get RetainedCount(): number;
|
|
57
78
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
|
|
79
|
+
* Every shard the queue still holds without an acknowledgement — on the wire, waiting in a flush's
|
|
80
|
+
* batch, or retained for a retry — oldest first. A page that is about to unload re-sends these with
|
|
81
|
+
* `keepalive`, because its ordinary requests are aborted with it.
|
|
82
|
+
*/
|
|
83
|
+
Unacknowledged(): RecordingShard[];
|
|
84
|
+
/**
|
|
85
|
+
* Uploads retained shards oldest-first, then `fresh`. A failure never stops the rest of the
|
|
86
|
+
* flush. At {@link MAX_CONCURRENT_FLUSHES} in flight nothing is uploaded and `fresh` is retained
|
|
87
|
+
* for the next flush instead. Never rejects.
|
|
62
88
|
*/
|
|
63
|
-
Flush(
|
|
89
|
+
Flush(fresh: RecordingShard[]): Promise<void>;
|
|
64
90
|
/**
|
|
65
91
|
* Ends the queue with its recording: discards retained shards and stops any flush in flight from
|
|
66
|
-
* sending more. An upload already on the wire cannot be aborted
|
|
67
|
-
*
|
|
92
|
+
* sending more. An upload already on the wire cannot be aborted, so at most that one request can
|
|
93
|
+
* still land. Discarded shards remain in the IndexedDB store and are drained on a later load.
|
|
68
94
|
*/
|
|
69
95
|
Close(): void;
|
|
70
|
-
/**
|
|
96
|
+
/** Sorted re-insert: concurrent flushes can fail out of index order, and the oldest must go first. */
|
|
97
|
+
private retain;
|
|
98
|
+
/** Warns once per stall so a wedged uploader is visible without logging every tick. */
|
|
71
99
|
private warnCapacitySkip;
|
|
72
|
-
private captureShard;
|
|
73
|
-
private takeIndexedSnapshot;
|
|
74
100
|
private uploadWithTimeout;
|
|
75
101
|
private send;
|
|
76
102
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecordingShardQueue.d.ts","sourceRoot":"","sources":["../../src/session/RecordingShardQueue.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"RecordingShardQueue.d.ts","sourceRoot":"","sources":["../../src/session/RecordingShardQueue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAarF;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,IAAI,CAAC;AAE/D,yFAAyF;AACzF,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAYvC;;;GAGG;AACH,qBAAa,mBAAmB;IAclB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAe,OAAO,CAAC,QAAQ,CAAC,SAAS;IAb5E,OAAO,CAAC,QAAQ,CAAsB;IACtC,yFAAyF;IACzF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqC;IACjE,OAAO,CAAC,QAAQ,CAAK;IACrB,sFAAsF;IACtF,OAAO,CAAC,cAAc,CAAS;IAC/B,0FAA0F;IAC1F,OAAO,CAAC,MAAM,CAAS;IAEvB;;;OAGG;gBAC0B,MAAM,EAAE,WAAW,EAAmB,SAAS,GAAE,aAA+B;IAE7G,2DAA2D;IAC3D,IAAW,aAAa,IAAI,MAAM,CAEjC;IAED;;;;OAIG;IACI,cAAc,IAAI,cAAc,EAAE;IAIzC;;;;OAIG;IACU,KAAK,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAqC1D;;;;OAIG;IACI,KAAK,IAAI,IAAI;IAMpB,sGAAsG;IACtG,OAAO,CAAC,MAAM;IAgBd,uFAAuF;IACvF,OAAO,CAAC,gBAAgB;YAUV,iBAAiB;YAYjB,IAAI;CA+CnB"}
|
|
@@ -1,71 +1,92 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* single transient error left a permanent gap in the recovered recording (#5197). The queue keeps
|
|
7
|
-
* failed shards and resends them, oldest first, on later flushes — bounded so a dead server cannot
|
|
8
|
-
* grow memory without limit.
|
|
9
|
-
*
|
|
10
|
-
* @module @memberjunction/realtime-runtime
|
|
2
|
+
* Warn reason for a `Retry` outcome with no thrown error. A real transport has already logged the
|
|
3
|
+
* status or fetch error on its own line (with no transport at all, the runtime warned once at start),
|
|
4
|
+
* so this only must not read as a server refusal. It carries no "will be retried": the same reason
|
|
5
|
+
* ends the give-up line, where that would be false.
|
|
11
6
|
*/
|
|
7
|
+
const RETRY_REASON = 'transport or server error, not a refusal';
|
|
8
|
+
/** Warn reason for a `Refused` outcome. */
|
|
9
|
+
const REFUSED_REASON = 'server refused the shard for good';
|
|
12
10
|
/** Total upload attempts per shard (the first try plus retries) before it is dropped. */
|
|
13
11
|
export const MAX_SHARD_UPLOAD_ATTEMPTS = 3;
|
|
14
12
|
/**
|
|
15
|
-
* Flushes allowed in flight at once.
|
|
16
|
-
* slower than a tick; an unbounded pile-up on a dead network would
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* so the next capture is one larger window (bounded by the upload timeout x attempts); if that
|
|
20
|
-
* shard is later dropped, recovery fills a correspondingly longer gap with silence.
|
|
13
|
+
* Flushes allowed in flight at once. Flushes are ~5 s apart, so overlap only happens when an upload
|
|
14
|
+
* is slower than a tick; an unbounded pile-up on a dead network would start a new batch of uploads
|
|
15
|
+
* every tick. A flush that arrives at capacity does not upload, but it does not lose its shards
|
|
16
|
+
* either: they are retained and the next flush that has a free slot sends them, oldest first.
|
|
21
17
|
*/
|
|
22
18
|
export const MAX_CONCURRENT_FLUSHES = 2;
|
|
23
19
|
/**
|
|
24
20
|
* Per-attempt upload deadline. A hung request would otherwise pin one of the
|
|
25
21
|
* {@link MAX_CONCURRENT_FLUSHES} slots forever and silently stop all later shards. A timeout counts
|
|
26
22
|
* as a failed attempt; if the request later succeeds anyway that is harmless, because the server
|
|
27
|
-
* key is per-index and a resend is an idempotent overwrite. The timeout is
|
|
28
|
-
*
|
|
29
|
-
* stay rate-bounded regardless (at most {@link MAX_CONCURRENT_FLUSHES} flushes, each
|
|
30
|
-
* most one batch, per timeout window)
|
|
23
|
+
* key is per-index and a resend of the same bytes is an idempotent overwrite. The timeout is
|
|
24
|
+
* logical only: the transport may accept no AbortSignal, so a timed-out request may still be on the
|
|
25
|
+
* wire. Requests stay rate-bounded regardless (at most {@link MAX_CONCURRENT_FLUSHES} flushes, each
|
|
26
|
+
* sending at most one batch, per timeout window).
|
|
31
27
|
*/
|
|
32
28
|
export const SHARD_UPLOAD_TIMEOUT_MS = 60_000;
|
|
29
|
+
/**
|
|
30
|
+
* Cap on shards held for retry: 10 minutes of 5 s shards. A dead network with flushes at capacity
|
|
31
|
+
* would otherwise grow the retained list (and its bytes) without bound. Overflow drops the oldest;
|
|
32
|
+
* dropped shards remain in the IndexedDB store for the next-load drain.
|
|
33
|
+
*/
|
|
34
|
+
export const MAX_RETAINED_SHARDS = 120;
|
|
33
35
|
/** One-line reason for a warn message; the raw value is still passed along for its stack. */
|
|
34
36
|
function describeFailure(failure) {
|
|
35
37
|
return failure instanceof Error ? failure.message : String(failure);
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
+
* Uploads indexed shards and retains failures for retry on later flushes. A retry keeps its shard's
|
|
41
|
+
* index and bytes. Safe to call {@link Flush} from an overlapping timer.
|
|
40
42
|
*/
|
|
41
43
|
export class RecordingShardQueue {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
/**
|
|
45
|
+
* @param upload sends one shard
|
|
46
|
+
* @param onGivenUp told about every shard dropped while the recording is live (not on {@link Close})
|
|
47
|
+
*/
|
|
48
|
+
constructor(upload, onGivenUp = () => undefined) {
|
|
49
|
+
this.upload = upload;
|
|
50
|
+
this.onGivenUp = onGivenUp;
|
|
44
51
|
this.retained = [];
|
|
52
|
+
/** Every shard handed to the queue and not yet acknowledged or given up on, by index. */
|
|
53
|
+
this.outstanding = new Map();
|
|
45
54
|
this.inFlight = 0;
|
|
46
55
|
/** True once a capacity stall has been logged; cleared when a flush actually runs. */
|
|
47
56
|
this.capacityWarned = false;
|
|
48
|
-
/** Set by {@link Close} when the recording ends; nothing is
|
|
57
|
+
/** Set by {@link Close} when the recording ends; nothing is sent or retained after it. */
|
|
49
58
|
this.closed = false;
|
|
50
|
-
/**
|
|
51
|
-
* Serializes "take a snapshot, then assign an index". The recorder advances its cursor
|
|
52
|
-
* synchronously but encodes asynchronously, so two overlapping snapshots could resolve out of
|
|
53
|
-
* order and give newer audio an older index. Never rejected: the link catches internally.
|
|
54
|
-
*/
|
|
55
|
-
this.captureChain = Promise.resolve();
|
|
56
59
|
}
|
|
57
60
|
/** Shards awaiting a retry (for tests and diagnostics). */
|
|
58
61
|
get RetainedCount() {
|
|
59
62
|
return this.retained.length;
|
|
60
63
|
}
|
|
61
64
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
|
|
65
|
+
* Every shard the queue still holds without an acknowledgement — on the wire, waiting in a flush's
|
|
66
|
+
* batch, or retained for a retry — oldest first. A page that is about to unload re-sends these with
|
|
67
|
+
* `keepalive`, because its ordinary requests are aborted with it.
|
|
68
|
+
*/
|
|
69
|
+
Unacknowledged() {
|
|
70
|
+
return [...this.outstanding.values()].sort((a, b) => a.Index - b.Index);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Uploads retained shards oldest-first, then `fresh`. A failure never stops the rest of the
|
|
74
|
+
* flush. At {@link MAX_CONCURRENT_FLUSHES} in flight nothing is uploaded and `fresh` is retained
|
|
75
|
+
* for the next flush instead. Never rejects.
|
|
66
76
|
*/
|
|
67
|
-
async Flush(
|
|
77
|
+
async Flush(fresh) {
|
|
78
|
+
if (this.closed) {
|
|
79
|
+
if (fresh.length > 0) {
|
|
80
|
+
console.warn(`[RealtimeSession] Recording flush after the recording ended; shard(s) ${fresh.map((s) => s.Index).join(', ')} not sent — they stay in the local store and are drained later.`);
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const freshPending = fresh.map((shard) => ({ Shard: shard, Attempts: 0 }));
|
|
85
|
+
for (const shard of fresh) {
|
|
86
|
+
this.outstanding.set(shard.Index, shard);
|
|
87
|
+
}
|
|
68
88
|
if (this.inFlight >= MAX_CONCURRENT_FLUSHES) {
|
|
89
|
+
this.retain(freshPending);
|
|
69
90
|
this.warnCapacitySkip();
|
|
70
91
|
return;
|
|
71
92
|
}
|
|
@@ -73,22 +94,15 @@ export class RecordingShardQueue {
|
|
|
73
94
|
this.inFlight++;
|
|
74
95
|
try {
|
|
75
96
|
// Swap the array out so a concurrent flush only sees shards nobody is currently sending.
|
|
76
|
-
const batch = this.retained;
|
|
97
|
+
const batch = [...this.retained, ...freshPending].sort((a, b) => a.Shard.Index - b.Shard.Index);
|
|
77
98
|
this.retained = [];
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// `closed` is re-checked before every send: a flush running when the recording ends must not
|
|
82
|
-
// keep uploading, or a resend could land after the end-of-call upload has deleted the shards.
|
|
83
|
-
for (const shard of batch) {
|
|
99
|
+
for (const pending of batch) {
|
|
100
|
+
// Re-checked before every send: a flush running when the recording ends must not keep
|
|
101
|
+
// uploading shards the terminal request is about to supersede.
|
|
84
102
|
if (this.closed) {
|
|
85
103
|
return;
|
|
86
104
|
}
|
|
87
|
-
await this.send(
|
|
88
|
-
}
|
|
89
|
-
const fresh = this.closed ? null : await this.captureShard(snapshot);
|
|
90
|
-
if (fresh && !this.closed) {
|
|
91
|
-
await this.send(fresh, upload);
|
|
105
|
+
await this.send(pending);
|
|
92
106
|
}
|
|
93
107
|
}
|
|
94
108
|
finally {
|
|
@@ -97,76 +111,84 @@ export class RecordingShardQueue {
|
|
|
97
111
|
}
|
|
98
112
|
/**
|
|
99
113
|
* Ends the queue with its recording: discards retained shards and stops any flush in flight from
|
|
100
|
-
* sending more. An upload already on the wire cannot be aborted
|
|
101
|
-
*
|
|
114
|
+
* sending more. An upload already on the wire cannot be aborted, so at most that one request can
|
|
115
|
+
* still land. Discarded shards remain in the IndexedDB store and are drained on a later load.
|
|
102
116
|
*/
|
|
103
117
|
Close() {
|
|
104
118
|
this.closed = true;
|
|
105
119
|
this.retained = [];
|
|
120
|
+
this.outstanding.clear();
|
|
106
121
|
}
|
|
107
|
-
/**
|
|
122
|
+
/** Sorted re-insert: concurrent flushes can fail out of index order, and the oldest must go first. */
|
|
123
|
+
retain(pending) {
|
|
124
|
+
this.retained.push(...pending);
|
|
125
|
+
this.retained.sort((a, b) => a.Shard.Index - b.Shard.Index);
|
|
126
|
+
const overflow = this.retained.length - MAX_RETAINED_SHARDS;
|
|
127
|
+
if (overflow > 0) {
|
|
128
|
+
const dropped = this.retained.splice(0, overflow);
|
|
129
|
+
for (const pending of dropped) {
|
|
130
|
+
this.outstanding.delete(pending.Shard.Index);
|
|
131
|
+
}
|
|
132
|
+
console.warn(`[RealtimeSession] Retry queue over ${MAX_RETAINED_SHARDS} shards; dropped oldest shard(s) ${dropped.map((p) => p.Shard.Index).join(', ')} from memory — they stay in the local IndexedDB store and are re-sent from it once uploads succeed again.`);
|
|
133
|
+
this.onGivenUp(dropped.map((p) => p.Shard));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Warns once per stall so a wedged uploader is visible without logging every tick. */
|
|
108
137
|
warnCapacitySkip() {
|
|
109
138
|
if (this.capacityWarned) {
|
|
110
139
|
return;
|
|
111
140
|
}
|
|
112
141
|
this.capacityWarned = true;
|
|
113
|
-
console.warn(`[RealtimeSession] Recording flush
|
|
114
|
-
}
|
|
115
|
-
captureShard(snapshot) {
|
|
116
|
-
const capture = this.captureChain.then(() => this.takeIndexedSnapshot(snapshot));
|
|
117
|
-
this.captureChain = capture.then(() => undefined);
|
|
118
|
-
return capture;
|
|
119
|
-
}
|
|
120
|
-
async takeIndexedSnapshot(snapshot) {
|
|
121
|
-
try {
|
|
122
|
-
const audio = await snapshot();
|
|
123
|
-
if (!audio) {
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
return { Index: this.nextIndex++, AudioBase64: audio, Attempts: 0 };
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
console.warn('[RealtimeSession] Failed to snapshot recording shard:', error);
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
142
|
+
console.warn(`[RealtimeSession] Recording flush at capacity: ${this.inFlight}/${MAX_CONCURRENT_FLUSHES} flushes in flight, ${this.retained.length} shard(s) retained. Retained shards are sent by the next flush with a free slot.`);
|
|
132
143
|
}
|
|
133
|
-
async uploadWithTimeout(
|
|
144
|
+
async uploadWithTimeout(shard) {
|
|
134
145
|
let timer;
|
|
135
146
|
const deadline = new Promise((_, reject) => {
|
|
136
147
|
timer = setTimeout(() => reject(new Error(`upload timed out after ${SHARD_UPLOAD_TIMEOUT_MS}ms`)), SHARD_UPLOAD_TIMEOUT_MS);
|
|
137
148
|
});
|
|
138
149
|
try {
|
|
139
|
-
return await Promise.race([upload(shard
|
|
150
|
+
return await Promise.race([this.upload(shard), deadline]);
|
|
140
151
|
}
|
|
141
152
|
finally {
|
|
142
153
|
clearTimeout(timer);
|
|
143
154
|
}
|
|
144
155
|
}
|
|
145
|
-
async send(
|
|
146
|
-
shard
|
|
147
|
-
|
|
148
|
-
let
|
|
156
|
+
async send(pending) {
|
|
157
|
+
const { Shard: shard } = pending;
|
|
158
|
+
pending.Attempts++;
|
|
159
|
+
let outcome;
|
|
160
|
+
let failure;
|
|
149
161
|
try {
|
|
150
|
-
|
|
162
|
+
outcome = await this.uploadWithTimeout(shard);
|
|
163
|
+
failure = outcome === 'Refused' ? REFUSED_REASON : RETRY_REASON;
|
|
151
164
|
}
|
|
152
165
|
catch (error) {
|
|
166
|
+
outcome = 'Retry';
|
|
153
167
|
failure = error;
|
|
154
168
|
}
|
|
155
|
-
if (
|
|
169
|
+
if (outcome === 'Acked') {
|
|
170
|
+
this.outstanding.delete(shard.Index);
|
|
156
171
|
return;
|
|
157
172
|
}
|
|
158
173
|
if (this.closed) {
|
|
159
|
-
console.warn(`[RealtimeSession] Recording shard ${shard.Index} upload failed after the recording ended (${describeFailure(failure)}); not retried — the
|
|
174
|
+
console.warn(`[RealtimeSession] Recording shard ${shard.Index} upload failed after the recording ended (${describeFailure(failure)}); not retried — it stays in the local store and is drained later:`, failure);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// Checked before the attempts cap, so a refusal on the last attempt still reads as a refusal.
|
|
178
|
+
if (outcome === 'Refused') {
|
|
179
|
+
console.warn(`[RealtimeSession] Recording shard ${shard.Index} upload failed (${REFUSED_REASON}); it will not be re-sent by the queue — it stays in the local store until a store drain settles it.`);
|
|
180
|
+
this.outstanding.delete(shard.Index);
|
|
181
|
+
this.onGivenUp([shard]);
|
|
160
182
|
return;
|
|
161
183
|
}
|
|
162
|
-
if (
|
|
163
|
-
console.warn(`[RealtimeSession] Dropping recording shard ${shard.Index} after ${
|
|
184
|
+
if (pending.Attempts >= MAX_SHARD_UPLOAD_ATTEMPTS) {
|
|
185
|
+
console.warn(`[RealtimeSession] Dropping recording shard ${shard.Index} from the retry queue after ${pending.Attempts}/${MAX_SHARD_UPLOAD_ATTEMPTS} failed uploads (${describeFailure(failure)}); it stays in the local store and is re-sent from it once uploads succeed again:`, failure);
|
|
186
|
+
this.outstanding.delete(shard.Index);
|
|
187
|
+
this.onGivenUp([shard]);
|
|
164
188
|
return;
|
|
165
189
|
}
|
|
166
|
-
console.warn(`[RealtimeSession] Recording shard ${shard.Index} upload failed (attempt ${
|
|
167
|
-
|
|
168
|
-
this.retained.push(shard);
|
|
169
|
-
this.retained.sort((a, b) => a.Index - b.Index);
|
|
190
|
+
console.warn(`[RealtimeSession] Recording shard ${shard.Index} upload failed (attempt ${pending.Attempts}/${MAX_SHARD_UPLOAD_ATTEMPTS}: ${describeFailure(failure)}); will retry next flush:`, failure);
|
|
191
|
+
this.retain([pending]);
|
|
170
192
|
}
|
|
171
193
|
}
|
|
172
194
|
//# sourceMappingURL=RecordingShardQueue.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecordingShardQueue.js","sourceRoot":"","sources":["../../src/session/RecordingShardQueue.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RecordingShardQueue.js","sourceRoot":"","sources":["../../src/session/RecordingShardQueue.ts"],"names":[],"mappings":"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"}
|