@bytescale/sdk 3.53.0 → 3.56.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/dist/browser/cjs/main.js +298 -191
- package/dist/browser/esm/main.mjs +299 -237
- package/dist/node/cjs/main.js +155 -155
- package/dist/node/esm/main.mjs +165 -218
- package/dist/types/private/dtos/AuthSwConfigEntryDto.d.ts +5 -0
- package/dist/types/private/model/AuthManagerInterface.d.ts +28 -1
- package/dist/types/private/model/AuthSession.d.ts +1 -0
- package/dist/types/public/browser/AuthManagerBrowser.d.ts +8 -1
- package/dist/types/public/node/AuthManagerNode.d.ts +3 -1
- package/dist/types/public/shared/generated/apis/FileApi.d.ts +13 -10
- package/dist/types/public/shared/generated/models/index.d.ts +80 -27
- package/dist/worker/cjs/main.js +140 -140
- package/dist/worker/esm/main.mjs +141 -186
- package/package.json +39 -8
- package/tests/AuthManagerBrowser.test.ts +234 -0
- package/tests/UploadManager.test.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytescale/sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.56.0",
|
|
4
4
|
"description": "Bytescale JavaScript SDK",
|
|
5
5
|
"author": "Bytescale <hello@bytescale.com> (https://www.bytescale.com)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -84,14 +84,45 @@
|
|
|
84
84
|
"prettier -w"
|
|
85
85
|
]
|
|
86
86
|
},
|
|
87
|
+
"overrides": {
|
|
88
|
+
"@eslint/eslintrc": {
|
|
89
|
+
"ajv": "6.14.0"
|
|
90
|
+
},
|
|
91
|
+
"anymatch": {
|
|
92
|
+
"picomatch": "2.3.2"
|
|
93
|
+
},
|
|
94
|
+
"brace-expansion": "1.1.13",
|
|
95
|
+
"cross-spawn": "7.0.6",
|
|
96
|
+
"eslint": {
|
|
97
|
+
"ajv": "6.14.0",
|
|
98
|
+
"flatted": "3.4.2",
|
|
99
|
+
"js-yaml": "3.14.2",
|
|
100
|
+
"table": {
|
|
101
|
+
"ajv": "8.18.0"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"jest-util": {
|
|
105
|
+
"picomatch": "2.3.2"
|
|
106
|
+
},
|
|
107
|
+
"micromatch": {
|
|
108
|
+
"picomatch": "2.3.2"
|
|
109
|
+
},
|
|
110
|
+
"minimatch": "3.1.4",
|
|
111
|
+
"readdirp": {
|
|
112
|
+
"picomatch": "2.3.2"
|
|
113
|
+
},
|
|
114
|
+
"table": {
|
|
115
|
+
"ajv": "8.18.0"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
87
118
|
"devDependencies": {
|
|
88
|
-
"@babel/cli": "7.
|
|
89
|
-
"@babel/core": "7.
|
|
90
|
-
"@babel/preset-env": "7.
|
|
119
|
+
"@babel/cli": "7.28.6",
|
|
120
|
+
"@babel/core": "7.29.0",
|
|
121
|
+
"@babel/preset-env": "7.29.2",
|
|
91
122
|
"@types/jest": "29.2.4",
|
|
92
123
|
"@typescript-eslint/eslint-plugin": "4.33.0",
|
|
93
124
|
"@typescript-eslint/parser": "4.33.0",
|
|
94
|
-
"babel-loader": "
|
|
125
|
+
"babel-loader": "10.1.1",
|
|
95
126
|
"babel-plugin-transform-async-to-promises": "0.8.18",
|
|
96
127
|
"eslint": "7.32.0",
|
|
97
128
|
"eslint-config-prettier": "6.15.0",
|
|
@@ -101,16 +132,16 @@
|
|
|
101
132
|
"eslint-plugin-promise": "4.2.1",
|
|
102
133
|
"eslint-plugin-return-types-object-literals": "1.0.1",
|
|
103
134
|
"eslint-plugin-standard": "4.1.0",
|
|
104
|
-
"husky": "
|
|
135
|
+
"husky": "9.1.7",
|
|
105
136
|
"jest": "29.3.1",
|
|
106
|
-
"lint-staged": "
|
|
137
|
+
"lint-staged": "16.4.0",
|
|
107
138
|
"node-fetch": "3.3.0",
|
|
108
139
|
"prettier": "2.8.8",
|
|
109
140
|
"ts-jest": "29.0.3",
|
|
110
141
|
"ts-loader": "9.5.1",
|
|
111
142
|
"tsc-alias": "1.2.10",
|
|
112
143
|
"typescript": "4.9.5",
|
|
113
|
-
"webpack": "5.
|
|
144
|
+
"webpack": "5.106.1",
|
|
114
145
|
"webpack-cli": "4.10.0",
|
|
115
146
|
"webpack-node-externals": "2.5.2",
|
|
116
147
|
"webpack-shell-plugin-next": "2.3.1"
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
import { Response as NodeFetchResponse } from "node-fetch";
|
|
3
|
+
import type { AuthSwConfigEntryDto, BeginAuthSessionParamsV1, BeginAuthSessionParamsV2 } from "../src/index.browser";
|
|
4
|
+
|
|
5
|
+
interface AuthManagerApi {
|
|
6
|
+
beginAuthSession: (params: BeginAuthSessionParamsV1 | BeginAuthSessionParamsV2) => Promise<void>;
|
|
7
|
+
endAuthSession: () => Promise<void>;
|
|
8
|
+
isAuthSessionActive: () => boolean;
|
|
9
|
+
isAuthSessionReady: () => boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("AuthManager browser service-worker config", () => {
|
|
13
|
+
const originalNavigator = Object.getOwnPropertyDescriptor(globalThis, "navigator");
|
|
14
|
+
const originalFetch = Object.getOwnPropertyDescriptor(globalThis, "fetch");
|
|
15
|
+
const originalWindow = Object.getOwnPropertyDescriptor(globalThis, "window");
|
|
16
|
+
const globalFetch = jest.fn(async (): Promise<Response> => {
|
|
17
|
+
throw new Error("This test must not use the global fetch API.");
|
|
18
|
+
});
|
|
19
|
+
const postMessage = jest.fn((_message: unknown): void => {});
|
|
20
|
+
const activeWorker = { postMessage, state: "activated" } as unknown as ServiceWorker;
|
|
21
|
+
const registration = {
|
|
22
|
+
active: activeWorker,
|
|
23
|
+
installing: null,
|
|
24
|
+
scope: "https://app.example.com/",
|
|
25
|
+
waiting: null
|
|
26
|
+
} as unknown as ServiceWorkerRegistration;
|
|
27
|
+
const serviceWorkerApi = {
|
|
28
|
+
controller: activeWorker,
|
|
29
|
+
getRegistrations: jest.fn(async (): Promise<ServiceWorkerRegistration[]> => [registration]),
|
|
30
|
+
register: jest.fn(async (): Promise<ServiceWorkerRegistration> => registration)
|
|
31
|
+
};
|
|
32
|
+
const navigatorValue: { serviceWorker?: typeof serviceWorkerApi } = { serviceWorker: serviceWorkerApi };
|
|
33
|
+
let AuthManager: AuthManagerApi;
|
|
34
|
+
|
|
35
|
+
beforeAll(async () => {
|
|
36
|
+
Object.defineProperty(globalThis, "fetch", { configurable: true, value: globalFetch });
|
|
37
|
+
Object.defineProperty(globalThis, "window", { configurable: true, value: {} });
|
|
38
|
+
Object.defineProperty(globalThis, "navigator", { configurable: true, value: navigatorValue });
|
|
39
|
+
AuthManager = (await import("../src/public/browser/AuthManagerBrowser")).AuthManager;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
navigatorValue.serviceWorker = serviceWorkerApi;
|
|
44
|
+
globalFetch.mockClear();
|
|
45
|
+
postMessage.mockClear();
|
|
46
|
+
serviceWorkerApi.getRegistrations.mockClear();
|
|
47
|
+
serviceWorkerApi.register.mockClear();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterAll(() => {
|
|
51
|
+
for (const [key, descriptor] of [
|
|
52
|
+
["fetch", originalFetch],
|
|
53
|
+
["navigator", originalNavigator],
|
|
54
|
+
["window", originalWindow]
|
|
55
|
+
] as const) {
|
|
56
|
+
if (descriptor === undefined) {
|
|
57
|
+
Reflect.deleteProperty(globalThis, key);
|
|
58
|
+
} else {
|
|
59
|
+
Object.defineProperty(globalThis, key, descriptor);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterEach(async () => {
|
|
65
|
+
navigatorValue.serviceWorker = serviceWorkerApi;
|
|
66
|
+
await AuthManager.endAuthSession();
|
|
67
|
+
jest.restoreAllMocks();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("V2 applies multiple entries and refreshes before the earliest expiry", async () => {
|
|
71
|
+
const firstConfig: AuthSwConfigEntryDto[] = [
|
|
72
|
+
{
|
|
73
|
+
expires: Date.now() + 21_000,
|
|
74
|
+
headers: [{ key: "Authorization", value: "Bearer account-a" }],
|
|
75
|
+
sourceUrlPrefixes: ["https://app.example.com/account-a/"],
|
|
76
|
+
urlPrefix: "https://upcdn.io/account-a/"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
expires: Date.now() + 60_000,
|
|
80
|
+
headers: [{ key: "Authorization", value: "Bearer account-b" }],
|
|
81
|
+
urlPrefix: "https://upcdn.io/account-b/"
|
|
82
|
+
}
|
|
83
|
+
];
|
|
84
|
+
const refreshedConfig: AuthSwConfigEntryDto[] = [
|
|
85
|
+
{
|
|
86
|
+
expires: undefined,
|
|
87
|
+
headers: [{ key: "Authorization", value: "Bearer refreshed" }],
|
|
88
|
+
urlPrefix: "https://upcdn.io/account-a/"
|
|
89
|
+
}
|
|
90
|
+
];
|
|
91
|
+
const getServiceWorkerConfig = jest
|
|
92
|
+
.fn<BeginAuthSessionParamsV2["getServiceWorkerConfig"]>()
|
|
93
|
+
.mockResolvedValueOnce(firstConfig)
|
|
94
|
+
.mockResolvedValueOnce(refreshedConfig);
|
|
95
|
+
|
|
96
|
+
await AuthManager.beginAuthSession({
|
|
97
|
+
getServiceWorkerConfig,
|
|
98
|
+
serviceWorkerScript: "/bytescale-auth-sw.js"
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(AuthManager.isAuthSessionActive()).toBe(true);
|
|
102
|
+
expect(AuthManager.isAuthSessionReady()).toBe(true);
|
|
103
|
+
expect(postMessage.mock.calls[0][0]).toEqual({
|
|
104
|
+
config: [
|
|
105
|
+
{
|
|
106
|
+
...firstConfig[0],
|
|
107
|
+
urlPrefix: "!bytescale-source-scoped!https://upcdn.io/account-a/"
|
|
108
|
+
},
|
|
109
|
+
firstConfig[1]
|
|
110
|
+
],
|
|
111
|
+
type: "SET_BYTESCALE_AUTH_CONFIG"
|
|
112
|
+
});
|
|
113
|
+
expect(firstConfig[0].urlPrefix).toBe("https://upcdn.io/account-a/");
|
|
114
|
+
|
|
115
|
+
await new Promise(resolve => setTimeout(resolve, 1_500));
|
|
116
|
+
|
|
117
|
+
expect(getServiceWorkerConfig).toHaveBeenCalledTimes(2);
|
|
118
|
+
expect(postMessage.mock.calls[1][0]).toEqual({
|
|
119
|
+
config: refreshedConfig,
|
|
120
|
+
type: "SET_BYTESCALE_AUTH_CONFIG"
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("V2 clears service-worker config without calling access-token endpoints", async () => {
|
|
125
|
+
await AuthManager.beginAuthSession({
|
|
126
|
+
getServiceWorkerConfig: async () => [
|
|
127
|
+
{
|
|
128
|
+
expires: undefined,
|
|
129
|
+
headers: [{ key: "Authorization", value: "Bearer account-a" }],
|
|
130
|
+
urlPrefix: "https://upcdn.io/account-a/"
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
serviceWorkerScript: "/bytescale-auth-sw.js"
|
|
134
|
+
});
|
|
135
|
+
await AuthManager.endAuthSession();
|
|
136
|
+
|
|
137
|
+
expect(globalFetch).not.toHaveBeenCalled();
|
|
138
|
+
expect(postMessage.mock.calls[1][0]).toEqual({ config: [], type: "SET_BYTESCALE_AUTH_CONFIG" });
|
|
139
|
+
expect(AuthManager.isAuthSessionActive()).toBe(false);
|
|
140
|
+
expect(AuthManager.isAuthSessionReady()).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("V2 rejects instead of falling back to cookies when service workers are unavailable", async () => {
|
|
144
|
+
delete navigatorValue.serviceWorker;
|
|
145
|
+
const getServiceWorkerConfig = jest.fn(async (): Promise<AuthSwConfigEntryDto[]> => []);
|
|
146
|
+
|
|
147
|
+
await expect(
|
|
148
|
+
AuthManager.beginAuthSession({ getServiceWorkerConfig, serviceWorkerScript: "/bytescale-auth-sw.js" })
|
|
149
|
+
).rejects.toThrow("requires service workers");
|
|
150
|
+
|
|
151
|
+
expect(getServiceWorkerConfig).not.toHaveBeenCalled();
|
|
152
|
+
expect(AuthManager.isAuthSessionActive()).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("V1 retains cookie fallback when service workers are unavailable", async () => {
|
|
156
|
+
delete navigatorValue.serviceWorker;
|
|
157
|
+
const fetchApi = jest.fn(async (_input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
158
|
+
switch (init?.method) {
|
|
159
|
+
case "GET":
|
|
160
|
+
return new NodeFetchResponse("jwt-a", {
|
|
161
|
+
headers: { "Content-Type": "text/plain" }
|
|
162
|
+
}) as unknown as Response;
|
|
163
|
+
case "PUT":
|
|
164
|
+
return new NodeFetchResponse(
|
|
165
|
+
JSON.stringify({ accessToken: "access-a", ttlSeconds: 3600 })
|
|
166
|
+
) as unknown as Response;
|
|
167
|
+
case "DELETE":
|
|
168
|
+
return new NodeFetchResponse(null, { status: 204 }) as unknown as Response;
|
|
169
|
+
default:
|
|
170
|
+
throw new Error(`Unexpected method: ${init?.method ?? "undefined"}`);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
await AuthManager.beginAuthSession({
|
|
175
|
+
accountId: "account-a",
|
|
176
|
+
authHeaders: async (): Promise<Record<string, string>> => ({}),
|
|
177
|
+
authUrl: "https://app.example.com/auth",
|
|
178
|
+
options: { fetchApi },
|
|
179
|
+
serviceWorkerScript: "/bytescale-auth-sw.js"
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect((fetchApi.mock.calls[1][0] as string).endsWith("?set-cookie=true")).toBe(true);
|
|
183
|
+
expect(postMessage).not.toHaveBeenCalled();
|
|
184
|
+
expect(AuthManager.isAuthSessionReady()).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("V1 retains the existing JWT, access-token, and single-entry service-worker flow", async () => {
|
|
188
|
+
const fetchApi = jest.fn(async (_input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
189
|
+
switch (init?.method) {
|
|
190
|
+
case "GET":
|
|
191
|
+
return new NodeFetchResponse("jwt-a", {
|
|
192
|
+
headers: { "Content-Type": "text/plain" }
|
|
193
|
+
}) as unknown as Response;
|
|
194
|
+
case "PUT":
|
|
195
|
+
return new NodeFetchResponse(
|
|
196
|
+
JSON.stringify({ accessToken: "access-a", ttlSeconds: 3600 })
|
|
197
|
+
) as unknown as Response;
|
|
198
|
+
case "DELETE":
|
|
199
|
+
return new NodeFetchResponse(null, { status: 204 }) as unknown as Response;
|
|
200
|
+
default:
|
|
201
|
+
throw new Error(`Unexpected method: ${init?.method ?? "undefined"}`);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
const params: BeginAuthSessionParamsV1 = {
|
|
205
|
+
accountId: "account-a",
|
|
206
|
+
authHeaders: async (): Promise<Record<string, string>> => ({ "X-App-Authorization": "app-token" }),
|
|
207
|
+
authUrl: "https://app.example.com/auth",
|
|
208
|
+
options: { fetchApi },
|
|
209
|
+
serviceWorkerScript: "/bytescale-auth-sw.js",
|
|
210
|
+
sourceUrlPrefixes: ["https://app.example.com/"]
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
await AuthManager.beginAuthSession(params);
|
|
214
|
+
|
|
215
|
+
expect(AuthManager.isAuthSessionReady()).toBe(true);
|
|
216
|
+
expect(postMessage.mock.calls[0][0]).toEqual({
|
|
217
|
+
config: [
|
|
218
|
+
{
|
|
219
|
+
expires: expect.any(Number),
|
|
220
|
+
headers: [{ key: "Authorization", value: "Bearer jwt-a" }],
|
|
221
|
+
sourceUrlPrefixes: params.sourceUrlPrefixes,
|
|
222
|
+
urlPrefix: "!bytescale-source-scoped!https://upcdn.io/account-a/"
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
type: "SET_BYTESCALE_AUTH_CONFIG"
|
|
226
|
+
});
|
|
227
|
+
expect(fetchApi.mock.calls.map(([, init]) => init?.method)).toEqual(["GET", "PUT"]);
|
|
228
|
+
|
|
229
|
+
await AuthManager.endAuthSession();
|
|
230
|
+
|
|
231
|
+
expect(fetchApi.mock.calls.map(([, init]) => init?.method)).toEqual(["GET", "PUT", "DELETE"]);
|
|
232
|
+
expect(postMessage.mock.calls[1][0]).toEqual({ config: [], type: "SET_BYTESCALE_AUTH_CONFIG" });
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -7,10 +7,10 @@ import { promises as fsAsync } from "fs";
|
|
|
7
7
|
import { prepareTempDirectory } from "./utils/TempUtils";
|
|
8
8
|
import * as Path from "path";
|
|
9
9
|
|
|
10
|
-
if (process.env.BYTESCALE_SECRET_API_KEY === undefined) {
|
|
10
|
+
if (process.env.BYTESCALE_SECRET_API_KEY === undefined || process.env.BYTESCALE_SECRET_API_KEY.trim().length === 0) {
|
|
11
11
|
throw new Error("Expected env var: BYTESCALE_SECRET_API_KEY");
|
|
12
12
|
}
|
|
13
|
-
if (process.env.BYTESCALE_ACCOUNT_ID === undefined) {
|
|
13
|
+
if (process.env.BYTESCALE_ACCOUNT_ID === undefined || process.env.BYTESCALE_ACCOUNT_ID.trim().length === 0) {
|
|
14
14
|
throw new Error("Expected env var: BYTESCALE_ACCOUNT_ID");
|
|
15
15
|
}
|
|
16
16
|
const accountId = process.env.BYTESCALE_ACCOUNT_ID;
|