0.10.6: Sync sign-in comes back through 127.0.0.1 #37

Merged
root-at-skic merged 1 commits from fix/sync-signin-loopback into main 2026-11-08 10:06:32 -08:00
4 changed files with 43 additions and 6 deletions

No files matched your search

+4
View File
@@ -1,5 +1,9 @@
# Changelog # 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 ## 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. - **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.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"version": "0.10.5", "version": "0.10.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"version": "0.10.5", "version": "0.10.6",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.20.4", "@codemirror/autocomplete": "^6.20.4",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"displayName": "Archify Diagram Viewer", "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.", "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", "publisher": "root-at-skic",
"license": "MIT", "license": "MIT",
"sponsor": { "sponsor": {
+36 -3
View File
@@ -1,4 +1,5 @@
import * as crypto from 'node:crypto'; import * as crypto from 'node:crypto';
import * as http from 'node:http';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { SyncApi, SyncApiError } from './api'; import { SyncApi, SyncApiError } from './api';
import { Account, TokenResponse } from './protocol'; 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 verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url'); const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
const state = crypto.randomBytes(16).toString('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`)); // On the desktop the browser comes back to a port on this machine: no
const redirectUri = callback.toString(true); // 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({ const query = new URLSearchParams({
response_type: 'code', response_type: 'code',
redirect_uri: redirectUri, redirect_uri: redirectUri,
@@ -119,6 +125,7 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl
const done = () => { const done = () => {
clearTimeout(timer); clearTimeout(timer);
this.pending.delete(state); this.pending.delete(state);
loopback?.close();
}; };
this.pending.set(state, { this.pending.set(state, {
resolve: (c) => (done(), resolve(c)), resolve: (c) => (done(), resolve(c)),
@@ -137,9 +144,35 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl
return session; 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
? '<!doctype html><meta charset="utf-8"><title>Archify Sync</title><body style="font:15px system-ui;margin:48px auto;max-width:420px"><h2>Signed in</h2><p>You can close this tab and go back to the editor.</p></body>'
: '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=`. */ /** The browser came back: `…/auth?code=&state=`. */
handleAuthUri(uri: vscode.Uri): void { 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 state = query.get('state') ?? '';
const code = query.get('code'); const code = query.get('code');
const pending = this.pending.get(state); const pending = this.pending.get(state);