@omen.foundation/game-sdk 1.0.14 → 1.0.15
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/dist/index.d.mts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +130 -389
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +130 -389
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,81 +1,32 @@
|
|
|
1
|
-
// src/pkce.ts
|
|
2
|
-
async function generatePKCE() {
|
|
3
|
-
const codeVerifier = generateRandomString(128);
|
|
4
|
-
const hash = await sha256(codeVerifier);
|
|
5
|
-
const codeChallenge = base64URLEncode(hash);
|
|
6
|
-
return { codeVerifier, codeChallenge };
|
|
7
|
-
}
|
|
8
|
-
function generateRandomString(length) {
|
|
9
|
-
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
10
|
-
const array = new Uint8Array(length);
|
|
11
|
-
crypto.getRandomValues(array);
|
|
12
|
-
return Array.from(array, (byte) => charset[byte % charset.length]).join("");
|
|
13
|
-
}
|
|
14
|
-
function base64URLEncode(buffer) {
|
|
15
|
-
const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));
|
|
16
|
-
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
17
|
-
}
|
|
18
|
-
async function sha256(message) {
|
|
19
|
-
const encoder = new TextEncoder();
|
|
20
|
-
const data = encoder.encode(message);
|
|
21
|
-
return crypto.subtle.digest("SHA-256", data);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
1
|
// src/oauth.ts
|
|
25
2
|
var OAuthFlow = class {
|
|
26
3
|
constructor(config) {
|
|
27
4
|
this.config = config;
|
|
28
5
|
}
|
|
29
6
|
/**
|
|
30
|
-
* Authenticate user via OAuth
|
|
7
|
+
* Authenticate user via OAuth 2.0 Authorization Code Grant with PKCE
|
|
31
8
|
*/
|
|
32
|
-
async authenticate(options) {
|
|
33
|
-
const state =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
console.log("[OAuthFlow] \u{1F9F9} Cleaning up old OAuth entries:", { count: allKeys.length });
|
|
37
|
-
allKeys.forEach((key) => {
|
|
38
|
-
try {
|
|
39
|
-
const data = localStorage.getItem(key);
|
|
40
|
-
if (data) {
|
|
41
|
-
const parsed = JSON.parse(data);
|
|
42
|
-
const age = Date.now() - (parsed.timestamp || 0);
|
|
43
|
-
if (age > 3e5) {
|
|
44
|
-
localStorage.removeItem(key);
|
|
45
|
-
console.log("[OAuthFlow] \u{1F5D1}\uFE0F Removed old entry:", key.substring(0, 50) + "...", { age: `${age}ms` });
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
} catch (e) {
|
|
49
|
-
localStorage.removeItem(key);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
} catch (error) {
|
|
53
|
-
console.warn("[OAuthFlow] \u26A0\uFE0F Error cleaning up old entries:", error);
|
|
54
|
-
}
|
|
55
|
-
let codeChallenge;
|
|
56
|
-
let codeVerifier;
|
|
57
|
-
if (options.enablePKCE !== false) {
|
|
58
|
-
const pkce = await generatePKCE();
|
|
59
|
-
codeChallenge = pkce.codeChallenge;
|
|
60
|
-
codeVerifier = pkce.codeVerifier;
|
|
61
|
-
sessionStorage.setItem(`omenx_pkce_${state}`, codeVerifier);
|
|
62
|
-
}
|
|
9
|
+
async authenticate(options = {}) {
|
|
10
|
+
const state = this.generateState();
|
|
11
|
+
const codeVerifier = this.generateCodeVerifier();
|
|
12
|
+
const codeChallenge = await this.generateCodeChallenge(codeVerifier);
|
|
63
13
|
return new Promise((resolve, reject) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
14
|
+
sessionStorage.setItem(`omenx_pkce_${state}`, codeVerifier);
|
|
15
|
+
const redirectUri = options.redirectUri || `${window.location.origin}/auth/callback`;
|
|
16
|
+
const authUrl = new URL(this.config.oauthAuthorizeUrl);
|
|
17
|
+
authUrl.searchParams.set("client_id", this.config.gameId);
|
|
18
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
19
|
+
authUrl.searchParams.set("response_type", "code");
|
|
20
|
+
authUrl.searchParams.set("scope", "openid profile email");
|
|
21
|
+
authUrl.searchParams.set("state", state);
|
|
22
|
+
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
23
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
24
|
+
const stateStorageKey = `omenx_oauth_state_${state}`;
|
|
25
|
+
sessionStorage.setItem(stateStorageKey, state);
|
|
75
26
|
const popup = window.open(
|
|
76
|
-
authUrl,
|
|
77
|
-
"OmenX
|
|
78
|
-
"width=500,height=600,left=
|
|
27
|
+
authUrl.toString(),
|
|
28
|
+
"OmenX OAuth",
|
|
29
|
+
"width=500,height=600,left=100,top=100"
|
|
79
30
|
);
|
|
80
31
|
if (!popup) {
|
|
81
32
|
const error = new Error("Failed to open popup window. Please allow popups for this site.");
|
|
@@ -83,346 +34,92 @@ var OAuthFlow = class {
|
|
|
83
34
|
reject(error);
|
|
84
35
|
return;
|
|
85
36
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (event.origin !== currentOrigin) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
} catch {
|
|
93
|
-
}
|
|
94
|
-
if (event.data?.type === "OMENX_OAUTH_CODE") {
|
|
95
|
-
window.removeEventListener("message", messageListener);
|
|
96
|
-
popup.close();
|
|
97
|
-
const { code, state: returnedState } = event.data;
|
|
98
|
-
if (returnedState !== state) {
|
|
99
|
-
const error = new Error("Invalid state parameter. Possible CSRF attack.");
|
|
100
|
-
this.config.onError?.(error);
|
|
101
|
-
reject(error);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const storedCodeVerifier = sessionStorage.getItem(`omenx_pkce_${state}`);
|
|
105
|
-
if (storedCodeVerifier) {
|
|
106
|
-
sessionStorage.removeItem(`omenx_pkce_${state}`);
|
|
107
|
-
codeVerifier = storedCodeVerifier;
|
|
108
|
-
}
|
|
37
|
+
try {
|
|
38
|
+
const allKeys = Object.keys(localStorage).filter((k) => k.startsWith("omenx_oauth_callback_"));
|
|
39
|
+
allKeys.forEach((key) => {
|
|
109
40
|
try {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
window.addEventListener("message", messageListener);
|
|
120
|
-
const storageListener = (event) => {
|
|
121
|
-
if (!event.key || !event.key.startsWith("omenx_oauth_callback_")) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
const callbackState = event.key.replace("omenx_oauth_callback_", "");
|
|
125
|
-
if (callbackState !== state) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
const data = JSON.parse(event.newValue || "{}");
|
|
130
|
-
if (data.code && data.state === state) {
|
|
131
|
-
console.log("[OAuthFlow] Received OAuth code via localStorage fallback");
|
|
132
|
-
messageReceived = true;
|
|
133
|
-
window.removeEventListener("storage", storageListener);
|
|
134
|
-
window.removeEventListener("message", wrappedMessageListener);
|
|
135
|
-
const { code: codeFromStorage } = data;
|
|
136
|
-
const storedCodeVerifier = sessionStorage.getItem(`omenx_pkce_${state}`);
|
|
137
|
-
let codeVerifier2;
|
|
138
|
-
if (storedCodeVerifier) {
|
|
139
|
-
sessionStorage.removeItem(`omenx_pkce_${state}`);
|
|
140
|
-
codeVerifier2 = storedCodeVerifier;
|
|
41
|
+
const data = localStorage.getItem(key);
|
|
42
|
+
if (data) {
|
|
43
|
+
const parsed = JSON.parse(data);
|
|
44
|
+
const age = Date.now() - (parsed.timestamp || 0);
|
|
45
|
+
if (age > 3e5) {
|
|
46
|
+
localStorage.removeItem(key);
|
|
47
|
+
}
|
|
141
48
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
resolve(tokenResponse);
|
|
145
|
-
}).catch((error) => {
|
|
146
|
-
this.config.onError?.(error);
|
|
147
|
-
reject(error);
|
|
148
|
-
});
|
|
149
|
-
localStorage.removeItem(event.key);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
localStorage.removeItem(key);
|
|
150
51
|
}
|
|
151
|
-
} catch (error) {
|
|
152
|
-
console.error("[OAuthFlow] Error processing localStorage callback:", error);
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
window.addEventListener("storage", storageListener);
|
|
156
|
-
let pollCount = 0;
|
|
157
|
-
const storageKey = `omenx_oauth_callback_${state}`;
|
|
158
|
-
const immediateCheck = () => {
|
|
159
|
-
const allKeys = Object.keys(localStorage).filter((k) => k.startsWith("omenx_oauth_callback_"));
|
|
160
|
-
console.log("[OAuthFlow] \u{1F50D} Immediate localStorage check:", {
|
|
161
|
-
count: allKeys.length,
|
|
162
|
-
keys: allKeys.map((k) => k.substring(0, 50) + "..."),
|
|
163
|
-
lookingFor: storageKey.substring(0, 50) + "...",
|
|
164
|
-
expectedState: state.substring(0, 20) + "..."
|
|
165
52
|
});
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const data = JSON.parse(storedData);
|
|
171
|
-
console.log("[OAuthFlow] \u{1F50D} Checking stored key:", {
|
|
172
|
-
key: key.substring(0, 50) + "...",
|
|
173
|
-
storedState: data.state?.substring(0, 20) + "...",
|
|
174
|
-
expectedState: state.substring(0, 20) + "...",
|
|
175
|
-
statesMatch: data.state === state,
|
|
176
|
-
hasCode: !!data.code,
|
|
177
|
-
timestamp: data.timestamp,
|
|
178
|
-
age: data.timestamp ? `${Date.now() - data.timestamp}ms` : "unknown"
|
|
179
|
-
});
|
|
180
|
-
} catch (e) {
|
|
181
|
-
console.warn("[OAuthFlow] \u26A0\uFE0F Error parsing stored key:", key, e);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
immediateCheck();
|
|
187
|
-
console.log("[OAuthFlow] \u{1F504} Starting localStorage polling interval (every 100ms)");
|
|
53
|
+
} catch (error) {
|
|
54
|
+
}
|
|
55
|
+
const storageKey = `omenx_oauth_callback_${state}`;
|
|
56
|
+
let messageReceived = false;
|
|
188
57
|
const pollInterval = setInterval(() => {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
console.log("[OAuthFlow] \u{1F50D} Polling localStorage...", {
|
|
193
|
-
key: storageKey.substring(0, 50) + "...",
|
|
194
|
-
found: !!stored,
|
|
195
|
-
pollCount,
|
|
196
|
-
statePreview: state.substring(0, 20) + "...",
|
|
197
|
-
intervalActive: true
|
|
198
|
-
});
|
|
199
|
-
if (pollCount % 100 === 0) {
|
|
200
|
-
immediateCheck();
|
|
201
|
-
}
|
|
58
|
+
if (messageReceived) {
|
|
59
|
+
clearInterval(pollInterval);
|
|
60
|
+
return;
|
|
202
61
|
}
|
|
62
|
+
const stored = localStorage.getItem(storageKey);
|
|
203
63
|
if (stored) {
|
|
204
64
|
try {
|
|
205
65
|
const data = JSON.parse(stored);
|
|
206
|
-
if (pollCount === 1 || pollCount % 10 === 0) {
|
|
207
|
-
console.log("[OAuthFlow] \u2705 Found stored data!", {
|
|
208
|
-
hasCode: !!data.code,
|
|
209
|
-
hasState: !!data.state,
|
|
210
|
-
stateMatch: data.state === state,
|
|
211
|
-
storedState: data.state?.substring(0, 20) + "...",
|
|
212
|
-
expectedState: state.substring(0, 20) + "...",
|
|
213
|
-
pollCount
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
66
|
if (data.code && data.state === state) {
|
|
217
67
|
const age = Date.now() - (data.timestamp || 0);
|
|
218
68
|
if (age < 3e4) {
|
|
219
|
-
|
|
220
|
-
age: `${age}ms`,
|
|
221
|
-
hasCode: !!data.code,
|
|
222
|
-
codePreview: data.code.substring(0, 10) + "...",
|
|
223
|
-
pollCount
|
|
224
|
-
});
|
|
69
|
+
messageReceived = true;
|
|
225
70
|
clearInterval(pollInterval);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
71
|
+
localStorage.removeItem(storageKey);
|
|
72
|
+
sessionStorage.removeItem(`omenx_pkce_${state}`);
|
|
73
|
+
sessionStorage.removeItem(stateStorageKey);
|
|
74
|
+
this.exchangeCodeForToken(data.code, redirectUri, codeVerifier).then(async (tokenResponse) => {
|
|
75
|
+
await this.config.onTokenReceived(tokenResponse);
|
|
76
|
+
if (popup && !popup.closed) {
|
|
77
|
+
try {
|
|
78
|
+
popup.close();
|
|
79
|
+
} catch (e) {
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
resolve(tokenResponse);
|
|
83
|
+
}).catch((error) => {
|
|
84
|
+
this.config.onError?.(error);
|
|
85
|
+
reject(error);
|
|
86
|
+
});
|
|
232
87
|
return;
|
|
233
88
|
} else {
|
|
234
|
-
console.warn("[OAuthFlow] \u26A0\uFE0F Callback data too old, removing:", { age: `${age}ms` });
|
|
235
89
|
localStorage.removeItem(storageKey);
|
|
236
90
|
}
|
|
237
|
-
} else {
|
|
238
|
-
console.warn("[OAuthFlow] \u26A0\uFE0F State mismatch!", {
|
|
239
|
-
storedState: data.state?.substring(0, 20) + "...",
|
|
240
|
-
expectedState: state.substring(0, 20) + "...",
|
|
241
|
-
statesMatch: data.state === state,
|
|
242
|
-
storedStateLength: data.state?.length,
|
|
243
|
-
expectedStateLength: state.length,
|
|
244
|
-
pollCount
|
|
245
|
-
});
|
|
246
91
|
}
|
|
247
92
|
} catch (error) {
|
|
248
|
-
console.error("[OAuthFlow]
|
|
249
|
-
stored: stored?.substring(0, 100) + "...",
|
|
250
|
-
pollCount
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
} else {
|
|
254
|
-
if (pollCount === 1 || pollCount % 5 === 0) {
|
|
255
|
-
const allKeys = Object.keys(localStorage).filter((k) => k.startsWith("omenx_oauth_callback_"));
|
|
256
|
-
if (allKeys.length > 0) {
|
|
257
|
-
console.log("[OAuthFlow] \u{1F50D} Found localStorage keys (checking all for state match):", {
|
|
258
|
-
count: allKeys.length,
|
|
259
|
-
keys: allKeys.map((k) => k.substring(0, 50) + "..."),
|
|
260
|
-
lookingFor: storageKey.substring(0, 50) + "...",
|
|
261
|
-
expectedState: state.substring(0, 20) + "...",
|
|
262
|
-
pollCount
|
|
263
|
-
});
|
|
264
|
-
for (const key of allKeys) {
|
|
265
|
-
const storedData = localStorage.getItem(key);
|
|
266
|
-
if (storedData) {
|
|
267
|
-
try {
|
|
268
|
-
const data = JSON.parse(storedData);
|
|
269
|
-
const statesMatch = data.state === state;
|
|
270
|
-
console.log("[OAuthFlow] \u{1F50D} Checking stored key:", {
|
|
271
|
-
key: key.substring(0, 50) + "...",
|
|
272
|
-
storedState: data.state?.substring(0, 20) + "...",
|
|
273
|
-
expectedState: state.substring(0, 20) + "...",
|
|
274
|
-
statesMatch,
|
|
275
|
-
storedStateLength: data.state?.length,
|
|
276
|
-
expectedStateLength: state.length,
|
|
277
|
-
hasCode: !!data.code,
|
|
278
|
-
pollCount
|
|
279
|
-
});
|
|
280
|
-
if (statesMatch && data.code) {
|
|
281
|
-
const age = Date.now() - (data.timestamp || 0);
|
|
282
|
-
if (age < 3e4) {
|
|
283
|
-
console.log("[OAuthFlow] \u2705\u2705\u2705 Found matching state in different key! Processing...", {
|
|
284
|
-
key: key.substring(0, 50) + "...",
|
|
285
|
-
age: `${age}ms`,
|
|
286
|
-
pollCount
|
|
287
|
-
});
|
|
288
|
-
clearInterval(pollInterval);
|
|
289
|
-
window.removeEventListener("storage", storageListener);
|
|
290
|
-
storageListener(new StorageEvent("storage", {
|
|
291
|
-
key,
|
|
292
|
-
newValue: storedData,
|
|
293
|
-
storageArea: localStorage
|
|
294
|
-
}));
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
} catch (e) {
|
|
299
|
-
console.warn("[OAuthFlow] \u26A0\uFE0F Error parsing key:", key, e);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
93
|
+
console.error("[OAuthFlow] Error parsing localStorage callback:", error);
|
|
304
94
|
}
|
|
305
95
|
}
|
|
306
96
|
}, 100);
|
|
307
|
-
let broadcastChannel = null;
|
|
308
|
-
try {
|
|
309
|
-
broadcastChannel = new BroadcastChannel("omenx_oauth");
|
|
310
|
-
const broadcastListener = (event) => {
|
|
311
|
-
if (event.data?.type === "OMENX_OAUTH_CODE" && event.data?.state === state) {
|
|
312
|
-
console.log("[OAuthFlow] \u2705 Received OAuth code via BroadcastChannel");
|
|
313
|
-
messageReceived = true;
|
|
314
|
-
clearInterval(pollInterval);
|
|
315
|
-
if (broadcastChannel) {
|
|
316
|
-
broadcastChannel.onmessage = null;
|
|
317
|
-
broadcastChannel.close();
|
|
318
|
-
}
|
|
319
|
-
window.removeEventListener("message", wrappedMessageListener);
|
|
320
|
-
window.removeEventListener("storage", storageListener);
|
|
321
|
-
const { code: codeFromBroadcast } = event.data;
|
|
322
|
-
const storedCodeVerifier = sessionStorage.getItem(`omenx_pkce_${state}`);
|
|
323
|
-
let codeVerifier2;
|
|
324
|
-
if (storedCodeVerifier) {
|
|
325
|
-
sessionStorage.removeItem(`omenx_pkce_${state}`);
|
|
326
|
-
codeVerifier2 = storedCodeVerifier;
|
|
327
|
-
}
|
|
328
|
-
this.exchangeCodeForToken(codeFromBroadcast, options.redirectUri, codeVerifier2).then(async (tokenResponse) => {
|
|
329
|
-
await this.config.onTokenReceived(tokenResponse);
|
|
330
|
-
resolve(tokenResponse);
|
|
331
|
-
}).catch((error) => {
|
|
332
|
-
this.config.onError?.(error);
|
|
333
|
-
reject(error);
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
};
|
|
337
|
-
broadcastChannel.onmessage = broadcastListener;
|
|
338
|
-
} catch (error) {
|
|
339
|
-
console.warn("[OAuthFlow] BroadcastChannel not supported:", error);
|
|
340
|
-
broadcastChannel = null;
|
|
341
|
-
}
|
|
342
|
-
console.log("[OAuthFlow] \u{1F50D} Starting OAuth flow, listening for callback...", {
|
|
343
|
-
state,
|
|
344
|
-
statePreview: state.substring(0, 20) + "...",
|
|
345
|
-
stateLength: state.length,
|
|
346
|
-
hasOpener: !!window.opener,
|
|
347
|
-
polling: true,
|
|
348
|
-
broadcastChannel: true,
|
|
349
|
-
expectedKey: `omenx_oauth_callback_${state}`
|
|
350
|
-
});
|
|
351
|
-
let messageReceived = false;
|
|
352
|
-
const originalMessageListener = messageListener;
|
|
353
|
-
const wrappedMessageListener = async (event) => {
|
|
354
|
-
messageReceived = true;
|
|
355
|
-
clearInterval(pollInterval);
|
|
356
|
-
window.removeEventListener("storage", storageListener);
|
|
357
|
-
await originalMessageListener(event);
|
|
358
|
-
};
|
|
359
|
-
window.removeEventListener("message", messageListener);
|
|
360
|
-
window.addEventListener("message", wrappedMessageListener);
|
|
361
|
-
const isPopupActuallyClosed = () => {
|
|
362
|
-
try {
|
|
363
|
-
if (!popup.closed) {
|
|
364
|
-
return false;
|
|
365
|
-
}
|
|
366
|
-
try {
|
|
367
|
-
const _ = popup.location.href;
|
|
368
|
-
return true;
|
|
369
|
-
} catch (e) {
|
|
370
|
-
return false;
|
|
371
|
-
}
|
|
372
|
-
} catch (e) {
|
|
373
|
-
return false;
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
let popupClosedTime = null;
|
|
377
|
-
const POPUP_CLOSED_GRACE_PERIOD = 5e3;
|
|
378
97
|
const checkClosed = setInterval(() => {
|
|
379
98
|
if (messageReceived) {
|
|
380
99
|
clearInterval(checkClosed);
|
|
381
|
-
clearInterval(pollInterval);
|
|
382
100
|
return;
|
|
383
101
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
const timeSinceClosed = Date.now() - popupClosedTime;
|
|
395
|
-
if (timeSinceClosed < POPUP_CLOSED_GRACE_PERIOD) {
|
|
396
|
-
console.log("[OAuthFlow] \u23F3 Popup closed, but still in grace period...", {
|
|
397
|
-
timeRemaining: `${POPUP_CLOSED_GRACE_PERIOD - timeSinceClosed}ms`,
|
|
398
|
-
pollCount
|
|
399
|
-
});
|
|
400
|
-
return;
|
|
102
|
+
let isClosed = false;
|
|
103
|
+
try {
|
|
104
|
+
if (popup.closed) {
|
|
105
|
+
try {
|
|
106
|
+
popup.location.href;
|
|
107
|
+
isClosed = true;
|
|
108
|
+
} catch (e) {
|
|
109
|
+
isClosed = false;
|
|
110
|
+
}
|
|
401
111
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
});
|
|
407
|
-
immediateCheck();
|
|
112
|
+
} catch (e) {
|
|
113
|
+
isClosed = false;
|
|
114
|
+
}
|
|
115
|
+
if (isClosed) {
|
|
408
116
|
clearInterval(checkClosed);
|
|
409
117
|
clearInterval(pollInterval);
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
if (!messageReceived) {
|
|
417
|
-
const error = new Error("Authentication was cancelled.");
|
|
418
|
-
this.config.onError?.(error);
|
|
419
|
-
reject(error);
|
|
420
|
-
}
|
|
421
|
-
} else {
|
|
422
|
-
if (popupClosedTime !== null) {
|
|
423
|
-
console.log("[OAuthFlow] \u2705 Popup is open again (was temporarily on different origin)");
|
|
424
|
-
popupClosedTime = null;
|
|
425
|
-
}
|
|
118
|
+
sessionStorage.removeItem(`omenx_pkce_${state}`);
|
|
119
|
+
sessionStorage.removeItem(stateStorageKey);
|
|
120
|
+
const error = new Error("Authentication was cancelled.");
|
|
121
|
+
this.config.onError?.(error);
|
|
122
|
+
reject(error);
|
|
426
123
|
}
|
|
427
124
|
}, 1e3);
|
|
428
125
|
});
|
|
@@ -434,7 +131,8 @@ var OAuthFlow = class {
|
|
|
434
131
|
const body = {
|
|
435
132
|
code,
|
|
436
133
|
redirect_uri: redirectUri,
|
|
437
|
-
grant_type: "authorization_code"
|
|
134
|
+
grant_type: "authorization_code",
|
|
135
|
+
client_id: this.config.gameId
|
|
438
136
|
};
|
|
439
137
|
if (codeVerifier) {
|
|
440
138
|
body.code_verifier = codeVerifier;
|
|
@@ -443,8 +141,6 @@ var OAuthFlow = class {
|
|
|
443
141
|
method: "POST",
|
|
444
142
|
headers: {
|
|
445
143
|
"Content-Type": "application/json"
|
|
446
|
-
// Note: API key should be included via Authorization header
|
|
447
|
-
// This should be set by the game's backend, not the SDK
|
|
448
144
|
},
|
|
449
145
|
body: JSON.stringify(body)
|
|
450
146
|
});
|
|
@@ -462,6 +158,25 @@ var OAuthFlow = class {
|
|
|
462
158
|
crypto.getRandomValues(array);
|
|
463
159
|
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
464
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Generate PKCE code verifier
|
|
163
|
+
*/
|
|
164
|
+
generateCodeVerifier() {
|
|
165
|
+
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
166
|
+
const array = new Uint8Array(128);
|
|
167
|
+
crypto.getRandomValues(array);
|
|
168
|
+
return Array.from(array, (byte) => charset[byte % charset.length]).join("");
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Generate PKCE code challenge from verifier
|
|
172
|
+
*/
|
|
173
|
+
async generateCodeChallenge(verifier) {
|
|
174
|
+
const encoder = new TextEncoder();
|
|
175
|
+
const data = encoder.encode(verifier);
|
|
176
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
177
|
+
const base64 = btoa(String.fromCharCode(...new Uint8Array(hash)));
|
|
178
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
179
|
+
}
|
|
465
180
|
};
|
|
466
181
|
|
|
467
182
|
// src/iframe-auth.ts
|
|
@@ -655,7 +370,17 @@ var OmenXGameSDK = class {
|
|
|
655
370
|
* Authenticate user via OAuth popup
|
|
656
371
|
*/
|
|
657
372
|
async authenticate(options) {
|
|
658
|
-
|
|
373
|
+
const tokenResponse = await this.oauthFlow.authenticate(options);
|
|
374
|
+
return {
|
|
375
|
+
accessToken: tokenResponse.access_token,
|
|
376
|
+
walletAddress: tokenResponse.user.walletAddress,
|
|
377
|
+
userId: tokenResponse.user.userId,
|
|
378
|
+
profileName: tokenResponse.user.profileName,
|
|
379
|
+
profilePicture: tokenResponse.user.profilePicture,
|
|
380
|
+
email: tokenResponse.user.email,
|
|
381
|
+
gameId: this.config.gameId,
|
|
382
|
+
timestamp: Date.now()
|
|
383
|
+
};
|
|
659
384
|
}
|
|
660
385
|
/**
|
|
661
386
|
* Get current authentication data
|
|
@@ -828,22 +553,38 @@ var OmenXServerSDK = class {
|
|
|
828
553
|
/**
|
|
829
554
|
* Player Operations
|
|
830
555
|
*/
|
|
556
|
+
/**
|
|
557
|
+
* Get full player data: NFTs (system + game) and balances for a wallet.
|
|
558
|
+
* Requires both nfts:read and balances:read scopes.
|
|
559
|
+
*/
|
|
560
|
+
async getPlayer(wallet, chainId) {
|
|
561
|
+
const response = await this.apiCall(
|
|
562
|
+
`/v1/players/${encodeURIComponent(wallet)}?chainId=${encodeURIComponent(chainId)}`
|
|
563
|
+
);
|
|
564
|
+
return response.json();
|
|
565
|
+
}
|
|
831
566
|
/**
|
|
832
567
|
* Get native and ERC20 token balances for a wallet
|
|
833
568
|
*/
|
|
834
569
|
async getPlayerBalances(wallet, chainId) {
|
|
835
|
-
const response = await this.apiCall(
|
|
570
|
+
const response = await this.apiCall(
|
|
571
|
+
`/v1/players/${encodeURIComponent(wallet)}/balances?chainId=${encodeURIComponent(chainId)}`
|
|
572
|
+
);
|
|
836
573
|
return response.json();
|
|
837
574
|
}
|
|
838
575
|
/**
|
|
839
|
-
* Get paginated NFTs for a wallet
|
|
576
|
+
* Get paginated NFTs for a wallet.
|
|
577
|
+
* With no contract: returns OmenX system NFTs (Asset Manager, Faucet, Early Adopter) + game NFTs.
|
|
578
|
+
* With contract: returns only NFTs for that contract.
|
|
840
579
|
*/
|
|
841
580
|
async getPlayerNfts(wallet, chainId, options) {
|
|
842
581
|
const params = new URLSearchParams({ chainId });
|
|
843
|
-
if (options?.contract) params.
|
|
844
|
-
if (options?.cursor) params.
|
|
845
|
-
if (options?.limit) params.
|
|
846
|
-
const response = await this.apiCall(
|
|
582
|
+
if (options?.contract) params.set("contract", options.contract);
|
|
583
|
+
if (options?.cursor) params.set("cursor", options.cursor);
|
|
584
|
+
if (options?.limit != null) params.set("limit", String(options.limit));
|
|
585
|
+
const response = await this.apiCall(
|
|
586
|
+
`/v1/players/${encodeURIComponent(wallet)}/nfts?${params.toString()}`
|
|
587
|
+
);
|
|
847
588
|
return response.json();
|
|
848
589
|
}
|
|
849
590
|
/**
|