@irtio/protocol 0.1.0 → 0.3.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/index.d.ts CHANGED
@@ -128,6 +128,34 @@ declare const ErrorCode: {
128
128
  readonly code: 20;
129
129
  readonly message: "client cannot keep up with the stream; reconnect for a fresh snapshot";
130
130
  };
131
+ readonly E_TOKEN_EXPIRED: {
132
+ readonly code: 21;
133
+ readonly message: "auth token expired";
134
+ };
135
+ readonly E_TOKEN_INVALID: {
136
+ readonly code: 22;
137
+ readonly message: "auth token signature is invalid";
138
+ };
139
+ readonly E_TOKEN_WRONG_PROJECT: {
140
+ readonly code: 23;
141
+ readonly message: "auth token is for another project";
142
+ };
143
+ readonly E_TOKEN_WRONG_ROOM: {
144
+ readonly code: 24;
145
+ readonly message: "auth token is for another room";
146
+ };
147
+ readonly E_TOKEN_MALFORMED: {
148
+ readonly code: 25;
149
+ readonly message: "auth token is malformed: {reason}";
150
+ };
151
+ readonly E_TOKEN_BAD_ISSUER: {
152
+ readonly code: 26;
153
+ readonly message: "auth token issuer {issuer} is not accepted";
154
+ };
155
+ readonly E_TOKEN_BAD_ALG: {
156
+ readonly code: 27;
157
+ readonly message: "auth token algorithm {alg} is not allowed";
158
+ };
131
159
  };
132
160
  type ErrorCodeName = keyof typeof ErrorCode;
