@nxcode/sdk 1.0.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.
- package/dist/nxcode.esm.js +635 -0
- package/dist/nxcode.esm.js.map +1 -0
- package/dist/nxcode.js +646 -0
- package/dist/nxcode.js.map +1 -0
- package/dist/nxcode.min.js +1 -0
- package/dist/types/auth.d.ts +59 -0
- package/dist/types/billing.d.ts +18 -0
- package/dist/types/index.d.ts +109 -0
- package/dist/types/payment.d.ts +26 -0
- package/dist/types/types.d.ts +47 -0
- package/package.json +37 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nxcode SDK - Authentication Module
|
|
3
|
+
*/
|
|
4
|
+
const STORAGE_KEY = 'nxcode_sdk_auth';
|
|
5
|
+
class NxcodeAuth {
|
|
6
|
+
constructor(apiEndpoint, appId) {
|
|
7
|
+
this.user = null;
|
|
8
|
+
this.token = null;
|
|
9
|
+
this.refreshToken = null;
|
|
10
|
+
this.expiresAt = null;
|
|
11
|
+
this.authCallbacks = [];
|
|
12
|
+
this.popupWindow = null;
|
|
13
|
+
this.messageHandler = null;
|
|
14
|
+
this.apiEndpoint = apiEndpoint;
|
|
15
|
+
this.appId = appId;
|
|
16
|
+
this.loadFromStorage();
|
|
17
|
+
this.setupMessageListener();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Initiate login flow via OAuth popup
|
|
21
|
+
*/
|
|
22
|
+
async login(provider = 'google') {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
// Open popup
|
|
25
|
+
const width = 500;
|
|
26
|
+
const height = 600;
|
|
27
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
28
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
29
|
+
const loginUrl = `${this.apiEndpoint}/api/sdk/auth/login?app_id=${this.appId}&provider=${provider}`;
|
|
30
|
+
this.popupWindow = window.open(loginUrl, 'nxcode_login', `width=${width},height=${height},left=${left},top=${top}`);
|
|
31
|
+
if (!this.popupWindow) {
|
|
32
|
+
reject(new Error('Failed to open login popup. Please allow popups for this site.'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Set up timeout
|
|
36
|
+
const timeout = setTimeout(() => {
|
|
37
|
+
this.popupWindow?.close();
|
|
38
|
+
reject(new Error('Login timeout'));
|
|
39
|
+
}, 120000); // 2 minute timeout
|
|
40
|
+
// Wait for message from popup
|
|
41
|
+
const handleMessage = (event) => {
|
|
42
|
+
if (event.data?.type !== 'NXCODE_SDK_AUTH')
|
|
43
|
+
return;
|
|
44
|
+
clearTimeout(timeout);
|
|
45
|
+
window.removeEventListener('message', handleMessage);
|
|
46
|
+
const result = event.data.payload;
|
|
47
|
+
if (result.success && result.user && result.token) {
|
|
48
|
+
this.setAuth(result);
|
|
49
|
+
resolve(result.user);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
reject(new Error(result.error || 'Login failed'));
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
window.addEventListener('message', handleMessage);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Logout and clear session
|
|
60
|
+
*/
|
|
61
|
+
async logout() {
|
|
62
|
+
try {
|
|
63
|
+
if (this.token) {
|
|
64
|
+
await fetch(`${this.apiEndpoint}/api/sdk/auth/logout`, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: this.getHeaders(),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.warn('Logout request failed:', error);
|
|
72
|
+
}
|
|
73
|
+
this.clearAuth();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get current user
|
|
77
|
+
*/
|
|
78
|
+
getUser() {
|
|
79
|
+
return this.user;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get current auth token
|
|
83
|
+
*/
|
|
84
|
+
getToken() {
|
|
85
|
+
// Check if token is expired
|
|
86
|
+
if (this.expiresAt && new Date() > this.expiresAt) {
|
|
87
|
+
// Token is expired, need to refresh
|
|
88
|
+
// Start refresh in background but don't return the expired token
|
|
89
|
+
this.refreshSession().catch(console.error);
|
|
90
|
+
// Return null to force re-auth rather than using expired token
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return this.token;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get token, refreshing if needed (async version)
|
|
97
|
+
*/
|
|
98
|
+
async getValidToken() {
|
|
99
|
+
if (this.expiresAt && new Date() > this.expiresAt) {
|
|
100
|
+
await this.refreshSession();
|
|
101
|
+
}
|
|
102
|
+
return this.token;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Register callback for auth state changes
|
|
106
|
+
*/
|
|
107
|
+
onAuthStateChange(callback) {
|
|
108
|
+
this.authCallbacks.push(callback);
|
|
109
|
+
// Call immediately with current state
|
|
110
|
+
callback(this.user);
|
|
111
|
+
// Return unsubscribe function
|
|
112
|
+
return () => {
|
|
113
|
+
const index = this.authCallbacks.indexOf(callback);
|
|
114
|
+
if (index > -1) {
|
|
115
|
+
this.authCallbacks.splice(index, 1);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if user is logged in
|
|
121
|
+
*/
|
|
122
|
+
isLoggedIn() {
|
|
123
|
+
return this.user !== null && this.token !== null;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Refresh the current session
|
|
127
|
+
*/
|
|
128
|
+
async refreshSession() {
|
|
129
|
+
if (!this.refreshToken) {
|
|
130
|
+
this.clearAuth();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const response = await fetch(`${this.apiEndpoint}/api/sdk/auth/refresh`, {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
headers: {
|
|
137
|
+
'Content-Type': 'application/json',
|
|
138
|
+
'X-App-Id': this.appId,
|
|
139
|
+
},
|
|
140
|
+
body: JSON.stringify({ refreshToken: this.refreshToken }),
|
|
141
|
+
});
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
throw new Error('Refresh failed');
|
|
144
|
+
}
|
|
145
|
+
const data = await response.json();
|
|
146
|
+
this.setAuth({
|
|
147
|
+
success: true,
|
|
148
|
+
token: data.token,
|
|
149
|
+
refreshToken: data.refreshToken,
|
|
150
|
+
expiresAt: data.expiresAt,
|
|
151
|
+
user: data.user,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
console.error('Session refresh failed:', error);
|
|
156
|
+
this.clearAuth();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Fetch current user info from server
|
|
161
|
+
*/
|
|
162
|
+
async fetchUser() {
|
|
163
|
+
if (!this.token)
|
|
164
|
+
return null;
|
|
165
|
+
try {
|
|
166
|
+
const response = await fetch(`${this.apiEndpoint}/api/sdk/auth/me`, {
|
|
167
|
+
headers: this.getHeaders(),
|
|
168
|
+
});
|
|
169
|
+
if (!response.ok) {
|
|
170
|
+
if (response.status === 401) {
|
|
171
|
+
await this.refreshSession();
|
|
172
|
+
return this.user;
|
|
173
|
+
}
|
|
174
|
+
throw new Error('Failed to fetch user');
|
|
175
|
+
}
|
|
176
|
+
const user = await response.json();
|
|
177
|
+
this.user = user;
|
|
178
|
+
this.notifyAuthChange();
|
|
179
|
+
this.saveToStorage();
|
|
180
|
+
return user;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
console.error('Failed to fetch user:', error);
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// ==================== Private Methods ====================
|
|
188
|
+
setAuth(result) {
|
|
189
|
+
if (result.success && result.user && result.token) {
|
|
190
|
+
this.user = result.user;
|
|
191
|
+
this.token = result.token;
|
|
192
|
+
this.refreshToken = result.refreshToken || null;
|
|
193
|
+
this.expiresAt = result.expiresAt ? new Date(result.expiresAt) : null;
|
|
194
|
+
this.saveToStorage();
|
|
195
|
+
this.notifyAuthChange();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
clearAuth() {
|
|
199
|
+
this.user = null;
|
|
200
|
+
this.token = null;
|
|
201
|
+
this.refreshToken = null;
|
|
202
|
+
this.expiresAt = null;
|
|
203
|
+
localStorage.removeItem(`${STORAGE_KEY}_${this.appId}`);
|
|
204
|
+
this.notifyAuthChange();
|
|
205
|
+
}
|
|
206
|
+
saveToStorage() {
|
|
207
|
+
if (this.user && this.token) {
|
|
208
|
+
const data = {
|
|
209
|
+
token: this.token,
|
|
210
|
+
refreshToken: this.refreshToken || '',
|
|
211
|
+
expiresAt: this.expiresAt?.toISOString() || '',
|
|
212
|
+
user: this.user,
|
|
213
|
+
};
|
|
214
|
+
localStorage.setItem(`${STORAGE_KEY}_${this.appId}`, JSON.stringify(data));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
loadFromStorage() {
|
|
218
|
+
try {
|
|
219
|
+
const stored = localStorage.getItem(`${STORAGE_KEY}_${this.appId}`);
|
|
220
|
+
if (stored) {
|
|
221
|
+
const data = JSON.parse(stored);
|
|
222
|
+
this.token = data.token;
|
|
223
|
+
this.refreshToken = data.refreshToken;
|
|
224
|
+
this.expiresAt = data.expiresAt ? new Date(data.expiresAt) : null;
|
|
225
|
+
this.user = data.user;
|
|
226
|
+
// Check if expired
|
|
227
|
+
if (this.expiresAt && new Date() > this.expiresAt) {
|
|
228
|
+
this.refreshSession().catch(() => this.clearAuth());
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
console.warn('Failed to load auth from storage:', error);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
notifyAuthChange() {
|
|
237
|
+
for (const callback of this.authCallbacks) {
|
|
238
|
+
try {
|
|
239
|
+
callback(this.user);
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
console.error('Auth callback error:', error);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
setupMessageListener() {
|
|
247
|
+
// Already set up message handling in login()
|
|
248
|
+
}
|
|
249
|
+
getHeaders() {
|
|
250
|
+
const headers = {
|
|
251
|
+
'Content-Type': 'application/json',
|
|
252
|
+
'X-App-Id': this.appId,
|
|
253
|
+
};
|
|
254
|
+
if (this.token) {
|
|
255
|
+
headers['Authorization'] = `Bearer ${this.token}`;
|
|
256
|
+
}
|
|
257
|
+
return headers;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Nxcode SDK - Billing Module
|
|
263
|
+
*/
|
|
264
|
+
class NxcodeBilling {
|
|
265
|
+
constructor(apiEndpoint, appId, getToken) {
|
|
266
|
+
this.apiEndpoint = apiEndpoint;
|
|
267
|
+
this.appId = appId;
|
|
268
|
+
this.getToken = getToken;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Get user's current balance
|
|
272
|
+
*/
|
|
273
|
+
async getBalance() {
|
|
274
|
+
const token = this.getToken();
|
|
275
|
+
if (!token) {
|
|
276
|
+
throw new Error('Not authenticated. Please login first.');
|
|
277
|
+
}
|
|
278
|
+
const response = await fetch(`${this.apiEndpoint}/api/sdk/billing/balance`, {
|
|
279
|
+
headers: this.getHeaders(token),
|
|
280
|
+
});
|
|
281
|
+
if (!response.ok) {
|
|
282
|
+
if (response.status === 401) {
|
|
283
|
+
throw new Error('Session expired. Please login again.');
|
|
284
|
+
}
|
|
285
|
+
throw new Error('Failed to fetch balance');
|
|
286
|
+
}
|
|
287
|
+
const data = await response.json();
|
|
288
|
+
return data.balance;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Open top-up page in new tab
|
|
292
|
+
*/
|
|
293
|
+
topUp() {
|
|
294
|
+
const topUpUrl = `${this.apiEndpoint}/api/sdk/billing/topup?app_id=${this.appId}`;
|
|
295
|
+
window.open(topUpUrl, '_blank');
|
|
296
|
+
}
|
|
297
|
+
// ==================== Private Methods ====================
|
|
298
|
+
getHeaders(token) {
|
|
299
|
+
return {
|
|
300
|
+
'Content-Type': 'application/json',
|
|
301
|
+
'X-App-Id': this.appId,
|
|
302
|
+
'Authorization': `Bearer ${token}`,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Nxcode SDK - Payment Module
|
|
309
|
+
*/
|
|
310
|
+
class NxcodePayment {
|
|
311
|
+
constructor(apiEndpoint, appId, getToken) {
|
|
312
|
+
this.apiEndpoint = apiEndpoint;
|
|
313
|
+
this.appId = appId;
|
|
314
|
+
this.getToken = getToken;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Process an in-app purchase
|
|
318
|
+
*
|
|
319
|
+
* @param options - Charge options
|
|
320
|
+
* @returns Charge result with transaction details
|
|
321
|
+
*/
|
|
322
|
+
async charge(options) {
|
|
323
|
+
const token = this.getToken();
|
|
324
|
+
if (!token) {
|
|
325
|
+
return {
|
|
326
|
+
success: false,
|
|
327
|
+
error: 'Not authenticated. Please login first.',
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
const response = await fetch(`${this.apiEndpoint}/api/sdk/payment/charge`, {
|
|
332
|
+
method: 'POST',
|
|
333
|
+
headers: this.getHeaders(token),
|
|
334
|
+
body: JSON.stringify({
|
|
335
|
+
amount: options.amount,
|
|
336
|
+
description: options.description,
|
|
337
|
+
metadata: options.metadata,
|
|
338
|
+
}),
|
|
339
|
+
});
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
if (response.status === 401) {
|
|
342
|
+
return {
|
|
343
|
+
success: false,
|
|
344
|
+
error: 'Session expired. Please login again.',
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (response.status === 402) {
|
|
348
|
+
return {
|
|
349
|
+
success: false,
|
|
350
|
+
error: 'Insufficient balance. Please top up.',
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const errorData = await response.json().catch(() => ({}));
|
|
354
|
+
return {
|
|
355
|
+
success: false,
|
|
356
|
+
error: errorData.detail || 'Payment failed',
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
const result = await response.json();
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
return {
|
|
364
|
+
success: false,
|
|
365
|
+
error: error instanceof Error ? error.message : 'Payment failed',
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Get user's transaction history
|
|
371
|
+
*
|
|
372
|
+
* @param limit - Maximum number of transactions to return
|
|
373
|
+
* @param offset - Pagination offset
|
|
374
|
+
* @returns Array of transactions
|
|
375
|
+
*/
|
|
376
|
+
async getTransactions(limit = 50, offset = 0) {
|
|
377
|
+
const token = this.getToken();
|
|
378
|
+
if (!token) {
|
|
379
|
+
throw new Error('Not authenticated. Please login first.');
|
|
380
|
+
}
|
|
381
|
+
const response = await fetch(`${this.apiEndpoint}/api/sdk/payment/transactions?limit=${limit}&offset=${offset}`, {
|
|
382
|
+
headers: this.getHeaders(token),
|
|
383
|
+
});
|
|
384
|
+
if (!response.ok) {
|
|
385
|
+
if (response.status === 401) {
|
|
386
|
+
throw new Error('Session expired. Please login again.');
|
|
387
|
+
}
|
|
388
|
+
throw new Error('Failed to fetch transactions');
|
|
389
|
+
}
|
|
390
|
+
const data = await response.json();
|
|
391
|
+
return data.transactions;
|
|
392
|
+
}
|
|
393
|
+
// ==================== Private Methods ====================
|
|
394
|
+
getHeaders(token) {
|
|
395
|
+
return {
|
|
396
|
+
'Content-Type': 'application/json',
|
|
397
|
+
'X-App-Id': this.appId,
|
|
398
|
+
'Authorization': `Bearer ${token}`,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Nxcode SDK
|
|
405
|
+
*
|
|
406
|
+
* A lightweight SDK for integrating Nxcode authentication, billing,
|
|
407
|
+
* and payment features into web applications.
|
|
408
|
+
*
|
|
409
|
+
* Usage:
|
|
410
|
+
* <script src="https://sdk.nxcode.io/v1/nxcode.js"></script>
|
|
411
|
+
* <script>
|
|
412
|
+
* const user = await Nxcode.auth.login();
|
|
413
|
+
* console.log('Logged in:', user.email);
|
|
414
|
+
* </script>
|
|
415
|
+
*/
|
|
416
|
+
// API endpoint - auto-detect or use default
|
|
417
|
+
const DEFAULT_API_ENDPOINT = 'https://studio-api.nxcode.io';
|
|
418
|
+
class NxcodeSDK {
|
|
419
|
+
constructor() {
|
|
420
|
+
this.config = null;
|
|
421
|
+
this._auth = null;
|
|
422
|
+
this._billing = null;
|
|
423
|
+
this._payment = null;
|
|
424
|
+
this.initPromise = null;
|
|
425
|
+
this.apiEndpoint = this.detectApiEndpoint();
|
|
426
|
+
this.autoInit();
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Auto-initialize SDK based on environment
|
|
430
|
+
*/
|
|
431
|
+
async autoInit() {
|
|
432
|
+
if (this.initPromise)
|
|
433
|
+
return this.initPromise;
|
|
434
|
+
this.initPromise = this.init();
|
|
435
|
+
return this.initPromise;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Initialize SDK by fetching configuration
|
|
439
|
+
*/
|
|
440
|
+
async init() {
|
|
441
|
+
try {
|
|
442
|
+
// Try to get config from server
|
|
443
|
+
const response = await fetch(`${this.apiEndpoint}/api/sdk/config`, {
|
|
444
|
+
credentials: 'include',
|
|
445
|
+
});
|
|
446
|
+
if (response.ok) {
|
|
447
|
+
this.config = await response.json();
|
|
448
|
+
this.config.apiEndpoint = this.apiEndpoint;
|
|
449
|
+
this.setupModules();
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
console.warn('Nxcode SDK: Could not auto-detect app config. Call Nxcode.configure() manually.');
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Manually configure SDK with app ID
|
|
458
|
+
*/
|
|
459
|
+
configure(appId) {
|
|
460
|
+
this.config = {
|
|
461
|
+
appId,
|
|
462
|
+
name: '',
|
|
463
|
+
billingMode: 'creator_pays',
|
|
464
|
+
apiEndpoint: this.apiEndpoint,
|
|
465
|
+
};
|
|
466
|
+
this.setupModules();
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Set up SDK modules after configuration
|
|
470
|
+
*/
|
|
471
|
+
setupModules() {
|
|
472
|
+
if (!this.config)
|
|
473
|
+
return;
|
|
474
|
+
this._auth = new NxcodeAuth(this.config.apiEndpoint, this.config.appId);
|
|
475
|
+
this._billing = new NxcodeBilling(this.config.apiEndpoint, this.config.appId, () => this._auth?.getToken() || null);
|
|
476
|
+
this._payment = new NxcodePayment(this.config.apiEndpoint, this.config.appId, () => this._auth?.getToken() || null);
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Detect API endpoint based on environment
|
|
480
|
+
*/
|
|
481
|
+
detectApiEndpoint() {
|
|
482
|
+
if (typeof window !== 'undefined') {
|
|
483
|
+
const hostname = window.location.hostname;
|
|
484
|
+
// Development environment
|
|
485
|
+
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
|
486
|
+
return 'http://localhost:8001';
|
|
487
|
+
}
|
|
488
|
+
// Check for dev environment
|
|
489
|
+
if (hostname.includes('.nxcode.dev') || hostname.includes('.nxcode.app')) {
|
|
490
|
+
return 'https://studio-api.nxcode.io';
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return DEFAULT_API_ENDPOINT;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Ensure SDK is initialized before operations
|
|
497
|
+
*/
|
|
498
|
+
async ensureInitialized() {
|
|
499
|
+
if (this.initPromise) {
|
|
500
|
+
await this.initPromise;
|
|
501
|
+
}
|
|
502
|
+
if (!this.config) {
|
|
503
|
+
throw new Error('Nxcode SDK not configured. Add X-App-Id header or call Nxcode.configure(appId).');
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// ==================== Auth Module ====================
|
|
507
|
+
get auth() {
|
|
508
|
+
const self = this;
|
|
509
|
+
return {
|
|
510
|
+
/**
|
|
511
|
+
* Login via OAuth popup
|
|
512
|
+
*/
|
|
513
|
+
async login(provider = 'google') {
|
|
514
|
+
await self.ensureInitialized();
|
|
515
|
+
return self._auth.login(provider);
|
|
516
|
+
},
|
|
517
|
+
/**
|
|
518
|
+
* Logout current user
|
|
519
|
+
*/
|
|
520
|
+
async logout() {
|
|
521
|
+
await self.ensureInitialized();
|
|
522
|
+
return self._auth.logout();
|
|
523
|
+
},
|
|
524
|
+
/**
|
|
525
|
+
* Get current user
|
|
526
|
+
*/
|
|
527
|
+
getUser() {
|
|
528
|
+
return self._auth?.getUser() || null;
|
|
529
|
+
},
|
|
530
|
+
/**
|
|
531
|
+
* Get auth token for API calls
|
|
532
|
+
*/
|
|
533
|
+
getToken() {
|
|
534
|
+
return self._auth?.getToken() || null;
|
|
535
|
+
},
|
|
536
|
+
/**
|
|
537
|
+
* Register auth state change callback
|
|
538
|
+
*/
|
|
539
|
+
onAuthStateChange(callback) {
|
|
540
|
+
if (!self._auth) {
|
|
541
|
+
// Store the actual unsubscribe function when SDK initializes
|
|
542
|
+
let actualUnsubscribe = null;
|
|
543
|
+
let cancelled = false;
|
|
544
|
+
// Queue callback until initialized
|
|
545
|
+
self.ensureInitialized().then(() => {
|
|
546
|
+
if (!cancelled && self._auth) {
|
|
547
|
+
actualUnsubscribe = self._auth.onAuthStateChange(callback);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
// Return unsubscribe that handles both immediate and deferred cases
|
|
551
|
+
return () => {
|
|
552
|
+
cancelled = true;
|
|
553
|
+
if (actualUnsubscribe) {
|
|
554
|
+
actualUnsubscribe();
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
return self._auth.onAuthStateChange(callback);
|
|
559
|
+
},
|
|
560
|
+
/**
|
|
561
|
+
* Check if user is logged in
|
|
562
|
+
*/
|
|
563
|
+
isLoggedIn() {
|
|
564
|
+
return self._auth?.isLoggedIn() || false;
|
|
565
|
+
},
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
// ==================== Billing Module ====================
|
|
569
|
+
get billing() {
|
|
570
|
+
const self = this;
|
|
571
|
+
return {
|
|
572
|
+
/**
|
|
573
|
+
* Get user's current balance
|
|
574
|
+
*/
|
|
575
|
+
async getBalance() {
|
|
576
|
+
await self.ensureInitialized();
|
|
577
|
+
return self._billing.getBalance();
|
|
578
|
+
},
|
|
579
|
+
/**
|
|
580
|
+
* Open top-up page
|
|
581
|
+
*/
|
|
582
|
+
topUp() {
|
|
583
|
+
self._billing?.topUp();
|
|
584
|
+
},
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
// ==================== Payment Module ====================
|
|
588
|
+
get payment() {
|
|
589
|
+
const self = this;
|
|
590
|
+
return {
|
|
591
|
+
/**
|
|
592
|
+
* Process in-app purchase
|
|
593
|
+
*/
|
|
594
|
+
async charge(options) {
|
|
595
|
+
await self.ensureInitialized();
|
|
596
|
+
return self._payment.charge(options);
|
|
597
|
+
},
|
|
598
|
+
/**
|
|
599
|
+
* Get transaction history
|
|
600
|
+
*/
|
|
601
|
+
async getTransactions(limit, offset) {
|
|
602
|
+
await self.ensureInitialized();
|
|
603
|
+
return self._payment.getTransactions(limit, offset);
|
|
604
|
+
},
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
// ==================== Utility Methods ====================
|
|
608
|
+
/**
|
|
609
|
+
* Get current configuration
|
|
610
|
+
*/
|
|
611
|
+
getConfig() {
|
|
612
|
+
return this.config;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Check if SDK is ready
|
|
616
|
+
*/
|
|
617
|
+
isReady() {
|
|
618
|
+
return this.config !== null && this._auth !== null;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Wait for SDK to be ready
|
|
622
|
+
*/
|
|
623
|
+
async ready() {
|
|
624
|
+
await this.ensureInitialized();
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// Create singleton instance
|
|
628
|
+
const Nxcode = new NxcodeSDK();
|
|
629
|
+
// Export for UMD (browser global)
|
|
630
|
+
if (typeof window !== 'undefined') {
|
|
631
|
+
window.Nxcode = Nxcode;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
export { Nxcode, Nxcode as default };
|
|
635
|
+
//# sourceMappingURL=nxcode.esm.js.map
|