spaps-sdk 1.2.0 → 1.2.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/dist/index.d.mts +77 -5
- package/dist/index.d.ts +77 -5
- package/dist/index.js +143 -5
- package/dist/index.mjs +143 -5
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -244,14 +244,13 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
|
|
|
244
244
|
password: string;
|
|
245
245
|
}) => Promise<any>;
|
|
246
246
|
/**
|
|
247
|
-
* Verify a magic link token
|
|
248
|
-
*
|
|
247
|
+
* Verify a magic link token and authenticate the user.
|
|
248
|
+
* Returns full auth response with tokens and auto-stores them.
|
|
249
249
|
*/
|
|
250
250
|
verifyMagicLink: (payload: {
|
|
251
251
|
token: string;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}>;
|
|
252
|
+
type?: string;
|
|
253
|
+
}) => Promise<AuthResponse>;
|
|
255
254
|
solana: {
|
|
256
255
|
linkWallet: (payload: {
|
|
257
256
|
wallet_address: string;
|
|
@@ -402,6 +401,51 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
|
|
|
402
401
|
revokeAll: () => Promise<any>;
|
|
403
402
|
touch: () => Promise<any>;
|
|
404
403
|
};
|
|
404
|
+
/**
|
|
405
|
+
* CFO (QuickBooks) namespace for managing multi-account connections
|
|
406
|
+
*/
|
|
407
|
+
cfo: {
|
|
408
|
+
/**
|
|
409
|
+
* Get all QuickBooks connections for the current user
|
|
410
|
+
*/
|
|
411
|
+
getConnections: () => Promise<{
|
|
412
|
+
connections: Array<{
|
|
413
|
+
id: string;
|
|
414
|
+
status: string;
|
|
415
|
+
companyName?: string;
|
|
416
|
+
realmId?: string;
|
|
417
|
+
connectedAt?: string;
|
|
418
|
+
}>;
|
|
419
|
+
}>;
|
|
420
|
+
/**
|
|
421
|
+
* Get connection status (returns primary connection for backward compatibility)
|
|
422
|
+
*/
|
|
423
|
+
getStatus: () => Promise<{
|
|
424
|
+
connection: {
|
|
425
|
+
id: string;
|
|
426
|
+
status: string;
|
|
427
|
+
companyName?: string;
|
|
428
|
+
realmId?: string;
|
|
429
|
+
} | null;
|
|
430
|
+
totalConnections: number;
|
|
431
|
+
}>;
|
|
432
|
+
/**
|
|
433
|
+
* Start adding a new QuickBooks connection
|
|
434
|
+
* Returns auth URL to redirect user to QuickBooks OAuth
|
|
435
|
+
*/
|
|
436
|
+
addConnection: () => Promise<{
|
|
437
|
+
authUrl: string;
|
|
438
|
+
connectionId: string;
|
|
439
|
+
existingConnections: number;
|
|
440
|
+
}>;
|
|
441
|
+
/**
|
|
442
|
+
* Disconnect a specific QuickBooks account
|
|
443
|
+
*/
|
|
444
|
+
disconnect: (connectionId: string) => Promise<{
|
|
445
|
+
success: boolean;
|
|
446
|
+
message: string;
|
|
447
|
+
}>;
|
|
448
|
+
};
|
|
405
449
|
createCheckoutSession(priceId: string, successUrl: string, cancelUrl?: string): Promise<{
|
|
406
450
|
data: CheckoutSession;
|
|
407
451
|
}>;
|
|
@@ -462,6 +506,12 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
|
|
|
462
506
|
isAuthenticated(): boolean;
|
|
463
507
|
getAccessToken(): string | undefined;
|
|
464
508
|
setAccessToken(token: string): void;
|
|
509
|
+
setRefreshToken(token: string): void;
|
|
510
|
+
/**
|
|
511
|
+
* Restore tokens (for SSO/OAuth flows)
|
|
512
|
+
* Sets both access and refresh tokens
|
|
513
|
+
*/
|
|
514
|
+
restoreTokens(accessToken: string, refreshToken?: string): void;
|
|
465
515
|
isLocalMode(): boolean;
|
|
466
516
|
/**
|
|
467
517
|
* Check if current user has admin privileges
|
|
@@ -478,6 +528,10 @@ declare class TokenManager {
|
|
|
478
528
|
private static readonly ACCESS_TOKEN_KEY;
|
|
479
529
|
private static readonly REFRESH_TOKEN_KEY;
|
|
480
530
|
private static readonly USER_KEY;
|
|
531
|
+
/**
|
|
532
|
+
* Cross-platform base64 decode (works in both Node.js and browser)
|
|
533
|
+
*/
|
|
534
|
+
private static base64Decode;
|
|
481
535
|
private static getStorage;
|
|
482
536
|
static storeTokens(tokens: AuthResponse): void;
|
|
483
537
|
static getAccessToken(): string | null;
|
|
@@ -486,6 +540,24 @@ declare class TokenManager {
|
|
|
486
540
|
static clearTokens(): void;
|
|
487
541
|
static isTokenExpired(token: string): boolean;
|
|
488
542
|
static autoRefreshToken(sdk: SPAPSClient): Promise<boolean>;
|
|
543
|
+
/**
|
|
544
|
+
* Parse auth tokens from URL fragment (for OAuth callbacks)
|
|
545
|
+
* URL format: https://app.com/callback#access_token=xxx&refresh_token=xxx
|
|
546
|
+
*/
|
|
547
|
+
static parseTokensFromUrlFragment(url?: string): {
|
|
548
|
+
access_token?: string;
|
|
549
|
+
refresh_token?: string;
|
|
550
|
+
user_id?: string;
|
|
551
|
+
} | null;
|
|
552
|
+
/**
|
|
553
|
+
* Handle OAuth callback - parse tokens from URL and store them
|
|
554
|
+
* Returns user info decoded from JWT, or null if no tokens found
|
|
555
|
+
*/
|
|
556
|
+
static handleOAuthCallback(sdk: SPAPSClient): {
|
|
557
|
+
id: string;
|
|
558
|
+
email?: string;
|
|
559
|
+
role?: string;
|
|
560
|
+
} | null;
|
|
489
561
|
}
|
|
490
562
|
declare class WalletUtils {
|
|
491
563
|
static detectChainType(address: string): 'solana' | 'ethereum' | 'bitcoin' | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -244,14 +244,13 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
|
|
|
244
244
|
password: string;
|
|
245
245
|
}) => Promise<any>;
|
|
246
246
|
/**
|
|
247
|
-
* Verify a magic link token
|
|
248
|
-
*
|
|
247
|
+
* Verify a magic link token and authenticate the user.
|
|
248
|
+
* Returns full auth response with tokens and auto-stores them.
|
|
249
249
|
*/
|
|
250
250
|
verifyMagicLink: (payload: {
|
|
251
251
|
token: string;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}>;
|
|
252
|
+
type?: string;
|
|
253
|
+
}) => Promise<AuthResponse>;
|
|
255
254
|
solana: {
|
|
256
255
|
linkWallet: (payload: {
|
|
257
256
|
wallet_address: string;
|
|
@@ -402,6 +401,51 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
|
|
|
402
401
|
revokeAll: () => Promise<any>;
|
|
403
402
|
touch: () => Promise<any>;
|
|
404
403
|
};
|
|
404
|
+
/**
|
|
405
|
+
* CFO (QuickBooks) namespace for managing multi-account connections
|
|
406
|
+
*/
|
|
407
|
+
cfo: {
|
|
408
|
+
/**
|
|
409
|
+
* Get all QuickBooks connections for the current user
|
|
410
|
+
*/
|
|
411
|
+
getConnections: () => Promise<{
|
|
412
|
+
connections: Array<{
|
|
413
|
+
id: string;
|
|
414
|
+
status: string;
|
|
415
|
+
companyName?: string;
|
|
416
|
+
realmId?: string;
|
|
417
|
+
connectedAt?: string;
|
|
418
|
+
}>;
|
|
419
|
+
}>;
|
|
420
|
+
/**
|
|
421
|
+
* Get connection status (returns primary connection for backward compatibility)
|
|
422
|
+
*/
|
|
423
|
+
getStatus: () => Promise<{
|
|
424
|
+
connection: {
|
|
425
|
+
id: string;
|
|
426
|
+
status: string;
|
|
427
|
+
companyName?: string;
|
|
428
|
+
realmId?: string;
|
|
429
|
+
} | null;
|
|
430
|
+
totalConnections: number;
|
|
431
|
+
}>;
|
|
432
|
+
/**
|
|
433
|
+
* Start adding a new QuickBooks connection
|
|
434
|
+
* Returns auth URL to redirect user to QuickBooks OAuth
|
|
435
|
+
*/
|
|
436
|
+
addConnection: () => Promise<{
|
|
437
|
+
authUrl: string;
|
|
438
|
+
connectionId: string;
|
|
439
|
+
existingConnections: number;
|
|
440
|
+
}>;
|
|
441
|
+
/**
|
|
442
|
+
* Disconnect a specific QuickBooks account
|
|
443
|
+
*/
|
|
444
|
+
disconnect: (connectionId: string) => Promise<{
|
|
445
|
+
success: boolean;
|
|
446
|
+
message: string;
|
|
447
|
+
}>;
|
|
448
|
+
};
|
|
405
449
|
createCheckoutSession(priceId: string, successUrl: string, cancelUrl?: string): Promise<{
|
|
406
450
|
data: CheckoutSession;
|
|
407
451
|
}>;
|
|
@@ -462,6 +506,12 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
|
|
|
462
506
|
isAuthenticated(): boolean;
|
|
463
507
|
getAccessToken(): string | undefined;
|
|
464
508
|
setAccessToken(token: string): void;
|
|
509
|
+
setRefreshToken(token: string): void;
|
|
510
|
+
/**
|
|
511
|
+
* Restore tokens (for SSO/OAuth flows)
|
|
512
|
+
* Sets both access and refresh tokens
|
|
513
|
+
*/
|
|
514
|
+
restoreTokens(accessToken: string, refreshToken?: string): void;
|
|
465
515
|
isLocalMode(): boolean;
|
|
466
516
|
/**
|
|
467
517
|
* Check if current user has admin privileges
|
|
@@ -478,6 +528,10 @@ declare class TokenManager {
|
|
|
478
528
|
private static readonly ACCESS_TOKEN_KEY;
|
|
479
529
|
private static readonly REFRESH_TOKEN_KEY;
|
|
480
530
|
private static readonly USER_KEY;
|
|
531
|
+
/**
|
|
532
|
+
* Cross-platform base64 decode (works in both Node.js and browser)
|
|
533
|
+
*/
|
|
534
|
+
private static base64Decode;
|
|
481
535
|
private static getStorage;
|
|
482
536
|
static storeTokens(tokens: AuthResponse): void;
|
|
483
537
|
static getAccessToken(): string | null;
|
|
@@ -486,6 +540,24 @@ declare class TokenManager {
|
|
|
486
540
|
static clearTokens(): void;
|
|
487
541
|
static isTokenExpired(token: string): boolean;
|
|
488
542
|
static autoRefreshToken(sdk: SPAPSClient): Promise<boolean>;
|
|
543
|
+
/**
|
|
544
|
+
* Parse auth tokens from URL fragment (for OAuth callbacks)
|
|
545
|
+
* URL format: https://app.com/callback#access_token=xxx&refresh_token=xxx
|
|
546
|
+
*/
|
|
547
|
+
static parseTokensFromUrlFragment(url?: string): {
|
|
548
|
+
access_token?: string;
|
|
549
|
+
refresh_token?: string;
|
|
550
|
+
user_id?: string;
|
|
551
|
+
} | null;
|
|
552
|
+
/**
|
|
553
|
+
* Handle OAuth callback - parse tokens from URL and store them
|
|
554
|
+
* Returns user info decoded from JWT, or null if no tokens found
|
|
555
|
+
*/
|
|
556
|
+
static handleOAuthCallback(sdk: SPAPSClient): {
|
|
557
|
+
id: string;
|
|
558
|
+
email?: string;
|
|
559
|
+
role?: string;
|
|
560
|
+
} | null;
|
|
489
561
|
}
|
|
490
562
|
declare class WalletUtils {
|
|
491
563
|
static detectChainType(address: string): 'solana' | 'ethereum' | 'bitcoin' | null;
|
package/dist/index.js
CHANGED
|
@@ -470,12 +470,24 @@ var SPAPSClient = class {
|
|
|
470
470
|
return data;
|
|
471
471
|
},
|
|
472
472
|
/**
|
|
473
|
-
* Verify a magic link token
|
|
474
|
-
*
|
|
473
|
+
* Verify a magic link token and authenticate the user.
|
|
474
|
+
* Returns full auth response with tokens and auto-stores them.
|
|
475
475
|
*/
|
|
476
476
|
verifyMagicLink: async (payload) => {
|
|
477
|
-
const res = await this.client.post("/api/auth/verify-magic-link",
|
|
478
|
-
|
|
477
|
+
const res = await this.client.post("/api/auth/verify-magic-link", {
|
|
478
|
+
token: payload.token,
|
|
479
|
+
type: payload.type || "magiclink"
|
|
480
|
+
});
|
|
481
|
+
const body = res.data;
|
|
482
|
+
if (body?.success === false) throw new Error(body?.error?.message || "Magic link verification failed");
|
|
483
|
+
const data = {
|
|
484
|
+
access_token: body.tokens?.access_token || body.access_token,
|
|
485
|
+
refresh_token: body.tokens?.refresh_token || body.refresh_token,
|
|
486
|
+
user: body.user
|
|
487
|
+
};
|
|
488
|
+
this.accessToken = data.access_token;
|
|
489
|
+
this.refreshToken = data.refresh_token;
|
|
490
|
+
return data;
|
|
479
491
|
},
|
|
480
492
|
solana: {
|
|
481
493
|
linkWallet: async (payload) => {
|
|
@@ -791,6 +803,40 @@ var SPAPSClient = class {
|
|
|
791
803
|
return this.unwrapApiResponse(res, "Failed to touch session");
|
|
792
804
|
}
|
|
793
805
|
};
|
|
806
|
+
/**
|
|
807
|
+
* CFO (QuickBooks) namespace for managing multi-account connections
|
|
808
|
+
*/
|
|
809
|
+
cfo = {
|
|
810
|
+
/**
|
|
811
|
+
* Get all QuickBooks connections for the current user
|
|
812
|
+
*/
|
|
813
|
+
getConnections: async () => {
|
|
814
|
+
const res = await this.client.get("/api/cfo/user-connections", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
|
|
815
|
+
return this.unwrapApiResponse(res, "Failed to get connections");
|
|
816
|
+
},
|
|
817
|
+
/**
|
|
818
|
+
* Get connection status (returns primary connection for backward compatibility)
|
|
819
|
+
*/
|
|
820
|
+
getStatus: async () => {
|
|
821
|
+
const res = await this.client.get("/api/cfo/user-status", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
|
|
822
|
+
return this.unwrapApiResponse(res, "Failed to get status");
|
|
823
|
+
},
|
|
824
|
+
/**
|
|
825
|
+
* Start adding a new QuickBooks connection
|
|
826
|
+
* Returns auth URL to redirect user to QuickBooks OAuth
|
|
827
|
+
*/
|
|
828
|
+
addConnection: async () => {
|
|
829
|
+
const res = await this.client.post("/api/cfo/user-add-connection", {}, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
|
|
830
|
+
return this.unwrapApiResponse(res, "Failed to start connection");
|
|
831
|
+
},
|
|
832
|
+
/**
|
|
833
|
+
* Disconnect a specific QuickBooks account
|
|
834
|
+
*/
|
|
835
|
+
disconnect: async (connectionId) => {
|
|
836
|
+
const res = await this.client.delete(`/api/cfo/user-connections/${connectionId}`, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
|
|
837
|
+
return this.unwrapApiResponse(res, "Failed to disconnect");
|
|
838
|
+
}
|
|
839
|
+
};
|
|
794
840
|
// Stripe Methods
|
|
795
841
|
async createCheckoutSession(priceId, successUrl, cancelUrl) {
|
|
796
842
|
return this.client.post("/api/stripe/create-checkout-session", {
|
|
@@ -914,6 +960,19 @@ var SPAPSClient = class {
|
|
|
914
960
|
setAccessToken(token) {
|
|
915
961
|
this.accessToken = token;
|
|
916
962
|
}
|
|
963
|
+
setRefreshToken(token) {
|
|
964
|
+
this.refreshToken = token;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Restore tokens (for SSO/OAuth flows)
|
|
968
|
+
* Sets both access and refresh tokens
|
|
969
|
+
*/
|
|
970
|
+
restoreTokens(accessToken, refreshToken) {
|
|
971
|
+
this.accessToken = accessToken;
|
|
972
|
+
if (refreshToken) {
|
|
973
|
+
this.refreshToken = refreshToken;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
917
976
|
isLocalMode() {
|
|
918
977
|
return this._isLocalMode;
|
|
919
978
|
}
|
|
@@ -968,6 +1027,17 @@ var TokenManager = class _TokenManager {
|
|
|
968
1027
|
static ACCESS_TOKEN_KEY = "sweet_potato_access_token";
|
|
969
1028
|
static REFRESH_TOKEN_KEY = "sweet_potato_refresh_token";
|
|
970
1029
|
static USER_KEY = "sweet_potato_user";
|
|
1030
|
+
/**
|
|
1031
|
+
* Cross-platform base64 decode (works in both Node.js and browser)
|
|
1032
|
+
*/
|
|
1033
|
+
static base64Decode(str) {
|
|
1034
|
+
if (typeof Buffer !== "undefined") {
|
|
1035
|
+
return Buffer.from(str, "base64").toString("utf8");
|
|
1036
|
+
} else if (typeof atob !== "undefined") {
|
|
1037
|
+
return atob(str);
|
|
1038
|
+
}
|
|
1039
|
+
throw new Error("No base64 decode method available");
|
|
1040
|
+
}
|
|
971
1041
|
static getStorage() {
|
|
972
1042
|
if (typeof globalThis !== "undefined" && globalThis.localStorage) return globalThis.localStorage;
|
|
973
1043
|
if (typeof globalThis !== "undefined" && globalThis.window?.localStorage) return globalThis.window.localStorage;
|
|
@@ -1008,7 +1078,7 @@ var TokenManager = class _TokenManager {
|
|
|
1008
1078
|
try {
|
|
1009
1079
|
const parts = token.split(".");
|
|
1010
1080
|
if (parts.length !== 3 || !parts[1]) return true;
|
|
1011
|
-
const payload = JSON.parse(
|
|
1081
|
+
const payload = JSON.parse(_TokenManager.base64Decode(parts[1]));
|
|
1012
1082
|
const now = Math.floor(Date.now() / 1e3);
|
|
1013
1083
|
return payload.exp < now;
|
|
1014
1084
|
} catch {
|
|
@@ -1032,6 +1102,74 @@ var TokenManager = class _TokenManager {
|
|
|
1032
1102
|
return false;
|
|
1033
1103
|
}
|
|
1034
1104
|
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Parse auth tokens from URL fragment (for OAuth callbacks)
|
|
1107
|
+
* URL format: https://app.com/callback#access_token=xxx&refresh_token=xxx
|
|
1108
|
+
*/
|
|
1109
|
+
static parseTokensFromUrlFragment(url) {
|
|
1110
|
+
let hash;
|
|
1111
|
+
if (url) {
|
|
1112
|
+
try {
|
|
1113
|
+
hash = new URL(url).hash;
|
|
1114
|
+
} catch {
|
|
1115
|
+
return null;
|
|
1116
|
+
}
|
|
1117
|
+
} else if (typeof window !== "undefined") {
|
|
1118
|
+
hash = window.location.hash;
|
|
1119
|
+
} else {
|
|
1120
|
+
return null;
|
|
1121
|
+
}
|
|
1122
|
+
if (!hash || hash.length < 2) return null;
|
|
1123
|
+
const params = new URLSearchParams(hash.substring(1));
|
|
1124
|
+
const access_token = params.get("access_token");
|
|
1125
|
+
const refresh_token = params.get("refresh_token");
|
|
1126
|
+
if (!access_token) return null;
|
|
1127
|
+
return {
|
|
1128
|
+
access_token,
|
|
1129
|
+
refresh_token: refresh_token || void 0,
|
|
1130
|
+
user_id: params.get("user_id") || void 0
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Handle OAuth callback - parse tokens from URL and store them
|
|
1135
|
+
* Returns user info decoded from JWT, or null if no tokens found
|
|
1136
|
+
*/
|
|
1137
|
+
static handleOAuthCallback(sdk) {
|
|
1138
|
+
const tokens = _TokenManager.parseTokensFromUrlFragment();
|
|
1139
|
+
if (!tokens?.access_token) {
|
|
1140
|
+
console.debug("[SPAPS] handleOAuthCallback: No access token found in URL fragment");
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
1143
|
+
if (typeof window !== "undefined") {
|
|
1144
|
+
window.history.replaceState(null, "", window.location.pathname + window.location.search);
|
|
1145
|
+
}
|
|
1146
|
+
try {
|
|
1147
|
+
const parts = tokens.access_token.split(".");
|
|
1148
|
+
if (parts.length !== 3 || !parts[1]) {
|
|
1149
|
+
console.warn("[SPAPS] handleOAuthCallback: Invalid JWT format - expected 3 parts");
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
const payload = JSON.parse(_TokenManager.base64Decode(parts[1]));
|
|
1153
|
+
const user = {
|
|
1154
|
+
id: payload.user_id || payload.sub,
|
|
1155
|
+
email: payload.email,
|
|
1156
|
+
role: payload.role || "user"
|
|
1157
|
+
};
|
|
1158
|
+
_TokenManager.storeTokens({
|
|
1159
|
+
access_token: tokens.access_token,
|
|
1160
|
+
refresh_token: tokens.refresh_token || "",
|
|
1161
|
+
user
|
|
1162
|
+
});
|
|
1163
|
+
sdk.setAccessToken(tokens.access_token);
|
|
1164
|
+
if (tokens.refresh_token) {
|
|
1165
|
+
sdk.setRefreshToken(tokens.refresh_token);
|
|
1166
|
+
}
|
|
1167
|
+
return user;
|
|
1168
|
+
} catch (err) {
|
|
1169
|
+
console.warn("[SPAPS] handleOAuthCallback: Failed to decode JWT", err);
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1035
1173
|
};
|
|
1036
1174
|
var WalletUtils = class _WalletUtils {
|
|
1037
1175
|
static detectChainType(address) {
|
package/dist/index.mjs
CHANGED
|
@@ -443,12 +443,24 @@ var SPAPSClient = class {
|
|
|
443
443
|
return data;
|
|
444
444
|
},
|
|
445
445
|
/**
|
|
446
|
-
* Verify a magic link token
|
|
447
|
-
*
|
|
446
|
+
* Verify a magic link token and authenticate the user.
|
|
447
|
+
* Returns full auth response with tokens and auto-stores them.
|
|
448
448
|
*/
|
|
449
449
|
verifyMagicLink: async (payload) => {
|
|
450
|
-
const res = await this.client.post("/api/auth/verify-magic-link",
|
|
451
|
-
|
|
450
|
+
const res = await this.client.post("/api/auth/verify-magic-link", {
|
|
451
|
+
token: payload.token,
|
|
452
|
+
type: payload.type || "magiclink"
|
|
453
|
+
});
|
|
454
|
+
const body = res.data;
|
|
455
|
+
if (body?.success === false) throw new Error(body?.error?.message || "Magic link verification failed");
|
|
456
|
+
const data = {
|
|
457
|
+
access_token: body.tokens?.access_token || body.access_token,
|
|
458
|
+
refresh_token: body.tokens?.refresh_token || body.refresh_token,
|
|
459
|
+
user: body.user
|
|
460
|
+
};
|
|
461
|
+
this.accessToken = data.access_token;
|
|
462
|
+
this.refreshToken = data.refresh_token;
|
|
463
|
+
return data;
|
|
452
464
|
},
|
|
453
465
|
solana: {
|
|
454
466
|
linkWallet: async (payload) => {
|
|
@@ -764,6 +776,40 @@ var SPAPSClient = class {
|
|
|
764
776
|
return this.unwrapApiResponse(res, "Failed to touch session");
|
|
765
777
|
}
|
|
766
778
|
};
|
|
779
|
+
/**
|
|
780
|
+
* CFO (QuickBooks) namespace for managing multi-account connections
|
|
781
|
+
*/
|
|
782
|
+
cfo = {
|
|
783
|
+
/**
|
|
784
|
+
* Get all QuickBooks connections for the current user
|
|
785
|
+
*/
|
|
786
|
+
getConnections: async () => {
|
|
787
|
+
const res = await this.client.get("/api/cfo/user-connections", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
|
|
788
|
+
return this.unwrapApiResponse(res, "Failed to get connections");
|
|
789
|
+
},
|
|
790
|
+
/**
|
|
791
|
+
* Get connection status (returns primary connection for backward compatibility)
|
|
792
|
+
*/
|
|
793
|
+
getStatus: async () => {
|
|
794
|
+
const res = await this.client.get("/api/cfo/user-status", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
|
|
795
|
+
return this.unwrapApiResponse(res, "Failed to get status");
|
|
796
|
+
},
|
|
797
|
+
/**
|
|
798
|
+
* Start adding a new QuickBooks connection
|
|
799
|
+
* Returns auth URL to redirect user to QuickBooks OAuth
|
|
800
|
+
*/
|
|
801
|
+
addConnection: async () => {
|
|
802
|
+
const res = await this.client.post("/api/cfo/user-add-connection", {}, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
|
|
803
|
+
return this.unwrapApiResponse(res, "Failed to start connection");
|
|
804
|
+
},
|
|
805
|
+
/**
|
|
806
|
+
* Disconnect a specific QuickBooks account
|
|
807
|
+
*/
|
|
808
|
+
disconnect: async (connectionId) => {
|
|
809
|
+
const res = await this.client.delete(`/api/cfo/user-connections/${connectionId}`, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
|
|
810
|
+
return this.unwrapApiResponse(res, "Failed to disconnect");
|
|
811
|
+
}
|
|
812
|
+
};
|
|
767
813
|
// Stripe Methods
|
|
768
814
|
async createCheckoutSession(priceId, successUrl, cancelUrl) {
|
|
769
815
|
return this.client.post("/api/stripe/create-checkout-session", {
|
|
@@ -887,6 +933,19 @@ var SPAPSClient = class {
|
|
|
887
933
|
setAccessToken(token) {
|
|
888
934
|
this.accessToken = token;
|
|
889
935
|
}
|
|
936
|
+
setRefreshToken(token) {
|
|
937
|
+
this.refreshToken = token;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Restore tokens (for SSO/OAuth flows)
|
|
941
|
+
* Sets both access and refresh tokens
|
|
942
|
+
*/
|
|
943
|
+
restoreTokens(accessToken, refreshToken) {
|
|
944
|
+
this.accessToken = accessToken;
|
|
945
|
+
if (refreshToken) {
|
|
946
|
+
this.refreshToken = refreshToken;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
890
949
|
isLocalMode() {
|
|
891
950
|
return this._isLocalMode;
|
|
892
951
|
}
|
|
@@ -941,6 +1000,17 @@ var TokenManager = class _TokenManager {
|
|
|
941
1000
|
static ACCESS_TOKEN_KEY = "sweet_potato_access_token";
|
|
942
1001
|
static REFRESH_TOKEN_KEY = "sweet_potato_refresh_token";
|
|
943
1002
|
static USER_KEY = "sweet_potato_user";
|
|
1003
|
+
/**
|
|
1004
|
+
* Cross-platform base64 decode (works in both Node.js and browser)
|
|
1005
|
+
*/
|
|
1006
|
+
static base64Decode(str) {
|
|
1007
|
+
if (typeof Buffer !== "undefined") {
|
|
1008
|
+
return Buffer.from(str, "base64").toString("utf8");
|
|
1009
|
+
} else if (typeof atob !== "undefined") {
|
|
1010
|
+
return atob(str);
|
|
1011
|
+
}
|
|
1012
|
+
throw new Error("No base64 decode method available");
|
|
1013
|
+
}
|
|
944
1014
|
static getStorage() {
|
|
945
1015
|
if (typeof globalThis !== "undefined" && globalThis.localStorage) return globalThis.localStorage;
|
|
946
1016
|
if (typeof globalThis !== "undefined" && globalThis.window?.localStorage) return globalThis.window.localStorage;
|
|
@@ -981,7 +1051,7 @@ var TokenManager = class _TokenManager {
|
|
|
981
1051
|
try {
|
|
982
1052
|
const parts = token.split(".");
|
|
983
1053
|
if (parts.length !== 3 || !parts[1]) return true;
|
|
984
|
-
const payload = JSON.parse(
|
|
1054
|
+
const payload = JSON.parse(_TokenManager.base64Decode(parts[1]));
|
|
985
1055
|
const now = Math.floor(Date.now() / 1e3);
|
|
986
1056
|
return payload.exp < now;
|
|
987
1057
|
} catch {
|
|
@@ -1005,6 +1075,74 @@ var TokenManager = class _TokenManager {
|
|
|
1005
1075
|
return false;
|
|
1006
1076
|
}
|
|
1007
1077
|
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Parse auth tokens from URL fragment (for OAuth callbacks)
|
|
1080
|
+
* URL format: https://app.com/callback#access_token=xxx&refresh_token=xxx
|
|
1081
|
+
*/
|
|
1082
|
+
static parseTokensFromUrlFragment(url) {
|
|
1083
|
+
let hash;
|
|
1084
|
+
if (url) {
|
|
1085
|
+
try {
|
|
1086
|
+
hash = new URL(url).hash;
|
|
1087
|
+
} catch {
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
} else if (typeof window !== "undefined") {
|
|
1091
|
+
hash = window.location.hash;
|
|
1092
|
+
} else {
|
|
1093
|
+
return null;
|
|
1094
|
+
}
|
|
1095
|
+
if (!hash || hash.length < 2) return null;
|
|
1096
|
+
const params = new URLSearchParams(hash.substring(1));
|
|
1097
|
+
const access_token = params.get("access_token");
|
|
1098
|
+
const refresh_token = params.get("refresh_token");
|
|
1099
|
+
if (!access_token) return null;
|
|
1100
|
+
return {
|
|
1101
|
+
access_token,
|
|
1102
|
+
refresh_token: refresh_token || void 0,
|
|
1103
|
+
user_id: params.get("user_id") || void 0
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Handle OAuth callback - parse tokens from URL and store them
|
|
1108
|
+
* Returns user info decoded from JWT, or null if no tokens found
|
|
1109
|
+
*/
|
|
1110
|
+
static handleOAuthCallback(sdk) {
|
|
1111
|
+
const tokens = _TokenManager.parseTokensFromUrlFragment();
|
|
1112
|
+
if (!tokens?.access_token) {
|
|
1113
|
+
console.debug("[SPAPS] handleOAuthCallback: No access token found in URL fragment");
|
|
1114
|
+
return null;
|
|
1115
|
+
}
|
|
1116
|
+
if (typeof window !== "undefined") {
|
|
1117
|
+
window.history.replaceState(null, "", window.location.pathname + window.location.search);
|
|
1118
|
+
}
|
|
1119
|
+
try {
|
|
1120
|
+
const parts = tokens.access_token.split(".");
|
|
1121
|
+
if (parts.length !== 3 || !parts[1]) {
|
|
1122
|
+
console.warn("[SPAPS] handleOAuthCallback: Invalid JWT format - expected 3 parts");
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
const payload = JSON.parse(_TokenManager.base64Decode(parts[1]));
|
|
1126
|
+
const user = {
|
|
1127
|
+
id: payload.user_id || payload.sub,
|
|
1128
|
+
email: payload.email,
|
|
1129
|
+
role: payload.role || "user"
|
|
1130
|
+
};
|
|
1131
|
+
_TokenManager.storeTokens({
|
|
1132
|
+
access_token: tokens.access_token,
|
|
1133
|
+
refresh_token: tokens.refresh_token || "",
|
|
1134
|
+
user
|
|
1135
|
+
});
|
|
1136
|
+
sdk.setAccessToken(tokens.access_token);
|
|
1137
|
+
if (tokens.refresh_token) {
|
|
1138
|
+
sdk.setRefreshToken(tokens.refresh_token);
|
|
1139
|
+
}
|
|
1140
|
+
return user;
|
|
1141
|
+
} catch (err) {
|
|
1142
|
+
console.warn("[SPAPS] handleOAuthCallback: Failed to decode JWT", err);
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1008
1146
|
};
|
|
1009
1147
|
var WalletUtils = class _WalletUtils {
|
|
1010
1148
|
static detectChainType(address) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spaps-sdk",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Sweet Potato Authentication & Payment Service SDK - Zero-config client with built-in permission checking and role-based access control",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"email": "buildooor@gmail.com"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"spaps-types": "^1.0.
|
|
47
|
+
"spaps-types": "^1.0.59",
|
|
48
48
|
"axios": "^1.6.0",
|
|
49
49
|
"cross-fetch": "^4.0.0"
|
|
50
50
|
},
|
|
@@ -72,4 +72,4 @@
|
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=14.0.0"
|
|
74
74
|
}
|
|
75
|
-
}
|
|
75
|
+
}
|