@bountyboard/arcade-sdk 1.0.0 → 1.1.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 +13 -6
- package/CHANGELOG.md +30 -0
- package/README.md +49 -4
- package/dist/{chunk-WCGBR3MI.js → chunk-OWOESH4X.js} +94 -11
- package/dist/chunk-OWOESH4X.js.map +1 -0
- package/dist/global-sdk.d.ts +78 -10
- package/dist/global.js +151 -20
- package/dist/index.cjs +93 -10
- 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 +151 -20
- 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 +59 -11
- package/dist/multiplayer.js.map +1 -1
- package/dist/{types-B6xH-lDi.d.cts → types-Cq8p7ZkC.d.cts} +72 -11
- package/dist/{types-B6xH-lDi.d.ts → types-Cq8p7ZkC.d.ts} +72 -11
- package/docs/game-design-playbook.md +5 -3
- package/package.json +5 -3
- package/dist/chunk-WCGBR3MI.js.map +0 -1
package/dist/multiplayer.cjs
CHANGED
|
@@ -67,6 +67,11 @@ function toBoolean(value) {
|
|
|
67
67
|
function cleanRewardedSize(value) {
|
|
68
68
|
return value === "small" || value === "medium" || value === "large" ? value : "";
|
|
69
69
|
}
|
|
70
|
+
function hasTransientUserActivation() {
|
|
71
|
+
if (typeof navigator === "undefined") return false;
|
|
72
|
+
const activation = navigator.userActivation;
|
|
73
|
+
return (activation == null ? void 0 : activation.isActive) === true;
|
|
74
|
+
}
|
|
70
75
|
function safeCall(fn, arg) {
|
|
71
76
|
if (typeof fn !== "function") return;
|
|
72
77
|
try {
|
|
@@ -143,7 +148,7 @@ function sanitizePlayer(value) {
|
|
|
143
148
|
function createBBArcade() {
|
|
144
149
|
let adPlacementLoad = null;
|
|
145
150
|
let adPreloadConfigured = false;
|
|
146
|
-
let rewardedBreakActive =
|
|
151
|
+
let rewardedBreakActive = null;
|
|
147
152
|
let rewardedBreakSequence = 0;
|
|
148
153
|
function isLocalAdHost() {
|
|
149
154
|
try {
|
|
@@ -554,6 +559,16 @@ function createBBArcade() {
|
|
|
554
559
|
function show() {
|
|
555
560
|
if (shown) return;
|
|
556
561
|
shown = true;
|
|
562
|
+
if (!hasTransientUserActivation()) {
|
|
563
|
+
const activationError = Object.assign(
|
|
564
|
+
{ status: "error", error: "direct_user_action_required" },
|
|
565
|
+
base
|
|
566
|
+
);
|
|
567
|
+
post("rewarded_ad", void 0, Object.assign({ event: "error" }, activationError));
|
|
568
|
+
safeCall(options.onError, activationError);
|
|
569
|
+
done(activationError);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
557
572
|
post("rewarded_ad", void 0, Object.assign({ event: "started" }, base));
|
|
558
573
|
try {
|
|
559
574
|
showAdFn();
|
|
@@ -577,6 +592,7 @@ function createBBArcade() {
|
|
|
577
592
|
base
|
|
578
593
|
);
|
|
579
594
|
post("rewarded_ad", void 0, Object.assign({ event: "error" }, beforeError));
|
|
595
|
+
if (shown) return;
|
|
580
596
|
safeCall(options.onError, beforeError);
|
|
581
597
|
done(beforeError);
|
|
582
598
|
}
|
|
@@ -585,9 +601,9 @@ function createBBArcade() {
|
|
|
585
601
|
adBreak({
|
|
586
602
|
type: "reward",
|
|
587
603
|
name: options.placement,
|
|
588
|
-
size: options.size || void 0,
|
|
589
604
|
beforeReward: startAd,
|
|
590
605
|
adViewed: function() {
|
|
606
|
+
if (resolved) return;
|
|
591
607
|
status = "viewed";
|
|
592
608
|
const viewed = Object.assign({ status }, base);
|
|
593
609
|
post("rewarded_ad", void 0, Object.assign({ event: "viewed" }, viewed));
|
|
@@ -595,6 +611,7 @@ function createBBArcade() {
|
|
|
595
611
|
safeCall(options.onReward, viewed);
|
|
596
612
|
},
|
|
597
613
|
adDismissed: function() {
|
|
614
|
+
if (resolved) return;
|
|
598
615
|
if (status !== "viewed") status = "dismissed";
|
|
599
616
|
const dismissed = Object.assign({ status }, base);
|
|
600
617
|
post("rewarded_ad", void 0, Object.assign({ event: "dismissed" }, dismissed));
|
|
@@ -602,6 +619,7 @@ function createBBArcade() {
|
|
|
602
619
|
safeCall(options.onDismissed, dismissed);
|
|
603
620
|
},
|
|
604
621
|
adBreakDone: function(placementInfo) {
|
|
622
|
+
if (resolved) return;
|
|
605
623
|
const result = rewardOffered || status !== "unavailable" ? { status } : { status: "unavailable", error: "no_rewarded_ad" };
|
|
606
624
|
if (placementInfo && typeof placementInfo === "object") {
|
|
607
625
|
result.breakStatus = cleanText(
|
|
@@ -635,6 +653,52 @@ function createBBArcade() {
|
|
|
635
653
|
});
|
|
636
654
|
});
|
|
637
655
|
}
|
|
656
|
+
function prepareRewardedAd(rawOptions) {
|
|
657
|
+
const normalized = normalizeRewardedOptions(rawOptions);
|
|
658
|
+
const base = {
|
|
659
|
+
placement: normalized.placement,
|
|
660
|
+
reward: normalized.reward,
|
|
661
|
+
adBreakId: normalized.adBreakId
|
|
662
|
+
};
|
|
663
|
+
if (normalized.size) base.size = normalized.size;
|
|
664
|
+
let ready2 = false;
|
|
665
|
+
let finalPromise;
|
|
666
|
+
return new Promise(function(resolvePreparation) {
|
|
667
|
+
finalPromise = rewardedAd(
|
|
668
|
+
Object.assign({}, rawOptions, {
|
|
669
|
+
// Preserve the id used by the ready handle and every lifecycle event;
|
|
670
|
+
// rewardedAd normalizes again, but this explicit id keeps both stages
|
|
671
|
+
// tied to the same placement.
|
|
672
|
+
adBreakId: normalized.adBreakId,
|
|
673
|
+
beforeReward: function(showAd) {
|
|
674
|
+
if (ready2) return;
|
|
675
|
+
ready2 = true;
|
|
676
|
+
let shown = false;
|
|
677
|
+
const prepared = Object.assign(
|
|
678
|
+
{ status: "ready" },
|
|
679
|
+
base,
|
|
680
|
+
{
|
|
681
|
+
show: function() {
|
|
682
|
+
if (!shown) {
|
|
683
|
+
shown = true;
|
|
684
|
+
if (hasTransientUserActivation()) {
|
|
685
|
+
safeCall(rawOptions && rawOptions.onStart);
|
|
686
|
+
}
|
|
687
|
+
showAd();
|
|
688
|
+
}
|
|
689
|
+
return finalPromise;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
);
|
|
693
|
+
resolvePreparation(prepared);
|
|
694
|
+
}
|
|
695
|
+
})
|
|
696
|
+
);
|
|
697
|
+
finalPromise.then(function(result) {
|
|
698
|
+
if (!ready2) resolvePreparation(result);
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
}
|
|
638
702
|
function preloadRewardedAds(rawOptions) {
|
|
639
703
|
const options = normalizeRewardedOptions(rawOptions);
|
|
640
704
|
if (!areRewardedAdsEnabled()) return Promise.resolve(false);
|
|
@@ -681,7 +745,7 @@ function createBBArcade() {
|
|
|
681
745
|
reward: normalized.reward
|
|
682
746
|
};
|
|
683
747
|
if (normalized.size) base.size = normalized.size;
|
|
684
|
-
if (rewardedBreakActive) {
|
|
748
|
+
if (rewardedBreakActive && rewardedBreakActive.state !== "ready") {
|
|
685
749
|
post(
|
|
686
750
|
"rewarded_ad",
|
|
687
751
|
void 0,
|
|
@@ -692,24 +756,32 @@ function createBBArcade() {
|
|
|
692
756
|
);
|
|
693
757
|
return Promise.resolve(false);
|
|
694
758
|
}
|
|
695
|
-
|
|
759
|
+
const activeBreak = { state: "loading" };
|
|
760
|
+
rewardedBreakActive = activeBreak;
|
|
696
761
|
let startCalled = false;
|
|
762
|
+
const callerBeforeReward = rawOptions.beforeReward;
|
|
697
763
|
return rewardedAd(
|
|
698
764
|
Object.assign({}, rawOptions, {
|
|
699
765
|
beforeReward: function(showAd) {
|
|
700
|
-
if (
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
766
|
+
if (rewardedBreakActive === activeBreak) activeBreak.state = "ready";
|
|
767
|
+
const showWithStart = function() {
|
|
768
|
+
if (startCalled || rewardedBreakActive !== activeBreak) return;
|
|
769
|
+
startCalled = true;
|
|
770
|
+
activeBreak.state = "showing";
|
|
771
|
+
if (hasTransientUserActivation()) safeCall(rawOptions.onStart);
|
|
772
|
+
showAd();
|
|
773
|
+
};
|
|
774
|
+
if (typeof callerBeforeReward === "function") callerBeforeReward(showWithStart);
|
|
775
|
+
else showWithStart();
|
|
704
776
|
}
|
|
705
777
|
})
|
|
706
778
|
).then(
|
|
707
779
|
function(result) {
|
|
708
|
-
rewardedBreakActive =
|
|
780
|
+
if (rewardedBreakActive === activeBreak) rewardedBreakActive = null;
|
|
709
781
|
return Boolean(result && result.status === "viewed");
|
|
710
782
|
},
|
|
711
783
|
function() {
|
|
712
|
-
rewardedBreakActive =
|
|
784
|
+
if (rewardedBreakActive === activeBreak) rewardedBreakActive = null;
|
|
713
785
|
return false;
|
|
714
786
|
}
|
|
715
787
|
);
|
|
@@ -747,6 +819,8 @@ function createBBArcade() {
|
|
|
747
819
|
},
|
|
748
820
|
rewardedAd,
|
|
749
821
|
showRewardedAd: rewardedAd,
|
|
822
|
+
prepareRewardedAd,
|
|
823
|
+
prepareRewardedBreak: prepareRewardedAd,
|
|
750
824
|
rewardedBreak,
|
|
751
825
|
save: function(blob) {
|
|
752
826
|
return request("save", String(blob));
|
|
@@ -792,6 +866,13 @@ function createInertBBArcade() {
|
|
|
792
866
|
reward: "reward",
|
|
793
867
|
adBreakId: "bbad_inert"
|
|
794
868
|
});
|
|
869
|
+
const prepareUnavailable = () => Promise.resolve({
|
|
870
|
+
status: "unavailable",
|
|
871
|
+
error: "ad_break_unavailable",
|
|
872
|
+
placement: "rewarded",
|
|
873
|
+
reward: "reward",
|
|
874
|
+
adBreakId: "bbad_inert"
|
|
875
|
+
});
|
|
795
876
|
return {
|
|
796
877
|
lockToHost: noop,
|
|
797
878
|
init: () => Promise.resolve(),
|
|
@@ -808,6 +889,8 @@ function createInertBBArcade() {
|
|
|
808
889
|
xrSessionEnd: noop,
|
|
809
890
|
rewardedAd: unavailable,
|
|
810
891
|
showRewardedAd: unavailable,
|
|
892
|
+
prepareRewardedAd: prepareUnavailable,
|
|
893
|
+
prepareRewardedBreak: prepareUnavailable,
|
|
811
894
|
rewardedBreak: () => Promise.resolve(false),
|
|
812
895
|
save: () => Promise.reject(sdkError2("unsupported")),
|
|
813
896
|
load: () => Promise.reject(sdkError2("unsupported")),
|
|
@@ -826,6 +909,7 @@ var BBArcade = typeof window === "undefined" ? createInertBBArcade() : createBBA
|
|
|
826
909
|
// src/multiplayer.ts
|
|
827
910
|
var WELCOME_TIMEOUT_MS = 1e4;
|
|
828
911
|
var MAX_RECONNECT_ATTEMPTS = 3;
|
|
912
|
+
var MAX_JOIN_DATA_BYTES = 1024;
|
|
829
913
|
var CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
830
914
|
var CODE_LENGTH = 4;
|
|
831
915
|
function sdkError3(code) {
|
|
@@ -847,15 +931,42 @@ function randomChars(length) {
|
|
|
847
931
|
function generateCode() {
|
|
848
932
|
return randomChars(CODE_LENGTH);
|
|
849
933
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
934
|
+
function utf8ByteLength(value) {
|
|
935
|
+
let bytes = 0;
|
|
936
|
+
for (let i = 0; i < value.length; i++) {
|
|
937
|
+
const code = value.charCodeAt(i);
|
|
938
|
+
if (code < 128) bytes += 1;
|
|
939
|
+
else if (code < 2048) bytes += 2;
|
|
940
|
+
else if (code >= 55296 && code <= 56319 && i + 1 < value.length) {
|
|
941
|
+
const next = value.charCodeAt(i + 1);
|
|
942
|
+
if (next >= 56320 && next <= 57343) {
|
|
943
|
+
bytes += 4;
|
|
944
|
+
i += 1;
|
|
945
|
+
} else {
|
|
946
|
+
bytes += 3;
|
|
947
|
+
}
|
|
948
|
+
} else bytes += 3;
|
|
949
|
+
}
|
|
950
|
+
return bytes;
|
|
951
|
+
}
|
|
952
|
+
function serializeJoinData(data) {
|
|
953
|
+
if (data === void 0) return null;
|
|
954
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) throw sdkError3("rejected");
|
|
955
|
+
let serialized;
|
|
956
|
+
try {
|
|
957
|
+
serialized = JSON.stringify(data);
|
|
958
|
+
} catch (e) {
|
|
959
|
+
throw sdkError3("rejected");
|
|
960
|
+
}
|
|
961
|
+
if (!serialized || utf8ByteLength(serialized) > MAX_JOIN_DATA_BYTES) {
|
|
962
|
+
throw sdkError3("rejected");
|
|
963
|
+
}
|
|
964
|
+
return serialized;
|
|
854
965
|
}
|
|
855
966
|
function requestTicket(roomId) {
|
|
856
967
|
const request = BBArcade._request;
|
|
857
968
|
if (typeof request !== "function") return Promise.reject(sdkError3("unsupported"));
|
|
858
|
-
return request("mp_ticket", { roomId
|
|
969
|
+
return request("mp_ticket", { roomId }).then((grant) => {
|
|
859
970
|
const g = grant;
|
|
860
971
|
if (!g || typeof g.ticket !== "string" || typeof g.roomUrl !== "string") {
|
|
861
972
|
throw sdkError3("error");
|
|
@@ -864,10 +975,11 @@ function requestTicket(roomId) {
|
|
|
864
975
|
});
|
|
865
976
|
}
|
|
866
977
|
var RoomConnection = class {
|
|
867
|
-
constructor(code, devGrant) {
|
|
978
|
+
constructor(code, devGrant, joinData) {
|
|
868
979
|
this.playerId = "";
|
|
869
980
|
this.players = [];
|
|
870
981
|
this.state = null;
|
|
982
|
+
this.connected = false;
|
|
871
983
|
this.ws = null;
|
|
872
984
|
// Keyed by event name; values are erased to (unknown) => void internally and
|
|
873
985
|
// retyped at the on()/emit() boundary, which keeps the public API strictly
|
|
@@ -882,12 +994,13 @@ var RoomConnection = class {
|
|
|
882
994
|
this.reconnectAttempts = 0;
|
|
883
995
|
this.code = code;
|
|
884
996
|
this.devGrant = devGrant;
|
|
997
|
+
this.joinData = joinData;
|
|
885
998
|
}
|
|
886
999
|
/** Connect and resolve on the server's welcome (post-ticket-verification). */
|
|
887
1000
|
connect(grant) {
|
|
888
1001
|
return new Promise((resolve, reject) => {
|
|
889
1002
|
let settled = false;
|
|
890
|
-
const url = grant.roomUrl + (grant.roomUrl.indexOf("?") === -1 ? "?" : "&") + "ticket=" + encodeURIComponent(grant.ticket);
|
|
1003
|
+
const url = grant.roomUrl + (grant.roomUrl.indexOf("?") === -1 ? "?" : "&") + "ticket=" + encodeURIComponent(grant.ticket) + (this.joinData === null ? "" : "&join=" + encodeURIComponent(this.joinData));
|
|
891
1004
|
let ws;
|
|
892
1005
|
try {
|
|
893
1006
|
ws = new WebSocket(url);
|
|
@@ -915,15 +1028,18 @@ var RoomConnection = class {
|
|
|
915
1028
|
}
|
|
916
1029
|
if (!msg || typeof msg.t !== "string") return;
|
|
917
1030
|
if (msg.t === "welcome") {
|
|
1031
|
+
const reconnected = this.reconnectAttempts > 0;
|
|
918
1032
|
this.playerId = String((_a = msg.playerId) != null ? _a : "");
|
|
919
1033
|
this.players = Array.isArray(msg.players) ? msg.players : [];
|
|
920
1034
|
this.state = (_b = msg.state) != null ? _b : null;
|
|
1035
|
+
this.connected = true;
|
|
921
1036
|
this.reconnectAttempts = 0;
|
|
922
1037
|
if (!settled) {
|
|
923
1038
|
settled = true;
|
|
924
1039
|
clearTimeout(welcomeTimer);
|
|
925
1040
|
resolve(this);
|
|
926
1041
|
}
|
|
1042
|
+
if (reconnected) this.emit("connection", { connected: true, reconnecting: false });
|
|
927
1043
|
return;
|
|
928
1044
|
}
|
|
929
1045
|
if (msg.t === "snapshot") {
|
|
@@ -973,6 +1089,7 @@ var RoomConnection = class {
|
|
|
973
1089
|
}
|
|
974
1090
|
};
|
|
975
1091
|
ws.onclose = () => {
|
|
1092
|
+
this.connected = false;
|
|
976
1093
|
if (!settled) {
|
|
977
1094
|
settled = true;
|
|
978
1095
|
clearTimeout(welcomeTimer);
|
|
@@ -980,6 +1097,7 @@ var RoomConnection = class {
|
|
|
980
1097
|
return;
|
|
981
1098
|
}
|
|
982
1099
|
if (this.intentionalClose || this.ended || this.terminal) {
|
|
1100
|
+
this.emit("connection", { connected: false, reconnecting: false });
|
|
983
1101
|
this.emit("close", { reconnecting: false });
|
|
984
1102
|
return;
|
|
985
1103
|
}
|
|
@@ -997,6 +1115,7 @@ var RoomConnection = class {
|
|
|
997
1115
|
return;
|
|
998
1116
|
}
|
|
999
1117
|
this.reconnectAttempts += 1;
|
|
1118
|
+
this.emit("connection", { connected: false, reconnecting: true });
|
|
1000
1119
|
this.emit("close", { reconnecting: true });
|
|
1001
1120
|
await new Promise((r) => setTimeout(r, 400 * this.reconnectAttempts));
|
|
1002
1121
|
if (this.intentionalClose || this.ended || this.terminal) return;
|
|
@@ -1010,11 +1129,16 @@ var RoomConnection = class {
|
|
|
1010
1129
|
}
|
|
1011
1130
|
}
|
|
1012
1131
|
send(input) {
|
|
1013
|
-
|
|
1132
|
+
this.trySend(input);
|
|
1133
|
+
}
|
|
1134
|
+
trySend(input) {
|
|
1135
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return false;
|
|
1014
1136
|
this.seq += 1;
|
|
1015
1137
|
try {
|
|
1016
1138
|
this.ws.send(JSON.stringify({ t: "input", seq: this.seq, data: input }));
|
|
1139
|
+
return true;
|
|
1017
1140
|
} catch (err) {
|
|
1141
|
+
return false;
|
|
1018
1142
|
}
|
|
1019
1143
|
}
|
|
1020
1144
|
on(event, handler) {
|
|
@@ -1030,6 +1154,7 @@ var RoomConnection = class {
|
|
|
1030
1154
|
leave() {
|
|
1031
1155
|
var _a;
|
|
1032
1156
|
this.intentionalClose = true;
|
|
1157
|
+
this.connected = false;
|
|
1033
1158
|
try {
|
|
1034
1159
|
(_a = this.ws) == null ? void 0 : _a.close(1e3);
|
|
1035
1160
|
} catch (err) {
|
|
@@ -1055,11 +1180,17 @@ function joinRoom(options) {
|
|
|
1055
1180
|
return Promise.reject(sdkError3("unsupported"));
|
|
1056
1181
|
}
|
|
1057
1182
|
const code = opts.create || !opts.code ? generateCode() : opts.code.toUpperCase();
|
|
1183
|
+
let joinData;
|
|
1184
|
+
try {
|
|
1185
|
+
joinData = serializeJoinData(opts.joinData);
|
|
1186
|
+
} catch (err) {
|
|
1187
|
+
return Promise.reject(err);
|
|
1188
|
+
}
|
|
1058
1189
|
if (opts.roomUrl && opts.ticket) {
|
|
1059
1190
|
const devGrant = { ticket: opts.ticket, roomUrl: opts.roomUrl, expiresAt: 0 };
|
|
1060
|
-
return new RoomConnection(code, devGrant).connect(devGrant);
|
|
1191
|
+
return new RoomConnection(code, devGrant, joinData).connect(devGrant);
|
|
1061
1192
|
}
|
|
1062
|
-
return requestTicket(code).then((grant) => new RoomConnection(code, null).connect(grant));
|
|
1193
|
+
return requestTicket(code).then((grant) => new RoomConnection(code, null, joinData).connect(grant));
|
|
1063
1194
|
}
|
|
1064
1195
|
var multiplayer = { joinRoom };
|
|
1065
1196
|
// Annotate the CommonJS export names for ESM import in node:
|