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