@bountyboard/arcade-sdk 1.1.0 → 1.2.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 +10 -7
- package/CHANGELOG.md +38 -0
- package/README.md +332 -86
- package/dist/{chunk-OWOESH4X.js → chunk-KDBBR532.js} +51 -9
- package/dist/chunk-KDBBR532.js.map +1 -0
- package/dist/global-sdk.d.ts +53 -3
- package/dist/global.js +139 -38
- package/dist/index.cjs +50 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/multiplayer.cjs +139 -38
- package/dist/multiplayer.cjs.map +1 -1
- package/dist/multiplayer.d.cts +2 -2
- package/dist/multiplayer.d.ts +2 -2
- package/dist/multiplayer.js +88 -29
- package/dist/multiplayer.js.map +1 -1
- package/dist/{types-Cq8p7ZkC.d.cts → types-dtsNXYZW.d.cts} +53 -3
- package/dist/{types-Cq8p7ZkC.d.ts → types-dtsNXYZW.d.ts} +53 -3
- package/docs/external-authoritative-servers.md +265 -0
- package/docs/llms.txt +715 -0
- package/docs/relay-rooms.md +134 -0
- package/package.json +2 -1
- package/dist/chunk-OWOESH4X.js.map +0 -1
package/dist/global.js
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
var AD_PLACEMENT_SCRIPT_ID = "bb-arcade-h5-ads";
|
|
13
13
|
var DEFAULT_AD_LOAD_TIMEOUT_MS = 6e3;
|
|
14
14
|
var SAVE_TIMEOUT_MS = 15e3;
|
|
15
|
+
var LOCK_TIMEOUT_MS = 5e3;
|
|
16
|
+
var MAX_SAVE_BYTES = 1024 * 1024;
|
|
15
17
|
var INIT_CONFIG_GRACE_MS = 1500;
|
|
16
18
|
var DEFAULT_ALLOW = [
|
|
17
19
|
"bountyboard.gg",
|
|
@@ -36,6 +38,14 @@
|
|
|
36
38
|
if (typeof value !== "string") return fallback || "";
|
|
37
39
|
return value.replace(/[^\w:.-]+/g, "_").slice(0, max || 80) || fallback || "";
|
|
38
40
|
}
|
|
41
|
+
var cachedTextEncoder = null;
|
|
42
|
+
function utf8ByteLength(value) {
|
|
43
|
+
if (typeof TextEncoder !== "undefined") {
|
|
44
|
+
cachedTextEncoder = cachedTextEncoder || new TextEncoder();
|
|
45
|
+
return cachedTextEncoder.encode(value).length;
|
|
46
|
+
}
|
|
47
|
+
return new Blob([value]).size;
|
|
48
|
+
}
|
|
39
49
|
function toBoolean(value) {
|
|
40
50
|
return value === true || value === "true" || value === "1";
|
|
41
51
|
}
|
|
@@ -155,13 +165,14 @@
|
|
|
155
165
|
};
|
|
156
166
|
let hostConfigReceived = false;
|
|
157
167
|
let hostPlayer = null;
|
|
168
|
+
const playerChangeHandlers = [];
|
|
158
169
|
let blocked = false;
|
|
159
170
|
const pending = {};
|
|
160
171
|
let seq = 0;
|
|
161
172
|
function post(type, score, extra) {
|
|
162
173
|
if (blocked) return;
|
|
163
174
|
const message = { source: SDK_MESSAGE_SOURCE, v: VERSION, type };
|
|
164
|
-
if (typeof score === "number" && isFinite(score)) message.score = Math.
|
|
175
|
+
if (typeof score === "number" && isFinite(score)) message.score = Math.trunc(score);
|
|
165
176
|
if (extra && typeof extra === "object") {
|
|
166
177
|
const fields = extra;
|
|
167
178
|
for (const key in fields) {
|
|
@@ -182,7 +193,7 @@
|
|
|
182
193
|
return true;
|
|
183
194
|
}
|
|
184
195
|
}
|
|
185
|
-
function request(type, payload) {
|
|
196
|
+
function request(type, payload, timeoutMs = SAVE_TIMEOUT_MS) {
|
|
186
197
|
return new Promise(function(resolve, reject) {
|
|
187
198
|
if (blocked) return reject(sdkError("error"));
|
|
188
199
|
if (!isEmbedded()) return reject(sdkError("unsupported"));
|
|
@@ -192,7 +203,7 @@
|
|
|
192
203
|
delete pending[requestId];
|
|
193
204
|
reject(sdkError("error"));
|
|
194
205
|
}
|
|
195
|
-
},
|
|
206
|
+
}, timeoutMs);
|
|
196
207
|
pending[requestId] = { resolve, reject, timer };
|
|
197
208
|
try {
|
|
198
209
|
window.parent.postMessage(
|
|
@@ -284,7 +295,7 @@
|
|
|
284
295
|
if (!hostnameAllowed(origin, allow)) blockHost(options);
|
|
285
296
|
return;
|
|
286
297
|
}
|
|
287
|
-
request("host").then(
|
|
298
|
+
request("host", void 0, LOCK_TIMEOUT_MS).then(
|
|
288
299
|
function(res) {
|
|
289
300
|
const r = res;
|
|
290
301
|
if (!hostnameAllowed(r && r.origin, allow)) blockHost(options);
|
|
@@ -308,7 +319,7 @@
|
|
|
308
319
|
lockToHostBestEffort(opts, allow);
|
|
309
320
|
return;
|
|
310
321
|
}
|
|
311
|
-
request("host_attest").then(
|
|
322
|
+
request("host_attest", void 0, LOCK_TIMEOUT_MS).then(
|
|
312
323
|
function(res) {
|
|
313
324
|
const r = res;
|
|
314
325
|
if (!r || !r.attestation) {
|
|
@@ -370,7 +381,15 @@
|
|
|
370
381
|
if (msg.source !== HOST_MESSAGE_SOURCE || msg.type !== "config") return;
|
|
371
382
|
hostConfigReceived = true;
|
|
372
383
|
if (msg.player !== void 0) {
|
|
373
|
-
|
|
384
|
+
const nextPlayer = sanitizePlayer(msg.player);
|
|
385
|
+
const changed = nextPlayer === null ? hostPlayer !== null : hostPlayer === null || nextPlayer.name !== hostPlayer.name || nextPlayer.avatarUrl !== hostPlayer.avatarUrl;
|
|
386
|
+
hostPlayer = nextPlayer;
|
|
387
|
+
if (changed) {
|
|
388
|
+
const snapshot = nextPlayer ? { name: nextPlayer.name, avatarUrl: nextPlayer.avatarUrl } : null;
|
|
389
|
+
for (let i = 0; i < playerChangeHandlers.length; i++) {
|
|
390
|
+
safeCall(playerChangeHandlers[i], snapshot);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
374
393
|
}
|
|
375
394
|
const rewardedAds = msg.rewardedAds;
|
|
376
395
|
if (rewardedAds && typeof rewardedAds === "object") {
|
|
@@ -502,6 +521,7 @@
|
|
|
502
521
|
let resolved = false;
|
|
503
522
|
let status = "unavailable";
|
|
504
523
|
let rewardOffered = false;
|
|
524
|
+
let adShown = false;
|
|
505
525
|
const waitTimer = setTimeout(function() {
|
|
506
526
|
if (rewardOffered || resolved) return;
|
|
507
527
|
const timeout = Object.assign(
|
|
@@ -546,6 +566,7 @@
|
|
|
546
566
|
}
|
|
547
567
|
post("rewarded_ad", void 0, Object.assign({ event: "started" }, base));
|
|
548
568
|
try {
|
|
569
|
+
adShown = true;
|
|
549
570
|
showAdFn();
|
|
550
571
|
} catch (err) {
|
|
551
572
|
const showError = Object.assign(
|
|
@@ -595,7 +616,13 @@
|
|
|
595
616
|
},
|
|
596
617
|
adBreakDone: function(placementInfo) {
|
|
597
618
|
if (resolved) return;
|
|
598
|
-
const result = rewardOffered || status !== "unavailable" ? {
|
|
619
|
+
const result = rewardOffered || status !== "unavailable" ? {
|
|
620
|
+
// A reward was offered but the ad never reached the screen
|
|
621
|
+
// (the game retained show() and let the break lapse): the
|
|
622
|
+
// break is over without a view, so resolve 'dismissed',
|
|
623
|
+
// not the non-terminal-looking 'ready'.
|
|
624
|
+
status: status === "ready" && !adShown ? "dismissed" : status
|
|
625
|
+
} : { status: "unavailable", error: "no_rewarded_ad" };
|
|
599
626
|
if (placementInfo && typeof placementInfo === "object") {
|
|
600
627
|
result.breakStatus = cleanText(
|
|
601
628
|
placementInfo.breakStatus
|
|
@@ -798,12 +825,25 @@
|
|
|
798
825
|
prepareRewardedBreak: prepareRewardedAd,
|
|
799
826
|
rewardedBreak,
|
|
800
827
|
save: function(blob) {
|
|
801
|
-
|
|
828
|
+
const data = String(blob);
|
|
829
|
+
if (utf8ByteLength(data) > MAX_SAVE_BYTES) {
|
|
830
|
+
return Promise.reject(sdkError("too_large"));
|
|
831
|
+
}
|
|
832
|
+
return request("save", data);
|
|
802
833
|
},
|
|
803
834
|
load: function() {
|
|
804
835
|
return request("load");
|
|
805
836
|
},
|
|
806
837
|
getPlayer,
|
|
838
|
+
onPlayerChange: function(handler) {
|
|
839
|
+
if (typeof handler !== "function") return function() {
|
|
840
|
+
};
|
|
841
|
+
playerChangeHandlers.push(handler);
|
|
842
|
+
return function() {
|
|
843
|
+
const idx = playerChangeHandlers.indexOf(handler);
|
|
844
|
+
if (idx !== -1) playerChangeHandlers.splice(idx, 1);
|
|
845
|
+
};
|
|
846
|
+
},
|
|
807
847
|
getVariant: function(key, variants) {
|
|
808
848
|
const sorted = Array.isArray(variants) ? variants.slice().sort() : [];
|
|
809
849
|
const fallback = sorted.length > 0 ? sorted[0] : null;
|
|
@@ -870,6 +910,8 @@
|
|
|
870
910
|
save: () => Promise.reject(sdkError2("unsupported")),
|
|
871
911
|
load: () => Promise.reject(sdkError2("unsupported")),
|
|
872
912
|
getPlayer: () => Promise.resolve(null),
|
|
913
|
+
onPlayerChange: () => () => {
|
|
914
|
+
},
|
|
873
915
|
getVariant: (key, variants) => {
|
|
874
916
|
const sorted = Array.isArray(variants) ? variants.slice().sort() : [];
|
|
875
917
|
return Promise.resolve(sorted.length > 0 ? sorted[0] : null);
|
|
@@ -883,13 +925,17 @@
|
|
|
883
925
|
|
|
884
926
|
// src/multiplayer.ts
|
|
885
927
|
var WELCOME_TIMEOUT_MS = 1e4;
|
|
928
|
+
var MIN_WELCOME_TIMEOUT_MS = 1e3;
|
|
929
|
+
var MAX_WELCOME_TIMEOUT_MS = 6e4;
|
|
886
930
|
var MAX_RECONNECT_ATTEMPTS = 3;
|
|
887
931
|
var MAX_JOIN_DATA_BYTES = 1024;
|
|
932
|
+
var SEAT_FULL_CLOSE_CODE = 4005;
|
|
888
933
|
var CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
889
934
|
var CODE_LENGTH = 4;
|
|
890
|
-
function sdkError3(code) {
|
|
935
|
+
function sdkError3(code, detail) {
|
|
891
936
|
const err = new Error(code);
|
|
892
937
|
err.code = code;
|
|
938
|
+
if (detail) err.detail = detail;
|
|
893
939
|
return err;
|
|
894
940
|
}
|
|
895
941
|
function randomChars(length) {
|
|
@@ -906,23 +952,13 @@
|
|
|
906
952
|
function generateCode() {
|
|
907
953
|
return randomChars(CODE_LENGTH);
|
|
908
954
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
else if (code < 2048) bytes += 2;
|
|
915
|
-
else if (code >= 55296 && code <= 56319 && i + 1 < value.length) {
|
|
916
|
-
const next = value.charCodeAt(i + 1);
|
|
917
|
-
if (next >= 56320 && next <= 57343) {
|
|
918
|
-
bytes += 4;
|
|
919
|
-
i += 1;
|
|
920
|
-
} else {
|
|
921
|
-
bytes += 3;
|
|
922
|
-
}
|
|
923
|
-
} else bytes += 3;
|
|
955
|
+
var cachedTextEncoder2 = null;
|
|
956
|
+
function utf8ByteLength2(value) {
|
|
957
|
+
if (typeof TextEncoder !== "undefined") {
|
|
958
|
+
cachedTextEncoder2 = cachedTextEncoder2 || new TextEncoder();
|
|
959
|
+
return cachedTextEncoder2.encode(value).length;
|
|
924
960
|
}
|
|
925
|
-
return
|
|
961
|
+
return new Blob([value]).size;
|
|
926
962
|
}
|
|
927
963
|
function serializeJoinData(data) {
|
|
928
964
|
if (data === void 0) return null;
|
|
@@ -933,28 +969,34 @@
|
|
|
933
969
|
} catch (e) {
|
|
934
970
|
throw sdkError3("rejected");
|
|
935
971
|
}
|
|
936
|
-
if (!serialized ||
|
|
972
|
+
if (!serialized || utf8ByteLength2(serialized) > MAX_JOIN_DATA_BYTES) {
|
|
937
973
|
throw sdkError3("rejected");
|
|
938
974
|
}
|
|
939
975
|
return serialized;
|
|
940
976
|
}
|
|
941
|
-
function requestTicket(
|
|
977
|
+
function requestTicket(payload) {
|
|
942
978
|
const request = BBArcade._request;
|
|
943
979
|
if (typeof request !== "function") return Promise.reject(sdkError3("unsupported"));
|
|
944
|
-
return request("mp_ticket",
|
|
980
|
+
return request("mp_ticket", payload).then((grant) => {
|
|
945
981
|
const g = grant;
|
|
946
982
|
if (!g || typeof g.ticket !== "string" || typeof g.roomUrl !== "string") {
|
|
947
983
|
throw sdkError3("error");
|
|
948
984
|
}
|
|
949
|
-
return {
|
|
985
|
+
return {
|
|
986
|
+
ticket: g.ticket,
|
|
987
|
+
roomUrl: g.roomUrl,
|
|
988
|
+
expiresAt: Number(g.expiresAt) || 0,
|
|
989
|
+
roomId: typeof g.roomId === "string" && g.roomId ? g.roomId : null
|
|
990
|
+
};
|
|
950
991
|
});
|
|
951
992
|
}
|
|
952
993
|
var RoomConnection = class {
|
|
953
|
-
constructor(code, devGrant, joinData) {
|
|
994
|
+
constructor(code, devGrant, joinData, welcomeTimeoutMs = WELCOME_TIMEOUT_MS) {
|
|
954
995
|
this.playerId = "";
|
|
955
996
|
this.players = [];
|
|
956
997
|
this.state = null;
|
|
957
998
|
this.connected = false;
|
|
999
|
+
this.latencyMs = null;
|
|
958
1000
|
this.ws = null;
|
|
959
1001
|
// Keyed by event name; values are erased to (unknown) => void internally and
|
|
960
1002
|
// retyped at the on()/emit() boundary, which keeps the public API strictly
|
|
@@ -967,15 +1009,20 @@
|
|
|
967
1009
|
// or the welcome timed out — permanent outcomes that must not reconnect.
|
|
968
1010
|
this.terminal = false;
|
|
969
1011
|
this.reconnectAttempts = 0;
|
|
1012
|
+
// The in-flight connect()'s settlement, so leave() can reject a join that
|
|
1013
|
+
// is still waiting on its welcome instead of stranding it on the timeout.
|
|
1014
|
+
this.pendingConnect = null;
|
|
970
1015
|
this.code = code;
|
|
971
1016
|
this.devGrant = devGrant;
|
|
972
1017
|
this.joinData = joinData;
|
|
1018
|
+
this.welcomeTimeoutMs = welcomeTimeoutMs;
|
|
973
1019
|
}
|
|
974
1020
|
/** Connect and resolve on the server's welcome (post-ticket-verification). */
|
|
975
1021
|
connect(grant) {
|
|
976
1022
|
return new Promise((resolve, reject) => {
|
|
977
1023
|
let settled = false;
|
|
978
1024
|
const url = grant.roomUrl + (grant.roomUrl.indexOf("?") === -1 ? "?" : "&") + "ticket=" + encodeURIComponent(grant.ticket) + (this.joinData === null ? "" : "&join=" + encodeURIComponent(this.joinData));
|
|
1025
|
+
const connectStartedAt = Date.now();
|
|
979
1026
|
let ws;
|
|
980
1027
|
try {
|
|
981
1028
|
ws = new WebSocket(url);
|
|
@@ -986,13 +1033,15 @@
|
|
|
986
1033
|
const welcomeTimer = setTimeout(() => {
|
|
987
1034
|
if (settled) return;
|
|
988
1035
|
settled = true;
|
|
1036
|
+
this.pendingConnect = null;
|
|
989
1037
|
this.terminal = true;
|
|
990
1038
|
try {
|
|
991
1039
|
ws.close();
|
|
992
1040
|
} catch (err) {
|
|
993
1041
|
}
|
|
994
1042
|
reject(sdkError3("error"));
|
|
995
|
-
},
|
|
1043
|
+
}, this.welcomeTimeoutMs);
|
|
1044
|
+
this.pendingConnect = { reject, timer: welcomeTimer };
|
|
996
1045
|
ws.onmessage = (event) => {
|
|
997
1046
|
var _a, _b, _c, _d, _e;
|
|
998
1047
|
let msg;
|
|
@@ -1003,14 +1052,23 @@
|
|
|
1003
1052
|
}
|
|
1004
1053
|
if (!msg || typeof msg.t !== "string") return;
|
|
1005
1054
|
if (msg.t === "welcome") {
|
|
1055
|
+
if (this.intentionalClose) {
|
|
1056
|
+
try {
|
|
1057
|
+
ws.close();
|
|
1058
|
+
} catch (err) {
|
|
1059
|
+
}
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1006
1062
|
const reconnected = this.reconnectAttempts > 0;
|
|
1007
1063
|
this.playerId = String((_a = msg.playerId) != null ? _a : "");
|
|
1008
1064
|
this.players = Array.isArray(msg.players) ? msg.players : [];
|
|
1009
1065
|
this.state = (_b = msg.state) != null ? _b : null;
|
|
1010
1066
|
this.connected = true;
|
|
1011
1067
|
this.reconnectAttempts = 0;
|
|
1068
|
+
this.latencyMs = Math.max(0, Date.now() - connectStartedAt);
|
|
1012
1069
|
if (!settled) {
|
|
1013
1070
|
settled = true;
|
|
1071
|
+
this.pendingConnect = null;
|
|
1014
1072
|
clearTimeout(welcomeTimer);
|
|
1015
1073
|
resolve(this);
|
|
1016
1074
|
}
|
|
@@ -1052,21 +1110,24 @@
|
|
|
1052
1110
|
if (!settled) {
|
|
1053
1111
|
settled = true;
|
|
1054
1112
|
this.terminal = true;
|
|
1113
|
+
this.pendingConnect = null;
|
|
1055
1114
|
clearTimeout(welcomeTimer);
|
|
1056
1115
|
try {
|
|
1057
1116
|
ws.close();
|
|
1058
1117
|
} catch (err) {
|
|
1059
1118
|
}
|
|
1060
|
-
reject(sdkError3(code === "unauthenticated" ? "unauthenticated" : "rejected"));
|
|
1119
|
+
reject(sdkError3(code === "unauthenticated" ? "unauthenticated" : "rejected", code));
|
|
1061
1120
|
return;
|
|
1062
1121
|
}
|
|
1063
1122
|
this.emit("error", { code });
|
|
1064
1123
|
}
|
|
1065
1124
|
};
|
|
1066
|
-
ws.onclose = () => {
|
|
1125
|
+
ws.onclose = (event) => {
|
|
1067
1126
|
this.connected = false;
|
|
1127
|
+
if (event && event.code === SEAT_FULL_CLOSE_CODE) this.terminal = true;
|
|
1068
1128
|
if (!settled) {
|
|
1069
1129
|
settled = true;
|
|
1130
|
+
this.pendingConnect = null;
|
|
1070
1131
|
clearTimeout(welcomeTimer);
|
|
1071
1132
|
reject(sdkError3("error"));
|
|
1072
1133
|
return;
|
|
@@ -1095,7 +1156,7 @@
|
|
|
1095
1156
|
await new Promise((r) => setTimeout(r, 400 * this.reconnectAttempts));
|
|
1096
1157
|
if (this.intentionalClose || this.ended || this.terminal) return;
|
|
1097
1158
|
try {
|
|
1098
|
-
const grant = (_a = this.devGrant) != null ? _a : await requestTicket(this.code);
|
|
1159
|
+
const grant = (_a = this.devGrant) != null ? _a : await requestTicket({ roomId: this.code });
|
|
1099
1160
|
if (this.intentionalClose || this.ended || this.terminal) return;
|
|
1100
1161
|
await this.connect(grant);
|
|
1101
1162
|
if (this.intentionalClose) this.leave();
|
|
@@ -1130,6 +1191,12 @@
|
|
|
1130
1191
|
var _a;
|
|
1131
1192
|
this.intentionalClose = true;
|
|
1132
1193
|
this.connected = false;
|
|
1194
|
+
if (this.pendingConnect) {
|
|
1195
|
+
const pending = this.pendingConnect;
|
|
1196
|
+
this.pendingConnect = null;
|
|
1197
|
+
clearTimeout(pending.timer);
|
|
1198
|
+
pending.reject(sdkError3("error"));
|
|
1199
|
+
}
|
|
1133
1200
|
try {
|
|
1134
1201
|
(_a = this.ws) == null ? void 0 : _a.close(1e3);
|
|
1135
1202
|
} catch (err) {
|
|
@@ -1149,23 +1216,57 @@
|
|
|
1149
1216
|
}
|
|
1150
1217
|
}
|
|
1151
1218
|
};
|
|
1219
|
+
var MATCH_ATTEMPTS = 3;
|
|
1220
|
+
function normalizeWelcomeTimeoutMs(value) {
|
|
1221
|
+
if (typeof value !== "number" || !isFinite(value)) return WELCOME_TIMEOUT_MS;
|
|
1222
|
+
return Math.min(MAX_WELCOME_TIMEOUT_MS, Math.max(MIN_WELCOME_TIMEOUT_MS, Math.floor(value)));
|
|
1223
|
+
}
|
|
1224
|
+
async function matchJoin(joinData, timeoutMs) {
|
|
1225
|
+
let lastError = sdkError3("error");
|
|
1226
|
+
for (let attempt = 0; attempt < MATCH_ATTEMPTS; attempt++) {
|
|
1227
|
+
const grant = await requestTicket({ matchmake: true });
|
|
1228
|
+
if (!grant.roomId) throw sdkError3("error");
|
|
1229
|
+
try {
|
|
1230
|
+
return await new RoomConnection(grant.roomId, null, joinData, timeoutMs).connect(grant);
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
lastError = error;
|
|
1233
|
+
const detail = error == null ? void 0 : error.detail;
|
|
1234
|
+
if (detail !== "room_full" && detail !== "room_over") throw error;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
throw lastError;
|
|
1238
|
+
}
|
|
1152
1239
|
function joinRoom(options) {
|
|
1153
1240
|
const opts = options != null ? options : {};
|
|
1154
1241
|
if (typeof window === "undefined" || typeof WebSocket === "undefined") {
|
|
1155
1242
|
return Promise.reject(sdkError3("unsupported"));
|
|
1156
1243
|
}
|
|
1157
|
-
const code = opts.create || !opts.code ? generateCode() : opts.code.toUpperCase();
|
|
1158
1244
|
let joinData;
|
|
1159
1245
|
try {
|
|
1160
1246
|
joinData = serializeJoinData(opts.joinData);
|
|
1161
1247
|
} catch (err) {
|
|
1162
1248
|
return Promise.reject(err);
|
|
1163
1249
|
}
|
|
1250
|
+
const timeoutMs = normalizeWelcomeTimeoutMs(opts.timeoutMs);
|
|
1251
|
+
if (opts.match) {
|
|
1252
|
+
if (opts.code || opts.create || opts.roomUrl || opts.ticket) {
|
|
1253
|
+
return Promise.reject(sdkError3("rejected"));
|
|
1254
|
+
}
|
|
1255
|
+
return matchJoin(joinData, timeoutMs);
|
|
1256
|
+
}
|
|
1257
|
+
const code = opts.create || !opts.code ? generateCode() : opts.code.toUpperCase();
|
|
1164
1258
|
if (opts.roomUrl && opts.ticket) {
|
|
1165
|
-
const devGrant = {
|
|
1166
|
-
|
|
1259
|
+
const devGrant = {
|
|
1260
|
+
ticket: opts.ticket,
|
|
1261
|
+
roomUrl: opts.roomUrl,
|
|
1262
|
+
expiresAt: 0,
|
|
1263
|
+
roomId: code
|
|
1264
|
+
};
|
|
1265
|
+
return new RoomConnection(code, devGrant, joinData, timeoutMs).connect(devGrant);
|
|
1167
1266
|
}
|
|
1168
|
-
return requestTicket(
|
|
1267
|
+
return requestTicket({ roomId: code }).then(
|
|
1268
|
+
(grant) => new RoomConnection(code, null, joinData, timeoutMs).connect(grant)
|
|
1269
|
+
);
|
|
1169
1270
|
}
|
|
1170
1271
|
var multiplayer = { joinRoom };
|
|
1171
1272
|
|
package/dist/index.cjs
CHANGED
|
@@ -38,6 +38,8 @@ var AD_PLACEMENT_SRC = "https://pagead2.googlesyndication.com/pagead/js/adsbygoo
|
|
|
38
38
|
var AD_PLACEMENT_SCRIPT_ID = "bb-arcade-h5-ads";
|
|
39
39
|
var DEFAULT_AD_LOAD_TIMEOUT_MS = 6e3;
|
|
40
40
|
var SAVE_TIMEOUT_MS = 15e3;
|
|
41
|
+
var LOCK_TIMEOUT_MS = 5e3;
|
|
42
|
+
var MAX_SAVE_BYTES = 1024 * 1024;
|
|
41
43
|
var INIT_CONFIG_GRACE_MS = 1500;
|
|
42
44
|
var DEFAULT_ALLOW = [
|
|
43
45
|
"bountyboard.gg",
|
|
@@ -62,6 +64,14 @@ function cleanText(value, fallback, max) {
|
|
|
62
64
|
if (typeof value !== "string") return fallback || "";
|
|
63
65
|
return value.replace(/[^\w:.-]+/g, "_").slice(0, max || 80) || fallback || "";
|
|
64
66
|
}
|
|
67
|
+
var cachedTextEncoder = null;
|
|
68
|
+
function utf8ByteLength(value) {
|
|
69
|
+
if (typeof TextEncoder !== "undefined") {
|
|
70
|
+
cachedTextEncoder = cachedTextEncoder || new TextEncoder();
|
|
71
|
+
return cachedTextEncoder.encode(value).length;
|
|
72
|
+
}
|
|
73
|
+
return new Blob([value]).size;
|
|
74
|
+
}
|
|
65
75
|
function toBoolean(value) {
|
|
66
76
|
return value === true || value === "true" || value === "1";
|
|
67
77
|
}
|
|
@@ -181,13 +191,14 @@ function createBBArcade() {
|
|
|
181
191
|
};
|
|
182
192
|
let hostConfigReceived = false;
|
|
183
193
|
let hostPlayer = null;
|
|
194
|
+
const playerChangeHandlers = [];
|
|
184
195
|
let blocked = false;
|
|
185
196
|
const pending = {};
|
|
186
197
|
let seq = 0;
|
|
187
198
|
function post(type, score, extra) {
|
|
188
199
|
if (blocked) return;
|
|
189
200
|
const message = { source: SDK_MESSAGE_SOURCE, v: VERSION, type };
|
|
190
|
-
if (typeof score === "number" && isFinite(score)) message.score = Math.
|
|
201
|
+
if (typeof score === "number" && isFinite(score)) message.score = Math.trunc(score);
|
|
191
202
|
if (extra && typeof extra === "object") {
|
|
192
203
|
const fields = extra;
|
|
193
204
|
for (const key in fields) {
|
|
@@ -208,7 +219,7 @@ function createBBArcade() {
|
|
|
208
219
|
return true;
|
|
209
220
|
}
|
|
210
221
|
}
|
|
211
|
-
function request(type, payload) {
|
|
222
|
+
function request(type, payload, timeoutMs = SAVE_TIMEOUT_MS) {
|
|
212
223
|
return new Promise(function(resolve, reject) {
|
|
213
224
|
if (blocked) return reject(sdkError("error"));
|
|
214
225
|
if (!isEmbedded()) return reject(sdkError("unsupported"));
|
|
@@ -218,7 +229,7 @@ function createBBArcade() {
|
|
|
218
229
|
delete pending[requestId];
|
|
219
230
|
reject(sdkError("error"));
|
|
220
231
|
}
|
|
221
|
-
},
|
|
232
|
+
}, timeoutMs);
|
|
222
233
|
pending[requestId] = { resolve, reject, timer };
|
|
223
234
|
try {
|
|
224
235
|
window.parent.postMessage(
|
|
@@ -310,7 +321,7 @@ function createBBArcade() {
|
|
|
310
321
|
if (!hostnameAllowed(origin, allow)) blockHost(options);
|
|
311
322
|
return;
|
|
312
323
|
}
|
|
313
|
-
request("host").then(
|
|
324
|
+
request("host", void 0, LOCK_TIMEOUT_MS).then(
|
|
314
325
|
function(res) {
|
|
315
326
|
const r = res;
|
|
316
327
|
if (!hostnameAllowed(r && r.origin, allow)) blockHost(options);
|
|
@@ -334,7 +345,7 @@ function createBBArcade() {
|
|
|
334
345
|
lockToHostBestEffort(opts, allow);
|
|
335
346
|
return;
|
|
336
347
|
}
|
|
337
|
-
request("host_attest").then(
|
|
348
|
+
request("host_attest", void 0, LOCK_TIMEOUT_MS).then(
|
|
338
349
|
function(res) {
|
|
339
350
|
const r = res;
|
|
340
351
|
if (!r || !r.attestation) {
|
|
@@ -396,7 +407,15 @@ function createBBArcade() {
|
|
|
396
407
|
if (msg.source !== HOST_MESSAGE_SOURCE || msg.type !== "config") return;
|
|
397
408
|
hostConfigReceived = true;
|
|
398
409
|
if (msg.player !== void 0) {
|
|
399
|
-
|
|
410
|
+
const nextPlayer = sanitizePlayer(msg.player);
|
|
411
|
+
const changed = nextPlayer === null ? hostPlayer !== null : hostPlayer === null || nextPlayer.name !== hostPlayer.name || nextPlayer.avatarUrl !== hostPlayer.avatarUrl;
|
|
412
|
+
hostPlayer = nextPlayer;
|
|
413
|
+
if (changed) {
|
|
414
|
+
const snapshot = nextPlayer ? { name: nextPlayer.name, avatarUrl: nextPlayer.avatarUrl } : null;
|
|
415
|
+
for (let i = 0; i < playerChangeHandlers.length; i++) {
|
|
416
|
+
safeCall(playerChangeHandlers[i], snapshot);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
400
419
|
}
|
|
401
420
|
const rewardedAds = msg.rewardedAds;
|
|
402
421
|
if (rewardedAds && typeof rewardedAds === "object") {
|
|
@@ -528,6 +547,7 @@ function createBBArcade() {
|
|
|
528
547
|
let resolved = false;
|
|
529
548
|
let status = "unavailable";
|
|
530
549
|
let rewardOffered = false;
|
|
550
|
+
let adShown = false;
|
|
531
551
|
const waitTimer = setTimeout(function() {
|
|
532
552
|
if (rewardOffered || resolved) return;
|
|
533
553
|
const timeout = Object.assign(
|
|
@@ -572,6 +592,7 @@ function createBBArcade() {
|
|
|
572
592
|
}
|
|
573
593
|
post("rewarded_ad", void 0, Object.assign({ event: "started" }, base));
|
|
574
594
|
try {
|
|
595
|
+
adShown = true;
|
|
575
596
|
showAdFn();
|
|
576
597
|
} catch (err) {
|
|
577
598
|
const showError = Object.assign(
|
|
@@ -621,7 +642,13 @@ function createBBArcade() {
|
|
|
621
642
|
},
|
|
622
643
|
adBreakDone: function(placementInfo) {
|
|
623
644
|
if (resolved) return;
|
|
624
|
-
const result = rewardOffered || status !== "unavailable" ? {
|
|
645
|
+
const result = rewardOffered || status !== "unavailable" ? {
|
|
646
|
+
// A reward was offered but the ad never reached the screen
|
|
647
|
+
// (the game retained show() and let the break lapse): the
|
|
648
|
+
// break is over without a view, so resolve 'dismissed',
|
|
649
|
+
// not the non-terminal-looking 'ready'.
|
|
650
|
+
status: status === "ready" && !adShown ? "dismissed" : status
|
|
651
|
+
} : { status: "unavailable", error: "no_rewarded_ad" };
|
|
625
652
|
if (placementInfo && typeof placementInfo === "object") {
|
|
626
653
|
result.breakStatus = cleanText(
|
|
627
654
|
placementInfo.breakStatus
|
|
@@ -824,12 +851,25 @@ function createBBArcade() {
|
|
|
824
851
|
prepareRewardedBreak: prepareRewardedAd,
|
|
825
852
|
rewardedBreak,
|
|
826
853
|
save: function(blob) {
|
|
827
|
-
|
|
854
|
+
const data = String(blob);
|
|
855
|
+
if (utf8ByteLength(data) > MAX_SAVE_BYTES) {
|
|
856
|
+
return Promise.reject(sdkError("too_large"));
|
|
857
|
+
}
|
|
858
|
+
return request("save", data);
|
|
828
859
|
},
|
|
829
860
|
load: function() {
|
|
830
861
|
return request("load");
|
|
831
862
|
},
|
|
832
863
|
getPlayer,
|
|
864
|
+
onPlayerChange: function(handler) {
|
|
865
|
+
if (typeof handler !== "function") return function() {
|
|
866
|
+
};
|
|
867
|
+
playerChangeHandlers.push(handler);
|
|
868
|
+
return function() {
|
|
869
|
+
const idx = playerChangeHandlers.indexOf(handler);
|
|
870
|
+
if (idx !== -1) playerChangeHandlers.splice(idx, 1);
|
|
871
|
+
};
|
|
872
|
+
},
|
|
833
873
|
getVariant: function(key, variants) {
|
|
834
874
|
const sorted = Array.isArray(variants) ? variants.slice().sort() : [];
|
|
835
875
|
const fallback = sorted.length > 0 ? sorted[0] : null;
|
|
@@ -896,6 +936,8 @@ function createInertBBArcade() {
|
|
|
896
936
|
save: () => Promise.reject(sdkError2("unsupported")),
|
|
897
937
|
load: () => Promise.reject(sdkError2("unsupported")),
|
|
898
938
|
getPlayer: () => Promise.resolve(null),
|
|
939
|
+
onPlayerChange: () => () => {
|
|
940
|
+
},
|
|
899
941
|
getVariant: (key, variants) => {
|
|
900
942
|
const sorted = Array.isArray(variants) ? variants.slice().sort() : [];
|
|
901
943
|
return Promise.resolve(sorted.length > 0 ? sorted[0] : null);
|