@zaflun/lumio-protocol 0.2.3 → 0.6.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/CHANGELOG.md ADDED
@@ -0,0 +1,98 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@zaflun/lumio-protocol` are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## 0.6.0
9
+
10
+ ### Added
11
+
12
+ - **Per-extension runtime origins** — new entry point
13
+ `@zaflun/lumio-protocol/origin` (also re-exported from the package root) with
14
+ `extensionRuntimeLabel(extensionId)` (canonical lowercase UUID → 32 lowercase
15
+ hex, otherwise `null`), `extensionRuntimeOrigin(extensionId, template)`
16
+ (`https://{label}.runtime.ext.lumio.vision` → the extension's origin, `null` for an
17
+ invalid id or template), `extensionRuntimeOriginLabel(origin, template)` and
18
+ `isValidExtensionRuntimeOriginTemplate(template)`. The `/origin` entry has no
19
+ runtime dependencies. Additive.
20
+
21
+ ## 0.5.0
22
+
23
+ ### Added
24
+
25
+ - **Kick event types** — `kick:resub`, `kick:kicks`, `kick:reward`,
26
+ `kick:reward_status` and `kick:stream_update` are in `LUMIO_EVENT_TYPES`,
27
+ `LumioEventType` and the `lumioEventTypeSchema` Zod enum. The Lumio webhook
28
+ receiver emits them for Kick subscription renewals, KICKs gifts, channel-point
29
+ redemptions (first delivery of a redemption `id` → `kick:reward`, every later
30
+ status change → `kick:reward_status`) and livestream metadata changes. Every
31
+ Kick event carries canonical top-level fields (`user_id`, `user_login`,
32
+ `user_name`, `avatarUrl`, `is_anonymous`) next to the original Kick payload.
33
+ `LUMIO_EVENT_TYPES` now holds 58 event types.
34
+
35
+ Additive: existing event strings are unchanged. Consumers that exhaustively
36
+ `switch` over `LumioEventType` need arms for the five new Kick types.
37
+
38
+ ## 0.4.0
39
+
40
+ ### Added
41
+
42
+ - **YouTube stream lifecycle** — `youtube:stream_online` and
43
+ `youtube:stream_offline`, emitted by the poll-driven YouTube worker on the
44
+ channel-status online/offline edge (once per real transition).
45
+
46
+ ## 0.3.0
47
+
48
+ ### Removed
49
+
50
+ - **Trovo event types** — `trovo:subscribe`, `trovo:spell` and `trovo:chat` are
51
+ gone from `LUMIO_EVENT_TYPES`, `LumioEventType` and the `lumioEventTypeSchema`
52
+ Zod enum. The Trovo platform has been shut down by its operator, so these
53
+ events can no longer be emitted. `LUMIO_EVENT_TYPES` now holds 72 event types
54
+ across Twitch, YouTube, Kick, Spotify, StreamElements, and per-platform +
55
+ cross-platform chat.
56
+
57
+ **Breaking for consumers** that reference a Trovo event type by name or
58
+ exhaustively `switch` over `LumioEventType`. Drop the Trovo arms; validation of
59
+ a Trovo event string now fails closed via `lumioEventTypeSchema`.
60
+
61
+ ## 0.2.4
62
+
63
+ Wire-format types for the Lumio Extension Platform — the contract for
64
+ communication between extension iframes/workers and the Lumio host. Ships as an
65
+ ESM build with generated `.d.ts` declarations; `zod` is a runtime dependency for
66
+ the validated schemas.
67
+
68
+ ### Exports
69
+
70
+ - **Events** (`src/events.ts`)
71
+ - `LUMIO_EVENT_TYPES` — 75 platform event types across Twitch, YouTube, Kick,
72
+ Trovo, Spotify, StreamElements, and per-platform + cross-platform chat, with
73
+ `LumioEventType` and the `lumioEventTypeSchema` Zod enum.
74
+ - `ACTION_TYPES` — 21 action types (Chat, OBS, Spotify, Events, Overlay), with
75
+ `LumioActionType` and `lumioActionTypeSchema`.
76
+ - Sound-system event payloads `SoundPlayEvent` / `SoundStopEvent`.
77
+ - `STORAGE_SCOPES` (`instance`, `overlay`, `account`, `global`) with
78
+ `StorageScope` and `storageScopeSchema`.
79
+ - **Messages** (`src/messages.ts`)
80
+ - Theme schemas (`themeSchema`, colors/fonts/border-radius) and
81
+ `extensionConfigSchema`.
82
+ - `MESSAGE_TYPES` — 10 host↔iframe message types, individual per-type Zod
83
+ schemas, and the `lumioMessageSchema` discriminated union with `LumioMessage`.
84
+ - **Components** (`src/components.ts`)
85
+ - `LUMIO_COMPONENT_TYPES` — 30 serialized-UI component types with
86
+ `LumioComponentType` and the `LumioComponentNode` tree interface.
87
+ - JSON-RPC 2.0 wire types (`JsonRpcRequest` / `JsonRpcResponse` /
88
+ `JsonRpcNotification`), worker↔host param interfaces, the `Sound` data type,
89
+ and the `WORKER_METHODS` / `HOST_METHODS` method-name constants (including
90
+ `sound.play` / `sound.stop` / `sound.list`).
91
+
92
+ ### Notes
93
+
94
+ - Adds the sound-system surface (`SoundPlayEvent` / `SoundStopEvent`, `Sound`,
95
+ and the `SOUND_PLAY` / `SOUND_STOP` / `SOUND_LIST` worker methods) that mirrors
96
+ the host sound-management contract.
97
+ - First release published with a changelog. Prior public releases: `0.2.3`,
98
+ `0.1.1`, `0.1.0`.
package/README.md CHANGED
@@ -10,10 +10,10 @@ npm install @zaflun/lumio-protocol
10
10
 
