@pylonsync/sync 0.3.139 → 0.3.142
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 +1 -1
- package/src/index.ts +89 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1683,11 +1683,100 @@ export class SyncEngine {
|
|
|
1683
1683
|
* mutates the server session (sign-in, sign-out, `/api/auth/select-org`)
|
|
1684
1684
|
* so the cached session and React subscribers pick up the change without
|
|
1685
1685
|
* waiting for the next pull.
|
|
1686
|
+
*
|
|
1687
|
+
* Most apps shouldn't need to call this directly — prefer the higher-
|
|
1688
|
+
* level helpers below (`selectOrg`, `clearOrg`, `signOut`) which do the
|
|
1689
|
+
* fetch + notify in one step, or the React `useSession()` hook which
|
|
1690
|
+
* exposes the same helpers bound to the in-scope engine. This is the
|
|
1691
|
+
* escape hatch for code that talks to /api/auth/* via its own client.
|
|
1686
1692
|
*/
|
|
1687
1693
|
notifySessionChanged(): Promise<void> {
|
|
1688
1694
|
return this.refreshResolvedSession();
|
|
1689
1695
|
}
|
|
1690
1696
|
|
|
1697
|
+
/**
|
|
1698
|
+
* Switch the caller's active tenant (organization) and refresh the
|
|
1699
|
+
* resolved session in one shot. Membership is verified server-side
|
|
1700
|
+
* (POST /api/auth/select-org throws 403 if the user isn't a member
|
|
1701
|
+
* of the target org), and the engine's local replica resets so
|
|
1702
|
+
* `db.useQuery` stops returning the previous tenant's rows.
|
|
1703
|
+
*
|
|
1704
|
+
* Throws on any non-2xx response. The error carries the
|
|
1705
|
+
* server-issued JSON error body when available, so callers can
|
|
1706
|
+
* branch on `err.code === "NOT_A_MEMBER"` etc.
|
|
1707
|
+
*/
|
|
1708
|
+
async selectOrg(orgId: string): Promise<void> {
|
|
1709
|
+
await this.authMutate("/api/auth/select-org", { orgId });
|
|
1710
|
+
await this.refreshResolvedSession();
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
/**
|
|
1714
|
+
* Drop the caller's active tenant — back to the "no active org"
|
|
1715
|
+
* state typical of a login-lobby route. Refreshes the resolved
|
|
1716
|
+
* session so React subscribers re-render with `tenantId: null`.
|
|
1717
|
+
*/
|
|
1718
|
+
async clearOrg(): Promise<void> {
|
|
1719
|
+
await this.authMutate("/api/auth/select-org", { orgId: null });
|
|
1720
|
+
await this.refreshResolvedSession();
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
/**
|
|
1724
|
+
* Revoke the current session server-side (DELETE /api/auth/session)
|
|
1725
|
+
* and refresh — leaves the caller anonymous. Local sync stops on
|
|
1726
|
+
* the next pull cycle; replica content stays in IndexedDB so a
|
|
1727
|
+
* subsequent sign-in as the same user is instant.
|
|
1728
|
+
*/
|
|
1729
|
+
async signOut(): Promise<void> {
|
|
1730
|
+
await this.authMutate("/api/auth/session", undefined, "DELETE");
|
|
1731
|
+
await this.refreshResolvedSession();
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
/** Shared transport for the auth helpers above. Same bearer/cookie
|
|
1735
|
+
* policy as `request()` — keeps the auth flows on the same
|
|
1736
|
+
* authentication footing as data sync. */
|
|
1737
|
+
private async authMutate(
|
|
1738
|
+
path: string,
|
|
1739
|
+
body?: unknown,
|
|
1740
|
+
method = "POST",
|
|
1741
|
+
): Promise<unknown> {
|
|
1742
|
+
const headers: Record<string, string> = {};
|
|
1743
|
+
if (body !== undefined) headers["Content-Type"] = "application/json";
|
|
1744
|
+
const token =
|
|
1745
|
+
this.config.token ??
|
|
1746
|
+
this.storage.get(this.tokenStorageKey()) ??
|
|
1747
|
+
undefined;
|
|
1748
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
1749
|
+
const res = await fetch(`${this.config.baseUrl}${path}`, {
|
|
1750
|
+
method,
|
|
1751
|
+
headers,
|
|
1752
|
+
credentials: "include",
|
|
1753
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
1754
|
+
});
|
|
1755
|
+
const text = await res.text();
|
|
1756
|
+
let parsed: unknown = null;
|
|
1757
|
+
if (text) {
|
|
1758
|
+
try {
|
|
1759
|
+
parsed = JSON.parse(text);
|
|
1760
|
+
} catch {
|
|
1761
|
+
// Server returned non-JSON (HTML error page from a proxy,
|
|
1762
|
+
// empty 204, etc.) — fall through; the !res.ok branch will
|
|
1763
|
+
// synthesise a useful Error from the status.
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
if (!res.ok) {
|
|
1767
|
+
const err = new Error(
|
|
1768
|
+
(parsed as { error?: { message?: string } } | null)?.error?.message ??
|
|
1769
|
+
`${method} ${path} failed: ${res.status}`,
|
|
1770
|
+
) as Error & { status?: number; code?: string };
|
|
1771
|
+
err.status = res.status;
|
|
1772
|
+
const code = (parsed as { error?: { code?: string } } | null)?.error
|
|
1773
|
+
?.code;
|
|
1774
|
+
if (code) err.code = code;
|
|
1775
|
+
throw err;
|
|
1776
|
+
}
|
|
1777
|
+
return parsed;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1691
1780
|
/**
|
|
1692
1781
|
* In-flight push promise. Used as a mutex so a slow push can't be restarted
|
|
1693
1782
|
* by the poll timer or a user mutation, which would resend the same batch
|