@freedomofpress/webcat 0.0.0-stage → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +45 -0
- package/data/block.json +101 -0
- package/data/list.json +80 -0
- package/dist/hooks/content.js +1 -0
- package/dist/lib/browser/chrome.d.ts +86 -0
- package/dist/lib/browser/chrome.js +138 -0
- package/dist/lib/browser/kvstore.d.ts +70 -0
- package/dist/lib/browser/kvstore.js +241 -0
- package/dist/lib/browser/permissions.d.ts +113 -0
- package/dist/lib/browser/permissions.js +182 -0
- package/dist/lib/browser/requests.d.ts +145 -0
- package/dist/lib/browser/requests.js +292 -0
- package/dist/lib/browser/scripting.d.ts +19 -0
- package/dist/lib/browser/scripting.js +92 -0
- package/dist/lib/browser/sync.d.ts +68 -0
- package/dist/lib/browser/sync.js +114 -0
- package/dist/lib/browser/utils.d.ts +5 -0
- package/dist/lib/browser/utils.js +11 -0
- package/dist/lib/config.d.ts +45 -0
- package/dist/lib/config.js +45 -0
- package/dist/lib/validator_set.d.ts +4 -0
- package/dist/lib/validator_set.js +20 -0
- package/dist/lib/webcat/cache.d.ts +200 -0
- package/dist/lib/webcat/cache.js +308 -0
- package/dist/lib/webcat/canonicalize.d.ts +1 -0
- package/dist/lib/webcat/canonicalize.js +58 -0
- package/dist/lib/webcat/db.d.ts +30 -0
- package/dist/lib/webcat/db.js +131 -0
- package/dist/lib/webcat/encoding.d.ts +8 -0
- package/dist/lib/webcat/encoding.js +57 -0
- package/dist/lib/webcat/handler.d.ts +26 -0
- package/dist/lib/webcat/handler.js +419 -0
- package/dist/lib/webcat/hookbuilder.d.ts +47 -0
- package/dist/lib/webcat/hookbuilder.js +170 -0
- package/dist/lib/webcat/interfaces/bundle.d.ts +46 -0
- package/dist/lib/webcat/interfaces/bundle.js +8 -0
- package/dist/lib/webcat/interfaces/database.d.ts +68 -0
- package/dist/lib/webcat/interfaces/database.js +1 -0
- package/dist/lib/webcat/interfaces/errors.d.ts +451 -0
- package/dist/lib/webcat/interfaces/errors.js +109 -0
- package/dist/lib/webcat/interfaces/log.d.ts +14 -0
- package/dist/lib/webcat/interfaces/log.js +10 -0
- package/dist/lib/webcat/interfaces/originstate.d.ts +40 -0
- package/dist/lib/webcat/interfaces/originstate.js +1 -0
- package/dist/lib/webcat/interfaces/requeststate.d.ts +10 -0
- package/dist/lib/webcat/interfaces/requeststate.js +1 -0
- package/dist/lib/webcat/logger.d.ts +64 -0
- package/dist/lib/webcat/logger.js +112 -0
- package/dist/lib/webcat/originstate.d.ts +45 -0
- package/dist/lib/webcat/originstate.js +302 -0
- package/dist/lib/webcat/parsers.d.ts +21 -0
- package/dist/lib/webcat/parsers.js +36 -0
- package/dist/lib/webcat/request.d.ts +11 -0
- package/dist/lib/webcat/request.js +78 -0
- package/dist/lib/webcat/resources.d.ts +3 -0
- package/dist/lib/webcat/resources.js +41 -0
- package/dist/lib/webcat/response.d.ts +39 -0
- package/dist/lib/webcat/response.js +394 -0
- package/dist/lib/webcat/ui.d.ts +70 -0
- package/dist/lib/webcat/ui.js +282 -0
- package/dist/lib/webcat/updater.d.ts +115 -0
- package/dist/lib/webcat/updater.js +296 -0
- package/dist/lib/webcat/utils.d.ts +8 -0
- package/dist/lib/webcat/utils.js +74 -0
- package/dist/lib/webcat/validators.d.ts +16 -0
- package/dist/lib/webcat/validators.js +457 -0
- package/dist/lib/webcat.d.ts +11 -0
- package/dist/lib/webcat.js +130 -0
- package/icons/dark/webcat-error.SVG +1 -0
- package/icons/dark/webcat-ok.SVG +1 -0
- package/icons/dark/webcat.SVG +1 -0
- package/icons/light/webcat-error.SVG +1 -0
- package/icons/light/webcat-ok.SVG +1 -0
- package/icons/light/webcat.SVG +1 -0
- package/package.json +72 -4
- package/pages/error.css +9 -0
- package/pages/error.html +70 -0
- package/pages/error.js +51 -0
- package/pages/popup.css +16 -0
- package/pages/popup.html +14 -0
- package/pages/popup.js +48 -0
- package/README.md +0 -3
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
var __typeError = (msg) => {
|
|
2
|
+
throw TypeError(msg);
|
|
3
|
+
};
|
|
4
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
5
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
6
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
7
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
8
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
9
|
+
var _db, _warnings, _showWarnings, _WebcatUI_instances, onPermissionsChanged_fn, onErrorPageNavigation_fn, _onNavigation;
|
|
10
|
+
import { BrowserChromeController } from "../browser/chrome.js";
|
|
11
|
+
import permissions from "../browser/permissions.js";
|
|
12
|
+
import { WebcatErrorCode } from "./interfaces/errors.js";
|
|
13
|
+
import { logger } from "./logger.js";
|
|
14
|
+
import { getFQDN, clearBrowserCaches } from "./utils.js";
|
|
15
|
+
var __create = Object.create;
|
|
16
|
+
var __defProp = Object.defineProperty;
|
|
17
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
18
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
19
|
+
var __typeError2 = (msg) => {
|
|
20
|
+
throw TypeError(msg);
|
|
21
|
+
};
|
|
22
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
23
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
24
|
+
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
|
|
25
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
26
|
+
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError2("Function expected") : fn;
|
|
27
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError2("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
28
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
29
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
30
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self);
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
34
|
+
var it, done, ctx, k = flags & 7, p = false;
|
|
35
|
+
var j = 0;
|
|
36
|
+
var extraInitializers = array[j] || (array[j] = []);
|
|
37
|
+
var desc = k && (target = target.prototype, k < 5 && (k > 3 || !p) && __getOwnPropDesc(target, name));
|
|
38
|
+
__name(target, name);
|
|
39
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
40
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
41
|
+
it = (0, decorators[i])(target, ctx), done._ = 1;
|
|
42
|
+
__expectFn(it) && (target = it);
|
|
43
|
+
}
|
|
44
|
+
return __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
45
|
+
};
|
|
46
|
+
var _WebcatUI_decorators, _init, _a;
|
|
47
|
+
_WebcatUI_decorators = [permissions.require("webNavigation")];
|
|
48
|
+
class WebcatUI extends (_a = BrowserChromeController) {
|
|
49
|
+
/**
|
|
50
|
+
* @param db The database to load enrollments from.
|
|
51
|
+
* @param store The store to use for persistence.
|
|
52
|
+
* @param config
|
|
53
|
+
*/
|
|
54
|
+
constructor(db, store, config) {
|
|
55
|
+
super(config);
|
|
56
|
+
__privateAdd(this, _WebcatUI_instances);
|
|
57
|
+
__privateAdd(this, _db);
|
|
58
|
+
__privateAdd(this, _warnings);
|
|
59
|
+
__privateAdd(this, _showWarnings, false);
|
|
60
|
+
__privateAdd(this, _onNavigation, async function(details) {
|
|
61
|
+
if (details.frameId !== 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const title = await browser.pageAction.getTitle({ tabId: details.tabId });
|
|
65
|
+
if (title !== browser.i18n.getMessage("WEBCAT_webcatVerificationFailed")) {
|
|
66
|
+
this.showWarningIcon(details.tabId);
|
|
67
|
+
}
|
|
68
|
+
}.bind(this));
|
|
69
|
+
__privateSet(this, _db, db);
|
|
70
|
+
__privateSet(this, _warnings, store.namespace("warnings"));
|
|
71
|
+
permissions.addEventListener(
|
|
72
|
+
"permissionerror",
|
|
73
|
+
__privateMethod(this, _WebcatUI_instances, onPermissionsChanged_fn).bind(this)
|
|
74
|
+
);
|
|
75
|
+
permissions.addEventListener(
|
|
76
|
+
"permissionrestored",
|
|
77
|
+
__privateMethod(this, _WebcatUI_instances, onPermissionsChanged_fn).bind(this)
|
|
78
|
+
);
|
|
79
|
+
browser.webNavigation.onCommitted.addListener(
|
|
80
|
+
__privateMethod(this, _WebcatUI_instances, onErrorPageNavigation_fn).bind(this),
|
|
81
|
+
{ url: [{ urlPrefix: this.getPageURL("error") }] }
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Displays the default WEBCAT icon in the browser's URL bar. If the UI has
|
|
86
|
+
* warnings, shows the warning icon instead.
|
|
87
|
+
*
|
|
88
|
+
* @param tabId The ID of the tab to display the icon in.
|
|
89
|
+
* @param options Icon options.
|
|
90
|
+
*/
|
|
91
|
+
async showIcon(tabId, options) {
|
|
92
|
+
if (__privateGet(this, _showWarnings)) {
|
|
93
|
+
await this.showWarningIcon(tabId);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
await super.showIcon(tabId, {
|
|
97
|
+
name: options?.name ?? "webcat",
|
|
98
|
+
title: options?.title ?? browser.i18n.getMessage("WEBCAT_webcatIsRunning"),
|
|
99
|
+
popup: options?.popup
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Displays the warning icon in the browser's URL bar. Clicking on the icon
|
|
104
|
+
* opens the popup with full warning info.
|
|
105
|
+
*
|
|
106
|
+
* @param tabId The ID of the tab to display the icon in.
|
|
107
|
+
*/
|
|
108
|
+
async showWarningIcon(tabId) {
|
|
109
|
+
const warnings = {};
|
|
110
|
+
for (const key of await __privateGet(this, _warnings).getKeys()) {
|
|
111
|
+
warnings[key] = await __privateGet(this, _warnings).get(key);
|
|
112
|
+
}
|
|
113
|
+
await super.showIcon(tabId, {
|
|
114
|
+
// We don't have a separate icon for warnings, at least for now
|
|
115
|
+
name: "webcat-error",
|
|
116
|
+
title: browser.i18n.getMessage("WEBCAT_webcatNotOperational"),
|
|
117
|
+
popup: {
|
|
118
|
+
name: "popup",
|
|
119
|
+
fragment: new URLSearchParams({ warnings: JSON.stringify(warnings) })
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Displays the OK icon in the browser's URL bar. If the UI has warnings,
|
|
125
|
+
* shows the warning icon instead.
|
|
126
|
+
*
|
|
127
|
+
* @param tabId The ID of the tab to display the icon in.
|
|
128
|
+
* @param delegation Optional delegation information to include in the title
|
|
129
|
+
* text.
|
|
130
|
+
*/
|
|
131
|
+
async showOKIcon(tabId, delegation) {
|
|
132
|
+
if (__privateGet(this, _showWarnings)) {
|
|
133
|
+
await this.showWarningIcon(tabId);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
logger.addLog(
|
|
137
|
+
"info",
|
|
138
|
+
delegation ? `Setting ok icon (delegation: ${delegation})` : "Setting ok icon",
|
|
139
|
+
tabId,
|
|
140
|
+
""
|
|
141
|
+
);
|
|
142
|
+
let title = browser.i18n.getMessage("WEBCAT_webcatVerificationSuccessful");
|
|
143
|
+
if (delegation) {
|
|
144
|
+
title += ` (${delegation})`;
|
|
145
|
+
}
|
|
146
|
+
await super.showIcon(tabId, {
|
|
147
|
+
name: "webcat-ok",
|
|
148
|
+
title
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Displays the error icon in the browser's URL bar.
|
|
153
|
+
*
|
|
154
|
+
* @param tabId The ID of the tab to display the icon in.
|
|
155
|
+
*/
|
|
156
|
+
async showErrorIcon(tabId) {
|
|
157
|
+
await super.showIcon(tabId, {
|
|
158
|
+
name: "webcat-error",
|
|
159
|
+
title: browser.i18n.getMessage("WEBCAT_webcatVerificationFailed")
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Loads the error page in the tab(s) indicated by details.
|
|
164
|
+
*
|
|
165
|
+
* @param details Details for the request that triggered the error.
|
|
166
|
+
* @param error The error information.
|
|
167
|
+
* @param replace true to replace the current history entry, false to add a
|
|
168
|
+
* new one.
|
|
169
|
+
*/
|
|
170
|
+
async showErrorPage(details, error, replace = !details.state.isFrame) {
|
|
171
|
+
const tabIds = /* @__PURE__ */ new Set();
|
|
172
|
+
const frameLookups = [];
|
|
173
|
+
if (details.tabId < 0) {
|
|
174
|
+
const tabs = await browser.tabs.query({});
|
|
175
|
+
for (const tab of tabs) {
|
|
176
|
+
if (tab.url && tab.id && /https?:\/\//i.test(tab.url) && details.state.fqdn === getFQDN(tab.url)) {
|
|
177
|
+
tabIds.add(tab.id);
|
|
178
|
+
} else if (tab.id) {
|
|
179
|
+
frameLookups.push(
|
|
180
|
+
browser.webNavigation.getAllFrames({ tabId: tab.id }).then(
|
|
181
|
+
(frames) => frames.forEach((frame) => {
|
|
182
|
+
if (/https?:\/\//i.test(frame.url) && details.state.fqdn === getFQDN(frame.url)) {
|
|
183
|
+
tabIds.add(frame.tabId);
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
await Promise.all(frameLookups);
|
|
191
|
+
} else {
|
|
192
|
+
tabIds.add(details.tabId);
|
|
193
|
+
}
|
|
194
|
+
const code = error.code;
|
|
195
|
+
const params = new URLSearchParams({ code, host: details.state.fqdn });
|
|
196
|
+
if ((code === WebcatErrorCode.File.MISMATCH || code === WebcatErrorCode.File.MISSING) && error?.details?.[0]) {
|
|
197
|
+
params.set("file", error.details[0]);
|
|
198
|
+
}
|
|
199
|
+
const tabUpdates = [];
|
|
200
|
+
tabIds.forEach(
|
|
201
|
+
(tabId) => tabUpdates.push(
|
|
202
|
+
this.loadPage(tabId, "error", {
|
|
203
|
+
fragment: params,
|
|
204
|
+
replace
|
|
205
|
+
})
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
await Promise.all(tabUpdates);
|
|
209
|
+
await clearBrowserCaches([details.state.fqdn]);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Sets the message for the specified warning.
|
|
213
|
+
*
|
|
214
|
+
* @param id The ID of the warning.
|
|
215
|
+
* @param args The arguments of the warning. Must be serializable as JSON.
|
|
216
|
+
*/
|
|
217
|
+
async setWarning(id, ...args) {
|
|
218
|
+
await __privateGet(this, _warnings).set({ [id]: args });
|
|
219
|
+
if (__privateGet(this, _showWarnings) === false) {
|
|
220
|
+
browser.webNavigation.onCommitted.addListener(__privateGet(this, _onNavigation));
|
|
221
|
+
const tabs = await browser.tabs.query({});
|
|
222
|
+
await Promise.all(
|
|
223
|
+
tabs.map(async (tab) => {
|
|
224
|
+
const title = await browser.pageAction.getTitle({
|
|
225
|
+
tabId: tab.id
|
|
226
|
+
});
|
|
227
|
+
if (title !== browser.i18n.getMessage("WEBCAT_webcatVerificationFailed")) {
|
|
228
|
+
this.showWarningIcon(tab.id);
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
__privateSet(this, _showWarnings, true);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Clears a specific warning from the UI.
|
|
237
|
+
*
|
|
238
|
+
* @param id The ID of the warning to clear.
|
|
239
|
+
*/
|
|
240
|
+
async clearWarning(id) {
|
|
241
|
+
await __privateGet(this, _warnings).remove(id);
|
|
242
|
+
const warnings = await __privateGet(this, _warnings).getKeys();
|
|
243
|
+
if (warnings.length === 0) {
|
|
244
|
+
__privateSet(this, _showWarnings, false);
|
|
245
|
+
browser.webNavigation.onCommitted.removeListener(__privateGet(this, _onNavigation));
|
|
246
|
+
const tabs = await browser.tabs.query({});
|
|
247
|
+
await Promise.all(
|
|
248
|
+
tabs.map(async (tab) => {
|
|
249
|
+
const title = await browser.pageAction.getTitle({
|
|
250
|
+
tabId: tab.id
|
|
251
|
+
});
|
|
252
|
+
if (title === browser.i18n.getMessage("WEBCAT_webcatNotOperational")) {
|
|
253
|
+
await this.hideIcon(tab.id);
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
_db = new WeakMap();
|
|
261
|
+
_warnings = new WeakMap();
|
|
262
|
+
_showWarnings = new WeakMap();
|
|
263
|
+
_WebcatUI_instances = new WeakSet();
|
|
264
|
+
onPermissionsChanged_fn = async function() {
|
|
265
|
+
const missing = permissions.getMissing();
|
|
266
|
+
if (missing.size > 0) {
|
|
267
|
+
await this.setWarning("permissions", Array.from(missing).sort());
|
|
268
|
+
} else {
|
|
269
|
+
await clearBrowserCaches(await __privateGet(this, _db).listAllFQDNs());
|
|
270
|
+
this.clearWarning("permissions");
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
onErrorPageNavigation_fn = async function(details) {
|
|
274
|
+
await this.showErrorIcon(details.tabId);
|
|
275
|
+
};
|
|
276
|
+
_onNavigation = new WeakMap();
|
|
277
|
+
_init = __decoratorStart(_a);
|
|
278
|
+
WebcatUI = __decorateElement(_init, 0, "WebcatUI", _WebcatUI_decorators, WebcatUI);
|
|
279
|
+
__runInitializers(_init, 1, WebcatUI);
|
|
280
|
+
export {
|
|
281
|
+
WebcatUI
|
|
282
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ValidatorJson } from "@freedomofpress/cometbft/dist/types";
|
|
2
|
+
import { Database } from "./interfaces/database";
|
|
3
|
+
/**
|
|
4
|
+
* An event indicating the completion of an update attempt.
|
|
5
|
+
*/
|
|
6
|
+
export declare class UpdateEvent extends Event {
|
|
7
|
+
/**
|
|
8
|
+
* Indicates whether the update was local or networked. Set to true for
|
|
9
|
+
* updates loaded from local files and false for updates done over the
|
|
10
|
+
* network.
|
|
11
|
+
*/
|
|
12
|
+
readonly local: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Indicates whether the update was successful. Set to true if the update
|
|
15
|
+
* resulted in a new block being applied. If false, the update may have
|
|
16
|
+
* failed with an error, or there may not have been anything to update.
|
|
17
|
+
*/
|
|
18
|
+
readonly success: boolean;
|
|
19
|
+
constructor(success: boolean, local?: boolean);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Options for an {@link EnrollmentUpdater}.
|
|
23
|
+
*/
|
|
24
|
+
export interface EnrollmentUpdaterConfig {
|
|
25
|
+
/**
|
|
26
|
+
* The update endpoint that serves verifiable enrollments as `list.json` and
|
|
27
|
+
* `block.json`.
|
|
28
|
+
*/
|
|
29
|
+
endpoint: string;
|
|
30
|
+
/**
|
|
31
|
+
* The path to bundled `list.json` and `block.json` files.
|
|
32
|
+
*/
|
|
33
|
+
localDataPath: string;
|
|
34
|
+
/**
|
|
35
|
+
* The database to persist enrollments in.
|
|
36
|
+
*/
|
|
37
|
+
database: Database;
|
|
38
|
+
/**
|
|
39
|
+
* The set of CometBFT validators to verify enrollments against.
|
|
40
|
+
*/
|
|
41
|
+
validatorSet: ValidatorJson;
|
|
42
|
+
/** Expected CometBFT chain ID; blocks from any other chain are rejected. */
|
|
43
|
+
chainId: string;
|
|
44
|
+
/**
|
|
45
|
+
* The time in milliseconds between enrollment update checks. Determines how
|
|
46
|
+
* often the extension is activated to check whether an update is due. An
|
|
47
|
+
* update is only downloaded if, at check time, {@link updateInterval} has
|
|
48
|
+
* elapsed since the last update.
|
|
49
|
+
*
|
|
50
|
+
* @defaultValue {@link EnrollmentUpdater.DefaultCheckInterval}
|
|
51
|
+
*/
|
|
52
|
+
checkInterval?: number;
|
|
53
|
+
/**
|
|
54
|
+
* The minimum time in milliseconds between updates. Update checks are run
|
|
55
|
+
* periodically at an interval determined by this value. An update is only
|
|
56
|
+
* downloaded when a {@link checkInterval | check } runs and updateInterval
|
|
57
|
+
* has elapsed since the last update.
|
|
58
|
+
*
|
|
59
|
+
* @defaultValue {@link EnrollmentUpdater.DefaultUpdateInterval}
|
|
60
|
+
*/
|
|
61
|
+
updateInterval?: number;
|
|
62
|
+
/**
|
|
63
|
+
* The timeout, in milliseconds, of update download attempts.
|
|
64
|
+
*
|
|
65
|
+
* @defaultValue {@link EnrollmentUpdater.DefaultFetchTimeout}
|
|
66
|
+
*/
|
|
67
|
+
fetchTimeout?: number;
|
|
68
|
+
}
|
|
69
|
+
export interface EnrollmentUpdater extends EventTarget {
|
|
70
|
+
/** @group Methods */
|
|
71
|
+
addEventListener: EventTarget["addEventListener"] & ((type: "updated", callback: (event: UpdateEvent) => void) => void);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Handles loading enrollment updates periodically from the endpoint,
|
|
75
|
+
* storing them to the database, and notifying event consumers.
|
|
76
|
+
*/
|
|
77
|
+
export declare class EnrollmentUpdater extends EventTarget {
|
|
78
|
+
#private;
|
|
79
|
+
/**
|
|
80
|
+
* The default value for {@link EnrollmentUpdaterConfig.checkInterval},
|
|
81
|
+
* 5 minutes.
|
|
82
|
+
*/
|
|
83
|
+
static readonly DefaultCheckInterval: number;
|
|
84
|
+
/**
|
|
85
|
+
* The default value for {@link EnrollmentUpdaterConfig.updateInterval},
|
|
86
|
+
* 1 hour.
|
|
87
|
+
*/
|
|
88
|
+
static readonly DefaultUpdateInterval: number;
|
|
89
|
+
/**
|
|
90
|
+
* The default value for {@link EnrollmentUpdaterConfig.fetchTimeout},
|
|
91
|
+
* 3 seconds.
|
|
92
|
+
*/
|
|
93
|
+
static readonly DefaultFetchTimeout = 3000;
|
|
94
|
+
constructor(options: EnrollmentUpdaterConfig);
|
|
95
|
+
/**
|
|
96
|
+
* Loads bundled enrollments and initiates periodic updates.
|
|
97
|
+
*/
|
|
98
|
+
start(): void;
|
|
99
|
+
/**
|
|
100
|
+
* Immediately does a single update. If local is true, only the enrollment
|
|
101
|
+
* files bundled with the extension are consulted without accessing the network.
|
|
102
|
+
*/
|
|
103
|
+
update(local?: boolean): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Retries an update if the previous attempt failed. Unlike {@link update},
|
|
106
|
+
* retryIfFailed never throws.
|
|
107
|
+
*/
|
|
108
|
+
retryIfFailed(): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Determines whether an update is due.
|
|
111
|
+
*
|
|
112
|
+
* @returns A Promise that resolves to true if an update is due.
|
|
113
|
+
*/
|
|
114
|
+
isDue(): Promise<boolean>;
|
|
115
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
2
|
+
var __typeError = (msg) => {
|
|
3
|
+
throw TypeError(msg);
|
|
4
|
+
};
|
|
5
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
6
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
7
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
8
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
9
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
10
|
+
var __using = (stack, value, async) => {
|
|
11
|
+
if (value != null) {
|
|
12
|
+
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
|
|
13
|
+
var dispose, inner;
|
|
14
|
+
if (async) dispose = value[__knownSymbol("asyncDispose")];
|
|
15
|
+
if (dispose === void 0) {
|
|
16
|
+
dispose = value[__knownSymbol("dispose")];
|
|
17
|
+
if (async) inner = dispose;
|
|
18
|
+
}
|
|
19
|
+
if (typeof dispose !== "function") __typeError("Object not disposable");
|
|
20
|
+
if (inner) dispose = function() {
|
|
21
|
+
try {
|
|
22
|
+
inner.call(this);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
return Promise.reject(e);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
stack.push([async, dispose, value]);
|
|
28
|
+
} else if (async) {
|
|
29
|
+
stack.push([async]);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
var __callDispose = (stack, error, hasError) => {
|
|
34
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
35
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
36
|
+
};
|
|
37
|
+
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
|
|
38
|
+
var next = (it) => {
|
|
39
|
+
while (it = stack.pop()) {
|
|
40
|
+
try {
|
|
41
|
+
var result = it[1] && it[1].call(it[2]);
|
|
42
|
+
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
43
|
+
} catch (e) {
|
|
44
|
+
fail(e);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (hasError) throw error;
|
|
48
|
+
};
|
|
49
|
+
return next();
|
|
50
|
+
};
|
|
51
|
+
var _endpoint, _localDataPath, _db, _validatorSet, _chainId, _checkInterval, _updateInterval, _fetchTimeout, _alarm, _mutex, _lastUpdateFailed, __EnrollmentUpdater2_instances, checkAndUpdate_fn, handleUpdateAlarm_fn, fetchWithTimeout_fn, _a2;
|
|
52
|
+
import { importCommit } from "@freedomofpress/cometbft/dist/commit";
|
|
53
|
+
import { verifyCommit } from "@freedomofpress/cometbft/dist/lightclient";
|
|
54
|
+
import { importValidators } from "@freedomofpress/cometbft/dist/validators";
|
|
55
|
+
import { verifyWebcatProof } from "@freedomofpress/ics23/dist/webcat";
|
|
56
|
+
import permissions from "../browser/permissions.js";
|
|
57
|
+
import { Mutex } from "../browser/sync.js";
|
|
58
|
+
import { Uint8ArrayToBase64, hexToUint8Array } from "./encoding.js";
|
|
59
|
+
import { arraysEqual } from "./utils.js";
|
|
60
|
+
var __create = Object.create;
|
|
61
|
+
var __defProp = Object.defineProperty;
|
|
62
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
63
|
+
var __knownSymbol2 = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
64
|
+
var __typeError2 = (msg) => {
|
|
65
|
+
throw TypeError(msg);
|
|
66
|
+
};
|
|
67
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
68
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
69
|
+
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol2("metadata")] ?? null)];
|
|
70
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
71
|
+
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError2("Function expected") : fn;
|
|
72
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError2("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
73
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol2("metadata"), array[3]);
|
|
74
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
75
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self);
|
|
76
|
+
return value;
|
|
77
|
+
};
|
|
78
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
79
|
+
var it, done, ctx, k = flags & 7, p = false;
|
|
80
|
+
var j = 0;
|
|
81
|
+
var extraInitializers = array[j] || (array[j] = []);
|
|
82
|
+
var desc = k && (target = target.prototype, k < 5 && (k > 3 || !p) && __getOwnPropDesc(target, name));
|
|
83
|
+
__name(target, name);
|
|
84
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
85
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
86
|
+
it = (0, decorators[i])(target, ctx), done._ = 1;
|
|
87
|
+
__expectFn(it) && (target = it);
|
|
88
|
+
}
|
|
89
|
+
return __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
90
|
+
};
|
|
91
|
+
var _EnrollmentUpdater_decorators, _init, _a;
|
|
92
|
+
class UpdateEvent extends Event {
|
|
93
|
+
constructor(success, local = false) {
|
|
94
|
+
super("updated");
|
|
95
|
+
this.success = success;
|
|
96
|
+
this.local = local;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
_EnrollmentUpdater_decorators = [permissions.require("alarms")];
|
|
100
|
+
let _EnrollmentUpdater = (_a2 = class extends (_a = EventTarget) {
|
|
101
|
+
constructor(options) {
|
|
102
|
+
super();
|
|
103
|
+
__privateAdd(this, __EnrollmentUpdater2_instances);
|
|
104
|
+
__privateAdd(this, _endpoint);
|
|
105
|
+
__privateAdd(this, _localDataPath);
|
|
106
|
+
__privateAdd(this, _db);
|
|
107
|
+
__privateAdd(this, _validatorSet);
|
|
108
|
+
__privateAdd(this, _chainId);
|
|
109
|
+
__privateAdd(this, _checkInterval);
|
|
110
|
+
__privateAdd(this, _updateInterval);
|
|
111
|
+
__privateAdd(this, _fetchTimeout);
|
|
112
|
+
__privateAdd(this, _alarm);
|
|
113
|
+
__privateAdd(this, _mutex, new Mutex());
|
|
114
|
+
__privateAdd(this, _lastUpdateFailed, false);
|
|
115
|
+
__privateSet(this, _endpoint, options.endpoint);
|
|
116
|
+
__privateSet(this, _localDataPath, options.localDataPath);
|
|
117
|
+
__privateSet(this, _db, options.database);
|
|
118
|
+
__privateSet(this, _validatorSet, options.validatorSet);
|
|
119
|
+
__privateSet(this, _chainId, options.chainId);
|
|
120
|
+
__privateSet(this, _checkInterval, options.checkInterval || _a2.DefaultCheckInterval);
|
|
121
|
+
__privateSet(this, _updateInterval, options.updateInterval || _a2.DefaultUpdateInterval);
|
|
122
|
+
__privateSet(this, _fetchTimeout, options.fetchTimeout || _a2.DefaultFetchTimeout);
|
|
123
|
+
__privateSet(this, _alarm, `webcat-scheduled-update:${options.endpoint}`);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Loads bundled enrollments and initiates periodic updates.
|
|
127
|
+
*/
|
|
128
|
+
start() {
|
|
129
|
+
browser.alarms.onAlarm.addListener(__privateMethod(this, __EnrollmentUpdater2_instances, handleUpdateAlarm_fn).bind(this));
|
|
130
|
+
browser.alarms.get(__privateGet(this, _alarm)).then((alarm) => {
|
|
131
|
+
if (!alarm) {
|
|
132
|
+
browser.alarms.create(__privateGet(this, _alarm), {
|
|
133
|
+
periodInMinutes: __privateGet(this, _checkInterval) / 6e4
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
this.dispatchEvent(new Event("scheduled"));
|
|
137
|
+
});
|
|
138
|
+
console.log("[webcat] Importing bundled list");
|
|
139
|
+
this.update(true).finally(async () => {
|
|
140
|
+
console.log("[webcat] Attempting network update");
|
|
141
|
+
await __privateMethod(this, __EnrollmentUpdater2_instances, checkAndUpdate_fn).call(this);
|
|
142
|
+
}).catch((error) => {
|
|
143
|
+
console.error("[webcat] Bundled list import failed:", error);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Immediately does a single update. If local is true, only the enrollment
|
|
148
|
+
* files bundled with the extension are consulted without accessing the network.
|
|
149
|
+
*/
|
|
150
|
+
async update(local = false) {
|
|
151
|
+
try {
|
|
152
|
+
console.log("[webcat] Running production list updater");
|
|
153
|
+
await __privateGet(this, _db).setLastChecked();
|
|
154
|
+
let leavesUrl;
|
|
155
|
+
let blocksUrl;
|
|
156
|
+
if (local) {
|
|
157
|
+
console.log("[webcat] Loading bundled update files");
|
|
158
|
+
leavesUrl = browser.runtime.getURL(`${__privateGet(this, _localDataPath)}/list.json`);
|
|
159
|
+
blocksUrl = browser.runtime.getURL(`${__privateGet(this, _localDataPath)}/block.json`);
|
|
160
|
+
} else {
|
|
161
|
+
console.log("[webcat] Fetching update files");
|
|
162
|
+
leavesUrl = `${__privateGet(this, _endpoint)}list.json`;
|
|
163
|
+
blocksUrl = `${__privateGet(this, _endpoint)}block.json`;
|
|
164
|
+
}
|
|
165
|
+
const leavesResponse = __privateMethod(this, __EnrollmentUpdater2_instances, fetchWithTimeout_fn).call(this, leavesUrl);
|
|
166
|
+
const blockResponse = __privateMethod(this, __EnrollmentUpdater2_instances, fetchWithTimeout_fn).call(this, blocksUrl);
|
|
167
|
+
leavesResponse.catch(() => {
|
|
168
|
+
});
|
|
169
|
+
const block = await (await blockResponse).json();
|
|
170
|
+
console.log("[webcat] Update block fetched");
|
|
171
|
+
if (void 0) ;
|
|
172
|
+
const { proto: vset, cryptoIndex } = await importValidators(
|
|
173
|
+
__privateGet(this, _validatorSet)
|
|
174
|
+
);
|
|
175
|
+
const sh = importCommit(block);
|
|
176
|
+
const out = await verifyCommit(sh, vset, cryptoIndex, __privateGet(this, _chainId));
|
|
177
|
+
if (out.ok) {
|
|
178
|
+
console.log(
|
|
179
|
+
"[webcat] Block verified, app_hash: ",
|
|
180
|
+
Uint8ArrayToBase64(out.appHash),
|
|
181
|
+
"time: ",
|
|
182
|
+
out.headerTime
|
|
183
|
+
);
|
|
184
|
+
} else {
|
|
185
|
+
throw new Error(`Block verification failed: ${out}`);
|
|
186
|
+
}
|
|
187
|
+
if (!out.headerTime) {
|
|
188
|
+
throw new Error("Block verification did not return a time");
|
|
189
|
+
}
|
|
190
|
+
const meta = await __privateGet(this, _db).getBlockMeta();
|
|
191
|
+
if (meta !== null && out.headerTime.seconds <= meta.blockTime) {
|
|
192
|
+
console.log("[webcat] Block already applied, skipping");
|
|
193
|
+
__privateSet(this, _lastUpdateFailed, false);
|
|
194
|
+
this.dispatchEvent(new UpdateEvent(false, local));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const leaves = await (await leavesResponse).json();
|
|
198
|
+
if (!arraysEqual(hexToUint8Array(leaves.proof.app_hash), out.appHash)) {
|
|
199
|
+
throw new Error("app hash mismatch");
|
|
200
|
+
}
|
|
201
|
+
const verifiedLeaves = await verifyWebcatProof(leaves);
|
|
202
|
+
if (verifiedLeaves === false) {
|
|
203
|
+
throw new Error("proof did not verify against app hash");
|
|
204
|
+
}
|
|
205
|
+
await __privateGet(this, _db).updateList(verifiedLeaves, {
|
|
206
|
+
blockTime: Number(out.headerTime.seconds),
|
|
207
|
+
rootHash: leaves.proof.canonical_root_hash
|
|
208
|
+
});
|
|
209
|
+
if (!local) {
|
|
210
|
+
await __privateGet(this, _db).setLastUpdated();
|
|
211
|
+
}
|
|
212
|
+
console.log(`[webcat] List updated successfully`);
|
|
213
|
+
this.dispatchEvent(new UpdateEvent(true, local));
|
|
214
|
+
__privateSet(this, _lastUpdateFailed, false);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error("[webcat] Update failed:", error);
|
|
217
|
+
__privateSet(this, _lastUpdateFailed, true);
|
|
218
|
+
this.dispatchEvent(new UpdateEvent(false, local));
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Retries an update if the previous attempt failed. Unlike {@link update},
|
|
224
|
+
* retryIfFailed never throws.
|
|
225
|
+
*/
|
|
226
|
+
async retryIfFailed() {
|
|
227
|
+
if (__privateGet(this, _lastUpdateFailed)) {
|
|
228
|
+
console.log("[webcat] Retrying failed update on main_frame navigation");
|
|
229
|
+
try {
|
|
230
|
+
await __privateMethod(this, __EnrollmentUpdater2_instances, checkAndUpdate_fn).call(this);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error("[webcat] Retry update failed:", error);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Determines whether an update is due.
|
|
238
|
+
*
|
|
239
|
+
* @returns A Promise that resolves to true if an update is due.
|
|
240
|
+
*/
|
|
241
|
+
async isDue() {
|
|
242
|
+
const lastUpdated = await __privateGet(this, _db).getLastUpdated();
|
|
243
|
+
return lastUpdated === null || Date.now() - lastUpdated >= __privateGet(this, _updateInterval);
|
|
244
|
+
}
|
|
245
|
+
}, _endpoint = new WeakMap(), _localDataPath = new WeakMap(), _db = new WeakMap(), _validatorSet = new WeakMap(), _chainId = new WeakMap(), _checkInterval = new WeakMap(), _updateInterval = new WeakMap(), _fetchTimeout = new WeakMap(), _alarm = new WeakMap(), _mutex = new WeakMap(), _lastUpdateFailed = new WeakMap(), __EnrollmentUpdater2_instances = new WeakSet(), checkAndUpdate_fn = async function() {
|
|
246
|
+
var _stack = [];
|
|
247
|
+
try {
|
|
248
|
+
const _lock = __using(_stack, await __privateGet(this, _mutex).acquire());
|
|
249
|
+
if (await this.isDue() || void 0) {
|
|
250
|
+
console.log("[webcat] Running overdue scheduled update");
|
|
251
|
+
try {
|
|
252
|
+
await this.update();
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error("[webcat] Scheduled update failed:", error);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} catch (_) {
|
|
258
|
+
var _error = _, _hasError = true;
|
|
259
|
+
} finally {
|
|
260
|
+
__callDispose(_stack, _error, _hasError);
|
|
261
|
+
}
|
|
262
|
+
}, handleUpdateAlarm_fn = async function(alarm) {
|
|
263
|
+
if (alarm.name !== __privateGet(this, _alarm)) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
await __privateMethod(this, __EnrollmentUpdater2_instances, checkAndUpdate_fn).call(this);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error("[webcat] Error in update alarm handler:", error);
|
|
270
|
+
}
|
|
271
|
+
}, fetchWithTimeout_fn = async function(url, timeoutMs = __privateGet(this, _fetchTimeout)) {
|
|
272
|
+
const controller = new AbortController();
|
|
273
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
274
|
+
try {
|
|
275
|
+
const response = await fetch(url, {
|
|
276
|
+
cache: "no-store",
|
|
277
|
+
signal: controller.signal
|
|
278
|
+
});
|
|
279
|
+
clearTimeout(timeoutId);
|
|
280
|
+
return response;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
clearTimeout(timeoutId);
|
|
283
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
284
|
+
throw new Error(`Fetch timeout after ${timeoutMs}ms: ${url}`);
|
|
285
|
+
}
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}, _a2.DefaultCheckInterval = 5 * 60 * 1e3, _a2.DefaultUpdateInterval = 60 * 60 * 1e3, _a2.DefaultFetchTimeout = 3e3, _a2);
|
|
289
|
+
_init = __decoratorStart(_a);
|
|
290
|
+
_EnrollmentUpdater = __decorateElement(_init, 0, "EnrollmentUpdater", _EnrollmentUpdater_decorators, _EnrollmentUpdater);
|
|
291
|
+
__runInitializers(_init, 1, _EnrollmentUpdater);
|
|
292
|
+
let EnrollmentUpdater = _EnrollmentUpdater;
|
|
293
|
+
export {
|
|
294
|
+
EnrollmentUpdater,
|
|
295
|
+
UpdateEvent
|
|
296
|
+
};
|