@relayfile/sdk 0.10.61 → 0.10.62
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/client.d.ts +27 -1
- package/dist/client.js +26 -5
- package/dist/cloud-token-provider.js +35 -1
- package/dist/index.d.ts +1 -1
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/package.json +6 -6
package/dist/client.d.ts
CHANGED
|
@@ -12,7 +12,15 @@ export type AccessTokenProvider = string | (() => string | Promise<string>);
|
|
|
12
12
|
* `RELAYFILE_REFRESH_TOKEN`). Pass this as `token` and the client auto-wraps it
|
|
13
13
|
* in the correct rotating provider — a RelayAuth `relay_pa` pair refreshes at
|
|
14
14
|
* RelayAuth, a Cloud device-auth pair at Cloud — so the short access token is
|
|
15
|
-
* rotated automatically
|
|
15
|
+
* rotated automatically.
|
|
16
|
+
*
|
|
17
|
+
* Rotation is in-memory: on refresh the refresh token rotates too and the old
|
|
18
|
+
* one is revoked, so the pair you passed here (e.g. the one in your `.env`) is
|
|
19
|
+
* spent after the first refresh. In a long-lived process this is invisible, but
|
|
20
|
+
* a process that restarts (a redeploy, a new worker) will reload the original,
|
|
21
|
+
* now-revoked pair and fail with `invalid_grant` / "refresh token revoked".
|
|
22
|
+
* Supply {@link RelayFileClientOptions.onTokens} to persist each rotated pair so
|
|
23
|
+
* the next process boots from a live credential.
|
|
16
24
|
*/
|
|
17
25
|
export interface RelayFileTokenPair {
|
|
18
26
|
accessToken: string;
|
|
@@ -25,6 +33,15 @@ export interface RelayFileTokenPair {
|
|
|
25
33
|
/** Cloud API URL for a Cloud device-auth pair. */
|
|
26
34
|
apiUrl?: string;
|
|
27
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Called after the client rotates the access+refresh {@link RelayFileTokenPair}.
|
|
38
|
+
* Persist the new pair (e.g. write it back to your secret store / `.env`) so a
|
|
39
|
+
* later process starts from a live credential instead of the revoked original.
|
|
40
|
+
* The callback is best-effort and retried on the next request until it succeeds;
|
|
41
|
+
* a throw is swallowed (the in-memory access token stays usable) but leaves the
|
|
42
|
+
* pair unpersisted, so make it durable.
|
|
43
|
+
*/
|
|
44
|
+
export type RelayFileTokenPersister = (tokens: RelayFileTokenPair) => void | Promise<void>;
|
|
28
45
|
export interface RelayFileRetryOptions {
|
|
29
46
|
maxRetries?: number;
|
|
30
47
|
baseDelayMs?: number;
|
|
@@ -58,6 +75,15 @@ export interface RelayFileClientOptions {
|
|
|
58
75
|
* and `aud` containing `relayfile`.
|
|
59
76
|
*/
|
|
60
77
|
token: AccessTokenProvider | RelayFileTokenPair;
|
|
78
|
+
/**
|
|
79
|
+
* Persistence hook for a rotating {@link RelayFileTokenPair}. Called with the
|
|
80
|
+
* new pair each time the access token is refreshed (the refresh token rotates
|
|
81
|
+
* too). Write it back to your secret store so a process that restarts reloads a
|
|
82
|
+
* live credential instead of the spent original — without this, a `token` pair
|
|
83
|
+
* survives only within one process lifetime. Ignored when `token` is a string
|
|
84
|
+
* or token factory (that provider owns its own rotation).
|
|
85
|
+
*/
|
|
86
|
+
onTokens?: RelayFileTokenPersister;
|
|
61
87
|
fetchImpl?: typeof fetch;
|
|
62
88
|
userAgent?: string;
|
|
63
89
|
retry?: RelayFileRetryOptions;
|
package/dist/client.js
CHANGED
|
@@ -4,8 +4,9 @@ import { createRelayauthPathTokenAccessTokenProvider, isRelayauthRefreshToken }
|
|
|
4
4
|
import { InvalidStateError, MergeConflictError, ParentMovedError, PayloadTooLargeError, QueueFullError, RelayFileApiError, RevisionConflictError } from "./errors.js";
|
|
5
5
|
// Resolve the `token` option into a concrete AccessTokenProvider. A string or
|
|
6
6
|
// function is used as-is; a token PAIR is auto-wrapped in the rotating provider
|
|
7
|
-
// that matches the refresh token's issuer.
|
|
8
|
-
|
|
7
|
+
// that matches the refresh token's issuer. When `onTokens` is supplied, each
|
|
8
|
+
// rotated pair is handed back so the caller can persist it across restarts.
|
|
9
|
+
function resolveTokenOption(token, onTokens) {
|
|
9
10
|
if (typeof token === "string" || typeof token === "function") {
|
|
10
11
|
return token;
|
|
11
12
|
}
|
|
@@ -16,7 +17,17 @@ function resolveTokenOption(token) {
|
|
|
16
17
|
accessTokenExpiresAt: token.accessTokenExpiresAt,
|
|
17
18
|
refreshTokenExpiresAt: token.refreshTokenExpiresAt,
|
|
18
19
|
relayauthUrl: token.relayauthUrl
|
|
19
|
-
}
|
|
20
|
+
}, onTokens
|
|
21
|
+
? {
|
|
22
|
+
onTokens: (rotated) => onTokens({
|
|
23
|
+
accessToken: rotated.accessToken,
|
|
24
|
+
refreshToken: rotated.refreshToken,
|
|
25
|
+
accessTokenExpiresAt: rotated.accessTokenExpiresAt,
|
|
26
|
+
refreshTokenExpiresAt: rotated.refreshTokenExpiresAt,
|
|
27
|
+
relayauthUrl: rotated.relayauthUrl
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
: undefined);
|
|
20
31
|
}
|
|
21
32
|
return createRelayfileCloudAccessTokenProvider({
|
|
22
33
|
apiUrl: token.apiUrl,
|
|
@@ -25,7 +36,17 @@ function resolveTokenOption(token) {
|
|
|
25
36
|
// The cloud provider treats an unparseable expiry as "refresh now".
|
|
26
37
|
accessTokenExpiresAt: token.accessTokenExpiresAt ?? "",
|
|
27
38
|
refreshTokenExpiresAt: token.refreshTokenExpiresAt
|
|
28
|
-
}
|
|
39
|
+
}, onTokens
|
|
40
|
+
? {
|
|
41
|
+
onTokens: (rotated) => onTokens({
|
|
42
|
+
accessToken: rotated.accessToken,
|
|
43
|
+
refreshToken: rotated.refreshToken,
|
|
44
|
+
accessTokenExpiresAt: rotated.accessTokenExpiresAt,
|
|
45
|
+
refreshTokenExpiresAt: rotated.refreshTokenExpiresAt,
|
|
46
|
+
apiUrl: rotated.apiUrl
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
: undefined);
|
|
29
50
|
}
|
|
30
51
|
/** Default base URL for the hosted Relayfile API */
|
|
31
52
|
export const DEFAULT_RELAYFILE_BASE_URL = "https://api.relayfile.dev";
|
|
@@ -1249,7 +1270,7 @@ export class RelayFileClient {
|
|
|
1249
1270
|
retryOptions;
|
|
1250
1271
|
constructor(options) {
|
|
1251
1272
|
this.baseUrl = (options.baseUrl ?? DEFAULT_RELAYFILE_BASE_URL).replace(/\/+$/, "");
|
|
1252
|
-
this.tokenProvider = resolveTokenOption(options.token);
|
|
1273
|
+
this.tokenProvider = resolveTokenOption(options.token, options.onTokens);
|
|
1253
1274
|
this.fetchImpl = options.fetchImpl ?? fetch.bind(globalThis);
|
|
1254
1275
|
this.userAgent = options.userAgent;
|
|
1255
1276
|
this.retryOptions = normalizeRetryOptions(options.retry);
|
|
@@ -14,6 +14,11 @@ export function createRelayfileCloudAccessTokenProvider(initialTokens, options =
|
|
|
14
14
|
apiUrl: normalizeNonEmptyString(initialTokens.apiUrl) ?? cloudApiUrl
|
|
15
15
|
};
|
|
16
16
|
let refreshPromise;
|
|
17
|
+
// A rotated token set whose `onTokens` persistence callback has not yet
|
|
18
|
+
// succeeded. Until it does, a consumed refresh token may still live in the
|
|
19
|
+
// caller's persisted copy (→ `invalid_grant` after restart), so we retry the
|
|
20
|
+
// callback on every subsequent use rather than treating rotation as complete.
|
|
21
|
+
let pendingPersist;
|
|
17
22
|
return async () => {
|
|
18
23
|
if (shouldRefresh(tokens, refreshWindowMs)) {
|
|
19
24
|
if (!refreshPromise) {
|
|
@@ -26,6 +31,10 @@ export function createRelayfileCloudAccessTokenProvider(initialTokens, options =
|
|
|
26
31
|
refreshPromise = undefined;
|
|
27
32
|
}
|
|
28
33
|
}
|
|
34
|
+
else if (pendingPersist) {
|
|
35
|
+
// No refresh needed, but a prior rotation still owes a successful persist.
|
|
36
|
+
await persistPendingTokens();
|
|
37
|
+
}
|
|
29
38
|
return tokens.accessToken;
|
|
30
39
|
};
|
|
31
40
|
async function refresh() {
|
|
@@ -42,7 +51,32 @@ export function createRelayfileCloudAccessTokenProvider(initialTokens, options =
|
|
|
42
51
|
throw new CloudApiError(response.status, payload);
|
|
43
52
|
}
|
|
44
53
|
tokens = readTokenSetFromPayload(payload, cloudApiUrl);
|
|
45
|
-
|
|
54
|
+
pendingPersist = { ...tokens };
|
|
55
|
+
await persistPendingTokens();
|
|
56
|
+
}
|
|
57
|
+
// Best-effort, retried persistence. A callback failure never rejects the
|
|
58
|
+
// provider — the in-memory access token is valid and usable — but the set
|
|
59
|
+
// stays pending so the next provider() call re-attempts, so a durable copy
|
|
60
|
+
// eventually catches up with the rotated refresh token.
|
|
61
|
+
async function persistPendingTokens() {
|
|
62
|
+
if (!pendingPersist) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!options.onTokens) {
|
|
66
|
+
pendingPersist = undefined;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const toPersist = pendingPersist;
|
|
70
|
+
try {
|
|
71
|
+
await options.onTokens({ ...toPersist });
|
|
72
|
+
// Only clear if nothing rotated again while the callback was in flight.
|
|
73
|
+
if (pendingPersist === toPersist) {
|
|
74
|
+
pendingPersist = undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Keep pending; retried on the next use.
|
|
79
|
+
}
|
|
46
80
|
}
|
|
47
81
|
}
|
|
48
82
|
function shouldRefresh(tokens, refreshWindowMs) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { RelayFileClient, DEFAULT_RELAYFILE_BASE_URL, type AccessTokenProvider, type RelayFileTokenPair, type RelayFileChangeLogOptions, type ConnectWebSocketOptions, type RelayFileClientOptions, type RelayFileRetryOptions, type WebSocketConnection } from "./client.js";
|
|
1
|
+
export { RelayFileClient, DEFAULT_RELAYFILE_BASE_URL, type AccessTokenProvider, type RelayFileTokenPair, type RelayFileTokenPersister, type RelayFileChangeLogOptions, type ConnectWebSocketOptions, type RelayFileClientOptions, type RelayFileRetryOptions, type WebSocketConnection } from "./client.js";
|
|
2
2
|
export type { RelayFileReadCacheOptions } from "./types.js";
|
|
3
3
|
export { RelayfileSetup, RELAYFILE_SDK_VERSION, WorkspaceHandle } from "./setup.js";
|
|
4
4
|
export { type RelayfileCloudLoginOptions, type RelayfileCloudTokenSet, type RelayfileCloudTokenSetupOptions } from "./cloud-login.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const RELAYFILE_VERSION = "0.10.
|
|
1
|
+
export declare const RELAYFILE_VERSION = "0.10.62";
|
package/dist/package-version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/sync-package-version.mjs during every SDK build.
|
|
2
|
-
export const RELAYFILE_VERSION = "0.10.
|
|
2
|
+
export const RELAYFILE_VERSION = "0.10.62";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@relayfile/sdk",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.62",
|
|
4
4
|
"description": "TypeScript SDK for relayfile — real-time filesystem for humans and agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -60,15 +60,15 @@
|
|
|
60
60
|
"prepublishOnly": "npm run build"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@relayfile/core": "0.10.
|
|
63
|
+
"@relayfile/core": "0.10.62",
|
|
64
64
|
"ignore": "^7.0.5",
|
|
65
65
|
"tar": "^7.5.10"
|
|
66
66
|
},
|
|
67
67
|
"optionalDependencies": {
|
|
68
|
-
"@relayfile/mount-darwin-arm64": "0.10.
|
|
69
|
-
"@relayfile/mount-darwin-x64": "0.10.
|
|
70
|
-
"@relayfile/mount-linux-arm64": "0.10.
|
|
71
|
-
"@relayfile/mount-linux-x64": "0.10.
|
|
68
|
+
"@relayfile/mount-darwin-arm64": "0.10.62",
|
|
69
|
+
"@relayfile/mount-darwin-x64": "0.10.62",
|
|
70
|
+
"@relayfile/mount-linux-arm64": "0.10.62",
|
|
71
|
+
"@relayfile/mount-linux-x64": "0.10.62"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"typescript": "^5.7.3",
|