@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.
- package/README.md +46 -1
- package/dist/looplay-sdk.cjs.js +156 -14
- package/dist/looplay-sdk.cjs.js.map +1 -1
- package/dist/looplay-sdk.esm.js +156 -14
- package/dist/looplay-sdk.esm.js.map +1 -1
- package/dist/looplay-sdk.min.js +2 -2
- package/dist/looplay-sdk.min.js.map +1 -1
- package/dist/types/iframe/iframe-auth.d.ts +20 -3
- package/dist/types/iframe/types.d.ts +20 -0
- package/package.json +1 -1
package/dist/looplay-sdk.esm.js
CHANGED
|
@@ -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", {
|
|
825
|
-
|
|
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
|
|
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
|
-
|
|
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", {
|
|
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", {
|
|
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
|
-
|
|
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")
|
|
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(
|
|
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(
|
|
1004
|
-
|
|
1005
|
-
|
|
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 };
|