@wix/himalaya-cli 0.1387.0 → 0.1388.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.
Files changed (56) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/video/src/display.d.ts +66 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/video/src/index.d.ts +185 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-video.mjs +324 -0
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/package.json +4 -0
  5. package/dist/cli.mjs +5 -2
  6. package/guides/importable-sdks.md +1 -0
  7. package/guides/sdk-reference.json +253 -0
  8. package/package.json +2 -2
  9. package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +1 -1
  10. package/templates/beauty-salon/tier5-src/_shared/video-caption.ts +21 -0
  11. package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +31 -1
  12. package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +1 -1
  13. package/templates/branded-lite/dev/index.ts +982 -16
  14. package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +1 -1
  15. package/templates/branded-lite/tier5-src/_shared/video-caption.ts +21 -0
  16. package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +31 -1
  17. package/templates/branded-lite/tier5-src/himi-branded-lite-video/index.ts +23 -32
  18. package/templates/branded-lite/tier5-src/himi-branded-lite-videos/index.ts +44 -38
  19. package/templates/community-club/tier5-src/_shared/collection-contract.ts +1 -1
  20. package/templates/community-club/tier5-src/_shared/video-caption.ts +21 -0
  21. package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +31 -1
  22. package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +1 -1
  23. package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +1 -1
  24. package/templates/events-meetups/tier5-src/_shared/video-caption.ts +21 -0
  25. package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +31 -1
  26. package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +1 -1
  27. package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +1 -1
  28. package/templates/gym-fitness/tier5-src/_shared/video-caption.ts +21 -0
  29. package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +31 -1
  30. package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +1 -1
  31. package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +1 -1
  32. package/templates/nonprofit-cause/tier5-src/_shared/video-caption.ts +21 -0
  33. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +31 -1
  34. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +1 -1
  35. package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +1 -1
  36. package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +1 -1
  37. package/templates/podcast-creator/tier5-src/_shared/video-caption.ts +21 -0
  38. package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +31 -1
  39. package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +1 -1
  40. package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +1 -1
  41. package/templates/portfolio-creative/tier5-src/_shared/video-caption.ts +21 -0
  42. package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +31 -1
  43. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +1 -1
  44. package/templates/restaurant/tier5-src/_shared/collection-contract.ts +1 -1
  45. package/templates/restaurant/tier5-src/_shared/video-caption.ts +21 -0
  46. package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +31 -1
  47. package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +1 -1
  48. package/templates/retail-store/tier5-src/_shared/collection-contract.ts +1 -1
  49. package/templates/retail-store/tier5-src/_shared/video-caption.ts +21 -0
  50. package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +31 -1
  51. package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +1 -1
  52. package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +1 -1
  53. package/templates/yoga-studio/tier5-src/_shared/video-caption.ts +21 -0
  54. package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +31 -1
  55. package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +1 -1
  56. package/templates/branded-lite/tier5-src/_shared/vod.ts +0 -71
