@cmssy/core 16.7.1 → 16.9.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/index.cjs CHANGED
@@ -74,7 +74,7 @@ Set the listed environment variables (e.g. in .env.local) and restart the dev se
74
74
  }
75
75
 
76
76
  // src/version.ts
77
- var CMSSY_CORE_VERSION = "16.7.1" ;
77
+ var CMSSY_CORE_VERSION = "16.9.0" ;
78
78
  var CMSSY_USER_AGENT = `@cmssy/core/${CMSSY_CORE_VERSION}`;
79
79
 
80
80
  // src/data/http.ts
@@ -967,33 +967,7 @@ function applyCmssyCsp(response, options) {
967
967
  return response;
968
968
  }
969
969
 
970
- // src/verify-webhook.ts
971
- var CmssyWebhookError = class extends Error {
972
- constructor(message) {
973
- super(message);
974
- this.name = "CmssyWebhookError";
975
- }
976
- };
977
- var DEFAULT_TOLERANCE_SECONDS = 300;
978
- var MAX_SIGNATURES = 8;
979
- function parseSignatureHeader(header) {
980
- let timestamp = null;
981
- const signatures = [];
982
- for (const part of header.split(",")) {
983
- const idx = part.indexOf("=");
984
- if (idx === -1) continue;
985
- const key = part.slice(0, idx).trim();
986
- const value = part.slice(idx + 1).trim();
987
- if (key === "t") timestamp = Number(value);
988
- else if (key === "v1" && value && signatures.length < MAX_SIGNATURES) {
989
- signatures.push(value);
990
- }
991
- }
992
- if (timestamp === null || !Number.isFinite(timestamp) || !signatures.length) {
993
- throw new CmssyWebhookError("Malformed X-Cmssy-Signature header");
994
- }
995
- return { timestamp, signatures };
996
- }
970
+ // src/internal/hmac.ts
997
971
  function hexToBytes(hex) {
998
972
  if (hex.length % 2 !== 0 || /[^0-9a-fA-F]/.test(hex)) return null;
999
973
  const bytes = new Uint8Array(hex.length / 2);
@@ -1009,9 +983,7 @@ function timingSafeHexEqual(expectedHex, providedHex) {
1009
983
  if (expectedHex.length !== providedHex.length) return false;
1010
984
  const expected = hexToBytes(expectedHex);
1011
985
  const provided = hexToBytes(providedHex);
1012
- if (!expected || !provided) {
1013
- return false;
1014
- }
986
+ if (!expected || !provided) return false;
1015
987
  let diff = 0;
1016
988
  for (let i = 0; i < expected.length; i += 1) {
1017
989
  diff |= (expected[i] ?? 0) ^ (provided[i] ?? 0);
@@ -1034,6 +1006,71 @@ async function hmacSha256Hex(secret, message) {
1034
1006
  );
1035
1007
  return bytesToHex(new Uint8Array(signature));
1036
1008
  }
1009
+
1010
+ // src/edit-token.ts
1011
+ var CMSSY_EDIT_TOKEN_HEADER = "x-cmssy-edit-token";
1012
+ var DEFAULT_TTL_SECONDS = 12 * 60 * 60;
1013
+ var MAX_TOKEN_LENGTH = 256;
1014
+ function payload(expiresAt, page) {
1015
+ return `${expiresAt}.${page}`;
1016
+ }
1017
+ async function mintCmssyEditToken(secret, options = {}) {
1018
+ if (!secret) {
1019
+ throw new Error("cmssy: minting an edit token needs a draftSecret");
1020
+ }
1021
+ const now = options.now ?? Date.now();
1022
+ const ttl = options.ttlSeconds ?? DEFAULT_TTL_SECONDS;
1023
+ const expiresAt = now + ttl * 1e3;
1024
+ const signature = await hmacSha256Hex(
1025
+ secret,
1026
+ payload(expiresAt, options.page ?? "")
1027
+ );
1028
+ return `${expiresAt}.${signature}`;
1029
+ }
1030
+ async function verifyCmssyEditToken(token, secret, options = {}) {
1031
+ if (!token || !secret) return false;
1032
+ if (token.length > MAX_TOKEN_LENGTH) return false;
1033
+ const separator = token.indexOf(".");
1034
+ if (separator === -1) return false;
1035
+ const expiresAt = Number(token.slice(0, separator));
1036
+ const provided = token.slice(separator + 1);
1037
+ if (!Number.isSafeInteger(expiresAt) || !provided) return false;
1038
+ const now = options.now ?? Date.now();
1039
+ if (expiresAt <= now) return false;
1040
+ const expected = await hmacSha256Hex(
1041
+ secret,
1042
+ payload(expiresAt, options.page ?? "")
1043
+ );
1044
+ return timingSafeHexEqual(expected, provided);
1045
+ }
1046
+
1047
+ // src/verify-webhook.ts
1048
+ var CmssyWebhookError = class extends Error {
1049
+ constructor(message) {
1050
+ super(message);
1051
+ this.name = "CmssyWebhookError";
1052
+ }
1053
+ };
1054
+ var DEFAULT_TOLERANCE_SECONDS = 300;
1055
+ var MAX_SIGNATURES = 8;
1056
+ function parseSignatureHeader(header) {
1057
+ let timestamp = null;
1058
+ const signatures = [];
1059
+ for (const part of header.split(",")) {
1060
+ const idx = part.indexOf("=");
1061
+ if (idx === -1) continue;
1062
+ const key = part.slice(0, idx).trim();
1063
+ const value = part.slice(idx + 1).trim();
1064
+ if (key === "t") timestamp = Number(value);
1065
+ else if (key === "v1" && value && signatures.length < MAX_SIGNATURES) {
1066
+ signatures.push(value);
1067
+ }
1068
+ }
1069
+ if (timestamp === null || !Number.isFinite(timestamp) || !signatures.length) {
1070
+ throw new CmssyWebhookError("Malformed X-Cmssy-Signature header");
1071
+ }
1072
+ return { timestamp, signatures };
1073
+ }
1037
1074
  async function verifyCmssyWebhook(options) {
1038
1075
  const { body, signatureHeader, secret } = options;
1039
1076
  if (!signatureHeader) {
@@ -1078,6 +1115,7 @@ Object.defineProperty(exports, "evaluateFieldConditionGroup", {
1078
1115
  });
1079
1116
  exports.CMSSY_EDIT_HEADER = CMSSY_EDIT_HEADER;
1080
1117
  exports.CMSSY_EDIT_QUERY_PARAM = CMSSY_EDIT_QUERY_PARAM;
1118
+ exports.CMSSY_EDIT_TOKEN_HEADER = CMSSY_EDIT_TOKEN_HEADER;
1081
1119
  exports.CMSSY_LOCALE_HEADER = CMSSY_LOCALE_HEADER;
1082
1120
  exports.CMSSY_RATE_LIMIT_WINDOW_MS = CMSSY_RATE_LIMIT_WINDOW_MS;
1083
1121
  exports.CMSSY_RETRY_MODES = CMSSY_RETRY_MODES;
@@ -1104,6 +1142,7 @@ exports.localizeHref = localizeHref;
1104
1142
  exports.mediaAlt = mediaAlt;
1105
1143
  exports.mediaUrl = mediaUrl;
1106
1144
  exports.mediaUrls = mediaUrls;
1145
+ exports.mintCmssyEditToken = mintCmssyEditToken;
1107
1146
  exports.normalizeOrigin = normalizeOrigin;
1108
1147
  exports.parseEditorMessage = parseEditorMessage;
1109
1148
  exports.postToEditor = postToEditor;
@@ -1111,4 +1150,5 @@ exports.propsToSchema = propsToSchema;
1111
1150
  exports.registryToManifestBlocks = registryToManifestBlocks;
1112
1151
  exports.resolveCmssyLocale = resolveCmssyLocale;
1113
1152
  exports.resolveEditorOrigin = resolveEditorOrigin;
1153
+ exports.verifyCmssyEditToken = verifyCmssyEditToken;
1114
1154
  exports.verifyCmssyWebhook = verifyCmssyWebhook;
package/dist/index.d.cts CHANGED
@@ -26,7 +26,9 @@ interface RelationFieldOptions extends BaseFieldOptions {
26
26
  sort?: string;
27
27
  limit?: number;
28
28
  }
29
- type Only<O, Shape> = Shape & Record<Exclude<keyof O, keyof Shape>, never>;
29
+ type Only<O, Shape, Field extends string> = Shape & {
30
+ [Key in Exclude<keyof O, keyof Shape>]: `fields.${Field} has no option: ${Key & string}`;
31
+ };
30
32
  type OptionValue<O> = O extends {
31
33
  options: readonly (infer Option extends string)[];
32
34
  } ? Option : string;
@@ -50,28 +52,28 @@ type PageSelectorValue<O> = O extends {
50
52
  multiple: false;
51
53
  } ? PageRef | undefined : PageRef[];
52
54
  declare const fields: {
53
- text: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
54
- textarea: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
55
- richText: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
56
- markdown: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
57
- number: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<number, Declared<O>>;
58
- date: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
59
- datetime: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
60
- boolean: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<boolean, Declared<O>>;
61
- color: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
62
- link: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
63
- url: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
64
- email: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
65
- json: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<_cmssy_types.JsonValue, Declared<O>>;
66
- form: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
67
- table: <const O extends Only<O, TableFieldOptions>>(opts?: O) => TypedField<FieldTypeValueMap["table"], Declared<O>>;
68
- select: <const O extends Only<O, ChoiceFieldOptions>>(opts: O) => TypedField<OptionValue<O>, Declared<O>>;
69
- radio: <const O extends Only<O, ChoiceFieldOptions>>(opts: O) => TypedField<OptionValue<O>, Declared<O>>;
70
- multiselect: <const O extends Only<O, ChoiceFieldOptions>>(opts: O) => TypedField<OptionValue<O>[], Declared<O>>;
71
- media: <const O extends Only<O, MediaFieldOptions>>(opts?: O) => TypedField<MediaValue<O>, Declared<O>>;
72
- pageSelector: <const O extends Only<O, PageSelectorFieldOptions>>(opts?: O) => TypedField<PageSelectorValue<O>, Declared<O>>;
73
- repeater: <const O extends Only<O, RepeaterFieldOptions>>(opts: O) => TypedField<RepeaterValue<O>, Declared<O>>;
74
- relation: <const O extends Only<O, RelationFieldOptions>>(opts: O) => TypedField<RelationValue<O>, Declared<O>>;
55
+ text: <const O extends Only<O, BaseFieldOptions, "text">>(opts?: O) => TypedField<string, Declared<O>>;
56
+ textarea: <const O extends Only<O, BaseFieldOptions, "textarea">>(opts?: O) => TypedField<string, Declared<O>>;
57
+ richText: <const O extends Only<O, BaseFieldOptions, "richText">>(opts?: O) => TypedField<string, Declared<O>>;
58
+ markdown: <const O extends Only<O, BaseFieldOptions, "markdown">>(opts?: O) => TypedField<string, Declared<O>>;
59
+ number: <const O extends Only<O, BaseFieldOptions, "number">>(opts?: O) => TypedField<number, Declared<O>>;
60
+ date: <const O extends Only<O, BaseFieldOptions, "date">>(opts?: O) => TypedField<string, Declared<O>>;
61
+ datetime: <const O extends Only<O, BaseFieldOptions, "datetime">>(opts?: O) => TypedField<string, Declared<O>>;
62
+ boolean: <const O extends Only<O, BaseFieldOptions, "boolean">>(opts?: O) => TypedField<boolean, Declared<O>>;
63
+ color: <const O extends Only<O, BaseFieldOptions, "color">>(opts?: O) => TypedField<string, Declared<O>>;
64
+ link: <const O extends Only<O, BaseFieldOptions, "link">>(opts?: O) => TypedField<string, Declared<O>>;
65
+ url: <const O extends Only<O, BaseFieldOptions, "url">>(opts?: O) => TypedField<string, Declared<O>>;
66
+ email: <const O extends Only<O, BaseFieldOptions, "email">>(opts?: O) => TypedField<string, Declared<O>>;
67
+ json: <const O extends Only<O, BaseFieldOptions, "json">>(opts?: O) => TypedField<_cmssy_types.JsonValue, Declared<O>>;
68
+ form: <const O extends Only<O, BaseFieldOptions, "form">>(opts?: O) => TypedField<string, Declared<O>>;
69
+ table: <const O extends Only<O, TableFieldOptions, "table">>(opts?: O) => TypedField<FieldTypeValueMap["table"], Declared<O>>;
70
+ select: <const O extends Only<O, ChoiceFieldOptions, "select">>(opts: O) => TypedField<OptionValue<O>, Declared<O>>;
71
+ radio: <const O extends Only<O, ChoiceFieldOptions, "radio">>(opts: O) => TypedField<OptionValue<O>, Declared<O>>;
72
+ multiselect: <const O extends Only<O, ChoiceFieldOptions, "multiselect">>(opts: O) => TypedField<OptionValue<O>[], Declared<O>>;
73
+ media: <const O extends Only<O, MediaFieldOptions, "media">>(opts?: O) => TypedField<MediaValue<O>, Declared<O>>;
74
+ pageSelector: <const O extends Only<O, PageSelectorFieldOptions, "pageSelector">>(opts?: O) => TypedField<PageSelectorValue<O>, Declared<O>>;
75
+ repeater: <const O extends Only<O, RepeaterFieldOptions, "repeater">>(opts: O) => TypedField<RepeaterValue<O>, Declared<O>>;
76
+ relation: <const O extends Only<O, RelationFieldOptions, "relation">>(opts: O) => TypedField<RelationValue<O>, Declared<O>>;
75
77
  };
76
78
 
77
79
  /**
@@ -133,9 +135,22 @@ declare function isVerifiedEditUrl(url: {
133
135
  draftSecret: string;
134
136
  }): Promise<boolean>;
135
137
 
138
+ declare const CMSSY_EDIT_TOKEN_HEADER = "x-cmssy-edit-token";
139
+ interface MintCmssyEditTokenOptions {
140
+ page?: string;
141
+ ttlSeconds?: number;
142
+ now?: number;
143
+ }
144
+ declare function mintCmssyEditToken(secret: string, options?: MintCmssyEditTokenOptions): Promise<string>;
145
+ interface VerifyCmssyEditTokenOptions {
146
+ page?: string;
147
+ now?: number;
148
+ }
149
+ declare function verifyCmssyEditToken(token: string | null | undefined, secret: string, options?: VerifyCmssyEditTokenOptions): Promise<boolean>;
150
+
136
151
  declare class CmssyWebhookError extends Error {
137
152
  constructor(message: string);
138
153
  }
139
154
  declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): Promise<CmssyWebhookEvent>;
140
155
 
141
- export { type BlockManifest, type BlockManifestBlock, type BlockManifestSource, CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_SECRET_QUERY_PARAM, CmssyWebhookError, type MediaLike, blocksToMeta, blocksToSchemas, buildBlockManifest, fields, isVerifiedEditUrl, layoutRegionsToBridge, mediaAlt, mediaUrl, mediaUrls, propsToSchema, registryToManifestBlocks, verifyCmssyWebhook };
156
+ export { type BlockManifest, type BlockManifestBlock, type BlockManifestSource, CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_EDIT_TOKEN_HEADER, CMSSY_SECRET_QUERY_PARAM, CmssyWebhookError, type MediaLike, type MintCmssyEditTokenOptions, type VerifyCmssyEditTokenOptions, blocksToMeta, blocksToSchemas, buildBlockManifest, fields, isVerifiedEditUrl, layoutRegionsToBridge, mediaAlt, mediaUrl, mediaUrls, mintCmssyEditToken, propsToSchema, registryToManifestBlocks, verifyCmssyEditToken, verifyCmssyWebhook };
package/dist/index.d.ts CHANGED
@@ -26,7 +26,9 @@ interface RelationFieldOptions extends BaseFieldOptions {
26
26
  sort?: string;
27
27
  limit?: number;
28
28
  }
29
- type Only<O, Shape> = Shape & Record<Exclude<keyof O, keyof Shape>, never>;
29
+ type Only<O, Shape, Field extends string> = Shape & {
30
+ [Key in Exclude<keyof O, keyof Shape>]: `fields.${Field} has no option: ${Key & string}`;
31
+ };
30
32
  type OptionValue<O> = O extends {
31
33
  options: readonly (infer Option extends string)[];
32
34
  } ? Option : string;
@@ -50,28 +52,28 @@ type PageSelectorValue<O> = O extends {
50
52
  multiple: false;
51
53
  } ? PageRef | undefined : PageRef[];
52
54
  declare const fields: {
53
- text: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
54
- textarea: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
55
- richText: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
56
- markdown: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
57
- number: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<number, Declared<O>>;
58
- date: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
59
- datetime: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
60
- boolean: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<boolean, Declared<O>>;
61
- color: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
62
- link: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
63
- url: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
64
- email: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
65
- json: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<_cmssy_types.JsonValue, Declared<O>>;
66
- form: <const O extends Only<O, BaseFieldOptions>>(opts?: O) => TypedField<string, Declared<O>>;
67
- table: <const O extends Only<O, TableFieldOptions>>(opts?: O) => TypedField<FieldTypeValueMap["table"], Declared<O>>;
68
- select: <const O extends Only<O, ChoiceFieldOptions>>(opts: O) => TypedField<OptionValue<O>, Declared<O>>;
69
- radio: <const O extends Only<O, ChoiceFieldOptions>>(opts: O) => TypedField<OptionValue<O>, Declared<O>>;
70
- multiselect: <const O extends Only<O, ChoiceFieldOptions>>(opts: O) => TypedField<OptionValue<O>[], Declared<O>>;
71
- media: <const O extends Only<O, MediaFieldOptions>>(opts?: O) => TypedField<MediaValue<O>, Declared<O>>;
72
- pageSelector: <const O extends Only<O, PageSelectorFieldOptions>>(opts?: O) => TypedField<PageSelectorValue<O>, Declared<O>>;
73
- repeater: <const O extends Only<O, RepeaterFieldOptions>>(opts: O) => TypedField<RepeaterValue<O>, Declared<O>>;
74
- relation: <const O extends Only<O, RelationFieldOptions>>(opts: O) => TypedField<RelationValue<O>, Declared<O>>;
55
+ text: <const O extends Only<O, BaseFieldOptions, "text">>(opts?: O) => TypedField<string, Declared<O>>;
56
+ textarea: <const O extends Only<O, BaseFieldOptions, "textarea">>(opts?: O) => TypedField<string, Declared<O>>;
57
+ richText: <const O extends Only<O, BaseFieldOptions, "richText">>(opts?: O) => TypedField<string, Declared<O>>;
58
+ markdown: <const O extends Only<O, BaseFieldOptions, "markdown">>(opts?: O) => TypedField<string, Declared<O>>;
59
+ number: <const O extends Only<O, BaseFieldOptions, "number">>(opts?: O) => TypedField<number, Declared<O>>;
60
+ date: <const O extends Only<O, BaseFieldOptions, "date">>(opts?: O) => TypedField<string, Declared<O>>;
61
+ datetime: <const O extends Only<O, BaseFieldOptions, "datetime">>(opts?: O) => TypedField<string, Declared<O>>;
62
+ boolean: <const O extends Only<O, BaseFieldOptions, "boolean">>(opts?: O) => TypedField<boolean, Declared<O>>;
63
+ color: <const O extends Only<O, BaseFieldOptions, "color">>(opts?: O) => TypedField<string, Declared<O>>;
64
+ link: <const O extends Only<O, BaseFieldOptions, "link">>(opts?: O) => TypedField<string, Declared<O>>;
65
+ url: <const O extends Only<O, BaseFieldOptions, "url">>(opts?: O) => TypedField<string, Declared<O>>;
66
+ email: <const O extends Only<O, BaseFieldOptions, "email">>(opts?: O) => TypedField<string, Declared<O>>;
67
+ json: <const O extends Only<O, BaseFieldOptions, "json">>(opts?: O) => TypedField<_cmssy_types.JsonValue, Declared<O>>;
68
+ form: <const O extends Only<O, BaseFieldOptions, "form">>(opts?: O) => TypedField<string, Declared<O>>;
69
+ table: <const O extends Only<O, TableFieldOptions, "table">>(opts?: O) => TypedField<FieldTypeValueMap["table"], Declared<O>>;
70
+ select: <const O extends Only<O, ChoiceFieldOptions, "select">>(opts: O) => TypedField<OptionValue<O>, Declared<O>>;
71
+ radio: <const O extends Only<O, ChoiceFieldOptions, "radio">>(opts: O) => TypedField<OptionValue<O>, Declared<O>>;
72
+ multiselect: <const O extends Only<O, ChoiceFieldOptions, "multiselect">>(opts: O) => TypedField<OptionValue<O>[], Declared<O>>;
73
+ media: <const O extends Only<O, MediaFieldOptions, "media">>(opts?: O) => TypedField<MediaValue<O>, Declared<O>>;
74
+ pageSelector: <const O extends Only<O, PageSelectorFieldOptions, "pageSelector">>(opts?: O) => TypedField<PageSelectorValue<O>, Declared<O>>;
75
+ repeater: <const O extends Only<O, RepeaterFieldOptions, "repeater">>(opts: O) => TypedField<RepeaterValue<O>, Declared<O>>;
76
+ relation: <const O extends Only<O, RelationFieldOptions, "relation">>(opts: O) => TypedField<RelationValue<O>, Declared<O>>;
75
77
  };
76
78
 
77
79
  /**
@@ -133,9 +135,22 @@ declare function isVerifiedEditUrl(url: {
133
135
  draftSecret: string;
134
136
  }): Promise<boolean>;
135
137
 
138
+ declare const CMSSY_EDIT_TOKEN_HEADER = "x-cmssy-edit-token";
139
+ interface MintCmssyEditTokenOptions {
140
+ page?: string;
141
+ ttlSeconds?: number;
142
+ now?: number;
143
+ }
144
+ declare function mintCmssyEditToken(secret: string, options?: MintCmssyEditTokenOptions): Promise<string>;
145
+ interface VerifyCmssyEditTokenOptions {
146
+ page?: string;
147
+ now?: number;
148
+ }
149
+ declare function verifyCmssyEditToken(token: string | null | undefined, secret: string, options?: VerifyCmssyEditTokenOptions): Promise<boolean>;
150
+
136
151
  declare class CmssyWebhookError extends Error {
137
152
  constructor(message: string);
138
153
  }
139
154
  declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): Promise<CmssyWebhookEvent>;
140
155
 
141
- export { type BlockManifest, type BlockManifestBlock, type BlockManifestSource, CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_SECRET_QUERY_PARAM, CmssyWebhookError, type MediaLike, blocksToMeta, blocksToSchemas, buildBlockManifest, fields, isVerifiedEditUrl, layoutRegionsToBridge, mediaAlt, mediaUrl, mediaUrls, propsToSchema, registryToManifestBlocks, verifyCmssyWebhook };
156
+ export { type BlockManifest, type BlockManifestBlock, type BlockManifestSource, CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_EDIT_TOKEN_HEADER, CMSSY_SECRET_QUERY_PARAM, CmssyWebhookError, type MediaLike, type MintCmssyEditTokenOptions, type VerifyCmssyEditTokenOptions, blocksToMeta, blocksToSchemas, buildBlockManifest, fields, isVerifiedEditUrl, layoutRegionsToBridge, mediaAlt, mediaUrl, mediaUrls, mintCmssyEditToken, propsToSchema, registryToManifestBlocks, verifyCmssyEditToken, verifyCmssyWebhook };
package/dist/index.js CHANGED
@@ -73,7 +73,7 @@ Set the listed environment variables (e.g. in .env.local) and restart the dev se
73
73
  }
74
74
 
75
75
  // src/version.ts
76
- var CMSSY_CORE_VERSION = "16.7.1" ;
76
+ var CMSSY_CORE_VERSION = "16.9.0" ;
77
77
  var CMSSY_USER_AGENT = `@cmssy/core/${CMSSY_CORE_VERSION}`;
78
78
 
79
79
  // src/data/http.ts
@@ -966,33 +966,7 @@ function applyCmssyCsp(response, options) {
966
966
  return response;
967
967
  }
968
968
 
969
- // src/verify-webhook.ts
970
- var CmssyWebhookError = class extends Error {
971
- constructor(message) {
972
- super(message);
973
- this.name = "CmssyWebhookError";
974
- }
975
- };
976
- var DEFAULT_TOLERANCE_SECONDS = 300;
977
- var MAX_SIGNATURES = 8;
978
- function parseSignatureHeader(header) {
979
- let timestamp = null;
980
- const signatures = [];
981
- for (const part of header.split(",")) {
982
- const idx = part.indexOf("=");
983
- if (idx === -1) continue;
984
- const key = part.slice(0, idx).trim();
985
- const value = part.slice(idx + 1).trim();
986
- if (key === "t") timestamp = Number(value);
987
- else if (key === "v1" && value && signatures.length < MAX_SIGNATURES) {
988
- signatures.push(value);
989
- }
990
- }
991
- if (timestamp === null || !Number.isFinite(timestamp) || !signatures.length) {
992
- throw new CmssyWebhookError("Malformed X-Cmssy-Signature header");
993
- }
994
- return { timestamp, signatures };
995
- }
969
+ // src/internal/hmac.ts
996
970
  function hexToBytes(hex) {
997
971
  if (hex.length % 2 !== 0 || /[^0-9a-fA-F]/.test(hex)) return null;
998
972
  const bytes = new Uint8Array(hex.length / 2);
@@ -1008,9 +982,7 @@ function timingSafeHexEqual(expectedHex, providedHex) {
1008
982
  if (expectedHex.length !== providedHex.length) return false;
1009
983
  const expected = hexToBytes(expectedHex);
1010
984
  const provided = hexToBytes(providedHex);
1011
- if (!expected || !provided) {
1012
- return false;
1013
- }
985
+ if (!expected || !provided) return false;
1014
986
  let diff = 0;
1015
987
  for (let i = 0; i < expected.length; i += 1) {
1016
988
  diff |= (expected[i] ?? 0) ^ (provided[i] ?? 0);
@@ -1033,6 +1005,71 @@ async function hmacSha256Hex(secret, message) {
1033
1005
  );
1034
1006
  return bytesToHex(new Uint8Array(signature));
1035
1007
  }
1008
+
1009
+ // src/edit-token.ts
1010
+ var CMSSY_EDIT_TOKEN_HEADER = "x-cmssy-edit-token";
1011
+ var DEFAULT_TTL_SECONDS = 12 * 60 * 60;
1012
+ var MAX_TOKEN_LENGTH = 256;
1013
+ function payload(expiresAt, page) {
1014
+ return `${expiresAt}.${page}`;
1015
+ }
1016
+ async function mintCmssyEditToken(secret, options = {}) {
1017
+ if (!secret) {
1018
+ throw new Error("cmssy: minting an edit token needs a draftSecret");
1019
+ }
1020
+ const now = options.now ?? Date.now();
1021
+ const ttl = options.ttlSeconds ?? DEFAULT_TTL_SECONDS;
1022
+ const expiresAt = now + ttl * 1e3;
1023
+ const signature = await hmacSha256Hex(
1024
+ secret,
1025
+ payload(expiresAt, options.page ?? "")
1026
+ );
1027
+ return `${expiresAt}.${signature}`;
1028
+ }
1029
+ async function verifyCmssyEditToken(token, secret, options = {}) {
1030
+ if (!token || !secret) return false;
1031
+ if (token.length > MAX_TOKEN_LENGTH) return false;
1032
+ const separator = token.indexOf(".");
1033
+ if (separator === -1) return false;
1034
+ const expiresAt = Number(token.slice(0, separator));
1035
+ const provided = token.slice(separator + 1);
1036
+ if (!Number.isSafeInteger(expiresAt) || !provided) return false;
1037
+ const now = options.now ?? Date.now();
1038
+ if (expiresAt <= now) return false;
1039
+ const expected = await hmacSha256Hex(
1040
+ secret,
1041
+ payload(expiresAt, options.page ?? "")
1042
+ );
1043
+ return timingSafeHexEqual(expected, provided);
1044
+ }
1045
+
1046
+ // src/verify-webhook.ts
1047
+ var CmssyWebhookError = class extends Error {
1048
+ constructor(message) {
1049
+ super(message);
1050
+ this.name = "CmssyWebhookError";
1051
+ }
1052
+ };
1053
+ var DEFAULT_TOLERANCE_SECONDS = 300;
1054
+ var MAX_SIGNATURES = 8;
1055
+ function parseSignatureHeader(header) {
1056
+ let timestamp = null;
1057
+ const signatures = [];
1058
+ for (const part of header.split(",")) {
1059
+ const idx = part.indexOf("=");
1060
+ if (idx === -1) continue;
1061
+ const key = part.slice(0, idx).trim();
1062
+ const value = part.slice(idx + 1).trim();
1063
+ if (key === "t") timestamp = Number(value);
1064
+ else if (key === "v1" && value && signatures.length < MAX_SIGNATURES) {
1065
+ signatures.push(value);
1066
+ }
1067
+ }
1068
+ if (timestamp === null || !Number.isFinite(timestamp) || !signatures.length) {
1069
+ throw new CmssyWebhookError("Malformed X-Cmssy-Signature header");
1070
+ }
1071
+ return { timestamp, signatures };
1072
+ }
1036
1073
  async function verifyCmssyWebhook(options) {
1037
1074
  const { body, signatureHeader, secret } = options;
1038
1075
  if (!signatureHeader) {
@@ -1071,4 +1108,4 @@ async function verifyCmssyWebhook(options) {
1071
1108
  return parsed;
1072
1109
  }
1073
1110
 
1074
- export { CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_RATE_LIMIT_WINDOW_MS, CMSSY_RETRY_MODES, CMSSY_SECRET_QUERY_PARAM, CmssyRequestError, CmssyWebhookError, DEFAULT_CMSSY_API_URL, DEFAULT_CMSSY_EDITOR_ORIGINS, PROTOCOL_VERSION, SHORTCUT_ACTIONS, applyCmssyCsp, blocksToMeta, blocksToSchemas, buildBlockManifest, createCmssyClient, defineCmssyConfig, defineCmssyLayout, fields, graphqlRequest, isProtocolCompatible, isVerifiedEditUrl, layoutRegionsToBridge, localizeHref, mediaAlt, mediaUrl, mediaUrls, normalizeOrigin, parseEditorMessage, postToEditor, propsToSchema, registryToManifestBlocks, resolveCmssyLocale, resolveEditorOrigin, verifyCmssyWebhook };
1111
+ export { CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM, CMSSY_EDIT_TOKEN_HEADER, CMSSY_LOCALE_HEADER, CMSSY_RATE_LIMIT_WINDOW_MS, CMSSY_RETRY_MODES, CMSSY_SECRET_QUERY_PARAM, CmssyRequestError, CmssyWebhookError, DEFAULT_CMSSY_API_URL, DEFAULT_CMSSY_EDITOR_ORIGINS, PROTOCOL_VERSION, SHORTCUT_ACTIONS, applyCmssyCsp, blocksToMeta, blocksToSchemas, buildBlockManifest, createCmssyClient, defineCmssyConfig, defineCmssyLayout, fields, graphqlRequest, isProtocolCompatible, isVerifiedEditUrl, layoutRegionsToBridge, localizeHref, mediaAlt, mediaUrl, mediaUrls, mintCmssyEditToken, normalizeOrigin, parseEditorMessage, postToEditor, propsToSchema, registryToManifestBlocks, resolveCmssyLocale, resolveEditorOrigin, verifyCmssyEditToken, verifyCmssyWebhook };
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  // src/version.ts
4
- var CMSSY_CORE_VERSION = "16.7.1" ;
4
+ var CMSSY_CORE_VERSION = "16.9.0" ;
5
5
  var CMSSY_USER_AGENT = `@cmssy/core/${CMSSY_CORE_VERSION}`;
6
6
 
7
7
  // src/data/http.ts
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var CMSSY_CORE_VERSION = "16.7.1" ;
2
+ var CMSSY_CORE_VERSION = "16.9.0" ;
3
3
  var CMSSY_USER_AGENT = `@cmssy/core/${CMSSY_CORE_VERSION}`;
4
4
 
5
5
  // src/data/http.ts
package/dist/internal.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
  var types = require('@cmssy/types');
4
4
 
5
5
  // src/version.ts
6
- var CMSSY_CORE_VERSION = "16.7.1" ;
6
+ var CMSSY_CORE_VERSION = "16.9.0" ;
7
7
  var CMSSY_USER_AGENT = `@cmssy/core/${CMSSY_CORE_VERSION}`;
8
8
 
9
9
  // src/data/http.ts
package/dist/internal.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { DEFAULT_LAYOUT_REGIONS } from '@cmssy/types';
2
2
 
3
3
  // src/version.ts
4
- var CMSSY_CORE_VERSION = "16.7.1" ;
4
+ var CMSSY_CORE_VERSION = "16.9.0" ;
5
5
  var CMSSY_USER_AGENT = `@cmssy/core/${CMSSY_CORE_VERSION}`;
6
6
 
7
7
  // src/data/http.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmssy/core",
3
- "version": "16.7.1",
3
+ "version": "16.9.0",
4
4
  "description": "Framework-agnostic cmssy gateway: GraphQL client, block/field system, editor protocol. No React, no Next.",
5
5
  "keywords": [
6
6
  "cmssy",