@pylonsync/react 0.3.268 → 0.3.270
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/package.json +3 -3
- package/src/baseUrl.test.ts +44 -0
- package/src/index.ts +29 -9
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.270",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "src/index.ts",
|
|
9
9
|
"types": "src/index.ts",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"check": "tsc -p tsconfig.json --noEmit"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@pylonsync/sdk": "0.3.
|
|
16
|
-
"@pylonsync/sync": "0.3.
|
|
15
|
+
"@pylonsync/sdk": "0.3.270",
|
|
16
|
+
"@pylonsync/sync": "0.3.270"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
19
|
"react": ">=19.0.0"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Regression: `getBaseUrl()` is the origin every @pylonsync/client auth helper
|
|
2
|
+
// (createOrg, passwordRegister, createInvite, …) and the room/shard hooks fetch
|
|
3
|
+
// against. It used to return a static `http://localhost:4321` until an explicit
|
|
4
|
+
// `configureClient({ baseUrl })` — so a unified same-origin SSR app (which never
|
|
5
|
+
// calls configureClient) fired all auth/org calls at the dev port: broken on any
|
|
6
|
+
// non-4321 dev port AND in production. The fix defaults to the page origin in a
|
|
7
|
+
// browser. These pin that contract.
|
|
8
|
+
|
|
9
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
import { configureClient, getBaseUrl } from "./index";
|
|
12
|
+
|
|
13
|
+
const realWindow = (globalThis as { window?: unknown }).window;
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
(globalThis as { window?: unknown }).window = realWindow;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("getBaseUrl() — same-origin default for unified SSR apps", () => {
|
|
19
|
+
// Run order matters: these three execute before any configureClient, so the
|
|
20
|
+
// first two observe the un-configured resolution path.
|
|
21
|
+
test("SSR/node (no window) keeps the localhost dev default", () => {
|
|
22
|
+
(globalThis as { window?: unknown }).window = undefined;
|
|
23
|
+
expect(getBaseUrl()).toBe("http://localhost:4321");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("browser, unconfigured → the page origin, not localhost:4321", () => {
|
|
27
|
+
(globalThis as { window?: unknown }).window = {
|
|
28
|
+
location: { origin: "https://app.example.com" },
|
|
29
|
+
};
|
|
30
|
+
expect(getBaseUrl()).toBe("https://app.example.com");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("an explicit configureClient({ baseUrl }) wins over the origin default", () => {
|
|
34
|
+
(globalThis as { window?: unknown }).window = {
|
|
35
|
+
location: { origin: "https://app.example.com" },
|
|
36
|
+
};
|
|
37
|
+
configureClient({ baseUrl: "https://api.example.com" });
|
|
38
|
+
expect(getBaseUrl()).toBe("https://api.example.com");
|
|
39
|
+
// …and still wins when server-rendered (no window) — a separate-origin API
|
|
40
|
+
// deploy must not silently flip to the page origin.
|
|
41
|
+
(globalThis as { window?: unknown }).window = undefined;
|
|
42
|
+
expect(getBaseUrl()).toBe("https://api.example.com");
|
|
43
|
+
});
|
|
44
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -159,9 +159,29 @@ let _baseUrl = "http://localhost:4321";
|
|
|
159
159
|
let _baseUrlConfigured = false;
|
|
160
160
|
let _appName = "default";
|
|
161
161
|
|
|
162
|
-
/** Current effective base URL. Used by hooks (useRoom, useShard)
|
|
163
|
-
*
|
|
162
|
+
/** Current effective base URL. Used by hooks (useRoom, useShard) and the
|
|
163
|
+
* @pylonsync/client auth helpers (createOrg, passwordRegister, createInvite,
|
|
164
|
+
* …) that share the client config but don't have access to the module-private
|
|
165
|
+
* state.
|
|
166
|
+
*
|
|
167
|
+
* When NOT explicitly configured, default to the page origin in a browser
|
|
168
|
+
* instead of the `http://localhost:4321` dev constant. A unified SSR/embedded
|
|
169
|
+
* app serves its API same-origin, so the static default was a footgun: every
|
|
170
|
+
* auth/org call fired at `localhost:4321` — broken on any non-4321 dev port
|
|
171
|
+
* AND in production (it would hit the engineer's dev port, not the app's
|
|
172
|
+
* domain). `init()`/`createSyncEngine` already resolve `window.location.origin`
|
|
173
|
+
* for the sync engine; this brings the auth helpers to the same origin so the
|
|
174
|
+
* two never disagree. An explicit `configureClient({ baseUrl })` still wins
|
|
175
|
+
* (separate-origin API setups), and SSR/node (no `window`) keeps the dev
|
|
176
|
+
* default (server-side calls use same-process paths anyway). */
|
|
164
177
|
export function getBaseUrl(): string {
|
|
178
|
+
if (
|
|
179
|
+
!_baseUrlConfigured &&
|
|
180
|
+
typeof window !== "undefined" &&
|
|
181
|
+
window.location?.origin
|
|
182
|
+
) {
|
|
183
|
+
return window.location.origin;
|
|
184
|
+
}
|
|
165
185
|
return _baseUrl;
|
|
166
186
|
}
|
|
167
187
|
|
|
@@ -250,7 +270,7 @@ function assertBaseUrlSafeForEnv(): void {
|
|
|
250
270
|
*/
|
|
251
271
|
function transportConfig(): import("@pylonsync/sync").TransportConfig {
|
|
252
272
|
return {
|
|
253
|
-
baseUrl:
|
|
273
|
+
baseUrl: getBaseUrl(),
|
|
254
274
|
getToken: () => currentAuthToken() ?? undefined,
|
|
255
275
|
};
|
|
256
276
|
}
|
|
@@ -328,7 +348,7 @@ export async function getAuthContext(
|
|
|
328
348
|
token?: string
|
|
329
349
|
): Promise<{ user_id: string | null }> {
|
|
330
350
|
return pylonFetch<{ user_id: string | null }>(
|
|
331
|
-
{ baseUrl:
|
|
351
|
+
{ baseUrl: getBaseUrl(), token },
|
|
332
352
|
"/api/auth/me",
|
|
333
353
|
);
|
|
334
354
|
}
|
|
@@ -349,7 +369,7 @@ export async function refreshSession(
|
|
|
349
369
|
token: string;
|
|
350
370
|
user_id: string;
|
|
351
371
|
expires_at: number;
|
|
352
|
-
}>({ baseUrl:
|
|
372
|
+
}>({ baseUrl: getBaseUrl(), token }, "/api/auth/refresh", { method: "POST" });
|
|
353
373
|
} catch {
|
|
354
374
|
return null;
|
|
355
375
|
}
|
|
@@ -453,7 +473,7 @@ export async function callFn<T = unknown>(
|
|
|
453
473
|
): Promise<T> {
|
|
454
474
|
return pylonFetch<T>(
|
|
455
475
|
{
|
|
456
|
-
baseUrl:
|
|
476
|
+
baseUrl: getBaseUrl(),
|
|
457
477
|
getToken: () => options.token ?? currentAuthToken() ?? undefined,
|
|
458
478
|
},
|
|
459
479
|
`/api/fn/${name}`,
|
|
@@ -481,7 +501,7 @@ export async function* streamFn(
|
|
|
481
501
|
// transport.
|
|
482
502
|
const res = await pylonFetchRaw(
|
|
483
503
|
{
|
|
484
|
-
baseUrl:
|
|
504
|
+
baseUrl: getBaseUrl(),
|
|
485
505
|
getToken: () => options.token ?? currentAuthToken() ?? undefined,
|
|
486
506
|
},
|
|
487
507
|
`/api/fn/${name}`,
|
|
@@ -605,7 +625,7 @@ export async function uploadFile(
|
|
|
605
625
|
|
|
606
626
|
return pylonFetch<UploadedFile>(
|
|
607
627
|
{
|
|
608
|
-
baseUrl:
|
|
628
|
+
baseUrl: getBaseUrl(),
|
|
609
629
|
getToken: () => options.token ?? currentAuthToken() ?? undefined,
|
|
610
630
|
},
|
|
611
631
|
"/api/files/upload",
|
|
@@ -638,7 +658,7 @@ export async function uploadFileMultipart(
|
|
|
638
658
|
|
|
639
659
|
return pylonFetch<UploadedFile>(
|
|
640
660
|
{
|
|
641
|
-
baseUrl:
|
|
661
|
+
baseUrl: getBaseUrl(),
|
|
642
662
|
getToken: () => options.token ?? currentAuthToken() ?? undefined,
|
|
643
663
|
},
|
|
644
664
|
"/api/files/upload",
|