@mutmutco/installer-launcher 1.3.1 → 1.4.0
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 +44 -0
- package/dist/launcher.js +7 -3
- package/dist/launcher.sea.cjs +7 -3
- package/dist/session.d.ts +19 -0
- package/dist/session.js +236 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -57,6 +57,50 @@ Only `login` (and the login half of `install`) opens a browser:
|
|
|
57
57
|
The product dir defaults to `~/.<product>/` on mac/linux and `%LOCALAPPDATA%\<product>` on
|
|
58
58
|
Windows, and holds `tokens.json` (0600), `state.json`, and `payload/`.
|
|
59
59
|
|
|
60
|
+
## Noninteractive session API
|
|
61
|
+
|
|
62
|
+
`@mutmutco/installer-launcher/session` is a plain ESM entry with TypeScript definitions;
|
|
63
|
+
importing it does not start the CLI or load a baked product config.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { getAccessToken, LoginRequiredError } from '@mutmutco/installer-launcher/session';
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const accessToken = await getAccessToken({
|
|
70
|
+
product: 'example-product',
|
|
71
|
+
host: 'https://gate.example.com',
|
|
72
|
+
loginKind: 'github', // explicit; use 'google' for a Google product gate
|
|
73
|
+
signal: AbortSignal.timeout(40_000), // shared with the caller's entire attempt
|
|
74
|
+
});
|
|
75
|
+
// Use accessToken for authenticated requests; never log it.
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error instanceof LoginRequiredError) {
|
|
78
|
+
// Ask the caller to sign in using its normal product login flow.
|
|
79
|
+
} else {
|
|
80
|
+
throw error; // transport, timeout, cancellation, storage or lock failure
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
All three identity options are required; no host/product/protocol is inferred. The API
|
|
86
|
+
reuses the machine's gate session and returns only its access token. Tokens within 60 seconds
|
|
87
|
+
of expiry are refreshed and persisted using the launcher's private store. It never opens a
|
|
88
|
+
browser, invokes `gh`, starts device authentication, or exposes the token-file format.
|
|
89
|
+
Missing/unusable sessions and permanent refresh rejection (GitHub gate 403; Google OAuth 400/401/403, e.g. expired or revoked grants) throw `LoginRequiredError`; a release
|
|
90
|
+
resource's `ForbiddenError` instead means revoked release access and is not this error.
|
|
91
|
+
Network and non-403 HTTP failures propagate without erasing the session.
|
|
92
|
+
|
|
93
|
+
Optional `dir` overrides `LAUNCHER_DIR`, then the usual platform product directory applies.
|
|
94
|
+
Optional `fetch` injects a transport for tests and must honor the supplied AbortSignal.
|
|
95
|
+
`timeoutMs` defaults to 10,000 and must be a positive integer at most 40,000. Its signal is
|
|
96
|
+
composed with `signal` and any transport input signal; cancellation is checked before
|
|
97
|
+
persisting or clearing refreshed tokens. For a 40-second attempt / 120-second overall staging
|
|
98
|
+
budget, pass the caller's attempt signal and keep the auth timeout inside that budget.
|
|
99
|
+
|
|
100
|
+
Calls take the existing installation lock and fail rather than queue behind another owner.
|
|
101
|
+
This protects callers using that lock, not all concurrent token writers: existing login paths
|
|
102
|
+
can release it before authentication. Do not assume cross-process refresh coalescing.
|
|
103
|
+
|
|
60
104
|
## Config
|
|
61
105
|
|
|
62
106
|
`config/product.template.json` per product:
|
package/dist/launcher.js
CHANGED
|
@@ -2011,7 +2011,8 @@ async function refreshGoogleToken(host, clientId, refreshToken, fetchImpl = fetc
|
|
|
2011
2011
|
client_id: clientId
|
|
2012
2012
|
}).toString()
|
|
2013
2013
|
});
|
|
2014
|
-
if (
|
|
2014
|
+
if ([400, 401, 403].includes(response.status)) return null;
|
|
2015
|
+
if (!response.ok) throw new Error(`POST ${server}/oauth/token \u2192 ${response.status}: refresh failed`);
|
|
2015
2016
|
const tokens = await response.json();
|
|
2016
2017
|
if (typeof tokens.access_token !== "string" || typeof tokens.expires_in !== "number") return null;
|
|
2017
2018
|
return {
|
|
@@ -2026,13 +2027,15 @@ function page(title, body) {
|
|
|
2026
2027
|
}
|
|
2027
2028
|
|
|
2028
2029
|
// src/tokens.ts
|
|
2029
|
-
async function ensureFreshToken(config, dir, fetchImpl = fetch) {
|
|
2030
|
+
async function ensureFreshToken(config, dir, fetchImpl = fetch, signal) {
|
|
2031
|
+
signal?.throwIfAborted();
|
|
2030
2032
|
const stored = readTokens(dir);
|
|
2031
2033
|
if (!stored) return null;
|
|
2032
2034
|
if (stored.expiresAt - Date.now() > 6e4) return stored.accessToken;
|
|
2033
2035
|
if (!stored.refreshToken) return null;
|
|
2034
2036
|
if (config.loginKind === "github") {
|
|
2035
2037
|
const refreshed2 = await refreshAccessToken(config, stored.refreshToken, fetchImpl);
|
|
2038
|
+
signal?.throwIfAborted();
|
|
2036
2039
|
if (!refreshed2) {
|
|
2037
2040
|
clearTokens(dir);
|
|
2038
2041
|
return null;
|
|
@@ -2049,6 +2052,7 @@ async function ensureFreshToken(config, dir, fetchImpl = fetch) {
|
|
|
2049
2052
|
return null;
|
|
2050
2053
|
}
|
|
2051
2054
|
const refreshed = await refreshGoogleToken(config.host, stored.clientId, stored.refreshToken, fetchImpl);
|
|
2055
|
+
signal?.throwIfAborted();
|
|
2052
2056
|
if (!refreshed) {
|
|
2053
2057
|
clearTokens(dir);
|
|
2054
2058
|
return null;
|
|
@@ -2259,7 +2263,7 @@ async function refreshLauncher(target, options = {}) {
|
|
|
2259
2263
|
}
|
|
2260
2264
|
|
|
2261
2265
|
// src/index.ts
|
|
2262
|
-
var LAUNCHER_VERSION = true ? "1.
|
|
2266
|
+
var LAUNCHER_VERSION = true ? "1.4.0" : readVersionFromPackage();
|
|
2263
2267
|
function defaultPrint(message) {
|
|
2264
2268
|
process.stdout.write(`${message}
|
|
2265
2269
|
`);
|
package/dist/launcher.sea.cjs
CHANGED
|
@@ -2044,7 +2044,8 @@ async function refreshGoogleToken(host, clientId, refreshToken, fetchImpl = fetc
|
|
|
2044
2044
|
client_id: clientId
|
|
2045
2045
|
}).toString()
|
|
2046
2046
|
});
|
|
2047
|
-
if (
|
|
2047
|
+
if ([400, 401, 403].includes(response.status)) return null;
|
|
2048
|
+
if (!response.ok) throw new Error(`POST ${server}/oauth/token \u2192 ${response.status}: refresh failed`);
|
|
2048
2049
|
const tokens = await response.json();
|
|
2049
2050
|
if (typeof tokens.access_token !== "string" || typeof tokens.expires_in !== "number") return null;
|
|
2050
2051
|
return {
|
|
@@ -2059,13 +2060,15 @@ function page(title, body) {
|
|
|
2059
2060
|
}
|
|
2060
2061
|
|
|
2061
2062
|
// src/tokens.ts
|
|
2062
|
-
async function ensureFreshToken(config, dir, fetchImpl = fetch) {
|
|
2063
|
+
async function ensureFreshToken(config, dir, fetchImpl = fetch, signal) {
|
|
2064
|
+
signal?.throwIfAborted();
|
|
2063
2065
|
const stored = readTokens(dir);
|
|
2064
2066
|
if (!stored) return null;
|
|
2065
2067
|
if (stored.expiresAt - Date.now() > 6e4) return stored.accessToken;
|
|
2066
2068
|
if (!stored.refreshToken) return null;
|
|
2067
2069
|
if (config.loginKind === "github") {
|
|
2068
2070
|
const refreshed2 = await refreshAccessToken(config, stored.refreshToken, fetchImpl);
|
|
2071
|
+
signal?.throwIfAborted();
|
|
2069
2072
|
if (!refreshed2) {
|
|
2070
2073
|
clearTokens(dir);
|
|
2071
2074
|
return null;
|
|
@@ -2082,6 +2085,7 @@ async function ensureFreshToken(config, dir, fetchImpl = fetch) {
|
|
|
2082
2085
|
return null;
|
|
2083
2086
|
}
|
|
2084
2087
|
const refreshed = await refreshGoogleToken(config.host, stored.clientId, stored.refreshToken, fetchImpl);
|
|
2088
|
+
signal?.throwIfAborted();
|
|
2085
2089
|
if (!refreshed) {
|
|
2086
2090
|
clearTokens(dir);
|
|
2087
2091
|
return null;
|
|
@@ -2292,7 +2296,7 @@ async function refreshLauncher(target, options = {}) {
|
|
|
2292
2296
|
}
|
|
2293
2297
|
|
|
2294
2298
|
// src/index.ts
|
|
2295
|
-
var LAUNCHER_VERSION = true ? "1.
|
|
2299
|
+
var LAUNCHER_VERSION = true ? "1.4.0" : readVersionFromPackage();
|
|
2296
2300
|
function defaultPrint(message) {
|
|
2297
2301
|
process.stdout.write(`${message}
|
|
2298
2302
|
`);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface SessionOptions {
|
|
2
|
+
product: string;
|
|
3
|
+
host: string;
|
|
4
|
+
loginKind: 'github' | 'google';
|
|
5
|
+
/** Overrides LAUNCHER_DIR and the platform's default product directory. */
|
|
6
|
+
dir?: string;
|
|
7
|
+
/** Optional transport injection; defaults to the global fetch. */
|
|
8
|
+
fetch?: typeof fetch;
|
|
9
|
+
/** Cancellation for the caller's overall attempt. */
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
/** Auth budget in milliseconds: defaults to 10,000; positive and at most 40,000. */
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
/** The caller must arrange sign-in; this API never starts authentication. */
|
|
15
|
+
export declare class LoginRequiredError extends Error {
|
|
16
|
+
constructor();
|
|
17
|
+
}
|
|
18
|
+
/** Returns an existing gate access token, refreshing and persisting it when necessary. */
|
|
19
|
+
export declare function getAccessToken(options: SessionOptions): Promise<string>;
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// src/acquisition.ts
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, realpathSync, readdirSync, lstatSync, rmSync as rmSync2 } from "node:fs";
|
|
4
|
+
import { join as join2, resolve, relative, isAbsolute, dirname } from "node:path";
|
|
5
|
+
|
|
6
|
+
// src/store.ts
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, renameSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
function defaultProductDir(product) {
|
|
11
|
+
if (process.platform === "win32") {
|
|
12
|
+
const base = process.env.LOCALAPPDATA ?? join(tmpdir(), "launcher-fallback");
|
|
13
|
+
return join(base, product);
|
|
14
|
+
}
|
|
15
|
+
const home = process.env.HOME ?? tmpdir();
|
|
16
|
+
return join(home, `.${product}`);
|
|
17
|
+
}
|
|
18
|
+
function resolveProductDir(product, explicit) {
|
|
19
|
+
return explicit ?? process.env.LAUNCHER_DIR ?? defaultProductDir(product);
|
|
20
|
+
}
|
|
21
|
+
function tokensPath(dir) {
|
|
22
|
+
return join(dir, "tokens.json");
|
|
23
|
+
}
|
|
24
|
+
function readTokens(dir) {
|
|
25
|
+
try {
|
|
26
|
+
const data = JSON.parse(readFileSync(tokensPath(dir), "utf8"));
|
|
27
|
+
if (typeof data.accessToken !== "string" || !data.accessToken) return null;
|
|
28
|
+
if (typeof data.expiresAt !== "number" || !Number.isFinite(data.expiresAt)) return null;
|
|
29
|
+
const tokens = { accessToken: data.accessToken, expiresAt: data.expiresAt };
|
|
30
|
+
if (typeof data.refreshToken === "string" && data.refreshToken) tokens.refreshToken = data.refreshToken;
|
|
31
|
+
if (typeof data.clientId === "string" && data.clientId) tokens.clientId = data.clientId;
|
|
32
|
+
return tokens;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function writeTokens(dir, tokens) {
|
|
38
|
+
mkdirSync(dir, { recursive: true });
|
|
39
|
+
try {
|
|
40
|
+
writeFileSync(tokensPath(dir), `${JSON.stringify(tokens)}
|
|
41
|
+
`, { mode: 384 });
|
|
42
|
+
} catch {
|
|
43
|
+
writeFileSync(tokensPath(dir), `${JSON.stringify(tokens)}
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function clearTokens(dir) {
|
|
48
|
+
rmSync(tokensPath(dir), { force: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/config.ts
|
|
52
|
+
import { getAsset } from "node:sea";
|
|
53
|
+
|
|
54
|
+
// src/acquisition.ts
|
|
55
|
+
function lockInstallation(dir) {
|
|
56
|
+
mkdirSync2(dir, { recursive: true });
|
|
57
|
+
const path = join2(dir, "installation.lock");
|
|
58
|
+
const owner = `${process.pid}:${randomUUID()}`;
|
|
59
|
+
try {
|
|
60
|
+
writeFileSync2(path, owner, { flag: "wx", mode: 384 });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error.code !== "EEXIST") throw error;
|
|
63
|
+
const recovery = `${path}.recovery`;
|
|
64
|
+
try {
|
|
65
|
+
writeFileSync2(recovery, owner, { flag: "wx", mode: 384, flush: true });
|
|
66
|
+
} catch {
|
|
67
|
+
throw new Error("installation lock recovery is already pending");
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const previous = readFileSync2(path, "utf8");
|
|
71
|
+
const pid = Number(previous.split(":")[0]);
|
|
72
|
+
if (!Number.isInteger(pid) || pid <= 0) throw new Error("installation lock is malformed; recovery required");
|
|
73
|
+
try {
|
|
74
|
+
process.kill(pid, 0);
|
|
75
|
+
throw new Error("another installer is running");
|
|
76
|
+
} catch (probe) {
|
|
77
|
+
if (probe.code !== "ESRCH") throw probe;
|
|
78
|
+
}
|
|
79
|
+
unlinkSync(path);
|
|
80
|
+
writeFileSync2(path, owner, { flag: "wx", mode: 384, flush: true });
|
|
81
|
+
} finally {
|
|
82
|
+
unlinkSync(recovery);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
let released = false;
|
|
86
|
+
return () => {
|
|
87
|
+
if (!released && readFileSync2(path, "utf8") === owner) unlinkSync(path);
|
|
88
|
+
released = true;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/login-github.ts
|
|
93
|
+
async function postJson(url, body, fetchImpl) {
|
|
94
|
+
return fetchImpl(url, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: { "content-type": "application/json" },
|
|
97
|
+
body: JSON.stringify(body)
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async function readBody(response) {
|
|
101
|
+
try {
|
|
102
|
+
return await response.text();
|
|
103
|
+
} catch {
|
|
104
|
+
return "";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function gateRefusal(method, url, status, body) {
|
|
108
|
+
const head = body.replace(/\s+/g, " ").trim().slice(0, 200);
|
|
109
|
+
return `the sign-in server refused the request \u2014 ${method} ${url} \u2192 ${status}${head ? `: ${head}` : ""}`;
|
|
110
|
+
}
|
|
111
|
+
async function refreshAccessToken(config, refreshToken, fetchImpl = fetch) {
|
|
112
|
+
const url = `${config.host}/gate/refresh`;
|
|
113
|
+
const response = await postJson(url, { refresh_token: refreshToken }, fetchImpl);
|
|
114
|
+
if (response.status === 403) return null;
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
throw new Error(gateRefusal("POST", url, response.status, await readBody(response)));
|
|
117
|
+
}
|
|
118
|
+
const data = await response.json();
|
|
119
|
+
if (typeof data.access_token !== "string" || typeof data.expires_in !== "number") {
|
|
120
|
+
throw new Error("the sign-in server returned a malformed refresh");
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
accessToken: data.access_token,
|
|
124
|
+
expiresIn: data.expires_in,
|
|
125
|
+
...typeof data.refresh_token === "string" ? { refreshToken: data.refresh_token } : {}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/login-google.ts
|
|
130
|
+
async function refreshGoogleToken(host, clientId, refreshToken, fetchImpl = fetch) {
|
|
131
|
+
const server = host.replace(/\/+$/, "");
|
|
132
|
+
const response = await fetchImpl(`${server}/oauth/token`, {
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
135
|
+
body: new URLSearchParams({
|
|
136
|
+
grant_type: "refresh_token",
|
|
137
|
+
refresh_token: refreshToken,
|
|
138
|
+
client_id: clientId
|
|
139
|
+
}).toString()
|
|
140
|
+
});
|
|
141
|
+
if ([400, 401, 403].includes(response.status)) return null;
|
|
142
|
+
if (!response.ok) throw new Error(`POST ${server}/oauth/token \u2192 ${response.status}: refresh failed`);
|
|
143
|
+
const tokens = await response.json();
|
|
144
|
+
if (typeof tokens.access_token !== "string" || typeof tokens.expires_in !== "number") return null;
|
|
145
|
+
return {
|
|
146
|
+
accessToken: tokens.access_token,
|
|
147
|
+
refreshToken: typeof tokens.refresh_token === "string" ? tokens.refresh_token : refreshToken,
|
|
148
|
+
expiresIn: tokens.expires_in
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/tokens.ts
|
|
153
|
+
async function ensureFreshToken(config, dir, fetchImpl = fetch, signal) {
|
|
154
|
+
signal?.throwIfAborted();
|
|
155
|
+
const stored = readTokens(dir);
|
|
156
|
+
if (!stored) return null;
|
|
157
|
+
if (stored.expiresAt - Date.now() > 6e4) return stored.accessToken;
|
|
158
|
+
if (!stored.refreshToken) return null;
|
|
159
|
+
if (config.loginKind === "github") {
|
|
160
|
+
const refreshed2 = await refreshAccessToken(config, stored.refreshToken, fetchImpl);
|
|
161
|
+
signal?.throwIfAborted();
|
|
162
|
+
if (!refreshed2) {
|
|
163
|
+
clearTokens(dir);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
writeTokens(dir, {
|
|
167
|
+
accessToken: refreshed2.accessToken,
|
|
168
|
+
refreshToken: refreshed2.refreshToken ?? stored.refreshToken,
|
|
169
|
+
expiresAt: Date.now() + refreshed2.expiresIn * 1e3
|
|
170
|
+
});
|
|
171
|
+
return refreshed2.accessToken;
|
|
172
|
+
}
|
|
173
|
+
if (!stored.clientId) {
|
|
174
|
+
clearTokens(dir);
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const refreshed = await refreshGoogleToken(config.host, stored.clientId, stored.refreshToken, fetchImpl);
|
|
178
|
+
signal?.throwIfAborted();
|
|
179
|
+
if (!refreshed) {
|
|
180
|
+
clearTokens(dir);
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
writeTokens(dir, {
|
|
184
|
+
accessToken: refreshed.accessToken,
|
|
185
|
+
refreshToken: refreshed.refreshToken,
|
|
186
|
+
expiresAt: Date.now() + refreshed.expiresIn * 1e3,
|
|
187
|
+
clientId: stored.clientId
|
|
188
|
+
});
|
|
189
|
+
return refreshed.accessToken;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/session.ts
|
|
193
|
+
var LoginRequiredError = class extends Error {
|
|
194
|
+
constructor() {
|
|
195
|
+
super("Product sign-in is required.");
|
|
196
|
+
this.name = "LoginRequiredError";
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
async function getAccessToken(options) {
|
|
200
|
+
if (!options.product || options.loginKind !== "github" && options.loginKind !== "google") {
|
|
201
|
+
throw new Error("Session options require product and loginKind (github or google).");
|
|
202
|
+
}
|
|
203
|
+
const host = new URL(options.host);
|
|
204
|
+
if (host.protocol !== "https:" && host.protocol !== "http:") {
|
|
205
|
+
throw new Error("Session host must be an http(s) URL.");
|
|
206
|
+
}
|
|
207
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
208
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 4e4) {
|
|
209
|
+
throw new Error("Session timeoutMs must be a positive integer at most 40000.");
|
|
210
|
+
}
|
|
211
|
+
const signal = AbortSignal.any([AbortSignal.timeout(timeoutMs), ...options.signal ? [options.signal] : []]);
|
|
212
|
+
signal.throwIfAborted();
|
|
213
|
+
const transport = options.fetch ?? fetch;
|
|
214
|
+
const fetchImpl = (input, init) => {
|
|
215
|
+
const inherited = init?.signal ?? (input instanceof Request ? input.signal : void 0);
|
|
216
|
+
return transport(input, { ...init, signal: inherited ? AbortSignal.any([signal, inherited]) : signal });
|
|
217
|
+
};
|
|
218
|
+
const dir = resolveProductDir(options.product, options.dir);
|
|
219
|
+
const unlock = lockInstallation(dir);
|
|
220
|
+
try {
|
|
221
|
+
const token = await ensureFreshToken(
|
|
222
|
+
{ host: options.host.replace(/\/+$/, ""), loginKind: options.loginKind },
|
|
223
|
+
dir,
|
|
224
|
+
fetchImpl,
|
|
225
|
+
signal
|
|
226
|
+
);
|
|
227
|
+
if (!token) throw new LoginRequiredError();
|
|
228
|
+
return token;
|
|
229
|
+
} finally {
|
|
230
|
+
unlock();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
export {
|
|
234
|
+
LoginRequiredError,
|
|
235
|
+
getAccessToken
|
|
236
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/installer-launcher",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Single-executable (SEA) launcher: sign-in, gated payload fetch, self-update. One copy ships per product.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
"exports": {
|
|
15
15
|
".": "./dist/launcher.js",
|
|
16
16
|
"./acquisition-required": "./dist/acquisition-required.mjs",
|
|
17
|
-
"./refresh": "./dist/refresh.js"
|
|
17
|
+
"./refresh": "./dist/refresh.js",
|
|
18
|
+
"./session": {
|
|
19
|
+
"types": "./dist/session.d.ts",
|
|
20
|
+
"import": "./dist/session.js"
|
|
21
|
+
}
|
|
18
22
|
},
|
|
19
23
|
"files": [
|
|
20
24
|
"dist",
|