@bountyboard/arcade-sdk 1.0.1 → 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 -5
- package/CHANGELOG.md +68 -0
- package/README.md +331 -75
- package/dist/{chunk-OWOESH4X.js → chunk-KDBBR532.js} +51 -9
- package/dist/chunk-KDBBR532.js.map +1 -0
- package/dist/global-sdk.d.ts +66 -3
- package/dist/global.js +175 -26
- 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 +175 -26
- 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 +126 -19
- package/dist/multiplayer.js.map +1 -1
- package/dist/{types-BKChtV3H.d.cts → types-dtsNXYZW.d.cts} +69 -3
- package/dist/{types-BKChtV3H.d.ts → types-dtsNXYZW.d.ts} +69 -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 +6 -3
- 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,12 +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;
|
|
931
|
+
var MAX_JOIN_DATA_BYTES = 1024;
|
|
932
|
+
var SEAT_FULL_CLOSE_CODE = 4005;
|
|
887
933
|
var CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
888
934
|
var CODE_LENGTH = 4;
|
|
889
|
-
function sdkError3(code) {
|
|
935
|
+
function sdkError3(code, detail) {
|
|
890
936
|
const err = new Error(code);
|
|
891
937
|
err.code = code;
|
|
938
|
+
if (detail) err.detail = detail;
|
|
892
939
|
return err;
|
|
893
940
|
}
|
|
894
941
|
function randomChars(length) {
|
|
@@ -905,27 +952,51 @@
|
|
|
905
952
|
function generateCode() {
|
|
906
953
|
return randomChars(CODE_LENGTH);
|
|
907
954
|
}
|
|
908
|
-
var
|
|
909
|
-
function
|
|
910
|
-
if (
|
|
911
|
-
|
|
955
|
+
var cachedTextEncoder2 = null;
|
|
956
|
+
function utf8ByteLength2(value) {
|
|
957
|
+
if (typeof TextEncoder !== "undefined") {
|
|
958
|
+
cachedTextEncoder2 = cachedTextEncoder2 || new TextEncoder();
|
|
959
|
+
return cachedTextEncoder2.encode(value).length;
|
|
960
|
+
}
|
|
961
|
+
return new Blob([value]).size;
|
|
912
962
|
}
|
|
913
|
-
function
|
|
963
|
+
function serializeJoinData(data) {
|
|
964
|
+
if (data === void 0) return null;
|
|
965
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) throw sdkError3("rejected");
|
|
966
|
+
let serialized;
|
|
967
|
+
try {
|
|
968
|
+
serialized = JSON.stringify(data);
|
|
969
|
+
} catch (e) {
|
|
970
|
+
throw sdkError3("rejected");
|
|
971
|
+
}
|
|
972
|
+
if (!serialized || utf8ByteLength2(serialized) > MAX_JOIN_DATA_BYTES) {
|
|
973
|
+
throw sdkError3("rejected");
|
|
974
|
+
}
|
|
975
|
+
return serialized;
|
|
976
|
+
}
|
|
977
|
+
function requestTicket(payload) {
|
|
914
978
|
const request = BBArcade._request;
|
|
915
979
|
if (typeof request !== "function") return Promise.reject(sdkError3("unsupported"));
|
|
916
|
-
return request("mp_ticket",
|
|
980
|
+
return request("mp_ticket", payload).then((grant) => {
|
|
917
981
|
const g = grant;
|
|
918
982
|
if (!g || typeof g.ticket !== "string" || typeof g.roomUrl !== "string") {
|
|
919
983
|
throw sdkError3("error");
|
|
920
984
|
}
|
|
921
|
-
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
|
+
};
|
|
922
991
|
});
|
|
923
992
|
}
|
|
924
993
|
var RoomConnection = class {
|
|
925
|
-
constructor(code, devGrant) {
|
|
994
|
+
constructor(code, devGrant, joinData, welcomeTimeoutMs = WELCOME_TIMEOUT_MS) {
|
|
926
995
|
this.playerId = "";
|
|
927
996
|
this.players = [];
|
|
928
997
|
this.state = null;
|
|
998
|
+
this.connected = false;
|
|
999
|
+
this.latencyMs = null;
|
|
929
1000
|
this.ws = null;
|
|
930
1001
|
// Keyed by event name; values are erased to (unknown) => void internally and
|
|
931
1002
|
// retyped at the on()/emit() boundary, which keeps the public API strictly
|
|
@@ -938,14 +1009,20 @@
|
|
|
938
1009
|
// or the welcome timed out — permanent outcomes that must not reconnect.
|
|
939
1010
|
this.terminal = false;
|
|
940
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;
|
|
941
1015
|
this.code = code;
|
|
942
1016
|
this.devGrant = devGrant;
|
|
1017
|
+
this.joinData = joinData;
|
|
1018
|
+
this.welcomeTimeoutMs = welcomeTimeoutMs;
|
|
943
1019
|
}
|
|
944
1020
|
/** Connect and resolve on the server's welcome (post-ticket-verification). */
|
|
945
1021
|
connect(grant) {
|
|
946
1022
|
return new Promise((resolve, reject) => {
|
|
947
1023
|
let settled = false;
|
|
948
|
-
const url = grant.roomUrl + (grant.roomUrl.indexOf("?") === -1 ? "?" : "&") + "ticket=" + encodeURIComponent(grant.ticket);
|
|
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();
|
|
949
1026
|
let ws;
|
|
950
1027
|
try {
|
|
951
1028
|
ws = new WebSocket(url);
|
|
@@ -956,13 +1033,15 @@
|
|
|
956
1033
|
const welcomeTimer = setTimeout(() => {
|
|
957
1034
|
if (settled) return;
|
|
958
1035
|
settled = true;
|
|
1036
|
+
this.pendingConnect = null;
|
|
959
1037
|
this.terminal = true;
|
|
960
1038
|
try {
|
|
961
1039
|
ws.close();
|
|
962
1040
|
} catch (err) {
|
|
963
1041
|
}
|
|
964
1042
|
reject(sdkError3("error"));
|
|
965
|
-
},
|
|
1043
|
+
}, this.welcomeTimeoutMs);
|
|
1044
|
+
this.pendingConnect = { reject, timer: welcomeTimer };
|
|
966
1045
|
ws.onmessage = (event) => {
|
|
967
1046
|
var _a, _b, _c, _d, _e;
|
|
968
1047
|
let msg;
|
|
@@ -973,15 +1052,27 @@
|
|
|
973
1052
|
}
|
|
974
1053
|
if (!msg || typeof msg.t !== "string") return;
|
|
975
1054
|
if (msg.t === "welcome") {
|
|
1055
|
+
if (this.intentionalClose) {
|
|
1056
|
+
try {
|
|
1057
|
+
ws.close();
|
|
1058
|
+
} catch (err) {
|
|
1059
|
+
}
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
const reconnected = this.reconnectAttempts > 0;
|
|
976
1063
|
this.playerId = String((_a = msg.playerId) != null ? _a : "");
|
|
977
1064
|
this.players = Array.isArray(msg.players) ? msg.players : [];
|
|
978
1065
|
this.state = (_b = msg.state) != null ? _b : null;
|
|
1066
|
+
this.connected = true;
|
|
979
1067
|
this.reconnectAttempts = 0;
|
|
1068
|
+
this.latencyMs = Math.max(0, Date.now() - connectStartedAt);
|
|
980
1069
|
if (!settled) {
|
|
981
1070
|
settled = true;
|
|
1071
|
+
this.pendingConnect = null;
|
|
982
1072
|
clearTimeout(welcomeTimer);
|
|
983
1073
|
resolve(this);
|
|
984
1074
|
}
|
|
1075
|
+
if (reconnected) this.emit("connection", { connected: true, reconnecting: false });
|
|
985
1076
|
return;
|
|
986
1077
|
}
|
|
987
1078
|
if (msg.t === "snapshot") {
|
|
@@ -1019,25 +1110,30 @@
|
|
|
1019
1110
|
if (!settled) {
|
|
1020
1111
|
settled = true;
|
|
1021
1112
|
this.terminal = true;
|
|
1113
|
+
this.pendingConnect = null;
|
|
1022
1114
|
clearTimeout(welcomeTimer);
|
|
1023
1115
|
try {
|
|
1024
1116
|
ws.close();
|
|
1025
1117
|
} catch (err) {
|
|
1026
1118
|
}
|
|
1027
|
-
reject(sdkError3(code === "unauthenticated" ? "unauthenticated" : "rejected"));
|
|
1119
|
+
reject(sdkError3(code === "unauthenticated" ? "unauthenticated" : "rejected", code));
|
|
1028
1120
|
return;
|
|
1029
1121
|
}
|
|
1030
1122
|
this.emit("error", { code });
|
|
1031
1123
|
}
|
|
1032
1124
|
};
|
|
1033
|
-
ws.onclose = () => {
|
|
1125
|
+
ws.onclose = (event) => {
|
|
1126
|
+
this.connected = false;
|
|
1127
|
+
if (event && event.code === SEAT_FULL_CLOSE_CODE) this.terminal = true;
|
|
1034
1128
|
if (!settled) {
|
|
1035
1129
|
settled = true;
|
|
1130
|
+
this.pendingConnect = null;
|
|
1036
1131
|
clearTimeout(welcomeTimer);
|
|
1037
1132
|
reject(sdkError3("error"));
|
|
1038
1133
|
return;
|
|
1039
1134
|
}
|
|
1040
1135
|
if (this.intentionalClose || this.ended || this.terminal) {
|
|
1136
|
+
this.emit("connection", { connected: false, reconnecting: false });
|
|
1041
1137
|
this.emit("close", { reconnecting: false });
|
|
1042
1138
|
return;
|
|
1043
1139
|
}
|
|
@@ -1055,11 +1151,12 @@
|
|
|
1055
1151
|
return;
|
|
1056
1152
|
}
|
|
1057
1153
|
this.reconnectAttempts += 1;
|
|
1154
|
+
this.emit("connection", { connected: false, reconnecting: true });
|
|
1058
1155
|
this.emit("close", { reconnecting: true });
|
|
1059
1156
|
await new Promise((r) => setTimeout(r, 400 * this.reconnectAttempts));
|
|
1060
1157
|
if (this.intentionalClose || this.ended || this.terminal) return;
|
|
1061
1158
|
try {
|
|
1062
|
-
const grant = (_a = this.devGrant) != null ? _a : await requestTicket(this.code);
|
|
1159
|
+
const grant = (_a = this.devGrant) != null ? _a : await requestTicket({ roomId: this.code });
|
|
1063
1160
|
if (this.intentionalClose || this.ended || this.terminal) return;
|
|
1064
1161
|
await this.connect(grant);
|
|
1065
1162
|
if (this.intentionalClose) this.leave();
|
|
@@ -1068,11 +1165,16 @@
|
|
|
1068
1165
|
}
|
|
1069
1166
|
}
|
|
1070
1167
|
send(input) {
|
|
1071
|
-
|
|
1168
|
+
this.trySend(input);
|
|
1169
|
+
}
|
|
1170
|
+
trySend(input) {
|
|
1171
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return false;
|
|
1072
1172
|
this.seq += 1;
|
|
1073
1173
|
try {
|
|
1074
1174
|
this.ws.send(JSON.stringify({ t: "input", seq: this.seq, data: input }));
|
|
1175
|
+
return true;
|
|
1075
1176
|
} catch (err) {
|
|
1177
|
+
return false;
|
|
1076
1178
|
}
|
|
1077
1179
|
}
|
|
1078
1180
|
on(event, handler) {
|
|
@@ -1088,6 +1190,13 @@
|
|
|
1088
1190
|
leave() {
|
|
1089
1191
|
var _a;
|
|
1090
1192
|
this.intentionalClose = true;
|
|
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
|
+
}
|
|
1091
1200
|
try {
|
|
1092
1201
|
(_a = this.ws) == null ? void 0 : _a.close(1e3);
|
|
1093
1202
|
} catch (err) {
|
|
@@ -1107,17 +1216,57 @@
|
|
|
1107
1216
|
}
|
|
1108
1217
|
}
|
|
1109
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
|
+
}
|
|
1110
1239
|
function joinRoom(options) {
|
|
1111
1240
|
const opts = options != null ? options : {};
|
|
1112
1241
|
if (typeof window === "undefined" || typeof WebSocket === "undefined") {
|
|
1113
1242
|
return Promise.reject(sdkError3("unsupported"));
|
|
1114
1243
|
}
|
|
1244
|
+
let joinData;
|
|
1245
|
+
try {
|
|
1246
|
+
joinData = serializeJoinData(opts.joinData);
|
|
1247
|
+
} catch (err) {
|
|
1248
|
+
return Promise.reject(err);
|
|
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
|
+
}
|
|
1115
1257
|
const code = opts.create || !opts.code ? generateCode() : opts.code.toUpperCase();
|
|
1116
1258
|
if (opts.roomUrl && opts.ticket) {
|
|
1117
|
-
const devGrant = {
|
|
1118
|
-
|
|
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);
|
|
1119
1266
|
}
|
|
1120
|
-
return requestTicket(
|
|
1267
|
+
return requestTicket({ roomId: code }).then(
|
|
1268
|
+
(grant) => new RoomConnection(code, null, joinData, timeoutMs).connect(grant)
|
|
1269
|
+
);
|
|
1121
1270
|
}
|
|
1122
1271
|
var multiplayer = { joinRoom };
|
|
1123
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);
|