@solarpunkltd/swarm-chat-js 7.1.0 → 7.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 +50 -8
- package/dist/chat.d.ts +7 -0
- package/dist/follower.d.ts +37 -6
- package/dist/history.d.ts +20 -6
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +232 -109
- package/dist/message/index.d.ts +1 -0
- package/dist/message/note.d.ts +38 -0
- package/dist/message.cjs +1 -1
- package/dist/message.js +139 -95
- package/dist/notes.d.ts +81 -0
- package/dist/swarm.d.ts +8 -1
- package/package.json +1 -1
package/dist/message.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@ethersphere/bee-js"),t=require("zod");var n=7,r=2048,i=128,a=500,o=20,s={TEXT:`text`,THREAD:`thread`,REACTION:`reaction`};function c(e){let t=0;for(let n of e)t++;return t}function l(e){return t.z.string().regex(RegExp(`^[0-9a-f]{${e}}$`),`expected ${e} lowercase hex characters`)}function u(e,n){return t.z.string().refine(t=>{let r=c(t);return r>=e&&r<=n},`expected ${e} to ${n} characters`)}var d=l(32),f=t.z.strictObject({v:t.z.literal(7),topic:u(1,128),id:d,type:t.z.enum([s.TEXT,s.THREAD,s.REACTION]),target:t.z.union([t.z.literal(``),d]),text:u(1,500),name:u(1,20),addr:l(40),ts:t.z.number().int().nonnegative().max(2**53-1),sig:l(130)}).refine(e=>e.type===s.TEXT==(e.target===``),{message:`a text message has no target, a thread reply or a reaction names one`,path:[`target`]}),p=class extends Error{constructor(e,t){super(`chat message refused (${e}): ${t}`),this.reason=e,this.name=`ChatMessageError`}},m=new TextEncoder,h=new TextDecoder(`utf-8`,{fatal:!0});function g(e){return m.encode(JSON.stringify([e.v,e.topic,e.id,e.type,e.target,e.text,e.name,e.addr,e.ts]))}function _(e){let t={v:e.v,topic:e.topic,id:e.id,type:e.type,target:e.target,text:e.text,name:e.name,addr:e.addr,ts:e.ts,sig:e.sig};return m.encode(JSON.stringify(t))}function v(){let e=crypto.getRandomValues(new Uint8Array(16));return Array.from(e,e=>e.toString(16).padStart(2,`0`)).join(``)}function y(e){return e.issues.map(e=>`${e.path.join(`.`)||`message`}: ${e.message}`).join(`, `)}function b(t,n){let i=t instanceof e.PrivateKey?t:new e.PrivateKey(t),a={v:7,topic:n.topic,id:n.id??v(),type:n.type,target:n.target??``,text:n.text,name:n.name,addr:i.publicKey().address().toHex(),ts:n.ts??Date.now()},o={...a,sig:`0`.repeat(130)},s=f.safeParse(o);if(!s.success)throw new p(`shape`,y(s.error));let c=_(o).byteLength;if(c>2048)throw new p(`too-large`,`${c} bytes, the cap is ${r}`);let l={...a,sig:i.sign(g(a)).toHex()};return{message:l,bytes:_(l)}}function x(t){try{return new e.Signature(t.sig).recoverPublicKey(g(t)).address().toHex()===t.addr}catch{return!1}}function S(e){let t=f.safeParse(e);return t.success?x(t.data)?{ok:!0,message:t.data}:{ok:!1,reason:`signature`,detail:`the signature was not made by addr over these fields`}:{ok:!1,reason:`shape`,detail:y(t.error)}}function C(e){if(e.byteLength>2048)return{ok:!1,reason:`too-large`,detail:`${e.byteLength} bytes, the cap is ${r}`};let t;try{t=h.decode(e)}catch{return{ok:!1,reason:`not-utf8`,detail:`the payload is not valid UTF-8`}}let n;try{n=JSON.parse(t)}catch{return{ok:!1,reason:`not-json`,detail:`the payload is not JSON`}}return S(n)}var w=4096,T=t.z.string().regex(/^[0-9a-f]{64}([0-9a-f]{64})?$/,`expected a 64 or 128 hex character reference`),E=t.z.number().int().nonnegative().max(2**53-1),D=t.z.strictObject({ref:T,toSeq:E}),O=t.z.strictObject({v:t.z.literal(7),seq:E,at:E,msg:f,history:D.nullable()}),k=t.z.strictObject({seq:E,at:E,msg:f}),A=t.z.strictObject({v:t.z.literal(7),topic:t.z.string().min(1),fromSeq:E,toSeq:E,messages:t.z.array(t.z.unknown()),prev:D.nullable()}).refine(e=>e.fromSeq<=e.toSeq,{message:`fromSeq is after toSeq`,path:[`fromSeq`]}),j=new TextDecoder(`utf-8`,{fatal:!0});function M(e){let t;try{t=j.decode(e)}catch{return{ok:!1,reason:`not-utf8`,detail:`the payload is not valid UTF-8`}}try{return{ok:!0,value:JSON.parse(t)}}catch{return{ok:!1,reason:`not-json`,detail:`the payload is not JSON`}}}function N(e){return e.issues.map(e=>`${e.path.join(`.`)||`value`}: ${e.message}`).join(`, `)}function P(e,t,n){let r=M(e);if(!r.ok)return r;let i=O.safeParse(r.value);return i.success?i.data.seq===t?i.data.msg.topic===n?{ok:!0,value:i.data}:{ok:!1,reason:`mismatch`,detail:`the message belongs to another chat`}:{ok:!1,reason:`mismatch`,detail:`seq ${i.data.seq} at index ${t}`}:{ok:!1,reason:`shape`,detail:N(i.error)}}function F(e,t,n){let r=M(e);if(!r.ok)return r;let i=A.safeParse(r.value);if(!i.success)return{ok:!1,reason:`shape`,detail:N(i.error)};if(i.data.topic!==t||i.data.toSeq!==n.toSeq)return{ok:!1,reason:`mismatch`,detail:`the file is not the one its link names`};let{fromSeq:a,toSeq:o,prev:s}=i.data,c=[],l=0;for(let e of i.data.messages){let n=k.safeParse(e);n.success&&n.data.seq>=a&&n.data.seq<=o&&n.data.msg.topic===t?c.push(n.data):l++}return{ok:!0,value:{topic:t,fromSeq:a,toSeq:o,rows:c,prev:s,skipped:l}}}exports.ChatMessageError=p,exports.MAX_ENTRY_BYTES=w,exports.MAX_MESSAGE_BYTES=r,exports.MAX_NAME_CHARACTERS=o,exports.MAX_TEXT_CHARACTERS=a,exports.MAX_TOPIC_CHARACTERS=i,exports.MESSAGE_VERSION=n,exports.MessageType=s,exports.chatMessageSchema=f,exports.checkChatMessage=S,exports.countCharacters=c,exports.createChatMessage=b,exports.encodeChatMessage=_,exports.feedEntrySchema=O,exports.hasValidSignature=x,exports.historyFileSchema=A,exports.historyLinkSchema=D,exports.historyRowSchema=k,exports.newMessageId=v,exports.parseChatMessage=C,exports.parseFeedEntry=P,exports.parseHistoryFile=F,exports.signedBytes=g;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@ethersphere/bee-js"),t=require("zod");var n=7,r=2048,i=128,a=500,o=20,s={TEXT:`text`,THREAD:`thread`,REACTION:`reaction`};function c(e){let t=0;for(let n of e)t++;return t}function l(e){return t.z.string().regex(RegExp(`^[0-9a-f]{${e}}$`),`expected ${e} lowercase hex characters`)}function u(e,n){return t.z.string().refine(t=>{let r=c(t);return r>=e&&r<=n},`expected ${e} to ${n} characters`)}var d=l(32),f=t.z.strictObject({v:t.z.literal(7),topic:u(1,128),id:d,type:t.z.enum([s.TEXT,s.THREAD,s.REACTION]),target:t.z.union([t.z.literal(``),d]),text:u(1,500),name:u(1,20),addr:l(40),ts:t.z.number().int().nonnegative().max(2**53-1),sig:l(130)}).refine(e=>e.type===s.TEXT==(e.target===``),{message:`a text message has no target, a thread reply or a reaction names one`,path:[`target`]}),p=class extends Error{constructor(e,t){super(`chat message refused (${e}): ${t}`),this.reason=e,this.name=`ChatMessageError`}},m=new TextEncoder,h=new TextDecoder(`utf-8`,{fatal:!0});function g(e){return m.encode(JSON.stringify([e.v,e.topic,e.id,e.type,e.target,e.text,e.name,e.addr,e.ts]))}function _(e){let t={v:e.v,topic:e.topic,id:e.id,type:e.type,target:e.target,text:e.text,name:e.name,addr:e.addr,ts:e.ts,sig:e.sig};return m.encode(JSON.stringify(t))}function v(){let e=crypto.getRandomValues(new Uint8Array(16));return Array.from(e,e=>e.toString(16).padStart(2,`0`)).join(``)}function y(e){return e.issues.map(e=>`${e.path.join(`.`)||`message`}: ${e.message}`).join(`, `)}function b(t,n){let i=t instanceof e.PrivateKey?t:new e.PrivateKey(t),a={v:7,topic:n.topic,id:n.id??v(),type:n.type,target:n.target??``,text:n.text,name:n.name,addr:i.publicKey().address().toHex(),ts:n.ts??Date.now()},o={...a,sig:`0`.repeat(130)},s=f.safeParse(o);if(!s.success)throw new p(`shape`,y(s.error));let c=_(o).byteLength;if(c>2048)throw new p(`too-large`,`${c} bytes, the cap is ${r}`);let l={...a,sig:i.sign(g(a)).toHex()};return{message:l,bytes:_(l)}}function x(t){try{return new e.Signature(t.sig).recoverPublicKey(g(t)).address().toHex()===t.addr}catch{return!1}}function S(e){let t=f.safeParse(e);return t.success?x(t.data)?{ok:!0,message:t.data}:{ok:!1,reason:`signature`,detail:`the signature was not made by addr over these fields`}:{ok:!1,reason:`shape`,detail:y(t.error)}}function C(e){if(e.byteLength>2048)return{ok:!1,reason:`too-large`,detail:`${e.byteLength} bytes, the cap is ${r}`};let t;try{t=h.decode(e)}catch{return{ok:!1,reason:`not-utf8`,detail:`the payload is not valid UTF-8`}}let n;try{n=JSON.parse(t)}catch{return{ok:!1,reason:`not-json`,detail:`the payload is not JSON`}}return S(n)}var w=4096,T=t.z.string().regex(/^[0-9a-f]{64}([0-9a-f]{64})?$/,`expected a 64 or 128 hex character reference`),E=t.z.number().int().nonnegative().max(2**53-1),D=t.z.strictObject({ref:T,toSeq:E}),O=t.z.strictObject({v:t.z.literal(7),seq:E,at:E,msg:f,history:D.nullable()}),k=t.z.strictObject({seq:E,at:E,msg:f}),A=t.z.strictObject({v:t.z.literal(7),topic:t.z.string().min(1),fromSeq:E,toSeq:E,messages:t.z.array(t.z.unknown()),prev:D.nullable()}).refine(e=>e.fromSeq<=e.toSeq,{message:`fromSeq is after toSeq`,path:[`fromSeq`]}),j=new TextDecoder(`utf-8`,{fatal:!0});function M(e){let t;try{t=j.decode(e)}catch{return{ok:!1,reason:`not-utf8`,detail:`the payload is not valid UTF-8`}}try{return{ok:!0,value:JSON.parse(t)}}catch{return{ok:!1,reason:`not-json`,detail:`the payload is not JSON`}}}function N(e){return e.issues.map(e=>`${e.path.join(`.`)||`value`}: ${e.message}`).join(`, `)}function P(e,t,n){let r=M(e);if(!r.ok)return r;let i=O.safeParse(r.value);return i.success?i.data.seq===t?i.data.msg.topic===n?{ok:!0,value:i.data}:{ok:!1,reason:`mismatch`,detail:`the message belongs to another chat`}:{ok:!1,reason:`mismatch`,detail:`seq ${i.data.seq} at index ${t}`}:{ok:!1,reason:`shape`,detail:N(i.error)}}function F(e,t,n){let r=M(e);if(!r.ok)return r;let i=A.safeParse(r.value);if(!i.success)return{ok:!1,reason:`shape`,detail:N(i.error)};if(i.data.topic!==t||i.data.toSeq!==n.toSeq)return{ok:!1,reason:`mismatch`,detail:`the file is not the one its link names`};let{fromSeq:a,toSeq:o,prev:s}=i.data,c=[],l=0;for(let e of i.data.messages){let n=k.safeParse(e);n.success&&n.data.seq>=a&&n.data.seq<=o&&n.data.msg.topic===t?c.push(n.data):l++}return{ok:!0,value:{topic:t,fromSeq:a,toSeq:o,rows:c,prev:s,skipped:l}}}var I=1,L=2e3,R=3e4,z=256,B=t.z.strictObject({v:t.z.literal(1),newest:t.z.number().int().min(-1).max(2**53-1),writtenAt:t.z.number().int().nonnegative().max(2**53-1)});function V(e){if(!Number.isInteger(e)||e<=0)throw RangeError(`a note slot is a positive whole number of milliseconds, not ${e}`)}function H(e,t){return V(t),Math.floor(e/t)}function U(e,t){return V(t),(e+1)*t}function W(e,t,n){return`${e}/note/${t}/${n}`}function G(t,n,r){return e.Identifier.fromString(W(t,n,r))}function K(t,n,r,i){let a=G(t,n,r);return new e.Reference(e.Bytes.keccak256(e.Bytes.concat(a.toUint8Array(),new e.EthAddress(i).toUint8Array())))}var q=new TextEncoder,J=new TextDecoder(`utf-8`,{fatal:!0});function Y(e){let t={v:1,newest:e.newest,writtenAt:e.writtenAt};return q.encode(JSON.stringify(t))}function X(e){if(e.length>256)return null;try{let t=B.safeParse(JSON.parse(J.decode(e)));return t.success?t.data:null}catch{return null}}exports.ChatMessageError=p,exports.DEFAULT_NOTE_HEARTBEAT_MS=R,exports.DEFAULT_NOTE_SLOT_MS=L,exports.MAX_ENTRY_BYTES=w,exports.MAX_MESSAGE_BYTES=r,exports.MAX_NAME_CHARACTERS=o,exports.MAX_NOTE_BYTES=z,exports.MAX_TEXT_CHARACTERS=a,exports.MAX_TOPIC_CHARACTERS=i,exports.MESSAGE_VERSION=n,exports.MessageType=s,exports.NOTE_VERSION=I,exports.chatMessageSchema=f,exports.checkChatMessage=S,exports.countCharacters=c,exports.createChatMessage=b,exports.encodeChatMessage=_,exports.encodeSlotNote=Y,exports.feedEntrySchema=O,exports.hasValidSignature=x,exports.historyFileSchema=A,exports.historyLinkSchema=D,exports.historyRowSchema=k,exports.newMessageId=v,exports.noteAddress=K,exports.noteIdentifier=G,exports.noteIdentifierText=W,exports.noteSlotEnd=U,exports.noteSlotOf=H,exports.parseChatMessage=C,exports.parseFeedEntry=P,exports.parseHistoryFile=F,exports.parseSlotNote=X,exports.signedBytes=g,exports.slotNoteSchema=B;
|
package/dist/message.js
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { z as
|
|
1
|
+
import { Bytes as e, EthAddress as t, Identifier as n, PrivateKey as r, Reference as i, Signature as a } from "@ethersphere/bee-js";
|
|
2
|
+
import { z as o } from "zod";
|
|
3
3
|
//#region src/message/message.ts
|
|
4
|
-
var
|
|
4
|
+
var s = 7, c = 2048, l = 128, u = 500, d = 20, f = {
|
|
5
5
|
TEXT: "text",
|
|
6
6
|
THREAD: "thread",
|
|
7
7
|
REACTION: "reaction"
|
|
8
8
|
};
|
|
9
|
-
function
|
|
9
|
+
function p(e) {
|
|
10
10
|
let t = 0;
|
|
11
11
|
for (let n of e) t++;
|
|
12
12
|
return t;
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
return
|
|
14
|
+
function m(e) {
|
|
15
|
+
return o.string().regex(RegExp(`^[0-9a-f]{${e}}$`), `expected ${e} lowercase hex characters`);
|
|
16
16
|
}
|
|
17
|
-
function
|
|
18
|
-
return
|
|
19
|
-
let r =
|
|
17
|
+
function h(e, t) {
|
|
18
|
+
return o.string().refine((n) => {
|
|
19
|
+
let r = p(n);
|
|
20
20
|
return r >= e && r <= t;
|
|
21
21
|
}, `expected ${e} to ${t} characters`);
|
|
22
22
|
}
|
|
23
|
-
var
|
|
24
|
-
v:
|
|
25
|
-
topic:
|
|
26
|
-
id:
|
|
27
|
-
type:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
var g = m(32), _ = o.strictObject({
|
|
24
|
+
v: o.literal(7),
|
|
25
|
+
topic: h(1, 128),
|
|
26
|
+
id: g,
|
|
27
|
+
type: o.enum([
|
|
28
|
+
f.TEXT,
|
|
29
|
+
f.THREAD,
|
|
30
|
+
f.REACTION
|
|
31
31
|
]),
|
|
32
|
-
target:
|
|
33
|
-
text:
|
|
34
|
-
name:
|
|
35
|
-
addr:
|
|
36
|
-
ts:
|
|
37
|
-
sig:
|
|
38
|
-
}).refine((e) => e.type ===
|
|
32
|
+
target: o.union([o.literal(""), g]),
|
|
33
|
+
text: h(1, 500),
|
|
34
|
+
name: h(1, 20),
|
|
35
|
+
addr: m(40),
|
|
36
|
+
ts: o.number().int().nonnegative().max(2 ** 53 - 1),
|
|
37
|
+
sig: m(130)
|
|
38
|
+
}).refine((e) => e.type === f.TEXT == (e.target === ""), {
|
|
39
39
|
message: "a text message has no target, a thread reply or a reaction names one",
|
|
40
40
|
path: ["target"]
|
|
41
|
-
}),
|
|
41
|
+
}), v = class extends Error {
|
|
42
42
|
constructor(e, t) {
|
|
43
43
|
super(`chat message refused (${e}): ${t}`), this.reason = e, this.name = "ChatMessageError";
|
|
44
44
|
}
|
|
45
|
-
},
|
|
46
|
-
function
|
|
47
|
-
return
|
|
45
|
+
}, y = new TextEncoder(), b = new TextDecoder("utf-8", { fatal: !0 });
|
|
46
|
+
function x(e) {
|
|
47
|
+
return y.encode(JSON.stringify([
|
|
48
48
|
e.v,
|
|
49
49
|
e.topic,
|
|
50
50
|
e.id,
|
|
@@ -56,7 +56,7 @@ function _(e) {
|
|
|
56
56
|
e.ts
|
|
57
57
|
]));
|
|
58
58
|
}
|
|
59
|
-
function
|
|
59
|
+
function S(e) {
|
|
60
60
|
let t = {
|
|
61
61
|
v: e.v,
|
|
62
62
|
topic: e.topic,
|
|
@@ -69,52 +69,52 @@ function v(e) {
|
|
|
69
69
|
ts: e.ts,
|
|
70
70
|
sig: e.sig
|
|
71
71
|
};
|
|
72
|
-
return
|
|
72
|
+
return y.encode(JSON.stringify(t));
|
|
73
73
|
}
|
|
74
|
-
function
|
|
74
|
+
function C() {
|
|
75
75
|
let e = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
|
|
76
76
|
return Array.from(e, (e) => e.toString(16).padStart(2, "0")).join("");
|
|
77
77
|
}
|
|
78
|
-
function
|
|
78
|
+
function w(e) {
|
|
79
79
|
return e.issues.map((e) => `${e.path.join(".") || "message"}: ${e.message}`).join(", ");
|
|
80
80
|
}
|
|
81
|
-
function
|
|
82
|
-
let
|
|
81
|
+
function T(e, t) {
|
|
82
|
+
let n = e instanceof r ? e : new r(e), i = {
|
|
83
83
|
v: 7,
|
|
84
|
-
topic:
|
|
85
|
-
id:
|
|
86
|
-
type:
|
|
87
|
-
target:
|
|
88
|
-
text:
|
|
89
|
-
name:
|
|
90
|
-
addr:
|
|
91
|
-
ts:
|
|
92
|
-
},
|
|
93
|
-
...
|
|
84
|
+
topic: t.topic,
|
|
85
|
+
id: t.id ?? C(),
|
|
86
|
+
type: t.type,
|
|
87
|
+
target: t.target ?? "",
|
|
88
|
+
text: t.text,
|
|
89
|
+
name: t.name,
|
|
90
|
+
addr: n.publicKey().address().toHex(),
|
|
91
|
+
ts: t.ts ?? Date.now()
|
|
92
|
+
}, a = {
|
|
93
|
+
...i,
|
|
94
94
|
sig: "0".repeat(130)
|
|
95
|
-
},
|
|
96
|
-
if (!
|
|
97
|
-
let
|
|
98
|
-
if (
|
|
95
|
+
}, o = _.safeParse(a);
|
|
96
|
+
if (!o.success) throw new v("shape", w(o.error));
|
|
97
|
+
let s = S(a).byteLength;
|
|
98
|
+
if (s > 2048) throw new v("too-large", `${s} bytes, the cap is ${c}`);
|
|
99
99
|
let l = {
|
|
100
|
-
...
|
|
101
|
-
sig:
|
|
100
|
+
...i,
|
|
101
|
+
sig: n.sign(x(i)).toHex()
|
|
102
102
|
};
|
|
103
103
|
return {
|
|
104
104
|
message: l,
|
|
105
|
-
bytes:
|
|
105
|
+
bytes: S(l)
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
|
-
function
|
|
108
|
+
function E(e) {
|
|
109
109
|
try {
|
|
110
|
-
return new
|
|
110
|
+
return new a(e.sig).recoverPublicKey(x(e)).address().toHex() === e.addr;
|
|
111
111
|
} catch {
|
|
112
112
|
return !1;
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
-
function
|
|
116
|
-
let t =
|
|
117
|
-
return t.success ?
|
|
115
|
+
function D(e) {
|
|
116
|
+
let t = _.safeParse(e);
|
|
117
|
+
return t.success ? E(t.data) ? {
|
|
118
118
|
ok: !0,
|
|
119
119
|
message: t.data
|
|
120
120
|
} : {
|
|
@@ -124,18 +124,18 @@ function C(e) {
|
|
|
124
124
|
} : {
|
|
125
125
|
ok: !1,
|
|
126
126
|
reason: "shape",
|
|
127
|
-
detail:
|
|
127
|
+
detail: w(t.error)
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
|
-
function
|
|
130
|
+
function O(e) {
|
|
131
131
|
if (e.byteLength > 2048) return {
|
|
132
132
|
ok: !1,
|
|
133
133
|
reason: "too-large",
|
|
134
|
-
detail: `${e.byteLength} bytes, the cap is ${
|
|
134
|
+
detail: `${e.byteLength} bytes, the cap is ${c}`
|
|
135
135
|
};
|
|
136
136
|
let t;
|
|
137
137
|
try {
|
|
138
|
-
t =
|
|
138
|
+
t = b.decode(e);
|
|
139
139
|
} catch {
|
|
140
140
|
return {
|
|
141
141
|
ok: !1,
|
|
@@ -153,38 +153,38 @@ function w(e) {
|
|
|
153
153
|
detail: "the payload is not JSON"
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
|
-
return
|
|
156
|
+
return D(n);
|
|
157
157
|
}
|
|
158
158
|
//#endregion
|
|
159
159
|
//#region src/message/entry.ts
|
|
160
|
-
var
|
|
161
|
-
ref:
|
|
162
|
-
toSeq:
|
|
163
|
-
}),
|
|
164
|
-
v:
|
|
165
|
-
seq:
|
|
166
|
-
at:
|
|
167
|
-
msg:
|
|
168
|
-
history:
|
|
169
|
-
}),
|
|
170
|
-
seq:
|
|
171
|
-
at:
|
|
172
|
-
msg:
|
|
173
|
-
}),
|
|
174
|
-
v:
|
|
175
|
-
topic:
|
|
176
|
-
fromSeq:
|
|
177
|
-
toSeq:
|
|
178
|
-
messages:
|
|
179
|
-
prev:
|
|
160
|
+
var k = 4096, A = o.string().regex(/^[0-9a-f]{64}([0-9a-f]{64})?$/, "expected a 64 or 128 hex character reference"), j = o.number().int().nonnegative().max(2 ** 53 - 1), M = o.strictObject({
|
|
161
|
+
ref: A,
|
|
162
|
+
toSeq: j
|
|
163
|
+
}), N = o.strictObject({
|
|
164
|
+
v: o.literal(7),
|
|
165
|
+
seq: j,
|
|
166
|
+
at: j,
|
|
167
|
+
msg: _,
|
|
168
|
+
history: M.nullable()
|
|
169
|
+
}), P = o.strictObject({
|
|
170
|
+
seq: j,
|
|
171
|
+
at: j,
|
|
172
|
+
msg: _
|
|
173
|
+
}), F = o.strictObject({
|
|
174
|
+
v: o.literal(7),
|
|
175
|
+
topic: o.string().min(1),
|
|
176
|
+
fromSeq: j,
|
|
177
|
+
toSeq: j,
|
|
178
|
+
messages: o.array(o.unknown()),
|
|
179
|
+
prev: M.nullable()
|
|
180
180
|
}).refine((e) => e.fromSeq <= e.toSeq, {
|
|
181
181
|
message: "fromSeq is after toSeq",
|
|
182
182
|
path: ["fromSeq"]
|
|
183
|
-
}),
|
|
184
|
-
function
|
|
183
|
+
}), I = new TextDecoder("utf-8", { fatal: !0 });
|
|
184
|
+
function L(e) {
|
|
185
185
|
let t;
|
|
186
186
|
try {
|
|
187
|
-
t =
|
|
187
|
+
t = I.decode(e);
|
|
188
188
|
} catch {
|
|
189
189
|
return {
|
|
190
190
|
ok: !1,
|
|
@@ -205,13 +205,13 @@ function N(e) {
|
|
|
205
205
|
};
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
|
-
function
|
|
208
|
+
function R(e) {
|
|
209
209
|
return e.issues.map((e) => `${e.path.join(".") || "value"}: ${e.message}`).join(", ");
|
|
210
210
|
}
|
|
211
|
-
function
|
|
212
|
-
let r =
|
|
211
|
+
function z(e, t, n) {
|
|
212
|
+
let r = L(e);
|
|
213
213
|
if (!r.ok) return r;
|
|
214
|
-
let i =
|
|
214
|
+
let i = N.safeParse(r.value);
|
|
215
215
|
return i.success ? i.data.seq === t ? i.data.msg.topic === n ? {
|
|
216
216
|
ok: !0,
|
|
217
217
|
value: i.data
|
|
@@ -226,17 +226,17 @@ function F(e, t, n) {
|
|
|
226
226
|
} : {
|
|
227
227
|
ok: !1,
|
|
228
228
|
reason: "shape",
|
|
229
|
-
detail:
|
|
229
|
+
detail: R(i.error)
|
|
230
230
|
};
|
|
231
231
|
}
|
|
232
|
-
function
|
|
233
|
-
let r =
|
|
232
|
+
function B(e, t, n) {
|
|
233
|
+
let r = L(e);
|
|
234
234
|
if (!r.ok) return r;
|
|
235
|
-
let i =
|
|
235
|
+
let i = F.safeParse(r.value);
|
|
236
236
|
if (!i.success) return {
|
|
237
237
|
ok: !1,
|
|
238
238
|
reason: "shape",
|
|
239
|
-
detail:
|
|
239
|
+
detail: R(i.error)
|
|
240
240
|
};
|
|
241
241
|
if (i.data.topic !== t || i.data.toSeq !== n.toSeq) return {
|
|
242
242
|
ok: !1,
|
|
@@ -245,7 +245,7 @@ function I(e, t, n) {
|
|
|
245
245
|
};
|
|
246
246
|
let { fromSeq: a, toSeq: o, prev: s } = i.data, c = [], l = 0;
|
|
247
247
|
for (let e of i.data.messages) {
|
|
248
|
-
let n =
|
|
248
|
+
let n = P.safeParse(e);
|
|
249
249
|
n.success && n.data.seq >= a && n.data.seq <= o && n.data.msg.topic === t ? c.push(n.data) : l++;
|
|
250
250
|
}
|
|
251
251
|
return {
|
|
@@ -261,4 +261,48 @@ function I(e, t, n) {
|
|
|
261
261
|
};
|
|
262
262
|
}
|
|
263
263
|
//#endregion
|
|
264
|
-
|
|
264
|
+
//#region src/message/note.ts
|
|
265
|
+
var V = 1, H = 2e3, U = 3e4, W = 256, G = o.strictObject({
|
|
266
|
+
v: o.literal(1),
|
|
267
|
+
newest: o.number().int().min(-1).max(2 ** 53 - 1),
|
|
268
|
+
writtenAt: o.number().int().nonnegative().max(2 ** 53 - 1)
|
|
269
|
+
});
|
|
270
|
+
function K(e) {
|
|
271
|
+
if (!Number.isInteger(e) || e <= 0) throw RangeError(`a note slot is a positive whole number of milliseconds, not ${e}`);
|
|
272
|
+
}
|
|
273
|
+
function q(e, t) {
|
|
274
|
+
return K(t), Math.floor(e / t);
|
|
275
|
+
}
|
|
276
|
+
function J(e, t) {
|
|
277
|
+
return K(t), (e + 1) * t;
|
|
278
|
+
}
|
|
279
|
+
function Y(e, t, n) {
|
|
280
|
+
return `${e}/note/${t}/${n}`;
|
|
281
|
+
}
|
|
282
|
+
function X(e, t, r) {
|
|
283
|
+
return n.fromString(Y(e, t, r));
|
|
284
|
+
}
|
|
285
|
+
function Z(n, r, a, o) {
|
|
286
|
+
let s = X(n, r, a);
|
|
287
|
+
return new i(e.keccak256(e.concat(s.toUint8Array(), new t(o).toUint8Array())));
|
|
288
|
+
}
|
|
289
|
+
var Q = new TextEncoder(), $ = new TextDecoder("utf-8", { fatal: !0 });
|
|
290
|
+
function ee(e) {
|
|
291
|
+
let t = {
|
|
292
|
+
v: 1,
|
|
293
|
+
newest: e.newest,
|
|
294
|
+
writtenAt: e.writtenAt
|
|
295
|
+
};
|
|
296
|
+
return Q.encode(JSON.stringify(t));
|
|
297
|
+
}
|
|
298
|
+
function te(e) {
|
|
299
|
+
if (e.length > 256) return null;
|
|
300
|
+
try {
|
|
301
|
+
let t = G.safeParse(JSON.parse($.decode(e)));
|
|
302
|
+
return t.success ? t.data : null;
|
|
303
|
+
} catch {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
//#endregion
|
|
308
|
+
export { v as ChatMessageError, U as DEFAULT_NOTE_HEARTBEAT_MS, H as DEFAULT_NOTE_SLOT_MS, k as MAX_ENTRY_BYTES, c as MAX_MESSAGE_BYTES, d as MAX_NAME_CHARACTERS, W as MAX_NOTE_BYTES, u as MAX_TEXT_CHARACTERS, l as MAX_TOPIC_CHARACTERS, s as MESSAGE_VERSION, f as MessageType, V as NOTE_VERSION, _ as chatMessageSchema, D as checkChatMessage, p as countCharacters, T as createChatMessage, S as encodeChatMessage, ee as encodeSlotNote, N as feedEntrySchema, E as hasValidSignature, F as historyFileSchema, M as historyLinkSchema, P as historyRowSchema, C as newMessageId, Z as noteAddress, X as noteIdentifier, Y as noteIdentifierText, J as noteSlotEnd, q as noteSlotOf, O as parseChatMessage, z as parseFeedEntry, B as parseHistoryFile, te as parseSlotNote, x as signedBytes, G as slotNoteSchema };
|
package/dist/notes.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type SlotNote } from './message/index.js';
|
|
2
|
+
import { type ChatSource } from './swarm.js';
|
|
3
|
+
export interface NoteSettings {
|
|
4
|
+
/** The length of a time slot. Must be the server's, since it is part of every note's address. */
|
|
5
|
+
slotMs: number;
|
|
6
|
+
/** The longest the server goes without a note while a chat is active. Must be the server's. */
|
|
7
|
+
heartbeatMs: number;
|
|
8
|
+
/** How long after a slot ends, by the server's clock, its note is asked for. */
|
|
9
|
+
marginMs: number;
|
|
10
|
+
/** Notes read at once when looking back over several slots, newest first. */
|
|
11
|
+
scanBatch: number;
|
|
12
|
+
/** The furthest the clock correction goes either way. */
|
|
13
|
+
clockLimitMs: number;
|
|
14
|
+
}
|
|
15
|
+
export declare const DEFAULT_NOTE_SETTINGS: NoteSettings;
|
|
16
|
+
/** A note that was found, and the time slot it was found at. */
|
|
17
|
+
export interface FoundNote {
|
|
18
|
+
slot: number;
|
|
19
|
+
note: SlotNote;
|
|
20
|
+
}
|
|
21
|
+
export type NoteSource = Required<Pick<ChatSource, 'readNote'>>;
|
|
22
|
+
/** Whether a source can read notes, which a source written before notes existed cannot. */
|
|
23
|
+
export declare function readsNotes(source: ChatSource): source is ChatSource & NoteSource;
|
|
24
|
+
/**
|
|
25
|
+
* How far this viewer's clock runs ahead of the server's, in milliseconds, as far as reads can tell. A negative value
|
|
26
|
+
* is a clock that runs behind.
|
|
27
|
+
*
|
|
28
|
+
* Reads can only bound it from above. Whatever the server stamped, a note's `writtenAt` or an entry's `at`, it stamped
|
|
29
|
+
* before this viewer received it, so the viewer is at most `received - stamped` ahead. A read scheduled by this
|
|
30
|
+
* viewer's own clock says nothing more, since the same clock decides when it is sent. Learning that a clock runs
|
|
31
|
+
* ahead would need a read sent before the note existed, which is the request this whole scheme exists to avoid.
|
|
32
|
+
*
|
|
33
|
+
* So the correction trusts the local clock, `min(0, bound)`, which only ever moves a read earlier when the bound
|
|
34
|
+
* proves the clock runs behind, and never makes a read early. Once notes stop being found for longer than the
|
|
35
|
+
* heartbeat, the local clock is no longer trusted and every read waits out the whole bound. That is late by at most
|
|
36
|
+
* the freshest read's own delay and never early.
|
|
37
|
+
*
|
|
38
|
+
* A clock that runs behind sees every stamp exactly as old as it expects, since it reads everything as late as it
|
|
39
|
+
* runs behind, so no read proves it behind and its messages arrive that much late. Proving it would also take a read
|
|
40
|
+
* of something not yet written by the viewer's own clock.
|
|
41
|
+
*/
|
|
42
|
+
export declare class ClockCorrection {
|
|
43
|
+
private readonly limitMs;
|
|
44
|
+
private aheadAtMost;
|
|
45
|
+
private trusted;
|
|
46
|
+
constructor(limitMs: number);
|
|
47
|
+
observe(stampedMs: number, receivedMs: number): void;
|
|
48
|
+
/** Reads have stopped finding notes, which a clock that runs ahead of the server's explains. */
|
|
49
|
+
distrust(): void;
|
|
50
|
+
get isTrusted(): boolean;
|
|
51
|
+
get ms(): number;
|
|
52
|
+
}
|
|
53
|
+
/** Reads notes for the follower and the opening alike, so both share one clock correction and one count. */
|
|
54
|
+
export declare class NoteReader {
|
|
55
|
+
private readonly source;
|
|
56
|
+
readonly now: () => number;
|
|
57
|
+
readonly clock: ClockCorrection;
|
|
58
|
+
/** Note reads that found a note, and those that found none, an unreadable chunk or a note that failed its check. */
|
|
59
|
+
readonly counts: {
|
|
60
|
+
found: number;
|
|
61
|
+
empty: number;
|
|
62
|
+
};
|
|
63
|
+
readonly settings: NoteSettings;
|
|
64
|
+
constructor(source: NoteSource, settings: Partial<NoteSettings>, now: () => number);
|
|
65
|
+
/** The newest time slot whose note may be asked for at `now`: over, and the margin past, by the server's clock. */
|
|
66
|
+
lastReadableSlot(now?: number): number;
|
|
67
|
+
/** The local time from which slot `slot`'s note may be asked for. */
|
|
68
|
+
readableAt(slot: number): number;
|
|
69
|
+
/** How many slots back a reader looks for a note: one heartbeat and two slots, the longest a note can be apart. */
|
|
70
|
+
get lookBackSlots(): number;
|
|
71
|
+
/** How long a follower goes without finding a note before it stops trusting its clock. */
|
|
72
|
+
get silenceMs(): number;
|
|
73
|
+
/** One slot's note, or null when there is none to be had. Rejects only when the gateway fails. */
|
|
74
|
+
read(slot: number): Promise<SlotNote | null>;
|
|
75
|
+
/**
|
|
76
|
+
* The newest note from slot `newest` back to slot `oldest`, read newest first, `scanBatch` at a time, stopping at
|
|
77
|
+
* the first batch that finds one. A note names the newest feed slot written by then, so the newest note found is the
|
|
78
|
+
* only one that matters.
|
|
79
|
+
*/
|
|
80
|
+
scan(newest: number, oldest: number): Promise<FoundNote | null>;
|
|
81
|
+
}
|
package/dist/swarm.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export interface ChatSource {
|
|
|
17
17
|
} | null>;
|
|
18
18
|
/** A history file's bytes. Rejects when it cannot be read. */
|
|
19
19
|
readFile(reference: string): Promise<Uint8Array>;
|
|
20
|
+
/**
|
|
21
|
+
* The payload of time slot `slot`'s note, or null when Bee answered that it is not there. Sorts its failures as
|
|
22
|
+
* `readSlot` does. A source without it follows the chat by polling the next slot, as before notes existed.
|
|
23
|
+
*/
|
|
24
|
+
readNote?(slot: number): Promise<Uint8Array | null>;
|
|
20
25
|
}
|
|
21
26
|
/** Bee's head lookup did not answer within its timeout, which on a long chat or a busy gateway is ordinary. */
|
|
22
27
|
export declare class HeadLookupTimeoutError extends Error {
|
|
@@ -59,8 +64,10 @@ export declare function readSlotThrough(index: number, read: () => Promise<Uint8
|
|
|
59
64
|
/**
|
|
60
65
|
* Reads a chat feed through bee-js's feed reader with an explicit index, which reads `GET /chunks/{address}` and
|
|
61
66
|
* answers 404 only when the chunk could not be found, where `/soc/{owner}/{id}` answers 404 for any failure at all.
|
|
67
|
+
* Notes are read through bee-js's single owner chunk reader, which reads the same `GET /chunks/{address}` and checks
|
|
68
|
+
* the chunk is the owner's.
|
|
62
69
|
*/
|
|
63
|
-
export declare function beeChatSource(bee: Bee, owner: string, chatTopic: string, timeouts: SwarmTimeouts): ChatSource;
|
|
70
|
+
export declare function beeChatSource(bee: Bee, owner: string, chatTopic: string, timeouts: SwarmTimeouts, noteSlotMs?: number): ChatSource;
|
|
64
71
|
/**
|
|
65
72
|
* A batch id for a gateway that stamps what is written through it and ignores the one it is sent. Bee refuses a
|
|
66
73
|
* write with no batch id at all, so something well formed has to go. The value 6.x sent, kept so a gateway that
|