@ariobarin/glossa 0.2.0 → 0.2.1
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.
- package/README.md +1 -1
- package/dist/app.js +90 -112
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Expose only a narrow project you trust. Keep credentials and regulated or sensit
|
|
|
28
28
|
|
|
29
29
|
Pass a directory to expose another project, and add `--label <name>` when several online workspaces need a non-sensitive identifier. The terminal shows the selected project, access profile, connection state, and recent activity. Press `q` or Ctrl+C to disconnect.
|
|
30
30
|
|
|
31
|
-
The first time a computer runs Glossa,
|
|
31
|
+
The first time a computer runs Glossa, follow its browser authorization link. A headless or SSH-only machine prints the link so you can open it elsewhere. The CLI uses that authorization once to enroll the computer and stores only its revocable device credential, not a Google or Auth0 refresh token. Later workspace sessions reuse that pairing without user sign-in.
|
|
32
32
|
|
|
33
33
|
Account-level CLI actions such as `glossa status` still open sign-in when needed. Run `glossa unpair` to revoke this computer and remove its local pairing before moving it to another Glossa account.
|
|
34
34
|
|
package/dist/app.js
CHANGED
|
@@ -27147,11 +27147,10 @@ async function postForm(fetchRequest, url2, values, signal) {
|
|
|
27147
27147
|
}
|
|
27148
27148
|
return data;
|
|
27149
27149
|
}
|
|
27150
|
-
async function
|
|
27150
|
+
async function authorizeWithDeviceFlow(options, dependencies = {}) {
|
|
27151
27151
|
const fetchRequest = dependencies.fetch ?? fetch;
|
|
27152
27152
|
const wait = dependencies.delay ?? defaultDelay;
|
|
27153
27153
|
const browse = dependencies.openBrowser ?? openBrowser;
|
|
27154
|
-
const save = dependencies.saveCredentials ?? saveCredentials;
|
|
27155
27154
|
const now = dependencies.now ?? Date.now;
|
|
27156
27155
|
const log = dependencies.log ?? console.log;
|
|
27157
27156
|
try {
|
|
@@ -27189,26 +27188,25 @@ async function loginWithDeviceFlow(options, dependencies = {}) {
|
|
|
27189
27188
|
});
|
|
27190
27189
|
const data = await response.json();
|
|
27191
27190
|
if (response.ok && "access_token" in data) {
|
|
27192
|
-
if (!data.refresh_token) {
|
|
27193
|
-
throw new Error("Auth0 did not issue a refresh token.");
|
|
27194
|
-
}
|
|
27195
27191
|
const grantedScope = data.scope ?? options.scope;
|
|
27196
|
-
if (!grantedScopesSatisfyRequest(
|
|
27192
|
+
if (!grantedScopesSatisfyRequest(
|
|
27193
|
+
grantedScope,
|
|
27194
|
+
options.scope,
|
|
27195
|
+
Boolean(data.refresh_token)
|
|
27196
|
+
)) {
|
|
27197
27197
|
throw new Error("Auth0 did not grant the permissions Glossa requires.");
|
|
27198
27198
|
}
|
|
27199
|
-
|
|
27199
|
+
return {
|
|
27200
27200
|
issuer: options.issuer,
|
|
27201
27201
|
clientId: options.clientId,
|
|
27202
27202
|
audience: options.audience,
|
|
27203
27203
|
accessToken: data.access_token,
|
|
27204
|
-
refreshToken: data.refresh_token,
|
|
27204
|
+
...data.refresh_token ? { refreshToken: data.refresh_token } : {},
|
|
27205
27205
|
expiresAt: new Date(now() + data.expires_in * 1e3).toISOString(),
|
|
27206
27206
|
tokenType: data.token_type,
|
|
27207
27207
|
scope: grantedScope,
|
|
27208
27208
|
requestedScope: options.scope
|
|
27209
|
-
}
|
|
27210
|
-
log("Signed in to Glossa.");
|
|
27211
|
-
return;
|
|
27209
|
+
};
|
|
27212
27210
|
}
|
|
27213
27211
|
const error46 = data;
|
|
27214
27212
|
if (error46.error === "authorization_pending") continue;
|
|
@@ -27228,6 +27226,15 @@ async function loginWithDeviceFlow(options, dependencies = {}) {
|
|
|
27228
27226
|
throw error46;
|
|
27229
27227
|
}
|
|
27230
27228
|
}
|
|
27229
|
+
async function loginWithDeviceFlow(options, dependencies = {}) {
|
|
27230
|
+
const credentials = await authorizeWithDeviceFlow(options, dependencies);
|
|
27231
|
+
if (!credentials.refreshToken) {
|
|
27232
|
+
throw new Error("Auth0 did not issue a refresh token.");
|
|
27233
|
+
}
|
|
27234
|
+
const save = dependencies.saveCredentials ?? saveCredentials;
|
|
27235
|
+
await save(credentials);
|
|
27236
|
+
(dependencies.log ?? console.log)("Signed in to Glossa.");
|
|
27237
|
+
}
|
|
27231
27238
|
|
|
27232
27239
|
// src/auth-login.ts
|
|
27233
27240
|
function normalizedIssuer(value) {
|
|
@@ -40370,12 +40377,6 @@ function loadRelayEndpoints(environment = process.env) {
|
|
|
40370
40377
|
function defaultDeviceName() {
|
|
40371
40378
|
return deviceNameSchema.parse(os2.hostname());
|
|
40372
40379
|
}
|
|
40373
|
-
var DevicePairingExpiredError = class extends Error {
|
|
40374
|
-
constructor() {
|
|
40375
|
-
super("The Glossa pairing code expired. Run Glossa again to get a new code.");
|
|
40376
|
-
this.name = "DevicePairingExpiredError";
|
|
40377
|
-
}
|
|
40378
|
-
};
|
|
40379
40380
|
function relayError(status, data) {
|
|
40380
40381
|
if (status === 401) return new Error("Glossa login was rejected. Sign in again when prompted.");
|
|
40381
40382
|
if (status === 403 && data.error === "account_disabled") {
|
|
@@ -40429,68 +40430,49 @@ async function listDevices(endpoints, credentials, fetchRequest = fetch) {
|
|
|
40429
40430
|
if (!response.ok) throw relayError(response.status, data);
|
|
40430
40431
|
return parseDevices2(data.devices);
|
|
40431
40432
|
}
|
|
40432
|
-
async function
|
|
40433
|
-
const
|
|
40434
|
-
|
|
40435
|
-
|
|
40436
|
-
|
|
40437
|
-
body: JSON.stringify({ name, platform: `${process.platform}-${process.arch}` })
|
|
40438
|
-
});
|
|
40439
|
-
let data = {};
|
|
40440
|
-
try {
|
|
40441
|
-
data = await response.json();
|
|
40442
|
-
} catch {
|
|
40443
|
-
}
|
|
40444
|
-
if (!response.ok) {
|
|
40445
|
-
if (response.status === 429) {
|
|
40446
|
-
throw new Error("Glossa pairing is rate limited. Try again later.");
|
|
40447
|
-
}
|
|
40448
|
-
throw new Error(`The Glossa relay returned HTTP ${response.status}.`);
|
|
40449
|
-
}
|
|
40450
|
-
if (typeof data.pairing_id !== "string" || typeof data.user_code !== "string" || typeof data.pairing_secret !== "string" || typeof data.expires_at !== "string" || !Number.isInteger(data.poll_interval_ms) || data.poll_interval_ms < 1e3 || data.poll_interval_ms > 1e4 || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(data.pairing_id) || !/^[A-HJ-NP-Z2-9]{5}-[A-HJ-NP-Z2-9]{5}$/.test(data.user_code) || data.pairing_secret.length < 32 || data.pairing_secret.length > 128 || !Number.isFinite(Date.parse(data.expires_at))) {
|
|
40451
|
-
throw new Error("The Glossa relay returned an invalid pairing response.");
|
|
40452
|
-
}
|
|
40453
|
-
return {
|
|
40454
|
-
pairingId: data.pairing_id,
|
|
40455
|
-
userCode: data.user_code,
|
|
40456
|
-
pairingSecret: data.pairing_secret,
|
|
40457
|
-
expiresAt: data.expires_at,
|
|
40458
|
-
pollIntervalMs: data.poll_interval_ms
|
|
40459
|
-
};
|
|
40460
|
-
}
|
|
40461
|
-
async function completeDevicePairing(endpoints, pairing, fetchRequest = fetch) {
|
|
40462
|
-
const response = await fetchRequest(
|
|
40463
|
-
`${endpoints.relayOrigin}/v1/device-pairings/${encodeURIComponent(pairing.pairingId)}/complete`,
|
|
40464
|
-
{
|
|
40433
|
+
async function enrollDevice(endpoints, credentials, deviceName, fetchRequest = fetch) {
|
|
40434
|
+
const baseName = deviceNameSchema.parse(deviceName);
|
|
40435
|
+
let name = baseName;
|
|
40436
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
40437
|
+
const response = await fetchRequest(`${endpoints.relayOrigin}/v1/devices/enroll`, {
|
|
40465
40438
|
method: "POST",
|
|
40466
|
-
headers: {
|
|
40467
|
-
|
|
40439
|
+
headers: {
|
|
40440
|
+
authorization: `${credentials.tokenType} ${credentials.accessToken}`,
|
|
40441
|
+
"content-type": "application/json"
|
|
40442
|
+
},
|
|
40443
|
+
body: JSON.stringify({ name, platform: `${process.platform}-${process.arch}` })
|
|
40444
|
+
});
|
|
40445
|
+
let data = {};
|
|
40446
|
+
try {
|
|
40447
|
+
data = await response.json();
|
|
40448
|
+
} catch {
|
|
40468
40449
|
}
|
|
40469
|
-
|
|
40470
|
-
|
|
40471
|
-
|
|
40472
|
-
|
|
40473
|
-
|
|
40474
|
-
|
|
40475
|
-
|
|
40476
|
-
|
|
40477
|
-
|
|
40478
|
-
|
|
40479
|
-
|
|
40480
|
-
|
|
40481
|
-
|
|
40482
|
-
|
|
40483
|
-
|
|
40484
|
-
|
|
40485
|
-
|
|
40486
|
-
|
|
40450
|
+
if (response.status === 409 && data.error === "device_name_conflict" && attempt < 2) {
|
|
40451
|
+
const activeNames = new Set(
|
|
40452
|
+
(await listDevices(endpoints, credentials, fetchRequest)).filter((device) => device.revokedAt === null).map((device) => device.name)
|
|
40453
|
+
);
|
|
40454
|
+
for (let suffix = 2; ; suffix += 1) {
|
|
40455
|
+
const ending = `-${suffix}`;
|
|
40456
|
+
const candidate = `${baseName.slice(0, 80 - ending.length)}${ending}`;
|
|
40457
|
+
if (!activeNames.has(candidate)) {
|
|
40458
|
+
name = deviceNameSchema.parse(candidate);
|
|
40459
|
+
break;
|
|
40460
|
+
}
|
|
40461
|
+
}
|
|
40462
|
+
continue;
|
|
40463
|
+
}
|
|
40464
|
+
if (!response.ok) throw relayError(response.status, data);
|
|
40465
|
+
if (typeof data.device?.id !== "string" || typeof data.device.name !== "string" || typeof data.device_token !== "string") {
|
|
40466
|
+
throw new Error("The Glossa relay returned an invalid device enrollment response.");
|
|
40467
|
+
}
|
|
40468
|
+
return {
|
|
40469
|
+
relayOrigin: endpoints.relayOrigin,
|
|
40470
|
+
deviceId: data.device.id,
|
|
40471
|
+
deviceName: data.device.name,
|
|
40472
|
+
token: data.device_token
|
|
40473
|
+
};
|
|
40487
40474
|
}
|
|
40488
|
-
|
|
40489
|
-
relayOrigin: endpoints.relayOrigin,
|
|
40490
|
-
deviceId: data.device.id,
|
|
40491
|
-
deviceName: data.device.name,
|
|
40492
|
-
token: data.device_token
|
|
40493
|
-
};
|
|
40475
|
+
throw new Error("Glossa could not enroll this computer.");
|
|
40494
40476
|
}
|
|
40495
40477
|
async function revokePairedDevice(endpoints, device, fetchRequest = fetch) {
|
|
40496
40478
|
const response = await fetchRequest(`${endpoints.relayOrigin}/device`, {
|
|
@@ -47864,7 +47846,7 @@ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, ex
|
|
|
47864
47846
|
for (const subscriber of animationSubscribersRef.current.values()) {
|
|
47865
47847
|
nextDueTime = Math.min(nextDueTime, subscriber.nextDueTime);
|
|
47866
47848
|
}
|
|
47867
|
-
const
|
|
47849
|
+
const delay5 = Math.max(0, nextDueTime - performance.now());
|
|
47868
47850
|
animationTimerRef.current = setTimeout(() => {
|
|
47869
47851
|
animationTimerRef.current = void 0;
|
|
47870
47852
|
const currentTime = performance.now();
|
|
@@ -47878,7 +47860,7 @@ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, ex
|
|
|
47878
47860
|
subscriber.nextDueTime = subscriber.startTime + elapsedFrames * subscriber.interval;
|
|
47879
47861
|
}
|
|
47880
47862
|
scheduleAnimationTick();
|
|
47881
|
-
},
|
|
47863
|
+
}, delay5);
|
|
47882
47864
|
}, [clearAnimationTimer]);
|
|
47883
47865
|
const animationSubscribe = (0, import_react15.useCallback)((callback, interval) => {
|
|
47884
47866
|
const startTime = performance.now();
|
|
@@ -50109,40 +50091,36 @@ async function withDevicePairingLease(action, signal, directory = updateRuntimeD
|
|
|
50109
50091
|
}
|
|
50110
50092
|
|
|
50111
50093
|
// src/device-pairing.ts
|
|
50112
|
-
import { setTimeout as delay3 } from "node:timers/promises";
|
|
50113
|
-
async function defaultWait(milliseconds, signal) {
|
|
50114
|
-
await delay3(milliseconds, void 0, signal ? { signal } : void 0);
|
|
50115
|
-
}
|
|
50116
50094
|
function canceledError2() {
|
|
50117
50095
|
return new Error("Pairing canceled.");
|
|
50118
50096
|
}
|
|
50097
|
+
function pairingScope(scope) {
|
|
50098
|
+
return scope.split(/\s+/).filter((value) => value && value !== "offline_access").join(" ");
|
|
50099
|
+
}
|
|
50119
50100
|
async function pairDevice(endpoints, signal, dependencies = {}) {
|
|
50120
|
-
const
|
|
50121
|
-
const
|
|
50101
|
+
const authorize = dependencies.authorizeWithDeviceFlow ?? authorizeWithDeviceFlow;
|
|
50102
|
+
const enroll = dependencies.enrollDevice ?? enrollDevice;
|
|
50103
|
+
const authConfig = dependencies.loadAuthConfig ?? loadAuthConfig;
|
|
50122
50104
|
const name = dependencies.defaultDeviceName ?? defaultDeviceName;
|
|
50123
|
-
const wait = dependencies.wait ?? defaultWait;
|
|
50124
|
-
const now = dependencies.now ?? Date.now;
|
|
50125
50105
|
const log = dependencies.log ?? console.log;
|
|
50126
50106
|
const baseFetch = dependencies.fetch ?? fetch;
|
|
50127
50107
|
const fetchRequest = signal ? async (input, init) => await baseFetch(input, { ...init, signal }) : baseFetch;
|
|
50128
50108
|
try {
|
|
50129
50109
|
signal?.throwIfAborted();
|
|
50130
|
-
const challenge = await begin(endpoints, name(), fetchRequest);
|
|
50131
50110
|
log("This computer needs to be paired with Glossa.");
|
|
50132
|
-
|
|
50133
|
-
|
|
50134
|
-
|
|
50135
|
-
|
|
50136
|
-
|
|
50137
|
-
|
|
50138
|
-
|
|
50139
|
-
|
|
50140
|
-
|
|
50141
|
-
|
|
50142
|
-
|
|
50143
|
-
|
|
50144
|
-
|
|
50145
|
-
throw new Error("The Glossa pairing code expired. Run Glossa again to get a new code.");
|
|
50111
|
+
const auth = authConfig();
|
|
50112
|
+
const credentials = await authorize(
|
|
50113
|
+
{
|
|
50114
|
+
...auth,
|
|
50115
|
+
scope: pairingScope(auth.scope),
|
|
50116
|
+
...signal ? { signal } : {}
|
|
50117
|
+
},
|
|
50118
|
+
{ fetch: fetchRequest, log }
|
|
50119
|
+
);
|
|
50120
|
+
signal?.throwIfAborted();
|
|
50121
|
+
const paired = await enroll(endpoints, credentials, name(), fetchRequest);
|
|
50122
|
+
log(`Paired with Glossa as ${paired.deviceName}.`);
|
|
50123
|
+
return paired;
|
|
50146
50124
|
} catch (error46) {
|
|
50147
50125
|
if (signal?.aborted || error46 instanceof Error && error46.name === "AbortError") {
|
|
50148
50126
|
throw canceledError2();
|
|
@@ -50155,7 +50133,7 @@ async function pairDevice(endpoints, signal, dependencies = {}) {
|
|
|
50155
50133
|
import { spawn as spawn2 } from "node:child_process";
|
|
50156
50134
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
50157
50135
|
import { StringDecoder } from "node:string_decoder";
|
|
50158
|
-
import { setTimeout as
|
|
50136
|
+
import { setTimeout as delay3 } from "node:timers/promises";
|
|
50159
50137
|
|
|
50160
50138
|
// src/worker/errors.ts
|
|
50161
50139
|
var WorkerError = class extends Error {
|
|
@@ -50270,7 +50248,7 @@ async function waitForChange(record2, afterSequence, waitMs) {
|
|
|
50270
50248
|
try {
|
|
50271
50249
|
await Promise.race([
|
|
50272
50250
|
change,
|
|
50273
|
-
|
|
50251
|
+
delay3(waitMs, void 0, { signal: waitController.signal })
|
|
50274
50252
|
]);
|
|
50275
50253
|
} finally {
|
|
50276
50254
|
record2.changeWaiters.delete(changed);
|
|
@@ -50441,7 +50419,7 @@ async function terminateProcessTree(child) {
|
|
|
50441
50419
|
} catch {
|
|
50442
50420
|
child.kill("SIGTERM");
|
|
50443
50421
|
}
|
|
50444
|
-
await
|
|
50422
|
+
await delay3(2e3);
|
|
50445
50423
|
if (child.exitCode === null) {
|
|
50446
50424
|
try {
|
|
50447
50425
|
process.kill(-child.pid, "SIGKILL");
|
|
@@ -50606,7 +50584,7 @@ var CommandService = class {
|
|
|
50606
50584
|
try {
|
|
50607
50585
|
await Promise.race([
|
|
50608
50586
|
record2.completion,
|
|
50609
|
-
|
|
50587
|
+
delay3(waitMs, void 0, { signal: waitController.signal })
|
|
50610
50588
|
]);
|
|
50611
50589
|
} finally {
|
|
50612
50590
|
waitController.abort();
|
|
@@ -50632,7 +50610,7 @@ var CommandService = class {
|
|
|
50632
50610
|
try {
|
|
50633
50611
|
await Promise.race([
|
|
50634
50612
|
record2.completion,
|
|
50635
|
-
|
|
50613
|
+
delay3(waitMs, void 0, { signal: waitController.signal })
|
|
50636
50614
|
]);
|
|
50637
50615
|
} finally {
|
|
50638
50616
|
waitController.abort();
|
|
@@ -52485,7 +52463,7 @@ var RemoteWorker = class {
|
|
|
52485
52463
|
if (error46 instanceof DeviceRejectedError || error46 instanceof RelayAccessProfileUnsupportedError) {
|
|
52486
52464
|
throw error46;
|
|
52487
52465
|
}
|
|
52488
|
-
const
|
|
52466
|
+
const delay5 = reconnectDelayMs(
|
|
52489
52467
|
failures,
|
|
52490
52468
|
this.#random,
|
|
52491
52469
|
this.#reconnectBaseMs,
|
|
@@ -52495,10 +52473,10 @@ var RemoteWorker = class {
|
|
|
52495
52473
|
this.#onStatus({
|
|
52496
52474
|
state: "retrying",
|
|
52497
52475
|
error: error46 instanceof Error ? error46 : new Error(String(error46)),
|
|
52498
|
-
retryInMs:
|
|
52476
|
+
retryInMs: delay5
|
|
52499
52477
|
});
|
|
52500
52478
|
try {
|
|
52501
|
-
await this.#sleep(
|
|
52479
|
+
await this.#sleep(delay5, this.#signal);
|
|
52502
52480
|
} catch (sleepError) {
|
|
52503
52481
|
if (this.#signal.aborted) return;
|
|
52504
52482
|
throw sleepError;
|
|
@@ -54750,7 +54728,7 @@ import { createHash as createHash3 } from "node:crypto";
|
|
|
54750
54728
|
import { lstat as lstat3, mkdir as mkdir8, rm as rm7, stat as stat4 } from "node:fs/promises";
|
|
54751
54729
|
import net from "node:net";
|
|
54752
54730
|
import path13 from "node:path";
|
|
54753
|
-
import { setTimeout as
|
|
54731
|
+
import { setTimeout as delay4 } from "node:timers/promises";
|
|
54754
54732
|
var GUARD_STALE_MS = 5e3;
|
|
54755
54733
|
var GUARD_RETRY_MS = 10;
|
|
54756
54734
|
var GUARD_TIMEOUT_MS = 1e4;
|
|
@@ -54822,7 +54800,7 @@ async function acquireGuard2(directory, identity) {
|
|
|
54822
54800
|
"Timed out while checking whether this workspace is already active."
|
|
54823
54801
|
);
|
|
54824
54802
|
}
|
|
54825
|
-
await
|
|
54803
|
+
await delay4(GUARD_RETRY_MS);
|
|
54826
54804
|
}
|
|
54827
54805
|
}
|
|
54828
54806
|
}
|
|
@@ -54911,7 +54889,7 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54911
54889
|
}
|
|
54912
54890
|
|
|
54913
54891
|
// src/main.ts
|
|
54914
|
-
var VERSION = "0.2.
|
|
54892
|
+
var VERSION = "0.2.1";
|
|
54915
54893
|
var DISTRIBUTION = "npm";
|
|
54916
54894
|
var HELP = `Glossa ${VERSION}
|
|
54917
54895
|
|