@cmssy/core 11.5.1 → 11.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{content-client-CJQ-nafm.d.cts → content-client-BPpRfq0h.d.cts} +1 -30
- package/dist/{content-client-CJQ-nafm.d.ts → content-client-BPpRfq0h.d.ts} +1 -30
- package/dist/{csp-CpiqfM03.d.ts → csp-DjFT5SmH.d.ts} +1 -65
- package/dist/{csp-0D9pcuDp.d.cts → csp-ZchQ-sC7.d.cts} +1 -65
- package/dist/index.cjs +24 -16
- package/dist/index.d.cts +3 -32
- package/dist/index.d.ts +3 -32
- package/dist/index.js +24 -16
- package/dist/internal/locale.cjs +0 -20
- package/dist/internal/locale.d.cts +2 -6
- package/dist/internal/locale.d.ts +2 -6
- package/dist/internal/locale.js +1 -18
- package/dist/internal.cjs +3 -44
- package/dist/internal.d.cts +6 -53
- package/dist/internal.d.ts +6 -53
- package/dist/internal.js +4 -38
- package/dist/locale-BS61R-fF.d.cts +30 -0
- package/dist/locale-DQZ80cew.d.ts +30 -0
- package/dist/preflight.d.cts +1 -1
- package/dist/preflight.d.ts +1 -1
- package/dist/testing.cjs +25 -173
- package/dist/testing.d.cts +2 -50
- package/dist/testing.d.ts +2 -50
- package/dist/testing.js +25 -173
- package/package.json +42 -17
- package/dist/locale-Clouq4s4.d.ts +0 -68
- package/dist/locale-XMylB_kz.d.cts +0 -68
|
@@ -1,48 +1,20 @@
|
|
|
1
1
|
import { CmssyClientConfig, CmssyLayoutGroup, CmssyPageData, CmssyPageMeta, CmssyPageSummary } from '@cmssy/types';
|
|
2
2
|
|
|
3
|
-
/** HTTP-level failure from the cmssy API, with a machine-readable status. */
|
|
4
3
|
declare class CmssyRequestError extends Error {
|
|
5
4
|
readonly status: number;
|
|
6
|
-
/**
|
|
7
|
-
* How long the server asked us to wait, in ms, when it said so. Present on a
|
|
8
|
-
* 429 that carried `Retry-After`; a caller can serve stale content for that
|
|
9
|
-
* long instead of guessing.
|
|
10
|
-
*/
|
|
11
5
|
readonly retryAfterMs?: number;
|
|
12
6
|
constructor(message: string, status: number, retryAfterMs?: number);
|
|
13
7
|
}
|
|
14
8
|
interface RetryPolicy {
|
|
15
|
-
/** Additional attempts after the first request (default 3). */
|
|
16
9
|
maxRetries?: number;
|
|
17
|
-
/** Exponential backoff base in ms: base * 2^attempt (default 300). */
|
|
18
10
|
baseDelayMs?: number;
|
|
19
|
-
/** Upper bound for a guessed backoff wait (default 3000). */
|
|
20
11
|
maxDelayMs?: number;
|
|
21
|
-
/**
|
|
22
|
-
* Upper bound for a wait the server asked for by `Retry-After` (default
|
|
23
|
-
* 10000). Beyond it there is nothing to gain from holding a render open, so
|
|
24
|
-
* the request fails immediately and carries `retryAfterMs` - the caller is in
|
|
25
|
-
* a better position to decide between stale content and an error page.
|
|
26
|
-
*/
|
|
27
12
|
maxRetryAfterMs?: number;
|
|
28
|
-
/** HTTP statuses that trigger a retry (default [429, 503]). */
|
|
29
13
|
retryStatuses?: number[];
|
|
30
14
|
}
|
|
31
15
|
|
|
32
|
-
/**
|
|
33
|
-
* The cmssy cloud GraphQL delivery endpoint. It is the same for every workspace,
|
|
34
|
-
* so consumers never need to set it - `apiUrl` defaults to this. Self-hosted /
|
|
35
|
-
* staging deployments override it via config.
|
|
36
|
-
*/
|
|
37
16
|
declare const DEFAULT_CMSSY_API_URL = "https://api.cmssy.io/graphql";
|
|
38
17
|
declare function resolveApiUrl(apiUrl: string | undefined): string;
|
|
39
|
-
/**
|
|
40
|
-
* Public delivery endpoint for a workspace: the org-scoped path
|
|
41
|
-
* `{base}/public/{orgSlug}/{workspaceSlug}/graphql`. `apiUrl` is the GraphQL
|
|
42
|
-
* base (its trailing `/graphql` is stripped); the org path is what tells the
|
|
43
|
-
* backend which workspace to serve, so slugs only need to be unique per org.
|
|
44
|
-
*/
|
|
45
|
-
declare function resolvePublicUrl(config: CmssyClientConfig): string;
|
|
46
18
|
interface FetchLikeResponse {
|
|
47
19
|
ok: boolean;
|
|
48
20
|
status: number;
|
|
@@ -66,11 +38,10 @@ interface FetchPageOptions {
|
|
|
66
38
|
signal?: AbortSignal;
|
|
67
39
|
retry?: RetryPolicy | false;
|
|
68
40
|
}
|
|
69
|
-
declare function normalizeSlug(path: string | string[] | undefined): string;
|
|
70
41
|
declare function fetchPage(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyPageData | null>;
|
|
71
42
|
declare function fetchPageById(config: CmssyClientConfig, pageId: string, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageData | null>;
|
|
72
43
|
declare function fetchPages(config: CmssyClientConfig, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageSummary[]>;
|
|
73
44
|
declare function fetchPageMeta(config: CmssyClientConfig, path: string | string[] | undefined, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageMeta | null>;
|
|
74
45
|
declare function fetchLayouts(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyLayoutGroup[]>;
|
|
75
46
|
|
|
76
|
-
export { CmssyRequestError as C, DEFAULT_CMSSY_API_URL as D, type FetchLike as F, type RetryPolicy as R, type FetchLikeResponse as a, type FetchPageOptions as b, fetchPage as c, fetchPageById as d, fetchPageMeta as e, fetchLayouts as f, fetchPages as g,
|
|
47
|
+
export { CmssyRequestError as C, DEFAULT_CMSSY_API_URL as D, type FetchLike as F, type RetryPolicy as R, type FetchLikeResponse as a, type FetchPageOptions as b, fetchPage as c, fetchPageById as d, fetchPageMeta as e, fetchLayouts as f, fetchPages as g, resolveApiUrl as r };
|
|
@@ -1,48 +1,20 @@
|
|
|
1
1
|
import { CmssyClientConfig, CmssyLayoutGroup, CmssyPageData, CmssyPageMeta, CmssyPageSummary } from '@cmssy/types';
|
|
2
2
|
|
|
3
|
-
/** HTTP-level failure from the cmssy API, with a machine-readable status. */
|
|
4
3
|
declare class CmssyRequestError extends Error {
|
|
5
4
|
readonly status: number;
|
|
6
|
-
/**
|
|
7
|
-
* How long the server asked us to wait, in ms, when it said so. Present on a
|
|
8
|
-
* 429 that carried `Retry-After`; a caller can serve stale content for that
|
|
9
|
-
* long instead of guessing.
|
|
10
|
-
*/
|
|
11
5
|
readonly retryAfterMs?: number;
|
|
12
6
|
constructor(message: string, status: number, retryAfterMs?: number);
|
|
13
7
|
}
|
|
14
8
|
interface RetryPolicy {
|
|
15
|
-
/** Additional attempts after the first request (default 3). */
|
|
16
9
|
maxRetries?: number;
|
|
17
|
-
/** Exponential backoff base in ms: base * 2^attempt (default 300). */
|
|
18
10
|
baseDelayMs?: number;
|
|
19
|
-
/** Upper bound for a guessed backoff wait (default 3000). */
|
|
20
11
|
maxDelayMs?: number;
|
|
21
|
-
/**
|
|
22
|
-
* Upper bound for a wait the server asked for by `Retry-After` (default
|
|
23
|
-
* 10000). Beyond it there is nothing to gain from holding a render open, so
|
|
24
|
-
* the request fails immediately and carries `retryAfterMs` - the caller is in
|
|
25
|
-
* a better position to decide between stale content and an error page.
|
|
26
|
-
*/
|
|
27
12
|
maxRetryAfterMs?: number;
|
|
28
|
-
/** HTTP statuses that trigger a retry (default [429, 503]). */
|
|
29
13
|
retryStatuses?: number[];
|
|
30
14
|
}
|
|
31
15
|
|
|
32
|
-
/**
|
|
33
|
-
* The cmssy cloud GraphQL delivery endpoint. It is the same for every workspace,
|
|
34
|
-
* so consumers never need to set it - `apiUrl` defaults to this. Self-hosted /
|
|
35
|
-
* staging deployments override it via config.
|
|
36
|
-
*/
|
|
37
16
|
declare const DEFAULT_CMSSY_API_URL = "https://api.cmssy.io/graphql";
|
|
38
17
|
declare function resolveApiUrl(apiUrl: string | undefined): string;
|
|
39
|
-
/**
|
|
40
|
-
* Public delivery endpoint for a workspace: the org-scoped path
|
|
41
|
-
* `{base}/public/{orgSlug}/{workspaceSlug}/graphql`. `apiUrl` is the GraphQL
|
|
42
|
-
* base (its trailing `/graphql` is stripped); the org path is what tells the
|
|
43
|
-
* backend which workspace to serve, so slugs only need to be unique per org.
|
|
44
|
-
*/
|
|
45
|
-
declare function resolvePublicUrl(config: CmssyClientConfig): string;
|
|
46
18
|
interface FetchLikeResponse {
|
|
47
19
|
ok: boolean;
|
|
48
20
|
status: number;
|
|
@@ -66,11 +38,10 @@ interface FetchPageOptions {
|
|
|
66
38
|
signal?: AbortSignal;
|
|
67
39
|
retry?: RetryPolicy | false;
|
|
68
40
|
}
|
|
69
|
-
declare function normalizeSlug(path: string | string[] | undefined): string;
|
|
70
41
|
declare function fetchPage(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyPageData | null>;
|
|
71
42
|
declare function fetchPageById(config: CmssyClientConfig, pageId: string, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageData | null>;
|
|
72
43
|
declare function fetchPages(config: CmssyClientConfig, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageSummary[]>;
|
|
73
44
|
declare function fetchPageMeta(config: CmssyClientConfig, path: string | string[] | undefined, options?: Pick<FetchPageOptions, "fetch" | "signal" | "retry">): Promise<CmssyPageMeta | null>;
|
|
74
45
|
declare function fetchLayouts(config: CmssyClientConfig, path: string | string[] | undefined, options?: FetchPageOptions): Promise<CmssyLayoutGroup[]>;
|
|
75
46
|
|
|
76
|
-
export { CmssyRequestError as C, DEFAULT_CMSSY_API_URL as D, type FetchLike as F, type RetryPolicy as R, type FetchLikeResponse as a, type FetchPageOptions as b, fetchPage as c, fetchPageById as d, fetchPageMeta as e, fetchLayouts as f, fetchPages as g,
|
|
47
|
+
export { CmssyRequestError as C, DEFAULT_CMSSY_API_URL as D, type FetchLike as F, type RetryPolicy as R, type FetchLikeResponse as a, type FetchPageOptions as b, fetchPage as c, fetchPageById as d, fetchPageMeta as e, fetchLayouts as f, fetchPages as g, resolveApiUrl as r };
|
|
@@ -1,82 +1,26 @@
|
|
|
1
1
|
import { CmssyClientConfig, BlockRect, BlockSchema, BlockMeta } from '@cmssy/types';
|
|
2
|
-
import { G as GraphqlRequestOptions } from './locale-
|
|
2
|
+
import { G as GraphqlRequestOptions } from './locale-DQZ80cew.js';
|
|
3
3
|
|
|
4
4
|
declare const DEFAULT_CMSSY_EDITOR_ORIGINS: string[];
|
|
5
5
|
declare function isDevelopment(): boolean;
|
|
6
6
|
declare function resolveEditorOrigin(editorOrigin: string | string[] | undefined): string | string[];
|
|
7
7
|
interface CmssyConfig {
|
|
8
|
-
/**
|
|
9
|
-
* Full GraphQL delivery endpoint. Defaults to the cmssy cloud endpoint
|
|
10
|
-
* (`https://api.cmssy.io/graphql`); set it only for self-hosted / staging.
|
|
11
|
-
*/
|
|
12
8
|
apiUrl?: string;
|
|
13
|
-
/** Organization slug - part of the org-scoped delivery path. */
|
|
14
9
|
org: string;
|
|
15
10
|
workspaceSlug: string;
|
|
16
11
|
draftSecret: string;
|
|
17
|
-
/**
|
|
18
|
-
* A cmssy API token (`cs_…`) that opts this app into the editor-controlled dev
|
|
19
|
-
* preview. In development only, the SDK sends it on every page fetch so the
|
|
20
|
-
* backend can resolve the token's user and honour that user's dev-preview flag
|
|
21
|
-
* (toggled from the editor's dev-mode switch): flag on + a saved dev draft ⇒
|
|
22
|
-
* the draft overlay renders, otherwise published content. Server-only (never
|
|
23
|
-
* reaches the client); ignored outside development. See the Quickstart
|
|
24
|
-
* "Dev preview" section.
|
|
25
|
-
*/
|
|
26
12
|
devToken?: string;
|
|
27
|
-
/**
|
|
28
|
-
* Origin(s) allowed to frame your app in the editor: one origin, a list, or
|
|
29
|
-
* the comma-separated string an env var carries - all three are accepted, so
|
|
30
|
-
* `process.env.CMSSY_EDITOR_ORIGIN` can be passed through as-is. Defaults to
|
|
31
|
-
* {@link DEFAULT_CMSSY_EDITOR_ORIGINS}; set it only for self-hosted admins.
|
|
32
|
-
*/
|
|
33
13
|
editorOrigin?: string | string[];
|
|
34
|
-
/**
|
|
35
|
-
* Canonical absolute site URL (e.g. https://cmssy.com), used by
|
|
36
|
-
* createCmssyRobots / createCmssySitemap. When omitted the helpers derive the
|
|
37
|
-
* origin from the request `host` header at render time (multi-domain safe).
|
|
38
|
-
*/
|
|
39
14
|
siteUrl?: string;
|
|
40
|
-
/**
|
|
41
|
-
* Fallback locale resolver for a site whose URLs carry no language (e.g. a
|
|
42
|
-
* cookie or Accept-Language strategy). The workspace site config remains the
|
|
43
|
-
* source of truth for the default and enabled languages.
|
|
44
|
-
*/
|
|
45
15
|
resolveLocale?: () => string | Promise<string>;
|
|
46
16
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Env-shaped input for {@link defineCmssyConfig}: the required string fields are
|
|
49
|
-
* widened to `string | undefined` so a config can pass `process.env.*` straight
|
|
50
|
-
* through without a `?? ""` fallback (which would mask a missing value as an
|
|
51
|
-
* empty string) or a cast.
|
|
52
|
-
*/
|
|
53
17
|
type CmssyEnvConfig = Omit<CmssyConfig, "org" | "workspaceSlug" | "draftSecret"> & {
|
|
54
18
|
org?: string;
|
|
55
19
|
workspaceSlug?: string;
|
|
56
20
|
draftSecret?: string;
|
|
57
21
|
};
|
|
58
|
-
/**
|
|
59
|
-
* Validates an env-sourced config and returns a strictly-typed
|
|
60
|
-
* {@link CmssyConfig}. Pass raw `process.env.*` values; this throws a clear,
|
|
61
|
-
* actionable error listing any missing required variables (rendered by the
|
|
62
|
-
* Next.js error overlay / boundary), so the app fails fast instead of running
|
|
63
|
-
* with silently-empty config.
|
|
64
|
-
*/
|
|
65
22
|
declare function defineCmssyConfig(config: CmssyEnvConfig): CmssyConfig;
|
|
66
23
|
|
|
67
|
-
/**
|
|
68
|
-
* Accepting a *typed* document, not a string, is what makes a query's variables
|
|
69
|
-
* and its result checked together. graphql-codegen emits one of three shapes,
|
|
70
|
-
* and the SDK takes all of them so an app never has to care which mode its
|
|
71
|
-
* codegen runs in - or add `graphql` to its runtime dependencies to print an
|
|
72
|
-
* AST it already has.
|
|
73
|
-
*/
|
|
74
|
-
/**
|
|
75
|
-
* A document that carries its result and variable types. Structurally
|
|
76
|
-
* compatible with `TypedDocumentNode` (`@graphql-typed-document-node/core`) and
|
|
77
|
-
* with graphql-codegen's `TypedDocumentString`, both of which declare the same
|
|
78
|
-
* phantom `__apiType`.
|
|
79
|
-
*/
|
|
80
24
|
interface CmssyTypedDocument<Result, Variables> {
|
|
81
25
|
__apiType?: (variables: Variables) => Result;
|
|
82
26
|
}
|
|
@@ -86,15 +30,8 @@ interface QueryScopedOptions extends GraphqlRequestOptions {
|
|
|
86
30
|
}
|
|
87
31
|
interface CmssyClient {
|
|
88
32
|
readonly config: CmssyClientConfig;
|
|
89
|
-
/**
|
|
90
|
-
* Hand it a document that carries its types (what graphql-codegen emits, in
|
|
91
|
-
* either mode) and the variables are checked and the result inferred; hand it
|
|
92
|
-
* a query string and it behaves exactly as before. Same method, no second
|
|
93
|
-
* way to do the same thing.
|
|
94
|
-
*/
|
|
95
33
|
query<Result, Variables>(document: CmssyTypedDocument<Result, Variables>, variables: Variables, options?: GraphqlRequestOptions): Promise<Result>;
|
|
96
34
|
query<T = unknown>(document: string, variables?: Record<string, unknown>, options?: GraphqlRequestOptions): Promise<T>;
|
|
97
|
-
/** {@link CmssyClient.query} with the workspace id resolved and injected. */
|
|
98
35
|
queryScoped<Result, Variables>(document: CmssyTypedDocument<Result, Variables>, variables: Omit<Variables, "workspaceId">, options?: QueryScopedOptions): Promise<Result>;
|
|
99
36
|
queryScoped<T = unknown>(document: string, variables?: Record<string, unknown>, options?: QueryScopedOptions): Promise<T>;
|
|
100
37
|
resolveWorkspaceId(options?: GraphqlRequestOptions): Promise<string>;
|
|
@@ -200,7 +137,6 @@ declare function resolveInitialTarget(editorOrigin: string | string[]): string;
|
|
|
200
137
|
declare function parseEditorMessage(data: unknown, origin: string, expectedOrigin: string | string[]): EditorToAppMessage | null;
|
|
201
138
|
|
|
202
139
|
interface CmssyCspOptions {
|
|
203
|
-
/** Defaults to the cmssy cloud admin origin when unset. */
|
|
204
140
|
editorOrigin?: string | string[];
|
|
205
141
|
}
|
|
206
142
|
interface MutableHeaders {
|
|
@@ -1,82 +1,26 @@
|
|
|
1
1
|
import { CmssyClientConfig, BlockRect, BlockSchema, BlockMeta } from '@cmssy/types';
|
|
2
|
-
import { G as GraphqlRequestOptions } from './locale-
|
|
2
|
+
import { G as GraphqlRequestOptions } from './locale-BS61R-fF.cjs';
|
|
3
3
|
|
|
4
4
|
declare const DEFAULT_CMSSY_EDITOR_ORIGINS: string[];
|
|
5
5
|
declare function isDevelopment(): boolean;
|
|
6
6
|
declare function resolveEditorOrigin(editorOrigin: string | string[] | undefined): string | string[];
|
|
7
7
|
interface CmssyConfig {
|
|
8
|
-
/**
|
|
9
|
-
* Full GraphQL delivery endpoint. Defaults to the cmssy cloud endpoint
|
|
10
|
-
* (`https://api.cmssy.io/graphql`); set it only for self-hosted / staging.
|
|
11
|
-
*/
|
|
12
8
|
apiUrl?: string;
|
|
13
|
-
/** Organization slug - part of the org-scoped delivery path. */
|
|
14
9
|
org: string;
|
|
15
10
|
workspaceSlug: string;
|
|
16
11
|
draftSecret: string;
|
|
17
|
-
/**
|
|
18
|
-
* A cmssy API token (`cs_…`) that opts this app into the editor-controlled dev
|
|
19
|
-
* preview. In development only, the SDK sends it on every page fetch so the
|
|
20
|
-
* backend can resolve the token's user and honour that user's dev-preview flag
|
|
21
|
-
* (toggled from the editor's dev-mode switch): flag on + a saved dev draft ⇒
|
|
22
|
-
* the draft overlay renders, otherwise published content. Server-only (never
|
|
23
|
-
* reaches the client); ignored outside development. See the Quickstart
|
|
24
|
-
* "Dev preview" section.
|
|
25
|
-
*/
|
|
26
12
|
devToken?: string;
|
|
27
|
-
/**
|
|
28
|
-
* Origin(s) allowed to frame your app in the editor: one origin, a list, or
|
|
29
|
-
* the comma-separated string an env var carries - all three are accepted, so
|
|
30
|
-
* `process.env.CMSSY_EDITOR_ORIGIN` can be passed through as-is. Defaults to
|
|
31
|
-
* {@link DEFAULT_CMSSY_EDITOR_ORIGINS}; set it only for self-hosted admins.
|
|
32
|
-
*/
|
|
33
13
|
editorOrigin?: string | string[];
|
|
34
|
-
/**
|
|
35
|
-
* Canonical absolute site URL (e.g. https://cmssy.com), used by
|
|
36
|
-
* createCmssyRobots / createCmssySitemap. When omitted the helpers derive the
|
|
37
|
-
* origin from the request `host` header at render time (multi-domain safe).
|
|
38
|
-
*/
|
|
39
14
|
siteUrl?: string;
|
|
40
|
-
/**
|
|
41
|
-
* Fallback locale resolver for a site whose URLs carry no language (e.g. a
|
|
42
|
-
* cookie or Accept-Language strategy). The workspace site config remains the
|
|
43
|
-
* source of truth for the default and enabled languages.
|
|
44
|
-
*/
|
|
45
15
|
resolveLocale?: () => string | Promise<string>;
|
|
46
16
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Env-shaped input for {@link defineCmssyConfig}: the required string fields are
|
|
49
|
-
* widened to `string | undefined` so a config can pass `process.env.*` straight
|
|
50
|
-
* through without a `?? ""` fallback (which would mask a missing value as an
|
|
51
|
-
* empty string) or a cast.
|
|
52
|
-
*/
|
|
53
17
|
type CmssyEnvConfig = Omit<CmssyConfig, "org" | "workspaceSlug" | "draftSecret"> & {
|
|
54
18
|
org?: string;
|
|
55
19
|
workspaceSlug?: string;
|
|
56
20
|
draftSecret?: string;
|
|
57
21
|
};
|
|
58
|
-
/**
|
|
59
|
-
* Validates an env-sourced config and returns a strictly-typed
|
|
60
|
-
* {@link CmssyConfig}. Pass raw `process.env.*` values; this throws a clear,
|
|
61
|
-
* actionable error listing any missing required variables (rendered by the
|
|
62
|
-
* Next.js error overlay / boundary), so the app fails fast instead of running
|
|
63
|
-
* with silently-empty config.
|
|
64
|
-
*/
|
|
65
22
|
declare function defineCmssyConfig(config: CmssyEnvConfig): CmssyConfig;
|
|
66
23
|
|
|
67
|
-
/**
|
|
68
|
-
* Accepting a *typed* document, not a string, is what makes a query's variables
|
|
69
|
-
* and its result checked together. graphql-codegen emits one of three shapes,
|
|
70
|
-
* and the SDK takes all of them so an app never has to care which mode its
|
|
71
|
-
* codegen runs in - or add `graphql` to its runtime dependencies to print an
|
|
72
|
-
* AST it already has.
|
|
73
|
-
*/
|
|
74
|
-
/**
|
|
75
|
-
* A document that carries its result and variable types. Structurally
|
|
76
|
-
* compatible with `TypedDocumentNode` (`@graphql-typed-document-node/core`) and
|
|
77
|
-
* with graphql-codegen's `TypedDocumentString`, both of which declare the same
|
|
78
|
-
* phantom `__apiType`.
|
|
79
|
-
*/
|
|
80
24
|
interface CmssyTypedDocument<Result, Variables> {
|
|
81
25
|
__apiType?: (variables: Variables) => Result;
|
|
82
26
|
}
|
|
@@ -86,15 +30,8 @@ interface QueryScopedOptions extends GraphqlRequestOptions {
|
|
|
86
30
|
}
|
|
87
31
|
interface CmssyClient {
|
|
88
32
|
readonly config: CmssyClientConfig;
|
|
89
|
-
/**
|
|
90
|
-
* Hand it a document that carries its types (what graphql-codegen emits, in
|
|
91
|
-
* either mode) and the variables are checked and the result inferred; hand it
|
|
92
|
-
* a query string and it behaves exactly as before. Same method, no second
|
|
93
|
-
* way to do the same thing.
|
|
94
|
-
*/
|
|
95
33
|
query<Result, Variables>(document: CmssyTypedDocument<Result, Variables>, variables: Variables, options?: GraphqlRequestOptions): Promise<Result>;
|
|
96
34
|
query<T = unknown>(document: string, variables?: Record<string, unknown>, options?: GraphqlRequestOptions): Promise<T>;
|
|
97
|
-
/** {@link CmssyClient.query} with the workspace id resolved and injected. */
|
|
98
35
|
queryScoped<Result, Variables>(document: CmssyTypedDocument<Result, Variables>, variables: Omit<Variables, "workspaceId">, options?: QueryScopedOptions): Promise<Result>;
|
|
99
36
|
queryScoped<T = unknown>(document: string, variables?: Record<string, unknown>, options?: QueryScopedOptions): Promise<T>;
|
|
100
37
|
resolveWorkspaceId(options?: GraphqlRequestOptions): Promise<string>;
|
|
@@ -200,7 +137,6 @@ declare function resolveInitialTarget(editorOrigin: string | string[]): string;
|
|
|
200
137
|
declare function parseEditorMessage(data: unknown, origin: string, expectedOrigin: string | string[]): EditorToAppMessage | null;
|
|
201
138
|
|
|
202
139
|
interface CmssyCspOptions {
|
|
203
|
-
/** Defaults to the cmssy cloud admin origin when unset. */
|
|
204
140
|
editorOrigin?: string | string[];
|
|
205
141
|
}
|
|
206
142
|
interface MutableHeaders {
|
package/dist/index.cjs
CHANGED
|
@@ -76,11 +76,6 @@ Set the listed environment variables (e.g. in .env.local) and restart the dev se
|
|
|
76
76
|
// src/data/http.ts
|
|
77
77
|
var CmssyRequestError = class extends Error {
|
|
78
78
|
status;
|
|
79
|
-
/**
|
|
80
|
-
* How long the server asked us to wait, in ms, when it said so. Present on a
|
|
81
|
-
* 429 that carried `Retry-After`; a caller can serve stale content for that
|
|
82
|
-
* long instead of guessing.
|
|
83
|
-
*/
|
|
84
79
|
retryAfterMs;
|
|
85
80
|
constructor(message, status, retryAfterMs2) {
|
|
86
81
|
super(message);
|
|
@@ -451,8 +446,6 @@ function createCmssyClient(input) {
|
|
|
451
446
|
const client = {
|
|
452
447
|
config,
|
|
453
448
|
resolveWorkspaceId: resolveWorkspaceId2,
|
|
454
|
-
// One implementation behind both overloads: a string passes through
|
|
455
|
-
// documentText untouched, a typed document gives up its query text.
|
|
456
449
|
query: ((document2, variables = {}, options) => query(documentText(document2), variables, options)),
|
|
457
450
|
queryScoped: ((document2, variables = {}, options = {}) => queryScoped(
|
|
458
451
|
documentText(document2),
|
|
@@ -764,21 +757,24 @@ var CmssyWebhookError = class extends Error {
|
|
|
764
757
|
}
|
|
765
758
|
};
|
|
766
759
|
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
760
|
+
var MAX_SIGNATURES = 8;
|
|
767
761
|
function parseSignatureHeader(header) {
|
|
768
762
|
let timestamp = null;
|
|
769
|
-
|
|
763
|
+
const signatures = [];
|
|
770
764
|
for (const part of header.split(",")) {
|
|
771
765
|
const idx = part.indexOf("=");
|
|
772
766
|
if (idx === -1) continue;
|
|
773
767
|
const key = part.slice(0, idx).trim();
|
|
774
768
|
const value = part.slice(idx + 1).trim();
|
|
775
769
|
if (key === "t") timestamp = Number(value);
|
|
776
|
-
else if (key === "v1"
|
|
770
|
+
else if (key === "v1" && value && signatures.length < MAX_SIGNATURES) {
|
|
771
|
+
signatures.push(value);
|
|
772
|
+
}
|
|
777
773
|
}
|
|
778
|
-
if (timestamp === null || !Number.isFinite(timestamp) || !
|
|
774
|
+
if (timestamp === null || !Number.isFinite(timestamp) || !signatures.length) {
|
|
779
775
|
throw new CmssyWebhookError("Malformed X-Cmssy-Signature header");
|
|
780
776
|
}
|
|
781
|
-
return { timestamp,
|
|
777
|
+
return { timestamp, signatures };
|
|
782
778
|
}
|
|
783
779
|
function hexToBytes(hex) {
|
|
784
780
|
if (hex.length % 2 !== 0 || /[^0-9a-fA-F]/.test(hex)) return null;
|
|
@@ -792,9 +788,10 @@ function bytesToHex(bytes) {
|
|
|
792
788
|
return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
793
789
|
}
|
|
794
790
|
function timingSafeHexEqual(expectedHex, providedHex) {
|
|
791
|
+
if (expectedHex.length !== providedHex.length) return false;
|
|
795
792
|
const expected = hexToBytes(expectedHex);
|
|
796
793
|
const provided = hexToBytes(providedHex);
|
|
797
|
-
if (!expected || !provided
|
|
794
|
+
if (!expected || !provided) {
|
|
798
795
|
return false;
|
|
799
796
|
}
|
|
800
797
|
let diff = 0;
|
|
@@ -824,17 +821,28 @@ async function verifyCmssyWebhook(options) {
|
|
|
824
821
|
if (!signatureHeader) {
|
|
825
822
|
throw new CmssyWebhookError("Missing X-Cmssy-Signature header");
|
|
826
823
|
}
|
|
827
|
-
|
|
824
|
+
const candidates = typeof secret === "string" ? [secret] : Array.isArray(secret) ? secret : [];
|
|
825
|
+
if (candidates.some((value) => typeof value !== "string")) {
|
|
826
|
+
throw new CmssyWebhookError("Webhook secret must be a string");
|
|
827
|
+
}
|
|
828
|
+
const secrets = candidates.filter(Boolean);
|
|
829
|
+
if (!secrets.length) {
|
|
828
830
|
throw new CmssyWebhookError("Missing webhook secret");
|
|
829
831
|
}
|
|
830
|
-
const { timestamp,
|
|
832
|
+
const { timestamp, signatures } = parseSignatureHeader(signatureHeader);
|
|
831
833
|
const toleranceMs = (options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS) * 1e3;
|
|
832
834
|
const now = options.now ?? Date.now();
|
|
833
835
|
if (Math.abs(now - timestamp) > toleranceMs) {
|
|
834
836
|
throw new CmssyWebhookError("Webhook timestamp outside tolerance");
|
|
835
837
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
+
let matched = false;
|
|
839
|
+
for (const candidate of secrets) {
|
|
840
|
+
const expected = await hmacSha256Hex(candidate, `${timestamp}.${body}`);
|
|
841
|
+
for (const signature of signatures) {
|
|
842
|
+
if (timingSafeHexEqual(expected, signature)) matched = true;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (!matched) {
|
|
838
846
|
throw new CmssyWebhookError("Webhook signature mismatch");
|
|
839
847
|
}
|
|
840
848
|
let parsed;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { A as AppToEditorMessage, B as BoundsMessage, C as ClickMessage, a as CmssyClient, b as CmssyConfig, c as CmssyCspOptions, d as CmssyEnvConfig, e as CmssyTypedDocument, D as DEFAULT_CMSSY_EDITOR_ORIGINS, E as EditorToAppMessage, P as PROTOCOL_VERSION, f as ParentReadyMessage, g as PatchMessage, h as PostTarget, Q as QueryScopedOptions, R as ReadyMessage, S as SelectMessage, i as applyCmssyCsp, j as createCmssyClient, k as defineCmssyConfig, l as isProtocolCompatible, n as normalizeOrigin, p as parseEditorMessage, m as postToEditor, r as resolveEditorOrigin } from './csp-
|
|
2
|
-
export { C as CMSSY_LOCALE_HEADER, G as GraphqlRequestOptions, g as graphqlRequest, l as localizeHref, r as resolveCmssyLocale } from './locale-
|
|
3
|
-
export { C as CmssyRequestError, D as DEFAULT_CMSSY_API_URL, F as FetchLike, a as FetchLikeResponse, b as FetchPageOptions, R as RetryPolicy } from './content-client-
|
|
1
|
+
export { A as AppToEditorMessage, B as BoundsMessage, C as ClickMessage, a as CmssyClient, b as CmssyConfig, c as CmssyCspOptions, d as CmssyEnvConfig, e as CmssyTypedDocument, D as DEFAULT_CMSSY_EDITOR_ORIGINS, E as EditorToAppMessage, P as PROTOCOL_VERSION, f as ParentReadyMessage, g as PatchMessage, h as PostTarget, Q as QueryScopedOptions, R as ReadyMessage, S as SelectMessage, i as applyCmssyCsp, j as createCmssyClient, k as defineCmssyConfig, l as isProtocolCompatible, n as normalizeOrigin, p as parseEditorMessage, m as postToEditor, r as resolveEditorOrigin } from './csp-ZchQ-sC7.cjs';
|
|
2
|
+
export { C as CMSSY_LOCALE_HEADER, G as GraphqlRequestOptions, g as graphqlRequest, l as localizeHref, r as resolveCmssyLocale } from './locale-BS61R-fF.cjs';
|
|
3
|
+
export { C as CmssyRequestError, D as DEFAULT_CMSSY_API_URL, F as FetchLike, a as FetchLikeResponse, b as FetchPageOptions, R as RetryPolicy } from './content-client-BPpRfq0h.cjs';
|
|
4
4
|
import * as _cmssy_types from '@cmssy/types';
|
|
5
5
|
import { FieldOptions, TypedField, BlockPropsSchema, InferBlockContent, RelationMode, CmssyModelRecord, VerifyCmssyWebhookOptions, CmssyWebhookEvent } from '@cmssy/types';
|
|
6
6
|
export { BlockMeta, BlockPropsSchema, BlockRect, BlockSchema, BuildBlockContextExtra, CmssyBlockAuthContext, CmssyBlockContext, CmssyBlockMember, CmssyBlockPage, CmssyBlockWorkspace, CmssyBranding, CmssyClientConfig, CmssyFormDefinition, CmssyFormField, CmssyFormSettings, CmssyFormSubmitResponse, CmssyLayoutGroup, CmssyLayoutSettings, CmssyLocaleContext, CmssyLocalizedValue, CmssyModelDefinition, CmssyModelRecord, CmssyPageData, CmssyPageMeta, CmssyPageSummary, CmssyRecordList, CmssySiteConfig, CmssyWebhookEvent, CmssyWebhookOrder, FieldCondition, FieldConditionGroup, FieldConditionLogic, FieldControl, FieldDefinition, FieldOptions, FieldType, InferBlockContent, LayoutPosition, RawBlock, RawLayoutBlock, SubmitFormInput, TypedField, VerifyCmssyWebhookOptions, evaluateFieldConditionGroup, layoutPositionValues } from '@cmssy/types';
|
|
@@ -17,25 +17,13 @@ type MediaValue<O> = O extends {
|
|
|
17
17
|
type RepeaterValue<O> = O extends {
|
|
18
18
|
itemSchema: infer Schema extends BlockPropsSchema;
|
|
19
19
|
} ? InferBlockContent<Schema>[] : Record<string, unknown>[];
|
|
20
|
-
/**
|
|
21
|
-
* The value a relation field holds AFTER server-side resolution: the SDK
|
|
22
|
-
* replaces the stored record id(s) with the records themselves before the
|
|
23
|
-
* component renders. `mode: "all"` and `multiple: true` yield a list; the
|
|
24
|
-
* default is a single record - `undefined` when the reference dangles, which
|
|
25
|
-
* no `required` flag can rule out.
|
|
26
|
-
*/
|
|
27
20
|
type RelationValue<O> = O extends {
|
|
28
21
|
mode: "all";
|
|
29
22
|
} | {
|
|
30
23
|
multiple: true;
|
|
31
24
|
} ? CmssyModelRecord[] : CmssyModelRecord | undefined;
|
|
32
25
|
interface RelationFieldOptions extends Omit<FieldOptions, "options" | "itemSchema"> {
|
|
33
|
-
/** Slug of the model the field points at. */
|
|
34
26
|
model: string;
|
|
35
|
-
/**
|
|
36
|
-
* "all" binds the field to every record of the model (no editor picking;
|
|
37
|
-
* `sort`/`limit` shape the list). Omit it for editor-picked record id(s).
|
|
38
|
-
*/
|
|
39
27
|
mode?: RelationMode;
|
|
40
28
|
multiple?: boolean;
|
|
41
29
|
sort?: string;
|
|
@@ -79,26 +67,9 @@ declare function isVerifiedEditUrl(url: {
|
|
|
79
67
|
draftSecret: string;
|
|
80
68
|
}): Promise<boolean>;
|
|
81
69
|
|
|
82
|
-
/**
|
|
83
|
-
* Verify + parse an inbound cmssy webhook (CMS-693 / CMS-694).
|
|
84
|
-
*
|
|
85
|
-
* cmssy signs each delivery with HMAC-SHA256 over `${timestamp}.${body}`
|
|
86
|
-
* and sends the result in the `X-Cmssy-Signature: t=<ms>,v1=<hex>`
|
|
87
|
-
* header, plus a unique `X-Cmssy-Webhook-Id`. This helper recomputes the
|
|
88
|
-
* signature (timing-safe compare), rejects stale timestamps to bound
|
|
89
|
-
* replay, and returns the typed event.
|
|
90
|
-
*
|
|
91
|
-
* IMPORTANT: pass the RAW request body string (e.g. `await req.text()`),
|
|
92
|
-
* never a re-serialized object - the signed bytes must match exactly.
|
|
93
|
-
*/
|
|
94
70
|
declare class CmssyWebhookError extends Error {
|
|
95
71
|
constructor(message: string);
|
|
96
72
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Verify the signature + freshness and return the parsed event. Throws
|
|
99
|
-
* `CmssyWebhookError` on any failure (missing/malformed header, bad
|
|
100
|
-
* signature, stale timestamp, invalid JSON) - catch it and respond 400.
|
|
101
|
-
*/
|
|
102
73
|
declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): Promise<CmssyWebhookEvent>;
|
|
103
74
|
|
|
104
75
|
export { CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_SECRET_QUERY_PARAM, CmssyWebhookError, fields, isVerifiedEditUrl, verifyCmssyWebhook };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { A as AppToEditorMessage, B as BoundsMessage, C as ClickMessage, a as CmssyClient, b as CmssyConfig, c as CmssyCspOptions, d as CmssyEnvConfig, e as CmssyTypedDocument, D as DEFAULT_CMSSY_EDITOR_ORIGINS, E as EditorToAppMessage, P as PROTOCOL_VERSION, f as ParentReadyMessage, g as PatchMessage, h as PostTarget, Q as QueryScopedOptions, R as ReadyMessage, S as SelectMessage, i as applyCmssyCsp, j as createCmssyClient, k as defineCmssyConfig, l as isProtocolCompatible, n as normalizeOrigin, p as parseEditorMessage, m as postToEditor, r as resolveEditorOrigin } from './csp-
|
|
2
|
-
export { C as CMSSY_LOCALE_HEADER, G as GraphqlRequestOptions, g as graphqlRequest, l as localizeHref, r as resolveCmssyLocale } from './locale-
|
|
3
|
-
export { C as CmssyRequestError, D as DEFAULT_CMSSY_API_URL, F as FetchLike, a as FetchLikeResponse, b as FetchPageOptions, R as RetryPolicy } from './content-client-
|
|
1
|
+
export { A as AppToEditorMessage, B as BoundsMessage, C as ClickMessage, a as CmssyClient, b as CmssyConfig, c as CmssyCspOptions, d as CmssyEnvConfig, e as CmssyTypedDocument, D as DEFAULT_CMSSY_EDITOR_ORIGINS, E as EditorToAppMessage, P as PROTOCOL_VERSION, f as ParentReadyMessage, g as PatchMessage, h as PostTarget, Q as QueryScopedOptions, R as ReadyMessage, S as SelectMessage, i as applyCmssyCsp, j as createCmssyClient, k as defineCmssyConfig, l as isProtocolCompatible, n as normalizeOrigin, p as parseEditorMessage, m as postToEditor, r as resolveEditorOrigin } from './csp-DjFT5SmH.js';
|
|
2
|
+
export { C as CMSSY_LOCALE_HEADER, G as GraphqlRequestOptions, g as graphqlRequest, l as localizeHref, r as resolveCmssyLocale } from './locale-DQZ80cew.js';
|
|
3
|
+
export { C as CmssyRequestError, D as DEFAULT_CMSSY_API_URL, F as FetchLike, a as FetchLikeResponse, b as FetchPageOptions, R as RetryPolicy } from './content-client-BPpRfq0h.js';
|
|
4
4
|
import * as _cmssy_types from '@cmssy/types';
|
|
5
5
|
import { FieldOptions, TypedField, BlockPropsSchema, InferBlockContent, RelationMode, CmssyModelRecord, VerifyCmssyWebhookOptions, CmssyWebhookEvent } from '@cmssy/types';
|
|
6
6
|
export { BlockMeta, BlockPropsSchema, BlockRect, BlockSchema, BuildBlockContextExtra, CmssyBlockAuthContext, CmssyBlockContext, CmssyBlockMember, CmssyBlockPage, CmssyBlockWorkspace, CmssyBranding, CmssyClientConfig, CmssyFormDefinition, CmssyFormField, CmssyFormSettings, CmssyFormSubmitResponse, CmssyLayoutGroup, CmssyLayoutSettings, CmssyLocaleContext, CmssyLocalizedValue, CmssyModelDefinition, CmssyModelRecord, CmssyPageData, CmssyPageMeta, CmssyPageSummary, CmssyRecordList, CmssySiteConfig, CmssyWebhookEvent, CmssyWebhookOrder, FieldCondition, FieldConditionGroup, FieldConditionLogic, FieldControl, FieldDefinition, FieldOptions, FieldType, InferBlockContent, LayoutPosition, RawBlock, RawLayoutBlock, SubmitFormInput, TypedField, VerifyCmssyWebhookOptions, evaluateFieldConditionGroup, layoutPositionValues } from '@cmssy/types';
|
|
@@ -17,25 +17,13 @@ type MediaValue<O> = O extends {
|
|
|
17
17
|
type RepeaterValue<O> = O extends {
|
|
18
18
|
itemSchema: infer Schema extends BlockPropsSchema;
|
|
19
19
|
} ? InferBlockContent<Schema>[] : Record<string, unknown>[];
|
|
20
|
-
/**
|
|
21
|
-
* The value a relation field holds AFTER server-side resolution: the SDK
|
|
22
|
-
* replaces the stored record id(s) with the records themselves before the
|
|
23
|
-
* component renders. `mode: "all"` and `multiple: true` yield a list; the
|
|
24
|
-
* default is a single record - `undefined` when the reference dangles, which
|
|
25
|
-
* no `required` flag can rule out.
|
|
26
|
-
*/
|
|
27
20
|
type RelationValue<O> = O extends {
|
|
28
21
|
mode: "all";
|
|
29
22
|
} | {
|
|
30
23
|
multiple: true;
|
|
31
24
|
} ? CmssyModelRecord[] : CmssyModelRecord | undefined;
|
|
32
25
|
interface RelationFieldOptions extends Omit<FieldOptions, "options" | "itemSchema"> {
|
|
33
|
-
/** Slug of the model the field points at. */
|
|
34
26
|
model: string;
|
|
35
|
-
/**
|
|
36
|
-
* "all" binds the field to every record of the model (no editor picking;
|
|
37
|
-
* `sort`/`limit` shape the list). Omit it for editor-picked record id(s).
|
|
38
|
-
*/
|
|
39
27
|
mode?: RelationMode;
|
|
40
28
|
multiple?: boolean;
|
|
41
29
|
sort?: string;
|
|
@@ -79,26 +67,9 @@ declare function isVerifiedEditUrl(url: {
|
|
|
79
67
|
draftSecret: string;
|
|
80
68
|
}): Promise<boolean>;
|
|
81
69
|
|
|
82
|
-
/**
|
|
83
|
-
* Verify + parse an inbound cmssy webhook (CMS-693 / CMS-694).
|
|
84
|
-
*
|
|
85
|
-
* cmssy signs each delivery with HMAC-SHA256 over `${timestamp}.${body}`
|
|
86
|
-
* and sends the result in the `X-Cmssy-Signature: t=<ms>,v1=<hex>`
|
|
87
|
-
* header, plus a unique `X-Cmssy-Webhook-Id`. This helper recomputes the
|
|
88
|
-
* signature (timing-safe compare), rejects stale timestamps to bound
|
|
89
|
-
* replay, and returns the typed event.
|
|
90
|
-
*
|
|
91
|
-
* IMPORTANT: pass the RAW request body string (e.g. `await req.text()`),
|
|
92
|
-
* never a re-serialized object - the signed bytes must match exactly.
|
|
93
|
-
*/
|
|
94
70
|
declare class CmssyWebhookError extends Error {
|
|
95
71
|
constructor(message: string);
|
|
96
72
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Verify the signature + freshness and return the parsed event. Throws
|
|
99
|
-
* `CmssyWebhookError` on any failure (missing/malformed header, bad
|
|
100
|
-
* signature, stale timestamp, invalid JSON) - catch it and respond 400.
|
|
101
|
-
*/
|
|
102
73
|
declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): Promise<CmssyWebhookEvent>;
|
|
103
74
|
|
|
104
75
|
export { CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_SECRET_QUERY_PARAM, CmssyWebhookError, fields, isVerifiedEditUrl, verifyCmssyWebhook };
|
package/dist/index.js
CHANGED
|
@@ -74,11 +74,6 @@ Set the listed environment variables (e.g. in .env.local) and restart the dev se
|
|
|
74
74
|
// src/data/http.ts
|
|
75
75
|
var CmssyRequestError = class extends Error {
|
|
76
76
|
status;
|
|
77
|
-
/**
|
|
78
|
-
* How long the server asked us to wait, in ms, when it said so. Present on a
|
|
79
|
-
* 429 that carried `Retry-After`; a caller can serve stale content for that
|
|
80
|
-
* long instead of guessing.
|
|
81
|
-
*/
|
|
82
77
|
retryAfterMs;
|
|
83
78
|
constructor(message, status, retryAfterMs2) {
|
|
84
79
|
super(message);
|
|
@@ -449,8 +444,6 @@ function createCmssyClient(input) {
|
|
|
449
444
|
const client = {
|
|
450
445
|
config,
|
|
451
446
|
resolveWorkspaceId: resolveWorkspaceId2,
|
|
452
|
-
// One implementation behind both overloads: a string passes through
|
|
453
|
-
// documentText untouched, a typed document gives up its query text.
|
|
454
447
|
query: ((document2, variables = {}, options) => query(documentText(document2), variables, options)),
|
|
455
448
|
queryScoped: ((document2, variables = {}, options = {}) => queryScoped(
|
|
456
449
|
documentText(document2),
|
|
@@ -762,21 +755,24 @@ var CmssyWebhookError = class extends Error {
|
|
|
762
755
|
}
|
|
763
756
|
};
|
|
764
757
|
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
758
|
+
var MAX_SIGNATURES = 8;
|
|
765
759
|
function parseSignatureHeader(header) {
|
|
766
760
|
let timestamp = null;
|
|
767
|
-
|
|
761
|
+
const signatures = [];
|
|
768
762
|
for (const part of header.split(",")) {
|
|
769
763
|
const idx = part.indexOf("=");
|
|
770
764
|
if (idx === -1) continue;
|
|
771
765
|
const key = part.slice(0, idx).trim();
|
|
772
766
|
const value = part.slice(idx + 1).trim();
|
|
773
767
|
if (key === "t") timestamp = Number(value);
|
|
774
|
-
else if (key === "v1"
|
|
768
|
+
else if (key === "v1" && value && signatures.length < MAX_SIGNATURES) {
|
|
769
|
+
signatures.push(value);
|
|
770
|
+
}
|
|
775
771
|
}
|
|
776
|
-
if (timestamp === null || !Number.isFinite(timestamp) || !
|
|
772
|
+
if (timestamp === null || !Number.isFinite(timestamp) || !signatures.length) {
|
|
777
773
|
throw new CmssyWebhookError("Malformed X-Cmssy-Signature header");
|
|
778
774
|
}
|
|
779
|
-
return { timestamp,
|
|
775
|
+
return { timestamp, signatures };
|
|
780
776
|
}
|
|
781
777
|
function hexToBytes(hex) {
|
|
782
778
|
if (hex.length % 2 !== 0 || /[^0-9a-fA-F]/.test(hex)) return null;
|
|
@@ -790,9 +786,10 @@ function bytesToHex(bytes) {
|
|
|
790
786
|
return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
791
787
|
}
|
|
792
788
|
function timingSafeHexEqual(expectedHex, providedHex) {
|
|
789
|
+
if (expectedHex.length !== providedHex.length) return false;
|
|
793
790
|
const expected = hexToBytes(expectedHex);
|
|
794
791
|
const provided = hexToBytes(providedHex);
|
|
795
|
-
if (!expected || !provided
|
|
792
|
+
if (!expected || !provided) {
|
|
796
793
|
return false;
|
|
797
794
|
}
|
|
798
795
|
let diff = 0;
|
|
@@ -822,17 +819,28 @@ async function verifyCmssyWebhook(options) {
|
|
|
822
819
|
if (!signatureHeader) {
|
|
823
820
|
throw new CmssyWebhookError("Missing X-Cmssy-Signature header");
|
|
824
821
|
}
|
|
825
|
-
|
|
822
|
+
const candidates = typeof secret === "string" ? [secret] : Array.isArray(secret) ? secret : [];
|
|
823
|
+
if (candidates.some((value) => typeof value !== "string")) {
|
|
824
|
+
throw new CmssyWebhookError("Webhook secret must be a string");
|
|
825
|
+
}
|
|
826
|
+
const secrets = candidates.filter(Boolean);
|
|
827
|
+
if (!secrets.length) {
|
|
826
828
|
throw new CmssyWebhookError("Missing webhook secret");
|
|
827
829
|
}
|
|
828
|
-
const { timestamp,
|
|
830
|
+
const { timestamp, signatures } = parseSignatureHeader(signatureHeader);
|
|
829
831
|
const toleranceMs = (options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS) * 1e3;
|
|
830
832
|
const now = options.now ?? Date.now();
|
|
831
833
|
if (Math.abs(now - timestamp) > toleranceMs) {
|
|
832
834
|
throw new CmssyWebhookError("Webhook timestamp outside tolerance");
|
|
833
835
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
+
let matched = false;
|
|
837
|
+
for (const candidate of secrets) {
|
|
838
|
+
const expected = await hmacSha256Hex(candidate, `${timestamp}.${body}`);
|
|
839
|
+
for (const signature of signatures) {
|
|
840
|
+
if (timingSafeHexEqual(expected, signature)) matched = true;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (!matched) {
|
|
836
844
|
throw new CmssyWebhookError("Webhook signature mismatch");
|
|
837
845
|
}
|
|
838
846
|
let parsed;
|