@foir/sdk 0.5.0 → 0.8.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/CHANGELOG.md +1126 -0
- package/README.md +94 -0
- package/dist/auth/auth.d.ts +243 -0
- package/dist/auth/hosted.d.ts +40 -0
- package/dist/auth/index.d.ts +21 -0
- package/dist/auth/operations.d.ts +144 -0
- package/dist/auth/ops.d.ts +99 -0
- package/dist/auth/session-client.d.ts +76 -0
- package/dist/auth-session.js +137 -0
- package/dist/auth.js +973 -92
- package/dist/chunk-IGNLWT3H.js +35 -0
- package/dist/chunk-OHYVKTGB.js +45 -0
- package/dist/chunk-TAL5H6VA.js +39 -0
- package/dist/client/platform-client.d.ts +29 -0
- package/dist/client.js +275 -37
- package/dist/codegen/block-registry.d.ts +13 -23
- package/dist/codegen/block-registry.js +3 -31
- package/dist/codegen/block-scaffold.d.ts +12 -25
- package/dist/codegen/block-scaffold.js +3 -41
- package/dist/codegen/index.d.ts +12 -61
- package/dist/codegen/index.js +7 -11
- package/dist/codegen/item-registry.d.ts +28 -0
- package/dist/codegen/item-registry.js +7 -0
- package/dist/codegen/item-scaffold.d.ts +29 -0
- package/dist/codegen/item-scaffold.js +7 -0
- package/dist/codegen/preset.d.ts +67 -0
- package/dist/codegen/shared.d.ts +36 -0
- package/dist/components/EditorProvider.d.ts +36 -0
- package/dist/content/content-renderer.d.ts +113 -0
- package/dist/content/index.d.ts +18 -0
- package/dist/content/richtext.d.ts +118 -0
- package/dist/content.js +227 -80
- package/dist/data/builtins.d.ts +506 -0
- package/dist/data/client.d.ts +166 -0
- package/dist/data/document.d.ts +251 -0
- package/dist/data/index.d.ts +73 -0
- package/dist/data/manifest.d.ts +424 -0
- package/dist/data/schema.d.ts +313 -0
- package/dist/data/selection.d.ts +524 -0
- package/dist/data/subjects.d.ts +221 -0
- package/dist/data/subscription-document.d.ts +75 -0
- package/dist/data/subscriptions.d.ts +181 -0
- package/dist/data/transport.d.ts +325 -0
- package/dist/data/ws-transport.d.ts +197 -0
- package/dist/data.js +1683 -0
- package/dist/graphql/cdn-route.d.ts +26 -0
- package/dist/graphql/client.d.ts +30 -0
- package/dist/graphql/content-client.d.ts +66 -0
- package/dist/graphql/foir-request.d.ts +47 -0
- package/dist/hooks/useAutoResize.d.ts +22 -0
- package/dist/hooks/useEditor.d.ts +16 -0
- package/dist/index.d.ts +10 -394
- package/dist/media/BlurhashCanvas.d.ts +10 -0
- package/dist/media/ProgressiveMedia.d.ts +56 -0
- package/dist/media/index.d.ts +18 -0
- package/dist/media/media.d.ts +139 -0
- package/dist/media/screen-capture.d.ts +81 -0
- package/dist/media/useProgressiveLoad.d.ts +23 -0
- package/dist/media.js +102 -0
- package/dist/next/cookieAdapter.d.ts +26 -0
- package/dist/next/index.d.ts +15 -0
- package/dist/next/middleware.d.ts +49 -0
- package/dist/next/session.d.ts +53 -0
- package/dist/next.js +763 -78
- package/dist/protocol/constants.d.ts +6 -0
- package/dist/protocol/messages.d.ts +155 -0
- package/dist/protocol/validation.d.ts +8 -0
- package/dist/push/index.d.ts +14 -0
- package/dist/push/operations.d.ts +22 -0
- package/dist/push/web-push.d.ts +39 -0
- package/dist/push.js +892 -78
- package/dist/react/FoirFeedback.d.ts +62 -0
- package/dist/react/FoirProvider.d.ts +116 -0
- package/dist/react/OAuthButtons.d.ts +20 -0
- package/dist/react/SignIn.d.ts +22 -0
- package/dist/react/UserButton.d.ts +13 -0
- package/dist/react/feedback-notes.d.ts +101 -0
- package/dist/react/index.d.ts +25 -0
- package/dist/react/localStorageAdapter.d.ts +10 -0
- package/dist/react.js +1373 -91
- package/dist/{realtime.d.ts → realtime/index.d.ts} +35 -16
- package/dist/realtime.js +1 -1
- package/dist/server/callback-client.d.ts +92 -0
- package/dist/server/events-client.d.ts +279 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/operation-types.d.ts +134 -0
- package/dist/server/require-scoped-token.d.ts +76 -0
- package/dist/server/server-client.d.ts +34 -0
- package/dist/server/types.d.ts +54 -0
- package/dist/server/verify-scoped-token.d.ts +100 -0
- package/dist/server/webhook-helpers.d.ts +36 -0
- package/dist/server.js +298 -0
- package/dist/transport/foir-http.d.ts +234 -0
- package/dist/transport/persisted-query.d.ts +68 -0
- package/dist/transport/retry.d.ts +61 -0
- package/dist/types/index.d.ts +124 -0
- package/package.json +35 -13
- package/dist/auth.d.ts +0 -382
- package/dist/chunk-ADO5FJNM.js +0 -30
- package/dist/client.d.ts +0 -135
- package/dist/content.d.ts +0 -96
- package/dist/media.d.ts +0 -234
- package/dist/next.d.ts +0 -178
- package/dist/push.d.ts +0 -109
- package/dist/react.d.ts +0 -319
- package/dist/server.d.ts +0 -534
- package/dist/shared-DkWlEd2Z.d.ts +0 -18
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Foir-hosted first-party sessions, from the browser — the model for a
|
|
3
|
+
* backend-less SPA (Vite, CRA, a static site) that wants no tokens in JS.
|
|
4
|
+
*
|
|
5
|
+
* The sibling `fetchHostedSession` in ./hosted does the same exchange from a
|
|
6
|
+
* SERVER: it reads the cookie off an incoming request and forwards it by hand.
|
|
7
|
+
* A browser cannot set a `Cookie` header, so that function is unusable from an
|
|
8
|
+
* SPA by construction. This one lets the browser send the cookie the only way it
|
|
9
|
+
* can — `credentials: 'include'` — and keeps the resulting bearer in memory.
|
|
10
|
+
*
|
|
11
|
+
* How the pieces divide:
|
|
12
|
+
*
|
|
13
|
+
* cookie — long-lived (30d), HttpOnly, first-party on your registrable
|
|
14
|
+
* domain, set by Foir when hosted login finalizes. JS cannot read
|
|
15
|
+
* it, so XSS cannot exfiltrate it.
|
|
16
|
+
* bearer — 15 minutes, held in a closure here. Never written to
|
|
17
|
+
* localStorage/sessionStorage/IndexedDB, never in a cookie of ours.
|
|
18
|
+
*
|
|
19
|
+
* There is no refresh token in this flow. Re-exchanging the cookie IS the
|
|
20
|
+
* refresh, which is why nothing here needs rotation or reuse-detection: the
|
|
21
|
+
* durable credential is an opaque server-side session handle, revocable at any
|
|
22
|
+
* time from the admin panel or `signOut()`.
|
|
23
|
+
*
|
|
24
|
+
* REQUIRES a verified custom login domain that shares a registrable domain with
|
|
25
|
+
* your app (app.acme.com + login.acme.com). That is what makes the cookie
|
|
26
|
+
* first-party and lets `SameSite=Lax` send it. The same endpoints exist on
|
|
27
|
+
* api.foir.dev but a browser cannot reach them there — the cookie would be
|
|
28
|
+
* cross-site, and that CORS surface deliberately refuses credentials.
|
|
29
|
+
*/
|
|
30
|
+
export interface FoirSessionConfig {
|
|
31
|
+
/**
|
|
32
|
+
* Your verified custom login domain, e.g. `https://login.acme.com`. Must share
|
|
33
|
+
* a registrable domain with the app calling it.
|
|
34
|
+
*/
|
|
35
|
+
loginOrigin: string;
|
|
36
|
+
/** Session-exchange path. Default `/session/token`. */
|
|
37
|
+
sessionPath?: string;
|
|
38
|
+
/** Logout path. Default `/session/logout`. */
|
|
39
|
+
logoutPath?: string;
|
|
40
|
+
/** Seconds before expiry to re-exchange. Default 60. */
|
|
41
|
+
refreshSkewSeconds?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Called when the session goes away — the cookie expired, was revoked, or
|
|
44
|
+
* `signOut()` ran. Use it to redirect to `signInUrl()`.
|
|
45
|
+
*/
|
|
46
|
+
onSignOut?: () => void;
|
|
47
|
+
}
|
|
48
|
+
/** The identity behind the current session. */
|
|
49
|
+
export interface FoirSessionUser {
|
|
50
|
+
customerId: string;
|
|
51
|
+
email: string;
|
|
52
|
+
}
|
|
53
|
+
export interface FoirSessionClient {
|
|
54
|
+
/**
|
|
55
|
+
* The current access token, exchanging or re-exchanging the cookie as needed.
|
|
56
|
+
* Null when there is no session. Concurrent callers share one in-flight
|
|
57
|
+
* exchange. Call this before each API request — do not cache the result.
|
|
58
|
+
*/
|
|
59
|
+
getToken(): Promise<string | null>;
|
|
60
|
+
/** The signed-in customer, or null. Exchanges if the session isn't loaded yet. */
|
|
61
|
+
getUser(): Promise<FoirSessionUser | null>;
|
|
62
|
+
/** Revoke the session server-side and clear the cookie. Idempotent. */
|
|
63
|
+
signOut(): Promise<void>;
|
|
64
|
+
/** Where to send the browser to sign in. */
|
|
65
|
+
signInUrl(returnTo?: string): string;
|
|
66
|
+
/** Observe session changes. Returns an unsubscribe function. */
|
|
67
|
+
subscribe(listener: (user: FoirSessionUser | null) => void): () => void;
|
|
68
|
+
/** Cancel the refresh timer. Call on unmount. */
|
|
69
|
+
destroy(): void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create a session client. Safe to call during SSR — it does nothing until a
|
|
73
|
+
* method is invoked, and `getToken()` on the server returns null rather than
|
|
74
|
+
* throwing (there is no cookie jar to draw on).
|
|
75
|
+
*/
|
|
76
|
+
export declare function createFoirSessionClient(config: FoirSessionConfig): FoirSessionClient;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/auth/session-client.ts
|
|
2
|
+
var DEFAULT_SESSION_PATH = "/session/token";
|
|
3
|
+
var DEFAULT_LOGOUT_PATH = "/session/logout";
|
|
4
|
+
var DEFAULT_REFRESH_SKEW_SECONDS = 60;
|
|
5
|
+
var MIN_REFRESH_DELAY_MS = 5e3;
|
|
6
|
+
var CSRF_HEADER = { "X-Foir-Session": "1" };
|
|
7
|
+
function createFoirSessionClient(config) {
|
|
8
|
+
const sessionUrl = `${trimSlash(config.loginOrigin)}${config.sessionPath ?? DEFAULT_SESSION_PATH}`;
|
|
9
|
+
const logoutUrl = `${trimSlash(config.loginOrigin)}${config.logoutPath ?? DEFAULT_LOGOUT_PATH}`;
|
|
10
|
+
const skewMs = (config.refreshSkewSeconds ?? DEFAULT_REFRESH_SKEW_SECONDS) * 1e3;
|
|
11
|
+
let cached = null;
|
|
12
|
+
let inflight = null;
|
|
13
|
+
let timer = null;
|
|
14
|
+
let destroyed = false;
|
|
15
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
16
|
+
function emit(user) {
|
|
17
|
+
for (const l of listeners) l(user);
|
|
18
|
+
}
|
|
19
|
+
function clearTimer() {
|
|
20
|
+
if (timer !== null) {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
timer = null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function scheduleRefresh(expiresAt) {
|
|
26
|
+
clearTimer();
|
|
27
|
+
if (destroyed || typeof setTimeout === "undefined") return;
|
|
28
|
+
const delay = Math.max(expiresAt - Date.now() - skewMs, MIN_REFRESH_DELAY_MS);
|
|
29
|
+
timer = setTimeout(() => {
|
|
30
|
+
void exchange();
|
|
31
|
+
}, delay);
|
|
32
|
+
timer.unref?.();
|
|
33
|
+
}
|
|
34
|
+
function forget() {
|
|
35
|
+
const had = cached !== null;
|
|
36
|
+
cached = null;
|
|
37
|
+
clearTimer();
|
|
38
|
+
if (had) emit(null);
|
|
39
|
+
}
|
|
40
|
+
function exchange() {
|
|
41
|
+
if (inflight) return inflight;
|
|
42
|
+
const pending = (async () => {
|
|
43
|
+
let res;
|
|
44
|
+
try {
|
|
45
|
+
res = await fetch(sessionUrl, {
|
|
46
|
+
method: "GET",
|
|
47
|
+
// The whole point: send the first-party cookie cross-origin (but
|
|
48
|
+
// same-site) to the login domain. Without this the cookie is not sent.
|
|
49
|
+
credentials: "include",
|
|
50
|
+
headers: { ...CSRF_HEADER }
|
|
51
|
+
});
|
|
52
|
+
} catch {
|
|
53
|
+
return cached;
|
|
54
|
+
}
|
|
55
|
+
if (res.status === 401) {
|
|
56
|
+
forget();
|
|
57
|
+
config.onSignOut?.();
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (!res.ok) return cached;
|
|
61
|
+
let json;
|
|
62
|
+
try {
|
|
63
|
+
json = await res.json();
|
|
64
|
+
} catch {
|
|
65
|
+
return cached;
|
|
66
|
+
}
|
|
67
|
+
if (!json.access_token || !json.customer_id) return cached;
|
|
68
|
+
const expiresIn = typeof json.expires_in === "number" ? json.expires_in : 0;
|
|
69
|
+
const next = {
|
|
70
|
+
token: json.access_token,
|
|
71
|
+
expiresAt: Date.now() + expiresIn * 1e3,
|
|
72
|
+
user: { customerId: json.customer_id, email: json.email ?? "" }
|
|
73
|
+
};
|
|
74
|
+
const changed = cached?.user.customerId !== next.user.customerId;
|
|
75
|
+
cached = next;
|
|
76
|
+
scheduleRefresh(next.expiresAt);
|
|
77
|
+
if (changed) emit(next.user);
|
|
78
|
+
return next;
|
|
79
|
+
})();
|
|
80
|
+
inflight = pending;
|
|
81
|
+
void pending.finally(() => {
|
|
82
|
+
if (inflight === pending) inflight = null;
|
|
83
|
+
});
|
|
84
|
+
return pending;
|
|
85
|
+
}
|
|
86
|
+
function fresh() {
|
|
87
|
+
if (!cached) return null;
|
|
88
|
+
return Date.now() < cached.expiresAt - skewMs ? cached : null;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
async getToken() {
|
|
92
|
+
const hit = fresh();
|
|
93
|
+
if (hit) return hit.token;
|
|
94
|
+
const got = await exchange();
|
|
95
|
+
return got?.token ?? null;
|
|
96
|
+
},
|
|
97
|
+
async getUser() {
|
|
98
|
+
const hit = fresh();
|
|
99
|
+
if (hit) return hit.user;
|
|
100
|
+
const got = await exchange();
|
|
101
|
+
return got?.user ?? null;
|
|
102
|
+
},
|
|
103
|
+
async signOut() {
|
|
104
|
+
try {
|
|
105
|
+
await fetch(logoutUrl, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
credentials: "include",
|
|
108
|
+
headers: { ...CSRF_HEADER }
|
|
109
|
+
});
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
forget();
|
|
113
|
+
config.onSignOut?.();
|
|
114
|
+
},
|
|
115
|
+
signInUrl(returnTo) {
|
|
116
|
+
const dest = new URL(trimSlash(config.loginOrigin));
|
|
117
|
+
const target = returnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
|
|
118
|
+
if (target && target !== "/") dest.searchParams.set("returnTo", target);
|
|
119
|
+
return dest.toString();
|
|
120
|
+
},
|
|
121
|
+
subscribe(listener) {
|
|
122
|
+
listeners.add(listener);
|
|
123
|
+
return () => listeners.delete(listener);
|
|
124
|
+
},
|
|
125
|
+
destroy() {
|
|
126
|
+
destroyed = true;
|
|
127
|
+
clearTimer();
|
|
128
|
+
listeners.clear();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function trimSlash(s) {
|
|
133
|
+
return s.replace(/\/+$/, "");
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
createFoirSessionClient
|
|
137
|
+
};
|