11
11
  ## What's included
12
12
 
13
- - **49 Event Types** — Twitch, YouTube, Kick, Trovo, Spotify, StreamElements, Chat
13
+ - **54 Event Types** — Twitch, YouTube, Kick, Spotify, StreamElements, Chat
14
14
  - **21 Action Types** — Chat, OBS, Spotify, Events, Overlay
15
15
  - **Message Schemas** — JSON-RPC 2.0 wire format (Zod validated)
16
- - **Component Types** — `LumioComponentNode` for serialized UI trees
16
+ - **30 Component Types** — `LumioComponentNode` for serialized UI trees
17
17
 
18
18
  ## Usage
19
19
 
@@ -0,0 +1,51 @@
1
+ // src/origin.ts
2
+ var EXTENSION_RUNTIME_LABEL_PLACEHOLDER = "{label}";
3
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
4
+ var LABEL_PATTERN = /^[0-9a-f]{32}$/;
5
+ var DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
6
+ var TEMPLATE_PATTERN = /^(https:\/\/|http:\/\/)\{label\}(\.[a-z0-9.-]+(?::[0-9]{1,5})?)$/;
7
+ function parseTemplate(template) {
8
+ const match = TEMPLATE_PATTERN.exec(template);
9
+ if (!match) return null;
10
+ const prefix = match[1];
11
+ const suffix = match[2];
12
+ const host = suffix.slice(1).replace(/:[0-9]+$/, "");
13
+ if (prefix === "http://" && host !== "localhost") return null;
14
+ if (!host.split(".").every((label) => DNS_LABEL_PATTERN.test(label))) return null;
15
+ const sample = `${prefix}${"0".repeat(32)}${suffix}`;
16
+ try {
17
+ if (new URL(sample).origin !== sample) return null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ return { prefix, suffix };
22
+ }
23
+ function extensionRuntimeLabel(extensionId) {
24
+ if (typeof extensionId !== "string" || !UUID_PATTERN.test(extensionId)) return null;
25
+ return extensionId.replaceAll("-", "");
26
+ }
27
+ function extensionRuntimeOrigin(extensionId, template) {
28
+ const label = extensionRuntimeLabel(extensionId);
29
+ if (!label) return null;
30
+ const parsed = parseTemplate(template);
31
+ if (!parsed) return null;
32
+ return `${parsed.prefix}${label}${parsed.suffix}`;
33
+ }
34
+ function extensionRuntimeOriginLabel(origin, template) {
35
+ const parsed = parseTemplate(template);
36
+ if (!parsed) return null;
37
+ if (!origin.startsWith(parsed.prefix) || !origin.endsWith(parsed.suffix)) return null;
38
+ const label = origin.slice(parsed.prefix.length, origin.length - parsed.suffix.length);
39
+ return LABEL_PATTERN.test(label) ? label : null;
40
+ }
41
+ function isValidExtensionRuntimeOriginTemplate(template) {
42
+ return parseTemplate(template) !== null;
43
+ }
44
+
45
+ export {
46
+ EXTENSION_RUNTIME_LABEL_PLACEHOLDER,
47
+ extensionRuntimeLabel,
48
+ extensionRuntimeOrigin,
49
+ extensionRuntimeOriginLabel,
50
+ isValidExtensionRuntimeOriginTemplate
51
+ };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod/v4';
2
+ export { EXTENSION_RUNTIME_LABEL_PLACEHOLDER, extensionRuntimeLabel, extensionRuntimeOrigin, extensionRuntimeOriginLabel, isValidExtensionRuntimeOriginTemplate } from './origin.js';
2
3
 
3
4
  declare const themeColorsSchema: z.ZodObject<{
4
5
  primary: z.ZodString;
@@ -287,7 +288,7 @@ declare const lumioMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
287
288
  }, z.core.$strip>], "type">;
