@fairfox/polly 0.14.0 → 0.15.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/dist/src/background/index.js +342 -3
- package/dist/src/background/index.js.map +7 -4
- package/dist/src/background/message-router.js +342 -3
- package/dist/src/background/message-router.js.map +7 -4
- package/dist/src/index.js +402 -99
- package/dist/src/index.js.map +8 -5
- package/dist/src/shared/adapters/index.d.ts +3 -0
- package/dist/src/shared/adapters/index.js +356 -4
- package/dist/src/shared/adapters/index.js.map +7 -4
- package/dist/src/shared/lib/adapter-factory.d.ts +80 -0
- package/dist/src/shared/lib/context-helpers.js +342 -3
- package/dist/src/shared/lib/context-helpers.js.map +7 -4
- package/dist/src/shared/lib/message-bus.js +342 -3
- package/dist/src/shared/lib/message-bus.js.map +7 -4
- package/dist/src/shared/lib/state.d.ts +5 -1
- package/dist/src/shared/lib/state.js +274 -1173
- package/dist/src/shared/lib/state.js.map +6 -19
- package/dist/src/shared/lib/storage-adapter.d.ts +42 -0
- package/dist/src/shared/lib/sync-adapter.d.ts +79 -0
- package/dist/src/shared/state/app-state.js +294 -1173
- package/dist/src/shared/state/app-state.js.map +6 -18
- package/dist/tools/analysis/src/extract/handlers.d.ts +48 -1
- package/dist/tools/analysis/src/types/core.d.ts +20 -0
- package/dist/tools/teach/src/cli.js +454 -7
- package/dist/tools/teach/src/cli.js.map +4 -4
- package/dist/tools/teach/src/index.js +232 -7
- package/dist/tools/teach/src/index.js.map +3 -3
- package/dist/tools/verify/src/cli.js +234 -9
- package/dist/tools/verify/src/cli.js.map +4 -4
- package/dist/tools/visualize/src/cli.js +232 -7
- package/dist/tools/visualize/src/cli.js.map +3 -3
- package/package.json +1 -1
|
@@ -48,1194 +48,301 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
48
48
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
// src/shared/lib/
|
|
52
|
-
var
|
|
53
|
-
__export(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
clearConstraints: () => clearConstraints,
|
|
59
|
-
checkPreconditions: () => checkPreconditions,
|
|
60
|
-
checkPostconditions: () => checkPostconditions
|
|
61
|
-
});
|
|
62
|
-
function registerConstraint(field, messageType, constraint) {
|
|
63
|
-
if (!registry.has(field)) {
|
|
64
|
-
registry.set(field, new Map);
|
|
65
|
-
}
|
|
66
|
-
registry.get(field)?.set(messageType, constraint);
|
|
67
|
-
}
|
|
68
|
-
function registerConstraints(field, constraints) {
|
|
69
|
-
for (const [messageType, constraint] of Object.entries(constraints)) {
|
|
70
|
-
const runtimeConstraint = {
|
|
71
|
-
message: constraint.message
|
|
72
|
-
};
|
|
73
|
-
if (typeof constraint.requires === "function") {
|
|
74
|
-
runtimeConstraint.requires = constraint.requires;
|
|
75
|
-
}
|
|
76
|
-
if (typeof constraint.ensures === "function") {
|
|
77
|
-
runtimeConstraint.ensures = constraint.ensures;
|
|
78
|
-
}
|
|
79
|
-
if (runtimeConstraint.requires || runtimeConstraint.ensures) {
|
|
80
|
-
registerConstraint(field, messageType, runtimeConstraint);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
function executeConstraint(predicate, state, messageType, field, customMessage, constraintType = "Precondition") {
|
|
85
|
-
try {
|
|
86
|
-
const result = predicate(state);
|
|
87
|
-
if (!result) {
|
|
88
|
-
const message = customMessage || `${constraintType} failed for ${messageType} on field ${field}`;
|
|
89
|
-
throw new Error(message);
|
|
90
|
-
}
|
|
91
|
-
} catch (error) {
|
|
92
|
-
if (error instanceof Error && error.message.includes(`${constraintType} failed`)) {
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
const message = customMessage || `${constraintType} check error for ${messageType} on field ${field}`;
|
|
96
|
-
throw new Error(`${message}: ${error}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function checkPreconditions(messageType, state) {
|
|
100
|
-
for (const [field, constraints] of registry) {
|
|
101
|
-
const constraint = constraints.get(messageType);
|
|
102
|
-
if (constraint?.requires) {
|
|
103
|
-
executeConstraint(constraint.requires, state, messageType, field, constraint.message, "Precondition");
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
function checkPostconditions(messageType, state) {
|
|
108
|
-
for (const [field, constraints] of registry) {
|
|
109
|
-
const constraint = constraints.get(messageType);
|
|
110
|
-
if (constraint?.ensures) {
|
|
111
|
-
executeConstraint(constraint.ensures, state, messageType, field, constraint.message, "Postcondition");
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
function clearConstraints() {
|
|
116
|
-
registry.clear();
|
|
117
|
-
}
|
|
118
|
-
function getRegisteredConstraints() {
|
|
119
|
-
return new Map(registry);
|
|
120
|
-
}
|
|
121
|
-
function isRuntimeConstraintsEnabled() {
|
|
122
|
-
if (typeof process !== "undefined" && process.env) {
|
|
123
|
-
return process.env["POLLY_RUNTIME_CONSTRAINTS"] === "true";
|
|
124
|
-
}
|
|
125
|
-
if (typeof Bun !== "undefined" && Bun.env) {
|
|
126
|
-
return Bun.env["POLLY_RUNTIME_CONSTRAINTS"] === "true";
|
|
127
|
-
}
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
var registry;
|
|
131
|
-
var init_constraints = __esm(() => {
|
|
132
|
-
registry = new Map;
|
|
51
|
+
// src/shared/lib/storage-adapter.ts
|
|
52
|
+
var exports_storage_adapter = {};
|
|
53
|
+
__export(exports_storage_adapter, {
|
|
54
|
+
createStorageAdapter: () => createStorageAdapter,
|
|
55
|
+
MemoryStorageAdapter: () => MemoryStorageAdapter,
|
|
56
|
+
IndexedDBAdapter: () => IndexedDBAdapter,
|
|
57
|
+
ChromeStorageAdapter: () => ChromeStorageAdapter
|
|
133
58
|
});
|
|
134
59
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
60
|
+
class IndexedDBAdapter {
|
|
61
|
+
dbName;
|
|
62
|
+
storeName = "state";
|
|
63
|
+
dbPromise = null;
|
|
64
|
+
constructor(dbName = "polly-state") {
|
|
65
|
+
this.dbName = dbName;
|
|
66
|
+
}
|
|
67
|
+
getDB() {
|
|
68
|
+
if (this.dbPromise)
|
|
69
|
+
return this.dbPromise;
|
|
70
|
+
this.dbPromise = new Promise((resolve, reject) => {
|
|
71
|
+
const request = indexedDB.open(this.dbName, 1);
|
|
72
|
+
request.onerror = () => reject(request.error);
|
|
73
|
+
request.onsuccess = () => resolve(request.result);
|
|
74
|
+
request.onupgradeneeded = (event) => {
|
|
75
|
+
const db = event.target.result;
|
|
76
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
77
|
+
db.createObjectStore(this.storeName);
|
|
144
78
|
}
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
async update(id, updateProperties) {
|
|
149
|
-
await chrome.contextMenus.update(id, updateProperties);
|
|
150
|
-
}
|
|
151
|
-
async remove(id) {
|
|
152
|
-
await chrome.contextMenus.remove(id);
|
|
153
|
-
}
|
|
154
|
-
async removeAll() {
|
|
155
|
-
await chrome.contextMenus.removeAll();
|
|
156
|
-
}
|
|
157
|
-
onClicked(callback) {
|
|
158
|
-
chrome.contextMenus.onClicked.addListener(callback);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// src/shared/adapters/chrome/offscreen.chrome.ts
|
|
163
|
-
class ChromeOffscreenAdapter {
|
|
164
|
-
async createDocument(parameters) {
|
|
165
|
-
await chrome.offscreen.createDocument({
|
|
166
|
-
url: parameters.url,
|
|
167
|
-
reasons: parameters.reasons,
|
|
168
|
-
justification: parameters.justification
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
async closeDocument() {
|
|
172
|
-
await chrome.offscreen.closeDocument();
|
|
173
|
-
}
|
|
174
|
-
async hasDocument() {
|
|
175
|
-
const existingContexts = await chrome.runtime.getContexts({
|
|
176
|
-
contextTypes: ["OFFSCREEN_DOCUMENT"]
|
|
79
|
+
};
|
|
177
80
|
});
|
|
178
|
-
return
|
|
81
|
+
return this.dbPromise;
|
|
179
82
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
...sender.tab && {
|
|
193
|
-
tab: {
|
|
194
|
-
id: sender.tab.id ?? 0,
|
|
195
|
-
url: sender.tab.url ?? "",
|
|
196
|
-
title: sender.tab.title ?? ""
|
|
83
|
+
async get(keys) {
|
|
84
|
+
try {
|
|
85
|
+
const db = await this.getDB();
|
|
86
|
+
const result = {};
|
|
87
|
+
await Promise.all(keys.map((key) => new Promise((resolve, reject) => {
|
|
88
|
+
const transaction = db.transaction([this.storeName], "readonly");
|
|
89
|
+
const store = transaction.objectStore(this.storeName);
|
|
90
|
+
const request = store.get(key);
|
|
91
|
+
request.onerror = () => reject(request.error);
|
|
92
|
+
request.onsuccess = () => {
|
|
93
|
+
if (request.result !== undefined) {
|
|
94
|
+
result[key] = request.result;
|
|
197
95
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
chrome.runtime.onMessage.addListener(wrappedCallback);
|
|
206
|
-
ChromeRuntimeAdapter.listenerCount++;
|
|
207
|
-
if (ChromeRuntimeAdapter.listenerCount > 1) {
|
|
208
|
-
console.warn(`⚠️ WARNING: ${ChromeRuntimeAdapter.listenerCount} chrome.runtime.onMessage listeners registered!
|
|
209
|
-
|
|
210
|
-
Multiple listeners will cause message handlers to execute multiple times.
|
|
211
|
-
This is usually caused by:
|
|
212
|
-
1. Creating both MessageBus and MessageRouter with separate listeners
|
|
213
|
-
2. Calling createBackground() multiple times
|
|
214
|
-
3. Calling getMessageBus('background') after createBackground()
|
|
215
|
-
|
|
216
|
-
Fix: In background scripts, use createBackground() ONCE at startup.
|
|
217
|
-
Do not call getMessageBus('background') separately.`);
|
|
96
|
+
resolve();
|
|
97
|
+
};
|
|
98
|
+
})));
|
|
99
|
+
return result;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.warn("[Polly] IndexedDB get failed:", error);
|
|
102
|
+
return {};
|
|
218
103
|
}
|
|
219
104
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
105
|
+
async set(items) {
|
|
106
|
+
try {
|
|
107
|
+
const db = await this.getDB();
|
|
108
|
+
await Promise.all(Object.entries(items).map(([key, value]) => new Promise((resolve, reject) => {
|
|
109
|
+
const transaction = db.transaction([this.storeName], "readwrite");
|
|
110
|
+
const store = transaction.objectStore(this.storeName);
|
|
111
|
+
const request = store.put(value, key);
|
|
112
|
+
request.onerror = () => reject(request.error);
|
|
113
|
+
request.onsuccess = () => resolve();
|
|
114
|
+
})));
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.warn("[Polly] IndexedDB set failed:", error);
|
|
226
117
|
}
|
|
227
118
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return chrome.runtime.id;
|
|
242
|
-
}
|
|
243
|
-
openOptionsPage() {
|
|
244
|
-
chrome.runtime.openOptionsPage();
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
class ChromePortAdapter {
|
|
249
|
-
port;
|
|
250
|
-
listeners = {
|
|
251
|
-
message: new Set,
|
|
252
|
-
disconnect: new Set
|
|
253
|
-
};
|
|
254
|
-
constructor(port) {
|
|
255
|
-
this.port = port;
|
|
256
|
-
this.port.onMessage.addListener((message) => {
|
|
257
|
-
for (const callback of this.listeners.message) {
|
|
258
|
-
callback(message);
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
this.port.onDisconnect.addListener(() => {
|
|
262
|
-
for (const callback of this.listeners.disconnect) {
|
|
263
|
-
callback();
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
get name() {
|
|
268
|
-
return this.port.name;
|
|
269
|
-
}
|
|
270
|
-
postMessage(message) {
|
|
271
|
-
this.port.postMessage(message);
|
|
272
|
-
}
|
|
273
|
-
onMessage(callback) {
|
|
274
|
-
this.listeners.message.add(callback);
|
|
275
|
-
}
|
|
276
|
-
onDisconnect(callback) {
|
|
277
|
-
this.listeners.disconnect.add(callback);
|
|
278
|
-
}
|
|
279
|
-
disconnect() {
|
|
280
|
-
this.port.disconnect();
|
|
119
|
+
async remove(keys) {
|
|
120
|
+
try {
|
|
121
|
+
const db = await this.getDB();
|
|
122
|
+
await Promise.all(keys.map((key) => new Promise((resolve, reject) => {
|
|
123
|
+
const transaction = db.transaction([this.storeName], "readwrite");
|
|
124
|
+
const store = transaction.objectStore(this.storeName);
|
|
125
|
+
const request = store.delete(key);
|
|
126
|
+
request.onerror = () => reject(request.error);
|
|
127
|
+
request.onsuccess = () => resolve();
|
|
128
|
+
})));
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.warn("[Polly] IndexedDB remove failed:", error);
|
|
131
|
+
}
|
|
281
132
|
}
|
|
282
133
|
}
|
|
283
134
|
|
|
284
|
-
// src/shared/adapters/chrome/storage.chrome.ts
|
|
285
135
|
class ChromeStorageAdapter {
|
|
286
136
|
async get(keys) {
|
|
287
|
-
if (
|
|
288
|
-
return
|
|
137
|
+
if (typeof chrome === "undefined" || !chrome.storage) {
|
|
138
|
+
return {};
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
return await chrome.storage.local.get(keys);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.warn("[Polly] Chrome storage get failed:", error);
|
|
144
|
+
return {};
|
|
289
145
|
}
|
|
290
|
-
return await chrome.storage.local.get(keys);
|
|
291
146
|
}
|
|
292
147
|
async set(items) {
|
|
293
|
-
|
|
148
|
+
if (typeof chrome === "undefined" || !chrome.storage) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
await chrome.storage.local.set(items);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.warn("[Polly] Chrome storage set failed:", error);
|
|
155
|
+
}
|
|
294
156
|
}
|
|
295
157
|
async remove(keys) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
async clear() {
|
|
299
|
-
await chrome.storage.local.clear();
|
|
300
|
-
}
|
|
301
|
-
onChanged(callback) {
|
|
302
|
-
chrome.storage.onChanged.addListener((changes, areaName) => {
|
|
303
|
-
const mappedChanges = {};
|
|
304
|
-
for (const [key, change] of Object.entries(changes)) {
|
|
305
|
-
mappedChanges[key] = {
|
|
306
|
-
oldValue: change.oldValue,
|
|
307
|
-
newValue: change.newValue
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
callback(mappedChanges, areaName);
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// src/shared/adapters/chrome/tabs.chrome.ts
|
|
316
|
-
class ChromeTabsAdapter {
|
|
317
|
-
async query(queryInfo) {
|
|
318
|
-
return chrome.tabs.query(queryInfo);
|
|
319
|
-
}
|
|
320
|
-
async get(tabId) {
|
|
321
|
-
return chrome.tabs.get(tabId);
|
|
322
|
-
}
|
|
323
|
-
async sendMessage(tabId, message) {
|
|
324
|
-
return chrome.tabs.sendMessage(tabId, message);
|
|
325
|
-
}
|
|
326
|
-
async reload(tabId, reloadProperties) {
|
|
327
|
-
if (reloadProperties) {
|
|
328
|
-
await chrome.tabs.reload(tabId, reloadProperties);
|
|
329
|
-
} else {
|
|
330
|
-
await chrome.tabs.reload(tabId);
|
|
158
|
+
if (typeof chrome === "undefined" || !chrome.storage) {
|
|
159
|
+
return;
|
|
331
160
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
onUpdated(callback) {
|
|
337
|
-
chrome.tabs.onUpdated.addListener(callback);
|
|
338
|
-
}
|
|
339
|
-
onActivated(callback) {
|
|
340
|
-
chrome.tabs.onActivated.addListener(callback);
|
|
341
|
-
}
|
|
342
|
-
async create(createProperties) {
|
|
343
|
-
return chrome.tabs.create(createProperties);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// src/shared/adapters/chrome/window.chrome.ts
|
|
348
|
-
class ChromeWindowAdapter {
|
|
349
|
-
postMessage(message, targetOrigin) {
|
|
350
|
-
window.postMessage(message, targetOrigin);
|
|
351
|
-
}
|
|
352
|
-
addEventListener(type, listener) {
|
|
353
|
-
window.addEventListener(type, listener);
|
|
354
|
-
}
|
|
355
|
-
removeEventListener(type, listener) {
|
|
356
|
-
window.removeEventListener(type, listener);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// src/shared/adapters/fetch.adapter.ts
|
|
361
|
-
class BrowserFetchAdapter {
|
|
362
|
-
fetch(input, init) {
|
|
363
|
-
return fetch(input, init);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// src/shared/adapters/logger.adapter.ts
|
|
368
|
-
class MessageLoggerAdapter {
|
|
369
|
-
runtime;
|
|
370
|
-
sourceContext;
|
|
371
|
-
options;
|
|
372
|
-
constructor(runtime, sourceContext, options) {
|
|
373
|
-
this.runtime = runtime;
|
|
374
|
-
this.sourceContext = sourceContext;
|
|
375
|
-
this.options = options;
|
|
376
|
-
}
|
|
377
|
-
debug(message, context) {
|
|
378
|
-
this.sendLog("debug", message, context);
|
|
379
|
-
}
|
|
380
|
-
info(message, context) {
|
|
381
|
-
this.sendLog("info", message, context);
|
|
382
|
-
}
|
|
383
|
-
warn(message, context) {
|
|
384
|
-
this.sendLog("warn", message, context);
|
|
385
|
-
}
|
|
386
|
-
error(message, error, context) {
|
|
387
|
-
this.sendLog("error", message, context, error);
|
|
388
|
-
}
|
|
389
|
-
log(level, message, context) {
|
|
390
|
-
this.sendLog(level, message, context);
|
|
391
|
-
}
|
|
392
|
-
sendLog(level, message, context, error) {
|
|
393
|
-
if (this.options?.consoleMirror) {
|
|
394
|
-
const consoleMethod = console[level] || console.log;
|
|
395
|
-
consoleMethod(`[${this.sourceContext}]`, message, context || "", error || "");
|
|
161
|
+
try {
|
|
162
|
+
await chrome.storage.local.remove(keys);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.warn("[Polly] Chrome storage remove failed:", error);
|
|
396
165
|
}
|
|
397
|
-
const logMessage = {
|
|
398
|
-
type: "LOG",
|
|
399
|
-
level,
|
|
400
|
-
message,
|
|
401
|
-
context,
|
|
402
|
-
error: error?.message,
|
|
403
|
-
stack: error?.stack,
|
|
404
|
-
source: this.sourceContext,
|
|
405
|
-
timestamp: Date.now()
|
|
406
|
-
};
|
|
407
|
-
this.runtime.sendMessage(logMessage).catch((sendError) => {
|
|
408
|
-
if (this.options?.fallbackToConsole !== false) {
|
|
409
|
-
console[level](`[${this.sourceContext}] ${message}`, context || "", error || "");
|
|
410
|
-
console.warn("Failed to send log message:", sendError);
|
|
411
|
-
}
|
|
412
|
-
});
|
|
413
166
|
}
|
|
414
167
|
}
|
|
415
168
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
logger: new MessageLoggerAdapter(runtime2, context, {
|
|
428
|
-
...options?.consoleMirror !== undefined && { consoleMirror: options.consoleMirror },
|
|
429
|
-
fallbackToConsole: true
|
|
430
|
-
})
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// src/shared/types/messages.ts
|
|
435
|
-
var ALL_CONTEXTS = [
|
|
436
|
-
"background",
|
|
437
|
-
"content",
|
|
438
|
-
"page",
|
|
439
|
-
"devtools",
|
|
440
|
-
"popup",
|
|
441
|
-
"options",
|
|
442
|
-
"sidepanel",
|
|
443
|
-
"offscreen"
|
|
444
|
-
];
|
|
445
|
-
var defaultSettings = {
|
|
446
|
-
theme: "auto",
|
|
447
|
-
autoSync: true,
|
|
448
|
-
debugMode: false,
|
|
449
|
-
notifications: true,
|
|
450
|
-
apiEndpoint: "https://api.example.com",
|
|
451
|
-
refreshInterval: 60000
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
// src/shared/lib/errors.ts
|
|
455
|
-
class ExtensionError extends Error {
|
|
456
|
-
code;
|
|
457
|
-
context;
|
|
458
|
-
constructor(message, code, context) {
|
|
459
|
-
super(message);
|
|
460
|
-
this.code = code;
|
|
461
|
-
this.context = context;
|
|
462
|
-
this.name = this.constructor.name;
|
|
463
|
-
Error.captureStackTrace?.(this, this.constructor);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
class TimeoutError extends ExtensionError {
|
|
468
|
-
timeoutMs;
|
|
469
|
-
constructor(message, timeoutMs, context) {
|
|
470
|
-
super(message, "TIMEOUT_ERROR", { ...context, timeoutMs });
|
|
471
|
-
this.timeoutMs = timeoutMs;
|
|
169
|
+
class MemoryStorageAdapter {
|
|
170
|
+
storage = new Map;
|
|
171
|
+
async get(keys) {
|
|
172
|
+
const result = {};
|
|
173
|
+
for (const key of keys) {
|
|
174
|
+
const value = this.storage.get(key);
|
|
175
|
+
if (value !== undefined) {
|
|
176
|
+
result[key] = value;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
472
180
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
super(message, "CONNECTION_ERROR", context);
|
|
181
|
+
async set(items) {
|
|
182
|
+
for (const [key, value] of Object.entries(items)) {
|
|
183
|
+
this.storage.set(key, value);
|
|
184
|
+
}
|
|
478
185
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
super(message, "MESSAGE_ROUTER_ERROR", context);
|
|
186
|
+
async remove(keys) {
|
|
187
|
+
for (const key of keys) {
|
|
188
|
+
this.storage.delete(key);
|
|
189
|
+
}
|
|
484
190
|
}
|
|
485
191
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
constructor(message, messageType, context) {
|
|
490
|
-
super(message, "HANDLER_ERROR", { ...context, messageType });
|
|
491
|
-
this.messageType = messageType;
|
|
192
|
+
function createStorageAdapter() {
|
|
193
|
+
if (typeof chrome !== "undefined" && chrome.storage && chrome.runtime) {
|
|
194
|
+
return new ChromeStorageAdapter;
|
|
492
195
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
class APIError extends ExtensionError {
|
|
496
|
-
statusCode;
|
|
497
|
-
constructor(message, statusCode, context) {
|
|
498
|
-
super(message, "API_ERROR", { ...context, statusCode });
|
|
499
|
-
this.statusCode = statusCode;
|
|
196
|
+
if (typeof indexedDB !== "undefined") {
|
|
197
|
+
return new IndexedDBAdapter;
|
|
500
198
|
}
|
|
199
|
+
return new MemoryStorageAdapter;
|
|
501
200
|
}
|
|
502
201
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
202
|
+
// src/shared/lib/sync-adapter.ts
|
|
203
|
+
var exports_sync_adapter = {};
|
|
204
|
+
__export(exports_sync_adapter, {
|
|
205
|
+
createSyncAdapter: () => createSyncAdapter,
|
|
206
|
+
NoOpSyncAdapter: () => NoOpSyncAdapter,
|
|
207
|
+
ChromeRuntimeSyncAdapter: () => ChromeRuntimeSyncAdapter,
|
|
208
|
+
BroadcastChannelSyncAdapter: () => BroadcastChannelSyncAdapter
|
|
209
|
+
});
|
|
508
210
|
|
|
509
|
-
class
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
514
|
-
throw(error) {
|
|
515
|
-
this.logger.error(error.message, error, error.context);
|
|
516
|
-
throw error;
|
|
517
|
-
}
|
|
518
|
-
reject(error) {
|
|
519
|
-
this.logger.error(error.message, error, error.context);
|
|
520
|
-
return error;
|
|
521
|
-
}
|
|
522
|
-
wrap(error, message, code, context) {
|
|
523
|
-
const originalError = error instanceof Error ? error : new Error(String(error));
|
|
524
|
-
const wrappedError = new ExtensionError(`${message}: ${originalError.message}`, code, {
|
|
525
|
-
...context,
|
|
526
|
-
originalError: originalError.message,
|
|
527
|
-
originalStack: originalError.stack
|
|
528
|
-
});
|
|
529
|
-
if (originalError.stack) {
|
|
530
|
-
wrappedError.stack = originalError.stack;
|
|
531
|
-
}
|
|
532
|
-
this.logger.error(wrappedError.message, wrappedError, wrappedError.context);
|
|
533
|
-
return wrappedError;
|
|
211
|
+
class NoOpSyncAdapter {
|
|
212
|
+
broadcast(_message) {}
|
|
213
|
+
onMessage(_callback) {
|
|
214
|
+
return () => {};
|
|
534
215
|
}
|
|
535
216
|
}
|
|
536
217
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
readyState: document.readyState
|
|
547
|
-
};
|
|
548
|
-
},
|
|
549
|
-
queryElements(selector) {
|
|
550
|
-
const elements = document.querySelectorAll(selector);
|
|
551
|
-
return Array.from(elements).map((el) => ({
|
|
552
|
-
tagName: el.tagName,
|
|
553
|
-
id: el.id,
|
|
554
|
-
className: el.className,
|
|
555
|
-
textContent: el.textContent?.slice(0, 100) || ""
|
|
556
|
-
}));
|
|
557
|
-
},
|
|
558
|
-
getPageMetadata() {
|
|
559
|
-
const metadata = {};
|
|
560
|
-
const metaTags = document.querySelectorAll("meta");
|
|
561
|
-
for (const tag of Array.from(metaTags)) {
|
|
562
|
-
const name = tag.getAttribute("name") || tag.getAttribute("property");
|
|
563
|
-
const content = tag.getAttribute("content");
|
|
564
|
-
if (name && content) {
|
|
565
|
-
metadata[name] = content;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
return metadata;
|
|
569
|
-
},
|
|
570
|
-
injectCSS(css) {
|
|
571
|
-
const styleId = `ext-injected-${Date.now()}`;
|
|
572
|
-
const style = document.createElement("style");
|
|
573
|
-
style.id = styleId;
|
|
574
|
-
style.textContent = css;
|
|
575
|
-
document.head.appendChild(style);
|
|
576
|
-
},
|
|
577
|
-
removeCSS(styleId) {
|
|
578
|
-
const style = document.getElementById(styleId);
|
|
579
|
-
if (style) {
|
|
580
|
-
style.remove();
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
function createDevToolsHelpers() {
|
|
586
|
-
return {
|
|
587
|
-
get inspectedTabId() {
|
|
588
|
-
return chrome.devtools?.inspectedWindow?.tabId;
|
|
589
|
-
},
|
|
590
|
-
evalInPage(code) {
|
|
591
|
-
return new Promise((resolve, reject) => {
|
|
592
|
-
if (!chrome.devtools?.inspectedWindow) {
|
|
593
|
-
reject(new Error("DevTools inspectedWindow not available"));
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
chrome.devtools.inspectedWindow.eval(code, (result, error) => {
|
|
597
|
-
if (error) {
|
|
598
|
-
reject(new Error(error.isException ? error.value : "Execution error"));
|
|
599
|
-
} else {
|
|
600
|
-
resolve(result);
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
});
|
|
604
|
-
},
|
|
605
|
-
getPageResource(url) {
|
|
606
|
-
return new Promise((resolve, reject) => {
|
|
607
|
-
if (!chrome.devtools?.inspectedWindow) {
|
|
608
|
-
reject(new Error("DevTools inspectedWindow not available"));
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
chrome.devtools.inspectedWindow.getResources((resources) => {
|
|
612
|
-
const resource = resources.find((r) => r.url === url);
|
|
613
|
-
if (!resource) {
|
|
614
|
-
reject(new Error(`Resource not found: ${url}`));
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
resource.getContent((content, encoding) => {
|
|
618
|
-
if (encoding === "base64") {
|
|
619
|
-
resolve(atob(content));
|
|
620
|
-
} else {
|
|
621
|
-
resolve(content);
|
|
622
|
-
}
|
|
218
|
+
class ChromeRuntimeSyncAdapter {
|
|
219
|
+
listeners = [];
|
|
220
|
+
port = null;
|
|
221
|
+
constructor() {
|
|
222
|
+
if (typeof chrome !== "undefined" && chrome.runtime) {
|
|
223
|
+
chrome.runtime.onMessage.addListener((message, _sender, _sendResponse) => {
|
|
224
|
+
if (message.type === "STATE_SYNC") {
|
|
225
|
+
this.listeners.forEach((listener) => {
|
|
226
|
+
listener(message);
|
|
623
227
|
});
|
|
624
|
-
}
|
|
228
|
+
}
|
|
625
229
|
});
|
|
626
|
-
},
|
|
627
|
-
reloadInspectedPage(options = {}) {
|
|
628
|
-
if (!chrome.devtools?.inspectedWindow) {
|
|
629
|
-
console.warn("DevTools inspectedWindow not available");
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
chrome.devtools.inspectedWindow.reload(options);
|
|
633
|
-
}
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
function createPopupHelpers(adapters) {
|
|
637
|
-
return {
|
|
638
|
-
async getCurrentTab() {
|
|
639
|
-
const tabs2 = await adapters.tabs.query({ active: true, currentWindow: true });
|
|
640
|
-
return tabs2[0];
|
|
641
|
-
},
|
|
642
|
-
closePopup() {
|
|
643
|
-
window.close();
|
|
644
|
-
},
|
|
645
|
-
setDimensions(width, height) {
|
|
646
|
-
document.body.style.width = `${width}px`;
|
|
647
|
-
document.body.style.height = `${height}px`;
|
|
648
|
-
}
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
function createOptionsHelpers(adapters) {
|
|
652
|
-
return {
|
|
653
|
-
openInNewTab(path) {
|
|
654
|
-
adapters.tabs.create({ url: path });
|
|
655
|
-
},
|
|
656
|
-
showSaveConfirmation(message = "Settings saved!", duration = 3000) {
|
|
657
|
-
const notification = document.createElement("div");
|
|
658
|
-
notification.textContent = message;
|
|
659
|
-
notification.style.cssText = `
|
|
660
|
-
position: fixed;
|
|
661
|
-
top: 20px;
|
|
662
|
-
right: 20px;
|
|
663
|
-
background: #4caf50;
|
|
664
|
-
color: white;
|
|
665
|
-
padding: 12px 24px;
|
|
666
|
-
border-radius: 4px;
|
|
667
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
668
|
-
z-index: 10000;
|
|
669
|
-
animation: slideIn 0.3s ease;
|
|
670
|
-
`;
|
|
671
|
-
document.body.appendChild(notification);
|
|
672
|
-
setTimeout(() => {
|
|
673
|
-
notification.style.animation = "slideOut 0.3s ease";
|
|
674
|
-
setTimeout(() => notification.remove(), 300);
|
|
675
|
-
}, duration);
|
|
676
|
-
},
|
|
677
|
-
showError(message, duration = 5000) {
|
|
678
|
-
const notification = document.createElement("div");
|
|
679
|
-
notification.textContent = message;
|
|
680
|
-
notification.style.cssText = `
|
|
681
|
-
position: fixed;
|
|
682
|
-
top: 20px;
|
|
683
|
-
right: 20px;
|
|
684
|
-
background: #f44336;
|
|
685
|
-
color: white;
|
|
686
|
-
padding: 12px 24px;
|
|
687
|
-
border-radius: 4px;
|
|
688
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
689
|
-
z-index: 10000;
|
|
690
|
-
animation: slideIn 0.3s ease;
|
|
691
|
-
`;
|
|
692
|
-
document.body.appendChild(notification);
|
|
693
|
-
setTimeout(() => {
|
|
694
|
-
notification.style.animation = "slideOut 0.3s ease";
|
|
695
|
-
setTimeout(() => notification.remove(), 300);
|
|
696
|
-
}, duration);
|
|
697
|
-
}
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
function createSidePanelHelpers(adapters) {
|
|
701
|
-
return {
|
|
702
|
-
async getCurrentTab() {
|
|
703
|
-
const tabs2 = await adapters.tabs.query({ active: true, currentWindow: true });
|
|
704
|
-
return tabs2[0];
|
|
705
|
-
},
|
|
706
|
-
isVisible() {
|
|
707
|
-
return document.visibilityState === "visible";
|
|
708
|
-
},
|
|
709
|
-
setWidth(width) {
|
|
710
|
-
document.body.style.width = `${width}px`;
|
|
711
230
|
}
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
function createBackgroundHelpers(adapters) {
|
|
715
|
-
return {
|
|
716
|
-
async getAllTabs() {
|
|
717
|
-
return adapters.tabs.query({});
|
|
718
|
-
},
|
|
719
|
-
getExtensionViews(type) {
|
|
720
|
-
return chrome.extension.getViews(type && (type === "popup" || type === "tab") ? { type } : undefined);
|
|
721
|
-
},
|
|
722
|
-
openOptionsPage() {
|
|
723
|
-
adapters.runtime.openOptionsPage();
|
|
724
|
-
},
|
|
725
|
-
setBadge(text, color = "#f44336") {
|
|
726
|
-
chrome.action.setBadgeText({ text });
|
|
727
|
-
chrome.action.setBadgeBackgroundColor({ color });
|
|
728
|
-
},
|
|
729
|
-
clearBadge() {
|
|
730
|
-
chrome.action.setBadgeText({ text: "" });
|
|
731
|
-
}
|
|
732
|
-
};
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// src/shared/lib/handler-execution-tracker.ts
|
|
736
|
-
class HandlerExecutionTracker {
|
|
737
|
-
executions = new Map;
|
|
738
|
-
isDevelopment;
|
|
739
|
-
constructor() {
|
|
740
|
-
this.isDevelopment = typeof process !== "undefined" && (process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test");
|
|
741
231
|
}
|
|
742
|
-
|
|
743
|
-
if (!
|
|
232
|
+
broadcast(message) {
|
|
233
|
+
if (typeof chrome === "undefined" || !chrome.runtime) {
|
|
234
|
+
console.warn("[SyncAdapter] chrome.runtime not available");
|
|
744
235
|
return;
|
|
745
|
-
let handlerCounts = this.executions.get(messageId);
|
|
746
|
-
if (!handlerCounts) {
|
|
747
|
-
handlerCounts = new Map;
|
|
748
|
-
this.executions.set(messageId, handlerCounts);
|
|
749
236
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
1. Both MessageBus and MessageRouter registered listeners
|
|
760
|
-
2. createBackground() called multiple times
|
|
761
|
-
3. Handler registered in multiple places
|
|
762
|
-
|
|
763
|
-
Fix: Ensure only ONE listener is registered. In background scripts,
|
|
764
|
-
use createBackground() instead of getMessageBus().
|
|
765
|
-
`);
|
|
766
|
-
console.error(error);
|
|
767
|
-
console.error("Execution trace for message:", messageId);
|
|
768
|
-
console.error(Array.from(handlerCounts.entries()));
|
|
769
|
-
throw error;
|
|
770
|
-
}
|
|
771
|
-
setTimeout(() => {
|
|
772
|
-
this.executions.delete(messageId);
|
|
773
|
-
}, 5000);
|
|
774
|
-
}
|
|
775
|
-
reset() {
|
|
776
|
-
this.executions.clear();
|
|
777
|
-
}
|
|
778
|
-
getExecutionCount(messageId, handlerType) {
|
|
779
|
-
return this.executions.get(messageId)?.get(handlerType) || 0;
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
var globalExecutionTracker = new HandlerExecutionTracker;
|
|
783
|
-
|
|
784
|
-
// src/shared/lib/message-bus.ts
|
|
785
|
-
function isRoutedMessage(value) {
|
|
786
|
-
if (typeof value !== "object" || value === null)
|
|
787
|
-
return false;
|
|
788
|
-
if (!("id" in value) || !("source" in value) || !("targets" in value) || !("payload" in value)) {
|
|
789
|
-
return false;
|
|
790
|
-
}
|
|
791
|
-
return typeof value.id === "string" && typeof value.source === "string" && Array.isArray(value.targets) && typeof value.payload === "object" && value.payload !== null;
|
|
792
|
-
}
|
|
793
|
-
function isRoutedResponse(value) {
|
|
794
|
-
if (typeof value !== "object" || value === null)
|
|
795
|
-
return false;
|
|
796
|
-
if (!("id" in value) || !("success" in value)) {
|
|
797
|
-
return false;
|
|
798
|
-
}
|
|
799
|
-
return typeof value.id === "string" && typeof value.success === "boolean";
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
class MessageBus {
|
|
803
|
-
context;
|
|
804
|
-
adapters;
|
|
805
|
-
helpers;
|
|
806
|
-
pendingRequests = new Map;
|
|
807
|
-
handlers = new Map;
|
|
808
|
-
port = null;
|
|
809
|
-
errorHandler;
|
|
810
|
-
userErrorHandlers = [];
|
|
811
|
-
stateAccessor = null;
|
|
812
|
-
messageListener = null;
|
|
813
|
-
constructor(context, adapters, options) {
|
|
814
|
-
this.context = context;
|
|
815
|
-
this.adapters = adapters || createChromeAdapters(context);
|
|
816
|
-
this.errorHandler = new ErrorHandler(this.adapters.logger);
|
|
817
|
-
this.helpers = this.createContextHelpers();
|
|
818
|
-
if (!options?.skipListenerSetup) {
|
|
819
|
-
this.setupListeners();
|
|
237
|
+
try {
|
|
238
|
+
chrome.runtime.sendMessage({
|
|
239
|
+
type: "STATE_SYNC",
|
|
240
|
+
key: message.key,
|
|
241
|
+
value: message.value,
|
|
242
|
+
clock: message.clock
|
|
243
|
+
});
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.warn("[SyncAdapter] Failed to broadcast state update:", error);
|
|
820
246
|
}
|
|
821
247
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
if (
|
|
827
|
-
|
|
828
|
-
} else {
|
|
829
|
-
targets = [options.target];
|
|
830
|
-
}
|
|
831
|
-
} else {
|
|
832
|
-
const inferredTarget = this.inferTarget(payload.type);
|
|
833
|
-
if (!inferredTarget) {
|
|
834
|
-
throw new Error(`Message type "${payload.type}" is not a framework message. Please provide explicit 'target' option.`);
|
|
248
|
+
onMessage(callback) {
|
|
249
|
+
this.listeners.push(callback);
|
|
250
|
+
return () => {
|
|
251
|
+
const index = this.listeners.indexOf(callback);
|
|
252
|
+
if (index > -1) {
|
|
253
|
+
this.listeners.splice(index, 1);
|
|
835
254
|
}
|
|
836
|
-
targets = Array.isArray(inferredTarget) ? inferredTarget : [inferredTarget];
|
|
837
|
-
}
|
|
838
|
-
const message = {
|
|
839
|
-
id,
|
|
840
|
-
source: this.context,
|
|
841
|
-
targets,
|
|
842
|
-
...options?.tabId !== undefined && { tabId: options.tabId },
|
|
843
|
-
timestamp: Date.now(),
|
|
844
|
-
payload
|
|
845
|
-
};
|
|
846
|
-
return new Promise((resolve, reject) => {
|
|
847
|
-
const timeoutMs = options?.timeout || 5000;
|
|
848
|
-
const timeout = setTimeout(() => {
|
|
849
|
-
this.pendingRequests.delete(id);
|
|
850
|
-
const error = new TimeoutError(`Message timeout: ${payload.type}`, timeoutMs, {
|
|
851
|
-
messageType: payload.type,
|
|
852
|
-
targets
|
|
853
|
-
});
|
|
854
|
-
this.notifyErrorHandlers(error);
|
|
855
|
-
reject(this.errorHandler.reject(error));
|
|
856
|
-
}, timeoutMs);
|
|
857
|
-
this.pendingRequests.set(id, {
|
|
858
|
-
resolve: (value) => {
|
|
859
|
-
clearTimeout(timeout);
|
|
860
|
-
resolve(value);
|
|
861
|
-
},
|
|
862
|
-
reject: (error) => {
|
|
863
|
-
clearTimeout(timeout);
|
|
864
|
-
reject(error);
|
|
865
|
-
},
|
|
866
|
-
timestamp: Date.now(),
|
|
867
|
-
timeout
|
|
868
|
-
});
|
|
869
|
-
this.sendMessage(message);
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
broadcast(payload) {
|
|
873
|
-
const message = {
|
|
874
|
-
id: crypto.randomUUID(),
|
|
875
|
-
source: this.context,
|
|
876
|
-
targets: ALL_CONTEXTS,
|
|
877
|
-
timestamp: Date.now(),
|
|
878
|
-
payload
|
|
879
255
|
};
|
|
880
|
-
this.sendMessage(message);
|
|
881
|
-
}
|
|
882
|
-
on(type, handler) {
|
|
883
|
-
const existing = this.handlers.get(type) || [];
|
|
884
|
-
existing.push(handler);
|
|
885
|
-
this.handlers.set(type, existing);
|
|
886
|
-
}
|
|
887
|
-
registerHandlers(handlers) {
|
|
888
|
-
for (const [type, handler] of Object.entries(handlers)) {
|
|
889
|
-
if (handler) {
|
|
890
|
-
const existing = this.handlers.get(type) || [];
|
|
891
|
-
existing.push(handler);
|
|
892
|
-
this.handlers.set(type, existing);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
onError(handler) {
|
|
897
|
-
this.userErrorHandlers.push(handler);
|
|
898
|
-
}
|
|
899
|
-
setStateAccessor(accessor) {
|
|
900
|
-
this.stateAccessor = accessor;
|
|
901
|
-
}
|
|
902
|
-
async sendToBackground(payload, options) {
|
|
903
|
-
return this.send(payload, { ...options, target: "background" });
|
|
904
|
-
}
|
|
905
|
-
async sendToContentScript(tabId, payload, options) {
|
|
906
|
-
return this.send(payload, { ...options, target: "content", tabId });
|
|
907
|
-
}
|
|
908
|
-
async sendToAllTabs(payload, options) {
|
|
909
|
-
const tabs2 = await this.adapters.tabs.query({});
|
|
910
|
-
return Promise.all(tabs2.map((tab) => tab.id ? this.sendToContentScript(tab.id, payload, options) : Promise.resolve(undefined)));
|
|
911
256
|
}
|
|
912
|
-
|
|
913
|
-
return
|
|
914
|
-
}
|
|
915
|
-
async sendToOptions(payload, options) {
|
|
916
|
-
return this.send(payload, { ...options, target: "options" });
|
|
917
|
-
}
|
|
918
|
-
async sendToDevTools(payload, options) {
|
|
919
|
-
return this.send(payload, { ...options, target: "devtools" });
|
|
920
|
-
}
|
|
921
|
-
async sendToSidePanel(payload, options) {
|
|
922
|
-
return this.send(payload, { ...options, target: "sidepanel" });
|
|
923
|
-
}
|
|
924
|
-
connect(name) {
|
|
925
|
-
if (this.port) {
|
|
926
|
-
console.warn(`[${this.context}] Port already connected: ${this.port.name}`);
|
|
927
|
-
return;
|
|
928
|
-
}
|
|
929
|
-
this.port = this.adapters.runtime.connect(name);
|
|
930
|
-
this.port.onMessage((message) => {
|
|
931
|
-
if (isRoutedMessage(message) || isRoutedResponse(message)) {
|
|
932
|
-
this.handleMessage(message);
|
|
933
|
-
}
|
|
934
|
-
});
|
|
935
|
-
this.port.onDisconnect(() => {
|
|
936
|
-
this.adapters.logger.warn("Port disconnected", {
|
|
937
|
-
context: this.context,
|
|
938
|
-
portName: name
|
|
939
|
-
});
|
|
940
|
-
this.port = null;
|
|
941
|
-
for (const [id, pending] of this.pendingRequests.entries()) {
|
|
942
|
-
const error = new ConnectionError("Port disconnected", {
|
|
943
|
-
context: this.context,
|
|
944
|
-
portName: name,
|
|
945
|
-
requestId: id
|
|
946
|
-
});
|
|
947
|
-
this.notifyErrorHandlers(error);
|
|
948
|
-
pending.reject(this.errorHandler.reject(error));
|
|
949
|
-
clearTimeout(pending.timeout);
|
|
950
|
-
this.pendingRequests.delete(id);
|
|
951
|
-
}
|
|
952
|
-
});
|
|
257
|
+
connect() {
|
|
258
|
+
return Promise.resolve();
|
|
953
259
|
}
|
|
954
260
|
disconnect() {
|
|
261
|
+
this.listeners = [];
|
|
955
262
|
if (this.port) {
|
|
956
263
|
this.port.disconnect();
|
|
957
264
|
this.port = null;
|
|
958
265
|
}
|
|
959
266
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
this.handlers.clear();
|
|
963
|
-
for (const pending of this.pendingRequests.values()) {
|
|
964
|
-
clearTimeout(pending.timeout);
|
|
965
|
-
}
|
|
966
|
-
this.pendingRequests.clear();
|
|
967
|
-
if (this.messageListener) {
|
|
968
|
-
this.adapters.runtime.removeMessageListener(this.messageListener);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
setupListeners() {
|
|
972
|
-
this.messageListener = (message, sender, sendResponse) => {
|
|
973
|
-
if (isRoutedMessage(message) || isRoutedResponse(message)) {
|
|
974
|
-
this.handleMessage(message, sender).then((response) => sendResponse(response)).catch((error) => {
|
|
975
|
-
sendResponse({ success: false, error: error.message });
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
return true;
|
|
979
|
-
};
|
|
980
|
-
this.adapters.runtime.onMessage(this.messageListener);
|
|
981
|
-
if (this.context === "content" || this.context === "page") {
|
|
982
|
-
this.adapters.window.addEventListener("message", (event) => {
|
|
983
|
-
if (event.source !== window)
|
|
984
|
-
return;
|
|
985
|
-
if (event.data?.__extensionMessage) {
|
|
986
|
-
this.handleMessage(event.data.message);
|
|
987
|
-
}
|
|
988
|
-
});
|
|
989
|
-
}
|
|
267
|
+
isConnected() {
|
|
268
|
+
return typeof chrome !== "undefined" && !!chrome.runtime;
|
|
990
269
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
class BroadcastChannelSyncAdapter {
|
|
273
|
+
channel = null;
|
|
274
|
+
listeners = [];
|
|
275
|
+
constructor(channelName = "polly-sync") {
|
|
276
|
+
if (typeof BroadcastChannel !== "undefined") {
|
|
277
|
+
this.channel = new BroadcastChannel(channelName);
|
|
278
|
+
this.channel.onmessage = (event) => {
|
|
279
|
+
if (event.data.type === "STATE_SYNC") {
|
|
280
|
+
this.listeners.forEach((listener) => {
|
|
281
|
+
listener(event.data);
|
|
1002
282
|
});
|
|
1003
|
-
this.notifyErrorHandlers(error);
|
|
1004
|
-
pending.reject(this.errorHandler.reject(error));
|
|
1005
283
|
}
|
|
1006
|
-
}
|
|
1007
|
-
|
|
284
|
+
};
|
|
285
|
+
} else {
|
|
286
|
+
console.warn("[SyncAdapter] BroadcastChannel not available");
|
|
1008
287
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
288
|
+
}
|
|
289
|
+
broadcast(message) {
|
|
290
|
+
if (!this.channel) {
|
|
291
|
+
console.warn("[SyncAdapter] BroadcastChannel not initialized");
|
|
1013
292
|
return;
|
|
1014
293
|
}
|
|
1015
|
-
const handlers = this.handlers.get(message.payload.type);
|
|
1016
|
-
if (!handlers || handlers.length === 0) {
|
|
1017
|
-
if (message.targets.length === 1) {
|
|
1018
|
-
console.warn(`[${this.context}] No handler for message type: ${message.payload.type}`);
|
|
1019
|
-
}
|
|
1020
|
-
return { success: false, error: "No handler" };
|
|
1021
|
-
}
|
|
1022
|
-
if (message.targets.length > 1) {
|
|
1023
|
-
try {
|
|
1024
|
-
globalExecutionTracker.track(message.id, message.payload.type);
|
|
1025
|
-
await Promise.all(handlers.map((handler) => handler(message.payload, message)));
|
|
1026
|
-
return { success: true, data: undefined, timestamp: Date.now() };
|
|
1027
|
-
} catch (error) {
|
|
1028
|
-
return {
|
|
1029
|
-
success: false,
|
|
1030
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1031
|
-
timestamp: Date.now()
|
|
1032
|
-
};
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
if (message.payload.type === "LOG") {
|
|
1036
|
-
try {
|
|
1037
|
-
globalExecutionTracker.track(message.id, message.payload.type);
|
|
1038
|
-
await Promise.all(handlers.map((handler) => handler(message.payload, message)));
|
|
1039
|
-
const response = {
|
|
1040
|
-
id: message.id,
|
|
1041
|
-
success: true,
|
|
1042
|
-
timestamp: Date.now()
|
|
1043
|
-
};
|
|
1044
|
-
this.sendResponse(message, response);
|
|
1045
|
-
return response;
|
|
1046
|
-
} catch (error) {
|
|
1047
|
-
const response = {
|
|
1048
|
-
id: message.id,
|
|
1049
|
-
success: false,
|
|
1050
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1051
|
-
timestamp: Date.now()
|
|
1052
|
-
};
|
|
1053
|
-
this.sendResponse(message, response);
|
|
1054
|
-
return response;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
294
|
try {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
checkPreconditions2(message.payload.type, currentState);
|
|
1065
|
-
}
|
|
1066
|
-
} catch (error) {
|
|
1067
|
-
if (error instanceof Error) {
|
|
1068
|
-
throw error;
|
|
1069
|
-
}
|
|
1070
|
-
if (error && typeof error === "object" && "code" in error && error.code === "MODULE_NOT_FOUND") {} else {
|
|
1071
|
-
throw error;
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
const handler = handlers[0];
|
|
1076
|
-
if (!handler) {
|
|
1077
|
-
throw new Error(`Handler not found for ${message.payload.type}`);
|
|
1078
|
-
}
|
|
1079
|
-
const data = await handler(message.payload, message);
|
|
1080
|
-
if (this.stateAccessor) {
|
|
1081
|
-
try {
|
|
1082
|
-
const { checkPostconditions: checkPostconditions2, isRuntimeConstraintsEnabled: isRuntimeConstraintsEnabled2 } = await Promise.resolve().then(() => (init_constraints(), exports_constraints));
|
|
1083
|
-
if (isRuntimeConstraintsEnabled2()) {
|
|
1084
|
-
const currentState = this.stateAccessor();
|
|
1085
|
-
checkPostconditions2(message.payload.type, currentState);
|
|
1086
|
-
}
|
|
1087
|
-
} catch (error) {
|
|
1088
|
-
if (error instanceof Error && error.message.includes("Postcondition")) {
|
|
1089
|
-
console.error(`[${this.context}] Postcondition failed:`, error.message);
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
const response = {
|
|
1094
|
-
id: message.id,
|
|
1095
|
-
success: true,
|
|
1096
|
-
data,
|
|
1097
|
-
timestamp: Date.now()
|
|
1098
|
-
};
|
|
1099
|
-
this.sendResponse(message, response);
|
|
1100
|
-
return response;
|
|
295
|
+
this.channel.postMessage({
|
|
296
|
+
type: "STATE_SYNC",
|
|
297
|
+
key: message.key,
|
|
298
|
+
value: message.value,
|
|
299
|
+
clock: message.clock
|
|
300
|
+
});
|
|
1101
301
|
} catch (error) {
|
|
1102
|
-
|
|
1103
|
-
id: message.id,
|
|
1104
|
-
success: false,
|
|
1105
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
1106
|
-
timestamp: Date.now()
|
|
1107
|
-
};
|
|
1108
|
-
this.sendResponse(message, response);
|
|
1109
|
-
return response;
|
|
302
|
+
console.warn("[SyncAdapter] Failed to broadcast state update:", error);
|
|
1110
303
|
}
|
|
1111
304
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
} else {
|
|
1120
|
-
this.adapters.runtime.sendMessage(message);
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
sendResponse(request, response) {
|
|
1124
|
-
if (this.context === "content" && request.source === "page") {
|
|
1125
|
-
this.adapters.window.postMessage({ __extensionMessage: true, message: response }, "*");
|
|
1126
|
-
} else if (this.context === "page" && request.source === "content") {
|
|
1127
|
-
this.adapters.window.postMessage({ __extensionMessage: true, message: response }, "*");
|
|
1128
|
-
} else if (this.port && (this.context === "devtools" || this.context === "content")) {
|
|
1129
|
-
this.port.postMessage(response);
|
|
1130
|
-
} else {
|
|
1131
|
-
this.adapters.runtime.sendMessage(response);
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
inferTarget(type) {
|
|
1135
|
-
const handlers = {
|
|
1136
|
-
DOM_QUERY: "content",
|
|
1137
|
-
DOM_UPDATE: "content",
|
|
1138
|
-
DOM_INSERT: "content",
|
|
1139
|
-
DOM_REMOVE: "content",
|
|
1140
|
-
PAGE_EVAL: "page",
|
|
1141
|
-
PAGE_GET_VAR: "page",
|
|
1142
|
-
PAGE_CALL_FN: "page",
|
|
1143
|
-
PAGE_SET_VAR: "page",
|
|
1144
|
-
API_REQUEST: "background",
|
|
1145
|
-
API_BATCH: "background",
|
|
1146
|
-
CLIPBOARD_WRITE: "offscreen",
|
|
1147
|
-
CLIPBOARD_WRITE_HTML: "offscreen",
|
|
1148
|
-
CLIPBOARD_WRITE_RICH: "offscreen",
|
|
1149
|
-
CLIPBOARD_READ: "offscreen",
|
|
1150
|
-
CONTEXT_MENU_CREATE: "background",
|
|
1151
|
-
CONTEXT_MENU_REMOVE: "background",
|
|
1152
|
-
STATE_SYNC: ALL_CONTEXTS,
|
|
1153
|
-
TAB_QUERY: "background",
|
|
1154
|
-
TAB_GET_CURRENT: "background",
|
|
1155
|
-
TAB_RELOAD: "background",
|
|
1156
|
-
LOG: "background",
|
|
1157
|
-
LOGS_GET: "background",
|
|
1158
|
-
LOGS_CLEAR: "background",
|
|
1159
|
-
LOGS_EXPORT: "background"
|
|
305
|
+
onMessage(callback) {
|
|
306
|
+
this.listeners.push(callback);
|
|
307
|
+
return () => {
|
|
308
|
+
const index = this.listeners.indexOf(callback);
|
|
309
|
+
if (index > -1) {
|
|
310
|
+
this.listeners.splice(index, 1);
|
|
311
|
+
}
|
|
1160
312
|
};
|
|
1161
|
-
function isHandlerKey(key) {
|
|
1162
|
-
return key in handlers;
|
|
1163
|
-
}
|
|
1164
|
-
if (isHandlerKey(type)) {
|
|
1165
|
-
return handlers[type];
|
|
1166
|
-
}
|
|
1167
|
-
return;
|
|
1168
313
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
case "content":
|
|
1172
|
-
return createContentScriptHelpers();
|
|
1173
|
-
case "devtools":
|
|
1174
|
-
return createDevToolsHelpers();
|
|
1175
|
-
case "popup":
|
|
1176
|
-
return createPopupHelpers(this.adapters);
|
|
1177
|
-
case "options":
|
|
1178
|
-
return createOptionsHelpers(this.adapters);
|
|
1179
|
-
case "sidepanel":
|
|
1180
|
-
return createSidePanelHelpers(this.adapters);
|
|
1181
|
-
case "background":
|
|
1182
|
-
return createBackgroundHelpers(this.adapters);
|
|
1183
|
-
default:
|
|
1184
|
-
return {};
|
|
1185
|
-
}
|
|
314
|
+
connect() {
|
|
315
|
+
return Promise.resolve();
|
|
1186
316
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
console.error(`[${this.context}] Error in error handler:`, handlerError);
|
|
1193
|
-
}
|
|
317
|
+
disconnect() {
|
|
318
|
+
this.listeners = [];
|
|
319
|
+
if (this.channel) {
|
|
320
|
+
this.channel.close();
|
|
321
|
+
this.channel = null;
|
|
1194
322
|
}
|
|
1195
323
|
}
|
|
324
|
+
isConnected() {
|
|
325
|
+
return this.channel !== null;
|
|
326
|
+
}
|
|
1196
327
|
}
|
|
1197
|
-
function
|
|
1198
|
-
|
|
328
|
+
function createSyncAdapter() {
|
|
329
|
+
if (typeof chrome !== "undefined" && chrome.runtime) {
|
|
330
|
+
return new ChromeRuntimeSyncAdapter;
|
|
331
|
+
}
|
|
332
|
+
if (typeof BroadcastChannel !== "undefined") {
|
|
333
|
+
return new BroadcastChannelSyncAdapter;
|
|
334
|
+
}
|
|
335
|
+
return new NoOpSyncAdapter;
|
|
1199
336
|
}
|
|
1200
337
|
|
|
1201
338
|
// src/shared/lib/state.ts
|
|
1202
339
|
import { effect, signal } from "@preact/signals";
|
|
1203
340
|
var stateRegistry = new Map;
|
|
1204
|
-
function getCurrentContext() {
|
|
1205
|
-
if (typeof chrome !== "undefined") {
|
|
1206
|
-
if (typeof chrome.devtools !== "undefined") {
|
|
1207
|
-
return "devtools";
|
|
1208
|
-
}
|
|
1209
|
-
if (typeof chrome.runtime !== "undefined") {
|
|
1210
|
-
try {
|
|
1211
|
-
if (typeof self !== "undefined" && "ServiceWorkerGlobalScope" in self) {
|
|
1212
|
-
return "background";
|
|
1213
|
-
}
|
|
1214
|
-
} catch {}
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
1218
|
-
if (typeof chrome === "undefined" || typeof chrome.runtime === "undefined") {
|
|
1219
|
-
return "page";
|
|
1220
|
-
}
|
|
1221
|
-
if (typeof window.location !== "undefined" && window.location.protocol === "chrome-extension:") {
|
|
1222
|
-
const path = window.location.pathname;
|
|
1223
|
-
if (path.includes("/popup"))
|
|
1224
|
-
return "popup";
|
|
1225
|
-
if (path.includes("/options"))
|
|
1226
|
-
return "options";
|
|
1227
|
-
if (path.includes("/offscreen"))
|
|
1228
|
-
return "offscreen";
|
|
1229
|
-
}
|
|
1230
|
-
return "content";
|
|
1231
|
-
}
|
|
1232
|
-
return "background";
|
|
1233
|
-
}
|
|
1234
341
|
function $sharedState(key, initialValue, options = {}) {
|
|
1235
342
|
const sig = createState(key, initialValue, {
|
|
1236
343
|
...options,
|
|
1237
|
-
|
|
1238
|
-
|
|
344
|
+
enableSync: true,
|
|
345
|
+
enablePersist: true
|
|
1239
346
|
});
|
|
1240
347
|
const entry = stateRegistry.get(key);
|
|
1241
348
|
if (entry) {
|
|
@@ -1246,15 +353,15 @@ function $sharedState(key, initialValue, options = {}) {
|
|
|
1246
353
|
function $syncedState(key, initialValue, options = {}) {
|
|
1247
354
|
return createState(key, initialValue, {
|
|
1248
355
|
...options,
|
|
1249
|
-
|
|
1250
|
-
|
|
356
|
+
enableSync: true,
|
|
357
|
+
enablePersist: false
|
|
1251
358
|
});
|
|
1252
359
|
}
|
|
1253
360
|
function $persistedState(key, initialValue, options = {}) {
|
|
1254
361
|
const sig = createState(key, initialValue, {
|
|
1255
362
|
...options,
|
|
1256
|
-
|
|
1257
|
-
|
|
363
|
+
enableSync: false,
|
|
364
|
+
enablePersist: true
|
|
1258
365
|
});
|
|
1259
366
|
const entry = stateRegistry.get(key);
|
|
1260
367
|
if (entry) {
|
|
@@ -1284,17 +391,28 @@ function deepEqual(a, b) {
|
|
|
1284
391
|
}
|
|
1285
392
|
return true;
|
|
1286
393
|
}
|
|
394
|
+
function resolveAdapters(options) {
|
|
395
|
+
if (options.storage || options.sync) {
|
|
396
|
+
return {
|
|
397
|
+
storage: options.storage || (options.enablePersist ? createStorageAdapter() : null),
|
|
398
|
+
sync: options.sync || (options.enableSync ? createSyncAdapter() : null)
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
if (options.bus) {
|
|
402
|
+
return {
|
|
403
|
+
storage: options.bus.adapters.storage,
|
|
404
|
+
sync: options.enableSync ? createSyncAdapter() : null
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
storage: options.enablePersist ? createStorageAdapter() : null,
|
|
409
|
+
sync: options.enableSync ? createSyncAdapter() : null
|
|
410
|
+
};
|
|
411
|
+
}
|
|
1287
412
|
function createState(key, initialValue, options) {
|
|
1288
413
|
if (stateRegistry.has(key)) {
|
|
1289
414
|
return stateRegistry.get(key)?.signal;
|
|
1290
415
|
}
|
|
1291
|
-
const currentContext = getCurrentContext();
|
|
1292
|
-
if (currentContext === "page" && (options.sync || options.persist)) {
|
|
1293
|
-
const stateFn = options.sync && options.persist ? "$sharedState" : options.persist ? "$persistedState" : "$syncedState";
|
|
1294
|
-
throw new Error(`[web-ext] ${stateFn}() is not available in page context.
|
|
1295
|
-
Page scripts are execution contexts for content scripts and should not maintain state.
|
|
1296
|
-
Use state in the content script instead, or use $state() for local-only state.`);
|
|
1297
|
-
}
|
|
1298
416
|
const sig = signal(initialValue);
|
|
1299
417
|
if (options.verify) {
|
|
1300
418
|
const mirror = JSON.parse(JSON.stringify(initialValue));
|
|
@@ -1306,15 +424,9 @@ Use state in the content script instead, or use $state() for local-only state.`)
|
|
|
1306
424
|
loaded: Promise.resolve(),
|
|
1307
425
|
updating: false
|
|
1308
426
|
};
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
bus = options.bus || getMessageBus(currentContext);
|
|
1313
|
-
}
|
|
1314
|
-
return bus;
|
|
1315
|
-
};
|
|
1316
|
-
if (options.persist) {
|
|
1317
|
-
entry.loaded = loadFromStorage(key, sig, entry, getBus(), options.validator);
|
|
427
|
+
const adapters = resolveAdapters(options);
|
|
428
|
+
if (options.enablePersist && adapters.storage) {
|
|
429
|
+
entry.loaded = loadFromStorage(key, sig, entry, adapters.storage, options.validator);
|
|
1318
430
|
}
|
|
1319
431
|
entry.loaded.then(() => {
|
|
1320
432
|
let debounceTimer = null;
|
|
@@ -1340,11 +452,11 @@ Use state in the content script instead, or use $state() for local-only state.`)
|
|
|
1340
452
|
}
|
|
1341
453
|
entry.clock++;
|
|
1342
454
|
const doUpdate = () => {
|
|
1343
|
-
if (options.
|
|
1344
|
-
persistToStorage(key, value, entry.clock,
|
|
455
|
+
if (options.enablePersist && adapters.storage) {
|
|
456
|
+
persistToStorage(key, value, entry.clock, adapters.storage);
|
|
1345
457
|
}
|
|
1346
|
-
if (options.sync) {
|
|
1347
|
-
broadcastUpdate(key, value, entry.clock,
|
|
458
|
+
if (options.enableSync && adapters.sync) {
|
|
459
|
+
broadcastUpdate(key, value, entry.clock, adapters.sync);
|
|
1348
460
|
}
|
|
1349
461
|
};
|
|
1350
462
|
if (options.debounceMs) {
|
|
@@ -1356,46 +468,40 @@ Use state in the content script instead, or use $state() for local-only state.`)
|
|
|
1356
468
|
}
|
|
1357
469
|
});
|
|
1358
470
|
});
|
|
1359
|
-
if (options.sync) {
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
471
|
+
if (options.enableSync && adapters.sync) {
|
|
472
|
+
if (adapters.sync.connect) {
|
|
473
|
+
adapters.sync.connect();
|
|
474
|
+
}
|
|
475
|
+
adapters.sync.onMessage((message) => {
|
|
476
|
+
if (message.key !== key)
|
|
477
|
+
return;
|
|
478
|
+
const oldClock = entry.clock;
|
|
479
|
+
entry.clock = Math.max(entry.clock, message.clock);
|
|
480
|
+
if (message.clock > oldClock) {
|
|
481
|
+
if (options.validator && !options.validator(message.value)) {
|
|
482
|
+
console.warn(`[Polly] State "${key}": Received invalid value from sync (clock: ${message.clock})`, message.value);
|
|
1367
483
|
return;
|
|
1368
|
-
const oldClock = entry.clock;
|
|
1369
|
-
entry.clock = Math.max(entry.clock, payload.clock);
|
|
1370
|
-
if (payload.clock > oldClock) {
|
|
1371
|
-
if (options.validator && !options.validator(payload.value)) {
|
|
1372
|
-
console.warn(`[web-ext] State "${key}": Received invalid value from sync (clock: ${payload.clock})`, payload.value);
|
|
1373
|
-
return;
|
|
1374
|
-
}
|
|
1375
|
-
if (deepEqual(entry.signal.value, payload.value)) {
|
|
1376
|
-
return;
|
|
1377
|
-
}
|
|
1378
|
-
applyUpdate(entry, payload.value, payload.clock);
|
|
1379
484
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
485
|
+
if (deepEqual(entry.signal.value, message.value)) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
applyUpdate(entry, message.value, message.clock);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
1383
491
|
}
|
|
1384
492
|
stateRegistry.set(key, entry);
|
|
1385
493
|
return sig;
|
|
1386
494
|
}
|
|
1387
|
-
async function loadFromStorage(key, sig, entry,
|
|
1388
|
-
if (!bus)
|
|
1389
|
-
return;
|
|
495
|
+
async function loadFromStorage(key, sig, entry, storage, validator) {
|
|
1390
496
|
try {
|
|
1391
|
-
const result = await
|
|
497
|
+
const result = await storage.get([key, `${key}:clock`]);
|
|
1392
498
|
if (result[key] !== undefined) {
|
|
1393
499
|
const storedValue = result[key];
|
|
1394
500
|
if (validator) {
|
|
1395
501
|
if (validator(storedValue)) {
|
|
1396
502
|
sig.value = storedValue;
|
|
1397
503
|
} else {
|
|
1398
|
-
console.warn(`[
|
|
504
|
+
console.warn(`[Polly] State "${key}": Stored value failed validation, using initial value`, storedValue);
|
|
1399
505
|
}
|
|
1400
506
|
} else {
|
|
1401
507
|
sig.value = storedValue;
|
|
@@ -1405,33 +511,28 @@ async function loadFromStorage(key, sig, entry, bus, validator) {
|
|
|
1405
511
|
entry.clock = result[`${key}:clock`];
|
|
1406
512
|
}
|
|
1407
513
|
} catch (error) {
|
|
1408
|
-
console.warn(`[
|
|
514
|
+
console.warn(`[Polly] Failed to load state from storage: ${key}`, error);
|
|
1409
515
|
}
|
|
1410
516
|
}
|
|
1411
|
-
function persistToStorage(key, value, clock,
|
|
1412
|
-
if (!bus)
|
|
1413
|
-
return;
|
|
517
|
+
function persistToStorage(key, value, clock, storage) {
|
|
1414
518
|
try {
|
|
1415
|
-
|
|
519
|
+
storage.set({
|
|
1416
520
|
[key]: value,
|
|
1417
521
|
[`${key}:clock`]: clock
|
|
1418
522
|
});
|
|
1419
523
|
} catch (error) {
|
|
1420
|
-
console.warn(`Failed to persist state to storage: ${key}`, error);
|
|
524
|
+
console.warn(`[Polly] Failed to persist state to storage: ${key}`, error);
|
|
1421
525
|
}
|
|
1422
526
|
}
|
|
1423
|
-
function broadcastUpdate(key, value, clock,
|
|
1424
|
-
if (!bus)
|
|
1425
|
-
return;
|
|
527
|
+
function broadcastUpdate(key, value, clock, sync) {
|
|
1426
528
|
try {
|
|
1427
|
-
|
|
1428
|
-
type: "STATE_SYNC",
|
|
529
|
+
sync.broadcast({
|
|
1429
530
|
key,
|
|
1430
531
|
value,
|
|
1431
532
|
clock
|
|
1432
533
|
});
|
|
1433
534
|
} catch (error) {
|
|
1434
|
-
console.warn(`Failed to broadcast state update: ${key}`, error);
|
|
535
|
+
console.warn(`[Polly] Failed to broadcast state update: ${key}`, error);
|
|
1435
536
|
}
|
|
1436
537
|
}
|
|
1437
538
|
function applyUpdate(entry, value, clock) {
|
|
@@ -1456,4 +557,4 @@ export {
|
|
|
1456
557
|
$persistedState
|
|
1457
558
|
};
|
|
1458
559
|
|
|
1459
|
-
//# debugId=
|
|
560
|
+
//# debugId=44A9C265AE724F4564756E2164756E21
|