@goodz-core/sdk 0.1.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/auth/index.d.ts +143 -0
- package/dist/auth/index.js +3 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/chunk-2ZETOE2X.js +18 -0
- package/dist/chunk-2ZETOE2X.js.map +1 -0
- package/dist/chunk-EUKUN4JF.js +126 -0
- package/dist/chunk-EUKUN4JF.js.map +1 -0
- package/dist/chunk-G7NKU6PT.js +183 -0
- package/dist/chunk-G7NKU6PT.js.map +1 -0
- package/dist/core/index.d.ts +591 -0
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/zcoin-utils.d.ts +58 -0
- package/dist/zcoin-utils.js +3 -0
- package/dist/zcoin-utils.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @goodz-core/sdk/auth — OAuth 2.1 helpers for GoodZ.Core
|
|
3
|
+
*
|
|
4
|
+
* Provides token management utilities for server-to-server integration.
|
|
5
|
+
*
|
|
6
|
+
* This module focuses on:
|
|
7
|
+
* - Token refresh with automatic retry and mutex
|
|
8
|
+
* - Creating a token provider for use with createGoodZClient
|
|
9
|
+
* - OAuth URL builders for authorization code flow
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
interface TokenPair {
|
|
14
|
+
accessToken: string;
|
|
15
|
+
refreshToken: string;
|
|
16
|
+
expiresAt: number;
|
|
17
|
+
}
|
|
18
|
+
interface TokenManagerConfig {
|
|
19
|
+
/**
|
|
20
|
+
* GoodZ.Core base URL.
|
|
21
|
+
* @default "https://goodzcore.manus.space"
|
|
22
|
+
*/
|
|
23
|
+
coreUrl?: string;
|
|
24
|
+
/** OAuth client_id (format: od_xxxxxxxx) */
|
|
25
|
+
clientId: string;
|
|
26
|
+
/** OAuth client_secret (for confidential server-to-server clients) */
|
|
27
|
+
clientSecret?: string;
|
|
28
|
+
/** Initial token pair — typically obtained from the OAuth callback */
|
|
29
|
+
initialTokens: TokenPair;
|
|
30
|
+
/**
|
|
31
|
+
* Called when tokens are refreshed. Persist the new tokens here.
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* onTokenRefresh: async (tokens) => {
|
|
35
|
+
* await db.update(users).set({
|
|
36
|
+
* accessToken: tokens.accessToken,
|
|
37
|
+
* refreshToken: tokens.refreshToken,
|
|
38
|
+
* tokenExpiresAt: tokens.expiresAt,
|
|
39
|
+
* }).where(eq(users.id, userId));
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
onTokenRefresh?: (tokens: TokenPair) => void | Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Buffer time in ms before expiry to trigger refresh.
|
|
46
|
+
* @default 60000 (1 minute)
|
|
47
|
+
*/
|
|
48
|
+
refreshBufferMs?: number;
|
|
49
|
+
}
|
|
50
|
+
interface OAuthUrlConfig {
|
|
51
|
+
/**
|
|
52
|
+
* GoodZ.Core base URL.
|
|
53
|
+
* @default "https://goodzcore.manus.space"
|
|
54
|
+
*/
|
|
55
|
+
coreUrl?: string;
|
|
56
|
+
/** OAuth client_id */
|
|
57
|
+
clientId: string;
|
|
58
|
+
/** Redirect URI registered with the OAuth app */
|
|
59
|
+
redirectUri: string;
|
|
60
|
+
/** Requested scopes (space-separated) */
|
|
61
|
+
scope?: string;
|
|
62
|
+
/** PKCE code verifier (for public clients) */
|
|
63
|
+
codeVerifier?: string;
|
|
64
|
+
/** State parameter for CSRF protection */
|
|
65
|
+
state?: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Manages OAuth token lifecycle — auto-refresh, mutex, and persistence.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* import { TokenManager } from "@goodz-core/sdk/auth";
|
|
73
|
+
* import { createGoodZClient } from "@goodz-core/sdk/core";
|
|
74
|
+
*
|
|
75
|
+
* const tokenManager = new TokenManager({
|
|
76
|
+
* clientId: "od_xxxxxxxx",
|
|
77
|
+
* initialTokens: { accessToken, refreshToken, expiresAt },
|
|
78
|
+
* onTokenRefresh: async (tokens) => {
|
|
79
|
+
* await saveToDatabase(tokens);
|
|
80
|
+
* },
|
|
81
|
+
* });
|
|
82
|
+
*
|
|
83
|
+
* const goodz = createGoodZClient({
|
|
84
|
+
* getAccessToken: () => tokenManager.getValidToken(),
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
declare class TokenManager {
|
|
89
|
+
private tokens;
|
|
90
|
+
private refreshPromise;
|
|
91
|
+
private readonly config;
|
|
92
|
+
constructor(config: TokenManagerConfig);
|
|
93
|
+
/**
|
|
94
|
+
* Get a valid access token, refreshing if necessary.
|
|
95
|
+
* Safe to call concurrently — uses a mutex to prevent duplicate refreshes.
|
|
96
|
+
*/
|
|
97
|
+
getValidToken(): Promise<string>;
|
|
98
|
+
/** Get current tokens without triggering refresh. */
|
|
99
|
+
getCurrentTokens(): TokenPair;
|
|
100
|
+
/** Manually update tokens (e.g., after re-authentication). */
|
|
101
|
+
setTokens(tokens: TokenPair): void;
|
|
102
|
+
/** Check if the current access token is expired or about to expire. */
|
|
103
|
+
isExpired(): boolean;
|
|
104
|
+
private doRefresh;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Build the OAuth authorization URL for the authorization code flow.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* const authUrl = buildAuthorizationUrl({
|
|
112
|
+
* clientId: "od_xxxxxxxx",
|
|
113
|
+
* redirectUri: "https://myapp.com/callback",
|
|
114
|
+
* scope: "read write",
|
|
115
|
+
* state: crypto.randomUUID(),
|
|
116
|
+
* });
|
|
117
|
+
* // Redirect user to authUrl
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
declare function buildAuthorizationUrl(config: OAuthUrlConfig): string;
|
|
121
|
+
/**
|
|
122
|
+
* Exchange an authorization code for tokens.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* const tokens = await exchangeCode({
|
|
127
|
+
* clientId: "od_xxxxxxxx",
|
|
128
|
+
* clientSecret: "secret",
|
|
129
|
+
* code: searchParams.get("code")!,
|
|
130
|
+
* redirectUri: "https://myapp.com/callback",
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
declare function exchangeCode(config: {
|
|
135
|
+
coreUrl?: string;
|
|
136
|
+
clientId: string;
|
|
137
|
+
clientSecret?: string;
|
|
138
|
+
code: string;
|
|
139
|
+
redirectUri: string;
|
|
140
|
+
codeVerifier?: string;
|
|
141
|
+
}): Promise<TokenPair>;
|
|
142
|
+
|
|
143
|
+
export { type OAuthUrlConfig, TokenManager, type TokenManagerConfig, type TokenPair, buildAuthorizationUrl, exchangeCode };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/zcoin-utils.ts
|
|
2
|
+
var ZCOIN_PRECISION = 100;
|
|
3
|
+
function toHundredths(displayAmount) {
|
|
4
|
+
return Math.round(displayAmount * ZCOIN_PRECISION);
|
|
5
|
+
}
|
|
6
|
+
function toDisplay(hundredths) {
|
|
7
|
+
return hundredths / ZCOIN_PRECISION;
|
|
8
|
+
}
|
|
9
|
+
function formatZcoin(hundredths) {
|
|
10
|
+
return (hundredths / ZCOIN_PRECISION).toFixed(2);
|
|
11
|
+
}
|
|
12
|
+
function formatZcoinWithSymbol(hundredths) {
|
|
13
|
+
return `Z ${formatZcoin(hundredths)}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { ZCOIN_PRECISION, formatZcoin, formatZcoinWithSymbol, toDisplay, toHundredths };
|
|
17
|
+
//# sourceMappingURL=chunk-2ZETOE2X.js.map
|
|
18
|
+
//# sourceMappingURL=chunk-2ZETOE2X.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/zcoin-utils.ts"],"names":[],"mappings":";AAaO,IAAM,eAAA,GAAkB;AAYxB,SAAS,aAAa,aAAA,EAA+B;AAC1D,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,aAAA,GAAgB,eAAe,CAAA;AACnD;AAYO,SAAS,UAAU,UAAA,EAA4B;AACpD,EAAA,OAAO,UAAA,GAAa,eAAA;AACtB;AAYO,SAAS,YAAY,UAAA,EAA4B;AACtD,EAAA,OAAA,CAAQ,UAAA,GAAa,eAAA,EAAiB,OAAA,CAAQ,CAAC,CAAA;AACjD;AAWO,SAAS,sBAAsB,UAAA,EAA4B;AAChE,EAAA,OAAO,CAAA,EAAA,EAAK,WAAA,CAAY,UAAU,CAAC,CAAA,CAAA;AACrC","file":"chunk-2ZETOE2X.js","sourcesContent":["/**\n * @goodz-core/sdk — Z-coin Precision Utilities\n *\n * GoodZ.Core stores Z-coin amounts in \"hundredths\" (1/100th of a Z-coin).\n * For example, 10.50 Z-coin is stored as 1050 hundredths.\n *\n * These helpers convert between display values and hundredths to prevent\n * the off-by-100x errors that plagued early Commerce integration.\n *\n * @module\n */\n\n/** Precision multiplier: 1 Z-coin = 100 hundredths */\nexport const ZCOIN_PRECISION = 100;\n\n/**\n * Convert a display Z-coin amount to hundredths for API calls.\n *\n * @example\n * ```ts\n * toHundredths(10.50) // → 1050\n * toHundredths(100) // → 10000\n * toHundredths(0.01) // → 1\n * ```\n */\nexport function toHundredths(displayAmount: number): number {\n return Math.round(displayAmount * ZCOIN_PRECISION);\n}\n\n/**\n * Convert hundredths to a display Z-coin amount.\n *\n * @example\n * ```ts\n * toDisplay(1050) // → 10.5\n * toDisplay(10000) // → 100\n * toDisplay(1) // → 0.01\n * ```\n */\nexport function toDisplay(hundredths: number): number {\n return hundredths / ZCOIN_PRECISION;\n}\n\n/**\n * Format a hundredths value as a display string with 2 decimal places.\n *\n * @example\n * ```ts\n * formatZcoin(1050) // → \"10.50\"\n * formatZcoin(10000) // → \"100.00\"\n * formatZcoin(1) // → \"0.01\"\n * ```\n */\nexport function formatZcoin(hundredths: number): string {\n return (hundredths / ZCOIN_PRECISION).toFixed(2);\n}\n\n/**\n * Format a hundredths value as a display string with the Z-coin symbol.\n *\n * @example\n * ```ts\n * formatZcoinWithSymbol(1050) // → \"Z 10.50\"\n * formatZcoinWithSymbol(10000) // → \"Z 100.00\"\n * ```\n */\nexport function formatZcoinWithSymbol(hundredths: number): string {\n return `Z ${formatZcoin(hundredths)}`;\n}\n"]}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/auth/index.ts
|
|
2
|
+
var TokenManager = class {
|
|
3
|
+
tokens;
|
|
4
|
+
refreshPromise = null;
|
|
5
|
+
config;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = {
|
|
8
|
+
coreUrl: "https://goodzcore.manus.space",
|
|
9
|
+
refreshBufferMs: 6e4,
|
|
10
|
+
...config
|
|
11
|
+
};
|
|
12
|
+
this.tokens = { ...config.initialTokens };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get a valid access token, refreshing if necessary.
|
|
16
|
+
* Safe to call concurrently — uses a mutex to prevent duplicate refreshes.
|
|
17
|
+
*/
|
|
18
|
+
async getValidToken() {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const needsRefresh = now >= this.tokens.expiresAt - this.config.refreshBufferMs;
|
|
21
|
+
if (!needsRefresh) {
|
|
22
|
+
return this.tokens.accessToken;
|
|
23
|
+
}
|
|
24
|
+
if (this.refreshPromise) {
|
|
25
|
+
const refreshed = await this.refreshPromise;
|
|
26
|
+
return refreshed.accessToken;
|
|
27
|
+
}
|
|
28
|
+
this.refreshPromise = this.doRefresh();
|
|
29
|
+
try {
|
|
30
|
+
const refreshed = await this.refreshPromise;
|
|
31
|
+
return refreshed.accessToken;
|
|
32
|
+
} finally {
|
|
33
|
+
this.refreshPromise = null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Get current tokens without triggering refresh. */
|
|
37
|
+
getCurrentTokens() {
|
|
38
|
+
return { ...this.tokens };
|
|
39
|
+
}
|
|
40
|
+
/** Manually update tokens (e.g., after re-authentication). */
|
|
41
|
+
setTokens(tokens) {
|
|
42
|
+
this.tokens = { ...tokens };
|
|
43
|
+
}
|
|
44
|
+
/** Check if the current access token is expired or about to expire. */
|
|
45
|
+
isExpired() {
|
|
46
|
+
return Date.now() >= this.tokens.expiresAt - this.config.refreshBufferMs;
|
|
47
|
+
}
|
|
48
|
+
async doRefresh() {
|
|
49
|
+
const { coreUrl, clientId, clientSecret } = this.config;
|
|
50
|
+
const body = {
|
|
51
|
+
grant_type: "refresh_token",
|
|
52
|
+
refresh_token: this.tokens.refreshToken,
|
|
53
|
+
client_id: clientId
|
|
54
|
+
};
|
|
55
|
+
if (clientSecret) {
|
|
56
|
+
body.client_secret = clientSecret;
|
|
57
|
+
}
|
|
58
|
+
const res = await fetch(`${coreUrl}/api/oauth/token`, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
61
|
+
body: new URLSearchParams(body)
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
const errorText = await res.text().catch(() => res.statusText);
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Token refresh failed (${res.status}): ${errorText}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const data = await res.json();
|
|
70
|
+
const newTokens = {
|
|
71
|
+
accessToken: data.access_token,
|
|
72
|
+
refreshToken: data.refresh_token,
|
|
73
|
+
expiresAt: Date.now() + data.expires_in * 1e3
|
|
74
|
+
};
|
|
75
|
+
this.tokens = newTokens;
|
|
76
|
+
if (this.config.onTokenRefresh) {
|
|
77
|
+
await this.config.onTokenRefresh(newTokens);
|
|
78
|
+
}
|
|
79
|
+
return newTokens;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
function buildAuthorizationUrl(config) {
|
|
83
|
+
const baseUrl = (config.coreUrl ?? "https://goodzcore.manus.space").replace(/\/$/, "");
|
|
84
|
+
const params = new URLSearchParams({
|
|
85
|
+
response_type: "code",
|
|
86
|
+
client_id: config.clientId,
|
|
87
|
+
redirect_uri: config.redirectUri
|
|
88
|
+
});
|
|
89
|
+
if (config.scope) params.set("scope", config.scope);
|
|
90
|
+
if (config.state) params.set("state", config.state);
|
|
91
|
+
if (config.codeVerifier) {
|
|
92
|
+
params.set("code_challenge", config.codeVerifier);
|
|
93
|
+
params.set("code_challenge_method", "plain");
|
|
94
|
+
}
|
|
95
|
+
return `${baseUrl}/api/oauth/authorize?${params.toString()}`;
|
|
96
|
+
}
|
|
97
|
+
async function exchangeCode(config) {
|
|
98
|
+
const baseUrl = (config.coreUrl ?? "https://goodzcore.manus.space").replace(/\/$/, "");
|
|
99
|
+
const body = {
|
|
100
|
+
grant_type: "authorization_code",
|
|
101
|
+
code: config.code,
|
|
102
|
+
redirect_uri: config.redirectUri,
|
|
103
|
+
client_id: config.clientId
|
|
104
|
+
};
|
|
105
|
+
if (config.clientSecret) body.client_secret = config.clientSecret;
|
|
106
|
+
if (config.codeVerifier) body.code_verifier = config.codeVerifier;
|
|
107
|
+
const res = await fetch(`${baseUrl}/api/oauth/token`, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
110
|
+
body: new URLSearchParams(body)
|
|
111
|
+
});
|
|
112
|
+
if (!res.ok) {
|
|
113
|
+
const errorText = await res.text().catch(() => res.statusText);
|
|
114
|
+
throw new Error(`Code exchange failed (${res.status}): ${errorText}`);
|
|
115
|
+
}
|
|
116
|
+
const data = await res.json();
|
|
117
|
+
return {
|
|
118
|
+
accessToken: data.access_token,
|
|
119
|
+
refreshToken: data.refresh_token,
|
|
120
|
+
expiresAt: Date.now() + data.expires_in * 1e3
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { TokenManager, buildAuthorizationUrl, exchangeCode };
|
|
125
|
+
//# sourceMappingURL=chunk-EUKUN4JF.js.map
|
|
126
|
+
//# sourceMappingURL=chunk-EUKUN4JF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth/index.ts"],"names":[],"mappings":";AAyGO,IAAM,eAAN,MAAmB;AAAA,EAChB,MAAA;AAAA,EACA,cAAA,GAA4C,IAAA;AAAA,EACnC,MAAA;AAAA,EAKjB,YAAY,MAAA,EAA4B;AACtC,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,OAAA,EAAS,+BAAA;AAAA,MACT,eAAA,EAAiB,GAAA;AAAA,MACjB,GAAG;AAAA,KACL;AACA,IAAA,IAAA,CAAK,MAAA,GAAS,EAAE,GAAG,MAAA,CAAO,aAAA,EAAc;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aAAA,GAAiC;AACrC,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,MAAM,eACJ,GAAA,IAAO,IAAA,CAAK,MAAA,CAAO,SAAA,GAAY,KAAK,MAAA,CAAO,eAAA;AAE7C,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA,OAAO,KAAK,MAAA,CAAO,WAAA;AAAA,IACrB;AAGA,IAAA,IAAI,KAAK,cAAA,EAAgB;AACvB,MAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,cAAA;AAC7B,MAAA,OAAO,SAAA,CAAU,WAAA;AAAA,IACnB;AAGA,IAAA,IAAA,CAAK,cAAA,GAAiB,KAAK,SAAA,EAAU;AACrC,IAAA,IAAI;AACF,MAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,cAAA;AAC7B,MAAA,OAAO,SAAA,CAAU,WAAA;AAAA,IACnB,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,cAAA,GAAiB,IAAA;AAAA,IACxB;AAAA,EACF;AAAA;AAAA,EAGA,gBAAA,GAA8B;AAC5B,IAAA,OAAO,EAAE,GAAG,IAAA,CAAK,MAAA,EAAO;AAAA,EAC1B;AAAA;AAAA,EAGA,UAAU,MAAA,EAAyB;AACjC,IAAA,IAAA,CAAK,MAAA,GAAS,EAAE,GAAG,MAAA,EAAO;AAAA,EAC5B;AAAA;AAAA,EAGA,SAAA,GAAqB;AACnB,IAAA,OAAO,KAAK,GAAA,EAAI,IAAK,KAAK,MAAA,CAAO,SAAA,GAAY,KAAK,MAAA,CAAO,eAAA;AAAA,EAC3D;AAAA,EAEA,MAAc,SAAA,GAAgC;AAC5C,IAAA,MAAM,EAAE,OAAA,EAAS,QAAA,EAAU,YAAA,KAAiB,IAAA,CAAK,MAAA;AAEjD,IAAA,MAAM,IAAA,GAA+B;AAAA,MACnC,UAAA,EAAY,eAAA;AAAA,MACZ,aAAA,EAAe,KAAK,MAAA,CAAO,YAAA;AAAA,MAC3B,SAAA,EAAW;AAAA,KACb;AACA,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,IAAA,CAAK,aAAA,GAAgB,YAAA;AAAA,IACvB;AAEA,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,gBAAA,CAAA,EAAoB;AAAA,MACpD,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,mCAAA,EAAoC;AAAA,MAC/D,IAAA,EAAM,IAAI,eAAA,CAAgB,IAAI;AAAA,KAC/B,CAAA;AAED,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,MAAM,SAAA,GAAY,MAAM,GAAA,CAAI,IAAA,GAAO,KAAA,CAAM,MAAM,IAAI,UAAU,CAAA;AAC7D,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,sBAAA,EAAyB,GAAA,CAAI,MAAM,CAAA,GAAA,EAAM,SAAS,CAAA;AAAA,OACpD;AAAA,IACF;AAEA,IAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAM7B,IAAA,MAAM,SAAA,GAAuB;AAAA,MAC3B,aAAa,IAAA,CAAK,YAAA;AAAA,MAClB,cAAc,IAAA,CAAK,aAAA;AAAA,MACnB,SAAA,EAAW,IAAA,CAAK,GAAA,EAAI,GAAI,KAAK,UAAA,GAAa;AAAA,KAC5C;AAEA,IAAA,IAAA,CAAK,MAAA,GAAS,SAAA;AAGd,IAAA,IAAI,IAAA,CAAK,OAAO,cAAA,EAAgB;AAC9B,MAAA,MAAM,IAAA,CAAK,MAAA,CAAO,cAAA,CAAe,SAAS,CAAA;AAAA,IAC5C;AAEA,IAAA,OAAO,SAAA;AAAA,EACT;AACF;AAkBO,SAAS,sBAAsB,MAAA,EAAgC;AACpE,EAAA,MAAM,WAAW,MAAA,CAAO,OAAA,IAAW,+BAAA,EAAiC,OAAA,CAAQ,OAAO,EAAE,CAAA;AAErF,EAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB;AAAA,IACjC,aAAA,EAAe,MAAA;AAAA,IACf,WAAW,MAAA,CAAO,QAAA;AAAA,IAClB,cAAc,MAAA,CAAO;AAAA,GACtB,CAAA;AAED,EAAA,IAAI,OAAO,KAAA,EAAO,MAAA,CAAO,GAAA,CAAI,OAAA,EAAS,OAAO,KAAK,CAAA;AAClD,EAAA,IAAI,OAAO,KAAA,EAAO,MAAA,CAAO,GAAA,CAAI,OAAA,EAAS,OAAO,KAAK,CAAA;AAElD,EAAA,IAAI,OAAO,YAAA,EAAc;AAGvB,IAAA,MAAA,CAAO,GAAA,CAAI,gBAAA,EAAkB,MAAA,CAAO,YAAY,CAAA;AAChD,IAAA,MAAA,CAAO,GAAA,CAAI,yBAAyB,OAAO,CAAA;AAAA,EAC7C;AAEA,EAAA,OAAO,CAAA,EAAG,OAAO,CAAA,qBAAA,EAAwB,MAAA,CAAO,UAAU,CAAA,CAAA;AAC5D;AAeA,eAAsB,aAAa,MAAA,EAOZ;AACrB,EAAA,MAAM,WAAW,MAAA,CAAO,OAAA,IAAW,+BAAA,EAAiC,OAAA,CAAQ,OAAO,EAAE,CAAA;AAErF,EAAA,MAAM,IAAA,GAA+B;AAAA,IACnC,UAAA,EAAY,oBAAA;AAAA,IACZ,MAAM,MAAA,CAAO,IAAA;AAAA,IACb,cAAc,MAAA,CAAO,WAAA;AAAA,IACrB,WAAW,MAAA,CAAO;AAAA,GACpB;AAEA,EAAA,IAAI,MAAA,CAAO,YAAA,EAAc,IAAA,CAAK,aAAA,GAAgB,MAAA,CAAO,YAAA;AACrD,EAAA,IAAI,MAAA,CAAO,YAAA,EAAc,IAAA,CAAK,aAAA,GAAgB,MAAA,CAAO,YAAA;AAErD,EAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,gBAAA,CAAA,EAAoB;AAAA,IACpD,MAAA,EAAQ,MAAA;AAAA,IACR,OAAA,EAAS,EAAE,cAAA,EAAgB,mCAAA,EAAoC;AAAA,IAC/D,IAAA,EAAM,IAAI,eAAA,CAAgB,IAAI;AAAA,GAC/B,CAAA;AAED,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,IAAA,MAAM,SAAA,GAAY,MAAM,GAAA,CAAI,IAAA,GAAO,KAAA,CAAM,MAAM,IAAI,UAAU,CAAA;AAC7D,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,sBAAA,EAAyB,IAAI,MAAM,CAAA,GAAA,EAAM,SAAS,CAAA,CAAE,CAAA;AAAA,EACtE;AAEA,EAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAM7B,EAAA,OAAO;AAAA,IACL,aAAa,IAAA,CAAK,YAAA;AAAA,IAClB,cAAc,IAAA,CAAK,aAAA;AAAA,IACnB,SAAA,EAAW,IAAA,CAAK,GAAA,EAAI,GAAI,KAAK,UAAA,GAAa;AAAA,GAC5C;AACF","file":"chunk-EUKUN4JF.js","sourcesContent":["/**\n * @goodz-core/sdk/auth — OAuth 2.1 helpers for GoodZ.Core\n *\n * Provides token management utilities for server-to-server integration.\n *\n * This module focuses on:\n * - Token refresh with automatic retry and mutex\n * - Creating a token provider for use with createGoodZClient\n * - OAuth URL builders for authorization code flow\n *\n * @module\n */\n\n// ─── Types ───────────────────────────────────────────────────\n\nexport interface TokenPair {\n accessToken: string;\n refreshToken: string;\n expiresAt: number; // Unix timestamp in ms\n}\n\nexport interface TokenManagerConfig {\n /**\n * GoodZ.Core base URL.\n * @default \"https://goodzcore.manus.space\"\n */\n coreUrl?: string;\n\n /** OAuth client_id (format: od_xxxxxxxx) */\n clientId: string;\n\n /** OAuth client_secret (for confidential server-to-server clients) */\n clientSecret?: string;\n\n /** Initial token pair — typically obtained from the OAuth callback */\n initialTokens: TokenPair;\n\n /**\n * Called when tokens are refreshed. Persist the new tokens here.\n * @example\n * ```ts\n * onTokenRefresh: async (tokens) => {\n * await db.update(users).set({\n * accessToken: tokens.accessToken,\n * refreshToken: tokens.refreshToken,\n * tokenExpiresAt: tokens.expiresAt,\n * }).where(eq(users.id, userId));\n * }\n * ```\n */\n onTokenRefresh?: (tokens: TokenPair) => void | Promise<void>;\n\n /**\n * Buffer time in ms before expiry to trigger refresh.\n * @default 60000 (1 minute)\n */\n refreshBufferMs?: number;\n}\n\nexport interface OAuthUrlConfig {\n /**\n * GoodZ.Core base URL.\n * @default \"https://goodzcore.manus.space\"\n */\n coreUrl?: string;\n\n /** OAuth client_id */\n clientId: string;\n\n /** Redirect URI registered with the OAuth app */\n redirectUri: string;\n\n /** Requested scopes (space-separated) */\n scope?: string;\n\n /** PKCE code verifier (for public clients) */\n codeVerifier?: string;\n\n /** State parameter for CSRF protection */\n state?: string;\n}\n\n// ─── Token Manager ───────────────────────────────────────────\n\n/**\n * Manages OAuth token lifecycle — auto-refresh, mutex, and persistence.\n *\n * @example\n * ```ts\n * import { TokenManager } from \"@goodz-core/sdk/auth\";\n * import { createGoodZClient } from \"@goodz-core/sdk/core\";\n *\n * const tokenManager = new TokenManager({\n * clientId: \"od_xxxxxxxx\",\n * initialTokens: { accessToken, refreshToken, expiresAt },\n * onTokenRefresh: async (tokens) => {\n * await saveToDatabase(tokens);\n * },\n * });\n *\n * const goodz = createGoodZClient({\n * getAccessToken: () => tokenManager.getValidToken(),\n * });\n * ```\n */\nexport class TokenManager {\n private tokens: TokenPair;\n private refreshPromise: Promise<TokenPair> | null = null;\n private readonly config: Required<\n Pick<TokenManagerConfig, \"coreUrl\" | \"clientId\" | \"refreshBufferMs\">\n > &\n TokenManagerConfig;\n\n constructor(config: TokenManagerConfig) {\n this.config = {\n coreUrl: \"https://goodzcore.manus.space\",\n refreshBufferMs: 60_000,\n ...config,\n };\n this.tokens = { ...config.initialTokens };\n }\n\n /**\n * Get a valid access token, refreshing if necessary.\n * Safe to call concurrently — uses a mutex to prevent duplicate refreshes.\n */\n async getValidToken(): Promise<string> {\n const now = Date.now();\n const needsRefresh =\n now >= this.tokens.expiresAt - this.config.refreshBufferMs;\n\n if (!needsRefresh) {\n return this.tokens.accessToken;\n }\n\n // Mutex: if a refresh is already in progress, wait for it\n if (this.refreshPromise) {\n const refreshed = await this.refreshPromise;\n return refreshed.accessToken;\n }\n\n // Start refresh\n this.refreshPromise = this.doRefresh();\n try {\n const refreshed = await this.refreshPromise;\n return refreshed.accessToken;\n } finally {\n this.refreshPromise = null;\n }\n }\n\n /** Get current tokens without triggering refresh. */\n getCurrentTokens(): TokenPair {\n return { ...this.tokens };\n }\n\n /** Manually update tokens (e.g., after re-authentication). */\n setTokens(tokens: TokenPair): void {\n this.tokens = { ...tokens };\n }\n\n /** Check if the current access token is expired or about to expire. */\n isExpired(): boolean {\n return Date.now() >= this.tokens.expiresAt - this.config.refreshBufferMs;\n }\n\n private async doRefresh(): Promise<TokenPair> {\n const { coreUrl, clientId, clientSecret } = this.config;\n\n const body: Record<string, string> = {\n grant_type: \"refresh_token\",\n refresh_token: this.tokens.refreshToken,\n client_id: clientId,\n };\n if (clientSecret) {\n body.client_secret = clientSecret;\n }\n\n const res = await fetch(`${coreUrl}/api/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams(body),\n });\n\n if (!res.ok) {\n const errorText = await res.text().catch(() => res.statusText);\n throw new Error(\n `Token refresh failed (${res.status}): ${errorText}`\n );\n }\n\n const data = (await res.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n\n const newTokens: TokenPair = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: Date.now() + data.expires_in * 1000,\n };\n\n this.tokens = newTokens;\n\n // Notify consumer to persist\n if (this.config.onTokenRefresh) {\n await this.config.onTokenRefresh(newTokens);\n }\n\n return newTokens;\n }\n}\n\n// ─── OAuth URL Builders ──────────────────────────────────────\n\n/**\n * Build the OAuth authorization URL for the authorization code flow.\n *\n * @example\n * ```ts\n * const authUrl = buildAuthorizationUrl({\n * clientId: \"od_xxxxxxxx\",\n * redirectUri: \"https://myapp.com/callback\",\n * scope: \"read write\",\n * state: crypto.randomUUID(),\n * });\n * // Redirect user to authUrl\n * ```\n */\nexport function buildAuthorizationUrl(config: OAuthUrlConfig): string {\n const baseUrl = (config.coreUrl ?? \"https://goodzcore.manus.space\").replace(/\\/$/, \"\");\n\n const params = new URLSearchParams({\n response_type: \"code\",\n client_id: config.clientId,\n redirect_uri: config.redirectUri,\n });\n\n if (config.scope) params.set(\"scope\", config.scope);\n if (config.state) params.set(\"state\", config.state);\n\n if (config.codeVerifier) {\n // PKCE: generate code_challenge from code_verifier\n // For S256, we'd need crypto.subtle — for now, use plain\n params.set(\"code_challenge\", config.codeVerifier);\n params.set(\"code_challenge_method\", \"plain\");\n }\n\n return `${baseUrl}/api/oauth/authorize?${params.toString()}`;\n}\n\n/**\n * Exchange an authorization code for tokens.\n *\n * @example\n * ```ts\n * const tokens = await exchangeCode({\n * clientId: \"od_xxxxxxxx\",\n * clientSecret: \"secret\",\n * code: searchParams.get(\"code\")!,\n * redirectUri: \"https://myapp.com/callback\",\n * });\n * ```\n */\nexport async function exchangeCode(config: {\n coreUrl?: string;\n clientId: string;\n clientSecret?: string;\n code: string;\n redirectUri: string;\n codeVerifier?: string;\n}): Promise<TokenPair> {\n const baseUrl = (config.coreUrl ?? \"https://goodzcore.manus.space\").replace(/\\/$/, \"\");\n\n const body: Record<string, string> = {\n grant_type: \"authorization_code\",\n code: config.code,\n redirect_uri: config.redirectUri,\n client_id: config.clientId,\n };\n\n if (config.clientSecret) body.client_secret = config.clientSecret;\n if (config.codeVerifier) body.code_verifier = config.codeVerifier;\n\n const res = await fetch(`${baseUrl}/api/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams(body),\n });\n\n if (!res.ok) {\n const errorText = await res.text().catch(() => res.statusText);\n throw new Error(`Code exchange failed (${res.status}): ${errorText}`);\n }\n\n const data = (await res.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: Date.now() + data.expires_in * 1000,\n };\n}\n"]}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import superjson from 'superjson';
|
|
2
|
+
|
|
3
|
+
// src/transport.ts
|
|
4
|
+
var GoodZApiError = class extends Error {
|
|
5
|
+
code;
|
|
6
|
+
httpStatus;
|
|
7
|
+
path;
|
|
8
|
+
zodErrors;
|
|
9
|
+
data;
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
super(opts.message);
|
|
12
|
+
this.name = "GoodZApiError";
|
|
13
|
+
this.code = opts.code;
|
|
14
|
+
this.httpStatus = opts.httpStatus;
|
|
15
|
+
this.path = opts.path;
|
|
16
|
+
this.zodErrors = opts.zodErrors;
|
|
17
|
+
this.data = opts.data;
|
|
18
|
+
}
|
|
19
|
+
/** Human-readable summary including field errors if present. */
|
|
20
|
+
toDetailedString() {
|
|
21
|
+
const parts = [
|
|
22
|
+
`[GoodZApiError] ${this.code} on ${this.path}: ${this.message}`
|
|
23
|
+
];
|
|
24
|
+
if (this.zodErrors?.length) {
|
|
25
|
+
parts.push("Field errors:");
|
|
26
|
+
for (const e of this.zodErrors) {
|
|
27
|
+
parts.push(` - ${e.path.join(".")}: ${e.message} (${e.code})`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return parts.join("\n");
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
async function callQuery(config, path, input) {
|
|
34
|
+
const url = `${config.baseUrl}/api/trpc/${path}`;
|
|
35
|
+
const headers = await config.getHeaders();
|
|
36
|
+
const serialized = input !== void 0 ? superjson.serialize(input) : void 0;
|
|
37
|
+
const queryUrl = serialized ? `${url}?input=${encodeURIComponent(JSON.stringify(serialized))}` : url;
|
|
38
|
+
const res = await fetch(queryUrl, {
|
|
39
|
+
method: "GET",
|
|
40
|
+
headers: {
|
|
41
|
+
...headers,
|
|
42
|
+
"Content-Type": "application/json"
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return parseResponse(res, path);
|
|
46
|
+
}
|
|
47
|
+
async function callMutation(config, path, input) {
|
|
48
|
+
const url = `${config.baseUrl}/api/trpc/${path}`;
|
|
49
|
+
const headers = await config.getHeaders();
|
|
50
|
+
const serialized = input !== void 0 ? superjson.serialize(input) : void 0;
|
|
51
|
+
const res = await fetch(url, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers: {
|
|
54
|
+
...headers,
|
|
55
|
+
"Content-Type": "application/json"
|
|
56
|
+
},
|
|
57
|
+
body: serialized ? JSON.stringify(serialized) : void 0
|
|
58
|
+
});
|
|
59
|
+
return parseResponse(res, path);
|
|
60
|
+
}
|
|
61
|
+
async function parseResponse(res, path) {
|
|
62
|
+
const text = await res.text();
|
|
63
|
+
let body;
|
|
64
|
+
try {
|
|
65
|
+
body = JSON.parse(text);
|
|
66
|
+
} catch {
|
|
67
|
+
throw new GoodZApiError({
|
|
68
|
+
message: `Invalid JSON response: ${text.slice(0, 200)}`,
|
|
69
|
+
code: "PARSE_ERROR",
|
|
70
|
+
httpStatus: res.status,
|
|
71
|
+
path
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const envelope = Array.isArray(body) ? body[0] : body;
|
|
75
|
+
if (envelope?.error) {
|
|
76
|
+
const err = envelope.error;
|
|
77
|
+
const errJson = err?.json ?? err;
|
|
78
|
+
const errData = errJson?.data ?? {};
|
|
79
|
+
throw new GoodZApiError({
|
|
80
|
+
message: errJson?.message ?? "Unknown API error",
|
|
81
|
+
code: errData?.code ?? errJson?.code ?? "UNKNOWN",
|
|
82
|
+
httpStatus: errData?.httpStatus ?? res.status,
|
|
83
|
+
path: errData?.path ?? path,
|
|
84
|
+
zodErrors: errData?.zodError?.issues,
|
|
85
|
+
data: errData
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const resultData = envelope?.result?.data;
|
|
89
|
+
if (resultData === void 0) {
|
|
90
|
+
if (res.ok) return void 0;
|
|
91
|
+
throw new GoodZApiError({
|
|
92
|
+
message: `Unexpected response shape from ${path}`,
|
|
93
|
+
code: "UNEXPECTED_RESPONSE",
|
|
94
|
+
httpStatus: res.status,
|
|
95
|
+
path
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (resultData && typeof resultData === "object" && "json" in resultData) {
|
|
99
|
+
return superjson.deserialize(resultData);
|
|
100
|
+
}
|
|
101
|
+
return resultData;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/core/index.ts
|
|
105
|
+
function createGoodZClient(config = {}) {
|
|
106
|
+
const {
|
|
107
|
+
coreUrl = "https://goodzcore.manus.space",
|
|
108
|
+
accessToken,
|
|
109
|
+
getAccessToken,
|
|
110
|
+
headers: customHeaders
|
|
111
|
+
} = config;
|
|
112
|
+
const transport = {
|
|
113
|
+
baseUrl: coreUrl.replace(/\/$/, ""),
|
|
114
|
+
getHeaders: async () => {
|
|
115
|
+
const h = { ...customHeaders };
|
|
116
|
+
const token = getAccessToken ? await getAccessToken() : accessToken;
|
|
117
|
+
if (token) {
|
|
118
|
+
h["Authorization"] = `Bearer ${token}`;
|
|
119
|
+
}
|
|
120
|
+
return h;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const q = (path) => (input) => callQuery(transport, path, input);
|
|
124
|
+
const m = (path) => (input) => callMutation(transport, path, input);
|
|
125
|
+
return {
|
|
126
|
+
// ── zcoin ──────────────────────────────────────────────
|
|
127
|
+
zcoin: {
|
|
128
|
+
getMyBalance: q("zcoin.getMyBalance"),
|
|
129
|
+
getMyHistory: q("zcoin.getMyHistory"),
|
|
130
|
+
getDepositPackages: q("zcoin.getDepositPackages"),
|
|
131
|
+
createDepositOrder: m("zcoin.createDepositOrder"),
|
|
132
|
+
getDepositStatus: q("zcoin.getDepositStatus"),
|
|
133
|
+
commercialTransfer: m("zcoin.commercialTransfer"),
|
|
134
|
+
mintAndCharge: m("zcoin.mintAndCharge"),
|
|
135
|
+
chargeUser: m("zcoin.chargeUser"),
|
|
136
|
+
createDirectPurchaseOrder: m("zcoin.createDirectPurchaseOrder")
|
|
137
|
+
},
|
|
138
|
+
// ── inventory ──────────────────────────────────────────
|
|
139
|
+
inventory: {
|
|
140
|
+
getUserInventory: q("inventory.getUserInventory"),
|
|
141
|
+
confirmOwnership: q("inventory.confirmOwnership"),
|
|
142
|
+
mint: m("inventory.mint"),
|
|
143
|
+
transfer: m("inventory.transfer"),
|
|
144
|
+
transferByCard: m("inventory.transferByCard"),
|
|
145
|
+
grantMintAuth: m("inventory.grantMintAuth"),
|
|
146
|
+
transferHistory: q("inventory.transferHistory")
|
|
147
|
+
},
|
|
148
|
+
// ── collectible ────────────────────────────────────────
|
|
149
|
+
collectible: {
|
|
150
|
+
getInstanceById: q("collectible.getInstanceById"),
|
|
151
|
+
getPublicInstance: q("collectible.getPublicInstance"),
|
|
152
|
+
getPublicInstancesBatch: q("collectible.getPublicInstancesBatch"),
|
|
153
|
+
getCardProfile: q("collectible.getCardProfile"),
|
|
154
|
+
getShellImageUrl: q("collectible.getShellImageUrl")
|
|
155
|
+
},
|
|
156
|
+
// ── user ───────────────────────────────────────────────
|
|
157
|
+
user: {
|
|
158
|
+
getPublicProfile: q("user.getPublicProfile"),
|
|
159
|
+
getPublicProfileById: q("user.getPublicProfileById")
|
|
160
|
+
},
|
|
161
|
+
// ── auth ───────────────────────────────────────────────
|
|
162
|
+
auth: {
|
|
163
|
+
me: q("auth.me"),
|
|
164
|
+
getOAuthAppInfo: q("auth.getOAuthAppInfo")
|
|
165
|
+
},
|
|
166
|
+
// ── ip (franchise/series/card) ─────────────────────────
|
|
167
|
+
ip: {
|
|
168
|
+
getFranchise: q("franchise.get"),
|
|
169
|
+
getSeries: q("series.get"),
|
|
170
|
+
listSeriesByFranchise: q("series.listByFranchise"),
|
|
171
|
+
getCard: q("card.get"),
|
|
172
|
+
listCardsBySeries: q("card.listBySeries")
|
|
173
|
+
},
|
|
174
|
+
// ── raw escape hatches ─────────────────────────────────
|
|
175
|
+
rawQuery: (path, input) => callQuery(transport, path, input),
|
|
176
|
+
rawMutation: (path, input) => callMutation(transport, path, input)
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
var createUserClient = createGoodZClient;
|
|
180
|
+
|
|
181
|
+
export { GoodZApiError, createGoodZClient, createUserClient };
|
|
182
|
+
//# sourceMappingURL=chunk-G7NKU6PT.js.map
|
|
183
|
+
//# sourceMappingURL=chunk-G7NKU6PT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transport.ts","../src/core/index.ts"],"names":[],"mappings":"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","file":"chunk-G7NKU6PT.js","sourcesContent":["/**\n * @goodz-core/sdk — HTTP Transport Layer\n *\n * Speaks the tRPC v11 HTTP wire protocol directly using fetch.\n * Handles superjson serialization, batching, and error parsing.\n *\n * Wire format reference:\n * - Queries: GET /api/trpc/{procedure}?input={superjson-encoded}\n * - Mutations: POST /api/trpc/{procedure} body: {superjson-encoded}\n * - Batch: GET /api/trpc/{p1},{p2}?input={0: ..., 1: ...}\n *\n * @internal\n */\n\nimport superjson from \"superjson\";\nimport type { GoodZApiFieldError } from \"./types\";\n\n// ─── Error class ─────────────────────────────────────────────\n\nexport class GoodZApiError extends Error {\n public readonly code: string;\n public readonly httpStatus: number;\n public readonly path: string;\n public readonly zodErrors?: GoodZApiFieldError[];\n public readonly data?: unknown;\n\n constructor(opts: {\n message: string;\n code: string;\n httpStatus: number;\n path: string;\n zodErrors?: GoodZApiFieldError[];\n data?: unknown;\n }) {\n super(opts.message);\n this.name = \"GoodZApiError\";\n this.code = opts.code;\n this.httpStatus = opts.httpStatus;\n this.path = opts.path;\n this.zodErrors = opts.zodErrors;\n this.data = opts.data;\n }\n\n /** Human-readable summary including field errors if present. */\n toDetailedString(): string {\n const parts = [\n `[GoodZApiError] ${this.code} on ${this.path}: ${this.message}`,\n ];\n if (this.zodErrors?.length) {\n parts.push(\"Field errors:\");\n for (const e of this.zodErrors) {\n parts.push(` - ${e.path.join(\".\")}: ${e.message} (${e.code})`);\n }\n }\n return parts.join(\"\\n\");\n }\n}\n\n// ─── Transport config ────────────────────────────────────────\n\nexport interface TransportConfig {\n baseUrl: string;\n getHeaders: () => Record<string, string> | Promise<Record<string, string>>;\n}\n\n// ─── Core transport functions ────────────────────────────────\n\n/**\n * Call a tRPC query (GET request).\n * Uses POST with method override for compatibility with Commerce/Exchange\n * server-to-server patterns (some proxies strip GET bodies).\n */\nexport async function callQuery<TInput, TOutput>(\n config: TransportConfig,\n path: string,\n input?: TInput,\n): Promise<TOutput> {\n const url = `${config.baseUrl}/api/trpc/${path}`;\n const headers = await config.getHeaders();\n\n // Use GET with query params for queries (standard tRPC)\n const serialized = input !== undefined ? superjson.serialize(input) : undefined;\n const queryUrl = serialized\n ? `${url}?input=${encodeURIComponent(JSON.stringify(serialized))}`\n : url;\n\n const res = await fetch(queryUrl, {\n method: \"GET\",\n headers: {\n ...headers,\n \"Content-Type\": \"application/json\",\n },\n });\n\n return parseResponse<TOutput>(res, path);\n}\n\n/**\n * Call a tRPC mutation (POST request).\n */\nexport async function callMutation<TInput, TOutput>(\n config: TransportConfig,\n path: string,\n input?: TInput,\n): Promise<TOutput> {\n const url = `${config.baseUrl}/api/trpc/${path}`;\n const headers = await config.getHeaders();\n\n const serialized = input !== undefined ? superjson.serialize(input) : undefined;\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n ...headers,\n \"Content-Type\": \"application/json\",\n },\n body: serialized ? JSON.stringify(serialized) : undefined,\n });\n\n return parseResponse<TOutput>(res, path);\n}\n\n// ─── Response parser ─────────────────────────────────────────\n\nasync function parseResponse<T>(res: Response, path: string): Promise<T> {\n const text = await res.text();\n let body: any;\n\n try {\n body = JSON.parse(text);\n } catch {\n throw new GoodZApiError({\n message: `Invalid JSON response: ${text.slice(0, 200)}`,\n code: \"PARSE_ERROR\",\n httpStatus: res.status,\n path,\n });\n }\n\n // tRPC wraps responses in { result: { data: ... } }\n // Batch responses are arrays: [{ result: { data: ... } }]\n const envelope = Array.isArray(body) ? body[0] : body;\n\n // Check for tRPC error envelope\n if (envelope?.error) {\n const err = envelope.error;\n const errJson = err?.json ?? err;\n const errData = errJson?.data ?? {};\n\n throw new GoodZApiError({\n message: errJson?.message ?? \"Unknown API error\",\n code: errData?.code ?? errJson?.code ?? \"UNKNOWN\",\n httpStatus: errData?.httpStatus ?? res.status,\n path: errData?.path ?? path,\n zodErrors: errData?.zodError?.issues,\n data: errData,\n });\n }\n\n // Success path: extract and deserialize the data\n const resultData = envelope?.result?.data;\n if (resultData === undefined) {\n // Some queries return null/undefined legitimately\n if (res.ok) return undefined as T;\n\n throw new GoodZApiError({\n message: `Unexpected response shape from ${path}`,\n code: \"UNEXPECTED_RESPONSE\",\n httpStatus: res.status,\n path,\n });\n }\n\n // superjson wraps data in { json: ..., meta?: ... }\n // Check if it's a superjson envelope\n if (resultData && typeof resultData === \"object\" && \"json\" in resultData) {\n return superjson.deserialize(resultData) as T;\n }\n\n // Plain JSON (shouldn't happen with superjson transformer, but be safe)\n return resultData as T;\n}\n","/**\n * @goodz-core/sdk/core — Type-safe API client for GoodZ.Core\n *\n * Zero tRPC dependency — speaks the tRPC HTTP wire protocol directly\n * using fetch + superjson. All types are hand-crafted and self-contained.\n *\n * @example\n * ```ts\n * import { createGoodZClient } from \"@goodz-core/sdk/core\";\n *\n * const goodz = createGoodZClient({\n * coreUrl: \"https://goodzcore.manus.space\",\n * accessToken: \"your-jwt-token\",\n * });\n *\n * // Fully typed — IDE autocomplete for all inputs and outputs\n * const balance = await goodz.zcoin.getMyBalance();\n * const result = await goodz.zcoin.commercialTransfer({ ... });\n * const instance = await goodz.collectible.getInstanceById({ instanceId: 90447 });\n * ```\n *\n * @module\n */\n\nimport { callQuery, callMutation, GoodZApiError } from \"../transport\";\nimport type { TransportConfig } from \"../transport\";\nimport type {\n // zcoin\n ZcoinGetMyBalanceOutput,\n ZcoinGetMyHistoryInput,\n ZcoinCommercialTransferInput,\n ZcoinCommercialTransferOutput,\n ZcoinMintAndChargeInput,\n ZcoinMintAndChargeOutput,\n ZcoinChargeUserInput,\n ZcoinChargeUserOutput,\n ZcoinCreateDirectPurchaseOrderInput,\n ZcoinCreateDirectPurchaseOrderOutput,\n ZcoinGetDepositPackagesInput,\n ZcoinDepositPackage,\n ZcoinCreateDepositOrderInput,\n ZcoinCreateDepositOrderOutput,\n ZcoinGetDepositStatusInput,\n ZcoinGetDepositStatusOutput,\n // inventory\n InventoryGetUserInventoryInput,\n InventoryConfirmOwnershipInput,\n InventoryConfirmOwnershipOutput,\n InventoryMintInput,\n InventoryMintOutput,\n InventoryTransferInput,\n InventoryTransferOutput,\n InventoryTransferByCardInput,\n InventoryTransferByCardOutput,\n InventoryGrantMintAuthInput,\n InventoryTransferHistoryInput,\n // collectible\n CollectibleGetInstanceByIdInput,\n CollectibleGetPublicInstanceInput,\n CollectibleGetPublicInstancesBatchInput,\n CollectibleGetCardProfileInput,\n CollectibleGetShellImageUrlInput,\n // user\n UserGetPublicProfileInput,\n UserPublicProfile,\n UserGetPublicProfileByIdInput,\n // auth\n AuthGetOAuthAppInfoInput,\n AuthOAuthAppInfo,\n AuthUser,\n // ip\n FranchiseGetInput,\n SeriesGetInput,\n SeriesListByFranchiseInput,\n CardGetInput,\n CardListBySeriesInput,\n} from \"../types\";\n\n// ─── Re-export types and error class ─────────────────────────\n\nexport { GoodZApiError } from \"../transport\";\nexport type * from \"../types\";\n\n// ─── Client config ───────────────────────────────────────────\n\nexport interface GoodZClientConfig {\n /**\n * GoodZ.Core base URL.\n * @default \"https://goodzcore.manus.space\"\n */\n coreUrl?: string;\n\n /**\n * Static access token (JWT) for authentication.\n * For server-to-server calls, obtain this via OAuth client_credentials flow.\n * For user-context calls, pass the user's access token.\n */\n accessToken?: string;\n\n /**\n * Dynamic token provider — called before every request.\n * Use this when tokens may rotate (e.g., auto-refresh).\n * Takes precedence over `accessToken` if both are set.\n */\n getAccessToken?: () => string | Promise<string>;\n\n /**\n * Custom headers to include in every request.\n * Useful for passing app identifiers or tracing headers.\n */\n headers?: Record<string, string>;\n}\n\n// ─── Namespace interfaces (for type documentation) ───────────\n\nexport interface ZcoinNamespace {\n /** Get the authenticated user's Z-coin balance. */\n getMyBalance(): Promise<ZcoinGetMyBalanceOutput>;\n\n /** Get the authenticated user's Z-coin transaction history. */\n getMyHistory(input?: ZcoinGetMyHistoryInput): Promise<any[]>;\n\n /** Get available Z-coin deposit packages with pricing. */\n getDepositPackages(input?: ZcoinGetDepositPackagesInput): Promise<ZcoinDepositPackage[]>;\n\n /** Create a Stripe checkout session for Z-coin deposit. */\n createDepositOrder(input: ZcoinCreateDepositOrderInput): Promise<ZcoinCreateDepositOrderOutput>;\n\n /** Check the status of a deposit checkout session. */\n getDepositStatus(input: ZcoinGetDepositStatusInput): Promise<ZcoinGetDepositStatusOutput>;\n\n /**\n * Atomic commercial transfer: Z-coin payment + ownership transfer in one transaction.\n * This is the primary API for Commerce and Exchange purchase flows.\n *\n * Idempotent via referenceId — duplicate calls return the same result.\n *\n * @throws {GoodZApiError} BAD_REQUEST — insufficient balance\n * @throws {GoodZApiError} CONFLICT — version conflict (retry)\n * @throws {GoodZApiError} FORBIDDEN — seller doesn't own instance\n */\n commercialTransfer(input: ZcoinCommercialTransferInput): Promise<ZcoinCommercialTransferOutput>;\n\n /**\n * Mint a new card instance and charge the buyer in one atomic transaction.\n * Used by Commerce for gacha and direct-from-creator purchases.\n *\n * Requires mint authorization (granted via inventory.grantMintAuth).\n * Idempotent via referenceId.\n *\n * @throws {GoodZApiError} FORBIDDEN — no mint authorization\n * @throws {GoodZApiError} BAD_REQUEST — insufficient balance\n */\n mintAndCharge(input: ZcoinMintAndChargeInput): Promise<ZcoinMintAndChargeOutput>;\n\n /**\n * Charge a user's Z-coin balance for an in-app purchase.\n * Used by apps that sell non-GoodZ digital goods/services.\n *\n * Idempotent via appOrderId.\n *\n * @throws {GoodZApiError} BAD_REQUEST — insufficient balance\n * @throws {GoodZApiError} CONFLICT — version conflict\n */\n chargeUser(input: ZcoinChargeUserInput): Promise<ZcoinChargeUserOutput>;\n\n /**\n * Create a direct purchase checkout session (fiat → Z-coin → transfer).\n * Transparent intermediation: user sees fiat price, Core handles conversion.\n */\n createDirectPurchaseOrder(input: ZcoinCreateDirectPurchaseOrderInput): Promise<ZcoinCreateDirectPurchaseOrderOutput>;\n}\n\nexport interface InventoryNamespace {\n /** Get a user's inventory (owned card instances). */\n getUserInventory(input: InventoryGetUserInventoryInput): Promise<any[]>;\n\n /** Check if a user owns at least one instance of a specific card. */\n confirmOwnership(input: InventoryConfirmOwnershipInput): Promise<InventoryConfirmOwnershipOutput>;\n\n /**\n * Mint new card instances. Requires franchise ownership or admin role.\n * For Commerce/Exchange, use zcoin.mintAndCharge instead (includes payment).\n */\n mint(input: InventoryMintInput): Promise<InventoryMintOutput>;\n\n /**\n * Transfer a specific card instance to another user.\n * For commercial transfers, use zcoin.commercialTransfer instead.\n *\n * @deprecated for reason=\"purchase\"|\"trade\" — use commercialTransfer\n */\n transfer(input: InventoryTransferInput): Promise<InventoryTransferOutput>;\n\n /**\n * Transfer card instances by cardId (transfers oldest instances).\n * For commercial transfers, use zcoin.commercialTransfer instead.\n *\n * @deprecated for reason=\"purchase\"|\"trade\" — use commercialTransfer\n */\n transferByCard(input: InventoryTransferByCardInput): Promise<InventoryTransferByCardOutput>;\n\n /**\n * Grant mint authorization to another user/app for a specific card.\n * Required before Commerce can call zcoin.mintAndCharge for that card.\n */\n grantMintAuth(input: InventoryGrantMintAuthInput): Promise<any>;\n\n /** Get transfer/ownership history for an instance, card, or user. */\n transferHistory(input: InventoryTransferHistoryInput): Promise<any[]>;\n}\n\nexport interface CollectibleNamespace {\n /** Get a card instance by its numeric ID. Returns full instance data with card chain. */\n getInstanceById(input: CollectibleGetInstanceByIdInput): Promise<any>;\n\n /** Get a card instance by its instance code (public-facing identifier). */\n getPublicInstance(input: CollectibleGetPublicInstanceInput): Promise<any>;\n\n /** Batch-fetch multiple card instances by their IDs (max 100). */\n getPublicInstancesBatch(input: CollectibleGetPublicInstancesBatchInput): Promise<any[]>;\n\n /** Get the card profile (metadata, rarity, series info). */\n getCardProfile(input: CollectibleGetCardProfileInput): Promise<any>;\n\n /** Get the shell (packaging) image URL for a card. */\n getShellImageUrl(input: CollectibleGetShellImageUrlInput): Promise<any>;\n}\n\nexport interface UserNamespace {\n /** Get a user's public profile by openId. */\n getPublicProfile(input: UserGetPublicProfileInput): Promise<UserPublicProfile>;\n\n /** Get a user's public profile by internal userId. */\n getPublicProfileById(input: UserGetPublicProfileByIdInput): Promise<UserPublicProfile>;\n}\n\nexport interface AuthNamespace {\n /** Get the authenticated user's profile. Returns null if not authenticated. */\n me(): Promise<AuthUser | null>;\n\n /** Get public info about an OAuth app by its client ID. */\n getOAuthAppInfo(input: AuthGetOAuthAppInfoInput): Promise<AuthOAuthAppInfo | null>;\n}\n\nexport interface IpNamespace {\n /** Get a franchise by ID or slug. */\n getFranchise(input: FranchiseGetInput): Promise<any>;\n\n /** Get a series by ID or slug. */\n getSeries(input: SeriesGetInput): Promise<any>;\n\n /** List all series in a franchise. */\n listSeriesByFranchise(input: SeriesListByFranchiseInput): Promise<any[]>;\n\n /** Get a card by ID. */\n getCard(input: CardGetInput): Promise<any>;\n\n /** List all cards in a series. */\n listCardsBySeries(input: CardListBySeriesInput): Promise<any[]>;\n}\n\n// ─── GoodZClient type ────────────────────────────────────────\n\nexport interface GoodZClient {\n readonly zcoin: ZcoinNamespace;\n readonly inventory: InventoryNamespace;\n readonly collectible: CollectibleNamespace;\n readonly user: UserNamespace;\n readonly auth: AuthNamespace;\n readonly ip: IpNamespace;\n\n /**\n * Make a raw tRPC query call. Use this for routes not yet covered\n * by the typed namespaces.\n */\n rawQuery<T = any>(path: string, input?: any): Promise<T>;\n\n /**\n * Make a raw tRPC mutation call. Use this for routes not yet covered\n * by the typed namespaces.\n */\n rawMutation<T = any>(path: string, input?: any): Promise<T>;\n}\n\n// ─── Client factory ──────────────────────────────────────────\n\n/**\n * Create a type-safe GoodZ.Core API client.\n *\n * @example Server-to-server with static token\n * ```ts\n * const goodz = createGoodZClient({\n * accessToken: process.env.CORE_ACCESS_TOKEN,\n * });\n * ```\n *\n * @example With dynamic token provider (auto-refresh)\n * ```ts\n * const goodz = createGoodZClient({\n * getAccessToken: async () => {\n * const token = await refreshTokenIfNeeded();\n * return token;\n * },\n * });\n * ```\n *\n * @example Acting on behalf of a user\n * ```ts\n * const userGoodz = createGoodZClient({\n * accessToken: userAccessToken,\n * });\n * const balance = await userGoodz.zcoin.getMyBalance();\n * ```\n */\nexport function createGoodZClient(config: GoodZClientConfig = {}): GoodZClient {\n const {\n coreUrl = \"https://goodzcore.manus.space\",\n accessToken,\n getAccessToken,\n headers: customHeaders,\n } = config;\n\n const transport: TransportConfig = {\n baseUrl: coreUrl.replace(/\\/$/, \"\"),\n getHeaders: async () => {\n const h: Record<string, string> = { ...customHeaders };\n const token = getAccessToken ? await getAccessToken() : accessToken;\n if (token) {\n h[\"Authorization\"] = `Bearer ${token}`;\n }\n return h;\n },\n };\n\n // Helper shortcuts\n const q = <I, O>(path: string) => (input?: I) => callQuery<I, O>(transport, path, input);\n const m = <I, O>(path: string) => (input?: I) => callMutation<I, O>(transport, path, input);\n\n return {\n // ── zcoin ──────────────────────────────────────────────\n zcoin: {\n getMyBalance: q<void, ZcoinGetMyBalanceOutput>(\"zcoin.getMyBalance\"),\n getMyHistory: q<ZcoinGetMyHistoryInput, any[]>(\"zcoin.getMyHistory\"),\n getDepositPackages: q<ZcoinGetDepositPackagesInput, ZcoinDepositPackage[]>(\"zcoin.getDepositPackages\"),\n createDepositOrder: m<ZcoinCreateDepositOrderInput, ZcoinCreateDepositOrderOutput>(\"zcoin.createDepositOrder\"),\n getDepositStatus: q<ZcoinGetDepositStatusInput, ZcoinGetDepositStatusOutput>(\"zcoin.getDepositStatus\"),\n commercialTransfer: m<ZcoinCommercialTransferInput, ZcoinCommercialTransferOutput>(\"zcoin.commercialTransfer\"),\n mintAndCharge: m<ZcoinMintAndChargeInput, ZcoinMintAndChargeOutput>(\"zcoin.mintAndCharge\"),\n chargeUser: m<ZcoinChargeUserInput, ZcoinChargeUserOutput>(\"zcoin.chargeUser\"),\n createDirectPurchaseOrder: m<ZcoinCreateDirectPurchaseOrderInput, ZcoinCreateDirectPurchaseOrderOutput>(\"zcoin.createDirectPurchaseOrder\"),\n },\n\n // ── inventory ──────────────────────────────────────────\n inventory: {\n getUserInventory: q<InventoryGetUserInventoryInput, any[]>(\"inventory.getUserInventory\"),\n confirmOwnership: q<InventoryConfirmOwnershipInput, InventoryConfirmOwnershipOutput>(\"inventory.confirmOwnership\"),\n mint: m<InventoryMintInput, InventoryMintOutput>(\"inventory.mint\"),\n transfer: m<InventoryTransferInput, InventoryTransferOutput>(\"inventory.transfer\"),\n transferByCard: m<InventoryTransferByCardInput, InventoryTransferByCardOutput>(\"inventory.transferByCard\"),\n grantMintAuth: m<InventoryGrantMintAuthInput, any>(\"inventory.grantMintAuth\"),\n transferHistory: q<InventoryTransferHistoryInput, any[]>(\"inventory.transferHistory\"),\n },\n\n // ── collectible ────────────────────────────────────────\n collectible: {\n getInstanceById: q<CollectibleGetInstanceByIdInput, any>(\"collectible.getInstanceById\"),\n getPublicInstance: q<CollectibleGetPublicInstanceInput, any>(\"collectible.getPublicInstance\"),\n getPublicInstancesBatch: q<CollectibleGetPublicInstancesBatchInput, any[]>(\"collectible.getPublicInstancesBatch\"),\n getCardProfile: q<CollectibleGetCardProfileInput, any>(\"collectible.getCardProfile\"),\n getShellImageUrl: q<CollectibleGetShellImageUrlInput, any>(\"collectible.getShellImageUrl\"),\n },\n\n // ── user ───────────────────────────────────────────────\n user: {\n getPublicProfile: q<UserGetPublicProfileInput, UserPublicProfile>(\"user.getPublicProfile\"),\n getPublicProfileById: q<UserGetPublicProfileByIdInput, UserPublicProfile>(\"user.getPublicProfileById\"),\n },\n\n // ── auth ───────────────────────────────────────────────\n auth: {\n me: q<void, AuthUser | null>(\"auth.me\"),\n getOAuthAppInfo: q<AuthGetOAuthAppInfoInput, AuthOAuthAppInfo | null>(\"auth.getOAuthAppInfo\"),\n },\n\n // ── ip (franchise/series/card) ─────────────────────────\n ip: {\n getFranchise: q<FranchiseGetInput, any>(\"franchise.get\"),\n getSeries: q<SeriesGetInput, any>(\"series.get\"),\n listSeriesByFranchise: q<SeriesListByFranchiseInput, any[]>(\"series.listByFranchise\"),\n getCard: q<CardGetInput, any>(\"card.get\"),\n listCardsBySeries: q<CardListBySeriesInput, any[]>(\"card.listBySeries\"),\n },\n\n // ── raw escape hatches ─────────────────────────────────\n rawQuery: <T = any>(path: string, input?: any) => callQuery<any, T>(transport, path, input),\n rawMutation: <T = any>(path: string, input?: any) => callMutation<any, T>(transport, path, input),\n };\n}\n\n/**\n * Alias for createGoodZClient — creates a client acting on behalf of a user.\n * Semantically identical, but makes the intent clearer in server-to-server code.\n */\nexport const createUserClient = createGoodZClient;\n"]}
|