@freqhole/haruspex 0.3.3 → 0.3.5

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.
@@ -25,6 +25,11 @@ export interface AddPeerContext {
25
25
  /** the pending record a knock-status re-check is running for. */
26
26
  retryPending: PendingRemote | null;
27
27
  remote: SavedRemote | null;
28
+ /** an already-saved remote matching the current attempt's target
29
+ * (set at DUPLICATE_RESULT, consulted at CONNECTION_RESULT - see its
30
+ * handling below for why this doesn't block outright the way an http
31
+ * duplicate still does). */
32
+ existingRemote: SavedRemote | null;
28
33
  }
29
34
  export declare function initialContext(): AddPeerContext;
30
35
  export declare function projectState(ctx: AddPeerContext): AddPeerState;
@@ -1 +1 @@
1
- {"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../../../src/flows/add-peer/machine.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,EACd,UAAU,EACV,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,mBAAmB,OAAO,CAAC;AACxC,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAE1C;oCACoC;AACpC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,CAAC;IAC7D,OAAO,EAAE,OAAO,GAAG,YAAY,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kDAAkD;IAClD,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;IAClC,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,kEAAkE;IAClE,UAAU,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzD,iEAAiE;IACjE,YAAY,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAgB,cAAc,IAAI,cAAc,CAgB/C;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,CA2B9D;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,cAAc,CAAC;IACpB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AA0BD,wBAAgB,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,GAAG,gBAAgB,CAwarF"}
1
+ {"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../../../src/flows/add-peer/machine.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,EACd,UAAU,EACV,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,mBAAmB,OAAO,CAAC;AACxC,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAE1C;oCACoC;AACpC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,CAAC;IAC7D,OAAO,EAAE,OAAO,GAAG,YAAY,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kDAAkD;IAClD,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;IAClC,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,kEAAkE;IAClE,UAAU,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzD,iEAAiE;IACjE,YAAY,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B;;;iCAG6B;IAC7B,cAAc,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED,wBAAgB,cAAc,IAAI,cAAc,CAiB/C;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,CA2B9D;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,cAAc,CAAC;IACpB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AA4BD,wBAAgB,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,GAAG,gBAAgB,CAyerF"}
@@ -38,6 +38,7 @@ export function initialContext() {
38
38
  knockDraft: null,
39
39
  retryPending: null,
40
40
  remote: null,
41
+ existingRemote: null,
41
42
  };
42
43
  }
43
44
  export function projectState(ctx) {
@@ -64,8 +65,8 @@ export function projectState(ctx) {
64
65
  case "knock_sent":
65
66
  return { step: "knock_sent" };
66
67
  case "complete":
67
- // remote is always set by the REMOTE_CREATED transition that enters this step
68
- return { step: "complete", remote: ctx.remote };
68
+ // remote is always set by the transition that enters this step
69
+ return { step: "complete", remote: ctx.remote, alreadyExisted: ctx.existingRemote !== null };
69
70
  }
70
71
  }
71
72
  function noop(ctx) {
@@ -135,18 +136,27 @@ export function transition(ctx, event) {
135
136
  progress: null,
136
137
  serverInfo: null,
137
138
  retryPending: null,
139
+ existingRemote: null,
138
140
  };
139
141
  return { ctx: next, effects: [{ type: "CHECK_DUPLICATE", target }] };
140
142
  }