288
289
  type LumioMessage = z.infer<typeof lumioMessageSchema>;
289
290
 
290
- declare const LUMIO_EVENT_TYPES: readonly ["twitch:follower", "twitch:subscribe", "twitch:gift_sub", "twitch:resub", "twitch:cheer", "twitch:raid", "twitch:reward", "twitch:hype_train", "twitch:hype_train_end", "twitch:poll", "twitch:poll_end", "twitch:prediction", "twitch:prediction_lock", "twitch:prediction_end", "twitch:goal", "twitch:goal_end", "twitch:ad_break", "twitch:ban", "twitch:unban", "twitch:stream_online", "twitch:stream_offline", "twitch:stream_update", "youtube:subscribe", "youtube:member", "youtube:superchat", "youtube:supersticker", "youtube:gift_membership", "youtube:gift_membership_received", "youtube:poll", "kick:follower", "kick:subscribe", "kick:gift", "kick:stream_online", "kick:stream_offline", "trovo:subscribe", "trovo:spell", "spotify:track", "spotify:play", "spotify:pause", "spotify:skip", "spotify:volume", "spotify:queue_add", "spotify:device", "spotify:playlist_add", "spotify:playlist_remove", "spotify:playlist_create", "spotify:playlist_edit", "spotify:playlist_delete", "streamelements:tip", "twitch:chat", "youtube:chat", "kick:chat", "trovo:chat", "chat:message"];
291
+ declare const LUMIO_EVENT_TYPES: readonly ["twitch:follower", "twitch:subscribe", "twitch:gift_sub", "twitch:resub", "twitch:cheer", "twitch:raid", "twitch:reward", "twitch:hype_train", "twitch:hype_train_end", "twitch:poll", "twitch:poll_end", "twitch:prediction", "twitch:prediction_lock", "twitch:prediction_end", "twitch:goal", "twitch:goal_end", "twitch:ad_break", "twitch:ban", "twitch:unban", "twitch:stream_online", "twitch:stream_offline", "twitch:stream_update", "youtube:subscribe", "youtube:member", "youtube:superchat", "youtube:supersticker", "youtube:gift_membership", "youtube:gift_membership_received", "youtube:poll", "youtube:stream_online", "youtube:stream_offline", "kick:follower", "kick:subscribe", "kick:resub", "kick:gift", "kick:kicks", "kick:reward", "kick:reward_status", "kick:stream_online", "kick:stream_offline", "kick:stream_update", "spotify:track", "spotify:play", "spotify:pause", "spotify:skip", "spotify:volume", "spotify:queue_add", "spotify:device", "spotify:playlist_add", "spotify:playlist_remove", "spotify:playlist_create", "spotify:playlist_edit", "spotify:playlist_delete", "streamelements:tip", "twitch:chat", "youtube:chat", "kick:chat", "chat:message"];
291
292
  type LumioEventType = (typeof LUMIO_EVENT_TYPES)[number];
