@ryusenkei/maezae 0.1.0 → 0.2.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/README.md +12 -0
- package/dist/body.d.ts +2 -2
- package/dist/body.js +168 -34
- package/dist/delivery.d.ts +2 -2
- package/dist/{index-D11fUsoi.d.ts → index-C8duwjns.d.ts} +29 -3
- package/dist/{index-57nirXxn.d.ts → index-CLDe_wBc.d.ts} +7 -2
- package/dist/integrations.d.ts +72 -0
- package/dist/integrations.js +6 -0
- package/dist/model.d.ts +82 -2
- package/dist/model.js +339 -1
- package/dist/preview.d.ts +2 -44
- package/dist/preview.js +1 -103
- package/dist/signed-B_58gV21.d.ts +44 -0
- package/dist/src-BrLwIWaT.js +7 -0
- package/dist/src-CA_67dWy.js +103 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ maezae の SDK(導入先のサイト向け)
|
|
|
10
10
|
| `@ryusenkei/maezae/body` | 本文のブロックの型と描画の補助 |
|
|
11
11
|
| `@ryusenkei/maezae/model` | コンテンツスキーマの型・ページのパス・画像の切り抜き |
|
|
12
12
|
| `@ryusenkei/maezae/preview` | プレビューと下書きの取得の署名(ヘッダー `x-preview-signature`) |
|
|
13
|
+
| `@ryusenkei/maezae/integrations` | 外の関数・Webhook が受け取る本文の型と署名の検証(ヘッダー `x-cms-signature`) |
|
|
13
14
|
|
|
14
15
|
## 取得(Cloudflare Workers の service binding)
|
|
15
16
|
|
|
@@ -35,3 +36,14 @@ import { SIGNED_HEADERS, verifyRequest } from '@ryusenkei/maezae/preview';
|
|
|
35
36
|
|
|
36
37
|
const isPreview = await verifyRequest(env.PREVIEW_TOKEN, request, { header: SIGNED_HEADERS.preview });
|
|
37
38
|
```
|
|
39
|
+
|
|
40
|
+
## 外の関数・Webhook の受け手
|
|
41
|
+
|
|
42
|
+
- 鍵: 媒体の管理画面の「設定 → 連携」の登録ごとの署名の鍵
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { type PublishCheckRequest, SIGNED_HEADERS, verifyRequest } from '@ryusenkei/maezae/integrations';
|
|
46
|
+
|
|
47
|
+
if (!(await verifyRequest(env.CMS_FUNCTION_SECRET, request, { header: SIGNED_HEADERS.outbound }))) return new Response(null, { status: 401 });
|
|
48
|
+
const body = (await request.json()) as PublishCheckRequest;
|
|
49
|
+
```
|
package/dist/body.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { _ as Rich, a as PropType, c as Body, d as Inline, f as InlineComponent, g as Mark, h as ListItem, i as PropSchema, l as ComponentBlock, m as List, o as PropsSchema, p as Link, r as DeclaredComponent, s as Block, t as blockSchemaOf, u as HeadingLevel, v as StandardBlock, y as Text } from "./index-C8duwjns.js";
|
|
2
2
|
/** meta description の長さの上限(検索結果で省略されにくい字数の目安) */
|
|
3
3
|
export declare const DESCRIPTION_MAX = 120;
|
|
4
4
|
/**
|
|
@@ -21,4 +21,4 @@ export declare const readMinOf: <M>(body: Body<M>, speed?: number) => number;
|
|
|
21
21
|
export declare const linksOf: <M>(body: Body<M>) => string[];
|
|
22
22
|
/** 本文のリンク先として許可する形か(保存の検査とサイトの出力で共通) */
|
|
23
23
|
export declare const isSafeHref: (href: string) => boolean;
|
|
24
|
-
export { type Block, type Body, type ComponentBlock, type HeadingLevel, type Inline, type InlineComponent, type Link, type List, type ListItem, type Mark, type Rich, type StandardBlock, type Text, blockSchemaOf };
|
|
24
|
+
export { type Block, type Body, type ComponentBlock, type DeclaredComponent, type HeadingLevel, type Inline, type InlineComponent, type Link, type List, type ListItem, type Mark, type PropSchema, type PropType, type PropsSchema, type Rich, type StandardBlock, type Text, blockSchemaOf };
|
package/dist/body.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
// ../body/src/
|
|
3
|
-
/**
|
|
4
|
-
const
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const head = [...excerpt].slice(0, max).join("");
|
|
13
|
-
const end = head.lastIndexOf("。");
|
|
14
|
-
return end > 0 ? head.slice(0, end + 1) : `${[...head].slice(0, max - 1).join("")}…`;
|
|
15
|
-
}
|
|
2
|
+
// ../body/src/components.ts
|
|
3
|
+
/** 予約の名前(ブロック) */
|
|
4
|
+
const RESERVED_BLOCK_NAMES = /* @__PURE__ */ new Set(["block"]);
|
|
5
|
+
/** 予約の名前(インライン) */
|
|
6
|
+
const RESERVED_INLINE_NAMES = /* @__PURE__ */ new Set([
|
|
7
|
+
"mark",
|
|
8
|
+
"sup",
|
|
9
|
+
"sub",
|
|
10
|
+
"link"
|
|
11
|
+
]);
|
|
16
12
|
// ../body/src/library.ts
|
|
17
13
|
/** 注のコンポーネントの名前(本文からの算出 citesOf が参照) */
|
|
18
14
|
const CITE = "cite";
|
|
@@ -231,6 +227,125 @@ const CORE_COMPONENTS = [
|
|
|
231
227
|
];
|
|
232
228
|
/** 名前から核のコンポーネント(なければ undefined) */
|
|
233
229
|
const coreComponentOf = (name) => CORE_COMPONENTS.find((c) => c.name === name);
|
|
230
|
+
// ../body/src/declared.ts
|
|
231
|
+
const NAME = /^[A-Za-z][A-Za-z0-9-]*$/;
|
|
232
|
+
const KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
233
|
+
const propSchema = z.lazy(() => z.object({
|
|
234
|
+
type: z.enum([
|
|
235
|
+
"string",
|
|
236
|
+
"number",
|
|
237
|
+
"integer",
|
|
238
|
+
"boolean",
|
|
239
|
+
"array",
|
|
240
|
+
"object"
|
|
241
|
+
]),
|
|
242
|
+
title: z.string().min(1).optional(),
|
|
243
|
+
description: z.string().optional(),
|
|
244
|
+
enum: z.array(z.string().min(1)).min(1).optional(),
|
|
245
|
+
items: propSchema.optional(),
|
|
246
|
+
properties: z.record(z.string().regex(KEY, "引数の名前は英字で始まる英数字"), propSchema).optional(),
|
|
247
|
+
required: z.array(z.string()).optional()
|
|
248
|
+
}).strict().superRefine((p, ctx) => {
|
|
249
|
+
if (p.enum && p.type !== "string") ctx.addIssue({
|
|
250
|
+
code: "custom",
|
|
251
|
+
message: "選択肢 enum は文字列の引数だけ",
|
|
252
|
+
path: ["enum"]
|
|
253
|
+
});
|
|
254
|
+
if (p.type === "array" !== (p.items !== void 0)) ctx.addIssue({
|
|
255
|
+
code: "custom",
|
|
256
|
+
message: "配列の引数は items が必要(ほかの型は不可)",
|
|
257
|
+
path: ["items"]
|
|
258
|
+
});
|
|
259
|
+
if ((p.properties || p.required) && p.type !== "object") ctx.addIssue({
|
|
260
|
+
code: "custom",
|
|
261
|
+
message: "properties・required はオブジェクトの引数だけ",
|
|
262
|
+
path: ["properties"]
|
|
263
|
+
});
|
|
264
|
+
for (const key of p.required ?? []) if (!p.properties?.[key]) ctx.addIssue({
|
|
265
|
+
code: "custom",
|
|
266
|
+
message: `必須の引数 ${key} が properties にない`,
|
|
267
|
+
path: ["required"]
|
|
268
|
+
});
|
|
269
|
+
}));
|
|
270
|
+
const declaredComponent = z.object({
|
|
271
|
+
name: z.string().regex(NAME, "部品の名前は英字で始まる英数字とハイフン"),
|
|
272
|
+
label: z.string().min(1, "部品の表示名が未入力"),
|
|
273
|
+
description: z.string().optional(),
|
|
274
|
+
inline: z.boolean().optional(),
|
|
275
|
+
props: z.object({
|
|
276
|
+
type: z.literal("object"),
|
|
277
|
+
properties: z.record(z.string().regex(KEY, "引数の名前は英字で始まる英数字"), propSchema),
|
|
278
|
+
required: z.array(z.string()).optional()
|
|
279
|
+
}).strict()
|
|
280
|
+
}).strict().superRefine((d, ctx) => {
|
|
281
|
+
if (coreComponentOf(d.name) || RESERVED_BLOCK_NAMES.has(d.name) || RESERVED_INLINE_NAMES.has(d.name)) ctx.addIssue({
|
|
282
|
+
code: "custom",
|
|
283
|
+
message: `部品の名前 ${d.name} はコアの部品・予約の名前と重複`,
|
|
284
|
+
path: ["name"]
|
|
285
|
+
});
|
|
286
|
+
for (const key of d.props.required ?? []) if (!d.props.properties[key]) ctx.addIssue({
|
|
287
|
+
code: "custom",
|
|
288
|
+
message: `必須の引数 ${key} が properties にない`,
|
|
289
|
+
path: ["props", "required"]
|
|
290
|
+
});
|
|
291
|
+
if (d.inline) {
|
|
292
|
+
for (const [key, p] of Object.entries(d.props.properties)) if (p.type !== "string") ctx.addIssue({
|
|
293
|
+
code: "custom",
|
|
294
|
+
message: `インラインの部品の引数は文字列だけ: ${key}`,
|
|
295
|
+
path: [
|
|
296
|
+
"props",
|
|
297
|
+
"properties",
|
|
298
|
+
key
|
|
299
|
+
]
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
z.array(declaredComponent).superRefine((list, ctx) => {
|
|
304
|
+
const seen = /* @__PURE__ */ new Set();
|
|
305
|
+
list.forEach((d, i) => {
|
|
306
|
+
if (seen.has(d.name)) ctx.addIssue({
|
|
307
|
+
code: "custom",
|
|
308
|
+
message: `部品の名前 ${d.name} が重複`,
|
|
309
|
+
path: [i, "name"]
|
|
310
|
+
});
|
|
311
|
+
seen.add(d.name);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
/** 1つの引数の検査(label はメッセージの項目名) */
|
|
315
|
+
function propZod(p, label) {
|
|
316
|
+
const wrong = { error: (iss) => iss.input === void 0 ? `${label}が未入力` : `${label}の形が不正` };
|
|
317
|
+
switch (p.type) {
|
|
318
|
+
case "string": return p.enum ? z.enum(p.enum, wrong) : z.string(wrong);
|
|
319
|
+
case "number": return z.number(wrong);
|
|
320
|
+
case "integer": return z.number(wrong).int(`${label}は整数`);
|
|
321
|
+
case "boolean": return z.boolean(wrong);
|
|
322
|
+
case "array": return z.array(propZod(p.items, label), wrong);
|
|
323
|
+
case "object": return objectZod(p.properties ?? {}, p.required ?? [], wrong);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/** オブジェクトの引数の検査(必須でない引数は省略可、宣言にない引数は除去) */
|
|
327
|
+
const objectZod = (properties, required, error) => z.object(Object.fromEntries(Object.entries(properties).map(([key, p]) => [key, required.includes(key) ? propZod(p, p.title ?? key) : propZod(p, p.title ?? key).optional()])), error);
|
|
328
|
+
const propsCache = /* @__PURE__ */ new WeakMap();
|
|
329
|
+
/** 宣言した部品の引数の検査(宣言ごとに作成を再利用) */
|
|
330
|
+
function declaredPropsZod(d) {
|
|
331
|
+
let schema = propsCache.get(d);
|
|
332
|
+
if (!schema) propsCache.set(d, schema = objectZod(d.props.properties, d.props.required ?? [], { error: () => `${d.label}の引数の形が不正` }));
|
|
333
|
+
return schema;
|
|
334
|
+
}
|
|
335
|
+
// ../body/src/description.ts
|
|
336
|
+
/** meta description の長さの上限(検索結果で省略されにくい字数の目安) */
|
|
337
|
+
const DESCRIPTION_MAX = 120;
|
|
338
|
+
/**
|
|
339
|
+
* 説明を meta description の長さに調整
|
|
340
|
+
* - 上限を超える場合: 上限までの最後の句点まで
|
|
341
|
+
* - 句点がない場合: 上限で切って省略の記号
|
|
342
|
+
*/
|
|
343
|
+
function descriptionOf(excerpt, max = 120) {
|
|
344
|
+
if ([...excerpt].length <= max) return excerpt;
|
|
345
|
+
const head = [...excerpt].slice(0, max).join("");
|
|
346
|
+
const end = head.lastIndexOf("。");
|
|
347
|
+
return end > 0 ? head.slice(0, end + 1) : `${[...head].slice(0, max - 1).join("")}…`;
|
|
348
|
+
}
|
|
234
349
|
// ../body/src/derive.ts
|
|
235
350
|
/** 読了時間の既定の分速(字/分) */
|
|
236
351
|
const READING_SPEED = 500;
|
|
@@ -412,42 +527,61 @@ const text = z.object({
|
|
|
412
527
|
marks: z.array(z.enum(MARKS)).optional(),
|
|
413
528
|
link: link.optional()
|
|
414
529
|
});
|
|
415
|
-
|
|
530
|
+
/** 部品の props の検査(名前で核のコンポーネント、なければ宣言した部品、どちらもないか種類が違えば null) */
|
|
531
|
+
function propsOf(name, inline, media, declared) {
|
|
532
|
+
const core = coreComponentOf(name);
|
|
533
|
+
if (core) return Boolean(core.inline) === inline ? core.props(media) : null;
|
|
534
|
+
const d = declared.find((c) => c.name === name);
|
|
535
|
+
return d && Boolean(d.inline) === inline ? declaredPropsZod(d) : null;
|
|
536
|
+
}
|
|
537
|
+
const inlineComponentOf = (declared) => z.object({
|
|
416
538
|
component: z.string().min(1),
|
|
417
539
|
props: z.record(z.string(), z.string()).optional(),
|
|
418
540
|
text: z.string().optional()
|
|
419
541
|
}).superRefine((c, ctx) => {
|
|
420
|
-
const
|
|
421
|
-
if (!
|
|
542
|
+
const props = propsOf(c.component, true, z.never(), declared);
|
|
543
|
+
if (!props) return void ctx.addIssue({
|
|
422
544
|
code: "custom",
|
|
423
545
|
message: `インラインのコンポーネント ${c.component} はない`
|
|
424
546
|
});
|
|
425
|
-
const r =
|
|
547
|
+
const r = props.safeParse(c.props ?? {});
|
|
426
548
|
if (!r.success) for (const issue of r.error.issues) ctx.addIssue({
|
|
427
549
|
code: "custom",
|
|
428
550
|
message: issue.message,
|
|
429
551
|
path: ["props", ...issue.path]
|
|
430
552
|
});
|
|
431
553
|
});
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
text
|
|
436
|
-
|
|
437
|
-
const
|
|
554
|
+
/** インラインの1要素(component を持つ要素は部品としてだけ検査、union で文字の形に落ちて部品の検査を素通りしないため) */
|
|
555
|
+
function inlineItemOf(declared) {
|
|
556
|
+
const component = inlineComponentOf(declared);
|
|
557
|
+
const plain = z.union([z.string(), text]);
|
|
558
|
+
return z.unknown().transform((value, ctx) => {
|
|
559
|
+
const r = (typeof value === "object" && value !== null && "component" in value ? component : plain).safeParse(value);
|
|
560
|
+
if (r.success) return r.data;
|
|
561
|
+
for (const issue of r.error.issues) ctx.addIssue({
|
|
562
|
+
code: "custom",
|
|
563
|
+
message: issue.message,
|
|
564
|
+
path: issue.path
|
|
565
|
+
});
|
|
566
|
+
return z.NEVER;
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
const richOf = (cites, declared) => z.array(inlineItemOf(declared)).transform((items) => cites ? items.flatMap((item) => typeof item === "string" ? liftCites(item) : [item]) : items);
|
|
438
570
|
const variant = z.string().optional();
|
|
439
|
-
/** 本文の1ブロックの検査(profile は本文の設定、media
|
|
440
|
-
function blockSchemaOf(profile, media) {
|
|
441
|
-
const rich = richOf(profile.components.includes(CITE));
|
|
442
|
-
const list = z.
|
|
571
|
+
/** 本文の1ブロックの検査(profile は本文の設定、media は画像の参照の検査、declared は導入先が宣言した部品) */
|
|
572
|
+
function blockSchemaOf(profile, media, declared = []) {
|
|
573
|
+
const rich = richOf(profile.components.includes(CITE), declared);
|
|
574
|
+
const list = z.object({
|
|
443
575
|
type: z.literal("list"),
|
|
444
576
|
ordered: z.boolean().optional(),
|
|
445
577
|
items: z.array(z.object({
|
|
446
578
|
content: rich,
|
|
447
|
-
children
|
|
579
|
+
get children() {
|
|
580
|
+
return list.optional();
|
|
581
|
+
}
|
|
448
582
|
})),
|
|
449
583
|
variant
|
|
450
|
-
})
|
|
584
|
+
});
|
|
451
585
|
const heading = z.object({
|
|
452
586
|
type: z.literal("heading"),
|
|
453
587
|
level: z.union([
|
|
@@ -462,7 +596,7 @@ function blockSchemaOf(profile, media) {
|
|
|
462
596
|
id: z.string().optional(),
|
|
463
597
|
variant
|
|
464
598
|
});
|
|
465
|
-
return z.
|
|
599
|
+
return z.discriminatedUnion("type", [
|
|
466
600
|
z.object({
|
|
467
601
|
type: z.literal("paragraph"),
|
|
468
602
|
content: rich,
|
|
@@ -513,15 +647,15 @@ function blockSchemaOf(profile, media) {
|
|
|
513
647
|
]).transform((block, ctx) => {
|
|
514
648
|
let out = block;
|
|
515
649
|
if (out.type === "component") {
|
|
516
|
-
const
|
|
517
|
-
if (!
|
|
650
|
+
const props = propsOf(out.name, false, media, declared);
|
|
651
|
+
if (!props) {
|
|
518
652
|
ctx.addIssue({
|
|
519
653
|
code: "custom",
|
|
520
654
|
message: `コンポーネント ${out.name} はない`
|
|
521
655
|
});
|
|
522
656
|
return z.NEVER;
|
|
523
657
|
}
|
|
524
|
-
const r =
|
|
658
|
+
const r = props.safeParse(out.props);
|
|
525
659
|
if (!r.success) {
|
|
526
660
|
for (const issue of r.error.issues) ctx.addIssue({
|
|
527
661
|
code: "custom",
|
package/dist/delivery.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
import {
|
|
1
|
+
import { C as ContentType, E as Publishing, O as PageDef, S as ContentModel, T as FieldDef, _ as Hotspot, g as Crop, k as PageEntry, n as DateString, s as CropRect, t as Clock, w as CountDef, x as ContentFormat, y as Media } from "./index-CLDe_wBc.js";
|
|
2
|
+
import { c as Body } from "./index-C8duwjns.js";
|
|
3
3
|
/** 配信 API の1件の値(項目の key と id・updated_at) */
|
|
4
4
|
type Item = Readonly<Record<string, unknown>>;
|
|
5
5
|
/** 配信 API が解決した実体(hotspot・crop は元の画像に対する割合) */
|
|
@@ -90,6 +90,32 @@ type ComponentBlock = Readonly<{
|
|
|
90
90
|
type Block<M> = StandardBlock<M> | ComponentBlock;
|
|
91
91
|
/** 本文 */
|
|
92
92
|
type Body<M> = readonly Block<M>[];
|
|
93
|
+
/** 引数の型 */
|
|
94
|
+
type PropType = 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object';
|
|
95
|
+
/** 1つの引数の形(title は管理画面の項目名、enum は文字列の選択肢、items は配列の要素、properties・required はオブジェクトの中) */
|
|
96
|
+
type PropSchema = Readonly<{
|
|
97
|
+
type: PropType;
|
|
98
|
+
title?: string;
|
|
99
|
+
description?: string;
|
|
100
|
+
enum?: readonly string[];
|
|
101
|
+
items?: PropSchema;
|
|
102
|
+
properties?: Readonly<Record<string, PropSchema>>;
|
|
103
|
+
required?: readonly string[];
|
|
104
|
+
}>;
|
|
105
|
+
/** 引数の全体の形(最上位はオブジェクト) */
|
|
106
|
+
type PropsSchema = Readonly<{
|
|
107
|
+
type: 'object';
|
|
108
|
+
properties: Readonly<Record<string, PropSchema>>;
|
|
109
|
+
required?: readonly string[];
|
|
110
|
+
}>;
|
|
111
|
+
/** 導入先が宣言する部品(label は管理画面の表示名、inline の省略はブロック) */
|
|
112
|
+
type DeclaredComponent = Readonly<{
|
|
113
|
+
name: string;
|
|
114
|
+
label: string;
|
|
115
|
+
description?: string;
|
|
116
|
+
inline?: boolean;
|
|
117
|
+
props: PropsSchema;
|
|
118
|
+
}>;
|
|
93
119
|
/** 本文の設定 */
|
|
94
120
|
type BodyProfile = Readonly<{
|
|
95
121
|
headings: readonly HeadingLevel[];
|
|
@@ -126,6 +152,6 @@ type BodyProfile = Readonly<{
|
|
|
126
152
|
/** 使用できるコンポーネントの名前(ブロック・インライン) */
|
|
127
153
|
components: readonly string[];
|
|
128
154
|
}>;
|
|
129
|
-
/** 本文の1ブロックの検査(profile は本文の設定、media
|
|
130
|
-
declare function blockSchemaOf<M>(profile: BodyProfile, media: z.ZodType<M
|
|
131
|
-
export {
|
|
155
|
+
/** 本文の1ブロックの検査(profile は本文の設定、media は画像の参照の検査、declared は導入先が宣言した部品) */
|
|
156
|
+
declare function blockSchemaOf<M>(profile: BodyProfile, media: z.ZodType<M>, declared?: readonly DeclaredComponent[]): z.ZodType<Block<M>>;
|
|
157
|
+
export { Rich as _, PropType as a, Body as c, Inline as d, InlineComponent as f, Mark as g, ListItem as h, PropSchema as i, ComponentBlock as l, List as m, BodyProfile as n, PropsSchema as o, Link as p, DeclaredComponent as r, Block as s, blockSchemaOf as t, HeadingLevel as u, StandardBlock as v, Text as y };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as BodyProfile } from "./index-
|
|
1
|
+
import { n as BodyProfile, r as DeclaredComponent } from "./index-C8duwjns.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
/** ページの行の値(項目の key と値、表の列・集計の値も含む) */
|
|
4
4
|
type PageEntry = Readonly<Record<string, unknown>>;
|
|
@@ -188,6 +188,7 @@ type ContentType = Readonly<{
|
|
|
188
188
|
/** コンテンツスキーマ(導入先ごとに1つ) */
|
|
189
189
|
type ContentModel = Readonly<{
|
|
190
190
|
types: readonly ContentType[];
|
|
191
|
+
components?: readonly DeclaredComponent[];
|
|
191
192
|
}>;
|
|
192
193
|
/** コンテンツスキーマの型(id から、なければ例外: 導入先の設定の誤り) */
|
|
193
194
|
declare function typeOf(model: ContentModel, id: string): ContentType;
|
|
@@ -309,6 +310,10 @@ type CropRect = Readonly<{
|
|
|
309
310
|
declare function cropRect(width: number, height: number, crop: Crop): CropRect;
|
|
310
311
|
/** 元の画像に対する見せたい点を、切り抜いた後の画像に対する割合に変換(範囲の外は端に寄せる) */
|
|
311
312
|
declare function croppedHotspot(hotspot: Hotspot, crop: Crop): Hotspot;
|
|
313
|
+
/** 画像の型 */
|
|
314
|
+
declare const IMAGE_TYPE: ContentType;
|
|
315
|
+
/** 動画の型(配信と再生は最初に使用する場所が決まったときに実装) */
|
|
316
|
+
declare const VIDEO_TYPE: ContentType;
|
|
312
317
|
/** 暦の日付(`YYYY-MM-DD`) */
|
|
313
318
|
type DateString = `${number}-${number}-${number}`;
|
|
314
319
|
/** 日付だけの値(`YYYY-MM-DD`)か */
|
|
@@ -332,4 +337,4 @@ type Clock = Readonly<{
|
|
|
332
337
|
}>;
|
|
333
338
|
/** タイムゾーンの日時の変換を作成 */
|
|
334
339
|
declare function createClock(timeZone: string): Clock;
|
|
335
|
-
export {
|
|
340
|
+
export { pagePathOf as A, ContentType as C, typeOf as D, Publishing as E, PageDef as O, ContentModel as S, FieldDef as T, Hotspot as _, IMAGE_TYPE as a, mediaSchema as b, coverPosition as c, croppedHotspot as d, objectPosition as f, Crop as g, AssetRef as h, isDateOnly as i, publicPathOf as j, PageEntry as k, coverStart as l, Asset as m, DateString as n, VIDEO_TYPE as o, stillOf as p, createClock as r, CropRect as s, Clock as t, cropRect as u, LocalImage as v, CountDef as w, ContentFormat as x, Media as y };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { r as VerifyRequestOptions, s as verifyRequest, t as SIGNED_HEADERS } from "./signed-B_58gV21.js";
|
|
2
|
+
/** 依頼する時点(save_check は型の保存前、publish_check は提出・公開の前の検査) */
|
|
3
|
+
export declare const FUNCTION_HOOKS: readonly ["save_check", "publish_check"];
|
|
4
|
+
/** 依頼する時点 */
|
|
5
|
+
type FunctionHook = (typeof FUNCTION_HOOKS)[number];
|
|
6
|
+
/** 保存前の検査の本文(id は作成なら null、values は宣言と拡張機能で補正した後の項目の値、user は操作した人のアカウントの ID とロール) */
|
|
7
|
+
type SaveCheckRequest = Readonly<{
|
|
8
|
+
hook: 'save_check';
|
|
9
|
+
type: string;
|
|
10
|
+
id: string | null;
|
|
11
|
+
values: Readonly<Record<string, unknown>>;
|
|
12
|
+
user: Readonly<{
|
|
13
|
+
id: string;
|
|
14
|
+
role: string;
|
|
15
|
+
}>;
|
|
16
|
+
at: string;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* 保存前の検査の応答(HTTP の状態は 2xx)
|
|
20
|
+
* - problems: 1件でもあれば保存を拒否(path は項目の key、message は画面に表示する理由)
|
|
21
|
+
* - values: 補正する項目だけの値(入力に重ねる、なしは補正なし)
|
|
22
|
+
*/
|
|
23
|
+
type SaveCheckResponse = Readonly<{
|
|
24
|
+
problems?: readonly Readonly<{
|
|
25
|
+
path: string;
|
|
26
|
+
message: string;
|
|
27
|
+
}>[];
|
|
28
|
+
values?: Readonly<Record<string, unknown>>;
|
|
29
|
+
}>;
|
|
30
|
+
/** 提出・公開前の検査の本文(stage は submit か publish、values は保存済みの行の値、id はテストの呼び出しだけ null) */
|
|
31
|
+
type PublishCheckRequest = Readonly<{
|
|
32
|
+
hook: 'publish_check';
|
|
33
|
+
type: string;
|
|
34
|
+
id: string | null;
|
|
35
|
+
stage: 'submit' | 'publish';
|
|
36
|
+
values: Readonly<Record<string, unknown>>;
|
|
37
|
+
user: Readonly<{
|
|
38
|
+
id: string;
|
|
39
|
+
role: string;
|
|
40
|
+
}>;
|
|
41
|
+
at: string;
|
|
42
|
+
}>;
|
|
43
|
+
/** 提出・公開前の検査の応答(HTTP の状態は 2xx、problems が1件でもあれば提出・公開を拒否、値の補正はなし) */
|
|
44
|
+
type PublishCheckResponse = Readonly<{
|
|
45
|
+
problems?: readonly Readonly<{
|
|
46
|
+
path: string;
|
|
47
|
+
message: string;
|
|
48
|
+
}>[];
|
|
49
|
+
}>;
|
|
50
|
+
/** 外の関数への依頼の本文 */
|
|
51
|
+
type FunctionRequest = SaveCheckRequest | PublishCheckRequest;
|
|
52
|
+
/** 送れる操作(レビューの依頼・公開・非公開・差し戻し) */
|
|
53
|
+
declare const WEBHOOK_ACTIONS: readonly ["submitted", "published", "unpublished", "rejected"];
|
|
54
|
+
/** 送れる操作 */
|
|
55
|
+
type WebhookAction = (typeof WEBHOOK_ACTIONS)[number];
|
|
56
|
+
/** 送れる出来事(編集機能を持つ型の <型の id>.<操作> と、拡張機能が宣言した出来事) */
|
|
57
|
+
type WebhookEvent = `${string}.${string}`;
|
|
58
|
+
/**
|
|
59
|
+
* json の形式の本文(編集機能を持つ型の出来事、署名はヘッダー x-cms-signature で登録ごとの鍵)
|
|
60
|
+
* - item: 行の ID と題、by: 操作した人の表示名、at: 送信の日時(ISO 8601)
|
|
61
|
+
*/
|
|
62
|
+
type WebhookBody = Readonly<{
|
|
63
|
+
event: WebhookEvent;
|
|
64
|
+
type: string;
|
|
65
|
+
item: Readonly<{
|
|
66
|
+
id: string;
|
|
67
|
+
title: string;
|
|
68
|
+
}>;
|
|
69
|
+
by: string;
|
|
70
|
+
at: string;
|
|
71
|
+
}>;
|
|
72
|
+
export { type FunctionHook, type FunctionRequest, type PublishCheckRequest, type PublishCheckResponse, SIGNED_HEADERS, type SaveCheckRequest, type SaveCheckResponse, type VerifyRequestOptions, type WebhookAction, type WebhookBody, type WebhookEvent, verifyRequest };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { a as verifyRequest, t as SIGNED_HEADERS } from "./src-CA_67dWy.js";
|
|
2
|
+
import "./src-BrLwIWaT.js";
|
|
3
|
+
// ../cms-contract/src/functions.ts
|
|
4
|
+
/** 依頼する時点(save_check は型の保存前、publish_check は提出・公開の前の検査) */
|
|
5
|
+
const FUNCTION_HOOKS = ["save_check", "publish_check"];
|
|
6
|
+
export { FUNCTION_HOOKS, SIGNED_HEADERS, verifyRequest };
|
package/dist/model.d.ts
CHANGED
|
@@ -1,2 +1,82 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
|
|
1
|
+
import { A as pagePathOf, C as ContentType, D as typeOf, O as PageDef, S as ContentModel, T as FieldDef, _ as Hotspot, a as IMAGE_TYPE, b as mediaSchema, c as coverPosition, d as croppedHotspot, f as objectPosition, g as Crop, h as AssetRef, i as isDateOnly, j as publicPathOf, k as PageEntry, l as coverStart, m as Asset, n as DateString, o as VIDEO_TYPE, p as stillOf, r as createClock, s as CropRect, t as Clock, u as cropRect, v as LocalImage, y as Media } from "./index-CLDe_wBc.js";
|
|
2
|
+
/** メニューのグループ(collapsed は既定で折りたたむ) */
|
|
3
|
+
type MenuGroup = Readonly<{
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
collapsed?: boolean;
|
|
7
|
+
items: readonly string[];
|
|
8
|
+
}>;
|
|
9
|
+
/** メニューの1段(項目の id かグループ) */
|
|
10
|
+
type MenuEntry = string | MenuGroup;
|
|
11
|
+
/** 編集画面のセクション(label が空は見出しなし、collapsed は既定で折りたたむ) */
|
|
12
|
+
type EditorSection = Readonly<{
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
collapsed?: boolean;
|
|
16
|
+
items: readonly string[];
|
|
17
|
+
}>;
|
|
18
|
+
/** コンテンツタイプの編集画面の配置(hidden は配置しない項目) */
|
|
19
|
+
type EditorLayout = Readonly<{
|
|
20
|
+
sections: readonly EditorSection[];
|
|
21
|
+
hidden?: readonly string[];
|
|
22
|
+
}>;
|
|
23
|
+
/** 画面の設定(editors・columns のキーはコンテンツタイプの id) */
|
|
24
|
+
type ScreenSettings = Readonly<{
|
|
25
|
+
menu: readonly MenuEntry[];
|
|
26
|
+
editors: Readonly<Record<string, EditorLayout>>;
|
|
27
|
+
columns: Readonly<Record<string, readonly string[]>>;
|
|
28
|
+
}>;
|
|
29
|
+
/** 操作 */
|
|
30
|
+
declare const ACTIONS: readonly ["read", "create", "update", "publish", "delete"];
|
|
31
|
+
/** 操作 */
|
|
32
|
+
type Action = (typeof ACTIONS)[number];
|
|
33
|
+
/** 段階 */
|
|
34
|
+
declare const LEVELS: readonly ["all", "own", "approval", "none"];
|
|
35
|
+
/** 段階 */
|
|
36
|
+
type Level = (typeof LEVELS)[number];
|
|
37
|
+
/** 権限の表(キーは対象の id、'*' は表にない対象の既定、操作がなければ不可) */
|
|
38
|
+
type Permissions = Readonly<Record<string, Readonly<Partial<Record<Action, Level>>>>>;
|
|
39
|
+
/** ビューで許可できる一括操作(assign: 担当者の割り当て、publish: 公開) */
|
|
40
|
+
declare const VIEW_ACTIONS: readonly ["assign", "publish"];
|
|
41
|
+
/** ビューで許可できる一括操作 */
|
|
42
|
+
type ViewAction = (typeof VIEW_ACTIONS)[number];
|
|
43
|
+
/** ビューの内容(filters は列の id ごとの絞り込みの値、query は検索の文、columns は表示する列、editable は一覧で編集する列) */
|
|
44
|
+
type ViewDefinition = Readonly<{
|
|
45
|
+
filters: Readonly<Record<string, string>>;
|
|
46
|
+
query: string;
|
|
47
|
+
columns: readonly string[];
|
|
48
|
+
editable: readonly string[];
|
|
49
|
+
actions: readonly ViewAction[];
|
|
50
|
+
}>;
|
|
51
|
+
/** スキーマと設定の形式の名前 */
|
|
52
|
+
export declare const CONFIG_FORMAT = "cms-config";
|
|
53
|
+
/** スキーマと設定(schema はコンテンツスキーマ ContentModel・screens は画面設定 ScreenSettings の形、roles は管理者以外、views はチームのビュー) */
|
|
54
|
+
type ConfigBundle = Readonly<{
|
|
55
|
+
format: typeof CONFIG_FORMAT;
|
|
56
|
+
version: 1;
|
|
57
|
+
exported_at: string;
|
|
58
|
+
schema: unknown;
|
|
59
|
+
screens: unknown;
|
|
60
|
+
roles: readonly Readonly<{
|
|
61
|
+
id: string;
|
|
62
|
+
label: string;
|
|
63
|
+
permissions: Permissions;
|
|
64
|
+
}>[];
|
|
65
|
+
views: readonly Readonly<{
|
|
66
|
+
id: string;
|
|
67
|
+
type: string;
|
|
68
|
+
label: string;
|
|
69
|
+
definition: ViewDefinition;
|
|
70
|
+
roles: readonly string[];
|
|
71
|
+
}>[];
|
|
72
|
+
extensions: readonly Readonly<{
|
|
73
|
+
id: string;
|
|
74
|
+
enabled: boolean;
|
|
75
|
+
settings: Readonly<Record<string, unknown>>;
|
|
76
|
+
}>[];
|
|
77
|
+
}>;
|
|
78
|
+
/** 中立の既定のスキーマ(新しい媒体の作成で適用する型: post・author・tag・image・video、設定の移入の土台) */
|
|
79
|
+
export declare const DEFAULT_SCHEMA: ContentModel;
|
|
80
|
+
/** 既定の画面設定(新しい媒体の作成で適用する中立の値、移入する screens の土台) */
|
|
81
|
+
export declare const DEFAULT_SCREENS: ScreenSettings;
|
|
82
|
+
export { type Asset, type AssetRef, type Clock, type ConfigBundle, type ContentModel, type ContentType, type Crop, type CropRect, type DateString, type FieldDef, type Hotspot, IMAGE_TYPE, type LocalImage, type Media, type PageDef, type PageEntry, type ScreenSettings, VIDEO_TYPE, coverPosition, coverStart, createClock, cropRect, croppedHotspot, isDateOnly, mediaSchema, objectPosition, pagePathOf, publicPathOf, stillOf, typeOf };
|
package/dist/model.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { a as coverPosition, c as croppedHotspot, d as createClock, f as isDateOnly, i as typeOf, l as objectPosition, n as publicPathOf, o as coverStart, s as cropRect, t as pagePathOf, u as stillOf } from "./page-UEKb7_gu.js";
|
|
2
|
+
import "./src-BrLwIWaT.js";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
// ../model/src/asset.ts
|
|
4
5
|
const unit = z.number().min(0).max(1);
|
|
@@ -28,4 +29,341 @@ const mediaSchema = z.union([mediaBase.extend({ kind: z.literal("image") }), med
|
|
|
28
29
|
poster: mediaBase
|
|
29
30
|
})]);
|
|
30
31
|
z.object({ asset: z.string().min(1) });
|
|
31
|
-
|
|
32
|
+
// ../model/src/media.ts
|
|
33
|
+
const file = [
|
|
34
|
+
{
|
|
35
|
+
key: "mime_type",
|
|
36
|
+
label: "形式",
|
|
37
|
+
type: "text",
|
|
38
|
+
required: "save",
|
|
39
|
+
description: "MIME タイプ"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: "size",
|
|
43
|
+
label: "サイズ",
|
|
44
|
+
type: "number",
|
|
45
|
+
required: "save",
|
|
46
|
+
description: "バイト数"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: "width",
|
|
50
|
+
label: "幅",
|
|
51
|
+
type: "number",
|
|
52
|
+
required: "save",
|
|
53
|
+
description: "px"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
key: "height",
|
|
57
|
+
label: "高さ",
|
|
58
|
+
type: "number",
|
|
59
|
+
required: "save",
|
|
60
|
+
description: "px"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
key: "alt",
|
|
64
|
+
label: "代替テキスト",
|
|
65
|
+
type: "text",
|
|
66
|
+
description: "使用する側の既定の値、使用する場所ごとに上書き可"
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
/** 画像の型 */
|
|
70
|
+
const IMAGE_TYPE = {
|
|
71
|
+
id: "image",
|
|
72
|
+
label: "画像",
|
|
73
|
+
description: "見せたい点(hotspot)と切り抜き(crop)を画像ごとに1つ保持、切り抜く場所すべてで共通",
|
|
74
|
+
core: file,
|
|
75
|
+
fields: []
|
|
76
|
+
};
|
|
77
|
+
/** 動画の型(配信と再生は最初に使用する場所が決まったときに実装) */
|
|
78
|
+
const VIDEO_TYPE = {
|
|
79
|
+
id: "video",
|
|
80
|
+
label: "動画",
|
|
81
|
+
core: [
|
|
82
|
+
...file,
|
|
83
|
+
{
|
|
84
|
+
key: "duration",
|
|
85
|
+
label: "長さ",
|
|
86
|
+
type: "number",
|
|
87
|
+
description: "秒"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
key: "poster",
|
|
91
|
+
label: "ポスター画像",
|
|
92
|
+
type: "media",
|
|
93
|
+
accept: ["image"],
|
|
94
|
+
description: "読み込み前・動きを抑える設定・OG で代わりに表示"
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
fields: []
|
|
98
|
+
};
|
|
99
|
+
// ../cms-contract/src/transfer.ts
|
|
100
|
+
/** スキーマと設定の形式の名前 */
|
|
101
|
+
const CONFIG_FORMAT = "cms-config";
|
|
102
|
+
// src/defaults.ts
|
|
103
|
+
/** 中立の既定のスキーマ(新しい媒体の作成で適用する型: post・author・tag・image・video、設定の移入の土台) */
|
|
104
|
+
const DEFAULT_SCHEMA = { "types": [
|
|
105
|
+
{
|
|
106
|
+
"id": "post",
|
|
107
|
+
"label": "投稿",
|
|
108
|
+
"table": "articles",
|
|
109
|
+
"publishing": "review",
|
|
110
|
+
"core": [
|
|
111
|
+
{
|
|
112
|
+
"key": "title",
|
|
113
|
+
"label": "タイトル",
|
|
114
|
+
"type": "text",
|
|
115
|
+
"required": "save"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"key": "writers",
|
|
119
|
+
"label": "著者",
|
|
120
|
+
"type": "reference",
|
|
121
|
+
"to": "author",
|
|
122
|
+
"multiple": true
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"key": "image",
|
|
126
|
+
"label": "メインの画像",
|
|
127
|
+
"type": "media",
|
|
128
|
+
"accept": ["image"]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"key": "body",
|
|
132
|
+
"label": "本文",
|
|
133
|
+
"type": "body",
|
|
134
|
+
"profile": {
|
|
135
|
+
"headings": [
|
|
136
|
+
2,
|
|
137
|
+
3,
|
|
138
|
+
4
|
|
139
|
+
],
|
|
140
|
+
"marks": [
|
|
141
|
+
"strong",
|
|
142
|
+
"em",
|
|
143
|
+
"code",
|
|
144
|
+
"strike"
|
|
145
|
+
],
|
|
146
|
+
"list": {
|
|
147
|
+
"ordered": true,
|
|
148
|
+
"nested": true
|
|
149
|
+
},
|
|
150
|
+
"quote": {
|
|
151
|
+
"multiple": true,
|
|
152
|
+
"cite": true
|
|
153
|
+
},
|
|
154
|
+
"code": { "caption": false },
|
|
155
|
+
"table": {
|
|
156
|
+
"head": "optional",
|
|
157
|
+
"caption": "optional"
|
|
158
|
+
},
|
|
159
|
+
"divider": true,
|
|
160
|
+
"components": ["gallery", "link-card"]
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"key": "published_at",
|
|
165
|
+
"label": "公開日時",
|
|
166
|
+
"type": "datetime"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"key": "revised_at",
|
|
170
|
+
"label": "更新日時",
|
|
171
|
+
"type": "datetime"
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
"fields": [{
|
|
175
|
+
"key": "tags",
|
|
176
|
+
"label": "タグ",
|
|
177
|
+
"type": "reference",
|
|
178
|
+
"to": "tag",
|
|
179
|
+
"multiple": true
|
|
180
|
+
}],
|
|
181
|
+
"pages": [{
|
|
182
|
+
"path": "/posts/{id}",
|
|
183
|
+
"public": [{
|
|
184
|
+
"key": "status",
|
|
185
|
+
"in": ["published"]
|
|
186
|
+
}, {
|
|
187
|
+
"key": "published_at",
|
|
188
|
+
"notFuture": true
|
|
189
|
+
}]
|
|
190
|
+
}]
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"id": "author",
|
|
194
|
+
"label": "著者",
|
|
195
|
+
"fields": [
|
|
196
|
+
{
|
|
197
|
+
"key": "name",
|
|
198
|
+
"label": "名前",
|
|
199
|
+
"type": "text",
|
|
200
|
+
"required": "save"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"key": "bio",
|
|
204
|
+
"label": "紹介",
|
|
205
|
+
"type": "textarea"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"key": "portrait",
|
|
209
|
+
"label": "顔の画像",
|
|
210
|
+
"type": "media",
|
|
211
|
+
"accept": ["image"]
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
"pages": [{ "path": "/authors/{id}" }]
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"id": "tag",
|
|
218
|
+
"label": "タグ",
|
|
219
|
+
"fields": [{
|
|
220
|
+
"key": "label",
|
|
221
|
+
"label": "名前",
|
|
222
|
+
"type": "text",
|
|
223
|
+
"required": "save"
|
|
224
|
+
}]
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"id": "image",
|
|
228
|
+
"label": "画像",
|
|
229
|
+
"description": "見せたい点(hotspot)と切り抜き(crop)を画像ごとに1つ保持、切り抜く場所すべてで共通",
|
|
230
|
+
"core": [
|
|
231
|
+
{
|
|
232
|
+
"key": "mime_type",
|
|
233
|
+
"label": "形式",
|
|
234
|
+
"type": "text",
|
|
235
|
+
"required": "save",
|
|
236
|
+
"description": "MIME タイプ"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"key": "size",
|
|
240
|
+
"label": "サイズ",
|
|
241
|
+
"type": "number",
|
|
242
|
+
"required": "save",
|
|
243
|
+
"description": "バイト数"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"key": "width",
|
|
247
|
+
"label": "幅",
|
|
248
|
+
"type": "number",
|
|
249
|
+
"required": "save",
|
|
250
|
+
"description": "px"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"key": "height",
|
|
254
|
+
"label": "高さ",
|
|
255
|
+
"type": "number",
|
|
256
|
+
"required": "save",
|
|
257
|
+
"description": "px"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"key": "alt",
|
|
261
|
+
"label": "代替テキスト",
|
|
262
|
+
"type": "text",
|
|
263
|
+
"description": "使用する側の既定の値、使用する場所ごとに上書き可"
|
|
264
|
+
}
|
|
265
|
+
],
|
|
266
|
+
"fields": []
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"id": "video",
|
|
270
|
+
"label": "動画",
|
|
271
|
+
"core": [
|
|
272
|
+
{
|
|
273
|
+
"key": "mime_type",
|
|
274
|
+
"label": "形式",
|
|
275
|
+
"type": "text",
|
|
276
|
+
"required": "save",
|
|
277
|
+
"description": "MIME タイプ"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"key": "size",
|
|
281
|
+
"label": "サイズ",
|
|
282
|
+
"type": "number",
|
|
283
|
+
"required": "save",
|
|
284
|
+
"description": "バイト数"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"key": "width",
|
|
288
|
+
"label": "幅",
|
|
289
|
+
"type": "number",
|
|
290
|
+
"required": "save",
|
|
291
|
+
"description": "px"
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
"key": "height",
|
|
295
|
+
"label": "高さ",
|
|
296
|
+
"type": "number",
|
|
297
|
+
"required": "save",
|
|
298
|
+
"description": "px"
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"key": "alt",
|
|
302
|
+
"label": "代替テキスト",
|
|
303
|
+
"type": "text",
|
|
304
|
+
"description": "使用する側の既定の値、使用する場所ごとに上書き可"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"key": "duration",
|
|
308
|
+
"label": "長さ",
|
|
309
|
+
"type": "number",
|
|
310
|
+
"description": "秒"
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"key": "poster",
|
|
314
|
+
"label": "ポスター画像",
|
|
315
|
+
"type": "media",
|
|
316
|
+
"accept": ["image"],
|
|
317
|
+
"description": "読み込み前・動きを抑える設定・OG で代わりに表示"
|
|
318
|
+
}
|
|
319
|
+
],
|
|
320
|
+
"fields": []
|
|
321
|
+
}
|
|
322
|
+
] };
|
|
323
|
+
/** 既定の画面設定(新しい媒体の作成で適用する中立の値、移入する screens の土台) */
|
|
324
|
+
const DEFAULT_SCREENS = {
|
|
325
|
+
"menu": [
|
|
326
|
+
"dashboard",
|
|
327
|
+
"calendar",
|
|
328
|
+
{
|
|
329
|
+
"id": "content",
|
|
330
|
+
"label": "コンテンツ",
|
|
331
|
+
"items": ["type:post", "media"]
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"id": "masters",
|
|
335
|
+
"label": "マスタ",
|
|
336
|
+
"items": ["type:tag", "type:author"]
|
|
337
|
+
}
|
|
338
|
+
],
|
|
339
|
+
"editors": { "post": { "sections": [
|
|
340
|
+
{
|
|
341
|
+
"id": "classify",
|
|
342
|
+
"label": "分類",
|
|
343
|
+
"items": ["writers", "tags"]
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
"id": "publish",
|
|
347
|
+
"label": "公開",
|
|
348
|
+
"items": ["published_at", "revised_at"]
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"id": "excerpt",
|
|
352
|
+
"label": "説明",
|
|
353
|
+
"items": ["excerpt"]
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
"id": "image",
|
|
357
|
+
"label": "メインの画像",
|
|
358
|
+
"items": ["image"]
|
|
359
|
+
}
|
|
360
|
+
] } },
|
|
361
|
+
"columns": { "post": [
|
|
362
|
+
"status",
|
|
363
|
+
"writers",
|
|
364
|
+
"assignee",
|
|
365
|
+
"published_at",
|
|
366
|
+
"updated_at"
|
|
367
|
+
] }
|
|
368
|
+
};
|
|
369
|
+
export { CONFIG_FORMAT, DEFAULT_SCHEMA, DEFAULT_SCREENS, IMAGE_TYPE, VIDEO_TYPE, coverPosition, coverStart, createClock, cropRect, croppedHotspot, isDateOnly, mediaSchema, objectPosition, pagePathOf, publicPathOf, stillOf, typeOf };
|
package/dist/preview.d.ts
CHANGED
|
@@ -1,44 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
/** MCP → CMS の署名(鍵は MCP_TOKEN) */
|
|
4
|
-
readonly mcp: "x-mcp-signature";
|
|
5
|
-
/** MCP → CMS のログインのメールアドレス(署名の主体) */
|
|
6
|
-
readonly mcpEmail: "x-mcp-email";
|
|
7
|
-
/** CMS ⇄ サイトのプレビューと下書きの署名(鍵は PREVIEW_TOKEN) */
|
|
8
|
-
readonly preview: "x-preview-signature";
|
|
9
|
-
/** CMS → 外の送信先の Webhook・外の関数の署名(鍵は登録ごと、受け手は verifyParts と同じ手順で照合) */
|
|
10
|
-
readonly outbound: "x-cms-signature";
|
|
11
|
-
};
|
|
12
|
-
/** 署名の対象(body は本文、なしは空) */
|
|
13
|
-
type SignedParts = Readonly<{
|
|
14
|
-
method: string;
|
|
15
|
-
url: string;
|
|
16
|
-
body?: string | ArrayBuffer | Uint8Array | null;
|
|
17
|
-
/** 主体(MCP のログインのメールアドレスなど、なしは空) */
|
|
18
|
-
subject?: string | null;
|
|
19
|
-
}>;
|
|
20
|
-
/** 署名のヘッダーの値(t=<秒>,v1=<署名>) */
|
|
21
|
-
export declare function signParts(secret: string, parts: SignedParts, now?: number): Promise<string>;
|
|
22
|
-
/** 署名の照合(形の誤り・時刻のずれの超過・不一致は false) */
|
|
23
|
-
export declare function verifyParts(secret: string, header: string | null | undefined, parts: SignedParts, { now, maxSkewSec }?: {
|
|
24
|
-
now?: number;
|
|
25
|
-
maxSkewSec?: number;
|
|
26
|
-
}): Promise<boolean>;
|
|
27
|
-
/** 署名を付けたヘッダー(init のヘッダーに署名と主体を追加した新しい Headers) */
|
|
28
|
-
export declare function signedHeaders(secret: string, { method, url, body, subject }: SignedParts, { header, subjectHeader, init, now }: Readonly<{
|
|
29
|
-
header: string;
|
|
30
|
-
subjectHeader?: string;
|
|
31
|
-
init?: HeadersInit;
|
|
32
|
-
now?: number;
|
|
33
|
-
}>): Promise<Headers>;
|
|
34
|
-
/** 署名つきのリクエストを照合する設定 */
|
|
35
|
-
type VerifyRequestOptions = Readonly<{
|
|
36
|
-
/** 署名のヘッダーの名前 */
|
|
37
|
-
header: string;
|
|
38
|
-
/** 主体のヘッダーの名前(なしは主体なしで照合) */
|
|
39
|
-
subjectHeader?: string;
|
|
40
|
-
now?: number;
|
|
41
|
-
}>;
|
|
42
|
-
/** リクエストの署名の照合(本文は複製から読み込み、元のリクエストの本文は残す) */
|
|
43
|
-
export declare function verifyRequest(secret: string | undefined, request: Request, { header, subjectHeader, now }: VerifyRequestOptions): Promise<boolean>;
|
|
44
|
-
export type { SignedParts, VerifyRequestOptions };
|
|
1
|
+
import { a as signedHeaders, i as signParts, n as SignedParts, o as verifyParts, r as VerifyRequestOptions, s as verifyRequest, t as SIGNED_HEADERS } from "./signed-B_58gV21.js";
|
|
2
|
+
export { SIGNED_HEADERS, type SignedParts, type VerifyRequestOptions, signParts, signedHeaders, verifyParts, verifyRequest };
|
package/dist/preview.js
CHANGED
|
@@ -1,103 +1 @@
|
|
|
1
|
-
|
|
2
|
-
const CERTS_TTL_MS = 3e5;
|
|
3
|
-
const REFRESH_INTERVAL_MS = 3e4;
|
|
4
|
-
/** 公開鍵の一覧を保持する取得(取得の失敗は保持しない) */
|
|
5
|
-
function cachedCerts(fetcher, clock = Date.now) {
|
|
6
|
-
const cache = /* @__PURE__ */ new Map();
|
|
7
|
-
return async (url, refresh = false) => {
|
|
8
|
-
const hit = cache.get(url);
|
|
9
|
-
const age = hit ? clock() - hit.at : Infinity;
|
|
10
|
-
if (hit && age < (refresh ? REFRESH_INTERVAL_MS : CERTS_TTL_MS)) return hit;
|
|
11
|
-
const fresh = await fetcher(url);
|
|
12
|
-
cache.set(url, {
|
|
13
|
-
keys: fresh.keys,
|
|
14
|
-
at: clock()
|
|
15
|
-
});
|
|
16
|
-
return fresh;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
cachedCerts(async (url) => {
|
|
20
|
-
const res = await fetch(url);
|
|
21
|
-
if (!res.ok) throw new Error(`JWKS の取得に失敗(${res.status})`);
|
|
22
|
-
return res.json();
|
|
23
|
-
});
|
|
24
|
-
/** base64url の復号 */
|
|
25
|
-
const decodeBase64Url = (s) => Uint8Array.from(atob(s.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(s.length / 4) * 4, "=")), (c) => c.charCodeAt(0));
|
|
26
|
-
// ../access/src/signed.ts
|
|
27
|
-
const enc = new TextEncoder();
|
|
28
|
-
/** 既定で許す時刻のずれ(秒) */
|
|
29
|
-
const MAX_SKEW_SEC = 300;
|
|
30
|
-
/** 経路ごとのヘッダーの名前(署名と主体) */
|
|
31
|
-
const SIGNED_HEADERS = {
|
|
32
|
-
/** MCP → CMS の署名(鍵は MCP_TOKEN) */
|
|
33
|
-
mcp: "x-mcp-signature",
|
|
34
|
-
/** MCP → CMS のログインのメールアドレス(署名の主体) */
|
|
35
|
-
mcpEmail: "x-mcp-email",
|
|
36
|
-
/** CMS ⇄ サイトのプレビューと下書きの署名(鍵は PREVIEW_TOKEN) */
|
|
37
|
-
preview: "x-preview-signature",
|
|
38
|
-
/** CMS → 外の送信先の Webhook・外の関数の署名(鍵は登録ごと、受け手は verifyParts と同じ手順で照合) */
|
|
39
|
-
outbound: "x-cms-signature"
|
|
40
|
-
};
|
|
41
|
-
const base64Url = (bytes) => btoa(String.fromCharCode(...new Uint8Array(bytes))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
42
|
-
const hmacKey = (secret) => crypto.subtle.importKey("raw", enc.encode(secret), {
|
|
43
|
-
name: "HMAC",
|
|
44
|
-
hash: "SHA-256"
|
|
45
|
-
}, false, ["sign", "verify"]);
|
|
46
|
-
async function messageOf(t, { method, url, body, subject }) {
|
|
47
|
-
const { pathname, search } = new URL(url);
|
|
48
|
-
const bytes = typeof body === "string" ? enc.encode(body) : body ? Uint8Array.from(new Uint8Array(body)) : /* @__PURE__ */ new Uint8Array();
|
|
49
|
-
const digest = base64Url(await crypto.subtle.digest("SHA-256", bytes));
|
|
50
|
-
return enc.encode([
|
|
51
|
-
"v1",
|
|
52
|
-
t,
|
|
53
|
-
method.toUpperCase(),
|
|
54
|
-
`${pathname}${search}`,
|
|
55
|
-
subject ?? "",
|
|
56
|
-
digest
|
|
57
|
-
].join("\n"));
|
|
58
|
-
}
|
|
59
|
-
/** 署名のヘッダーの値(t=<秒>,v1=<署名>) */
|
|
60
|
-
async function signParts(secret, parts, now = Date.now()) {
|
|
61
|
-
const t = Math.floor(now / 1e3);
|
|
62
|
-
const sig = await crypto.subtle.sign("HMAC", await hmacKey(secret), await messageOf(t, parts));
|
|
63
|
-
return `t=${t},v1=${base64Url(sig)}`;
|
|
64
|
-
}
|
|
65
|
-
/** 署名の照合(形の誤り・時刻のずれの超過・不一致は false) */
|
|
66
|
-
async function verifyParts(secret, header, parts, { now = Date.now(), maxSkewSec = MAX_SKEW_SEC } = {}) {
|
|
67
|
-
if (!secret || !header) return false;
|
|
68
|
-
const fields = Object.fromEntries(header.split(",").map((f) => f.trim().split("=", 2)));
|
|
69
|
-
const t = Number(fields.t);
|
|
70
|
-
if (!Number.isInteger(t) || !fields.v1 || Math.abs(now / 1e3 - t) > maxSkewSec) return false;
|
|
71
|
-
try {
|
|
72
|
-
return await crypto.subtle.verify("HMAC", await hmacKey(secret), decodeBase64Url(fields.v1), await messageOf(t, parts));
|
|
73
|
-
} catch {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
/** 署名を付けたヘッダー(init のヘッダーに署名と主体を追加した新しい Headers) */
|
|
78
|
-
async function signedHeaders(secret, { method, url, body, subject }, { header, subjectHeader, init, now }) {
|
|
79
|
-
const headers = new Headers(init);
|
|
80
|
-
if (subjectHeader && subject) headers.set(subjectHeader, subject);
|
|
81
|
-
headers.set(header, await signParts(secret, {
|
|
82
|
-
method,
|
|
83
|
-
url,
|
|
84
|
-
body: body ?? null,
|
|
85
|
-
subject: subjectHeader ? subject : null
|
|
86
|
-
}, now));
|
|
87
|
-
return headers;
|
|
88
|
-
}
|
|
89
|
-
/** リクエストの署名の照合(本文は複製から読み込み、元のリクエストの本文は残す) */
|
|
90
|
-
async function verifyRequest(secret, request, { header, subjectHeader, now }) {
|
|
91
|
-
const value = request.headers.get(header);
|
|
92
|
-
if (!secret || !value) return false;
|
|
93
|
-
const body = request.body ? await request.clone().arrayBuffer() : null;
|
|
94
|
-
return verifyParts(secret, value, {
|
|
95
|
-
method: request.method,
|
|
96
|
-
url: request.url,
|
|
97
|
-
body,
|
|
98
|
-
subject: subjectHeader ? request.headers.get(subjectHeader) : null
|
|
99
|
-
}, now === void 0 ? {} : { now });
|
|
100
|
-
}
|
|
101
|
-
"0".repeat(32);
|
|
102
|
-
"0".repeat(16);
|
|
103
|
-
export { SIGNED_HEADERS, signParts, signedHeaders, verifyParts, verifyRequest };
|
|
1
|
+
export { t as SIGNED_HEADERS, n as signParts, r as signedHeaders, i as verifyParts, a as verifyRequest } from "./src-CA_67dWy.js";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** 経路ごとのヘッダーの名前(署名と主体) */
|
|
2
|
+
declare const SIGNED_HEADERS: {
|
|
3
|
+
/** MCP → CMS の署名(鍵は MCP_TOKEN) */
|
|
4
|
+
readonly mcp: "x-mcp-signature";
|
|
5
|
+
/** MCP → CMS のログインのメールアドレス(署名の主体) */
|
|
6
|
+
readonly mcpEmail: "x-mcp-email";
|
|
7
|
+
/** CMS ⇄ サイトのプレビューと下書きの署名(鍵は PREVIEW_TOKEN) */
|
|
8
|
+
readonly preview: "x-preview-signature";
|
|
9
|
+
/** CMS → 外の送信先の Webhook・外の関数の署名(鍵は登録ごと、受け手は verifyParts と同じ手順で照合) */
|
|
10
|
+
readonly outbound: "x-cms-signature";
|
|
11
|
+
};
|
|
12
|
+
/** 署名の対象(body は本文、なしは空) */
|
|
13
|
+
type SignedParts = Readonly<{
|
|
14
|
+
method: string;
|
|
15
|
+
url: string;
|
|
16
|
+
body?: string | ArrayBuffer | Uint8Array | null;
|
|
17
|
+
/** 主体(MCP のログインのメールアドレスなど、なしは空) */
|
|
18
|
+
subject?: string | null;
|
|
19
|
+
}>;
|
|
20
|
+
/** 署名のヘッダーの値(t=<秒>,v1=<署名>) */
|
|
21
|
+
declare function signParts(secret: string, parts: SignedParts, now?: number): Promise<string>;
|
|
22
|
+
/** 署名の照合(形の誤り・時刻のずれの超過・不一致は false) */
|
|
23
|
+
declare function verifyParts(secret: string, header: string | null | undefined, parts: SignedParts, { now, maxSkewSec }?: {
|
|
24
|
+
now?: number;
|
|
25
|
+
maxSkewSec?: number;
|
|
26
|
+
}): Promise<boolean>;
|
|
27
|
+
/** 署名を付けたヘッダー(init のヘッダーに署名と主体を追加した新しい Headers) */
|
|
28
|
+
declare function signedHeaders(secret: string, { method, url, body, subject }: SignedParts, { header, subjectHeader, init, now }: Readonly<{
|
|
29
|
+
header: string;
|
|
30
|
+
subjectHeader?: string;
|
|
31
|
+
init?: HeadersInit;
|
|
32
|
+
now?: number;
|
|
33
|
+
}>): Promise<Headers>;
|
|
34
|
+
/** 署名つきのリクエストを照合する設定 */
|
|
35
|
+
type VerifyRequestOptions = Readonly<{
|
|
36
|
+
/** 署名のヘッダーの名前 */
|
|
37
|
+
header: string;
|
|
38
|
+
/** 主体のヘッダーの名前(なしは主体なしで照合) */
|
|
39
|
+
subjectHeader?: string;
|
|
40
|
+
now?: number;
|
|
41
|
+
}>;
|
|
42
|
+
/** リクエストの署名の照合(本文は複製から読み込み、元のリクエストの本文は残す) */
|
|
43
|
+
declare function verifyRequest(secret: string | undefined, request: Request, { header, subjectHeader, now }: VerifyRequestOptions): Promise<boolean>;
|
|
44
|
+
export { signedHeaders as a, signParts as i, SignedParts as n, verifyParts as o, VerifyRequestOptions as r, verifyRequest as s, SIGNED_HEADERS as t };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// ../access/src/jwt.ts
|
|
2
|
+
const CERTS_TTL_MS = 3e5;
|
|
3
|
+
const REFRESH_INTERVAL_MS = 3e4;
|
|
4
|
+
/** 公開鍵の一覧を保持する取得(取得の失敗は保持しない) */
|
|
5
|
+
function cachedCerts(fetcher, clock = Date.now) {
|
|
6
|
+
const cache = /* @__PURE__ */ new Map();
|
|
7
|
+
return async (url, refresh = false) => {
|
|
8
|
+
const hit = cache.get(url);
|
|
9
|
+
const age = hit ? clock() - hit.at : Infinity;
|
|
10
|
+
if (hit && age < (refresh ? REFRESH_INTERVAL_MS : CERTS_TTL_MS)) return hit;
|
|
11
|
+
const fresh = await fetcher(url);
|
|
12
|
+
cache.set(url, {
|
|
13
|
+
keys: fresh.keys,
|
|
14
|
+
at: clock()
|
|
15
|
+
});
|
|
16
|
+
return fresh;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
cachedCerts(async (url) => {
|
|
20
|
+
const res = await fetch(url);
|
|
21
|
+
if (!res.ok) throw new Error(`JWKS の取得に失敗(${res.status})`);
|
|
22
|
+
return res.json();
|
|
23
|
+
});
|
|
24
|
+
/** base64url の復号 */
|
|
25
|
+
const decodeBase64Url = (s) => Uint8Array.from(atob(s.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(s.length / 4) * 4, "=")), (c) => c.charCodeAt(0));
|
|
26
|
+
// ../access/src/signed.ts
|
|
27
|
+
const enc = new TextEncoder();
|
|
28
|
+
/** 既定で許す時刻のずれ(秒) */
|
|
29
|
+
const MAX_SKEW_SEC = 300;
|
|
30
|
+
/** 経路ごとのヘッダーの名前(署名と主体) */
|
|
31
|
+
const SIGNED_HEADERS = {
|
|
32
|
+
/** MCP → CMS の署名(鍵は MCP_TOKEN) */
|
|
33
|
+
mcp: "x-mcp-signature",
|
|
34
|
+
/** MCP → CMS のログインのメールアドレス(署名の主体) */
|
|
35
|
+
mcpEmail: "x-mcp-email",
|
|
36
|
+
/** CMS ⇄ サイトのプレビューと下書きの署名(鍵は PREVIEW_TOKEN) */
|
|
37
|
+
preview: "x-preview-signature",
|
|
38
|
+
/** CMS → 外の送信先の Webhook・外の関数の署名(鍵は登録ごと、受け手は verifyParts と同じ手順で照合) */
|
|
39
|
+
outbound: "x-cms-signature"
|
|
40
|
+
};
|
|
41
|
+
const base64Url = (bytes) => btoa(String.fromCharCode(...new Uint8Array(bytes))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
42
|
+
const hmacKey = (secret) => crypto.subtle.importKey("raw", enc.encode(secret), {
|
|
43
|
+
name: "HMAC",
|
|
44
|
+
hash: "SHA-256"
|
|
45
|
+
}, false, ["sign", "verify"]);
|
|
46
|
+
async function messageOf(t, { method, url, body, subject }) {
|
|
47
|
+
const { pathname, search } = new URL(url);
|
|
48
|
+
const bytes = typeof body === "string" ? enc.encode(body) : body ? Uint8Array.from(new Uint8Array(body)) : /* @__PURE__ */ new Uint8Array();
|
|
49
|
+
const digest = base64Url(await crypto.subtle.digest("SHA-256", bytes));
|
|
50
|
+
return enc.encode([
|
|
51
|
+
"v1",
|
|
52
|
+
t,
|
|
53
|
+
method.toUpperCase(),
|
|
54
|
+
`${pathname}${search}`,
|
|
55
|
+
subject ?? "",
|
|
56
|
+
digest
|
|
57
|
+
].join("\n"));
|
|
58
|
+
}
|
|
59
|
+
/** 署名のヘッダーの値(t=<秒>,v1=<署名>) */
|
|
60
|
+
async function signParts(secret, parts, now = Date.now()) {
|
|
61
|
+
const t = Math.floor(now / 1e3);
|
|
62
|
+
const sig = await crypto.subtle.sign("HMAC", await hmacKey(secret), await messageOf(t, parts));
|
|
63
|
+
return `t=${t},v1=${base64Url(sig)}`;
|
|
64
|
+
}
|
|
65
|
+
/** 署名の照合(形の誤り・時刻のずれの超過・不一致は false) */
|
|
66
|
+
async function verifyParts(secret, header, parts, { now = Date.now(), maxSkewSec = MAX_SKEW_SEC } = {}) {
|
|
67
|
+
if (!secret || !header) return false;
|
|
68
|
+
const fields = Object.fromEntries(header.split(",").map((f) => f.trim().split("=", 2)));
|
|
69
|
+
const t = Number(fields.t);
|
|
70
|
+
if (!Number.isInteger(t) || !fields.v1 || Math.abs(now / 1e3 - t) > maxSkewSec) return false;
|
|
71
|
+
try {
|
|
72
|
+
return await crypto.subtle.verify("HMAC", await hmacKey(secret), decodeBase64Url(fields.v1), await messageOf(t, parts));
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** 署名を付けたヘッダー(init のヘッダーに署名と主体を追加した新しい Headers) */
|
|
78
|
+
async function signedHeaders(secret, { method, url, body, subject }, { header, subjectHeader, init, now }) {
|
|
79
|
+
const headers = new Headers(init);
|
|
80
|
+
if (subjectHeader && subject) headers.set(subjectHeader, subject);
|
|
81
|
+
headers.set(header, await signParts(secret, {
|
|
82
|
+
method,
|
|
83
|
+
url,
|
|
84
|
+
body: body ?? null,
|
|
85
|
+
subject: subjectHeader ? subject : null
|
|
86
|
+
}, now));
|
|
87
|
+
return headers;
|
|
88
|
+
}
|
|
89
|
+
/** リクエストの署名の照合(本文は複製から読み込み、元のリクエストの本文は残す) */
|
|
90
|
+
async function verifyRequest(secret, request, { header, subjectHeader, now }) {
|
|
91
|
+
const value = request.headers.get(header);
|
|
92
|
+
if (!secret || !value) return false;
|
|
93
|
+
const body = request.body ? await request.clone().arrayBuffer() : null;
|
|
94
|
+
return verifyParts(secret, value, {
|
|
95
|
+
method: request.method,
|
|
96
|
+
url: request.url,
|
|
97
|
+
body,
|
|
98
|
+
subject: subjectHeader ? request.headers.get(subjectHeader) : null
|
|
99
|
+
}, now === void 0 ? {} : { now });
|
|
100
|
+
}
|
|
101
|
+
"0".repeat(32);
|
|
102
|
+
"0".repeat(16);
|
|
103
|
+
export { verifyRequest as a, verifyParts as i, signParts as n, signedHeaders as r, SIGNED_HEADERS as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryusenkei/maezae",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "maezae の導入先のサイト向けの SDK(配信 API の取得・本文の型・プレビューの署名)",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "RYUSENKEI",
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"./preview": {
|
|
28
28
|
"types": "./dist/preview.d.ts",
|
|
29
29
|
"default": "./dist/preview.js"
|
|
30
|
+
},
|
|
31
|
+
"./integrations": {
|
|
32
|
+
"types": "./dist/integrations.d.ts",
|
|
33
|
+
"default": "./dist/integrations.js"
|
|
30
34
|
}
|
|
31
35
|
},
|
|
32
36
|
"files": [
|