From 95c6e601994f704e2e476f79ad75e0fdf7a6aa87 Mon Sep 17 00:00:00 2001 From: Root At Skic Date: Sun, 8 Nov 2026 20:05:11 +0200 Subject: [PATCH] 0.10.6: Sync sign-in comes back through 127.0.0.1 Browsers open a vscode:// link only after a click on the page. With a live identity provider session the sign-in ran without one, so the editor kept waiting. On the desktop the browser now returns to a one-off listener on 127.0.0.1; remote and web editors keep the URI handler. --- CHANGELOG.md | 4 ++++ package-lock.json | 4 ++-- package.json | 2 +- src/sync/auth.ts | 39 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d57189c..77882ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.10.6 — 2026-11-08 + +- **Sync sign-in comes back by itself.** The browser now returns the sign-in to a one-off port on this machine (`127.0.0.1`) instead of a `vscode://` link. Browsers open such links only after a click, so when you were already signed in to the identity provider the editor kept waiting. Remote and web editors still use the link. + ## 0.10.5 — 2026-11-08 - **Sync button fixed.** It is now an icon only (a cloud; green with a broadcast icon once the model is shared), the same size on every page and theme. On a diagram it sits in the toolbar after **Export** instead of over it; in narrow windows and high contrast themes it no longer shrinks or loses its colour. diff --git a/package-lock.json b/package-lock.json index cc73539..0a0a47e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "archify-vscode-ext", - "version": "0.10.5", + "version": "0.10.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "archify-vscode-ext", - "version": "0.10.5", + "version": "0.10.6", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.20.4", diff --git a/package.json b/package.json index 25c402e..692fcc4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "archify-vscode-ext", "displayName": "Archify Diagram Viewer", "description": "Archify diagrams in VS Code. Write diagrams as .archify files in YAML: they open as the interactive diagram, with live updates, validation, templates and completion. Also previews Archify's own JSON files (*.architecture.json, …) and opens rendered Archify HTML.", - "version": "0.10.5", + "version": "0.10.6", "publisher": "root-at-skic", "license": "MIT", "sponsor": { diff --git a/src/sync/auth.ts b/src/sync/auth.ts index 6da9378..ed6e636 100644 --- a/src/sync/auth.ts +++ b/src/sync/auth.ts @@ -1,4 +1,5 @@ import * as crypto from 'node:crypto'; +import * as http from 'node:http'; import * as vscode from 'vscode'; import { SyncApi, SyncApiError } from './api'; import { Account, TokenResponse } from './protocol'; @@ -100,8 +101,13 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl const verifier = crypto.randomBytes(32).toString('base64url'); const challenge = crypto.createHash('sha256').update(verifier).digest('base64url'); const state = crypto.randomBytes(16).toString('base64url'); - const callback = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://${this.context.extension.id}/auth`)); - const redirectUri = callback.toString(true); + // On the desktop the browser comes back to a port on this machine: no + // click needed, and it reaches this window. Remote and web editors use + // the extension's URI handler (vscode://…/auth) instead. + const loopback = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Desktop ? await this.listen().catch(() => undefined) : undefined; + const redirectUri = loopback + ? `http://127.0.0.1:${loopback.port}/auth` + : (await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://${this.context.extension.id}/auth`))).toString(true); const query = new URLSearchParams({ response_type: 'code', redirect_uri: redirectUri, @@ -119,6 +125,7 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl const done = () => { clearTimeout(timer); this.pending.delete(state); + loopback?.close(); }; this.pending.set(state, { resolve: (c) => (done(), resolve(c)), @@ -137,9 +144,35 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl return session; } + /** A one-off HTTP listener on 127.0.0.1 for the browser's way back; closed when the sign-in ends. */ + private listen(): Promise<{ port: number; close: () => void }> { + return new Promise((resolve, reject) => { + const server = http.createServer((req, res) => { + const url = new URL(req.url ?? '/', 'http://127.0.0.1'); + const known = url.pathname === '/auth' && this.pending.has(url.searchParams.get('state') ?? ''); + res.writeHead(known ? 200 : 404, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' }); + res.end( + known + ? 'Archify Sync

Signed in

You can close this tab and go back to the editor.

' + : 'Not found', + ); + if (known) this.completeSignIn(url.searchParams); + }); + server.once('error', reject); + server.listen(0, '127.0.0.1', () => { + const address = server.address(); + if (!address || typeof address === 'string') return reject(new Error('no port')); + resolve({ port: address.port, close: () => server.close() }); + }); + }); + } + /** The browser came back: `…/auth?code=&state=`. */ handleAuthUri(uri: vscode.Uri): void { - const query = new URLSearchParams(uri.query); + this.completeSignIn(new URLSearchParams(uri.query)); + } + + private completeSignIn(query: URLSearchParams): void { const state = query.get('state') ?? ''; const code = query.get('code'); const pending = this.pending.get(state);