@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/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 { PrivateKey as e, Signature as t } from "@ethersphere/bee-js";
2
- import { z as n } from "zod";
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 r = 7, i = 2048, a = 128, o = 500, s = 20, c = {
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 l(e) {
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 u(e) {
15
- return n.string().regex(RegExp(`^[0-9a-f]{${e}}$`), `expected ${e} lowercase hex characters`);
14
+ function m(e) {
15
+ return o.string().regex(RegExp(`^[0-9a-f]{${e}}$`), `expected ${e} lowercase hex characters`);
16
16
  }
17
- function d(e, t) {
18
- return n.string().refine((n) => {
19
- let r = l(n);
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 f = u(32), p = n.strictObject({
24
- v: n.literal(7),
25
- topic: d(1, 128),
26
- id: f,
27
- type: n.enum([
28
- c.TEXT,
29
- c.THREAD,
30
- c.REACTION
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: n.union([n.literal(""), f]),
33
- text: d(1, 500),
34
- name: d(1, 20),
35
- addr: u(40),
36
- ts: n.number().int().nonnegative().max(2 ** 53 - 1),
37
- sig: u(130)
38
- }).refine((e) => e.type === c.TEXT == (e.target === ""), {
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
- }), m = class extends Error {
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
- }, h = new TextEncoder(), g = new TextDecoder("utf-8", { fatal: !0 });
46
- function _(e) {
47
- return h.encode(JSON.stringify([
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 v(e) {
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 h.encode(JSON.stringify(t));
72
+ return y.encode(JSON.stringify(t));
73
73
  }
74
- function y() {
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 b(e) {
78
+ function w(e) {
79
79
  return e.issues.map((e) => `${e.path.join(".") || "message"}: ${e.message}`).join(", ");
80
80
  }
81
- function x(t, n) {
82
- let r = t instanceof e ? t : new e(t), a = {
81
+ function T(e, t) {
82
+ let n = e instanceof r ? e : new r(e), i = {
83
83
  v: 7,
84
- topic: n.topic,
85
- id: n.id ?? y(),
86
- type: n.type,
87
- target: n.target ?? "",
88
- text: n.text,
89
- name: n.name,
90
- addr: r.publicKey().address().toHex(),
91
- ts: n.ts ?? Date.now()
92
- }, o = {
93
- ...a,
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
- }, s = p.safeParse(o);
96
- if (!s.success) throw new m("shape", b(s.error));
97
- let c = v(o).byteLength;
98
- if (c > 2048) throw new m("too-large", `${c} bytes, the cap is ${i}`);
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
- ...a,
101
- sig: r.sign(_(a)).toHex()
100
+ ...i,
101
+ sig: n.sign(x(i)).toHex()
102
102
  };
103
103
  return {
104
104
  message: l,
105
- bytes: v(l)
105
+ bytes: S(l)
106
106
  };
107
107
  }
108
- function S(e) {
108
+ function E(e) {
109
109
  try {
110
- return new t(e.sig).recoverPublicKey(_(e)).address().toHex() === e.addr;
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 C(e) {
116
- let t = p.safeParse(e);
117
- return t.success ? S(t.data) ? {
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: b(t.error)
127
+ detail: w(t.error)
128
128
  };
129
129
  }
130
- function w(e) {
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 ${i}`
134
+ detail: `${e.byteLength} bytes, the cap is ${c}`
135
135
  };
136
136
  let t;
137
137
  try {
138
- t = g.decode(e);
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 C(n);
156
+ return D(n);
157
157
  }
158
158
  //#endregion
159
159
  //#region src/message/entry.ts
160
- var T = 4096, E = n.string().regex(/^[0-9a-f]{64}([0-9a-f]{64})?$/, "expected a 64 or 128 hex character reference"), D = n.number().int().nonnegative().max(2 ** 53 - 1), O = n.strictObject({
161
- ref: E,
162
- toSeq: D
163
- }), k = n.strictObject({
164
- v: n.literal(7),
165
- seq: D,
166
- at: D,
167
- msg: p,
168
- history: O.nullable()
169
- }), A = n.strictObject({
170
- seq: D,
171
- at: D,
172
- msg: p
173
- }), j = n.strictObject({
174
- v: n.literal(7),
175
- topic: n.string().min(1),
176
- fromSeq: D,
177
- toSeq: D,
178
- messages: n.array(n.unknown()),
179
- prev: O.nullable()
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
- }), M = new TextDecoder("utf-8", { fatal: !0 });
184
- function N(e) {
183
+ }), I = new TextDecoder("utf-8", { fatal: !0 });
184
+ function L(e) {
185
185
  let t;
186
186
  try {
187
- t = M.decode(e);
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 P(e) {
208
+ function R(e) {
209
209
  return e.issues.map((e) => `${e.path.join(".") || "value"}: ${e.message}`).join(", ");
210
210
  }
211
- function F(e, t, n) {
212
- let r = N(e);
211
+ function z(e, t, n) {
212
+ let r = L(e);
213
213
  if (!r.ok) return r;
214
- let i = k.safeParse(r.value);
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: P(i.error)
229
+ detail: R(i.error)
230
230
  };
231
231
  }
232
- function I(e, t, n) {
233
- let r = N(e);
232
+ function B(e, t, n) {
233
+ let r = L(e);
234
234
  if (!r.ok) return r;
235
- let i = j.safeParse(r.value);
235
+ let i = F.safeParse(r.value);
236
236
  if (!i.success) return {
237
237
  ok: !1,
238
238
  reason: "shape",
239
- detail: P(i.error)
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 = A.safeParse(e);
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
- export { m as ChatMessageError, T as MAX_ENTRY_BYTES, i as MAX_MESSAGE_BYTES, s as MAX_NAME_CHARACTERS, o as MAX_TEXT_CHARACTERS, a as MAX_TOPIC_CHARACTERS, r as MESSAGE_VERSION, c as MessageType, p as chatMessageSchema, C as checkChatMessage, l as countCharacters, x as createChatMessage, v as encodeChatMessage, k as feedEntrySchema, S as hasValidSignature, j as historyFileSchema, O as historyLinkSchema, A as historyRowSchema, y as newMessageId, w as parseChatMessage, F as parseFeedEntry, I as parseHistoryFile, _ as signedBytes };
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 };
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solarpunkltd/swarm-chat-js",
3
- "version": "7.1.0",
3
+ "version": "7.2.0",
4
4
  "description": "JS library for chatting on Swarm",
5
5
  "keywords": [
6
6
  "chat",