@pipeup/share 0.5.1 → 0.5.3
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/cli.js +448 -465
- package/dist/pipeup+share.min.js +1 -11
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -7,24 +7,24 @@ import { readFile, writeFile } from "node:fs/promises";
|
|
|
7
7
|
var utf8 = (text2) => new TextEncoder().encode(text2);
|
|
8
8
|
var text = (bytes) => new TextDecoder().decode(bytes);
|
|
9
9
|
function toB64u(bytes) {
|
|
10
|
-
let
|
|
11
|
-
for (let i = 0; i < bytes.length; i += 32768)
|
|
12
|
-
return btoa(
|
|
10
|
+
let s2 = "";
|
|
11
|
+
for (let i = 0; i < bytes.length; i += 32768) s2 += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
12
|
+
return btoa(s2).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
13
13
|
}
|
|
14
|
-
function fromB64u(
|
|
15
|
-
const std =
|
|
14
|
+
function fromB64u(s2) {
|
|
15
|
+
const std = s2.replace(/-/g, "+").replace(/_/g, "/");
|
|
16
16
|
const bin = atob(std + "=".repeat((4 - std.length % 4) % 4));
|
|
17
|
-
return Uint8Array.from(bin, (
|
|
17
|
+
return Uint8Array.from(bin, (c2) => c2.charCodeAt(0));
|
|
18
18
|
}
|
|
19
19
|
function toB64(bytes) {
|
|
20
|
-
let
|
|
21
|
-
for (let i = 0; i < bytes.length; i += 32768)
|
|
22
|
-
return btoa(
|
|
20
|
+
let s2 = "";
|
|
21
|
+
for (let i = 0; i < bytes.length; i += 32768) s2 += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
22
|
+
return btoa(s2);
|
|
23
23
|
}
|
|
24
|
-
function fromB64(
|
|
25
|
-
return Uint8Array.from(atob(
|
|
24
|
+
function fromB64(s2) {
|
|
25
|
+
return Uint8Array.from(atob(s2), (c2) => c2.charCodeAt(0));
|
|
26
26
|
}
|
|
27
|
-
var randomBytes = (
|
|
27
|
+
var randomBytes = (n2) => crypto.getRandomValues(new Uint8Array(n2));
|
|
28
28
|
async function sha256(data) {
|
|
29
29
|
return new Uint8Array(await crypto.subtle.digest("SHA-256", data));
|
|
30
30
|
}
|
|
@@ -70,19 +70,19 @@ async function pipe(data, t2, cap = Infinity) {
|
|
|
70
70
|
}
|
|
71
71
|
const out = new Uint8Array(total);
|
|
72
72
|
let at = 0;
|
|
73
|
-
for (const
|
|
74
|
-
out.set(
|
|
75
|
-
at +=
|
|
73
|
+
for (const c2 of chunks2) {
|
|
74
|
+
out.set(c2, at);
|
|
75
|
+
at += c2.length;
|
|
76
76
|
}
|
|
77
77
|
return out;
|
|
78
78
|
}
|
|
79
|
-
var seal = (
|
|
80
|
-
async function sealJson(
|
|
79
|
+
var seal = (l2, p2, ops) => sealJson(l2, p2, { pipeup: 1, doc: l2.docId, sealed: false, ops });
|
|
80
|
+
async function sealJson(l2, p2, value) {
|
|
81
81
|
const plain = utf8(JSON.stringify(value));
|
|
82
82
|
const iv = randomBytes(12);
|
|
83
83
|
const body = await crypto.subtle.encrypt(
|
|
84
|
-
{ name: "AES-GCM", iv, additionalData: utf8(
|
|
85
|
-
await
|
|
84
|
+
{ name: "AES-GCM", iv, additionalData: utf8(l2.aad(p2)) },
|
|
85
|
+
await l2.key(p2),
|
|
86
86
|
await pipe(plain, new CompressionStream("deflate-raw"))
|
|
87
87
|
);
|
|
88
88
|
const out = new Uint8Array(13 + body.byteLength);
|
|
@@ -91,12 +91,12 @@ async function sealJson(l, p2, value) {
|
|
|
91
91
|
out.set(new Uint8Array(body), 13);
|
|
92
92
|
return out;
|
|
93
93
|
}
|
|
94
|
-
async function openJson(
|
|
94
|
+
async function openJson(l2, p2, bytes) {
|
|
95
95
|
try {
|
|
96
96
|
if (bytes.length < 13 + 16 || bytes[0] !== 1) return null;
|
|
97
97
|
const plain = await crypto.subtle.decrypt(
|
|
98
|
-
{ name: "AES-GCM", iv: bytes.slice(1, 13), additionalData: utf8(
|
|
99
|
-
await
|
|
98
|
+
{ name: "AES-GCM", iv: bytes.slice(1, 13), additionalData: utf8(l2.aad(p2)) },
|
|
99
|
+
await l2.key(p2),
|
|
100
100
|
bytes.slice(13)
|
|
101
101
|
);
|
|
102
102
|
return JSON.parse(
|
|
@@ -106,25 +106,25 @@ async function openJson(l, p2, bytes) {
|
|
|
106
106
|
return null;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
-
function asBatch(
|
|
109
|
+
function asBatch(l2, value) {
|
|
110
110
|
const file = value;
|
|
111
|
-
if (file?.pipeup !== 1 || file.doc !==
|
|
111
|
+
if (file?.pipeup !== 1 || file.doc !== l2.docId || !Array.isArray(file.ops) || file.ops.length > MAX_OPS)
|
|
112
112
|
return null;
|
|
113
113
|
let newer = 0;
|
|
114
114
|
for (const op of file.ops) {
|
|
115
|
-
const
|
|
116
|
-
if (typeof
|
|
115
|
+
const v2 = op?.body?.v;
|
|
116
|
+
if (typeof v2 === "number" && v2 !== 1) newer++;
|
|
117
117
|
}
|
|
118
118
|
return { ops: file.ops, newer };
|
|
119
119
|
}
|
|
120
|
-
async function open(
|
|
121
|
-
return asBatch(
|
|
120
|
+
async function open(l2, p2, bytes) {
|
|
121
|
+
return asBatch(l2, await openJson(l2, p2, bytes));
|
|
122
122
|
}
|
|
123
123
|
var toText = (bytes) => `pu1.${toB64u(bytes)}`;
|
|
124
|
-
var fromText = (
|
|
125
|
-
if (!/^pu1\.[A-Za-z0-9_-]+$/.test(
|
|
124
|
+
var fromText = (s2) => {
|
|
125
|
+
if (!/^pu1\.[A-Za-z0-9_-]+$/.test(s2)) return null;
|
|
126
126
|
try {
|
|
127
|
-
return fromB64u(
|
|
127
|
+
return fromB64u(s2.slice(4));
|
|
128
128
|
} catch {
|
|
129
129
|
return null;
|
|
130
130
|
}
|
|
@@ -145,135 +145,100 @@ var RetryAfter = class extends Error {
|
|
|
145
145
|
var P = 2n ** 256n - 2n ** 32n - 977n;
|
|
146
146
|
|
|
147
147
|
// ../pipeup/dist/pipeup.core.js
|
|
148
|
-
function
|
|
149
|
-
let
|
|
150
|
-
for (let
|
|
151
|
-
return btoa(
|
|
148
|
+
function e(e3) {
|
|
149
|
+
let t2 = "";
|
|
150
|
+
for (let r2 of e3) t2 += String.fromCharCode(r2);
|
|
151
|
+
return btoa(t2).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
152
152
|
}
|
|
153
|
-
function
|
|
154
|
-
let
|
|
155
|
-
for (let
|
|
156
|
-
return
|
|
153
|
+
function t(e3) {
|
|
154
|
+
let t2 = atob(e3.replace(/-/g, "+").replace(/_/g, "/")), r2 = new Uint8Array(t2.length);
|
|
155
|
+
for (let e4 = 0; e4 < t2.length; e4++) r2[e4] = t2.charCodeAt(e4);
|
|
156
|
+
return r2;
|
|
157
157
|
}
|
|
158
|
-
function
|
|
159
|
-
return
|
|
158
|
+
function r(e3) {
|
|
159
|
+
return new TextEncoder().encode(e3);
|
|
160
160
|
}
|
|
161
|
-
function
|
|
162
|
-
|
|
161
|
+
function n(e3) {
|
|
162
|
+
if (null === e3 || "object" != typeof e3) return JSON.stringify(e3);
|
|
163
|
+
if (Array.isArray(e3)) return "[" + e3.map(n).join(",") + "]";
|
|
164
|
+
let t2 = e3;
|
|
165
|
+
return "{" + Object.keys(t2).filter((e4) => void 0 !== t2[e4]).sort().map((e4) => JSON.stringify(e4) + ":" + n(t2[e4])).join(",") + "}";
|
|
163
166
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
let
|
|
168
|
-
return
|
|
167
|
+
var a = /^[A-Za-z0-9_-]{43}$/;
|
|
168
|
+
var s = /* @__PURE__ */ new Map();
|
|
169
|
+
async function l() {
|
|
170
|
+
let t2 = await crypto.subtle.generateKey({ name: "Ed25519" }, false, ["sign", "verify"]);
|
|
171
|
+
return { publicKey: e(new Uint8Array(await crypto.subtle.exportKey("raw", t2.publicKey))), privateKey: t2.privateKey };
|
|
169
172
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
async function R() {
|
|
174
|
-
let t2 = await crypto.subtle.generateKey({ name: "Ed25519" }, false, ["sign", "verify"]), e = new Uint8Array(await crypto.subtle.exportKey("raw", t2.publicKey));
|
|
175
|
-
return { publicKey: f(e), privateKey: t2.privateKey };
|
|
173
|
+
async function u(t2, r2) {
|
|
174
|
+
let n2 = await crypto.subtle.sign({ name: "Ed25519" }, t2.privateKey, r2);
|
|
175
|
+
return e(new Uint8Array(n2));
|
|
176
176
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
var d = 1e4;
|
|
178
|
+
var c = 2 ** 40;
|
|
179
|
+
var h = 2 ** 47;
|
|
180
|
+
var p = 1e4;
|
|
181
|
+
var f = /^[0-9a-f]{8}$/;
|
|
182
|
+
var m = /^[A-Za-z0-9_-]{43}$/;
|
|
183
|
+
var y = /^[A-Za-z0-9_-]{43}$/;
|
|
184
|
+
var g = /* @__PURE__ */ new Set(["create", "reply", "edit", "delete", "resolve", "reopen"]);
|
|
185
|
+
async function w(e3, t2) {
|
|
186
|
+
if (e3.author !== t2.publicKey) throw new Error("pipeup: an op must be signed by its author");
|
|
187
|
+
return { body: e3, sig: await u(t2, r(n(e3))) };
|
|
180
188
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
return await crypto.subtle.verify({ name: "Ed25519" }, await n, g(r), e);
|
|
194
|
-
} catch {
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
var k = 1e4;
|
|
199
|
-
var Ce = 2 ** 18;
|
|
200
|
-
var D = 2 ** 40;
|
|
201
|
-
var L = 2 ** 47;
|
|
202
|
-
var p = { doc: 64, path: 2e3, snapshot: 500, anchorId: 200, quote: 1e4, context: 64, viewEntries: 16, viewKey: 64, viewValue: 200 };
|
|
203
|
-
var Me = /^[0-9a-f]{8}$/;
|
|
204
|
-
var F = /^[A-Za-z0-9_-]{43}$/;
|
|
205
|
-
var $e = /^[A-Za-z0-9_-]{43}$/;
|
|
206
|
-
var Ne = /* @__PURE__ */ new Set(["create", "reply", "edit", "delete", "resolve", "reopen"]);
|
|
207
|
-
async function Y(t2, e) {
|
|
208
|
-
if (t2.author !== e.publicKey) throw new Error("pipeup: an op must be signed by its author");
|
|
209
|
-
return { body: t2, sig: await S(e, m(x(t2))) };
|
|
210
|
-
}
|
|
211
|
-
var h = (t2, e) => typeof t2 == "string" && t2.length <= e;
|
|
212
|
-
var O = (t2) => !!t2 && typeof t2 == "object";
|
|
213
|
-
var V = (t2) => typeof t2 == "number" && Number.isFinite(t2) && t2 >= 0 && t2 <= 1;
|
|
214
|
-
function B(t2) {
|
|
215
|
-
if (!O(t2)) return false;
|
|
216
|
-
let { id: e, path: r, fingerprint: n, snapshot: o, quote: i, point: s, view: a } = t2;
|
|
217
|
-
if (!h(r, p.path) || !h(o, p.snapshot) || typeof n != "string" || !Me.test(n) || e !== void 0 && (!h(e, p.anchorId) || e === "") || i !== void 0 && (!O(i) || !h(i.exact, p.quote) || i.exact === "" || !h(i.prefix, p.context) || !h(i.suffix, p.context)) || s !== void 0 && (!O(s) || !V(s.x) || !V(s.y))) return false;
|
|
218
|
-
if (a !== void 0) {
|
|
219
|
-
if (!O(a) || Array.isArray(a)) return false;
|
|
220
|
-
let d = Object.entries(a);
|
|
221
|
-
if (d.length > p.viewEntries || !d.every(([c, l]) => c.length <= p.viewKey && h(l, p.viewValue))) return false;
|
|
189
|
+
var b = (e3, t2) => "string" == typeof e3 && e3.length <= t2;
|
|
190
|
+
var x = (e3) => !!e3 && "object" == typeof e3;
|
|
191
|
+
var v = (e3) => "number" == typeof e3 && Number.isFinite(e3) && e3 >= 0 && e3 <= 1;
|
|
192
|
+
function A(e3) {
|
|
193
|
+
if (!x(e3)) return false;
|
|
194
|
+
let { id: t2, path: r2, fingerprint: n2, snapshot: i, quote: o, point: a2, view: s2 } = e3;
|
|
195
|
+
if (!(b(r2, 2e3) && b(i, 500) && "string" == typeof n2 && f.test(n2) && (void 0 === t2 || b(t2, 200) && "" !== t2) && (void 0 === o || x(o) && b(o.exact, p) && "" !== o.exact && b(o.prefix, 64) && b(o.suffix, 64)) && (void 0 === a2 || x(a2) && v(a2.x) && v(a2.y)))) return false;
|
|
196
|
+
if (void 0 !== s2) {
|
|
197
|
+
if (!x(s2) || Array.isArray(s2)) return false;
|
|
198
|
+
let e4 = Object.entries(s2);
|
|
199
|
+
if (e4.length > 16 || !e4.every(([e5, t3]) => e5.length <= 64 && b(t3, 200))) return false;
|
|
222
200
|
}
|
|
223
201
|
return true;
|
|
224
202
|
}
|
|
225
|
-
function
|
|
226
|
-
if (!
|
|
227
|
-
let { body:
|
|
228
|
-
if (
|
|
229
|
-
let
|
|
230
|
-
return !(
|
|
231
|
-
}
|
|
232
|
-
function ee(t2) {
|
|
233
|
-
return Q(t2.body.author, m(x(t2.body)), t2.sig);
|
|
234
|
-
}
|
|
235
|
-
function te(t2, e) {
|
|
236
|
-
return t2.body.clock !== e.body.clock ? t2.body.clock - e.body.clock : t2.body.at !== e.body.at ? t2.body.at - e.body.at : t2.body.author !== e.body.author ? t2.body.author < e.body.author ? -1 : 1 : t2.body.id !== e.body.id ? t2.body.id < e.body.id ? -1 : 1 : 0;
|
|
203
|
+
function E(e3) {
|
|
204
|
+
if (!x(e3)) return false;
|
|
205
|
+
let { body: t2, sig: r2 } = e3;
|
|
206
|
+
if ("string" != typeof r2 || !x(t2) || JSON.stringify({ body: t2, sig: r2 }).length > 262144) return false;
|
|
207
|
+
let n2 = t2;
|
|
208
|
+
return !(1 !== n2.v || "string" != typeof n2.id || !m.test(n2.id) || !g.has(n2.kind) || !b(n2.doc, 64) || "" === n2.doc || "string" != typeof n2.thread || !m.test(n2.thread) || "string" != typeof n2.author || !y.test(n2.author) || "string" != typeof n2.name || n2.name.length > 80 || n2.name !== n2.name.trim() || "number" != typeof n2.clock || !Number.isSafeInteger(n2.clock) || n2.clock < 1 || n2.clock > c || "number" != typeof n2.at || !Number.isSafeInteger(n2.at) || n2.at < 0 || n2.at > h || void 0 !== n2.text && (!b(n2.text, d) || n2.text !== n2.text.trim()) || void 0 !== n2.target && ("string" != typeof n2.target || !m.test(n2.target)) || ("create" === n2.kind || "reply" === n2.kind || "edit" === n2.kind) && !n2.text || ("reply" === n2.kind || "edit" === n2.kind || "delete" === n2.kind) && !n2.target || "create" === n2.kind && (n2.id !== n2.thread || !A(n2.anchor)));
|
|
237
209
|
}
|
|
238
|
-
function
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (e
|
|
243
|
-
let
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
i.resolved = o.kind === "resolve";
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
let s = o.target ? r.get(o.target) : void 0;
|
|
254
|
-
if (!(!s || s.thread !== o.thread)) {
|
|
255
|
-
if (o.kind === "reply") {
|
|
256
|
-
if (r.has(o.id) || o.text === void 0) continue;
|
|
257
|
-
let a = ne(o.id, o.author, o.name, o.text, o.at);
|
|
258
|
-
i.root.replies.push(a), r.set(o.id, { comment: a, thread: o.thread });
|
|
259
|
-
continue;
|
|
210
|
+
function k(i) {
|
|
211
|
+
return (async function(r2, n2, i2) {
|
|
212
|
+
if (!a.test(r2)) return false;
|
|
213
|
+
try {
|
|
214
|
+
if (e(t(r2)) !== r2) return false;
|
|
215
|
+
let o = s.get(r2);
|
|
216
|
+
if (!o) {
|
|
217
|
+
s.size >= 512 && s.delete(s.keys().next().value), o = crypto.subtle.importKey("raw", t(r2), { name: "Ed25519" }, true, ["verify"]), s.set(r2, o);
|
|
218
|
+
let e3 = o;
|
|
219
|
+
e3.catch(() => {
|
|
220
|
+
s.get(r2) === e3 && s.delete(r2);
|
|
221
|
+
});
|
|
260
222
|
}
|
|
261
|
-
|
|
223
|
+
return await crypto.subtle.verify({ name: "Ed25519" }, await o, t(i2), n2);
|
|
224
|
+
} catch {
|
|
225
|
+
return false;
|
|
262
226
|
}
|
|
263
|
-
}
|
|
264
|
-
for (let { comment: o } of r.values()) o.name || (o.unnamed = true, o.name = n.get(o.author) ?? "");
|
|
265
|
-
return [...e.values()];
|
|
227
|
+
})(i.body.author, r(n(i.body)), i.sig);
|
|
266
228
|
}
|
|
267
|
-
function
|
|
268
|
-
return
|
|
229
|
+
function $(e3, t2) {
|
|
230
|
+
return e3.body.clock !== t2.body.clock ? e3.body.clock - t2.body.clock : e3.body.at !== t2.body.at ? e3.body.at - t2.body.at : e3.body.author !== t2.body.author ? e3.body.author < t2.body.author ? -1 : 1 : e3.body.id !== t2.body.id ? e3.body.id < t2.body.id ? -1 : 1 : 0;
|
|
269
231
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return f(new Uint8Array(r));
|
|
232
|
+
function M(e3, t2, r2, n2, i) {
|
|
233
|
+
return { id: e3, author: t2, name: r2, text: n2, at: i, edited: false, deleted: false, replies: [] };
|
|
273
234
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
235
|
+
async function S(t2) {
|
|
236
|
+
let i = { ...t2, id: void 0, thread: "create" === t2.kind ? void 0 : t2.thread }, o = await crypto.subtle.digest("SHA-256", r(n(i)));
|
|
237
|
+
return e(new Uint8Array(o));
|
|
238
|
+
}
|
|
239
|
+
var C = class {
|
|
240
|
+
constructor(e3) {
|
|
241
|
+
this.doc = e3;
|
|
277
242
|
}
|
|
278
243
|
ops = /* @__PURE__ */ new Map();
|
|
279
244
|
clock = 0;
|
|
@@ -286,100 +251,83 @@ var T = class {
|
|
|
286
251
|
return [...this.ops.values()];
|
|
287
252
|
}
|
|
288
253
|
threads() {
|
|
289
|
-
return
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
254
|
+
return (function(e3) {
|
|
255
|
+
let t2 = /* @__PURE__ */ new Map(), r2 = /* @__PURE__ */ new Map(), n2 = /* @__PURE__ */ new Map();
|
|
256
|
+
for (let { body: i } of [...e3].sort($)) {
|
|
257
|
+
if (i.name && n2.set(i.author, i.name), "create" === i.kind) {
|
|
258
|
+
if (t2.has(i.id) || !i.anchor || void 0 === i.text) continue;
|
|
259
|
+
let e5 = M(i.id, i.author, i.name, i.text, i.at);
|
|
260
|
+
t2.set(i.id, { id: i.id, anchor: i.anchor, root: e5, resolved: false }), r2.set(i.id, { comment: e5, thread: i.id });
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
let e4 = t2.get(i.thread);
|
|
264
|
+
if (!e4) continue;
|
|
265
|
+
if ("resolve" === i.kind || "reopen" === i.kind) {
|
|
266
|
+
e4.resolved = "resolve" === i.kind;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
let o = i.target ? r2.get(i.target) : void 0;
|
|
270
|
+
if (o && o.thread === i.thread) {
|
|
271
|
+
if ("reply" === i.kind) {
|
|
272
|
+
if (r2.has(i.id) || void 0 === i.text) continue;
|
|
273
|
+
let t3 = M(i.id, i.author, i.name, i.text, i.at);
|
|
274
|
+
e4.root.replies.push(t3), r2.set(i.id, { comment: t3, thread: i.thread });
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
i.author !== o.comment.author || o.comment.deleted || ("edit" === i.kind && void 0 !== i.text && (o.comment.text = i.text, o.comment.edited = true), "delete" === i.kind && (o.comment.text = "", o.comment.deleted = true));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
for (let { comment: e4 } of r2.values()) e4.name || (e4.unnamed = true, e4.name = n2.get(e4.author) ?? "");
|
|
281
|
+
return [...t2.values()];
|
|
282
|
+
})(this.ops.values());
|
|
283
|
+
}
|
|
284
|
+
async add(e3) {
|
|
285
|
+
let t2 = [];
|
|
286
|
+
for (let r2 of e3) {
|
|
287
|
+
if (!E(r2) || r2.body.doc !== this.doc || this.ops.has(r2.body.id) || r2.body.id !== await S(r2.body) || !await k(r2)) continue;
|
|
288
|
+
let e4 = { body: r2.body, sig: r2.sig };
|
|
289
|
+
this.ops.set(e4.body.id, e4), this.clock = Math.max(this.clock, e4.body.clock), "create" === e4.body.kind && this.createAt.set(e4.body.author, Math.max(this.createAt.get(e4.body.author) ?? 0, e4.body.at)), this.threadAt.set(e4.body.thread, Math.max(this.threadAt.get(e4.body.thread) ?? 0, e4.body.at)), t2.push(e4);
|
|
297
290
|
}
|
|
298
|
-
return
|
|
291
|
+
return t2;
|
|
299
292
|
}
|
|
300
|
-
async append(
|
|
301
|
-
let
|
|
302
|
-
|
|
303
|
-
let i = this.ops.get(
|
|
293
|
+
async append(e3, t2, r2) {
|
|
294
|
+
let n2 = { v: 1, ...r2, id: "", thread: "create" === r2.kind ? "" : r2.thread, doc: this.doc, author: e3.publicKey, name: t2, clock: Math.min(this.clock + 1, c), at: Math.min(Math.max(Date.now(), "create" === r2.kind ? (this.createAt.get(e3.publicKey) ?? 0) + 1 : (this.threadAt.get(r2.thread) ?? 0) + 1), h) };
|
|
295
|
+
n2.id = await S(n2);
|
|
296
|
+
let i = this.ops.get(n2.id);
|
|
304
297
|
if (i) return i;
|
|
305
|
-
|
|
306
|
-
let [
|
|
307
|
-
if (!
|
|
308
|
-
return
|
|
298
|
+
"create" === n2.kind && (n2.thread = n2.id);
|
|
299
|
+
let [o] = await this.add([await w(n2, e3)]);
|
|
300
|
+
if (!o) throw new Error("pipeup: could not record this change");
|
|
301
|
+
return o;
|
|
309
302
|
}
|
|
310
303
|
};
|
|
311
|
-
var
|
|
312
|
-
|
|
313
|
-
|
|
304
|
+
var j = "AI assistant";
|
|
305
|
+
var I = (e3) => e3 === j || e3.startsWith(`${j} `);
|
|
306
|
+
var D = /^([A-Za-z0-9_-]{16}):([A-Za-z0-9_-]{43})$/;
|
|
307
|
+
function F() {
|
|
308
|
+
return `${(function(t2 = 16) {
|
|
309
|
+
return e(crypto.getRandomValues(new Uint8Array(t2)));
|
|
310
|
+
})(12)}:${e(crypto.getRandomValues(new Uint8Array(32)))}`;
|
|
314
311
|
}
|
|
315
|
-
async function
|
|
316
|
-
let
|
|
317
|
-
if (!
|
|
318
|
-
|
|
319
|
-
return { id: r, key: o };
|
|
312
|
+
async function U(e3) {
|
|
313
|
+
let r2 = D.exec(e3.trim()), n2 = r2?.[1], i = r2?.[2];
|
|
314
|
+
if (!n2 || !i) throw new Error('pipeup: data-pipeup-doc must look like "<id>:<key>", as made by newDocumentAttribute()');
|
|
315
|
+
return { id: n2, key: await crypto.subtle.importKey("raw", t(i), { name: "AES-GCM" }, false, ["encrypt", "decrypt"]) };
|
|
320
316
|
}
|
|
321
|
-
async function
|
|
322
|
-
let
|
|
323
|
-
return { iv:
|
|
317
|
+
async function R(t2, n2, i) {
|
|
318
|
+
let o = crypto.getRandomValues(new Uint8Array(12)), a2 = await crypto.subtle.encrypt({ name: "AES-GCM", iv: o, additionalData: r(i) }, t2, r(n2));
|
|
319
|
+
return { iv: e(o), data: e(new Uint8Array(a2)) };
|
|
324
320
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
throw new Error("pipeup: this feedback could not be opened with this document");
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
var Le = 5e6;
|
|
334
|
-
var se = (t2) => `pipeup-file:${t2}`;
|
|
335
|
-
async function ae(t2, e, r) {
|
|
336
|
-
let n = r ? { pipeup: 1, doc: t2, sealed: true, ...await oe(r, JSON.stringify(e), se(t2)) } : { pipeup: 1, doc: t2, sealed: false, ops: e };
|
|
337
|
-
return JSON.stringify(n, null, 2);
|
|
338
|
-
}
|
|
339
|
-
async function de(t2, e, r) {
|
|
340
|
-
if (t2.length > Le) throw new Error("pipeup: this feedback file is too large");
|
|
341
|
-
let n;
|
|
342
|
-
try {
|
|
343
|
-
n = JSON.parse(t2);
|
|
344
|
-
} catch {
|
|
345
|
-
throw new Error("pipeup: this is not an Pipeup feedback file");
|
|
346
|
-
}
|
|
347
|
-
if (!n || typeof n != "object" || n.pipeup !== 1 || typeof n.doc != "string") throw new Error("pipeup: this is not an Pipeup feedback file");
|
|
348
|
-
if (n.doc !== e) throw new Error("pipeup: this feedback is for a different document");
|
|
349
|
-
if (!n.sealed) return Array.isArray(n.ops) ? n.ops : [];
|
|
350
|
-
if (!r) throw new Error("pipeup: this feedback is sealed, and this page has no document key");
|
|
351
|
-
if (typeof n.iv != "string" || typeof n.data != "string") throw new Error("pipeup: this is not an Pipeup feedback file");
|
|
352
|
-
let o;
|
|
353
|
-
try {
|
|
354
|
-
o = await ie(r, { iv: n.iv, data: n.data }, se(e));
|
|
355
|
-
} catch {
|
|
356
|
-
throw new Error("pipeup: this feedback could not be opened with this document's key");
|
|
357
|
-
}
|
|
358
|
-
let i;
|
|
359
|
-
try {
|
|
360
|
-
i = JSON.parse(o);
|
|
361
|
-
} catch {
|
|
362
|
-
throw new Error("pipeup: this is not an Pipeup feedback file");
|
|
363
|
-
}
|
|
364
|
-
return Array.isArray(i) ? i : [];
|
|
365
|
-
}
|
|
366
|
-
var Be = 5e3;
|
|
367
|
-
var _e = /^[a-z][a-z0-9-]{1,23}\/[a-z][a-z0-9-]{0,31}$/;
|
|
368
|
-
var ce = 80;
|
|
369
|
-
var E = class extends Error {
|
|
370
|
-
constructor(r, n, o = 1) {
|
|
371
|
-
super("pipeup: shown here, but not saved yet");
|
|
372
|
-
this.id = r;
|
|
373
|
-
this.cause = n;
|
|
374
|
-
this.count = o;
|
|
375
|
-
this.name = "UnsavedChangeError";
|
|
321
|
+
var H = (e3) => `pipeup-file:${e3}`;
|
|
322
|
+
var L = /^[a-z][a-z0-9-]{1,23}\/[a-z][a-z0-9-]{0,31}$/;
|
|
323
|
+
var z = class extends Error {
|
|
324
|
+
constructor(e3, t2, r2 = 1) {
|
|
325
|
+
super("pipeup: shown here, but not saved yet"), this.id = e3, this.cause = t2, this.count = r2, this.name = "UnsavedChangeError";
|
|
376
326
|
}
|
|
377
327
|
};
|
|
378
|
-
var K = class
|
|
379
|
-
constructor(
|
|
380
|
-
this.log =
|
|
381
|
-
this.options = r;
|
|
382
|
-
this.displayName = r.name.trim() ? pe(r.name) : "";
|
|
328
|
+
var K = class e2 {
|
|
329
|
+
constructor(e3, t2) {
|
|
330
|
+
this.log = e3, this.options = t2, this.displayName = t2.name.trim() ? B(t2.name) : "";
|
|
383
331
|
}
|
|
384
332
|
listeners = /* @__PURE__ */ new Set();
|
|
385
333
|
cache = [];
|
|
@@ -387,12 +335,12 @@ var K = class t {
|
|
|
387
335
|
displayName;
|
|
388
336
|
e = Promise.resolve();
|
|
389
337
|
unsaved = [];
|
|
390
|
-
static async open(
|
|
391
|
-
if (typeof
|
|
392
|
-
let
|
|
393
|
-
|
|
394
|
-
let
|
|
395
|
-
return
|
|
338
|
+
static async open(t2) {
|
|
339
|
+
if ("string" != typeof t2.doc || "" === t2.doc || t2.doc.length > 64) throw new Error("pipeup: the document id must be 1 to 64 characters");
|
|
340
|
+
let r2 = new C(t2.doc), n2 = await t2.store.load(t2.doc), i = n2.length - (await r2.add(n2)).length;
|
|
341
|
+
i && console.warn(`pipeup: ${i} saved change${1 === i ? "" : "s"} on this page couldn't be read; kept as saved`);
|
|
342
|
+
let o = new e2(r2, t2);
|
|
343
|
+
return o.refresh(false), o;
|
|
396
344
|
}
|
|
397
345
|
get id() {
|
|
398
346
|
return this.options.doc;
|
|
@@ -403,8 +351,8 @@ var K = class t {
|
|
|
403
351
|
get name() {
|
|
404
352
|
return this.displayName;
|
|
405
353
|
}
|
|
406
|
-
set name(
|
|
407
|
-
this.displayName =
|
|
354
|
+
set name(e3) {
|
|
355
|
+
this.displayName = B(e3), this.refresh(true, [], "");
|
|
408
356
|
}
|
|
409
357
|
threads() {
|
|
410
358
|
return this.cache;
|
|
@@ -412,141 +360,176 @@ var K = class t {
|
|
|
412
360
|
ops() {
|
|
413
361
|
return this.log.all();
|
|
414
362
|
}
|
|
415
|
-
onChange(
|
|
416
|
-
return this.listeners.add(
|
|
363
|
+
onChange(e3) {
|
|
364
|
+
return this.listeners.add(e3), () => this.listeners.delete(e3);
|
|
417
365
|
}
|
|
418
|
-
comment(
|
|
419
|
-
return this.enqueue(() => this.write({ kind: "create", anchor:
|
|
366
|
+
comment(e3, t2) {
|
|
367
|
+
return this.enqueue(() => this.write({ kind: "create", anchor: e3, text: _(t2) }));
|
|
420
368
|
}
|
|
421
|
-
reply(
|
|
422
|
-
return this.enqueue(() => this.write({ kind: "reply", thread: this.find(
|
|
369
|
+
reply(e3, t2) {
|
|
370
|
+
return this.enqueue(() => this.write({ kind: "reply", thread: this.find(e3).thread, target: e3, text: _(t2) }));
|
|
423
371
|
}
|
|
424
|
-
edit(
|
|
372
|
+
edit(e3, t2) {
|
|
425
373
|
return this.enqueue(async () => {
|
|
426
|
-
let { thread:
|
|
427
|
-
await this.write({ kind: "edit", thread:
|
|
374
|
+
let { thread: r2 } = this.mine(e3);
|
|
375
|
+
await this.write({ kind: "edit", thread: r2, target: e3, text: _(t2) });
|
|
428
376
|
});
|
|
429
377
|
}
|
|
430
|
-
remove(
|
|
378
|
+
remove(e3) {
|
|
431
379
|
return this.enqueue(async () => {
|
|
432
|
-
let { thread:
|
|
433
|
-
await this.write({ kind: "delete", thread:
|
|
380
|
+
let { thread: t2 } = this.mine(e3);
|
|
381
|
+
await this.write({ kind: "delete", thread: t2, target: e3 });
|
|
434
382
|
});
|
|
435
383
|
}
|
|
436
|
-
resolve(
|
|
384
|
+
resolve(e3) {
|
|
437
385
|
return this.enqueue(async () => {
|
|
438
|
-
await this.write({ kind: "resolve", thread: this.thread(
|
|
386
|
+
await this.write({ kind: "resolve", thread: this.thread(e3) });
|
|
439
387
|
});
|
|
440
388
|
}
|
|
441
|
-
reopen(
|
|
389
|
+
reopen(e3) {
|
|
442
390
|
return this.enqueue(async () => {
|
|
443
|
-
await this.write({ kind: "reopen", thread: this.thread(
|
|
391
|
+
await this.write({ kind: "reopen", thread: this.thread(e3) });
|
|
444
392
|
});
|
|
445
393
|
}
|
|
446
|
-
sign(
|
|
447
|
-
if (!
|
|
448
|
-
return
|
|
449
|
-
${
|
|
394
|
+
sign(e3, t2) {
|
|
395
|
+
if (!L.test(e3)) throw new Error('pipeup: a signing purpose looks like "<add-on id>/<name>"');
|
|
396
|
+
return u(this.options.identity, r(`pipeup:${e3}
|
|
397
|
+
${t2}`));
|
|
450
398
|
}
|
|
451
399
|
flush() {
|
|
452
400
|
return this.enqueue(() => this.persist());
|
|
453
401
|
}
|
|
454
402
|
exportFile() {
|
|
455
|
-
return
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
403
|
+
return (async function(e3, t2, r2) {
|
|
404
|
+
let n2 = r2 ? { pipeup: 1, doc: e3, sealed: true, ...await R(r2, JSON.stringify(t2), H(e3)) } : { pipeup: 1, doc: e3, sealed: false, ops: t2 };
|
|
405
|
+
return JSON.stringify(n2, null, 2);
|
|
406
|
+
})(this.options.doc, this.log.all(), this.options.key);
|
|
407
|
+
}
|
|
408
|
+
async importFile(e3) {
|
|
409
|
+
return this.merge(await (async function(e4, n2, i) {
|
|
410
|
+
if (e4.length > 5e6) throw new Error("pipeup: this feedback file is too large");
|
|
411
|
+
let o, a2, s2;
|
|
412
|
+
try {
|
|
413
|
+
o = JSON.parse(e4);
|
|
414
|
+
} catch {
|
|
415
|
+
throw new Error("pipeup: this is not an Pipeup feedback file");
|
|
416
|
+
}
|
|
417
|
+
if (!o || "object" != typeof o || 1 !== o.pipeup || "string" != typeof o.doc) throw new Error("pipeup: this is not an Pipeup feedback file");
|
|
418
|
+
if (o.doc !== n2) throw new Error("pipeup: this feedback is for a different document");
|
|
419
|
+
if (!o.sealed) return Array.isArray(o.ops) ? o.ops : [];
|
|
420
|
+
if (!i) throw new Error("pipeup: this feedback is sealed, and this page has no document key");
|
|
421
|
+
if ("string" != typeof o.iv || "string" != typeof o.data) throw new Error("pipeup: this is not an Pipeup feedback file");
|
|
422
|
+
try {
|
|
423
|
+
a2 = await (async function(e5, n3, i2) {
|
|
424
|
+
try {
|
|
425
|
+
let o2 = await crypto.subtle.decrypt({ name: "AES-GCM", iv: t(n3.iv), additionalData: r(i2) }, e5, t(n3.data));
|
|
426
|
+
return new TextDecoder().decode(o2);
|
|
427
|
+
} catch {
|
|
428
|
+
throw new Error("pipeup: this feedback could not be opened with this document");
|
|
429
|
+
}
|
|
430
|
+
})(i, { iv: o.iv, data: o.data }, H(n2));
|
|
431
|
+
} catch {
|
|
432
|
+
throw new Error("pipeup: this feedback could not be opened with this document's key");
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
s2 = JSON.parse(a2);
|
|
436
|
+
} catch {
|
|
437
|
+
throw new Error("pipeup: this is not an Pipeup feedback file");
|
|
438
|
+
}
|
|
439
|
+
return Array.isArray(s2) ? s2 : [];
|
|
440
|
+
})(e3, this.options.doc, this.options.key), "file");
|
|
459
441
|
}
|
|
460
|
-
merge(
|
|
461
|
-
return
|
|
462
|
-
let
|
|
463
|
-
this.unsaved.push(...
|
|
442
|
+
merge(e3, t2) {
|
|
443
|
+
return e3.length > 5e3 ? Promise.reject(new RangeError("pipeup: too many changes to merge at once")) : this.enqueue(async () => {
|
|
444
|
+
let r2 = await this.log.add([...e3]);
|
|
445
|
+
this.unsaved.push(...r2), r2.length > 0 && this.refresh(true, r2, t2);
|
|
464
446
|
try {
|
|
465
447
|
await this.persist();
|
|
466
|
-
} catch (
|
|
467
|
-
throw
|
|
448
|
+
} catch (e4) {
|
|
449
|
+
throw r2.length > 0 ? new z(r2[0].body.id, e4, r2.length) : e4;
|
|
468
450
|
}
|
|
469
|
-
return
|
|
451
|
+
return r2.length;
|
|
470
452
|
});
|
|
471
453
|
}
|
|
472
|
-
enqueue(
|
|
473
|
-
let
|
|
474
|
-
return this.e =
|
|
475
|
-
}),
|
|
454
|
+
enqueue(e3) {
|
|
455
|
+
let t2 = this.e.then(e3);
|
|
456
|
+
return this.e = t2.catch(() => {
|
|
457
|
+
}), t2;
|
|
476
458
|
}
|
|
477
|
-
async write(
|
|
478
|
-
let
|
|
479
|
-
this.unsaved.push(
|
|
459
|
+
async write(e3) {
|
|
460
|
+
let t2 = await this.log.append(this.options.identity, this.displayName, e3);
|
|
461
|
+
this.unsaved.push(t2), this.refresh(true, [t2], "local");
|
|
480
462
|
try {
|
|
481
463
|
await this.persist();
|
|
482
|
-
} catch (
|
|
483
|
-
throw new
|
|
464
|
+
} catch (e4) {
|
|
465
|
+
throw new z(t2.body.id, e4);
|
|
484
466
|
}
|
|
485
|
-
return
|
|
467
|
+
return t2.body.id;
|
|
486
468
|
}
|
|
487
469
|
async persist() {
|
|
488
|
-
if (this.unsaved.length
|
|
489
|
-
let
|
|
490
|
-
await this.options.store.append(this.options.doc,
|
|
470
|
+
if (0 === this.unsaved.length) return;
|
|
471
|
+
let e3 = [...this.unsaved];
|
|
472
|
+
await this.options.store.append(this.options.doc, e3), this.unsaved = this.unsaved.filter((t2) => !e3.includes(t2));
|
|
491
473
|
}
|
|
492
|
-
refresh(
|
|
474
|
+
refresh(e3, t2 = [], r2 = "") {
|
|
493
475
|
this.cache = this.log.threads(), this.comments.clear();
|
|
494
|
-
let
|
|
495
|
-
|
|
496
|
-
for (let
|
|
476
|
+
let n2 = (e4, t3) => {
|
|
477
|
+
e4.unnamed && e4.author === this.me && this.displayName && (e4.name = this.displayName), this.comments.set(e4.id, { comment: e4, thread: t3 });
|
|
478
|
+
for (let r3 of e4.replies) n2(r3, t3);
|
|
497
479
|
};
|
|
498
|
-
for (let
|
|
499
|
-
if (
|
|
500
|
-
|
|
501
|
-
} catch (
|
|
502
|
-
globalThis.reportError?.(
|
|
480
|
+
for (let e4 of this.cache) n2(e4.root, e4.id);
|
|
481
|
+
if (e3) for (let e4 of this.listeners) try {
|
|
482
|
+
e4(this.cache, t2, r2);
|
|
483
|
+
} catch (e5) {
|
|
484
|
+
globalThis.reportError?.(e5);
|
|
503
485
|
}
|
|
504
486
|
}
|
|
505
|
-
find(
|
|
506
|
-
let
|
|
507
|
-
if (!
|
|
508
|
-
return
|
|
487
|
+
find(e3) {
|
|
488
|
+
let t2 = this.comments.get(e3);
|
|
489
|
+
if (!t2) throw new Error("pipeup: there is no comment with that id");
|
|
490
|
+
return t2;
|
|
509
491
|
}
|
|
510
|
-
mine(
|
|
511
|
-
let
|
|
512
|
-
if (
|
|
513
|
-
return
|
|
492
|
+
mine(e3) {
|
|
493
|
+
let t2 = this.find(e3);
|
|
494
|
+
if (t2.comment.author !== this.me) throw new Error("pipeup: only the writer can change a comment");
|
|
495
|
+
return t2;
|
|
514
496
|
}
|
|
515
|
-
thread(
|
|
516
|
-
if (!this.cache.some((
|
|
517
|
-
return
|
|
497
|
+
thread(e3) {
|
|
498
|
+
if (!this.cache.some((t2) => t2.id === e3)) throw new Error("pipeup: there is no thread with that id");
|
|
499
|
+
return e3;
|
|
518
500
|
}
|
|
519
501
|
};
|
|
520
|
-
function
|
|
521
|
-
let
|
|
522
|
-
if (!
|
|
523
|
-
if (
|
|
524
|
-
return
|
|
502
|
+
function _(e3) {
|
|
503
|
+
let t2 = e3.trim();
|
|
504
|
+
if (!t2) throw new Error("pipeup: a comment needs some words");
|
|
505
|
+
if (t2.length > d) throw new Error(`pipeup: comments are limited to ${d} characters`);
|
|
506
|
+
return t2;
|
|
525
507
|
}
|
|
526
|
-
function
|
|
527
|
-
let
|
|
528
|
-
if (!
|
|
529
|
-
|
|
508
|
+
function B(e3) {
|
|
509
|
+
let t2 = e3.replace(/\s+/g, " ").trim();
|
|
510
|
+
if (!t2 || t2.length > 80) throw new Error("pipeup: choose a name of 1 to 80 characters");
|
|
511
|
+
if (I(t2)) throw new Error("pipeup: that name is kept for AI replies; choose another");
|
|
512
|
+
return t2;
|
|
530
513
|
}
|
|
531
|
-
var
|
|
514
|
+
var G = class {
|
|
532
515
|
docs = /* @__PURE__ */ new Map();
|
|
533
516
|
profile = null;
|
|
534
|
-
async load(
|
|
535
|
-
return [...this.docs.get(
|
|
517
|
+
async load(e3) {
|
|
518
|
+
return [...this.docs.get(e3)?.values() ?? []];
|
|
536
519
|
}
|
|
537
|
-
async append(
|
|
538
|
-
let
|
|
539
|
-
|
|
540
|
-
for (let
|
|
520
|
+
async append(e3, t2) {
|
|
521
|
+
let r2 = this.docs.get(e3);
|
|
522
|
+
r2 || this.docs.set(e3, r2 = /* @__PURE__ */ new Map());
|
|
523
|
+
for (let e4 of t2) r2.set(e4.body.id, e4);
|
|
541
524
|
}
|
|
542
525
|
async loadProfile() {
|
|
543
526
|
return this.profile;
|
|
544
527
|
}
|
|
545
|
-
async saveProfile(
|
|
546
|
-
this.profile =
|
|
528
|
+
async saveProfile(e3) {
|
|
529
|
+
this.profile = e3;
|
|
547
530
|
}
|
|
548
|
-
async saveProfileIfAbsent(
|
|
549
|
-
return this.profile || (this.profile =
|
|
531
|
+
async saveProfileIfAbsent(e3) {
|
|
532
|
+
return this.profile || (this.profile = e3), this.profile;
|
|
550
533
|
}
|
|
551
534
|
};
|
|
552
535
|
|
|
@@ -603,9 +586,9 @@ var refusal = (status) => {
|
|
|
603
586
|
};
|
|
604
587
|
async function json(res) {
|
|
605
588
|
const t2 = await res.text();
|
|
606
|
-
const
|
|
607
|
-
if (!
|
|
608
|
-
return [
|
|
589
|
+
const v2 = t2.length < 1e7 && JSON.parse(t2);
|
|
590
|
+
if (!v2 || typeof v2 !== "object") throw new Error("the answer was not usable");
|
|
591
|
+
return [v2, t2.length];
|
|
609
592
|
}
|
|
610
593
|
function chunks(ops, max = 9e5) {
|
|
611
594
|
const out = [];
|
|
@@ -632,8 +615,8 @@ var sleep = (ms, signal) => new Promise((resolve) => {
|
|
|
632
615
|
);
|
|
633
616
|
});
|
|
634
617
|
var retryAfter = (res, fallback = 10) => {
|
|
635
|
-
const
|
|
636
|
-
return
|
|
618
|
+
const n2 = Number(res.headers.get("Retry-After"));
|
|
619
|
+
return n2 > 0 ? Math.min(n2, 3600) : fallback;
|
|
637
620
|
};
|
|
638
621
|
var idsOf = (ops) => ops.map((o) => o?.body?.id).filter((i) => typeof i === "string");
|
|
639
622
|
|
|
@@ -642,9 +625,9 @@ var MAX_BODY = 524288;
|
|
|
642
625
|
var MAX_PAGES = 1e3;
|
|
643
626
|
var batchId = async (ct) => toB64u(await sha256(utf8(ct))).slice(0, 22);
|
|
644
627
|
var Mailbox = class {
|
|
645
|
-
constructor(
|
|
646
|
-
this.a =
|
|
647
|
-
this.l =
|
|
628
|
+
constructor(a2, l2, f2, signal) {
|
|
629
|
+
this.a = a2;
|
|
630
|
+
this.l = l2;
|
|
648
631
|
this.f = f2;
|
|
649
632
|
this.signal = signal;
|
|
650
633
|
}
|
|
@@ -660,27 +643,27 @@ var Mailbox = class {
|
|
|
660
643
|
let reset = 0;
|
|
661
644
|
for (let pages = 0; pages < MAX_PAGES; ) {
|
|
662
645
|
const url = `${this.a.mailbox}/ops${this.cursor ? `?since=${encodeURIComponent(this.cursor)}` : ""}`;
|
|
663
|
-
const
|
|
664
|
-
if (
|
|
665
|
-
if (
|
|
646
|
+
const r2 = await GET(this.f, url, this.signal);
|
|
647
|
+
if (r2.status === 404) throw new Problem("gone");
|
|
648
|
+
if (r2.status === 409 && reset++ < 2) {
|
|
666
649
|
this.cursor = "";
|
|
667
650
|
continue;
|
|
668
651
|
}
|
|
669
|
-
if (!
|
|
652
|
+
if (!r2.ok) throw new Error(`HTTP ${r2.status}`);
|
|
670
653
|
pages++;
|
|
671
|
-
const [
|
|
672
|
-
for (const
|
|
673
|
-
if (typeof
|
|
674
|
-
this.seen.add(
|
|
675
|
-
const bytes = fromText(
|
|
654
|
+
const [j2] = await json(r2);
|
|
655
|
+
for (const b2 of Array.isArray(j2.batches) ? j2.batches : []) {
|
|
656
|
+
if (typeof b2?.id !== "string" || typeof b2.ct !== "string" || this.seen.has(b2.id)) continue;
|
|
657
|
+
this.seen.add(b2.id);
|
|
658
|
+
const bytes = fromText(b2.ct);
|
|
676
659
|
const o = bytes && await open(this.l, "share", bytes);
|
|
677
660
|
if (!o) continue;
|
|
678
661
|
out.push(o);
|
|
679
662
|
for (const id of idsOf(o.ops)) this.ids.add(id);
|
|
680
663
|
}
|
|
681
|
-
if (typeof
|
|
682
|
-
this.expires = typeof
|
|
683
|
-
if (
|
|
664
|
+
if (typeof j2.next === "string") this.cursor = j2.next;
|
|
665
|
+
this.expires = typeof j2.expires === "string" ? Date.parse(j2.expires) || null : null;
|
|
666
|
+
if (j2.more !== true) break;
|
|
684
667
|
}
|
|
685
668
|
return out;
|
|
686
669
|
}
|
|
@@ -705,18 +688,18 @@ var Mailbox = class {
|
|
|
705
688
|
await this.one(part.slice(mid));
|
|
706
689
|
};
|
|
707
690
|
if (init.body.length > MAX_BODY && part.length > 1) return split();
|
|
708
|
-
const
|
|
709
|
-
const
|
|
710
|
-
if (
|
|
711
|
-
if (
|
|
712
|
-
const refused = refusal(
|
|
691
|
+
const r2 = await this.f(url, { ...init, signal: this.signal });
|
|
692
|
+
const s2 = r2.status;
|
|
693
|
+
if (s2 === 200 || s2 === 201 || s2 === 400) return;
|
|
694
|
+
if (s2 === 413 && part.length > 1) return split();
|
|
695
|
+
const refused = refusal(s2);
|
|
713
696
|
if (refused) throw refused;
|
|
714
|
-
if (
|
|
715
|
-
const [body] = await json(
|
|
716
|
-
const
|
|
717
|
-
throw new RetryAfter(
|
|
697
|
+
if (s2 === 429) {
|
|
698
|
+
const [body] = await json(r2).catch(() => [{}]);
|
|
699
|
+
const n2 = Number(body.retryAfter);
|
|
700
|
+
throw new RetryAfter(r2.headers.get("Retry-After") ? retryAfter(r2) : n2 > 0 ? Math.min(n2, 3600) : 10);
|
|
718
701
|
}
|
|
719
|
-
throw new Error(`HTTP ${
|
|
702
|
+
throw new Error(`HTTP ${s2}`);
|
|
720
703
|
}
|
|
721
704
|
};
|
|
722
705
|
|
|
@@ -735,42 +718,42 @@ var commentBody = (paste, sealed) => ({
|
|
|
735
718
|
async function answer(res) {
|
|
736
719
|
if (res.status === 429) throw new RetryAfter(retryAfter(res));
|
|
737
720
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
738
|
-
const [
|
|
739
|
-
if (
|
|
740
|
-
const
|
|
741
|
-
throw new RetryAfter(
|
|
721
|
+
const [j2] = await json(res);
|
|
722
|
+
if (j2.status === 0) return j2;
|
|
723
|
+
const n2 = Number(/\d+/.exec(String(j2.message ?? ""))?.[0]);
|
|
724
|
+
throw new RetryAfter(n2 >= 1 && n2 <= 3600 ? n2 : 30, true);
|
|
742
725
|
}
|
|
743
|
-
async function createPaste(f2, base,
|
|
744
|
-
const ct = toB64(await seal(
|
|
745
|
-
const
|
|
726
|
+
async function createPaste(f2, base, l2, expire = "never", signal) {
|
|
727
|
+
const ct = toB64(await seal(l2, "share", []));
|
|
728
|
+
const j2 = await answer(
|
|
746
729
|
await f2(base, { ...POST({ v: 2, adata: [spec(), "plaintext", 1, 0], ct, meta: { expire } }), signal })
|
|
747
730
|
);
|
|
748
|
-
if (typeof
|
|
731
|
+
if (typeof j2.id !== "string" || typeof j2.deletetoken !== "string")
|
|
749
732
|
throw new Error("the answer had no paste id");
|
|
750
|
-
return { id:
|
|
733
|
+
return { id: j2.id, deleteToken: j2.deletetoken };
|
|
751
734
|
}
|
|
752
735
|
async function deletePaste(f2, base, id, token) {
|
|
753
736
|
return f2(base, POST({ pasteid: id, deletetoken: token }));
|
|
754
737
|
}
|
|
755
738
|
async function readPaste(f2, base, id, signal) {
|
|
756
|
-
const
|
|
757
|
-
if (
|
|
758
|
-
if (!
|
|
759
|
-
const [
|
|
760
|
-
if (
|
|
761
|
-
const ttl =
|
|
762
|
-
return { j, size, ttl: typeof ttl === "number" && ttl > 0 ? ttl * 1e3 : null };
|
|
739
|
+
const r2 = await GET(f2, `${base}?${id}`, signal);
|
|
740
|
+
if (r2.status === 404) return null;
|
|
741
|
+
if (!r2.ok) throw new Error(`HTTP ${r2.status}`);
|
|
742
|
+
const [j2, size] = await json(r2);
|
|
743
|
+
if (j2.status !== 0) return null;
|
|
744
|
+
const ttl = j2.meta?.time_to_live;
|
|
745
|
+
return { j: j2, size, ttl: typeof ttl === "number" && ttl > 0 ? ttl * 1e3 : null };
|
|
763
746
|
}
|
|
764
747
|
var PrivateBin = class {
|
|
765
|
-
constructor(
|
|
748
|
+
constructor(a2, l2, f2, signal, gen, onGen = () => {
|
|
766
749
|
}) {
|
|
767
|
-
this.a =
|
|
768
|
-
this.l =
|
|
750
|
+
this.a = a2;
|
|
751
|
+
this.l = l2;
|
|
769
752
|
this.f = f2;
|
|
770
753
|
this.signal = signal;
|
|
771
754
|
this.onGen = onGen;
|
|
772
|
-
this.head = gen ||
|
|
773
|
-
this.trail.push(
|
|
755
|
+
this.head = gen || a2.paste;
|
|
756
|
+
this.trail.push(a2.paste);
|
|
774
757
|
}
|
|
775
758
|
ids = /* @__PURE__ */ new Set();
|
|
776
759
|
expires = null;
|
|
@@ -784,27 +767,27 @@ var PrivateBin = class {
|
|
|
784
767
|
seen = /* @__PURE__ */ new Map();
|
|
785
768
|
async scan(comments, out) {
|
|
786
769
|
let next;
|
|
787
|
-
for (const
|
|
788
|
-
if (typeof
|
|
789
|
-
let
|
|
790
|
-
if (
|
|
791
|
-
|
|
770
|
+
for (const c2 of Array.isArray(comments) ? comments : []) {
|
|
771
|
+
if (typeof c2?.id !== "string" || typeof c2.ct !== "string") continue;
|
|
772
|
+
let n2 = this.seen.get(c2.id);
|
|
773
|
+
if (n2 === void 0) {
|
|
774
|
+
n2 = null;
|
|
792
775
|
let bytes = null;
|
|
793
776
|
try {
|
|
794
|
-
if (
|
|
777
|
+
if (c2.ct.length < 14e5) bytes = fromB64(c2.ct);
|
|
795
778
|
} catch {
|
|
796
779
|
}
|
|
797
|
-
const
|
|
798
|
-
const nx =
|
|
799
|
-
const
|
|
800
|
-
if (typeof nx === "string" && NEXT.test(nx))
|
|
801
|
-
else if (
|
|
802
|
-
out.push(
|
|
803
|
-
for (const id of idsOf(
|
|
780
|
+
const v2 = bytes && await openJson(this.l, "share", bytes);
|
|
781
|
+
const nx = v2?.next;
|
|
782
|
+
const b2 = typeof nx === "string" ? null : asBatch(this.l, v2);
|
|
783
|
+
if (typeof nx === "string" && NEXT.test(nx)) n2 = nx;
|
|
784
|
+
else if (b2) {
|
|
785
|
+
out.push(b2);
|
|
786
|
+
for (const id of idsOf(b2.ops)) this.ids.add(id);
|
|
804
787
|
}
|
|
805
|
-
this.seen.set(
|
|
788
|
+
this.seen.set(c2.id, n2);
|
|
806
789
|
}
|
|
807
|
-
next ??=
|
|
790
|
+
next ??= n2 ?? void 0;
|
|
808
791
|
}
|
|
809
792
|
return next;
|
|
810
793
|
}
|
|
@@ -815,8 +798,8 @@ var PrivateBin = class {
|
|
|
815
798
|
let fell = false;
|
|
816
799
|
let got = false;
|
|
817
800
|
for (let hops = 0; ; ) {
|
|
818
|
-
const
|
|
819
|
-
if (!
|
|
801
|
+
const r2 = await readPaste(this.f, this.a.base, id, this.signal);
|
|
802
|
+
if (!r2) {
|
|
820
803
|
if (got) break;
|
|
821
804
|
if (!fell && id !== this.a.paste) {
|
|
822
805
|
fell = true;
|
|
@@ -833,11 +816,11 @@ var PrivateBin = class {
|
|
|
833
816
|
this.moved = true;
|
|
834
817
|
this.onGen(id);
|
|
835
818
|
}
|
|
836
|
-
const comments =
|
|
819
|
+
const comments = r2.j.comments;
|
|
837
820
|
const next = await this.scan(comments, out);
|
|
838
|
-
this.expires =
|
|
821
|
+
this.expires = r2.ttl && Date.now() + r2.ttl;
|
|
839
822
|
const first = (Array.isArray(comments) ? comments[0]?.ct?.length : 0) ?? 0;
|
|
840
|
-
this.big = !next &&
|
|
823
|
+
this.big = !next && r2.size > ROLL_BYTES && r2.size > 2 * first;
|
|
841
824
|
if (!next || ++hops >= MAX_HOPS) break;
|
|
842
825
|
id = next;
|
|
843
826
|
}
|
|
@@ -864,9 +847,9 @@ var PrivateBin = class {
|
|
|
864
847
|
for (let tries = 0; ; tries++) {
|
|
865
848
|
try {
|
|
866
849
|
return await run2();
|
|
867
|
-
} catch (
|
|
868
|
-
if (!(
|
|
869
|
-
await sleep(
|
|
850
|
+
} catch (e3) {
|
|
851
|
+
if (!(e3 instanceof RetryAfter) || tries > 5 || this.signal?.aborted) throw e3;
|
|
852
|
+
await sleep(e3.seconds * 1e3 * (1 + Math.random() * 0.2), this.signal);
|
|
870
853
|
}
|
|
871
854
|
}
|
|
872
855
|
};
|
|
@@ -904,9 +887,9 @@ function setAttribute(html, name, value) {
|
|
|
904
887
|
const old = attr(name).exec(tag);
|
|
905
888
|
let next;
|
|
906
889
|
if (old) {
|
|
907
|
-
const
|
|
908
|
-
const
|
|
909
|
-
next = tag.slice(0, old.index) + `${old[1]}${name}${old[2]}${
|
|
890
|
+
const q = old[3][0] === "'" ? "'" : '"';
|
|
891
|
+
const v2 = q === "'" ? value.replace(/&/g, "&").replace(/'/g, "'") : escaped;
|
|
892
|
+
next = tag.slice(0, old.index) + `${old[1]}${name}${old[2]}${q}${v2}${q}` + tag.slice(old.index + old[0].length);
|
|
910
893
|
} else {
|
|
911
894
|
const at = tag.length - 1;
|
|
912
895
|
next = `${tag.slice(0, at)} ${name}="${escaped}"${tag.slice(at)}`;
|
|
@@ -921,13 +904,13 @@ var dateWords = (ms) => new Date(ms).toLocaleDateString("en-GB", {
|
|
|
921
904
|
timeZone: "UTC"
|
|
922
905
|
});
|
|
923
906
|
function spanWords(ms) {
|
|
924
|
-
const
|
|
925
|
-
if (ms < 36e5) return
|
|
926
|
-
if (ms < 2 * DAY) return
|
|
927
|
-
if (ms < 6.5 * DAY) return
|
|
928
|
-
if (ms < 60 * DAY) return
|
|
929
|
-
if (ms < 730 * DAY) return
|
|
930
|
-
return
|
|
907
|
+
const n2 = (x2, unit) => `${x2} ${unit}${x2 === 1 ? "" : "s"}`;
|
|
908
|
+
if (ms < 36e5) return n2(Math.max(1, Math.round(ms / 6e4)), "minute");
|
|
909
|
+
if (ms < 2 * DAY) return n2(Math.round(ms / 36e5), "hour");
|
|
910
|
+
if (ms < 6.5 * DAY) return n2(Math.round(ms / DAY), "day");
|
|
911
|
+
if (ms < 60 * DAY) return n2(Math.round(ms / (7 * DAY)), "week");
|
|
912
|
+
if (ms < 730 * DAY) return n2(Math.round(ms / (30 * DAY)), "month");
|
|
913
|
+
return n2(Math.round(ms / (365 * DAY)), "year");
|
|
931
914
|
}
|
|
932
915
|
function keepWords(host, ttlMs, now = Date.now()) {
|
|
933
916
|
if (ttlMs === null) return `${host} keeps this shared copy until it is deleted.`;
|
|
@@ -938,17 +921,17 @@ function parseArgs(argv) {
|
|
|
938
921
|
const flags = /* @__PURE__ */ new Map();
|
|
939
922
|
const rest = [];
|
|
940
923
|
for (let i = 0; i < argv.length; i++) {
|
|
941
|
-
const
|
|
942
|
-
if (
|
|
943
|
-
else if (
|
|
944
|
-
const [name, inline] =
|
|
924
|
+
const a2 = argv[i];
|
|
925
|
+
if (a2 === "-h") flags.set("help", true);
|
|
926
|
+
else if (a2.startsWith("--")) {
|
|
927
|
+
const [name, inline] = a2.slice(2).split(/=(.*)/s, 2);
|
|
945
928
|
if (WITH_VALUE.has(name)) {
|
|
946
|
-
const
|
|
947
|
-
if (
|
|
948
|
-
flags.set(name,
|
|
929
|
+
const v2 = inline ?? argv[++i];
|
|
930
|
+
if (v2 === void 0 || v2.startsWith("--")) throw new UsageError(`--${name} needs a value`);
|
|
931
|
+
flags.set(name, v2);
|
|
949
932
|
} else if (["mailbox", "new-doc", "help"].includes(name)) flags.set(name, true);
|
|
950
933
|
else throw new UsageError(`I don't know the option --${name}`);
|
|
951
|
-
} else rest.push(
|
|
934
|
+
} else rest.push(a2);
|
|
952
935
|
}
|
|
953
936
|
return { command: rest[0], file: rest[1], flags };
|
|
954
937
|
}
|
|
@@ -979,37 +962,37 @@ async function patiently(io, run2) {
|
|
|
979
962
|
for (let tries = 0; ; tries++) {
|
|
980
963
|
try {
|
|
981
964
|
return await run2();
|
|
982
|
-
} catch (
|
|
983
|
-
if (!(
|
|
984
|
-
await wait(io,
|
|
965
|
+
} catch (e3) {
|
|
966
|
+
if (!(e3 instanceof RetryAfter) || tries > 8) throw e3;
|
|
967
|
+
await wait(io, e3.seconds * 1e3 + 500);
|
|
985
968
|
}
|
|
986
969
|
}
|
|
987
970
|
}
|
|
988
971
|
function server(value, mailbox) {
|
|
989
972
|
if (typeof value !== "string") throw new UsageError("--server needs the address of the service");
|
|
990
|
-
const
|
|
973
|
+
const a2 = parseShare(
|
|
991
974
|
mailbox ? `${value.replace(/\/+$/, "")}/m/${"x".repeat(22)}#pm1.${"x".repeat(43)}` : `${value}?${"x".repeat(16)}#${"x".repeat(43)}`,
|
|
992
975
|
true
|
|
993
976
|
);
|
|
994
|
-
if (
|
|
977
|
+
if (a2.kind === "off")
|
|
995
978
|
throw new UsageError("--server must be an https: address (http://localhost is allowed for development)");
|
|
996
|
-
const
|
|
997
|
-
return mailbox ? value.replace(/\/+$/, "") :
|
|
979
|
+
const u2 = new URL(value);
|
|
980
|
+
return mailbox ? value.replace(/\/+$/, "") : u2.origin + u2.pathname;
|
|
998
981
|
}
|
|
999
982
|
async function feedbackOps(file, docAttr, io) {
|
|
1000
|
-
const { id, key } = await
|
|
983
|
+
const { id, key } = await U(docAttr);
|
|
1001
984
|
const doc = await K.open({
|
|
1002
985
|
doc: id,
|
|
1003
986
|
key,
|
|
1004
|
-
store: new
|
|
1005
|
-
identity: await
|
|
987
|
+
store: new G(),
|
|
988
|
+
identity: await l(),
|
|
1006
989
|
name: ""
|
|
1007
990
|
});
|
|
1008
991
|
try {
|
|
1009
992
|
await doc.importFile(await io.read(file));
|
|
1010
|
-
} catch (
|
|
993
|
+
} catch (e3) {
|
|
1011
994
|
throw new Error(
|
|
1012
|
-
`I couldn't read ${file} as this page's feedback: ${
|
|
995
|
+
`I couldn't read ${file} as this page's feedback: ${e3.message.replace(/^pipeup: /, "")}`
|
|
1013
996
|
);
|
|
1014
997
|
}
|
|
1015
998
|
return [...doc.ops()];
|
|
@@ -1028,18 +1011,18 @@ Run the same command with --new-doc and I will write a fresh one into the page.
|
|
|
1028
1011
|
);
|
|
1029
1012
|
throw new UsageError("the page needs a document identity");
|
|
1030
1013
|
}
|
|
1031
|
-
docAttr =
|
|
1014
|
+
docAttr = F();
|
|
1032
1015
|
html = setAttribute(html, "data-pipeup-doc", docAttr);
|
|
1033
1016
|
io.out("Gave the page a new document identity (data-pipeup-doc).");
|
|
1034
1017
|
}
|
|
1035
|
-
const { id } = await
|
|
1018
|
+
const { id } = await U(docAttr).catch(() => {
|
|
1036
1019
|
throw new UsageError("data-pipeup-doc in the page isn't a valid document identity");
|
|
1037
1020
|
});
|
|
1038
1021
|
const from = typeof flags.get("from") === "string" ? flags.get("from") : null;
|
|
1039
1022
|
const ops = from ? await feedbackOps(from, docAttr, io) : [];
|
|
1040
1023
|
const room = randomBytes(32);
|
|
1041
1024
|
const key = toB64u(room);
|
|
1042
|
-
const
|
|
1025
|
+
const l2 = ladder(id, room);
|
|
1043
1026
|
const had = readAttribute(html, "data-pipeup-share");
|
|
1044
1027
|
let address;
|
|
1045
1028
|
let stopKey;
|
|
@@ -1052,31 +1035,31 @@ Run the same command with --new-doc and I will write a fresh one into the page.
|
|
|
1052
1035
|
headers: create2 ? { Authorization: `Bearer ${create2}` } : {},
|
|
1053
1036
|
body: JSON.stringify({ v: 1 })
|
|
1054
1037
|
});
|
|
1055
|
-
const
|
|
1038
|
+
const j2 = await res.json().catch(() => ({}));
|
|
1056
1039
|
if (res.status === 401 || res.status === 403)
|
|
1057
1040
|
throw new Error(
|
|
1058
1041
|
`${host} wouldn't make a mailbox. It wants a create token: set PIPEUP_MAILBOX_CREATE_TOKEN and run again.`
|
|
1059
1042
|
);
|
|
1060
|
-
if (res.status !== 201 || typeof
|
|
1043
|
+
if (res.status !== 201 || typeof j2.mailbox !== "string" || typeof j2.stop !== "string")
|
|
1061
1044
|
throw new Error(`${host} wouldn't make a mailbox (it answered ${res.status}).`);
|
|
1062
|
-
const token = typeof
|
|
1063
|
-
address = `${base}/m/${
|
|
1064
|
-
stopKey =
|
|
1065
|
-
backend = new Mailbox({ mailbox: `${base}/m/${
|
|
1066
|
-
const until = typeof
|
|
1067
|
-
const keep =
|
|
1045
|
+
const token = typeof j2.token === "string" ? j2.token : null;
|
|
1046
|
+
address = `${base}/m/${j2.mailbox}#pm1.${key}${token ? `.${token}` : ""}`;
|
|
1047
|
+
stopKey = j2.stop;
|
|
1048
|
+
backend = new Mailbox({ mailbox: `${base}/m/${j2.mailbox}`, ...token ? { token } : {} }, l2, io.fetch);
|
|
1049
|
+
const until = typeof j2.expires === "string" ? Date.parse(j2.expires) : NaN;
|
|
1050
|
+
const keep = j2.keep === "last-write" ? ", and longer each time a comment is added" : "";
|
|
1068
1051
|
io.out(
|
|
1069
1052
|
Number.isNaN(until) ? `${host} keeps this shared copy until it is deleted.` : `${host} keeps this shared copy until ${dateWords(until)}${keep}.`
|
|
1070
1053
|
);
|
|
1071
|
-
const lim =
|
|
1054
|
+
const lim = j2.limits;
|
|
1072
1055
|
if (lim?.mailbox) io.out(`It holds up to ${Math.round(lim.mailbox / 1048576)} MB of comments.`);
|
|
1073
1056
|
} else {
|
|
1074
|
-
const made = await patiently(io, () => createPaste(io.fetch, base,
|
|
1075
|
-
throw new Error(`${host} wouldn't make a shared copy: ${
|
|
1057
|
+
const made = await patiently(io, () => createPaste(io.fetch, base, l2, "never")).catch((e3) => {
|
|
1058
|
+
throw new Error(`${host} wouldn't make a shared copy: ${e3.message}`);
|
|
1076
1059
|
});
|
|
1077
1060
|
address = `${base}?${made.id}#${key}`;
|
|
1078
1061
|
stopKey = made.deleteToken;
|
|
1079
|
-
backend = new PrivateBin({ base, paste: made.id },
|
|
1062
|
+
backend = new PrivateBin({ base, paste: made.id }, l2, io.fetch);
|
|
1080
1063
|
const back = await readPaste(io.fetch, base, made.id).catch(() => null);
|
|
1081
1064
|
if (!back)
|
|
1082
1065
|
throw new Error(
|
|
@@ -1099,34 +1082,34 @@ Run the same command with --new-doc and I will write a fresh one into the page.
|
|
|
1099
1082
|
}
|
|
1100
1083
|
async function addressOf(file, io) {
|
|
1101
1084
|
const html = await io.read(file);
|
|
1102
|
-
const
|
|
1103
|
-
if (
|
|
1085
|
+
const a2 = parseShare(readAttribute(html, "data-pipeup-share"), true);
|
|
1086
|
+
if (a2.kind === "off") throw new UsageError(`${file}: ${a2.reason}`);
|
|
1104
1087
|
const docAttr = readAttribute(html, "data-pipeup-doc");
|
|
1105
|
-
const doc = docAttr ? (await
|
|
1088
|
+
const doc = docAttr ? (await U(docAttr).catch(() => null))?.id : null;
|
|
1106
1089
|
if (!doc) throw new UsageError(`${file} has no valid data-pipeup-doc`);
|
|
1107
|
-
return { html, a, l: ladder(doc,
|
|
1090
|
+
return { html, a: a2, l: ladder(doc, a2.key) };
|
|
1108
1091
|
}
|
|
1109
1092
|
async function stop(file, flags, io) {
|
|
1110
1093
|
const key = flags.get("key");
|
|
1111
1094
|
if (typeof key !== "string") throw new UsageError("stop needs --key with the stop key that create printed");
|
|
1112
|
-
const { a, l } = await addressOf(file, io);
|
|
1113
|
-
if (
|
|
1114
|
-
const res = await io.fetch(
|
|
1095
|
+
const { a: a2, l: l2 } = await addressOf(file, io);
|
|
1096
|
+
if (a2.kind === "mailbox") {
|
|
1097
|
+
const res = await io.fetch(a2.mailbox, { method: "DELETE", headers: { Authorization: `Bearer ${key}` } });
|
|
1115
1098
|
if (res.status === 403) throw new Error("That isn't this mailbox's stop key, so nothing was deleted.");
|
|
1116
1099
|
if (res.status === 404) throw new Error("The shared copy is already gone.");
|
|
1117
1100
|
if (res.status !== 204 && res.status !== 200)
|
|
1118
1101
|
throw new Error(`The server answered ${res.status}, so I can't say it was deleted.`);
|
|
1119
1102
|
io.out("Deleted the shared copy. Everyone keeps the comments they already have.");
|
|
1120
1103
|
} else {
|
|
1121
|
-
const
|
|
1104
|
+
const b2 = new PrivateBin(a2, l2, io.fetch);
|
|
1122
1105
|
const later = [];
|
|
1123
|
-
await
|
|
1106
|
+
await b2.read().catch(() => {
|
|
1124
1107
|
});
|
|
1125
|
-
for (const id of
|
|
1126
|
-
later.push({ id, ttl: (await readPaste(io.fetch,
|
|
1127
|
-
const res = await deletePaste(io.fetch,
|
|
1128
|
-
const
|
|
1129
|
-
if (
|
|
1108
|
+
for (const id of b2.trail.slice(1))
|
|
1109
|
+
later.push({ id, ttl: (await readPaste(io.fetch, a2.base, id).catch(() => null))?.ttl ?? null });
|
|
1110
|
+
const res = await deletePaste(io.fetch, a2.base, a2.paste, key);
|
|
1111
|
+
const j2 = await res.json().catch(() => ({}));
|
|
1112
|
+
if (j2.status !== 0)
|
|
1130
1113
|
throw new Error(
|
|
1131
1114
|
"That isn't this shared copy's stop key (or it is already gone), so nothing was deleted."
|
|
1132
1115
|
);
|
|
@@ -1148,17 +1131,17 @@ async function stop(file, flags, io) {
|
|
|
1148
1131
|
);
|
|
1149
1132
|
}
|
|
1150
1133
|
async function update(file, io) {
|
|
1151
|
-
const { html, a, l } = await addressOf(file, io);
|
|
1152
|
-
if (
|
|
1153
|
-
const
|
|
1154
|
-
await
|
|
1134
|
+
const { html, a: a2, l: l2 } = await addressOf(file, io);
|
|
1135
|
+
if (a2.kind === "mailbox") return io.out("A mailbox has no newer copies, so there is nothing to update.");
|
|
1136
|
+
const b2 = new PrivateBin(a2, l2, io.fetch);
|
|
1137
|
+
await b2.read().catch((e3) => {
|
|
1155
1138
|
throw new Error(
|
|
1156
|
-
`I couldn't read the shared copy: ${
|
|
1139
|
+
`I couldn't read the shared copy: ${e3.message === "gone" ? "it is gone" : e3.message}`
|
|
1157
1140
|
);
|
|
1158
1141
|
});
|
|
1159
|
-
if (
|
|
1142
|
+
if (b2.head === a2.paste) return io.out("The page already points at the newest copy.");
|
|
1160
1143
|
const fragment = new URL(readAttribute(html, "data-pipeup-share")).hash;
|
|
1161
|
-
await io.write(file, setAttribute(html, "data-pipeup-share", `${
|
|
1144
|
+
await io.write(file, setAttribute(html, "data-pipeup-share", `${a2.base}?${b2.head}${fragment}`));
|
|
1162
1145
|
io.out(`Pointed ${file} at the newest copy, so new reviewers don't have to walk through the older ones.`);
|
|
1163
1146
|
}
|
|
1164
1147
|
async function run(argv, io) {
|
|
@@ -1175,19 +1158,19 @@ async function run(argv, io) {
|
|
|
1175
1158
|
else if (command === "stop") await stop(file, flags, io);
|
|
1176
1159
|
else await update(file, io);
|
|
1177
1160
|
return 0;
|
|
1178
|
-
} catch (
|
|
1179
|
-
if (
|
|
1180
|
-
io.err(`${
|
|
1161
|
+
} catch (e3) {
|
|
1162
|
+
if (e3 instanceof UsageError) {
|
|
1163
|
+
io.err(`${e3.message.replace(/\.$/, "")}. Try: share --help`);
|
|
1181
1164
|
return 2;
|
|
1182
1165
|
}
|
|
1183
|
-
io.err(
|
|
1166
|
+
io.err(e3.message);
|
|
1184
1167
|
return 1;
|
|
1185
1168
|
}
|
|
1186
1169
|
}
|
|
1187
1170
|
|
|
1188
1171
|
// share/src/cli.ts
|
|
1189
1172
|
process.exitCode = await run(process.argv.slice(2), {
|
|
1190
|
-
fetch: (i,
|
|
1173
|
+
fetch: (i, n2) => fetch(i, n2),
|
|
1191
1174
|
env: process.env,
|
|
1192
1175
|
read: (p2) => readFile(p2, "utf8"),
|
|
1193
1176
|
write: (p2, t2) => writeFile(p2, t2),
|