@01.software/sdk 0.5.3 → 0.5.5
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/auth.cjs +22 -46
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.js +22 -47
- package/dist/auth.js.map +1 -1
- package/dist/index.cjs +922 -1069
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +922 -1072
- package/dist/index.js.map +1 -1
- package/dist/realtime.cjs +75 -101
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.js +75 -102
- package/dist/realtime.js.map +1 -1
- package/dist/ui/code-block.cjs +13 -35
- package/dist/ui/code-block.cjs.map +1 -1
- package/dist/ui/code-block.js +13 -35
- package/dist/ui/code-block.js.map +1 -1
- package/dist/ui/flow/server.cjs +26 -68
- package/dist/ui/flow/server.cjs.map +1 -1
- package/dist/ui/flow/server.js +26 -71
- package/dist/ui/flow/server.js.map +1 -1
- package/dist/ui/flow.cjs +244 -238
- package/dist/ui/flow.cjs.map +1 -1
- package/dist/ui/flow.js +244 -240
- package/dist/ui/flow.js.map +1 -1
- package/dist/ui/form.cjs +40 -84
- package/dist/ui/form.cjs.map +1 -1
- package/dist/ui/form.js +40 -86
- package/dist/ui/form.js.map +1 -1
- package/dist/ui/image.cjs +27 -40
- package/dist/ui/image.cjs.map +1 -1
- package/dist/ui/image.js +27 -42
- package/dist/ui/image.js.map +1 -1
- package/dist/ui/rich-text.cjs +33 -67
- package/dist/ui/rich-text.cjs.map +1 -1
- package/dist/ui/rich-text.js +33 -69
- package/dist/ui/rich-text.js.map +1 -1
- package/dist/ui/video.cjs +32 -50
- package/dist/ui/video.cjs.map +1 -1
- package/dist/ui/video.js +32 -52
- package/dist/ui/video.js.map +1 -1
- package/dist/webhook.cjs +48 -73
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.js +48 -74
- package/dist/webhook.js.map +1 -1
- package/package.json +3 -3
package/dist/ui/video.js
CHANGED
|
@@ -1,20 +1,4 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
4
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
-
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
6
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
|
-
var __spreadValues = (a, b) => {
|
|
8
|
-
for (var prop in b || (b = {}))
|
|
9
|
-
if (__hasOwnProp.call(b, prop))
|
|
10
|
-
__defNormalProp(a, prop, b[prop]);
|
|
11
|
-
if (__getOwnPropSymbols)
|
|
12
|
-
for (var prop of __getOwnPropSymbols(b)) {
|
|
13
|
-
if (__propIsEnum.call(b, prop))
|
|
14
|
-
__defNormalProp(a, prop, b[prop]);
|
|
15
|
-
}
|
|
16
|
-
return a;
|
|
17
|
-
};
|
|
18
2
|
|
|
19
3
|
// src/ui/Video/index.tsx
|
|
20
4
|
import React, { forwardRef } from "react";
|
|
@@ -24,24 +8,22 @@ import MuxPlayer from "@mux/mux-player-react";
|
|
|
24
8
|
var MUX_IMAGE_BASE = "https://image.mux.com";
|
|
25
9
|
var MUX_STREAM_BASE = "https://stream.mux.com";
|
|
26
10
|
function getVideoThumbnail(playbackId, options) {
|
|
27
|
-
var _a;
|
|
28
11
|
const params = new URLSearchParams();
|
|
29
|
-
params.set("width", String(
|
|
30
|
-
if (options
|
|
31
|
-
if (
|
|
32
|
-
if (options
|
|
33
|
-
if (options
|
|
34
|
-
if (options
|
|
35
|
-
if (options
|
|
12
|
+
params.set("width", String(options?.width ?? 640));
|
|
13
|
+
if (options?.height) params.set("height", String(options.height));
|
|
14
|
+
if (options?.time != null) params.set("time", String(options.time));
|
|
15
|
+
if (options?.fitMode) params.set("fit_mode", options.fitMode);
|
|
16
|
+
if (options?.flipH) params.set("flip_h", "true");
|
|
17
|
+
if (options?.flipV) params.set("flip_v", "true");
|
|
18
|
+
if (options?.rotate) params.set("rotate", String(options.rotate));
|
|
36
19
|
return `${MUX_IMAGE_BASE}/${playbackId}/thumbnail.jpg?${params}`;
|
|
37
20
|
}
|
|
38
21
|
function getVideoGif(playbackId, options) {
|
|
39
|
-
var _a;
|
|
40
22
|
const params = new URLSearchParams();
|
|
41
|
-
params.set("width", String(
|
|
42
|
-
if (
|
|
43
|
-
if (
|
|
44
|
-
if (options
|
|
23
|
+
params.set("width", String(options?.width ?? 320));
|
|
24
|
+
if (options?.start != null) params.set("start", String(options.start));
|
|
25
|
+
if (options?.end != null) params.set("end", String(options.end));
|
|
26
|
+
if (options?.fps) params.set("fps", String(options.fps));
|
|
45
27
|
return `${MUX_IMAGE_BASE}/${playbackId}/animated.gif?${params}`;
|
|
46
28
|
}
|
|
47
29
|
function getVideoStoryboard(playbackId) {
|
|
@@ -56,18 +38,15 @@ function getVideoMp4Url(playbackId, resolution = "high") {
|
|
|
56
38
|
|
|
57
39
|
// src/ui/Video/index.tsx
|
|
58
40
|
function resolvePlaybackId(video) {
|
|
59
|
-
var _a;
|
|
60
41
|
if (typeof video === "string") return video;
|
|
61
|
-
return
|
|
42
|
+
return video.muxPlaybackId ?? null;
|
|
62
43
|
}
|
|
63
44
|
function resolveTitle(props) {
|
|
64
|
-
var _a;
|
|
65
45
|
if (props.title !== void 0) return props.title;
|
|
66
|
-
if (typeof props.video !== "string") return
|
|
46
|
+
if (typeof props.video !== "string") return props.video.title ?? void 0;
|
|
67
47
|
return void 0;
|
|
68
48
|
}
|
|
69
49
|
function resolvePoster(props) {
|
|
70
|
-
var _a;
|
|
71
50
|
if (props.poster) return props.poster;
|
|
72
51
|
if (typeof props.video !== "string" && props.video.thumbnail) {
|
|
73
52
|
const thumb = props.video.thumbnail;
|
|
@@ -80,18 +59,16 @@ function resolvePoster(props) {
|
|
|
80
59
|
if (!playbackId) return void 0;
|
|
81
60
|
return getVideoThumbnail(playbackId, {
|
|
82
61
|
width: 1280,
|
|
83
|
-
time:
|
|
62
|
+
time: props.thumbnailTime ?? 0
|
|
84
63
|
});
|
|
85
64
|
}
|
|
86
65
|
function resolveAspectRatio(video) {
|
|
87
|
-
var _a;
|
|
88
66
|
if (typeof video === "string") return void 0;
|
|
89
|
-
return
|
|
67
|
+
return video.aspectRatio ?? void 0;
|
|
90
68
|
}
|
|
91
69
|
function resolveStatus(video) {
|
|
92
|
-
var _a;
|
|
93
70
|
if (typeof video === "string") return "ready";
|
|
94
|
-
return
|
|
71
|
+
return video.status ?? "waiting";
|
|
95
72
|
}
|
|
96
73
|
var VideoPlayer = forwardRef(
|
|
97
74
|
function VideoPlayer2(props, ref) {
|
|
@@ -131,24 +108,25 @@ var VideoPlayer = forwardRef(
|
|
|
131
108
|
return /* @__PURE__ */ React.createElement(
|
|
132
109
|
"div",
|
|
133
110
|
{
|
|
134
|
-
className: placeholderClassName
|
|
135
|
-
style:
|
|
111
|
+
className: placeholderClassName ?? className,
|
|
112
|
+
style: {
|
|
136
113
|
display: "flex",
|
|
137
114
|
alignItems: "center",
|
|
138
115
|
justifyContent: "center",
|
|
139
|
-
aspectRatio: aspectRatio
|
|
116
|
+
aspectRatio: aspectRatio ?? "16/9",
|
|
140
117
|
backgroundColor: "#000",
|
|
141
118
|
color: "#888",
|
|
142
119
|
fontSize: 14,
|
|
143
|
-
width: "100%"
|
|
144
|
-
|
|
120
|
+
width: "100%",
|
|
121
|
+
...placeholderStyle
|
|
122
|
+
}
|
|
145
123
|
},
|
|
146
|
-
placeholder
|
|
124
|
+
placeholder ?? (status === "preparing" ? "Video is processing..." : status === "errored" ? "Video processing failed" : "Video not available")
|
|
147
125
|
);
|
|
148
126
|
}
|
|
149
127
|
return /* @__PURE__ */ React.createElement(
|
|
150
128
|
MuxPlayer,
|
|
151
|
-
|
|
129
|
+
{
|
|
152
130
|
ref,
|
|
153
131
|
playbackId,
|
|
154
132
|
streamType,
|
|
@@ -166,17 +144,19 @@ var VideoPlayer = forwardRef(
|
|
|
166
144
|
nohotkeys: noHotKeys,
|
|
167
145
|
maxResolution,
|
|
168
146
|
className,
|
|
169
|
-
style:
|
|
170
|
-
aspectRatio: aspectRatio
|
|
171
|
-
width: "100%"
|
|
172
|
-
|
|
147
|
+
style: {
|
|
148
|
+
aspectRatio: aspectRatio ?? "16/9",
|
|
149
|
+
width: "100%",
|
|
150
|
+
...style
|
|
151
|
+
},
|
|
173
152
|
onPlay,
|
|
174
153
|
onPause,
|
|
175
154
|
onEnded,
|
|
176
155
|
onTimeUpdate,
|
|
177
156
|
onLoadedData,
|
|
178
|
-
onError
|
|
179
|
-
|
|
157
|
+
onError,
|
|
158
|
+
...playerProps
|
|
159
|
+
}
|
|
180
160
|
);
|
|
181
161
|
}
|
|
182
162
|
);
|
package/dist/ui/video.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ui/Video/index.tsx","../../src/utils/video.ts"],"sourcesContent":["'use client'\n\nimport React, { forwardRef, type CSSProperties } from 'react'\nimport MuxPlayer from '@mux/mux-player-react'\nimport type {\n MuxPlayerRefAttributes,\n MuxPlayerProps,\n} from '@mux/mux-player-react'\nimport type { Video, Image } from '../../payload-types'\nimport { getVideoThumbnail } from '../../utils/video'\n\n// Re-export video utilities for convenience\nexport {\n getVideoThumbnail,\n getVideoGif,\n getVideoStreamUrl,\n getVideoMp4Url,\n getVideoStoryboard,\n type VideoThumbnailOptions,\n type VideoGifOptions,\n} from '../../utils/video'\n\n// ── CSS custom properties ──\n\nexport interface VideoPlayerCSSProperties extends CSSProperties {\n [key: `--${string}`]: string | undefined\n}\n\n// ── Props ──\n\nexport interface VideoPlayerProps {\n /** Video document or playbackId string */\n video: Video | string\n\n // ── Theming ──\n /** Theme accent color */\n accentColor?: string\n /** Theme primary color (controls) */\n primaryColor?: string\n /** Theme secondary color (time display, etc.) */\n secondaryColor?: string\n\n // ── Playback ──\n /** Title overlay (auto-resolved from Video.title if omitted) */\n title?: string\n /** Autoplay behavior */\n autoPlay?: boolean | 'muted' | 'any'\n /** Loop playback */\n loop?: boolean\n /** Muted by default */\n muted?: boolean\n /** Preload strategy (default: 'metadata') */\n preload?: 'auto' | 'metadata' | 'none'\n /** Start time in seconds */\n startTime?: number\n /** Stream type (default: 'on-demand') */\n streamType?: 'on-demand' | 'live' | 'll-live'\n /** Audio-only mode */\n audio?: boolean\n /** Disable keyboard shortcuts */\n noHotKeys?: boolean\n /** Max resolution cap */\n maxResolution?: '720p' | '1080p' | '1440p' | '2160p'\n\n // ── Poster ──\n /** Thumbnail time for auto-generated poster (default: 0) */\n thumbnailTime?: number\n /** Custom poster URL (overrides auto-generated thumbnail) */\n poster?: string\n\n // ── Placeholder (when video not ready) ──\n /** Placeholder text when video is not ready */\n placeholder?: string\n /** CSS class for the placeholder */\n placeholderClassName?: string\n /** Inline styles for the placeholder */\n placeholderStyle?: CSSProperties\n\n // ── Styling ──\n /** CSS class */\n className?: string\n /** Inline styles (supports --mux CSS custom properties) */\n style?: VideoPlayerCSSProperties\n\n // ── Events ──\n onPlay?: () => void\n onPause?: () => void\n onEnded?: () => void\n onTimeUpdate?: () => void\n onLoadedData?: () => void\n onError?: () => void\n\n // ── Escape hatch ──\n /** Extra props forwarded directly to MuxPlayer */\n playerProps?: Omit<\n MuxPlayerProps,\n | 'playbackId'\n | 'streamType'\n | 'accentColor'\n | 'primaryColor'\n | 'secondaryColor'\n | 'title'\n | 'autoPlay'\n | 'loop'\n | 'muted'\n | 'preload'\n | 'startTime'\n | 'poster'\n | 'audio'\n | 'nohotkeys'\n | 'maxResolution'\n | 'className'\n | 'style'\n >\n}\n\n// ── Helpers ──\n\nfunction resolvePlaybackId(video: Video | string): string | null {\n if (typeof video === 'string') return video\n return video.muxPlaybackId ?? null\n}\n\nfunction resolveTitle(props: VideoPlayerProps): string | undefined {\n if (props.title !== undefined) return props.title\n if (typeof props.video !== 'string') return props.video.title ?? undefined\n return undefined\n}\n\nfunction resolvePoster(props: VideoPlayerProps): string | undefined {\n // 1. Explicit poster URL (highest priority)\n if (props.poster) return props.poster\n\n // 2. Video.thumbnail uploaded image\n if (typeof props.video !== 'string' && props.video.thumbnail) {\n const thumb = props.video.thumbnail\n if (typeof thumb === 'object' && thumb !== null && 'url' in thumb) {\n const url = (thumb as Image).url\n if (url) return url\n }\n }\n\n // 3. Auto-generate from Mux image API\n const playbackId = resolvePlaybackId(props.video)\n if (!playbackId) return undefined\n return getVideoThumbnail(playbackId, {\n width: 1280,\n time: props.thumbnailTime ?? 0,\n })\n}\n\nfunction resolveAspectRatio(video: Video | string): string | undefined {\n if (typeof video === 'string') return undefined\n return video.aspectRatio ?? undefined\n}\n\nfunction resolveStatus(video: Video | string): string {\n if (typeof video === 'string') return 'ready'\n return video.status ?? 'waiting'\n}\n\n// ── Component ──\n\n/**\n * Mux video player component.\n * Requires `@mux/mux-player-react` (optional peer dependency).\n *\n * @example Basic usage\n * ```tsx\n * import { VideoPlayer } from '@01.software/sdk/ui/video'\n *\n * <VideoPlayer video={videoDoc} />\n * ```\n *\n * @example With playback ID string\n * ```tsx\n * <VideoPlayer video=\"abc123def\" autoPlay=\"muted\" loop />\n * ```\n *\n * @example Custom theming with CSS custom properties\n * ```tsx\n * <VideoPlayer\n * video={videoDoc}\n * accentColor=\"#f43f5e\"\n * primaryColor=\"#fff\"\n * className=\"rounded-lg overflow-hidden\"\n * style={{ '--controls-backdrop-color': 'rgba(0,0,0,0.6)' }}\n * />\n * ```\n *\n * @example Audio-only mode\n * ```tsx\n * <VideoPlayer video={videoDoc} audio />\n * ```\n */\nexport const VideoPlayer: React.ForwardRefExoticComponent<\n VideoPlayerProps & React.RefAttributes<MuxPlayerRefAttributes>\n> = forwardRef<MuxPlayerRefAttributes, VideoPlayerProps>(\n function VideoPlayer(props, ref) {\n const {\n video,\n accentColor,\n primaryColor,\n secondaryColor,\n autoPlay,\n loop,\n muted,\n preload = 'metadata',\n startTime,\n streamType = 'on-demand',\n audio,\n noHotKeys,\n maxResolution,\n placeholder,\n placeholderClassName,\n placeholderStyle,\n className,\n style,\n onPlay,\n onPause,\n onEnded,\n onTimeUpdate,\n onLoadedData,\n onError,\n playerProps,\n } = props\n\n const playbackId = resolvePlaybackId(video)\n const title = resolveTitle(props)\n const poster = resolvePoster(props)\n const aspectRatio = resolveAspectRatio(video)\n const status = resolveStatus(video)\n\n // Show placeholder when no playbackId or video not ready\n if (!playbackId || (typeof video !== 'string' && status !== 'ready')) {\n return (\n <div\n className={placeholderClassName ?? className}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n aspectRatio: aspectRatio ?? '16/9',\n backgroundColor: '#000',\n color: '#888',\n fontSize: 14,\n width: '100%',\n ...placeholderStyle,\n }}\n >\n {placeholder ??\n (status === 'preparing'\n ? 'Video is processing...'\n : status === 'errored'\n ? 'Video processing failed'\n : 'Video not available')}\n </div>\n )\n }\n\n return (\n <MuxPlayer\n ref={ref}\n playbackId={playbackId}\n streamType={streamType}\n accentColor={accentColor}\n primaryColor={primaryColor}\n secondaryColor={secondaryColor}\n title={title}\n autoPlay={autoPlay}\n loop={loop}\n muted={muted}\n preload={preload}\n startTime={startTime}\n poster={poster}\n audio={audio}\n nohotkeys={noHotKeys}\n maxResolution={maxResolution}\n className={className}\n style={{\n aspectRatio: aspectRatio ?? '16/9',\n width: '100%',\n ...style,\n }}\n onPlay={onPlay}\n onPause={onPause}\n onEnded={onEnded}\n onTimeUpdate={onTimeUpdate}\n onLoadedData={onLoadedData}\n onError={onError}\n {...playerProps}\n />\n )\n },\n)\n\n// ── Type Exports ──\n\nexport type { MuxPlayerRefAttributes as VideoPlayerRef } from '@mux/mux-player-react'\nexport type { Video as VideoData } from '../../payload-types'\n","const MUX_IMAGE_BASE = 'https://image.mux.com'\nconst MUX_STREAM_BASE = 'https://stream.mux.com'\n\n// ── Thumbnail ──\n\nexport interface VideoThumbnailOptions {\n /** Width in pixels (default: 640) */\n width?: number\n /** Height in pixels (auto if omitted) */\n height?: number\n /** Time offset in seconds (default: 0) */\n time?: number\n /** Fit mode (default: 'smartcrop') */\n fitMode?: 'preserve' | 'stretch' | 'crop' | 'smartcrop' | 'pad'\n /** Flip horizontally */\n flipH?: boolean\n /** Flip vertically */\n flipV?: boolean\n /** Rotation in degrees (90, 180, 270) */\n rotate?: 90 | 180 | 270\n}\n\n/**\n * Returns a Mux thumbnail URL for a video.\n *\n * @example\n * ```ts\n * getVideoThumbnail('abc123')\n * // => 'https://image.mux.com/abc123/thumbnail.jpg?width=640'\n *\n * getVideoThumbnail('abc123', { width: 320, time: 5 })\n * // => 'https://image.mux.com/abc123/thumbnail.jpg?width=320&time=5'\n * ```\n */\nexport function getVideoThumbnail(\n playbackId: string,\n options?: VideoThumbnailOptions,\n): string {\n const params = new URLSearchParams()\n params.set('width', String(options?.width ?? 640))\n if (options?.height) params.set('height', String(options.height))\n if (options?.time != null) params.set('time', String(options.time))\n if (options?.fitMode) params.set('fit_mode', options.fitMode)\n if (options?.flipH) params.set('flip_h', 'true')\n if (options?.flipV) params.set('flip_v', 'true')\n if (options?.rotate) params.set('rotate', String(options.rotate))\n return `${MUX_IMAGE_BASE}/${playbackId}/thumbnail.jpg?${params}`\n}\n\n// ── Animated GIF ──\n\nexport interface VideoGifOptions {\n /** Width in pixels (default: 320) */\n width?: number\n /** Start time in seconds (default: 0) */\n start?: number\n /** End time in seconds (default: start + 5) */\n end?: number\n /** Frames per second (default: 15) */\n fps?: number\n}\n\n/**\n * Returns a Mux animated GIF URL for a video.\n *\n * @example\n * ```ts\n * getVideoGif('abc123')\n * // => 'https://image.mux.com/abc123/animated.gif?width=320'\n *\n * getVideoGif('abc123', { width: 240, start: 2, end: 6 })\n * // => 'https://image.mux.com/abc123/animated.gif?width=240&start=2&end=6'\n * ```\n */\nexport function getVideoGif(\n playbackId: string,\n options?: VideoGifOptions,\n): string {\n const params = new URLSearchParams()\n params.set('width', String(options?.width ?? 320))\n if (options?.start != null) params.set('start', String(options.start))\n if (options?.end != null) params.set('end', String(options.end))\n if (options?.fps) params.set('fps', String(options.fps))\n return `${MUX_IMAGE_BASE}/${playbackId}/animated.gif?${params}`\n}\n\n// ── Storyboard ──\n\n/**\n * Returns a Mux storyboard VTT URL for timeline hover previews.\n *\n * @example\n * ```ts\n * getVideoStoryboard('abc123')\n * // => 'https://image.mux.com/abc123/storyboard.vtt'\n * ```\n */\nexport function getVideoStoryboard(playbackId: string): string {\n return `${MUX_IMAGE_BASE}/${playbackId}/storyboard.vtt`\n}\n\n// ── Stream / Playback URLs ──\n\n/**\n * Returns the HLS stream URL for a video.\n *\n * @example\n * ```ts\n * getVideoStreamUrl('abc123')\n * // => 'https://stream.mux.com/abc123.m3u8'\n * ```\n */\nexport function getVideoStreamUrl(playbackId: string): string {\n return `${MUX_STREAM_BASE}/${playbackId}.m3u8`\n}\n\n/**\n * Returns a Mux MP4 download URL for a video.\n * Only available if the asset was created with `mp4_support` enabled.\n *\n * @example\n * ```ts\n * getVideoMp4Url('abc123', 'high')\n * // => 'https://stream.mux.com/abc123/high.mp4'\n * ```\n */\nexport function getVideoMp4Url(\n playbackId: string,\n resolution: 'high' | 'medium' | 'low' = 'high',\n): string {\n return `${MUX_STREAM_BASE}/${playbackId}/${resolution}.mp4`\n}\n"],"mappings":"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","names":["VideoPlayer"]}
|
|
1
|
+
{"version":3,"sources":["../../src/ui/Video/index.tsx","../../src/utils/video.ts"],"sourcesContent":["'use client'\n\nimport React, { forwardRef, type CSSProperties } from 'react'\nimport MuxPlayer from '@mux/mux-player-react'\nimport type {\n MuxPlayerRefAttributes,\n MuxPlayerProps,\n} from '@mux/mux-player-react'\nimport type { Video, Image } from '../../payload-types'\nimport { getVideoThumbnail } from '../../utils/video'\n\n// Re-export video utilities for convenience\nexport {\n getVideoThumbnail,\n getVideoGif,\n getVideoStreamUrl,\n getVideoMp4Url,\n getVideoStoryboard,\n type VideoThumbnailOptions,\n type VideoGifOptions,\n} from '../../utils/video'\n\n// ── CSS custom properties ──\n\nexport interface VideoPlayerCSSProperties extends CSSProperties {\n [key: `--${string}`]: string | undefined\n}\n\n// ── Props ──\n\nexport interface VideoPlayerProps {\n /** Video document or playbackId string */\n video: Video | string\n\n // ── Theming ──\n /** Theme accent color */\n accentColor?: string\n /** Theme primary color (controls) */\n primaryColor?: string\n /** Theme secondary color (time display, etc.) */\n secondaryColor?: string\n\n // ── Playback ──\n /** Title overlay (auto-resolved from Video.title if omitted) */\n title?: string\n /** Autoplay behavior */\n autoPlay?: boolean | 'muted' | 'any'\n /** Loop playback */\n loop?: boolean\n /** Muted by default */\n muted?: boolean\n /** Preload strategy (default: 'metadata') */\n preload?: 'auto' | 'metadata' | 'none'\n /** Start time in seconds */\n startTime?: number\n /** Stream type (default: 'on-demand') */\n streamType?: 'on-demand' | 'live' | 'll-live'\n /** Audio-only mode */\n audio?: boolean\n /** Disable keyboard shortcuts */\n noHotKeys?: boolean\n /** Max resolution cap */\n maxResolution?: '720p' | '1080p' | '1440p' | '2160p'\n\n // ── Poster ──\n /** Thumbnail time for auto-generated poster (default: 0) */\n thumbnailTime?: number\n /** Custom poster URL (overrides auto-generated thumbnail) */\n poster?: string\n\n // ── Placeholder (when video not ready) ──\n /** Placeholder text when video is not ready */\n placeholder?: string\n /** CSS class for the placeholder */\n placeholderClassName?: string\n /** Inline styles for the placeholder */\n placeholderStyle?: CSSProperties\n\n // ── Styling ──\n /** CSS class */\n className?: string\n /** Inline styles (supports --mux CSS custom properties) */\n style?: VideoPlayerCSSProperties\n\n // ── Events ──\n onPlay?: () => void\n onPause?: () => void\n onEnded?: () => void\n onTimeUpdate?: () => void\n onLoadedData?: () => void\n onError?: () => void\n\n // ── Escape hatch ──\n /** Extra props forwarded directly to MuxPlayer */\n playerProps?: Omit<\n MuxPlayerProps,\n | 'playbackId'\n | 'streamType'\n | 'accentColor'\n | 'primaryColor'\n | 'secondaryColor'\n | 'title'\n | 'autoPlay'\n | 'loop'\n | 'muted'\n | 'preload'\n | 'startTime'\n | 'poster'\n | 'audio'\n | 'nohotkeys'\n | 'maxResolution'\n | 'className'\n | 'style'\n >\n}\n\n// ── Helpers ──\n\nfunction resolvePlaybackId(video: Video | string): string | null {\n if (typeof video === 'string') return video\n return video.muxPlaybackId ?? null\n}\n\nfunction resolveTitle(props: VideoPlayerProps): string | undefined {\n if (props.title !== undefined) return props.title\n if (typeof props.video !== 'string') return props.video.title ?? undefined\n return undefined\n}\n\nfunction resolvePoster(props: VideoPlayerProps): string | undefined {\n // 1. Explicit poster URL (highest priority)\n if (props.poster) return props.poster\n\n // 2. Video.thumbnail uploaded image\n if (typeof props.video !== 'string' && props.video.thumbnail) {\n const thumb = props.video.thumbnail\n if (typeof thumb === 'object' && thumb !== null && 'url' in thumb) {\n const url = (thumb as Image).url\n if (url) return url\n }\n }\n\n // 3. Auto-generate from Mux image API\n const playbackId = resolvePlaybackId(props.video)\n if (!playbackId) return undefined\n return getVideoThumbnail(playbackId, {\n width: 1280,\n time: props.thumbnailTime ?? 0,\n })\n}\n\nfunction resolveAspectRatio(video: Video | string): string | undefined {\n if (typeof video === 'string') return undefined\n return video.aspectRatio ?? undefined\n}\n\nfunction resolveStatus(video: Video | string): string {\n if (typeof video === 'string') return 'ready'\n return video.status ?? 'waiting'\n}\n\n// ── Component ──\n\n/**\n * Mux video player component.\n * Requires `@mux/mux-player-react` (optional peer dependency).\n *\n * @example Basic usage\n * ```tsx\n * import { VideoPlayer } from '@01.software/sdk/ui/video'\n *\n * <VideoPlayer video={videoDoc} />\n * ```\n *\n * @example With playback ID string\n * ```tsx\n * <VideoPlayer video=\"abc123def\" autoPlay=\"muted\" loop />\n * ```\n *\n * @example Custom theming with CSS custom properties\n * ```tsx\n * <VideoPlayer\n * video={videoDoc}\n * accentColor=\"#f43f5e\"\n * primaryColor=\"#fff\"\n * className=\"rounded-lg overflow-hidden\"\n * style={{ '--controls-backdrop-color': 'rgba(0,0,0,0.6)' }}\n * />\n * ```\n *\n * @example Audio-only mode\n * ```tsx\n * <VideoPlayer video={videoDoc} audio />\n * ```\n */\nexport const VideoPlayer: React.ForwardRefExoticComponent<\n VideoPlayerProps & React.RefAttributes<MuxPlayerRefAttributes>\n> = forwardRef<MuxPlayerRefAttributes, VideoPlayerProps>(\n function VideoPlayer(props, ref) {\n const {\n video,\n accentColor,\n primaryColor,\n secondaryColor,\n autoPlay,\n loop,\n muted,\n preload = 'metadata',\n startTime,\n streamType = 'on-demand',\n audio,\n noHotKeys,\n maxResolution,\n placeholder,\n placeholderClassName,\n placeholderStyle,\n className,\n style,\n onPlay,\n onPause,\n onEnded,\n onTimeUpdate,\n onLoadedData,\n onError,\n playerProps,\n } = props\n\n const playbackId = resolvePlaybackId(video)\n const title = resolveTitle(props)\n const poster = resolvePoster(props)\n const aspectRatio = resolveAspectRatio(video)\n const status = resolveStatus(video)\n\n // Show placeholder when no playbackId or video not ready\n if (!playbackId || (typeof video !== 'string' && status !== 'ready')) {\n return (\n <div\n className={placeholderClassName ?? className}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n aspectRatio: aspectRatio ?? '16/9',\n backgroundColor: '#000',\n color: '#888',\n fontSize: 14,\n width: '100%',\n ...placeholderStyle,\n }}\n >\n {placeholder ??\n (status === 'preparing'\n ? 'Video is processing...'\n : status === 'errored'\n ? 'Video processing failed'\n : 'Video not available')}\n </div>\n )\n }\n\n return (\n <MuxPlayer\n ref={ref}\n playbackId={playbackId}\n streamType={streamType}\n accentColor={accentColor}\n primaryColor={primaryColor}\n secondaryColor={secondaryColor}\n title={title}\n autoPlay={autoPlay}\n loop={loop}\n muted={muted}\n preload={preload}\n startTime={startTime}\n poster={poster}\n audio={audio}\n nohotkeys={noHotKeys}\n maxResolution={maxResolution}\n className={className}\n style={{\n aspectRatio: aspectRatio ?? '16/9',\n width: '100%',\n ...style,\n }}\n onPlay={onPlay}\n onPause={onPause}\n onEnded={onEnded}\n onTimeUpdate={onTimeUpdate}\n onLoadedData={onLoadedData}\n onError={onError}\n {...playerProps}\n />\n )\n },\n)\n\n// ── Type Exports ──\n\nexport type { MuxPlayerRefAttributes as VideoPlayerRef } from '@mux/mux-player-react'\nexport type { Video as VideoData } from '../../payload-types'\n","const MUX_IMAGE_BASE = 'https://image.mux.com'\nconst MUX_STREAM_BASE = 'https://stream.mux.com'\n\n// ── Thumbnail ──\n\nexport interface VideoThumbnailOptions {\n /** Width in pixels (default: 640) */\n width?: number\n /** Height in pixels (auto if omitted) */\n height?: number\n /** Time offset in seconds (default: 0) */\n time?: number\n /** Fit mode (default: 'smartcrop') */\n fitMode?: 'preserve' | 'stretch' | 'crop' | 'smartcrop' | 'pad'\n /** Flip horizontally */\n flipH?: boolean\n /** Flip vertically */\n flipV?: boolean\n /** Rotation in degrees (90, 180, 270) */\n rotate?: 90 | 180 | 270\n}\n\n/**\n * Returns a Mux thumbnail URL for a video.\n *\n * @example\n * ```ts\n * getVideoThumbnail('abc123')\n * // => 'https://image.mux.com/abc123/thumbnail.jpg?width=640'\n *\n * getVideoThumbnail('abc123', { width: 320, time: 5 })\n * // => 'https://image.mux.com/abc123/thumbnail.jpg?width=320&time=5'\n * ```\n */\nexport function getVideoThumbnail(\n playbackId: string,\n options?: VideoThumbnailOptions,\n): string {\n const params = new URLSearchParams()\n params.set('width', String(options?.width ?? 640))\n if (options?.height) params.set('height', String(options.height))\n if (options?.time != null) params.set('time', String(options.time))\n if (options?.fitMode) params.set('fit_mode', options.fitMode)\n if (options?.flipH) params.set('flip_h', 'true')\n if (options?.flipV) params.set('flip_v', 'true')\n if (options?.rotate) params.set('rotate', String(options.rotate))\n return `${MUX_IMAGE_BASE}/${playbackId}/thumbnail.jpg?${params}`\n}\n\n// ── Animated GIF ──\n\nexport interface VideoGifOptions {\n /** Width in pixels (default: 320) */\n width?: number\n /** Start time in seconds (default: 0) */\n start?: number\n /** End time in seconds (default: start + 5) */\n end?: number\n /** Frames per second (default: 15) */\n fps?: number\n}\n\n/**\n * Returns a Mux animated GIF URL for a video.\n *\n * @example\n * ```ts\n * getVideoGif('abc123')\n * // => 'https://image.mux.com/abc123/animated.gif?width=320'\n *\n * getVideoGif('abc123', { width: 240, start: 2, end: 6 })\n * // => 'https://image.mux.com/abc123/animated.gif?width=240&start=2&end=6'\n * ```\n */\nexport function getVideoGif(\n playbackId: string,\n options?: VideoGifOptions,\n): string {\n const params = new URLSearchParams()\n params.set('width', String(options?.width ?? 320))\n if (options?.start != null) params.set('start', String(options.start))\n if (options?.end != null) params.set('end', String(options.end))\n if (options?.fps) params.set('fps', String(options.fps))\n return `${MUX_IMAGE_BASE}/${playbackId}/animated.gif?${params}`\n}\n\n// ── Storyboard ──\n\n/**\n * Returns a Mux storyboard VTT URL for timeline hover previews.\n *\n * @example\n * ```ts\n * getVideoStoryboard('abc123')\n * // => 'https://image.mux.com/abc123/storyboard.vtt'\n * ```\n */\nexport function getVideoStoryboard(playbackId: string): string {\n return `${MUX_IMAGE_BASE}/${playbackId}/storyboard.vtt`\n}\n\n// ── Stream / Playback URLs ──\n\n/**\n * Returns the HLS stream URL for a video.\n *\n * @example\n * ```ts\n * getVideoStreamUrl('abc123')\n * // => 'https://stream.mux.com/abc123.m3u8'\n * ```\n */\nexport function getVideoStreamUrl(playbackId: string): string {\n return `${MUX_STREAM_BASE}/${playbackId}.m3u8`\n}\n\n/**\n * Returns a Mux MP4 download URL for a video.\n * Only available if the asset was created with `mp4_support` enabled.\n *\n * @example\n * ```ts\n * getVideoMp4Url('abc123', 'high')\n * // => 'https://stream.mux.com/abc123/high.mp4'\n * ```\n */\nexport function getVideoMp4Url(\n playbackId: string,\n resolution: 'high' | 'medium' | 'low' = 'high',\n): string {\n return `${MUX_STREAM_BASE}/${playbackId}/${resolution}.mp4`\n}\n"],"mappings":"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","names":["VideoPlayer"]}
|
package/dist/webhook.cjs
CHANGED
|
@@ -16,26 +16,6 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var __async = (__this, __arguments, generator) => {
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
var fulfilled = (value) => {
|
|
22
|
-
try {
|
|
23
|
-
step(generator.next(value));
|
|
24
|
-
} catch (e) {
|
|
25
|
-
reject(e);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
var rejected = (value) => {
|
|
29
|
-
try {
|
|
30
|
-
step(generator.throw(value));
|
|
31
|
-
} catch (e) {
|
|
32
|
-
reject(e);
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
36
|
-
step((generator = generator.apply(__this, __arguments)).next());
|
|
37
|
-
});
|
|
38
|
-
};
|
|
39
19
|
|
|
40
20
|
// src/webhook.ts
|
|
41
21
|
var webhook_exports = {};
|
|
@@ -52,73 +32,68 @@ function isValidWebhookEvent(data) {
|
|
|
52
32
|
const obj = data;
|
|
53
33
|
return typeof obj.collection === "string" && (obj.operation === "create" || obj.operation === "update") && typeof obj.data === "object" && obj.data !== null;
|
|
54
34
|
}
|
|
55
|
-
function verifySignature(payload, secret, signature) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
);
|
|
72
|
-
return crypto.subtle.verify("HMAC", key, sigBytes, encoder.encode(payload));
|
|
73
|
-
});
|
|
35
|
+
async function verifySignature(payload, secret, signature) {
|
|
36
|
+
const encoder = new TextEncoder();
|
|
37
|
+
const key = await crypto.subtle.importKey(
|
|
38
|
+
"raw",
|
|
39
|
+
encoder.encode(secret),
|
|
40
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
41
|
+
false,
|
|
42
|
+
["verify"]
|
|
43
|
+
);
|
|
44
|
+
if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const sigBytes = new Uint8Array(
|
|
48
|
+
(signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
|
|
49
|
+
);
|
|
50
|
+
return crypto.subtle.verify("HMAC", key, sigBytes, encoder.encode(payload));
|
|
74
51
|
}
|
|
75
|
-
function handleWebhook(request, handler, options) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!valid) {
|
|
83
|
-
return new Response(
|
|
84
|
-
JSON.stringify({ error: "Invalid webhook signature" }),
|
|
85
|
-
{ status: 401, headers: { "Content-Type": "application/json" } }
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
} else {
|
|
89
|
-
console.warn(
|
|
90
|
-
"[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
const body = JSON.parse(rawBody);
|
|
94
|
-
if (!isValidWebhookEvent(body)) {
|
|
52
|
+
async function handleWebhook(request, handler, options) {
|
|
53
|
+
try {
|
|
54
|
+
const rawBody = await request.text();
|
|
55
|
+
if (options?.secret) {
|
|
56
|
+
const signature = request.headers.get("x-webhook-signature") || "";
|
|
57
|
+
const valid = await verifySignature(rawBody, options.secret, signature);
|
|
58
|
+
if (!valid) {
|
|
95
59
|
return new Response(
|
|
96
|
-
JSON.stringify({ error: "Invalid webhook
|
|
97
|
-
{ status:
|
|
60
|
+
JSON.stringify({ error: "Invalid webhook signature" }),
|
|
61
|
+
{ status: 401, headers: { "Content-Type": "application/json" } }
|
|
98
62
|
);
|
|
99
63
|
}
|
|
100
|
-
|
|
64
|
+
} else {
|
|
65
|
+
console.warn(
|
|
66
|
+
"[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const body = JSON.parse(rawBody);
|
|
70
|
+
if (!isValidWebhookEvent(body)) {
|
|
101
71
|
return new Response(
|
|
102
|
-
JSON.stringify({
|
|
103
|
-
{ status:
|
|
72
|
+
JSON.stringify({ error: "Invalid webhook event format" }),
|
|
73
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
104
74
|
);
|
|
105
|
-
} catch (error) {
|
|
106
|
-
console.error("Webhook processing error:", error);
|
|
107
|
-
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
108
|
-
status: 500,
|
|
109
|
-
headers: { "Content-Type": "application/json" }
|
|
110
|
-
});
|
|
111
75
|
}
|
|
112
|
-
|
|
76
|
+
await handler(body);
|
|
77
|
+
return new Response(
|
|
78
|
+
JSON.stringify({ success: true, message: "Webhook processed" }),
|
|
79
|
+
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
80
|
+
);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error("Webhook processing error:", error);
|
|
83
|
+
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
84
|
+
status: 500,
|
|
85
|
+
headers: { "Content-Type": "application/json" }
|
|
86
|
+
});
|
|
87
|
+
}
|
|
113
88
|
}
|
|
114
89
|
function createTypedWebhookHandler(collection, handler) {
|
|
115
|
-
return (event) =>
|
|
90
|
+
return async (event) => {
|
|
116
91
|
if (event.collection !== collection) {
|
|
117
92
|
throw new Error(
|
|
118
93
|
`Expected collection "${collection}", got "${event.collection}"`
|
|
119
94
|
);
|
|
120
95
|
}
|
|
121
96
|
return handler(event);
|
|
122
|
-
}
|
|
97
|
+
};
|
|
123
98
|
}
|
|
124
99
|
//# sourceMappingURL=webhook.cjs.map
|
package/dist/webhook.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/webhook.ts","../src/core/webhook/index.ts"],"sourcesContent":["export {\n handleWebhook,\n createTypedWebhookHandler,\n isValidWebhookEvent,\n} from './core/webhook'\nexport type {\n WebhookEvent,\n WebhookHandler,\n WebhookOperation,\n WebhookOptions,\n} from './core/webhook'\n","import type { Collection } from '../client/types'\nimport type { CollectionType } from '../collection/types'\n\nexport type WebhookOperation = 'create' | 'update'\n\nexport interface WebhookEvent<T extends Collection = Collection> {\n collection: T\n operation: WebhookOperation\n data: CollectionType<T>\n}\n\nexport type WebhookHandler<T extends Collection = Collection> = (\n event: WebhookEvent<T>,\n) => Promise<void> | void\n\nexport interface WebhookOptions {\n secret?: string\n}\n\nexport function isValidWebhookEvent(data: unknown): data is WebhookEvent {\n if (typeof data !== 'object' || data === null) return false\n const obj = data as Record<string, unknown>\n return (\n typeof obj.collection === 'string' &&\n (obj.operation === 'create' || obj.operation === 'update') &&\n typeof obj.data === 'object' &&\n obj.data !== null\n )\n}\n\nasync function verifySignature(\n payload: string,\n secret: string,\n signature: string,\n): Promise<boolean> {\n const encoder = new TextEncoder()\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['verify'],\n )\n // Validate and convert hex signature to Uint8Array\n if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {\n return false\n }\n const sigBytes = new Uint8Array(\n (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16)),\n )\n // crypto.subtle.verify performs constant-time comparison internally\n return crypto.subtle.verify('HMAC', key, sigBytes, encoder.encode(payload))\n}\n\nexport async function handleWebhook<T extends Collection = Collection>(\n request: Request,\n handler: WebhookHandler<T>,\n options?: WebhookOptions,\n): Promise<Response> {\n try {\n const rawBody = await request.text()\n\n if (options?.secret) {\n const signature = request.headers.get('x-webhook-signature') || ''\n const valid = await verifySignature(rawBody, options.secret, signature)\n if (!valid) {\n return new Response(\n JSON.stringify({ error: 'Invalid webhook signature' }),\n { status: 401, headers: { 'Content-Type': 'application/json' } },\n )\n }\n } else {\n console.warn(\n '[@01.software/sdk] Webhook signature verification is disabled. ' +\n 'Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification.',\n )\n }\n\n const body = JSON.parse(rawBody)\n\n if (!isValidWebhookEvent(body)) {\n return new Response(\n JSON.stringify({ error: 'Invalid webhook event format' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n await handler(body as WebhookEvent<T>)\n\n return new Response(\n JSON.stringify({ success: true, message: 'Webhook processed' }),\n { status: 200, headers: { 'Content-Type': 'application/json' } },\n )\n } catch (error) {\n console.error('Webhook processing error:', error)\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n}\n\nexport function createTypedWebhookHandler<T extends Collection>(\n collection: T,\n handler: (event: WebhookEvent<T>) => Promise<void> | void,\n): WebhookHandler<T> {\n return async (event: WebhookEvent<T>) => {\n if (event.collection !== collection) {\n throw new Error(\n `Expected collection \"${collection}\", got \"${event.collection}\"`,\n )\n }\n return handler(event)\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/webhook.ts","../src/core/webhook/index.ts"],"sourcesContent":["export {\n handleWebhook,\n createTypedWebhookHandler,\n isValidWebhookEvent,\n} from './core/webhook'\nexport type {\n WebhookEvent,\n WebhookHandler,\n WebhookOperation,\n WebhookOptions,\n} from './core/webhook'\n","import type { Collection } from '../client/types'\nimport type { CollectionType } from '../collection/types'\n\nexport type WebhookOperation = 'create' | 'update'\n\nexport interface WebhookEvent<T extends Collection = Collection> {\n collection: T\n operation: WebhookOperation\n data: CollectionType<T>\n}\n\nexport type WebhookHandler<T extends Collection = Collection> = (\n event: WebhookEvent<T>,\n) => Promise<void> | void\n\nexport interface WebhookOptions {\n secret?: string\n}\n\nexport function isValidWebhookEvent(data: unknown): data is WebhookEvent {\n if (typeof data !== 'object' || data === null) return false\n const obj = data as Record<string, unknown>\n return (\n typeof obj.collection === 'string' &&\n (obj.operation === 'create' || obj.operation === 'update') &&\n typeof obj.data === 'object' &&\n obj.data !== null\n )\n}\n\nasync function verifySignature(\n payload: string,\n secret: string,\n signature: string,\n): Promise<boolean> {\n const encoder = new TextEncoder()\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['verify'],\n )\n // Validate and convert hex signature to Uint8Array\n if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {\n return false\n }\n const sigBytes = new Uint8Array(\n (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16)),\n )\n // crypto.subtle.verify performs constant-time comparison internally\n return crypto.subtle.verify('HMAC', key, sigBytes, encoder.encode(payload))\n}\n\nexport async function handleWebhook<T extends Collection = Collection>(\n request: Request,\n handler: WebhookHandler<T>,\n options?: WebhookOptions,\n): Promise<Response> {\n try {\n const rawBody = await request.text()\n\n if (options?.secret) {\n const signature = request.headers.get('x-webhook-signature') || ''\n const valid = await verifySignature(rawBody, options.secret, signature)\n if (!valid) {\n return new Response(\n JSON.stringify({ error: 'Invalid webhook signature' }),\n { status: 401, headers: { 'Content-Type': 'application/json' } },\n )\n }\n } else {\n console.warn(\n '[@01.software/sdk] Webhook signature verification is disabled. ' +\n 'Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification.',\n )\n }\n\n const body = JSON.parse(rawBody)\n\n if (!isValidWebhookEvent(body)) {\n return new Response(\n JSON.stringify({ error: 'Invalid webhook event format' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n await handler(body as WebhookEvent<T>)\n\n return new Response(\n JSON.stringify({ success: true, message: 'Webhook processed' }),\n { status: 200, headers: { 'Content-Type': 'application/json' } },\n )\n } catch (error) {\n console.error('Webhook processing error:', error)\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n}\n\nexport function createTypedWebhookHandler<T extends Collection>(\n collection: T,\n handler: (event: WebhookEvent<T>) => Promise<void> | void,\n): WebhookHandler<T> {\n return async (event: WebhookEvent<T>) => {\n if (event.collection !== collection) {\n throw new Error(\n `Expected collection \"${collection}\", got \"${event.collection}\"`,\n )\n }\n return handler(event)\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACmBO,SAAS,oBAAoB,MAAqC;AACvE,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,QAAM,MAAM;AACZ,SACE,OAAO,IAAI,eAAe,aACzB,IAAI,cAAc,YAAY,IAAI,cAAc,aACjD,OAAO,IAAI,SAAS,YACpB,IAAI,SAAS;AAEjB;AAEA,eAAe,gBACb,SACA,QACA,WACkB;AAClB,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,MAAM,MAAM,OAAO,OAAO;AAAA,IAC9B;AAAA,IACA,QAAQ,OAAO,MAAM;AAAA,IACrB,EAAE,MAAM,QAAQ,MAAM,UAAU;AAAA,IAChC;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAEA,MAAI,UAAU,SAAS,MAAM,KAAK,CAAC,iBAAiB,KAAK,SAAS,GAAG;AACnE,WAAO;AAAA,EACT;AACA,QAAM,WAAW,IAAI;AAAA,KAClB,UAAU,MAAM,OAAO,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,SAAS,MAAM,EAAE,CAAC;AAAA,EACnE;AAEA,SAAO,OAAO,OAAO,OAAO,QAAQ,KAAK,UAAU,QAAQ,OAAO,OAAO,CAAC;AAC5E;AAEA,eAAsB,cACpB,SACA,SACA,SACmB;AACnB,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,KAAK;AAEnC,QAAI,SAAS,QAAQ;AACnB,YAAM,YAAY,QAAQ,QAAQ,IAAI,qBAAqB,KAAK;AAChE,YAAM,QAAQ,MAAM,gBAAgB,SAAS,QAAQ,QAAQ,SAAS;AACtE,UAAI,CAAC,OAAO;AACV,eAAO,IAAI;AAAA,UACT,KAAK,UAAU,EAAE,OAAO,4BAA4B,CAAC;AAAA,UACrD,EAAE,QAAQ,KAAK,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,QACjE;AAAA,MACF;AAAA,IACF,OAAO;AACL,cAAQ;AAAA,QACN;AAAA,MAEF;AAAA,IACF;AAEA,UAAM,OAAO,KAAK,MAAM,OAAO;AAE/B,QAAI,CAAC,oBAAoB,IAAI,GAAG;AAC9B,aAAO,IAAI;AAAA,QACT,KAAK,UAAU,EAAE,OAAO,+BAA+B,CAAC;AAAA,QACxD,EAAE,QAAQ,KAAK,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,MACjE;AAAA,IACF;AAEA,UAAM,QAAQ,IAAuB;AAErC,WAAO,IAAI;AAAA,MACT,KAAK,UAAU,EAAE,SAAS,MAAM,SAAS,oBAAoB,CAAC;AAAA,MAC9D,EAAE,QAAQ,KAAK,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,IACjE;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,6BAA6B,KAAK;AAEhD,WAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,wBAAwB,CAAC,GAAG;AAAA,MACtE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAChD,CAAC;AAAA,EACH;AACF;AAEO,SAAS,0BACd,YACA,SACmB;AACnB,SAAO,OAAO,UAA2B;AACvC,QAAI,MAAM,eAAe,YAAY;AACnC,YAAM,IAAI;AAAA,QACR,wBAAwB,UAAU,WAAW,MAAM,UAAU;AAAA,MAC/D;AAAA,IACF;AACA,WAAO,QAAQ,KAAK;AAAA,EACtB;AACF;","names":[]}
|
package/dist/webhook.js
CHANGED
|
@@ -1,98 +1,72 @@
|
|
|
1
|
-
var __async = (__this, __arguments, generator) => {
|
|
2
|
-
return new Promise((resolve, reject) => {
|
|
3
|
-
var fulfilled = (value) => {
|
|
4
|
-
try {
|
|
5
|
-
step(generator.next(value));
|
|
6
|
-
} catch (e) {
|
|
7
|
-
reject(e);
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
var rejected = (value) => {
|
|
11
|
-
try {
|
|
12
|
-
step(generator.throw(value));
|
|
13
|
-
} catch (e) {
|
|
14
|
-
reject(e);
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
18
|
-
step((generator = generator.apply(__this, __arguments)).next());
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
|
-
|
|
22
1
|
// src/core/webhook/index.ts
|
|
23
2
|
function isValidWebhookEvent(data) {
|
|
24
3
|
if (typeof data !== "object" || data === null) return false;
|
|
25
4
|
const obj = data;
|
|
26
5
|
return typeof obj.collection === "string" && (obj.operation === "create" || obj.operation === "update") && typeof obj.data === "object" && obj.data !== null;
|
|
27
6
|
}
|
|
28
|
-
function verifySignature(payload, secret, signature) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
45
|
-
return crypto.subtle.verify("HMAC", key, sigBytes, encoder.encode(payload));
|
|
46
|
-
});
|
|
7
|
+
async function verifySignature(payload, secret, signature) {
|
|
8
|
+
const encoder = new TextEncoder();
|
|
9
|
+
const key = await crypto.subtle.importKey(
|
|
10
|
+
"raw",
|
|
11
|
+
encoder.encode(secret),
|
|
12
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
13
|
+
false,
|
|
14
|
+
["verify"]
|
|
15
|
+
);
|
|
16
|
+
if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const sigBytes = new Uint8Array(
|
|
20
|
+
(signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
|
|
21
|
+
);
|
|
22
|
+
return crypto.subtle.verify("HMAC", key, sigBytes, encoder.encode(payload));
|
|
47
23
|
}
|
|
48
|
-
function handleWebhook(request, handler, options) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (!valid) {
|
|
56
|
-
return new Response(
|
|
57
|
-
JSON.stringify({ error: "Invalid webhook signature" }),
|
|
58
|
-
{ status: 401, headers: { "Content-Type": "application/json" } }
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
} else {
|
|
62
|
-
console.warn(
|
|
63
|
-
"[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
const body = JSON.parse(rawBody);
|
|
67
|
-
if (!isValidWebhookEvent(body)) {
|
|
24
|
+
async function handleWebhook(request, handler, options) {
|
|
25
|
+
try {
|
|
26
|
+
const rawBody = await request.text();
|
|
27
|
+
if (options?.secret) {
|
|
28
|
+
const signature = request.headers.get("x-webhook-signature") || "";
|
|
29
|
+
const valid = await verifySignature(rawBody, options.secret, signature);
|
|
30
|
+
if (!valid) {
|
|
68
31
|
return new Response(
|
|
69
|
-
JSON.stringify({ error: "Invalid webhook
|
|
70
|
-
{ status:
|
|
32
|
+
JSON.stringify({ error: "Invalid webhook signature" }),
|
|
33
|
+
{ status: 401, headers: { "Content-Type": "application/json" } }
|
|
71
34
|
);
|
|
72
35
|
}
|
|
73
|
-
|
|
36
|
+
} else {
|
|
37
|
+
console.warn(
|
|
38
|
+
"[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const body = JSON.parse(rawBody);
|
|
42
|
+
if (!isValidWebhookEvent(body)) {
|
|
74
43
|
return new Response(
|
|
75
|
-
JSON.stringify({
|
|
76
|
-
{ status:
|
|
44
|
+
JSON.stringify({ error: "Invalid webhook event format" }),
|
|
45
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
77
46
|
);
|
|
78
|
-
} catch (error) {
|
|
79
|
-
console.error("Webhook processing error:", error);
|
|
80
|
-
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
81
|
-
status: 500,
|
|
82
|
-
headers: { "Content-Type": "application/json" }
|
|
83
|
-
});
|
|
84
47
|
}
|
|
85
|
-
|
|
48
|
+
await handler(body);
|
|
49
|
+
return new Response(
|
|
50
|
+
JSON.stringify({ success: true, message: "Webhook processed" }),
|
|
51
|
+
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
52
|
+
);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error("Webhook processing error:", error);
|
|
55
|
+
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
56
|
+
status: 500,
|
|
57
|
+
headers: { "Content-Type": "application/json" }
|
|
58
|
+
});
|
|
59
|
+
}
|
|
86
60
|
}
|
|
87
61
|
function createTypedWebhookHandler(collection, handler) {
|
|
88
|
-
return (event) =>
|
|
62
|
+
return async (event) => {
|
|
89
63
|
if (event.collection !== collection) {
|
|
90
64
|
throw new Error(
|
|
91
65
|
`Expected collection "${collection}", got "${event.collection}"`
|
|
92
66
|
);
|
|
93
67
|
}
|
|
94
68
|
return handler(event);
|
|
95
|
-
}
|
|
69
|
+
};
|
|
96
70
|
}
|
|
97
71
|
export {
|
|
98
72
|
createTypedWebhookHandler,
|
package/dist/webhook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/webhook/index.ts"],"sourcesContent":["import type { Collection } from '../client/types'\nimport type { CollectionType } from '../collection/types'\n\nexport type WebhookOperation = 'create' | 'update'\n\nexport interface WebhookEvent<T extends Collection = Collection> {\n collection: T\n operation: WebhookOperation\n data: CollectionType<T>\n}\n\nexport type WebhookHandler<T extends Collection = Collection> = (\n event: WebhookEvent<T>,\n) => Promise<void> | void\n\nexport interface WebhookOptions {\n secret?: string\n}\n\nexport function isValidWebhookEvent(data: unknown): data is WebhookEvent {\n if (typeof data !== 'object' || data === null) return false\n const obj = data as Record<string, unknown>\n return (\n typeof obj.collection === 'string' &&\n (obj.operation === 'create' || obj.operation === 'update') &&\n typeof obj.data === 'object' &&\n obj.data !== null\n )\n}\n\nasync function verifySignature(\n payload: string,\n secret: string,\n signature: string,\n): Promise<boolean> {\n const encoder = new TextEncoder()\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['verify'],\n )\n // Validate and convert hex signature to Uint8Array\n if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {\n return false\n }\n const sigBytes = new Uint8Array(\n (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16)),\n )\n // crypto.subtle.verify performs constant-time comparison internally\n return crypto.subtle.verify('HMAC', key, sigBytes, encoder.encode(payload))\n}\n\nexport async function handleWebhook<T extends Collection = Collection>(\n request: Request,\n handler: WebhookHandler<T>,\n options?: WebhookOptions,\n): Promise<Response> {\n try {\n const rawBody = await request.text()\n\n if (options?.secret) {\n const signature = request.headers.get('x-webhook-signature') || ''\n const valid = await verifySignature(rawBody, options.secret, signature)\n if (!valid) {\n return new Response(\n JSON.stringify({ error: 'Invalid webhook signature' }),\n { status: 401, headers: { 'Content-Type': 'application/json' } },\n )\n }\n } else {\n console.warn(\n '[@01.software/sdk] Webhook signature verification is disabled. ' +\n 'Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification.',\n )\n }\n\n const body = JSON.parse(rawBody)\n\n if (!isValidWebhookEvent(body)) {\n return new Response(\n JSON.stringify({ error: 'Invalid webhook event format' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n await handler(body as WebhookEvent<T>)\n\n return new Response(\n JSON.stringify({ success: true, message: 'Webhook processed' }),\n { status: 200, headers: { 'Content-Type': 'application/json' } },\n )\n } catch (error) {\n console.error('Webhook processing error:', error)\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n}\n\nexport function createTypedWebhookHandler<T extends Collection>(\n collection: T,\n handler: (event: WebhookEvent<T>) => Promise<void> | void,\n): WebhookHandler<T> {\n return async (event: WebhookEvent<T>) => {\n if (event.collection !== collection) {\n throw new Error(\n `Expected collection \"${collection}\", got \"${event.collection}\"`,\n )\n }\n return handler(event)\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/core/webhook/index.ts"],"sourcesContent":["import type { Collection } from '../client/types'\nimport type { CollectionType } from '../collection/types'\n\nexport type WebhookOperation = 'create' | 'update'\n\nexport interface WebhookEvent<T extends Collection = Collection> {\n collection: T\n operation: WebhookOperation\n data: CollectionType<T>\n}\n\nexport type WebhookHandler<T extends Collection = Collection> = (\n event: WebhookEvent<T>,\n) => Promise<void> | void\n\nexport interface WebhookOptions {\n secret?: string\n}\n\nexport function isValidWebhookEvent(data: unknown): data is WebhookEvent {\n if (typeof data !== 'object' || data === null) return false\n const obj = data as Record<string, unknown>\n return (\n typeof obj.collection === 'string' &&\n (obj.operation === 'create' || obj.operation === 'update') &&\n typeof obj.data === 'object' &&\n obj.data !== null\n )\n}\n\nasync function verifySignature(\n payload: string,\n secret: string,\n signature: string,\n): Promise<boolean> {\n const encoder = new TextEncoder()\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['verify'],\n )\n // Validate and convert hex signature to Uint8Array\n if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {\n return false\n }\n const sigBytes = new Uint8Array(\n (signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16)),\n )\n // crypto.subtle.verify performs constant-time comparison internally\n return crypto.subtle.verify('HMAC', key, sigBytes, encoder.encode(payload))\n}\n\nexport async function handleWebhook<T extends Collection = Collection>(\n request: Request,\n handler: WebhookHandler<T>,\n options?: WebhookOptions,\n): Promise<Response> {\n try {\n const rawBody = await request.text()\n\n if (options?.secret) {\n const signature = request.headers.get('x-webhook-signature') || ''\n const valid = await verifySignature(rawBody, options.secret, signature)\n if (!valid) {\n return new Response(\n JSON.stringify({ error: 'Invalid webhook signature' }),\n { status: 401, headers: { 'Content-Type': 'application/json' } },\n )\n }\n } else {\n console.warn(\n '[@01.software/sdk] Webhook signature verification is disabled. ' +\n 'Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification.',\n )\n }\n\n const body = JSON.parse(rawBody)\n\n if (!isValidWebhookEvent(body)) {\n return new Response(\n JSON.stringify({ error: 'Invalid webhook event format' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n await handler(body as WebhookEvent<T>)\n\n return new Response(\n JSON.stringify({ success: true, message: 'Webhook processed' }),\n { status: 200, headers: { 'Content-Type': 'application/json' } },\n )\n } catch (error) {\n console.error('Webhook processing error:', error)\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n}\n\nexport function createTypedWebhookHandler<T extends Collection>(\n collection: T,\n handler: (event: WebhookEvent<T>) => Promise<void> | void,\n): WebhookHandler<T> {\n return async (event: WebhookEvent<T>) => {\n if (event.collection !== collection) {\n throw new Error(\n `Expected collection \"${collection}\", got \"${event.collection}\"`,\n )\n }\n return handler(event)\n }\n}\n"],"mappings":";AAmBO,SAAS,oBAAoB,MAAqC;AACvE,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,QAAM,MAAM;AACZ,SACE,OAAO,IAAI,eAAe,aACzB,IAAI,cAAc,YAAY,IAAI,cAAc,aACjD,OAAO,IAAI,SAAS,YACpB,IAAI,SAAS;AAEjB;AAEA,eAAe,gBACb,SACA,QACA,WACkB;AAClB,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,MAAM,MAAM,OAAO,OAAO;AAAA,IAC9B;AAAA,IACA,QAAQ,OAAO,MAAM;AAAA,IACrB,EAAE,MAAM,QAAQ,MAAM,UAAU;AAAA,IAChC;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAEA,MAAI,UAAU,SAAS,MAAM,KAAK,CAAC,iBAAiB,KAAK,SAAS,GAAG;AACnE,WAAO;AAAA,EACT;AACA,QAAM,WAAW,IAAI;AAAA,KAClB,UAAU,MAAM,OAAO,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,SAAS,MAAM,EAAE,CAAC;AAAA,EACnE;AAEA,SAAO,OAAO,OAAO,OAAO,QAAQ,KAAK,UAAU,QAAQ,OAAO,OAAO,CAAC;AAC5E;AAEA,eAAsB,cACpB,SACA,SACA,SACmB;AACnB,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,KAAK;AAEnC,QAAI,SAAS,QAAQ;AACnB,YAAM,YAAY,QAAQ,QAAQ,IAAI,qBAAqB,KAAK;AAChE,YAAM,QAAQ,MAAM,gBAAgB,SAAS,QAAQ,QAAQ,SAAS;AACtE,UAAI,CAAC,OAAO;AACV,eAAO,IAAI;AAAA,UACT,KAAK,UAAU,EAAE,OAAO,4BAA4B,CAAC;AAAA,UACrD,EAAE,QAAQ,KAAK,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,QACjE;AAAA,MACF;AAAA,IACF,OAAO;AACL,cAAQ;AAAA,QACN;AAAA,MAEF;AAAA,IACF;AAEA,UAAM,OAAO,KAAK,MAAM,OAAO;AAE/B,QAAI,CAAC,oBAAoB,IAAI,GAAG;AAC9B,aAAO,IAAI;AAAA,QACT,KAAK,UAAU,EAAE,OAAO,+BAA+B,CAAC;AAAA,QACxD,EAAE,QAAQ,KAAK,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,MACjE;AAAA,IACF;AAEA,UAAM,QAAQ,IAAuB;AAErC,WAAO,IAAI;AAAA,MACT,KAAK,UAAU,EAAE,SAAS,MAAM,SAAS,oBAAoB,CAAC;AAAA,MAC9D,EAAE,QAAQ,KAAK,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,IACjE;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,6BAA6B,KAAK;AAEhD,WAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,wBAAwB,CAAC,GAAG;AAAA,MACtE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAChD,CAAC;AAAA,EACH;AACF;AAEO,SAAS,0BACd,YACA,SACmB;AACnB,SAAO,OAAO,UAA2B;AACvC,QAAI,MAAM,eAAe,YAAY;AACnC,YAAM,IAAI;AAAA,QACR,wBAAwB,UAAU,WAAW,MAAM,UAAU;AAAA,MAC/D;AAAA,IACF;AACA,WAAO,QAAQ,KAAK;AAAA,EACtB;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@01.software/sdk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "01.software SDK",
|
|
5
5
|
"author": "<office@01.works>",
|
|
6
6
|
"keywords": [],
|
|
@@ -148,8 +148,8 @@
|
|
|
148
148
|
"shadcn": "^3.6.3",
|
|
149
149
|
"tsup": "^8.3.7",
|
|
150
150
|
"vitest": "^3.2.3",
|
|
151
|
-
"@repo/
|
|
152
|
-
"@repo/
|
|
151
|
+
"@repo/typescript-config": "0.0.0",
|
|
152
|
+
"@repo/eslint-config": "0.0.0"
|
|
153
153
|
},
|
|
154
154
|
"dependencies": {
|
|
155
155
|
"@payloadcms/richtext-lexical": ">=3.78.0",
|