sleev 0.0.12 → 0.0.13
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 +19 -39
- package/dist/auth/browser.d.ts +12 -0
- package/dist/auth/browser.js +33 -24
- package/dist/auth/browser.js.map +1 -0
- package/dist/auth/firebase-tokens.d.ts +39 -0
- package/dist/auth/firebase-tokens.js +173 -0
- package/dist/auth/firebase-tokens.js.map +1 -0
- package/dist/auth/firebase.d.ts +17 -0
- package/dist/auth/firebase.js +55 -0
- package/dist/auth/firebase.js.map +1 -0
- package/dist/auth/login.d.ts +22 -0
- package/dist/auth/login.js +31 -28
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/loopback.d.ts +19 -0
- package/dist/auth/loopback.js +114 -44
- package/dist/auth/loopback.js.map +1 -0
- package/dist/auth/schema.d.ts +26 -0
- package/dist/auth/schema.js +59 -17
- package/dist/auth/schema.js.map +1 -0
- package/dist/auth/token.d.ts +19 -0
- package/dist/auth/token.js +53 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/auth/verify.d.ts +6 -0
- package/dist/auth/verify.js +22 -0
- package/dist/auth/verify.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +245 -59
- package/dist/cli.js.map +1 -0
- package/dist/config/paths.d.ts +12 -0
- package/dist/config/paths.js +34 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/store.d.ts +63 -0
- package/dist/config/store.js +270 -0
- package/dist/config/store.js.map +1 -0
- package/dist/control/local-installations.d.ts +13 -0
- package/dist/control/local-installations.js +37 -0
- package/dist/control/local-installations.js.map +1 -0
- package/dist/daemon/config.d.ts +10 -0
- package/dist/daemon/config.js +52 -0
- package/dist/daemon/config.js.map +1 -0
- package/dist/daemon/controller.d.ts +29 -0
- package/dist/daemon/controller.js +34 -0
- package/dist/daemon/controller.js.map +1 -0
- package/dist/daemon/health.d.ts +8 -0
- package/dist/daemon/health.js +47 -0
- package/dist/daemon/health.js.map +1 -0
- package/dist/daemon/start.d.ts +5 -0
- package/dist/daemon/start.js +24 -0
- package/dist/daemon/start.js.map +1 -0
- package/dist/daemon/systemd.d.ts +35 -0
- package/dist/daemon/systemd.js +181 -0
- package/dist/daemon/systemd.js.map +1 -0
- package/dist/gateway/acquire.d.ts +14 -0
- package/dist/gateway/acquire.js +112 -0
- package/dist/gateway/acquire.js.map +1 -0
- package/dist/gateway/manifest.d.ts +26 -0
- package/dist/gateway/manifest.js +98 -0
- package/dist/gateway/manifest.js.map +1 -0
- package/dist/gateway/platform.d.ts +5 -0
- package/dist/gateway/platform.js +27 -0
- package/dist/gateway/platform.js.map +1 -0
- package/dist/harness/claude.d.ts +8 -0
- package/dist/{apps → harness}/claude.js +28 -28
- package/dist/harness/claude.js.map +1 -0
- package/dist/harness/codex.d.ts +10 -0
- package/dist/{apps → harness}/codex.js +50 -45
- package/dist/harness/codex.js.map +1 -0
- package/dist/harness/index.d.ts +13 -0
- package/dist/harness/index.js +63 -0
- package/dist/harness/index.js.map +1 -0
- package/dist/harness/opencode-config.d.ts +8 -0
- package/dist/{apps → harness}/opencode-config.js +116 -83
- package/dist/harness/opencode-config.js.map +1 -0
- package/dist/harness/opencode.d.ts +14 -0
- package/dist/harness/opencode.js +50 -0
- package/dist/harness/opencode.js.map +1 -0
- package/dist/harness/providers.d.ts +8 -0
- package/dist/harness/providers.js +30 -0
- package/dist/harness/providers.js.map +1 -0
- package/dist/harness/types.d.ts +14 -0
- package/dist/harness/types.js +2 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/product.d.ts +12 -0
- package/dist/product.js +53 -10
- package/dist/product.js.map +1 -0
- package/dist/tui/clipboard.d.ts +1 -0
- package/dist/tui/clipboard.js +48 -0
- package/dist/tui/clipboard.js.map +1 -0
- package/dist/tui/openTuiApp.d.ts +1 -0
- package/dist/tui/openTuiApp.js +1436 -0
- package/dist/tui/openTuiApp.js.map +1 -0
- package/dist/tui/pages/flow-screen.d.ts +50 -0
- package/dist/tui/pages/flow-screen.js +435 -0
- package/dist/tui/pages/flow-screen.js.map +1 -0
- package/dist/tui/pages/footer.d.ts +7 -0
- package/dist/tui/pages/footer.js +14 -0
- package/dist/tui/pages/footer.js.map +1 -0
- package/dist/tui/pages/home-screen.d.ts +19 -0
- package/dist/tui/pages/home-screen.js +89 -0
- package/dist/tui/pages/home-screen.js.map +1 -0
- package/dist/tui/pages/index.d.ts +17 -0
- package/dist/tui/pages/index.js +11 -0
- package/dist/tui/pages/index.js.map +1 -0
- package/dist/tui/pages/local-setup-screen.d.ts +23 -0
- package/dist/tui/pages/local-setup-screen.js +134 -0
- package/dist/tui/pages/local-setup-screen.js.map +1 -0
- package/dist/tui/pages/location-card.d.ts +19 -0
- package/dist/tui/pages/location-card.js +116 -0
- package/dist/tui/pages/location-card.js.map +1 -0
- package/dist/tui/pages/menu-screen.d.ts +24 -0
- package/dist/tui/pages/menu-screen.js +117 -0
- package/dist/tui/pages/menu-screen.js.map +1 -0
- package/dist/tui/pages/session-grid.d.ts +23 -0
- package/dist/tui/pages/session-grid.js +552 -0
- package/dist/tui/pages/session-grid.js.map +1 -0
- package/dist/tui/pages/setup-screen.d.ts +15 -0
- package/dist/tui/pages/setup-screen.js +80 -0
- package/dist/tui/pages/setup-screen.js.map +1 -0
- package/dist/tui/pages/terminal-log.d.ts +14 -0
- package/dist/tui/pages/terminal-log.js +104 -0
- package/dist/tui/pages/terminal-log.js.map +1 -0
- package/dist/tui/pages/welcome-screen.d.ts +16 -0
- package/dist/tui/pages/welcome-screen.js +80 -0
- package/dist/tui/pages/welcome-screen.js.map +1 -0
- package/dist/tui/saved-tokens.d.ts +10 -0
- package/dist/tui/saved-tokens.js +104 -0
- package/dist/tui/saved-tokens.js.map +1 -0
- package/dist/tui/theme.d.ts +25 -0
- package/dist/tui/theme.js +27 -0
- package/dist/tui/theme.js.map +1 -0
- package/package.json +55 -38
- package/dist/apps/opencode.js +0 -158
- package/dist/apps.js +0 -148
- package/dist/auth/command.js +0 -109
- package/dist/auth/index.js +0 -2
- package/dist/auth/validation.js +0 -27
- package/dist/credentials/index.js +0 -3
- package/dist/credentials/schema.js +0 -63
- package/dist/credentials/store.js +0 -52
- package/dist/harnesses.js +0 -66
- package/dist/index.js +0 -3
- package/dist/providers.js +0 -30
- package/dist/terminal.js +0 -55
package/README.md
CHANGED
|
@@ -1,49 +1,29 @@
|
|
|
1
|
-
#
|
|
1
|
+
# sleeved
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Local Sleeve proxy daemon and setup TUI.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This repository is intentionally early. The first milestone is the local
|
|
6
|
+
installer shell:
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
- launch a TUI
|
|
9
|
+
- persist local config under platform-standard user directories
|
|
10
|
+
- prepare daemon lifecycle commands
|
|
11
|
+
- prepare harness setup commands
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
OpenTUI currently runs on Bun. The package is still structured for npm
|
|
14
|
+
publication, with non-TUI commands kept Node-compatible while the interactive
|
|
15
|
+
TUI entrypoint is isolated.
|
|
10
16
|
|
|
11
17
|
```bash
|
|
12
|
-
|
|
18
|
+
npm install
|
|
19
|
+
npm run check
|
|
20
|
+
npm test
|
|
21
|
+
npm run build
|
|
13
22
|
```
|
|
14
23
|
|
|
15
|
-
Sleev will:
|
|
16
|
-
|
|
17
|
-
1. Open your browser.
|
|
18
|
-
2. Ask you to sign in or create an account.
|
|
19
|
-
3. Return you to the terminal after login.
|
|
20
|
-
4. Ask which supported local apps you want to use with Sleev.
|
|
21
|
-
|
|
22
|
-
After that, your local apps can use Sleev without logging in again.
|
|
23
|
-
|
|
24
|
-
## Logout
|
|
25
|
-
|
|
26
|
-
Run:
|
|
27
|
-
|
|
28
24
|
```bash
|
|
29
|
-
|
|
25
|
+
sleeved
|
|
26
|
+
sleeved paths
|
|
27
|
+
sleeved status
|
|
28
|
+
sleeved daemon status
|
|
30
29
|
```
|
|
31
|
-
|
|
32
|
-
This removes your local Sleev login from this machine.
|
|
33
|
-
|
|
34
|
-
## Already Logged In
|
|
35
|
-
|
|
36
|
-
If you need to replace your current local login, run:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npx sleev auth login --force
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Requirements
|
|
43
|
-
|
|
44
|
-
- Node.js 20 or newer
|
|
45
|
-
- A browser you can sign in with
|
|
46
|
-
|
|
47
|
-
## Notes
|
|
48
|
-
|
|
49
|
-
Your login is stored locally on your machine. Do not share your Sleev auth file.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type Command = {
|
|
2
|
+
command: string;
|
|
3
|
+
args: string[];
|
|
4
|
+
};
|
|
5
|
+
type Context = {
|
|
6
|
+
platform?: NodeJS.Platform;
|
|
7
|
+
env?: NodeJS.ProcessEnv;
|
|
8
|
+
release?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function openBrowser(url: string): Promise<boolean>;
|
|
11
|
+
export declare function browserCommands(url: string, context?: Context): Command[];
|
|
12
|
+
export {};
|
package/dist/auth/browser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// Cross-platform browser opener used by the login
|
|
2
|
-
import { spawn } from
|
|
3
|
-
import { existsSync, readFileSync } from
|
|
1
|
+
// Cross-platform browser opener used by the login handoff, including WSL fallbacks.
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
export async function openBrowser(url) {
|
|
5
5
|
const commands = browserCommands(url);
|
|
6
6
|
for (const command of commands) {
|
|
@@ -13,7 +13,7 @@ export async function openBrowser(url) {
|
|
|
13
13
|
function open(command) {
|
|
14
14
|
return new Promise((resolve) => {
|
|
15
15
|
const child = spawn(command.command, command.args, {
|
|
16
|
-
stdio:
|
|
16
|
+
stdio: "ignore",
|
|
17
17
|
windowsHide: true,
|
|
18
18
|
});
|
|
19
19
|
const timer = setTimeout(() => finish(true), 3000);
|
|
@@ -23,44 +23,51 @@ function open(command) {
|
|
|
23
23
|
return;
|
|
24
24
|
done = true;
|
|
25
25
|
clearTimeout(timer);
|
|
26
|
-
child.removeAllListeners(
|
|
27
|
-
child.removeAllListeners(
|
|
26
|
+
child.removeAllListeners("error");
|
|
27
|
+
child.removeAllListeners("close");
|
|
28
28
|
if (ok)
|
|
29
29
|
child.unref();
|
|
30
30
|
resolve(ok);
|
|
31
31
|
};
|
|
32
|
-
child.once(
|
|
33
|
-
child.once(
|
|
32
|
+
child.once("error", () => finish(false));
|
|
33
|
+
child.once("close", (code) => finish(code === 0));
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
export function browserCommands(url, context = {}) {
|
|
37
37
|
const platform = context.platform ?? process.platform;
|
|
38
|
-
if (platform ===
|
|
39
|
-
return [{ command:
|
|
40
|
-
if (platform ===
|
|
38
|
+
if (platform === "darwin")
|
|
39
|
+
return [{ command: "open", args: [url] }];
|
|
40
|
+
if (platform === "win32") {
|
|
41
41
|
return [
|
|
42
|
-
{ command:
|
|
43
|
-
{ command:
|
|
42
|
+
{ command: "rundll32", args: ["url.dll,FileProtocolHandler", url] },
|
|
43
|
+
{ command: "powershell", args: powershellArgs(url) },
|
|
44
44
|
];
|
|
45
45
|
}
|
|
46
|
-
if (platform ===
|
|
46
|
+
if (platform === "linux" && isWsl(context)) {
|
|
47
47
|
return [
|
|
48
48
|
...linuxCommands(url),
|
|
49
|
-
{ command:
|
|
50
|
-
{ command:
|
|
49
|
+
{ command: "wslview", args: [url] },
|
|
50
|
+
{ command: "powershell.exe", args: powershellArgs(url) },
|
|
51
51
|
];
|
|
52
52
|
}
|
|
53
53
|
return linuxCommands(url);
|
|
54
54
|
}
|
|
55
55
|
function linuxCommands(url) {
|
|
56
56
|
return [
|
|
57
|
-
{ command:
|
|
58
|
-
{ command:
|
|
59
|
-
{ command:
|
|
57
|
+
{ command: "sensible-browser", args: [url] },
|
|
58
|
+
{ command: "gio", args: ["open", url] },
|
|
59
|
+
{ command: "xdg-open", args: [url] },
|
|
60
60
|
];
|
|
61
61
|
}
|
|
62
62
|
function powershellArgs(url) {
|
|
63
|
-
return [
|
|
63
|
+
return [
|
|
64
|
+
"-NoProfile",
|
|
65
|
+
"-NonInteractive",
|
|
66
|
+
"-ExecutionPolicy",
|
|
67
|
+
"Bypass",
|
|
68
|
+
"-Command",
|
|
69
|
+
`Start-Process '${ps(url)}'`,
|
|
70
|
+
];
|
|
64
71
|
}
|
|
65
72
|
function ps(value) {
|
|
66
73
|
return value.replace(/'/g, "''");
|
|
@@ -70,11 +77,13 @@ function isWsl(context) {
|
|
|
70
77
|
if (env.WSL_DISTRO_NAME || env.WSL_INTEROP)
|
|
71
78
|
return true;
|
|
72
79
|
const release = context.release ?? kernelRelease();
|
|
73
|
-
return release.toLowerCase().includes(
|
|
80
|
+
return (release.toLowerCase().includes("microsoft") ||
|
|
81
|
+
release.toLowerCase().includes("wsl"));
|
|
74
82
|
}
|
|
75
83
|
function kernelRelease() {
|
|
76
|
-
const path =
|
|
84
|
+
const path = "/proc/sys/kernel/osrelease";
|
|
77
85
|
if (!existsSync(path))
|
|
78
|
-
return
|
|
79
|
-
return readFileSync(path,
|
|
86
|
+
return "";
|
|
87
|
+
return readFileSync(path, "utf8");
|
|
80
88
|
}
|
|
89
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/auth/browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { FirebaseLoginResult } from "./firebase.js";
|
|
2
|
+
export type StoredFirebaseAuth = {
|
|
3
|
+
version: 1;
|
|
4
|
+
refreshToken: string;
|
|
5
|
+
accountId: string;
|
|
6
|
+
email: string | null;
|
|
7
|
+
label: string | null;
|
|
8
|
+
updatedAt: string;
|
|
9
|
+
};
|
|
10
|
+
export declare class FirebaseAuthRequiredError extends Error {
|
|
11
|
+
constructor(message?: string);
|
|
12
|
+
}
|
|
13
|
+
export declare class FirebaseTokenError extends Error {
|
|
14
|
+
constructor(message: string);
|
|
15
|
+
}
|
|
16
|
+
export declare function readStoredFirebaseAuth(file?: string): Promise<StoredFirebaseAuth | null>;
|
|
17
|
+
export declare function writeStoredFirebaseAuth(auth: StoredFirebaseAuth, file?: string): Promise<void>;
|
|
18
|
+
export declare function clearStoredFirebaseAuth(file?: string): Promise<void>;
|
|
19
|
+
export declare function saveFirebaseLogin(result: FirebaseLoginResult, now?: () => Date): Promise<StoredFirebaseAuth>;
|
|
20
|
+
export type FirebaseTokenManagerDeps = {
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
fetchImpl?: typeof fetch;
|
|
23
|
+
now?: () => number;
|
|
24
|
+
load?: () => Promise<StoredFirebaseAuth | null>;
|
|
25
|
+
save?: (auth: StoredFirebaseAuth) => Promise<void>;
|
|
26
|
+
clear?: () => Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
export declare class FirebaseTokenManager {
|
|
29
|
+
private readonly apiKey;
|
|
30
|
+
private readonly fetchImpl;
|
|
31
|
+
private readonly now;
|
|
32
|
+
private readonly load;
|
|
33
|
+
private readonly save;
|
|
34
|
+
private readonly clear;
|
|
35
|
+
private cache;
|
|
36
|
+
constructor(deps?: FirebaseTokenManagerDeps);
|
|
37
|
+
getIdToken(forceRefresh?: boolean): Promise<string>;
|
|
38
|
+
private exchange;
|
|
39
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Stores the Firebase refresh token and mints short-lived ID tokens from it
|
|
2
|
+
// so the CLI can call the control server with Firebase auth, headlessly.
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { resolveSleevedPaths } from "../config/paths.js";
|
|
6
|
+
import { firebaseConfig } from "../product.js";
|
|
7
|
+
const SECURE_TOKEN_URL = "https://securetoken.googleapis.com/v1/token";
|
|
8
|
+
// Refresh a little early so an in-flight request never carries a token that
|
|
9
|
+
// expires mid-flight.
|
|
10
|
+
const EXPIRY_SKEW_MS = 60_000;
|
|
11
|
+
// Thrown when there is no usable refresh token (never signed in, or the
|
|
12
|
+
// refresh token was revoked/expired). Callers should prompt a fresh sign-in.
|
|
13
|
+
export class FirebaseAuthRequiredError extends Error {
|
|
14
|
+
constructor(message = "Firebase sign-in is required.") {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "FirebaseAuthRequiredError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class FirebaseTokenError extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "FirebaseTokenError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function readStoredFirebaseAuth(file = resolveSleevedPaths().firebaseCredentialsFile) {
|
|
26
|
+
try {
|
|
27
|
+
return normalize(JSON.parse(await fs.readFile(file, "utf8")));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (isNotFoundError(error))
|
|
31
|
+
return null;
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function writeStoredFirebaseAuth(auth, file = resolveSleevedPaths().firebaseCredentialsFile) {
|
|
36
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
37
|
+
await fs.writeFile(file, `${JSON.stringify(auth, null, 2)}\n`, {
|
|
38
|
+
mode: 0o600,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export async function clearStoredFirebaseAuth(file = resolveSleevedPaths().firebaseCredentialsFile) {
|
|
42
|
+
try {
|
|
43
|
+
await fs.unlink(file);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (!isNotFoundError(error))
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export async function saveFirebaseLogin(result, now = () => new Date()) {
|
|
51
|
+
const auth = {
|
|
52
|
+
version: 1,
|
|
53
|
+
refreshToken: result.refreshToken,
|
|
54
|
+
accountId: result.accountId,
|
|
55
|
+
email: result.email,
|
|
56
|
+
label: result.label,
|
|
57
|
+
updatedAt: now().toISOString(),
|
|
58
|
+
};
|
|
59
|
+
await writeStoredFirebaseAuth(auth);
|
|
60
|
+
return auth;
|
|
61
|
+
}
|
|
62
|
+
// Caches the ID token in memory and refreshes it from the stored refresh
|
|
63
|
+
// token when it is missing or about to expire.
|
|
64
|
+
export class FirebaseTokenManager {
|
|
65
|
+
apiKey;
|
|
66
|
+
fetchImpl;
|
|
67
|
+
now;
|
|
68
|
+
load;
|
|
69
|
+
save;
|
|
70
|
+
clear;
|
|
71
|
+
cache = null;
|
|
72
|
+
constructor(deps = {}) {
|
|
73
|
+
this.apiKey = deps.apiKey ?? firebaseConfig().apiKey;
|
|
74
|
+
this.fetchImpl = deps.fetchImpl ?? fetch;
|
|
75
|
+
this.now = deps.now ?? Date.now;
|
|
76
|
+
this.load = deps.load ?? (() => readStoredFirebaseAuth());
|
|
77
|
+
this.save = deps.save ?? ((auth) => writeStoredFirebaseAuth(auth));
|
|
78
|
+
this.clear = deps.clear ?? (() => clearStoredFirebaseAuth());
|
|
79
|
+
}
|
|
80
|
+
async getIdToken(forceRefresh = false) {
|
|
81
|
+
if (!forceRefresh &&
|
|
82
|
+
this.cache &&
|
|
83
|
+
this.now() < this.cache.expiresAtMs - EXPIRY_SKEW_MS) {
|
|
84
|
+
return this.cache.idToken;
|
|
85
|
+
}
|
|
86
|
+
const stored = await this.load();
|
|
87
|
+
if (!stored) {
|
|
88
|
+
throw new FirebaseAuthRequiredError();
|
|
89
|
+
}
|
|
90
|
+
const exchanged = await this.exchange(stored.refreshToken);
|
|
91
|
+
// Firebase may rotate the refresh token; persist it if it changed.
|
|
92
|
+
if (exchanged.refresh_token !== stored.refreshToken) {
|
|
93
|
+
await this.save({
|
|
94
|
+
...stored,
|
|
95
|
+
refreshToken: exchanged.refresh_token,
|
|
96
|
+
updatedAt: new Date(this.now()).toISOString(),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const ttlMs = Number(exchanged.expires_in) * 1000;
|
|
100
|
+
this.cache = {
|
|
101
|
+
idToken: exchanged.id_token,
|
|
102
|
+
expiresAtMs: this.now() + (Number.isFinite(ttlMs) ? ttlMs : 3_600_000),
|
|
103
|
+
};
|
|
104
|
+
return exchanged.id_token;
|
|
105
|
+
}
|
|
106
|
+
async exchange(refreshToken) {
|
|
107
|
+
let response;
|
|
108
|
+
try {
|
|
109
|
+
response = await this.fetchImpl(`${SECURE_TOKEN_URL}?key=${encodeURIComponent(this.apiKey)}`, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: {
|
|
112
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
113
|
+
},
|
|
114
|
+
body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
throw new FirebaseTokenError(`Could not reach Firebase token endpoint: ${error instanceof Error ? error.message : "network error"}`);
|
|
119
|
+
}
|
|
120
|
+
const body = (await response.json().catch(() => null));
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
const reason = body?.error?.message ?? `HTTP ${response.status}`;
|
|
123
|
+
// A revoked/invalid/expired refresh token is unrecoverable
|
|
124
|
+
// without a fresh browser sign-in.
|
|
125
|
+
if (reason === "TOKEN_EXPIRED" ||
|
|
126
|
+
reason === "USER_DISABLED" ||
|
|
127
|
+
reason === "USER_NOT_FOUND" ||
|
|
128
|
+
reason === "INVALID_REFRESH_TOKEN" ||
|
|
129
|
+
reason === "INVALID_GRANT") {
|
|
130
|
+
this.cache = null;
|
|
131
|
+
await this.clear();
|
|
132
|
+
throw new FirebaseAuthRequiredError(`Firebase session is no longer valid (${reason}). Sign in again.`);
|
|
133
|
+
}
|
|
134
|
+
throw new FirebaseTokenError(`Firebase token refresh failed: ${reason}`);
|
|
135
|
+
}
|
|
136
|
+
if (!body ||
|
|
137
|
+
typeof body.id_token !== "string" ||
|
|
138
|
+
typeof body.refresh_token !== "string" ||
|
|
139
|
+
typeof body.expires_in !== "string") {
|
|
140
|
+
throw new FirebaseTokenError("Firebase token response was malformed.");
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
id_token: body.id_token,
|
|
144
|
+
refresh_token: body.refresh_token,
|
|
145
|
+
expires_in: body.expires_in,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function normalize(value) {
|
|
150
|
+
if (typeof value !== "object" || value === null)
|
|
151
|
+
return null;
|
|
152
|
+
const v = value;
|
|
153
|
+
if (typeof v.refreshToken !== "string" || !v.refreshToken)
|
|
154
|
+
return null;
|
|
155
|
+
if (typeof v.accountId !== "string" || !v.accountId)
|
|
156
|
+
return null;
|
|
157
|
+
return {
|
|
158
|
+
version: 1,
|
|
159
|
+
refreshToken: v.refreshToken,
|
|
160
|
+
accountId: v.accountId,
|
|
161
|
+
email: typeof v.email === "string" ? v.email : null,
|
|
162
|
+
label: typeof v.label === "string" ? v.label : null,
|
|
163
|
+
updatedAt: typeof v.updatedAt === "string"
|
|
164
|
+
? v.updatedAt
|
|
165
|
+
: new Date(0).toISOString(),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function isNotFoundError(error) {
|
|
169
|
+
return (error instanceof Error &&
|
|
170
|
+
"code" in error &&
|
|
171
|
+
error.code === "ENOENT");
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=firebase-tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firebase-tokens.js","sourceRoot":"","sources":["../../src/auth/firebase-tokens.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,yEAAyE;AACzE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,MAAM,gBAAgB,GAAG,6CAA6C,CAAC;AACvE,4EAA4E;AAC5E,sBAAsB;AACtB,MAAM,cAAc,GAAG,MAAM,CAAC;AAW9B,wEAAwE;AACxE,6EAA6E;AAC7E,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAChD,YAAY,OAAO,GAAG,+BAA+B;QACjD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC5C,CAAC;CACJ;AAED,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IACzC,YAAY,OAAe;QACvB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACrC,CAAC;CACJ;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CACxC,IAAI,GAAG,mBAAmB,EAAE,CAAC,uBAAuB;IAEpD,IAAI,CAAC;QACD,OAAO,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAClE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,eAAe,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACxC,MAAM,KAAK,CAAC;IAChB,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CACzC,IAAwB,EACxB,IAAI,GAAG,mBAAmB,EAAE,CAAC,uBAAuB;IAEpD,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QAC3D,IAAI,EAAE,KAAK;KACd,CAAC,CAAC;AACP,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CACzC,IAAI,GAAG,mBAAmB,EAAE,CAAC,uBAAuB;IAEpD,IAAI,CAAC;QACD,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;YAAE,MAAM,KAAK,CAAC;IAC7C,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACnC,MAA2B,EAC3B,MAAkB,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE;IAElC,MAAM,IAAI,GAAuB;QAC7B,OAAO,EAAE,CAAC;QACV,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE;KACjC,CAAC;IACF,MAAM,uBAAuB,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,IAAI,CAAC;AAChB,CAAC;AAiBD,yEAAyE;AACzE,+CAA+C;AAC/C,MAAM,OAAO,oBAAoB;IACZ,MAAM,CAAS;IACf,SAAS,CAAe;IACxB,GAAG,CAAe;IAClB,IAAI,CAA2C;IAC/C,IAAI,CAA8C;IAClD,KAAK,CAAsB;IACpC,KAAK,GAAoD,IAAI,CAAC;IAEtE,YAAY,OAAiC,EAAE;QAC3C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,cAAc,EAAE,CAAC,MAAM,CAAC;QACrD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;QACzC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,sBAAsB,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC;QACnE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,uBAAuB,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,YAAY,GAAG,KAAK;QACjC,IACI,CAAC,YAAY;YACb,IAAI,CAAC,KAAK;YACV,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,cAAc,EACtD,CAAC;YACC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;QAC9B,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,IAAI,yBAAyB,EAAE,CAAC;QAC1C,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAE3D,mEAAmE;QACnE,IAAI,SAAS,CAAC,aAAa,KAAK,MAAM,CAAC,YAAY,EAAE,CAAC;YAClD,MAAM,IAAI,CAAC,IAAI,CAAC;gBACZ,GAAG,MAAM;gBACT,YAAY,EAAE,SAAS,CAAC,aAAa;gBACrC,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE;aAChD,CAAC,CAAC;QACP,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;QAClD,IAAI,CAAC,KAAK,GAAG;YACT,OAAO,EAAE,SAAS,CAAC,QAAQ;YAC3B,WAAW,EACP,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;SAChE,CAAC;QACF,OAAO,SAAS,CAAC,QAAQ,CAAC;IAC9B,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAC,YAAoB;QACvC,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACD,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAC3B,GAAG,gBAAgB,QAAQ,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAC5D;gBACI,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACL,cAAc,EAAE,mCAAmC;iBACtD;gBACD,IAAI,EAAE,0CAA0C,kBAAkB,CAAC,YAAY,CAAC,EAAE;aACrF,CACJ,CAAC;QACN,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,IAAI,kBAAkB,CACxB,4CACI,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAC7C,EAAE,CACL,CAAC;QACN,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAI3C,CAAC;QAEX,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,OAAO,IAAI,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC;YACjE,2DAA2D;YAC3D,mCAAmC;YACnC,IACI,MAAM,KAAK,eAAe;gBAC1B,MAAM,KAAK,eAAe;gBAC1B,MAAM,KAAK,gBAAgB;gBAC3B,MAAM,KAAK,uBAAuB;gBAClC,MAAM,KAAK,eAAe,EAC5B,CAAC;gBACC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;gBAClB,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;gBACnB,MAAM,IAAI,yBAAyB,CAC/B,wCAAwC,MAAM,mBAAmB,CACpE,CAAC;YACN,CAAC;YACD,MAAM,IAAI,kBAAkB,CACxB,kCAAkC,MAAM,EAAE,CAC7C,CAAC;QACN,CAAC;QAED,IACI,CAAC,IAAI;YACL,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ;YACjC,OAAO,IAAI,CAAC,aAAa,KAAK,QAAQ;YACtC,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,EACrC,CAAC;YACC,MAAM,IAAI,kBAAkB,CACxB,wCAAwC,CAC3C,CAAC;QACN,CAAC;QAED,OAAO;YACH,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,UAAU,EAAE,IAAI,CAAC,UAAU;SAC9B,CAAC;IACN,CAAC;CACJ;AAED,SAAS,SAAS,CAAC,KAAc;IAC7B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC7D,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,IAAI,OAAO,CAAC,CAAC,YAAY,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IACvE,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IACjE,OAAO;QACH,OAAO,EAAE,CAAC;QACV,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;QACnD,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;QACnD,SAAS,EACL,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ;YAC3B,CAAC,CAAC,CAAC,CAAC,SAAS;YACb,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;KACtC,CAAC;AACN,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACnC,OAAO,CACH,KAAK,YAAY,KAAK;QACtB,MAAM,IAAI,KAAK;QACd,KAA+B,CAAC,IAAI,KAAK,QAAQ,CACrD,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Writable } from "node:stream";
|
|
2
|
+
export type FirebaseLoginResult = {
|
|
3
|
+
refreshToken: string;
|
|
4
|
+
accountId: string;
|
|
5
|
+
email: string | null;
|
|
6
|
+
label: string | null;
|
|
7
|
+
};
|
|
8
|
+
export type FirebaseLoginOptions = {
|
|
9
|
+
webOrigin?: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
output?: Writable;
|
|
12
|
+
opener?: (url: string) => Promise<boolean>;
|
|
13
|
+
onUrl?: (url: string) => void;
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
};
|
|
17
|
+
export declare function firebaseLogin(options?: FirebaseLoginOptions): Promise<FirebaseLoginResult>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Browser handoff that returns a Firebase refresh token for headless,
|
|
2
|
+
// Firebase-authenticated control-server calls (local installation mode).
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { hostname } from "node:os";
|
|
5
|
+
import { webOrigin as defaultWebOrigin } from "../product.js";
|
|
6
|
+
import { openBrowser } from "./browser.js";
|
|
7
|
+
import { startFirebaseLoopback } from "./loopback.js";
|
|
8
|
+
const FIREBASE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
9
|
+
export async function firebaseLogin(options = {}) {
|
|
10
|
+
const output = options.output ?? process.stdout;
|
|
11
|
+
const origin = options.webOrigin ?? defaultWebOrigin();
|
|
12
|
+
const opener = options.opener ?? openBrowser;
|
|
13
|
+
const state = randomBytes(32).toString("base64url");
|
|
14
|
+
const loopback = await startFirebaseLoopback({
|
|
15
|
+
state,
|
|
16
|
+
webOrigin: origin,
|
|
17
|
+
timeoutMs: options.timeoutMs ?? FIREBASE_TIMEOUT_MS,
|
|
18
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
19
|
+
});
|
|
20
|
+
const url = firebaseUrl(origin, state, loopback.callback, options.label);
|
|
21
|
+
options.onUrl?.(url);
|
|
22
|
+
output.write(`Opening ${origin} to sign in.\n`);
|
|
23
|
+
try {
|
|
24
|
+
const opened = await opener(url);
|
|
25
|
+
if (!opened) {
|
|
26
|
+
output.write("Could not open a browser automatically. Open this URL instead:\n");
|
|
27
|
+
output.write(`${url}\n`);
|
|
28
|
+
}
|
|
29
|
+
output.write("Waiting for browser confirmation...\n");
|
|
30
|
+
const payload = await loopback.wait;
|
|
31
|
+
output.write("Browser login confirmed.\n");
|
|
32
|
+
return {
|
|
33
|
+
refreshToken: payload.refreshToken,
|
|
34
|
+
accountId: payload.accountId,
|
|
35
|
+
email: payload.email,
|
|
36
|
+
label: payload.label,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
await loopback.close();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function firebaseUrl(origin, state, callback, label) {
|
|
44
|
+
const url = new URL("/cli/login", origin);
|
|
45
|
+
url.searchParams.set("mode", "signin");
|
|
46
|
+
url.searchParams.set("client", "sleeved");
|
|
47
|
+
url.searchParams.set("handoff", "firebase");
|
|
48
|
+
url.searchParams.set("state", state);
|
|
49
|
+
url.searchParams.set("callback", callback);
|
|
50
|
+
url.searchParams.set("device", hostname().slice(0, 80));
|
|
51
|
+
if (label)
|
|
52
|
+
url.searchParams.set("label", label);
|
|
53
|
+
return url.toString();
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=firebase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firebase.js","sourceRoot":"","sources":["../../src/auth/firebase.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Writable } from "node:stream";
|
|
2
|
+
import type { AccountIdentity } from "./schema.js";
|
|
3
|
+
export type LoginMode = "signup" | "signin";
|
|
4
|
+
export type LoginResult = {
|
|
5
|
+
label: string | null;
|
|
6
|
+
accountId: string;
|
|
7
|
+
email: string | null;
|
|
8
|
+
createdAt: string;
|
|
9
|
+
};
|
|
10
|
+
export type LoginOptions = {
|
|
11
|
+
mode?: LoginMode;
|
|
12
|
+
webOrigin?: string;
|
|
13
|
+
output?: Writable;
|
|
14
|
+
opener?: (url: string) => Promise<boolean>;
|
|
15
|
+
onUrl?: (url: string) => void;
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
now?: () => Date;
|
|
19
|
+
expectedAccount?: AccountIdentity;
|
|
20
|
+
label?: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function login(options?: LoginOptions): Promise<LoginResult>;
|
package/dist/auth/login.js
CHANGED
|
@@ -1,43 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { openBrowser } from './browser.js';
|
|
7
|
-
import { startLoopback } from './loopback.js';
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { hostname } from "node:os";
|
|
3
|
+
import { webOrigin as defaultWebOrigin } from "../product.js";
|
|
4
|
+
import { openBrowser } from "./browser.js";
|
|
5
|
+
import { startLoopback } from "./loopback.js";
|
|
8
6
|
const LOGIN_TIMEOUT_MS = 10 * 60 * 1000;
|
|
9
7
|
export async function login(options = {}) {
|
|
10
|
-
|
|
11
|
-
brand(output);
|
|
12
|
-
return runHandoff({ ...options, mode: options.mode ?? 'signup', output });
|
|
8
|
+
return runHandoff({ ...options, mode: options.mode ?? "signin" });
|
|
13
9
|
}
|
|
14
10
|
async function runHandoff(options) {
|
|
15
|
-
const output = options.output;
|
|
16
|
-
const mode = options.mode;
|
|
11
|
+
const output = options.output ?? process.stdout;
|
|
17
12
|
const origin = options.webOrigin ?? defaultWebOrigin();
|
|
18
13
|
const opener = options.opener ?? openBrowser;
|
|
19
|
-
const state = randomBytes(32).toString(
|
|
14
|
+
const state = randomBytes(32).toString("base64url");
|
|
20
15
|
const loopback = await startLoopback({
|
|
21
16
|
state,
|
|
22
17
|
webOrigin: origin,
|
|
23
18
|
timeoutMs: options.timeoutMs ?? LOGIN_TIMEOUT_MS,
|
|
19
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
20
|
+
...(options.expectedAccount
|
|
21
|
+
? { expectedAccount: options.expectedAccount }
|
|
22
|
+
: {}),
|
|
24
23
|
});
|
|
25
|
-
const url = loginUrl(origin, mode, state, loopback.callback);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
field(output, 'url', url);
|
|
24
|
+
const url = loginUrl(origin, options.mode, state, loopback.callback, options.label);
|
|
25
|
+
options.onUrl?.(url);
|
|
26
|
+
output.write(`Opening ${origin} to sign in.\n`);
|
|
29
27
|
try {
|
|
30
28
|
const opened = await opener(url);
|
|
31
29
|
if (!opened) {
|
|
32
|
-
|
|
30
|
+
output.write("Could not open a browser automatically. Open this URL instead:\n");
|
|
31
|
+
output.write(`${url}\n`);
|
|
33
32
|
}
|
|
34
|
-
|
|
33
|
+
output.write("Waiting for browser confirmation...\n");
|
|
35
34
|
const payload = await loopback.wait;
|
|
36
|
-
|
|
35
|
+
output.write("Browser login confirmed.\n");
|
|
37
36
|
return {
|
|
38
|
-
token: payload.token,
|
|
39
37
|
label: payload.label,
|
|
40
|
-
|
|
38
|
+
accountId: payload.accountId,
|
|
39
|
+
email: payload.email,
|
|
41
40
|
createdAt: (options.now ?? (() => new Date()))().toISOString(),
|
|
42
41
|
};
|
|
43
42
|
}
|
|
@@ -45,11 +44,15 @@ async function runHandoff(options) {
|
|
|
45
44
|
await loopback.close();
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
|
-
function loginUrl(origin, mode, state, callback) {
|
|
49
|
-
const url = new URL(
|
|
50
|
-
url.searchParams.set(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
url.searchParams.set(
|
|
47
|
+
function loginUrl(origin, mode, state, callback, label) {
|
|
48
|
+
const url = new URL("/cli/login", origin);
|
|
49
|
+
url.searchParams.set("mode", mode);
|
|
50
|
+
if (label)
|
|
51
|
+
url.searchParams.set("label", label);
|
|
52
|
+
url.searchParams.set("client", "sleeved");
|
|
53
|
+
url.searchParams.set("state", state);
|
|
54
|
+
url.searchParams.set("callback", callback);
|
|
55
|
+
url.searchParams.set("device", hostname().slice(0, 80));
|
|
54
56
|
return url.toString();
|
|
55
57
|
}
|
|
58
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type AccountIdentity, type FirebaseHandoff, type LoginHandoff, type TokenHandoff } from "./schema.js";
|
|
2
|
+
export type Loopback<T = LoginHandoff> = {
|
|
3
|
+
callback: string;
|
|
4
|
+
wait: Promise<T>;
|
|
5
|
+
close: () => Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
type BaseLoopbackOptions = {
|
|
8
|
+
state: string;
|
|
9
|
+
webOrigin: string;
|
|
10
|
+
timeoutMs: number;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
};
|
|
13
|
+
export type LoopbackOptions = BaseLoopbackOptions & {
|
|
14
|
+
expectedAccount?: AccountIdentity;
|
|
15
|
+
};
|
|
16
|
+
export declare function startLoopback(options: LoopbackOptions): Promise<Loopback>;
|
|
17
|
+
export declare function startTokenLoopback(options: BaseLoopbackOptions): Promise<Loopback<TokenHandoff>>;
|
|
18
|
+
export declare function startFirebaseLoopback(options: BaseLoopbackOptions): Promise<Loopback<FirebaseHandoff>>;
|
|
19
|
+
export {};
|