@aiam/ciba 0.8.1 → 0.8.3

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.
Files changed (2) hide show
  1. package/ciba.mjs +22 -7
  2. package/package.json +1 -1
package/ciba.mjs CHANGED
@@ -496,10 +496,6 @@ function startDaemon(provider, deviceDoc, privateKey, serverUrl) {
496
496
  dlog(`cache miss; writing requests[${newRid}] attrs=${JSON.stringify(attrs)}`);
497
497
  requests.set(newRid, { ...attrs, status: 'pending', created_at: new Date().toISOString() });
498
498
 
499
- // Watch both token:<newRid> AND resources[requestedResource] for
500
- // the result. resources map updates survive WS reconnects because
501
- // Yjs re-syncs the full doc state; token:<newRid> may be missed
502
- // if the write happened during a reconnect gap.
503
499
  const prevTokenMapName = resourcesMap.get(requestedResource);
504
500
  const newTokenMap = deviceDoc.getMap(`token:${newRid}`);
505
501
 
@@ -512,14 +508,28 @@ function startDaemon(provider, deviceDoc, privateKey, serverUrl) {
512
508
  30_000
513
509
  ).then((newMapName) => {
514
510
  const m = deviceDoc.getMap(newMapName);
515
- // wait for ciphertext to land on the new map (may arrive right after resources update)
516
511
  return firstInYMap(m, (key) => key === 'ciphertext' || key === 'error', 5_000)
517
512
  .then(() => m).catch(() => m);
518
513
  });
519
514
 
515
+ // Poll every 2s — handles updates that arrived during a WS reconnect
516
+ // gap where Yjs observers were missed.
517
+ const viaPoll = new Promise((resolve, reject) => {
518
+ const iv = setInterval(() => {
519
+ const name = resourcesMap.get(requestedResource);
520
+ if (name && name !== prevTokenMapName) {
521
+ const m = deviceDoc.getMap(name);
522
+ if (m.get('ciphertext')) { clearInterval(iv); resolve(m); return; }
523
+ }
524
+ if (newTokenMap.get('ciphertext')) { clearInterval(iv); resolve(newTokenMap); }
525
+ }, 2000);
526
+ setTimeout(() => { clearInterval(iv); reject(new Error('Timeout')); }, 31_000);
527
+ });
528
+
520
529
  Promise.race([
521
530
  viaRid.then((map) => ({ src: 'rid', map })),
522
531
  viaResources.then((map) => ({ src: 'resources', map })),
532
+ viaPoll.then((map) => ({ src: 'poll', map })),
523
533
  ])
524
534
  .then(({ src, map }) => {
525
535
  dlog(`token resolved via ${src} for ${requestedResource}`);
@@ -752,9 +762,14 @@ const logoutCmd = defineCommand({
752
762
  const stopCmd = defineCommand({
753
763
  meta: { description: 'Stop daemon and clear session' },
754
764
  args: {},
755
- run() {
756
- const cfg = loadConfig();
765
+ async run() {
757
766
  if (cfg.pid) { try { process.kill(parseInt(cfg.pid)); } catch {} }
767
+ // Kill all orphaned ciba daemon processes (previous --persist runs that
768
+ // were never approved or whose parent exited without storing the PID).
769
+ try {
770
+ const { execFileSync } = await import('node:child_process');
771
+ execFileSync('pkill', ['-f', 'ciba login --daemon'], { stdio: 'ignore' });
772
+ } catch { /* pkill not found or no matching processes */ }
758
773
  if (existsSync(SOCKET_PATH)) unlinkSync(SOCKET_PATH);
759
774
  const sessionFile = join(CONFIG_DIR, 'session');
760
775
  if (existsSync(sessionFile)) unlinkSync(sessionFile);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiam/ciba",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "OAuth 2.0 Device Authorization Grant CLI with cross-device push approval (Yjs sync, ECDH-encrypted token delivery, persistent device id)",
5
5
  "type": "module",
6
6
  "bin": {