@lumiastream/ui 0.6.0 → 0.6.2
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 +106 -34
- package/dist/se-import.d.ts +2 -2
- package/dist/se-import.js +106 -34
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -4865,6 +4865,65 @@ var RESERVED_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
|
4865
4865
|
"widgetAuthor",
|
|
4866
4866
|
"widgetDuration"
|
|
4867
4867
|
]);
|
|
4868
|
+
var SE_LISTENER_TO_LUMIA_EVENTS = {
|
|
4869
|
+
message: ["chat"],
|
|
4870
|
+
"tip-latest": ["alert", "alert.trigger"],
|
|
4871
|
+
"merch-latest": ["alert", "alert.trigger"],
|
|
4872
|
+
"subscriber-latest": ["alert", "alert.trigger"],
|
|
4873
|
+
"subscriber-resub-latest": ["alert", "alert.trigger"],
|
|
4874
|
+
"subscriber-gifted-latest": ["alert", "alert.trigger"],
|
|
4875
|
+
"subscriber-bomb-latest": ["alert", "alert.trigger"],
|
|
4876
|
+
"follower-latest": ["alert", "alert.trigger"],
|
|
4877
|
+
"cheer-latest": ["alert", "alert.trigger"],
|
|
4878
|
+
"raid-latest": ["alert", "alert.trigger"],
|
|
4879
|
+
"host-latest": ["alert", "alert.trigger"],
|
|
4880
|
+
"redemption-latest": ["alert", "alert.trigger"],
|
|
4881
|
+
"hypetrain-start": ["alert", "alert.trigger"],
|
|
4882
|
+
"hypetrain-level-progress": ["alert", "alert.trigger"],
|
|
4883
|
+
"hypetrain-end": ["alert", "alert.trigger"],
|
|
4884
|
+
"sponsor-latest": ["alert", "alert.trigger"],
|
|
4885
|
+
"member-latest": ["alert", "alert.trigger"],
|
|
4886
|
+
"superchat-latest": ["alert", "alert.trigger"],
|
|
4887
|
+
"kappagen-launched": ["alert", "alert.trigger"]
|
|
4888
|
+
};
|
|
4889
|
+
function listenersToLumiaEvents(listeners, listener) {
|
|
4890
|
+
const keys = /* @__PURE__ */ new Set();
|
|
4891
|
+
if (Array.isArray(listeners)) {
|
|
4892
|
+
for (const k of listeners) if (typeof k === "string") keys.add(k);
|
|
4893
|
+
} else if (listeners && typeof listeners === "object") {
|
|
4894
|
+
for (const [k, v] of Object.entries(listeners)) if (v) keys.add(k);
|
|
4895
|
+
}
|
|
4896
|
+
if (typeof listener === "string" && listener) keys.add(listener);
|
|
4897
|
+
const out = /* @__PURE__ */ new Set();
|
|
4898
|
+
for (const k of keys) {
|
|
4899
|
+
const mapped = SE_LISTENER_TO_LUMIA_EVENTS[k];
|
|
4900
|
+
if (mapped) for (const e of mapped) out.add(e);
|
|
4901
|
+
}
|
|
4902
|
+
return [...out];
|
|
4903
|
+
}
|
|
4904
|
+
var SE_FIRST_PARTY_SCRIPT_RX = /^(?:https?:)?\/\/(?:[^/]+\.)?(?:streamelements\.com|lumiastream\.com)\//i;
|
|
4905
|
+
var EXTERNAL_SCRIPT_SRC_RX = /<script\b[^>]*\bsrc\s*=\s*["']([^"']+)["']/gi;
|
|
4906
|
+
var CHAT_COMMAND_VALUE_RX = /^\s*!\S+/;
|
|
4907
|
+
function hasExternalScriptLoader(html, js) {
|
|
4908
|
+
const haystack = `${html ?? ""}
|
|
4909
|
+
${js ?? ""}`;
|
|
4910
|
+
EXTERNAL_SCRIPT_SRC_RX.lastIndex = 0;
|
|
4911
|
+
for (const m of haystack.matchAll(EXTERNAL_SCRIPT_SRC_RX)) {
|
|
4912
|
+
const src = m[1]?.trim();
|
|
4913
|
+
if (!src) continue;
|
|
4914
|
+
if (src.startsWith("/") && !src.startsWith("//")) continue;
|
|
4915
|
+
if (SE_FIRST_PARTY_SCRIPT_RX.test(src)) continue;
|
|
4916
|
+
return true;
|
|
4917
|
+
}
|
|
4918
|
+
return false;
|
|
4919
|
+
}
|
|
4920
|
+
function hasChatCommandFieldValue(fieldData) {
|
|
4921
|
+
if (!fieldData) return false;
|
|
4922
|
+
for (const v of Object.values(fieldData)) {
|
|
4923
|
+
if (typeof v === "string" && CHAT_COMMAND_VALUE_RX.test(v)) return true;
|
|
4924
|
+
}
|
|
4925
|
+
return false;
|
|
4926
|
+
}
|
|
4868
4927
|
function translateConfigs(sourceFields) {
|
|
4869
4928
|
if (!sourceFields) return [];
|
|
4870
4929
|
return Object.entries(sourceFields).filter(([key]) => !RESERVED_FIELD_KEYS.has(key)).map(([key, f]) => {
|
|
@@ -4913,6 +4972,19 @@ function mapCustom(widget, ctx) {
|
|
|
4913
4972
|
const fieldData = parse(v.fieldData) ?? v.fieldData;
|
|
4914
4973
|
const widgetProvider = widget.provider;
|
|
4915
4974
|
const provider = widgetProvider === "kick" || widgetProvider === "youtube" || widgetProvider === "twitch" ? widgetProvider : ctx?.provider ?? "twitch";
|
|
4975
|
+
const eventsList = listenersToLumiaEvents(
|
|
4976
|
+
widget.listeners,
|
|
4977
|
+
widget.listener
|
|
4978
|
+
);
|
|
4979
|
+
const externalLoader = hasExternalScriptLoader(v.html, v.js);
|
|
4980
|
+
const wantsChat = externalLoader || hasChatCommandFieldValue(fieldData);
|
|
4981
|
+
if (externalLoader) {
|
|
4982
|
+
if (!eventsList.includes("alert")) eventsList.push("alert");
|
|
4983
|
+
if (!eventsList.includes("alert.trigger")) eventsList.push("alert.trigger");
|
|
4984
|
+
}
|
|
4985
|
+
if (wantsChat && !eventsList.includes("chat")) eventsList.push("chat");
|
|
4986
|
+
const data = { ...fieldData ?? {} };
|
|
4987
|
+
if (eventsList.length > 0) data.events = eventsList;
|
|
4916
4988
|
return buildUnit(
|
|
4917
4989
|
widget,
|
|
4918
4990
|
"custom",
|
|
@@ -4928,7 +5000,7 @@ function mapCustom(widget, ctx) {
|
|
|
4928
5000
|
css: v.css ?? "",
|
|
4929
5001
|
js: v.js ?? "",
|
|
4930
5002
|
configs: translateConfigs(fields),
|
|
4931
|
-
data
|
|
5003
|
+
data,
|
|
4932
5004
|
flavor: "streamelements",
|
|
4933
5005
|
// Native field on the Custom module content. The compatibility
|
|
4934
5006
|
// shim in Embed.tsx reads this to pick the right listener-name
|
|
@@ -7038,7 +7110,7 @@ async function mirrorOneAsset(url, upload, signal, proxyAssetFetch) {
|
|
|
7038
7110
|
const blob = await fetchAssetBlob(url, signal, proxyAssetFetch);
|
|
7039
7111
|
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
7040
7112
|
if (blob.size > MAX_MIRROR_ASSET_BYTES) {
|
|
7041
|
-
throw new Error(`Asset is ${(blob.size / (1024 * 1024)).toFixed(1)} MB
|
|
7113
|
+
throw new Error(`Asset is ${(blob.size / (1024 * 1024)).toFixed(1)} MB, over the ${MAX_MIRROR_ASSET_BYTES / (1024 * 1024)} MB import cap.`);
|
|
7042
7114
|
}
|
|
7043
7115
|
const filename = filenameFromURL(url);
|
|
7044
7116
|
const type = blob.type || guessMime(filename);
|
|
@@ -7060,7 +7132,7 @@ async function fetchAssetBlob(url, signal, proxyAssetFetch) {
|
|
|
7060
7132
|
if (!res.ok) throw new Error(`Source returned HTTP ${res.status}`);
|
|
7061
7133
|
const declared = Number(res.headers.get("content-length"));
|
|
7062
7134
|
if (Number.isFinite(declared) && declared > MAX_MIRROR_ASSET_BYTES) {
|
|
7063
|
-
throw new Error(`Asset is ${(declared / (1024 * 1024)).toFixed(1)} MB
|
|
7135
|
+
throw new Error(`Asset is ${(declared / (1024 * 1024)).toFixed(1)} MB, over the ${MAX_MIRROR_ASSET_BYTES / (1024 * 1024)} MB import cap.`);
|
|
7064
7136
|
}
|
|
7065
7137
|
return res.blob();
|
|
7066
7138
|
}
|
|
@@ -7300,7 +7372,7 @@ function decodeJwtPayload(jwt) {
|
|
|
7300
7372
|
}
|
|
7301
7373
|
const claims = parsed;
|
|
7302
7374
|
if (typeof claims.channel !== "string" || typeof claims.authToken !== "string") {
|
|
7303
|
-
throw new SEAuthError("invalid-jwt", "Token payload is missing the 'channel' or 'authToken' claim
|
|
7375
|
+
throw new SEAuthError("invalid-jwt", "Token payload is missing the 'channel' or 'authToken' claim. not a StreamElements account token.");
|
|
7304
7376
|
}
|
|
7305
7377
|
if (typeof claims.exp === "number" && claims.exp * 1e3 < Date.now()) {
|
|
7306
7378
|
throw new SEAuthError("expired", "This StreamElements token has expired. Generate a new one on the SE Account page.");
|
|
@@ -7309,7 +7381,7 @@ function decodeJwtPayload(jwt) {
|
|
|
7309
7381
|
}
|
|
7310
7382
|
var SEClient = class _SEClient {
|
|
7311
7383
|
// `jwt` is held alongside the decoded claims so the client can switch auth
|
|
7312
|
-
// modes without re-parsing. Defaults to `bearer-jwt
|
|
7384
|
+
// modes without re-parsing. Defaults to `bearer-jwt`. confirmed against
|
|
7313
7385
|
// the user's diagnostic 2026-05-16: every endpoint the existing 5 helpers
|
|
7314
7386
|
// hit (users/current, channels/me, overlays, bot/commands, plus
|
|
7315
7387
|
// sessions/loyalty/bits leaderboard transitively) returns 200, AND
|
|
@@ -7397,7 +7469,7 @@ var SEClient = class _SEClient {
|
|
|
7397
7469
|
let detail = "";
|
|
7398
7470
|
try {
|
|
7399
7471
|
const body = await res.json();
|
|
7400
|
-
if (body?.message) detail = `
|
|
7472
|
+
if (body?.message) detail = ` - ${body.message}`;
|
|
7401
7473
|
} catch {
|
|
7402
7474
|
}
|
|
7403
7475
|
throw new Error(`StreamElements API returned HTTP ${res.status}${detail}`);
|
|
@@ -7439,7 +7511,7 @@ async function fetchElementWidgetInstancePublic(channelId, widgetInstanceId, pub
|
|
|
7439
7511
|
let detail = "";
|
|
7440
7512
|
try {
|
|
7441
7513
|
const body = JSON.parse(await result.errorBody());
|
|
7442
|
-
if (body?.message) detail = `
|
|
7514
|
+
if (body?.message) detail = ` - ${body.message}`;
|
|
7443
7515
|
} catch {
|
|
7444
7516
|
}
|
|
7445
7517
|
const reason = result.status === 401 || result.status === 403 ? "StreamElements rejected the widget share token. The share link may have been revoked or made private." : result.status === 404 ? "Element widget not found. Double-check the share URL." : `HTTP ${result.status}`;
|
|
@@ -8447,15 +8519,15 @@ var PLACEHOLDER_WIDGET_TYPES = /* @__PURE__ */ new Set([
|
|
|
8447
8519
|
"streamlabels"
|
|
8448
8520
|
]);
|
|
8449
8521
|
var PLACEHOLDER_REASONS = {
|
|
8450
|
-
donation_ticker: "No native Lumia equivalent yet
|
|
8451
|
-
stream_boss: "Lumia has a native streamboss module
|
|
8452
|
-
credits: "Lumia has a native credits module
|
|
8453
|
-
spin_wheel: "Lumia has a native spinwheel module
|
|
8454
|
-
emote_wall: "Lumia has a native emotebox module
|
|
8455
|
-
sub_train: "No native Lumia equivalent yet
|
|
8522
|
+
donation_ticker: "No native Lumia equivalent yet. imported as a placeholder text layer.",
|
|
8523
|
+
stream_boss: "Lumia has a native streamboss module. set it up directly rather than importing the SL config.",
|
|
8524
|
+
credits: "Lumia has a native credits module. set it up directly rather than importing the SL config.",
|
|
8525
|
+
spin_wheel: "Lumia has a native spinwheel module. set it up directly rather than importing the SL config.",
|
|
8526
|
+
emote_wall: "Lumia has a native emotebox module. set it up directly rather than importing the SL config.",
|
|
8527
|
+
sub_train: "No native Lumia equivalent yet. imported as a placeholder text layer.",
|
|
8456
8528
|
streamlabels: "Use Lumia template variables (e.g. {{twitch_last_follower}}) inside a Text module instead of importing the SL streamlabels config."
|
|
8457
8529
|
};
|
|
8458
|
-
var CUSTOM_HTML_REVIEW_REASON = "The Streamlabs widget used custom HTML/CSS/JS that Lumia cannot host. The native Lumia module is the right starting point
|
|
8530
|
+
var CUSTOM_HTML_REVIEW_REASON = "The Streamlabs widget used custom HTML/CSS/JS that Lumia cannot host. The native Lumia module is the right starting point. recreate the styling there. The original SL settings are stashed under module.content.importMeta for reference.";
|
|
8459
8531
|
function stampImportMeta(module, widgetType, role, extra = {}) {
|
|
8460
8532
|
stampImportMetaMerge(module, {
|
|
8461
8533
|
source: "streamlabs",
|
|
@@ -8613,7 +8685,7 @@ function importStreamlabsWidget(response, widgetType, options = {}) {
|
|
|
8613
8685
|
});
|
|
8614
8686
|
reviewItems.push({
|
|
8615
8687
|
moduleId: placeholder.module.id,
|
|
8616
|
-
// SL doesn't have a single "widget" object the way SE does
|
|
8688
|
+
// SL doesn't have a single "widget" object the way SE does. we
|
|
8617
8689
|
// stash the settings snapshot here so the review step can show
|
|
8618
8690
|
// the raw config the user imported. Cast through unknown because
|
|
8619
8691
|
// ReviewItem is currently typed against SEWidget; widening the
|
|
@@ -8631,7 +8703,7 @@ function importStreamlabsWidget(response, widgetType, options = {}) {
|
|
|
8631
8703
|
layers.push(placeholder.layer);
|
|
8632
8704
|
modules[placeholder.layer.id] = placeholder.module;
|
|
8633
8705
|
coverage.notes.push(
|
|
8634
|
-
`Unknown Streamlabs widget type "${widgetType}"
|
|
8706
|
+
`Unknown Streamlabs widget type "${widgetType}". imported as a placeholder.`
|
|
8635
8707
|
);
|
|
8636
8708
|
coverage.mappings.push({
|
|
8637
8709
|
seType: `streamlabs:${widgetType}`,
|
|
@@ -9044,7 +9116,7 @@ function StepHeader({
|
|
|
9044
9116
|
] });
|
|
9045
9117
|
}
|
|
9046
9118
|
var JWT_FEATURES = [
|
|
9047
|
-
"All your overlays
|
|
9119
|
+
"All your overlays: pick one, many, or all",
|
|
9048
9120
|
"Live counters (follower / sub / tip / bits goals)",
|
|
9049
9121
|
"Chat bot commands",
|
|
9050
9122
|
"Loyalty points + watch time",
|
|
@@ -9198,7 +9270,7 @@ function StepModePicker({
|
|
|
9198
9270
|
}
|
|
9199
9271
|
)
|
|
9200
9272
|
] }),
|
|
9201
|
-
caveat: "Requires pasting your StreamElements JWT. Stored only in memory
|
|
9273
|
+
caveat: "Requires pasting your StreamElements JWT. Stored only in memory. Never sent to Lumia.",
|
|
9202
9274
|
onClick: onChooseJwt
|
|
9203
9275
|
}
|
|
9204
9276
|
)
|
|
@@ -9223,7 +9295,7 @@ function StepConnect({
|
|
|
9223
9295
|
{
|
|
9224
9296
|
number: 1,
|
|
9225
9297
|
title: "Connect your StreamElements account",
|
|
9226
|
-
subtitle: "Paste your StreamElements JWT to import overlays, live counters, chat commands, loyalty points, watch time, and recent events. The token is read in your browser only
|
|
9298
|
+
subtitle: "Paste your StreamElements JWT to import overlays, live counters, chat commands, loyalty points, watch time, and recent events. The token is read in your browser only. Lumia never sends it to our servers."
|
|
9227
9299
|
}
|
|
9228
9300
|
),
|
|
9229
9301
|
/* @__PURE__ */ jsx14(
|
|
@@ -11872,15 +11944,15 @@ function importElementOverlay(response, options = {}) {
|
|
|
11872
11944
|
// src/se-import/index.ts
|
|
11873
11945
|
var IMPORT_META_KEY2 = "importMeta";
|
|
11874
11946
|
var REVIEW_REASONS = {
|
|
11875
|
-
"se-widget-bit-boss": "No native Lumia equivalent
|
|
11947
|
+
"se-widget-bit-boss": "No native Lumia equivalent. bit boss fight bar with HP/damage states.",
|
|
11876
11948
|
// `se-widget-hype-cup` intentionally omitted — it now routes to the native
|
|
11877
11949
|
// `tipjar` module (see dispatcher.ts) and lands with `partial` status, so it
|
|
11878
11950
|
// never reaches the placeholder-reason fallback. If a future feature flag
|
|
11879
11951
|
// disables tipjar auto-import, add an entry to FLAG_OFF_REASONS instead.
|
|
11880
|
-
"se-widget-media-share": "No native Lumia equivalent
|
|
11881
|
-
"se-widget-contest": "No native Lumia equivalent
|
|
11882
|
-
"se-widget-giveaway": "No native Lumia equivalent
|
|
11883
|
-
"se-widget-botcounter": "No native Lumia equivalent
|
|
11952
|
+
"se-widget-media-share": "No native Lumia equivalent. viewer-submitted media player.",
|
|
11953
|
+
"se-widget-contest": "No native Lumia equivalent. SE-specific contest widget.",
|
|
11954
|
+
"se-widget-giveaway": "No native Lumia equivalent. SE giveaway service.",
|
|
11955
|
+
"se-widget-botcounter": "No native Lumia equivalent. chatter/message counter.",
|
|
11884
11956
|
"se-widget-top-cheerers-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render cheer rankings.',
|
|
11885
11957
|
"se-widget-top-gifters-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render gifter rankings.',
|
|
11886
11958
|
"se-widget-top-tippers-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render tipper rankings.',
|
|
@@ -11890,7 +11962,7 @@ var REVIEW_REASONS = {
|
|
|
11890
11962
|
// so we can't pre-populate the slideshow items. The streamer adds their
|
|
11891
11963
|
// product images by hand in the slideshow editor. Status = 'partial' (see
|
|
11892
11964
|
// dispatcher.ts), so this string surfaces in the review step.
|
|
11893
|
-
"se-widget-merch-products-rotator": "Imported as an empty slideshow. Lumia doesn't fetch your Fourthwall product catalog yet, so add your product images manually in the slideshow editor
|
|
11965
|
+
"se-widget-merch-products-rotator": "Imported as an empty slideshow. Lumia doesn't fetch your Fourthwall product catalog yet, so add your product images manually in the slideshow editor. the layer is pre-positioned and styled to match your SE widget."
|
|
11894
11966
|
// Seasonal widgets (snow / halloween / halloween-2019 / xmas / valentine / easter
|
|
11895
11967
|
// / world-cup) intentionally omitted — they route directly to image/slideshow/
|
|
11896
11968
|
// video modules now (see dispatcher.ts SEASONAL_IMAGE_TYPES), so they always
|
|
@@ -11898,7 +11970,7 @@ var REVIEW_REASONS = {
|
|
|
11898
11970
|
};
|
|
11899
11971
|
function reasonFor(seType, status, flaggedOff) {
|
|
11900
11972
|
if (flaggedOff && FLAG_OFF_REASONS[seType]) return FLAG_OFF_REASONS[seType];
|
|
11901
|
-
return REVIEW_REASONS[seType] ?? (status === "template" ? "Imported as a generic template stub." : "No native mapping yet
|
|
11973
|
+
return REVIEW_REASONS[seType] ?? (status === "template" ? "Imported as a generic template stub." : "No native mapping yet. imported as a labelled text placeholder.");
|
|
11902
11974
|
}
|
|
11903
11975
|
function buildSEBootstrapDescription(overlayId, sourceUrl) {
|
|
11904
11976
|
const head = overlayId ? `Imported from StreamElements overlay ${overlayId}` : "Imported from StreamElements";
|
|
@@ -11928,7 +12000,7 @@ async function fetchSEBootstrap({
|
|
|
11928
12000
|
}) {
|
|
11929
12001
|
if (!apikey) {
|
|
11930
12002
|
throw new Error(
|
|
11931
|
-
"Missing access token. Use the public preview URL (Overlay menu \u2192 Get URL \u2192 Public link)
|
|
12003
|
+
"Missing access token. Use the public preview URL (Overlay menu \u2192 Get URL \u2192 Public link). editor URLs do not include a token."
|
|
11932
12004
|
);
|
|
11933
12005
|
}
|
|
11934
12006
|
const result = await fetchWithProxyFallback(buildBootstrapUrl(overlayId), {
|
|
@@ -11939,7 +12011,7 @@ async function fetchSEBootstrap({
|
|
|
11939
12011
|
let detail = "";
|
|
11940
12012
|
try {
|
|
11941
12013
|
const body = JSON.parse(await result.errorBody());
|
|
11942
|
-
detail = body?.message ?
|
|
12014
|
+
detail = body?.message ? `. ${body.message}` : "";
|
|
11943
12015
|
} catch {
|
|
11944
12016
|
}
|
|
11945
12017
|
const reason = result.status === 401 || result.status === 403 ? "StreamElements rejected the URL token. The preview link may have been revoked." : result.status === 404 ? "Overlay not found. Double-check the URL." : `HTTP ${result.status}`;
|
|
@@ -12081,17 +12153,17 @@ function importSEBootstrap(bootstrap, options = {}) {
|
|
|
12081
12153
|
}
|
|
12082
12154
|
if (coverage.mappings.some((m) => m.status === "placeholder")) {
|
|
12083
12155
|
coverage.notes.push(
|
|
12084
|
-
"Some widgets imported as placeholder text layers
|
|
12156
|
+
"Some widgets imported as placeholder text layers. see SE/05-widget-to-module-mapping.md for status."
|
|
12085
12157
|
);
|
|
12086
12158
|
}
|
|
12087
12159
|
if (coverage.mappings.some((m) => m.seType === "se-widget-merch-goal")) {
|
|
12088
12160
|
coverage.notes.push(
|
|
12089
|
-
"Merch-goal widgets imported as generic goals
|
|
12161
|
+
"Merch-goal widgets imported as generic goals. see SE/03-missing-variables.md \xA78 for the missing data source."
|
|
12090
12162
|
);
|
|
12091
12163
|
}
|
|
12092
12164
|
if (coverage.mappings.some((m) => m.seType === "se-widget-custom-event-list")) {
|
|
12093
12165
|
coverage.notes.push(
|
|
12094
|
-
"\u26A0 Custom widgets are experimental
|
|
12166
|
+
"\u26A0 Custom widgets are experimental. they run under the SE compatibility shim (Embed.tsx) which handles most SE APIs but may not cover every edge case. Verify each custom layer renders correctly in the editor."
|
|
12095
12167
|
);
|
|
12096
12168
|
}
|
|
12097
12169
|
const overlay = {
|
|
@@ -12236,7 +12308,7 @@ function buildAIPromptForSEWidget(widget, canvas) {
|
|
|
12236
12308
|
const fullVariables = JSON.stringify(variables, null, 2);
|
|
12237
12309
|
const truncated = fullVariables.length > VARIABLES_BUDGET;
|
|
12238
12310
|
const variablesJson = truncated ? `${fullVariables.slice(0, VARIABLES_BUDGET)}
|
|
12239
|
-
\u2026[truncated
|
|
12311
|
+
\u2026[truncated. ${fullVariables.length - VARIABLES_BUDGET} more bytes elided]` : fullVariables;
|
|
12240
12312
|
return [
|
|
12241
12313
|
"Recreate a StreamElements overlay widget as a Lumia Stream custom overlay (HTML + CSS + JS).",
|
|
12242
12314
|
"",
|
|
@@ -12244,14 +12316,14 @@ function buildAIPromptForSEWidget(widget, canvas) {
|
|
|
12244
12316
|
listener ? `Primary event listener: ${listener}` : "",
|
|
12245
12317
|
listeners ? `Subscribes to events: ${listeners.join(", ")}` : "",
|
|
12246
12318
|
`Canvas: ${landing.width} \xD7 ${landing.height}`,
|
|
12247
|
-
truncated ? "Note: the widget config below is truncated
|
|
12319
|
+
truncated ? "Note: the widget config below is truncated. focus on visible/structural fields and ignore tail content you cannot see." : "",
|
|
12248
12320
|
"",
|
|
12249
12321
|
"Widget configuration (JSON):",
|
|
12250
12322
|
"```json",
|
|
12251
12323
|
variablesJson,
|
|
12252
12324
|
"```",
|
|
12253
12325
|
"",
|
|
12254
|
-
"Constraints
|
|
12326
|
+
"Constraints. your output must run inside a Lumia Custom Overlay sandboxed iframe:",
|
|
12255
12327
|
'- Use `window.Overlay.on("alert", handler)` / `window.Overlay.on("chat", handler)` to receive Lumia events.',
|
|
12256
12328
|
"- Lumia template variables use double-brace syntax: {{username}}, {{amount}}, {{message}}, {{currency}}.",
|
|
12257
12329
|
"- The `window.DATA` object holds user-configurable values (mirrors your `configs`).",
|
package/dist/se-import.d.ts
CHANGED
|
@@ -183,7 +183,7 @@ declare function fetchWithProxyFallback(url: string, options?: FetchWithProxyOpt
|
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
185
|
* Distinct importable CDN URLs referenced anywhere inside the overlay's
|
|
186
|
-
* settings. Covers both StreamElements and Streamlabs source hosts
|
|
186
|
+
* settings. Covers both StreamElements and Streamlabs source hosts, see
|
|
187
187
|
* IMPORTABLE_CDN_REGEX above for the full host list.
|
|
188
188
|
*
|
|
189
189
|
* Name kept as `findSEAssetURLs` for back-compat with existing callers; the
|
|
@@ -211,7 +211,7 @@ interface AssetMirrorProgress {
|
|
|
211
211
|
*
|
|
212
212
|
* The host call site is responsible for retries and progress reporting;
|
|
213
213
|
* this function is intentionally single-shot. Pass `signal` (AbortSignal)
|
|
214
|
-
* to abort the source-side `fetch` when the modal closes mid-mirror
|
|
214
|
+
* to abort the source-side `fetch` when the modal closes mid-mirror, the
|
|
215
215
|
* upload itself is consumer-defined and the caller decides whether/how to
|
|
216
216
|
* short-circuit it. The proxy path doesn't take a signal because the
|
|
217
217
|
* host-provided fetch shape varies.
|
package/dist/se-import.js
CHANGED
|
@@ -1785,6 +1785,65 @@ var RESERVED_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
|
1785
1785
|
"widgetAuthor",
|
|
1786
1786
|
"widgetDuration"
|
|
1787
1787
|
]);
|
|
1788
|
+
var SE_LISTENER_TO_LUMIA_EVENTS = {
|
|
1789
|
+
message: ["chat"],
|
|
1790
|
+
"tip-latest": ["alert", "alert.trigger"],
|
|
1791
|
+
"merch-latest": ["alert", "alert.trigger"],
|
|
1792
|
+
"subscriber-latest": ["alert", "alert.trigger"],
|
|
1793
|
+
"subscriber-resub-latest": ["alert", "alert.trigger"],
|
|
1794
|
+
"subscriber-gifted-latest": ["alert", "alert.trigger"],
|
|
1795
|
+
"subscriber-bomb-latest": ["alert", "alert.trigger"],
|
|
1796
|
+
"follower-latest": ["alert", "alert.trigger"],
|
|
1797
|
+
"cheer-latest": ["alert", "alert.trigger"],
|
|
1798
|
+
"raid-latest": ["alert", "alert.trigger"],
|
|
1799
|
+
"host-latest": ["alert", "alert.trigger"],
|
|
1800
|
+
"redemption-latest": ["alert", "alert.trigger"],
|
|
1801
|
+
"hypetrain-start": ["alert", "alert.trigger"],
|
|
1802
|
+
"hypetrain-level-progress": ["alert", "alert.trigger"],
|
|
1803
|
+
"hypetrain-end": ["alert", "alert.trigger"],
|
|
1804
|
+
"sponsor-latest": ["alert", "alert.trigger"],
|
|
1805
|
+
"member-latest": ["alert", "alert.trigger"],
|
|
1806
|
+
"superchat-latest": ["alert", "alert.trigger"],
|
|
1807
|
+
"kappagen-launched": ["alert", "alert.trigger"]
|
|
1808
|
+
};
|
|
1809
|
+
function listenersToLumiaEvents(listeners, listener) {
|
|
1810
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1811
|
+
if (Array.isArray(listeners)) {
|
|
1812
|
+
for (const k of listeners) if (typeof k === "string") keys.add(k);
|
|
1813
|
+
} else if (listeners && typeof listeners === "object") {
|
|
1814
|
+
for (const [k, v] of Object.entries(listeners)) if (v) keys.add(k);
|
|
1815
|
+
}
|
|
1816
|
+
if (typeof listener === "string" && listener) keys.add(listener);
|
|
1817
|
+
const out = /* @__PURE__ */ new Set();
|
|
1818
|
+
for (const k of keys) {
|
|
1819
|
+
const mapped = SE_LISTENER_TO_LUMIA_EVENTS[k];
|
|
1820
|
+
if (mapped) for (const e of mapped) out.add(e);
|
|
1821
|
+
}
|
|
1822
|
+
return [...out];
|
|
1823
|
+
}
|
|
1824
|
+
var SE_FIRST_PARTY_SCRIPT_RX = /^(?:https?:)?\/\/(?:[^/]+\.)?(?:streamelements\.com|lumiastream\.com)\//i;
|
|
1825
|
+
var EXTERNAL_SCRIPT_SRC_RX = /<script\b[^>]*\bsrc\s*=\s*["']([^"']+)["']/gi;
|
|
1826
|
+
var CHAT_COMMAND_VALUE_RX = /^\s*!\S+/;
|
|
1827
|
+
function hasExternalScriptLoader(html, js) {
|
|
1828
|
+
const haystack = `${html ?? ""}
|
|
1829
|
+
${js ?? ""}`;
|
|
1830
|
+
EXTERNAL_SCRIPT_SRC_RX.lastIndex = 0;
|
|
1831
|
+
for (const m of haystack.matchAll(EXTERNAL_SCRIPT_SRC_RX)) {
|
|
1832
|
+
const src = m[1]?.trim();
|
|
1833
|
+
if (!src) continue;
|
|
1834
|
+
if (src.startsWith("/") && !src.startsWith("//")) continue;
|
|
1835
|
+
if (SE_FIRST_PARTY_SCRIPT_RX.test(src)) continue;
|
|
1836
|
+
return true;
|
|
1837
|
+
}
|
|
1838
|
+
return false;
|
|
1839
|
+
}
|
|
1840
|
+
function hasChatCommandFieldValue(fieldData) {
|
|
1841
|
+
if (!fieldData) return false;
|
|
1842
|
+
for (const v of Object.values(fieldData)) {
|
|
1843
|
+
if (typeof v === "string" && CHAT_COMMAND_VALUE_RX.test(v)) return true;
|
|
1844
|
+
}
|
|
1845
|
+
return false;
|
|
1846
|
+
}
|
|
1788
1847
|
function translateConfigs(sourceFields) {
|
|
1789
1848
|
if (!sourceFields) return [];
|
|
1790
1849
|
return Object.entries(sourceFields).filter(([key]) => !RESERVED_FIELD_KEYS.has(key)).map(([key, f]) => {
|
|
@@ -1833,6 +1892,19 @@ function mapCustom(widget, ctx) {
|
|
|
1833
1892
|
const fieldData = parse(v.fieldData) ?? v.fieldData;
|
|
1834
1893
|
const widgetProvider = widget.provider;
|
|
1835
1894
|
const provider = widgetProvider === "kick" || widgetProvider === "youtube" || widgetProvider === "twitch" ? widgetProvider : ctx?.provider ?? "twitch";
|
|
1895
|
+
const eventsList = listenersToLumiaEvents(
|
|
1896
|
+
widget.listeners,
|
|
1897
|
+
widget.listener
|
|
1898
|
+
);
|
|
1899
|
+
const externalLoader = hasExternalScriptLoader(v.html, v.js);
|
|
1900
|
+
const wantsChat = externalLoader || hasChatCommandFieldValue(fieldData);
|
|
1901
|
+
if (externalLoader) {
|
|
1902
|
+
if (!eventsList.includes("alert")) eventsList.push("alert");
|
|
1903
|
+
if (!eventsList.includes("alert.trigger")) eventsList.push("alert.trigger");
|
|
1904
|
+
}
|
|
1905
|
+
if (wantsChat && !eventsList.includes("chat")) eventsList.push("chat");
|
|
1906
|
+
const data = { ...fieldData ?? {} };
|
|
1907
|
+
if (eventsList.length > 0) data.events = eventsList;
|
|
1836
1908
|
return buildUnit(
|
|
1837
1909
|
widget,
|
|
1838
1910
|
"custom",
|
|
@@ -1848,7 +1920,7 @@ function mapCustom(widget, ctx) {
|
|
|
1848
1920
|
css: v.css ?? "",
|
|
1849
1921
|
js: v.js ?? "",
|
|
1850
1922
|
configs: translateConfigs(fields),
|
|
1851
|
-
data
|
|
1923
|
+
data,
|
|
1852
1924
|
flavor: "streamelements",
|
|
1853
1925
|
// Native field on the Custom module content. The compatibility
|
|
1854
1926
|
// shim in Embed.tsx reads this to pick the right listener-name
|
|
@@ -3958,7 +4030,7 @@ async function mirrorOneAsset(url, upload, signal, proxyAssetFetch) {
|
|
|
3958
4030
|
const blob = await fetchAssetBlob(url, signal, proxyAssetFetch);
|
|
3959
4031
|
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
3960
4032
|
if (blob.size > MAX_MIRROR_ASSET_BYTES) {
|
|
3961
|
-
throw new Error(`Asset is ${(blob.size / (1024 * 1024)).toFixed(1)} MB
|
|
4033
|
+
throw new Error(`Asset is ${(blob.size / (1024 * 1024)).toFixed(1)} MB, over the ${MAX_MIRROR_ASSET_BYTES / (1024 * 1024)} MB import cap.`);
|
|
3962
4034
|
}
|
|
3963
4035
|
const filename = filenameFromURL(url);
|
|
3964
4036
|
const type = blob.type || guessMime(filename);
|
|
@@ -3980,7 +4052,7 @@ async function fetchAssetBlob(url, signal, proxyAssetFetch) {
|
|
|
3980
4052
|
if (!res.ok) throw new Error(`Source returned HTTP ${res.status}`);
|
|
3981
4053
|
const declared = Number(res.headers.get("content-length"));
|
|
3982
4054
|
if (Number.isFinite(declared) && declared > MAX_MIRROR_ASSET_BYTES) {
|
|
3983
|
-
throw new Error(`Asset is ${(declared / (1024 * 1024)).toFixed(1)} MB
|
|
4055
|
+
throw new Error(`Asset is ${(declared / (1024 * 1024)).toFixed(1)} MB, over the ${MAX_MIRROR_ASSET_BYTES / (1024 * 1024)} MB import cap.`);
|
|
3984
4056
|
}
|
|
3985
4057
|
return res.blob();
|
|
3986
4058
|
}
|
|
@@ -4220,7 +4292,7 @@ function decodeJwtPayload(jwt) {
|
|
|
4220
4292
|
}
|
|
4221
4293
|
const claims = parsed;
|
|
4222
4294
|
if (typeof claims.channel !== "string" || typeof claims.authToken !== "string") {
|
|
4223
|
-
throw new SEAuthError("invalid-jwt", "Token payload is missing the 'channel' or 'authToken' claim
|
|
4295
|
+
throw new SEAuthError("invalid-jwt", "Token payload is missing the 'channel' or 'authToken' claim. not a StreamElements account token.");
|
|
4224
4296
|
}
|
|
4225
4297
|
if (typeof claims.exp === "number" && claims.exp * 1e3 < Date.now()) {
|
|
4226
4298
|
throw new SEAuthError("expired", "This StreamElements token has expired. Generate a new one on the SE Account page.");
|
|
@@ -4229,7 +4301,7 @@ function decodeJwtPayload(jwt) {
|
|
|
4229
4301
|
}
|
|
4230
4302
|
var SEClient = class _SEClient {
|
|
4231
4303
|
// `jwt` is held alongside the decoded claims so the client can switch auth
|
|
4232
|
-
// modes without re-parsing. Defaults to `bearer-jwt
|
|
4304
|
+
// modes without re-parsing. Defaults to `bearer-jwt`. confirmed against
|
|
4233
4305
|
// the user's diagnostic 2026-05-16: every endpoint the existing 5 helpers
|
|
4234
4306
|
// hit (users/current, channels/me, overlays, bot/commands, plus
|
|
4235
4307
|
// sessions/loyalty/bits leaderboard transitively) returns 200, AND
|
|
@@ -4317,7 +4389,7 @@ var SEClient = class _SEClient {
|
|
|
4317
4389
|
let detail = "";
|
|
4318
4390
|
try {
|
|
4319
4391
|
const body = await res.json();
|
|
4320
|
-
if (body?.message) detail = `
|
|
4392
|
+
if (body?.message) detail = ` - ${body.message}`;
|
|
4321
4393
|
} catch {
|
|
4322
4394
|
}
|
|
4323
4395
|
throw new Error(`StreamElements API returned HTTP ${res.status}${detail}`);
|
|
@@ -4359,7 +4431,7 @@ async function fetchElementWidgetInstancePublic(channelId, widgetInstanceId, pub
|
|
|
4359
4431
|
let detail = "";
|
|
4360
4432
|
try {
|
|
4361
4433
|
const body = JSON.parse(await result.errorBody());
|
|
4362
|
-
if (body?.message) detail = `
|
|
4434
|
+
if (body?.message) detail = ` - ${body.message}`;
|
|
4363
4435
|
} catch {
|
|
4364
4436
|
}
|
|
4365
4437
|
const reason = result.status === 401 || result.status === 403 ? "StreamElements rejected the widget share token. The share link may have been revoked or made private." : result.status === 404 ? "Element widget not found. Double-check the share URL." : `HTTP ${result.status}`;
|
|
@@ -7301,15 +7373,15 @@ var PLACEHOLDER_WIDGET_TYPES = /* @__PURE__ */ new Set([
|
|
|
7301
7373
|
"streamlabels"
|
|
7302
7374
|
]);
|
|
7303
7375
|
var PLACEHOLDER_REASONS = {
|
|
7304
|
-
donation_ticker: "No native Lumia equivalent yet
|
|
7305
|
-
stream_boss: "Lumia has a native streamboss module
|
|
7306
|
-
credits: "Lumia has a native credits module
|
|
7307
|
-
spin_wheel: "Lumia has a native spinwheel module
|
|
7308
|
-
emote_wall: "Lumia has a native emotebox module
|
|
7309
|
-
sub_train: "No native Lumia equivalent yet
|
|
7376
|
+
donation_ticker: "No native Lumia equivalent yet. imported as a placeholder text layer.",
|
|
7377
|
+
stream_boss: "Lumia has a native streamboss module. set it up directly rather than importing the SL config.",
|
|
7378
|
+
credits: "Lumia has a native credits module. set it up directly rather than importing the SL config.",
|
|
7379
|
+
spin_wheel: "Lumia has a native spinwheel module. set it up directly rather than importing the SL config.",
|
|
7380
|
+
emote_wall: "Lumia has a native emotebox module. set it up directly rather than importing the SL config.",
|
|
7381
|
+
sub_train: "No native Lumia equivalent yet. imported as a placeholder text layer.",
|
|
7310
7382
|
streamlabels: "Use Lumia template variables (e.g. {{twitch_last_follower}}) inside a Text module instead of importing the SL streamlabels config."
|
|
7311
7383
|
};
|
|
7312
|
-
var CUSTOM_HTML_REVIEW_REASON = "The Streamlabs widget used custom HTML/CSS/JS that Lumia cannot host. The native Lumia module is the right starting point
|
|
7384
|
+
var CUSTOM_HTML_REVIEW_REASON = "The Streamlabs widget used custom HTML/CSS/JS that Lumia cannot host. The native Lumia module is the right starting point. recreate the styling there. The original SL settings are stashed under module.content.importMeta for reference.";
|
|
7313
7385
|
function stampImportMeta(module, widgetType, role, extra = {}) {
|
|
7314
7386
|
stampImportMetaMerge(module, {
|
|
7315
7387
|
source: "streamlabs",
|
|
@@ -7467,7 +7539,7 @@ function importStreamlabsWidget(response, widgetType, options = {}) {
|
|
|
7467
7539
|
});
|
|
7468
7540
|
reviewItems.push({
|
|
7469
7541
|
moduleId: placeholder.module.id,
|
|
7470
|
-
// SL doesn't have a single "widget" object the way SE does
|
|
7542
|
+
// SL doesn't have a single "widget" object the way SE does. we
|
|
7471
7543
|
// stash the settings snapshot here so the review step can show
|
|
7472
7544
|
// the raw config the user imported. Cast through unknown because
|
|
7473
7545
|
// ReviewItem is currently typed against SEWidget; widening the
|
|
@@ -7485,7 +7557,7 @@ function importStreamlabsWidget(response, widgetType, options = {}) {
|
|
|
7485
7557
|
layers.push(placeholder.layer);
|
|
7486
7558
|
modules[placeholder.layer.id] = placeholder.module;
|
|
7487
7559
|
coverage.notes.push(
|
|
7488
|
-
`Unknown Streamlabs widget type "${widgetType}"
|
|
7560
|
+
`Unknown Streamlabs widget type "${widgetType}". imported as a placeholder.`
|
|
7489
7561
|
);
|
|
7490
7562
|
coverage.mappings.push({
|
|
7491
7563
|
seType: `streamlabs:${widgetType}`,
|
|
@@ -7920,7 +7992,7 @@ function StepHeader({
|
|
|
7920
7992
|
] });
|
|
7921
7993
|
}
|
|
7922
7994
|
var JWT_FEATURES = [
|
|
7923
|
-
"All your overlays
|
|
7995
|
+
"All your overlays: pick one, many, or all",
|
|
7924
7996
|
"Live counters (follower / sub / tip / bits goals)",
|
|
7925
7997
|
"Chat bot commands",
|
|
7926
7998
|
"Loyalty points + watch time",
|
|
@@ -8074,7 +8146,7 @@ function StepModePicker({
|
|
|
8074
8146
|
}
|
|
8075
8147
|
)
|
|
8076
8148
|
] }),
|
|
8077
|
-
caveat: "Requires pasting your StreamElements JWT. Stored only in memory
|
|
8149
|
+
caveat: "Requires pasting your StreamElements JWT. Stored only in memory. Never sent to Lumia.",
|
|
8078
8150
|
onClick: onChooseJwt
|
|
8079
8151
|
}
|
|
8080
8152
|
)
|
|
@@ -8099,7 +8171,7 @@ function StepConnect({
|
|
|
8099
8171
|
{
|
|
8100
8172
|
number: 1,
|
|
8101
8173
|
title: "Connect your StreamElements account",
|
|
8102
|
-
subtitle: "Paste your StreamElements JWT to import overlays, live counters, chat commands, loyalty points, watch time, and recent events. The token is read in your browser only
|
|
8174
|
+
subtitle: "Paste your StreamElements JWT to import overlays, live counters, chat commands, loyalty points, watch time, and recent events. The token is read in your browser only. Lumia never sends it to our servers."
|
|
8103
8175
|
}
|
|
8104
8176
|
),
|
|
8105
8177
|
/* @__PURE__ */ jsx14(
|
|
@@ -10748,15 +10820,15 @@ function importElementOverlay(response, options = {}) {
|
|
|
10748
10820
|
// src/se-import/index.ts
|
|
10749
10821
|
var IMPORT_META_KEY2 = "importMeta";
|
|
10750
10822
|
var REVIEW_REASONS = {
|
|
10751
|
-
"se-widget-bit-boss": "No native Lumia equivalent
|
|
10823
|
+
"se-widget-bit-boss": "No native Lumia equivalent. bit boss fight bar with HP/damage states.",
|
|
10752
10824
|
// `se-widget-hype-cup` intentionally omitted — it now routes to the native
|
|
10753
10825
|
// `tipjar` module (see dispatcher.ts) and lands with `partial` status, so it
|
|
10754
10826
|
// never reaches the placeholder-reason fallback. If a future feature flag
|
|
10755
10827
|
// disables tipjar auto-import, add an entry to FLAG_OFF_REASONS instead.
|
|
10756
|
-
"se-widget-media-share": "No native Lumia equivalent
|
|
10757
|
-
"se-widget-contest": "No native Lumia equivalent
|
|
10758
|
-
"se-widget-giveaway": "No native Lumia equivalent
|
|
10759
|
-
"se-widget-botcounter": "No native Lumia equivalent
|
|
10828
|
+
"se-widget-media-share": "No native Lumia equivalent. viewer-submitted media player.",
|
|
10829
|
+
"se-widget-contest": "No native Lumia equivalent. SE-specific contest widget.",
|
|
10830
|
+
"se-widget-giveaway": "No native Lumia equivalent. SE giveaway service.",
|
|
10831
|
+
"se-widget-botcounter": "No native Lumia equivalent. chatter/message counter.",
|
|
10760
10832
|
"se-widget-top-cheerers-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render cheer rankings.',
|
|
10761
10833
|
"se-widget-top-gifters-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render gifter rankings.',
|
|
10762
10834
|
"se-widget-top-tippers-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render tipper rankings.',
|
|
@@ -10766,7 +10838,7 @@ var REVIEW_REASONS = {
|
|
|
10766
10838
|
// so we can't pre-populate the slideshow items. The streamer adds their
|
|
10767
10839
|
// product images by hand in the slideshow editor. Status = 'partial' (see
|
|
10768
10840
|
// dispatcher.ts), so this string surfaces in the review step.
|
|
10769
|
-
"se-widget-merch-products-rotator": "Imported as an empty slideshow. Lumia doesn't fetch your Fourthwall product catalog yet, so add your product images manually in the slideshow editor
|
|
10841
|
+
"se-widget-merch-products-rotator": "Imported as an empty slideshow. Lumia doesn't fetch your Fourthwall product catalog yet, so add your product images manually in the slideshow editor. the layer is pre-positioned and styled to match your SE widget."
|
|
10770
10842
|
// Seasonal widgets (snow / halloween / halloween-2019 / xmas / valentine / easter
|
|
10771
10843
|
// / world-cup) intentionally omitted — they route directly to image/slideshow/
|
|
10772
10844
|
// video modules now (see dispatcher.ts SEASONAL_IMAGE_TYPES), so they always
|
|
@@ -10774,7 +10846,7 @@ var REVIEW_REASONS = {
|
|
|
10774
10846
|
};
|
|
10775
10847
|
function reasonFor(seType, status, flaggedOff) {
|
|
10776
10848
|
if (flaggedOff && FLAG_OFF_REASONS[seType]) return FLAG_OFF_REASONS[seType];
|
|
10777
|
-
return REVIEW_REASONS[seType] ?? (status === "template" ? "Imported as a generic template stub." : "No native mapping yet
|
|
10849
|
+
return REVIEW_REASONS[seType] ?? (status === "template" ? "Imported as a generic template stub." : "No native mapping yet. imported as a labelled text placeholder.");
|
|
10778
10850
|
}
|
|
10779
10851
|
function buildSEBootstrapDescription(overlayId, sourceUrl) {
|
|
10780
10852
|
const head = overlayId ? `Imported from StreamElements overlay ${overlayId}` : "Imported from StreamElements";
|
|
@@ -10804,7 +10876,7 @@ async function fetchSEBootstrap({
|
|
|
10804
10876
|
}) {
|
|
10805
10877
|
if (!apikey) {
|
|
10806
10878
|
throw new Error(
|
|
10807
|
-
"Missing access token. Use the public preview URL (Overlay menu \u2192 Get URL \u2192 Public link)
|
|
10879
|
+
"Missing access token. Use the public preview URL (Overlay menu \u2192 Get URL \u2192 Public link). editor URLs do not include a token."
|
|
10808
10880
|
);
|
|
10809
10881
|
}
|
|
10810
10882
|
const result = await fetchWithProxyFallback(buildBootstrapUrl(overlayId), {
|
|
@@ -10815,7 +10887,7 @@ async function fetchSEBootstrap({
|
|
|
10815
10887
|
let detail = "";
|
|
10816
10888
|
try {
|
|
10817
10889
|
const body = JSON.parse(await result.errorBody());
|
|
10818
|
-
detail = body?.message ?
|
|
10890
|
+
detail = body?.message ? `. ${body.message}` : "";
|
|
10819
10891
|
} catch {
|
|
10820
10892
|
}
|
|
10821
10893
|
const reason = result.status === 401 || result.status === 403 ? "StreamElements rejected the URL token. The preview link may have been revoked." : result.status === 404 ? "Overlay not found. Double-check the URL." : `HTTP ${result.status}`;
|
|
@@ -10957,17 +11029,17 @@ function importSEBootstrap(bootstrap, options = {}) {
|
|
|
10957
11029
|
}
|
|
10958
11030
|
if (coverage.mappings.some((m) => m.status === "placeholder")) {
|
|
10959
11031
|
coverage.notes.push(
|
|
10960
|
-
"Some widgets imported as placeholder text layers
|
|
11032
|
+
"Some widgets imported as placeholder text layers. see SE/05-widget-to-module-mapping.md for status."
|
|
10961
11033
|
);
|
|
10962
11034
|
}
|
|
10963
11035
|
if (coverage.mappings.some((m) => m.seType === "se-widget-merch-goal")) {
|
|
10964
11036
|
coverage.notes.push(
|
|
10965
|
-
"Merch-goal widgets imported as generic goals
|
|
11037
|
+
"Merch-goal widgets imported as generic goals. see SE/03-missing-variables.md \xA78 for the missing data source."
|
|
10966
11038
|
);
|
|
10967
11039
|
}
|
|
10968
11040
|
if (coverage.mappings.some((m) => m.seType === "se-widget-custom-event-list")) {
|
|
10969
11041
|
coverage.notes.push(
|
|
10970
|
-
"\u26A0 Custom widgets are experimental
|
|
11042
|
+
"\u26A0 Custom widgets are experimental. they run under the SE compatibility shim (Embed.tsx) which handles most SE APIs but may not cover every edge case. Verify each custom layer renders correctly in the editor."
|
|
10971
11043
|
);
|
|
10972
11044
|
}
|
|
10973
11045
|
const overlay = {
|
|
@@ -11112,7 +11184,7 @@ function buildAIPromptForSEWidget(widget, canvas) {
|
|
|
11112
11184
|
const fullVariables = JSON.stringify(variables, null, 2);
|
|
11113
11185
|
const truncated = fullVariables.length > VARIABLES_BUDGET;
|
|
11114
11186
|
const variablesJson = truncated ? `${fullVariables.slice(0, VARIABLES_BUDGET)}
|
|
11115
|
-
\u2026[truncated
|
|
11187
|
+
\u2026[truncated. ${fullVariables.length - VARIABLES_BUDGET} more bytes elided]` : fullVariables;
|
|
11116
11188
|
return [
|
|
11117
11189
|
"Recreate a StreamElements overlay widget as a Lumia Stream custom overlay (HTML + CSS + JS).",
|
|
11118
11190
|
"",
|
|
@@ -11120,14 +11192,14 @@ function buildAIPromptForSEWidget(widget, canvas) {
|
|
|
11120
11192
|
listener ? `Primary event listener: ${listener}` : "",
|
|
11121
11193
|
listeners ? `Subscribes to events: ${listeners.join(", ")}` : "",
|
|
11122
11194
|
`Canvas: ${landing.width} \xD7 ${landing.height}`,
|
|
11123
|
-
truncated ? "Note: the widget config below is truncated
|
|
11195
|
+
truncated ? "Note: the widget config below is truncated. focus on visible/structural fields and ignore tail content you cannot see." : "",
|
|
11124
11196
|
"",
|
|
11125
11197
|
"Widget configuration (JSON):",
|
|
11126
11198
|
"```json",
|
|
11127
11199
|
variablesJson,
|
|
11128
11200
|
"```",
|
|
11129
11201
|
"",
|
|
11130
|
-
"Constraints
|
|
11202
|
+
"Constraints. your output must run inside a Lumia Custom Overlay sandboxed iframe:",
|
|
11131
11203
|
'- Use `window.Overlay.on("alert", handler)` / `window.Overlay.on("chat", handler)` to receive Lumia events.',
|
|
11132
11204
|
"- Lumia template variables use double-brace syntax: {{username}}, {{amount}}, {{message}}, {{currency}}.",
|
|
11133
11205
|
"- The `window.DATA` object holds user-configurable values (mirrors your `configs`).",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumiastream/ui",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"author": "Lumia Stream",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"description": "Lumia UI Kit",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"vitest": "^4.1.6"
|
|
129
129
|
},
|
|
130
130
|
"dependencies": {
|
|
131
|
-
"@lumiastream/lumia-translations": "1.16.
|
|
131
|
+
"@lumiastream/lumia-translations": "1.16.1",
|
|
132
132
|
"@lumiastream/lumia-types": "^3.3.7-alpha.2",
|
|
133
133
|
"classnames": "^2.5.1",
|
|
134
134
|
"globals": "^17.4.0",
|