@bountyboard/arcade-sdk 1.0.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/AGENTS.md +75 -0
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/chunk-WCGBR3MI.js +805 -0
- package/dist/chunk-WCGBR3MI.js.map +1 -0
- package/dist/global-sdk.d.ts +342 -0
- package/dist/global.js +1044 -0
- package/dist/index.cjs +832 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/multiplayer.cjs +1070 -0
- package/dist/multiplayer.cjs.map +1 -0
- package/dist/multiplayer.d.cts +7 -0
- package/dist/multiplayer.d.ts +7 -0
- package/dist/multiplayer.js +248 -0
- package/dist/multiplayer.js.map +1 -0
- package/dist/types-B6xH-lDi.d.cts +328 -0
- package/dist/types-B6xH-lDi.d.ts +328 -0
- package/docs/game-design-playbook.md +73 -0
- package/package.json +59 -0
package/dist/global.js
ADDED
|
@@ -0,0 +1,1044 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
// src/protocol.ts
|
|
4
|
+
var VERSION = 1;
|
|
5
|
+
var SDK_MESSAGE_SOURCE = "bb-arcade";
|
|
6
|
+
var HOST_MESSAGE_SOURCE = "bb-arcade-host";
|
|
7
|
+
|
|
8
|
+
// src/core.ts
|
|
9
|
+
var BOUNTY_BOARD_URL = "https://www.bountyboard.gg/arcade";
|
|
10
|
+
var ADSENSE_CLIENT_ID = "ca-pub-7727110228190547";
|
|
11
|
+
var AD_PLACEMENT_SRC = "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=" + ADSENSE_CLIENT_ID;
|
|
12
|
+
var AD_PLACEMENT_SCRIPT_ID = "bb-arcade-h5-ads";
|
|
13
|
+
var DEFAULT_AD_LOAD_TIMEOUT_MS = 6e3;
|
|
14
|
+
var SAVE_TIMEOUT_MS = 15e3;
|
|
15
|
+
var INIT_CONFIG_GRACE_MS = 1500;
|
|
16
|
+
var DEFAULT_ALLOW = [
|
|
17
|
+
"bountyboard.gg",
|
|
18
|
+
"bountyboard-staging.vercel.app",
|
|
19
|
+
"localhost",
|
|
20
|
+
"127.0.0.1"
|
|
21
|
+
];
|
|
22
|
+
var SITELOCK_PUBLIC_KEY_B64 = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6WC8fJweDpfKRcZW9Q9eOLxPXmFCO8R8lY1kfZbuAQt65E0CTu2rM80XfW3nYrSAqO+Eev8gxrGS0bAnNaMDGg==";
|
|
23
|
+
var SITELOCK_MAX_SKEW_MS = 5 * 60 * 1e3;
|
|
24
|
+
function sdkError(code) {
|
|
25
|
+
const err = new Error(code);
|
|
26
|
+
err.code = code;
|
|
27
|
+
return err;
|
|
28
|
+
}
|
|
29
|
+
function b64ToBytes(b64) {
|
|
30
|
+
const bin = atob(b64);
|
|
31
|
+
const bytes = new Uint8Array(bin.length);
|
|
32
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
33
|
+
return bytes;
|
|
34
|
+
}
|
|
35
|
+
function cleanText(value, fallback, max) {
|
|
36
|
+
if (typeof value !== "string") return fallback || "";
|
|
37
|
+
return value.replace(/[^\w:.-]+/g, "_").slice(0, max || 80) || fallback || "";
|
|
38
|
+
}
|
|
39
|
+
function toBoolean(value) {
|
|
40
|
+
return value === true || value === "true" || value === "1";
|
|
41
|
+
}
|
|
42
|
+
function cleanRewardedSize(value) {
|
|
43
|
+
return value === "small" || value === "medium" || value === "large" ? value : "";
|
|
44
|
+
}
|
|
45
|
+
function safeCall(fn, arg) {
|
|
46
|
+
if (typeof fn !== "function") return;
|
|
47
|
+
try {
|
|
48
|
+
fn(arg);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
setTimeout(function() {
|
|
51
|
+
throw err;
|
|
52
|
+
}, 0);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function toAllowedHost(value) {
|
|
56
|
+
const raw = String(value).toLowerCase().trim();
|
|
57
|
+
if (!raw) return "";
|
|
58
|
+
try {
|
|
59
|
+
return new URL(raw.indexOf("//") === -1 ? "https://" + raw : raw).hostname;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return raw;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function hostnameAllowed(origin, allowHosts) {
|
|
65
|
+
if (!origin || origin === "null" || typeof origin !== "string") return false;
|
|
66
|
+
let host;
|
|
67
|
+
try {
|
|
68
|
+
host = new URL(origin).hostname.toLowerCase();
|
|
69
|
+
} catch (err) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
for (let i = 0; i < allowHosts.length; i++) {
|
|
73
|
+
const allowed = allowHosts[i];
|
|
74
|
+
if (host === allowed || host.slice(-(allowed.length + 1)) === "." + allowed) return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
function verifyHostAttestation(att, senderOrigin) {
|
|
79
|
+
const a = att;
|
|
80
|
+
if (!a || typeof a.origin !== "string" || typeof a.ts !== "number" || typeof a.sig !== "string")
|
|
81
|
+
return Promise.resolve(false);
|
|
82
|
+
if (a.origin !== senderOrigin) return Promise.resolve(false);
|
|
83
|
+
if (Math.abs(Date.now() - a.ts) > SITELOCK_MAX_SKEW_MS) return Promise.resolve(false);
|
|
84
|
+
try {
|
|
85
|
+
return window.crypto.subtle.importKey(
|
|
86
|
+
"spki",
|
|
87
|
+
b64ToBytes(SITELOCK_PUBLIC_KEY_B64),
|
|
88
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
89
|
+
false,
|
|
90
|
+
["verify"]
|
|
91
|
+
).then(function(key) {
|
|
92
|
+
return window.crypto.subtle.verify(
|
|
93
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
94
|
+
key,
|
|
95
|
+
b64ToBytes(a.sig),
|
|
96
|
+
new TextEncoder().encode(a.origin + ":" + a.ts)
|
|
97
|
+
);
|
|
98
|
+
}).then(
|
|
99
|
+
function(ok) {
|
|
100
|
+
return ok === true;
|
|
101
|
+
},
|
|
102
|
+
function() {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
return Promise.resolve(false);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function sanitizePlayer(value) {
|
|
111
|
+
if (!value || typeof value !== "object") return null;
|
|
112
|
+
const raw = value;
|
|
113
|
+
const name = typeof raw.name === "string" ? raw.name.trim().slice(0, 120) : "";
|
|
114
|
+
if (!name) return null;
|
|
115
|
+
const avatarUrl = typeof raw.avatarUrl === "string" && raw.avatarUrl.trim() ? raw.avatarUrl : null;
|
|
116
|
+
return { name, avatarUrl };
|
|
117
|
+
}
|
|
118
|
+
function createBBArcade() {
|
|
119
|
+
let adPlacementLoad = null;
|
|
120
|
+
let adPreloadConfigured = false;
|
|
121
|
+
let rewardedBreakActive = false;
|
|
122
|
+
let rewardedBreakSequence = 0;
|
|
123
|
+
function isLocalAdHost() {
|
|
124
|
+
try {
|
|
125
|
+
const protocol = window.location && window.location.protocol;
|
|
126
|
+
const host = window.location && window.location.hostname;
|
|
127
|
+
return protocol === "file:" || host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
128
|
+
} catch (err) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const localAdHost = isLocalAdHost();
|
|
133
|
+
const config = {
|
|
134
|
+
rewardedAds: {
|
|
135
|
+
enabled: true,
|
|
136
|
+
adChannel: "",
|
|
137
|
+
testMode: localAdHost,
|
|
138
|
+
loadTimeoutMs: DEFAULT_AD_LOAD_TIMEOUT_MS,
|
|
139
|
+
preload: false
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const hostConfig = {
|
|
143
|
+
rewardedAds: {
|
|
144
|
+
received: false,
|
|
145
|
+
enabled: localAdHost,
|
|
146
|
+
adChannel: "",
|
|
147
|
+
testMode: localAdHost,
|
|
148
|
+
preload: false
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
let hostConfigReceived = false;
|
|
152
|
+
let hostPlayer = null;
|
|
153
|
+
let blocked = false;
|
|
154
|
+
const pending = {};
|
|
155
|
+
let seq = 0;
|
|
156
|
+
function post(type, score, extra) {
|
|
157
|
+
if (blocked) return;
|
|
158
|
+
const message = { source: SDK_MESSAGE_SOURCE, v: VERSION, type };
|
|
159
|
+
if (typeof score === "number" && isFinite(score)) message.score = Math.floor(score);
|
|
160
|
+
if (extra && typeof extra === "object") {
|
|
161
|
+
const fields = extra;
|
|
162
|
+
for (const key in fields) {
|
|
163
|
+
if (Object.prototype.hasOwnProperty.call(fields, key) && fields[key] !== void 0) {
|
|
164
|
+
message[key] = fields[key];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
window.parent.postMessage(message, "*");
|
|
170
|
+
} catch (err) {
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function isEmbedded() {
|
|
174
|
+
try {
|
|
175
|
+
return window.parent != null && window.parent !== window;
|
|
176
|
+
} catch (err) {
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function request(type, payload) {
|
|
181
|
+
return new Promise(function(resolve, reject) {
|
|
182
|
+
if (blocked) return reject(sdkError("error"));
|
|
183
|
+
if (!isEmbedded()) return reject(sdkError("unsupported"));
|
|
184
|
+
const requestId = type + "_" + ++seq + "_" + Date.now();
|
|
185
|
+
const timer = setTimeout(function() {
|
|
186
|
+
if (pending[requestId]) {
|
|
187
|
+
delete pending[requestId];
|
|
188
|
+
reject(sdkError("error"));
|
|
189
|
+
}
|
|
190
|
+
}, SAVE_TIMEOUT_MS);
|
|
191
|
+
pending[requestId] = { resolve, reject, timer };
|
|
192
|
+
try {
|
|
193
|
+
window.parent.postMessage(
|
|
194
|
+
{
|
|
195
|
+
source: SDK_MESSAGE_SOURCE,
|
|
196
|
+
v: VERSION,
|
|
197
|
+
type,
|
|
198
|
+
requestId,
|
|
199
|
+
payload
|
|
200
|
+
},
|
|
201
|
+
"*"
|
|
202
|
+
);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
clearTimeout(timer);
|
|
205
|
+
delete pending[requestId];
|
|
206
|
+
reject(sdkError("error"));
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
window.addEventListener("message", function(event) {
|
|
211
|
+
if (event.source !== window.parent) return;
|
|
212
|
+
const data = event.data;
|
|
213
|
+
if (!data || data.source !== SDK_MESSAGE_SOURCE) return;
|
|
214
|
+
if (typeof data.type !== "string" || !data.type.endsWith("_result")) return;
|
|
215
|
+
const entry = pending[data.requestId];
|
|
216
|
+
if (!entry) return;
|
|
217
|
+
clearTimeout(entry.timer);
|
|
218
|
+
delete pending[data.requestId];
|
|
219
|
+
if (data.type === "host_result") return entry.resolve({ origin: event.origin });
|
|
220
|
+
if (data.type === "host_attest_result")
|
|
221
|
+
return entry.resolve({ origin: event.origin, attestation: data.attestation || null });
|
|
222
|
+
if (data.type === "experiment_result")
|
|
223
|
+
return entry.resolve(typeof data.variant === "string" ? data.variant : null);
|
|
224
|
+
if (data.ok)
|
|
225
|
+
entry.resolve(
|
|
226
|
+
data.type === "load_result" ? data.data != null ? data.data : null : data.data
|
|
227
|
+
);
|
|
228
|
+
else entry.reject(sdkError(data.code || "error"));
|
|
229
|
+
});
|
|
230
|
+
function embeddingOriginSync() {
|
|
231
|
+
try {
|
|
232
|
+
if (window.top === window.self) return window.location.origin;
|
|
233
|
+
} catch (err) {
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const ancestors = window.location.ancestorOrigins;
|
|
237
|
+
if (ancestors && ancestors.length && ancestors[0] && ancestors[0] !== "null")
|
|
238
|
+
return ancestors[0];
|
|
239
|
+
} catch (err) {
|
|
240
|
+
}
|
|
241
|
+
if (document.referrer) {
|
|
242
|
+
try {
|
|
243
|
+
return new URL(document.referrer).origin;
|
|
244
|
+
} catch (err) {
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
function blockHost(options) {
|
|
250
|
+
if (blocked) return;
|
|
251
|
+
blocked = true;
|
|
252
|
+
if (typeof options.onBlocked === "function") {
|
|
253
|
+
try {
|
|
254
|
+
options.onBlocked();
|
|
255
|
+
} catch (err) {
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (options.redirect) {
|
|
260
|
+
try {
|
|
261
|
+
window.top.location.href = options.redirect;
|
|
262
|
+
return;
|
|
263
|
+
} catch (err) {
|
|
264
|
+
try {
|
|
265
|
+
window.location.href = options.redirect;
|
|
266
|
+
return;
|
|
267
|
+
} catch (err2) {
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
document.documentElement.innerHTML = `<div style="position:fixed;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;font-family:system-ui,-apple-system,sans-serif;background:#1a1206;color:#fff;text-align:center;padding:24px;"><div style="font-size:18px;font-weight:700;">This game isn't available here</div><a href="` + BOUNTY_BOARD_URL + '" target="_blank" rel="noopener" style="display:inline-block;background:#f5b942;color:#1a1206;font-weight:700;font-size:14px;text-decoration:none;padding:10px 20px;border-radius:999px;">Play the original on Bounty Board</a></div>';
|
|
273
|
+
} catch (err) {
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function lockToHostBestEffort(options, allow) {
|
|
277
|
+
const origin = embeddingOriginSync();
|
|
278
|
+
if (origin != null) {
|
|
279
|
+
if (!hostnameAllowed(origin, allow)) blockHost(options);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
request("host").then(
|
|
283
|
+
function(res) {
|
|
284
|
+
const r = res;
|
|
285
|
+
if (!hostnameAllowed(r && r.origin, allow)) blockHost(options);
|
|
286
|
+
},
|
|
287
|
+
function() {
|
|
288
|
+
blockHost(options);
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
function lockToHost(options) {
|
|
293
|
+
const opts = options || {};
|
|
294
|
+
const allow = DEFAULT_ALLOW.slice();
|
|
295
|
+
if (opts.allow && opts.allow.length) {
|
|
296
|
+
for (let i = 0; i < opts.allow.length; i++) {
|
|
297
|
+
const host = toAllowedHost(opts.allow[i]);
|
|
298
|
+
if (host) allow.push(host);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (opts.signed) {
|
|
302
|
+
if (!(window.crypto && window.crypto.subtle)) {
|
|
303
|
+
lockToHostBestEffort(opts, allow);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
request("host_attest").then(
|
|
307
|
+
function(res) {
|
|
308
|
+
const r = res;
|
|
309
|
+
if (!r || !r.attestation) {
|
|
310
|
+
lockToHostBestEffort(opts, allow);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
verifyHostAttestation(r.attestation, r.origin).then(function(ok) {
|
|
314
|
+
if (!ok || !hostnameAllowed(r.attestation && r.attestation.origin, allow))
|
|
315
|
+
blockHost(opts);
|
|
316
|
+
});
|
|
317
|
+
},
|
|
318
|
+
function() {
|
|
319
|
+
blockHost(opts);
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
lockToHostBestEffort(opts, allow);
|
|
325
|
+
}
|
|
326
|
+
function nextAdBreakId() {
|
|
327
|
+
rewardedBreakSequence += 1;
|
|
328
|
+
return [
|
|
329
|
+
"bbad",
|
|
330
|
+
Date.now().toString(36),
|
|
331
|
+
rewardedBreakSequence.toString(36),
|
|
332
|
+
Math.random().toString(36).slice(2, 10)
|
|
333
|
+
].join("_");
|
|
334
|
+
}
|
|
335
|
+
function configure(nextConfig) {
|
|
336
|
+
if (!nextConfig || typeof nextConfig !== "object") return;
|
|
337
|
+
const rewardedAds = nextConfig.rewardedAds;
|
|
338
|
+
if (!rewardedAds || typeof rewardedAds !== "object") return;
|
|
339
|
+
if (rewardedAds.enabled !== void 0) {
|
|
340
|
+
config.rewardedAds.enabled = rewardedAds.enabled !== false;
|
|
341
|
+
}
|
|
342
|
+
if (rewardedAds.adChannel !== void 0) {
|
|
343
|
+
config.rewardedAds.adChannel = cleanText(rewardedAds.adChannel);
|
|
344
|
+
}
|
|
345
|
+
if (rewardedAds.testMode !== void 0) {
|
|
346
|
+
config.rewardedAds.testMode = toBoolean(rewardedAds.testMode);
|
|
347
|
+
}
|
|
348
|
+
if (rewardedAds.preload !== void 0) {
|
|
349
|
+
config.rewardedAds.preload = toBoolean(rewardedAds.preload);
|
|
350
|
+
}
|
|
351
|
+
if (typeof rewardedAds.loadTimeoutMs === "number" && isFinite(rewardedAds.loadTimeoutMs) && rewardedAds.loadTimeoutMs > 0) {
|
|
352
|
+
config.rewardedAds.loadTimeoutMs = Math.min(15e3, Math.floor(rewardedAds.loadTimeoutMs));
|
|
353
|
+
}
|
|
354
|
+
if (config.rewardedAds.preload) preloadRewardedAds();
|
|
355
|
+
}
|
|
356
|
+
let configWaiters = [];
|
|
357
|
+
function notifyConfigReceived() {
|
|
358
|
+
const waiters = configWaiters;
|
|
359
|
+
configWaiters = [];
|
|
360
|
+
for (let i = 0; i < waiters.length; i++) safeCall(waiters[i]);
|
|
361
|
+
}
|
|
362
|
+
function applyHostConfig(message) {
|
|
363
|
+
if (!message || typeof message !== "object") return;
|
|
364
|
+
const msg = message;
|
|
365
|
+
if (msg.source !== HOST_MESSAGE_SOURCE || msg.type !== "config") return;
|
|
366
|
+
hostConfigReceived = true;
|
|
367
|
+
if (msg.player !== void 0) {
|
|
368
|
+
hostPlayer = sanitizePlayer(msg.player);
|
|
369
|
+
}
|
|
370
|
+
const rewardedAds = msg.rewardedAds;
|
|
371
|
+
if (rewardedAds && typeof rewardedAds === "object") {
|
|
372
|
+
hostConfig.rewardedAds.received = true;
|
|
373
|
+
if (rewardedAds.enabled !== void 0) {
|
|
374
|
+
hostConfig.rewardedAds.enabled = toBoolean(rewardedAds.enabled);
|
|
375
|
+
}
|
|
376
|
+
if (rewardedAds.adChannel !== void 0) {
|
|
377
|
+
hostConfig.rewardedAds.adChannel = cleanText(rewardedAds.adChannel);
|
|
378
|
+
}
|
|
379
|
+
if (rewardedAds.testMode !== void 0) {
|
|
380
|
+
hostConfig.rewardedAds.testMode = toBoolean(rewardedAds.testMode);
|
|
381
|
+
}
|
|
382
|
+
if (rewardedAds.preload !== void 0) {
|
|
383
|
+
hostConfig.rewardedAds.preload = toBoolean(rewardedAds.preload);
|
|
384
|
+
}
|
|
385
|
+
if (hostConfig.rewardedAds.enabled && (hostConfig.rewardedAds.preload || config.rewardedAds.preload)) {
|
|
386
|
+
preloadRewardedAds();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
notifyConfigReceived();
|
|
390
|
+
}
|
|
391
|
+
function areRewardedAdsEnabled() {
|
|
392
|
+
return config.rewardedAds.enabled !== false && hostConfig.rewardedAds.enabled === true;
|
|
393
|
+
}
|
|
394
|
+
function installAdPlacementShim() {
|
|
395
|
+
const w = window;
|
|
396
|
+
w.adsbygoogle = w.adsbygoogle || [];
|
|
397
|
+
if (typeof w.adBreak !== "function") {
|
|
398
|
+
w.adBreak = function(placement) {
|
|
399
|
+
w.adsbygoogle.push(placement);
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (typeof w.adConfig !== "function") {
|
|
403
|
+
w.adConfig = function(settings) {
|
|
404
|
+
w.adsbygoogle.push(settings);
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function ensureAdPlacement(options) {
|
|
409
|
+
if (typeof document === "undefined") return Promise.resolve(null);
|
|
410
|
+
installAdPlacementShim();
|
|
411
|
+
const w = window;
|
|
412
|
+
if (typeof w.adBreak !== "function") return Promise.resolve(null);
|
|
413
|
+
if (adPlacementLoad) return adPlacementLoad;
|
|
414
|
+
adPlacementLoad = new Promise(function(resolve) {
|
|
415
|
+
let script = document.getElementById(AD_PLACEMENT_SCRIPT_ID);
|
|
416
|
+
if (!script) {
|
|
417
|
+
const el = document.createElement("script");
|
|
418
|
+
el.id = AD_PLACEMENT_SCRIPT_ID;
|
|
419
|
+
el.async = true;
|
|
420
|
+
el.src = AD_PLACEMENT_SRC;
|
|
421
|
+
el.crossOrigin = "anonymous";
|
|
422
|
+
el.setAttribute("data-ad-client", ADSENSE_CLIENT_ID);
|
|
423
|
+
if (options.adChannel) el.setAttribute("data-ad-channel", options.adChannel);
|
|
424
|
+
if (options.testMode) el.setAttribute("data-adbreak-test", "on");
|
|
425
|
+
(document.head || document.documentElement).appendChild(el);
|
|
426
|
+
script = el;
|
|
427
|
+
}
|
|
428
|
+
resolve(w.adBreak);
|
|
429
|
+
});
|
|
430
|
+
return adPlacementLoad;
|
|
431
|
+
}
|
|
432
|
+
function requestAdPreload() {
|
|
433
|
+
const w = window;
|
|
434
|
+
if (adPreloadConfigured || typeof w.adConfig !== "function") return;
|
|
435
|
+
adPreloadConfigured = true;
|
|
436
|
+
try {
|
|
437
|
+
w.adConfig({ preloadAdBreaks: "on" });
|
|
438
|
+
} catch (err) {
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function normalizeRewardedOptions(options) {
|
|
442
|
+
const opts = options && typeof options === "object" ? options : {};
|
|
443
|
+
const hostHasConfig = hostConfig.rewardedAds.received;
|
|
444
|
+
return {
|
|
445
|
+
placement: cleanText(opts.placement || opts.name, "rewarded"),
|
|
446
|
+
reward: cleanText(opts.reward, "reward"),
|
|
447
|
+
adBreakId: cleanText(opts.adBreakId, nextAdBreakId(), 128),
|
|
448
|
+
size: cleanRewardedSize(opts.size),
|
|
449
|
+
adChannel: cleanText(
|
|
450
|
+
hostHasConfig ? hostConfig.rewardedAds.adChannel : opts.adChannel || config.rewardedAds.adChannel
|
|
451
|
+
),
|
|
452
|
+
testMode: hostHasConfig ? hostConfig.rewardedAds.testMode : opts.testMode !== void 0 ? toBoolean(opts.testMode) : config.rewardedAds.testMode || hostConfig.rewardedAds.testMode,
|
|
453
|
+
loadTimeoutMs: typeof opts.loadTimeoutMs === "number" && isFinite(opts.loadTimeoutMs) ? Math.min(15e3, Math.max(1, Math.floor(opts.loadTimeoutMs))) : config.rewardedAds.loadTimeoutMs,
|
|
454
|
+
beforeReward: opts.beforeReward,
|
|
455
|
+
adViewed: opts.adViewed,
|
|
456
|
+
adDismissed: opts.adDismissed,
|
|
457
|
+
adBreakDone: opts.adBreakDone,
|
|
458
|
+
onReward: opts.onReward,
|
|
459
|
+
onDismissed: opts.onDismissed,
|
|
460
|
+
onDone: opts.onDone,
|
|
461
|
+
onError: opts.onError,
|
|
462
|
+
onUnavailable: opts.onUnavailable
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function rewardedAd(rawOptions) {
|
|
466
|
+
const options = normalizeRewardedOptions(rawOptions);
|
|
467
|
+
const base = {
|
|
468
|
+
placement: options.placement,
|
|
469
|
+
reward: options.reward,
|
|
470
|
+
adBreakId: options.adBreakId
|
|
471
|
+
};
|
|
472
|
+
if (options.size) base.size = options.size;
|
|
473
|
+
post("rewarded_ad", void 0, Object.assign({ event: "requested" }, base));
|
|
474
|
+
if (!areRewardedAdsEnabled()) {
|
|
475
|
+
const disabled = Object.assign(
|
|
476
|
+
{ status: "unavailable", error: "host_disabled" },
|
|
477
|
+
base
|
|
478
|
+
);
|
|
479
|
+
post("rewarded_ad", void 0, Object.assign({ event: "unavailable" }, disabled));
|
|
480
|
+
safeCall(options.onUnavailable, disabled);
|
|
481
|
+
safeCall(options.onDone, disabled);
|
|
482
|
+
return Promise.resolve(disabled);
|
|
483
|
+
}
|
|
484
|
+
return ensureAdPlacement(options).then(function(adBreak) {
|
|
485
|
+
if (typeof adBreak !== "function") {
|
|
486
|
+
const unavailable = Object.assign(
|
|
487
|
+
{ status: "unavailable", error: "ad_break_unavailable" },
|
|
488
|
+
base
|
|
489
|
+
);
|
|
490
|
+
post("rewarded_ad", void 0, Object.assign({ event: "unavailable" }, unavailable));
|
|
491
|
+
safeCall(options.onUnavailable, unavailable);
|
|
492
|
+
safeCall(options.onDone, unavailable);
|
|
493
|
+
return unavailable;
|
|
494
|
+
}
|
|
495
|
+
requestAdPreload();
|
|
496
|
+
return new Promise(function(resolve) {
|
|
497
|
+
let resolved = false;
|
|
498
|
+
let status = "unavailable";
|
|
499
|
+
let rewardOffered = false;
|
|
500
|
+
const waitTimer = setTimeout(function() {
|
|
501
|
+
if (rewardOffered || resolved) return;
|
|
502
|
+
const timeout = Object.assign(
|
|
503
|
+
{ status: "unavailable", error: "ad_break_timeout" },
|
|
504
|
+
base
|
|
505
|
+
);
|
|
506
|
+
post("rewarded_ad", void 0, Object.assign({ event: "unavailable" }, timeout));
|
|
507
|
+
safeCall(options.onUnavailable, timeout);
|
|
508
|
+
done(timeout);
|
|
509
|
+
}, options.loadTimeoutMs);
|
|
510
|
+
function done(result) {
|
|
511
|
+
if (resolved) return;
|
|
512
|
+
resolved = true;
|
|
513
|
+
clearTimeout(waitTimer);
|
|
514
|
+
const finalResult = Object.assign(
|
|
515
|
+
{ status: "unavailable" },
|
|
516
|
+
base,
|
|
517
|
+
result || {}
|
|
518
|
+
);
|
|
519
|
+
post("rewarded_ad", void 0, Object.assign({ event: "done" }, finalResult));
|
|
520
|
+
safeCall(options.onDone, finalResult);
|
|
521
|
+
resolve(finalResult);
|
|
522
|
+
}
|
|
523
|
+
function startAd(showAdFn) {
|
|
524
|
+
if (resolved) return;
|
|
525
|
+
rewardOffered = true;
|
|
526
|
+
clearTimeout(waitTimer);
|
|
527
|
+
status = "ready";
|
|
528
|
+
let shown = false;
|
|
529
|
+
function show() {
|
|
530
|
+
if (shown) return;
|
|
531
|
+
shown = true;
|
|
532
|
+
post("rewarded_ad", void 0, Object.assign({ event: "started" }, base));
|
|
533
|
+
try {
|
|
534
|
+
showAdFn();
|
|
535
|
+
} catch (err) {
|
|
536
|
+
const showError = Object.assign(
|
|
537
|
+
{ status: "error", error: "show_ad_error" },
|
|
538
|
+
base
|
|
539
|
+
);
|
|
540
|
+
post("rewarded_ad", void 0, Object.assign({ event: "error" }, showError));
|
|
541
|
+
safeCall(options.onError, showError);
|
|
542
|
+
done(showError);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
post("rewarded_ad", void 0, Object.assign({ event: "ready" }, base));
|
|
546
|
+
try {
|
|
547
|
+
if (typeof options.beforeReward === "function") options.beforeReward(show);
|
|
548
|
+
else show();
|
|
549
|
+
} catch (err) {
|
|
550
|
+
const beforeError = Object.assign(
|
|
551
|
+
{ status: "error", error: "before_reward_error" },
|
|
552
|
+
base
|
|
553
|
+
);
|
|
554
|
+
post("rewarded_ad", void 0, Object.assign({ event: "error" }, beforeError));
|
|
555
|
+
safeCall(options.onError, beforeError);
|
|
556
|
+
done(beforeError);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
adBreak({
|
|
561
|
+
type: "reward",
|
|
562
|
+
name: options.placement,
|
|
563
|
+
size: options.size || void 0,
|
|
564
|
+
beforeReward: startAd,
|
|
565
|
+
adViewed: function() {
|
|
566
|
+
status = "viewed";
|
|
567
|
+
const viewed = Object.assign({ status }, base);
|
|
568
|
+
post("rewarded_ad", void 0, Object.assign({ event: "viewed" }, viewed));
|
|
569
|
+
safeCall(options.adViewed, viewed);
|
|
570
|
+
safeCall(options.onReward, viewed);
|
|
571
|
+
},
|
|
572
|
+
adDismissed: function() {
|
|
573
|
+
if (status !== "viewed") status = "dismissed";
|
|
574
|
+
const dismissed = Object.assign({ status }, base);
|
|
575
|
+
post("rewarded_ad", void 0, Object.assign({ event: "dismissed" }, dismissed));
|
|
576
|
+
safeCall(options.adDismissed, dismissed);
|
|
577
|
+
safeCall(options.onDismissed, dismissed);
|
|
578
|
+
},
|
|
579
|
+
adBreakDone: function(placementInfo) {
|
|
580
|
+
const result = rewardOffered || status !== "unavailable" ? { status } : { status: "unavailable", error: "no_rewarded_ad" };
|
|
581
|
+
if (placementInfo && typeof placementInfo === "object") {
|
|
582
|
+
result.breakStatus = cleanText(
|
|
583
|
+
placementInfo.breakStatus
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
if (result.status === "unavailable") {
|
|
587
|
+
post(
|
|
588
|
+
"rewarded_ad",
|
|
589
|
+
void 0,
|
|
590
|
+
Object.assign({ event: "unavailable" }, base, result)
|
|
591
|
+
);
|
|
592
|
+
safeCall(
|
|
593
|
+
options.onUnavailable,
|
|
594
|
+
Object.assign({ status: "unavailable" }, base, result)
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
safeCall(options.adBreakDone, placementInfo);
|
|
598
|
+
done(result);
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
} catch (err) {
|
|
602
|
+
const error = Object.assign(
|
|
603
|
+
{ status: "error", error: "ad_break_error" },
|
|
604
|
+
base
|
|
605
|
+
);
|
|
606
|
+
post("rewarded_ad", void 0, Object.assign({ event: "error" }, error));
|
|
607
|
+
safeCall(options.onError, error);
|
|
608
|
+
done(error);
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
function preloadRewardedAds(rawOptions) {
|
|
614
|
+
const options = normalizeRewardedOptions(rawOptions);
|
|
615
|
+
if (!areRewardedAdsEnabled()) return Promise.resolve(false);
|
|
616
|
+
return ensureAdPlacement(options).then(function(adBreak) {
|
|
617
|
+
if (typeof adBreak !== "function") return false;
|
|
618
|
+
requestAdPreload();
|
|
619
|
+
return true;
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
function whenHostConfig(graceMs) {
|
|
623
|
+
if (hostConfigReceived || !isEmbedded()) return Promise.resolve();
|
|
624
|
+
return new Promise(function(resolve) {
|
|
625
|
+
const timer = setTimeout(resolve, graceMs);
|
|
626
|
+
configWaiters.push(function() {
|
|
627
|
+
clearTimeout(timer);
|
|
628
|
+
resolve();
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
function getPlayer() {
|
|
633
|
+
if (blocked) return Promise.resolve(null);
|
|
634
|
+
return whenHostConfig(INIT_CONFIG_GRACE_MS).then(function() {
|
|
635
|
+
return hostPlayer ? { name: hostPlayer.name, avatarUrl: hostPlayer.avatarUrl } : null;
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
function init(options) {
|
|
639
|
+
configure(options);
|
|
640
|
+
post("init");
|
|
641
|
+
return whenHostConfig(INIT_CONFIG_GRACE_MS);
|
|
642
|
+
}
|
|
643
|
+
function ready() {
|
|
644
|
+
post("ready");
|
|
645
|
+
}
|
|
646
|
+
function normalizeRewardedBreakInput(optionsOrOnStart) {
|
|
647
|
+
if (typeof optionsOrOnStart === "function") return { onStart: optionsOrOnStart };
|
|
648
|
+
return optionsOrOnStart && typeof optionsOrOnStart === "object" ? optionsOrOnStart : {};
|
|
649
|
+
}
|
|
650
|
+
function rewardedBreak(optionsOrOnStart) {
|
|
651
|
+
const rawOptions = normalizeRewardedBreakInput(optionsOrOnStart);
|
|
652
|
+
const normalized = normalizeRewardedOptions(rawOptions);
|
|
653
|
+
const base = {
|
|
654
|
+
adBreakId: normalized.adBreakId,
|
|
655
|
+
placement: normalized.placement,
|
|
656
|
+
reward: normalized.reward
|
|
657
|
+
};
|
|
658
|
+
if (normalized.size) base.size = normalized.size;
|
|
659
|
+
if (rewardedBreakActive) {
|
|
660
|
+
post(
|
|
661
|
+
"rewarded_ad",
|
|
662
|
+
void 0,
|
|
663
|
+
Object.assign(
|
|
664
|
+
{ event: "unavailable", status: "unavailable", error: "ad_in_progress" },
|
|
665
|
+
base
|
|
666
|
+
)
|
|
667
|
+
);
|
|
668
|
+
return Promise.resolve(false);
|
|
669
|
+
}
|
|
670
|
+
rewardedBreakActive = true;
|
|
671
|
+
let startCalled = false;
|
|
672
|
+
return rewardedAd(
|
|
673
|
+
Object.assign({}, rawOptions, {
|
|
674
|
+
beforeReward: function(showAd) {
|
|
675
|
+
if (startCalled) return;
|
|
676
|
+
startCalled = true;
|
|
677
|
+
safeCall(rawOptions.onStart);
|
|
678
|
+
showAd();
|
|
679
|
+
}
|
|
680
|
+
})
|
|
681
|
+
).then(
|
|
682
|
+
function(result) {
|
|
683
|
+
rewardedBreakActive = false;
|
|
684
|
+
return Boolean(result && result.status === "viewed");
|
|
685
|
+
},
|
|
686
|
+
function() {
|
|
687
|
+
rewardedBreakActive = false;
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
window.addEventListener("message", function(event) {
|
|
693
|
+
if (event.source !== window.parent) return;
|
|
694
|
+
if (!hostnameAllowed(event.origin, DEFAULT_ALLOW)) return;
|
|
695
|
+
applyHostConfig(event.data);
|
|
696
|
+
});
|
|
697
|
+
const sdk = {
|
|
698
|
+
lockToHost,
|
|
699
|
+
init,
|
|
700
|
+
configure,
|
|
701
|
+
preloadRewardedAds,
|
|
702
|
+
preloadRewardedAd: preloadRewardedAds,
|
|
703
|
+
ready,
|
|
704
|
+
gameLoadingFinished: ready,
|
|
705
|
+
gameplayStart: function() {
|
|
706
|
+
post("gameplay_start");
|
|
707
|
+
},
|
|
708
|
+
gameplayStop: function() {
|
|
709
|
+
post("gameplay_stop");
|
|
710
|
+
},
|
|
711
|
+
submitScore: function(score, opts) {
|
|
712
|
+
post("score", score, opts && opts.mode === "daily" ? { mode: "daily" } : void 0);
|
|
713
|
+
},
|
|
714
|
+
gameOver: function(score, opts) {
|
|
715
|
+
post("gameover", score, opts && opts.mode === "daily" ? { mode: "daily" } : void 0);
|
|
716
|
+
},
|
|
717
|
+
xrSessionStart: function() {
|
|
718
|
+
post("xr_session_start");
|
|
719
|
+
},
|
|
720
|
+
xrSessionEnd: function() {
|
|
721
|
+
post("xr_session_end");
|
|
722
|
+
},
|
|
723
|
+
rewardedAd,
|
|
724
|
+
showRewardedAd: rewardedAd,
|
|
725
|
+
rewardedBreak,
|
|
726
|
+
save: function(blob) {
|
|
727
|
+
return request("save", String(blob));
|
|
728
|
+
},
|
|
729
|
+
load: function() {
|
|
730
|
+
return request("load");
|
|
731
|
+
},
|
|
732
|
+
getPlayer,
|
|
733
|
+
getVariant: function(key, variants) {
|
|
734
|
+
const sorted = Array.isArray(variants) ? variants.slice().sort() : [];
|
|
735
|
+
const fallback = sorted.length > 0 ? sorted[0] : null;
|
|
736
|
+
if (typeof key !== "string" || !key || sorted.length < 2) {
|
|
737
|
+
return Promise.resolve(fallback);
|
|
738
|
+
}
|
|
739
|
+
return request("experiment", { key, variants: sorted }).then(
|
|
740
|
+
function(variant) {
|
|
741
|
+
return typeof variant === "string" ? variant : fallback;
|
|
742
|
+
},
|
|
743
|
+
function() {
|
|
744
|
+
return fallback;
|
|
745
|
+
}
|
|
746
|
+
);
|
|
747
|
+
},
|
|
748
|
+
version: VERSION
|
|
749
|
+
};
|
|
750
|
+
sdk._request = request;
|
|
751
|
+
return sdk;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/inert.ts
|
|
755
|
+
function sdkError2(code) {
|
|
756
|
+
const err = new Error(code);
|
|
757
|
+
err.code = code;
|
|
758
|
+
return err;
|
|
759
|
+
}
|
|
760
|
+
function createInertBBArcade() {
|
|
761
|
+
const noop = () => {
|
|
762
|
+
};
|
|
763
|
+
const unavailable = () => Promise.resolve({
|
|
764
|
+
status: "unavailable",
|
|
765
|
+
error: "ad_break_unavailable",
|
|
766
|
+
placement: "rewarded",
|
|
767
|
+
reward: "reward",
|
|
768
|
+
adBreakId: "bbad_inert"
|
|
769
|
+
});
|
|
770
|
+
return {
|
|
771
|
+
lockToHost: noop,
|
|
772
|
+
init: () => Promise.resolve(),
|
|
773
|
+
configure: noop,
|
|
774
|
+
preloadRewardedAds: () => Promise.resolve(false),
|
|
775
|
+
preloadRewardedAd: () => Promise.resolve(false),
|
|
776
|
+
ready: noop,
|
|
777
|
+
gameLoadingFinished: noop,
|
|
778
|
+
gameplayStart: noop,
|
|
779
|
+
gameplayStop: noop,
|
|
780
|
+
submitScore: noop,
|
|
781
|
+
gameOver: noop,
|
|
782
|
+
xrSessionStart: noop,
|
|
783
|
+
xrSessionEnd: noop,
|
|
784
|
+
rewardedAd: unavailable,
|
|
785
|
+
showRewardedAd: unavailable,
|
|
786
|
+
rewardedBreak: () => Promise.resolve(false),
|
|
787
|
+
save: () => Promise.reject(sdkError2("unsupported")),
|
|
788
|
+
load: () => Promise.reject(sdkError2("unsupported")),
|
|
789
|
+
getPlayer: () => Promise.resolve(null),
|
|
790
|
+
getVariant: (key, variants) => {
|
|
791
|
+
const sorted = Array.isArray(variants) ? variants.slice().sort() : [];
|
|
792
|
+
return Promise.resolve(sorted.length > 0 ? sorted[0] : null);
|
|
793
|
+
},
|
|
794
|
+
version: VERSION
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// src/index.ts
|
|
799
|
+
var BBArcade = typeof window === "undefined" ? createInertBBArcade() : createBBArcade();
|
|
800
|
+
|
|
801
|
+
// src/multiplayer.ts
|
|
802
|
+
var WELCOME_TIMEOUT_MS = 1e4;
|
|
803
|
+
var MAX_RECONNECT_ATTEMPTS = 3;
|
|
804
|
+
var CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
805
|
+
var CODE_LENGTH = 4;
|
|
806
|
+
function sdkError3(code) {
|
|
807
|
+
const err = new Error(code);
|
|
808
|
+
err.code = code;
|
|
809
|
+
return err;
|
|
810
|
+
}
|
|
811
|
+
function randomChars(length) {
|
|
812
|
+
const indices = new Uint8Array(length);
|
|
813
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
814
|
+
crypto.getRandomValues(indices);
|
|
815
|
+
} else {
|
|
816
|
+
for (let i = 0; i < length; i++) indices[i] = Math.floor(Math.random() * 256);
|
|
817
|
+
}
|
|
818
|
+
let out = "";
|
|
819
|
+
for (let i = 0; i < length; i++) out += CODE_ALPHABET[indices[i] % CODE_ALPHABET.length];
|
|
820
|
+
return out;
|
|
821
|
+
}
|
|
822
|
+
function generateCode() {
|
|
823
|
+
return randomChars(CODE_LENGTH);
|
|
824
|
+
}
|
|
825
|
+
var guestKey = null;
|
|
826
|
+
function getGuestKey() {
|
|
827
|
+
if (!guestKey) guestKey = randomChars(16);
|
|
828
|
+
return guestKey;
|
|
829
|
+
}
|
|
830
|
+
function requestTicket(roomId) {
|
|
831
|
+
const request = BBArcade._request;
|
|
832
|
+
if (typeof request !== "function") return Promise.reject(sdkError3("unsupported"));
|
|
833
|
+
return request("mp_ticket", { roomId, guestKey: getGuestKey() }).then((grant) => {
|
|
834
|
+
const g = grant;
|
|
835
|
+
if (!g || typeof g.ticket !== "string" || typeof g.roomUrl !== "string") {
|
|
836
|
+
throw sdkError3("error");
|
|
837
|
+
}
|
|
838
|
+
return { ticket: g.ticket, roomUrl: g.roomUrl, expiresAt: Number(g.expiresAt) || 0 };
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
var RoomConnection = class {
|
|
842
|
+
constructor(code, devGrant) {
|
|
843
|
+
this.playerId = "";
|
|
844
|
+
this.players = [];
|
|
845
|
+
this.state = null;
|
|
846
|
+
this.ws = null;
|
|
847
|
+
// Keyed by event name; values are erased to (unknown) => void internally and
|
|
848
|
+
// retyped at the on()/emit() boundary, which keeps the public API strictly
|
|
849
|
+
// typed without fighting generic index-assignment rules.
|
|
850
|
+
this.handlers = {};
|
|
851
|
+
this.seq = 0;
|
|
852
|
+
this.intentionalClose = false;
|
|
853
|
+
this.ended = false;
|
|
854
|
+
// Set when the server definitively rejected us (bad ticket, room full/over)
|
|
855
|
+
// or the welcome timed out — permanent outcomes that must not reconnect.
|
|
856
|
+
this.terminal = false;
|
|
857
|
+
this.reconnectAttempts = 0;
|
|
858
|
+
this.code = code;
|
|
859
|
+
this.devGrant = devGrant;
|
|
860
|
+
}
|
|
861
|
+
/** Connect and resolve on the server's welcome (post-ticket-verification). */
|
|
862
|
+
connect(grant) {
|
|
863
|
+
return new Promise((resolve, reject) => {
|
|
864
|
+
let settled = false;
|
|
865
|
+
const url = grant.roomUrl + (grant.roomUrl.indexOf("?") === -1 ? "?" : "&") + "ticket=" + encodeURIComponent(grant.ticket);
|
|
866
|
+
let ws;
|
|
867
|
+
try {
|
|
868
|
+
ws = new WebSocket(url);
|
|
869
|
+
} catch (err) {
|
|
870
|
+
return reject(sdkError3("error"));
|
|
871
|
+
}
|
|
872
|
+
this.ws = ws;
|
|
873
|
+
const welcomeTimer = setTimeout(() => {
|
|
874
|
+
if (settled) return;
|
|
875
|
+
settled = true;
|
|
876
|
+
this.terminal = true;
|
|
877
|
+
try {
|
|
878
|
+
ws.close();
|
|
879
|
+
} catch (err) {
|
|
880
|
+
}
|
|
881
|
+
reject(sdkError3("error"));
|
|
882
|
+
}, WELCOME_TIMEOUT_MS);
|
|
883
|
+
ws.onmessage = (event) => {
|
|
884
|
+
var _a, _b, _c, _d, _e;
|
|
885
|
+
let msg;
|
|
886
|
+
try {
|
|
887
|
+
msg = JSON.parse(String(event.data));
|
|
888
|
+
} catch (err) {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
if (!msg || typeof msg.t !== "string") return;
|
|
892
|
+
if (msg.t === "welcome") {
|
|
893
|
+
this.playerId = String((_a = msg.playerId) != null ? _a : "");
|
|
894
|
+
this.players = Array.isArray(msg.players) ? msg.players : [];
|
|
895
|
+
this.state = (_b = msg.state) != null ? _b : null;
|
|
896
|
+
this.reconnectAttempts = 0;
|
|
897
|
+
if (!settled) {
|
|
898
|
+
settled = true;
|
|
899
|
+
clearTimeout(welcomeTimer);
|
|
900
|
+
resolve(this);
|
|
901
|
+
}
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
if (msg.t === "snapshot") {
|
|
905
|
+
this.state = (_c = msg.state) != null ? _c : this.state;
|
|
906
|
+
this.emit("snapshot", { tick: Number(msg.tick) || 0, state: this.state });
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (msg.t === "player_join") {
|
|
910
|
+
const player = msg.player;
|
|
911
|
+
if (player && player.id && !this.players.some((p) => p.id === player.id)) {
|
|
912
|
+
this.players.push(player);
|
|
913
|
+
}
|
|
914
|
+
this.emit("playerJoin", { player });
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
if (msg.t === "player_leave") {
|
|
918
|
+
const playerId = String((_d = msg.playerId) != null ? _d : "");
|
|
919
|
+
this.players = this.players.filter((p) => p.id !== playerId);
|
|
920
|
+
this.emit("playerLeave", { playerId });
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
if (msg.t === "event") {
|
|
924
|
+
this.emit("event", msg.data);
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (msg.t === "match_end") {
|
|
928
|
+
this.ended = true;
|
|
929
|
+
this.emit("end", {
|
|
930
|
+
results: Array.isArray(msg.results) ? msg.results : []
|
|
931
|
+
});
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
if (msg.t === "error") {
|
|
935
|
+
const code = String((_e = msg.code) != null ? _e : "error");
|
|
936
|
+
if (!settled) {
|
|
937
|
+
settled = true;
|
|
938
|
+
this.terminal = true;
|
|
939
|
+
clearTimeout(welcomeTimer);
|
|
940
|
+
try {
|
|
941
|
+
ws.close();
|
|
942
|
+
} catch (err) {
|
|
943
|
+
}
|
|
944
|
+
reject(sdkError3(code === "unauthenticated" ? "unauthenticated" : "rejected"));
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
this.emit("error", { code });
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
ws.onclose = () => {
|
|
951
|
+
if (!settled) {
|
|
952
|
+
settled = true;
|
|
953
|
+
clearTimeout(welcomeTimer);
|
|
954
|
+
reject(sdkError3("error"));
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (this.intentionalClose || this.ended || this.terminal) {
|
|
958
|
+
this.emit("close", { reconnecting: false });
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
void this.reconnect();
|
|
962
|
+
};
|
|
963
|
+
ws.onerror = () => {
|
|
964
|
+
};
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
async reconnect() {
|
|
968
|
+
var _a;
|
|
969
|
+
if (this.intentionalClose || this.ended || this.terminal) return;
|
|
970
|
+
if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
971
|
+
this.emit("close", { reconnecting: false });
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
this.reconnectAttempts += 1;
|
|
975
|
+
this.emit("close", { reconnecting: true });
|
|
976
|
+
await new Promise((r) => setTimeout(r, 400 * this.reconnectAttempts));
|
|
977
|
+
if (this.intentionalClose || this.ended || this.terminal) return;
|
|
978
|
+
try {
|
|
979
|
+
const grant = (_a = this.devGrant) != null ? _a : await requestTicket(this.code);
|
|
980
|
+
if (this.intentionalClose || this.ended || this.terminal) return;
|
|
981
|
+
await this.connect(grant);
|
|
982
|
+
if (this.intentionalClose) this.leave();
|
|
983
|
+
} catch (err) {
|
|
984
|
+
void this.reconnect();
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
send(input) {
|
|
988
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
989
|
+
this.seq += 1;
|
|
990
|
+
try {
|
|
991
|
+
this.ws.send(JSON.stringify({ t: "input", seq: this.seq, data: input }));
|
|
992
|
+
} catch (err) {
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
on(event, handler) {
|
|
996
|
+
var _a, _b;
|
|
997
|
+
const list = (_b = (_a = this.handlers)[event]) != null ? _b : _a[event] = [];
|
|
998
|
+
const entry = handler;
|
|
999
|
+
list.push(entry);
|
|
1000
|
+
return () => {
|
|
1001
|
+
const idx = list.indexOf(entry);
|
|
1002
|
+
if (idx !== -1) list.splice(idx, 1);
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
leave() {
|
|
1006
|
+
var _a;
|
|
1007
|
+
this.intentionalClose = true;
|
|
1008
|
+
try {
|
|
1009
|
+
(_a = this.ws) == null ? void 0 : _a.close(1e3);
|
|
1010
|
+
} catch (err) {
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
emit(event, data) {
|
|
1014
|
+
const list = this.handlers[event];
|
|
1015
|
+
if (!list) return;
|
|
1016
|
+
for (const handler of list.slice()) {
|
|
1017
|
+
try {
|
|
1018
|
+
handler(data);
|
|
1019
|
+
} catch (err) {
|
|
1020
|
+
setTimeout(() => {
|
|
1021
|
+
throw err;
|
|
1022
|
+
}, 0);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
function joinRoom(options) {
|
|
1028
|
+
const opts = options != null ? options : {};
|
|
1029
|
+
if (typeof window === "undefined" || typeof WebSocket === "undefined") {
|
|
1030
|
+
return Promise.reject(sdkError3("unsupported"));
|
|
1031
|
+
}
|
|
1032
|
+
const code = opts.create || !opts.code ? generateCode() : opts.code.toUpperCase();
|
|
1033
|
+
if (opts.roomUrl && opts.ticket) {
|
|
1034
|
+
const devGrant = { ticket: opts.ticket, roomUrl: opts.roomUrl, expiresAt: 0 };
|
|
1035
|
+
return new RoomConnection(code, devGrant).connect(devGrant);
|
|
1036
|
+
}
|
|
1037
|
+
return requestTicket(code).then((grant) => new RoomConnection(code, null).connect(grant));
|
|
1038
|
+
}
|
|
1039
|
+
var multiplayer = { joinRoom };
|
|
1040
|
+
|
|
1041
|
+
// src/global.ts
|
|
1042
|
+
BBArcade.multiplayer = multiplayer;
|
|
1043
|
+
window.BBArcade = BBArcade;
|
|
1044
|
+
})();
|