@pylonsync/sync 0.3.133 → 0.3.134

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.3.133",
6
+ "version": "0.3.134",
7
7
  "type": "module",
8
8
  "main": "src/index.ts",
9
9
  "types": "src/index.ts",
package/src/index.ts CHANGED
@@ -2004,9 +2004,17 @@ export class SyncEngine {
2004
2004
  undefined;
2005
2005
  if (token) headers["Authorization"] = `Bearer ${token}`;
2006
2006
 
2007
+ // credentials: "include" so cookie-auth apps (Yapless and any other
2008
+ // app relying on the `<app>_session` cookie pylon sets at login)
2009
+ // actually authenticate on /api/sync/pull + /api/entities/<E>/cursor.
2010
+ // Without it the pull goes anonymous, every policy default-denies,
2011
+ // and the response is `{changes: []}` even when the same browser
2012
+ // session can read every row via the entity API. Reported in
2013
+ // Repro C against v0.3.131; closed in v0.3.134.
2007
2014
  const res = await fetch(`${this.config.baseUrl}${path}`, {
2008
2015
  method,
2009
2016
  headers,
2017
+ credentials: "include",
2010
2018
  body: body ? JSON.stringify(body) : undefined,
2011
2019
  });
2012
2020
 
@@ -294,3 +294,60 @@ describe("LocalStore.entityNames", () => {
294
294
  expect(names).toEqual(["A", "B"]);
295
295
  });
296
296
  });
297
+
298
+ describe("SyncEngine cookie auth", () => {
299
+ // Regression test for Repro C (v0.3.131): the SyncEngine's `request`
300
+ // method must send `credentials: "include"` on every HTTP call so
301
+ // cookie-authenticated browser sessions reach the server with their
302
+ // session cookie. Without it, /api/sync/pull and the entity
303
+ // reconcile endpoint go anonymous, the default-deny policy returns
304
+ // nothing, and the local replica stays empty forever — even when
305
+ // the same browser session can read every row via the entity API.
306
+ test("pull request sends cookies (credentials: include)", async () => {
307
+ let capturedInit: RequestInit | undefined;
308
+ const restore = installFetch(async (url, init) => {
309
+ capturedInit = init;
310
+ if (url.includes("/api/sync/pull")) {
311
+ return {
312
+ status: 200,
313
+ body: { changes: [], cursor: { last_seq: 0 }, has_more: false },
314
+ };
315
+ }
316
+ return { status: 404, body: {} };
317
+ });
318
+ try {
319
+ const engine = makeEngine();
320
+ await engine.pull();
321
+ expect(capturedInit).toBeDefined();
322
+ expect(capturedInit!.credentials).toBe("include");
323
+ } finally {
324
+ restore();
325
+ }
326
+ });
327
+
328
+ test("reconcile entity fetch sends cookies (credentials: include)", async () => {
329
+ let capturedInit: RequestInit | undefined;
330
+ const restore = installFetch(async (url, init) => {
331
+ if (url.includes("/api/entities/")) {
332
+ capturedInit = init;
333
+ }
334
+ return {
335
+ status: 200,
336
+ body: {
337
+ data: [{ id: "r1", title: "alive" }],
338
+ next_cursor: null,
339
+ has_more: false,
340
+ },
341
+ };
342
+ });
343
+ try {
344
+ const engine = makeEngine();
345
+ seedStore(engine, "Recording", [{ id: "r1", title: "alive" }]);
346
+ await engine.reconcile(["Recording"]);
347
+ expect(capturedInit).toBeDefined();
348
+ expect(capturedInit!.credentials).toBe("include");
349
+ } finally {
350
+ restore();
351
+ }
352
+ });
353
+ });