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