@@ -0,0 +1,66 @@
1
+ import type { WixVideo, WixVideoChannel, WixVideoInChannel } from "./index.js";
2
+ import { type WixImageSize } from "@wix/himalaya/wix-media";
3
+ /** True for a video whose file Wix hosts (it plays through `getVideoStreamUrl`). */
4
+ export declare function isHostedVideo(video: WixVideo): boolean;
5
+ /** True when the video is published AND its media is ready to play. */
6
+ export declare function isVideoReady(video: WixVideo): boolean;
7
+ /** True while an upload is still being processed (try again later). */
8
+ export declare function isVideoProcessing(video: WixVideo): boolean;
9
+ /** The 11-character YouTube id of a linked YouTube video, or "". The embed URL is
10
+ * treated as data: only the id is taken, never the URL itself. */
11
+ export declare function youtubeVideoId(video: WixVideo): string;
12
+ /**
13
+ * The video's card image, sized for display: the owner's cover, then the poster, then
14
+ * the media thumbnail, then (for a YouTube video) YouTube's own thumbnail. "" when there
15
+ * is none, so a card can show its placeholder.
16
+ *
17
+ * Wix media goes through `wixMediaUrl`: a stored cover is the untouched upload.
18
+ */
19
+ export declare function videoCoverUrl(video: WixVideo, size?: WixImageSize | null): string;
20
+ /** The channel's cover image, sized for display, or "". */
21
+ export declare function channelCoverUrl(channel: WixVideoChannel, size?: WixImageSize | null): string;
22
+ /** The video's length in whole seconds, or undefined when the gateway gives none. */
23
+ export declare function videoDurationSec(video: WixVideo): number | undefined;
24
+ /** "0:23", "4:05", "1:02:03". "" for an unknown length. */
25
+ export declare function formatVideoDuration(seconds: number | undefined): string;
26
+ /** True when the video has an enabled SALE or RENT price. */
27
+ export declare function isPaidVideo(video: WixVideo): boolean;
28
+ /**
29
+ * May the current viewer watch this video? FAILS CLOSED: missing pricing on the channel
30
+ * or the video, or an entitlement that cannot be established, is "no".
31
+ *
32
+ * A channel with a required subscription or a pricing plan needs an active (or a
33
+ * cancelled but unexpired) subscription. A paid video needs that subscription or its
34
+ * own active sale or rent purchase. Query the channel with `CHANNEL_ACCESS_FIELDS` and
35
+ * the video with `VIDEO_FIELDS`.
36
+ */
37
+ export declare function canWatchVideo(channel: WixVideoChannel, video: WixVideo, now?: number): boolean;
38
+ /**
39
+ * Up to `limit` ready videos from the channels' preview pages (`listChannels` with
40
+ * `videosPerChannel`, or `listLatestVideos`). Channels take turns — the first video of
41
+ * each channel, then the second of each, … — so one large channel cannot fill the
42
+ * whole rail. Duplicates (one video in two channels) appear once.
43
+ */
44
+ export declare function latestVideos(channels: WixVideoChannel[], limit?: number): WixVideoInChannel[];
45
+ /** The fields a video card binds to (`$item.*`). Shared by every carousel layout so the
46
+ * caption placement is a layout choice, never a data change. */
47
+ export interface WixVideoCard {
48
+ id: string;
49
+ channelId: string;
50
+ title: string;
51
+ /** "4:05 · Channel" — the parts that are known, joined. */
52
+ meta: string;
53
+ /** "4:05", or "". */
54
+ duration: string;
55
+ channelTitle: string;
56
+ coverUrl: string;
57
+ hasCover: boolean;
58
+ noCover: boolean;
59
+ /** True for a paid video (it may ask the viewer to buy or subscribe). */
60
+ isPaid: boolean;
61
+ notPaid: boolean;
62
+ /** One spoken label: title, length and channel. */
63
+ accessibilityLabel: string;
64
+ }
65
+ /** Shape one video for a card. `untitled` replaces an empty title. */
66
+ export declare function videoCard(entry: WixVideoInChannel, untitled?: string, size?: WixImageSize | null): WixVideoCard;
@@ -0,0 +1,185 @@
1
+ export * from "./display.js";
2
+ declare global {
3
+ var himalaya: {
4
+ wixApi?: {
5
+ call: (method: string, path: string, body?: unknown, headers?: Record<string, string>) => Promise<unknown>;
6
+ };
7
+ };
8
+ }
9
+ /** The Wix Video GraphQL gateway (visitor and member reads). */
10
+ export declare const WIX_VIDEO_GRAPHQL_URL = "https://manage.wix.com/_api/vod/public/graphql";
11
+ /** Per-call options. `site` reads a business other than the app's active one. */
12
+ export interface WixCallOptions {
13
+ site?: string;
14
+ }
15
+ /** One price option of a channel (`SUBSCRIPTION` | `PRICING_PLAN`) or a video (`SALE` | `RENT`). */
16
+ export interface WixVideoPricing {
17
+ type?: string;
18
+ enabled?: boolean;
19
+ /** Channel only: true when every video needs the subscription or plan. */
20
+ required?: boolean;
21
+ price?: number;
22
+ currency?: string;
23
+ /** Rent period in hours (video) or subscription period (channel). */
24
+ period?: number;
25
+ }
26
+ /** The viewer's purchase of a channel subscription or of one video. */
27
+ export interface WixVideoPurchase {
28
+ /** `ACTIVE` | `PENDING` | `CANCELED` | `EXPIRED` | … */
29
+ status?: string;
30
+ expiresAt?: string;
31
+ }
32
+ /** The uploaded or linked media of a video. */
33
+ export interface WixVideoMedia {
34
+ /** `READY` once the file can play; `IN_QUEUE` / `IN_PROGRESS` / … while it processes. */
35
+ status?: string;
36
+ /** Length in milliseconds. */
37
+ duration?: number;
38
+ thumbnailUrl?: string;
39
+ /** The embed URL of a linked (YouTube, Vimeo, …) video. */
40
+ externalUrl?: string;
41
+ externalId?: string;
42
+ }
43
+ /** A selectable image (cover or poster). `selected` is the image URL. */
44
+ export interface WixVideoImage {
45
+ selected?: string | null;
46
+ }
47
+ /** A page of a paginated list. */
48
+ export interface WixVideoPage<T> {
49
+ total?: number;
50
+ cursor?: string | null;
51
+ entities?: T[];
52
+ }
53
+ export interface WixVideo {
54
+ id: string;
55
+ title?: string;
56
+ description?: string;
57
+ publisher?: string;
58
+ /** `COMPUTER` | `GOOGLE_DRIVE` | `DROPBOX` (hosted) | `YOUTUBE` | `VIMEO` | `FACEBOOK` | `LIVE`. */
59
+ source?: string;
60
+ /** `VISIBLE` for a published video. */
61
+ status?: string;
62
+ cover?: WixVideoImage | null;
63
+ poster?: WixVideoImage | null;
64
+ content?: WixVideoMedia | null;
65
+ pricing?: WixVideoPricing[];
66
+ /** The viewer's SALE purchase, when the query asks for it. */
67
+ purchase?: WixVideoPurchase | null;
68
+ /** The viewer's RENT purchase, when the query asks for it (GraphQL alias). */
69
+ rent_purchase?: WixVideoPurchase | null;
70
+ }
71
+ export interface WixVideoChannel {
72
+ id: string;
73
+ title?: string;
74
+ description?: string;
75
+ /** `INTERNAL` | `YOUTUBE`. */
76
+ source?: string;
77
+ featuredVideoId?: string | null;
78
+ hasPaidVideos?: boolean;
79
+ cover?: WixVideoImage | null;
80
+ pricing?: WixVideoPricing[];
81
+ /** The viewer's SUBSCRIPTION purchase, when the query asks for it. */
82
+ purchase?: WixVideoPurchase | null;
83
+ /** The channel's videos: only `total` in a list, a page when the query asks for one. */
84
+ videos?: WixVideoPage<WixVideo>;
85
+ }
86
+ /** A video together with the channel it belongs to. */
87
+ export interface WixVideoInChannel {
88
+ channel: WixVideoChannel;
89
+ video: WixVideo;
90
+ }
91
+ /** Flags every error this client throws carries, so a caller can branch without
92
+ * matching message text. */
93
+ export interface WixVideoErrorFlags {
94
+ /** The gateway refused the viewer (membership, purchase or permission). */
95
+ denied: boolean;
96
+ /** The gateway needs a signed-in member. */
97
+ signInRequired: boolean;
98
+ /** The requested channel does not exist or is not visible. */
99
+ notFound: boolean;
100
+ }
101
+ export type WixVideoError = Error & WixVideoErrorFlags;
102
+ /** Build an error carrying the `WixVideoErrorFlags` (each defaults to false). */
103
+ export declare function wixVideoError(message: string, flags?: Partial<WixVideoErrorFlags>): WixVideoError;
104
+ /** True when a web bridge refused the gateway URL. Wix Video is then out of reach on
105
+ * this surface: the same read works in the native apps. */
106
+ export declare function isWixVideoWebRefusal(error: unknown): boolean;
107
+ /**
108
+ * Run one GraphQL read against the Wix Video gateway and return its `data`.
109
+ *
110
+ * The gateway answers HTTP 200 with an `errors` array for a refused or failed query.
111
+ * That is thrown, never returned as empty data: a failed query must not look like a
112
+ * site with no videos.
113
+ *
114
+ * @scope visitor
115
+ */
116
+ export declare function wixVideoQuery<T>(query: string, variables?: Record<string, unknown>, opts?: WixCallOptions): Promise<T>;
117
+ /** Read every page of a cursor-paginated list. Rows are de-duplicated by id. A
118
+ * repeated or malformed cursor throws instead of looping or returning a partial list. */
119
+ export declare function wixVideoPages<T extends {
120
+ id: string;
121
+ }>(fetchPage: (cursor: string | null) => Promise<WixVideoPage<T> | null | undefined>): Promise<T[]>;
122
+ /** The channel fields a list or card needs. */
123
+ export declare const CHANNEL_FIELDS = "id title description source featuredVideoId hasPaidVideos cover { selected } videos { total }";
124
+ /** The channel fields an access decision needs (`canWatchVideo`). */
125
+ export declare const CHANNEL_ACCESS_FIELDS = "id title pricing { type enabled required } purchase(filter: {type: SUBSCRIPTION}) { status expiresAt }";
126
+ /** The video fields a card needs: title, images, length and processing state. */
127
+ export declare const VIDEO_CARD_FIELDS = "id title source status cover { selected } poster { selected } content { status duration thumbnailUrl externalUrl externalId } pricing { type enabled }";
128
+ /** The video fields playback and an access decision need. */
129
+ export declare const VIDEO_FIELDS = "id title description source status cover { selected } poster { selected } content { status duration thumbnailUrl externalUrl externalId } pricing { type enabled } purchase(filter: {type: SALE}) { status } rent_purchase: purchase(filter: {type: RENT}) { status }";
130
+ export interface ListChannelsOptions extends WixCallOptions {
131
+ /** Also read the first N videos of each channel (owner's order). 0 (default) reads none. */
132
+ videosPerChannel?: number;
133
+ }
134
+ /**
135
+ * Every channel of the site, in the owner's order (all pages).
136
+ *
137
+ * @scope visitor
138
+ */
139
+ export declare function listChannels(opts?: ListChannelsOptions): Promise<WixVideoChannel[]>;
140
+ /**
141
+ * One channel and all its videos, in the owner's order. Throws a `notFound` error when
142
+ * the channel does not exist.
143
+ *
144
+ * @scope visitor
145
+ */
146
+ export declare function listChannelVideos(channelId: string, opts?: WixCallOptions): Promise<{
147
+ channel: WixVideoChannel;
148
+ videos: WixVideo[];
149
+ }>;
150
+ /**
151
+ * One video with its channel's access fields. `null` when the channel does not exist;
152
+ * `video: null` when the channel has no such video.
153
+ *
154
+ * @scope visitor
155
+ */
156
+ export declare function getVideo(channelId: string, videoId: string, opts?: WixCallOptions): Promise<{
157
+ channel: WixVideoChannel;
158
+ video: WixVideo | null;
159
+ } | null>;
160
+ /**
161
+ * An authorized HLS stream URL for a hosted video (source `COMPUTER`, `GOOGLE_DRIVE`
162
+ * or `DROPBOX`), or "" when the gateway gives none. Check `canWatchVideo` first: the
163
+ * gateway authorizes the stream per viewer. A linked (YouTube, …) video has no stream
164
+ * here; use `youtubeVideoId`.
165
+ *
166
+ * @scope visitor
167
+ */
168
+ export declare function getVideoStreamUrl(channelId: string, videoId: string, opts?: WixCallOptions): Promise<string>;
169
+ export interface ListLatestVideosOptions extends WixCallOptions {
170
+ /** Most videos to return (default 8). */
171
+ limit?: number;
172
+ /** Channels to read (first page, default 10). */
173
+ channels?: number;
174
+ /** Videos to read from each channel (default 4). */
175
+ perChannel?: number;
176
+ }
177
+ /**
178
+ * The newest ready videos across the site's channels, for a rail or carousel: ONE
179
+ * request that reads the first videos of the first channels. Channel order and each
180
+ * channel's own video order are kept, and the channels take turns, so one large
181
+ * channel cannot fill the whole rail.
182
+ *
183
+ * @scope visitor
184
+ */
185
+ export declare function listLatestVideos(opts?: ListLatestVideosOptions): Promise<WixVideoInChannel[]>;
@@ -0,0 +1,324 @@
1
+ // ../../stdlib/wix-sdk/media/src/index.ts
2
+ var WIX_LIST_IMAGE = { width: 1e3, height: 1e3, quality: 85 };
3
+ var WIX_MEDIA = /^https?:\/\/static\.wixstatic\.com\/media\/[^/]/;
4
+ var WIX_IMAGE = /^wix:image:\/\/v1\/([^/]+)/i;
5
+ var WIX_TRANSFORM = /^(https?:\/\/static\.wixstatic\.com\/media\/[^/]+\/v1\/)([^/]+)\/([^/?#]+)(?:\/([^/?#]+))?([?#].*)?$/i;
6
+ var SIZING_PARAM = /^(w|h|q)_\d+$/i;
7
+ var WEB_URL = /^https?:\/\//i;
8
+ var RESIZABLE_MODES = /* @__PURE__ */ new Set(["fit", "fill"]);
9
+ var OWNED_PARAM = /^(w|h|q)_/i;
10
+ var DROPPED_PARAM = /^enc_/i;
11
+ function wixMediaId(reference) {
12
+ return WIX_IMAGE.exec(reference)?.[1] ?? (reference.includes("/") || reference.includes(":") ? "" : reference);
13
+ }
14
+ function resolveWixMediaReference(reference) {
15
+ const mediaId = wixMediaId(reference);
16
+ return mediaId ? `https://static.wixstatic.com/media/${mediaId}` : reference;
17
+ }
18
+ function requestedParams(size) {
19
+ return [`w_${size.width}`, `h_${size.height}`, `q_${size.quality ?? 85}`];
20
+ }
21
+ function fittedParams(size, source, mode) {
22
+ const quality = `q_${size.quality ?? 85}`;
23
+ const { width: sourceWidth, height: sourceHeight } = source;
24
+ if (sourceWidth && sourceHeight) {
25
+ if (mode.toLowerCase() === "fill") {
26
+ const scale = Math.min(size.width / sourceWidth, size.height / sourceHeight, 1);
27
+ return [
28
+ `w_${Math.max(1, Math.round(sourceWidth * scale))}`,
29
+ `h_${Math.max(1, Math.round(sourceHeight * scale))}`,
30
+ quality
31
+ ];
32
+ }
33
+ return [
34
+ `w_${Math.min(sourceWidth, size.width)}`,
35
+ `h_${Math.min(sourceHeight, size.height)}`,
36
+ quality
37
+ ];
38
+ }
39
+ if (sourceWidth) return [`w_${Math.min(sourceWidth, size.width)}`, quality];
40
+ if (sourceHeight) return [`h_${Math.min(sourceHeight, size.height)}`, quality];
41
+ return requestedParams(size);
42
+ }
43
+ function resizeWixTransform(url, size) {
44
+ const transform = WIX_TRANSFORM.exec(url);
45
+ if (!transform || !RESIZABLE_MODES.has(transform[2].toLowerCase())) return url;
46
+ const [, base, mode, rawParams, filename, suffix] = transform;
47
+ const source = rawParams.split(",").filter(Boolean);
48
+ if (!filename && !source.some((param) => SIZING_PARAM.test(param))) return url;
49
+ const dimensions = dimensionsOf(source);
50
+ if (mode.toLowerCase() === "fill" && !dimensions.width && !dimensions.height) return url;
51
+ const stated = [dimensions.width, dimensions.height].filter((d) => d !== void 0);
52
+ if (stated.length > 0 && (dimensions.width ?? 0) <= size.width && (dimensions.height ?? 0) <= size.height) return url;
53
+ const carried = source.filter((p) => !OWNED_PARAM.test(p) && !DROPPED_PARAM.test(p));
54
+ const params = [...fittedParams(size, dimensions, mode), ...carried];
55
+ return `${base}${mode}/${params.join(",")}/image.jpg${suffix ?? ""}`;
56
+ }
57
+ function wixTransformDimension(params, name) {
58
+ const match = params.find((param) => new RegExp(`^${name}_\\d+$`, "i").test(param));
59
+ return match ? Number(match.slice(2)) : void 0;
60
+ }
61
+ function dimensionsOf(params) {
62
+ return {
63
+ width: wixTransformDimension(params, "w"),
64
+ height: wixTransformDimension(params, "h")
65
+ };
66
+ }
67
+ function wixMediaUrl(value, size = WIX_LIST_IMAGE) {
68
+ const reference = typeof value === "string" ? value.trim() : "";
69
+ if (!reference) return "";
70
+ const resolvedUrl = resolveWixMediaReference(reference);
71
+ if (!WEB_URL.test(resolvedUrl)) return "";
72
+ if (size === null) return resolvedUrl;
73
+ if (!WIX_MEDIA.test(resolvedUrl)) return resolvedUrl;
74
+ const base = resolvedUrl.split(/[?#]/)[0];
75
+ if (base.includes("/v1/")) return resizeWixTransform(resolvedUrl, size);
76
+ const suffix = resolvedUrl.slice(base.length);
77
+ return `${base}/v1/fit/${requestedParams(size).join(",")}/image.jpg${suffix}`;
78
+ }
79
+
80
+ // ../../stdlib/wix-sdk/video/src/display.ts
81
+ var HOSTED_SOURCES = /* @__PURE__ */ new Set(["COMPUTER", "GOOGLE_DRIVE", "DROPBOX"]);
82
+ var PROCESSING = /PROCESS|PROGRESS|PENDING|QUEUE|INITIALIZED|TRANSCOD|UPLOAD|CONVERT|PREVIEW_READY/;
83
+ function isHostedVideo(video) {
84
+ return HOSTED_SOURCES.has(video.source ?? "");
85
+ }
86
+ function isVideoReady(video) {
87
+ return (video.status ?? "VISIBLE") === "VISIBLE" && video.content?.status === "READY";
88
+ }
89
+ function isVideoProcessing(video) {
90
+ return PROCESSING.test(video.content?.status ?? "");
91
+ }
92
+ function youtubeVideoId(video) {
93
+ const match = /^https:\/\/(?:(?:www\.|m\.)?youtube\.com|www\.youtube-nocookie\.com)\/embed\/([A-Za-z0-9_-]{11})(?:[?/#]|$)/.exec(video.content?.externalUrl ?? "");
94
+ if (match) return match[1];
95
+ const id = video.content?.externalId ?? "";
96
+ return /^[A-Za-z0-9_-]{11}$/.test(id) ? id : "";
97
+ }
98
+ function videoCoverUrl(video, size = WIX_LIST_IMAGE) {
99
+ for (const candidate of [video.cover?.selected, video.poster?.selected, video.content?.thumbnailUrl]) {
100
+ const url = wixMediaUrl(candidate, size);
101
+ if (url) return url;
102
+ }
103
+ const youtube = video.source === "YOUTUBE" ? youtubeVideoId(video) : "";
104
+ return youtube ? `https://i.ytimg.com/vi/${youtube}/hqdefault.jpg` : "";
105
+ }
106
+ function channelCoverUrl(channel, size = WIX_LIST_IMAGE) {
107
+ return wixMediaUrl(channel.cover?.selected, size);
108
+ }
109
+ function videoDurationSec(video) {
110
+ const ms = video.content?.duration;
111
+ return typeof ms === "number" && Number.isFinite(ms) && ms > 0 ? Math.round(ms / 1e3) : void 0;
112
+ }
113
+ function formatVideoDuration(seconds) {
114
+ if (seconds === void 0 || !Number.isFinite(seconds) || seconds <= 0) return "";
115
+ const total = Math.round(seconds);
116
+ const h = Math.floor(total / 3600);
117
+ const m = Math.floor(total % 3600 / 60);
118
+ const s = String(total % 60).padStart(2, "0");
119
+ return h ? `${h}:${String(m).padStart(2, "0")}:${s}` : `${m}:${s}`;
120
+ }
121
+ function isPaidVideo(video) {
122
+ return Array.isArray(video.pricing) && video.pricing.some((p) => p.enabled);
123
+ }
124
+ function canWatchVideo(channel, video, now = Date.now()) {
125
+ if (!Array.isArray(channel.pricing) || !Array.isArray(video.pricing)) return false;
126
+ const subscription = channel.purchase;
127
+ const expires = subscription?.expiresAt ? Date.parse(subscription.expiresAt) : void 0;
128
+ const subscribed = subscription?.status === "ACTIVE" && (expires === void 0 || expires > now) || subscription?.status === "CANCELED" && expires !== void 0 && expires > now;
129
+ const channelOptions = channel.pricing.filter((p) => p.enabled);
130
+ if (channelOptions.some((p) => p.required || p.type === "PRICING_PLAN") && !subscribed) return false;
131
+ return !isPaidVideo(video) || subscribed || video.purchase?.status === "ACTIVE" || video.rent_purchase?.status === "ACTIVE";
132
+ }
133
+ function latestVideos(channels, limit = 8) {
134
+ const queues = channels.map((channel) => ({
135
+ channel,
136
+ videos: (channel.videos?.entities ?? []).filter((video) => video?.id && isVideoReady(video))
137
+ }));
138
+ const out = [];
139
+ const seen = /* @__PURE__ */ new Set();
140
+ for (let round = 0; out.length < limit && queues.some((q) => q.videos.length > round); round++) {
141
+ for (const { channel, videos } of queues) {
142
+ const video = videos[round];
143
+ if (!video || seen.has(video.id)) continue;
144
+ seen.add(video.id);
145
+ out.push({ channel, video });
146
+ if (out.length >= limit) break;
147
+ }
148
+ }
149
+ return out;
150
+ }
151
+ function videoCard(entry, untitled = "Video", size = WIX_LIST_IMAGE) {
152
+ const { channel, video } = entry;
153
+ const title = video.title?.trim() || untitled;
154
+ const duration = formatVideoDuration(videoDurationSec(video));
155
+ const channelTitle = channel.title?.trim() ?? "";
156
+ const coverUrl = videoCoverUrl(video, size);
157
+ const isPaid = isPaidVideo(video);
158
+ return {
159
+ id: video.id,
160
+ channelId: channel.id,
161
+ title,
162
+ meta: [duration, channelTitle].filter(Boolean).join(" \xB7 "),
163
+ duration,
164
+ channelTitle,
165
+ coverUrl,
166
+ hasCover: !!coverUrl,
167
+ noCover: !coverUrl,
168
+ isPaid,
169
+ notPaid: !isPaid,
170
+ accessibilityLabel: [title, duration, channelTitle].filter(Boolean).join(", ")
171
+ };
172
+ }
173
+
174
+ // ../../stdlib/wix-sdk/video/src/index.ts
175
+ var WIX_VIDEO_GRAPHQL_URL = "https://manage.wix.com/_api/vod/public/graphql";
176
+ function wixVideoError(message, flags = {}) {
177
+ return Object.assign(new Error(message), {
178
+ denied: flags.denied ?? false,
179
+ signInRequired: flags.signInRequired ?? false,
180
+ notFound: flags.notFound ?? false
181
+ });
182
+ }
183
+ function isWixVideoWebRefusal(error) {
184
+ return /webwix_owner_only|absolute_urls_not_allowed/.test(String(error?.message ?? error));
185
+ }
186
+ function transportError(error) {
187
+ const message = String(error?.message ?? error);
188
+ const status = Number(/^HTTP\s+(\d{3})\b/.exec(message)?.[1]);
189
+ return Object.assign(wixVideoError(message, { signInRequired: status === 401, denied: status === 401 || status === 403 }), { cause: error });
190
+ }
191
+ function callHeaders(opts) {
192
+ const site = opts?.site?.trim();
193
+ return site ? { "wix-site-id": site } : void 0;
194
+ }
195
+ async function wixVideoQuery(query, variables = {}, opts) {
196
+ const wixApi = globalThis.himalaya?.wixApi;
197
+ if (!wixApi) throw wixVideoError("@himalaya/wix-video: host did not install the wixApi bridge");
198
+ const headers = callHeaders(opts);
199
+ let response;
200
+ try {
201
+ response = await (headers ? wixApi.call("POST", WIX_VIDEO_GRAPHQL_URL, { query, variables }, headers) : wixApi.call("POST", WIX_VIDEO_GRAPHQL_URL, { query, variables }));
202
+ } catch (error) {
203
+ throw transportError(error);
204
+ }
205
+ if (response?.errors?.length) {
206
+ const codes = response.errors.map((e) => e.extensions?.code ?? "");
207
+ const signInRequired = codes.some((code) => /^(UNAUTHENTICATED|UNAUTHORIZED)$/i.test(code));
208
+ const denied = codes.some((code) => /FORBIDDEN|UNAUTH|ACCESS_DENIED|PERMISSION_DENIED/i.test(code));
209
+ throw wixVideoError(signInRequired ? "Video unauthenticated" : denied ? "Video access denied" : "Wix Video query failed", { denied, signInRequired });
210
+ }
211
+ const data = response && Object.prototype.hasOwnProperty.call(response, "data") ? response.data : response;
212
+ if (!data || typeof data !== "object") throw wixVideoError("Invalid Wix Video response");
213
+ return data;
214
+ }
215
+ async function wixVideoPages(fetchPage) {
216
+ const rows = /* @__PURE__ */ new Map();
217
+ const cursors = /* @__PURE__ */ new Set();
218
+ let cursor = null;
219
+ for (let page = 0; page < 200; page++) {
220
+ const result = await fetchPage(cursor);
221
+ if (!result || !Array.isArray(result.entities)) throw wixVideoError("Invalid Wix Video page");
222
+ for (const row of result.entities) if (row?.id) rows.set(row.id, row);
223
+ if (!result.cursor) return [...rows.values()];
224
+ if (typeof result.cursor !== "string" || cursors.has(result.cursor)) throw wixVideoError("Invalid Wix Video cursor");
225
+ cursor = result.cursor;
226
+ cursors.add(cursor);
227
+ }
228
+ throw wixVideoError("Wix Video pagination limit exceeded");
229
+ }
230
+ var CHANNEL_FIELDS = "id title description source featuredVideoId hasPaidVideos cover { selected } videos { total }";
231
+ var CHANNEL_ACCESS_FIELDS = "id title pricing { type enabled required } purchase(filter: {type: SUBSCRIPTION}) { status expiresAt }";
232
+ var VIDEO_CARD_FIELDS = "id title source status cover { selected } poster { selected } content { status duration thumbnailUrl externalUrl externalId } pricing { type enabled }";
233
+ var VIDEO_FIELDS = "id title description source status cover { selected } poster { selected } content { status duration thumbnailUrl externalUrl externalId } pricing { type enabled } purchase(filter: {type: SALE}) { status } rent_purchase: purchase(filter: {type: RENT}) { status }";
234
+ function channelsQuery(videosPerChannel) {
235
+ const preview = videosPerChannel > 0 ? ` preview: videos(sort: CUSTOM_DESC, limit: ${Math.floor(videosPerChannel)}) { entities { ${VIDEO_CARD_FIELDS} } }` : "";
236
+ return `query WixVideoChannels($cursor: String) {
237
+ channels(sort: CUSTOM_ASC, cursor: $cursor) { total cursor entities { ${CHANNEL_FIELDS}${preview} } }
238
+ }`;
239
+ }
240
+ function withPreview(channel) {
241
+ const { preview, ...rest } = channel;
242
+ if (!preview) return rest;
243
+ return { ...rest, videos: { ...rest.videos, entities: Array.isArray(preview.entities) ? preview.entities : [] } };
244
+ }
245
+ async function listChannels(opts = {}) {
246
+ const query = channelsQuery(Math.max(0, opts.videosPerChannel ?? 0));
247
+ const channels = await wixVideoPages(async (cursor) => {
248
+ const data = await wixVideoQuery(query, { cursor }, opts);
249
+ return data.channels;
250
+ });
251
+ return channels.map(withPreview);
252
+ }
253
+ async function listChannelVideos(channelId, opts) {
254
+ if (!channelId) throw wixVideoError("listChannelVideos: channelId is required", { notFound: true });
255
+ const query = `query WixVideoChannelVideos($channelId: ID!, $cursor: String) {
256
+ channel(filter: {id: $channelId}) { ${CHANNEL_ACCESS_FIELDS} cover { selected } videos(sort: CUSTOM_DESC, cursor: $cursor) { cursor entities { ${VIDEO_FIELDS} } } }
257
+ }`;
258
+ let channel;
259
+ const videos = await wixVideoPages(async (cursor) => {
260
+ const data = await wixVideoQuery(query, { channelId, cursor }, opts);
261
+ if (!data.channel) throw wixVideoError("Channel unavailable", { notFound: true });
262
+ channel = data.channel;
263
+ return data.channel.videos;
264
+ });
265
+ const { videos: _page, ...rest } = channel;
266
+ return { channel: rest, videos };
267
+ }
268
+ async function getVideo(channelId, videoId, opts) {
269
+ const query = `query WixVideo($channelId: ID!, $videoId: ID!) {
270
+ channel(filter: {id: $channelId}) { ${CHANNEL_ACCESS_FIELDS} video(filter: {id: $videoId}) { ${VIDEO_FIELDS} } }
271
+ }`;
272
+ const data = await wixVideoQuery(query, { channelId, videoId }, opts);
273
+ if (!data.channel) return null;
274
+ const { video, ...channel } = data.channel;
275
+ return { channel, video: video ?? null };
276
+ }
277
+ async function getVideoStreamUrl(channelId, videoId, opts) {
278
+ const query = `query WixVideoPlay($channelId: ID!, $videoId: ID!) {
279
+ channel(filter: {id: $channelId}) { video(filter: {id: $videoId}) { play(type: CONTENT) { entity { url hls } } } }
280
+ }`;
281
+ const data = await wixVideoQuery(query, { channelId, videoId }, opts);
282
+ const entity = data.channel?.video?.play?.entity;
283
+ const url = entity?.hls || entity?.url || "";
284
+ return /^https:\/\//.test(url) ? url : "";
285
+ }
286
+ async function listLatestVideos(opts = {}) {
287
+ const channels = Math.max(1, Math.floor(opts.channels ?? 10));
288
+ const perChannel = Math.max(1, Math.floor(opts.perChannel ?? 4));
289
+ const query = `query WixVideoLatest {
290
+ channels(sort: CUSTOM_ASC, limit: ${channels}) { entities { ${CHANNEL_FIELDS} preview: videos(sort: CUSTOM_DESC, limit: ${perChannel}) { entities { ${VIDEO_CARD_FIELDS} } } } }
291
+ }`;
292
+ const data = await wixVideoQuery(query, {}, opts);
293
+ if (!data.channels || !Array.isArray(data.channels.entities)) throw wixVideoError("Invalid Wix Video page");
294
+ return latestVideos(data.channels.entities.filter((c) => c?.id).map(withPreview), opts.limit ?? 8);
295
+ }
296
+ export {
297
+ CHANNEL_ACCESS_FIELDS,
298
+ CHANNEL_FIELDS,
299
+ VIDEO_CARD_FIELDS,
300
+ VIDEO_FIELDS,
301
+ WIX_VIDEO_GRAPHQL_URL,
302
+ canWatchVideo,
303
+ channelCoverUrl,
304
+ formatVideoDuration,
305
+ getVideo,
306
+ getVideoStreamUrl,
307
+ isHostedVideo,
308
+ isPaidVideo,
309
+ isVideoProcessing,
310
+ isVideoReady,
311
+ isWixVideoWebRefusal,
312
+ latestVideos,
313
+ listChannelVideos,
314
+ listChannels,
315
+ listLatestVideos,
316
+ videoCard,
317
+ videoCoverUrl,
318
+ videoDurationSec,
319
+ wixVideoError,
320
+ wixVideoPages,
321
+ wixVideoQuery,
322
+ youtubeVideoId
323
+ };
324
+ //# sourceMappingURL=wix-video.mjs.map
@@ -506,6 +506,10 @@
506
506
  "types": "./dist/_types/stdlib/wix-sdk/users/src/index.d.ts",
507
507
  "import": "./dist/wix-users.mjs"
508
508
  },
509
+ "./wix-video": {
510
+ "types": "./dist/_types/stdlib/wix-sdk/video/src/index.d.ts",
511
+ "import": "./dist/wix-video.mjs"
512
+ },
509
513
  "./wix-workflows": {
510
514
  "types": "./dist/_types/stdlib/wix-sdk/workflows-v1/src/index.d.ts",
511
515
  "import": "./dist/wix-workflows.mjs"