@stacksjs/browser-extension 0.70.247 → 0.70.249

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.
@@ -17,7 +17,7 @@ export declare function imageSize(bytes: Uint8Array): { width: number, height: n
17
17
  */
18
18
  export declare function amoToken(issuer: string, secret: string, now?: number): string;
19
19
  /** Read the previews a listing currently shows. */
20
- export declare function listFirefoxPreviews(addonId: string, token: string): Promise<FirefoxPreview[]>;
20
+ export declare function listFirefoxPreviews(addonId: string, auth: { issuer: string, secret: string }): Promise<FirefoxPreview[]>;
21
21
  /**
22
22
  * Bring the listing's screenshots in line with the config.
23
23
  *
@@ -37,19 +37,30 @@ function resolveAuth(options) {
37
37
  throw Error(`[browser-extension] missing Firefox Add-ons credentials: ${missing.join(", ")}`);
38
38
  return { issuer, secret };
39
39
  }
40
- async function amo(path, token, init = {}) {
40
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
41
+ function retryAfterMs(response, body) {
42
+ const header = Number(response.headers.get("retry-after"));
43
+ if (Number.isFinite(header) && header > 0)
44
+ return header * 1000;
45
+ const stated = /available in (\d+) seconds?/i.exec(body);
46
+ return stated ? Number(stated[1]) * 1000 : void 0;
47
+ }
48
+ async function amo(path, auth, init = {}, attempt = 0) {
41
49
  const response = await fetch(`${AMO_API}${path}`, {
42
50
  ...init,
43
- headers: { Authorization: `JWT ${token}`, ...init.headers }
51
+ headers: { Authorization: `JWT ${amoToken(auth.issuer, auth.secret)}`, ...init.headers }
44
52
  });
45
- if (!response.ok && response.status !== 404) {
46
- const detail = await response.text().catch(() => "");
47
- throw Error(`[browser-extension] AMO ${init.method ?? "GET"} ${path} failed (${response.status})${detail ? `: ${detail.slice(0, 300)}` : ""}`);
53
+ if (response.ok || response.status === 404)
54
+ return response;
55
+ const detail = await response.text().catch(() => "");
56
+ if (response.status === 429 && attempt < 4) {
57
+ await sleep((retryAfterMs(response, detail) ?? 60000) + 1000);
58
+ return amo(path, auth, init, attempt + 1);
48
59
  }
49
- return response;
60
+ throw Error(`[browser-extension] AMO ${init.method ?? "GET"} ${path} failed (${response.status})${detail ? `: ${detail.slice(0, 300)}` : ""}`);
50
61
  }
51
- export async function listFirefoxPreviews(addonId, token) {
52
- const response = await amo(`/addons/addon/${encodeURIComponent(addonId)}/`, token);
62
+ export async function listFirefoxPreviews(addonId, auth) {
63
+ const response = await amo(`/addons/addon/${encodeURIComponent(addonId)}/`, auth);
53
64
  if (response.status === 404)
54
65
  return [];
55
66
  return ((await response.json()).previews ?? []).map((preview, index) => ({
@@ -69,7 +80,7 @@ export async function syncFirefoxPreviews(config, options = {}) {
69
80
  throw Error(`[browser-extension] Firefox screenshot not found: ${path}`);
70
81
  })), size = imageSize(bytes);
71
82
  return { path: file, bytes, width: size?.width ?? 0, height: size?.height ?? 0 };
72
- })), { issuer, secret } = resolveAuth(options), existing = await listFirefoxPreviews(config.geckoId, amoToken(issuer, secret));
83
+ })), auth = resolveAuth(options), existing = await listFirefoxPreviews(config.geckoId, auth);
73
84
  if (files.every((file) => file.width > 0 && file.height > 0) && existing.length === files.length && existing.slice().sort((a, b) => a.position - b.position).every((preview, index) => preview.size[0] === files[index].width && preview.size[1] === files[index].height))
74
85
  return { unchanged: !0, uploaded: [], removed: [] };
75
86
  if (options.dryRun)
@@ -79,12 +90,12 @@ export async function syncFirefoxPreviews(config, options = {}) {
79
90
  const form = new FormData;
80
91
  form.append("image", new Blob([file.bytes], { type: "image/png" }), basename(file.path));
81
92
  form.append("position", String(index));
82
- const created = await (await amo(`/addons/addon/${encodeURIComponent(config.geckoId)}/previews/`, amoToken(issuer, secret), {
93
+ const created = await (await amo(`/addons/addon/${encodeURIComponent(config.geckoId)}/previews/`, auth, {
83
94
  method: "POST",
84
95
  body: form
85
96
  })).json(), caption = store.screenshotCaptions?.[index];
86
97
  if (caption)
87
- await amo(`/addons/addon/${encodeURIComponent(config.geckoId)}/previews/${created.id}/`, amoToken(issuer, secret), {
98
+ await amo(`/addons/addon/${encodeURIComponent(config.geckoId)}/previews/${created.id}/`, auth, {
88
99
  method: "PATCH",
89
100
  headers: { "Content-Type": "application/json" },
90
101
  body: JSON.stringify({ caption: { "en-US": caption } })
@@ -93,7 +104,7 @@ export async function syncFirefoxPreviews(config, options = {}) {
93
104
  }
94
105
  const removed = [];
95
106
  for (const preview of existing) {
96
- await amo(`/addons/addon/${encodeURIComponent(config.geckoId)}/previews/${preview.id}/`, amoToken(issuer, secret), { method: "DELETE" });
107
+ await amo(`/addons/addon/${encodeURIComponent(config.geckoId)}/previews/${preview.id}/`, auth, { method: "DELETE" });
97
108
  removed.push(preview.id);
98
109
  }
99
110
  return { unchanged: !1, uploaded, removed };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/browser-extension",
3
3
  "type": "module",
4
- "version": "0.70.247",
4
+ "version": "0.70.249",
5
5
  "description": "Build MV3 browser extensions (Chrome, Firefox, Safari) the Stacks way — manifest, content/background scripts, DNR rules, packaging, Safari container app, all config-driven.",
6
6
  "author": "Chris Breuer",
7
7
  "contributors": [