@remixhq/core 0.1.38 → 0.1.39
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/dist/api.d.ts +1 -1
- package/dist/auth.d.ts +10 -7
- package/dist/auth.js +3 -3
- package/dist/{chunk-OMJ4JDME.js → chunk-QCRTVR5N.js} +66 -68
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/{tokenProvider-B9so5Pm3.d.ts → tokenProvider-BP3YfJHm.d.ts} +3 -3
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
package/dist/auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { W as WithRefreshLock, S as SessionStore, a as StoredSession } from './tokenProvider-
|
|
2
|
-
export { R as RefreshLockOptions, e as RefreshStoredSession, f as ResolvedAuthToken, T as TokenProvider, c as createRefreshLock, b as createStoredSessionTokenProvider, s as shouldRefreshSoon, d as storedSessionSchema } from './tokenProvider-
|
|
1
|
+
import { W as WithRefreshLock, S as SessionStore, a as StoredSession } from './tokenProvider-BP3YfJHm.js';
|
|
2
|
+
export { R as RefreshLockOptions, e as RefreshStoredSession, f as ResolvedAuthToken, T as TokenProvider, c as createRefreshLock, b as createStoredSessionTokenProvider, s as shouldRefreshSoon, d as storedSessionSchema } from './tokenProvider-BP3YfJHm.js';
|
|
3
3
|
import { CoreConfig } from './config.js';
|
|
4
4
|
import 'zod';
|
|
5
5
|
|
|
@@ -31,10 +31,10 @@ declare function createDefaultRefreshLock(filePath?: string): WithRefreshLock;
|
|
|
31
31
|
* - `isNetworkError`: transient — DNS / TCP / TLS / undici fetch
|
|
32
32
|
* dropped mid-flight. Keep the existing session (it may already
|
|
33
33
|
* be unusable but the *refresh token* is most likely still alive
|
|
34
|
-
* server-side, especially within
|
|
34
|
+
* server-side, especially within the backend's refresh-token reuse interval). The
|
|
35
35
|
* next attempt will retry.
|
|
36
36
|
*
|
|
37
|
-
* - `isInvalidGrantError`: terminal —
|
|
37
|
+
* - `isInvalidGrantError`: terminal — the backend explicitly said the
|
|
38
38
|
* refresh token is dead (revoked, reused outside the grace
|
|
39
39
|
* window, user signed out elsewhere). No amount of retrying will
|
|
40
40
|
* fix it; the user must re-authenticate.
|
|
@@ -46,19 +46,22 @@ declare function createDefaultRefreshLock(filePath?: string): WithRefreshLock;
|
|
|
46
46
|
declare function isNetworkError(err: unknown): boolean;
|
|
47
47
|
declare function isInvalidGrantError(err: unknown): boolean;
|
|
48
48
|
|
|
49
|
-
type
|
|
49
|
+
type RemixAuthHelpers = {
|
|
50
50
|
startGoogleLogin(params: {
|
|
51
51
|
redirectTo: string;
|
|
52
|
+
state?: string;
|
|
52
53
|
}): Promise<{
|
|
53
54
|
url: string;
|
|
55
|
+
codeVerifier?: string;
|
|
54
56
|
}>;
|
|
55
57
|
exchangeCode(params: {
|
|
56
58
|
code: string;
|
|
59
|
+
codeVerifier?: string;
|
|
57
60
|
}): Promise<StoredSession>;
|
|
58
61
|
refreshWithStoredSession(params: {
|
|
59
62
|
session: StoredSession;
|
|
60
63
|
}): Promise<StoredSession>;
|
|
61
64
|
};
|
|
62
|
-
declare function
|
|
65
|
+
declare function createRemixAuthHelpers(config: CoreConfig): RemixAuthHelpers;
|
|
63
66
|
|
|
64
|
-
export { SessionStore, StoredSession,
|
|
67
|
+
export { type RemixAuthHelpers, SessionStore, StoredSession, WithRefreshLock, createDefaultRefreshLock, createLocalSessionStore, createRemixAuthHelpers, defaultSessionFilePath, isInvalidGrantError, isNetworkError };
|
package/dist/auth.js
CHANGED
|
@@ -2,21 +2,21 @@ import {
|
|
|
2
2
|
createDefaultRefreshLock,
|
|
3
3
|
createLocalSessionStore,
|
|
4
4
|
createRefreshLock,
|
|
5
|
+
createRemixAuthHelpers,
|
|
5
6
|
createStoredSessionTokenProvider,
|
|
6
|
-
createSupabaseAuthHelpers,
|
|
7
7
|
defaultSessionFilePath,
|
|
8
8
|
isInvalidGrantError,
|
|
9
9
|
isNetworkError,
|
|
10
10
|
shouldRefreshSoon,
|
|
11
11
|
storedSessionSchema
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-QCRTVR5N.js";
|
|
13
13
|
import "./chunk-7XJGOKEO.js";
|
|
14
14
|
export {
|
|
15
15
|
createDefaultRefreshLock,
|
|
16
16
|
createLocalSessionStore,
|
|
17
17
|
createRefreshLock,
|
|
18
|
+
createRemixAuthHelpers,
|
|
18
19
|
createStoredSessionTokenProvider,
|
|
19
|
-
createSupabaseAuthHelpers,
|
|
20
20
|
defaultSessionFilePath,
|
|
21
21
|
isInvalidGrantError,
|
|
22
22
|
isNetworkError,
|
|
@@ -209,7 +209,7 @@ function isNetworkError(err) {
|
|
|
209
209
|
if (e.cause) return isNetworkError(e.cause);
|
|
210
210
|
return false;
|
|
211
211
|
}
|
|
212
|
-
var INVALID_GRANT_MESSAGE_PATTERN = /invalid[_ ]grant|invalid refresh token|refresh[_ ]token[_ ]not[_ ]found|token has expired or (been )?revoked|already used|user from sub claim in jwt does not exist/i;
|
|
212
|
+
var INVALID_GRANT_MESSAGE_PATTERN = /invalid[_ ]grant|invalid refresh token|refresh[_ ]token[_ ]not[_ ]found|refresh token (has )?(been )?(expired|revoked)|token has expired or (been )?revoked|already used|user from sub claim in jwt does not exist/i;
|
|
213
213
|
function isInvalidGrantError(err) {
|
|
214
214
|
if (!err || typeof err !== "object") return false;
|
|
215
215
|
const e = err;
|
|
@@ -222,6 +222,12 @@ function isInvalidGrantError(err) {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
// src/auth/tokenProvider.ts
|
|
225
|
+
function firstNonEmptyEnvToken() {
|
|
226
|
+
for (const value of [process.env.REMIX_ACCESS_TOKEN, process.env.COMERGE_ACCESS_TOKEN]) {
|
|
227
|
+
if (typeof value === "string" && value.trim().length > 0) return value.trim();
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
225
231
|
function shouldRefreshSoon(session, skewSeconds = 60) {
|
|
226
232
|
const nowSec = Math.floor(Date.now() / 1e3);
|
|
227
233
|
return session.expires_at <= nowSec + skewSeconds;
|
|
@@ -230,15 +236,15 @@ function createStoredSessionTokenProvider(params) {
|
|
|
230
236
|
const withLock = params.withRefreshLock ?? (async (fn) => fn());
|
|
231
237
|
return async (opts) => {
|
|
232
238
|
const forceRefresh = Boolean(opts?.forceRefresh);
|
|
233
|
-
const envToken =
|
|
234
|
-
if (
|
|
235
|
-
return { token: envToken
|
|
239
|
+
const envToken = firstNonEmptyEnvToken();
|
|
240
|
+
if (envToken) {
|
|
241
|
+
return { token: envToken, session: null, fromEnv: true };
|
|
236
242
|
}
|
|
237
243
|
let session = await params.sessionStore.getSession();
|
|
238
244
|
if (!session) {
|
|
239
245
|
throw new RemixError("Not signed in.", {
|
|
240
246
|
exitCode: 2,
|
|
241
|
-
hint: "Run `remix login`, or set
|
|
247
|
+
hint: "Run `remix login`, or set REMIX_ACCESS_TOKEN for CI."
|
|
242
248
|
});
|
|
243
249
|
}
|
|
244
250
|
if (forceRefresh || shouldRefreshSoon(session)) {
|
|
@@ -250,7 +256,7 @@ function createStoredSessionTokenProvider(params) {
|
|
|
250
256
|
if (!base) {
|
|
251
257
|
throw new RemixError("Not signed in.", {
|
|
252
258
|
exitCode: 2,
|
|
253
|
-
hint: "Run `remix login`, or set
|
|
259
|
+
hint: "Run `remix login`, or set REMIX_ACCESS_TOKEN for CI."
|
|
254
260
|
});
|
|
255
261
|
}
|
|
256
262
|
const siblingRefreshed = latest != null && latest.access_token !== sessionAtEntry.access_token;
|
|
@@ -281,80 +287,72 @@ function createStoredSessionTokenProvider(params) {
|
|
|
281
287
|
}
|
|
282
288
|
|
|
283
289
|
// src/auth/supabase.ts
|
|
284
|
-
import
|
|
285
|
-
function
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
},
|
|
292
|
-
removeItem: (k) => {
|
|
293
|
-
map.delete(k);
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
function createSupabaseClient(config, storage) {
|
|
298
|
-
return createClient(config.supabaseUrl, config.supabaseAnonKey, {
|
|
299
|
-
auth: {
|
|
300
|
-
flowType: "pkce",
|
|
301
|
-
persistSession: false,
|
|
302
|
-
autoRefreshToken: false,
|
|
303
|
-
detectSessionInUrl: false,
|
|
304
|
-
storage
|
|
305
|
-
},
|
|
306
|
-
global: {
|
|
307
|
-
headers: {
|
|
308
|
-
"X-Requested-By": "comerge-cli"
|
|
309
|
-
}
|
|
310
|
-
}
|
|
290
|
+
import crypto from "crypto";
|
|
291
|
+
async function postBackend(config, path3, body, options) {
|
|
292
|
+
const response = await fetch(new URL(path3, config.apiUrl), {
|
|
293
|
+
method: "POST",
|
|
294
|
+
headers: { "Content-Type": "application/json", "X-Requested-By": "remix-cli" },
|
|
295
|
+
...options?.credentials ? { credentials: options.credentials } : {},
|
|
296
|
+
body: JSON.stringify(body)
|
|
311
297
|
});
|
|
298
|
+
const payload = await response.json();
|
|
299
|
+
if (!response.ok || !payload.success || payload.responseObject == null) {
|
|
300
|
+
throw new RemixError(payload.message || `Remix backend request failed with status ${response.status}.`, {
|
|
301
|
+
exitCode: 1
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return payload.responseObject;
|
|
312
305
|
}
|
|
313
|
-
function toStoredSession(
|
|
314
|
-
if (!
|
|
315
|
-
throw new RemixError("
|
|
306
|
+
function toStoredSession(payload) {
|
|
307
|
+
if (!payload.accessToken || !payload.refreshToken) {
|
|
308
|
+
throw new RemixError("Remix backend did not return a token pair.", { exitCode: 1 });
|
|
316
309
|
}
|
|
310
|
+
const expiresIn = Number.isFinite(payload.expiresIn) && payload.expiresIn ? payload.expiresIn : 15 * 60;
|
|
317
311
|
return {
|
|
318
|
-
access_token:
|
|
319
|
-
refresh_token:
|
|
320
|
-
expires_at:
|
|
321
|
-
token_type:
|
|
322
|
-
user:
|
|
312
|
+
access_token: payload.accessToken,
|
|
313
|
+
refresh_token: payload.refreshToken,
|
|
314
|
+
expires_at: Math.floor(Date.now() / 1e3) + expiresIn,
|
|
315
|
+
token_type: payload.tokenType ?? "Bearer",
|
|
316
|
+
user: payload.user?.id ? { id: payload.user.id, email: payload.user.email ?? null } : void 0
|
|
323
317
|
};
|
|
324
318
|
}
|
|
325
|
-
function
|
|
326
|
-
const
|
|
327
|
-
const
|
|
319
|
+
function createPkcePair() {
|
|
320
|
+
const codeVerifier = crypto.randomBytes(32).toString("base64url");
|
|
321
|
+
const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
|
|
322
|
+
return { codeVerifier, codeChallenge };
|
|
323
|
+
}
|
|
324
|
+
function normalizeBrowserReturnTo(redirectTo) {
|
|
325
|
+
try {
|
|
326
|
+
const url = new URL(redirectTo);
|
|
327
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
328
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
329
|
+
}
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
return redirectTo;
|
|
333
|
+
}
|
|
334
|
+
function createRemixAuthHelpers(config) {
|
|
328
335
|
return {
|
|
329
336
|
async startGoogleLogin(params) {
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
access_type: "offline",
|
|
337
|
-
prompt: "consent"
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
if (error) throw error;
|
|
342
|
-
if (!data?.url) throw new RemixError("Supabase did not return an OAuth URL.", { exitCode: 1 });
|
|
343
|
-
return { url: data.url };
|
|
337
|
+
const path3 = params.state ? "/v1/auth/cli/login-url" : "/v1/auth/login-url";
|
|
338
|
+
const pkce = params.state ? createPkcePair() : null;
|
|
339
|
+
const body = params.state ? { returnTo: params.redirectTo, state: params.state, codeChallenge: pkce?.codeChallenge } : { returnTo: normalizeBrowserReturnTo(params.redirectTo) };
|
|
340
|
+
const result = await postBackend(config, path3, body, params.state ? void 0 : { credentials: "include" });
|
|
341
|
+
if (!result.url) throw new RemixError("Remix backend did not return an auth URL.", { exitCode: 1 });
|
|
342
|
+
return pkce ? { url: result.url, codeVerifier: pkce.codeVerifier } : { url: result.url };
|
|
344
343
|
},
|
|
345
344
|
async exchangeCode(params) {
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
345
|
+
const result = await postBackend(config, "/v1/auth/cli/exchange", {
|
|
346
|
+
code: params.code,
|
|
347
|
+
...params.codeVerifier ? { codeVerifier: params.codeVerifier } : {}
|
|
348
|
+
});
|
|
349
|
+
return toStoredSession(result);
|
|
350
350
|
},
|
|
351
351
|
async refreshWithStoredSession(params) {
|
|
352
|
-
const
|
|
353
|
-
|
|
352
|
+
const result = await postBackend(config, "/v1/auth/token/refresh", {
|
|
353
|
+
refreshToken: params.session.refresh_token
|
|
354
354
|
});
|
|
355
|
-
|
|
356
|
-
if (!data?.session) throw new RemixError("No session returned after refresh.", { exitCode: 1 });
|
|
357
|
-
return toStoredSession(data.session);
|
|
355
|
+
return toStoredSession(result);
|
|
358
356
|
}
|
|
359
357
|
};
|
|
360
358
|
}
|
|
@@ -369,5 +367,5 @@ export {
|
|
|
369
367
|
isInvalidGrantError,
|
|
370
368
|
shouldRefreshSoon,
|
|
371
369
|
createStoredSessionTokenProvider,
|
|
372
|
-
|
|
370
|
+
createRemixAuthHelpers
|
|
373
371
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { CliError, REMIX_ERROR_CODES, CliError as RemixError, RemixErrorCode } from './errors.js';
|
|
2
2
|
export { CoreConfig, ResolveConfigOptions, configSchema, resolveConfig } from './config.js';
|
|
3
|
-
export { S as SessionStore, a as StoredSession, W as WithRefreshLock, c as createRefreshLock, b as createStoredSessionTokenProvider, s as shouldRefreshSoon, d as storedSessionSchema } from './tokenProvider-
|
|
4
|
-
export { createDefaultRefreshLock, createLocalSessionStore,
|
|
3
|
+
export { S as SessionStore, a as StoredSession, W as WithRefreshLock, c as createRefreshLock, b as createStoredSessionTokenProvider, s as shouldRefreshSoon, d as storedSessionSchema } from './tokenProvider-BP3YfJHm.js';
|
|
4
|
+
export { createDefaultRefreshLock, createLocalSessionStore, createRemixAuthHelpers, defaultSessionFilePath, isInvalidGrantError, isNetworkError } from './auth.js';
|
|
5
5
|
export { AgentMemoryItem, AgentMemoryKind, AgentMemorySearchItem, AgentMemorySearchResponse, AgentMemorySummary, AgentMemoryTimelineResponse, ApiClient, AppContext, AppContextAccessPath, AppPreviewExpectedKind, AppPreviewProcessStatus, AppPreviewQuery, AppPreviewResponse, AppReconcileResponse, AppSandboxCommandRun, AppShareLinkSummary, Bundle, BundlePlatform, BundleStatus, ChangeStepDiffResponse, CreateAppShareLinkPayload, DetectProjectRuntimeTargetsPayload, ImportProjectRuntimeEnvPayload, InitiateBundleRequest, InvitationRecord, MergeRequest, MergeRequestReview, MergeRequestStatus, ProjectRuntimeEnvMetadata, ProjectRuntimeEnvScope, ProjectRuntimeTargetMetadata, ProjectRuntimeTargetScope, ProjectTrigger, ProjectTriggerPayload, ProjectTriggerScope, ProjectTriggerStep, ProjectTriggerStepPayload, ReconcilePreflightResponse, ResolveProjectRuntimeEnvForLocalPullResponse, RunAppRuntimeTargetPayload, RunAppSandboxCommandPayload, RunAppTriggerEventPayload, RunAppTriggerPayload, RuntimeCommandKind, RuntimeTargetKind, SetProjectRuntimeEnvPayload, SetProjectRuntimeTargetPayload, SyncLocalResponse, SyncUpstreamResponse, TriggerEventMetadata, TriggerEventSource, TriggerIntegrationStatus, TriggerMode, TriggerRun, TriggerStepRun, TriggerStepType, UpdateProjectTriggerPayload, createApiClient } from './api.js';
|
|
6
6
|
import 'zod';
|
|
7
7
|
import './contracts-DTeSJnoC.js';
|
package/dist/index.js
CHANGED
|
@@ -5,14 +5,14 @@ import {
|
|
|
5
5
|
createDefaultRefreshLock,
|
|
6
6
|
createLocalSessionStore,
|
|
7
7
|
createRefreshLock,
|
|
8
|
+
createRemixAuthHelpers,
|
|
8
9
|
createStoredSessionTokenProvider,
|
|
9
|
-
createSupabaseAuthHelpers,
|
|
10
10
|
defaultSessionFilePath,
|
|
11
11
|
isInvalidGrantError,
|
|
12
12
|
isNetworkError,
|
|
13
13
|
shouldRefreshSoon,
|
|
14
14
|
storedSessionSchema
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-QCRTVR5N.js";
|
|
16
16
|
import {
|
|
17
17
|
configSchema,
|
|
18
18
|
resolveConfig
|
|
@@ -33,8 +33,8 @@ export {
|
|
|
33
33
|
createDefaultRefreshLock,
|
|
34
34
|
createLocalSessionStore,
|
|
35
35
|
createRefreshLock,
|
|
36
|
+
createRemixAuthHelpers,
|
|
36
37
|
createStoredSessionTokenProvider,
|
|
37
|
-
createSupabaseAuthHelpers,
|
|
38
38
|
defaultSessionFilePath,
|
|
39
39
|
isInvalidGrantError,
|
|
40
40
|
isNetworkError,
|
|
@@ -69,12 +69,12 @@ type TokenProvider = (opts?: {
|
|
|
69
69
|
declare function shouldRefreshSoon(session: StoredSession, skewSeconds?: number): boolean;
|
|
70
70
|
/**
|
|
71
71
|
* Token provider that resolves a fresh access token from a stored
|
|
72
|
-
*
|
|
72
|
+
* Remix app session, refreshing it through `refreshStoredSession` when
|
|
73
73
|
* the cached token is at/near expiry.
|
|
74
74
|
*
|
|
75
75
|
* # Why this code is more careful than it looks
|
|
76
76
|
*
|
|
77
|
-
*
|
|
77
|
+
* Remix rotates refresh tokens: every successful refresh server-
|
|
78
78
|
* side invalidates the old refresh token and issues a new one. If we
|
|
79
79
|
* lose the new token before persisting it (network drop after the
|
|
80
80
|
* server processed our request; multiple processes refreshing the
|
|
@@ -101,7 +101,7 @@ declare function shouldRefreshSoon(session: StoredSession, skewSeconds?: number)
|
|
|
101
101
|
* 3. **Network-vs-auth error classification** — `fetch failed`
|
|
102
102
|
* keeps the existing session (the refresh may or may not have
|
|
103
103
|
* landed server-side; we'll find out on the next attempt, ideally
|
|
104
|
-
* within
|
|
104
|
+
* within the backend's reuse interval). Only `invalid_grant` /
|
|
105
105
|
* "refresh token not found" clears state and surfaces a clean
|
|
106
106
|
* sign-in prompt — by then the token is definitively dead and
|
|
107
107
|
* retrying won't help.
|