codex-overleaf-link 2.2.0 → 2.3.0
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/README.md +25 -23
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/runtime-manifest.json +5 -71
- package/extension/src/background.js +53 -69
- package/extension/src/backgroundUpdateCoordinator.js +128 -66
- package/extension/src/content/applyResultFormatters.js +2 -3
- package/extension/src/content/contentRuntime.js +650 -1449
- package/extension/src/content/contextTray.js +4 -6
- package/extension/src/content/diagnosticsController.js +1 -2
- package/extension/src/content/diffReviewPanel.js +14 -13
- package/extension/src/content/generated/content.bundle.js +41370 -0
- package/extension/src/content/generated/content.bundle.meta.json +102 -0
- package/extension/src/content/legacyGlobalRegistry.js +129 -0
- package/extension/src/content/markdownText.js +5 -7
- package/extension/src/content/modelPicker.js +1 -1
- package/extension/src/content/modelPickerSupport.js +10 -5
- package/extension/src/content/moduleRegistryKernel.js +125 -0
- package/extension/src/content/nativeCompatibilityController.js +313 -0
- package/extension/src/content/otWarmMirror.js +7 -5
- package/extension/src/content/pageBridgeClient.js +224 -0
- package/extension/src/content/panelMaintenance.js +8 -3
- package/extension/src/content/postNavigationSettlementPersistence.js +100 -0
- package/extension/src/content/projectProviderSelection.js +4 -12
- package/extension/src/content/projectSessionCleanup.js +3 -3
- package/extension/src/content/providerSettingsCoordinator.js +59 -26
- package/extension/src/content/providerSettingsDialog.js +30 -18
- package/extension/src/content/recentProjects.js +15 -23
- package/extension/src/content/runGuidanceController.js +1 -1
- package/extension/src/content/runQueueScheduler.js +129 -29
- package/extension/src/content/runSettlementPersistence.js +51 -0
- package/extension/src/content/runTimelineView.js +11 -8
- package/extension/src/content/scopedPersistenceCoordinator.js +100 -0
- package/extension/src/content/scopedPersistenceDetachedCommit.js +47 -0
- package/extension/src/content/scopedPersistencePanelState.js +111 -0
- package/extension/src/content/sessionManager.js +15 -12
- package/extension/src/content/sessionPersistence.js +67 -8
- package/extension/src/content/updateNotice.js +10 -4
- package/extension/src/content/writebackOrchestrator.js +91 -23
- package/extension/src/contentScript.js +5 -1
- package/extension/src/page/trackedChangesLifecycle.js +6 -1
- package/extension/src/pageBridge.js +47 -98
- package/extension/src/shared/agentTranscript.js +8 -5
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/governanceRules.js +1 -1
- package/extension/src/shared/managedUpdateProjection.js +337 -0
- package/extension/src/shared/nativeRequestIdentity.js +60 -0
- package/extension/src/shared/otText.js +0 -19
- package/extension/src/shared/pageRpcContract.js +136 -0
- package/extension/src/shared/runExecutionSnapshot.js +177 -0
- package/extension/src/shared/runExecutionSnapshotCodec.js +168 -0
- package/extension/src/shared/runInputQueue.js +81 -34
- package/extension/src/shared/scopedPersistenceBrowserAdapter.js +79 -0
- package/extension/src/shared/scopedPersistenceQueuePolicy.js +181 -0
- package/extension/src/shared/scopedPersistenceTransaction.js +238 -0
- package/extension/src/shared/sessionState.js +64 -48
- package/extension/src/shared/settlementFacts.js +351 -0
- package/extension/src/shared/storageDb.js +19 -5
- package/extension/src/shared/storageMigration.js +275 -61
- package/extension/src/shared/storageSessionClaims.js +60 -0
- package/extension/src/shared/updateConsent.js +4 -0
- package/extension/src/shared/updateRevocationIntent.js +109 -0
- package/extension/src/shared/writebackEvidenceProjection.js +150 -0
- package/extension/src/shared/writebackOperationEvidence.js +143 -0
- package/extension/src/shared/writebackSettlement.js +705 -0
- package/native-host/src/codexRuntimeIdentity.js +1 -1
- package/native-host/src/codexSessionRunner.js +7 -13
- package/native-host/src/index.js +3 -69
- package/native-host/src/managedInstall.js +50 -8
- package/native-host/src/mirrorWorkspace.js +15 -8
- package/native-host/src/nativeTransportEnvelope.js +148 -0
- package/native-host/src/taskRunnerRuntime.js +3 -3
- package/package.json +13 -5
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
(function initCodexOverleafRunInputQueue(root, factory) {
|
|
2
2
|
if (typeof module === 'object' && module.exports) {
|
|
3
|
-
module.exports = factory();
|
|
3
|
+
module.exports = factory(require('./runExecutionSnapshot'));
|
|
4
4
|
} else {
|
|
5
|
-
root.
|
|
5
|
+
root.CodexOverleafModuleRegistry.define('RunInputQueue', ['RunExecutionSnapshot'], factory);
|
|
6
6
|
}
|
|
7
|
-
})(typeof window !== 'undefined' ? window : globalThis, function runInputQueueFactory() {
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function runInputQueueFactory(RunExecutionSnapshot) {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
const MAX_ITEMS = 20;
|
|
11
11
|
const MAX_TEXT_CHARS = 12000;
|
|
12
12
|
const MAX_QUEUE_BYTES = 128 * 1024;
|
|
13
|
+
const MAX_PERSISTED_QUEUE_BYTES = 512 * 1024;
|
|
13
14
|
const ACTIVE_STATUSES = new Set(['claimed', 'executing', 'steering']);
|
|
14
15
|
const VALID_STATUSES = new Set(['queued', 'paused', ...ACTIVE_STATUSES]);
|
|
15
16
|
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
if (options.recoverActive === true && ACTIVE_STATUSES.has(status)) {
|
|
28
29
|
status = 'paused';
|
|
29
30
|
}
|
|
31
|
+
const executionSnapshot = normalizeExecutionSnapshot(raw, options.fallbackInput);
|
|
30
32
|
normalized.push({
|
|
31
33
|
id,
|
|
32
34
|
clientUserMessageId: String(raw.clientUserMessageId || id),
|
|
@@ -38,7 +40,8 @@
|
|
|
38
40
|
linkedRunId: status === 'executing' ? String(raw.linkedRunId || '') : '',
|
|
39
41
|
claimToken: status === 'claimed' || status === 'executing' ? String(raw.claimToken || '') : '',
|
|
40
42
|
pauseReason: status === 'paused' ? String(raw.pauseReason || '') : '',
|
|
41
|
-
|
|
43
|
+
executionSnapshot: RunExecutionSnapshot.cloneSnapshot(executionSnapshot),
|
|
44
|
+
payload: RunExecutionSnapshot.toQueuePayload(executionSnapshot)
|
|
42
45
|
});
|
|
43
46
|
if (normalized.length >= MAX_ITEMS) {
|
|
44
47
|
break;
|
|
@@ -63,6 +66,7 @@
|
|
|
63
66
|
const nowFactory = deps.now || (() => new Date().toISOString());
|
|
64
67
|
const id = String(input.id || idFactory());
|
|
65
68
|
const timestamp = String(nowFactory());
|
|
69
|
+
const executionSnapshot = normalizeExecutionSnapshot(input, input.fallbackInput);
|
|
66
70
|
const item = {
|
|
67
71
|
id,
|
|
68
72
|
clientUserMessageId: String(input.clientUserMessageId || id),
|
|
@@ -74,10 +78,11 @@
|
|
|
74
78
|
linkedRunId: '',
|
|
75
79
|
claimToken: '',
|
|
76
80
|
pauseReason: input.status === 'paused' ? String(input.pauseReason || '') : '',
|
|
77
|
-
|
|
81
|
+
executionSnapshot: RunExecutionSnapshot.cloneSnapshot(executionSnapshot),
|
|
82
|
+
payload: RunExecutionSnapshot.toQueuePayload(executionSnapshot)
|
|
78
83
|
};
|
|
79
84
|
const next = [...queue, item];
|
|
80
|
-
if (
|
|
85
|
+
if (estimateLegacyBytes(next) > MAX_QUEUE_BYTES || estimateBytes(next) > MAX_PERSISTED_QUEUE_BYTES) {
|
|
81
86
|
return failure(queue, 'queue_too_large');
|
|
82
87
|
}
|
|
83
88
|
return { ok: true, queue: next, item };
|
|
@@ -114,13 +119,37 @@
|
|
|
114
119
|
return { ok: false, queue, item: null };
|
|
115
120
|
}
|
|
116
121
|
const tokenFactory = deps.randomUUID || (() => 'claim_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8));
|
|
117
|
-
|
|
122
|
+
let item = {
|
|
118
123
|
...queue[index],
|
|
119
124
|
status: 'claimed',
|
|
120
125
|
claimToken: String(tokenFactory()),
|
|
121
126
|
updatedAt: new Date().toISOString(),
|
|
122
127
|
pauseReason: ''
|
|
123
128
|
};
|
|
129
|
+
try {
|
|
130
|
+
const captured = RunExecutionSnapshot.normalizeSnapshot(
|
|
131
|
+
item.executionSnapshot || item.payload?.executionSnapshot || item.payload,
|
|
132
|
+
{ source: item.executionSnapshot?.source || item.payload?.executionSnapshot?.source || 'submitted' }
|
|
133
|
+
);
|
|
134
|
+
const snapshot = deps.resolveExecutionSnapshot instanceof Function
|
|
135
|
+
? deps.resolveExecutionSnapshot(captured)
|
|
136
|
+
: captured;
|
|
137
|
+
item = {
|
|
138
|
+
...item,
|
|
139
|
+
executionSnapshot: RunExecutionSnapshot.cloneSnapshot(snapshot),
|
|
140
|
+
payload: RunExecutionSnapshot.toQueuePayload(snapshot)
|
|
141
|
+
};
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
queue,
|
|
146
|
+
item: null,
|
|
147
|
+
error: {
|
|
148
|
+
code: error?.code || 'provider_revision_conflict',
|
|
149
|
+
message: error?.message || ''
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
124
153
|
queue[index] = item;
|
|
125
154
|
return { ok: true, queue, item };
|
|
126
155
|
}
|
|
@@ -160,21 +189,14 @@
|
|
|
160
189
|
return normalizeQueue(items).map(item => item.id === id ? updater(item) : item);
|
|
161
190
|
}
|
|
162
191
|
|
|
163
|
-
function
|
|
164
|
-
return
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
autoRecompile: payload.autoRecompile !== false,
|
|
172
|
-
requireReviewing: payload.requireReviewing !== false,
|
|
173
|
-
focusFiles: (Array.isArray(payload.focusFiles) ? payload.focusFiles : [])
|
|
174
|
-
.map(path => String(path || '').replace(/\\/g, '/').replace(/^\/+/, '').trim())
|
|
175
|
-
.filter(Boolean)
|
|
176
|
-
.slice(0, 100)
|
|
177
|
-
};
|
|
192
|
+
function normalizeExecutionSnapshot(raw = {}, fallbackInput = {}) {
|
|
193
|
+
return RunExecutionSnapshot.captureRawQueueTuple(raw, fallbackInput);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function normalizePayload(payload = {}, fallbackInput = {}) {
|
|
197
|
+
return RunExecutionSnapshot.toQueuePayload(
|
|
198
|
+
RunExecutionSnapshot.captureRawQueueTuple(payload, fallbackInput)
|
|
199
|
+
);
|
|
178
200
|
}
|
|
179
201
|
|
|
180
202
|
function normalizeTimestamp(value) {
|
|
@@ -183,7 +205,10 @@
|
|
|
183
205
|
|
|
184
206
|
function trimToBudget(items) {
|
|
185
207
|
const queue = items.slice(-MAX_ITEMS);
|
|
186
|
-
while (queue.length &&
|
|
208
|
+
while (queue.length && (
|
|
209
|
+
estimateLegacyBytes(queue) > MAX_QUEUE_BYTES
|
|
210
|
+
|| estimateBytes(queue) > MAX_PERSISTED_QUEUE_BYTES
|
|
211
|
+
)) {
|
|
187
212
|
queue.shift();
|
|
188
213
|
}
|
|
189
214
|
return queue;
|
|
@@ -196,6 +221,29 @@
|
|
|
196
221
|
: text.length * 2;
|
|
197
222
|
}
|
|
198
223
|
|
|
224
|
+
function estimateLegacyBytes(items) {
|
|
225
|
+
return estimateBytes((Array.isArray(items) ? items : []).map(item => {
|
|
226
|
+
const legacyItem = { ...(item || {}) };
|
|
227
|
+
delete legacyItem.executionSnapshot;
|
|
228
|
+
return {
|
|
229
|
+
...legacyItem,
|
|
230
|
+
payload: item?.payload && typeof item.payload === 'object'
|
|
231
|
+
? {
|
|
232
|
+
mode: item.payload.mode,
|
|
233
|
+
providerId: item.payload.providerId,
|
|
234
|
+
providerRevision: item.payload.providerRevision,
|
|
235
|
+
model: item.payload.model,
|
|
236
|
+
reasoningEffort: item.payload.reasoningEffort,
|
|
237
|
+
speedTier: item.payload.speedTier,
|
|
238
|
+
autoRecompile: item.payload.autoRecompile,
|
|
239
|
+
requireReviewing: item.payload.requireReviewing,
|
|
240
|
+
focusFiles: item.payload.focusFiles
|
|
241
|
+
}
|
|
242
|
+
: {}
|
|
243
|
+
};
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
|
|
199
247
|
function compactForStorage(items, helpers = {}) {
|
|
200
248
|
const normalizeField = helpers.normalizeField || (value => String(value || ''));
|
|
201
249
|
const normalizeDisplay = helpers.normalizeDisplay || normalizeField;
|
|
@@ -204,6 +252,11 @@
|
|
|
204
252
|
.slice(-MAX_ITEMS)
|
|
205
253
|
.map(item => {
|
|
206
254
|
const payload = item?.payload && typeof item.payload === 'object' ? item.payload : {};
|
|
255
|
+
const snapshot = RunExecutionSnapshot.captureRawQueueTuple(item, payload);
|
|
256
|
+
const compactSnapshot = RunExecutionSnapshot.cloneSnapshot({
|
|
257
|
+
...snapshot,
|
|
258
|
+
focusFiles: normalizePaths(snapshot.focusFiles)
|
|
259
|
+
});
|
|
207
260
|
return {
|
|
208
261
|
id: normalizeField(item?.id, 160),
|
|
209
262
|
clientUserMessageId: normalizeField(item?.clientUserMessageId, 160),
|
|
@@ -215,17 +268,8 @@
|
|
|
215
268
|
linkedRunId: normalizeField(item?.linkedRunId, 160),
|
|
216
269
|
claimToken: normalizeField(item?.claimToken, 160),
|
|
217
270
|
pauseReason: normalizeField(item?.pauseReason, 160),
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
providerId: normalizeField(payload.providerId, 160) || 'builtin',
|
|
221
|
-
providerRevision: normalizeField(payload.providerRevision, 160),
|
|
222
|
-
model: normalizeField(payload.model, 160),
|
|
223
|
-
reasoningEffort: normalizeField(payload.reasoningEffort, 32),
|
|
224
|
-
speedTier: payload.speedTier === 'fast' ? 'fast' : 'standard',
|
|
225
|
-
autoRecompile: payload.autoRecompile !== false,
|
|
226
|
-
requireReviewing: payload.requireReviewing !== false,
|
|
227
|
-
focusFiles: normalizePaths(payload.focusFiles)
|
|
228
|
-
}
|
|
271
|
+
executionSnapshot: compactSnapshot,
|
|
272
|
+
payload: RunExecutionSnapshot.toQueuePayload(compactSnapshot)
|
|
229
273
|
};
|
|
230
274
|
})
|
|
231
275
|
.filter(item => item.id && item.text);
|
|
@@ -238,10 +282,13 @@
|
|
|
238
282
|
return {
|
|
239
283
|
MAX_ITEMS,
|
|
240
284
|
MAX_QUEUE_BYTES,
|
|
285
|
+
MAX_PERSISTED_QUEUE_BYTES,
|
|
241
286
|
MAX_TEXT_CHARS,
|
|
242
287
|
claimNext,
|
|
243
288
|
compactForStorage,
|
|
244
289
|
enqueue,
|
|
290
|
+
estimateBytes,
|
|
291
|
+
estimateLegacyBytes,
|
|
245
292
|
markExecuting,
|
|
246
293
|
markSteering,
|
|
247
294
|
normalizeQueue,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory(require('./scopedPersistenceQueuePolicy'));
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafModuleRegistry.define(
|
|
6
|
+
'ScopedPersistenceBrowserAdapter',
|
|
7
|
+
['ScopedPersistenceQueuePolicy'],
|
|
8
|
+
factory
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
})(typeof globalThis !== 'undefined' ? globalThis : this, function (QueuePolicy) {
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
var META_PREFIX = 'codex-overleaf-scoped-persistence-v1:';
|
|
15
|
+
var fallbackMeta = new Map();
|
|
16
|
+
var cleanPart = QueuePolicy.cleanPart;
|
|
17
|
+
var normalizeMeta = QueuePolicy.normalizeMeta;
|
|
18
|
+
|
|
19
|
+
function normalizeScope(input) {
|
|
20
|
+
return Object.freeze({
|
|
21
|
+
accountScopeId: cleanPart(input && input.accountScopeId),
|
|
22
|
+
projectId: cleanPart(input && input.projectId)
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function scopeKey(scope) {
|
|
27
|
+
var normalized = normalizeScope(scope);
|
|
28
|
+
if (!normalized.accountScopeId) {
|
|
29
|
+
var error = new Error('A stable account scope is required for durable persistence.');
|
|
30
|
+
error.code = 'account_scope_unavailable';
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
return META_PREFIX + encodeURIComponent(normalized.accountScopeId)
|
|
34
|
+
+ ':' + encodeURIComponent(normalized.projectId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createBrowserAdapter(options) {
|
|
38
|
+
options = options || {};
|
|
39
|
+
var chromeApi = options.chromeApi || (typeof chrome !== 'undefined' ? chrome : null);
|
|
40
|
+
var locksApi = Object.prototype.hasOwnProperty.call(options, 'locksApi')
|
|
41
|
+
? options.locksApi
|
|
42
|
+
: (typeof navigator !== 'undefined' ? navigator.locks : null);
|
|
43
|
+
return {
|
|
44
|
+
async read(scope) {
|
|
45
|
+
var key = scopeKey(scope);
|
|
46
|
+
if (!chromeApi || !chromeApi.storage || !chromeApi.storage.local) {
|
|
47
|
+
return normalizeMeta(fallbackMeta.get(key));
|
|
48
|
+
}
|
|
49
|
+
var stored = await chromeApi.storage.local.get(key);
|
|
50
|
+
return normalizeMeta(stored && stored[key]);
|
|
51
|
+
},
|
|
52
|
+
async write(scope, meta) {
|
|
53
|
+
var key = scopeKey(scope);
|
|
54
|
+
var normalized = normalizeMeta(meta);
|
|
55
|
+
if (!chromeApi || !chromeApi.storage || !chromeApi.storage.local) {
|
|
56
|
+
fallbackMeta.set(key, normalized);
|
|
57
|
+
return normalized;
|
|
58
|
+
}
|
|
59
|
+
await chromeApi.storage.local.set({ [key]: normalized });
|
|
60
|
+
return normalized;
|
|
61
|
+
},
|
|
62
|
+
withLock(scope, work) {
|
|
63
|
+
var key = scopeKey(scope);
|
|
64
|
+
if (locksApi && typeof locksApi.request === 'function') {
|
|
65
|
+
return locksApi.request(key, { mode: 'exclusive' }, work);
|
|
66
|
+
}
|
|
67
|
+
var error = new Error('A cross-realm browser lock is required for durable persistence.');
|
|
68
|
+
error.code = 'storage_lock_unavailable';
|
|
69
|
+
return Promise.reject(error);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Object.freeze({
|
|
75
|
+
createBrowserAdapter: createBrowserAdapter,
|
|
76
|
+
normalizeScope: normalizeScope,
|
|
77
|
+
scopeKey: scopeKey
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
var api = factory();
|
|
3
|
+
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
4
|
+
if (root) root.CodexOverleafScopedPersistenceQueuePolicy = api;
|
|
5
|
+
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
function createQueueClaim(input) {
|
|
9
|
+
return Object.freeze({
|
|
10
|
+
itemId: cleanPart(input && input.itemId),
|
|
11
|
+
ownerId: cleanPart(input && input.ownerId),
|
|
12
|
+
queueRevision: normalizeRevision(input && input.queueRevision),
|
|
13
|
+
claimedAt: cleanPart(input && input.claimedAt) || new Date().toISOString()
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isClaimCurrent(claim, queueState) {
|
|
18
|
+
return Boolean(claim && queueState
|
|
19
|
+
&& cleanPart(claim.itemId) === cleanPart(queueState.itemId)
|
|
20
|
+
&& cleanPart(claim.ownerId) === cleanPart(queueState.ownerId)
|
|
21
|
+
&& normalizeRevision(claim.queueRevision) === normalizeRevision(queueState.queueRevision));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function mergeTombstones(existing, additions, limit) {
|
|
25
|
+
var result = Object.assign({}, existing && typeof existing === 'object' ? existing : {});
|
|
26
|
+
var now = new Date().toISOString();
|
|
27
|
+
(Array.isArray(additions) ? additions : []).forEach(function (id) {
|
|
28
|
+
var cleanId = cleanPart(id);
|
|
29
|
+
if (cleanId) result[cleanId] = now;
|
|
30
|
+
});
|
|
31
|
+
var max = Number.isFinite(Number(limit)) ? Math.max(1, Number(limit)) : 100;
|
|
32
|
+
return Object.fromEntries(Object.entries(result)
|
|
33
|
+
.sort(function (left, right) { return String(right[1]).localeCompare(String(left[1])); })
|
|
34
|
+
.slice(0, max));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeMeta(value) {
|
|
38
|
+
return {
|
|
39
|
+
revision: normalizeRevision(value && value.revision),
|
|
40
|
+
queueRevision: normalizeRevision(value && value.queueRevision),
|
|
41
|
+
writerId: cleanPart(value && value.writerId),
|
|
42
|
+
updatedAt: cleanPart(value && value.updatedAt),
|
|
43
|
+
queueClaims: normalizeQueueClaims(value && value.queueClaims),
|
|
44
|
+
queueTombstones: normalizeTombstoneMap(value && value.queueTombstones),
|
|
45
|
+
sessionTombstones: normalizeTombstoneMap(value && value.sessionTombstones)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function normalizeQueueMutations(options) {
|
|
50
|
+
var values = [];
|
|
51
|
+
if (options && options.queueMutation) values.push(options.queueMutation);
|
|
52
|
+
if (options && Array.isArray(options.queueMutations)) values.push.apply(values, options.queueMutations);
|
|
53
|
+
return values.filter(function (value) {
|
|
54
|
+
return value && typeof value === 'object' && cleanPart(value.type);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function applyQueueMutations(meta, mutations, writerId) {
|
|
59
|
+
var next = normalizeMeta(meta);
|
|
60
|
+
for (var mutation of mutations) {
|
|
61
|
+
var type = cleanPart(mutation.type);
|
|
62
|
+
var itemId = cleanPart(mutation.itemId);
|
|
63
|
+
var sessionId = cleanPart(mutation.sessionId);
|
|
64
|
+
if (!itemId && type !== 'transition') {
|
|
65
|
+
return { ok: false, reason: 'invalid_queue_mutation' };
|
|
66
|
+
}
|
|
67
|
+
if (type === 'remove' || type === 'release' || type === 'restore') {
|
|
68
|
+
var claimed = next.queueClaims[itemId];
|
|
69
|
+
var mutationToken = cleanPart(mutation.claimToken);
|
|
70
|
+
if (claimed && !isExpiredClaim(claimed)
|
|
71
|
+
&& (claimed.ownerId !== writerId
|
|
72
|
+
|| !mutationToken
|
|
73
|
+
|| claimed.claimToken !== mutationToken)) {
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
reason: 'queue_claim_conflict',
|
|
77
|
+
ownerId: claimed.ownerId,
|
|
78
|
+
itemId: itemId
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (type === 'claim') {
|
|
83
|
+
var existing = next.queueClaims[itemId];
|
|
84
|
+
if (existing && existing.ownerId !== writerId && !isExpiredClaim(existing)) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
reason: 'queue_claim_conflict',
|
|
88
|
+
ownerId: existing.ownerId,
|
|
89
|
+
itemId: itemId
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
next.queueRevision += 1;
|
|
93
|
+
next.queueClaims[itemId] = {
|
|
94
|
+
...createQueueClaim({
|
|
95
|
+
itemId: itemId,
|
|
96
|
+
ownerId: writerId,
|
|
97
|
+
queueRevision: next.queueRevision
|
|
98
|
+
}),
|
|
99
|
+
sessionId: sessionId,
|
|
100
|
+
claimToken: cleanPart(mutation.claimToken)
|
|
101
|
+
};
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (type === 'remove') {
|
|
105
|
+
next.queueRevision += 1;
|
|
106
|
+
next.queueTombstones = mergeTombstones(next.queueTombstones, [itemId], 500);
|
|
107
|
+
delete next.queueClaims[itemId];
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (type === 'release') {
|
|
111
|
+
next.queueRevision += 1;
|
|
112
|
+
delete next.queueClaims[itemId];
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (type === 'restore') {
|
|
116
|
+
next.queueRevision += 1;
|
|
117
|
+
delete next.queueClaims[itemId];
|
|
118
|
+
delete next.queueTombstones[itemId];
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (type === 'enqueue' || type === 'transition') {
|
|
122
|
+
next.queueRevision += 1;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
return { ok: false, reason: 'invalid_queue_mutation' };
|
|
126
|
+
}
|
|
127
|
+
return { ok: true, meta: next };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function normalizeQueueClaims(value) {
|
|
131
|
+
var source = value && typeof value === 'object' ? value : {};
|
|
132
|
+
var result = {};
|
|
133
|
+
Object.entries(source).forEach(function (entry) {
|
|
134
|
+
var itemId = cleanPart(entry[0]);
|
|
135
|
+
var claim = entry[1] && typeof entry[1] === 'object' ? entry[1] : {};
|
|
136
|
+
var ownerId = cleanPart(claim.ownerId);
|
|
137
|
+
if (!itemId || !ownerId) return;
|
|
138
|
+
result[itemId] = {
|
|
139
|
+
itemId: itemId,
|
|
140
|
+
ownerId: ownerId,
|
|
141
|
+
sessionId: cleanPart(claim.sessionId),
|
|
142
|
+
claimToken: cleanPart(claim.claimToken),
|
|
143
|
+
queueRevision: normalizeRevision(claim.queueRevision),
|
|
144
|
+
claimedAt: cleanPart(claim.claimedAt)
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function normalizeTombstoneMap(value) {
|
|
151
|
+
var source = value && typeof value === 'object' ? value : {};
|
|
152
|
+
return Object.fromEntries(Object.entries(source)
|
|
153
|
+
.map(function (entry) { return [cleanPart(entry[0]), cleanPart(entry[1])]; })
|
|
154
|
+
.filter(function (entry) { return entry[0] && entry[1]; }));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function isExpiredClaim(claim, now) {
|
|
158
|
+
var claimedAt = Date.parse(claim && claim.claimedAt || '');
|
|
159
|
+
return !claimedAt || (Number(now || Date.now()) - claimedAt) > 60 * 1000;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function normalizeRevision(value) {
|
|
163
|
+
var number = Number(value);
|
|
164
|
+
return Number.isSafeInteger(number) && number >= 0 ? number : 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function cleanPart(value) {
|
|
168
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return Object.freeze({
|
|
172
|
+
applyQueueMutations: applyQueueMutations,
|
|
173
|
+
cleanPart: cleanPart,
|
|
174
|
+
createQueueClaim: createQueueClaim,
|
|
175
|
+
isClaimCurrent: isClaimCurrent,
|
|
176
|
+
mergeTombstones: mergeTombstones,
|
|
177
|
+
normalizeMeta: normalizeMeta,
|
|
178
|
+
normalizeQueueMutations: normalizeQueueMutations,
|
|
179
|
+
normalizeRevision: normalizeRevision
|
|
180
|
+
});
|
|
181
|
+
});
|