@clockworkdog/cogs-client 3.1.3 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,49 @@
1
+ import { MediaObjectFit } from '.';
2
+ import CogsConnection from './CogsConnection';
3
+ import MediaClipStateMessage from './types/MediaClipStateMessage';
4
+ import { VideoState } from './types/VideoState';
5
+ type EventTypes = {
6
+ state: VideoState;
7
+ videoClipState: MediaClipStateMessage;
8
+ };
9
+ export default class VideoPlayer {
10
+ private cogsConnection;
11
+ private eventTarget;
12
+ private globalVolume;
13
+ private videoClipPlayers;
14
+ private activeClip?;
15
+ private pendingClip?;
16
+ private parentElement;
17
+ private sinkId;
18
+ constructor(cogsConnection: CogsConnection<any>, parentElement?: HTMLElement);
19
+ setParentElement(parentElement: HTMLElement): void;
20
+ resetParentElement(): void;
21
+ setGlobalVolume(globalVolume: number): void;
22
+ playVideoClip(path: string, { playId, volume, loop, fit }: {
23
+ playId: string;
24
+ volume: number;
25
+ loop: boolean;
26
+ fit: MediaObjectFit;
27
+ }): void;
28
+ pauseVideoClip(): void;
29
+ stopVideoClip(): void;
30
+ setVideoClipVolume({ volume }: {
31
+ volume: number;
32
+ }): void;
33
+ setVideoClipFit({ fit }: {
34
+ fit: MediaObjectFit;
35
+ }): void;
36
+ private handleStoppedClip;
37
+ private updateVideoClipPlayer;
38
+ setAudioSink(sinkId: string): void;
39
+ private updateConfig;
40
+ private notifyStateListeners;
41
+ private notifyClipStateListeners;
42
+ addEventListener<EventName extends keyof EventTypes>(type: EventName, listener: (ev: CustomEvent<EventTypes[EventName]>) => void, options?: boolean | AddEventListenerOptions): void;
43
+ removeEventListener<EventName extends keyof EventTypes>(type: EventName, listener: (ev: CustomEvent<EventTypes[EventName]>) => void, options?: boolean | EventListenerOptions): void;
44
+ private dispatchEvent;
45
+ private createVideoElement;
46
+ private createClipPlayer;
47
+ private unloadClip;
48
+ }
49
+ export {};
@@ -0,0 +1,394 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const VideoState_1 = require("./types/VideoState");
4
+ const DEFAULT_PARENT_ELEMENT = document.body;
5
+ class VideoPlayer {
6
+ constructor(
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ cogsConnection, parentElement = DEFAULT_PARENT_ELEMENT) {
9
+ this.cogsConnection = cogsConnection;
10
+ this.eventTarget = new EventTarget();
11
+ this.globalVolume = 1;
12
+ this.videoClipPlayers = {};
13
+ this.sinkId = '';
14
+ this.parentElement = parentElement;
15
+ // Send the current status of each clip to COGS
16
+ this.addEventListener('videoClipState', ({ detail }) => {
17
+ cogsConnection.sendMediaClipState(detail);
18
+ });
19
+ // Listen for video control messages
20
+ cogsConnection.addEventListener('message', ({ message }) => {
21
+ switch (message.type) {
22
+ case 'media_config_update':
23
+ this.setGlobalVolume(message.globalVolume);
24
+ if (message.audioOutput !== undefined) {
25
+ const sinkId = cogsConnection.getAudioSinkId(message.audioOutput);
26
+ this.setAudioSink(sinkId !== null && sinkId !== void 0 ? sinkId : '');
27
+ }
28
+ this.updateConfig(message.files);
29
+ break;
30
+ case 'video_play':
31
+ this.playVideoClip(message.file, {
32
+ playId: message.playId,
33
+ volume: message.volume,
34
+ loop: Boolean(message.loop),
35
+ fit: message.fit,
36
+ });
37
+ break;
38
+ case 'video_pause':
39
+ this.pauseVideoClip();
40
+ break;
41
+ case 'video_stop':
42
+ this.stopVideoClip();
43
+ break;
44
+ case 'video_set_volume':
45
+ this.setVideoClipVolume({ volume: message.volume });
46
+ break;
47
+ case 'video_set_fit':
48
+ this.setVideoClipFit({ fit: message.fit });
49
+ break;
50
+ }
51
+ });
52
+ // On connection, send the current playing state of all clips
53
+ // (Usually empty unless websocket is reconnecting)
54
+ const sendInitialClipStates = () => {
55
+ const files = Object.entries(this.videoClipPlayers).map(([file, player]) => {
56
+ const status = !player.videoElement.paused
57
+ ? 'playing'
58
+ : player.videoElement.currentTime === 0 || player.videoElement.currentTime === player.videoElement.duration
59
+ ? 'paused'
60
+ : 'stopped';
61
+ return [file, status];
62
+ });
63
+ cogsConnection.sendInitialMediaClipStates({ mediaType: 'video', files });
64
+ };
65
+ cogsConnection.addEventListener('open', sendInitialClipStates);
66
+ sendInitialClipStates();
67
+ }
68
+ setParentElement(parentElement) {
69
+ this.parentElement = parentElement;
70
+ Object.values(this.videoClipPlayers).forEach((clipPlayer) => {
71
+ parentElement.appendChild(clipPlayer.videoElement);
72
+ });
73
+ }
74
+ resetParentElement() {
75
+ this.setParentElement(DEFAULT_PARENT_ELEMENT);
76
+ }
77
+ setGlobalVolume(globalVolume) {
78
+ Object.values(this.videoClipPlayers).forEach((clipPlayer) => {
79
+ setVideoElementVolume(clipPlayer.videoElement, clipPlayer.volume * globalVolume);
80
+ });
81
+ this.globalVolume = globalVolume;
82
+ this.notifyStateListeners();
83
+ }
84
+ playVideoClip(path, { playId, volume, loop, fit }) {
85
+ var _a;
86
+ if (!this.videoClipPlayers[path]) {
87
+ this.videoClipPlayers[path] = this.createClipPlayer(path, { preload: 'none', ephemeral: true, fit });
88
+ }
89
+ // Check if there's already a pending clip, which has now been superseded and abort the play operation
90
+ if (this.pendingClip) {
91
+ this.updateVideoClipPlayer(this.pendingClip.path, (clipPlayer) => {
92
+ clipPlayer.videoElement.load(); // Resets the media element
93
+ return clipPlayer;
94
+ });
95
+ }
96
+ // New pending clip is video being requested
97
+ if (((_a = this.activeClip) === null || _a === void 0 ? void 0 : _a.path) !== path) {
98
+ this.pendingClip = { path, playId, actionOncePlaying: 'play' };
99
+ }
100
+ // Setup and play the pending clip's player
101
+ this.updateVideoClipPlayer(path, (clipPlayer) => {
102
+ clipPlayer.volume = volume;
103
+ setVideoElementVolume(clipPlayer.videoElement, volume * this.globalVolume);
104
+ clipPlayer.videoElement.loop = loop;
105
+ clipPlayer.videoElement.style.objectFit = fit;
106
+ if (clipPlayer.videoElement.currentTime === clipPlayer.videoElement.duration) {
107
+ clipPlayer.videoElement.currentTime = 0;
108
+ }
109
+ clipPlayer.videoElement.play();
110
+ // Display right away if there's currently no active clip
111
+ if (!this.activeClip) {
112
+ clipPlayer.videoElement.style.display = 'block';
113
+ }
114
+ return clipPlayer;
115
+ });
116
+ }
117
+ pauseVideoClip() {
118
+ // Pending clip should be paused when it loads and becomes active
119
+ if (this.pendingClip) {
120
+ this.pendingClip.actionOncePlaying = 'pause';
121
+ }
122
+ // Pause the currently active clip
123
+ if (this.activeClip) {
124
+ const { playId, path } = this.activeClip;
125
+ this.updateVideoClipPlayer(path, (clipPlayer) => {
126
+ var _a;
127
+ (_a = clipPlayer.videoElement) === null || _a === void 0 ? void 0 : _a.pause();
128
+ return clipPlayer;
129
+ });
130
+ this.notifyClipStateListeners(playId, path, 'paused');
131
+ }
132
+ }
133
+ stopVideoClip() {
134
+ // Pending clip should be stopped when it loads and becomes active
135
+ if (this.pendingClip) {
136
+ this.pendingClip.actionOncePlaying = 'stop';
137
+ }
138
+ // Stop the currently active clip
139
+ if (this.activeClip) {
140
+ this.handleStoppedClip(this.activeClip.path);
141
+ }
142
+ }
143
+ setVideoClipVolume({ volume }) {
144
+ var _a, _b;
145
+ // If there is a pending clip, this is latest to have been played so update its volume
146
+ const clipToUpdate = (_b = (_a = this.pendingClip) !== null && _a !== void 0 ? _a : this.activeClip) !== null && _b !== void 0 ? _b : undefined;
147
+ if (!clipToUpdate) {
148
+ return;
149
+ }
150
+ if (!(volume >= 0 && volume <= 1)) {
151
+ console.warn('Invalid volume', volume);
152
+ return;
153
+ }
154
+ this.updateVideoClipPlayer(clipToUpdate.path, (clipPlayer) => {
155
+ if (clipPlayer.videoElement) {
156
+ clipPlayer.volume = volume;
157
+ setVideoElementVolume(clipPlayer.videoElement, volume * this.globalVolume);
158
+ }
159
+ return clipPlayer;
160
+ });
161
+ }
162
+ setVideoClipFit({ fit }) {
163
+ var _a, _b;
164
+ // If there is a pending clip, this is latest to have been played so update its fit
165
+ const clipToUpdate = (_b = (_a = this.pendingClip) !== null && _a !== void 0 ? _a : this.activeClip) !== null && _b !== void 0 ? _b : undefined;
166
+ if (!clipToUpdate) {
167
+ return;
168
+ }
169
+ this.updateVideoClipPlayer(clipToUpdate.path, (clipPlayer) => {
170
+ if (clipPlayer.videoElement) {
171
+ clipPlayer.videoElement.style.objectFit = fit;
172
+ }
173
+ return clipPlayer;
174
+ });
175
+ }
176
+ handleStoppedClip(path) {
177
+ var _a;
178
+ if (!this.activeClip || this.activeClip.path !== path) {
179
+ return;
180
+ }
181
+ const playId = this.activeClip.playId;
182
+ // Once an ephemeral clip stops, cleanup and remove the player
183
+ if ((_a = this.videoClipPlayers[this.activeClip.path]) === null || _a === void 0 ? void 0 : _a.config.ephemeral) {
184
+ this.unloadClip(path);
185
+ }
186
+ this.activeClip = undefined;
187
+ this.updateVideoClipPlayer(path, (clipPlayer) => {
188
+ clipPlayer.videoElement.pause();
189
+ clipPlayer.videoElement.currentTime = 0;
190
+ clipPlayer.videoElement.style.display = 'none';
191
+ return clipPlayer;
192
+ });
193
+ this.notifyClipStateListeners(playId, path, 'stopped');
194
+ }
195
+ updateVideoClipPlayer(path, update) {
196
+ if (this.videoClipPlayers[path]) {
197
+ const newPlayer = update(this.videoClipPlayers[path]);
198
+ if (newPlayer) {
199
+ this.videoClipPlayers[path] = newPlayer;
200
+ }
201
+ else {
202
+ delete this.videoClipPlayers[path];
203
+ }
204
+ this.notifyStateListeners();
205
+ }
206
+ }
207
+ setAudioSink(sinkId) {
208
+ for (const clipPlayer of Object.values(this.videoClipPlayers)) {
209
+ setPlayerSinkId(clipPlayer, sinkId);
210
+ }
211
+ this.sinkId = sinkId;
212
+ }
213
+ updateConfig(newPaths) {
214
+ const newVideoPaths = Object.fromEntries(Object.entries(newPaths).filter(([, { type }]) => type === 'video' || !type));
215
+ const previousClipPlayers = this.videoClipPlayers;
216
+ this.videoClipPlayers = (() => {
217
+ const clipPlayers = { ...previousClipPlayers };
218
+ const removedClips = Object.keys(previousClipPlayers).filter((previousPath) => !(previousPath in newVideoPaths));
219
+ removedClips.forEach((path) => {
220
+ var _a, _b;
221
+ if (((_a = this.activeClip) === null || _a === void 0 ? void 0 : _a.path) === path && ((_b = previousClipPlayers[path]) === null || _b === void 0 ? void 0 : _b.config.ephemeral) === false) {
222
+ this.updateVideoClipPlayer(path, (player) => {
223
+ player.config = { ...player.config, ephemeral: true };
224
+ return player;
225
+ });
226
+ }
227
+ else {
228
+ this.unloadClip(path);
229
+ delete clipPlayers[path];
230
+ }
231
+ });
232
+ const addedClips = Object.entries(newVideoPaths).filter(([newFile]) => !previousClipPlayers[newFile]);
233
+ addedClips.forEach(([path, config]) => {
234
+ clipPlayers[path] = this.createClipPlayer(path, { ...config, preload: preloadString(config.preload), ephemeral: false, fit: 'contain' });
235
+ });
236
+ const updatedClips = Object.entries(previousClipPlayers).filter(([previousPath]) => previousPath in newVideoPaths);
237
+ updatedClips.forEach(([path, previousClipPlayer]) => {
238
+ if (previousClipPlayer.config.preload !== newVideoPaths[path].preload) {
239
+ this.updateVideoClipPlayer(path, (player) => {
240
+ player.config = {
241
+ ...player.config,
242
+ preload: preloadString(newVideoPaths[path].preload),
243
+ ephemeral: false,
244
+ };
245
+ player.videoElement.preload = player.config.preload;
246
+ return player;
247
+ });
248
+ }
249
+ });
250
+ return clipPlayers;
251
+ })();
252
+ this.notifyStateListeners();
253
+ }
254
+ notifyStateListeners() {
255
+ var _a, _b, _c, _d, _e, _f, _g;
256
+ const VideoState = {
257
+ globalVolume: this.globalVolume,
258
+ isPlaying: this.activeClip ? !((_a = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _a === void 0 ? void 0 : _a.paused) : false,
259
+ clips: { ...this.videoClipPlayers },
260
+ activeClip: this.activeClip
261
+ ? {
262
+ path: this.activeClip.path,
263
+ state: !((_b = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _b === void 0 ? void 0 : _b.paused) ? VideoState_1.ActiveVideoClipState.Playing : VideoState_1.ActiveVideoClipState.Paused,
264
+ loop: (_d = (_c = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _c === void 0 ? void 0 : _c.loop) !== null && _d !== void 0 ? _d : false,
265
+ volume: ((_e = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _e === void 0 ? void 0 : _e.muted)
266
+ ? 0
267
+ : ((_g = (_f = this.videoClipPlayers[this.activeClip.path].videoElement) === null || _f === void 0 ? void 0 : _f.volume) !== null && _g !== void 0 ? _g : 0),
268
+ }
269
+ : undefined,
270
+ };
271
+ this.dispatchEvent('state', VideoState);
272
+ }
273
+ notifyClipStateListeners(playId, file, status) {
274
+ this.dispatchEvent('videoClipState', { playId, mediaType: 'video', file, status });
275
+ }
276
+ // Type-safe wrapper around EventTarget
277
+ addEventListener(type, listener, options) {
278
+ this.eventTarget.addEventListener(type, listener, options);
279
+ }
280
+ removeEventListener(type, listener, options) {
281
+ this.eventTarget.removeEventListener(type, listener, options);
282
+ }
283
+ dispatchEvent(type, detail) {
284
+ this.eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
285
+ }
286
+ createVideoElement(path, config, { volume }) {
287
+ const videoElement = document.createElement('video');
288
+ videoElement.playsInline = true; // Required for iOS
289
+ videoElement.src = this.cogsConnection.getAssetUrl(path);
290
+ videoElement.autoplay = false;
291
+ videoElement.loop = false;
292
+ setVideoElementVolume(videoElement, volume * this.globalVolume);
293
+ videoElement.preload = config.preload;
294
+ videoElement.addEventListener('playing', () => {
295
+ var _a, _b;
296
+ // If the clip is still the pending one when it actually start playing, then ensure it is in the correct state
297
+ if (((_a = this.pendingClip) === null || _a === void 0 ? void 0 : _a.path) === path) {
298
+ switch (this.pendingClip.actionOncePlaying) {
299
+ case 'play': {
300
+ // Continue playing, show the video element, and notify listeners
301
+ videoElement.style.display = 'block';
302
+ this.notifyClipStateListeners(this.pendingClip.playId, path, 'playing');
303
+ break;
304
+ }
305
+ case 'pause': {
306
+ // Pause playback, show the video element, and notify listeners
307
+ videoElement.style.display = 'block';
308
+ videoElement.pause();
309
+ this.notifyClipStateListeners(this.pendingClip.playId, path, 'paused');
310
+ break;
311
+ }
312
+ case 'stop': {
313
+ // Pause playback, leave the video element hidden, and notify listeners
314
+ videoElement.pause();
315
+ this.notifyClipStateListeners(this.pendingClip.playId, path, 'stopped');
316
+ break;
317
+ }
318
+ }
319
+ // If there was a previously active clip, then stop it
320
+ if (this.activeClip) {
321
+ this.handleStoppedClip(this.activeClip.path);
322
+ }
323
+ this.activeClip = this.pendingClip;
324
+ this.pendingClip = undefined;
325
+ }
326
+ else if (((_b = this.activeClip) === null || _b === void 0 ? void 0 : _b.path) === path) {
327
+ // If we were the active clip then just notify listeners that we are now playing
328
+ this.notifyClipStateListeners(this.activeClip.playId, path, 'playing');
329
+ }
330
+ else {
331
+ // Otherwise it shouldn't be playing, like because another clip became pending before we loaded,
332
+ // so we pause and don't show or notify listeners
333
+ videoElement.pause();
334
+ }
335
+ });
336
+ videoElement.addEventListener('ended', () => {
337
+ // Ignore if there's a pending clip, as once that starts playing the active clip will be stopped
338
+ // Also ignore if the video is set to loop
339
+ if (!this.pendingClip && !videoElement.loop) {
340
+ this.handleStoppedClip(path);
341
+ }
342
+ });
343
+ videoElement.style.position = 'absolute';
344
+ videoElement.style.top = '0';
345
+ videoElement.style.left = '0';
346
+ videoElement.style.width = '100%';
347
+ videoElement.style.height = '100%';
348
+ videoElement.style.objectFit = config.fit;
349
+ videoElement.style.display = 'none';
350
+ this.parentElement.appendChild(videoElement);
351
+ return videoElement;
352
+ }
353
+ createClipPlayer(path, config) {
354
+ const volume = 1;
355
+ const player = {
356
+ config,
357
+ videoElement: this.createVideoElement(path, config, { volume }),
358
+ volume,
359
+ };
360
+ setPlayerSinkId(player, this.sinkId);
361
+ return player;
362
+ }
363
+ unloadClip(path) {
364
+ var _a, _b;
365
+ if (((_a = this.activeClip) === null || _a === void 0 ? void 0 : _a.path) === path) {
366
+ const playId = this.activeClip.playId;
367
+ this.activeClip = undefined;
368
+ this.notifyClipStateListeners(playId, path, 'stopped');
369
+ }
370
+ (_b = this.videoClipPlayers[path]) === null || _b === void 0 ? void 0 : _b.videoElement.remove();
371
+ this.updateVideoClipPlayer(path, () => null);
372
+ }
373
+ }
374
+ exports.default = VideoPlayer;
375
+ function preloadString(preload) {
376
+ return typeof preload === 'string' ? preload : preload ? 'auto' : 'none';
377
+ }
378
+ function setPlayerSinkId(player, sinkId) {
379
+ if (sinkId === undefined) {
380
+ return;
381
+ }
382
+ if (typeof player.videoElement.setSinkId === 'function') {
383
+ player.videoElement.setSinkId(sinkId);
384
+ }
385
+ }
386
+ /**
387
+ * Set video volume
388
+ *
389
+ * This doesn't work on iOS (volume is read-only) so at least mute it if the volume is zero
390
+ */
391
+ function setVideoElementVolume(videoElement, volume) {
392
+ videoElement.volume = volume;
393
+ videoElement.muted = volume === 0;
394
+ }
@@ -2921,7 +2921,7 @@ const Bu = (e, t) => {
2921
2921
  ...t.checks ?? [],
2922
2922
  ...r.map((n) => typeof n == "function" ? { _zod: { check: n, def: { check: "custom" }, onattach: [] } } : n)
2923
2923
  ]
2924
- })), e.clone = (r, n) => $e(e, r, n), e.brand = () => e, e.register = (r, n) => (r.add(e, n), e), e.parse = (r, n) => Fu(e, r, n, { callee: e.parse }), e.safeParse = (r, n) => Gu(e, r, n), e.parseAsync = async (r, n) => qu(e, r, n, { callee: e.parseAsync }), e.safeParseAsync = async (r, n) => Vu(e, r, n), e.spa = e.safeParseAsync, e.encode = (r, n) => Wu(e, r, n), e.decode = (r, n) => Hu(e, r, n), e.encodeAsync = async (r, n) => Xu(e, r, n), e.decodeAsync = async (r, n) => Ku(e, r, n), e.safeEncode = (r, n) => Yu(e, r, n), e.safeDecode = (r, n) => Ju(e, r, n), e.safeEncodeAsync = async (r, n) => Qu(e, r, n), e.safeDecodeAsync = async (r, n) => ec(e, r, n), e.refine = (r, n) => e.check(Wc(r, n)), e.superRefine = (r) => e.check(Hc(r)), e.overwrite = (r) => e.check(Ze(r)), e.optional = () => Yr(e), e.nullable = () => Jr(e), e.nullish = () => Yr(Jr(e)), e.nonoptional = (r) => jc(e, r), e.array = () => Ee(e), e.or = (r) => de([e, r]), e.and = (r) => Ac(e, r), e.transform = (r) => Qr(e, Cc(r)), e.default = (r) => Zc(e, r), e.prefault = (r) => xc(e, r), e.catch = (r) => Uc(e, r), e.pipe = (r) => Qr(e, r), e.readonly = () => qc(e), e.describe = (r) => {
2924
+ })), e.clone = (r, n) => $e(e, r, n), e.brand = () => e, e.register = (r, n) => (r.add(e, n), e), e.parse = (r, n) => Fu(e, r, n, { callee: e.parse }), e.safeParse = (r, n) => Gu(e, r, n), e.parseAsync = async (r, n) => qu(e, r, n, { callee: e.parseAsync }), e.safeParseAsync = async (r, n) => Vu(e, r, n), e.spa = e.safeParseAsync, e.encode = (r, n) => Wu(e, r, n), e.decode = (r, n) => Hu(e, r, n), e.encodeAsync = async (r, n) => Xu(e, r, n), e.decodeAsync = async (r, n) => Ku(e, r, n), e.safeEncode = (r, n) => Yu(e, r, n), e.safeDecode = (r, n) => Ju(e, r, n), e.safeEncodeAsync = async (r, n) => Qu(e, r, n), e.safeDecodeAsync = async (r, n) => ec(e, r, n), e.refine = (r, n) => e.check(Wc(r, n)), e.superRefine = (r) => e.check(Hc(r)), e.overwrite = (r) => e.check(Ze(r)), e.optional = () => Yr(e), e.nullable = () => Jr(e), e.nullish = () => Yr(Jr(e)), e.nonoptional = (r) => jc(e, r), e.array = () => ve(e), e.or = (r) => de([e, r]), e.and = (r) => Ac(e, r), e.transform = (r) => Qr(e, Cc(r)), e.default = (r) => Zc(e, r), e.prefault = (r) => xc(e, r), e.catch = (r) => Uc(e, r), e.pipe = (r) => Qr(e, r), e.readonly = () => qc(e), e.describe = (r) => {
2925
2925
  const n = e.clone();
2926
2926
  return Ve.add(n, { description: r }), n;
2927
2927
  }, Object.defineProperty(e, "description", {
@@ -3028,7 +3028,7 @@ function zs(e) {
3028
3028
  const Ic = /* @__PURE__ */ g("ZodArray", (e, t) => {
3029
3029
  va.init(e, t), H.init(e, t), e.element = t.element, e.min = (r, n) => e.check(Qe(r, n)), e.nonempty = (r) => e.check(Qe(1, r)), e.max = (r, n) => e.check(ks(r, n)), e.length = (r, n) => e.check(Ps(r, n)), e.unwrap = () => e.element;
3030
3030
  });
3031
- function Ee(e, t) {
3031
+ function ve(e, t) {
3032
3032
  return ku(Ic, e, t);
3033
3033
  }
3034
3034
  const Zs = /* @__PURE__ */ g("ZodObject", (e, t) => {
@@ -4339,10 +4339,10 @@ function Hs() {
4339
4339
  "64:ff9b:1::/48": "NAT64 (local-use)"
4340
4340
  }, K.RE_BAD_CHARACTERS = /([^0-9a-f:/%])/gi, K.RE_BAD_ADDRESS = /([0-9a-f]{5,}|:{3,}|[^:]:$|^:[^:]|\/$)/gi, K.RE_SUBNET_STRING = /\/\d{1,3}(?=%|$)/, K.RE_ZONE_STRING = /%.*$/, K.RE_URL = /^\[{0,1}([0-9a-f:]+)\]{0,1}/, K.RE_URL_WITH_PORT = /\[([0-9a-f:]+)\]:([0-9]{1,5})/), K;
4341
4341
  }
4342
- var ve = {}, hn;
4342
+ var Ee = {}, hn;
4343
4343
  function Xs() {
4344
- if (hn) return ve;
4345
- hn = 1, Object.defineProperty(ve, "__esModule", { value: !0 }), ve.escapeHtml = e, ve.spanAllZeroes = t, ve.spanAll = r, ve.spanLeadingZeroes = s, ve.simpleGroup = i;
4344
+ if (hn) return Ee;
4345
+ hn = 1, Object.defineProperty(Ee, "__esModule", { value: !0 }), Ee.escapeHtml = e, Ee.spanAllZeroes = t, Ee.spanAll = r, Ee.spanLeadingZeroes = s, Ee.simpleGroup = i;
4346
4346
  function e(o) {
4347
4347
  return o.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4348
4348
  }
@@ -4361,7 +4361,7 @@ function Xs() {
4361
4361
  function i(o, c = 0) {
4362
4362
  return o.split(":").map((l, a) => /group-v4/.test(l) ? l : `<span class="hover-group group-${a + c}">${n(l)}</span>`);
4363
4363
  }
4364
- return ve;
4364
+ return Ee;
4365
4365
  }
4366
4366
  var ne = {}, dn;
4367
4367
  function wl() {
@@ -5326,9 +5326,10 @@ class Il {
5326
5326
  }
5327
5327
  /** Checks whether `host` and `port` are matched by this pattern. */
5328
5328
  validate(t, r) {
5329
- return this.matchesHost(t) && (this.port === "*" || Number(this.port) === r);
5329
+ return this.validateHostname(t) && (this.port === "*" || Number(this.port) === r);
5330
5330
  }
5331
- matchesHost(t) {
5331
+ /** Checks whether `host` is matched by the hostname part of this pattern, ignoring the port. */
5332
+ validateHostname(t) {
5332
5333
  const r = this.host.toLowerCase();
5333
5334
  return t = t.toLowerCase(), r === "*" ? !0 : r === "localhost" ? t === "localhost" || Rl(t) : r === "private" ? yl(t) : r.startsWith("*.") ? t.endsWith(r.slice(1)) : r === t;
5334
5335
  }
@@ -6409,7 +6410,7 @@ function Jl() {
6409
6410
  }, sr;
6410
6411
  }
6411
6412
  var Ql = Jl();
6412
- const fr = /* @__PURE__ */ bl(Ql), us = Ee(ee().min(1)).refine((e) => new Set(e).size === e.length, {
6413
+ const fr = /* @__PURE__ */ bl(Ql), us = ve(ee().min(1)).refine((e) => new Set(e).size === e.length, {
6413
6414
  message: "Array items must be unique"
6414
6415
  });
6415
6416
  function ei(e) {
@@ -6485,12 +6486,12 @@ function ei(e) {
6485
6486
  height: te().gt(0).int(),
6486
6487
  visible: Pe().optional()
6487
6488
  }).optional(),
6488
- config: Ee(h).optional(),
6489
+ config: ve(h).optional(),
6489
6490
  events: q({
6490
- fromCogs: Ee(p).optional(),
6491
- toCogs: Ee(p).optional()
6491
+ fromCogs: ve(p).optional(),
6492
+ toCogs: ve(p).optional()
6492
6493
  }).optional(),
6493
- state: Ee(I).optional(),
6494
+ state: ve(I).optional(),
6494
6495
  media: q({
6495
6496
  audio: G(!0).optional(),
6496
6497
  video: G(!0).optional(),
@@ -6506,10 +6507,10 @@ function ei(e) {
6506
6507
  }).optional(),
6507
6508
  permissions: ur({
6508
6509
  network: ur({
6509
- access: Ee(
6510
+ access: ve(
6510
6511
  e({
6511
- hosts: Ee(ee().refine(v, { error: "Invalid network host pattern" })).min(1),
6512
- caCertificate: ee().optional()
6512
+ hosts: ve(ee().refine(v, { error: "Invalid network host pattern" })).min(1),
6513
+ caCertificates: ve(ee().min(1)).optional()
6513
6514
  })
6514
6515
  ).optional()
6515
6516
  }).optional()