@stasho/vf-records 0.1.0 → 0.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.
@@ -8,6 +8,10 @@ export type ExpectedRecord = {
8
8
  kind: "transfer";
9
9
  domain: string;
10
10
  newVault: string;
11
+ } | {
12
+ kind: "accept";
13
+ domain: string;
14
+ transfer: string;
11
15
  };
12
16
  export type CheckResult = {
13
17
  ok: true;
@@ -53,6 +53,9 @@ export function checkPreparedMessage(args) {
53
53
  if (record.kind === "transfer" && args.expected.kind === "transfer" && record.newVault !== args.expected.newVault) {
54
54
  return refuse(`transfer target ${record.newVault} differs from ${args.expected.newVault}`);
55
55
  }
56
+ if (record.kind === "accept" && args.expected.kind === "accept" && record.transfer !== args.expected.transfer) {
57
+ return refuse(`accept names transfer ${record.transfer}, you asked for ${args.expected.transfer}`);
58
+ }
56
59
  const t = Date.parse(record.ts);
57
60
  if (!Number.isFinite(t) || Math.abs(t - args.now) > CLIENT_CHECK_SKEW_MS)
58
61
  return refuse("record timestamp is more than 10 minutes from this clock");
package/dist/records.d.ts CHANGED
@@ -51,6 +51,26 @@ export type VfRecord = {
51
51
  vault: string;
52
52
  op: "bind" | "unbind";
53
53
  ts: string;
54
+ }
55
+ /**
56
+ * Signed by the INCOMING authority of a pending transfer, naming the
57
+ * transfer transaction it accepts. Bound to that signature so it is
58
+ * single-use by construction: a rejected transfer's acceptance cannot be
59
+ * replayed on a later request to the same key (spec § 2, Decision #446).
60
+ */
61
+ | {
62
+ kind: "accept";
63
+ domain: string;
64
+ transfer: string;
65
+ ts: string;
54
66
  };
55
67
  export declare function parsePayload(payload: string): VfRecord | null;
68
+ /**
69
+ * Parses a record the way the backend index STORES it: the decoded
70
+ * `VfRecord` as JSON (`{"kind":"pub","domain":…}`), not the raw memo text.
71
+ * Validation is delegated to `parsePayload` by re-encoding the candidate, so
72
+ * there is exactly one validator; every field must be a string for the
73
+ * re-encode to be faithful.
74
+ */
75
+ export declare function parseStoredRecord(json: string): VfRecord | null;
56
76
  export declare function encodeRecord(record: VfRecord): string;
package/dist/records.js CHANGED
@@ -103,8 +103,37 @@ export function parsePayload(payload) {
103
103
  return null;
104
104
  return { kind: "bind", domain, vault, op, ts };
105
105
  }
106
+ if (kind === "accept") {
107
+ const transfer = str("transfer");
108
+ return transfer ? { kind: "accept", domain, transfer, ts } : null;
109
+ }
106
110
  return null;
107
111
  }
112
+ const RECORD_KINDS = new Set(["pub", "transfer", "bind", "accept"]);
113
+ /**
114
+ * Parses a record the way the backend index STORES it: the decoded
115
+ * `VfRecord` as JSON (`{"kind":"pub","domain":…}`), not the raw memo text.
116
+ * Validation is delegated to `parsePayload` by re-encoding the candidate, so
117
+ * there is exactly one validator; every field must be a string for the
118
+ * re-encode to be faithful.
119
+ */
120
+ export function parseStoredRecord(json) {
121
+ let candidate;
122
+ try {
123
+ candidate = JSON.parse(json);
124
+ }
125
+ catch {
126
+ return null;
127
+ }
128
+ if (typeof candidate !== "object" || candidate === null)
129
+ return null;
130
+ const fields = candidate;
131
+ if (!RECORD_KINDS.has(fields["kind"]))
132
+ return null;
133
+ if (!Object.values(fields).every((v) => typeof v === "string"))
134
+ return null;
135
+ return parsePayload(encodeRecord(fields));
136
+ }
108
137
  export function encodeRecord(record) {
109
138
  if (record.kind === "pub") {
110
139
  return `${VF_PREFIX}pub:${JSON.stringify({
@@ -116,6 +145,11 @@ export function encodeRecord(record) {
116
145
  d: record.domain, newVault: record.newVault, t: record.ts,
117
146
  })}`;
118
147
  }
148
+ if (record.kind === "accept") {
149
+ return `${VF_PREFIX}accept:${JSON.stringify({
150
+ d: record.domain, transfer: record.transfer, t: record.ts,
151
+ })}`;
152
+ }
119
153
  return `${VF_PREFIX}bind:${JSON.stringify({
120
154
  d: record.domain, vault: record.vault, op: record.op, t: record.ts,
121
155
  })}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stasho/vf-records",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Stasho Verified Frontends record codec + the client-side check of a prepared publish message. Zero dependencies, browser-safe.",
5
5
  "license": "MIT",
6
6
  "type": "module",