@looplay/sdk 0.4.0 → 0.5.1

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.
@@ -738,6 +738,7 @@ var TelegramAuthProvider = class {
738
738
  // src/iframe/iframe-auth.ts
739
739
  var DEFAULT_ANON_ID_STORAGE_KEY = "looplay:anon-id";
740
740
  var DEFAULT_ANONYMOUS_FALLBACK_TIMEOUT_MS = 4e3;
741
+ var DEFAULT_REQUEST_ADS_TIMEOUT_MS = 5e3;
741
742
  var INITIAL_STATE = {
742
743
  jwt: null,
743
744
  gameId: null,
@@ -787,6 +788,12 @@ var LooplayIframeAuth = class {
787
788
  options;
788
789
  messageListener = null;
789
790
  anonymousFallbackTimer = null;
791
+ pendingAdsRequests = /* @__PURE__ */ new Map();
792
+ requestAdsSequence = 0;
793
+ adsRequestInFlight = false;
794
+ adsRequestsSent = 0;
795
+ lastAdsRequestAt = 0;
796
+ autoAdsActionCount = 0;
790
797
  constructor(options = {}) {
791
798
  this.options = options;
792
799
  }
@@ -821,8 +828,57 @@ var LooplayIframeAuth = class {
821
828
  this.debug("standalone mode detected; auth request skipped");
822
829
  return;
823
830
  }
824
- this.debug("requesting auth from parent", { targetOrigin: this.resolveParentOrigin() ?? "*" });
825
- window.parent.postMessage({ type: "LOOPLAY_AUTH_REQUEST" }, this.resolveParentOrigin() ?? "*");
831
+ this.debug("requesting auth from parent", {
832
+ targetOrigin: this.resolveParentOrigin() ?? "*"
833
+ });
834
+ window.parent.postMessage(
835
+ { type: "LOOPLAY_AUTH_REQUEST" },
836
+ this.resolveParentOrigin() ?? "*"
837
+ );
838
+ }
839
+ /**
840
+ * Requests the parent host to show a rewarded ad.
841
+ * In a Telegram Mini App setup, the game sends this request from the iframe
842
+ * and the parent page (the Mini App shell) bridges it to Adsgram, then
843
+ * responds with `LOOPLAY_ADS_WATCHED` after the ad finishes.
844
+ */
845
+ requestAds(options = {}) {
846
+ if (typeof window === "undefined" || window.parent === window) {
847
+ this.debug("requestAds skipped; standalone mode detected");
848
+ return Promise.resolve(false);
849
+ }
850
+ if (this.adsRequestInFlight) {
851
+ this.debug("requestAds skipped; another ad request is already in flight");
852
+ return Promise.resolve(false);
853
+ }
854
+ const parentOrigin = this.resolveParentOrigin() ?? "*";
855
+ const requestId = `ads-${Date.now()}-${++this.requestAdsSequence}`;
856
+ const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_ADS_TIMEOUT_MS;
857
+ this.adsRequestInFlight = true;
858
+ this.adsRequestsSent += 1;
859
+ this.lastAdsRequestAt = Date.now();
860
+ this.debug("requesting rewarded ad from parent", {
861
+ requestId,
862
+ targetOrigin: parentOrigin,
863
+ gameId: this.getGameId()
864
+ });
865
+ return new Promise((resolve) => {
866
+ const timer = setTimeout(() => {
867
+ this.pendingAdsRequests.delete(requestId);
868
+ this.adsRequestInFlight = false;
869
+ this.debug("requestAds timed out", { requestId, timeoutMs });
870
+ resolve(false);
871
+ }, timeoutMs);
872
+ this.pendingAdsRequests.set(requestId, { resolve, timer });
873
+ window.parent.postMessage(
874
+ {
875
+ type: "LOOPLAY_REQUEST_ADS",
876
+ requestId,
877
+ gameId: this.getGameId()
878
+ },
879
+ parentOrigin
880
+ );
881
+ });
826
882
  }
827
883
  /** Idempotent; attaches the postMessage listener (if embedded) and arms the anonymous fallback. */
828
884
  init() {
@@ -846,14 +902,13 @@ var LooplayIframeAuth = class {
846
902
  this.messageListener = (event) => {
847
903
  if (event.source !== window.parent) return;
848
904
  if (parentOrigin && event.origin !== parentOrigin) {
849
- this.debug("ignored auth message from unexpected origin", {
905
+ this.debug("ignored message from unexpected origin", {
850
906
  expectedOrigin: parentOrigin,
851
907
  receivedOrigin: event.origin
852
908
  });
853
909
  return;
854
910
  }
855
- if (!isRecord(event.data) || event.data.type !== "LOOPLAY_AUTH") return;
856
- this.handleAuthMessage(event.data, event.origin);
911
+ this.handleIncomingMessage(event.data, event.origin);
857
912
  };
858
913
  window.addEventListener("message", this.messageListener);
859
914
  this.anonymousFallbackTimer = setTimeout(() => {
@@ -870,6 +925,11 @@ var LooplayIframeAuth = class {
870
925
  if (this.anonymousFallbackTimer !== null) {
871
926
  clearTimeout(this.anonymousFallbackTimer);
872
927
  }
928
+ for (const pending of this.pendingAdsRequests.values()) {
929
+ if (pending.timer !== null) clearTimeout(pending.timer);
930
+ pending.resolve(false);
931
+ }
932
+ this.pendingAdsRequests.clear();
873
933
  this.messageListener = null;
874
934
  this.anonymousFallbackTimer = null;
875
935
  this.initialized = false;
@@ -892,7 +952,9 @@ var LooplayIframeAuth = class {
892
952
  trackView() {
893
953
  const gameId = this.getGameId();
894
954
  if (!this.canTrack() || !gameId) {
895
- this.debug("trackView skipped; auth is not ready", { hasGameId: Boolean(gameId) });
955
+ this.debug("trackView skipped; auth is not ready", {
956
+ hasGameId: Boolean(gameId)
957
+ });
896
958
  return Promise.resolve(false);
897
959
  }
898
960
  this.debug("trackView dispatched", { gameId });
@@ -905,7 +967,9 @@ var LooplayIframeAuth = class {
905
967
  trackPlay(playTimeSeconds) {
906
968
  const gameId = this.getGameId();
907
969
  if (!this.canTrack() || !gameId) {
908
- this.debug("trackPlay skipped; auth is not ready", { hasGameId: Boolean(gameId) });
970
+ this.debug("trackPlay skipped; auth is not ready", {
971
+ hasGameId: Boolean(gameId)
972
+ });
909
973
  return Promise.resolve(false);
910
974
  }
911
975
  this.debug("trackPlay dispatched", { gameId, playTimeSeconds });
@@ -918,12 +982,16 @@ var LooplayIframeAuth = class {
918
982
  return Promise.resolve(false);
919
983
  }
920
984
  this.debug("trackMatch dispatched", { gameId, matchId, ...opts });
921
- return this.getClient().trackMatch(gameId, {
985
+ const result = this.getClient().trackMatch(gameId, {
922
986
  matchId,
923
987
  matchDurationSeconds: opts.durationSeconds,
924
988
  isCompleted: opts.isCompleted,
925
989
  isWin: opts.isWin
926
990
  });
991
+ void result.then((ok) => {
992
+ if (ok) this.recordAutoRequestAdsActivity();
993
+ }).catch(() => void 0);
994
+ return result;
927
995
  }
928
996
  emitGameEvent(actionCode, opts) {
929
997
  const gameId = this.getGameId();
@@ -936,7 +1004,8 @@ var LooplayIframeAuth = class {
936
1004
  }
937
1005
  /** Idempotent; auto-emits GAME_STARTED exactly once per distinct auth session (bearer or anonymous). */
938
1006
  initLifecycleTracking() {
939
- if (this.lifecycleTrackingInitialized || typeof window === "undefined") return;
1007
+ if (this.lifecycleTrackingInitialized || typeof window === "undefined")
1008
+ return;
940
1009
  this.lifecycleTrackingInitialized = true;
941
1010
  this.debug("lifecycle tracking initialized");
942
1011
  this.subscribe(() => {
@@ -986,7 +1055,9 @@ ${identity}`;
986
1055
  origin,
987
1056
  receivedAt: Date.now()
988
1057
  });
989
- this.debug("auth message missing game identity; falling back to anonymous tracking");
1058
+ this.debug(
1059
+ "auth message missing game identity; falling back to anonymous tracking"
1060
+ );
990
1061
  this.activateAnonymousMode();
991
1062
  return;
992
1063
  }
@@ -1000,10 +1071,13 @@ ${identity}`;
1000
1071
  origin,
1001
1072
  receivedAt: Date.now()
1002
1073
  });
1003
- this.debug("auth message missing jwt; falling back to anonymous tracking", {
1004
- gameId: resolvedGameId,
1005
- apiUrl: resolvedApiUrl
1006
- });
1074
+ this.debug(
1075
+ "auth message missing jwt; falling back to anonymous tracking",
1076
+ {
1077
+ gameId: resolvedGameId,
1078
+ apiUrl: resolvedApiUrl
1079
+ }
1080
+ );
1007
1081
  this.activateAnonymousMode();
1008
1082
  return;
1009
1083
  }
@@ -1017,6 +1091,45 @@ ${identity}`;
1017
1091
  receivedAt: Date.now()
1018
1092
  });
1019
1093
  }
1094
+ handleRequestAdsResult(data) {
1095
+ if (data.type !== "LOOPLAY_ADS_WATCHED") return;
1096
+ const requestId = toOptionalString(data.requestId);
1097
+ if (!requestId) return;
1098
+ const pending = this.pendingAdsRequests.get(requestId);
1099
+ if (!pending) return;
1100
+ this.pendingAdsRequests.delete(requestId);
1101
+ if (pending.timer !== null) clearTimeout(pending.timer);
1102
+ this.adsRequestInFlight = false;
1103
+ this.debug("requestAds watched message received", { requestId });
1104
+ pending.resolve(true);
1105
+ }
1106
+ handleRequestAdsUnavailable(data) {
1107
+ if (data.type !== "LOOPLAY_ADS_UNAVAILABLE") return;
1108
+ const requestId = toOptionalString(data.requestId);
1109
+ if (!requestId) return;
1110
+ const pending = this.pendingAdsRequests.get(requestId);
1111
+ if (!pending) return;
1112
+ this.pendingAdsRequests.delete(requestId);
1113
+ if (pending.timer !== null) clearTimeout(pending.timer);
1114
+ this.adsRequestInFlight = false;
1115
+ this.debug("requestAds unavailable message received", { requestId });
1116
+ pending.resolve(false);
1117
+ }
1118
+ handleIncomingMessage(data, origin) {
1119
+ if (!isRecord(data)) return;
1120
+ if (data.type === "LOOPLAY_AUTH") {
1121
+ this.handleAuthMessage(data, origin);
1122
+ return;
1123
+ }
1124
+ if (data.type === "LOOPLAY_ADS_WATCHED") {
1125
+ this.handleRequestAdsResult(data);
1126
+ return;
1127
+ }
1128
+ if (data.type === "LOOPLAY_ADS_UNAVAILABLE") {
1129
+ this.handleRequestAdsUnavailable(data);
1130
+ return;
1131
+ }
1132
+ }
1020
1133
  setState(next) {
1021
1134
  this.state = next;
1022
1135
  this.client = null;
@@ -1041,6 +1154,35 @@ ${identity}`;
1041
1154
  debug(message, details) {
1042
1155
  this.options.onDebug?.(message, details);
1043
1156
  }
1157
+ recordAutoRequestAdsActivity() {
1158
+ this.autoAdsActionCount += 1;
1159
+ const config = this.options.autoRequestAds;
1160
+ if (!config?.enabled) return;
1161
+ if (this.adsRequestInFlight) return;
1162
+ if (!this.canTrack()) return;
1163
+ const now = Date.now();
1164
+ if (config.cooldownMs && now - this.lastAdsRequestAt < config.cooldownMs) {
1165
+ return;
1166
+ }
1167
+ if (config.maxRequestsPerSession && this.adsRequestsSent >= config.maxRequestsPerSession) {
1168
+ return;
1169
+ }
1170
+ const threshold = config.afterActionCount ?? 0;
1171
+ const shouldRequest = threshold > 0 && this.autoAdsActionCount % threshold === 0;
1172
+ if (!shouldRequest) return;
1173
+ const chance = config.chance ?? 1;
1174
+ if (chance <= 0 || Math.random() > chance) {
1175
+ this.debug("auto requestAds skipped by chance", {
1176
+ actionCount: this.autoAdsActionCount,
1177
+ chance
1178
+ });
1179
+ return;
1180
+ }
1181
+ this.debug("auto requestAds triggered", {
1182
+ actionCount: this.autoAdsActionCount
1183
+ });
1184
+ void this.requestAds();
1185
+ }
1044
1186
  };
1045
1187
 
1046
1188
  export { ApiClient, BrowserLocalStorageAuthStorage, HttpClient, HttpError, LooplayAuth, LooplayIframeAuth, LooplaySDK, LooplaySDKError, MemoryAuthStorage, MissingAuthError, MissingBaseUrlError, NotAuthenticatedError, NotInitializedError, ServiceClient, TelegramAuthProvider };