thinkpool-pair 0.7.3 → 0.7.4

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/account.mjs +27 -19
  2. package/package.json +1 -1
package/account.mjs CHANGED
@@ -19,27 +19,35 @@ const VERSION = (() => { try { return JSON.parse(fs.readFileSync(new URL('./pack
19
19
 
20
20
  const BRIDGE = fileURLToPath(new URL('./bridge.mjs', import.meta.url))
21
21
 
22
- // ── login: web hands the bridge a session over an ephemeral realtime channel ──
23
- // The bridge prints a one-time URL with a high-entropy code; the signed-in web
24
- // page broadcasts {refresh_token,…} to tplink:<code> after the user approves.
25
- // (Phase-1 handoff an edge-function device-code flow can harden this later.)
22
+ // ── login: OAuth-style DEVICE-CODE flow (hardened) ──────────────────────────
23
+ // The bridge asks Postgres (start_device_code) for a secret device_code + a short
24
+ // human user_code, prints the user_code + URL, then POLLS claim_device_code with
25
+ // the secret until the signed-in web has approved (approve_device_code stores the
26
+ // browser's session). The session is delivered only over authenticated TLS RPCs
27
+ // + a single-use secret — never broadcast on a public channel.
26
28
  export async function runLogin(SUPABASE_URL, SUPABASE_ANON, WEB_BASE) {
27
29
  const sb = createClient(SUPABASE_URL, SUPABASE_ANON, { auth: { persistSession: false } })
28
- const code = randomUUID().replace(/-/g, '').slice(0, 12)
29
- const ch = sb.channel(`tplink:${code}`, { config: { broadcast: { self: false } } })
30
- let done = false
31
- ch.on('broadcast', { event: 'session' }, async ({ payload }) => {
32
- if (done || !payload?.refresh_token) return
33
- done = true
34
- saveAuth(payload)
35
- process.stderr.write(`\n linked as ${payload.email || 'your account'}. You can close the browser tab.\n Now run: npx thinkpool-pair\n`)
36
- try { await sb.removeChannel(ch) } catch { /* noop */ }
37
- process.exit(0)
38
- })
39
- await new Promise((res) => ch.subscribe((s) => { if (s === 'SUBSCRIBED') res() }))
40
- const url = `${WEB_BASE}/code/link?c=${code}`
41
- process.stderr.write(`\n ◆ Link this device to your ThinkPool account.\n Open (signed in) and approve:\n\n ${url}\n\n Code: ${code} — waiting up to 5 min…\n`)
42
- setTimeout(() => { if (!done) { process.stderr.write('\n ◇ link timed out — run `npx thinkpool-pair login` again.\n'); process.exit(1) } }, 5 * 60 * 1000)
30
+ const { data, error } = await sb.rpc('start_device_code')
31
+ const row = Array.isArray(data) ? data[0] : data
32
+ if (error || !row?.device_code) { process.stderr.write(`\n ✗ couldn't start login: ${error?.message || 'no response'}\n`); process.exit(1) }
33
+ const { device_code, user_code } = row
34
+ const url = `${WEB_BASE}/code/link?c=${encodeURIComponent(user_code)}`
35
+ process.stderr.write(`\n ◆ Link this device to your ThinkPool account.\n Open (signed in) and approve:\n\n ${url}\n\n code: ${user_code}\n\n Waiting…\n`)
36
+ const started = Date.now()
37
+ const poll = async () => {
38
+ if (Date.now() - started > 5 * 60 * 1000) { process.stderr.write('\n ◇ link timed out — run `npx thinkpool-pair login` again.\n'); process.exit(1) }
39
+ try {
40
+ const { data: r } = await sb.rpc('claim_device_code', { p_device_code: device_code })
41
+ if (r?.status === 'approved' && r.session?.refresh_token) {
42
+ saveAuth(r.session)
43
+ process.stderr.write(`\n ◆ linked as ${r.session.email || 'your account'}. You can close the browser tab.\n Now run: npx thinkpool-pair\n`)
44
+ process.exit(0)
45
+ }
46
+ if (r?.status === 'expired' || r?.status === 'not_found') { process.stderr.write('\n ◇ link expired — run `npx thinkpool-pair login` again.\n'); process.exit(1) }
47
+ } catch { /* transient — keep polling */ }
48
+ setTimeout(poll, 3000)
49
+ }
50
+ poll()
43
51
  await new Promise(() => {})
44
52
  }
45
53
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "description": "Share a local coding-agent CLI (Claude Code, Codex, Gemini, Aider, …) into a ThinkPool Code room, live.",
5
5
  "type": "module",
6
6
  "bin": {