@kora-platform/cli 0.8.0-rc1 → 0.8.0-rc10
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-client.d.ts +29 -18
- package/dist/api-client.js +24 -23
- package/dist/api-types.d.ts +22 -2
- package/dist/artifact-commands.js +5 -3
- package/dist/auth-commands.js +84 -8
- package/dist/cli-errors.d.ts +7 -1
- package/dist/cli-errors.js +12 -1
- package/dist/command-flags.d.ts +1 -0
- package/dist/command-flags.js +7 -0
- package/dist/command-registry.js +64 -51
- package/dist/commands.js +90 -48
- package/dist/error-code.d.ts +2 -0
- package/dist/error-code.js +9 -0
- package/dist/extension-commands.js +4 -4
- package/dist/files.d.ts +13 -6
- package/dist/files.js +125 -37
- package/dist/format.d.ts +1 -0
- package/dist/format.js +11 -6
- package/dist/runner.js +14 -5
- package/dist/schema-registry-data.d.ts +71 -27
- package/dist/schema-registry-data.js +87 -36
- package/dist/session-store.js +80 -0
- package/dist/transport-refresh.d.ts +10 -0
- package/dist/transport-refresh.js +51 -0
- package/dist/transport.d.ts +21 -0
- package/dist/transport.js +80 -36
- package/package.json +1 -1
- package/dist/dotenv.d.ts +0 -1
- package/dist/dotenv.js +0 -26
package/dist/session-store.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { mkdir, open, rm, stat } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
1
3
|
import { readSessionFile, removeSessionFile, writeSessionFile } from "./session.js";
|
|
4
|
+
const REFRESH_LOCK_STALE_MS = 30_000;
|
|
5
|
+
const REFRESH_LOCK_RETRY_MS = 25;
|
|
2
6
|
export function createFileSessionStore(path) {
|
|
7
|
+
const refreshLockPath = `${path}.refresh.lock`;
|
|
3
8
|
return {
|
|
4
9
|
async clear() {
|
|
5
10
|
await removeSessionFile(path);
|
|
@@ -9,11 +14,15 @@ export function createFileSessionStore(path) {
|
|
|
9
14
|
},
|
|
10
15
|
async write(session) {
|
|
11
16
|
await writeSessionFile(path, session);
|
|
17
|
+
},
|
|
18
|
+
async withRefreshLock(operation) {
|
|
19
|
+
return withFileRefreshLock(refreshLockPath, operation);
|
|
12
20
|
}
|
|
13
21
|
};
|
|
14
22
|
}
|
|
15
23
|
export function createMemorySessionStore(initial = null) {
|
|
16
24
|
let current = initial;
|
|
25
|
+
let refreshLock = Promise.resolve();
|
|
17
26
|
return {
|
|
18
27
|
async clear() {
|
|
19
28
|
current = null;
|
|
@@ -23,6 +32,77 @@ export function createMemorySessionStore(initial = null) {
|
|
|
23
32
|
},
|
|
24
33
|
async write(session) {
|
|
25
34
|
current = session;
|
|
35
|
+
},
|
|
36
|
+
async withRefreshLock(operation) {
|
|
37
|
+
const previous = refreshLock;
|
|
38
|
+
let release;
|
|
39
|
+
refreshLock = new Promise((resolve) => {
|
|
40
|
+
release = resolve;
|
|
41
|
+
});
|
|
42
|
+
await previous;
|
|
43
|
+
try {
|
|
44
|
+
return await operation();
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
release();
|
|
48
|
+
}
|
|
26
49
|
}
|
|
27
50
|
};
|
|
28
51
|
}
|
|
52
|
+
async function withFileRefreshLock(lockPath, operation) {
|
|
53
|
+
const release = await acquireFileRefreshLock(lockPath);
|
|
54
|
+
try {
|
|
55
|
+
return await operation();
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
await release();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function acquireFileRefreshLock(lockPath) {
|
|
62
|
+
await mkdir(dirname(lockPath), { recursive: true });
|
|
63
|
+
while (true) {
|
|
64
|
+
try {
|
|
65
|
+
const handle = await open(lockPath, "wx", 0o600);
|
|
66
|
+
try {
|
|
67
|
+
await handle.writeFile(`${String(process.pid)} ${new Date().toISOString()}\n`);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
await handle.close();
|
|
71
|
+
}
|
|
72
|
+
return async () => {
|
|
73
|
+
await rm(lockPath, { force: true });
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (!isFileExistsError(error)) {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
await removeStaleFileRefreshLock(lockPath);
|
|
81
|
+
await sleep(REFRESH_LOCK_RETRY_MS);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function removeStaleFileRefreshLock(lockPath) {
|
|
86
|
+
try {
|
|
87
|
+
const fileStat = await stat(lockPath);
|
|
88
|
+
if (Date.now() - fileStat.mtimeMs > REFRESH_LOCK_STALE_MS) {
|
|
89
|
+
await rm(lockPath, { force: true });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (!isMissingFileError(error)) {
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function sleep(ms) {
|
|
99
|
+
await new Promise((resolve) => {
|
|
100
|
+
setTimeout(resolve, ms);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function isFileExistsError(error) {
|
|
104
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === "EEXIST");
|
|
105
|
+
}
|
|
106
|
+
function isMissingFileError(error) {
|
|
107
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
108
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CliSessionState } from "./session.js";
|
|
2
|
+
import type { SessionStore } from "./transport.js";
|
|
3
|
+
export declare function refreshSessionWithCoordination(input: {
|
|
4
|
+
isRefreshTokenInvalidError: (error: unknown) => boolean;
|
|
5
|
+
refreshSessionDirect: (session: CliSessionState) => Promise<CliSessionState>;
|
|
6
|
+
session: CliSessionState;
|
|
7
|
+
sessionStore: SessionStore;
|
|
8
|
+
shouldRefresh: (session: CliSessionState) => boolean;
|
|
9
|
+
}): Promise<CliSessionState>;
|
|
10
|
+
export declare function resolveEffectiveSession(requestSession: CliSessionState | null, sessionStore: SessionStore): Promise<CliSessionState | null>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export async function refreshSessionWithCoordination(input) {
|
|
2
|
+
return withRefreshLock(input.sessionStore, async () => {
|
|
3
|
+
const effectiveSession = (await resolveEffectiveSession(input.session, input.sessionStore)) ?? input.session;
|
|
4
|
+
if (isSessionNewer(effectiveSession, input.session) && !input.shouldRefresh(effectiveSession)) {
|
|
5
|
+
return effectiveSession;
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
return await input.refreshSessionDirect(effectiveSession);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (input.isRefreshTokenInvalidError(error)) {
|
|
12
|
+
const recoveredSession = await resolveEffectiveSession(input.session, input.sessionStore);
|
|
13
|
+
if (recoveredSession &&
|
|
14
|
+
isSessionNewer(recoveredSession, input.session) &&
|
|
15
|
+
!input.shouldRefresh(recoveredSession)) {
|
|
16
|
+
return recoveredSession;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async function withRefreshLock(sessionStore, operation) {
|
|
24
|
+
return sessionStore.withRefreshLock
|
|
25
|
+
? sessionStore.withRefreshLock(operation)
|
|
26
|
+
: operation();
|
|
27
|
+
}
|
|
28
|
+
export async function resolveEffectiveSession(requestSession, sessionStore) {
|
|
29
|
+
if (!requestSession) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const storedSession = await sessionStore.read();
|
|
33
|
+
if (!storedSession || !isSameSessionIdentity(storedSession, requestSession)) {
|
|
34
|
+
return requestSession;
|
|
35
|
+
}
|
|
36
|
+
return isSessionNewer(storedSession, requestSession) ? storedSession : requestSession;
|
|
37
|
+
}
|
|
38
|
+
function isSameSessionIdentity(candidate, current) {
|
|
39
|
+
return candidate.baseUrl === current.baseUrl
|
|
40
|
+
&& candidate.user.id === current.user.id
|
|
41
|
+
&& candidate.accessTokenPayload.sub === current.accessTokenPayload.sub;
|
|
42
|
+
}
|
|
43
|
+
function isSessionNewer(candidate, current) {
|
|
44
|
+
if (candidate.accessToken === current.accessToken && candidate.refreshToken === current.refreshToken) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (candidate.accessTokenPayload.exp !== current.accessTokenPayload.exp) {
|
|
48
|
+
return candidate.accessTokenPayload.exp > current.accessTokenPayload.exp;
|
|
49
|
+
}
|
|
50
|
+
return candidate.accessTokenPayload.iat >= current.accessTokenPayload.iat;
|
|
51
|
+
}
|
package/dist/transport.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type CliSessionState } from "./session.js";
|
|
|
2
2
|
export interface SessionStore {
|
|
3
3
|
clear(): Promise<void>;
|
|
4
4
|
read(): Promise<CliSessionState | null>;
|
|
5
|
+
withRefreshLock?<T>(operation: () => Promise<T>): Promise<T>;
|
|
5
6
|
write(session: CliSessionState): Promise<void>;
|
|
6
7
|
}
|
|
7
8
|
export interface CliAuthSettings {
|
|
@@ -10,6 +11,19 @@ export interface CliAuthSettings {
|
|
|
10
11
|
oidcEnabled: boolean;
|
|
11
12
|
selfServiceOrgCreationEnabled: boolean;
|
|
12
13
|
}
|
|
14
|
+
export interface CliDeviceLoginStart {
|
|
15
|
+
deviceCode: string;
|
|
16
|
+
expiresAt: string;
|
|
17
|
+
pollIntervalSeconds: number;
|
|
18
|
+
userCode: string;
|
|
19
|
+
verificationPath: string;
|
|
20
|
+
}
|
|
21
|
+
export type CliDeviceLoginClaim = {
|
|
22
|
+
session: CliSessionState;
|
|
23
|
+
status: "approved";
|
|
24
|
+
} | {
|
|
25
|
+
status: "denied" | "expired" | "pending";
|
|
26
|
+
};
|
|
13
27
|
export type ApiErrorDetails = Record<string, unknown>;
|
|
14
28
|
type RequestBody = unknown | ((session: CliSessionState) => unknown);
|
|
15
29
|
type BytesRequest = {
|
|
@@ -26,6 +40,7 @@ type BytesRequest = {
|
|
|
26
40
|
validateHeaders?: (headers: Headers) => void;
|
|
27
41
|
};
|
|
28
42
|
export declare class ApiError extends Error {
|
|
43
|
+
readonly code: string;
|
|
29
44
|
readonly detail: string;
|
|
30
45
|
readonly details?: ApiErrorDetails;
|
|
31
46
|
readonly instance: string;
|
|
@@ -34,6 +49,7 @@ export declare class ApiError extends Error {
|
|
|
34
49
|
readonly title: string;
|
|
35
50
|
readonly type: string;
|
|
36
51
|
constructor(input: {
|
|
52
|
+
code?: string;
|
|
37
53
|
detail: string;
|
|
38
54
|
details?: ApiErrorDetails;
|
|
39
55
|
instance: string;
|
|
@@ -59,6 +75,11 @@ export declare function createPlatformTransport(input: {
|
|
|
59
75
|
name: string;
|
|
60
76
|
password: string;
|
|
61
77
|
}): Promise<CliSessionState>;
|
|
78
|
+
startDeviceLogin(baseUrl: string): Promise<CliDeviceLoginStart>;
|
|
79
|
+
claimDeviceLogin(claim: {
|
|
80
|
+
baseUrl: string;
|
|
81
|
+
deviceCode: string;
|
|
82
|
+
}): Promise<CliDeviceLoginClaim>;
|
|
62
83
|
refreshSession(session: CliSessionState): Promise<CliSessionState>;
|
|
63
84
|
requestJson<T>(request: {
|
|
64
85
|
body?: RequestBody;
|
package/dist/transport.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { normalizePublicErrorCode, readPublicErrorCodeFromType } from "./error-code.js";
|
|
1
2
|
import { extractRefreshTokenFromHeaders } from "./session.js";
|
|
3
|
+
import { refreshSessionWithCoordination, resolveEffectiveSession } from "./transport-refresh.js";
|
|
2
4
|
export class ApiError extends Error {
|
|
5
|
+
code;
|
|
3
6
|
detail;
|
|
4
7
|
details;
|
|
5
8
|
instance;
|
|
@@ -10,6 +13,7 @@ export class ApiError extends Error {
|
|
|
10
13
|
constructor(input) {
|
|
11
14
|
super(input.detail);
|
|
12
15
|
this.name = "ApiError";
|
|
16
|
+
this.code = normalizePublicErrorCode(input.code ?? readPublicErrorCodeFromType(input.type));
|
|
13
17
|
this.detail = input.detail;
|
|
14
18
|
if (input.details) {
|
|
15
19
|
this.details = input.details;
|
|
@@ -25,6 +29,13 @@ export class ApiError extends Error {
|
|
|
25
29
|
}
|
|
26
30
|
export function createPlatformTransport(input) {
|
|
27
31
|
const now = input.now ?? Date.now;
|
|
32
|
+
const refreshSession = async (session) => refreshSessionWithCoordination({
|
|
33
|
+
isRefreshTokenInvalidError,
|
|
34
|
+
refreshSessionDirect: async (effectiveSession) => refreshSessionDirect(input.sessionStore, effectiveSession),
|
|
35
|
+
session,
|
|
36
|
+
sessionStore: input.sessionStore,
|
|
37
|
+
shouldRefresh: (effectiveSession) => shouldRefresh(effectiveSession, now)
|
|
38
|
+
});
|
|
28
39
|
return {
|
|
29
40
|
async getAuthSettings(baseUrl) {
|
|
30
41
|
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
@@ -66,19 +77,54 @@ export function createPlatformTransport(input) {
|
|
|
66
77
|
await input.sessionStore.write(session);
|
|
67
78
|
return session;
|
|
68
79
|
},
|
|
69
|
-
async
|
|
70
|
-
const
|
|
80
|
+
async startDeviceLogin(baseUrl) {
|
|
81
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
82
|
+
const path = "/api/v1/auth/device/start";
|
|
83
|
+
const response = await fetch(joinBaseUrl(normalizedBaseUrl, path), {
|
|
84
|
+
method: "POST"
|
|
85
|
+
});
|
|
86
|
+
return handleJsonResponse(response, path);
|
|
87
|
+
},
|
|
88
|
+
async claimDeviceLogin(claim) {
|
|
89
|
+
const normalizedBaseUrl = normalizeBaseUrl(claim.baseUrl);
|
|
90
|
+
const path = "/api/v1/auth/device/claim";
|
|
91
|
+
const response = await fetch(joinBaseUrl(normalizedBaseUrl, path), {
|
|
71
92
|
body: JSON.stringify({
|
|
72
|
-
|
|
93
|
+
deviceCode: claim.deviceCode
|
|
73
94
|
}),
|
|
74
95
|
headers: {
|
|
75
96
|
"content-type": "application/json"
|
|
76
97
|
},
|
|
77
98
|
method: "POST"
|
|
78
99
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
throw await toApiError(response, path);
|
|
102
|
+
}
|
|
103
|
+
const payload = await response.json();
|
|
104
|
+
if (payload.status !== "approved") {
|
|
105
|
+
return { status: payload.status };
|
|
106
|
+
}
|
|
107
|
+
if (!payload.tokens || !payload.user) {
|
|
108
|
+
throw new Error("Device login approval response did not include a session.");
|
|
109
|
+
}
|
|
110
|
+
const refreshToken = extractRefreshTokenFromHeaders(response.headers);
|
|
111
|
+
if (!refreshToken) {
|
|
112
|
+
throw new Error("Refresh token cookie was not returned by the Platform API.");
|
|
113
|
+
}
|
|
114
|
+
const session = {
|
|
115
|
+
accessToken: payload.tokens.accessToken,
|
|
116
|
+
accessTokenPayload: payload.tokens.accessTokenPayload,
|
|
117
|
+
activeOrg: null,
|
|
118
|
+
baseUrl: normalizedBaseUrl,
|
|
119
|
+
refreshToken,
|
|
120
|
+
refreshTokenExpiresAt: payload.tokens.refreshTokenExpiresAt,
|
|
121
|
+
user: payload.user
|
|
122
|
+
};
|
|
123
|
+
await input.sessionStore.write(session);
|
|
124
|
+
return { session, status: "approved" };
|
|
125
|
+
},
|
|
126
|
+
async refreshSession(session) {
|
|
127
|
+
return refreshSession(session);
|
|
82
128
|
},
|
|
83
129
|
async requestJson(request) {
|
|
84
130
|
let session = await resolveEffectiveSession(request.session, input.sessionStore);
|
|
@@ -92,11 +138,11 @@ export function createPlatformTransport(input) {
|
|
|
92
138
|
});
|
|
93
139
|
}
|
|
94
140
|
if (shouldRefresh(session, now) && canRefresh(session)) {
|
|
95
|
-
session = await
|
|
141
|
+
session = await refreshSession(session);
|
|
96
142
|
}
|
|
97
143
|
const firstResponse = await authenticatedFetch(session, request);
|
|
98
144
|
if (firstResponse.status === 401 && canRefresh(session)) {
|
|
99
|
-
session = await
|
|
145
|
+
session = await refreshSession(session);
|
|
100
146
|
return handleJsonResponse(await authenticatedFetch(session, request), request.path);
|
|
101
147
|
}
|
|
102
148
|
return handleJsonResponse(firstResponse, request.path);
|
|
@@ -113,17 +159,36 @@ export function createPlatformTransport(input) {
|
|
|
113
159
|
});
|
|
114
160
|
}
|
|
115
161
|
if (shouldRefresh(session, now) && canRefresh(session)) {
|
|
116
|
-
session = await
|
|
162
|
+
session = await refreshSession(session);
|
|
117
163
|
}
|
|
118
164
|
const firstResponse = await authenticatedFetch(session, request);
|
|
119
165
|
if (firstResponse.status === 401 && canRefresh(session)) {
|
|
120
|
-
session = await
|
|
166
|
+
session = await refreshSession(session);
|
|
121
167
|
return handleBytesResponse(await authenticatedFetch(session, request), request);
|
|
122
168
|
}
|
|
123
169
|
return handleBytesResponse(firstResponse, request);
|
|
124
170
|
}
|
|
125
171
|
};
|
|
126
172
|
}
|
|
173
|
+
async function refreshSessionDirect(sessionStore, session) {
|
|
174
|
+
const response = await fetch(joinBaseUrl(session.baseUrl, "/api/v1/auth/refresh"), {
|
|
175
|
+
body: JSON.stringify({
|
|
176
|
+
refreshToken: session.refreshToken
|
|
177
|
+
}),
|
|
178
|
+
headers: {
|
|
179
|
+
"content-type": "application/json"
|
|
180
|
+
},
|
|
181
|
+
method: "POST"
|
|
182
|
+
});
|
|
183
|
+
const nextSession = await parseSessionResponse(response, session.baseUrl, "/api/v1/auth/refresh", session.activeOrg);
|
|
184
|
+
await sessionStore.write(nextSession);
|
|
185
|
+
return nextSession;
|
|
186
|
+
}
|
|
187
|
+
function isRefreshTokenInvalidError(error) {
|
|
188
|
+
return error instanceof ApiError &&
|
|
189
|
+
error.code === "auth/token_invalid" &&
|
|
190
|
+
error.instance === "/api/v1/auth/refresh";
|
|
191
|
+
}
|
|
127
192
|
async function authenticatedFetch(session, request) {
|
|
128
193
|
const body = resolveRequestBody(request.body, session);
|
|
129
194
|
const hasJsonBody = body !== undefined;
|
|
@@ -243,11 +308,12 @@ async function readResponseBytes(response, request) {
|
|
|
243
308
|
}
|
|
244
309
|
function createMaxBytesError(request, actualBytes, maxBytes) {
|
|
245
310
|
return request.createMaxBytesError?.({ actualBytes, maxBytes }) ?? new ApiError({
|
|
311
|
+
code: "request/too_large",
|
|
246
312
|
detail: `Response body is ${String(actualBytes)} bytes, which exceeds the limit of ${String(maxBytes)} bytes.`,
|
|
247
313
|
instance: request.path,
|
|
248
314
|
status: 413,
|
|
249
315
|
title: "Payload Too Large",
|
|
250
|
-
type: "https://errors.kora.dev/request/
|
|
316
|
+
type: "https://errors.kora.dev/request/too_large"
|
|
251
317
|
});
|
|
252
318
|
}
|
|
253
319
|
function concatBytes(chunks, totalBytes) {
|
|
@@ -271,10 +337,11 @@ async function toApiError(response, path) {
|
|
|
271
337
|
if (contentType.includes("application/json")) {
|
|
272
338
|
const rawBody = await response.text();
|
|
273
339
|
const parsed = tryParseJsonErrorBody(rawBody);
|
|
274
|
-
const code = parsed?.error?.code ?? "internal/error";
|
|
340
|
+
const code = normalizePublicErrorCode(parsed?.error?.code ?? "internal/error");
|
|
275
341
|
const rawDetail = (parsed?.error?.message ?? rawBody) || response.statusText;
|
|
276
342
|
const detail = relabelBackendText(rawDetail || response.statusText);
|
|
277
343
|
return new ApiError({
|
|
344
|
+
code,
|
|
278
345
|
detail,
|
|
279
346
|
...(parsed?.error?.details ? { details: parsed.error.details } : {}),
|
|
280
347
|
instance: path,
|
|
@@ -286,6 +353,7 @@ async function toApiError(response, path) {
|
|
|
286
353
|
}
|
|
287
354
|
const rawDetail = await response.text();
|
|
288
355
|
return new ApiError({
|
|
356
|
+
code: "internal/error",
|
|
289
357
|
detail: relabelBackendText(rawDetail),
|
|
290
358
|
instance: path,
|
|
291
359
|
...(rawDetail ? { rawDetail } : {}),
|
|
@@ -337,27 +405,3 @@ function shouldRefresh(session, now) {
|
|
|
337
405
|
function canRefresh(session) {
|
|
338
406
|
return session.refreshToken.trim().length > 0;
|
|
339
407
|
}
|
|
340
|
-
async function resolveEffectiveSession(requestSession, sessionStore) {
|
|
341
|
-
if (!requestSession) {
|
|
342
|
-
return null;
|
|
343
|
-
}
|
|
344
|
-
const storedSession = await sessionStore.read();
|
|
345
|
-
if (!storedSession || !isSameSessionIdentity(storedSession, requestSession)) {
|
|
346
|
-
return requestSession;
|
|
347
|
-
}
|
|
348
|
-
return isSessionNewer(storedSession, requestSession) ? storedSession : requestSession;
|
|
349
|
-
}
|
|
350
|
-
function isSameSessionIdentity(candidate, current) {
|
|
351
|
-
return candidate.baseUrl === current.baseUrl
|
|
352
|
-
&& candidate.user.id === current.user.id
|
|
353
|
-
&& candidate.accessTokenPayload.sub === current.accessTokenPayload.sub;
|
|
354
|
-
}
|
|
355
|
-
function isSessionNewer(candidate, current) {
|
|
356
|
-
if (candidate.accessToken === current.accessToken && candidate.refreshToken === current.refreshToken) {
|
|
357
|
-
return false;
|
|
358
|
-
}
|
|
359
|
-
if (candidate.accessTokenPayload.exp !== current.accessTokenPayload.exp) {
|
|
360
|
-
return candidate.accessTokenPayload.exp > current.accessTokenPayload.exp;
|
|
361
|
-
}
|
|
362
|
-
return candidate.accessTokenPayload.iat >= current.accessTokenPayload.iat;
|
|
363
|
-
}
|
package/package.json
CHANGED
package/dist/dotenv.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function parseDotEnv(source: string): Record<string, string>;
|
package/dist/dotenv.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
2
|
-
export function parseDotEnv(source) {
|
|
3
|
-
const parsed = {};
|
|
4
|
-
const lines = source.split(/\r?\n/u);
|
|
5
|
-
for (const rawLine of lines) {
|
|
6
|
-
const line = rawLine.trim();
|
|
7
|
-
if (line.length === 0 || line.startsWith("#")) {
|
|
8
|
-
continue;
|
|
9
|
-
}
|
|
10
|
-
const separatorIndex = line.indexOf("=");
|
|
11
|
-
if (separatorIndex <= 0) {
|
|
12
|
-
continue;
|
|
13
|
-
}
|
|
14
|
-
const rawName = line.slice(0, separatorIndex).trim();
|
|
15
|
-
if (!ENV_NAME_PATTERN.test(rawName)) {
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
let value = line.slice(separatorIndex + 1).trim();
|
|
19
|
-
if ((value.startsWith("\"") && value.endsWith("\"")) ||
|
|
20
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
21
|
-
value = value.slice(1, -1);
|
|
22
|
-
}
|
|
23
|
-
parsed[rawName] = value;
|
|
24
|
-
}
|
|
25
|
-
return parsed;
|
|
26
|
-
}
|