frameio 3.2.2 → 4.1.1

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.
Files changed (119) hide show
  1. package/README.md +159 -4
  2. package/dist/cjs/BaseClient.d.ts +4 -0
  3. package/dist/cjs/BaseClient.js +4 -2
  4. package/dist/cjs/api/resources/accountPermissions/client/Client.js +6 -6
  5. package/dist/cjs/api/resources/accounts/client/Client.js +13 -13
  6. package/dist/cjs/api/resources/comments/client/Client.js +48 -48
  7. package/dist/cjs/api/resources/files/client/Client.js +77 -77
  8. package/dist/cjs/api/resources/folders/client/Client.js +56 -56
  9. package/dist/cjs/api/resources/metadata/client/Client.d.ts +4 -4
  10. package/dist/cjs/api/resources/metadata/client/Client.js +18 -18
  11. package/dist/cjs/api/resources/metadata/client/requests/BulkUpdateMetadataParams.d.ts +4 -4
  12. package/dist/cjs/api/resources/metadataFields/client/Client.js +28 -28
  13. package/dist/cjs/api/resources/projectPermissions/client/Client.js +20 -20
  14. package/dist/cjs/api/resources/projects/client/Client.js +34 -34
  15. package/dist/cjs/api/resources/shares/client/Client.d.ts +4 -4
  16. package/dist/cjs/api/resources/shares/client/Client.js +72 -72
  17. package/dist/cjs/api/resources/shares/client/requests/AddAssetParams.d.ts +1 -1
  18. package/dist/cjs/api/resources/shares/client/requests/CreateShareParams.d.ts +2 -2
  19. package/dist/cjs/api/resources/shares/client/requests/UpdateShareParams.d.ts +1 -1
  20. package/dist/cjs/api/resources/users/client/Client.js +7 -7
  21. package/dist/cjs/api/resources/versionStacks/client/Client.d.ts +6 -2
  22. package/dist/cjs/api/resources/versionStacks/client/Client.js +48 -44
  23. package/dist/cjs/api/resources/versionStacks/client/requests/VersionStackCreateParams.d.ts +1 -1
  24. package/dist/cjs/api/resources/webhooks/client/Client.js +34 -34
  25. package/dist/cjs/api/resources/workspacePermissions/client/Client.js +20 -20
  26. package/dist/cjs/api/resources/workspaces/client/Client.js +34 -34
  27. package/dist/cjs/api/types/Account.d.ts +0 -2
  28. package/dist/cjs/api/types/AuditLogwithIncludes.d.ts +4 -0
  29. package/dist/cjs/api/types/AuditLogwithIncludes.js +4 -0
  30. package/dist/cjs/api/types/Filters.d.ts +4 -0
  31. package/dist/cjs/api/types/Filters.js +4 -0
  32. package/dist/cjs/index.d.ts +4 -0
  33. package/dist/cjs/index.js +21 -1
  34. package/dist/cjs/oauth/BaseAuth.d.ts +66 -0
  35. package/dist/cjs/oauth/BaseAuth.js +113 -0
  36. package/dist/cjs/oauth/NativeAppAuth.d.ts +32 -0
  37. package/dist/cjs/oauth/NativeAppAuth.js +35 -0
  38. package/dist/cjs/oauth/SPAAuth.d.ts +38 -0
  39. package/dist/cjs/oauth/SPAAuth.js +96 -0
  40. package/dist/cjs/oauth/ServerToServerAuth.d.ts +17 -0
  41. package/dist/cjs/oauth/ServerToServerAuth.js +49 -0
  42. package/dist/cjs/oauth/TokenManager.d.ts +83 -0
  43. package/dist/cjs/oauth/TokenManager.js +174 -0
  44. package/dist/cjs/oauth/WebAppAuth.d.ts +29 -0
  45. package/dist/cjs/oauth/WebAppAuth.js +88 -0
  46. package/dist/cjs/oauth/errors.d.ts +41 -0
  47. package/dist/cjs/oauth/errors.js +83 -0
  48. package/dist/cjs/oauth/http.d.ts +70 -0
  49. package/dist/cjs/oauth/http.js +280 -0
  50. package/dist/cjs/oauth/index.d.ts +34 -0
  51. package/dist/cjs/oauth/index.js +47 -0
  52. package/dist/cjs/oauth/logger.d.ts +17 -0
  53. package/dist/cjs/oauth/logger.js +18 -0
  54. package/dist/cjs/oauth/pkce.d.ts +30 -0
  55. package/dist/cjs/oauth/pkce.js +102 -0
  56. package/dist/cjs/oauth/validation.d.ts +17 -0
  57. package/dist/cjs/oauth/validation.js +55 -0
  58. package/dist/cjs/version.d.ts +1 -1
  59. package/dist/cjs/version.js +1 -1
  60. package/dist/esm/BaseClient.d.mts +4 -0
  61. package/dist/esm/BaseClient.mjs +4 -2
  62. package/dist/esm/api/resources/accountPermissions/client/Client.mjs +7 -7
  63. package/dist/esm/api/resources/accounts/client/Client.mjs +13 -13
  64. package/dist/esm/api/resources/comments/client/Client.mjs +49 -49
  65. package/dist/esm/api/resources/files/client/Client.mjs +78 -78
  66. package/dist/esm/api/resources/folders/client/Client.mjs +57 -57
  67. package/dist/esm/api/resources/metadata/client/Client.d.mts +4 -4
  68. package/dist/esm/api/resources/metadata/client/Client.mjs +19 -19
  69. package/dist/esm/api/resources/metadata/client/requests/BulkUpdateMetadataParams.d.mts +4 -4
  70. package/dist/esm/api/resources/metadataFields/client/Client.mjs +29 -29
  71. package/dist/esm/api/resources/projectPermissions/client/Client.mjs +21 -21
  72. package/dist/esm/api/resources/projects/client/Client.mjs +35 -35
  73. package/dist/esm/api/resources/shares/client/Client.d.mts +4 -4
  74. package/dist/esm/api/resources/shares/client/Client.mjs +73 -73
  75. package/dist/esm/api/resources/shares/client/requests/AddAssetParams.d.mts +1 -1
  76. package/dist/esm/api/resources/shares/client/requests/CreateShareParams.d.mts +2 -2
  77. package/dist/esm/api/resources/shares/client/requests/UpdateShareParams.d.mts +1 -1
  78. package/dist/esm/api/resources/users/client/Client.mjs +8 -8
  79. package/dist/esm/api/resources/versionStacks/client/Client.d.mts +6 -2
  80. package/dist/esm/api/resources/versionStacks/client/Client.mjs +49 -45
  81. package/dist/esm/api/resources/versionStacks/client/requests/VersionStackCreateParams.d.mts +1 -1
  82. package/dist/esm/api/resources/webhooks/client/Client.mjs +35 -35
  83. package/dist/esm/api/resources/workspacePermissions/client/Client.mjs +21 -21
  84. package/dist/esm/api/resources/workspaces/client/Client.mjs +35 -35
  85. package/dist/esm/api/types/Account.d.mts +0 -2
  86. package/dist/esm/api/types/AuditLogwithIncludes.d.mts +4 -0
  87. package/dist/esm/api/types/AuditLogwithIncludes.mjs +4 -0
  88. package/dist/esm/api/types/Filters.d.mts +4 -0
  89. package/dist/esm/api/types/Filters.mjs +4 -0
  90. package/dist/esm/index.d.mts +4 -0
  91. package/dist/esm/index.mjs +6 -0
  92. package/dist/esm/oauth/BaseAuth.d.mts +66 -0
  93. package/dist/esm/oauth/BaseAuth.mjs +109 -0
  94. package/dist/esm/oauth/NativeAppAuth.d.mts +32 -0
  95. package/dist/esm/oauth/NativeAppAuth.mjs +31 -0
  96. package/dist/esm/oauth/SPAAuth.d.mts +38 -0
  97. package/dist/esm/oauth/SPAAuth.mjs +92 -0
  98. package/dist/esm/oauth/ServerToServerAuth.d.mts +17 -0
  99. package/dist/esm/oauth/ServerToServerAuth.mjs +45 -0
  100. package/dist/esm/oauth/TokenManager.d.mts +83 -0
  101. package/dist/esm/oauth/TokenManager.mjs +170 -0
  102. package/dist/esm/oauth/WebAppAuth.d.mts +29 -0
  103. package/dist/esm/oauth/WebAppAuth.mjs +84 -0
  104. package/dist/esm/oauth/errors.d.mts +41 -0
  105. package/dist/esm/oauth/errors.mjs +72 -0
  106. package/dist/esm/oauth/http.d.mts +70 -0
  107. package/dist/esm/oauth/http.mjs +274 -0
  108. package/dist/esm/oauth/index.d.mts +34 -0
  109. package/dist/esm/oauth/index.mjs +30 -0
  110. package/dist/esm/oauth/logger.d.mts +17 -0
  111. package/dist/esm/oauth/logger.mjs +15 -0
  112. package/dist/esm/oauth/pkce.d.mts +30 -0
  113. package/dist/esm/oauth/pkce.mjs +98 -0
  114. package/dist/esm/oauth/validation.d.mts +17 -0
  115. package/dist/esm/oauth/validation.mjs +51 -0
  116. package/dist/esm/version.d.mts +1 -1
  117. package/dist/esm/version.mjs +1 -1
  118. package/package.json +1 -1
  119. package/reference.md +14 -10
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Internal HTTP helper for Adobe IMS token endpoint calls.
3
+ *
4
+ * Supports configurable timeouts, retries with exponential backoff,
5
+ * rate-limit handling (429), and response validation.
6
+ *
7
+ * Allows configurable IMS base URL for staging/alternative environments,
8
+ * and optional fetch injection for proxy, TLS, and custom HTTP handling.
9
+ */
10
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
11
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
12
+ return new (P || (P = Promise))(function (resolve, reject) {
13
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
14
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
15
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
16
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
17
+ });
18
+ };
19
+ import { AuthenticationError, ConfigurationError, NetworkError, RateLimitError, TokenExpiredError } from "./errors";
20
+ import { noopLogger } from "./logger";
21
+ // Default Adobe IMS base URL (production). Override via imsBaseUrl for staging.
22
+ export const DEFAULT_IMS_BASE_URL = "https://ims-na1.adobelogin.com";
23
+ /**
24
+ * Build authorize, token, and revoke URLs from an IMS base URL.
25
+ *
26
+ * @throws {Error} if the base URL does not use HTTPS.
27
+ */
28
+ export function buildImsUrls(imsBaseUrl) {
29
+ if (!imsBaseUrl.toLowerCase().startsWith("https://")) {
30
+ throw new ConfigurationError(`imsBaseUrl must use HTTPS, got: ${imsBaseUrl}`);
31
+ }
32
+ const base = imsBaseUrl.replace(/\/$/, "");
33
+ return {
34
+ authorizeUrl: `${base}/ims/authorize/v2`,
35
+ tokenUrl: `${base}/ims/token/v3`,
36
+ revokeUrl: `${base}/ims/revoke`,
37
+ };
38
+ }
39
+ const DEFAULT_URLS = buildImsUrls(DEFAULT_IMS_BASE_URL);
40
+ export const AUTHORIZE_URL = DEFAULT_URLS.authorizeUrl;
41
+ export const TOKEN_URL = DEFAULT_URLS.tokenUrl;
42
+ export const REVOKE_URL = DEFAULT_URLS.revokeUrl;
43
+ // OAuth 2.0 RFC 6749 specifies space-separated scopes
44
+ export const DEFAULT_SCOPES = "openid email profile offline_access additional_info.roles";
45
+ export const S2S_SCOPES = "openid AdobeID frame.s2s.all";
46
+ // Defaults
47
+ export const DEFAULT_TIMEOUT = 30000; // milliseconds
48
+ export const DEFAULT_MAX_RETRIES = 2;
49
+ const RETRY_BACKOFF_SCHEDULE = [1000, 2000, 5000, 10000]; // ms
50
+ const MAX_RETRY_AFTER_MS = 60000; // cap Retry-After to 60 s, matching Python
51
+ const RETRYABLE_STATUS_CODES = new Set([500, 502, 503, 504]);
52
+ /**
53
+ * Parse a Retry-After header value (seconds or HTTP-date).
54
+ * Returns milliseconds to wait, or undefined.
55
+ */
56
+ function parseRetryAfter(header) {
57
+ if (!header)
58
+ return undefined;
59
+ const seconds = Number(header);
60
+ if (!isNaN(seconds))
61
+ return seconds * 1000;
62
+ const date = new Date(header);
63
+ if (isNaN(date.getTime()))
64
+ return undefined;
65
+ return Math.max(date.getTime() - Date.now(), 0);
66
+ }
67
+ /**
68
+ * Validate that a token response has the required fields.
69
+ */
70
+ function validateTokenResponse(data, log) {
71
+ if (typeof data !== "object" || data === null) {
72
+ throw new AuthenticationError("invalid_response", "Token endpoint returned non-object response.");
73
+ }
74
+ const obj = data;
75
+ if (!("access_token" in obj) || typeof obj.access_token !== "string" || !obj.access_token) {
76
+ throw new AuthenticationError("invalid_response", "Token response missing or empty 'access_token'.");
77
+ }
78
+ const tokenType = obj.token_type;
79
+ if (tokenType == null) {
80
+ log.debug("Token response missing 'token_type' field.");
81
+ }
82
+ else if (typeof tokenType === "string" && tokenType.toLowerCase() !== "bearer") {
83
+ log.warn(`Unexpected token_type: ${tokenType} (expected 'bearer').`);
84
+ }
85
+ if (obj.expires_in !== undefined && obj.expires_in !== null) {
86
+ if (typeof obj.expires_in !== "number" || obj.expires_in <= 0) {
87
+ log.warn(`Invalid expires_in value (${obj.expires_in}), using default.`);
88
+ delete obj.expires_in;
89
+ }
90
+ }
91
+ return obj;
92
+ }
93
+ /** Sleep for a given number of milliseconds. */
94
+ function sleep(ms) {
95
+ return new Promise((resolve) => setTimeout(resolve, ms));
96
+ }
97
+ /**
98
+ * Send a POST to the Adobe IMS token endpoint.
99
+ *
100
+ * Rate-limit retries (429) are tracked separately from error retries
101
+ * (5xx / network failures), so a 429 does not consume the error retry
102
+ * budget and vice versa.
103
+ *
104
+ * @param data - Form-encoded body parameters.
105
+ * @param options - Timeout, retry, and logger options.
106
+ * @returns Parsed token response.
107
+ */
108
+ export function tokenRequest(data_1) {
109
+ return __awaiter(this, arguments, void 0, function* (data, options = {}) {
110
+ var _a, _b, _c, _d, _e, _f;
111
+ const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : DEFAULT_TIMEOUT;
112
+ const maxRetries = (_b = options.maxRetries) !== null && _b !== void 0 ? _b : DEFAULT_MAX_RETRIES;
113
+ const log = (_c = options.logger) !== null && _c !== void 0 ? _c : noopLogger;
114
+ const tokenUrl = (_d = options.tokenUrl) !== null && _d !== void 0 ? _d : TOKEN_URL;
115
+ const fetchFn = (_e = options.fetch) !== null && _e !== void 0 ? _e : (typeof globalThis.fetch !== "undefined" ? globalThis.fetch.bind(globalThis) : undefined);
116
+ if (fetchFn === undefined) {
117
+ throw new NetworkError("no fetch implementation available; please provide options.fetch or polyfill global fetch");
118
+ }
119
+ const body = new URLSearchParams(data).toString();
120
+ const maxErrorAttempts = maxRetries + 1;
121
+ const maxRateLimitAttempts = maxRetries + 1;
122
+ const maxTotal = maxErrorAttempts + maxRateLimitAttempts;
123
+ let errorAttempts = 0;
124
+ let rateLimitAttempts = 0;
125
+ for (let i = 0; i < maxTotal; i++) {
126
+ let response;
127
+ try {
128
+ log.debug(`Token request (errors=${errorAttempts}/${maxErrorAttempts}, rate_limits=${rateLimitAttempts}/${maxRateLimitAttempts})`);
129
+ const controller = new AbortController();
130
+ const timer = setTimeout(() => controller.abort(), timeout);
131
+ try {
132
+ response = yield fetchFn(tokenUrl, {
133
+ method: "POST",
134
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
135
+ body,
136
+ signal: controller.signal,
137
+ });
138
+ }
139
+ finally {
140
+ clearTimeout(timer);
141
+ }
142
+ }
143
+ catch (err) {
144
+ errorAttempts++;
145
+ log.warn(`Network error on attempt ${errorAttempts}: ${err}`);
146
+ if (errorAttempts < maxErrorAttempts) {
147
+ const backoff = RETRY_BACKOFF_SCHEDULE[Math.min(errorAttempts - 1, RETRY_BACKOFF_SCHEDULE.length - 1)];
148
+ log.debug(`Retrying in ${backoff}ms...`);
149
+ yield sleep(backoff);
150
+ continue;
151
+ }
152
+ if (err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError")) {
153
+ throw new NetworkError(`Token request timed out after ${timeout}ms (${maxErrorAttempts} attempts).`);
154
+ }
155
+ throw new NetworkError(`Token request failed after ${maxErrorAttempts} attempts: ${err instanceof Error ? err.message : String(err)}`);
156
+ }
157
+ // Rate limit handling — separate budget from error retries
158
+ if (response.status === 429) {
159
+ rateLimitAttempts++;
160
+ const retryAfterMs = parseRetryAfter(response.headers.get("retry-after"));
161
+ const retryAfterSec = retryAfterMs !== undefined ? retryAfterMs / 1000 : undefined;
162
+ log.warn(`Rate limited (429), attempt ${rateLimitAttempts}/${maxRateLimitAttempts}. Retry-After: ${retryAfterSec}s`);
163
+ if (rateLimitAttempts < maxRateLimitAttempts) {
164
+ const waitMs = Math.min(retryAfterMs !== null && retryAfterMs !== void 0 ? retryAfterMs : RETRY_BACKOFF_SCHEDULE[Math.min(rateLimitAttempts - 1, RETRY_BACKOFF_SCHEDULE.length - 1)], MAX_RETRY_AFTER_MS);
165
+ yield sleep(waitMs);
166
+ continue;
167
+ }
168
+ throw new RateLimitError(retryAfterSec);
169
+ }
170
+ // Retryable server errors
171
+ if (RETRYABLE_STATUS_CODES.has(response.status)) {
172
+ errorAttempts++;
173
+ log.warn(`Server error ${response.status}, attempt ${errorAttempts}/${maxErrorAttempts}`);
174
+ if (errorAttempts < maxErrorAttempts) {
175
+ const backoff = RETRY_BACKOFF_SCHEDULE[Math.min(errorAttempts - 1, RETRY_BACKOFF_SCHEDULE.length - 1)];
176
+ yield sleep(backoff);
177
+ continue;
178
+ }
179
+ // Fall through to error handling below
180
+ }
181
+ if (!response.ok) {
182
+ let errorCode = `http_${response.status}`;
183
+ let errorDesc;
184
+ try {
185
+ const errBody = (yield response.json());
186
+ errorCode = (_f = errBody.error) !== null && _f !== void 0 ? _f : errorCode;
187
+ errorDesc = errBody.error_description;
188
+ }
189
+ catch (_g) {
190
+ // ignore parse errors
191
+ }
192
+ // Detect expired refresh token
193
+ if ((errorCode === "invalid_grant" || errorCode === "invalid_token") &&
194
+ errorDesc &&
195
+ errorDesc.toLowerCase().includes("expired")) {
196
+ log.error(`Token expired: ${errorCode}`);
197
+ throw new TokenExpiredError(`${errorCode} — ${errorDesc}`);
198
+ }
199
+ log.error(`Authentication error: ${errorCode}`);
200
+ throw new AuthenticationError(errorCode, errorDesc);
201
+ }
202
+ // Success
203
+ let responseBody;
204
+ try {
205
+ responseBody = yield response.json();
206
+ }
207
+ catch (_h) {
208
+ throw new AuthenticationError("invalid_response", "Token endpoint returned non-JSON body");
209
+ }
210
+ const result = validateTokenResponse(responseBody, log);
211
+ log.info(`Token acquired (length=${result.access_token.length})`);
212
+ return result;
213
+ }
214
+ // Should not reach here, but just in case
215
+ throw new NetworkError("Token request failed after all retries.");
216
+ });
217
+ }
218
+ /**
219
+ * Revoke an access or refresh token.
220
+ *
221
+ * For confidential clients (with client_secret), credentials are sent via
222
+ * HTTP Basic Auth per RFC 7009. For public clients, the client_id is sent
223
+ * as a query parameter.
224
+ *
225
+ * This is best-effort — errors are logged but not thrown.
226
+ */
227
+ export function revokeRequest(token_1, clientId_1, clientSecret_1) {
228
+ return __awaiter(this, arguments, void 0, function* (token, clientId, clientSecret, options = {}) {
229
+ var _a, _b, _c, _d;
230
+ const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : DEFAULT_TIMEOUT;
231
+ const log = (_b = options.logger) !== null && _b !== void 0 ? _b : noopLogger;
232
+ const revokeUrl = (_c = options.revokeUrl) !== null && _c !== void 0 ? _c : REVOKE_URL;
233
+ const fetchFn = (_d = options.fetch) !== null && _d !== void 0 ? _d : (typeof globalThis.fetch !== "undefined" ? globalThis.fetch.bind(globalThis) : undefined);
234
+ if (fetchFn === undefined) {
235
+ log.warn("Token revocation skipped: no fetch implementation available");
236
+ return;
237
+ }
238
+ const headers = {
239
+ "Content-Type": "application/x-www-form-urlencoded",
240
+ };
241
+ const data = { token };
242
+ let url = revokeUrl;
243
+ if (clientSecret) {
244
+ // Confidential client: use HTTP Basic Auth (RFC 7009 §2.1)
245
+ const credentials = btoa(`${clientId}:${clientSecret}`);
246
+ headers["Authorization"] = `Basic ${credentials}`;
247
+ }
248
+ else {
249
+ // Public client: send client_id as query parameter
250
+ url = `${revokeUrl}?${new URLSearchParams({ client_id: clientId }).toString()}`;
251
+ }
252
+ try {
253
+ const controller = new AbortController();
254
+ const timer = setTimeout(() => controller.abort(), timeout);
255
+ try {
256
+ const response = yield fetchFn(url, {
257
+ method: "POST",
258
+ headers,
259
+ body: new URLSearchParams(data).toString(),
260
+ signal: controller.signal,
261
+ });
262
+ if (!response.ok) {
263
+ log.warn(`Token revocation returned status ${response.status}`);
264
+ }
265
+ }
266
+ finally {
267
+ clearTimeout(timer);
268
+ }
269
+ }
270
+ catch (err) {
271
+ log.warn(`Token revocation failed: ${err}`);
272
+ }
273
+ });
274
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * frameio OAuth 2.0 authentication for the Frame.io V4 SDK.
3
+ *
4
+ * Provides four authentication flows:
5
+ *
6
+ * - {@link ServerToServerAuth} — client_credentials grant (backend services)
7
+ * - {@link WebAppAuth} — authorization_code grant (server-side apps)
8
+ * - {@link SPAAuth} — authorization_code + PKCE (browser apps)
9
+ * - {@link NativeAppAuth} — authorization_code + PKCE (desktop / mobile)
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { FrameioClient, ServerToServerAuth } from 'frameio';
14
+ *
15
+ * const auth = new ServerToServerAuth({ clientId: '...', clientSecret: '...' });
16
+ * const client = new FrameioClient({ token: () => auth.getToken() });
17
+ * ```
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+ export type { BaseAuthOptions } from "./BaseAuth";
22
+ export { ServerToServerAuth } from "./ServerToServerAuth";
23
+ export type { ServerToServerAuthOptions } from "./ServerToServerAuth";
24
+ export { WebAppAuth } from "./WebAppAuth";
25
+ export type { WebAppAuthOptions } from "./WebAppAuth";
26
+ export { SPAAuth } from "./SPAAuth";
27
+ export type { SPAAuthOptions, AuthorizationUrlResult } from "./SPAAuth";
28
+ export { NativeAppAuth } from "./NativeAppAuth";
29
+ export type { NativeAppAuthOptions } from "./NativeAppAuth";
30
+ export { FrameioAuthError, AuthenticationError, TokenExpiredError, NetworkError, RateLimitError, PKCEError, ConfigurationError, } from "./errors";
31
+ export type { Logger } from "./logger";
32
+ export { noopLogger } from "./logger";
33
+ export { DEFAULT_IMS_BASE_URL, buildImsUrls } from "./http";
34
+ export type { TokenResponse, ExportedTokens, OnTokenRefreshed } from "./TokenManager";
@@ -0,0 +1,30 @@
1
+ /**
2
+ * frameio OAuth 2.0 authentication for the Frame.io V4 SDK.
3
+ *
4
+ * Provides four authentication flows:
5
+ *
6
+ * - {@link ServerToServerAuth} — client_credentials grant (backend services)
7
+ * - {@link WebAppAuth} — authorization_code grant (server-side apps)
8
+ * - {@link SPAAuth} — authorization_code + PKCE (browser apps)
9
+ * - {@link NativeAppAuth} — authorization_code + PKCE (desktop / mobile)
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { FrameioClient, ServerToServerAuth } from 'frameio';
14
+ *
15
+ * const auth = new ServerToServerAuth({ clientId: '...', clientSecret: '...' });
16
+ * const client = new FrameioClient({ token: () => auth.getToken() });
17
+ * ```
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+ // Auth flows
22
+ export { ServerToServerAuth } from "./ServerToServerAuth";
23
+ export { WebAppAuth } from "./WebAppAuth";
24
+ export { SPAAuth } from "./SPAAuth";
25
+ export { NativeAppAuth } from "./NativeAppAuth";
26
+ // Errors
27
+ export { FrameioAuthError, AuthenticationError, TokenExpiredError, NetworkError, RateLimitError, PKCEError, ConfigurationError, } from "./errors";
28
+ export { noopLogger } from "./logger";
29
+ // Config
30
+ export { DEFAULT_IMS_BASE_URL, buildImsUrls } from "./http";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Pluggable logger interface for frameio-auth-sdk.
3
+ *
4
+ * Consumers can supply any object matching this interface (e.g. `console`,
5
+ * `winston`, `pino`) via the `logger` option on auth class constructors.
6
+ *
7
+ * The default is a no-op logger (silent).
8
+ */
9
+ /** Logger interface accepted by all frameio-auth-sdk classes. */
10
+ export interface Logger {
11
+ debug(msg: string): void;
12
+ info(msg: string): void;
13
+ warn(msg: string): void;
14
+ error(msg: string): void;
15
+ }
16
+ /** Silent no-op logger — the default when no logger is provided. */
17
+ export declare const noopLogger: Logger;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Pluggable logger interface for frameio-auth-sdk.
3
+ *
4
+ * Consumers can supply any object matching this interface (e.g. `console`,
5
+ * `winston`, `pino`) via the `logger` option on auth class constructors.
6
+ *
7
+ * The default is a no-op logger (silent).
8
+ */
9
+ /** Silent no-op logger — the default when no logger is provided. */
10
+ export const noopLogger = {
11
+ debug() { },
12
+ info() { },
13
+ warn() { },
14
+ error() { },
15
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * PKCE (Proof Key for Code Exchange) utilities.
3
+ *
4
+ * Implements code_verifier generation and S256 code_challenge derivation
5
+ * per RFC 7636.
6
+ *
7
+ * Uses the Web Crypto API (`globalThis.crypto`) so it works in both
8
+ * Node.js (>=18) and browsers.
9
+ */
10
+ /**
11
+ * Generate a cryptographically random code verifier.
12
+ *
13
+ * Uses `globalThis.crypto.getRandomValues` which is available in
14
+ * browsers and Node.js >= 18.
15
+ *
16
+ * @param length - Length of the verifier (43–128 inclusive). Defaults to 128.
17
+ * @returns A random string of unreserved URI characters.
18
+ */
19
+ export declare function generateCodeVerifier(length?: number): string;
20
+ /**
21
+ * Derive the S256 code challenge from a code verifier.
22
+ *
23
+ * `code_challenge = BASE64URL(SHA256(code_verifier))`
24
+ *
25
+ * Uses `crypto.subtle.digest` which is available in browsers and Node.js >= 18.
26
+ *
27
+ * @param verifier - The code verifier string.
28
+ * @returns Base64url-encoded (no padding) SHA-256 hash.
29
+ */
30
+ export declare function generateCodeChallenge(verifier: string): Promise<string>;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * PKCE (Proof Key for Code Exchange) utilities.
3
+ *
4
+ * Implements code_verifier generation and S256 code_challenge derivation
5
+ * per RFC 7636.
6
+ *
7
+ * Uses the Web Crypto API (`globalThis.crypto`) so it works in both
8
+ * Node.js (>=18) and browsers.
9
+ */
10
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
11
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
12
+ return new (P || (P = Promise))(function (resolve, reject) {
13
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
14
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
15
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
16
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
17
+ });
18
+ };
19
+ /** Unreserved URI characters allowed in a code verifier (RFC 7636 §4.1). */
20
+ const VERIFIER_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
21
+ const MIN_VERIFIER_LENGTH = 43;
22
+ const MAX_VERIFIER_LENGTH = 128;
23
+ const DEFAULT_VERIFIER_LENGTH = 128;
24
+ /**
25
+ * Generate a cryptographically random code verifier.
26
+ *
27
+ * Uses `globalThis.crypto.getRandomValues` which is available in
28
+ * browsers and Node.js >= 18.
29
+ *
30
+ * @param length - Length of the verifier (43–128 inclusive). Defaults to 128.
31
+ * @returns A random string of unreserved URI characters.
32
+ */
33
+ export function generateCodeVerifier(length = DEFAULT_VERIFIER_LENGTH) {
34
+ if (length < MIN_VERIFIER_LENGTH || length > MAX_VERIFIER_LENGTH) {
35
+ throw new RangeError(`code_verifier length must be between ${MIN_VERIFIER_LENGTH} and ${MAX_VERIFIER_LENGTH}, got ${length}`);
36
+ }
37
+ // Rejection sampling: discard random bytes that would introduce modulo bias.
38
+ // Only accept values below the largest multiple of the charset length that
39
+ // fits in a byte (256), so every character has equal probability.
40
+ const charsetLen = VERIFIER_CHARS.length; // 66
41
+ const limit = 256 - (256 % charsetLen); // 252
42
+ const chars = new Array(length);
43
+ // Batch random bytes; refill when exhausted. Oversize slightly to absorb the
44
+ // ~1.6% rejection rate without a second refill in the common case.
45
+ let buf = new Uint8Array(Math.ceil(length * 1.1));
46
+ globalThis.crypto.getRandomValues(buf);
47
+ let bufPos = 0;
48
+ for (let i = 0; i < length;) {
49
+ if (bufPos >= buf.length) {
50
+ buf = new Uint8Array(length);
51
+ globalThis.crypto.getRandomValues(buf);
52
+ bufPos = 0;
53
+ }
54
+ const b = buf[bufPos++];
55
+ if (b < limit) {
56
+ chars[i] = VERIFIER_CHARS[b % charsetLen];
57
+ i++;
58
+ }
59
+ // else: discard and advance to next byte
60
+ }
61
+ return chars.join("");
62
+ }
63
+ /**
64
+ * Derive the S256 code challenge from a code verifier.
65
+ *
66
+ * `code_challenge = BASE64URL(SHA256(code_verifier))`
67
+ *
68
+ * Uses `crypto.subtle.digest` which is available in browsers and Node.js >= 18.
69
+ *
70
+ * @param verifier - The code verifier string.
71
+ * @returns Base64url-encoded (no padding) SHA-256 hash.
72
+ */
73
+ export function generateCodeChallenge(verifier) {
74
+ return __awaiter(this, void 0, void 0, function* () {
75
+ const data = new TextEncoder().encode(verifier);
76
+ const hashBuffer = yield globalThis.crypto.subtle.digest("SHA-256", data);
77
+ const hashArray = new Uint8Array(hashBuffer);
78
+ // Base64url encode (no padding)
79
+ let base64 = "";
80
+ const len = hashArray.length;
81
+ for (let i = 0; i < len; i += 3) {
82
+ const a = hashArray[i];
83
+ const b = i + 1 < len ? hashArray[i + 1] : 0;
84
+ const c = i + 2 < len ? hashArray[i + 2] : 0;
85
+ base64 += toBase64Url((a >> 2) & 0x3f);
86
+ base64 += toBase64Url(((a & 0x03) << 4) | ((b >> 4) & 0x0f));
87
+ if (i + 1 < len)
88
+ base64 += toBase64Url(((b & 0x0f) << 2) | ((c >> 6) & 0x03));
89
+ if (i + 2 < len)
90
+ base64 += toBase64Url(c & 0x3f);
91
+ }
92
+ return base64;
93
+ });
94
+ }
95
+ const BASE64URL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
96
+ function toBase64Url(index) {
97
+ return BASE64URL_CHARS[index];
98
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Input validation helpers for frameio-auth-sdk.
3
+ */
4
+ import type { ExportedTokens } from "./TokenManager";
5
+ /**
6
+ * Validate that a redirect URI uses HTTPS, except for loopback addresses
7
+ * where HTTP is allowed for local development.
8
+ *
9
+ * @throws {ConfigurationError} if the URI uses http:// with a non-loopback host.
10
+ */
11
+ export declare function validateRedirectUriScheme(uri: string): void;
12
+ /**
13
+ * Validate the structure of an exported token object before importing.
14
+ *
15
+ * @throws {ConfigurationError} if any field has an unexpected type.
16
+ */
17
+ export declare function validateTokenImport(data: unknown): asserts data is ExportedTokens;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Input validation helpers for frameio-auth-sdk.
3
+ */
4
+ import { ConfigurationError } from "./errors";
5
+ /** Hosts that are allowed to use http:// redirect URIs. */
6
+ const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
7
+ /**
8
+ * Validate that a redirect URI uses HTTPS, except for loopback addresses
9
+ * where HTTP is allowed for local development.
10
+ *
11
+ * @throws {ConfigurationError} if the URI uses http:// with a non-loopback host.
12
+ */
13
+ export function validateRedirectUriScheme(uri) {
14
+ const lower = uri.toLowerCase();
15
+ if (!lower.startsWith("http://")) {
16
+ return; // https:// and custom schemes are always allowed
17
+ }
18
+ const hostPart = lower.slice("http://".length);
19
+ // Extract host, handling IPv6 brackets (e.g. [::1])
20
+ let host;
21
+ if (hostPart.startsWith("[")) {
22
+ const bracketEnd = hostPart.indexOf("]");
23
+ host = bracketEnd !== -1 ? hostPart.slice(0, bracketEnd + 1) : hostPart.split("/")[0];
24
+ }
25
+ else {
26
+ host = hostPart.split("/")[0].split(":")[0];
27
+ }
28
+ if (!LOOPBACK_HOSTS.has(host)) {
29
+ throw new ConfigurationError(`Redirect URI must use HTTPS for non-loopback hosts, got: ${uri}`);
30
+ }
31
+ }
32
+ /**
33
+ * Validate the structure of an exported token object before importing.
34
+ *
35
+ * @throws {ConfigurationError} if any field has an unexpected type.
36
+ */
37
+ export function validateTokenImport(data) {
38
+ if (typeof data !== "object" || data === null) {
39
+ throw new ConfigurationError("Token import data must be a non-null object.");
40
+ }
41
+ const obj = data;
42
+ if ("access_token" in obj && obj.access_token !== null && typeof obj.access_token !== "string") {
43
+ throw new ConfigurationError(`access_token must be a string or null, got ${typeof obj.access_token}`);
44
+ }
45
+ if ("refresh_token" in obj && obj.refresh_token !== null && typeof obj.refresh_token !== "string") {
46
+ throw new ConfigurationError(`refresh_token must be a string or null, got ${typeof obj.refresh_token}`);
47
+ }
48
+ if ("expires_at" in obj && typeof obj.expires_at !== "number") {
49
+ throw new ConfigurationError(`expires_at must be a number, got ${typeof obj.expires_at}`);
50
+ }
51
+ }
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "3.2.2";
1
+ export declare const SDK_VERSION = "4.1.0";
@@ -1 +1 @@
1
- export const SDK_VERSION = "3.2.2";
1
+ export const SDK_VERSION = "4.1.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frameio",
3
- "version": "3.2.2",
3
+ "version": "4.1.1",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
package/reference.md CHANGED
@@ -1453,7 +1453,11 @@ await client.versionStacks.show("b2702c44-c6da-4bb6-8bbd-be6e547ccf1b", "b2702c4
1453
1453
  <dl>
1454
1454
  <dd>
1455
1455
 
1456
- Copy version stack. <br/>Rate Limits: 10 calls per 1.00 minute(s) per account_user
1456
+ Copy version stack. <br/>Rate Limits: 10 calls per 1.00 minute(s) per account_user<br><br>
1457
+
1458
+ Currently, copying version stacks between Adobe storage backed projects is not supported. Copying individual
1459
+ files within a version stack and then restacking them is currently the supported method for copying version
1460
+ stacks for these projects.
1457
1461
  </dd>
1458
1462
  </dl>
1459
1463
  </dd>
@@ -1638,7 +1642,7 @@ Create a new Version Stack under the parent folder. <br/>Rate Limits: 10 calls p
1638
1642
  ```typescript
1639
1643
  await client.versionStacks.create("b2702c44-c6da-4bb6-8bbd-be6e547ccf1b", "b2702c44-c6da-4bb6-8bbd-be6e547ccf1b", {
1640
1644
  data: {
1641
- file_ids: ["ed21422e-c39d-483f-b1e7-fc7c2ad0ff5e", "2e77ba9b-5fe4-4134-856d-7f920138d81f"]
1645
+ file_ids: ["bf6a7751-b5e4-4f2e-b7e4-67ee53d740e8", "0604f9c0-36a4-4b59-ab89-f48e3b172dc0"]
1642
1646
  }
1643
1647
  });
1644
1648
 
@@ -3544,7 +3548,7 @@ await client.shares.update("b2702c44-c6da-4bb6-8bbd-be6e547ccf1b", "b2702c44-c6d
3544
3548
  access: "public",
3545
3549
  description: "A descriptive summary of the share",
3546
3550
  downloading_enabled: true,
3547
- expiration: "2026-03-30T16:33:48Z",
3551
+ expiration: "2026-04-15T00:53:07Z",
3548
3552
  name: "Share Name",
3549
3553
  passphrase: "as!dfj39sd(*"
3550
3554
  }
@@ -3986,7 +3990,7 @@ Add new asset share. <br/>Rate Limits: 10 calls per 1.00 minute(s) per account_u
3986
3990
  ```typescript
3987
3991
  await client.shares.addAsset("b2702c44-c6da-4bb6-8bbd-be6e547ccf1b", "b2702c44-c6da-4bb6-8bbd-be6e547ccf1b", {
3988
3992
  data: {
3989
- asset_id: "b54fa55a-9523-4441-aafc-9d69a7b4c371"
3993
+ asset_id: "f2631de7-7f1d-423e-825f-350106ad5332"
3990
3994
  }
3991
3995
  });
3992
3996
 
@@ -4170,9 +4174,9 @@ await client.shares.create("b2702c44-c6da-4bb6-8bbd-be6e547ccf1b", "b2702c44-c6d
4170
4174
  data: {
4171
4175
  type: "asset",
4172
4176
  access: "public",
4173
- asset_ids: ["be63580a-96c7-46fe-8cc8-6b44f0a32b30", "209e1ab7-9ab5-4699-8e03-8c89436cbca1"],
4177
+ asset_ids: ["ead05077-22fb-4f16-af36-44fdabb31c7f", "4cb5fe30-845a-4fc5-beb0-5f6e1e684a7c"],
4174
4178
  downloading_enabled: true,
4175
- expiration: "2026-03-30T16:33:48Z",
4179
+ expiration: "2026-04-15T00:53:07Z",
4176
4180
  name: "Share Name",
4177
4181
  passphrase: "as!dfj39sd(*"
4178
4182
  }
@@ -4258,16 +4262,16 @@ Update metadata values across multiple files. <br/>Rate Limits: 10 calls per 1.0
4258
4262
  ```typescript
4259
4263
  await client.metadata.bulkUpdate("b2702c44-c6da-4bb6-8bbd-be6e547ccf1b", "b2702c44-c6da-4bb6-8bbd-be6e547ccf1b", {
4260
4264
  data: {
4261
- file_ids: ["f5b93f6a-af32-4b06-be25-0f75e7e3c7df", "0d1a2427-280e-41d9-9811-688a0ed60fd3"],
4265
+ file_ids: ["055e9655-5445-425c-9158-4dcff313a8a1", "7e91a226-d1ac-4bea-afd1-9128c52ffc8d"],
4262
4266
  values: [{
4263
- field_definition_id: "9b87e46b-e2ad-452d-9ae8-7c933dd47fd1",
4267
+ field_definition_id: "174f1f07-3e07-4ad0-ac74-a27c0dc4a781",
4264
4268
  value: [
4265
4269
  {
4266
- "id": "7891fdf2-6107-42cb-b692-93e99aafe838",
4270
+ "id": "f1ef4511-a8ff-4645-b406-47e3362ad15a",
4267
4271
  "type": "user"
4268
4272
  },
4269
4273
  {
4270
- "id": "79411f50-f1a3-4acb-866d-03735ad369db",
4274
+ "id": "3a015842-e804-496a-abbe-577a332fb255",
4271
4275
  "type": "account_user_group"
4272
4276
  }
4273
4277
  ]