@looplay/sdk 0.4.0 → 0.5.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.
@@ -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,11 +967,17 @@ 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 });
912
- return this.getClient().trackPlay(gameId, playTimeSeconds);
976
+ const result = this.getClient().trackPlay(gameId, playTimeSeconds);
977
+ void result.then((ok) => {
978
+ if (ok) this.recordAutoRequestAdsActivity();
979
+ }).catch(() => void 0);
980
+ return result;
913
981
  }
914
982
  trackMatch(matchId, opts) {
915
983
  const gameId = this.getGameId();
@@ -918,12 +986,16 @@ var LooplayIframeAuth = class {
918
986
  return Promise.resolve(false);
919
987
  }
920
988
  this.debug("trackMatch dispatched", { gameId, matchId, ...opts });
921
- return this.getClient().trackMatch(gameId, {
989
+ const result = this.getClient().trackMatch(gameId, {
922
990
  matchId,
923
991
  matchDurationSeconds: opts.durationSeconds,
924
992
  isCompleted: opts.isCompleted,
925
993
  isWin: opts.isWin
926
994
  });
995
+ void result.then((ok) => {
996
+ if (ok) this.recordAutoRequestAdsActivity();
997
+ }).catch(() => void 0);
998
+ return result;
927
999
  }
928
1000
  emitGameEvent(actionCode, opts) {
929
1001
  const gameId = this.getGameId();
@@ -936,7 +1008,8 @@ var LooplayIframeAuth = class {
936
1008
  }
937
1009
  /** Idempotent; auto-emits GAME_STARTED exactly once per distinct auth session (bearer or anonymous). */
938
1010
  initLifecycleTracking() {
939
- if (this.lifecycleTrackingInitialized || typeof window === "undefined") return;
1011
+ if (this.lifecycleTrackingInitialized || typeof window === "undefined")
1012
+ return;
940
1013
  this.lifecycleTrackingInitialized = true;
941
1014
  this.debug("lifecycle tracking initialized");
942
1015
  this.subscribe(() => {
@@ -986,7 +1059,9 @@ ${identity}`;
986
1059
  origin,
987
1060
  receivedAt: Date.now()
988
1061
  });
989
- this.debug("auth message missing game identity; falling back to anonymous tracking");
1062
+ this.debug(
1063
+ "auth message missing game identity; falling back to anonymous tracking"
1064
+ );
990
1065
  this.activateAnonymousMode();
991
1066
  return;
992
1067
  }
@@ -1000,10 +1075,13 @@ ${identity}`;
1000
1075
  origin,
1001
1076
  receivedAt: Date.now()
1002
1077
  });
1003
- this.debug("auth message missing jwt; falling back to anonymous tracking", {
1004
- gameId: resolvedGameId,
1005
- apiUrl: resolvedApiUrl
1006
- });
1078
+ this.debug(
1079
+ "auth message missing jwt; falling back to anonymous tracking",
1080
+ {
1081
+ gameId: resolvedGameId,
1082
+ apiUrl: resolvedApiUrl
1083
+ }
1084
+ );
1007
1085
  this.activateAnonymousMode();
1008
1086
  return;
1009
1087
  }
@@ -1017,6 +1095,45 @@ ${identity}`;
1017
1095
  receivedAt: Date.now()
1018
1096
  });
1019
1097
  }
1098
+ handleRequestAdsResult(data) {
1099
+ if (data.type !== "LOOPLAY_ADS_WATCHED") return;
1100
+ const requestId = toOptionalString(data.requestId);
1101
+ if (!requestId) return;
1102
+ const pending = this.pendingAdsRequests.get(requestId);
1103
+ if (!pending) return;
1104
+ this.pendingAdsRequests.delete(requestId);
1105
+ if (pending.timer !== null) clearTimeout(pending.timer);
1106
+ this.adsRequestInFlight = false;
1107
+ this.debug("requestAds watched message received", { requestId });
1108
+ pending.resolve(true);
1109
+ }
1110
+ handleRequestAdsUnavailable(data) {
1111
+ if (data.type !== "LOOPLAY_ADS_UNAVAILABLE") return;
1112
+ const requestId = toOptionalString(data.requestId);
1113
+ if (!requestId) return;
1114
+ const pending = this.pendingAdsRequests.get(requestId);
1115
+ if (!pending) return;
1116
+ this.pendingAdsRequests.delete(requestId);
1117
+ if (pending.timer !== null) clearTimeout(pending.timer);
1118
+ this.adsRequestInFlight = false;
1119
+ this.debug("requestAds unavailable message received", { requestId });
1120
+ pending.resolve(false);
1121
+ }
1122
+ handleIncomingMessage(data, origin) {
1123
+ if (!isRecord(data)) return;
1124
+ if (data.type === "LOOPLAY_AUTH") {
1125
+ this.handleAuthMessage(data, origin);
1126
+ return;
1127
+ }
1128
+ if (data.type === "LOOPLAY_ADS_WATCHED") {
1129
+ this.handleRequestAdsResult(data);
1130
+ return;
1131
+ }
1132
+ if (data.type === "LOOPLAY_ADS_UNAVAILABLE") {
1133
+ this.handleRequestAdsUnavailable(data);
1134
+ return;
1135
+ }
1136
+ }
1020
1137
  setState(next) {
1021
1138
  this.state = next;
1022
1139
  this.client = null;
@@ -1041,6 +1158,35 @@ ${identity}`;
1041
1158
  debug(message, details) {
1042
1159
  this.options.onDebug?.(message, details);
1043
1160
  }
1161
+ recordAutoRequestAdsActivity() {
1162
+ this.autoAdsActionCount += 1;
1163
+ const config = this.options.autoRequestAds;
1164
+ if (!config?.enabled) return;
1165
+ if (this.adsRequestInFlight) return;
1166
+ if (!this.canTrack()) return;
1167
+ const now = Date.now();
1168
+ if (config.cooldownMs && now - this.lastAdsRequestAt < config.cooldownMs) {
1169
+ return;
1170
+ }
1171
+ if (config.maxRequestsPerSession && this.adsRequestsSent >= config.maxRequestsPerSession) {
1172
+ return;
1173
+ }
1174
+ const threshold = config.afterActionCount ?? 0;
1175
+ const shouldRequest = threshold > 0 && this.autoAdsActionCount % threshold === 0;
1176
+ if (!shouldRequest) return;
1177
+ const chance = config.chance ?? 1;
1178
+ if (chance <= 0 || Math.random() > chance) {
1179
+ this.debug("auto requestAds skipped by chance", {
1180
+ actionCount: this.autoAdsActionCount,
1181
+ chance
1182
+ });
1183
+ return;
1184
+ }
1185
+ this.debug("auto requestAds triggered", {
1186
+ actionCount: this.autoAdsActionCount
1187
+ });
1188
+ void this.requestAds();
1189
+ }
1044
1190
  };
1045
1191
 
1046
1192
  export { ApiClient, BrowserLocalStorageAuthStorage, HttpClient, HttpError, LooplayAuth, LooplayIframeAuth, LooplaySDK, LooplaySDKError, MemoryAuthStorage, MissingAuthError, MissingBaseUrlError, NotAuthenticatedError, NotInitializedError, ServiceClient, TelegramAuthProvider };