fedipod-server 0.12.1 → 0.13.1
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 +7 -0
- package/dist/claims.d.ts +8 -0
- package/dist/claims.js +10 -0
- package/dist/handler.d.ts +7 -0
- package/dist/handler.js +33 -1
- package/dist/handler.jsonld +4 -0
- package/lib/client/c2s.mjs +6 -4
- package/lib/core/as2.mjs +69 -18
- package/lib/core/graphview.mjs +289 -0
- package/lib/core/intake/index.mjs +4 -4
- package/lib/core/intake/verify.mjs +4 -3
- package/lib/core/publisher/restore.mjs +7 -4
- package/lib/gateway/front-core.mjs +14 -6
- package/lib/server/embed.mjs +59 -1
- package/package.json +1 -1
- package/web/app/agent.mjs +7 -6
- package/web/app/boot.mjs +2 -3
- package/web/app/deliver-relay.mjs +18 -1
- package/web/app/dist/boot.js +22 -3
- package/web/app/dist/boot.js.map +2 -2
- package/web/app/dist/sw.js +261 -44
- package/web/app/dist/sw.js.map +4 -4
- package/web/app/keys-browser.mjs +27 -4
- package/web/app/signup.mjs +2 -3
- package/web/app/site/boot.js +22 -3
- package/web/app/site/sw.js +341 -58
package/web/app/site/sw.js
CHANGED
|
@@ -27444,10 +27444,10 @@ var require_fromRdf = __commonJS({
|
|
|
27444
27444
|
var {
|
|
27445
27445
|
// RDF,
|
|
27446
27446
|
RDF_LIST,
|
|
27447
|
-
RDF_FIRST,
|
|
27448
|
-
RDF_REST,
|
|
27449
|
-
RDF_NIL,
|
|
27450
|
-
RDF_TYPE,
|
|
27447
|
+
RDF_FIRST: RDF_FIRST2,
|
|
27448
|
+
RDF_REST: RDF_REST2,
|
|
27449
|
+
RDF_NIL: RDF_NIL2,
|
|
27450
|
+
RDF_TYPE: RDF_TYPE2,
|
|
27451
27451
|
// RDF_PLAIN_LITERAL,
|
|
27452
27452
|
// RDF_XML_LITERAL,
|
|
27453
27453
|
RDF_JSON_LITERAL,
|
|
@@ -27506,14 +27506,14 @@ var require_fromRdf = __commonJS({
|
|
|
27506
27506
|
if (objectIsNode && !(objectNodeId in nodeMap)) {
|
|
27507
27507
|
nodeMap[objectNodeId] = { "@id": objectNodeId };
|
|
27508
27508
|
}
|
|
27509
|
-
if (p ===
|
|
27509
|
+
if (p === RDF_TYPE2 && !useRdfType && objectIsNode) {
|
|
27510
27510
|
_addValue(node, "@type", objectNodeId, { propertyIsArray: true });
|
|
27511
27511
|
continue;
|
|
27512
27512
|
}
|
|
27513
27513
|
const value = _RDFToObject(o, useNativeTypes, rdfDirection, options);
|
|
27514
27514
|
_addValue(node, p, value, { propertyIsArray: true });
|
|
27515
27515
|
if (objectIsNode) {
|
|
27516
|
-
if (objectNodeId ===
|
|
27516
|
+
if (objectNodeId === RDF_NIL2) {
|
|
27517
27517
|
const object = nodeMap[objectNodeId];
|
|
27518
27518
|
if (!("usages" in object)) {
|
|
27519
27519
|
object.usages = [];
|
|
@@ -27536,10 +27536,10 @@ var require_fromRdf = __commonJS({
|
|
|
27536
27536
|
}
|
|
27537
27537
|
for (const name in graphMap) {
|
|
27538
27538
|
const graphObject = graphMap[name];
|
|
27539
|
-
if (!(
|
|
27539
|
+
if (!(RDF_NIL2 in graphObject)) {
|
|
27540
27540
|
continue;
|
|
27541
27541
|
}
|
|
27542
|
-
const nil = graphObject[
|
|
27542
|
+
const nil = graphObject[RDF_NIL2];
|
|
27543
27543
|
if (!nil.usages) {
|
|
27544
27544
|
continue;
|
|
27545
27545
|
}
|
|
@@ -27550,8 +27550,8 @@ var require_fromRdf = __commonJS({
|
|
|
27550
27550
|
const list3 = [];
|
|
27551
27551
|
const listNodes = [];
|
|
27552
27552
|
let nodeKeyCount = Object.keys(node).length;
|
|
27553
|
-
while (property ===
|
|
27554
|
-
list3.push(node[
|
|
27553
|
+
while (property === RDF_REST2 && types.isObject(referencedOnce[node["@id"]]) && types.isArray(node[RDF_FIRST2]) && node[RDF_FIRST2].length === 1 && types.isArray(node[RDF_REST2]) && node[RDF_REST2].length === 1 && (nodeKeyCount === 3 || nodeKeyCount === 4 && types.isArray(node["@type"]) && node["@type"].length === 1 && node["@type"][0] === RDF_LIST)) {
|
|
27554
|
+
list3.push(node[RDF_FIRST2][0]);
|
|
27555
27555
|
listNodes.push(node["@id"]);
|
|
27556
27556
|
usage = referencedOnce[node["@id"]];
|
|
27557
27557
|
node = usage.node;
|
|
@@ -27752,10 +27752,10 @@ var require_toRdf = __commonJS({
|
|
|
27752
27752
|
var {
|
|
27753
27753
|
// RDF,
|
|
27754
27754
|
// RDF_LIST,
|
|
27755
|
-
RDF_FIRST,
|
|
27756
|
-
RDF_REST,
|
|
27757
|
-
RDF_NIL,
|
|
27758
|
-
RDF_TYPE,
|
|
27755
|
+
RDF_FIRST: RDF_FIRST2,
|
|
27756
|
+
RDF_REST: RDF_REST2,
|
|
27757
|
+
RDF_NIL: RDF_NIL2,
|
|
27758
|
+
RDF_TYPE: RDF_TYPE2,
|
|
27759
27759
|
// RDF_PLAIN_LITERAL,
|
|
27760
27760
|
// RDF_XML_LITERAL,
|
|
27761
27761
|
RDF_JSON_LITERAL,
|
|
@@ -27813,7 +27813,7 @@ var require_toRdf = __commonJS({
|
|
|
27813
27813
|
for (let property of properties) {
|
|
27814
27814
|
const items = node[property];
|
|
27815
27815
|
if (property === "@type") {
|
|
27816
|
-
property =
|
|
27816
|
+
property = RDF_TYPE2;
|
|
27817
27817
|
} else if (isKeyword(property)) {
|
|
27818
27818
|
continue;
|
|
27819
27819
|
}
|
|
@@ -27893,9 +27893,9 @@ var require_toRdf = __commonJS({
|
|
|
27893
27893
|
}
|
|
27894
27894
|
}
|
|
27895
27895
|
function _listToRDF(list3, issuer, dataset2, graphTerm, rdfDirection, options) {
|
|
27896
|
-
const first = { termType: "NamedNode", value:
|
|
27897
|
-
const rest = { termType: "NamedNode", value:
|
|
27898
|
-
const nil = { termType: "NamedNode", value:
|
|
27896
|
+
const first = { termType: "NamedNode", value: RDF_FIRST2 };
|
|
27897
|
+
const rest = { termType: "NamedNode", value: RDF_REST2 };
|
|
27898
|
+
const nil = { termType: "NamedNode", value: RDF_NIL2 };
|
|
27899
27899
|
const last = list3.pop();
|
|
27900
27900
|
const result = last ? {
|
|
27901
27901
|
termType: "BlankNode",
|
|
@@ -55467,6 +55467,187 @@ var CONTEXTS = {
|
|
|
55467
55467
|
"https://purl.archive.org/miscellany": miscellany_default
|
|
55468
55468
|
};
|
|
55469
55469
|
|
|
55470
|
+
// lib/core/graphview.mjs
|
|
55471
|
+
init_wire();
|
|
55472
|
+
var RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
|
|
55473
|
+
var RDF_FIRST = "http://www.w3.org/1999/02/22-rdf-syntax-ns#first";
|
|
55474
|
+
var RDF_REST = "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest";
|
|
55475
|
+
var RDF_NIL = "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil";
|
|
55476
|
+
var MAX_DEPTH = 12;
|
|
55477
|
+
var ALWAYS_LIST = /* @__PURE__ */ new Set([
|
|
55478
|
+
"items",
|
|
55479
|
+
"orderedItems",
|
|
55480
|
+
"to",
|
|
55481
|
+
"cc",
|
|
55482
|
+
"bto",
|
|
55483
|
+
"bcc",
|
|
55484
|
+
"tag",
|
|
55485
|
+
"attachment",
|
|
55486
|
+
"anyOf",
|
|
55487
|
+
"oneOf",
|
|
55488
|
+
"audience",
|
|
55489
|
+
"attributedTo",
|
|
55490
|
+
"alsoKnownAs"
|
|
55491
|
+
]);
|
|
55492
|
+
function expandIri(value, prefixes) {
|
|
55493
|
+
if (typeof value !== "string") return null;
|
|
55494
|
+
if (/^https?:\/\//.test(value)) return value;
|
|
55495
|
+
const colon = value.indexOf(":");
|
|
55496
|
+
if (colon < 1) return null;
|
|
55497
|
+
const base = prefixes[value.slice(0, colon)];
|
|
55498
|
+
return base ? base + value.slice(colon + 1) : null;
|
|
55499
|
+
}
|
|
55500
|
+
function buildTerms() {
|
|
55501
|
+
const terms = /* @__PURE__ */ new Map();
|
|
55502
|
+
const order = [AS_CTX, ...Object.keys(CONTEXTS).filter((u) => u !== AS_CTX)];
|
|
55503
|
+
for (const url of order) {
|
|
55504
|
+
const ctx = CONTEXTS[url]?.["@context"];
|
|
55505
|
+
if (!ctx || typeof ctx !== "object") continue;
|
|
55506
|
+
const prefixes = {};
|
|
55507
|
+
for (const [k, v] of Object.entries(ctx)) {
|
|
55508
|
+
if (typeof v === "string" && /^https?:\/\//.test(v)) prefixes[k] = v;
|
|
55509
|
+
}
|
|
55510
|
+
for (const [name, def] of Object.entries(ctx)) {
|
|
55511
|
+
if (name.startsWith("@") || terms.has(name)) continue;
|
|
55512
|
+
const raw = typeof def === "string" ? def : def?.["@id"];
|
|
55513
|
+
const iri = expandIri(raw, prefixes);
|
|
55514
|
+
if (!iri) continue;
|
|
55515
|
+
terms.set(name, { iri, list: def?.["@container"] === "@list", container: def?.["@container"] ?? null });
|
|
55516
|
+
}
|
|
55517
|
+
}
|
|
55518
|
+
return terms;
|
|
55519
|
+
}
|
|
55520
|
+
var TERMS = buildTerms();
|
|
55521
|
+
var BY_IRI = (() => {
|
|
55522
|
+
const m = /* @__PURE__ */ new Map();
|
|
55523
|
+
for (const [name, { iri }] of TERMS) {
|
|
55524
|
+
const held = m.get(iri);
|
|
55525
|
+
if (!held || name.length < held.length) m.set(iri, name);
|
|
55526
|
+
}
|
|
55527
|
+
return m;
|
|
55528
|
+
})();
|
|
55529
|
+
var NAMES_BY_IRI = (() => {
|
|
55530
|
+
const m = /* @__PURE__ */ new Map();
|
|
55531
|
+
for (const [name, { iri, container }] of TERMS) {
|
|
55532
|
+
if (container === "@language" || container === "@index") continue;
|
|
55533
|
+
const held = m.get(iri);
|
|
55534
|
+
if (held) held.push(name);
|
|
55535
|
+
else m.set(iri, [name]);
|
|
55536
|
+
}
|
|
55537
|
+
return m;
|
|
55538
|
+
})();
|
|
55539
|
+
function indexQuads(quads) {
|
|
55540
|
+
const bySubject = /* @__PURE__ */ new Map();
|
|
55541
|
+
const objects = /* @__PURE__ */ new Set();
|
|
55542
|
+
const blanks = /* @__PURE__ */ new Set();
|
|
55543
|
+
for (const q of quads) {
|
|
55544
|
+
if (q.subject.termType === "BlankNode") blanks.add(q.subject.value);
|
|
55545
|
+
if (q.object.termType === "BlankNode") blanks.add(q.object.value);
|
|
55546
|
+
const s = q.subject.value;
|
|
55547
|
+
let preds = bySubject.get(s);
|
|
55548
|
+
if (!preds) bySubject.set(s, preds = /* @__PURE__ */ new Map());
|
|
55549
|
+
let vals = preds.get(q.predicate.value);
|
|
55550
|
+
if (!vals) preds.set(q.predicate.value, vals = []);
|
|
55551
|
+
vals.push(q.object);
|
|
55552
|
+
if (q.object.termType === "NamedNode" || q.object.termType === "BlankNode") {
|
|
55553
|
+
objects.add(q.object.value);
|
|
55554
|
+
}
|
|
55555
|
+
}
|
|
55556
|
+
return { bySubject, objects, blanks };
|
|
55557
|
+
}
|
|
55558
|
+
function findRoot({ bySubject, objects, blanks }) {
|
|
55559
|
+
const all = [...bySubject.keys()];
|
|
55560
|
+
const named = all.filter((s) => !blanks.has(s));
|
|
55561
|
+
const free = all.filter((s) => !objects.has(s));
|
|
55562
|
+
const freeNamed = free.find((s) => !blanks.has(s));
|
|
55563
|
+
if (freeNamed) return freeNamed;
|
|
55564
|
+
if (free.length) return free[0];
|
|
55565
|
+
for (const s of named) if (bySubject.get(s).has(RDF_TYPE)) return s;
|
|
55566
|
+
for (const s of all) if (bySubject.get(s).has(RDF_TYPE)) return s;
|
|
55567
|
+
return named[0] ?? all[0] ?? null;
|
|
55568
|
+
}
|
|
55569
|
+
function readList(head, ctx, depth, path) {
|
|
55570
|
+
const out = [];
|
|
55571
|
+
const walked = /* @__PURE__ */ new Set();
|
|
55572
|
+
let node = head;
|
|
55573
|
+
while (node && node !== RDF_NIL && !walked.has(node)) {
|
|
55574
|
+
walked.add(node);
|
|
55575
|
+
const preds = ctx.bySubject.get(node);
|
|
55576
|
+
if (!preds) break;
|
|
55577
|
+
const first = preds.get(RDF_FIRST)?.[0];
|
|
55578
|
+
if (first) {
|
|
55579
|
+
const value = toValue(first, ctx, depth, path);
|
|
55580
|
+
if (value !== null) out.push(value);
|
|
55581
|
+
}
|
|
55582
|
+
node = preds.get(RDF_REST)?.[0]?.value ?? null;
|
|
55583
|
+
}
|
|
55584
|
+
return out;
|
|
55585
|
+
}
|
|
55586
|
+
function isListHead(term3, ctx) {
|
|
55587
|
+
if (term3.termType !== "BlankNode" && term3.termType !== "NamedNode") return false;
|
|
55588
|
+
return !!ctx.bySubject.get(term3.value)?.has(RDF_FIRST);
|
|
55589
|
+
}
|
|
55590
|
+
function toValue(term3, ctx, depth, path) {
|
|
55591
|
+
if (term3.termType === "Literal") return term3.value;
|
|
55592
|
+
if (isListHead(term3, ctx)) return readList(term3.value, ctx, depth, path);
|
|
55593
|
+
const preds = ctx.bySubject.get(term3.value);
|
|
55594
|
+
if (preds && preds.size) return makeView(term3.value, ctx, depth + 1, path);
|
|
55595
|
+
return term3.termType === "BlankNode" ? null : term3.value;
|
|
55596
|
+
}
|
|
55597
|
+
function readProperty(subject, name, ctx, depth = 0, path = /* @__PURE__ */ new Set()) {
|
|
55598
|
+
const preds = ctx.bySubject.get(subject);
|
|
55599
|
+
if (!preds) return void 0;
|
|
55600
|
+
if (name === "id") return ctx.blanks.has(subject) ? void 0 : subject;
|
|
55601
|
+
if (name === "type") {
|
|
55602
|
+
const types = (preds.get(RDF_TYPE) ?? []).map((t) => BY_IRI.get(t.value) ?? t.value);
|
|
55603
|
+
if (!types.length) return void 0;
|
|
55604
|
+
return types.length === 1 ? types[0] : types;
|
|
55605
|
+
}
|
|
55606
|
+
const term3 = TERMS.get(name);
|
|
55607
|
+
if (!term3) return void 0;
|
|
55608
|
+
const values = preds.get(term3.iri);
|
|
55609
|
+
if (!values || !values.length) return void 0;
|
|
55610
|
+
const listed = values.filter((v) => isListHead(v, ctx));
|
|
55611
|
+
if (term3.list) {
|
|
55612
|
+
if (!listed.length) return void 0;
|
|
55613
|
+
return readList(listed[0].value, ctx, depth, path);
|
|
55614
|
+
}
|
|
55615
|
+
if (listed.length === values.length && listed.length === 1) return readList(listed[0].value, ctx, depth, path);
|
|
55616
|
+
const out = values.map((v) => toValue(v, ctx, depth, path)).filter((v) => v !== null);
|
|
55617
|
+
if (ALWAYS_LIST.has(name)) return out.flat();
|
|
55618
|
+
return out.length === 1 ? out[0] : out;
|
|
55619
|
+
}
|
|
55620
|
+
function makeView(subject, ctx, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
55621
|
+
if (depth > MAX_DEPTH || seen.has(subject)) {
|
|
55622
|
+
return ctx.blanks.has(subject) ? null : subject;
|
|
55623
|
+
}
|
|
55624
|
+
const preds = ctx.bySubject.get(subject);
|
|
55625
|
+
if (!preds) return ctx.blanks.has(subject) ? null : subject;
|
|
55626
|
+
const path = new Set(seen).add(subject);
|
|
55627
|
+
const out = {};
|
|
55628
|
+
const id = readProperty(subject, "id", ctx, depth, path);
|
|
55629
|
+
if (id !== void 0) out.id = id;
|
|
55630
|
+
const type = readProperty(subject, "type", ctx, depth, path);
|
|
55631
|
+
if (type !== void 0) out.type = type;
|
|
55632
|
+
for (const predicate of preds.keys()) {
|
|
55633
|
+
if (predicate === RDF_TYPE || predicate === RDF_FIRST || predicate === RDF_REST) continue;
|
|
55634
|
+
for (const name of NAMES_BY_IRI.get(predicate) ?? []) {
|
|
55635
|
+
if (name === "id" || name === "type") continue;
|
|
55636
|
+
const value = readProperty(subject, name, ctx, depth, path);
|
|
55637
|
+
if (value !== void 0) out[name] = value;
|
|
55638
|
+
}
|
|
55639
|
+
}
|
|
55640
|
+
return out;
|
|
55641
|
+
}
|
|
55642
|
+
function graphView(quads, { root = null } = {}) {
|
|
55643
|
+
if (!quads || !quads.length) return null;
|
|
55644
|
+
const ctx = indexQuads(quads);
|
|
55645
|
+
const subject = root && ctx.bySubject.has(root) ? root : findRoot(ctx);
|
|
55646
|
+
if (!subject) return null;
|
|
55647
|
+
const view = makeView(subject, ctx);
|
|
55648
|
+
return view && typeof view === "object" ? view : null;
|
|
55649
|
+
}
|
|
55650
|
+
|
|
55470
55651
|
// lib/core/as2.mjs
|
|
55471
55652
|
var AS2Error = class extends Error {
|
|
55472
55653
|
constructor(reason, { context = null } = {}) {
|
|
@@ -55509,17 +55690,35 @@ async function parseAS2(raw) {
|
|
|
55509
55690
|
}
|
|
55510
55691
|
return { doc, graph: graph2 };
|
|
55511
55692
|
}
|
|
55693
|
+
function groundContext(input) {
|
|
55694
|
+
const ctx = input["@context"];
|
|
55695
|
+
const held = (c) => typeof c === "string" ? Object.hasOwn(CONTEXTS, c) : c && typeof c === "object";
|
|
55696
|
+
let kept;
|
|
55697
|
+
if (ctx === void 0 || ctx === null) kept = [];
|
|
55698
|
+
else if (Array.isArray(ctx)) kept = ctx.filter(held);
|
|
55699
|
+
else kept = held(ctx) ? [ctx] : [];
|
|
55700
|
+
if (!kept.some((c) => c === AS_CTX)) kept.unshift(AS_CTX);
|
|
55701
|
+
return { ...input, "@context": kept.length === 1 ? kept[0] : kept };
|
|
55702
|
+
}
|
|
55512
55703
|
async function readLenient(raw) {
|
|
55704
|
+
let input = null;
|
|
55705
|
+
try {
|
|
55706
|
+
input = typeof raw === "string" ? JSON.parse(raw) : raw ?? null;
|
|
55707
|
+
} catch {
|
|
55708
|
+
}
|
|
55709
|
+
const own = input && typeof input === "object" ? input.id ?? input["@id"] : null;
|
|
55710
|
+
const root = typeof own === "string" && /^https?:\/\//u.test(own) ? own : null;
|
|
55513
55711
|
try {
|
|
55514
55712
|
const { doc, graph: graph2 } = await parseAS2(raw);
|
|
55515
|
-
return { doc, graph: graph2, degraded: null };
|
|
55713
|
+
return { doc, graph: graph2, view: graphView(graph2, { root }), degraded: null };
|
|
55516
55714
|
} catch (e) {
|
|
55517
|
-
|
|
55715
|
+
if (!input || typeof input !== "object") return { doc: null, graph: null, view: null, degraded: e.message };
|
|
55518
55716
|
try {
|
|
55519
|
-
|
|
55520
|
-
|
|
55717
|
+
const { graph: graph2 } = await parseAS2(groundContext(input));
|
|
55718
|
+
return { doc: input, graph: graph2, view: graphView(graph2, { root }), degraded: e.message };
|
|
55719
|
+
} catch (inner) {
|
|
55720
|
+
return { doc: input, graph: null, view: null, degraded: `${e.message}; grounded read also failed: ${inner.message}` };
|
|
55521
55721
|
}
|
|
55522
|
-
return { doc: doc && typeof doc === "object" ? doc : null, graph: null, degraded: e.message };
|
|
55523
55722
|
}
|
|
55524
55723
|
}
|
|
55525
55724
|
|
|
@@ -55542,13 +55741,14 @@ async function reconcileFollowers(publisher, contacts) {
|
|
|
55542
55741
|
try {
|
|
55543
55742
|
const res = await publisher.deliverer.signedFetch(actor, { headers: { accept: ACCEPT_AP } });
|
|
55544
55743
|
if (!res.ok) continue;
|
|
55545
|
-
const { doc, degraded } = await readLenient(await res.json());
|
|
55546
|
-
|
|
55547
|
-
if (
|
|
55744
|
+
const { doc, view, degraded } = await readLenient(await res.json());
|
|
55745
|
+
const actorDoc2 = view ?? doc;
|
|
55746
|
+
if (degraded) publisher.log?.(`actor ${actor} grounded to read: ${degraded}`);
|
|
55747
|
+
if (!actorDoc2?.inbox) continue;
|
|
55548
55748
|
contacts.followers.push({
|
|
55549
55749
|
actor,
|
|
55550
|
-
inbox:
|
|
55551
|
-
sharedInbox:
|
|
55750
|
+
inbox: actorDoc2.inbox,
|
|
55751
|
+
sharedInbox: actorDoc2.endpoints?.sharedInbox || null,
|
|
55552
55752
|
recovered: true,
|
|
55553
55753
|
// Said explicitly, because onUndo reads it: the pod publishes WHO
|
|
55554
55754
|
// follows, never the id of the Follow that did it, so a recovered
|
|
@@ -56822,8 +57022,8 @@ async function fetchAP(intake, url) {
|
|
|
56822
57022
|
let doc = null;
|
|
56823
57023
|
try {
|
|
56824
57024
|
const read2 = await readLenient(await readCapped3(res));
|
|
56825
|
-
if (read2.degraded) intake.log(`fetch ${url}
|
|
56826
|
-
doc = read2.doc;
|
|
57025
|
+
if (read2.degraded) intake.log(`fetch ${url} grounded to read: ${read2.degraded}`);
|
|
57026
|
+
doc = read2.view ?? read2.doc;
|
|
56827
57027
|
} catch (e) {
|
|
56828
57028
|
intake.log(`fetch ${url}: unreadable \u2014 ${e.message}`);
|
|
56829
57029
|
return null;
|
|
@@ -56886,7 +57086,8 @@ async function isGone(intake, url) {
|
|
|
56886
57086
|
if (res.status < 400) {
|
|
56887
57087
|
try {
|
|
56888
57088
|
const { readCapped: readCapped3 } = await Promise.resolve().then(() => (init_safefetch(), safefetch_exports));
|
|
56889
|
-
|
|
57089
|
+
const read2 = await readLenient(await readCapped3(res));
|
|
57090
|
+
return (read2.view ?? read2.doc)?.type === "Tombstone";
|
|
56890
57091
|
} catch {
|
|
56891
57092
|
return false;
|
|
56892
57093
|
}
|
|
@@ -57238,9 +57439,9 @@ async function onUpdate(intake, activity, actor) {
|
|
|
57238
57439
|
const note = await intake.fetchAP(objectId);
|
|
57239
57440
|
if (!note) throw new Error(`cannot refetch ${objectId} \u2014 will retry`);
|
|
57240
57441
|
if (note.id !== objectId || !isContentType(note.type)) return `object not verifiable content (${objectId}, ${note.type})`;
|
|
57241
|
-
const { attachmentsOf:
|
|
57442
|
+
const { attachmentsOf: attachmentsOf3, titledContent: titledContent2 } = await Promise.resolve().then(() => (init_wire(), wire_exports));
|
|
57242
57443
|
const content = titledContent2(note);
|
|
57243
|
-
const attachments =
|
|
57444
|
+
const attachments = attachmentsOf3(note);
|
|
57244
57445
|
const freshPoll = pollOf(note);
|
|
57245
57446
|
const freshEmojis = emojisOf(note);
|
|
57246
57447
|
intake.store.updateStatus(objectId, {
|
|
@@ -57374,8 +57575,8 @@ async function ingestNote(intake, objectId, actor, { via } = {}) {
|
|
|
57374
57575
|
const note = await intake.fetchAP(objectId);
|
|
57375
57576
|
if (!note) return `object fetch failed (${objectId})`;
|
|
57376
57577
|
if (note.id !== objectId || !isContentType(note.type)) return `object not verifiable content (${objectId}, ${note.type})`;
|
|
57377
|
-
const { attachmentsOf:
|
|
57378
|
-
const attachments =
|
|
57578
|
+
const { attachmentsOf: attachmentsOf3, titledContent: titledContent2 } = await Promise.resolve().then(() => (init_wire(), wire_exports));
|
|
57579
|
+
const attachments = attachmentsOf3(note);
|
|
57379
57580
|
const content = titledContent2(note);
|
|
57380
57581
|
const author = authorOf(note, actor);
|
|
57381
57582
|
if (!author) return `object names an author its origin does not vouch for (${objectId})`;
|
|
@@ -62658,8 +62859,8 @@ var Intake = class {
|
|
|
62658
62859
|
} else {
|
|
62659
62860
|
const got = await readItem(this.remote, item.url, { maxBytes: MAX_ITEM_BYTES, readCapped });
|
|
62660
62861
|
const read2 = got.raw === null ? null : await readLenient(got.raw);
|
|
62661
|
-
if (read2?.degraded) this.log(`inbox item ${item.url}
|
|
62662
|
-
const activity = read2?.doc ?? null;
|
|
62862
|
+
if (read2?.degraded) this.log(`inbox item ${item.url} grounded to read: ${read2.degraded}`);
|
|
62863
|
+
const activity = read2?.view ?? read2?.doc ?? null;
|
|
62663
62864
|
if (keepConcerning) {
|
|
62664
62865
|
const rejection = activity ? await this.handle(activity) : "unparsable JSON";
|
|
62665
62866
|
if (rejection) out.dropped++;
|
|
@@ -62799,8 +63000,8 @@ var Intake = class {
|
|
|
62799
63000
|
const got = await readItem(this.remote, url, { maxBytes: MAX_ITEM_BYTES, readCapped });
|
|
62800
63001
|
const raw = got.raw;
|
|
62801
63002
|
const read2 = raw ? await readLenient(raw) : null;
|
|
62802
|
-
if (read2?.degraded) this.log(`inbox item ${url}
|
|
62803
|
-
activity = read2?.doc ?? null;
|
|
63003
|
+
if (read2?.degraded) this.log(`inbox item ${url} grounded to read: ${read2.degraded}`);
|
|
63004
|
+
activity = read2?.view ?? read2?.doc ?? null;
|
|
62804
63005
|
if (read2?.graph) {
|
|
62805
63006
|
const failure = await checkShapes(read2.graph);
|
|
62806
63007
|
if (failure) {
|
|
@@ -64295,6 +64496,16 @@ var postUrl = (uri) => {
|
|
|
64295
64496
|
const m = String(uri).match(/^at:\/\/([^/]+)\/[^/]+\/(.+)$/);
|
|
64296
64497
|
return m ? `https://bsky.app/profile/${m[1]}/post/${m[2]}` : null;
|
|
64297
64498
|
};
|
|
64499
|
+
function attachmentsOf2(post) {
|
|
64500
|
+
const e = post?.embed || {};
|
|
64501
|
+
const images = [...e.images || [], ...e.media?.images || []].filter((i) => i?.fullsize).map((i) => ({ url: i.fullsize, mediaType: "image/jpeg", description: i.alt || "" }));
|
|
64502
|
+
const card = e.external?.thumb || e.media?.external?.thumb;
|
|
64503
|
+
if (card) {
|
|
64504
|
+
const ext = e.external || e.media.external;
|
|
64505
|
+
images.push({ url: card, mediaType: "image/jpeg", description: ext.title || ext.uri || "" });
|
|
64506
|
+
}
|
|
64507
|
+
return images;
|
|
64508
|
+
}
|
|
64298
64509
|
var BskyFeed = class {
|
|
64299
64510
|
constructor({ store, atproto, log: log2 = console.log, onNotification = null }) {
|
|
64300
64511
|
Object.assign(this, { store, atproto, log: log2, onNotification });
|
|
@@ -64350,14 +64561,27 @@ var BskyFeed = class {
|
|
|
64350
64561
|
return url;
|
|
64351
64562
|
}
|
|
64352
64563
|
// One Bluesky post into the statuses index. Returns its noteId, new or not.
|
|
64564
|
+
// A post seen before is completed, not duplicated: a reply first met as a
|
|
64565
|
+
// bare notification record learns its parent and its pictures from the
|
|
64566
|
+
// full view when that arrives.
|
|
64353
64567
|
_mirrorPost(post, { via = null } = {}) {
|
|
64354
64568
|
const noteId = post.uri;
|
|
64355
|
-
const
|
|
64356
|
-
|
|
64569
|
+
const inReplyTo = post.record?.reply?.parent?.uri || null;
|
|
64570
|
+
const attachments = attachmentsOf2(post);
|
|
64571
|
+
const existing = this.store.getStatuses().find((s) => s.noteId === noteId);
|
|
64572
|
+
if (existing) {
|
|
64573
|
+
const patch = {
|
|
64574
|
+
...inReplyTo && !existing.inReplyTo ? { inReplyTo } : {},
|
|
64575
|
+
...attachments.length && !existing.attachments?.length ? { attachments } : {}
|
|
64576
|
+
};
|
|
64577
|
+
if (Object.keys(patch).length && typeof this.store.updateStatus === "function") {
|
|
64578
|
+
this.store.updateStatus(noteId, patch);
|
|
64579
|
+
}
|
|
64580
|
+
return { noteId, added: false };
|
|
64581
|
+
}
|
|
64357
64582
|
const actor = this._rememberAuthor(post.author);
|
|
64358
64583
|
if (this.store.isBlocked(actor)) return { noteId, added: false };
|
|
64359
64584
|
const text = post.record?.text || "";
|
|
64360
|
-
const images = post.embed?.images || [];
|
|
64361
64585
|
this.store.addStatus({
|
|
64362
64586
|
noteId,
|
|
64363
64587
|
actor,
|
|
@@ -64367,12 +64591,46 @@ var BskyFeed = class {
|
|
|
64367
64591
|
...post.cid ? { cid: post.cid } : {},
|
|
64368
64592
|
link: postUrl(noteId),
|
|
64369
64593
|
...via ? { via } : {},
|
|
64370
|
-
...
|
|
64371
|
-
|
|
64372
|
-
} : {}
|
|
64594
|
+
...inReplyTo ? { inReplyTo } : {},
|
|
64595
|
+
...attachments.length ? { attachments } : {}
|
|
64373
64596
|
});
|
|
64374
64597
|
return { noteId, added: true };
|
|
64375
64598
|
}
|
|
64599
|
+
// The conversation under one post, asked for when a post is opened: the
|
|
64600
|
+
// ancestors it hangs from and the replies beneath it, each mirrored with
|
|
64601
|
+
// the reply link the thread view is built from. View cache only, like the
|
|
64602
|
+
// timeline. Returns how many posts were new; a thread Bluesky will not
|
|
64603
|
+
// give (gone, blocked, rate-limited) adds none and is logged, not thrown.
|
|
64604
|
+
async mirrorThread(uri, { depth = 6 } = {}) {
|
|
64605
|
+
if (!this.atproto?.connected()) return 0;
|
|
64606
|
+
if (this.quietUntil && Date.now() < this.quietUntil) return 0;
|
|
64607
|
+
let out;
|
|
64608
|
+
try {
|
|
64609
|
+
out = await this.atproto.xrpc("app.bsky.feed.getPostThread", { params: { uri, depth, parentHeight: 40 } });
|
|
64610
|
+
} catch (e) {
|
|
64611
|
+
if (e.status === 429 || e.status >= 500) this._backOff(e.status, null);
|
|
64612
|
+
else this.log(`bskyfeed: thread ${uri}: ${e.message}`);
|
|
64613
|
+
return 0;
|
|
64614
|
+
}
|
|
64615
|
+
const self2 = this.atproto.read()?.did;
|
|
64616
|
+
const ownMirrors = new Set(this.store.getStatuses().map((s) => s.atproto?.uri).filter(Boolean));
|
|
64617
|
+
let added = 0;
|
|
64618
|
+
const take2 = (node) => {
|
|
64619
|
+
const post = node?.post;
|
|
64620
|
+
if (!post?.uri) return;
|
|
64621
|
+
if (post.author?.did === self2 && ownMirrors.has(post.uri)) return;
|
|
64622
|
+
if (this._mirrorPost(post).added) added++;
|
|
64623
|
+
};
|
|
64624
|
+
for (let up = out?.thread?.parent; up; up = up.parent) take2(up);
|
|
64625
|
+
const walk = (node, left) => {
|
|
64626
|
+
take2(node);
|
|
64627
|
+
if (left <= 0) return;
|
|
64628
|
+
for (const r of node?.replies || []) walk(r, left - 1);
|
|
64629
|
+
};
|
|
64630
|
+
walk(out?.thread, depth);
|
|
64631
|
+
if (added) this.log(`bskyfeed: +${added} from the thread under ${uri}`);
|
|
64632
|
+
return added;
|
|
64633
|
+
}
|
|
64376
64634
|
async sweep() {
|
|
64377
64635
|
if (!this.atproto?.connected()) return;
|
|
64378
64636
|
if (this.quietUntil && Date.now() < this.quietUntil) return;
|
|
@@ -64411,8 +64669,13 @@ var BskyFeed = class {
|
|
|
64411
64669
|
this.store.addNotification({ type: "follow", actor, bsky: true });
|
|
64412
64670
|
await this.onNotification?.(n, { actor });
|
|
64413
64671
|
} else if (n.reason === "mention" || n.reason === "reply") {
|
|
64414
|
-
|
|
64415
|
-
|
|
64672
|
+
let view = null;
|
|
64673
|
+
try {
|
|
64674
|
+
const got = await this.atproto.xrpc("app.bsky.feed.getPosts", { params: { uris: n.uri } });
|
|
64675
|
+
view = (got?.posts || [])[0] || null;
|
|
64676
|
+
} catch {
|
|
64677
|
+
}
|
|
64678
|
+
this._mirrorPost(view || { uri: n.uri, cid: n.cid, author: n.author, record: n.record, indexedAt: n.indexedAt });
|
|
64416
64679
|
this.store.addNotification({ type: "mention", actor, noteId: n.uri, bsky: true });
|
|
64417
64680
|
await this.onNotification?.(n, { actor });
|
|
64418
64681
|
}
|
|
@@ -65744,6 +66007,10 @@ async function handle6(api, ctx) {
|
|
|
65744
66007
|
const mContext = /^\/api\/v1\/statuses\/([a-f0-9]+)\/context$/.exec(pathname);
|
|
65745
66008
|
if (mContext) {
|
|
65746
66009
|
const noteUrl = api.lookup(mContext[1]).s?.noteId;
|
|
66010
|
+
const opened = noteUrl && api.store.getStatuses().find((x) => x.noteId === noteUrl);
|
|
66011
|
+
if (opened?.kind === "bsky" && typeof api.agent?.bskyfeed?.mirrorThread === "function") {
|
|
66012
|
+
await api.agent.bskyfeed.mirrorThread(noteUrl);
|
|
66013
|
+
}
|
|
65747
66014
|
const all = api.store.getStatuses();
|
|
65748
66015
|
const byId = new Map(all.map((s) => [s.noteId, s]));
|
|
65749
66016
|
const ancestors = [];
|
|
@@ -66179,8 +66446,8 @@ var TagFeed = class {
|
|
|
66179
66446
|
await this.intake.fetchAP(author).catch(() => {
|
|
66180
66447
|
});
|
|
66181
66448
|
}
|
|
66182
|
-
const { attachmentsOf:
|
|
66183
|
-
const attachments =
|
|
66449
|
+
const { attachmentsOf: attachmentsOf3, titledContent: titledContent2 } = await Promise.resolve().then(() => (init_wire(), wire_exports));
|
|
66450
|
+
const attachments = attachmentsOf3(note);
|
|
66184
66451
|
this.store.addStatus({
|
|
66185
66452
|
noteId,
|
|
66186
66453
|
actor: author,
|
|
@@ -66829,21 +67096,28 @@ async function importSigningKey(keysRecord) {
|
|
|
66829
67096
|
"pkcs8",
|
|
66830
67097
|
pemToDer(keysRecord.rsa.privatePem),
|
|
66831
67098
|
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
66832
|
-
|
|
67099
|
+
false,
|
|
66833
67100
|
["sign"]
|
|
66834
67101
|
);
|
|
66835
67102
|
return { rsaPrivate, rsaPublicPem: keysRecord.rsa.publicPem, edPrivate: null, edPublicMultibase: null };
|
|
66836
67103
|
}
|
|
67104
|
+
var fromCache = (c) => ({ rsaPrivate: c.rsaPrivate, rsaPublicPem: c.rsaPublicPem, edPrivate: null, edPublicMultibase: null });
|
|
67105
|
+
var isOpenedKey = (c) => c?.rsaPrivate?.type === "private" && typeof c.rsaPublicPem === "string";
|
|
67106
|
+
async function cacheOpenedKeys(actorUrl, keysRecord) {
|
|
67107
|
+
const keys = await importSigningKey(keysRecord);
|
|
67108
|
+
await kvPut(keyCacheKey(actorUrl), { rsaPrivate: keys.rsaPrivate, rsaPublicPem: keys.rsaPublicPem }).catch(() => {
|
|
67109
|
+
});
|
|
67110
|
+
return keys;
|
|
67111
|
+
}
|
|
66837
67112
|
var keyCacheKey = (actorUrl) => `signing-keys:${actorUrl}`;
|
|
66838
67113
|
async function loadKeysFromPod(remote, urls) {
|
|
66839
67114
|
const cached = await kvGet(keyCacheKey(urls.actor)).catch(() => null);
|
|
66840
|
-
if (cached
|
|
67115
|
+
if (isOpenedKey(cached)) return fromCache(cached);
|
|
67116
|
+
if (cached?.rsa?.privatePem) return cacheOpenedKeys(urls.actor, cached);
|
|
66841
67117
|
const doc = await readWrappedKeys(remote, urls);
|
|
66842
67118
|
if (isKeyEnvelope(doc)) throw new KeyPasswordNeeded();
|
|
66843
67119
|
if (!doc || !doc.rsa) throw new Error("no signing key on the pod \u2014 sign up did not finish");
|
|
66844
|
-
|
|
66845
|
-
});
|
|
66846
|
-
return importSigningKey(doc);
|
|
67120
|
+
return cacheOpenedKeys(urls.actor, doc);
|
|
66847
67121
|
}
|
|
66848
67122
|
|
|
66849
67123
|
// lib/core/deliver.mjs
|
|
@@ -67164,6 +67438,14 @@ var Deliverer = class {
|
|
|
67164
67438
|
|
|
67165
67439
|
// web/app/deliver-relay.mjs
|
|
67166
67440
|
init_fedify_sig();
|
|
67441
|
+
function doorKeyOf(doorInboxUrl) {
|
|
67442
|
+
try {
|
|
67443
|
+
const seg2 = new URL(doorInboxUrl).pathname.split("/");
|
|
67444
|
+
return seg2[1] === "u" && seg2[2] ? decodeURIComponent(seg2[2]) : null;
|
|
67445
|
+
} catch {
|
|
67446
|
+
return null;
|
|
67447
|
+
}
|
|
67448
|
+
}
|
|
67167
67449
|
var RelayDeliverer = class extends Deliverer {
|
|
67168
67450
|
constructor(opts) {
|
|
67169
67451
|
super(opts);
|
|
@@ -67188,6 +67470,7 @@ var RelayDeliverer = class extends Deliverer {
|
|
|
67188
67470
|
headers: {
|
|
67189
67471
|
date: s.headers.date,
|
|
67190
67472
|
digest: s.headers.digest,
|
|
67473
|
+
accept: s.headers.accept,
|
|
67191
67474
|
"content-type": s.headers["content-type"],
|
|
67192
67475
|
signature: s.headers.signature
|
|
67193
67476
|
}
|
|
@@ -69415,7 +69698,9 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
69415
69698
|
actorId: this.urls.actor,
|
|
69416
69699
|
log: this.log,
|
|
69417
69700
|
relayUrl: `${frontOrigin.replace(/\/$/, "")}/api/relay`,
|
|
69418
|
-
|
|
69701
|
+
// The relay finds the account by the front's own key for it, which for a
|
|
69702
|
+
// mail-door account is the full address, not the bare handle.
|
|
69703
|
+
handle: doorKeyOf(config.gateway?.url) || config.handle,
|
|
69419
69704
|
sessionFetch: session.fetch
|
|
69420
69705
|
});
|
|
69421
69706
|
this.publisher = new Publisher({
|
|
@@ -69525,9 +69810,7 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
69525
69810
|
const rec = await generateKeys();
|
|
69526
69811
|
rec.mintedFor = this.urls.actor;
|
|
69527
69812
|
await writeWrappedKeys(this.remote, this.urls, await wrapKeys(rec, password));
|
|
69528
|
-
await
|
|
69529
|
-
});
|
|
69530
|
-
const keys = await importSigningKey(rec);
|
|
69813
|
+
const keys = await cacheOpenedKeys(this.urls.actor, rec);
|
|
69531
69814
|
this.publisher.publicKeyPem = keys.rsaPublicPem;
|
|
69532
69815
|
this.deliverer.rsaPrivate = keys.rsaPrivate;
|
|
69533
69816
|
await this.publisher.publishProfile();
|