@fairfox/polly 0.14.1 → 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 +376 -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 +232 -7
- package/dist/tools/verify/src/cli.js.map +3 -3
- 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,321 @@ 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
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// src/shared/adapters/chrome/runtime.chrome.ts
|
|
183
|
-
class ChromeRuntimeAdapter {
|
|
184
|
-
messageListeners = new Map;
|
|
185
|
-
static listenerCount = 0;
|
|
186
|
-
sendMessage(message) {
|
|
187
|
-
return chrome.runtime.sendMessage(message);
|
|
81
|
+
return this.dbPromise;
|
|
188
82
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// src/shared/adapters/index.ts
|
|
417
|
-
function createChromeAdapters(context, options) {
|
|
418
|
-
const runtime2 = new ChromeRuntimeAdapter;
|
|
419
|
-
return {
|
|
420
|
-
runtime: runtime2,
|
|
421
|
-
storage: new ChromeStorageAdapter,
|
|
422
|
-
tabs: new ChromeTabsAdapter,
|
|
423
|
-
window: new ChromeWindowAdapter,
|
|
424
|
-
offscreen: new ChromeOffscreenAdapter,
|
|
425
|
-
contextMenus: new ChromeContextMenusAdapter,
|
|
426
|
-
fetch: new BrowserFetchAdapter,
|
|
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
166
|
}
|
|
465
167
|
}
|
|
466
168
|
|
|
467
|
-
class
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
}
|
|
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
230
|
}
|
|
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
|
-
}
|
|
750
|
-
const count = (handlerCounts.get(handlerType) || 0) + 1;
|
|
751
|
-
handlerCounts.set(handlerType, count);
|
|
752
|
-
if (count > 1) {
|
|
753
|
-
const error = new Error(`\uD83D\uDD34 DOUBLE EXECUTION DETECTED
|
|
754
|
-
|
|
755
|
-
Handler "${handlerType}" executed ${count} times for message ${messageId}.
|
|
756
|
-
|
|
757
|
-
This indicates multiple chrome.runtime.onMessage listeners are registered.
|
|
758
|
-
Common causes:
|
|
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
236
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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
|
-
}
|
|
912
|
-
async sendToPopup(payload, options) {
|
|
913
|
-
return this.send(payload, { ...options, target: "popup" });
|
|
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
256
|
}
|
|
921
|
-
|
|
922
|
-
return
|
|
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
|
-
}
|
|
267
|
+
isConnected() {
|
|
268
|
+
return typeof chrome !== "undefined" && !!chrome.runtime;
|
|
970
269
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
if (event.source !== window)
|
|
984
|
-
return;
|
|
985
|
-
if (event.data?.__extensionMessage) {
|
|
986
|
-
this.handleMessage(event.data.message);
|
|
987
|
-
}
|
|
988
|
-
});
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
async handleMessage(message, _sender) {
|
|
992
|
-
if ("success" in message) {
|
|
993
|
-
const pending = this.pendingRequests.get(message.id);
|
|
994
|
-
if (pending) {
|
|
995
|
-
this.pendingRequests.delete(message.id);
|
|
996
|
-
clearTimeout(pending.timeout);
|
|
997
|
-
if (message.success) {
|
|
998
|
-
pending.resolve(message.data ?? undefined);
|
|
999
|
-
} else {
|
|
1000
|
-
const error = new HandlerError(message.error || "Unknown error", "unknown", {
|
|
1001
|
-
messageId: message.id
|
|
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;
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
sendMessage(message) {
|
|
1113
|
-
if (this.context === "content" && message.targets.includes("page")) {
|
|
1114
|
-
this.adapters.window.postMessage({ __extensionMessage: true, message }, "*");
|
|
1115
|
-
} else if (this.context === "page") {
|
|
1116
|
-
this.adapters.window.postMessage({ __extensionMessage: true, message }, "*");
|
|
1117
|
-
} else if (this.port) {
|
|
1118
|
-
this.port.postMessage(message);
|
|
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);
|
|
302
|
+
console.warn("[SyncAdapter] Failed to broadcast state update:", error);
|
|
1132
303
|
}
|
|
1133
304
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
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
|
|
|
338
|
+
// src/shared/types/messages.ts
|
|
339
|
+
var ALL_CONTEXTS = [
|
|
340
|
+
"background",
|
|
341
|
+
"content",
|
|
342
|
+
"page",
|
|
343
|
+
"devtools",
|
|
344
|
+
"popup",
|
|
345
|
+
"options",
|
|
346
|
+
"sidepanel",
|
|
347
|
+
"offscreen"
|
|
348
|
+
];
|
|
349
|
+
var defaultSettings = {
|
|
350
|
+
theme: "auto",
|
|
351
|
+
autoSync: true,
|
|
352
|
+
debugMode: false,
|
|
353
|
+
notifications: true,
|
|
354
|
+
apiEndpoint: "https://api.example.com",
|
|
355
|
+
refreshInterval: 60000
|
|
356
|
+
};
|
|
357
|
+
|
|
1201
358
|
// src/shared/lib/state.ts
|
|
1202
359
|
import { effect, signal } from "@preact/signals";
|
|
1203
360
|
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
361
|
function $sharedState(key, initialValue, options = {}) {
|
|
1235
362
|
const sig = createState(key, initialValue, {
|
|
1236
363
|
...options,
|
|
1237
|
-
|
|
1238
|
-
|
|
364
|
+
enableSync: true,
|
|
365
|
+
enablePersist: true
|
|
1239
366
|
});
|
|
1240
367
|
const entry = stateRegistry.get(key);
|
|
1241
368
|
if (entry) {
|
|
@@ -1246,15 +373,15 @@ function $sharedState(key, initialValue, options = {}) {
|
|
|
1246
373
|
function $syncedState(key, initialValue, options = {}) {
|
|
1247
374
|
return createState(key, initialValue, {
|
|
1248
375
|
...options,
|
|
1249
|
-
|
|
1250
|
-
|
|
376
|
+
enableSync: true,
|
|
377
|
+
enablePersist: false
|
|
1251
378
|
});
|
|
1252
379
|
}
|
|
1253
380
|
function $persistedState(key, initialValue, options = {}) {
|
|
1254
381
|
const sig = createState(key, initialValue, {
|
|
1255
382
|
...options,
|
|
1256
|
-
|
|
1257
|
-
|
|
383
|
+
enableSync: false,
|
|
384
|
+
enablePersist: true
|
|
1258
385
|
});
|
|
1259
386
|
const entry = stateRegistry.get(key);
|
|
1260
387
|
if (entry) {
|
|
@@ -1284,17 +411,28 @@ function deepEqual(a, b) {
|
|
|
1284
411
|
}
|
|
1285
412
|
return true;
|
|
1286
413
|
}
|
|
414
|
+
function resolveAdapters(options) {
|
|
415
|
+
if (options.storage || options.sync) {
|
|
416
|
+
return {
|
|
417
|
+
storage: options.storage || (options.enablePersist ? createStorageAdapter() : null),
|
|
418
|
+
sync: options.sync || (options.enableSync ? createSyncAdapter() : null)
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
if (options.bus) {
|
|
422
|
+
return {
|
|
423
|
+
storage: options.bus.adapters.storage,
|
|
424
|
+
sync: options.enableSync ? createSyncAdapter() : null
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
storage: options.enablePersist ? createStorageAdapter() : null,
|
|
429
|
+
sync: options.enableSync ? createSyncAdapter() : null
|
|
430
|
+
};
|
|
431
|
+
}
|
|
1287
432
|
function createState(key, initialValue, options) {
|
|
1288
433
|
if (stateRegistry.has(key)) {
|
|
1289
434
|
return stateRegistry.get(key)?.signal;
|
|
1290
435
|
}
|
|
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
436
|
const sig = signal(initialValue);
|
|
1299
437
|
if (options.verify) {
|
|
1300
438
|
const mirror = JSON.parse(JSON.stringify(initialValue));
|
|
@@ -1306,15 +444,9 @@ Use state in the content script instead, or use $state() for local-only state.`)
|
|
|
1306
444
|
loaded: Promise.resolve(),
|
|
1307
445
|
updating: false
|
|
1308
446
|
};
|
|
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);
|
|
447
|
+
const adapters = resolveAdapters(options);
|
|
448
|
+
if (options.enablePersist && adapters.storage) {
|
|
449
|
+
entry.loaded = loadFromStorage(key, sig, entry, adapters.storage, options.validator);
|
|
1318
450
|
}
|
|
1319
451
|
entry.loaded.then(() => {
|
|
1320
452
|
let debounceTimer = null;
|
|
@@ -1340,11 +472,11 @@ Use state in the content script instead, or use $state() for local-only state.`)
|
|
|
1340
472
|
}
|
|
1341
473
|
entry.clock++;
|
|
1342
474
|
const doUpdate = () => {
|
|
1343
|
-
if (options.
|
|
1344
|
-
persistToStorage(key, value, entry.clock,
|
|
475
|
+
if (options.enablePersist && adapters.storage) {
|
|
476
|
+
persistToStorage(key, value, entry.clock, adapters.storage);
|
|
1345
477
|
}
|
|
1346
|
-
if (options.sync) {
|
|
1347
|
-
broadcastUpdate(key, value, entry.clock,
|
|
478
|
+
if (options.enableSync && adapters.sync) {
|
|
479
|
+
broadcastUpdate(key, value, entry.clock, adapters.sync);
|
|
1348
480
|
}
|
|
1349
481
|
};
|
|
1350
482
|
if (options.debounceMs) {
|
|
@@ -1356,46 +488,40 @@ Use state in the content script instead, or use $state() for local-only state.`)
|
|
|
1356
488
|
}
|
|
1357
489
|
});
|
|
1358
490
|
});
|
|
1359
|
-
if (options.sync) {
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
491
|
+
if (options.enableSync && adapters.sync) {
|
|
492
|
+
if (adapters.sync.connect) {
|
|
493
|
+
adapters.sync.connect();
|
|
494
|
+
}
|
|
495
|
+
adapters.sync.onMessage((message) => {
|
|
496
|
+
if (message.key !== key)
|
|
497
|
+
return;
|
|
498
|
+
const oldClock = entry.clock;
|
|
499
|
+
entry.clock = Math.max(entry.clock, message.clock);
|
|
500
|
+
if (message.clock > oldClock) {
|
|
501
|
+
if (options.validator && !options.validator(message.value)) {
|
|
502
|
+
console.warn(`[Polly] State "${key}": Received invalid value from sync (clock: ${message.clock})`, message.value);
|
|
1367
503
|
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
504
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
505
|
+
if (deepEqual(entry.signal.value, message.value)) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
applyUpdate(entry, message.value, message.clock);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
1383
511
|
}
|
|
1384
512
|
stateRegistry.set(key, entry);
|
|
1385
513
|
return sig;
|
|
1386
514
|
}
|
|
1387
|
-
async function loadFromStorage(key, sig, entry,
|
|
1388
|
-
if (!bus)
|
|
1389
|
-
return;
|
|
515
|
+
async function loadFromStorage(key, sig, entry, storage, validator) {
|
|
1390
516
|
try {
|
|
1391
|
-
const result = await
|
|
517
|
+
const result = await storage.get([key, `${key}:clock`]);
|
|
1392
518
|
if (result[key] !== undefined) {
|
|
1393
519
|
const storedValue = result[key];
|
|
1394
520
|
if (validator) {
|
|
1395
521
|
if (validator(storedValue)) {
|
|
1396
522
|
sig.value = storedValue;
|
|
1397
523
|
} else {
|
|
1398
|
-
console.warn(`[
|
|
524
|
+
console.warn(`[Polly] State "${key}": Stored value failed validation, using initial value`, storedValue);
|
|
1399
525
|
}
|
|
1400
526
|
} else {
|
|
1401
527
|
sig.value = storedValue;
|
|
@@ -1405,33 +531,28 @@ async function loadFromStorage(key, sig, entry, bus, validator) {
|
|
|
1405
531
|
entry.clock = result[`${key}:clock`];
|
|
1406
532
|
}
|
|
1407
533
|
} catch (error) {
|
|
1408
|
-
console.warn(`[
|
|
534
|
+
console.warn(`[Polly] Failed to load state from storage: ${key}`, error);
|
|
1409
535
|
}
|
|
1410
536
|
}
|
|
1411
|
-
function persistToStorage(key, value, clock,
|
|
1412
|
-
if (!bus)
|
|
1413
|
-
return;
|
|
537
|
+
function persistToStorage(key, value, clock, storage) {
|
|
1414
538
|
try {
|
|
1415
|
-
|
|
539
|
+
storage.set({
|
|
1416
540
|
[key]: value,
|
|
1417
541
|
[`${key}:clock`]: clock
|
|
1418
542
|
});
|
|
1419
543
|
} catch (error) {
|
|
1420
|
-
console.warn(`Failed to persist state to storage: ${key}`, error);
|
|
544
|
+
console.warn(`[Polly] Failed to persist state to storage: ${key}`, error);
|
|
1421
545
|
}
|
|
1422
546
|
}
|
|
1423
|
-
function broadcastUpdate(key, value, clock,
|
|
1424
|
-
if (!bus)
|
|
1425
|
-
return;
|
|
547
|
+
function broadcastUpdate(key, value, clock, sync) {
|
|
1426
548
|
try {
|
|
1427
|
-
|
|
1428
|
-
type: "STATE_SYNC",
|
|
549
|
+
sync.broadcast({
|
|
1429
550
|
key,
|
|
1430
551
|
value,
|
|
1431
552
|
clock
|
|
1432
553
|
});
|
|
1433
554
|
} catch (error) {
|
|
1434
|
-
console.warn(`Failed to broadcast state update: ${key}`, error);
|
|
555
|
+
console.warn(`[Polly] Failed to broadcast state update: ${key}`, error);
|
|
1435
556
|
}
|
|
1436
557
|
}
|
|
1437
558
|
function applyUpdate(entry, value, clock) {
|
|
@@ -1463,4 +584,4 @@ export {
|
|
|
1463
584
|
currentTab
|
|
1464
585
|
};
|
|
1465
586
|
|
|
1466
|
-
//# debugId=
|
|
587
|
+
//# debugId=FBE4935174EC258C64756E2164756E21
|