292
293
  declare const lumioEventTypeSchema: z.ZodEnum<{
293
294
  "twitch:follower": "twitch:follower";
@@ -319,13 +320,18 @@ declare const lumioEventTypeSchema: z.ZodEnum<{
319
320
  "youtube:gift_membership": "youtube:gift_membership";
320
321
  "youtube:gift_membership_received": "youtube:gift_membership_received";
321
322
  "youtube:poll": "youtube:poll";
323
+ "youtube:stream_online": "youtube:stream_online";
324
+ "youtube:stream_offline": "youtube:stream_offline";
322
325
  "kick:follower": "kick:follower";
323
326
  "kick:subscribe": "kick:subscribe";
327
+ "kick:resub": "kick:resub";
324
328
  "kick:gift": "kick:gift";
329
+ "kick:kicks": "kick:kicks";
330
+ "kick:reward": "kick:reward";
331
+ "kick:reward_status": "kick:reward_status";
325
332
  "kick:stream_online": "kick:stream_online";
326
333
  "kick:stream_offline": "kick:stream_offline";
327
- "trovo:subscribe": "trovo:subscribe";
328
- "trovo:spell": "trovo:spell";
334
+ "kick:stream_update": "kick:stream_update";
329
335
  "spotify:track": "spotify:track";
330
336
  "spotify:play": "spotify:play";
331
337
  "spotify:pause": "spotify:pause";
@@ -342,7 +348,6 @@ declare const lumioEventTypeSchema: z.ZodEnum<{
342
348
  "twitch:chat": "twitch:chat";
343
349
  "youtube:chat": "youtube:chat";
344
350
  "kick:chat": "kick:chat";
345
- "trovo:chat": "trovo:chat";
346
351
  "chat:message": "chat:message";
347
352
  }>;
348
353
  declare const ACTION_TYPES: readonly ["chat:send", "chat:delete", "chat:ban", "chat:unban", "chat:timeout", "obs:set_scene", "obs:set_source_visible", "obs:start_stream", "obs:stop_stream", "obs:start_recording", "obs:stop_recording", "spotify:play", "spotify:pause", "spotify:skip", "spotify:seek", "spotify:volume", "spotify:shuffle", "spotify:repeat", "spotify:queue_add", "events:emit", "overlay:update_layer"];
@@ -464,6 +469,7 @@ interface Sound {
464
469
  file_size: number;
465
470
  duration_ms: number | null;
466
471
  created_at: string;
472
+ source_extension_name: string | null;
467
473
  }
468
474
  interface LifecycleInitParams {
469
475
  extensionId: string;
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({
@@ -140,15 +148,22 @@ var LUMIO_EVENT_TYPES = [
140
148
  "youtube:gift_membership",
141
149
  "youtube:gift_membership_received",
142
150
  "youtube:poll",
143
- // Kick
151
+ // YouTube stream lifecycle (ZAF-1444 / ZAF-622 §75) — emitted by the poll-driven
152
+ // YouTube worker on the channel-status online/offline edge, mirroring Twitch/Kick.
153
+ "youtube:stream_online",
154
+ "youtube:stream_offline",
155
+ // Kick — the webhook receiver normalizes every Kick payload into these types
156
+ // (ZAF-1624): canonical fields on top, the original payload kept in `raw`.
144
157
  "kick:follower",
145
158
  "kick:subscribe",
159
+ "kick:resub",
146
160
  "kick:gift",
161
+ "kick:kicks",
162
+ "kick:reward",
163
+ "kick:reward_status",
147
164
  "kick:stream_online",
148
165
  "kick:stream_offline",
149
- // Trovo
150
- "trovo:subscribe",
151
- "trovo:spell",
166
+ "kick:stream_update",
152
167
  // Spotify
153
168
  "spotify:track",
154
169
  "spotify:play",
@@ -168,7 +183,6 @@ var LUMIO_EVENT_TYPES = [
168
183
  "twitch:chat",
169
184
  "youtube:chat",
170
185
  "kick:chat",
171
- "trovo:chat",
172
186
  // Chat (cross-platform catch-all — fires for ALL platforms)
173
187
  "chat:message"
174
188
  ];
@@ -268,6 +282,7 @@ var HOST_METHODS = {
268
282
  };
269
283
  export {
270
284
  ACTION_TYPES,
285
+ EXTENSION_RUNTIME_LABEL_PLACEHOLDER,
271
286
  HOST_METHODS,
272
287
  LUMIO_COMPONENT_TYPES,
273
288
  LUMIO_EVENT_TYPES,
@@ -279,7 +294,11 @@ export {
279
294
  errorMessageSchema,
280
295
  eventMessageSchema,
281
296
  extensionConfigSchema,
297
+ extensionRuntimeLabel,
298
+ extensionRuntimeOrigin,
299
+ extensionRuntimeOriginLabel,
282
300
  initMessageSchema,
301
+ isValidExtensionRuntimeOriginTemplate,
283
302
  lumioActionTypeSchema,
284
303
  lumioEventTypeSchema,
285
304
  lumioMessageSchema,
@@ -0,0 +1,24 @@
1
+ /** Placeholder that a runtime origin template carries in place of the label. */
2
+ declare const EXTENSION_RUNTIME_LABEL_PLACEHOLDER = "{label}";
3
+ /**
4
+ * The runtime host label for an extension: its UUID as 32 lowercase hex
5
+ * characters. Returns `null` unless `extensionId` is a canonical lowercase,
6
+ * hyphenated UUID.
7
+ */
8
+ declare function extensionRuntimeLabel(extensionId: string): string | null;
9
+ /**
10
+ * The runtime origin for an extension under a template such as
11
+ * `https://{label}.lumio.ext.staging.zaflun.dev`. Returns `null` for an invalid
12
+ * extension id or an invalid template.
13
+ */
14
+ declare function extensionRuntimeOrigin(extensionId: string, template: string): string | null;
15
+ /**
16
+ * If `origin` is a runtime origin under `template`, returns its 32-hex label;
17
+ * otherwise `null`. Used where the extension id is not known, e.g. the runtime
18
+ * checking its own `location.origin`.
19
+ */
20
+ declare function extensionRuntimeOriginLabel(origin: string, template: string): string | null;
21
+ /** Whether `template` is a usable runtime origin template. */
22
+ declare function isValidExtensionRuntimeOriginTemplate(template: string): boolean;
23
+
24
+ export { EXTENSION_RUNTIME_LABEL_PLACEHOLDER, extensionRuntimeLabel, extensionRuntimeOrigin, extensionRuntimeOriginLabel, isValidExtensionRuntimeOriginTemplate };
package/dist/origin.js ADDED
@@ -0,0 +1,14 @@
1
+ import {
2
+ EXTENSION_RUNTIME_LABEL_PLACEHOLDER,
3
+ extensionRuntimeLabel,
4
+ extensionRuntimeOrigin,
5
+ extensionRuntimeOriginLabel,
6
+ isValidExtensionRuntimeOriginTemplate
7
+ } from "./chunk-746VSR4E.js";
8
+ export {
9
+ EXTENSION_RUNTIME_LABEL_PLACEHOLDER,
10
+ extensionRuntimeLabel,
11
+ extensionRuntimeOrigin,
12
+ extensionRuntimeOriginLabel,
13
+ isValidExtensionRuntimeOriginTemplate
14
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zaflun/lumio-protocol",
3
- "version": "0.2.3",
3
+ "version": "0.6.0",
4
4
  "description": "Wire-format types for Lumio extension iframe-to-host communication",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,13 +9,18 @@
9
9
  ".": {
10
10
  "import": "./dist/index.js",
11
11
  "types": "./dist/index.d.ts"
12
+ },
13
+ "./origin": {
14
+ "import": "./dist/origin.js",
15
+ "types": "./dist/origin.d.ts"
12
16
  }
13
17
  },
14
18
  "publishConfig": {
15
19
  "access": "public"
16
20
  },
17
21
  "files": [
18
- "dist"
22
+ "dist",
23
+ "CHANGELOG.md"
19
24
  ],
20
25
  "dependencies": {
21
26
  "zod": "^4.3.6"
@@ -41,7 +46,7 @@
41
46
  "email": "developers@zaflun.com"
42
47
  },
43
48
  "scripts": {
44
- "build": "tsup src/index.ts --format esm --dts --clean",
49
+ "build": "tsup src/index.ts src/origin.ts --format esm --dts --clean",
45
50
  "typecheck": "tsc --noEmit",
46
51
  "lint": "eslint src/",
47
52
  "test": "vitest run"