141
143
  case "DUPLICATE_RESULT": {
142
144
  if (ctx.step !== "testing")
143
145
  return noop(ctx);
144
- if (event.duplicateName) {
145
- return noop(toUrlInput(ctx, `this server is already added as "${event.duplicateName}". each server can only be added once.`));
146
+ // an http duplicate always blocks outright - there's no "maybe it's
147
+ // a player now" reprobe scenario for a plain web remote, and this
148
+ // preserves the tested "each server can only be added once" rule.
149
+ if (event.duplicate && ctx.target?.type === "http") {
150
+ return noop(toUrlInput(ctx, `this server is already added as "${event.duplicate.name}". each server can only be added once.`));
146
151
  }
147
- // persist the attempt BEFORE probing, so a closed tab can resume it
152
+ // a p2p duplicate does NOT block here - it might be an already-
153
+ // paired player whose trust was revoked, or one that's simply in
154
+ // player mode again right now - CONNECTION_RESULT below decides
155
+ // for real off the freshly-probed `player_device` flag, never a
156
+ // stored one (there isn't one - see `SavedRemote`'s doc comment).
157
+ // persist the attempt BEFORE probing, so a closed tab can resume it.
148
158
  return {
149
- ctx,
159
+ ctx: { ...ctx, existingRemote: event.duplicate },
150
160
  effects: [
151
161
  {
152
162
  type: "UPSERT_PENDING",
@@ -162,6 +172,55 @@ export function transition(ctx, event) {
162
172
  if (ctx.step !== "testing")
163
173
  return noop(ctx);
164
174
  const { outcome } = event;
175
+ // a player device is handled identically regardless of `outcome.kind`
176
+ // or whether it's brand-new or already saved: pin-pairing (or, for an
177
+ // already-saved remote, the host UI's live `queryPlayerPresence`
178
+ // check - see AddRemoteModal.tsx's `playerAccess` resource) is the
179
+ // ONLY way in - never the generic knock-request form (`needs_knock`),
180
+ // and never auto-`CREATE_REMOTE`d from a bare `already_authed` (that
181
+ // only proves this peer redeemed SOME pairing code in the past, not
182
+ // that it's currently in the player's live, ephemeral session - see
183
+ // this file's own history for that exact regression). always routes
184
+ // to "auth", on any non-failed outcome, never re-creating the remote
185
+ // from here either way.
186
+ if (outcome.kind !== "failed" && outcome.serverInfo.player_device) {
187
+ return {
188
+ ctx: {
189
+ ...ctx,
190
+ step: "auth",
191
+ serverInfo: outcome.serverInfo,
192
+ error: null,
193
+ progress: null,
194
+ },
195
+ effects: [
196
+ {
197
+ type: "UPSERT_PENDING",
198
+ peerAddr: addrKey(ctx),
199
+ patch: { stage: "connected", ...serverInfoPatch(outcome.serverInfo) },
200
+ },
201
+ ],
202
+ };
203
+ }
204
+ if (ctx.existingRemote && outcome.kind !== "failed") {
205
+ // already have this (non-player) remote saved, and whoami
206
+ // already succeeds against it - nothing new to do. gracefully
207
+ // land on "complete" instead of re-running knock/auth (or
208
+ // erroring on a duplicate-peer_addr create) for something the
209
+ // user already has access to.
210
+ return {
211
+ ctx: {
212
+ ...ctx,
213
+ step: "complete",
214
+ remote: ctx.existingRemote,
215
+ error: null,
216
+ progress: null,
217
+ },
218
+ effects: [
219
+ { type: "DELETE_PENDING_BY_ADDR", peerAddr: addrKey(ctx) },
220
+ { type: "SCHEDULE_TIMER", id: DISMISS_TIMER_ID, ms: COMPLETE_DISMISS_MS },
221
+ ],
222
+ };
223
+ }
165
224
  switch (outcome.kind) {
166
225
  case "already_authed":
167
226
  return {
@@ -224,7 +283,11 @@ export function transition(ctx, event) {
224
283
  return noop({ ...ctx, error: "no peer address available" });
225
284
  }
226
285
  return {
227
- ctx: { ...ctx, knockDraft: { username: event.username, message: event.message }, error: null },
286
+ ctx: {
287
+ ...ctx,
288
+ knockDraft: { username: event.username, message: event.message },
289
+ error: null,
290
+ },
228
291
  effects: [
229
292
  {
230
293
  type: "SEND_KNOCK",
@@ -442,7 +505,10 @@ export function transition(ctx, event) {
442
505
  };
443
506
  case "denied":
444
507
  return {
445
- ctx: { ...toUrlInput(ctx, "your access request was rejected by the server admin"), retryPending: null },
508
+ ctx: {
509
+ ...toUrlInput(ctx, "your access request was rejected by the server admin"),
510
+ retryPending: null,
511
+ },
446
512
  effects: pending
447
513
  ? [
448
514
  {
@@ -71,20 +71,36 @@ describe("SUBMIT_URL classification", () => {
71
71
  });
72
72
  });
73
73
  describe("duplicate rejection", () => {
74
- it("returns to url/input with a specific error on a duplicate", () => {
74
+ it("an http duplicate returns to url/input with a specific error", () => {
75
75
  const { ctx, effects } = run([
76
- { type: "SUBMIT_URL", input: NODE_ID },
77
- { type: "DUPLICATE_RESULT", duplicateName: "my server" },
76
+ { type: "SUBMIT_URL", input: "https://music.example.com" },
77
+ { type: "DUPLICATE_RESULT", duplicate: { remote_id: "r0", name: "my server" } },
78
78
  ]);
79
79
  expect(ctx.step).toBe("url");
80
80
  expect(ctx.subStep).toBe("input");
81
81
  expect(ctx.error).toContain('already added as "my server"');
82
82
  expect(effects).toEqual([]);
83
83
  });
84
+ it("a p2p duplicate does NOT block outright - it proceeds to a connection probe", () => {
85
+ const { ctx, effects } = run([
86
+ { type: "SUBMIT_URL", input: NODE_ID },
87
+ {
88
+ type: "DUPLICATE_RESULT",
89
+ duplicate: { remote_id: "r0", name: "old player", peer_addr: NODE_ID },
90
+ },
91
+ ]);
92
+ expect(ctx.step).toBe("testing");
93
+ expect(ctx.existingRemote).toEqual({ remote_id: "r0", name: "old player", peer_addr: NODE_ID });
94
+ expect(effectTypes(effects)).toEqual([
95
+ "UPSERT_PENDING",
96
+ "CLEAR_QUERY_PARAM",
97
+ "CHECK_CONNECTION",
98
+ ]);
99
+ });
84
100
  it("persists the pending record (stage testing) BEFORE probing", () => {
85
101
  const { effects } = run([
86
102
  { type: "SUBMIT_URL", input: NODE_ID },
87
- { type: "DUPLICATE_RESULT", duplicateName: null },
103
+ { type: "DUPLICATE_RESULT", duplicate: null },
88
104
  ]);
89
105
  // order is load-bearing: persist, clear query param, then probe
90
106
  expect(effectTypes(effects)).toEqual([
@@ -98,10 +114,69 @@ describe("duplicate rejection", () => {
98
114
  expect(upsert.patch.error_message).toBeNull();
99
115
  });
100
116
  });
117
+ describe("re-scanning an already-saved p2p peer", () => {
118
+ const existingPlayer = {
119
+ remote_id: "r0",
120
+ name: "old player",
121
+ peer_addr: NODE_ID,
122
+ };
123
+ const toReprobe = [
124
+ { type: "SUBMIT_URL", input: NODE_ID },
125
+ { type: "DUPLICATE_RESULT", duplicate: existingPlayer },
126
+ ];
127
+ it("a live player_device probe re-opens the pairing ui, even though it's already saved", () => {
128
+ const { ctx } = run([
129
+ ...toReprobe,
130
+ {
131
+ type: "CONNECTION_RESULT",
132
+ outcome: { kind: "needs_auth", serverInfo: { ...INFO, player_device: true } },
133
+ },
134
+ ]);
135
+ expect(ctx.step).toBe("auth");
136
+ expect(ctx.serverInfo?.player_device).toBe(true);
137
+ });
138
+ it("a live probe that ISN'T a player gracefully completes against the existing remote", () => {
139
+ const { ctx, effects } = run([
140
+ ...toReprobe,
141
+ { type: "CONNECTION_RESULT", outcome: { kind: "needs_auth", serverInfo: INFO } },
142
+ ]);
143
+ expect(projectState(ctx)).toEqual({
144
+ step: "complete",
145
+ remote: existingPlayer,
146
+ alreadyExisted: true,
147
+ });
148
+ expect(effects).toEqual([
149
+ { type: "DELETE_PENDING_BY_ADDR", peerAddr: NODE_ID },
150
+ { type: "SCHEDULE_TIMER", id: DISMISS_TIMER_ID, ms: COMPLETE_DISMISS_MS },
151
+ ]);
152
+ });
153
+ it("already_authed against an existing, non-player remote also gracefully completes", () => {
154
+ const { ctx } = run([
155
+ ...toReprobe,
156
+ { type: "CONNECTION_RESULT", outcome: { kind: "already_authed", serverInfo: INFO } },
157
+ ]);
158
+ expect(ctx.step).toBe("complete");
159
+ });
160
+ it("already_authed against an existing PLAYER remote routes to auth (not complete, not create) - regression: whoami only proves base peer trust, not current player-session membership, so it can't be trusted to skip the pin/session-access check (queryPlayerPresence, done by the host ui in the auth step) the way it safely can for a plain remote", () => {
161
+ const { ctx, effects } = run([
162
+ ...toReprobe,
163
+ {
164
+ type: "CONNECTION_RESULT",
165
+ outcome: { kind: "already_authed", serverInfo: { ...INFO, player_device: true } },
166
+ },
167
+ ]);
168
+ expect(ctx.step).toBe("auth");
169
+ expect(ctx.serverInfo?.player_device).toBe(true);
170
+ expect(ctx.existingRemote).toEqual(existingPlayer);
171
+ const upsert = effects[0];
172
+ expect(upsert.type).toBe("UPSERT_PENDING");
173
+ expect(upsert.patch.stage).toBe("connected");
174
+ });
175
+ });
101
176
  describe("connection outcomes", () => {
102
177
  const toProbe = [
103
178
  { type: "SUBMIT_URL", input: NODE_ID },
104
- { type: "DUPLICATE_RESULT", duplicateName: null },
179
+ { type: "DUPLICATE_RESULT", duplicate: null },
105
180
  ];
106
181
  it("already_authed short-circuits to CREATE_REMOTE", () => {
107
182
  const { ctx, effects } = run([
@@ -151,7 +226,7 @@ describe("connection outcomes", () => {
151
226
  describe("knock flow", () => {
152
227
  const toKnockForm = [
153
228
  { type: "SUBMIT_URL", input: NODE_ID },
154
- { type: "DUPLICATE_RESULT", duplicateName: null },
229
+ { type: "DUPLICATE_RESULT", duplicate: null },
155
230
  {
156
231
  type: "CONNECTION_RESULT",
157
232
  outcome: { kind: "needs_knock", serverInfo: { ...INFO, knocking_enabled: true } },
@@ -250,12 +325,12 @@ describe("knock-status re-check (RETRY_PENDING resume)", () => {
250
325
  describe("auth step", () => {
251
326
  const toP2pAuth = [
252
327
  { type: "SUBMIT_URL", input: NODE_ID },
253
- { type: "DUPLICATE_RESULT", duplicateName: null },
328
+ { type: "DUPLICATE_RESULT", duplicate: null },
254
329
  { type: "CONNECTION_RESULT", outcome: { kind: "needs_auth", serverInfo: INFO } },
255
330
  ];
256
331
  const toHttpAuth = [
257
332
  { type: "SUBMIT_URL", input: "https://music.example.com" },
258
- { type: "DUPLICATE_RESULT", duplicateName: null },
333
+ { type: "DUPLICATE_RESULT", duplicate: null },
259
334
  { type: "CONNECTION_RESULT", outcome: { kind: "needs_auth", serverInfo: INFO } },
260
335
  ];
261
336
  it("p2p login is an explicit error", () => {
@@ -284,7 +359,10 @@ describe("auth step", () => {
284
359
  ]);
285
360
  });
286
361
  it("http auth emits AUTH_HTTP", () => {
287
- const { effects } = run([...toHttpAuth, { type: "SUBMIT_AUTH", mode: "login", username: "ed" }]);
362
+ const { effects } = run([
363
+ ...toHttpAuth,
364
+ { type: "SUBMIT_AUTH", mode: "login", username: "ed" },
365
+ ]);
288
366
  expect(effects).toEqual([
289
367
  {
290
368
  type: "AUTH_HTTP",
@@ -319,12 +397,12 @@ describe("completion", () => {
319
397
  const remote = { remote_id: "r1", name: "test peer", peer_addr: NODE_ID };
320
398
  const toCreate = [
321
399
  { type: "SUBMIT_URL", input: NODE_ID },
322
- { type: "DUPLICATE_RESULT", duplicateName: null },
400
+ { type: "DUPLICATE_RESULT", duplicate: null },
323
401
  { type: "CONNECTION_RESULT", outcome: { kind: "already_authed", serverInfo: INFO } },
324
402
  ];
325
403
  it("REMOTE_CREATED enters complete, deletes the pending record, and schedules the dismiss timer", () => {
326
404
  const { ctx, effects } = run([...toCreate, { type: "REMOTE_CREATED", ok: true, remote }]);
327
- expect(projectState(ctx)).toEqual({ step: "complete", remote });
405
+ expect(projectState(ctx)).toEqual({ step: "complete", remote, alreadyExisted: false });
328
406
  expect(effects).toEqual([
329
407
  { type: "DELETE_PENDING_BY_ADDR", peerAddr: NODE_ID },
330
408
  { type: "SCHEDULE_TIMER", id: DISMISS_TIMER_ID, ms: COMPLETE_DISMISS_MS },
@@ -353,7 +431,7 @@ describe("COMPLETE_PEER_ADDR external push", () => {
353
431
  it("completes from mid-knock when the address matches the staged peer", () => {
354
432
  const { ctx, effects } = run([
355
433
  { type: "SUBMIT_URL", input: NODE_ID },
356
- { type: "DUPLICATE_RESULT", duplicateName: null },
434
+ { type: "DUPLICATE_RESULT", duplicate: null },
357
435
  {
358
436
  type: "CONNECTION_RESULT",
359
437
  outcome: { kind: "needs_knock", serverInfo: { ...INFO, knocking_enabled: true } },
@@ -393,7 +471,7 @@ describe("back / cancel / lifecycle", () => {
393
471
  it("BACK from auth returns to url/input without effects", () => {
394
472
  const { ctx, effects } = run([
395
473
  { type: "SUBMIT_URL", input: NODE_ID },
396
- { type: "DUPLICATE_RESULT", duplicateName: null },
474
+ { type: "DUPLICATE_RESULT", duplicate: null },
397
475
  { type: "CONNECTION_RESULT", outcome: { kind: "needs_auth", serverInfo: INFO } },
398
476
  { type: "BACK" },
399
477
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/flows/add-peer/runner.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,eAAe,EACf,YAAY,EAKb,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,WAAW;IAC1B,KAAK,IAAI,YAAY,CAAC;IACtB;;iBAEa;IACb,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,aAAa,EAAE,CAAC;IAC3C;6DACyD;IACzD,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,OAAO,IAAI,IAAI,CAAC;CACjB;AAuBD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,CAiUpE"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/flows/add-peer/runner.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,eAAe,EACf,YAAY,EAKb,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,WAAW;IAC1B,KAAK,IAAI,YAAY,CAAC;IACtB;;iBAEa;IACb,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,aAAa,EAAE,CAAC;IAC3C;6DACyD;IACzD,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,OAAO,IAAI,IAAI,CAAC;CACjB;AAuBD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,CA4UpE"}
@@ -14,7 +14,7 @@
14
14
  // `testing` and by MODAL_CLOSE) bumps it, and a superseded run's outcome
15
15
  // event is discarded instead of being fed to the machine.
16
16
  import { parsePeerAddress } from "../../share/peer-addr.js";
17
- import { initialContext, projectState, transition, } from "./machine.js";
17
+ import { initialContext, projectState, transition } from "./machine.js";
18
18
  function describeConnectError(err) {
19
19
  const msg = err instanceof Error ? err.message : String(err);
20
20
  if (/timeout/i.test(msg)) {
@@ -72,10 +72,14 @@ export function createAddPeerFlow(deps) {
72
72
  catch (err) {
73
73
  // an auth rejection on the probe itself can still mean "knockable":
74
74
  // the hello surface is public, so re-read it to route to the knock
75
- // form instead of a dead-end error.
75
+ // form instead of a dead-end error. a player_device peer routes to
76
+ // auth instead (see the non-error path below for why).
76
77
  if (isAuthRejection(err)) {
77
78
  try {
78
79
  const helloInfo = await deps.getServerInfo(target);
80
+ if (helloInfo?.player_device) {
81
+ return { kind: "needs_auth", serverInfo: helloInfo };
82
+ }
79
83
  if (helloInfo?.knocking_enabled) {
80
84
  return {
81
85
  kind: "needs_knock",
@@ -105,6 +109,14 @@ export function createAddPeerFlow(deps) {
105
109
  }
106
110
  if (authed)
107
111
  return { kind: "already_authed", serverInfo: info };
112
+ // a player_device peer (e.g. rathole) can ALSO have knocking enabled -
113
+ // it's genuinely both a real remote and a pairable player (see
114
+ // grimoire's server_info() doc comment) - route to "auth" either way,
115
+ // since that's the step that renders the player pairing ui (with its
116
+ // own "don't have an invite code? request access" knock fallback
117
+ // link when knocking_enabled), not the separate knock-only step.
118
+ if (info.player_device)
119
+ return { kind: "needs_auth", serverInfo: info };
108
120
  if (info.knocking_enabled)
109
121
  return { kind: "needs_knock", serverInfo: info };
110
122
  return { kind: "needs_auth", serverInfo: info };
@@ -194,7 +206,7 @@ export function createAddPeerFlow(deps) {
194
206
  const duplicate = target.type === "http"
195
207
  ? remotes.find((r) => r.base_url === target.url)
196
208
  : remotes.find((r) => r.peer_addr === target.peerAddr);
197
- await feed({ type: "DUPLICATE_RESULT", duplicateName: duplicate?.name ?? null });
209
+ await feed({ type: "DUPLICATE_RESULT", duplicate: duplicate ?? null });
198
210
  return;
199
211
  }
200
212
  case "UPSERT_PENDING":
@@ -97,17 +97,58 @@ describe("createAddPeerFlow (bundled runner)", () => {
97
97
  expect(onSuccess).toHaveBeenCalledWith(expect.objectContaining({ peer_addr: NODE_ID }));
98
98
  expect(flow.state().step).toBe("url");
99
99
  });
100
- it("rejects a duplicate before persisting anything", async () => {
100
+ it("an http duplicate is rejected before persisting anything", async () => {
101
101
  const { deps, pendingStore } = makeDeps({
102
- getAllRemotes: async () => [{ remote_id: "r0", name: "old hub", peer_addr: NODE_ID }],
102
+ getAllRemotes: async () => [
103
+ { remote_id: "r0", name: "old hub", base_url: "https://music.example.com" },
104
+ ],
103
105
  });
104
106
  const flow = createAddPeerFlow(deps);
105
- await flow.dispatch({ type: "SUBMIT_URL", input: NODE_ID });
107
+ await flow.dispatch({ type: "SUBMIT_URL", input: "https://music.example.com" });
106
108
  const state = flow.state();
107
109
  expect(state.step).toBe("url");
108
110
  expect(state.step === "url" && state.error).toContain('already added as "old hub"');
109
111
  expect(pendingStore.size).toBe(0);
110
112
  });
113
+ it("a p2p duplicate that probes as a non-player gracefully completes against the existing remote", async () => {
114
+ const existing = { remote_id: "r0", name: "old player", peer_addr: NODE_ID };
115
+ const { deps, pendingStore } = makeDeps({
116
+ getAllRemotes: async () => [existing],
117
+ });
118
+ const flow = createAddPeerFlow(deps);
119
+ await flow.dispatch({ type: "SUBMIT_URL", input: NODE_ID });
120
+ const state = flow.state();
121
+ expect(state.step).toBe("complete");
122
+ expect(state.step === "complete" && state.remote).toEqual(existing);
123
+ // no new remote was created, no pending record left behind
124
+ expect(pendingStore.size).toBe(0);
125
+ });
126
+ it("a p2p duplicate that probes as a player re-opens the pairing ui instead", async () => {
127
+ const existing = {
128
+ remote_id: "r0",
129
+ name: "old player",
130
+ peer_addr: NODE_ID,
131
+ };
132
+ const { deps } = makeDeps({
133
+ getAllRemotes: async () => [existing],
134
+ getServerInfo: async () => ({ ...INFO, player_device: true }),
135
+ });
136
+ const flow = createAddPeerFlow(deps);
137
+ await flow.dispatch({ type: "SUBMIT_URL", input: NODE_ID });
138
+ const state = flow.state();
139
+ expect(state.step).toBe("auth");
140
+ expect(state.step === "auth" && state.serverInfo?.player_device).toBe(true);
141
+ });
142
+ it("routes to auth (not the knock form) for a player_device peer, even when knocking is also enabled", async () => {
143
+ const { deps } = makeDeps({
144
+ getServerInfo: async () => ({ ...INFO, player_device: true, knocking_enabled: true }),
145
+ });
146
+ const flow = createAddPeerFlow(deps);
147
+ await flow.dispatch({ type: "SUBMIT_URL", input: NODE_ID });
148
+ const state = flow.state();
149
+ expect(state.step).toBe("auth");
150
+ expect(state.step === "auth" && state.serverInfo?.player_device).toBe(true);
151
+ });
111
152
  it("marks the pending record failed with a mapped error when the probe throws", async () => {
112
153
  const { deps, pendingStore } = makeDeps({
113
154
  getServerInfo: async () => {
@@ -38,7 +38,10 @@ export interface PeerServerInfo {
38
38
  * remote. absent/false for every real grimoire-backed remote. */
39
39
  player_device?: boolean | null;
40
40
  }
41
- /** a saved remote, as returned by the app's remote store. */
41
+ /** a saved remote, as returned by the app's remote store. "is this a
42
+ * player" is deliberately NOT one of these fields - it's a live,
43
+ * point-in-time fact (see `ConnectionOutcome`'s `serverInfo.player_device`),
44
+ * never a persisted property of the remote itself. */
42
45
  export interface SavedRemote {
43
46
  remote_id: string;
44
47
  name: string;
@@ -68,6 +71,7 @@ export type AddPeerState = {
68
71
  } | {
69
72
  step: "complete";
70
73
  remote: SavedRemote;
74
+ alreadyExisted: boolean;
71
75
  };
72
76
  /** outcome of a connection probe (hello + whoami against the target). */
73
77
  export type ConnectionOutcome = {
@@ -145,7 +149,7 @@ export type AddPeerEvent = {
145
149
  records: PendingRemote[];
146
150
  } | {
147
151
  type: "DUPLICATE_RESULT";
148
- duplicateName: string | null;
152
+ duplicate: SavedRemote | null;
149
153
  } | {
150
154
  type: "CONNECTION_RESULT";
151
155
  outcome: ConnectionOutcome;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/flows/add-peer/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/flows/add-peer/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freqhole/haruspex",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "freqhole auth domain: identity, webauthn, knock + friendz protocols, permissions",