133
161
  interface ErrorCatalogueEntry {
@@ -153,6 +181,17 @@ type Credential = {
153
181
  } | {
154
182
  readonly kind: 'token';
155
183
  readonly token: string;
184
+ }
185
+ /**
186
+ * Week 13 (D27): the project key AND a JWT. The key stays because the router routes on it —
187
+ * a HELLO carrying only a token has nothing to resolve a tenant from — and the token is
188
+ * verified in the guest supervisor (HS256, per-project secret), never by the router. Wire tag
189
+ * 2; the unused tag 1 (`token`) keeps its meaning of months rather than being repurposed.
190
+ */
191
+ | {
192
+ readonly kind: 'jwt';
193
+ readonly key: string;
194
+ readonly token: string;
156
195
  };
157
196
  interface Hello {
158
197
  readonly protocolVersion: number;
@@ -218,19 +257,31 @@ declare function deltaPayload(codecBytes: Uint8Array): Uint8Array;
218
257
  declare function writePayload(codecBytes: Uint8Array): Uint8Array;
219
258
  /**
220
259
  * The CORRECT frame payload: the schema-codec delta bytes with the judged client write tick
221
- * (u32) appended (week 8, D19). Additive: the codec's delta decoder never reads past the delta
222
- * body, so a decoder that does not know about the suffix ignores it; a reader that does
223
- * (`decodeDeltaFrom`) is left positioned exactly at it.
260
+ * (u32) appended (week 8, D19), and after it the server tick that write was *applied* at (u32,
261
+ * bug 1). Additive in both steps: the codec's delta decoder never reads past the delta body, so
262
+ * a decoder that does not know about a suffix ignores it; a reader that does (`decodeDeltaFrom`)
263
+ * is left positioned exactly at the first one.
264
+ *
265
+ * The two ticks are on different clocks and that is the point. `clientTick` is the client's own
266
+ * stamp, opaque to the server and echoed verbatim; `appliedTick` is the room's tick counter when
267
+ * the write landed. A predicting client stamps on its *predicted* clock, which leads authority,
268
+ * so without the second number it has no way to learn where on the server's clock its intent
269
+ * actually took effect, and its replay puts it a whole prediction lead too late.
224
270
  */
225
- declare function correctPayload(codecBytes: Uint8Array, clientTick: number): Uint8Array;
271
+ declare function correctPayload(codecBytes: Uint8Array, clientTick: number, appliedTick?: number): Uint8Array;
226
272
  /**
227
273
  * Reads the `clientTick` suffix a reader is positioned at after `decodeDeltaFrom`, or
228
274
  * `undefined` for a pre-week-8 CORRECT payload with no suffix.
229
275
  */
230
276
  declare function readCorrectClientTick(r: ByteReader): number | undefined;
277
+ /**
278
+ * Reads the `appliedTick` suffix that follows `clientTick`, or `undefined` from a server that
279
+ * does not send one. Call only after `readCorrectClientTick`, which leaves the reader on it.
280
+ */
281
+ declare function readCorrectAppliedTick(r: ByteReader): number | undefined;
231
282
  declare function encodeDeltaFrame(codecBytes: Uint8Array): Uint8Array;
232
283
  declare function encodeWriteFrame(codecBytes: Uint8Array): Uint8Array;
233
- declare function encodeCorrectFrame(codecBytes: Uint8Array, clientTick: number): Uint8Array;
284
+ declare function encodeCorrectFrame(codecBytes: Uint8Array, clientTick: number, appliedTick?: number): Uint8Array;
234
285
 
235
286
  interface Call {
236
287
  readonly reqId: number;
@@ -333,4 +384,4 @@ declare const relaySchema: Schema<{
333
384
  declare const RELAY_HASH8: Uint8Array;
334
385
  declare function isRelayHash8(hash8: Uint8Array): boolean;
335
386
 
336
- export { type Call, type ClientCallable, type Credential, ERROR_CATALOGUE, type ErrorCatalogueEntry, ErrorCode, type ErrorCodeDef, type ErrorCodeName, type ErrorPayload, type Frame, FrameType, type Hello, type Msg, type MsgTarget, PRESENCE_COLLECTION, PROTOCOL_VERSION, type Ping, type Pong, type PresenceRecord, RELAY_HASH8, type Reply, type Welcome, builtinRpcs, correctPayload, decodeCall, decodeErrorPayload, decodeFrame, decodeHello, decodeMsg, decodePing, decodePong, decodeReply, decodeWelcome, deltaPayload, encodeCall, encodeCorrectFrame, encodeDeltaFrame, encodeErrorPayload, encodeFrame, encodeHello, encodeMsg, encodePing, encodePong, encodeReply, encodeWelcome, encodeWriteFrame, errorByCode, formatError, isFrameType, isRelayHash8, presenceEntity, readCorrectClientTick, relaySchema, requestOwnership, rpcByIdOf, rpcIdOf, rpcTable, withBuiltins, writePayload };
387
+ export { type Call, type ClientCallable, type Credential, ERROR_CATALOGUE, type ErrorCatalogueEntry, ErrorCode, type ErrorCodeDef, type ErrorCodeName, type ErrorPayload, type Frame, FrameType, type Hello, type Msg, type MsgTarget, PRESENCE_COLLECTION, PROTOCOL_VERSION, type Ping, type Pong, type PresenceRecord, RELAY_HASH8, type Reply, type Welcome, builtinRpcs, correctPayload, decodeCall, decodeErrorPayload, decodeFrame, decodeHello, decodeMsg, decodePing, decodePong, decodeReply, decodeWelcome, deltaPayload, encodeCall, encodeCorrectFrame, encodeDeltaFrame, encodeErrorPayload, encodeFrame, encodeHello, encodeMsg, encodePing, encodePong, encodeReply, encodeWelcome, encodeWriteFrame, errorByCode, formatError, isFrameType, isRelayHash8, presenceEntity, readCorrectAppliedTick, readCorrectClientTick, relaySchema, requestOwnership, rpcByIdOf, rpcIdOf, rpcTable, withBuiltins, writePayload };
package/dist/index.js CHANGED
@@ -64,7 +64,17 @@ var ErrorCode = {
64
64
  E_SLOW_CONSUMER: {
65
65
  code: 20,
66
66
  message: "client cannot keep up with the stream; reconnect for a fresh snapshot"
67
- }
67
+ },
68
+ // Week 13 (D27): every JWT refusal has a name. `E_AUTH` stays the generic ("no secret
69
+ // configured", "wrong project key"); these say WHICH refusal a token hit, because week 12's
70
+ // staging sessions were survivable precisely because refusals said which they were.
71
+ E_TOKEN_EXPIRED: { code: 21, message: "auth token expired" },
72
+ E_TOKEN_INVALID: { code: 22, message: "auth token signature is invalid" },
73
+ E_TOKEN_WRONG_PROJECT: { code: 23, message: "auth token is for another project" },
74
+ E_TOKEN_WRONG_ROOM: { code: 24, message: "auth token is for another room" },
75
+ E_TOKEN_MALFORMED: { code: 25, message: "auth token is malformed: {reason}" },
76
+ E_TOKEN_BAD_ISSUER: { code: 26, message: "auth token issuer {issuer} is not accepted" },
77
+ E_TOKEN_BAD_ALG: { code: 27, message: "auth token algorithm {alg} is not allowed" }
68
78
  };
69
79
  var ERROR_CATALOGUE = Object.keys(ErrorCode).map((name) => ({ name, code: ErrorCode[name].code, message: ErrorCode[name].message }));
70
80
  var BY_CODE = new Map(
@@ -100,9 +110,13 @@ function encodeHello(h) {
100
110
  if (h.credential.kind === "key") {
101
111
  w.u8(0);
102
112
  w.str(h.credential.key);
103
- } else {
113
+ } else if (h.credential.kind === "token") {
104
114
  w.u8(1);
105
115
  w.str(h.credential.token);
116
+ } else {
117
+ w.u8(2);
118
+ w.str(h.credential.key);
119
+ w.str(h.credential.token);
106
120
  }
107
121
  w.str(h.roomId);
108
122
  w.bytes(h.schemaHash8);
@@ -123,6 +137,7 @@ function decodeHello(bytes) {
123
137
  let credential;
124
138
  if (credKind === 0) credential = { kind: "key", key: r.str() };
125
139
  else if (credKind === 1) credential = { kind: "token", token: r.str() };
140
+ else if (credKind === 2) credential = { kind: "jwt", key: r.str(), token: r.str() };
126
141
  else throw new Error(`decodeHello: unknown credential kind ${credKind}`);
127
142
  const roomId = r.str();
128
143
  const schemaHash8 = r.bytes(8);
@@ -207,23 +222,28 @@ function deltaPayload(codecBytes) {
207
222
  function writePayload(codecBytes) {
208
223
  return codecBytes;
209
224
  }
210
- function correctPayload(codecBytes, clientTick) {
211
- const w = new ByteWriter(codecBytes.length + 4);
225
+ function correctPayload(codecBytes, clientTick, appliedTick) {
226
+ const extra = appliedTick === void 0 ? 4 : 8;
227
+ const w = new ByteWriter(codecBytes.length + extra);
212
228
  w.bytes(codecBytes);
213
229
  w.u32(clientTick);
230
+ if (appliedTick !== void 0) w.u32(appliedTick);
214
231
  return w.finish();
215
232
  }
216
233
  function readCorrectClientTick(r) {
217
234
  return r.remaining >= 4 ? r.u32() : void 0;
218
235
  }
236
+ function readCorrectAppliedTick(r) {
237
+ return r.remaining >= 4 ? r.u32() : void 0;
238
+ }
219
239
  function encodeDeltaFrame(codecBytes) {
220
240
  return encodeFrame(FrameType.DELTA, deltaPayload(codecBytes));
221
241
  }
222
242
  function encodeWriteFrame(codecBytes) {
223
243
  return encodeFrame(FrameType.WRITE, writePayload(codecBytes));
224
244
  }
225
- function encodeCorrectFrame(codecBytes, clientTick) {
226
- return encodeFrame(FrameType.CORRECT, correctPayload(codecBytes, clientTick));
245
+ function encodeCorrectFrame(codecBytes, clientTick, appliedTick) {
246
+ return encodeFrame(FrameType.CORRECT, correctPayload(codecBytes, clientTick, appliedTick));
227
247
  }
228
248
 
229
249
  // src/rpc.ts
@@ -421,6 +441,7 @@ export {
421
441
  isFrameType,
422
442
  isRelayHash8,
423
443
  presenceEntity,
444
+ readCorrectAppliedTick,
424
445
  readCorrectClientTick,
425
446
  relaySchema,
426
447
  requestOwnership,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@irtio/protocol",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "irtio wire protocol: frames, framing, session payloads, error codes, built-in presence",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -20,7 +20,7 @@
20
20
  "dist"
21
21
  ],
22
22
  "dependencies": {
23
- "@irtio/schema": "0.1.0"
23
+ "@irtio/schema": "0.3.0"
24
24
  },
25
25
  "scripts": {
26
26
  "build": "tsup",