@zaflun/lumio-protocol 0.2.4 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,11 @@
1
+ import {
2
+ EXTENSION_RUNTIME_LABEL_PLACEHOLDER,
3
+ extensionRuntimeLabel,
4
+ extensionRuntimeOrigin,
5
+ extensionRuntimeOriginLabel,
6
+ isValidExtensionRuntimeOriginTemplate
7
+ } from "./chunk-746VSR4E.js";
8
+
1
9
  // src/messages.ts
2
10
  import { z } from "zod/v4";
3
11
  var themeColorsSchema = z.object({
@@ -29,7 +37,7 @@ var extensionConfigSchema = z.object({
29
37
  extension_id: z.string(),
30
38
  install_id: z.string(),
31
39
  account_id: z.string(),
32
- target: z.enum(["editor", "layer", "interactive"]),
40
+ target: z.enum(["editor", "layer"]),
33
41
  permissions: z.array(z.string()),
34
42
  settings: z.record(z.string(), z.unknown())
35
43
  });
@@ -112,11 +120,18 @@ var LUMIO_EVENT_TYPES = [
112
120
  // Twitch
113
121
  "twitch:follower",
114
122
  "twitch:subscribe",
115
- "twitch:gift_sub",
123
+ "twitch:gift",
116
124
  "twitch:resub",
117
125
  "twitch:cheer",
118
126
  "twitch:raid",
119
127
  "twitch:reward",
128
+ // Redemption status change (fulfilled / canceled) and reward lifecycle
129
+ // (created / updated / deleted on Twitch) — ZAF-2393. The lifecycle types are
130
+ // live-only (`broadcast_only`), never in the event history.
131
+ "twitch:reward_status",
132
+ "twitch:reward_add",
133
+ "twitch:reward_update",
134
+ "twitch:reward_remove",
120
135
  "twitch:hype_train",
121
136
  "twitch:hype_train_end",
122
137
  "twitch:poll",
@@ -140,15 +155,22 @@ var LUMIO_EVENT_TYPES = [
140
155
  "youtube:gift_membership",
141
156
  "youtube:gift_membership_received",
142
157
  "youtube:poll",
143
- // Kick
158
+ // YouTube stream lifecycle (ZAF-1444 / ZAF-622 §75) — emitted by the poll-driven
159
+ // YouTube worker on the channel-status online/offline edge, mirroring Twitch/Kick.
160
+ "youtube:stream_online",
161
+ "youtube:stream_offline",
162
+ // Kick — the webhook receiver normalizes every Kick payload into these types
163
+ // (ZAF-1624): canonical fields on top, the original payload kept in `raw`.
144
164
  "kick:follower",
145
165
  "kick:subscribe",
166
+ "kick:resub",
146
167
  "kick:gift",
168
+ "kick:kicks",
169
+ "kick:reward",
170
+ "kick:reward_status",
147
171
  "kick:stream_online",
148
172
  "kick:stream_offline",
149
- // Trovo
150
- "trovo:subscribe",
151
- "trovo:spell",
173
+ "kick:stream_update",
152
174
  // Spotify
153
175
  "spotify:track",
154
176
  "spotify:play",
@@ -168,7 +190,6 @@ var LUMIO_EVENT_TYPES = [
168
190
  "twitch:chat",
169
191
  "youtube:chat",
170
192
  "kick:chat",
171
- "trovo:chat",
172
193
  // Chat (cross-platform catch-all — fires for ALL platforms)
173
194
  "chat:message"
174
195
  ];
@@ -210,6 +231,648 @@ var STORAGE_SCOPES = [
210
231
  ];
211
232
  var storageScopeSchema = z2.enum(STORAGE_SCOPES);
212
233
 
234
+ // src/event-payloads.ts
235
+ import { z as z3 } from "zod/v4";
236
+ var str = z3.string();
237
+ var num = z3.number();
238
+ var bool = z3.boolean();
239
+ var nullableStr = z3.string().nullable();
240
+ var twitchBroadcaster = {
241
+ broadcaster_user_id: str,
242
+ broadcaster_user_login: str,
243
+ broadcaster_user_name: str
244
+ };
245
+ var twitchUser = {
246
+ user_id: str,
247
+ user_login: str,
248
+ user_name: str
249
+ };
250
+ var avatarUrl = { avatarUrl: str.optional() };
251
+ var twitchBadge = z3.looseObject({ set_id: str, id: str, info: str });
252
+ var twitchChatNotice = {
253
+ ...twitchBroadcaster,
254
+ ...twitchUser,
255
+ ...avatarUrl,
256
+ chatter_user_id: str,
257
+ chatter_user_login: str,
258
+ chatter_user_name: str,
259
+ chatter_is_anonymous: bool,
260
+ color: str,
261
+ badges: z3.array(twitchBadge),
262
+ message: z3.looseObject({ text: str, fragments: z3.array(z3.unknown()) }),
263
+ message_id: str,
264
+ notice_type: str,
265
+ system_message: str,
266
+ /** Emotes of `message`, normalised like chat-message emotes (ZAF-1306). */
267
+ emotes: z3.array(z3.unknown()).optional()
268
+ };
269
+ var twitchImage = z3.looseObject({ url_1x: str, url_2x: str, url_4x: str });
270
+ var twitchEnabledValue = z3.looseObject({ is_enabled: bool, value: num });
271
+ var twitchRewardDefinition = z3.looseObject({
272
+ ...twitchBroadcaster,
273
+ id: str,
274
+ title: str,
275
+ cost: num,
276
+ prompt: str,
277
+ background_color: str,
278
+ is_enabled: bool,
279
+ is_paused: bool,
280
+ is_in_stock: bool,
281
+ is_user_input_required: bool,
282
+ should_redemptions_skip_request_queue: bool,
283
+ max_per_stream: twitchEnabledValue,
284
+ max_per_user_per_stream: twitchEnabledValue,
285
+ global_cooldown: z3.looseObject({ is_enabled: bool, seconds: num }),
286
+ image: twitchImage.nullable(),
287
+ default_image: twitchImage,
288
+ cooldown_expires_at: nullableStr,
289
+ redemptions_redeemed_current_stream: num.nullable()
290
+ });
291
+ var twitchRedemption = z3.looseObject({
292
+ ...twitchBroadcaster,
293
+ ...twitchUser,
294
+ ...avatarUrl,
295
+ id: str,
296
+ user_input: str,
297
+ status: str,
298
+ redeemed_at: str,
299
+ reward: z3.looseObject({ id: str, title: str, cost: num, prompt: str })
300
+ });
301
+ var twitchPollVoting = z3.looseObject({ is_enabled: bool, amount_per_vote: num });
302
+ var twitchPollBase = {
303
+ ...twitchBroadcaster,
304
+ id: str,
305
+ title: str,
306
+ choices: z3.array(
307
+ z3.looseObject({
308
+ id: str,
309
+ title: str,
310
+ votes: num.optional(),
311
+ bits_votes: num.optional(),
312
+ channel_points_votes: num.optional()
313
+ })
314
+ ),
315
+ bits_voting: twitchPollVoting,
316
+ channel_points_voting: twitchPollVoting,
317
+ started_at: str
318
+ };
319
+ var twitchPredictionOutcome = z3.looseObject({
320
+ id: str,
321
+ title: str,
322
+ color: str,
323
+ users: num.optional(),
324
+ channel_points: num.optional(),
325
+ top_predictors: z3.array(
326
+ z3.looseObject({
327
+ ...twitchUser,
328
+ channel_points_used: num,
329
+ channel_points_won: num.nullable()
330
+ })
331
+ ).nullable().optional()
332
+ });
333
+ var twitchPredictionBase = {
334
+ ...twitchBroadcaster,
335
+ id: str,
336
+ title: str,
337
+ outcomes: z3.array(twitchPredictionOutcome),
338
+ started_at: str
339
+ };
340
+ var twitchGoalBase = {
341
+ ...twitchBroadcaster,
342
+ id: str,
343
+ type: str,
344
+ description: str,
345
+ current_amount: num,
346
+ target_amount: num,
347
+ started_at: str
348
+ };
349
+ var twitchHypeTrainBase = {
350
+ ...twitchBroadcaster,
351
+ id: str,
352
+ type: str,
353
+ level: num,
354
+ total: num,
355
+ top_contributions: z3.array(
356
+ z3.looseObject({ ...twitchUser, type: str, total: num })
357
+ ),
358
+ started_at: str,
359
+ is_shared_train: bool,
360
+ shared_train_participants: z3.array(z3.unknown()).nullable()
361
+ };
362
+ var moderationLogEntry = z3.looseObject({
363
+ action: str,
364
+ platform: str,
365
+ target_user_id: nullableStr,
366
+ target_username: nullableStr,
367
+ target_display_name: nullableStr,
368
+ moderator_name: str,
369
+ moderator_login: nullableStr,
370
+ reason: nullableStr,
371
+ duration_secs: num.nullable(),
372
+ ends_at: nullableStr,
373
+ target_message_id: nullableStr,
374
+ created_at: str
375
+ });
376
+ var chatMessagePayloadSchema = z3.looseObject({
377
+ account_id: str,
378
+ platform: str,
379
+ channel_name: nullableStr,
380
+ user_id: nullableStr,
381
+ username: str,
382
+ display_name: nullableStr,
383
+ avatar_url: nullableStr,
384
+ message: str,
385
+ /** Platform-specific emote entries. */
386
+ emotes: z3.array(z3.unknown()).nullable(),
387
+ /** Twitch GIF fragments `{ id, url, provider, alt }`; absent without a GIF. */
388
+ gifs: z3.array(z3.unknown()).optional(),
389
+ /** Platform-specific badge entries. */
390
+ badges: z3.array(z3.unknown()).nullable(),
391
+ color: nullableStr,
392
+ is_mod: bool,
393
+ is_sub: bool,
394
+ is_vip: bool,
395
+ sub_tier: nullableStr,
396
+ platform_message_id: nullableStr,
397
+ lumio_user_id: nullableStr,
398
+ reply_parent_msg_id: nullableStr,
399
+ reply_parent_username: nullableStr,
400
+ reply_parent_login: nullableStr,
401
+ reply_parent_message: nullableStr,
402
+ created_at: nullableStr,
403
+ message_type: nullableStr,
404
+ user_treatment: nullableStr,
405
+ live_chat_id: str.optional(),
406
+ is_shared_chat: bool.optional(),
407
+ source_channel: z3.unknown().optional(),
408
+ announcement_color: z3.unknown().optional()
409
+ });
410
+ var kickUser = z3.looseObject({
411
+ user_id: num,
412
+ username: str,
413
+ channel_slug: str,
414
+ is_verified: bool,
415
+ profile_picture: str,
416
+ is_anonymous: bool.optional(),
417
+ identity: z3.unknown().optional()
418
+ });
419
+ var kickCanonicalUser = {
420
+ user_id: str,
421
+ user_login: str,
422
+ user_name: str,
423
+ is_anonymous: bool
424
+ };
425
+ var kickSubscription = z3.looseObject({
426
+ ...kickCanonicalUser,
427
+ ...avatarUrl,
428
+ broadcaster: kickUser,
429
+ subscriber: kickUser,
430
+ duration: num,
431
+ cumulative_months: num,
432
+ created_at: str,
433
+ expires_at: str
434
+ });
435
+ var kickRedemption = z3.looseObject({
436
+ ...kickCanonicalUser,
437
+ broadcaster: kickUser,
438
+ redeemer: kickUser,
439
+ id: str,
440
+ user_input: str,
441
+ status: str,
442
+ redeemed_at: str,
443
+ reward: z3.looseObject({ id: str, title: str, cost: num })
444
+ });
445
+ var spotifySource = {
446
+ source: str.optional(),
447
+ extension_id: str.optional(),
448
+ install_id: str.optional()
449
+ };
450
+ var spotifyTrackInfo = {
451
+ action: str,
452
+ track_id: str,
453
+ track_name: str,
454
+ artist: str,
455
+ album: str,
456
+ album_art: str,
457
+ song_url: str
458
+ };
459
+ var youtubeAuthor = {
460
+ user_id: str,
461
+ user_login: str,
462
+ displayName: str,
463
+ avatarUrl: str
464
+ };
465
+ var LUMIO_EVENT_PAYLOAD_SCHEMAS = {
466
+ // Twitch
467
+ "twitch:follower": z3.looseObject({
468
+ ...twitchBroadcaster,
469
+ ...twitchUser,
470
+ ...avatarUrl,
471
+ followed_at: str
472
+ }),
473
+ "twitch:subscribe": z3.looseObject({
474
+ ...twitchChatNotice,
475
+ tier: str,
476
+ duration_months: num,
477
+ is_prime: bool.optional(),
478
+ sub: z3.looseObject({ sub_tier: str, is_prime: bool, duration_months: num })
479
+ }),
480
+ "twitch:gift": z3.looseObject({
481
+ ...twitchChatNotice,
482
+ /** Sub tier (`1000`, `2000`, `3000`). */
483
+ tier: str.optional(),
484
+ duration_months: num.optional(),
485
+ /** Gifts in a community gift (`community_sub_gift`). */
486
+ total: num.optional(),
487
+ /** The gifter's all-time gift count; `null` when anonymous. */
488
+ cumulative_total: num.nullable().optional(),
489
+ community_gift_id: nullableStr.optional(),
490
+ /** Set for a single gift (`sub_gift`), absent for a community gift. */
491
+ recipient_user_id: str.optional(),
492
+ recipient_user_login: str.optional(),
493
+ recipient_user_name: str.optional()
494
+ }),
495
+ "twitch:resub": z3.looseObject({
496
+ ...twitchChatNotice,
497
+ cumulative_months: num,
498
+ duration_months: num,
499
+ streak_months: num.nullable(),
500
+ is_gift: bool,
501
+ tier: str.optional(),
502
+ is_prime: bool.optional(),
503
+ gifter_is_anonymous: bool.nullable(),
504
+ gifter_user_login: nullableStr,
505
+ gifter_user_name: nullableStr,
506
+ resub: z3.looseObject({
507
+ cumulative_months: num,
508
+ duration_months: num,
509
+ streak_months: num.nullable(),
510
+ sub_plan: str,
511
+ is_gift: bool
512
+ })
513
+ }),
514
+ "twitch:cheer": z3.looseObject({
515
+ ...twitchBroadcaster,
516
+ ...avatarUrl,
517
+ user_id: nullableStr,
518
+ user_login: nullableStr,
519
+ user_name: nullableStr,
520
+ is_anonymous: bool,
521
+ bits: num,
522
+ message: str,
523
+ /** Cheermote entries for the cheer words in `message` (`kind: "cheermote"`). */
524
+ emotes: z3.array(z3.unknown()).optional(),
525
+ /** The cheermote for the total, when the channel's cheermotes loaded. */
526
+ cheermote: z3.unknown().optional()
527
+ }),
528
+ "twitch:raid": z3.looseObject({
529
+ from_broadcaster_user_id: str,
530
+ from_broadcaster_user_login: str,
531
+ from_broadcaster_user_name: str,
532
+ to_broadcaster_user_id: str,
533
+ to_broadcaster_user_login: str,
534
+ to_broadcaster_user_name: str,
535
+ viewers: num,
536
+ ...avatarUrl,
537
+ displayName: str.optional(),
538
+ /** The raider's channel info from Helix, when it loaded. */
539
+ title: str.optional(),
540
+ category: str.optional()
541
+ }),
542
+ "twitch:reward": twitchRedemption,
543
+ "twitch:reward_status": twitchRedemption,
544
+ "twitch:reward_add": twitchRewardDefinition,
545
+ "twitch:reward_update": twitchRewardDefinition,
546
+ "twitch:reward_remove": twitchRewardDefinition,
547
+ "twitch:hype_train": z3.looseObject({
548
+ ...twitchHypeTrainBase,
549
+ progress: num,
550
+ goal: num,
551
+ expires_at: str,
552
+ all_time_high_level: num.optional(),
553
+ all_time_high_total: num.optional()
554
+ }),
555
+ "twitch:hype_train_end": z3.looseObject({
556
+ ...twitchHypeTrainBase,
557
+ ended_at: str,
558
+ cooldown_ends_at: str
559
+ }),
560
+ "twitch:poll": z3.looseObject({ ...twitchPollBase, ends_at: str }),
561
+ "twitch:poll_end": z3.looseObject({
562
+ ...twitchPollBase,
563
+ status: str,
564
+ ended_at: str
565
+ }),
566
+ "twitch:prediction": z3.looseObject({ ...twitchPredictionBase, locks_at: str }),
567
+ "twitch:prediction_lock": z3.looseObject({
568
+ ...twitchPredictionBase,
569
+ locked_at: str
570
+ }),
571
+ "twitch:prediction_end": z3.looseObject({
572
+ ...twitchPredictionBase,
573
+ status: str,
574
+ ended_at: str,
575
+ winning_outcome_id: nullableStr
576
+ }),
577
+ "twitch:goal": z3.looseObject(twitchGoalBase),
578
+ "twitch:goal_end": z3.looseObject({
579
+ ...twitchGoalBase,
580
+ ended_at: str,
581
+ is_achieved: bool
582
+ }),
583
+ "twitch:ad_break": z3.looseObject({
584
+ ...twitchBroadcaster,
585
+ requester_user_id: str,
586
+ requester_user_login: str,
587
+ requester_user_name: str,
588
+ /** Seconds; Twitch's own sample payload sends it as a string. */
589
+ duration_seconds: z3.union([num, str]),
590
+ /** Twitch's own sample payload sends it as a string. */
591
+ is_automatic: z3.union([bool, str]),
592
+ started_at: str
593
+ }),
594
+ "twitch:ban": moderationLogEntry,
595
+ "twitch:unban": moderationLogEntry,
596
+ "twitch:stream_online": z3.looseObject({
597
+ ...twitchBroadcaster,
598
+ id: str,
599
+ type: str,
600
+ started_at: str,
601
+ /** Channel info from Helix, when it loaded. */
602
+ title: str.optional(),
603
+ category: str.optional()
604
+ }),
605
+ "twitch:stream_offline": z3.looseObject({
606
+ ...twitchBroadcaster,
607
+ /** Set when the stream's start is known. */
608
+ started_at: str.optional(),
609
+ duration_seconds: num.optional()
610
+ }),
611
+ "twitch:stream_update": z3.looseObject({
612
+ ...twitchBroadcaster,
613
+ title: str,
614
+ language: str,
615
+ category_id: str,
616
+ category_name: str,
617
+ content_classification_labels: z3.array(str),
618
+ tags: z3.array(str).optional(),
619
+ /** Diff against the previous channel info, when it was known. */
620
+ title_changed: bool.optional(),
621
+ category_changed: bool.optional(),
622
+ language_changed: bool.optional(),
623
+ tags_changed: bool.optional(),
624
+ old_title: str.optional(),
625
+ old_category_name: str.optional(),
626
+ old_tags: z3.array(str).optional()
627
+ }),
628
+ // YouTube
629
+ "youtube:subscribe": z3.looseObject({
630
+ channel_id: str,
631
+ video_id: str,
632
+ title: str,
633
+ published: str,
634
+ updated: str
635
+ }),
636
+ "youtube:member": z3.looseObject({
637
+ ...youtubeAuthor,
638
+ membership_event_type: str,
639
+ memberLevelName: nullableStr,
640
+ memberMonth: num.nullable(),
641
+ isUpgrade: bool,
642
+ display_message: str,
643
+ userComment: str
644
+ }),
645
+ "youtube:superchat": z3.looseObject({
646
+ ...youtubeAuthor,
647
+ amountMicros: num,
648
+ amountDisplayString: str,
649
+ currency: str,
650
+ tier: num,
651
+ userComment: str,
652
+ headerBackgroundColor: num,
653
+ bodyBackgroundColor: num
654
+ }),
655
+ "youtube:supersticker": z3.looseObject({
656
+ ...youtubeAuthor,
657
+ amountMicros: num,
658
+ amountDisplayString: str,
659
+ currency: str,
660
+ stickerName: str,
661
+ stickerUrl: str
662
+ }),
663
+ "youtube:gift_membership": z3.looseObject({
664
+ ...youtubeAuthor,
665
+ giftCount: num,
666
+ isGift: bool,
667
+ gift_memberships_level_name: nullableStr
668
+ }),
669
+ /** Stored in the event history only; never broadcast live (no fixture). */
670
+ "youtube:gift_membership_received": z3.looseObject({
671
+ user_id: nullableStr,
672
+ user_login: nullableStr,
673
+ displayName: str,
674
+ avatarUrl: nullableStr,
675
+ gifter_channel_id: nullableStr,
676
+ gifter_display_name: nullableStr,
677
+ gift_memberships_level_name: nullableStr
678
+ }),
679
+ "youtube:poll": z3.looseObject({
680
+ poll_id: str,
681
+ question: str,
682
+ status: str,
683
+ options: z3.array(
684
+ z3.looseObject({
685
+ option_text: str,
686
+ tally: nullableStr,
687
+ vote_ratio: num.nullable()
688
+ })
689
+ )
690
+ }),
691
+ "youtube:stream_online": z3.looseObject({
692
+ broadcast_id: str,
693
+ started_at: str,
694
+ title: str.optional(),
695
+ category: str.optional()
696
+ }),
697
+ "youtube:stream_offline": z3.looseObject({
698
+ broadcast_id: str,
699
+ started_at: str.optional(),
700
+ duration_seconds: num.optional()
701
+ }),
702
+ // Kick
703
+ "kick:follower": z3.looseObject({
704
+ ...kickCanonicalUser,
705
+ ...avatarUrl,
706
+ broadcaster: kickUser,
707
+ follower: kickUser
708
+ }),
709
+ "kick:subscribe": kickSubscription,
710
+ "kick:resub": kickSubscription,
711
+ "kick:gift": z3.looseObject({
712
+ ...kickCanonicalUser,
713
+ ...avatarUrl,
714
+ broadcaster: kickUser,
715
+ gifter: kickUser,
716
+ giftees: z3.array(kickUser),
717
+ /** Usernames of `giftees`. */
718
+ giftRecipients: z3.array(str),
719
+ giftCount: num,
720
+ total: num,
721
+ isGift: bool,
722
+ is_gift: bool,
723
+ /** The first giftee. */
724
+ recipient_user_id: str.optional(),
725
+ recipient_user_login: str.optional(),
726
+ recipient_user_name: str.optional(),
727
+ created_at: str,
728
+ expires_at: str
729
+ }),
730
+ "kick:kicks": z3.looseObject({
731
+ ...kickCanonicalUser,
732
+ ...avatarUrl,
733
+ broadcaster: kickUser,
734
+ sender: kickUser,
735
+ gift: z3.looseObject({
736
+ amount: num,
737
+ name: str,
738
+ type: str,
739
+ tier: str,
740
+ message: str,
741
+ pinned_time_seconds: num
742
+ }),
743
+ kicks: num,
744
+ message: str,
745
+ kick_gift_id: str,
746
+ kick_gift_name: str,
747
+ kick_gift_type: str,
748
+ kick_gift_tier: str,
749
+ kick_gift_color: str,
750
+ kick_gift_image: z3.looseObject({ static: str, animated: str }),
751
+ kick_pinned_time_seconds: num,
752
+ created_at: str
753
+ }),
754
+ "kick:reward": kickRedemption,
755
+ "kick:reward_status": kickRedemption,
756
+ "kick:stream_online": z3.looseObject({
757
+ broadcaster: kickUser,
758
+ is_live: bool,
759
+ title: str,
760
+ started_at: str,
761
+ ended_at: nullableStr
762
+ }),
763
+ "kick:stream_offline": z3.looseObject({
764
+ broadcaster: kickUser,
765
+ is_live: bool,
766
+ title: str,
767
+ started_at: str,
768
+ ended_at: str,
769
+ duration_seconds: num.optional()
770
+ }),
771
+ "kick:stream_update": z3.looseObject({
772
+ broadcaster: kickUser,
773
+ metadata: z3.looseObject({
774
+ title: str,
775
+ language: str,
776
+ has_mature_content: bool,
777
+ category: z3.looseObject({ id: num, name: str, thumbnail: str })
778
+ }),
779
+ title: str,
780
+ language: str,
781
+ has_mature_content: bool,
782
+ category_id: str,
783
+ category_name: str,
784
+ /** Diff against the previous stream info, when it was known. */
785
+ title_changed: bool.optional(),
786
+ category_changed: bool.optional(),
787
+ language_changed: bool.optional(),
788
+ old_title: str.optional()
789
+ }),
790
+ // Spotify
791
+ "spotify:track": z3.looseObject({
792
+ is_playing: bool,
793
+ progress_ms: num,
794
+ timestamp: num,
795
+ shuffle: str,
796
+ repeat: str,
797
+ track: z3.looseObject({
798
+ id: str,
799
+ uri: str,
800
+ name: str,
801
+ artist: str,
802
+ album: str,
803
+ album_art: str,
804
+ duration_ms: num
805
+ }),
806
+ device: z3.looseObject({
807
+ id: str,
808
+ name: str,
809
+ device_type: str,
810
+ is_active: bool,
811
+ volume_percent: num.nullable()
812
+ }).nullable(),
813
+ context: z3.looseObject({ context_type: str, id: str, name: nullableStr, uri: str }).nullable(),
814
+ /** The Lumio user whose action caused the change; `null` when it came from Spotify. */
815
+ triggered_by: nullableStr.optional()
816
+ }),
817
+ "spotify:play": z3.looseObject({
818
+ ...spotifyTrackInfo,
819
+ ...spotifySource,
820
+ track_uri: str
821
+ }),
822
+ "spotify:pause": z3.looseObject({ ...spotifyTrackInfo, ...spotifySource }),
823
+ "spotify:skip": z3.looseObject({ action: str, ...spotifySource }),
824
+ "spotify:volume": z3.looseObject({
825
+ ...spotifyTrackInfo,
826
+ ...spotifySource,
827
+ oldVolume: num,
828
+ newVolume: num,
829
+ volume_percent: num
830
+ }),
831
+ "spotify:queue_add": z3.looseObject({ uri: str, ...spotifySource }),
832
+ "spotify:device": z3.looseObject({
833
+ fromDevice: str,
834
+ fromDeviceType: str,
835
+ toDevice: str,
836
+ toDeviceType: str
837
+ }),
838
+ "spotify:playlist_add": z3.looseObject({ playlistId: str }),
839
+ "spotify:playlist_remove": z3.looseObject({ playlistId: str, trackUri: str }),
840
+ "spotify:playlist_create": z3.looseObject({ playlistName: str }),
841
+ "spotify:playlist_edit": z3.looseObject({ playlistName: str }),
842
+ "spotify:playlist_delete": z3.looseObject({ playlistId: str }),
843
+ // StreamElements
844
+ "streamelements:tip": z3.looseObject({
845
+ _id: str,
846
+ channel: str,
847
+ provider: str,
848
+ type: str,
849
+ flagged: bool,
850
+ createdAt: str,
851
+ updatedAt: str,
852
+ data: z3.looseObject({
853
+ tipId: str,
854
+ username: str,
855
+ displayName: str,
856
+ providerId: str.optional(),
857
+ amount: num,
858
+ currency: str,
859
+ message: str,
860
+ avatar: str.optional()
861
+ })
862
+ }),
863
+ // Chat
864
+ "twitch:chat": chatMessagePayloadSchema,
865
+ "youtube:chat": chatMessagePayloadSchema,
866
+ "kick:chat": chatMessagePayloadSchema,
867
+ "chat:message": chatMessagePayloadSchema
868
+ };
869
+ function lumioEventPayloadSchema(type) {
870
+ return LUMIO_EVENT_PAYLOAD_SCHEMAS[type];
871
+ }
872
+ function parseLumioEventPayload(type, data) {
873
+ return LUMIO_EVENT_PAYLOAD_SCHEMAS[type].safeParse(data);
874
+ }
875
+
213
876
  // src/components.ts
214
877
  var LUMIO_COMPONENT_TYPES = [
215
878
  "Box",
@@ -243,6 +906,34 @@ var LUMIO_COMPONENT_TYPES = [
243
906
  "EditorFontPicker",
244
907
  "EditorChannelRow"
245
908
  ];
909
+ var ACTION_EXECUTE_ERRORS = {
910
+ /** Malformed request: `action` is not a string or `params` is not an object. */
911
+ INVALID_REQUEST: -32602,
912
+ /** This host cannot run actions (no install, or a credential that may not run them). */
913
+ UNAVAILABLE: -32001,
914
+ /** The extension does not declare the action's permission, or the caller may not use it. */
915
+ DENIED: -32002,
916
+ /** The API does not dispatch this action. */
917
+ UNKNOWN_ACTION: -32003,
918
+ /** The API rejected the action's params. */
919
+ INVALID_PARAMS: -32004,
920
+ /** The action ran and failed (e.g. its target service is not connected). */
921
+ FAILED: -32005
922
+ };
923
+ var SERVER_FUNCTION_ERRORS = {
924
+ /** Malformed request: `fn` is not a function name or `params` is not an object. */
925
+ INVALID_REQUEST: -32602,
926
+ /** This host cannot call server functions (no install, or a credential that may not call them). */
927
+ UNAVAILABLE: -32001,
928
+ /** The caller may not run the function (an `editorOnly` function outside the editor, or missing permission). */
929
+ DENIED: -32002,
930
+ /** The install's published version has no server function with this name, or not of this kind. */
931
+ UNKNOWN_FUNCTION: -32003,
932
+ /** The API rejected the params (missing or mistyped `arg()` value, invalid row id or column value). */
933
+ INVALID_PARAMS: -32004,
934
+ /** The function ran and failed (rate limit, no data schema, storage quota, timeout). */
935
+ FAILED: -32005
936
+ };
246
937
  var WORKER_METHODS = {
247
938
  UI_RENDER: "ui.render",
248
939
  UI_READY: "ui.ready",
@@ -255,7 +946,8 @@ var WORKER_METHODS = {
255
946
  UI_ACTION: "ui.action",
256
947
  SOUND_PLAY: "sound.play",
257
948
  SOUND_STOP: "sound.stop",
258
- SOUND_LIST: "sound.list"
949
+ SOUND_LIST: "sound.list",
950
+ ACTION_EXECUTE: "action.execute"
259
951
  };
260
952
  var HOST_METHODS = {
261
953
  LIFECYCLE_INIT: "lifecycle.init",
@@ -264,27 +956,55 @@ var HOST_METHODS = {
264
956
  EVENT_PLATFORM: "event.platform",
265
957
  STORAGE_UPDATE: "storage.update",
266
958
  THEME_CHANGE: "theme.change",
267
- CONFIG_UPDATE: "config.update"
959
+ CONFIG_UPDATE: "config.update",
960
+ SERVER_REALTIME: "server.realtime"
961
+ };
962
+ var REALTIME_LIMITS = {
963
+ /** 1–64 characters of `A-Z a-z 0-9 _ . : -`, starting with a letter or digit. */
964
+ TOPIC_PATTERN: /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,63}$/,
965
+ /** Largest serialized payload of one push, in bytes. */
966
+ MAX_PAYLOAD_BYTES: 16 * 1024,
967
+ /** Most pushes one handler run may make. */
968
+ MAX_PUSHES_PER_INVOCATION: 32,
969
+ /** Pushes per install and minute; pushes over the cap are dropped. */
970
+ MAX_PUSHES_PER_MINUTE: 300,
971
+ /**
972
+ * Published bytes (serialized `{ topic, payload }`) per install and minute;
973
+ * pushes over the cap are dropped.
974
+ */
975
+ MAX_BYTES_PER_MINUTE: 1024 * 1024
268
976
  };
269
977
  export {
978
+ ACTION_EXECUTE_ERRORS,
270
979
  ACTION_TYPES,
980
+ EXTENSION_RUNTIME_LABEL_PLACEHOLDER,
271
981
  HOST_METHODS,
272
982
  LUMIO_COMPONENT_TYPES,
983
+ LUMIO_EVENT_PAYLOAD_SCHEMAS,
273
984
  LUMIO_EVENT_TYPES,
274
985
  MESSAGE_TYPES,
986
+ REALTIME_LIMITS,
987
+ SERVER_FUNCTION_ERRORS,
275
988
  STORAGE_SCOPES,
276
989
  WORKER_METHODS,
277
990
  actionMessageSchema,
991
+ chatMessagePayloadSchema,
278
992
  configUpdateMessageSchema,
279
993
  errorMessageSchema,
280
994
  eventMessageSchema,
281
995
  extensionConfigSchema,
996
+ extensionRuntimeLabel,
997
+ extensionRuntimeOrigin,
998
+ extensionRuntimeOriginLabel,
282
999
  initMessageSchema,
1000
+ isValidExtensionRuntimeOriginTemplate,
283
1001
  lumioActionTypeSchema,
1002
+ lumioEventPayloadSchema,
284
1003
  lumioEventTypeSchema,
285
1004
  lumioMessageSchema,
286
1005
  nodeConfigRequestMessageSchema,
287
1006
  nodeConfigUpdateMessageSchema,
1007
+ parseLumioEventPayload,
288
1008
  readyMessageSchema,
289
1009
  storageScopeSchema,
290
1010
  storageUpdateMessageSchema,