@sweidos/eidos 1.0.34 → 1.2.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 +171 -89
- package/dist/action.js +197 -91
- package/dist/async-storage-adapter.js +15 -12
- package/dist/cli.js +102 -0
- package/dist/devtools.js +1009 -551
- package/dist/eidos-sw.js +280 -188
- package/dist/eidos.cjs +15 -0
- package/dist/idb.js +59 -56
- package/dist/index.d.ts +135 -18
- package/dist/index.js +46 -42
- package/dist/nextjs.js +1 -10
- package/dist/push.cjs +120 -0
- package/dist/push.d.ts +28 -0
- package/dist/push.js +113 -0
- package/dist/query.cjs +131 -0
- package/dist/query.js +121 -41
- package/dist/queue-storage.js +5 -4
- package/dist/react/Devtools.d.ts +1 -1
- package/dist/react/Provider.js +11 -7
- package/dist/react/hooks.js +48 -38
- package/dist/react-native.js +47 -53
- package/dist/replay.js +15 -0
- package/dist/resource.js +77 -79
- package/dist/runtime.js +39 -28
- package/dist/store-slices.js +43 -0
- package/dist/store.js +32 -49
- package/dist/stores.js +25 -22
- package/dist/sveltekit.js +22 -6
- package/dist/sw-bridge.js +64 -49
- package/dist/testing.cjs +165 -0
- package/dist/testing.js +140 -70
- package/dist/version.js +4 -3
- package/dist/vite.cjs +48 -0
- package/dist/vite.js +45 -29
- package/package.json +57 -28
- package/dist/action.js.map +0 -1
- package/dist/async-storage-adapter.js.map +0 -1
- package/dist/eidos.cjs.js +0 -14
- package/dist/eidos.cjs.js.map +0 -1
- package/dist/idb.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/query.cjs.js +0 -48
- package/dist/queue-storage.js.map +0 -1
- package/dist/react/Provider.js.map +0 -1
- package/dist/react/hooks.js.map +0 -1
- package/dist/resource.js.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/stores.js.map +0 -1
- package/dist/sw-bridge.js.map +0 -1
- package/dist/testing.cjs.js +0 -86
- package/dist/version.js.map +0 -1
- package/dist/vite.cjs.js +0 -31
package/dist/action.js
CHANGED
|
@@ -1,69 +1,99 @@
|
|
|
1
|
-
import { useEidosStore as
|
|
2
|
-
import { getSwRegistration as
|
|
3
|
-
import {
|
|
4
|
-
import { _getQueueStorage as
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
getPending: () => I(),
|
|
9
|
-
update: (e, t) => h(e, t),
|
|
10
|
-
remove: (e) => Q(e),
|
|
11
|
-
clear: () => g()
|
|
12
|
-
};
|
|
13
|
-
function o() {
|
|
14
|
-
return v() ?? S;
|
|
1
|
+
import { useEidosStore as y } from "./store.js";
|
|
2
|
+
import { getSwRegistration as _ } from "./sw-bridge.js";
|
|
3
|
+
import { idbQueueStorage as S } from "./idb.js";
|
|
4
|
+
import { _getQueueStorage as M } from "./queue-storage.js";
|
|
5
|
+
var h = /* @__PURE__ */ new Map(), k = /* @__PURE__ */ new Map(), C = /* @__PURE__ */ new Map(), Q = /* @__PURE__ */ new Map(), x = /* @__PURE__ */ new Map(), p = /* @__PURE__ */ new Map();
|
|
6
|
+
function u() {
|
|
7
|
+
return M() ?? S;
|
|
15
8
|
}
|
|
16
9
|
function m() {
|
|
17
10
|
return crypto.randomUUID();
|
|
18
11
|
}
|
|
12
|
+
function v(e, t, i) {
|
|
13
|
+
return e(...t, i);
|
|
14
|
+
}
|
|
19
15
|
function U(e, t) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
return await e(...a);
|
|
30
|
-
} catch {
|
|
31
|
-
return p(r, r, a, t);
|
|
32
|
-
}
|
|
16
|
+
const i = t.name || e.name || m(), a = t.namespace ? `${t.namespace}::${i}` : i;
|
|
17
|
+
h.set(a, e), x.set(a, t), t.onRollback && k.set(a, t.onRollback), t.onConflict && C.set(a, t.onConflict), t.conflict && Q.set(a, t.conflict);
|
|
18
|
+
const c = async (...n) => {
|
|
19
|
+
const { isOnline: s } = y.getState(), l = t.reliability === "neverLose" || t.cancellable, o = l ? m() : "";
|
|
20
|
+
let d;
|
|
21
|
+
if (t.cancellable) {
|
|
22
|
+
const f = new AbortController();
|
|
23
|
+
p.set(o, f), d = f.signal;
|
|
33
24
|
}
|
|
25
|
+
const g = {
|
|
26
|
+
idempotencyKey: o,
|
|
27
|
+
attempt: 0,
|
|
28
|
+
signal: d
|
|
29
|
+
};
|
|
30
|
+
t.onOptimistic?.(...n, g);
|
|
34
31
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
if (t.reliability === "neverLose") {
|
|
33
|
+
if (!s) return R(a, a, n, t, o);
|
|
34
|
+
try {
|
|
35
|
+
return await v(e, n, g);
|
|
36
|
+
} catch (f) {
|
|
37
|
+
if (A(f)) throw f;
|
|
38
|
+
return R(a, a, n, t, o);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
return l ? await v(e, n, g) : await e(...n);
|
|
43
|
+
} catch (f) {
|
|
44
|
+
throw t.onRollback?.(...n), f;
|
|
45
|
+
}
|
|
46
|
+
} finally {
|
|
47
|
+
t.cancellable && p.delete(o);
|
|
38
48
|
}
|
|
49
|
+
}, r = async (n) => {
|
|
50
|
+
const s = p.get(n);
|
|
51
|
+
if (s)
|
|
52
|
+
return s.abort(), !0;
|
|
53
|
+
const l = (await u().getAll()).find((o) => o.idempotencyKey === n && o.status === "pending");
|
|
54
|
+
return l ? (y.getState().removeQueueItem(l.id), await u().remove(l.id), !0) : !1;
|
|
39
55
|
};
|
|
40
|
-
return Object.defineProperty(
|
|
56
|
+
return Object.defineProperty(c, "id", {
|
|
57
|
+
value: a,
|
|
58
|
+
writable: !1
|
|
59
|
+
}), Object.defineProperty(c, "config", {
|
|
60
|
+
value: t,
|
|
61
|
+
writable: !1
|
|
62
|
+
}), Object.defineProperty(c, "cancel", {
|
|
63
|
+
value: r,
|
|
64
|
+
writable: !1
|
|
65
|
+
}), c;
|
|
41
66
|
}
|
|
42
|
-
async function
|
|
43
|
-
const
|
|
44
|
-
|
|
67
|
+
async function R(e, t, i, a, c) {
|
|
68
|
+
const r = m(), n = {
|
|
69
|
+
schemaVersion: 2,
|
|
70
|
+
id: r,
|
|
45
71
|
actionId: e,
|
|
46
72
|
actionName: t,
|
|
47
|
-
|
|
73
|
+
idempotencyKey: c,
|
|
74
|
+
args: i,
|
|
48
75
|
queuedAt: Date.now(),
|
|
49
76
|
retryCount: 0,
|
|
50
|
-
maxRetries:
|
|
77
|
+
maxRetries: a.maxRetries ?? 3,
|
|
51
78
|
status: "pending",
|
|
52
|
-
priority:
|
|
79
|
+
priority: a.priority ?? "normal"
|
|
53
80
|
};
|
|
54
|
-
await
|
|
81
|
+
await u().add(n), y.getState().addQueueItem(n);
|
|
55
82
|
try {
|
|
56
|
-
const
|
|
57
|
-
|
|
83
|
+
const s = _();
|
|
84
|
+
s && "sync" in s && await s.sync.register("eidos-queue-replay");
|
|
58
85
|
} catch {
|
|
59
86
|
}
|
|
60
87
|
return {
|
|
61
88
|
queued: !0,
|
|
62
|
-
id:
|
|
89
|
+
id: r,
|
|
63
90
|
message: `"${t}" queued — will execute when online`
|
|
64
91
|
};
|
|
65
92
|
}
|
|
66
|
-
function
|
|
93
|
+
function A(e) {
|
|
94
|
+
return e instanceof DOMException && e.name === "AbortError";
|
|
95
|
+
}
|
|
96
|
+
function K(e) {
|
|
67
97
|
if (e instanceof Response) return e.status >= 400 && e.status < 500;
|
|
68
98
|
if (typeof e == "object" && e !== null) {
|
|
69
99
|
const t = e.status;
|
|
@@ -71,76 +101,152 @@ function x(e) {
|
|
|
71
101
|
}
|
|
72
102
|
return !1;
|
|
73
103
|
}
|
|
74
|
-
function
|
|
104
|
+
function O(e) {
|
|
75
105
|
return Math.min(2e3 * 2 ** e, 3e5) * (0.8 + Math.random() * 0.4);
|
|
76
106
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
107
|
+
function w() {
|
|
108
|
+
return {
|
|
109
|
+
attempted: 0,
|
|
110
|
+
succeeded: 0,
|
|
111
|
+
failed: 0,
|
|
112
|
+
retrying: 0,
|
|
113
|
+
skipped: 0,
|
|
114
|
+
conflicted: 0,
|
|
115
|
+
cancelled: 0
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
var b = !1, q = "eidos-queue-replay";
|
|
119
|
+
async function $() {
|
|
120
|
+
const e = y.getState();
|
|
121
|
+
if (!e.isOnline) return w();
|
|
122
|
+
if (typeof navigator < "u" && navigator.locks) return navigator.locks.request(q, { ifAvailable: !0 }, async (t) => t ? I(e) : w());
|
|
123
|
+
if (b) return w();
|
|
124
|
+
b = !0;
|
|
83
125
|
try {
|
|
84
|
-
return await
|
|
126
|
+
return await I(e);
|
|
85
127
|
} finally {
|
|
86
|
-
|
|
128
|
+
b = !1;
|
|
87
129
|
}
|
|
88
130
|
}
|
|
89
|
-
async function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
131
|
+
async function E(e, t) {
|
|
132
|
+
const i = h.get(e.actionId);
|
|
133
|
+
if (!i) return "skipped";
|
|
134
|
+
const a = x.get(e.actionId)?.cancellable;
|
|
135
|
+
let c;
|
|
136
|
+
if (a) {
|
|
137
|
+
const n = new AbortController();
|
|
138
|
+
p.set(e.idempotencyKey, n), c = n.signal;
|
|
139
|
+
}
|
|
140
|
+
const r = {
|
|
141
|
+
idempotencyKey: e.idempotencyKey,
|
|
142
|
+
attempt: e.retryCount,
|
|
143
|
+
signal: c
|
|
144
|
+
};
|
|
93
145
|
try {
|
|
94
|
-
await
|
|
95
|
-
const
|
|
96
|
-
return t.updateQueueItem(e.id, {
|
|
97
|
-
|
|
146
|
+
await v(i, e.args, r);
|
|
147
|
+
const n = Date.now();
|
|
148
|
+
return t.updateQueueItem(e.id, {
|
|
149
|
+
status: "succeeded",
|
|
150
|
+
completedAt: n
|
|
151
|
+
}), await u().update(e.id, {
|
|
152
|
+
status: "succeeded",
|
|
153
|
+
completedAt: n
|
|
154
|
+
}), setTimeout(() => {
|
|
155
|
+
t.removeQueueItem(e.id), u().remove(e.id);
|
|
98
156
|
}, 3e3), "succeeded";
|
|
99
|
-
} catch (
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
157
|
+
} catch (n) {
|
|
158
|
+
if (A(n))
|
|
159
|
+
return t.removeQueueItem(e.id), await u().remove(e.id), "cancelled";
|
|
160
|
+
if (K(n)) {
|
|
161
|
+
const l = Q.get(e.actionId);
|
|
162
|
+
let o;
|
|
163
|
+
if (l) switch (l.strategy) {
|
|
164
|
+
case "serverWins":
|
|
165
|
+
o = "skip";
|
|
166
|
+
break;
|
|
167
|
+
case "clientWins":
|
|
168
|
+
case "lastWriteWins":
|
|
169
|
+
o = "retry";
|
|
170
|
+
break;
|
|
171
|
+
case "merge":
|
|
172
|
+
case "custom": {
|
|
173
|
+
const d = {
|
|
174
|
+
error: n,
|
|
175
|
+
args: e.args,
|
|
176
|
+
attempt: e.retryCount,
|
|
177
|
+
idempotencyKey: e.idempotencyKey
|
|
178
|
+
};
|
|
179
|
+
o = l.resolve?.(d) ?? "retry";
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const d = C.get(e.actionId);
|
|
185
|
+
d && (o = d(n, e.args));
|
|
186
|
+
}
|
|
187
|
+
if (o === "skip")
|
|
188
|
+
return t.removeQueueItem(e.id), await u().remove(e.id), "conflicted";
|
|
189
|
+
o && typeof o == "object" && (e.args = o.resolved, t.updateQueueItem(e.id, { args: o.resolved }), await u().update(e.id, { args: o.resolved }));
|
|
104
190
|
}
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
return t.updateQueueItem(e.id, {
|
|
191
|
+
const s = e.retryCount + 1;
|
|
192
|
+
if (s >= e.maxRetries)
|
|
193
|
+
return t.updateQueueItem(e.id, {
|
|
194
|
+
status: "failed",
|
|
195
|
+
error: String(n),
|
|
196
|
+
retryCount: s
|
|
197
|
+
}), await u().update(e.id, {
|
|
198
|
+
status: "failed",
|
|
199
|
+
error: String(n),
|
|
200
|
+
retryCount: s
|
|
201
|
+
}), k.get(e.actionId)?.(...e.args), "failed";
|
|
108
202
|
{
|
|
109
|
-
const
|
|
110
|
-
return t.updateQueueItem(e.id, {
|
|
203
|
+
const l = Date.now() + O(s);
|
|
204
|
+
return t.updateQueueItem(e.id, {
|
|
205
|
+
status: "pending",
|
|
206
|
+
retryCount: s,
|
|
207
|
+
nextRetryAt: l
|
|
208
|
+
}), await u().update(e.id, {
|
|
209
|
+
status: "pending",
|
|
210
|
+
retryCount: s,
|
|
211
|
+
nextRetryAt: l
|
|
212
|
+
}), "retrying";
|
|
111
213
|
}
|
|
214
|
+
} finally {
|
|
215
|
+
a && p.delete(e.idempotencyKey);
|
|
112
216
|
}
|
|
113
217
|
}
|
|
114
|
-
async function
|
|
218
|
+
async function D(e, t, i) {
|
|
115
219
|
if (e.length === 0) return;
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
118
|
-
t.batchUpdateQueueItems(
|
|
119
|
-
|
|
120
|
-
|
|
220
|
+
const a = e.filter((r) => h.has(r.actionId));
|
|
221
|
+
if (i.skipped += e.length - a.length, a.length > 0) {
|
|
222
|
+
t.batchUpdateQueueItems(a.map((r) => ({
|
|
223
|
+
id: r.id,
|
|
224
|
+
update: { status: "replaying" }
|
|
225
|
+
})));
|
|
226
|
+
for (const r of a) u().update(r.id, { status: "replaying" });
|
|
121
227
|
}
|
|
122
|
-
const
|
|
123
|
-
for (const
|
|
124
|
-
const
|
|
125
|
-
|
|
228
|
+
const c = await Promise.allSettled(a.map((r) => E(r, t)));
|
|
229
|
+
for (const r of c) {
|
|
230
|
+
const n = r.status === "fulfilled" ? r.value : "failed";
|
|
231
|
+
n === "skipped" ? i.skipped++ : n === "conflicted" ? i.conflicted++ : n === "cancelled" ? i.cancelled++ : (i.attempted++, i[n]++);
|
|
126
232
|
}
|
|
127
233
|
}
|
|
128
|
-
async function
|
|
129
|
-
const t = await
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return a;
|
|
234
|
+
async function I(e) {
|
|
235
|
+
const t = await u().getPending(), i = Date.now(), a = t.filter((r) => r.retryCount < r.maxRetries && (!r.nextRetryAt || r.nextRetryAt <= i)), c = w();
|
|
236
|
+
for (const r of [
|
|
237
|
+
"high",
|
|
238
|
+
"normal",
|
|
239
|
+
"low"
|
|
240
|
+
]) await D(a.filter((n) => (n.priority ?? "normal") === r), e, c);
|
|
241
|
+
return c;
|
|
137
242
|
}
|
|
138
243
|
async function T() {
|
|
139
|
-
await
|
|
244
|
+
await u().clear(), y.getState().hydrateQueue([]);
|
|
140
245
|
}
|
|
141
246
|
export {
|
|
142
247
|
U as action,
|
|
143
248
|
T as clearQueue,
|
|
144
|
-
|
|
249
|
+
$ as replayQueue
|
|
145
250
|
};
|
|
146
|
-
|
|
251
|
+
|
|
252
|
+
//# sourceMappingURL=action.js.map
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
class l {
|
|
1
|
+
var i = "@eidos:queue", l = class {
|
|
3
2
|
constructor(t) {
|
|
4
3
|
this.storage = t;
|
|
5
4
|
}
|
|
@@ -15,28 +14,32 @@ class l {
|
|
|
15
14
|
await this.storage.setItem(i, JSON.stringify(t));
|
|
16
15
|
}
|
|
17
16
|
async add(t) {
|
|
18
|
-
const
|
|
19
|
-
|
|
17
|
+
const a = await this.readAll();
|
|
18
|
+
a.push(t), await this.writeAll(a);
|
|
20
19
|
}
|
|
21
20
|
async getAll() {
|
|
22
21
|
return this.readAll();
|
|
23
22
|
}
|
|
24
23
|
async getPending() {
|
|
25
|
-
return (await this.readAll()).filter((
|
|
24
|
+
return (await this.readAll()).filter((t) => t.status === "pending" || t.status === "failed");
|
|
26
25
|
}
|
|
27
|
-
async update(t,
|
|
28
|
-
const
|
|
29
|
-
s !== -1 && (
|
|
26
|
+
async update(t, a) {
|
|
27
|
+
const e = await this.readAll(), s = e.findIndex((r) => r.id === t);
|
|
28
|
+
s !== -1 && (e[s] = {
|
|
29
|
+
...e[s],
|
|
30
|
+
...a
|
|
31
|
+
}), await this.writeAll(e);
|
|
30
32
|
}
|
|
31
33
|
async remove(t) {
|
|
32
|
-
const
|
|
33
|
-
await this.writeAll(
|
|
34
|
+
const a = await this.readAll();
|
|
35
|
+
await this.writeAll(a.filter((e) => e.id !== t));
|
|
34
36
|
}
|
|
35
37
|
async clear() {
|
|
36
38
|
await this.storage.removeItem(i);
|
|
37
39
|
}
|
|
38
|
-
}
|
|
40
|
+
};
|
|
39
41
|
export {
|
|
40
42
|
l as AsyncStorageQueueStorage
|
|
41
43
|
};
|
|
42
|
-
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=async-storage-adapter.js.map
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
3
|
+
import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { createInterface } from "node:readline/promises";
|
|
6
|
+
//#region src/cli.ts
|
|
7
|
+
var PUBLIC_KEY_NAME = "EIDOS_VAPID_PUBLIC_KEY";
|
|
8
|
+
var PRIVATE_KEY_NAME = "EIDOS_VAPID_PRIVATE_KEY";
|
|
9
|
+
function base64UrlFromBuffer(buf) {
|
|
10
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
11
|
+
}
|
|
12
|
+
function base64UrlToBuffer(b64url) {
|
|
13
|
+
const b64 = (b64url + "=".repeat((4 - b64url.length % 4) % 4)).replace(/-/g, "+").replace(/_/g, "/");
|
|
14
|
+
return Buffer.from(b64, "base64");
|
|
15
|
+
}
|
|
16
|
+
/** Pads a base64url-encoded big-endian integer to `length` bytes (leading zeros). */
|
|
17
|
+
function padTo(b64url, length) {
|
|
18
|
+
const buf = base64UrlToBuffer(b64url);
|
|
19
|
+
if (buf.length === length) return buf;
|
|
20
|
+
const padded = Buffer.alloc(length);
|
|
21
|
+
buf.copy(padded, length - buf.length);
|
|
22
|
+
return padded;
|
|
23
|
+
}
|
|
24
|
+
function generateVapidKeys() {
|
|
25
|
+
const { publicKey, privateKey } = generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
26
|
+
const pubJwk = publicKey.export({ format: "jwk" });
|
|
27
|
+
const privJwk = privateKey.export({ format: "jwk" });
|
|
28
|
+
const x = padTo(pubJwk.x, 32);
|
|
29
|
+
const y = padTo(pubJwk.y, 32);
|
|
30
|
+
const point = Buffer.concat([
|
|
31
|
+
Buffer.from([4]),
|
|
32
|
+
x,
|
|
33
|
+
y
|
|
34
|
+
]);
|
|
35
|
+
const d = padTo(privJwk.d, 32);
|
|
36
|
+
return {
|
|
37
|
+
publicKey: base64UrlFromBuffer(point),
|
|
38
|
+
privateKey: base64UrlFromBuffer(d)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function detectEnvPrefix(cwd) {
|
|
42
|
+
if (existsSync(resolve(cwd, "next.config.js")) || existsSync(resolve(cwd, "next.config.ts"))) return "NEXT_PUBLIC_";
|
|
43
|
+
if (existsSync(resolve(cwd, "vite.config.ts")) || existsSync(resolve(cwd, "vite.config.js"))) return "VITE_";
|
|
44
|
+
if (existsSync(resolve(cwd, "svelte.config.js"))) return "PUBLIC_";
|
|
45
|
+
if (existsSync(resolve(cwd, "nuxt.config.ts")) || existsSync(resolve(cwd, "nuxt.config.js"))) return "NUXT_PUBLIC_";
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
function pickEnvFile(cwd) {
|
|
49
|
+
if (existsSync(resolve(cwd, ".env.local"))) return resolve(cwd, ".env.local");
|
|
50
|
+
if (existsSync(resolve(cwd, ".env"))) return resolve(cwd, ".env");
|
|
51
|
+
return resolve(cwd, ".env.local");
|
|
52
|
+
}
|
|
53
|
+
async function confirm(message) {
|
|
54
|
+
const rl = createInterface({
|
|
55
|
+
input: process.stdin,
|
|
56
|
+
output: process.stdout
|
|
57
|
+
});
|
|
58
|
+
const answer = await rl.question(`${message} (type "yes" to continue): `);
|
|
59
|
+
rl.close();
|
|
60
|
+
return answer.trim().toLowerCase() === "yes";
|
|
61
|
+
}
|
|
62
|
+
async function generateVapidKeysCommand() {
|
|
63
|
+
const cwd = process.cwd();
|
|
64
|
+
const publicKeyName = `${detectEnvPrefix(cwd)}${PUBLIC_KEY_NAME}`;
|
|
65
|
+
const force = process.argv.includes("--force");
|
|
66
|
+
const envFile = pickEnvFile(cwd);
|
|
67
|
+
const existing = existsSync(envFile) ? readFileSync(envFile, "utf8") : "";
|
|
68
|
+
const hasPublic = new RegExp(`^${publicKeyName}=`, "m").test(existing);
|
|
69
|
+
const hasPrivate = new RegExp(`^${PRIVATE_KEY_NAME}=`, "m").test(existing);
|
|
70
|
+
if (hasPublic && hasPrivate) {
|
|
71
|
+
if (!force) {
|
|
72
|
+
console.log(`VAPID keys already configured in ${envFile} — nothing to do.`);
|
|
73
|
+
console.log("Pass --force to regenerate (this invalidates ALL existing push subscriptions).");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!await confirm(`⚠ Regenerating VAPID keys will invalidate ALL existing push subscriptions in ${envFile}. Continue?`)) {
|
|
77
|
+
console.log("Aborted.");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const { publicKey, privateKey } = generateVapidKeys();
|
|
82
|
+
const lines = [`${publicKeyName}=${publicKey}`, `${PRIVATE_KEY_NAME}=${privateKey}`];
|
|
83
|
+
if (hasPublic && hasPrivate) writeFileSync(envFile, `${existing.split("\n").filter((line) => !line.startsWith(`${publicKeyName}=`) && !line.startsWith(`${PRIVATE_KEY_NAME}=`)).join("\n").replace(/\n+$/, "")}\n${lines.join("\n")}\n`);
|
|
84
|
+
else appendFileSync(envFile, `${existing.length > 0 && !existing.endsWith("\n") ? "\n" : ""}${lines.join("\n")}\n`);
|
|
85
|
+
console.log(`✓ VAPID keys written to ${envFile}`);
|
|
86
|
+
console.log("");
|
|
87
|
+
console.log(` ${publicKeyName}=${publicKey}`);
|
|
88
|
+
console.log(` ${PRIVATE_KEY_NAME}=${privateKey}`);
|
|
89
|
+
console.log("");
|
|
90
|
+
console.log(`Give ${PRIVATE_KEY_NAME} and ${publicKeyName} to your backend.`);
|
|
91
|
+
console.log("Backend needs a VAPID-capable web-push library (any language) to send notifications using subscription objects received via onSubscribe.");
|
|
92
|
+
}
|
|
93
|
+
var command = process.argv[2];
|
|
94
|
+
switch (command) {
|
|
95
|
+
case "generate-vapid-keys":
|
|
96
|
+
await generateVapidKeysCommand();
|
|
97
|
+
break;
|
|
98
|
+
default:
|
|
99
|
+
console.log("Usage: eidos generate-vapid-keys [--force]");
|
|
100
|
+
process.exit(command ? 1 : 0);
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|