@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.
@@ -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 = false;
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
- rewardedBreakActive = true;
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 (startCalled) return;
701
- startCalled = true;
702
- safeCall(rawOptions.onStart);
703
- showAd();
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 = false;
780
+ if (rewardedBreakActive === activeBreak) rewardedBreakActive = null;
709
781
  return Boolean(result && result.status === "viewed");
710
782
  },
711
783
  function() {
712
- rewardedBreakActive = false;
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
- var guestKey = null;
851
- function getGuestKey() {
852
- if (!guestKey) guestKey = randomChars(16);
853
- return guestKey;
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, guestKey: getGuestKey() }).then((grant) => {
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
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
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: