@thestatic-tv/dcl-sdk 2.2.10 → 2.3.0-dev.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/README.md +186 -50
- package/dist/index.d.mts +254 -10
- package/dist/index.d.ts +254 -10
- package/dist/index.js +1135 -13
- package/dist/index.mjs +1134 -13
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -96,8 +96,8 @@ function getPlayerWallet() {
|
|
|
96
96
|
return cachedWallet;
|
|
97
97
|
}
|
|
98
98
|
try {
|
|
99
|
-
const { getPlayer:
|
|
100
|
-
const player =
|
|
99
|
+
const { getPlayer: getPlayer3 } = __require("@dcl/sdk/players");
|
|
100
|
+
const player = getPlayer3();
|
|
101
101
|
return player?.userId ?? null;
|
|
102
102
|
} catch {
|
|
103
103
|
return null;
|
|
@@ -108,8 +108,8 @@ function getPlayerDisplayName() {
|
|
|
108
108
|
return cachedDisplayName;
|
|
109
109
|
}
|
|
110
110
|
try {
|
|
111
|
-
const { getPlayer:
|
|
112
|
-
const player =
|
|
111
|
+
const { getPlayer: getPlayer3 } = __require("@dcl/sdk/players");
|
|
112
|
+
const player = getPlayer3();
|
|
113
113
|
return player?.name ?? null;
|
|
114
114
|
} catch {
|
|
115
115
|
return null;
|
|
@@ -2121,6 +2121,1064 @@ var ChatUIModule = class {
|
|
|
2121
2121
|
}
|
|
2122
2122
|
};
|
|
2123
2123
|
|
|
2124
|
+
// src/ui/admin-panel-ui.tsx
|
|
2125
|
+
import ReactEcs4, { UiEntity as UiEntity4, Button as Button3, Label as Label4, Input as Input4 } from "@dcl/sdk/react-ecs";
|
|
2126
|
+
import { Color4 as Color45, Vector3 } from "@dcl/sdk/math";
|
|
2127
|
+
import { getPlayer as getPlayer2 } from "@dcl/sdk/players";
|
|
2128
|
+
import { movePlayerTo, openExternalUrl as openExternalUrl3 } from "~system/RestrictedActions";
|
|
2129
|
+
var BAN_KICK_POSITION = Vector3.create(16, -50, 16);
|
|
2130
|
+
var C = {
|
|
2131
|
+
bg: Color45.create(0.08, 0.08, 0.12, 0.98),
|
|
2132
|
+
header: Color45.create(0.9, 0.15, 0.15, 1),
|
|
2133
|
+
tabActive: Color45.create(0, 0.7, 0.7, 1),
|
|
2134
|
+
tabInactive: Color45.create(0.15, 0.15, 0.2, 1),
|
|
2135
|
+
section: Color45.create(0.12, 0.12, 0.18, 1),
|
|
2136
|
+
btn: Color45.create(0.2, 0.2, 0.28, 1),
|
|
2137
|
+
cyan: Color45.create(0, 0.8, 0.8, 1),
|
|
2138
|
+
magenta: Color45.create(0.85, 0.2, 0.55, 1),
|
|
2139
|
+
yellow: Color45.create(0.95, 0.75, 0.1, 1),
|
|
2140
|
+
green: Color45.create(0.2, 0.75, 0.3, 1),
|
|
2141
|
+
red: Color45.create(0.85, 0.2, 0.2, 1),
|
|
2142
|
+
purple: Color45.create(0.6, 0.3, 0.85, 1),
|
|
2143
|
+
orange: Color45.create(0.95, 0.5, 0.15, 1),
|
|
2144
|
+
text: Color45.White(),
|
|
2145
|
+
textDim: Color45.create(0.6, 0.6, 0.7, 1)
|
|
2146
|
+
};
|
|
2147
|
+
var AdminPanelUIModule = class {
|
|
2148
|
+
constructor(client, config) {
|
|
2149
|
+
// State
|
|
2150
|
+
this.isAdmin = false;
|
|
2151
|
+
this.isOwner = false;
|
|
2152
|
+
this.panelOpen = false;
|
|
2153
|
+
this.activeTab = "video";
|
|
2154
|
+
this.playerWallet = "";
|
|
2155
|
+
// Video tab state
|
|
2156
|
+
this.customVideoUrl = "";
|
|
2157
|
+
this.streamData = null;
|
|
2158
|
+
this.streamFetched = false;
|
|
2159
|
+
this.channelCreating = false;
|
|
2160
|
+
this.channelCreateError = "";
|
|
2161
|
+
this.channelDeleting = false;
|
|
2162
|
+
this.channelDeleteError = "";
|
|
2163
|
+
this.keyRotating = false;
|
|
2164
|
+
this.keyRotateStatus = "";
|
|
2165
|
+
this.streamControlling = false;
|
|
2166
|
+
this.streamControlStatus = "";
|
|
2167
|
+
this.pollIntervalId = null;
|
|
2168
|
+
this.trialClaiming = false;
|
|
2169
|
+
this.trialClaimError = "";
|
|
2170
|
+
// Mod tab state
|
|
2171
|
+
this.sceneAdmins = [];
|
|
2172
|
+
this.bannedWallets = [];
|
|
2173
|
+
this.newAdminWallet = "";
|
|
2174
|
+
this.newBanWallet = "";
|
|
2175
|
+
this.broadcastText = "";
|
|
2176
|
+
this.modStatus = "";
|
|
2177
|
+
this.modsFetched = false;
|
|
2178
|
+
// --- UI Components ---
|
|
2179
|
+
this.SectionHead = ({ label, color }) => /* @__PURE__ */ ReactEcs4.createElement(
|
|
2180
|
+
UiEntity4,
|
|
2181
|
+
{
|
|
2182
|
+
uiTransform: { width: "100%", height: 24, margin: { bottom: 6 }, padding: { left: 8 }, alignItems: "center" },
|
|
2183
|
+
uiBackground: { color: Color45.create(color.r * 0.3, color.g * 0.3, color.b * 0.3, 0.5) }
|
|
2184
|
+
},
|
|
2185
|
+
/* @__PURE__ */ ReactEcs4.createElement(Label4, { value: label, fontSize: 12, color })
|
|
2186
|
+
);
|
|
2187
|
+
this.TabBtn = ({ label, tab }) => /* @__PURE__ */ ReactEcs4.createElement(
|
|
2188
|
+
Button3,
|
|
2189
|
+
{
|
|
2190
|
+
uiTransform: { flexGrow: 1, height: 36, justifyContent: "center", alignItems: "center" },
|
|
2191
|
+
uiBackground: { color: this.activeTab === tab ? C.tabActive : C.tabInactive },
|
|
2192
|
+
value: label,
|
|
2193
|
+
fontSize: 12,
|
|
2194
|
+
color: this.activeTab === tab ? Color45.Black() : C.text,
|
|
2195
|
+
textAlign: "middle-center",
|
|
2196
|
+
onMouseDown: () => this.setActiveTab(tab)
|
|
2197
|
+
}
|
|
2198
|
+
);
|
|
2199
|
+
this.VideoTab = () => {
|
|
2200
|
+
if (!this.streamFetched) {
|
|
2201
|
+
this.fetchStreamData();
|
|
2202
|
+
}
|
|
2203
|
+
return /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", width: "100%", padding: 8 } }, !this.streamData?.hasChannel && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 12 } } }, /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "LIVE STREAM", color: C.btn }), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "No streaming channel linked", fontSize: 10, color: C.textDim, uiTransform: { margin: { bottom: 6 } } }), this.isOwner && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column" } }, this.streamData?.trialAvailable && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 8 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2204
|
+
Button3,
|
|
2205
|
+
{
|
|
2206
|
+
uiTransform: { width: 180, height: 40, margin: { bottom: 4 } },
|
|
2207
|
+
uiBackground: { color: this.trialClaiming ? C.btn : C.green },
|
|
2208
|
+
value: this.trialClaiming ? "Claiming..." : "\u{1F381} Start Free 4-Hour Trial",
|
|
2209
|
+
fontSize: 11,
|
|
2210
|
+
color: C.text,
|
|
2211
|
+
onMouseDown: () => this.claimTrial()
|
|
2212
|
+
}
|
|
2213
|
+
), this.trialClaimError && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.trialClaimError, fontSize: 9, color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "One-time trial \u2022 4 hours of streaming", fontSize: 8, color: C.textDim, uiTransform: { margin: { top: 2 } } })), !this.streamData?.trialAvailable && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column" } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2214
|
+
Button3,
|
|
2215
|
+
{
|
|
2216
|
+
uiTransform: { width: 150, height: 36, margin: { bottom: 4 } },
|
|
2217
|
+
uiBackground: { color: this.channelCreating ? C.btn : C.cyan },
|
|
2218
|
+
value: this.channelCreating ? "Creating..." : "+ Create Channel",
|
|
2219
|
+
fontSize: 11,
|
|
2220
|
+
color: C.text,
|
|
2221
|
+
onMouseDown: () => this.createChannel()
|
|
2222
|
+
}
|
|
2223
|
+
), this.channelCreateError && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.channelCreateError, fontSize: 9, color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Relay tier \u2022 $25/mo \u2022 8 hours streaming", fontSize: 8, color: C.textDim, uiTransform: { margin: { top: 2 } } })))), this.streamData?.hasChannel && this.isOwner && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 12 } } }, /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: this.streamData.isLive ? "\u{1F534} LIVE STREAM" : "STREAM SETTINGS", color: this.streamData.isLive ? C.red : C.yellow }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 6 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2224
|
+
Label4,
|
|
2225
|
+
{
|
|
2226
|
+
value: this.streamData.isLive ? `LIVE \u2022 ${this.streamData.currentViewers || 0} viewers` : "OFFLINE",
|
|
2227
|
+
fontSize: 11,
|
|
2228
|
+
color: this.streamData.isLive ? C.red : C.textDim
|
|
2229
|
+
}
|
|
2230
|
+
), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: ` \u2022 ${this.streamData.tier?.toUpperCase() || "RELAY"}`, fontSize: 10, color: C.cyan }), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: ` \u2022 ${this.streamData.sparksBalance || 0} Sparks`, fontSize: 10, color: C.textDim })), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2231
|
+
Label4,
|
|
2232
|
+
{
|
|
2233
|
+
value: `Channel: ${this.streamData.channelName || this.streamData.channelId}`,
|
|
2234
|
+
fontSize: 9,
|
|
2235
|
+
color: C.textDim,
|
|
2236
|
+
uiTransform: { margin: { bottom: 8 } }
|
|
2237
|
+
}
|
|
2238
|
+
), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 4 } } }, /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "RTMP Server:", fontSize: 9, color: C.textDim }), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2239
|
+
Label4,
|
|
2240
|
+
{
|
|
2241
|
+
value: this.streamData.rtmpUrl || "Loading...",
|
|
2242
|
+
fontSize: 10,
|
|
2243
|
+
color: this.streamData.rtmpUrl ? C.text : C.textDim,
|
|
2244
|
+
uiTransform: { margin: { left: 4 } }
|
|
2245
|
+
}
|
|
2246
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 4 } } }, /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Stream Key:", fontSize: 9, color: C.textDim }), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2247
|
+
Label4,
|
|
2248
|
+
{
|
|
2249
|
+
value: this.streamData.streamKey || "Loading...",
|
|
2250
|
+
fontSize: 10,
|
|
2251
|
+
color: this.streamData.streamKey ? C.cyan : C.textDim,
|
|
2252
|
+
uiTransform: { margin: { left: 4 } }
|
|
2253
|
+
}
|
|
2254
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 8 } } }, /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "HLS Playback:", fontSize: 9, color: C.textDim }), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2255
|
+
Label4,
|
|
2256
|
+
{
|
|
2257
|
+
value: this.streamData.hlsUrl || "Not available",
|
|
2258
|
+
fontSize: 10,
|
|
2259
|
+
color: this.streamData.hlsUrl ? C.green : C.textDim,
|
|
2260
|
+
uiTransform: { margin: { left: 4 } }
|
|
2261
|
+
}
|
|
2262
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 4 } } }, !this.streamData.isLive && /* @__PURE__ */ ReactEcs4.createElement(
|
|
2263
|
+
Button3,
|
|
2264
|
+
{
|
|
2265
|
+
uiTransform: { width: 100, height: 32, margin: 3 },
|
|
2266
|
+
uiBackground: { color: this.streamControlling ? C.btn : C.green },
|
|
2267
|
+
value: this.streamControlling ? "Starting..." : "Start Stream",
|
|
2268
|
+
fontSize: 10,
|
|
2269
|
+
color: C.text,
|
|
2270
|
+
onMouseDown: () => this.startStream()
|
|
2271
|
+
}
|
|
2272
|
+
), this.streamData.isLive && /* @__PURE__ */ ReactEcs4.createElement(
|
|
2273
|
+
Button3,
|
|
2274
|
+
{
|
|
2275
|
+
uiTransform: { width: 100, height: 32, margin: 3 },
|
|
2276
|
+
uiBackground: { color: this.streamControlling ? C.btn : C.red },
|
|
2277
|
+
value: this.streamControlling ? "Stopping..." : "Stop Stream",
|
|
2278
|
+
fontSize: 10,
|
|
2279
|
+
color: C.text,
|
|
2280
|
+
onMouseDown: () => this.stopStream()
|
|
2281
|
+
}
|
|
2282
|
+
), this.streamData.isLive && this.streamData.hlsUrl && /* @__PURE__ */ ReactEcs4.createElement(
|
|
2283
|
+
Button3,
|
|
2284
|
+
{
|
|
2285
|
+
uiTransform: { width: 90, height: 32, margin: 3 },
|
|
2286
|
+
uiBackground: { color: C.cyan },
|
|
2287
|
+
value: "Play on Screen",
|
|
2288
|
+
fontSize: 9,
|
|
2289
|
+
color: C.text,
|
|
2290
|
+
onMouseDown: () => this.config.onVideoPlay?.(this.streamData.hlsUrl)
|
|
2291
|
+
}
|
|
2292
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 4 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2293
|
+
Button3,
|
|
2294
|
+
{
|
|
2295
|
+
uiTransform: { width: 70, height: 28, margin: 3 },
|
|
2296
|
+
uiBackground: { color: C.btn },
|
|
2297
|
+
value: "Refresh",
|
|
2298
|
+
fontSize: 9,
|
|
2299
|
+
color: C.text,
|
|
2300
|
+
onMouseDown: () => this.refreshStreamStatus()
|
|
2301
|
+
}
|
|
2302
|
+
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2303
|
+
Button3,
|
|
2304
|
+
{
|
|
2305
|
+
uiTransform: { width: 85, height: 28, margin: 3 },
|
|
2306
|
+
uiBackground: { color: this.keyRotating ? C.btn : C.yellow },
|
|
2307
|
+
value: this.keyRotating ? "..." : "Rotate Key",
|
|
2308
|
+
fontSize: 9,
|
|
2309
|
+
color: C.text,
|
|
2310
|
+
onMouseDown: () => this.rotateStreamKey()
|
|
2311
|
+
}
|
|
2312
|
+
), !this.streamData.isLive && /* @__PURE__ */ ReactEcs4.createElement(
|
|
2313
|
+
Button3,
|
|
2314
|
+
{
|
|
2315
|
+
uiTransform: { width: 60, height: 28, margin: 3 },
|
|
2316
|
+
uiBackground: { color: this.channelDeleting ? C.btn : C.red },
|
|
2317
|
+
value: this.channelDeleting ? "..." : "Delete",
|
|
2318
|
+
fontSize: 9,
|
|
2319
|
+
color: C.text,
|
|
2320
|
+
onMouseDown: () => this.deleteChannel()
|
|
2321
|
+
}
|
|
2322
|
+
)), this.streamControlStatus === "started" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Stream started - begin broadcasting in OBS", fontSize: 9, color: C.green }), this.streamControlStatus === "stopped" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Stream stopped", fontSize: 9, color: C.textDim }), this.keyRotateStatus === "success" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Key rotated! Update OBS", fontSize: 9, color: C.green }), this.keyRotateStatus === "error" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Failed to rotate key", fontSize: 9, color: C.red }), this.channelDeleteError && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.channelDeleteError, fontSize: 9, color: C.red }), this.streamControlStatus === "error" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Stream control failed", fontSize: 9, color: C.red })), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "PLAY NOW", color: C.orange }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 12 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2323
|
+
Input4,
|
|
2324
|
+
{
|
|
2325
|
+
uiTransform: { width: 220, height: 32 },
|
|
2326
|
+
uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
|
|
2327
|
+
placeholder: "Video URL...",
|
|
2328
|
+
placeholderColor: C.textDim,
|
|
2329
|
+
color: C.text,
|
|
2330
|
+
fontSize: 11,
|
|
2331
|
+
value: this.customVideoUrl,
|
|
2332
|
+
onChange: (val) => {
|
|
2333
|
+
this.customVideoUrl = val;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2337
|
+
Button3,
|
|
2338
|
+
{
|
|
2339
|
+
uiTransform: { width: 70, height: 32, margin: { left: 6 } },
|
|
2340
|
+
uiBackground: { color: C.green },
|
|
2341
|
+
value: "Play",
|
|
2342
|
+
fontSize: 11,
|
|
2343
|
+
color: C.text,
|
|
2344
|
+
onMouseDown: () => {
|
|
2345
|
+
if (this.customVideoUrl) this.config.onVideoPlay?.(this.customVideoUrl);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2349
|
+
Button3,
|
|
2350
|
+
{
|
|
2351
|
+
uiTransform: { width: 60, height: 32, margin: { left: 4 } },
|
|
2352
|
+
uiBackground: { color: C.btn },
|
|
2353
|
+
value: "Clear",
|
|
2354
|
+
fontSize: 11,
|
|
2355
|
+
color: C.text,
|
|
2356
|
+
onMouseDown: () => {
|
|
2357
|
+
this.customVideoUrl = "";
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "PLAYBACK", color: C.green }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 12 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2361
|
+
Button3,
|
|
2362
|
+
{
|
|
2363
|
+
uiTransform: { width: 70, height: 32, margin: 3 },
|
|
2364
|
+
uiBackground: { color: C.green },
|
|
2365
|
+
value: "Play",
|
|
2366
|
+
fontSize: 11,
|
|
2367
|
+
color: C.text,
|
|
2368
|
+
onMouseDown: () => this.config.onCommand?.("videoPlay", { playing: true })
|
|
2369
|
+
}
|
|
2370
|
+
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2371
|
+
Button3,
|
|
2372
|
+
{
|
|
2373
|
+
uiTransform: { width: 70, height: 32, margin: 3 },
|
|
2374
|
+
uiBackground: { color: C.red },
|
|
2375
|
+
value: "Stop",
|
|
2376
|
+
fontSize: 11,
|
|
2377
|
+
color: C.text,
|
|
2378
|
+
onMouseDown: () => this.config.onVideoStop?.()
|
|
2379
|
+
}
|
|
2380
|
+
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2381
|
+
Button3,
|
|
2382
|
+
{
|
|
2383
|
+
uiTransform: { width: 90, height: 32, margin: 3 },
|
|
2384
|
+
uiBackground: { color: C.btn },
|
|
2385
|
+
value: "Reset Default",
|
|
2386
|
+
fontSize: 10,
|
|
2387
|
+
color: C.text,
|
|
2388
|
+
onMouseDown: () => this.config.onCommand?.("videoClear", {})
|
|
2389
|
+
}
|
|
2390
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "VIDEO SLOTS", color: C.cyan }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 8 } } }, /* @__PURE__ */ ReactEcs4.createElement(Button3, { uiTransform: { width: 65, height: 32, margin: 3 }, uiBackground: { color: C.cyan }, value: "Play 1", fontSize: 11, color: C.text, onMouseDown: () => this.config.onVideoSlotPlay?.("slot1") }), /* @__PURE__ */ ReactEcs4.createElement(Button3, { uiTransform: { width: 65, height: 32, margin: 3 }, uiBackground: { color: C.cyan }, value: "Play 2", fontSize: 11, color: C.text, onMouseDown: () => this.config.onVideoSlotPlay?.("slot2") }), /* @__PURE__ */ ReactEcs4.createElement(Button3, { uiTransform: { width: 65, height: 32, margin: 3 }, uiBackground: { color: C.cyan }, value: "Play 3", fontSize: 11, color: C.text, onMouseDown: () => this.config.onVideoSlotPlay?.("slot3") }), /* @__PURE__ */ ReactEcs4.createElement(Button3, { uiTransform: { width: 65, height: 32, margin: 3 }, uiBackground: { color: C.cyan }, value: "Play 4", fontSize: 11, color: C.text, onMouseDown: () => this.config.onVideoSlotPlay?.("slot4") }), /* @__PURE__ */ ReactEcs4.createElement(Button3, { uiTransform: { width: 65, height: 32, margin: 3 }, uiBackground: { color: C.cyan }, value: "Play 5", fontSize: 11, color: C.text, onMouseDown: () => this.config.onVideoSlotPlay?.("slot5") })), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2391
|
+
Button3,
|
|
2392
|
+
{
|
|
2393
|
+
uiTransform: { height: 20 },
|
|
2394
|
+
uiBackground: { color: Color45.create(0, 0, 0, 0) },
|
|
2395
|
+
value: "Edit slots at thestatic.tv \u2192",
|
|
2396
|
+
fontSize: 10,
|
|
2397
|
+
color: C.cyan,
|
|
2398
|
+
onMouseDown: () => openExternalUrl3({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
|
|
2399
|
+
}
|
|
2400
|
+
));
|
|
2401
|
+
};
|
|
2402
|
+
this.ModTab = () => /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", width: "100%", padding: 8 } }, this.modStatus === "loading" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Loading...", fontSize: 11, color: C.yellow }), this.modStatus === "saved" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Saved!", fontSize: 11, color: C.green }), this.modStatus === "error" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Error - check input", fontSize: 11, color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "BROADCAST", color: C.orange }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 12 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2403
|
+
Input4,
|
|
2404
|
+
{
|
|
2405
|
+
uiTransform: { width: 200, height: 32 },
|
|
2406
|
+
uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
|
|
2407
|
+
placeholder: "Message to all players...",
|
|
2408
|
+
placeholderColor: C.textDim,
|
|
2409
|
+
color: C.text,
|
|
2410
|
+
fontSize: 11,
|
|
2411
|
+
value: this.broadcastText,
|
|
2412
|
+
onChange: (val) => {
|
|
2413
|
+
this.broadcastText = val;
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2417
|
+
Button3,
|
|
2418
|
+
{
|
|
2419
|
+
uiTransform: { width: 55, height: 32, margin: { left: 6 } },
|
|
2420
|
+
uiBackground: { color: C.orange },
|
|
2421
|
+
value: "Send",
|
|
2422
|
+
fontSize: 11,
|
|
2423
|
+
color: C.text,
|
|
2424
|
+
onMouseDown: () => this.sendBroadcast()
|
|
2425
|
+
}
|
|
2426
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "CHAOS MODE", color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 12 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2427
|
+
Button3,
|
|
2428
|
+
{
|
|
2429
|
+
uiTransform: { width: 120, height: 32, margin: 3 },
|
|
2430
|
+
uiBackground: { color: C.red },
|
|
2431
|
+
value: "KICK ALL",
|
|
2432
|
+
fontSize: 11,
|
|
2433
|
+
color: C.text,
|
|
2434
|
+
onMouseDown: () => this.config.onCommand?.("kickAll", {})
|
|
2435
|
+
}
|
|
2436
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "SCENE ADMINS", color: C.purple }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 4 } } }, this.sceneAdmins.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "No scene admins", fontSize: 10, color: C.textDim }), this.sceneAdmins.map((wallet, i) => /* @__PURE__ */ ReactEcs4.createElement(
|
|
2437
|
+
UiEntity4,
|
|
2438
|
+
{
|
|
2439
|
+
key: `admin-${i}`,
|
|
2440
|
+
uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 3 }, width: "100%" }
|
|
2441
|
+
},
|
|
2442
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2443
|
+
Label4,
|
|
2444
|
+
{
|
|
2445
|
+
value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
|
|
2446
|
+
fontSize: 10,
|
|
2447
|
+
color: C.text,
|
|
2448
|
+
uiTransform: { width: 110 }
|
|
2449
|
+
}
|
|
2450
|
+
),
|
|
2451
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2452
|
+
Button3,
|
|
2453
|
+
{
|
|
2454
|
+
uiTransform: { width: 55, height: 24, margin: { left: 6 } },
|
|
2455
|
+
uiBackground: { color: C.btn },
|
|
2456
|
+
value: "Remove",
|
|
2457
|
+
fontSize: 9,
|
|
2458
|
+
color: C.text,
|
|
2459
|
+
onMouseDown: () => this.removeSceneAdmin(wallet)
|
|
2460
|
+
}
|
|
2461
|
+
)
|
|
2462
|
+
))), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 12 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2463
|
+
Input4,
|
|
2464
|
+
{
|
|
2465
|
+
uiTransform: { width: 200, height: 28 },
|
|
2466
|
+
uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
|
|
2467
|
+
placeholder: "0x... add admin",
|
|
2468
|
+
placeholderColor: C.textDim,
|
|
2469
|
+
color: C.text,
|
|
2470
|
+
fontSize: 10,
|
|
2471
|
+
value: this.newAdminWallet,
|
|
2472
|
+
onChange: (val) => {
|
|
2473
|
+
this.newAdminWallet = val;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2477
|
+
Button3,
|
|
2478
|
+
{
|
|
2479
|
+
uiTransform: { width: 50, height: 28, margin: { left: 6 } },
|
|
2480
|
+
uiBackground: { color: C.purple },
|
|
2481
|
+
value: "Add",
|
|
2482
|
+
fontSize: 10,
|
|
2483
|
+
color: C.text,
|
|
2484
|
+
onMouseDown: () => {
|
|
2485
|
+
if (this.newAdminWallet) this.addSceneAdmin(this.newAdminWallet);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "BANNED WALLETS", color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 4 } } }, this.bannedWallets.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "No banned wallets", fontSize: 10, color: C.textDim }), this.bannedWallets.map((wallet, i) => /* @__PURE__ */ ReactEcs4.createElement(
|
|
2489
|
+
UiEntity4,
|
|
2490
|
+
{
|
|
2491
|
+
key: `ban-${i}`,
|
|
2492
|
+
uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 3 }, width: "100%" }
|
|
2493
|
+
},
|
|
2494
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2495
|
+
Label4,
|
|
2496
|
+
{
|
|
2497
|
+
value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
|
|
2498
|
+
fontSize: 10,
|
|
2499
|
+
color: C.red,
|
|
2500
|
+
uiTransform: { width: 110 }
|
|
2501
|
+
}
|
|
2502
|
+
),
|
|
2503
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2504
|
+
Button3,
|
|
2505
|
+
{
|
|
2506
|
+
uiTransform: { width: 55, height: 24, margin: { left: 6 } },
|
|
2507
|
+
uiBackground: { color: C.green },
|
|
2508
|
+
value: "Unban",
|
|
2509
|
+
fontSize: 9,
|
|
2510
|
+
color: C.text,
|
|
2511
|
+
onMouseDown: () => this.unbanWallet(wallet)
|
|
2512
|
+
}
|
|
2513
|
+
)
|
|
2514
|
+
))), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 8 } } }, /* @__PURE__ */ ReactEcs4.createElement(
|
|
2515
|
+
Input4,
|
|
2516
|
+
{
|
|
2517
|
+
uiTransform: { width: 200, height: 28 },
|
|
2518
|
+
uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
|
|
2519
|
+
placeholder: "0x... ban wallet",
|
|
2520
|
+
placeholderColor: C.textDim,
|
|
2521
|
+
color: C.text,
|
|
2522
|
+
fontSize: 10,
|
|
2523
|
+
value: this.newBanWallet,
|
|
2524
|
+
onChange: (val) => {
|
|
2525
|
+
this.newBanWallet = val;
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2529
|
+
Button3,
|
|
2530
|
+
{
|
|
2531
|
+
uiTransform: { width: 50, height: 28, margin: { left: 6 } },
|
|
2532
|
+
uiBackground: { color: C.red },
|
|
2533
|
+
value: "Ban",
|
|
2534
|
+
fontSize: 10,
|
|
2535
|
+
color: C.text,
|
|
2536
|
+
onMouseDown: () => {
|
|
2537
|
+
if (this.newBanWallet) this.banWallet(this.newBanWallet);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
)), /* @__PURE__ */ ReactEcs4.createElement(
|
|
2541
|
+
Button3,
|
|
2542
|
+
{
|
|
2543
|
+
uiTransform: { height: 20 },
|
|
2544
|
+
uiBackground: { color: Color45.create(0, 0, 0, 0) },
|
|
2545
|
+
value: "Manage at thestatic.tv \u2192",
|
|
2546
|
+
fontSize: 9,
|
|
2547
|
+
color: C.cyan,
|
|
2548
|
+
onMouseDown: () => openExternalUrl3({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
|
|
2549
|
+
}
|
|
2550
|
+
));
|
|
2551
|
+
/**
|
|
2552
|
+
* Get the React-ECS component for the admin panel
|
|
2553
|
+
*/
|
|
2554
|
+
this.getComponent = () => {
|
|
2555
|
+
if (!this.isAdmin) return null;
|
|
2556
|
+
const tabs = [];
|
|
2557
|
+
if (this.config.sceneTabs && this.config.sceneTabs.length > 0) {
|
|
2558
|
+
this.config.sceneTabs.forEach((tab) => tabs.push({ label: tab.label, id: tab.id }));
|
|
2559
|
+
}
|
|
2560
|
+
if (this.config.showVideoTab !== false) {
|
|
2561
|
+
tabs.push({ label: "VIDEO", id: "video" });
|
|
2562
|
+
}
|
|
2563
|
+
if (this.config.showModTab !== false && this.isOwner) {
|
|
2564
|
+
tabs.push({ label: "MOD", id: "mod" });
|
|
2565
|
+
}
|
|
2566
|
+
if (!tabs.find((t) => t.id === this.activeTab) && tabs.length > 0) {
|
|
2567
|
+
this.activeTab = tabs[0].id;
|
|
2568
|
+
}
|
|
2569
|
+
return /* @__PURE__ */ ReactEcs4.createElement(
|
|
2570
|
+
UiEntity4,
|
|
2571
|
+
{
|
|
2572
|
+
uiTransform: {
|
|
2573
|
+
width: "100%",
|
|
2574
|
+
height: "100%",
|
|
2575
|
+
positionType: "absolute"
|
|
2576
|
+
}
|
|
2577
|
+
},
|
|
2578
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2579
|
+
UiEntity4,
|
|
2580
|
+
{
|
|
2581
|
+
uiTransform: {
|
|
2582
|
+
position: { right: 16, bottom: 16 },
|
|
2583
|
+
positionType: "absolute"
|
|
2584
|
+
}
|
|
2585
|
+
},
|
|
2586
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2587
|
+
Button3,
|
|
2588
|
+
{
|
|
2589
|
+
uiTransform: { width: 90, height: 36 },
|
|
2590
|
+
uiBackground: { color: this.panelOpen ? C.btn : C.header },
|
|
2591
|
+
value: this.panelOpen ? "CLOSE" : "ADMIN",
|
|
2592
|
+
fontSize: 13,
|
|
2593
|
+
color: C.text,
|
|
2594
|
+
onMouseDown: () => this.toggle()
|
|
2595
|
+
}
|
|
2596
|
+
)
|
|
2597
|
+
),
|
|
2598
|
+
this.panelOpen && /* @__PURE__ */ ReactEcs4.createElement(
|
|
2599
|
+
UiEntity4,
|
|
2600
|
+
{
|
|
2601
|
+
uiTransform: {
|
|
2602
|
+
width: 380,
|
|
2603
|
+
maxHeight: 800,
|
|
2604
|
+
position: { right: 16, bottom: 60 },
|
|
2605
|
+
positionType: "absolute",
|
|
2606
|
+
flexDirection: "column"
|
|
2607
|
+
},
|
|
2608
|
+
uiBackground: { color: C.bg }
|
|
2609
|
+
},
|
|
2610
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2611
|
+
UiEntity4,
|
|
2612
|
+
{
|
|
2613
|
+
uiTransform: {
|
|
2614
|
+
width: "100%",
|
|
2615
|
+
height: 44,
|
|
2616
|
+
justifyContent: "center",
|
|
2617
|
+
alignItems: "center",
|
|
2618
|
+
flexDirection: "row"
|
|
2619
|
+
},
|
|
2620
|
+
uiBackground: { color: C.header }
|
|
2621
|
+
},
|
|
2622
|
+
/* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.config.title || "ADMIN PANEL", fontSize: 15, color: C.text })
|
|
2623
|
+
),
|
|
2624
|
+
/* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { width: "100%", height: 36, flexDirection: "row" } }, tabs.map((tab) => /* @__PURE__ */ ReactEcs4.createElement(this.TabBtn, { label: tab.label, tab: tab.id }))),
|
|
2625
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2626
|
+
UiEntity4,
|
|
2627
|
+
{
|
|
2628
|
+
uiTransform: {
|
|
2629
|
+
width: "100%",
|
|
2630
|
+
flexGrow: 1,
|
|
2631
|
+
overflow: "scroll",
|
|
2632
|
+
flexDirection: "column"
|
|
2633
|
+
}
|
|
2634
|
+
},
|
|
2635
|
+
this.activeTab === "video" && /* @__PURE__ */ ReactEcs4.createElement(this.VideoTab, null),
|
|
2636
|
+
this.activeTab === "mod" && this.isOwner && /* @__PURE__ */ ReactEcs4.createElement(this.ModTab, null),
|
|
2637
|
+
this.config.sceneTabs?.map((tab) => this.activeTab === tab.id && tab.render())
|
|
2638
|
+
),
|
|
2639
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2640
|
+
UiEntity4,
|
|
2641
|
+
{
|
|
2642
|
+
uiTransform: {
|
|
2643
|
+
width: "100%",
|
|
2644
|
+
height: 28,
|
|
2645
|
+
justifyContent: "center",
|
|
2646
|
+
alignItems: "center"
|
|
2647
|
+
},
|
|
2648
|
+
uiBackground: { color: C.section }
|
|
2649
|
+
},
|
|
2650
|
+
/* @__PURE__ */ ReactEcs4.createElement(
|
|
2651
|
+
Button3,
|
|
2652
|
+
{
|
|
2653
|
+
uiTransform: { height: 20 },
|
|
2654
|
+
uiBackground: { color: Color45.create(0, 0, 0, 0) },
|
|
2655
|
+
value: `thestatic.tv/scene/${this.config.sceneId} \u2192`,
|
|
2656
|
+
fontSize: 10,
|
|
2657
|
+
color: C.cyan,
|
|
2658
|
+
onMouseDown: () => openExternalUrl3({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
|
|
2659
|
+
}
|
|
2660
|
+
)
|
|
2661
|
+
)
|
|
2662
|
+
)
|
|
2663
|
+
);
|
|
2664
|
+
};
|
|
2665
|
+
this.client = client;
|
|
2666
|
+
this.config = {
|
|
2667
|
+
showVideoTab: true,
|
|
2668
|
+
showModTab: true,
|
|
2669
|
+
title: "ADMIN PANEL",
|
|
2670
|
+
debug: false,
|
|
2671
|
+
...config
|
|
2672
|
+
};
|
|
2673
|
+
this.baseUrl = client.baseUrl || "https://thestatic.tv/api/v1/dcl";
|
|
2674
|
+
if (config.headerColor) {
|
|
2675
|
+
C.header = Color45.create(
|
|
2676
|
+
config.headerColor.r,
|
|
2677
|
+
config.headerColor.g,
|
|
2678
|
+
config.headerColor.b,
|
|
2679
|
+
config.headerColor.a
|
|
2680
|
+
);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
log(msg, ...args) {
|
|
2684
|
+
if (this.config.debug) {
|
|
2685
|
+
console.log(`[AdminPanel] ${msg}`, ...args);
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
/**
|
|
2689
|
+
* Initialize the admin panel - checks admin status
|
|
2690
|
+
*/
|
|
2691
|
+
async init() {
|
|
2692
|
+
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
2693
|
+
await this.checkAdminStatus();
|
|
2694
|
+
this.log("Initialized");
|
|
2695
|
+
}
|
|
2696
|
+
/**
|
|
2697
|
+
* Check if current player is an admin for this scene
|
|
2698
|
+
*/
|
|
2699
|
+
async checkAdminStatus() {
|
|
2700
|
+
const player = getPlayer2();
|
|
2701
|
+
if (!player?.userId) {
|
|
2702
|
+
this.log("No player data yet");
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
this.playerWallet = player.userId;
|
|
2706
|
+
try {
|
|
2707
|
+
const res = await fetch(
|
|
2708
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/admin-check?wallet=${player.userId}`
|
|
2709
|
+
);
|
|
2710
|
+
if (res.ok) {
|
|
2711
|
+
const data = await res.json();
|
|
2712
|
+
this.isAdmin = data.hasAccess;
|
|
2713
|
+
this.isOwner = data.isOwner || data.isAdmin || data.isSceneAdmin;
|
|
2714
|
+
if (data.isBanned) {
|
|
2715
|
+
this.config.onBroadcast?.("You have been banned from this scene.");
|
|
2716
|
+
setTimeout(() => this.banKickPlayer(), 2e3);
|
|
2717
|
+
this.log("Player is banned - kicking");
|
|
2718
|
+
}
|
|
2719
|
+
this.log("Admin status:", this.isAdmin, "Owner:", this.isOwner);
|
|
2720
|
+
}
|
|
2721
|
+
} catch (err) {
|
|
2722
|
+
this.log("Admin check error:", err);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
/**
|
|
2726
|
+
* Toggle the admin panel open/closed
|
|
2727
|
+
*/
|
|
2728
|
+
toggle() {
|
|
2729
|
+
this.panelOpen = !this.panelOpen;
|
|
2730
|
+
if (!this.panelOpen) {
|
|
2731
|
+
this.stopStreamPolling();
|
|
2732
|
+
} else if (this.activeTab === "video") {
|
|
2733
|
+
this.startStreamPolling();
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
/**
|
|
2737
|
+
* Check if the panel is currently open
|
|
2738
|
+
*/
|
|
2739
|
+
get isOpen() {
|
|
2740
|
+
return this.panelOpen;
|
|
2741
|
+
}
|
|
2742
|
+
/**
|
|
2743
|
+
* Check if current user has admin access
|
|
2744
|
+
*/
|
|
2745
|
+
get hasAccess() {
|
|
2746
|
+
return this.isAdmin;
|
|
2747
|
+
}
|
|
2748
|
+
/**
|
|
2749
|
+
* Register a custom scene tab (Full/Custom tier)
|
|
2750
|
+
*/
|
|
2751
|
+
registerSceneTab(tab) {
|
|
2752
|
+
if (!this.config.sceneTabs) {
|
|
2753
|
+
this.config.sceneTabs = [];
|
|
2754
|
+
}
|
|
2755
|
+
this.config.sceneTabs.push(tab);
|
|
2756
|
+
this.log("Registered scene tab:", tab.label);
|
|
2757
|
+
}
|
|
2758
|
+
// --- Stream Polling ---
|
|
2759
|
+
startStreamPolling() {
|
|
2760
|
+
if (this.pollIntervalId !== null) return;
|
|
2761
|
+
this.pollIntervalId = setInterval(() => {
|
|
2762
|
+
if (this.activeTab === "video" && this.panelOpen && this.streamData?.hasChannel) {
|
|
2763
|
+
this.refreshStreamStatus();
|
|
2764
|
+
}
|
|
2765
|
+
}, 1e4);
|
|
2766
|
+
this.log("Stream polling started");
|
|
2767
|
+
}
|
|
2768
|
+
stopStreamPolling() {
|
|
2769
|
+
if (this.pollIntervalId !== null) {
|
|
2770
|
+
clearInterval(this.pollIntervalId);
|
|
2771
|
+
this.pollIntervalId = null;
|
|
2772
|
+
this.log("Stream polling stopped");
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
// --- Stream API Calls ---
|
|
2776
|
+
async fetchStreamData() {
|
|
2777
|
+
if (this.streamFetched || !this.playerWallet) return;
|
|
2778
|
+
try {
|
|
2779
|
+
const res = await fetch(
|
|
2780
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream?wallet=${this.playerWallet}`
|
|
2781
|
+
);
|
|
2782
|
+
if (res.ok) {
|
|
2783
|
+
this.streamData = await res.json();
|
|
2784
|
+
this.streamFetched = true;
|
|
2785
|
+
this.log("Stream data fetched:", this.streamData?.channelId || "no channel");
|
|
2786
|
+
}
|
|
2787
|
+
} catch (err) {
|
|
2788
|
+
this.log("Stream fetch error:", err);
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
async refreshStreamStatus() {
|
|
2792
|
+
if (!this.playerWallet) return;
|
|
2793
|
+
try {
|
|
2794
|
+
const res = await fetch(
|
|
2795
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream?wallet=${this.playerWallet}`
|
|
2796
|
+
);
|
|
2797
|
+
if (res.ok) {
|
|
2798
|
+
this.streamData = await res.json();
|
|
2799
|
+
}
|
|
2800
|
+
} catch (err) {
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
async createChannel() {
|
|
2804
|
+
if (!this.playerWallet || this.channelCreating) return;
|
|
2805
|
+
this.channelCreating = true;
|
|
2806
|
+
this.channelCreateError = "";
|
|
2807
|
+
try {
|
|
2808
|
+
this.log("Creating channel for scene...");
|
|
2809
|
+
const res = await fetch(
|
|
2810
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream/create`,
|
|
2811
|
+
{
|
|
2812
|
+
method: "POST",
|
|
2813
|
+
headers: { "Content-Type": "application/json" },
|
|
2814
|
+
body: JSON.stringify({ wallet: this.playerWallet })
|
|
2815
|
+
}
|
|
2816
|
+
);
|
|
2817
|
+
const data = await res.json();
|
|
2818
|
+
if (res.ok) {
|
|
2819
|
+
this.log("Channel created:", data.channel);
|
|
2820
|
+
this.streamFetched = false;
|
|
2821
|
+
await this.fetchStreamData();
|
|
2822
|
+
this.config.onBroadcast?.(`Channel created: ${data.channel.channelId}`);
|
|
2823
|
+
} else {
|
|
2824
|
+
this.channelCreateError = data.error || "Failed to create channel";
|
|
2825
|
+
this.log("Channel creation failed:", data.error);
|
|
2826
|
+
}
|
|
2827
|
+
} catch (err) {
|
|
2828
|
+
this.channelCreateError = "Network error creating channel";
|
|
2829
|
+
this.log("Channel creation error:", err);
|
|
2830
|
+
}
|
|
2831
|
+
this.channelCreating = false;
|
|
2832
|
+
}
|
|
2833
|
+
async claimTrial() {
|
|
2834
|
+
if (!this.playerWallet || this.trialClaiming) return;
|
|
2835
|
+
this.trialClaiming = true;
|
|
2836
|
+
this.trialClaimError = "";
|
|
2837
|
+
try {
|
|
2838
|
+
this.log("Claiming streaming trial...");
|
|
2839
|
+
const res = await fetch(
|
|
2840
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream/claim-trial`,
|
|
2841
|
+
{
|
|
2842
|
+
method: "POST",
|
|
2843
|
+
headers: { "Content-Type": "application/json" },
|
|
2844
|
+
body: JSON.stringify({ wallet: this.playerWallet })
|
|
2845
|
+
}
|
|
2846
|
+
);
|
|
2847
|
+
const data = await res.json();
|
|
2848
|
+
if (res.ok) {
|
|
2849
|
+
this.log("Trial claimed:", data.channel);
|
|
2850
|
+
this.streamFetched = false;
|
|
2851
|
+
await this.fetchStreamData();
|
|
2852
|
+
this.config.onBroadcast?.(`4-hour streaming trial activated!`);
|
|
2853
|
+
} else {
|
|
2854
|
+
this.trialClaimError = data.error || "Failed to claim trial";
|
|
2855
|
+
this.log("Trial claim failed:", data.error);
|
|
2856
|
+
}
|
|
2857
|
+
} catch (err) {
|
|
2858
|
+
this.trialClaimError = "Network error claiming trial";
|
|
2859
|
+
this.log("Trial claim error:", err);
|
|
2860
|
+
}
|
|
2861
|
+
this.trialClaiming = false;
|
|
2862
|
+
}
|
|
2863
|
+
async deleteChannel() {
|
|
2864
|
+
if (!this.playerWallet || this.channelDeleting || !this.streamData?.hasChannel) return;
|
|
2865
|
+
this.channelDeleting = true;
|
|
2866
|
+
this.channelDeleteError = "";
|
|
2867
|
+
try {
|
|
2868
|
+
this.log("Deleting channel for scene...");
|
|
2869
|
+
const res = await fetch(
|
|
2870
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream/delete`,
|
|
2871
|
+
{
|
|
2872
|
+
method: "POST",
|
|
2873
|
+
headers: { "Content-Type": "application/json" },
|
|
2874
|
+
body: JSON.stringify({ wallet: this.playerWallet })
|
|
2875
|
+
}
|
|
2876
|
+
);
|
|
2877
|
+
const data = await res.json();
|
|
2878
|
+
if (res.ok) {
|
|
2879
|
+
this.log("Channel deleted:", data);
|
|
2880
|
+
this.streamData = null;
|
|
2881
|
+
this.streamFetched = false;
|
|
2882
|
+
this.config.onBroadcast?.("Channel deleted successfully");
|
|
2883
|
+
} else {
|
|
2884
|
+
this.channelDeleteError = data.error || "Failed to delete channel";
|
|
2885
|
+
this.log("Channel deletion failed:", data.error);
|
|
2886
|
+
}
|
|
2887
|
+
} catch (err) {
|
|
2888
|
+
this.channelDeleteError = "Network error deleting channel";
|
|
2889
|
+
this.log("Channel deletion error:", err);
|
|
2890
|
+
}
|
|
2891
|
+
this.channelDeleting = false;
|
|
2892
|
+
}
|
|
2893
|
+
async startStream() {
|
|
2894
|
+
if (!this.playerWallet || this.streamControlling || !this.streamData?.hasChannel || this.streamData.isLive) return;
|
|
2895
|
+
if ((this.streamData.sparksBalance || 0) <= 0) {
|
|
2896
|
+
this.streamControlStatus = "error";
|
|
2897
|
+
this.config.onBroadcast?.("No Sparks - cannot start stream");
|
|
2898
|
+
return;
|
|
2899
|
+
}
|
|
2900
|
+
this.streamControlling = true;
|
|
2901
|
+
this.streamControlStatus = "";
|
|
2902
|
+
try {
|
|
2903
|
+
this.log("Starting stream...");
|
|
2904
|
+
const res = await fetch(
|
|
2905
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream`,
|
|
2906
|
+
{
|
|
2907
|
+
method: "POST",
|
|
2908
|
+
headers: { "Content-Type": "application/json" },
|
|
2909
|
+
body: JSON.stringify({ action: "start", wallet: this.playerWallet })
|
|
2910
|
+
}
|
|
2911
|
+
);
|
|
2912
|
+
const data = await res.json();
|
|
2913
|
+
if (res.ok) {
|
|
2914
|
+
this.streamControlStatus = "started";
|
|
2915
|
+
this.log("Stream started");
|
|
2916
|
+
this.config.onBroadcast?.("Stream started!");
|
|
2917
|
+
this.streamFetched = false;
|
|
2918
|
+
await this.fetchStreamData();
|
|
2919
|
+
setTimeout(() => {
|
|
2920
|
+
this.streamControlStatus = "";
|
|
2921
|
+
}, 3e3);
|
|
2922
|
+
} else {
|
|
2923
|
+
this.streamControlStatus = "error";
|
|
2924
|
+
this.log("Start stream failed:", data.error);
|
|
2925
|
+
this.config.onBroadcast?.(data.error || "Failed to start");
|
|
2926
|
+
}
|
|
2927
|
+
} catch (err) {
|
|
2928
|
+
this.streamControlStatus = "error";
|
|
2929
|
+
this.log("Start stream error:", err);
|
|
2930
|
+
}
|
|
2931
|
+
this.streamControlling = false;
|
|
2932
|
+
}
|
|
2933
|
+
async stopStream() {
|
|
2934
|
+
if (!this.playerWallet || this.streamControlling || !this.streamData?.hasChannel || !this.streamData.isLive) return;
|
|
2935
|
+
this.streamControlling = true;
|
|
2936
|
+
this.streamControlStatus = "";
|
|
2937
|
+
try {
|
|
2938
|
+
this.log("Stopping stream...");
|
|
2939
|
+
const res = await fetch(
|
|
2940
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream`,
|
|
2941
|
+
{
|
|
2942
|
+
method: "POST",
|
|
2943
|
+
headers: { "Content-Type": "application/json" },
|
|
2944
|
+
body: JSON.stringify({ action: "stop", wallet: this.playerWallet })
|
|
2945
|
+
}
|
|
2946
|
+
);
|
|
2947
|
+
const data = await res.json();
|
|
2948
|
+
if (res.ok) {
|
|
2949
|
+
this.streamControlStatus = "stopped";
|
|
2950
|
+
this.log("Stream stopped");
|
|
2951
|
+
this.config.onBroadcast?.("Stream stopped");
|
|
2952
|
+
this.streamFetched = false;
|
|
2953
|
+
await this.fetchStreamData();
|
|
2954
|
+
setTimeout(() => {
|
|
2955
|
+
this.streamControlStatus = "";
|
|
2956
|
+
}, 3e3);
|
|
2957
|
+
} else {
|
|
2958
|
+
this.streamControlStatus = "error";
|
|
2959
|
+
this.log("Stop stream failed:", data.error);
|
|
2960
|
+
}
|
|
2961
|
+
} catch (err) {
|
|
2962
|
+
this.streamControlStatus = "error";
|
|
2963
|
+
this.log("Stop stream error:", err);
|
|
2964
|
+
}
|
|
2965
|
+
this.streamControlling = false;
|
|
2966
|
+
}
|
|
2967
|
+
async rotateStreamKey() {
|
|
2968
|
+
if (!this.playerWallet || this.keyRotating || !this.streamData?.hasChannel) return;
|
|
2969
|
+
this.keyRotating = true;
|
|
2970
|
+
this.keyRotateStatus = "";
|
|
2971
|
+
try {
|
|
2972
|
+
this.log("Rotating stream key...");
|
|
2973
|
+
const res = await fetch(
|
|
2974
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/stream`,
|
|
2975
|
+
{
|
|
2976
|
+
method: "POST",
|
|
2977
|
+
headers: { "Content-Type": "application/json" },
|
|
2978
|
+
body: JSON.stringify({ action: "rotateKey", wallet: this.playerWallet })
|
|
2979
|
+
}
|
|
2980
|
+
);
|
|
2981
|
+
const data = await res.json();
|
|
2982
|
+
if (res.ok) {
|
|
2983
|
+
this.keyRotateStatus = "success";
|
|
2984
|
+
this.log("Key rotated:", data.message);
|
|
2985
|
+
this.config.onBroadcast?.("Stream key rotated - update OBS settings");
|
|
2986
|
+
setTimeout(() => {
|
|
2987
|
+
this.keyRotateStatus = "";
|
|
2988
|
+
}, 3e3);
|
|
2989
|
+
} else {
|
|
2990
|
+
this.keyRotateStatus = "error";
|
|
2991
|
+
this.log("Key rotation failed:", data.error);
|
|
2992
|
+
}
|
|
2993
|
+
} catch (err) {
|
|
2994
|
+
this.keyRotateStatus = "error";
|
|
2995
|
+
this.log("Key rotation error:", err);
|
|
2996
|
+
}
|
|
2997
|
+
this.keyRotating = false;
|
|
2998
|
+
}
|
|
2999
|
+
// --- Mod Tab API Calls ---
|
|
3000
|
+
async fetchModData() {
|
|
3001
|
+
if (!this.playerWallet) return;
|
|
3002
|
+
this.modStatus = "loading";
|
|
3003
|
+
try {
|
|
3004
|
+
const res = await fetch(
|
|
3005
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/config?wallet=${this.playerWallet}`,
|
|
3006
|
+
{ headers: { "Content-Type": "application/json" } }
|
|
3007
|
+
);
|
|
3008
|
+
if (res.ok) {
|
|
3009
|
+
const data = await res.json();
|
|
3010
|
+
this.sceneAdmins = data.sceneAdmins || [];
|
|
3011
|
+
this.bannedWallets = data.bannedWallets || [];
|
|
3012
|
+
this.modsFetched = true;
|
|
3013
|
+
this.modStatus = "";
|
|
3014
|
+
this.log("Fetched mod data:", { sceneAdmins: this.sceneAdmins, bannedWallets: this.bannedWallets });
|
|
3015
|
+
} else {
|
|
3016
|
+
this.modStatus = "error";
|
|
3017
|
+
}
|
|
3018
|
+
} catch (err) {
|
|
3019
|
+
this.modStatus = "error";
|
|
3020
|
+
this.log("Fetch mod data error:", err);
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
async addSceneAdmin(wallet) {
|
|
3024
|
+
if (!wallet || !this.playerWallet) return;
|
|
3025
|
+
const normalized = wallet.toLowerCase().trim();
|
|
3026
|
+
if (!/^0x[a-f0-9]{40}$/i.test(normalized)) {
|
|
3027
|
+
this.modStatus = "error";
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
if (this.sceneAdmins.includes(normalized)) return;
|
|
3031
|
+
this.modStatus = "loading";
|
|
3032
|
+
try {
|
|
3033
|
+
const res = await fetch(
|
|
3034
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/config`,
|
|
3035
|
+
{
|
|
3036
|
+
method: "POST",
|
|
3037
|
+
headers: { "Content-Type": "application/json" },
|
|
3038
|
+
body: JSON.stringify({
|
|
3039
|
+
sceneAdmins: [...this.sceneAdmins, normalized],
|
|
3040
|
+
wallet: this.playerWallet
|
|
3041
|
+
})
|
|
3042
|
+
}
|
|
3043
|
+
);
|
|
3044
|
+
if (res.ok) {
|
|
3045
|
+
this.sceneAdmins = [...this.sceneAdmins, normalized];
|
|
3046
|
+
this.newAdminWallet = "";
|
|
3047
|
+
this.modStatus = "saved";
|
|
3048
|
+
setTimeout(() => {
|
|
3049
|
+
this.modStatus = "";
|
|
3050
|
+
}, 2e3);
|
|
3051
|
+
this.log("Added scene admin:", normalized);
|
|
3052
|
+
} else {
|
|
3053
|
+
this.modStatus = "error";
|
|
3054
|
+
}
|
|
3055
|
+
} catch (err) {
|
|
3056
|
+
this.modStatus = "error";
|
|
3057
|
+
this.log("Add admin error:", err);
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
async removeSceneAdmin(wallet) {
|
|
3061
|
+
if (!wallet || !this.playerWallet) return;
|
|
3062
|
+
this.modStatus = "loading";
|
|
3063
|
+
try {
|
|
3064
|
+
const res = await fetch(
|
|
3065
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/config`,
|
|
3066
|
+
{
|
|
3067
|
+
method: "POST",
|
|
3068
|
+
headers: { "Content-Type": "application/json" },
|
|
3069
|
+
body: JSON.stringify({
|
|
3070
|
+
sceneAdmins: this.sceneAdmins.filter((w) => w !== wallet),
|
|
3071
|
+
wallet: this.playerWallet
|
|
3072
|
+
})
|
|
3073
|
+
}
|
|
3074
|
+
);
|
|
3075
|
+
if (res.ok) {
|
|
3076
|
+
this.sceneAdmins = this.sceneAdmins.filter((w) => w !== wallet);
|
|
3077
|
+
this.modStatus = "saved";
|
|
3078
|
+
setTimeout(() => {
|
|
3079
|
+
this.modStatus = "";
|
|
3080
|
+
}, 2e3);
|
|
3081
|
+
this.log("Removed scene admin:", wallet);
|
|
3082
|
+
} else {
|
|
3083
|
+
this.modStatus = "error";
|
|
3084
|
+
}
|
|
3085
|
+
} catch (err) {
|
|
3086
|
+
this.modStatus = "error";
|
|
3087
|
+
this.log("Remove admin error:", err);
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
async banWallet(wallet) {
|
|
3091
|
+
if (!wallet || !this.playerWallet) return;
|
|
3092
|
+
const normalized = wallet.toLowerCase().trim();
|
|
3093
|
+
if (!/^0x[a-f0-9]{40}$/i.test(normalized)) {
|
|
3094
|
+
this.modStatus = "error";
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3097
|
+
if (this.bannedWallets.includes(normalized)) return;
|
|
3098
|
+
this.modStatus = "loading";
|
|
3099
|
+
try {
|
|
3100
|
+
const res = await fetch(
|
|
3101
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/config`,
|
|
3102
|
+
{
|
|
3103
|
+
method: "POST",
|
|
3104
|
+
headers: { "Content-Type": "application/json" },
|
|
3105
|
+
body: JSON.stringify({
|
|
3106
|
+
bannedWallets: [...this.bannedWallets, normalized],
|
|
3107
|
+
wallet: this.playerWallet
|
|
3108
|
+
})
|
|
3109
|
+
}
|
|
3110
|
+
);
|
|
3111
|
+
if (res.ok) {
|
|
3112
|
+
this.bannedWallets = [...this.bannedWallets, normalized];
|
|
3113
|
+
this.newBanWallet = "";
|
|
3114
|
+
this.modStatus = "saved";
|
|
3115
|
+
setTimeout(() => {
|
|
3116
|
+
this.modStatus = "";
|
|
3117
|
+
}, 2e3);
|
|
3118
|
+
this.log("Banned wallet:", normalized);
|
|
3119
|
+
this.config.onCommand?.("kickBanned", { wallet: normalized });
|
|
3120
|
+
} else {
|
|
3121
|
+
this.modStatus = "error";
|
|
3122
|
+
}
|
|
3123
|
+
} catch (err) {
|
|
3124
|
+
this.modStatus = "error";
|
|
3125
|
+
this.log("Ban error:", err);
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
async unbanWallet(wallet) {
|
|
3129
|
+
if (!wallet || !this.playerWallet) return;
|
|
3130
|
+
this.modStatus = "loading";
|
|
3131
|
+
try {
|
|
3132
|
+
const res = await fetch(
|
|
3133
|
+
`${this.baseUrl}/scene/${this.config.sceneId}/config`,
|
|
3134
|
+
{
|
|
3135
|
+
method: "POST",
|
|
3136
|
+
headers: { "Content-Type": "application/json" },
|
|
3137
|
+
body: JSON.stringify({
|
|
3138
|
+
bannedWallets: this.bannedWallets.filter((w) => w !== wallet),
|
|
3139
|
+
wallet: this.playerWallet
|
|
3140
|
+
})
|
|
3141
|
+
}
|
|
3142
|
+
);
|
|
3143
|
+
if (res.ok) {
|
|
3144
|
+
this.bannedWallets = this.bannedWallets.filter((w) => w !== wallet);
|
|
3145
|
+
this.modStatus = "saved";
|
|
3146
|
+
setTimeout(() => {
|
|
3147
|
+
this.modStatus = "";
|
|
3148
|
+
}, 2e3);
|
|
3149
|
+
this.log("Unbanned wallet:", wallet);
|
|
3150
|
+
} else {
|
|
3151
|
+
this.modStatus = "error";
|
|
3152
|
+
}
|
|
3153
|
+
} catch (err) {
|
|
3154
|
+
this.modStatus = "error";
|
|
3155
|
+
this.log("Unban error:", err);
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
banKickPlayer() {
|
|
3159
|
+
movePlayerTo({ newRelativePosition: BAN_KICK_POSITION });
|
|
3160
|
+
this.log("Player ban-kicked");
|
|
3161
|
+
}
|
|
3162
|
+
sendBroadcast() {
|
|
3163
|
+
if (!this.broadcastText.trim()) return;
|
|
3164
|
+
this.config.onBroadcast?.(this.broadcastText.trim());
|
|
3165
|
+
this.broadcastText = "";
|
|
3166
|
+
this.log("Broadcast sent");
|
|
3167
|
+
}
|
|
3168
|
+
setActiveTab(tab) {
|
|
3169
|
+
const previousTab = this.activeTab;
|
|
3170
|
+
this.activeTab = tab;
|
|
3171
|
+
if (tab === "mod" && !this.modsFetched && this.isOwner) {
|
|
3172
|
+
this.fetchModData();
|
|
3173
|
+
}
|
|
3174
|
+
if (tab === "video" && previousTab !== "video") {
|
|
3175
|
+
this.startStreamPolling();
|
|
3176
|
+
} else if (tab !== "video" && previousTab === "video") {
|
|
3177
|
+
this.stopStreamPolling();
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
};
|
|
3181
|
+
|
|
2124
3182
|
// src/StaticTVClient.ts
|
|
2125
3183
|
var DEFAULT_BASE_URL = "https://thestatic.tv/api/v1/dcl";
|
|
2126
3184
|
var KEY_TYPE_CHANNEL = "channel";
|
|
@@ -2133,22 +3191,22 @@ var StaticTVClient = class {
|
|
|
2133
3191
|
*
|
|
2134
3192
|
* @example
|
|
2135
3193
|
* ```typescript
|
|
2136
|
-
*
|
|
2137
|
-
* const staticTV = new StaticTVClient({
|
|
2138
|
-
* apiKey: 'dclk_your_channel_key_here',
|
|
2139
|
-
* debug: true
|
|
2140
|
-
* });
|
|
3194
|
+
* let staticTV: StaticTVClient
|
|
2141
3195
|
*
|
|
2142
|
-
*
|
|
2143
|
-
*
|
|
2144
|
-
*
|
|
2145
|
-
*
|
|
3196
|
+
* export function main() {
|
|
3197
|
+
* // All keys use dcls_ prefix - features determined by subscription
|
|
3198
|
+
* staticTV = new StaticTVClient({
|
|
3199
|
+
* apiKey: 'dcls_your_key_here'
|
|
3200
|
+
* })
|
|
3201
|
+
* // Session tracking starts automatically!
|
|
3202
|
+
* }
|
|
2146
3203
|
* ```
|
|
2147
3204
|
*/
|
|
2148
3205
|
constructor(config) {
|
|
2149
3206
|
this._keyType = null;
|
|
2150
3207
|
this._disabled = false;
|
|
2151
3208
|
this._fullFeaturesEnabled = false;
|
|
3209
|
+
this._proFeaturesEnabled = false;
|
|
2152
3210
|
/** Guide module - fetch channel lineup (full SDK only) */
|
|
2153
3211
|
this.guide = null;
|
|
2154
3212
|
/** Session module - track visitor sessions (all keys, null when disabled) */
|
|
@@ -2161,6 +3219,8 @@ var StaticTVClient = class {
|
|
|
2161
3219
|
this.guideUI = null;
|
|
2162
3220
|
/** Chat UI module - real-time chat UI (full SDK only) */
|
|
2163
3221
|
this.chatUI = null;
|
|
3222
|
+
/** Admin Panel module - Video/Mod tabs (pro SDK only) */
|
|
3223
|
+
this.adminPanel = null;
|
|
2164
3224
|
this.config = {
|
|
2165
3225
|
autoStartSession: true,
|
|
2166
3226
|
sessionHeartbeatInterval: 3e4,
|
|
@@ -2290,12 +3350,72 @@ var StaticTVClient = class {
|
|
|
2290
3350
|
get hasFullFeatures() {
|
|
2291
3351
|
return this._fullFeaturesEnabled;
|
|
2292
3352
|
}
|
|
3353
|
+
/**
|
|
3354
|
+
* Check if pro features are enabled (admin panel)
|
|
3355
|
+
*/
|
|
3356
|
+
get hasProFeatures() {
|
|
3357
|
+
return this._proFeaturesEnabled;
|
|
3358
|
+
}
|
|
2293
3359
|
/**
|
|
2294
3360
|
* Get the SDK type (lite or full) - only available after session starts
|
|
2295
3361
|
*/
|
|
2296
3362
|
get sdkType() {
|
|
2297
3363
|
return this.session?.sdkType || "lite";
|
|
2298
3364
|
}
|
|
3365
|
+
/**
|
|
3366
|
+
* Enable Pro features (Admin Panel with Video + Mod tabs)
|
|
3367
|
+
* Call this after creating the client to add admin panel functionality.
|
|
3368
|
+
*
|
|
3369
|
+
* @param config Admin panel configuration
|
|
3370
|
+
*
|
|
3371
|
+
* @example
|
|
3372
|
+
* ```typescript
|
|
3373
|
+
* const staticTV = new StaticTVClient({ apiKey: 'dcls_...' })
|
|
3374
|
+
*
|
|
3375
|
+
* // Enable admin panel
|
|
3376
|
+
* staticTV.enableProFeatures({
|
|
3377
|
+
* sceneId: 'my-scene',
|
|
3378
|
+
* title: 'MY SCENE ADMIN',
|
|
3379
|
+
* onVideoPlay: (url) => videoPlayer.play(url),
|
|
3380
|
+
* onVideoStop: () => videoPlayer.stop(),
|
|
3381
|
+
* onBroadcast: (text) => showNotification(text)
|
|
3382
|
+
* })
|
|
3383
|
+
* ```
|
|
3384
|
+
*/
|
|
3385
|
+
enableProFeatures(config) {
|
|
3386
|
+
if (this._proFeaturesEnabled) {
|
|
3387
|
+
this.log("Pro features already enabled");
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3390
|
+
this.adminPanel = new AdminPanelUIModule(this, config);
|
|
3391
|
+
this._proFeaturesEnabled = true;
|
|
3392
|
+
this.adminPanel.init().catch((err) => {
|
|
3393
|
+
this.log(`Admin panel init failed: ${err}`);
|
|
3394
|
+
});
|
|
3395
|
+
this.log("Pro features enabled (admin panel)");
|
|
3396
|
+
}
|
|
3397
|
+
/**
|
|
3398
|
+
* Register a custom scene tab for the admin panel (Full/Custom tier)
|
|
3399
|
+
* Must call enableProFeatures() first.
|
|
3400
|
+
*
|
|
3401
|
+
* @param tab The tab definition with label, id, and render function
|
|
3402
|
+
*
|
|
3403
|
+
* @example
|
|
3404
|
+
* ```typescript
|
|
3405
|
+
* staticTV.registerSceneTab({
|
|
3406
|
+
* label: 'LIGHTS',
|
|
3407
|
+
* id: 'lights',
|
|
3408
|
+
* render: () => <MyLightsControls />
|
|
3409
|
+
* })
|
|
3410
|
+
* ```
|
|
3411
|
+
*/
|
|
3412
|
+
registerSceneTab(tab) {
|
|
3413
|
+
if (!this.adminPanel) {
|
|
3414
|
+
this.log("Cannot register scene tab - call enableProFeatures() first");
|
|
3415
|
+
return;
|
|
3416
|
+
}
|
|
3417
|
+
this.adminPanel.registerSceneTab(tab);
|
|
3418
|
+
}
|
|
2299
3419
|
/**
|
|
2300
3420
|
* Cleanup when done (call before scene unload)
|
|
2301
3421
|
*/
|
|
@@ -2317,6 +3437,7 @@ var StaticTVClient = class {
|
|
|
2317
3437
|
}
|
|
2318
3438
|
};
|
|
2319
3439
|
export {
|
|
3440
|
+
AdminPanelUIModule,
|
|
2320
3441
|
ChatUIModule,
|
|
2321
3442
|
GuideModule,
|
|
2322
3443
|
GuideUIModule,
|