@intlayer/api 9.5.10 → 9.5.12
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/cjs/cms/createAuthManager.cjs +49 -15
- package/dist/cjs/cms/createAuthManager.cjs.map +1 -1
- package/dist/cjs/cms/index.cjs +1 -0
- package/dist/cjs/getIntlayerAPI/analytics.cjs +11 -1
- package/dist/cjs/getIntlayerAPI/analytics.cjs.map +1 -1
- package/dist/cjs/getIntlayerAPI/audit.cjs +24 -1
- package/dist/cjs/getIntlayerAPI/audit.cjs.map +1 -1
- package/dist/cjs/index.cjs +1 -0
- package/dist/esm/cms/createAuthManager.mjs +49 -16
- package/dist/esm/cms/createAuthManager.mjs.map +1 -1
- package/dist/esm/cms/index.mjs +2 -2
- package/dist/esm/getIntlayerAPI/analytics.mjs +11 -1
- package/dist/esm/getIntlayerAPI/analytics.mjs.map +1 -1
- package/dist/esm/getIntlayerAPI/audit.mjs +24 -1
- package/dist/esm/getIntlayerAPI/audit.mjs.map +1 -1
- package/dist/esm/index.mjs +2 -2
- package/dist/types/cms/createAuthManager.d.ts +4 -2
- package/dist/types/cms/createAuthManager.d.ts.map +1 -1
- package/dist/types/cms/index.d.ts +2 -2
- package/dist/types/getIntlayerAPI/analytics.d.ts +2 -0
- package/dist/types/getIntlayerAPI/analytics.d.ts.map +1 -1
- package/dist/types/getIntlayerAPI/audit.d.ts +13 -2
- package/dist/types/getIntlayerAPI/audit.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/package.json +3 -3
|
@@ -6,6 +6,40 @@ let defu = require("defu");
|
|
|
6
6
|
|
|
7
7
|
//#region src/cms/createAuthManager.ts
|
|
8
8
|
const ONE_MINUTE_MS = 6e4;
|
|
9
|
+
/**
|
|
10
|
+
* Process-wide token states, keyed by backend URL and credentials. Build
|
|
11
|
+
* steps and watch rebuilds each create their own manager; sharing the state
|
|
12
|
+
* lets them reuse one token instead of awaiting a new exchange every time.
|
|
13
|
+
*/
|
|
14
|
+
const accessTokenStates = /* @__PURE__ */ new Map();
|
|
15
|
+
/** Clears every cached access token, forcing the next call to re-authenticate. */
|
|
16
|
+
const clearAccessTokenCache = () => {
|
|
17
|
+
accessTokenStates.clear();
|
|
18
|
+
};
|
|
19
|
+
/** Returns the shared token state for the given credentials, creating it if missing. */
|
|
20
|
+
const getAccessTokenState = (editorConfig) => {
|
|
21
|
+
const stateKey = [
|
|
22
|
+
editorConfig?.backendURL,
|
|
23
|
+
editorConfig?.clientId,
|
|
24
|
+
editorConfig?.clientSecret
|
|
25
|
+
].join("\0");
|
|
26
|
+
let accessTokenState = accessTokenStates.get(stateKey);
|
|
27
|
+
if (!accessTokenState) {
|
|
28
|
+
accessTokenState = {};
|
|
29
|
+
accessTokenStates.set(stateKey, accessTokenState);
|
|
30
|
+
}
|
|
31
|
+
return accessTokenState;
|
|
32
|
+
};
|
|
33
|
+
/** HTTP statuses meaning the access token was rejected. */
|
|
34
|
+
const AUTHENTICATION_ERROR_STATUSES = /* @__PURE__ */ new Set([401, 403]);
|
|
35
|
+
/**
|
|
36
|
+
* Whether a failed call may succeed with a fresh token: an auth rejection, or
|
|
37
|
+
* an error without HTTP status. A `404` or `500` would fail again regardless.
|
|
38
|
+
*/
|
|
39
|
+
const isAuthenticationError = (error) => {
|
|
40
|
+
const status = error?.status;
|
|
41
|
+
return typeof status !== "number" || AUTHENTICATION_ERROR_STATUSES.has(status);
|
|
42
|
+
};
|
|
9
43
|
/** Whether the current runtime is a browser rather than a server. */
|
|
10
44
|
const isBrowser = () => typeof window !== "undefined";
|
|
11
45
|
/**
|
|
@@ -26,8 +60,8 @@ const getExpiryTimestamp = (token) => {
|
|
|
26
60
|
* Creates an instance-scoped {@link AuthManager} handling the OAuth2
|
|
27
61
|
* `client_credentials` flow (or a pre-issued CLI session token).
|
|
28
62
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
63
|
+
* Managers built with the same backend URL and credentials share one token
|
|
64
|
+
* state, so a process never exchanges credentials more than needed.
|
|
31
65
|
*
|
|
32
66
|
* @param intlayerConfig - Intlayer configuration carrying the `editor`
|
|
33
67
|
* credentials. Defaults to the build-time configuration.
|
|
@@ -48,10 +82,9 @@ const createAuthManager = (intlayerConfig = { editor: _intlayer_config_built.edi
|
|
|
48
82
|
...fetcherOptions.headers ?? {},
|
|
49
83
|
Authorization: `Bearer ${sessionToken}`
|
|
50
84
|
};
|
|
51
|
-
|
|
52
|
-
let expiryTimestamp;
|
|
53
|
-
let pendingRefresh;
|
|
85
|
+
const accessTokenState = getAccessTokenState(resolvedConfig?.editor);
|
|
54
86
|
const needsRefresh = () => {
|
|
87
|
+
const { accessToken, expiryTimestamp } = accessTokenState;
|
|
55
88
|
if (!accessToken) return true;
|
|
56
89
|
if (!expiryTimestamp) return false;
|
|
57
90
|
return Date.now() + ONE_MINUTE_MS >= expiryTimestamp;
|
|
@@ -59,19 +92,19 @@ const createAuthManager = (intlayerConfig = { editor: _intlayer_config_built.edi
|
|
|
59
92
|
const refreshToken = async () => {
|
|
60
93
|
const doRefresh = async () => {
|
|
61
94
|
const tokenData = (await require_getIntlayerAPI_oAuth.getOAuthAPI({}, resolvedConfig).getOAuth2AccessToken())?.data;
|
|
62
|
-
accessToken = tokenData?.accessToken;
|
|
63
|
-
expiryTimestamp = getExpiryTimestamp(tokenData);
|
|
95
|
+
accessTokenState.accessToken = tokenData?.accessToken;
|
|
96
|
+
accessTokenState.expiryTimestamp = getExpiryTimestamp(tokenData);
|
|
64
97
|
};
|
|
65
|
-
pendingRefresh ??= doRefresh().finally(() => {
|
|
66
|
-
pendingRefresh = void 0;
|
|
98
|
+
accessTokenState.pendingRefresh ??= doRefresh().finally(() => {
|
|
99
|
+
accessTokenState.pendingRefresh = void 0;
|
|
67
100
|
});
|
|
68
|
-
await pendingRefresh;
|
|
101
|
+
await accessTokenState.pendingRefresh;
|
|
69
102
|
};
|
|
70
103
|
const applyAuthHeader = () => {
|
|
71
|
-
if (!accessToken) return;
|
|
104
|
+
if (!accessTokenState.accessToken) return;
|
|
72
105
|
fetcherOptions.headers = {
|
|
73
106
|
...fetcherOptions.headers ?? {},
|
|
74
|
-
Authorization: `Bearer ${accessToken}`
|
|
107
|
+
Authorization: `Bearer ${accessTokenState.accessToken}`
|
|
75
108
|
};
|
|
76
109
|
};
|
|
77
110
|
const ensureAuth = async () => {
|
|
@@ -80,8 +113,8 @@ const createAuthManager = (intlayerConfig = { editor: _intlayer_config_built.edi
|
|
|
80
113
|
applyAuthHeader();
|
|
81
114
|
};
|
|
82
115
|
const reset = () => {
|
|
83
|
-
accessToken = void 0;
|
|
84
|
-
expiryTimestamp = void 0;
|
|
116
|
+
accessTokenState.accessToken = void 0;
|
|
117
|
+
accessTokenState.expiryTimestamp = void 0;
|
|
85
118
|
};
|
|
86
119
|
const withAuth = (method, skipAuth = !hasCredentials) => {
|
|
87
120
|
if (skipAuth && !sessionToken && isBrowser()) return async () => {
|
|
@@ -93,7 +126,7 @@ const createAuthManager = (intlayerConfig = { editor: _intlayer_config_built.edi
|
|
|
93
126
|
try {
|
|
94
127
|
return await method(...args);
|
|
95
128
|
} catch (error) {
|
|
96
|
-
if (sessionToken) throw error;
|
|
129
|
+
if (sessionToken || !isAuthenticationError(error)) throw error;
|
|
97
130
|
await refreshToken();
|
|
98
131
|
applyAuthHeader();
|
|
99
132
|
return await method(...args);
|
|
@@ -121,5 +154,6 @@ const createAuthManager = (intlayerConfig = { editor: _intlayer_config_built.edi
|
|
|
121
154
|
};
|
|
122
155
|
|
|
123
156
|
//#endregion
|
|
157
|
+
exports.clearAccessTokenCache = clearAccessTokenCache;
|
|
124
158
|
exports.createAuthManager = createAuthManager;
|
|
125
159
|
//# sourceMappingURL=createAuthManager.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createAuthManager.cjs","names":["editor","getEditorClientSecret","getOAuthAPI"],"sources":["../../../src/cms/createAuthManager.ts"],"sourcesContent":["import { editor } from '@intlayer/config/built';\nimport { getEditorClientSecret } from '@intlayer/config/secrets';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { defu } from 'defu';\nimport type { FetcherOptions } from '../fetcher';\nimport { getOAuthAPI } from '../getIntlayerAPI/oAuth';\n\n/**\n * Shape of the various OAuth2 token payloads the backend may return. Only the\n * fields used to derive the expiration timestamp are described here.\n */\ntype OAuthTokenLike = {\n accessToken?: string;\n accessTokenExpiresAt?: string | Date;\n expires_in?: number;\n expiresIn?: number;\n expiresAt?: string | Date;\n};\n\n/**\n * Any asynchronous API method exposed by a domain section\n * (e.g. `getDictionaries`, `pushDictionaries`).\n */\ntype AsyncMethod<Arguments extends unknown[], Result> = (\n ...args: Arguments\n) => Promise<Result>;\n\n/**\n * A domain section factory: builds a plain object of async methods from shared\n * fetcher options and the Intlayer configuration.\n */\ntype SectionFactory<Section extends Record<string, unknown>> = (\n fetcherOptions: FetcherOptions,\n intlayerConfig: IntlayerConfig\n) => Section;\n\nconst ONE_MINUTE_MS = 60_000;\n\n/** Whether the current runtime is a browser rather than a server. */\nconst isBrowser = (): boolean => typeof window !== 'undefined';\n\n/**\n * Resolves the expiration timestamp (in ms since epoch) from an OAuth2\n * token-like payload, supporting both absolute dates and relative durations.\n */\nconst getExpiryTimestamp = (\n token: OAuthTokenLike | undefined\n): number | undefined => {\n if (!token) return undefined;\n\n const dateLike = (token.accessTokenExpiresAt ?? token.expiresAt) as\n | string\n | Date\n | undefined;\n\n if (dateLike) {\n const timestamp =\n typeof dateLike === 'string'\n ? Date.parse(dateLike)\n : dateLike.getTime?.();\n if (typeof timestamp === 'number' && Number.isFinite(timestamp)) {\n return timestamp;\n }\n }\n\n const seconds = token.expires_in ?? token.expiresIn;\n if (typeof seconds === 'number' && Number.isFinite(seconds)) {\n return Date.now() + seconds * 1000;\n }\n\n return undefined;\n};\n\n/**\n * Options accepted when creating an {@link AuthManager}.\n */\nexport type AuthManagerOptions = {\n /**\n * Base fetcher options merged into every request (e.g. custom headers).\n */\n baseFetcherOptions?: FetcherOptions;\n /**\n * A pre-issued CLI session token (prefixed with `clisession_`). When provided,\n * it is used directly as the Bearer token and the OAuth2 `client_credentials`\n * exchange is skipped entirely.\n */\n sessionToken?: string;\n};\n\n/**\n * Options accepted when binding a domain section to an {@link AuthManager}.\n */\nexport type BindSectionOptions = {\n /**\n * Forces auth injection on (`false`) or off (`true`). Defaults to enabled\n * whenever credentials are available.\n */\n skipAuth?: boolean;\n};\n\n/**\n * Manages CMS authentication for a set of API calls.\n *\n * The manager owns a single mutable {@link FetcherOptions} object\n * (`fetcherOptions`). Domain factories bound through it share the same\n * reference, so refreshing the token in place transparently authenticates every\n * call — without rebuilding the API clients.\n */\nexport type AuthManager = {\n /**\n * Mutable fetcher options carrying the `Authorization` header. Bound sections\n * share this reference so the injected token is reused everywhere.\n */\n fetcherOptions: FetcherOptions;\n /**\n * The resolved Intlayer configuration (merged with the build-time defaults).\n */\n intlayerConfig: IntlayerConfig;\n /**\n * `true` when credentials are available — either a session token or a\n * `clientId`/`clientSecret` pair in the configuration.\n */\n hasCredentials: boolean;\n /**\n * Ensures a valid access token is available and applies it to\n * `fetcherOptions`. Called automatically by {@link withAuth}.\n */\n ensureAuth: () => Promise<void>;\n /**\n * Forces a token refresh on the next call by clearing the cached token.\n */\n reset: () => void;\n /**\n * Wraps a single API method so it injects (and, on failure, refreshes) the\n * access token on every invocation while preserving the method's signature.\n */\n withAuth: <Arguments extends unknown[], Result>(\n method: AsyncMethod<Arguments, Result>,\n skipAuth?: boolean\n ) => AsyncMethod<Arguments, Result>;\n /**\n * Instantiates a domain factory with the managed fetcher options and config,\n * returning a section whose every method authenticates automatically.\n *\n * @example\n * ```ts\n * const dictionary = authManager.bindSection(getDictionaryAPI);\n * await dictionary.pushDictionaries(dictionaries);\n * ```\n */\n bindSection: <Section extends Record<string, unknown>>(\n factory: SectionFactory<Section>,\n options?: BindSectionOptions\n ) => Section;\n};\n\n/**\n * Creates an instance-scoped {@link AuthManager} handling the OAuth2\n * `client_credentials` flow (or a pre-issued CLI session token).\n *\n * Unlike a global token cache, each manager keeps its own token state, so\n * independent SDK instances never interfere with one another.\n *\n * @param intlayerConfig - Intlayer configuration carrying the `editor`\n * credentials. Defaults to the build-time configuration.\n * @param options - Optional session token and base fetcher options.\n */\nexport const createAuthManager = (\n intlayerConfig: Pick<IntlayerConfig, 'editor'> = { editor },\n options: AuthManagerOptions = {}\n): AuthManager => {\n const { baseFetcherOptions = {}, sessionToken } = options;\n\n // `editor` comes from the generated build-time configuration, which never\n // carries `clientSecret` — it would be inlined into client bundles. The\n // secret is supplied server-side by `@intlayer/config/secrets`, which\n // resolves to an inert stub in the browser; `hasCredentials` then stays false\n // there and the OAuth2 exchange never runs.\n //\n // Only consulted when the configuration declares a `clientId`, so a project\n // that comments its credentials out stays uncredentialed even with the\n // environment variables still defined.\n const resolvedConfig = defu(intlayerConfig ?? {}, {\n editor: {\n ...editor,\n clientSecret: editor?.clientId ? getEditorClientSecret() : undefined,\n },\n }) as IntlayerConfig;\n\n // `credentials: 'omit'` prevents the browser from attaching session cookies;\n // authentication is handled exclusively through the injected Bearer token.\n // This is required because the backend only allows credentialed requests for\n // whitelisted first-party origins.\n const fetcherOptions: FetcherOptions = {\n ...baseFetcherOptions,\n credentials: 'omit',\n };\n\n const hasCredentials =\n Boolean(sessionToken) ||\n Boolean(\n resolvedConfig?.editor?.clientId && resolvedConfig?.editor?.clientSecret\n );\n\n // A session token is used as-is and never expires within the SDK's concern,\n // so inject it once up-front and skip the OAuth2 exchange.\n if (sessionToken) {\n fetcherOptions.headers = {\n ...(fetcherOptions.headers ?? {}),\n Authorization: `Bearer ${sessionToken}`,\n } as HeadersInit;\n }\n\n let accessToken: string | undefined;\n let expiryTimestamp: number | undefined;\n let pendingRefresh: Promise<void> | undefined;\n\n const needsRefresh = (): boolean => {\n if (!accessToken) return true;\n if (!expiryTimestamp) return false; // Unknown expiry: assume usable until failure.\n\n // Refresh one minute before the actual expiry to avoid edge-of-expiry races.\n return Date.now() + ONE_MINUTE_MS >= expiryTimestamp;\n };\n\n const refreshToken = async (): Promise<void> => {\n const doRefresh = async (): Promise<void> => {\n const oAuthAPI = getOAuthAPI({}, resolvedConfig);\n const response = await oAuthAPI.getOAuth2AccessToken();\n const tokenData = response?.data as OAuthTokenLike | undefined;\n\n accessToken = tokenData?.accessToken;\n expiryTimestamp = getExpiryTimestamp(tokenData);\n };\n\n // De-duplicate concurrent refreshes so a burst of calls triggers one fetch.\n pendingRefresh ??= doRefresh().finally(() => {\n pendingRefresh = undefined;\n });\n\n await pendingRefresh;\n };\n\n const applyAuthHeader = (): void => {\n if (!accessToken) return;\n fetcherOptions.headers = {\n ...(fetcherOptions.headers ?? {}),\n Authorization: `Bearer ${accessToken}`,\n } as HeadersInit;\n };\n\n const ensureAuth = async (): Promise<void> => {\n if (sessionToken) return; // Already injected; nothing to refresh.\n if (needsRefresh()) {\n await refreshToken();\n }\n applyAuthHeader();\n };\n\n const reset = (): void => {\n accessToken = undefined;\n expiryTimestamp = undefined;\n };\n\n const withAuth = <Arguments extends unknown[], Result>(\n method: AsyncMethod<Arguments, Result>,\n skipAuth = !hasCredentials\n ): AsyncMethod<Arguments, Result> => {\n // In a browser the project credentials are structurally unavailable, so an\n // authenticated call would silently go out unauthenticated and come back\n // 401. Fail with an actionable message instead: a browser must authenticate\n // as the signed-in *user* (a session token, or the session cookie on a\n // first-party origin), never as the project.\n if (skipAuth && !sessionToken && isBrowser()) {\n return async () => {\n throw new Error(\n 'Intlayer CMS credentials are not available in the browser. ' +\n '`clientSecret` is a server-side credential and is never bundled. ' +\n 'Call this from a server route, or pass a user session token: ' +\n 'createIntlayerCMS(config, { sessionToken }).'\n );\n };\n }\n\n if (skipAuth) return method;\n\n return async (...args: Arguments): Promise<Result> => {\n await ensureAuth();\n\n try {\n return await method(...args);\n } catch (error) {\n // The token may have been revoked or expired early: refresh once and\n // retry before surfacing the error. A session token cannot be\n // refreshed, so re-throw immediately in that case.\n if (sessionToken) throw error;\n\n await refreshToken();\n applyAuthHeader();\n return await method(...args);\n }\n };\n };\n\n const bindSection = <Section extends Record<string, unknown>>(\n factory: SectionFactory<Section>,\n bindOptions: BindSectionOptions = {}\n ): Section => {\n const section = factory(fetcherOptions, resolvedConfig);\n const skipAuth = bindOptions.skipAuth ?? !hasCredentials;\n\n return new Proxy(section, {\n get(target, property, receiver) {\n const value = Reflect.get(target, property, receiver);\n\n if (typeof value !== 'function') return value;\n\n return withAuth(\n (value as AsyncMethod<unknown[], unknown>).bind(target),\n skipAuth\n );\n },\n });\n };\n\n return {\n fetcherOptions,\n intlayerConfig: resolvedConfig,\n hasCredentials,\n ensureAuth,\n reset,\n withAuth,\n bindSection,\n };\n};\n"],"mappings":";;;;;;;AAoCA,MAAM,gBAAgB;;AAGtB,MAAM,kBAA2B,OAAO,WAAW;;;;;AAMnD,MAAM,sBACJ,UACuB;CACvB,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,WAAY,MAAM,wBAAwB,MAAM;CAKtD,IAAI,UAAU;EACZ,MAAM,YACJ,OAAO,aAAa,WAChB,KAAK,MAAM,QAAQ,IACnB,SAAS,UAAU;EACzB,IAAI,OAAO,cAAc,YAAY,OAAO,SAAS,SAAS,GAC5D,OAAO;CAEX;CAEA,MAAM,UAAU,MAAM,cAAc,MAAM;CAC1C,IAAI,OAAO,YAAY,YAAY,OAAO,SAAS,OAAO,GACxD,OAAO,KAAK,IAAI,IAAI,UAAU;AAIlC;;;;;;;;;;;;AAgGA,MAAa,qBACX,iBAAiD,EAAE,sCAAO,GAC1D,UAA8B,CAAC,MACf;CAChB,MAAM,EAAE,qBAAqB,CAAC,GAAG,iBAAiB;CAWlD,MAAM,qBAAiB,WAAK,kBAAkB,CAAC,GAAG,EAChD,QAAQ;EACN,GAAGA;EACH,cAAcA,+BAAQ,eAAWC,gDAAsB,IAAI;CAC7D,EACF,CAAC;CAMD,MAAM,iBAAiC;EACrC,GAAG;EACH,aAAa;CACf;CAEA,MAAM,iBACJ,QAAQ,YAAY,KACpB,QACE,gBAAgB,QAAQ,YAAY,gBAAgB,QAAQ,YAC9D;CAIF,IAAI,cACF,eAAe,UAAU;EACvB,GAAI,eAAe,WAAW,CAAC;EAC/B,eAAe,UAAU;CAC3B;CAGF,IAAI;CACJ,IAAI;CACJ,IAAI;CAEJ,MAAM,qBAA8B;EAClC,IAAI,CAAC,aAAa,OAAO;EACzB,IAAI,CAAC,iBAAiB,OAAO;EAG7B,OAAO,KAAK,IAAI,IAAI,iBAAiB;CACvC;CAEA,MAAM,eAAe,YAA2B;EAC9C,MAAM,YAAY,YAA2B;GAG3C,MAAM,aAAY,MAFDC,yCAAY,CAAC,GAAG,cACH,CAAC,CAAC,qBAAqB,EAC3B,EAAE;GAE5B,cAAc,WAAW;GACzB,kBAAkB,mBAAmB,SAAS;EAChD;EAGA,mBAAmB,UAAU,CAAC,CAAC,cAAc;GAC3C,iBAAiB;EACnB,CAAC;EAED,MAAM;CACR;CAEA,MAAM,wBAA8B;EAClC,IAAI,CAAC,aAAa;EAClB,eAAe,UAAU;GACvB,GAAI,eAAe,WAAW,CAAC;GAC/B,eAAe,UAAU;EAC3B;CACF;CAEA,MAAM,aAAa,YAA2B;EAC5C,IAAI,cAAc;EAClB,IAAI,aAAa,GACf,MAAM,aAAa;EAErB,gBAAgB;CAClB;CAEA,MAAM,cAAoB;EACxB,cAAc;EACd,kBAAkB;CACpB;CAEA,MAAM,YACJ,QACA,WAAW,CAAC,mBACuB;EAMnC,IAAI,YAAY,CAAC,gBAAgB,UAAU,GACzC,OAAO,YAAY;GACjB,MAAM,IAAI,MACR,uOAIF;EACF;EAGF,IAAI,UAAU,OAAO;EAErB,OAAO,OAAO,GAAG,SAAqC;GACpD,MAAM,WAAW;GAEjB,IAAI;IACF,OAAO,MAAM,OAAO,GAAG,IAAI;GAC7B,SAAS,OAAO;IAId,IAAI,cAAc,MAAM;IAExB,MAAM,aAAa;IACnB,gBAAgB;IAChB,OAAO,MAAM,OAAO,GAAG,IAAI;GAC7B;EACF;CACF;CAEA,MAAM,eACJ,SACA,cAAkC,CAAC,MACvB;EACZ,MAAM,UAAU,QAAQ,gBAAgB,cAAc;EACtD,MAAM,WAAW,YAAY,YAAY,CAAC;EAE1C,OAAO,IAAI,MAAM,SAAS,EACxB,IAAI,QAAQ,UAAU,UAAU;GAC9B,MAAM,QAAQ,QAAQ,IAAI,QAAQ,UAAU,QAAQ;GAEpD,IAAI,OAAO,UAAU,YAAY,OAAO;GAExC,OAAO,SACJ,MAA0C,KAAK,MAAM,GACtD,QACF;EACF,EACF,CAAC;CACH;CAEA,OAAO;EACL;EACA,gBAAgB;EAChB;EACA;EACA;EACA;EACA;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"createAuthManager.cjs","names":["editor","getEditorClientSecret","getOAuthAPI"],"sources":["../../../src/cms/createAuthManager.ts"],"sourcesContent":["import { editor } from '@intlayer/config/built';\nimport { getEditorClientSecret } from '@intlayer/config/secrets';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { defu } from 'defu';\nimport type { FetcherOptions } from '../fetcher';\nimport { getOAuthAPI } from '../getIntlayerAPI/oAuth';\n\n/**\n * Shape of the various OAuth2 token payloads the backend may return. Only the\n * fields used to derive the expiration timestamp are described here.\n */\ntype OAuthTokenLike = {\n accessToken?: string;\n accessTokenExpiresAt?: string | Date;\n expires_in?: number;\n expiresIn?: number;\n expiresAt?: string | Date;\n};\n\n/**\n * Any asynchronous API method exposed by a domain section\n * (e.g. `getDictionaries`, `pushDictionaries`).\n */\ntype AsyncMethod<Arguments extends unknown[], Result> = (\n ...args: Arguments\n) => Promise<Result>;\n\n/**\n * A domain section factory: builds a plain object of async methods from shared\n * fetcher options and the Intlayer configuration.\n */\ntype SectionFactory<Section extends Record<string, unknown>> = (\n fetcherOptions: FetcherOptions,\n intlayerConfig: IntlayerConfig\n) => Section;\n\nconst ONE_MINUTE_MS = 60_000;\n\n/**\n * OAuth2 token state for one set of credentials, shared by every manager\n * built with them.\n */\ntype AccessTokenState = {\n accessToken?: string;\n expiryTimestamp?: number;\n pendingRefresh?: Promise<void>;\n};\n\n/**\n * Process-wide token states, keyed by backend URL and credentials. Build\n * steps and watch rebuilds each create their own manager; sharing the state\n * lets them reuse one token instead of awaiting a new exchange every time.\n */\nconst accessTokenStates = new Map<string, AccessTokenState>();\n\n/** Clears every cached access token, forcing the next call to re-authenticate. */\nexport const clearAccessTokenCache = (): void => {\n accessTokenStates.clear();\n};\n\n/** Returns the shared token state for the given credentials, creating it if missing. */\nconst getAccessTokenState = (\n editorConfig: IntlayerConfig['editor'] | undefined\n): AccessTokenState => {\n const stateKey = [\n editorConfig?.backendURL,\n editorConfig?.clientId,\n editorConfig?.clientSecret,\n ].join('\\0');\n\n let accessTokenState = accessTokenStates.get(stateKey);\n\n if (!accessTokenState) {\n accessTokenState = {};\n accessTokenStates.set(stateKey, accessTokenState);\n }\n\n return accessTokenState;\n};\n\n/** HTTP statuses meaning the access token was rejected. */\nconst AUTHENTICATION_ERROR_STATUSES = new Set([401, 403]);\n\n/**\n * Whether a failed call may succeed with a fresh token: an auth rejection, or\n * an error without HTTP status. A `404` or `500` would fail again regardless.\n */\nconst isAuthenticationError = (error: unknown): boolean => {\n const status = (error as { status?: unknown } | undefined)?.status;\n\n return (\n typeof status !== 'number' || AUTHENTICATION_ERROR_STATUSES.has(status)\n );\n};\n\n/** Whether the current runtime is a browser rather than a server. */\nconst isBrowser = (): boolean => typeof window !== 'undefined';\n\n/**\n * Resolves the expiration timestamp (in ms since epoch) from an OAuth2\n * token-like payload, supporting both absolute dates and relative durations.\n */\nconst getExpiryTimestamp = (\n token: OAuthTokenLike | undefined\n): number | undefined => {\n if (!token) return undefined;\n\n const dateLike = (token.accessTokenExpiresAt ?? token.expiresAt) as\n | string\n | Date\n | undefined;\n\n if (dateLike) {\n const timestamp =\n typeof dateLike === 'string'\n ? Date.parse(dateLike)\n : dateLike.getTime?.();\n if (typeof timestamp === 'number' && Number.isFinite(timestamp)) {\n return timestamp;\n }\n }\n\n const seconds = token.expires_in ?? token.expiresIn;\n if (typeof seconds === 'number' && Number.isFinite(seconds)) {\n return Date.now() + seconds * 1000;\n }\n\n return undefined;\n};\n\n/**\n * Options accepted when creating an {@link AuthManager}.\n */\nexport type AuthManagerOptions = {\n /**\n * Base fetcher options merged into every request (e.g. custom headers).\n */\n baseFetcherOptions?: FetcherOptions;\n /**\n * A pre-issued CLI session token (prefixed with `clisession_`). When provided,\n * it is used directly as the Bearer token and the OAuth2 `client_credentials`\n * exchange is skipped entirely.\n */\n sessionToken?: string;\n};\n\n/**\n * Options accepted when binding a domain section to an {@link AuthManager}.\n */\nexport type BindSectionOptions = {\n /**\n * Forces auth injection on (`false`) or off (`true`). Defaults to enabled\n * whenever credentials are available.\n */\n skipAuth?: boolean;\n};\n\n/**\n * Manages CMS authentication for a set of API calls.\n *\n * The manager owns a single mutable {@link FetcherOptions} object\n * (`fetcherOptions`). Domain factories bound through it share the same\n * reference, so refreshing the token in place transparently authenticates every\n * call — without rebuilding the API clients.\n */\nexport type AuthManager = {\n /**\n * Mutable fetcher options carrying the `Authorization` header. Bound sections\n * share this reference so the injected token is reused everywhere.\n */\n fetcherOptions: FetcherOptions;\n /**\n * The resolved Intlayer configuration (merged with the build-time defaults).\n */\n intlayerConfig: IntlayerConfig;\n /**\n * `true` when credentials are available — either a session token or a\n * `clientId`/`clientSecret` pair in the configuration.\n */\n hasCredentials: boolean;\n /**\n * Ensures a valid access token is available and applies it to\n * `fetcherOptions`. Called automatically by {@link withAuth}.\n */\n ensureAuth: () => Promise<void>;\n /**\n * Forces a token refresh on the next call by clearing the cached token.\n */\n reset: () => void;\n /**\n * Wraps a single API method so it injects (and, on failure, refreshes) the\n * access token on every invocation while preserving the method's signature.\n */\n withAuth: <Arguments extends unknown[], Result>(\n method: AsyncMethod<Arguments, Result>,\n skipAuth?: boolean\n ) => AsyncMethod<Arguments, Result>;\n /**\n * Instantiates a domain factory with the managed fetcher options and config,\n * returning a section whose every method authenticates automatically.\n *\n * @example\n * ```ts\n * const dictionary = authManager.bindSection(getDictionaryAPI);\n * await dictionary.pushDictionaries(dictionaries);\n * ```\n */\n bindSection: <Section extends Record<string, unknown>>(\n factory: SectionFactory<Section>,\n options?: BindSectionOptions\n ) => Section;\n};\n\n/**\n * Creates an instance-scoped {@link AuthManager} handling the OAuth2\n * `client_credentials` flow (or a pre-issued CLI session token).\n *\n * Managers built with the same backend URL and credentials share one token\n * state, so a process never exchanges credentials more than needed.\n *\n * @param intlayerConfig - Intlayer configuration carrying the `editor`\n * credentials. Defaults to the build-time configuration.\n * @param options - Optional session token and base fetcher options.\n */\nexport const createAuthManager = (\n intlayerConfig: Pick<IntlayerConfig, 'editor'> = { editor },\n options: AuthManagerOptions = {}\n): AuthManager => {\n const { baseFetcherOptions = {}, sessionToken } = options;\n\n // `editor` comes from the generated build-time configuration, which never\n // carries `clientSecret` — it would be inlined into client bundles. The\n // secret is supplied server-side by `@intlayer/config/secrets`, which\n // resolves to an inert stub in the browser; `hasCredentials` then stays false\n // there and the OAuth2 exchange never runs.\n //\n // Only consulted when the configuration declares a `clientId`, so a project\n // that comments its credentials out stays uncredentialed even with the\n // environment variables still defined.\n const resolvedConfig = defu(intlayerConfig ?? {}, {\n editor: {\n ...editor,\n clientSecret: editor?.clientId ? getEditorClientSecret() : undefined,\n },\n }) as IntlayerConfig;\n\n // `credentials: 'omit'` prevents the browser from attaching session cookies;\n // authentication is handled exclusively through the injected Bearer token.\n // This is required because the backend only allows credentialed requests for\n // whitelisted first-party origins.\n const fetcherOptions: FetcherOptions = {\n ...baseFetcherOptions,\n credentials: 'omit',\n };\n\n const hasCredentials =\n Boolean(sessionToken) ||\n Boolean(\n resolvedConfig?.editor?.clientId && resolvedConfig?.editor?.clientSecret\n );\n\n // A session token is used as-is and never expires within the SDK's concern,\n // so inject it once up-front and skip the OAuth2 exchange.\n if (sessionToken) {\n fetcherOptions.headers = {\n ...(fetcherOptions.headers ?? {}),\n Authorization: `Bearer ${sessionToken}`,\n } as HeadersInit;\n }\n\n const accessTokenState = getAccessTokenState(resolvedConfig?.editor);\n\n const needsRefresh = (): boolean => {\n const { accessToken, expiryTimestamp } = accessTokenState;\n\n if (!accessToken) return true;\n if (!expiryTimestamp) return false; // Unknown expiry: assume usable until failure.\n\n // Refresh one minute before the actual expiry to avoid edge-of-expiry races.\n return Date.now() + ONE_MINUTE_MS >= expiryTimestamp;\n };\n\n const refreshToken = async (): Promise<void> => {\n const doRefresh = async (): Promise<void> => {\n const oAuthAPI = getOAuthAPI({}, resolvedConfig);\n const response = await oAuthAPI.getOAuth2AccessToken();\n const tokenData = response?.data as OAuthTokenLike | undefined;\n\n accessTokenState.accessToken = tokenData?.accessToken;\n accessTokenState.expiryTimestamp = getExpiryTimestamp(tokenData);\n };\n\n // De-duplicate concurrent refreshes so a burst of calls triggers one fetch.\n accessTokenState.pendingRefresh ??= doRefresh().finally(() => {\n accessTokenState.pendingRefresh = undefined;\n });\n\n await accessTokenState.pendingRefresh;\n };\n\n const applyAuthHeader = (): void => {\n if (!accessTokenState.accessToken) return;\n fetcherOptions.headers = {\n ...(fetcherOptions.headers ?? {}),\n Authorization: `Bearer ${accessTokenState.accessToken}`,\n } as HeadersInit;\n };\n\n const ensureAuth = async (): Promise<void> => {\n if (sessionToken) return; // Already injected; nothing to refresh.\n if (needsRefresh()) {\n await refreshToken();\n }\n applyAuthHeader();\n };\n\n const reset = (): void => {\n accessTokenState.accessToken = undefined;\n accessTokenState.expiryTimestamp = undefined;\n };\n\n const withAuth = <Arguments extends unknown[], Result>(\n method: AsyncMethod<Arguments, Result>,\n skipAuth = !hasCredentials\n ): AsyncMethod<Arguments, Result> => {\n // In a browser the project credentials are structurally unavailable, so an\n // authenticated call would silently go out unauthenticated and come back\n // 401. Fail with an actionable message instead: a browser must authenticate\n // as the signed-in *user* (a session token, or the session cookie on a\n // first-party origin), never as the project.\n if (skipAuth && !sessionToken && isBrowser()) {\n return async () => {\n throw new Error(\n 'Intlayer CMS credentials are not available in the browser. ' +\n '`clientSecret` is a server-side credential and is never bundled. ' +\n 'Call this from a server route, or pass a user session token: ' +\n 'createIntlayerCMS(config, { sessionToken }).'\n );\n };\n }\n\n if (skipAuth) return method;\n\n return async (...args: Arguments): Promise<Result> => {\n await ensureAuth();\n\n try {\n return await method(...args);\n } catch (error) {\n // The token may have been revoked or expired early: refresh once and\n // retry before surfacing the error. A session token cannot be\n // refreshed, so re-throw immediately in that case.\n if (sessionToken || !isAuthenticationError(error)) throw error;\n\n await refreshToken();\n applyAuthHeader();\n return await method(...args);\n }\n };\n };\n\n const bindSection = <Section extends Record<string, unknown>>(\n factory: SectionFactory<Section>,\n bindOptions: BindSectionOptions = {}\n ): Section => {\n const section = factory(fetcherOptions, resolvedConfig);\n const skipAuth = bindOptions.skipAuth ?? !hasCredentials;\n\n return new Proxy(section, {\n get(target, property, receiver) {\n const value = Reflect.get(target, property, receiver);\n\n if (typeof value !== 'function') return value;\n\n return withAuth(\n (value as AsyncMethod<unknown[], unknown>).bind(target),\n skipAuth\n );\n },\n });\n };\n\n return {\n fetcherOptions,\n intlayerConfig: resolvedConfig,\n hasCredentials,\n ensureAuth,\n reset,\n withAuth,\n bindSection,\n };\n};\n"],"mappings":"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"}
|
package/dist/cjs/cms/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
2
2
|
const require_cms_createAuthManager = require('./createAuthManager.cjs');
|
|
3
3
|
const require_cms_createIntlayerCMS = require('./createIntlayerCMS.cjs');
|
|
4
4
|
|
|
5
|
+
exports.clearAccessTokenCache = require_cms_createAuthManager.clearAccessTokenCache;
|
|
5
6
|
exports.createAuthManager = require_cms_createAuthManager.createAuthManager;
|
|
6
7
|
exports.createEndpoint = require_cms_createIntlayerCMS.createEndpoint;
|
|
7
8
|
exports.createIntlayerCMS = require_cms_createIntlayerCMS.createIntlayerCMS;
|
|
@@ -43,12 +43,22 @@ const getAnalyticsAPI = (authAPIOptions = {}, intlayerConfig) => {
|
|
|
43
43
|
* @returns The experiment result set.
|
|
44
44
|
*/
|
|
45
45
|
const getExperimentResults = async (experimentKey, otherOptions = {}) => await require_fetcher.fetcher(`${ANALYTICS_API_ROUTE}/experiments/${experimentKey}`, authAPIOptions, otherOptions, { method: "GET" });
|
|
46
|
+
/**
|
|
47
|
+
* Page metadata (title and meta description) for a given url.
|
|
48
|
+
* @param url - The page url to inspect.
|
|
49
|
+
* @returns Title and description of the page.
|
|
50
|
+
*/
|
|
51
|
+
const getPageMetadata = async (url, otherOptions = {}) => await require_fetcher.fetcher(`${ANALYTICS_API_ROUTE}/page-metadata`, authAPIOptions, otherOptions, {
|
|
52
|
+
method: "GET",
|
|
53
|
+
params: { url }
|
|
54
|
+
});
|
|
46
55
|
return {
|
|
47
56
|
sendEvents,
|
|
48
57
|
getOverview,
|
|
49
58
|
getAudience,
|
|
50
59
|
getContentStats,
|
|
51
|
-
getExperimentResults
|
|
60
|
+
getExperimentResults,
|
|
61
|
+
getPageMetadata
|
|
52
62
|
};
|
|
53
63
|
};
|
|
54
64
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.cjs","names":["editor","fetcher","createEndpoint"],"sources":["../../../src/getIntlayerAPI/analytics.ts"],"sourcesContent":["import type {\n AudienceRange,\n GetAnalyticsOverviewResult,\n GetAudienceResult,\n GetContentStatsResult,\n GetExperimentResultsResult,\n IngestAnalyticsBody,\n IngestAnalyticsResult,\n} from '@intlayer/backend';\nimport { editor } from '@intlayer/config/built';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { createEndpoint } from '../cms/createIntlayerCMS';\nimport { type FetcherOptions, fetcher } from '../fetcher';\n\nexport const getAnalyticsAPI = (\n authAPIOptions: FetcherOptions = {},\n intlayerConfig?: IntlayerConfig\n) => {\n const backendURL = intlayerConfig?.editor?.backendURL ?? editor.backendURL;\n\n const ANALYTICS_API_ROUTE = `${backendURL}/api/analytics`;\n\n /**\n * Ingest a batch of analytics events. Public — attribution is by the\n * project's `clientId` in the body.\n * @param body - Session id, sdk version, and the collected events.\n * @returns The number of accepted events.\n */\n const sendEvents = async (\n body: IngestAnalyticsBody,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<IngestAnalyticsResult>(\n `${ANALYTICS_API_ROUTE}/events`,\n authAPIOptions,\n otherOptions,\n {\n method: 'POST',\n body,\n }\n );\n\n /**\n * Page/locale totals for the authenticated project.\n * @returns Views grouped by url and locale.\n */\n const getOverview = async (otherOptions: FetcherOptions = {}) =>\n await fetcher<GetAnalyticsOverviewResult>(\n `${ANALYTICS_API_ROUTE}/overview`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n /**\n * Audience report: distinct visitors (today / 7d / window), page views, the\n * evolution series, and locale + country breakdowns.\n * @param range - Rolling window, either a named range (`1h`, `24h`, `7d`,\n * `30d`, `90d`, `6mo`, `1y`, `3y`) or a number of days (default 30).\n * @returns The audience statistics.\n */\n const getAudience = async (\n range: AudienceRange | number = 30,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetAudienceResult>(\n `${ANALYTICS_API_ROUTE}/audience`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n params: typeof range === 'number' ? { days: String(range) } : { range },\n }\n );\n\n /**\n * Per-content exposure totals — \"which content is actually shown\".\n * @returns Exposures grouped by dictionary key, key path, and locale.\n */\n const getContentStats = async (otherOptions: FetcherOptions = {}) =>\n await fetcher<GetContentStatsResult>(\n `${ANALYTICS_API_ROUTE}/content-stats`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n /**\n * A/B experiment results with per-variant conversion rates and significance.\n * @param experimentKey - The experiment to evaluate.\n * @returns The experiment result set.\n */\n const getExperimentResults = async (\n experimentKey: string,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetExperimentResultsResult>(\n `${ANALYTICS_API_ROUTE}/experiments/${experimentKey}`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n return {\n sendEvents,\n getOverview,\n getAudience,\n getContentStats,\n getExperimentResults,\n };\n};\n\n/**\n * Authenticated `analytics` endpoint bound to an Intlayer CMS authenticator.\n *\n * Pass an authenticator created with `createIntlayerCMS`, or omit it to use\n * the build-time configuration (`@intlayer/config/built`).\n */\nexport const analyticsEndpoint = createEndpoint(getAnalyticsAPI);\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"analytics.cjs","names":["editor","fetcher","createEndpoint"],"sources":["../../../src/getIntlayerAPI/analytics.ts"],"sourcesContent":["import type {\n AudienceRange,\n GetAnalyticsOverviewResult,\n GetAudienceResult,\n GetContentStatsResult,\n GetExperimentResultsResult,\n GetPageMetadataResult,\n IngestAnalyticsBody,\n IngestAnalyticsResult,\n} from '@intlayer/backend';\nimport { editor } from '@intlayer/config/built';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { createEndpoint } from '../cms/createIntlayerCMS';\nimport { type FetcherOptions, fetcher } from '../fetcher';\n\nexport const getAnalyticsAPI = (\n authAPIOptions: FetcherOptions = {},\n intlayerConfig?: IntlayerConfig\n) => {\n const backendURL = intlayerConfig?.editor?.backendURL ?? editor.backendURL;\n\n const ANALYTICS_API_ROUTE = `${backendURL}/api/analytics`;\n\n /**\n * Ingest a batch of analytics events. Public — attribution is by the\n * project's `clientId` in the body.\n * @param body - Session id, sdk version, and the collected events.\n * @returns The number of accepted events.\n */\n const sendEvents = async (\n body: IngestAnalyticsBody,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<IngestAnalyticsResult>(\n `${ANALYTICS_API_ROUTE}/events`,\n authAPIOptions,\n otherOptions,\n {\n method: 'POST',\n body,\n }\n );\n\n /**\n * Page/locale totals for the authenticated project.\n * @returns Views grouped by url and locale.\n */\n const getOverview = async (otherOptions: FetcherOptions = {}) =>\n await fetcher<GetAnalyticsOverviewResult>(\n `${ANALYTICS_API_ROUTE}/overview`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n /**\n * Audience report: distinct visitors (today / 7d / window), page views, the\n * evolution series, and locale + country breakdowns.\n * @param range - Rolling window, either a named range (`1h`, `24h`, `7d`,\n * `30d`, `90d`, `6mo`, `1y`, `3y`) or a number of days (default 30).\n * @returns The audience statistics.\n */\n const getAudience = async (\n range: AudienceRange | number = 30,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetAudienceResult>(\n `${ANALYTICS_API_ROUTE}/audience`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n params: typeof range === 'number' ? { days: String(range) } : { range },\n }\n );\n\n /**\n * Per-content exposure totals — \"which content is actually shown\".\n * @returns Exposures grouped by dictionary key, key path, and locale.\n */\n const getContentStats = async (otherOptions: FetcherOptions = {}) =>\n await fetcher<GetContentStatsResult>(\n `${ANALYTICS_API_ROUTE}/content-stats`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n /**\n * A/B experiment results with per-variant conversion rates and significance.\n * @param experimentKey - The experiment to evaluate.\n * @returns The experiment result set.\n */\n const getExperimentResults = async (\n experimentKey: string,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetExperimentResultsResult>(\n `${ANALYTICS_API_ROUTE}/experiments/${experimentKey}`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n /**\n * Page metadata (title and meta description) for a given url.\n * @param url - The page url to inspect.\n * @returns Title and description of the page.\n */\n const getPageMetadata = async (\n url: string,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetPageMetadataResult>(\n `${ANALYTICS_API_ROUTE}/page-metadata`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n params: { url },\n }\n );\n\n return {\n sendEvents,\n getOverview,\n getAudience,\n getContentStats,\n getExperimentResults,\n getPageMetadata,\n };\n};\n\n/**\n * Authenticated `analytics` endpoint bound to an Intlayer CMS authenticator.\n *\n * Pass an authenticator created with `createIntlayerCMS`, or omit it to use\n * the build-time configuration (`@intlayer/config/built`).\n */\nexport const analyticsEndpoint = createEndpoint(getAnalyticsAPI);\n"],"mappings":";;;;;;AAeA,MAAa,mBACX,iBAAiC,CAAC,GAClC,mBACG;CAGH,MAAM,sBAAsB,GAFT,gBAAgB,QAAQ,cAAcA,8BAAO,WAEtB;;;;;;;CAQ1C,MAAM,aAAa,OACjB,MACA,eAA+B,CAAC,MAEhC,MAAMC,wBACJ,GAAG,oBAAoB,UACvB,gBACA,cACA;EACE,QAAQ;EACR;CACF,CACF;;;;;CAMF,MAAM,cAAc,OAAO,eAA+B,CAAC,MACzD,MAAMA,wBACJ,GAAG,oBAAoB,YACvB,gBACA,cACA,EAAE,QAAQ,MAAM,CAClB;;;;;;;;CASF,MAAM,cAAc,OAClB,QAAgC,IAChC,eAA+B,CAAC,MAEhC,MAAMA,wBACJ,GAAG,oBAAoB,YACvB,gBACA,cACA;EACE,QAAQ;EACR,QAAQ,OAAO,UAAU,WAAW,EAAE,MAAM,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM;CACxE,CACF;;;;;CAMF,MAAM,kBAAkB,OAAO,eAA+B,CAAC,MAC7D,MAAMA,wBACJ,GAAG,oBAAoB,iBACvB,gBACA,cACA,EAAE,QAAQ,MAAM,CAClB;;;;;;CAOF,MAAM,uBAAuB,OAC3B,eACA,eAA+B,CAAC,MAEhC,MAAMA,wBACJ,GAAG,oBAAoB,eAAe,iBACtC,gBACA,cACA,EAAE,QAAQ,MAAM,CAClB;;;;;;CAOF,MAAM,kBAAkB,OACtB,KACA,eAA+B,CAAC,MAEhC,MAAMA,wBACJ,GAAG,oBAAoB,iBACvB,gBACA,cACA;EACE,QAAQ;EACR,QAAQ,EAAE,IAAI;CAChB,CACF;CAEF,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;;;;;;AAQA,MAAa,oBAAoBC,6CAAe,eAAe"}
|
|
@@ -24,8 +24,9 @@ const getAuditAPI = (authAPIOptions = {}, intlayerConfig) => {
|
|
|
24
24
|
});
|
|
25
25
|
const scanUrl = async (body, otherOptions = {}) => {
|
|
26
26
|
if (!body?.url) return;
|
|
27
|
-
const { url, onMessage, onDone } = body;
|
|
27
|
+
const { url, refresh, onMessage, onDone } = body;
|
|
28
28
|
const params = new URLSearchParams({ url });
|
|
29
|
+
if (refresh) params.set("refresh", "true");
|
|
29
30
|
const endpoint = `${AUDIT_API_ROUTE}?${params.toString()}`;
|
|
30
31
|
const response = await fetch(endpoint, {
|
|
31
32
|
method: "GET",
|
|
@@ -82,7 +83,29 @@ const getAuditAPI = (authAPIOptions = {}, intlayerConfig) => {
|
|
|
82
83
|
const cancelRecursiveAudit = async (params, otherOptions = {}) => await require_fetcher.fetcher(`${AUDIT_API_ROUTE}/recursive/${params?.jobId}/cancel`, authAPIOptions, otherOptions, { method: "POST" });
|
|
83
84
|
const pauseRecursiveAudit = async (params, otherOptions = {}) => await require_fetcher.fetcher(`${AUDIT_API_ROUTE}/recursive/${params?.jobId}/pause`, authAPIOptions, otherOptions, { method: "POST" });
|
|
84
85
|
const resumeRecursiveAudit = async (params, otherOptions = {}) => await require_fetcher.fetcher(`${AUDIT_API_ROUTE}/recursive/${params?.jobId}/resume`, authAPIOptions, otherOptions, { method: "POST" });
|
|
86
|
+
/**
|
|
87
|
+
* Admin — number of scanned domains using each technology.
|
|
88
|
+
*/
|
|
89
|
+
const getTechnologyUsage = async (params, otherOptions = {}) => await require_fetcher.fetcher(`${AUDIT_API_ROUTE}/technologies`, authAPIOptions, otherOptions, {
|
|
90
|
+
method: "GET",
|
|
91
|
+
params
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* Admin — scanned hosts, most recently scanned first, filterable by
|
|
95
|
+
* technology.
|
|
96
|
+
*/
|
|
97
|
+
const getScannedHosts = async (params, otherOptions = {}) => await require_fetcher.fetcher(`${AUDIT_API_ROUTE}/hosts`, authAPIOptions, otherOptions, {
|
|
98
|
+
method: "GET",
|
|
99
|
+
params
|
|
100
|
+
});
|
|
101
|
+
/**
|
|
102
|
+
* Admin — a scanned host with its stored scans, newest first.
|
|
103
|
+
*/
|
|
104
|
+
const getScannedHost = async (params, otherOptions = {}) => await require_fetcher.fetcher(`${AUDIT_API_ROUTE}/hosts/${encodeURIComponent(params?.host ?? "")}`, authAPIOptions, otherOptions, { method: "GET" });
|
|
85
105
|
return {
|
|
106
|
+
getTechnologyUsage,
|
|
107
|
+
getScannedHosts,
|
|
108
|
+
getScannedHost,
|
|
86
109
|
discoverUrls,
|
|
87
110
|
scanUrl,
|
|
88
111
|
startRecursiveAudit,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.cjs","names":["editor","fetcher","createEndpoint"],"sources":["../../../src/getIntlayerAPI/audit.ts"],"sourcesContent":["import type {\n AuditEvent,\n GetRecursiveAuditStatusResult,\n StartRecursiveAuditResult,\n} from '@intlayer/backend';\nimport { editor } from '@intlayer/config/built';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { createEndpoint } from '../cms/createIntlayerCMS';\nimport { type FetcherOptions, fetcher } from '../fetcher';\n\nexport type {\n AuditEvent,\n GetRecursiveAuditStatusResult,\n StartRecursiveAuditResult,\n};\n\nexport type ScanUrlBody = {\n url: string;\n onMessage?: (event: AuditEvent) => void;\n onDone?: () => void;\n};\n\nexport type DiscoverUrlsParams = {\n url: string;\n};\n\nexport type DiscoverUrlsResult = {\n urls: string[];\n};\n\nexport type StartRecursiveAuditBody = {\n url: string;\n urls?: string[];\n};\n\nexport type GetRecursiveAuditStatusParams = {\n jobId: string;\n};\n\nexport type RecursiveAuditJobParams = {\n jobId: string;\n};\n\nexport const getAuditAPI = (\n authAPIOptions: FetcherOptions = {},\n intlayerConfig?: IntlayerConfig\n) => {\n const backendURL = intlayerConfig?.editor?.backendURL ?? editor.backendURL;\n\n const AUDIT_API_ROUTE = `${backendURL}/api/scan`;\n\n /**\n * Streams a single-page SEO audit as Server-Sent Events.\n *\n * Usage:\n * ```ts\n * await audit.scanUrl({\n * url: 'https://example.com',\n * onMessage: (event) => console.log(event),\n * onDone: () => console.log('done'),\n * });\n * ```\n */\n const discoverUrls = async (\n params?: DiscoverUrlsParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<DiscoverUrlsResult>(\n `${AUDIT_API_ROUTE}/recursive/discover`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n params,\n }\n );\n\n const scanUrl = async (\n body?: ScanUrlBody,\n otherOptions: FetcherOptions = {}\n ) => {\n if (!body?.url) return;\n\n const { url, onMessage, onDone } = body;\n\n const params = new URLSearchParams({ url });\n const endpoint = `${AUDIT_API_ROUTE}?${params.toString()}`;\n\n const response = await fetch(endpoint, {\n method: 'GET',\n headers: {\n Accept: 'text/event-stream',\n ...authAPIOptions.headers,\n ...otherOptions.headers,\n },\n credentials: 'include',\n signal: otherOptions.signal,\n });\n\n if (!response.ok) {\n let errorMessage = 'An error occurred';\n try {\n const errorData = await response.json();\n errorMessage = JSON.stringify(errorData.error) ?? errorMessage;\n } catch {\n try {\n const errorText = await response.text();\n if (errorText) errorMessage = errorText;\n } catch {\n // ignore\n }\n }\n throw new Error(errorMessage);\n }\n\n const reader = response.body?.getReader();\n if (!reader) throw new Error('No reader available');\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const event = JSON.parse(line.slice(6)) as AuditEvent;\n onMessage?.(event);\n } catch {\n // ignore malformed lines\n }\n }\n }\n }\n\n onDone?.();\n };\n\n /**\n * Starts a recursive audit job for the given URL.\n */\n const startRecursiveAudit = async (\n body?: StartRecursiveAuditBody,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<StartRecursiveAuditResult>(\n `${AUDIT_API_ROUTE}/recursive/start`,\n authAPIOptions,\n otherOptions,\n {\n method: 'POST',\n params: body?.url ? { url: body.url } : undefined,\n body: body?.urls !== undefined ? { urls: body.urls as any } : undefined,\n }\n );\n\n /**\n * Gets the status of a recursive audit job.\n */\n const getRecursiveAuditStatus = async (\n params?: GetRecursiveAuditStatusParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetRecursiveAuditStatusResult>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n }\n );\n\n const cancelRecursiveAudit = async (\n params?: RecursiveAuditJobParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<{ success: boolean }>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}/cancel`,\n authAPIOptions,\n otherOptions,\n { method: 'POST' }\n );\n\n const pauseRecursiveAudit = async (\n params?: RecursiveAuditJobParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<{ success: boolean }>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}/pause`,\n authAPIOptions,\n otherOptions,\n { method: 'POST' }\n );\n\n const resumeRecursiveAudit = async (\n params?: RecursiveAuditJobParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<{ success: boolean }>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}/resume`,\n authAPIOptions,\n otherOptions,\n { method: 'POST' }\n );\n\n return {\n discoverUrls,\n scanUrl,\n startRecursiveAudit,\n getRecursiveAuditStatus,\n cancelRecursiveAudit,\n pauseRecursiveAudit,\n resumeRecursiveAudit,\n };\n};\n\n/**\n * Authenticated `audit` endpoint bound to an Intlayer CMS authenticator.\n *\n * Pass an authenticator created with `createIntlayerCMS`, or omit it to use\n * the build-time configuration (`@intlayer/config/built`).\n */\nexport const auditEndpoint = createEndpoint(getAuditAPI);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"audit.cjs","names":["editor","fetcher","createEndpoint"],"sources":["../../../src/getIntlayerAPI/audit.ts"],"sourcesContent":["import type {\n AuditEvent,\n GetRecursiveAuditStatusResult,\n GetScannedHostParams,\n GetScannedHostResult,\n GetScannedHostsQuery,\n GetScannedHostsResult,\n GetTechnologyUsageQuery,\n GetTechnologyUsageResult,\n StartRecursiveAuditResult,\n} from '@intlayer/backend';\nimport { editor } from '@intlayer/config/built';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { createEndpoint } from '../cms/createIntlayerCMS';\nimport { type FetcherOptions, fetcher } from '../fetcher';\n\nexport type {\n AuditEvent,\n GetRecursiveAuditStatusResult,\n GetScannedHostParams,\n GetScannedHostResult,\n GetScannedHostsQuery,\n GetScannedHostsResult,\n GetTechnologyUsageQuery,\n GetTechnologyUsageResult,\n StartRecursiveAuditResult,\n};\n\nexport type ScanUrlBody = {\n url: string;\n /**\n * Force a new audit. Without it, a URL audited less than an hour ago is\n * replayed from cache (its first event carries `cachedAt`).\n */\n refresh?: boolean;\n onMessage?: (event: AuditEvent) => void;\n onDone?: () => void;\n};\n\nexport type DiscoverUrlsParams = {\n url: string;\n};\n\nexport type DiscoverUrlsResult = {\n urls: string[];\n};\n\nexport type StartRecursiveAuditBody = {\n url: string;\n urls?: string[];\n};\n\nexport type GetRecursiveAuditStatusParams = {\n jobId: string;\n};\n\nexport type RecursiveAuditJobParams = {\n jobId: string;\n};\n\nexport const getAuditAPI = (\n authAPIOptions: FetcherOptions = {},\n intlayerConfig?: IntlayerConfig\n) => {\n const backendURL = intlayerConfig?.editor?.backendURL ?? editor.backendURL;\n\n const AUDIT_API_ROUTE = `${backendURL}/api/scan`;\n\n /**\n * Streams a single-page SEO audit as Server-Sent Events.\n *\n * Usage:\n * ```ts\n * await audit.scanUrl({\n * url: 'https://example.com',\n * onMessage: (event) => console.log(event),\n * onDone: () => console.log('done'),\n * });\n * ```\n */\n const discoverUrls = async (\n params?: DiscoverUrlsParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<DiscoverUrlsResult>(\n `${AUDIT_API_ROUTE}/recursive/discover`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n params,\n }\n );\n\n const scanUrl = async (\n body?: ScanUrlBody,\n otherOptions: FetcherOptions = {}\n ) => {\n if (!body?.url) return;\n\n const { url, refresh, onMessage, onDone } = body;\n\n const params = new URLSearchParams({ url });\n if (refresh) params.set('refresh', 'true');\n const endpoint = `${AUDIT_API_ROUTE}?${params.toString()}`;\n\n const response = await fetch(endpoint, {\n method: 'GET',\n headers: {\n Accept: 'text/event-stream',\n ...authAPIOptions.headers,\n ...otherOptions.headers,\n },\n credentials: 'include',\n signal: otherOptions.signal,\n });\n\n if (!response.ok) {\n let errorMessage = 'An error occurred';\n try {\n const errorData = await response.json();\n errorMessage = JSON.stringify(errorData.error) ?? errorMessage;\n } catch {\n try {\n const errorText = await response.text();\n if (errorText) errorMessage = errorText;\n } catch {\n // ignore\n }\n }\n throw new Error(errorMessage);\n }\n\n const reader = response.body?.getReader();\n if (!reader) throw new Error('No reader available');\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const event = JSON.parse(line.slice(6)) as AuditEvent;\n onMessage?.(event);\n } catch {\n // ignore malformed lines\n }\n }\n }\n }\n\n onDone?.();\n };\n\n /**\n * Starts a recursive audit job for the given URL.\n */\n const startRecursiveAudit = async (\n body?: StartRecursiveAuditBody,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<StartRecursiveAuditResult>(\n `${AUDIT_API_ROUTE}/recursive/start`,\n authAPIOptions,\n otherOptions,\n {\n method: 'POST',\n params: body?.url ? { url: body.url } : undefined,\n body: body?.urls !== undefined ? { urls: body.urls as any } : undefined,\n }\n );\n\n /**\n * Gets the status of a recursive audit job.\n */\n const getRecursiveAuditStatus = async (\n params?: GetRecursiveAuditStatusParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetRecursiveAuditStatusResult>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n }\n );\n\n const cancelRecursiveAudit = async (\n params?: RecursiveAuditJobParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<{ success: boolean }>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}/cancel`,\n authAPIOptions,\n otherOptions,\n { method: 'POST' }\n );\n\n const pauseRecursiveAudit = async (\n params?: RecursiveAuditJobParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<{ success: boolean }>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}/pause`,\n authAPIOptions,\n otherOptions,\n { method: 'POST' }\n );\n\n const resumeRecursiveAudit = async (\n params?: RecursiveAuditJobParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<{ success: boolean }>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}/resume`,\n authAPIOptions,\n otherOptions,\n { method: 'POST' }\n );\n\n /**\n * Admin — number of scanned domains using each technology.\n */\n const getTechnologyUsage = async (\n params?: GetTechnologyUsageQuery,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetTechnologyUsageResult>(\n `${AUDIT_API_ROUTE}/technologies`,\n authAPIOptions,\n otherOptions,\n { method: 'GET', params }\n );\n\n /**\n * Admin — scanned hosts, most recently scanned first, filterable by\n * technology.\n */\n const getScannedHosts = async (\n params?: GetScannedHostsQuery,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetScannedHostsResult>(\n `${AUDIT_API_ROUTE}/hosts`,\n authAPIOptions,\n otherOptions,\n { method: 'GET', params }\n );\n\n /**\n * Admin — a scanned host with its stored scans, newest first.\n */\n const getScannedHost = async (\n params?: GetScannedHostParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetScannedHostResult>(\n `${AUDIT_API_ROUTE}/hosts/${encodeURIComponent(params?.host ?? '')}`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n return {\n getTechnologyUsage,\n getScannedHosts,\n getScannedHost,\n discoverUrls,\n scanUrl,\n startRecursiveAudit,\n getRecursiveAuditStatus,\n cancelRecursiveAudit,\n pauseRecursiveAudit,\n resumeRecursiveAudit,\n };\n};\n\n/**\n * Authenticated `audit` endpoint bound to an Intlayer CMS authenticator.\n *\n * Pass an authenticator created with `createIntlayerCMS`, or omit it to use\n * the build-time configuration (`@intlayer/config/built`).\n */\nexport const auditEndpoint = createEndpoint(getAuditAPI);\n"],"mappings":"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"}
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -37,6 +37,7 @@ exports.analyticsEndpoint = require_getIntlayerAPI_analytics.analyticsEndpoint;
|
|
|
37
37
|
exports.assetEndpoint = require_getIntlayerAPI_asset.assetEndpoint;
|
|
38
38
|
exports.auditEndpoint = require_getIntlayerAPI_audit.auditEndpoint;
|
|
39
39
|
exports.bitbucketEndpoint = require_getIntlayerAPI_bitbucket.bitbucketEndpoint;
|
|
40
|
+
exports.clearAccessTokenCache = require_cms_createAuthManager.clearAccessTokenCache;
|
|
40
41
|
exports.createAuthManager = require_cms_createAuthManager.createAuthManager;
|
|
41
42
|
exports.createEndpoint = require_cms_createIntlayerCMS.createEndpoint;
|
|
42
43
|
exports.createIntlayerCMS = require_cms_createIntlayerCMS.createIntlayerCMS;
|
|
@@ -5,6 +5,40 @@ import { defu } from "defu";
|
|
|
5
5
|
|
|
6
6
|
//#region src/cms/createAuthManager.ts
|
|
7
7
|
const ONE_MINUTE_MS = 6e4;
|
|
8
|
+
/**
|
|
9
|
+
* Process-wide token states, keyed by backend URL and credentials. Build
|
|
10
|
+
* steps and watch rebuilds each create their own manager; sharing the state
|
|
11
|
+
* lets them reuse one token instead of awaiting a new exchange every time.
|
|
12
|
+
*/
|
|
13
|
+
const accessTokenStates = /* @__PURE__ */ new Map();
|
|
14
|
+
/** Clears every cached access token, forcing the next call to re-authenticate. */
|
|
15
|
+
const clearAccessTokenCache = () => {
|
|
16
|
+
accessTokenStates.clear();
|
|
17
|
+
};
|
|
18
|
+
/** Returns the shared token state for the given credentials, creating it if missing. */
|
|
19
|
+
const getAccessTokenState = (editorConfig) => {
|
|
20
|
+
const stateKey = [
|
|
21
|
+
editorConfig?.backendURL,
|
|
22
|
+
editorConfig?.clientId,
|
|
23
|
+
editorConfig?.clientSecret
|
|
24
|
+
].join("\0");
|
|
25
|
+
let accessTokenState = accessTokenStates.get(stateKey);
|
|
26
|
+
if (!accessTokenState) {
|
|
27
|
+
accessTokenState = {};
|
|
28
|
+
accessTokenStates.set(stateKey, accessTokenState);
|
|
29
|
+
}
|
|
30
|
+
return accessTokenState;
|
|
31
|
+
};
|
|
32
|
+
/** HTTP statuses meaning the access token was rejected. */
|
|
33
|
+
const AUTHENTICATION_ERROR_STATUSES = /* @__PURE__ */ new Set([401, 403]);
|
|
34
|
+
/**
|
|
35
|
+
* Whether a failed call may succeed with a fresh token: an auth rejection, or
|
|
36
|
+
* an error without HTTP status. A `404` or `500` would fail again regardless.
|
|
37
|
+
*/
|
|
38
|
+
const isAuthenticationError = (error) => {
|
|
39
|
+
const status = error?.status;
|
|
40
|
+
return typeof status !== "number" || AUTHENTICATION_ERROR_STATUSES.has(status);
|
|
41
|
+
};
|
|
8
42
|
/** Whether the current runtime is a browser rather than a server. */
|
|
9
43
|
const isBrowser = () => typeof window !== "undefined";
|
|
10
44
|
/**
|
|
@@ -25,8 +59,8 @@ const getExpiryTimestamp = (token) => {
|
|
|
25
59
|
* Creates an instance-scoped {@link AuthManager} handling the OAuth2
|
|
26
60
|
* `client_credentials` flow (or a pre-issued CLI session token).
|
|
27
61
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
62
|
+
* Managers built with the same backend URL and credentials share one token
|
|
63
|
+
* state, so a process never exchanges credentials more than needed.
|
|
30
64
|
*
|
|
31
65
|
* @param intlayerConfig - Intlayer configuration carrying the `editor`
|
|
32
66
|
* credentials. Defaults to the build-time configuration.
|
|
@@ -47,10 +81,9 @@ const createAuthManager = (intlayerConfig = { editor }, options = {}) => {
|
|
|
47
81
|
...fetcherOptions.headers ?? {},
|
|
48
82
|
Authorization: `Bearer ${sessionToken}`
|
|
49
83
|
};
|
|
50
|
-
|
|
51
|
-
let expiryTimestamp;
|
|
52
|
-
let pendingRefresh;
|
|
84
|
+
const accessTokenState = getAccessTokenState(resolvedConfig?.editor);
|
|
53
85
|
const needsRefresh = () => {
|
|
86
|
+
const { accessToken, expiryTimestamp } = accessTokenState;
|
|
54
87
|
if (!accessToken) return true;
|
|
55
88
|
if (!expiryTimestamp) return false;
|
|
56
89
|
return Date.now() + ONE_MINUTE_MS >= expiryTimestamp;
|
|
@@ -58,19 +91,19 @@ const createAuthManager = (intlayerConfig = { editor }, options = {}) => {
|
|
|
58
91
|
const refreshToken = async () => {
|
|
59
92
|
const doRefresh = async () => {
|
|
60
93
|
const tokenData = (await getOAuthAPI({}, resolvedConfig).getOAuth2AccessToken())?.data;
|
|
61
|
-
accessToken = tokenData?.accessToken;
|
|
62
|
-
expiryTimestamp = getExpiryTimestamp(tokenData);
|
|
94
|
+
accessTokenState.accessToken = tokenData?.accessToken;
|
|
95
|
+
accessTokenState.expiryTimestamp = getExpiryTimestamp(tokenData);
|
|
63
96
|
};
|
|
64
|
-
pendingRefresh ??= doRefresh().finally(() => {
|
|
65
|
-
pendingRefresh = void 0;
|
|
97
|
+
accessTokenState.pendingRefresh ??= doRefresh().finally(() => {
|
|
98
|
+
accessTokenState.pendingRefresh = void 0;
|
|
66
99
|
});
|
|
67
|
-
await pendingRefresh;
|
|
100
|
+
await accessTokenState.pendingRefresh;
|
|
68
101
|
};
|
|
69
102
|
const applyAuthHeader = () => {
|
|
70
|
-
if (!accessToken) return;
|
|
103
|
+
if (!accessTokenState.accessToken) return;
|
|
71
104
|
fetcherOptions.headers = {
|
|
72
105
|
...fetcherOptions.headers ?? {},
|
|
73
|
-
Authorization: `Bearer ${accessToken}`
|
|
106
|
+
Authorization: `Bearer ${accessTokenState.accessToken}`
|
|
74
107
|
};
|
|
75
108
|
};
|
|
76
109
|
const ensureAuth = async () => {
|
|
@@ -79,8 +112,8 @@ const createAuthManager = (intlayerConfig = { editor }, options = {}) => {
|
|
|
79
112
|
applyAuthHeader();
|
|
80
113
|
};
|
|
81
114
|
const reset = () => {
|
|
82
|
-
accessToken = void 0;
|
|
83
|
-
expiryTimestamp = void 0;
|
|
115
|
+
accessTokenState.accessToken = void 0;
|
|
116
|
+
accessTokenState.expiryTimestamp = void 0;
|
|
84
117
|
};
|
|
85
118
|
const withAuth = (method, skipAuth = !hasCredentials) => {
|
|
86
119
|
if (skipAuth && !sessionToken && isBrowser()) return async () => {
|
|
@@ -92,7 +125,7 @@ const createAuthManager = (intlayerConfig = { editor }, options = {}) => {
|
|
|
92
125
|
try {
|
|
93
126
|
return await method(...args);
|
|
94
127
|
} catch (error) {
|
|
95
|
-
if (sessionToken) throw error;
|
|
128
|
+
if (sessionToken || !isAuthenticationError(error)) throw error;
|
|
96
129
|
await refreshToken();
|
|
97
130
|
applyAuthHeader();
|
|
98
131
|
return await method(...args);
|
|
@@ -120,5 +153,5 @@ const createAuthManager = (intlayerConfig = { editor }, options = {}) => {
|
|
|
120
153
|
};
|
|
121
154
|
|
|
122
155
|
//#endregion
|
|
123
|
-
export { createAuthManager };
|
|
156
|
+
export { clearAccessTokenCache, createAuthManager };
|
|
124
157
|
//# sourceMappingURL=createAuthManager.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createAuthManager.mjs","names":[],"sources":["../../../src/cms/createAuthManager.ts"],"sourcesContent":["import { editor } from '@intlayer/config/built';\nimport { getEditorClientSecret } from '@intlayer/config/secrets';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { defu } from 'defu';\nimport type { FetcherOptions } from '../fetcher';\nimport { getOAuthAPI } from '../getIntlayerAPI/oAuth';\n\n/**\n * Shape of the various OAuth2 token payloads the backend may return. Only the\n * fields used to derive the expiration timestamp are described here.\n */\ntype OAuthTokenLike = {\n accessToken?: string;\n accessTokenExpiresAt?: string | Date;\n expires_in?: number;\n expiresIn?: number;\n expiresAt?: string | Date;\n};\n\n/**\n * Any asynchronous API method exposed by a domain section\n * (e.g. `getDictionaries`, `pushDictionaries`).\n */\ntype AsyncMethod<Arguments extends unknown[], Result> = (\n ...args: Arguments\n) => Promise<Result>;\n\n/**\n * A domain section factory: builds a plain object of async methods from shared\n * fetcher options and the Intlayer configuration.\n */\ntype SectionFactory<Section extends Record<string, unknown>> = (\n fetcherOptions: FetcherOptions,\n intlayerConfig: IntlayerConfig\n) => Section;\n\nconst ONE_MINUTE_MS = 60_000;\n\n/** Whether the current runtime is a browser rather than a server. */\nconst isBrowser = (): boolean => typeof window !== 'undefined';\n\n/**\n * Resolves the expiration timestamp (in ms since epoch) from an OAuth2\n * token-like payload, supporting both absolute dates and relative durations.\n */\nconst getExpiryTimestamp = (\n token: OAuthTokenLike | undefined\n): number | undefined => {\n if (!token) return undefined;\n\n const dateLike = (token.accessTokenExpiresAt ?? token.expiresAt) as\n | string\n | Date\n | undefined;\n\n if (dateLike) {\n const timestamp =\n typeof dateLike === 'string'\n ? Date.parse(dateLike)\n : dateLike.getTime?.();\n if (typeof timestamp === 'number' && Number.isFinite(timestamp)) {\n return timestamp;\n }\n }\n\n const seconds = token.expires_in ?? token.expiresIn;\n if (typeof seconds === 'number' && Number.isFinite(seconds)) {\n return Date.now() + seconds * 1000;\n }\n\n return undefined;\n};\n\n/**\n * Options accepted when creating an {@link AuthManager}.\n */\nexport type AuthManagerOptions = {\n /**\n * Base fetcher options merged into every request (e.g. custom headers).\n */\n baseFetcherOptions?: FetcherOptions;\n /**\n * A pre-issued CLI session token (prefixed with `clisession_`). When provided,\n * it is used directly as the Bearer token and the OAuth2 `client_credentials`\n * exchange is skipped entirely.\n */\n sessionToken?: string;\n};\n\n/**\n * Options accepted when binding a domain section to an {@link AuthManager}.\n */\nexport type BindSectionOptions = {\n /**\n * Forces auth injection on (`false`) or off (`true`). Defaults to enabled\n * whenever credentials are available.\n */\n skipAuth?: boolean;\n};\n\n/**\n * Manages CMS authentication for a set of API calls.\n *\n * The manager owns a single mutable {@link FetcherOptions} object\n * (`fetcherOptions`). Domain factories bound through it share the same\n * reference, so refreshing the token in place transparently authenticates every\n * call — without rebuilding the API clients.\n */\nexport type AuthManager = {\n /**\n * Mutable fetcher options carrying the `Authorization` header. Bound sections\n * share this reference so the injected token is reused everywhere.\n */\n fetcherOptions: FetcherOptions;\n /**\n * The resolved Intlayer configuration (merged with the build-time defaults).\n */\n intlayerConfig: IntlayerConfig;\n /**\n * `true` when credentials are available — either a session token or a\n * `clientId`/`clientSecret` pair in the configuration.\n */\n hasCredentials: boolean;\n /**\n * Ensures a valid access token is available and applies it to\n * `fetcherOptions`. Called automatically by {@link withAuth}.\n */\n ensureAuth: () => Promise<void>;\n /**\n * Forces a token refresh on the next call by clearing the cached token.\n */\n reset: () => void;\n /**\n * Wraps a single API method so it injects (and, on failure, refreshes) the\n * access token on every invocation while preserving the method's signature.\n */\n withAuth: <Arguments extends unknown[], Result>(\n method: AsyncMethod<Arguments, Result>,\n skipAuth?: boolean\n ) => AsyncMethod<Arguments, Result>;\n /**\n * Instantiates a domain factory with the managed fetcher options and config,\n * returning a section whose every method authenticates automatically.\n *\n * @example\n * ```ts\n * const dictionary = authManager.bindSection(getDictionaryAPI);\n * await dictionary.pushDictionaries(dictionaries);\n * ```\n */\n bindSection: <Section extends Record<string, unknown>>(\n factory: SectionFactory<Section>,\n options?: BindSectionOptions\n ) => Section;\n};\n\n/**\n * Creates an instance-scoped {@link AuthManager} handling the OAuth2\n * `client_credentials` flow (or a pre-issued CLI session token).\n *\n * Unlike a global token cache, each manager keeps its own token state, so\n * independent SDK instances never interfere with one another.\n *\n * @param intlayerConfig - Intlayer configuration carrying the `editor`\n * credentials. Defaults to the build-time configuration.\n * @param options - Optional session token and base fetcher options.\n */\nexport const createAuthManager = (\n intlayerConfig: Pick<IntlayerConfig, 'editor'> = { editor },\n options: AuthManagerOptions = {}\n): AuthManager => {\n const { baseFetcherOptions = {}, sessionToken } = options;\n\n // `editor` comes from the generated build-time configuration, which never\n // carries `clientSecret` — it would be inlined into client bundles. The\n // secret is supplied server-side by `@intlayer/config/secrets`, which\n // resolves to an inert stub in the browser; `hasCredentials` then stays false\n // there and the OAuth2 exchange never runs.\n //\n // Only consulted when the configuration declares a `clientId`, so a project\n // that comments its credentials out stays uncredentialed even with the\n // environment variables still defined.\n const resolvedConfig = defu(intlayerConfig ?? {}, {\n editor: {\n ...editor,\n clientSecret: editor?.clientId ? getEditorClientSecret() : undefined,\n },\n }) as IntlayerConfig;\n\n // `credentials: 'omit'` prevents the browser from attaching session cookies;\n // authentication is handled exclusively through the injected Bearer token.\n // This is required because the backend only allows credentialed requests for\n // whitelisted first-party origins.\n const fetcherOptions: FetcherOptions = {\n ...baseFetcherOptions,\n credentials: 'omit',\n };\n\n const hasCredentials =\n Boolean(sessionToken) ||\n Boolean(\n resolvedConfig?.editor?.clientId && resolvedConfig?.editor?.clientSecret\n );\n\n // A session token is used as-is and never expires within the SDK's concern,\n // so inject it once up-front and skip the OAuth2 exchange.\n if (sessionToken) {\n fetcherOptions.headers = {\n ...(fetcherOptions.headers ?? {}),\n Authorization: `Bearer ${sessionToken}`,\n } as HeadersInit;\n }\n\n let accessToken: string | undefined;\n let expiryTimestamp: number | undefined;\n let pendingRefresh: Promise<void> | undefined;\n\n const needsRefresh = (): boolean => {\n if (!accessToken) return true;\n if (!expiryTimestamp) return false; // Unknown expiry: assume usable until failure.\n\n // Refresh one minute before the actual expiry to avoid edge-of-expiry races.\n return Date.now() + ONE_MINUTE_MS >= expiryTimestamp;\n };\n\n const refreshToken = async (): Promise<void> => {\n const doRefresh = async (): Promise<void> => {\n const oAuthAPI = getOAuthAPI({}, resolvedConfig);\n const response = await oAuthAPI.getOAuth2AccessToken();\n const tokenData = response?.data as OAuthTokenLike | undefined;\n\n accessToken = tokenData?.accessToken;\n expiryTimestamp = getExpiryTimestamp(tokenData);\n };\n\n // De-duplicate concurrent refreshes so a burst of calls triggers one fetch.\n pendingRefresh ??= doRefresh().finally(() => {\n pendingRefresh = undefined;\n });\n\n await pendingRefresh;\n };\n\n const applyAuthHeader = (): void => {\n if (!accessToken) return;\n fetcherOptions.headers = {\n ...(fetcherOptions.headers ?? {}),\n Authorization: `Bearer ${accessToken}`,\n } as HeadersInit;\n };\n\n const ensureAuth = async (): Promise<void> => {\n if (sessionToken) return; // Already injected; nothing to refresh.\n if (needsRefresh()) {\n await refreshToken();\n }\n applyAuthHeader();\n };\n\n const reset = (): void => {\n accessToken = undefined;\n expiryTimestamp = undefined;\n };\n\n const withAuth = <Arguments extends unknown[], Result>(\n method: AsyncMethod<Arguments, Result>,\n skipAuth = !hasCredentials\n ): AsyncMethod<Arguments, Result> => {\n // In a browser the project credentials are structurally unavailable, so an\n // authenticated call would silently go out unauthenticated and come back\n // 401. Fail with an actionable message instead: a browser must authenticate\n // as the signed-in *user* (a session token, or the session cookie on a\n // first-party origin), never as the project.\n if (skipAuth && !sessionToken && isBrowser()) {\n return async () => {\n throw new Error(\n 'Intlayer CMS credentials are not available in the browser. ' +\n '`clientSecret` is a server-side credential and is never bundled. ' +\n 'Call this from a server route, or pass a user session token: ' +\n 'createIntlayerCMS(config, { sessionToken }).'\n );\n };\n }\n\n if (skipAuth) return method;\n\n return async (...args: Arguments): Promise<Result> => {\n await ensureAuth();\n\n try {\n return await method(...args);\n } catch (error) {\n // The token may have been revoked or expired early: refresh once and\n // retry before surfacing the error. A session token cannot be\n // refreshed, so re-throw immediately in that case.\n if (sessionToken) throw error;\n\n await refreshToken();\n applyAuthHeader();\n return await method(...args);\n }\n };\n };\n\n const bindSection = <Section extends Record<string, unknown>>(\n factory: SectionFactory<Section>,\n bindOptions: BindSectionOptions = {}\n ): Section => {\n const section = factory(fetcherOptions, resolvedConfig);\n const skipAuth = bindOptions.skipAuth ?? !hasCredentials;\n\n return new Proxy(section, {\n get(target, property, receiver) {\n const value = Reflect.get(target, property, receiver);\n\n if (typeof value !== 'function') return value;\n\n return withAuth(\n (value as AsyncMethod<unknown[], unknown>).bind(target),\n skipAuth\n );\n },\n });\n };\n\n return {\n fetcherOptions,\n intlayerConfig: resolvedConfig,\n hasCredentials,\n ensureAuth,\n reset,\n withAuth,\n bindSection,\n };\n};\n"],"mappings":";;;;;;AAoCA,MAAM,gBAAgB;;AAGtB,MAAM,kBAA2B,OAAO,WAAW;;;;;AAMnD,MAAM,sBACJ,UACuB;CACvB,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,WAAY,MAAM,wBAAwB,MAAM;CAKtD,IAAI,UAAU;EACZ,MAAM,YACJ,OAAO,aAAa,WAChB,KAAK,MAAM,QAAQ,IACnB,SAAS,UAAU;EACzB,IAAI,OAAO,cAAc,YAAY,OAAO,SAAS,SAAS,GAC5D,OAAO;CAEX;CAEA,MAAM,UAAU,MAAM,cAAc,MAAM;CAC1C,IAAI,OAAO,YAAY,YAAY,OAAO,SAAS,OAAO,GACxD,OAAO,KAAK,IAAI,IAAI,UAAU;AAIlC;;;;;;;;;;;;AAgGA,MAAa,qBACX,iBAAiD,EAAE,OAAO,GAC1D,UAA8B,CAAC,MACf;CAChB,MAAM,EAAE,qBAAqB,CAAC,GAAG,iBAAiB;CAWlD,MAAM,iBAAiB,KAAK,kBAAkB,CAAC,GAAG,EAChD,QAAQ;EACN,GAAG;EACH,cAAc,QAAQ,WAAW,sBAAsB,IAAI;CAC7D,EACF,CAAC;CAMD,MAAM,iBAAiC;EACrC,GAAG;EACH,aAAa;CACf;CAEA,MAAM,iBACJ,QAAQ,YAAY,KACpB,QACE,gBAAgB,QAAQ,YAAY,gBAAgB,QAAQ,YAC9D;CAIF,IAAI,cACF,eAAe,UAAU;EACvB,GAAI,eAAe,WAAW,CAAC;EAC/B,eAAe,UAAU;CAC3B;CAGF,IAAI;CACJ,IAAI;CACJ,IAAI;CAEJ,MAAM,qBAA8B;EAClC,IAAI,CAAC,aAAa,OAAO;EACzB,IAAI,CAAC,iBAAiB,OAAO;EAG7B,OAAO,KAAK,IAAI,IAAI,iBAAiB;CACvC;CAEA,MAAM,eAAe,YAA2B;EAC9C,MAAM,YAAY,YAA2B;GAG3C,MAAM,aAAY,MAFD,YAAY,CAAC,GAAG,cACH,CAAC,CAAC,qBAAqB,EAC3B,EAAE;GAE5B,cAAc,WAAW;GACzB,kBAAkB,mBAAmB,SAAS;EAChD;EAGA,mBAAmB,UAAU,CAAC,CAAC,cAAc;GAC3C,iBAAiB;EACnB,CAAC;EAED,MAAM;CACR;CAEA,MAAM,wBAA8B;EAClC,IAAI,CAAC,aAAa;EAClB,eAAe,UAAU;GACvB,GAAI,eAAe,WAAW,CAAC;GAC/B,eAAe,UAAU;EAC3B;CACF;CAEA,MAAM,aAAa,YAA2B;EAC5C,IAAI,cAAc;EAClB,IAAI,aAAa,GACf,MAAM,aAAa;EAErB,gBAAgB;CAClB;CAEA,MAAM,cAAoB;EACxB,cAAc;EACd,kBAAkB;CACpB;CAEA,MAAM,YACJ,QACA,WAAW,CAAC,mBACuB;EAMnC,IAAI,YAAY,CAAC,gBAAgB,UAAU,GACzC,OAAO,YAAY;GACjB,MAAM,IAAI,MACR,uOAIF;EACF;EAGF,IAAI,UAAU,OAAO;EAErB,OAAO,OAAO,GAAG,SAAqC;GACpD,MAAM,WAAW;GAEjB,IAAI;IACF,OAAO,MAAM,OAAO,GAAG,IAAI;GAC7B,SAAS,OAAO;IAId,IAAI,cAAc,MAAM;IAExB,MAAM,aAAa;IACnB,gBAAgB;IAChB,OAAO,MAAM,OAAO,GAAG,IAAI;GAC7B;EACF;CACF;CAEA,MAAM,eACJ,SACA,cAAkC,CAAC,MACvB;EACZ,MAAM,UAAU,QAAQ,gBAAgB,cAAc;EACtD,MAAM,WAAW,YAAY,YAAY,CAAC;EAE1C,OAAO,IAAI,MAAM,SAAS,EACxB,IAAI,QAAQ,UAAU,UAAU;GAC9B,MAAM,QAAQ,QAAQ,IAAI,QAAQ,UAAU,QAAQ;GAEpD,IAAI,OAAO,UAAU,YAAY,OAAO;GAExC,OAAO,SACJ,MAA0C,KAAK,MAAM,GACtD,QACF;EACF,EACF,CAAC;CACH;CAEA,OAAO;EACL;EACA,gBAAgB;EAChB;EACA;EACA;EACA;EACA;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"createAuthManager.mjs","names":[],"sources":["../../../src/cms/createAuthManager.ts"],"sourcesContent":["import { editor } from '@intlayer/config/built';\nimport { getEditorClientSecret } from '@intlayer/config/secrets';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { defu } from 'defu';\nimport type { FetcherOptions } from '../fetcher';\nimport { getOAuthAPI } from '../getIntlayerAPI/oAuth';\n\n/**\n * Shape of the various OAuth2 token payloads the backend may return. Only the\n * fields used to derive the expiration timestamp are described here.\n */\ntype OAuthTokenLike = {\n accessToken?: string;\n accessTokenExpiresAt?: string | Date;\n expires_in?: number;\n expiresIn?: number;\n expiresAt?: string | Date;\n};\n\n/**\n * Any asynchronous API method exposed by a domain section\n * (e.g. `getDictionaries`, `pushDictionaries`).\n */\ntype AsyncMethod<Arguments extends unknown[], Result> = (\n ...args: Arguments\n) => Promise<Result>;\n\n/**\n * A domain section factory: builds a plain object of async methods from shared\n * fetcher options and the Intlayer configuration.\n */\ntype SectionFactory<Section extends Record<string, unknown>> = (\n fetcherOptions: FetcherOptions,\n intlayerConfig: IntlayerConfig\n) => Section;\n\nconst ONE_MINUTE_MS = 60_000;\n\n/**\n * OAuth2 token state for one set of credentials, shared by every manager\n * built with them.\n */\ntype AccessTokenState = {\n accessToken?: string;\n expiryTimestamp?: number;\n pendingRefresh?: Promise<void>;\n};\n\n/**\n * Process-wide token states, keyed by backend URL and credentials. Build\n * steps and watch rebuilds each create their own manager; sharing the state\n * lets them reuse one token instead of awaiting a new exchange every time.\n */\nconst accessTokenStates = new Map<string, AccessTokenState>();\n\n/** Clears every cached access token, forcing the next call to re-authenticate. */\nexport const clearAccessTokenCache = (): void => {\n accessTokenStates.clear();\n};\n\n/** Returns the shared token state for the given credentials, creating it if missing. */\nconst getAccessTokenState = (\n editorConfig: IntlayerConfig['editor'] | undefined\n): AccessTokenState => {\n const stateKey = [\n editorConfig?.backendURL,\n editorConfig?.clientId,\n editorConfig?.clientSecret,\n ].join('\\0');\n\n let accessTokenState = accessTokenStates.get(stateKey);\n\n if (!accessTokenState) {\n accessTokenState = {};\n accessTokenStates.set(stateKey, accessTokenState);\n }\n\n return accessTokenState;\n};\n\n/** HTTP statuses meaning the access token was rejected. */\nconst AUTHENTICATION_ERROR_STATUSES = new Set([401, 403]);\n\n/**\n * Whether a failed call may succeed with a fresh token: an auth rejection, or\n * an error without HTTP status. A `404` or `500` would fail again regardless.\n */\nconst isAuthenticationError = (error: unknown): boolean => {\n const status = (error as { status?: unknown } | undefined)?.status;\n\n return (\n typeof status !== 'number' || AUTHENTICATION_ERROR_STATUSES.has(status)\n );\n};\n\n/** Whether the current runtime is a browser rather than a server. */\nconst isBrowser = (): boolean => typeof window !== 'undefined';\n\n/**\n * Resolves the expiration timestamp (in ms since epoch) from an OAuth2\n * token-like payload, supporting both absolute dates and relative durations.\n */\nconst getExpiryTimestamp = (\n token: OAuthTokenLike | undefined\n): number | undefined => {\n if (!token) return undefined;\n\n const dateLike = (token.accessTokenExpiresAt ?? token.expiresAt) as\n | string\n | Date\n | undefined;\n\n if (dateLike) {\n const timestamp =\n typeof dateLike === 'string'\n ? Date.parse(dateLike)\n : dateLike.getTime?.();\n if (typeof timestamp === 'number' && Number.isFinite(timestamp)) {\n return timestamp;\n }\n }\n\n const seconds = token.expires_in ?? token.expiresIn;\n if (typeof seconds === 'number' && Number.isFinite(seconds)) {\n return Date.now() + seconds * 1000;\n }\n\n return undefined;\n};\n\n/**\n * Options accepted when creating an {@link AuthManager}.\n */\nexport type AuthManagerOptions = {\n /**\n * Base fetcher options merged into every request (e.g. custom headers).\n */\n baseFetcherOptions?: FetcherOptions;\n /**\n * A pre-issued CLI session token (prefixed with `clisession_`). When provided,\n * it is used directly as the Bearer token and the OAuth2 `client_credentials`\n * exchange is skipped entirely.\n */\n sessionToken?: string;\n};\n\n/**\n * Options accepted when binding a domain section to an {@link AuthManager}.\n */\nexport type BindSectionOptions = {\n /**\n * Forces auth injection on (`false`) or off (`true`). Defaults to enabled\n * whenever credentials are available.\n */\n skipAuth?: boolean;\n};\n\n/**\n * Manages CMS authentication for a set of API calls.\n *\n * The manager owns a single mutable {@link FetcherOptions} object\n * (`fetcherOptions`). Domain factories bound through it share the same\n * reference, so refreshing the token in place transparently authenticates every\n * call — without rebuilding the API clients.\n */\nexport type AuthManager = {\n /**\n * Mutable fetcher options carrying the `Authorization` header. Bound sections\n * share this reference so the injected token is reused everywhere.\n */\n fetcherOptions: FetcherOptions;\n /**\n * The resolved Intlayer configuration (merged with the build-time defaults).\n */\n intlayerConfig: IntlayerConfig;\n /**\n * `true` when credentials are available — either a session token or a\n * `clientId`/`clientSecret` pair in the configuration.\n */\n hasCredentials: boolean;\n /**\n * Ensures a valid access token is available and applies it to\n * `fetcherOptions`. Called automatically by {@link withAuth}.\n */\n ensureAuth: () => Promise<void>;\n /**\n * Forces a token refresh on the next call by clearing the cached token.\n */\n reset: () => void;\n /**\n * Wraps a single API method so it injects (and, on failure, refreshes) the\n * access token on every invocation while preserving the method's signature.\n */\n withAuth: <Arguments extends unknown[], Result>(\n method: AsyncMethod<Arguments, Result>,\n skipAuth?: boolean\n ) => AsyncMethod<Arguments, Result>;\n /**\n * Instantiates a domain factory with the managed fetcher options and config,\n * returning a section whose every method authenticates automatically.\n *\n * @example\n * ```ts\n * const dictionary = authManager.bindSection(getDictionaryAPI);\n * await dictionary.pushDictionaries(dictionaries);\n * ```\n */\n bindSection: <Section extends Record<string, unknown>>(\n factory: SectionFactory<Section>,\n options?: BindSectionOptions\n ) => Section;\n};\n\n/**\n * Creates an instance-scoped {@link AuthManager} handling the OAuth2\n * `client_credentials` flow (or a pre-issued CLI session token).\n *\n * Managers built with the same backend URL and credentials share one token\n * state, so a process never exchanges credentials more than needed.\n *\n * @param intlayerConfig - Intlayer configuration carrying the `editor`\n * credentials. Defaults to the build-time configuration.\n * @param options - Optional session token and base fetcher options.\n */\nexport const createAuthManager = (\n intlayerConfig: Pick<IntlayerConfig, 'editor'> = { editor },\n options: AuthManagerOptions = {}\n): AuthManager => {\n const { baseFetcherOptions = {}, sessionToken } = options;\n\n // `editor` comes from the generated build-time configuration, which never\n // carries `clientSecret` — it would be inlined into client bundles. The\n // secret is supplied server-side by `@intlayer/config/secrets`, which\n // resolves to an inert stub in the browser; `hasCredentials` then stays false\n // there and the OAuth2 exchange never runs.\n //\n // Only consulted when the configuration declares a `clientId`, so a project\n // that comments its credentials out stays uncredentialed even with the\n // environment variables still defined.\n const resolvedConfig = defu(intlayerConfig ?? {}, {\n editor: {\n ...editor,\n clientSecret: editor?.clientId ? getEditorClientSecret() : undefined,\n },\n }) as IntlayerConfig;\n\n // `credentials: 'omit'` prevents the browser from attaching session cookies;\n // authentication is handled exclusively through the injected Bearer token.\n // This is required because the backend only allows credentialed requests for\n // whitelisted first-party origins.\n const fetcherOptions: FetcherOptions = {\n ...baseFetcherOptions,\n credentials: 'omit',\n };\n\n const hasCredentials =\n Boolean(sessionToken) ||\n Boolean(\n resolvedConfig?.editor?.clientId && resolvedConfig?.editor?.clientSecret\n );\n\n // A session token is used as-is and never expires within the SDK's concern,\n // so inject it once up-front and skip the OAuth2 exchange.\n if (sessionToken) {\n fetcherOptions.headers = {\n ...(fetcherOptions.headers ?? {}),\n Authorization: `Bearer ${sessionToken}`,\n } as HeadersInit;\n }\n\n const accessTokenState = getAccessTokenState(resolvedConfig?.editor);\n\n const needsRefresh = (): boolean => {\n const { accessToken, expiryTimestamp } = accessTokenState;\n\n if (!accessToken) return true;\n if (!expiryTimestamp) return false; // Unknown expiry: assume usable until failure.\n\n // Refresh one minute before the actual expiry to avoid edge-of-expiry races.\n return Date.now() + ONE_MINUTE_MS >= expiryTimestamp;\n };\n\n const refreshToken = async (): Promise<void> => {\n const doRefresh = async (): Promise<void> => {\n const oAuthAPI = getOAuthAPI({}, resolvedConfig);\n const response = await oAuthAPI.getOAuth2AccessToken();\n const tokenData = response?.data as OAuthTokenLike | undefined;\n\n accessTokenState.accessToken = tokenData?.accessToken;\n accessTokenState.expiryTimestamp = getExpiryTimestamp(tokenData);\n };\n\n // De-duplicate concurrent refreshes so a burst of calls triggers one fetch.\n accessTokenState.pendingRefresh ??= doRefresh().finally(() => {\n accessTokenState.pendingRefresh = undefined;\n });\n\n await accessTokenState.pendingRefresh;\n };\n\n const applyAuthHeader = (): void => {\n if (!accessTokenState.accessToken) return;\n fetcherOptions.headers = {\n ...(fetcherOptions.headers ?? {}),\n Authorization: `Bearer ${accessTokenState.accessToken}`,\n } as HeadersInit;\n };\n\n const ensureAuth = async (): Promise<void> => {\n if (sessionToken) return; // Already injected; nothing to refresh.\n if (needsRefresh()) {\n await refreshToken();\n }\n applyAuthHeader();\n };\n\n const reset = (): void => {\n accessTokenState.accessToken = undefined;\n accessTokenState.expiryTimestamp = undefined;\n };\n\n const withAuth = <Arguments extends unknown[], Result>(\n method: AsyncMethod<Arguments, Result>,\n skipAuth = !hasCredentials\n ): AsyncMethod<Arguments, Result> => {\n // In a browser the project credentials are structurally unavailable, so an\n // authenticated call would silently go out unauthenticated and come back\n // 401. Fail with an actionable message instead: a browser must authenticate\n // as the signed-in *user* (a session token, or the session cookie on a\n // first-party origin), never as the project.\n if (skipAuth && !sessionToken && isBrowser()) {\n return async () => {\n throw new Error(\n 'Intlayer CMS credentials are not available in the browser. ' +\n '`clientSecret` is a server-side credential and is never bundled. ' +\n 'Call this from a server route, or pass a user session token: ' +\n 'createIntlayerCMS(config, { sessionToken }).'\n );\n };\n }\n\n if (skipAuth) return method;\n\n return async (...args: Arguments): Promise<Result> => {\n await ensureAuth();\n\n try {\n return await method(...args);\n } catch (error) {\n // The token may have been revoked or expired early: refresh once and\n // retry before surfacing the error. A session token cannot be\n // refreshed, so re-throw immediately in that case.\n if (sessionToken || !isAuthenticationError(error)) throw error;\n\n await refreshToken();\n applyAuthHeader();\n return await method(...args);\n }\n };\n };\n\n const bindSection = <Section extends Record<string, unknown>>(\n factory: SectionFactory<Section>,\n bindOptions: BindSectionOptions = {}\n ): Section => {\n const section = factory(fetcherOptions, resolvedConfig);\n const skipAuth = bindOptions.skipAuth ?? !hasCredentials;\n\n return new Proxy(section, {\n get(target, property, receiver) {\n const value = Reflect.get(target, property, receiver);\n\n if (typeof value !== 'function') return value;\n\n return withAuth(\n (value as AsyncMethod<unknown[], unknown>).bind(target),\n skipAuth\n );\n },\n });\n };\n\n return {\n fetcherOptions,\n intlayerConfig: resolvedConfig,\n hasCredentials,\n ensureAuth,\n reset,\n withAuth,\n bindSection,\n };\n};\n"],"mappings":"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"}
|
package/dist/esm/cms/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createAuthManager } from "./createAuthManager.mjs";
|
|
1
|
+
import { clearAccessTokenCache, createAuthManager } from "./createAuthManager.mjs";
|
|
2
2
|
import { createEndpoint, createIntlayerCMS } from "./createIntlayerCMS.mjs";
|
|
3
3
|
|
|
4
|
-
export { createAuthManager, createEndpoint, createIntlayerCMS };
|
|
4
|
+
export { clearAccessTokenCache, createAuthManager, createEndpoint, createIntlayerCMS };
|
|
@@ -42,12 +42,22 @@ const getAnalyticsAPI = (authAPIOptions = {}, intlayerConfig) => {
|
|
|
42
42
|
* @returns The experiment result set.
|
|
43
43
|
*/
|
|
44
44
|
const getExperimentResults = async (experimentKey, otherOptions = {}) => await fetcher(`${ANALYTICS_API_ROUTE}/experiments/${experimentKey}`, authAPIOptions, otherOptions, { method: "GET" });
|
|
45
|
+
/**
|
|
46
|
+
* Page metadata (title and meta description) for a given url.
|
|
47
|
+
* @param url - The page url to inspect.
|
|
48
|
+
* @returns Title and description of the page.
|
|
49
|
+
*/
|
|
50
|
+
const getPageMetadata = async (url, otherOptions = {}) => await fetcher(`${ANALYTICS_API_ROUTE}/page-metadata`, authAPIOptions, otherOptions, {
|
|
51
|
+
method: "GET",
|
|
52
|
+
params: { url }
|
|
53
|
+
});
|
|
45
54
|
return {
|
|
46
55
|
sendEvents,
|
|
47
56
|
getOverview,
|
|
48
57
|
getAudience,
|
|
49
58
|
getContentStats,
|
|
50
|
-
getExperimentResults
|
|
59
|
+
getExperimentResults,
|
|
60
|
+
getPageMetadata
|
|
51
61
|
};
|
|
52
62
|
};
|
|
53
63
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.mjs","names":[],"sources":["../../../src/getIntlayerAPI/analytics.ts"],"sourcesContent":["import type {\n AudienceRange,\n GetAnalyticsOverviewResult,\n GetAudienceResult,\n GetContentStatsResult,\n GetExperimentResultsResult,\n IngestAnalyticsBody,\n IngestAnalyticsResult,\n} from '@intlayer/backend';\nimport { editor } from '@intlayer/config/built';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { createEndpoint } from '../cms/createIntlayerCMS';\nimport { type FetcherOptions, fetcher } from '../fetcher';\n\nexport const getAnalyticsAPI = (\n authAPIOptions: FetcherOptions = {},\n intlayerConfig?: IntlayerConfig\n) => {\n const backendURL = intlayerConfig?.editor?.backendURL ?? editor.backendURL;\n\n const ANALYTICS_API_ROUTE = `${backendURL}/api/analytics`;\n\n /**\n * Ingest a batch of analytics events. Public — attribution is by the\n * project's `clientId` in the body.\n * @param body - Session id, sdk version, and the collected events.\n * @returns The number of accepted events.\n */\n const sendEvents = async (\n body: IngestAnalyticsBody,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<IngestAnalyticsResult>(\n `${ANALYTICS_API_ROUTE}/events`,\n authAPIOptions,\n otherOptions,\n {\n method: 'POST',\n body,\n }\n );\n\n /**\n * Page/locale totals for the authenticated project.\n * @returns Views grouped by url and locale.\n */\n const getOverview = async (otherOptions: FetcherOptions = {}) =>\n await fetcher<GetAnalyticsOverviewResult>(\n `${ANALYTICS_API_ROUTE}/overview`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n /**\n * Audience report: distinct visitors (today / 7d / window), page views, the\n * evolution series, and locale + country breakdowns.\n * @param range - Rolling window, either a named range (`1h`, `24h`, `7d`,\n * `30d`, `90d`, `6mo`, `1y`, `3y`) or a number of days (default 30).\n * @returns The audience statistics.\n */\n const getAudience = async (\n range: AudienceRange | number = 30,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetAudienceResult>(\n `${ANALYTICS_API_ROUTE}/audience`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n params: typeof range === 'number' ? { days: String(range) } : { range },\n }\n );\n\n /**\n * Per-content exposure totals — \"which content is actually shown\".\n * @returns Exposures grouped by dictionary key, key path, and locale.\n */\n const getContentStats = async (otherOptions: FetcherOptions = {}) =>\n await fetcher<GetContentStatsResult>(\n `${ANALYTICS_API_ROUTE}/content-stats`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n /**\n * A/B experiment results with per-variant conversion rates and significance.\n * @param experimentKey - The experiment to evaluate.\n * @returns The experiment result set.\n */\n const getExperimentResults = async (\n experimentKey: string,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetExperimentResultsResult>(\n `${ANALYTICS_API_ROUTE}/experiments/${experimentKey}`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n return {\n sendEvents,\n getOverview,\n getAudience,\n getContentStats,\n getExperimentResults,\n };\n};\n\n/**\n * Authenticated `analytics` endpoint bound to an Intlayer CMS authenticator.\n *\n * Pass an authenticator created with `createIntlayerCMS`, or omit it to use\n * the build-time configuration (`@intlayer/config/built`).\n */\nexport const analyticsEndpoint = createEndpoint(getAnalyticsAPI);\n"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"analytics.mjs","names":[],"sources":["../../../src/getIntlayerAPI/analytics.ts"],"sourcesContent":["import type {\n AudienceRange,\n GetAnalyticsOverviewResult,\n GetAudienceResult,\n GetContentStatsResult,\n GetExperimentResultsResult,\n GetPageMetadataResult,\n IngestAnalyticsBody,\n IngestAnalyticsResult,\n} from '@intlayer/backend';\nimport { editor } from '@intlayer/config/built';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { createEndpoint } from '../cms/createIntlayerCMS';\nimport { type FetcherOptions, fetcher } from '../fetcher';\n\nexport const getAnalyticsAPI = (\n authAPIOptions: FetcherOptions = {},\n intlayerConfig?: IntlayerConfig\n) => {\n const backendURL = intlayerConfig?.editor?.backendURL ?? editor.backendURL;\n\n const ANALYTICS_API_ROUTE = `${backendURL}/api/analytics`;\n\n /**\n * Ingest a batch of analytics events. Public — attribution is by the\n * project's `clientId` in the body.\n * @param body - Session id, sdk version, and the collected events.\n * @returns The number of accepted events.\n */\n const sendEvents = async (\n body: IngestAnalyticsBody,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<IngestAnalyticsResult>(\n `${ANALYTICS_API_ROUTE}/events`,\n authAPIOptions,\n otherOptions,\n {\n method: 'POST',\n body,\n }\n );\n\n /**\n * Page/locale totals for the authenticated project.\n * @returns Views grouped by url and locale.\n */\n const getOverview = async (otherOptions: FetcherOptions = {}) =>\n await fetcher<GetAnalyticsOverviewResult>(\n `${ANALYTICS_API_ROUTE}/overview`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n /**\n * Audience report: distinct visitors (today / 7d / window), page views, the\n * evolution series, and locale + country breakdowns.\n * @param range - Rolling window, either a named range (`1h`, `24h`, `7d`,\n * `30d`, `90d`, `6mo`, `1y`, `3y`) or a number of days (default 30).\n * @returns The audience statistics.\n */\n const getAudience = async (\n range: AudienceRange | number = 30,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetAudienceResult>(\n `${ANALYTICS_API_ROUTE}/audience`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n params: typeof range === 'number' ? { days: String(range) } : { range },\n }\n );\n\n /**\n * Per-content exposure totals — \"which content is actually shown\".\n * @returns Exposures grouped by dictionary key, key path, and locale.\n */\n const getContentStats = async (otherOptions: FetcherOptions = {}) =>\n await fetcher<GetContentStatsResult>(\n `${ANALYTICS_API_ROUTE}/content-stats`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n /**\n * A/B experiment results with per-variant conversion rates and significance.\n * @param experimentKey - The experiment to evaluate.\n * @returns The experiment result set.\n */\n const getExperimentResults = async (\n experimentKey: string,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetExperimentResultsResult>(\n `${ANALYTICS_API_ROUTE}/experiments/${experimentKey}`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n /**\n * Page metadata (title and meta description) for a given url.\n * @param url - The page url to inspect.\n * @returns Title and description of the page.\n */\n const getPageMetadata = async (\n url: string,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetPageMetadataResult>(\n `${ANALYTICS_API_ROUTE}/page-metadata`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n params: { url },\n }\n );\n\n return {\n sendEvents,\n getOverview,\n getAudience,\n getContentStats,\n getExperimentResults,\n getPageMetadata,\n };\n};\n\n/**\n * Authenticated `analytics` endpoint bound to an Intlayer CMS authenticator.\n *\n * Pass an authenticator created with `createIntlayerCMS`, or omit it to use\n * the build-time configuration (`@intlayer/config/built`).\n */\nexport const analyticsEndpoint = createEndpoint(getAnalyticsAPI);\n"],"mappings":";;;;;AAeA,MAAa,mBACX,iBAAiC,CAAC,GAClC,mBACG;CAGH,MAAM,sBAAsB,GAFT,gBAAgB,QAAQ,cAAc,OAAO,WAEtB;;;;;;;CAQ1C,MAAM,aAAa,OACjB,MACA,eAA+B,CAAC,MAEhC,MAAM,QACJ,GAAG,oBAAoB,UACvB,gBACA,cACA;EACE,QAAQ;EACR;CACF,CACF;;;;;CAMF,MAAM,cAAc,OAAO,eAA+B,CAAC,MACzD,MAAM,QACJ,GAAG,oBAAoB,YACvB,gBACA,cACA,EAAE,QAAQ,MAAM,CAClB;;;;;;;;CASF,MAAM,cAAc,OAClB,QAAgC,IAChC,eAA+B,CAAC,MAEhC,MAAM,QACJ,GAAG,oBAAoB,YACvB,gBACA,cACA;EACE,QAAQ;EACR,QAAQ,OAAO,UAAU,WAAW,EAAE,MAAM,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM;CACxE,CACF;;;;;CAMF,MAAM,kBAAkB,OAAO,eAA+B,CAAC,MAC7D,MAAM,QACJ,GAAG,oBAAoB,iBACvB,gBACA,cACA,EAAE,QAAQ,MAAM,CAClB;;;;;;CAOF,MAAM,uBAAuB,OAC3B,eACA,eAA+B,CAAC,MAEhC,MAAM,QACJ,GAAG,oBAAoB,eAAe,iBACtC,gBACA,cACA,EAAE,QAAQ,MAAM,CAClB;;;;;;CAOF,MAAM,kBAAkB,OACtB,KACA,eAA+B,CAAC,MAEhC,MAAM,QACJ,GAAG,oBAAoB,iBACvB,gBACA,cACA;EACE,QAAQ;EACR,QAAQ,EAAE,IAAI;CAChB,CACF;CAEF,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;;;;;;AAQA,MAAa,oBAAoB,eAAe,eAAe"}
|
|
@@ -23,8 +23,9 @@ const getAuditAPI = (authAPIOptions = {}, intlayerConfig) => {
|
|
|
23
23
|
});
|
|
24
24
|
const scanUrl = async (body, otherOptions = {}) => {
|
|
25
25
|
if (!body?.url) return;
|
|
26
|
-
const { url, onMessage, onDone } = body;
|
|
26
|
+
const { url, refresh, onMessage, onDone } = body;
|
|
27
27
|
const params = new URLSearchParams({ url });
|
|
28
|
+
if (refresh) params.set("refresh", "true");
|
|
28
29
|
const endpoint = `${AUDIT_API_ROUTE}?${params.toString()}`;
|
|
29
30
|
const response = await fetch(endpoint, {
|
|
30
31
|
method: "GET",
|
|
@@ -81,7 +82,29 @@ const getAuditAPI = (authAPIOptions = {}, intlayerConfig) => {
|
|
|
81
82
|
const cancelRecursiveAudit = async (params, otherOptions = {}) => await fetcher(`${AUDIT_API_ROUTE}/recursive/${params?.jobId}/cancel`, authAPIOptions, otherOptions, { method: "POST" });
|
|
82
83
|
const pauseRecursiveAudit = async (params, otherOptions = {}) => await fetcher(`${AUDIT_API_ROUTE}/recursive/${params?.jobId}/pause`, authAPIOptions, otherOptions, { method: "POST" });
|
|
83
84
|
const resumeRecursiveAudit = async (params, otherOptions = {}) => await fetcher(`${AUDIT_API_ROUTE}/recursive/${params?.jobId}/resume`, authAPIOptions, otherOptions, { method: "POST" });
|
|
85
|
+
/**
|
|
86
|
+
* Admin — number of scanned domains using each technology.
|
|
87
|
+
*/
|
|
88
|
+
const getTechnologyUsage = async (params, otherOptions = {}) => await fetcher(`${AUDIT_API_ROUTE}/technologies`, authAPIOptions, otherOptions, {
|
|
89
|
+
method: "GET",
|
|
90
|
+
params
|
|
91
|
+
});
|
|
92
|
+
/**
|
|
93
|
+
* Admin — scanned hosts, most recently scanned first, filterable by
|
|
94
|
+
* technology.
|
|
95
|
+
*/
|
|
96
|
+
const getScannedHosts = async (params, otherOptions = {}) => await fetcher(`${AUDIT_API_ROUTE}/hosts`, authAPIOptions, otherOptions, {
|
|
97
|
+
method: "GET",
|
|
98
|
+
params
|
|
99
|
+
});
|
|
100
|
+
/**
|
|
101
|
+
* Admin — a scanned host with its stored scans, newest first.
|
|
102
|
+
*/
|
|
103
|
+
const getScannedHost = async (params, otherOptions = {}) => await fetcher(`${AUDIT_API_ROUTE}/hosts/${encodeURIComponent(params?.host ?? "")}`, authAPIOptions, otherOptions, { method: "GET" });
|
|
84
104
|
return {
|
|
105
|
+
getTechnologyUsage,
|
|
106
|
+
getScannedHosts,
|
|
107
|
+
getScannedHost,
|
|
85
108
|
discoverUrls,
|
|
86
109
|
scanUrl,
|
|
87
110
|
startRecursiveAudit,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.mjs","names":[],"sources":["../../../src/getIntlayerAPI/audit.ts"],"sourcesContent":["import type {\n AuditEvent,\n GetRecursiveAuditStatusResult,\n StartRecursiveAuditResult,\n} from '@intlayer/backend';\nimport { editor } from '@intlayer/config/built';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { createEndpoint } from '../cms/createIntlayerCMS';\nimport { type FetcherOptions, fetcher } from '../fetcher';\n\nexport type {\n AuditEvent,\n GetRecursiveAuditStatusResult,\n StartRecursiveAuditResult,\n};\n\nexport type ScanUrlBody = {\n url: string;\n onMessage?: (event: AuditEvent) => void;\n onDone?: () => void;\n};\n\nexport type DiscoverUrlsParams = {\n url: string;\n};\n\nexport type DiscoverUrlsResult = {\n urls: string[];\n};\n\nexport type StartRecursiveAuditBody = {\n url: string;\n urls?: string[];\n};\n\nexport type GetRecursiveAuditStatusParams = {\n jobId: string;\n};\n\nexport type RecursiveAuditJobParams = {\n jobId: string;\n};\n\nexport const getAuditAPI = (\n authAPIOptions: FetcherOptions = {},\n intlayerConfig?: IntlayerConfig\n) => {\n const backendURL = intlayerConfig?.editor?.backendURL ?? editor.backendURL;\n\n const AUDIT_API_ROUTE = `${backendURL}/api/scan`;\n\n /**\n * Streams a single-page SEO audit as Server-Sent Events.\n *\n * Usage:\n * ```ts\n * await audit.scanUrl({\n * url: 'https://example.com',\n * onMessage: (event) => console.log(event),\n * onDone: () => console.log('done'),\n * });\n * ```\n */\n const discoverUrls = async (\n params?: DiscoverUrlsParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<DiscoverUrlsResult>(\n `${AUDIT_API_ROUTE}/recursive/discover`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n params,\n }\n );\n\n const scanUrl = async (\n body?: ScanUrlBody,\n otherOptions: FetcherOptions = {}\n ) => {\n if (!body?.url) return;\n\n const { url, onMessage, onDone } = body;\n\n const params = new URLSearchParams({ url });\n const endpoint = `${AUDIT_API_ROUTE}?${params.toString()}`;\n\n const response = await fetch(endpoint, {\n method: 'GET',\n headers: {\n Accept: 'text/event-stream',\n ...authAPIOptions.headers,\n ...otherOptions.headers,\n },\n credentials: 'include',\n signal: otherOptions.signal,\n });\n\n if (!response.ok) {\n let errorMessage = 'An error occurred';\n try {\n const errorData = await response.json();\n errorMessage = JSON.stringify(errorData.error) ?? errorMessage;\n } catch {\n try {\n const errorText = await response.text();\n if (errorText) errorMessage = errorText;\n } catch {\n // ignore\n }\n }\n throw new Error(errorMessage);\n }\n\n const reader = response.body?.getReader();\n if (!reader) throw new Error('No reader available');\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const event = JSON.parse(line.slice(6)) as AuditEvent;\n onMessage?.(event);\n } catch {\n // ignore malformed lines\n }\n }\n }\n }\n\n onDone?.();\n };\n\n /**\n * Starts a recursive audit job for the given URL.\n */\n const startRecursiveAudit = async (\n body?: StartRecursiveAuditBody,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<StartRecursiveAuditResult>(\n `${AUDIT_API_ROUTE}/recursive/start`,\n authAPIOptions,\n otherOptions,\n {\n method: 'POST',\n params: body?.url ? { url: body.url } : undefined,\n body: body?.urls !== undefined ? { urls: body.urls as any } : undefined,\n }\n );\n\n /**\n * Gets the status of a recursive audit job.\n */\n const getRecursiveAuditStatus = async (\n params?: GetRecursiveAuditStatusParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetRecursiveAuditStatusResult>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n }\n );\n\n const cancelRecursiveAudit = async (\n params?: RecursiveAuditJobParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<{ success: boolean }>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}/cancel`,\n authAPIOptions,\n otherOptions,\n { method: 'POST' }\n );\n\n const pauseRecursiveAudit = async (\n params?: RecursiveAuditJobParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<{ success: boolean }>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}/pause`,\n authAPIOptions,\n otherOptions,\n { method: 'POST' }\n );\n\n const resumeRecursiveAudit = async (\n params?: RecursiveAuditJobParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<{ success: boolean }>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}/resume`,\n authAPIOptions,\n otherOptions,\n { method: 'POST' }\n );\n\n return {\n discoverUrls,\n scanUrl,\n startRecursiveAudit,\n getRecursiveAuditStatus,\n cancelRecursiveAudit,\n pauseRecursiveAudit,\n resumeRecursiveAudit,\n };\n};\n\n/**\n * Authenticated `audit` endpoint bound to an Intlayer CMS authenticator.\n *\n * Pass an authenticator created with `createIntlayerCMS`, or omit it to use\n * the build-time configuration (`@intlayer/config/built`).\n */\nexport const auditEndpoint = createEndpoint(getAuditAPI);\n"],"mappings":";;;;;AA2CA,MAAa,eACX,iBAAiC,CAAC,GAClC,mBACG;CAGH,MAAM,kBAAkB,GAFL,gBAAgB,QAAQ,cAAc,OAAO,WAE1B;;;;;;;;;;;;;CActC,MAAM,eAAe,OACnB,QACA,eAA+B,CAAC,MAEhC,MAAM,QACJ,GAAG,gBAAgB,sBACnB,gBACA,cACA;EACE,QAAQ;EACR;CACF,CACF;CAEF,MAAM,UAAU,OACd,MACA,eAA+B,CAAC,MAC7B;EACH,IAAI,CAAC,MAAM,KAAK;EAEhB,MAAM,EAAE,KAAK,WAAW,WAAW;EAEnC,MAAM,SAAS,IAAI,gBAAgB,EAAE,IAAI,CAAC;EAC1C,MAAM,WAAW,GAAG,gBAAgB,GAAG,OAAO,SAAS;EAEvD,MAAM,WAAW,MAAM,MAAM,UAAU;GACrC,QAAQ;GACR,SAAS;IACP,QAAQ;IACR,GAAG,eAAe;IAClB,GAAG,aAAa;GAClB;GACA,aAAa;GACb,QAAQ,aAAa;EACvB,CAAC;EAED,IAAI,CAAC,SAAS,IAAI;GAChB,IAAI,eAAe;GACnB,IAAI;IACF,MAAM,YAAY,MAAM,SAAS,KAAK;IACtC,eAAe,KAAK,UAAU,UAAU,KAAK,KAAK;GACpD,QAAQ;IACN,IAAI;KACF,MAAM,YAAY,MAAM,SAAS,KAAK;KACtC,IAAI,WAAW,eAAe;IAChC,QAAQ,CAER;GACF;GACA,MAAM,IAAI,MAAM,YAAY;EAC9B;EAEA,MAAM,SAAS,SAAS,MAAM,UAAU;EACxC,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,qBAAqB;EAElD,MAAM,UAAU,IAAI,YAAY;EAChC,IAAI,SAAS;EAEb,OAAO,MAAM;GACX,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAE1C,IAAI,MAAM;GAEV,UAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;GAChD,MAAM,QAAQ,OAAO,MAAM,IAAI;GAC/B,SAAS,MAAM,IAAI,KAAK;GAExB,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,WAAW,QAAQ,GAC1B,IAAI;IACF,MAAM,QAAQ,KAAK,MAAM,KAAK,MAAM,CAAC,CAAC;IACtC,YAAY,KAAK;GACnB,QAAQ,CAER;EAGN;EAEA,SAAS;CACX;;;;CAKA,MAAM,sBAAsB,OAC1B,MACA,eAA+B,CAAC,MAEhC,MAAM,QACJ,GAAG,gBAAgB,mBACnB,gBACA,cACA;EACE,QAAQ;EACR,QAAQ,MAAM,MAAM,EAAE,KAAK,KAAK,IAAI,IAAI;EACxC,MAAM,MAAM,SAAS,SAAY,EAAE,MAAM,KAAK,KAAY,IAAI;CAChE,CACF;;;;CAKF,MAAM,0BAA0B,OAC9B,QACA,eAA+B,CAAC,MAEhC,MAAM,QACJ,GAAG,gBAAgB,aAAa,QAAQ,SACxC,gBACA,cACA,EACE,QAAQ,MACV,CACF;CAEF,MAAM,uBAAuB,OAC3B,QACA,eAA+B,CAAC,MAEhC,MAAM,QACJ,GAAG,gBAAgB,aAAa,QAAQ,MAAM,UAC9C,gBACA,cACA,EAAE,QAAQ,OAAO,CACnB;CAEF,MAAM,sBAAsB,OAC1B,QACA,eAA+B,CAAC,MAEhC,MAAM,QACJ,GAAG,gBAAgB,aAAa,QAAQ,MAAM,SAC9C,gBACA,cACA,EAAE,QAAQ,OAAO,CACnB;CAEF,MAAM,uBAAuB,OAC3B,QACA,eAA+B,CAAC,MAEhC,MAAM,QACJ,GAAG,gBAAgB,aAAa,QAAQ,MAAM,UAC9C,gBACA,cACA,EAAE,QAAQ,OAAO,CACnB;CAEF,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;;;;;;AAQA,MAAa,gBAAgB,eAAe,WAAW"}
|
|
1
|
+
{"version":3,"file":"audit.mjs","names":[],"sources":["../../../src/getIntlayerAPI/audit.ts"],"sourcesContent":["import type {\n AuditEvent,\n GetRecursiveAuditStatusResult,\n GetScannedHostParams,\n GetScannedHostResult,\n GetScannedHostsQuery,\n GetScannedHostsResult,\n GetTechnologyUsageQuery,\n GetTechnologyUsageResult,\n StartRecursiveAuditResult,\n} from '@intlayer/backend';\nimport { editor } from '@intlayer/config/built';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { createEndpoint } from '../cms/createIntlayerCMS';\nimport { type FetcherOptions, fetcher } from '../fetcher';\n\nexport type {\n AuditEvent,\n GetRecursiveAuditStatusResult,\n GetScannedHostParams,\n GetScannedHostResult,\n GetScannedHostsQuery,\n GetScannedHostsResult,\n GetTechnologyUsageQuery,\n GetTechnologyUsageResult,\n StartRecursiveAuditResult,\n};\n\nexport type ScanUrlBody = {\n url: string;\n /**\n * Force a new audit. Without it, a URL audited less than an hour ago is\n * replayed from cache (its first event carries `cachedAt`).\n */\n refresh?: boolean;\n onMessage?: (event: AuditEvent) => void;\n onDone?: () => void;\n};\n\nexport type DiscoverUrlsParams = {\n url: string;\n};\n\nexport type DiscoverUrlsResult = {\n urls: string[];\n};\n\nexport type StartRecursiveAuditBody = {\n url: string;\n urls?: string[];\n};\n\nexport type GetRecursiveAuditStatusParams = {\n jobId: string;\n};\n\nexport type RecursiveAuditJobParams = {\n jobId: string;\n};\n\nexport const getAuditAPI = (\n authAPIOptions: FetcherOptions = {},\n intlayerConfig?: IntlayerConfig\n) => {\n const backendURL = intlayerConfig?.editor?.backendURL ?? editor.backendURL;\n\n const AUDIT_API_ROUTE = `${backendURL}/api/scan`;\n\n /**\n * Streams a single-page SEO audit as Server-Sent Events.\n *\n * Usage:\n * ```ts\n * await audit.scanUrl({\n * url: 'https://example.com',\n * onMessage: (event) => console.log(event),\n * onDone: () => console.log('done'),\n * });\n * ```\n */\n const discoverUrls = async (\n params?: DiscoverUrlsParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<DiscoverUrlsResult>(\n `${AUDIT_API_ROUTE}/recursive/discover`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n params,\n }\n );\n\n const scanUrl = async (\n body?: ScanUrlBody,\n otherOptions: FetcherOptions = {}\n ) => {\n if (!body?.url) return;\n\n const { url, refresh, onMessage, onDone } = body;\n\n const params = new URLSearchParams({ url });\n if (refresh) params.set('refresh', 'true');\n const endpoint = `${AUDIT_API_ROUTE}?${params.toString()}`;\n\n const response = await fetch(endpoint, {\n method: 'GET',\n headers: {\n Accept: 'text/event-stream',\n ...authAPIOptions.headers,\n ...otherOptions.headers,\n },\n credentials: 'include',\n signal: otherOptions.signal,\n });\n\n if (!response.ok) {\n let errorMessage = 'An error occurred';\n try {\n const errorData = await response.json();\n errorMessage = JSON.stringify(errorData.error) ?? errorMessage;\n } catch {\n try {\n const errorText = await response.text();\n if (errorText) errorMessage = errorText;\n } catch {\n // ignore\n }\n }\n throw new Error(errorMessage);\n }\n\n const reader = response.body?.getReader();\n if (!reader) throw new Error('No reader available');\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const event = JSON.parse(line.slice(6)) as AuditEvent;\n onMessage?.(event);\n } catch {\n // ignore malformed lines\n }\n }\n }\n }\n\n onDone?.();\n };\n\n /**\n * Starts a recursive audit job for the given URL.\n */\n const startRecursiveAudit = async (\n body?: StartRecursiveAuditBody,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<StartRecursiveAuditResult>(\n `${AUDIT_API_ROUTE}/recursive/start`,\n authAPIOptions,\n otherOptions,\n {\n method: 'POST',\n params: body?.url ? { url: body.url } : undefined,\n body: body?.urls !== undefined ? { urls: body.urls as any } : undefined,\n }\n );\n\n /**\n * Gets the status of a recursive audit job.\n */\n const getRecursiveAuditStatus = async (\n params?: GetRecursiveAuditStatusParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetRecursiveAuditStatusResult>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}`,\n authAPIOptions,\n otherOptions,\n {\n method: 'GET',\n }\n );\n\n const cancelRecursiveAudit = async (\n params?: RecursiveAuditJobParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<{ success: boolean }>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}/cancel`,\n authAPIOptions,\n otherOptions,\n { method: 'POST' }\n );\n\n const pauseRecursiveAudit = async (\n params?: RecursiveAuditJobParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<{ success: boolean }>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}/pause`,\n authAPIOptions,\n otherOptions,\n { method: 'POST' }\n );\n\n const resumeRecursiveAudit = async (\n params?: RecursiveAuditJobParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<{ success: boolean }>(\n `${AUDIT_API_ROUTE}/recursive/${params?.jobId}/resume`,\n authAPIOptions,\n otherOptions,\n { method: 'POST' }\n );\n\n /**\n * Admin — number of scanned domains using each technology.\n */\n const getTechnologyUsage = async (\n params?: GetTechnologyUsageQuery,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetTechnologyUsageResult>(\n `${AUDIT_API_ROUTE}/technologies`,\n authAPIOptions,\n otherOptions,\n { method: 'GET', params }\n );\n\n /**\n * Admin — scanned hosts, most recently scanned first, filterable by\n * technology.\n */\n const getScannedHosts = async (\n params?: GetScannedHostsQuery,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetScannedHostsResult>(\n `${AUDIT_API_ROUTE}/hosts`,\n authAPIOptions,\n otherOptions,\n { method: 'GET', params }\n );\n\n /**\n * Admin — a scanned host with its stored scans, newest first.\n */\n const getScannedHost = async (\n params?: GetScannedHostParams,\n otherOptions: FetcherOptions = {}\n ) =>\n await fetcher<GetScannedHostResult>(\n `${AUDIT_API_ROUTE}/hosts/${encodeURIComponent(params?.host ?? '')}`,\n authAPIOptions,\n otherOptions,\n { method: 'GET' }\n );\n\n return {\n getTechnologyUsage,\n getScannedHosts,\n getScannedHost,\n discoverUrls,\n scanUrl,\n startRecursiveAudit,\n getRecursiveAuditStatus,\n cancelRecursiveAudit,\n pauseRecursiveAudit,\n resumeRecursiveAudit,\n };\n};\n\n/**\n * Authenticated `audit` endpoint bound to an Intlayer CMS authenticator.\n *\n * Pass an authenticator created with `createIntlayerCMS`, or omit it to use\n * the build-time configuration (`@intlayer/config/built`).\n */\nexport const auditEndpoint = createEndpoint(getAuditAPI);\n"],"mappings":"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"}
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BACKEND_INTERNAL_URL_ENVIRONMENT_VARIABLE, resolveInternalBackendUrl } from "./resolveInternalBackendUrl.mjs";
|
|
2
2
|
import { FetcherError, fetcher, fetcherOptions } from "./fetcher.mjs";
|
|
3
3
|
import { getOAuthAPI } from "./getIntlayerAPI/oAuth.mjs";
|
|
4
|
-
import { createAuthManager } from "./cms/createAuthManager.mjs";
|
|
4
|
+
import { clearAccessTokenCache, createAuthManager } from "./cms/createAuthManager.mjs";
|
|
5
5
|
import { createEndpoint, createIntlayerCMS } from "./cms/createIntlayerCMS.mjs";
|
|
6
6
|
import { aiEndpoint, getAiAPI } from "./getIntlayerAPI/ai.mjs";
|
|
7
7
|
import { analyticsEndpoint, getAnalyticsAPI } from "./getIntlayerAPI/analytics.mjs";
|
|
@@ -29,4 +29,4 @@ import { fetchDistantDictionaries } from "./distantDictionary/fetchDistantDictio
|
|
|
29
29
|
import { fetchDistantDictionary } from "./distantDictionary/fetchDistantDictionary.mjs";
|
|
30
30
|
import { getIntlayerAPI } from "./getIntlayerAPI/index.mjs";
|
|
31
31
|
|
|
32
|
-
export { BACKEND_INTERNAL_URL_ENVIRONMENT_VARIABLE, FetcherError, aiEndpoint, analyticsEndpoint, assetEndpoint, auditEndpoint, bitbucketEndpoint, createAuthManager, createEndpoint, createIntlayerCMS, dictionaryEndpoint, editorEndpoint, environmentEndpoint, fetchDistantDictionaries, fetchDistantDictionary, fetcher, fetcherOptions, getAiAPI, getAnalyticsAPI, getAssetAPI, getAuditAPI, getBitbucketAPI, getDictionaryAPI, getEditorAPI, getEnvironmentAPI, getGithubAPI, getGitlabAPI, getIntlayerAPI, getIntlayerAPIProxy, getNewsletterAPI, getOAuthAPI, getOrganizationAPI, getProjectAPI, getReviewerAPI, getSearchAPI, getShowcaseProjectAPI, getStatusAPI, getStripeAPI, getTagAPI, getTranslateAPI, getUserAPI, githubEndpoint, gitlabEndpoint, newsletterEndpoint, organizationEndpoint, projectEndpoint, resolveInternalBackendUrl, reviewerEndpoint, searchEndpoint, showcaseProjectEndpoint, statusEndpoint, stripeEndpoint, tagEndpoint, translateEndpoint, userEndpoint };
|
|
32
|
+
export { BACKEND_INTERNAL_URL_ENVIRONMENT_VARIABLE, FetcherError, aiEndpoint, analyticsEndpoint, assetEndpoint, auditEndpoint, bitbucketEndpoint, clearAccessTokenCache, createAuthManager, createEndpoint, createIntlayerCMS, dictionaryEndpoint, editorEndpoint, environmentEndpoint, fetchDistantDictionaries, fetchDistantDictionary, fetcher, fetcherOptions, getAiAPI, getAnalyticsAPI, getAssetAPI, getAuditAPI, getBitbucketAPI, getDictionaryAPI, getEditorAPI, getEnvironmentAPI, getGithubAPI, getGitlabAPI, getIntlayerAPI, getIntlayerAPIProxy, getNewsletterAPI, getOAuthAPI, getOrganizationAPI, getProjectAPI, getReviewerAPI, getSearchAPI, getShowcaseProjectAPI, getStatusAPI, getStripeAPI, getTagAPI, getTranslateAPI, getUserAPI, githubEndpoint, gitlabEndpoint, newsletterEndpoint, organizationEndpoint, projectEndpoint, resolveInternalBackendUrl, reviewerEndpoint, searchEndpoint, showcaseProjectEndpoint, statusEndpoint, stripeEndpoint, tagEndpoint, translateEndpoint, userEndpoint };
|
|
@@ -11,6 +11,8 @@ type AsyncMethod<Arguments extends unknown[], Result> = (...args: Arguments) =>
|
|
|
11
11
|
* fetcher options and the Intlayer configuration.
|
|
12
12
|
*/
|
|
13
13
|
type SectionFactory<Section extends Record<string, unknown>> = (fetcherOptions: FetcherOptions, intlayerConfig: IntlayerConfig) => Section;
|
|
14
|
+
/** Clears every cached access token, forcing the next call to re-authenticate. */
|
|
15
|
+
export declare const clearAccessTokenCache: () => void;
|
|
14
16
|
/**
|
|
15
17
|
* Options accepted when creating an {@link AuthManager}.
|
|
16
18
|
*/
|
|
@@ -89,8 +91,8 @@ export type AuthManager = {
|
|
|
89
91
|
* Creates an instance-scoped {@link AuthManager} handling the OAuth2
|
|
90
92
|
* `client_credentials` flow (or a pre-issued CLI session token).
|
|
91
93
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
+
* Managers built with the same backend URL and credentials share one token
|
|
95
|
+
* state, so a process never exchanges credentials more than needed.
|
|
94
96
|
*
|
|
95
97
|
* @param intlayerConfig - Intlayer configuration carrying the `editor`
|
|
96
98
|
* credentials. Defaults to the build-time configuration.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createAuthManager.d.ts","names":[],"sources":["../../../src/cms/createAuthManager.ts"],"mappings":";;;;;;;KAuBK,YAAY,6BAA6B,cACzC,MAAM,cACN,QAAQ;;;;;KAMR,eAAe,gBAAgB,4BAClC,gBAAgB,gBAChB,gBAAgB,mBACb;;;;
|
|
1
|
+
{"version":3,"file":"createAuthManager.d.ts","names":[],"sources":["../../../src/cms/createAuthManager.ts"],"mappings":";;;;;;;KAuBK,YAAY,6BAA6B,cACzC,MAAM,cACN,QAAQ;;;;;KAMR,eAAe,gBAAgB,4BAClC,gBAAgB,gBAChB,gBAAgB,mBACb;;qBAsBQ;;;;YA6ED;;;;EAIV,qBAAqB;;;;;;EAMrB;;;;;YAMU;;;;;EAKV;;;;;;;;;;YAWU;;;;;EAKV,gBAAgB;;;;EAIhB,gBAAgB;;;;;EAKhB;;;;;EAKA,kBAAkB;;;;EAIlB;;;;;EAKA,WAAW,6BAA6B,QACtC,QAAQ,YAAY,WAAW,SAC/B,uBACG,YAAY,WAAW;;;;;;;;;;;EAW5B,cAAc,gBAAgB,yBAC5B,SAAS,eAAe,UACxB,UAAU,uBACP;;;;;;;;;;;;;qBAcM,oBAAiB,iBACZ,KAAK,2BAAyB,UACrC,uBACR"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { AuthManager, AuthManagerOptions, BindSectionOptions, createAuthManager } from "./createAuthManager.js";
|
|
1
|
+
import { AuthManager, AuthManagerOptions, BindSectionOptions, clearAccessTokenCache, createAuthManager } from "./createAuthManager.js";
|
|
2
2
|
import { CMSEndpoint, IntlayerCMS, IntlayerCMSOptions, createEndpoint, createIntlayerCMS } from "./createIntlayerCMS.js";
|
|
3
|
-
export { AuthManager, AuthManagerOptions, BindSectionOptions, CMSEndpoint, IntlayerCMS, IntlayerCMSOptions, createAuthManager, createEndpoint, createIntlayerCMS };
|
|
3
|
+
export { AuthManager, AuthManagerOptions, BindSectionOptions, CMSEndpoint, IntlayerCMS, IntlayerCMSOptions, clearAccessTokenCache, createAuthManager, createEndpoint, createIntlayerCMS };
|
|
@@ -10,6 +10,7 @@ export declare const getAnalyticsAPI: (authAPIOptions?: FetcherOptions, intlayer
|
|
|
10
10
|
getAudience: (range?: AudienceRange | number, otherOptions?: FetcherOptions) => Promise<GetAudienceResult>;
|
|
11
11
|
getContentStats: (otherOptions?: FetcherOptions) => Promise<GetContentStatsResult>;
|
|
12
12
|
getExperimentResults: (experimentKey: string, otherOptions?: FetcherOptions) => Promise<GetExperimentResultsResult>;
|
|
13
|
+
getPageMetadata: (url: string, otherOptions?: FetcherOptions) => Promise<GetPageMetadataResult>;
|
|
13
14
|
};
|
|
14
15
|
/**
|
|
15
16
|
* Authenticated `analytics` endpoint bound to an Intlayer CMS authenticator.
|
|
@@ -23,6 +24,7 @@ export declare const analyticsEndpoint: CMSEndpoint<{
|
|
|
23
24
|
getAudience: (range?: AudienceRange | number, otherOptions?: FetcherOptions) => Promise<GetAudienceResult>;
|
|
24
25
|
getContentStats: (otherOptions?: FetcherOptions) => Promise<GetContentStatsResult>;
|
|
25
26
|
getExperimentResults: (experimentKey: string, otherOptions?: FetcherOptions) => Promise<GetExperimentResultsResult>;
|
|
27
|
+
getPageMetadata: (url: string, otherOptions?: FetcherOptions) => Promise<GetPageMetadataResult>;
|
|
26
28
|
}>;
|
|
27
29
|
//#endregion
|
|
28
30
|
//# sourceMappingURL=analytics.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.d.ts","names":[],"sources":["../../../src/getIntlayerAPI/analytics.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","names":[],"sources":["../../../src/getIntlayerAPI/analytics.ts"],"mappings":";;;;;;qBAea,kBAAe,iBACV,gBAAc,iBACb;EAaT,aAAA,MAAA,qBAAmB,eACX,mBAAc,QAAA;EAgBW,cAAA,eAAA,mBAAc,QAAA;EAgB9C,cAAA,QAAA,wBAAsB,eACf,mBAAc,QAAA;EAgBe,kBAAA,eAAA,mBAAc,QAAA;EAc1C,uBAAA,uBAAM,eACP,mBAAc,QAAA;EAevB,kBAAA,aAAM,eACG,mBAAc,QAAA;;;;;;;;qBA4BnB,mBA5BmB;EAjFtB,aAAA,MAAA,qBAAmB,eACX,mBAAc,QAAA;EAgBW,cAAA,eAAA,mBAAc,QAAA;EAgB9C,cAAA,QAAA,wBAAsB,eACf,mBAAc,QAAA;EAgBe,kBAAA,eAAA,mBAAc,QAAA;EAc1C,uBAAA,uBAAM,eACP,mBAAc,QAAA;EAevB,kBAAA,aAAM,eACG,mBAAc,QAAA"}
|
|
@@ -2,10 +2,15 @@ import { FetcherOptions } from "../fetcher.js";
|
|
|
2
2
|
import { CMSEndpoint } from "../cms/createIntlayerCMS.js";
|
|
3
3
|
import "../index.js";
|
|
4
4
|
import { IntlayerConfig } from "@intlayer/types/config";
|
|
5
|
-
import { AuditEvent, GetRecursiveAuditStatusResult, StartRecursiveAuditResult } from "@intlayer/backend";
|
|
5
|
+
import { AuditEvent, GetRecursiveAuditStatusResult, GetScannedHostParams, GetScannedHostResult, GetScannedHostsQuery, GetScannedHostsResult, GetTechnologyUsageQuery, GetTechnologyUsageResult, StartRecursiveAuditResult } from "@intlayer/backend";
|
|
6
6
|
//#region src/getIntlayerAPI/audit.d.ts
|
|
7
7
|
export type ScanUrlBody = {
|
|
8
8
|
url: string;
|
|
9
|
+
/**
|
|
10
|
+
* Force a new audit. Without it, a URL audited less than an hour ago is
|
|
11
|
+
* replayed from cache (its first event carries `cachedAt`).
|
|
12
|
+
*/
|
|
13
|
+
refresh?: boolean;
|
|
9
14
|
onMessage?: (event: AuditEvent) => void;
|
|
10
15
|
onDone?: () => void;
|
|
11
16
|
};
|
|
@@ -26,6 +31,9 @@ export type RecursiveAuditJobParams = {
|
|
|
26
31
|
jobId: string;
|
|
27
32
|
};
|
|
28
33
|
export declare const getAuditAPI: (authAPIOptions?: FetcherOptions, intlayerConfig?: IntlayerConfig) => {
|
|
34
|
+
getTechnologyUsage: (params?: GetTechnologyUsageQuery, otherOptions?: FetcherOptions) => Promise<GetTechnologyUsageResult>;
|
|
35
|
+
getScannedHosts: (params?: GetScannedHostsQuery, otherOptions?: FetcherOptions) => Promise<GetScannedHostsResult>;
|
|
36
|
+
getScannedHost: (params?: GetScannedHostParams, otherOptions?: FetcherOptions) => Promise<GetScannedHostResult>;
|
|
29
37
|
discoverUrls: (params?: DiscoverUrlsParams, otherOptions?: FetcherOptions) => Promise<DiscoverUrlsResult>;
|
|
30
38
|
scanUrl: (body?: ScanUrlBody, otherOptions?: FetcherOptions) => Promise<void>;
|
|
31
39
|
startRecursiveAudit: (body?: StartRecursiveAuditBody, otherOptions?: FetcherOptions) => Promise<StartRecursiveAuditResult>;
|
|
@@ -47,6 +55,9 @@ export declare const getAuditAPI: (authAPIOptions?: FetcherOptions, intlayerConf
|
|
|
47
55
|
* the build-time configuration (`@intlayer/config/built`).
|
|
48
56
|
*/
|
|
49
57
|
export declare const auditEndpoint: CMSEndpoint<{
|
|
58
|
+
getTechnologyUsage: (params?: GetTechnologyUsageQuery, otherOptions?: FetcherOptions) => Promise<GetTechnologyUsageResult>;
|
|
59
|
+
getScannedHosts: (params?: GetScannedHostsQuery, otherOptions?: FetcherOptions) => Promise<GetScannedHostsResult>;
|
|
60
|
+
getScannedHost: (params?: GetScannedHostParams, otherOptions?: FetcherOptions) => Promise<GetScannedHostResult>;
|
|
50
61
|
discoverUrls: (params?: DiscoverUrlsParams, otherOptions?: FetcherOptions) => Promise<DiscoverUrlsResult>;
|
|
51
62
|
scanUrl: (body?: ScanUrlBody, otherOptions?: FetcherOptions) => Promise<void>;
|
|
52
63
|
startRecursiveAudit: (body?: StartRecursiveAuditBody, otherOptions?: FetcherOptions) => Promise<StartRecursiveAuditResult>;
|
|
@@ -62,5 +73,5 @@ export declare const auditEndpoint: CMSEndpoint<{
|
|
|
62
73
|
}>;
|
|
63
74
|
}>;
|
|
64
75
|
//#endregion
|
|
65
|
-
export type { AuditEvent, GetRecursiveAuditStatusResult, StartRecursiveAuditResult };
|
|
76
|
+
export type { AuditEvent, GetRecursiveAuditStatusResult, GetScannedHostParams, GetScannedHostResult, GetScannedHostsQuery, GetScannedHostsResult, GetTechnologyUsageQuery, GetTechnologyUsageResult, StartRecursiveAuditResult };
|
|
66
77
|
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","names":[],"sources":["../../../src/getIntlayerAPI/audit.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"audit.d.ts","names":[],"sources":["../../../src/getIntlayerAPI/audit.ts"],"mappings":";;;;;;YA4BY;EACV;;;;;EAKA;EACA,aAAa,OAAO;EACpB;;YAGU;EACV;;YAGU;EACV;;YAGU;EACV;EACA;;YAGU;EACV;;YAGU;EACV;;qBAGW,cAAW,iBACN,gBAAc,iBACb;EA4KN,qBAAA,SAAA,yBAAuB,eAClB,mBAAc,QAAA;EAcnB,kBAAA,SAAA,sBAAoB,eACf,mBAAc,QAAA;EAanB,iBAAA,SAAA,sBAAoB,eACf,mBAAc,QAAA;EAvLnB,eAAA,SAAA,oBAAkB,eACb,mBAAc,QAAA;EAarB,UAAA,OAAA,aAAW,eACJ,mBAAc;EAuErB,sBAAA,OAAA,yBAAuB,eAChB,mBAAc,QAAA;EAiBnB,0BAAA,SAAA,+BAA6B,eACxB,mBAAc,QAAA;EAYnB,uBAAA,SAAA,yBAAuB,eAClB,mBAAc;IAEH;;EAQhB,sBAAA,SAAA,yBAAuB,eAClB,mBAAc;IAEH;;EAQhB,uBAAA,SAAA,yBAAuB,eAClB,mBAAc;IAEH;;;;;;;;;qBAsEhB,eAxEmB;EAanB,qBAAA,SAAA,yBAAuB,eAClB,mBAAc,QAAA;EAcnB,kBAAA,SAAA,sBAAoB,eACf,mBAAc,QAAA;EAanB,iBAAA,SAAA,sBAAoB,eACf,mBAAc,QAAA;EAvLnB,eAAA,SAAA,oBAAkB,eACb,mBAAc,QAAA;EAarB,UAAA,OAAA,aAAW,eACJ,mBAAc;EAuErB,sBAAA,OAAA,yBAAuB,eAChB,mBAAc,QAAA;EAiBnB,0BAAA,SAAA,+BAA6B,eACxB,mBAAc,QAAA;EAYnB,uBAAA,SAAA,yBAAuB,eAClB,mBAAc;IAEH;;EAQhB,sBAAA,SAAA,yBAAuB,eAClB,mBAAc;IAEH;;EAQhB,uBAAA,SAAA,yBAAuB,eAClB,mBAAc;IAEH"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FetcherError, FetcherOptions, fetcher, fetcherOptions } from "./fetcher.js";
|
|
2
|
-
import { AuthManager, AuthManagerOptions, BindSectionOptions, createAuthManager } from "./cms/createAuthManager.js";
|
|
2
|
+
import { AuthManager, AuthManagerOptions, BindSectionOptions, clearAccessTokenCache, createAuthManager } from "./cms/createAuthManager.js";
|
|
3
3
|
import { CMSEndpoint, IntlayerCMS, IntlayerCMSOptions, createEndpoint, createIntlayerCMS } from "./cms/createIntlayerCMS.js";
|
|
4
4
|
import "./cms/index.js";
|
|
5
5
|
import { fetchDistantDictionaries } from "./distantDictionary/fetchDistantDictionaries.js";
|
|
@@ -7,7 +7,7 @@ import { fetchDistantDictionary } from "./distantDictionary/fetchDistantDictiona
|
|
|
7
7
|
import "./distantDictionary/index.js";
|
|
8
8
|
import { analyticsEndpoint, getAnalyticsAPI } from "./getIntlayerAPI/analytics.js";
|
|
9
9
|
import { assetEndpoint, getAssetAPI } from "./getIntlayerAPI/asset.js";
|
|
10
|
-
import { AuditEvent, DiscoverUrlsParams, DiscoverUrlsResult, GetRecursiveAuditStatusParams, GetRecursiveAuditStatusResult, RecursiveAuditJobParams, ScanUrlBody, StartRecursiveAuditBody, StartRecursiveAuditResult, auditEndpoint, getAuditAPI } from "./getIntlayerAPI/audit.js";
|
|
10
|
+
import { AuditEvent, DiscoverUrlsParams, DiscoverUrlsResult, GetRecursiveAuditStatusParams, GetRecursiveAuditStatusResult, GetScannedHostParams, GetScannedHostResult, GetScannedHostsQuery, GetScannedHostsResult, GetTechnologyUsageQuery, GetTechnologyUsageResult, RecursiveAuditJobParams, ScanUrlBody, StartRecursiveAuditBody, StartRecursiveAuditResult, auditEndpoint, getAuditAPI } from "./getIntlayerAPI/audit.js";
|
|
11
11
|
import { BitbucketAuthCallbackBody, BitbucketAuthCallbackResult, BitbucketCheckConfigBody, BitbucketCheckConfigResult, BitbucketGetAuthUrlResult, BitbucketGetConfigFileBody, BitbucketGetConfigFileResult, BitbucketListReposResult, BitbucketRepository, bitbucketEndpoint, getBitbucketAPI } from "./getIntlayerAPI/bitbucket.js";
|
|
12
12
|
import { dictionaryEndpoint, getDictionaryAPI } from "./getIntlayerAPI/dictionary.js";
|
|
13
13
|
import { editorEndpoint, getEditorAPI } from "./getIntlayerAPI/editor.js";
|
|
@@ -31,4 +31,4 @@ import { IntlayerAPIProxy, getIntlayerAPIProxy } from "./proxy.js";
|
|
|
31
31
|
import { BACKEND_INTERNAL_URL_ENVIRONMENT_VARIABLE, resolveInternalBackendUrl } from "./resolveInternalBackendUrl.js";
|
|
32
32
|
import { AIOptions } from "./types.js";
|
|
33
33
|
import { AskDocQuestionBody, AskDocQuestionResult, AutocompleteBody, ChatBody, ChatResult, ClientAction, aiEndpoint, getAiAPI } from "./getIntlayerAPI/ai.js";
|
|
34
|
-
export { type AIOptions, AskDocQuestionBody, type AskDocQuestionResult, type AuditEvent, AuthManager, AuthManagerOptions, AutocompleteBody, BACKEND_INTERNAL_URL_ENVIRONMENT_VARIABLE, BindSectionOptions, BitbucketAuthCallbackBody, BitbucketAuthCallbackResult, BitbucketCheckConfigBody, BitbucketCheckConfigResult, BitbucketGetAuthUrlResult, BitbucketGetConfigFileBody, BitbucketGetConfigFileResult, BitbucketListReposResult, BitbucketRepository, CMSEndpoint, ChatBody, type ChatResult, ClientAction, DiscoverUrlsParams, DiscoverUrlsResult, FetcherError, FetcherOptions, GetDemoSessionResult, GetRecursiveAuditStatusParams, type GetRecursiveAuditStatusResult, GitHubAuthCallbackBody, GitHubAuthCallbackResult, GitHubCheckConfigBody, GitHubCheckConfigResult, GitHubGetAuthUrlResult, GitHubGetConfigFileBody, GitHubGetConfigFileResult, GitHubGetTokenResult, GitHubListReposResult, GitHubRepository, GitLabAuthCallbackBody, GitLabAuthCallbackResult, GitLabCheckConfigBody, GitLabCheckConfigResult, GitLabGetAuthUrlResult, GitLabGetConfigFileBody, GitLabGetConfigFileResult, GitLabListProjectsResult, GitLabProject, IntlayerAPI, IntlayerAPIProxy, IntlayerCMS, IntlayerCMSOptions, OtherShowcaseProjectsQuery, RecursiveAuditJobParams, ScanUrlBody, ShowcaseProjectsQuery, StartRecursiveAuditBody, type StartRecursiveAuditResult, type TranslateDictionariesBody, type TranslateDictionariesResult, aiEndpoint, analyticsEndpoint, assetEndpoint, auditEndpoint, bitbucketEndpoint, createAuthManager, createEndpoint, createIntlayerCMS, dictionaryEndpoint, editorEndpoint, environmentEndpoint, fetchDistantDictionaries, fetchDistantDictionary, fetcher, fetcherOptions, getAiAPI, getAnalyticsAPI, getAssetAPI, getAuditAPI, getBitbucketAPI, getDictionaryAPI, getEditorAPI, getEnvironmentAPI, getGithubAPI, getGitlabAPI, getIntlayerAPI, getIntlayerAPIProxy, getNewsletterAPI, getOAuthAPI, getOrganizationAPI, getProjectAPI, getReviewerAPI, getSearchAPI, getShowcaseProjectAPI, getStatusAPI, getStripeAPI, getTagAPI, getTranslateAPI, getUserAPI, githubEndpoint, gitlabEndpoint, newsletterEndpoint, organizationEndpoint, projectEndpoint, resolveInternalBackendUrl, reviewerEndpoint, searchEndpoint, showcaseProjectEndpoint, statusEndpoint, stripeEndpoint, tagEndpoint, translateEndpoint, userEndpoint };
|
|
34
|
+
export { type AIOptions, AskDocQuestionBody, type AskDocQuestionResult, type AuditEvent, AuthManager, AuthManagerOptions, AutocompleteBody, BACKEND_INTERNAL_URL_ENVIRONMENT_VARIABLE, BindSectionOptions, BitbucketAuthCallbackBody, BitbucketAuthCallbackResult, BitbucketCheckConfigBody, BitbucketCheckConfigResult, BitbucketGetAuthUrlResult, BitbucketGetConfigFileBody, BitbucketGetConfigFileResult, BitbucketListReposResult, BitbucketRepository, CMSEndpoint, ChatBody, type ChatResult, ClientAction, DiscoverUrlsParams, DiscoverUrlsResult, FetcherError, FetcherOptions, GetDemoSessionResult, GetRecursiveAuditStatusParams, type GetRecursiveAuditStatusResult, type GetScannedHostParams, type GetScannedHostResult, type GetScannedHostsQuery, type GetScannedHostsResult, type GetTechnologyUsageQuery, type GetTechnologyUsageResult, GitHubAuthCallbackBody, GitHubAuthCallbackResult, GitHubCheckConfigBody, GitHubCheckConfigResult, GitHubGetAuthUrlResult, GitHubGetConfigFileBody, GitHubGetConfigFileResult, GitHubGetTokenResult, GitHubListReposResult, GitHubRepository, GitLabAuthCallbackBody, GitLabAuthCallbackResult, GitLabCheckConfigBody, GitLabCheckConfigResult, GitLabGetAuthUrlResult, GitLabGetConfigFileBody, GitLabGetConfigFileResult, GitLabListProjectsResult, GitLabProject, IntlayerAPI, IntlayerAPIProxy, IntlayerCMS, IntlayerCMSOptions, OtherShowcaseProjectsQuery, RecursiveAuditJobParams, ScanUrlBody, ShowcaseProjectsQuery, StartRecursiveAuditBody, type StartRecursiveAuditResult, type TranslateDictionariesBody, type TranslateDictionariesResult, aiEndpoint, analyticsEndpoint, assetEndpoint, auditEndpoint, bitbucketEndpoint, clearAccessTokenCache, createAuthManager, createEndpoint, createIntlayerCMS, dictionaryEndpoint, editorEndpoint, environmentEndpoint, fetchDistantDictionaries, fetchDistantDictionary, fetcher, fetcherOptions, getAiAPI, getAnalyticsAPI, getAssetAPI, getAuditAPI, getBitbucketAPI, getDictionaryAPI, getEditorAPI, getEnvironmentAPI, getGithubAPI, getGitlabAPI, getIntlayerAPI, getIntlayerAPIProxy, getNewsletterAPI, getOAuthAPI, getOrganizationAPI, getProjectAPI, getReviewerAPI, getSearchAPI, getShowcaseProjectAPI, getStatusAPI, getStripeAPI, getTagAPI, getTranslateAPI, getUserAPI, githubEndpoint, gitlabEndpoint, newsletterEndpoint, organizationEndpoint, projectEndpoint, resolveInternalBackendUrl, reviewerEndpoint, searchEndpoint, showcaseProjectEndpoint, statusEndpoint, stripeEndpoint, tagEndpoint, translateEndpoint, userEndpoint };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intlayer/api",
|
|
3
|
-
"version": "9.5.
|
|
3
|
+
"version": "9.5.12",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "SDK for interacting with the Intlayer API, enabling content auditing, and managing organizations, projects, and users.",
|
|
6
6
|
"keywords": [
|
|
@@ -265,8 +265,8 @@
|
|
|
265
265
|
"typecheck": "tsc --noEmit --project tsconfig.types.json"
|
|
266
266
|
},
|
|
267
267
|
"dependencies": {
|
|
268
|
-
"@intlayer/config": "9.5.
|
|
269
|
-
"@intlayer/types": "9.5.
|
|
268
|
+
"@intlayer/config": "9.5.12",
|
|
269
|
+
"@intlayer/types": "9.5.12",
|
|
270
270
|
"defu": "6.1.7"
|
|
271
271
|
},
|
|
272
272
|
"devDependencies": {
|