prividium 0.3.1 β 0.5.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/README.md +68 -1
- package/dist/sdk/chain-core.d.ts +20 -0
- package/dist/sdk/chain-core.js +50 -0
- package/dist/sdk/error-utils.js +2 -2
- package/dist/sdk/memory-storage.d.ts +7 -0
- package/dist/sdk/memory-storage.js +12 -0
- package/dist/sdk/prividium-chain.d.ts +1 -1
- package/dist/sdk/prividium-chain.js +10 -45
- package/dist/sdk/siwe-auth.d.ts +17 -0
- package/dist/sdk/siwe-auth.js +68 -0
- package/dist/sdk/siwe-chain.d.ts +28 -0
- package/dist/sdk/siwe-chain.js +166 -0
- package/dist/sdk/siwe.d.ts +11 -0
- package/dist/sdk/siwe.js +11 -0
- package/dist/sdk/storage.d.ts +5 -0
- package/dist/sdk/storage.js +8 -0
- package/dist/tsconfig.sdk.tsbuildinfo +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@ RPC and manages basic configuration.
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- π **Popup-based OAuth Authentication** - Secure authentication flow using popup windows
|
|
9
|
+
- π **Popup-based OAuth Authentication** - Secure authentication flow using popup windows (browser)
|
|
10
|
+
- π **SIWE Backend Authentication** - Programmatic Sign-In with Ethereum for server-side use (`prividium/siwe`)
|
|
10
11
|
- π **JWT Token Management** - Automatic token storage, validation, and expiration handling
|
|
11
12
|
- π **Viem Integration** - Drop-in transport for viem clients with automatic auth headers
|
|
12
13
|
- π οΈ **CLI Proxy** - Local authenticated JSON-RPC proxy and simple config management
|
|
@@ -359,6 +360,72 @@ interface PrividiumClientConfig {
|
|
|
359
360
|
|
|
360
361
|
**Returns:** `PublicClient` - Viem client instance.
|
|
361
362
|
|
|
363
|
+
## SIWE Backend Authentication
|
|
364
|
+
|
|
365
|
+
For server-side or backend use cases, the SDK provides a separate entry point with programmatic SIWE (Sign-In with
|
|
366
|
+
Ethereum) authentication. Instead of a browser popup, you provide a viem account and the SDK handles the entire flow
|
|
367
|
+
automatically.
|
|
368
|
+
|
|
369
|
+
### Quick Start (SIWE)
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
import { createPrividiumSiweChain } from 'prividium/siwe';
|
|
373
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
374
|
+
|
|
375
|
+
const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY');
|
|
376
|
+
|
|
377
|
+
const prividium = createPrividiumSiweChain({
|
|
378
|
+
account,
|
|
379
|
+
chain: { id: 7777, name: 'Prividiumβ’ Chain' },
|
|
380
|
+
prividiumApiBaseUrl: 'https://permissions.prividium.io',
|
|
381
|
+
domain: 'my-backend.example.com',
|
|
382
|
+
onReauthenticate: () => console.log('Session automatically refreshed'),
|
|
383
|
+
onReauthenticateError: (err) => console.error('Re-auth failed:', err)
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// Authenticate
|
|
387
|
+
await prividium.authorize();
|
|
388
|
+
|
|
389
|
+
// Make authenticated API calls
|
|
390
|
+
const user = await prividium.fetchUser();
|
|
391
|
+
|
|
392
|
+
// Transport automatically reauthenticates on session expiry
|
|
393
|
+
const balance = await client.getBalance({ address: prividium.address });
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### `createPrividiumSiweChain(config)`
|
|
397
|
+
|
|
398
|
+
Creates an SDK instance with programmatic SIWE authentication.
|
|
399
|
+
|
|
400
|
+
**Parameters:**
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
interface PrividiumSiweConfig {
|
|
404
|
+
chain: Chain; // Viem chain configuration (without rpcUrls)
|
|
405
|
+
prividiumApiBaseUrl: string; // Permissions API service base URL
|
|
406
|
+
account: LocalAccount; // Viem account for signing (e.g. from privateKeyToAccount)
|
|
407
|
+
domain: string; // Domain for SIWE message
|
|
408
|
+
storage?: Storage; // Custom storage (defaults to MemoryStorage)
|
|
409
|
+
autoReauthenticate?: boolean; // Auto-reauth on session expiry (default: true)
|
|
410
|
+
onAuthExpiry?: () => void; // Called when auth expires and reauth is disabled/failed
|
|
411
|
+
onReauthenticate?: () => void; // Called after successful automatic reauthentication
|
|
412
|
+
onReauthenticateError?: (error: Error) => void; // Called when automatic reauthentication fails
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Returns:** `PrividiumSiweChain` - with all the same methods as `PrividiumChain` (except `addNetworkToWallet` which is
|
|
417
|
+
browser-only), plus `address` (the wallet address derived from the account).
|
|
418
|
+
|
|
419
|
+
### Auto-Reauthentication
|
|
420
|
+
|
|
421
|
+
When `autoReauthenticate` is `true` (default), the SDK automatically handles expired sessions:
|
|
422
|
+
|
|
423
|
+
1. Before each API call, checks if the session is still valid
|
|
424
|
+
2. On 401/403 responses, reauthenticates and retries the call once
|
|
425
|
+
3. Concurrent reauth attempts are deduplicated (only one SIWE flow runs at a time)
|
|
426
|
+
|
|
427
|
+
Disable with `autoReauthenticate: false` to handle expiry manually via `onAuthExpiry`.
|
|
428
|
+
|
|
362
429
|
## Advanced Usage
|
|
363
430
|
|
|
364
431
|
### Custom Storage
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Chain } from 'viem';
|
|
2
|
+
import { z, type ZodType } from 'zod';
|
|
3
|
+
import { type AuthorizeTransactionParams, type AuthorizeTransactionResponse, type UserProfile } from './types.js';
|
|
4
|
+
export declare function rpcUrl(apiUrl: string): string;
|
|
5
|
+
export declare function walletRpcUrl(apiUrl: string, token: string): string;
|
|
6
|
+
export declare function buildChainObject(config: {
|
|
7
|
+
chain: Omit<Chain, 'rpcUrls'>;
|
|
8
|
+
prividiumApiBaseUrl: string;
|
|
9
|
+
}): Chain;
|
|
10
|
+
export type ApiCaller = <Schema extends ZodType>(schema: Schema, url: string, method: 'GET' | 'POST', body?: string) => Promise<z.infer<Schema>>;
|
|
11
|
+
export declare function createApiMethods(deps: {
|
|
12
|
+
prividiumApiCall: ApiCaller;
|
|
13
|
+
prividiumApiBaseUrl: string;
|
|
14
|
+
}): {
|
|
15
|
+
fetchUser(): Promise<UserProfile>;
|
|
16
|
+
getWalletToken(): Promise<string>;
|
|
17
|
+
getWalletRpcUrl(): Promise<string>;
|
|
18
|
+
invalidateWalletToken(): Promise<string>;
|
|
19
|
+
authorizeTransaction(params: AuthorizeTransactionParams): Promise<AuthorizeTransactionResponse>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { mainnet } from 'viem/chains';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { authorizeTransactionResponseSchema, profileSchema } from './types.js';
|
|
4
|
+
export function rpcUrl(apiUrl) {
|
|
5
|
+
return new URL('/rpc', apiUrl).toString();
|
|
6
|
+
}
|
|
7
|
+
export function walletRpcUrl(apiUrl, token) {
|
|
8
|
+
return new URL(`/rpc/wallet/${token}`, apiUrl).toString();
|
|
9
|
+
}
|
|
10
|
+
export function buildChainObject(config) {
|
|
11
|
+
return {
|
|
12
|
+
...mainnet,
|
|
13
|
+
...config.chain,
|
|
14
|
+
id: config.chain.id,
|
|
15
|
+
contracts: {
|
|
16
|
+
...config.chain.contracts,
|
|
17
|
+
multicall3: undefined // Prividiumβ’ doesn't support multicall yet
|
|
18
|
+
},
|
|
19
|
+
rpcUrls: { default: { http: [rpcUrl(config.prividiumApiBaseUrl)] } },
|
|
20
|
+
blockExplorers: config.chain.blockExplorers
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function createApiMethods(deps) {
|
|
24
|
+
const { prividiumApiCall, prividiumApiBaseUrl } = deps;
|
|
25
|
+
return {
|
|
26
|
+
async fetchUser() {
|
|
27
|
+
return await prividiumApiCall(profileSchema, `${prividiumApiBaseUrl}/api/profiles/me`, 'GET');
|
|
28
|
+
},
|
|
29
|
+
async getWalletToken() {
|
|
30
|
+
const { token } = await prividiumApiCall(z.object({ token: z.string() }), `${prividiumApiBaseUrl}/api/wallet/personal-rpc-token`, 'GET');
|
|
31
|
+
return token;
|
|
32
|
+
},
|
|
33
|
+
async getWalletRpcUrl() {
|
|
34
|
+
const walletToken = await this.getWalletToken();
|
|
35
|
+
return walletRpcUrl(prividiumApiBaseUrl, walletToken);
|
|
36
|
+
},
|
|
37
|
+
async invalidateWalletToken() {
|
|
38
|
+
const { newWalletToken } = await prividiumApiCall(z.object({ newWalletToken: z.string(), message: z.string() }), `${prividiumApiBaseUrl}/api/wallet/invalidate`, 'POST');
|
|
39
|
+
return newWalletToken;
|
|
40
|
+
},
|
|
41
|
+
async authorizeTransaction(params) {
|
|
42
|
+
return prividiumApiCall(authorizeTransactionResponseSchema, `${prividiumApiBaseUrl}/api/wallet/transaction-authorization`, 'POST', JSON.stringify({
|
|
43
|
+
...params,
|
|
44
|
+
// Always pass calldata and value, even if undefined
|
|
45
|
+
calldata: params?.calldata ?? '0x',
|
|
46
|
+
value: params.value?.toString() ?? '0'
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
package/dist/sdk/error-utils.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { UNAUTHORIZED_ERROR_CODE
|
|
2
|
+
import { UNAUTHORIZED_ERROR_CODE } from './rpc-error-codes.js';
|
|
3
3
|
const jsonRpcErrorSchema = z.object({
|
|
4
4
|
error: z.object({
|
|
5
5
|
code: z.number(),
|
|
@@ -15,7 +15,7 @@ export async function hasPrividiumUnauthorizedError(response) {
|
|
|
15
15
|
const parsed = jsonRpcErrorSchema.safeParse(await clonedResponse.json());
|
|
16
16
|
// { success: false, error: {...} } | { success: true, data: {...} }
|
|
17
17
|
if (parsed.success) {
|
|
18
|
-
return
|
|
18
|
+
return parsed.data.error.code === UNAUTHORIZED_ERROR_CODE;
|
|
19
19
|
}
|
|
20
20
|
return false;
|
|
21
21
|
}
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import { http } from 'viem';
|
|
2
|
-
import { mainnet } from 'viem/chains';
|
|
3
|
-
import { authorizeTransactionResponseSchema, profileSchema } from './types.js';
|
|
4
2
|
import { LocalStorage, TokenManager } from './storage.js';
|
|
5
3
|
import { PopupAuth } from './popup-auth.js';
|
|
6
4
|
import { hasPrividiumUnauthorizedError } from './error-utils.js';
|
|
7
|
-
import { PrividiumSessionError } from './errors';
|
|
8
|
-
import {
|
|
9
|
-
function rpcUrl(apiUrl) {
|
|
10
|
-
return new URL('/rpc', apiUrl).toString();
|
|
11
|
-
}
|
|
12
|
-
function walletRpcUrl(apiUrl, token) {
|
|
13
|
-
return new URL(`/rpc/wallet/${token}`, apiUrl).toString();
|
|
14
|
-
}
|
|
5
|
+
import { PrividiumSessionError } from './errors.js';
|
|
6
|
+
import { buildChainObject, createApiMethods, rpcUrl } from './chain-core.js';
|
|
15
7
|
export function createPrividiumChain(config) {
|
|
16
8
|
if (config.permissionsApiBaseUrl !== undefined) {
|
|
17
9
|
throw new Error('"permissionsApiBaseUrl" was deprecated. Please use "prividiumApiBaseUrl" instead.');
|
|
@@ -46,7 +38,7 @@ export function createPrividiumChain(config) {
|
|
|
46
38
|
},
|
|
47
39
|
...(body && { body })
|
|
48
40
|
});
|
|
49
|
-
if (response.status ===
|
|
41
|
+
if (response.status === 401) {
|
|
50
42
|
tokenManager.clearToken();
|
|
51
43
|
config.onAuthExpiry?.();
|
|
52
44
|
throw new PrividiumSessionError();
|
|
@@ -84,18 +76,9 @@ export function createPrividiumChain(config) {
|
|
|
84
76
|
}
|
|
85
77
|
}
|
|
86
78
|
});
|
|
79
|
+
const apiMethods = createApiMethods({ prividiumApiCall, prividiumApiBaseUrl: config.prividiumApiBaseUrl });
|
|
87
80
|
return {
|
|
88
|
-
chain:
|
|
89
|
-
...mainnet,
|
|
90
|
-
...config.chain,
|
|
91
|
-
id: config.chain.id,
|
|
92
|
-
contracts: {
|
|
93
|
-
...config.chain.contracts,
|
|
94
|
-
multicall3: undefined // Prividiumβ’ doesn't support multicall yet
|
|
95
|
-
},
|
|
96
|
-
rpcUrls: { default: { http: [rpcUrl(config.prividiumApiBaseUrl)] } },
|
|
97
|
-
blockExplorers: config.chain.blockExplorers
|
|
98
|
-
},
|
|
81
|
+
chain: buildChainObject(config),
|
|
99
82
|
transport,
|
|
100
83
|
async authorize(options) {
|
|
101
84
|
return popupAuth.authorize(options);
|
|
@@ -107,29 +90,11 @@ export function createPrividiumChain(config) {
|
|
|
107
90
|
return popupAuth.isAuthorized();
|
|
108
91
|
},
|
|
109
92
|
getAuthHeaders,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return token;
|
|
116
|
-
},
|
|
117
|
-
async getWalletRpcUrl() {
|
|
118
|
-
const walletToken = await this.getWalletToken();
|
|
119
|
-
return walletRpcUrl(config.prividiumApiBaseUrl, walletToken);
|
|
120
|
-
},
|
|
121
|
-
async invalidateWalletToken() {
|
|
122
|
-
const { newWalletToken } = await prividiumApiCall(z.object({ newWalletToken: z.string(), message: z.string() }), `${config.prividiumApiBaseUrl}/api/wallet/invalidate`, 'POST');
|
|
123
|
-
return newWalletToken;
|
|
124
|
-
},
|
|
125
|
-
async authorizeTransaction(params) {
|
|
126
|
-
return prividiumApiCall(authorizeTransactionResponseSchema, `${config.prividiumApiBaseUrl}/api/wallet/transaction-authorization`, 'POST', JSON.stringify({
|
|
127
|
-
...params,
|
|
128
|
-
// Always pass calldata and value, even if undefined
|
|
129
|
-
calldata: params?.calldata ?? '0x',
|
|
130
|
-
value: params.value?.toString() ?? '0'
|
|
131
|
-
}));
|
|
132
|
-
},
|
|
93
|
+
fetchUser: apiMethods.fetchUser,
|
|
94
|
+
getWalletToken: apiMethods.getWalletToken,
|
|
95
|
+
getWalletRpcUrl: apiMethods.getWalletRpcUrl,
|
|
96
|
+
invalidateWalletToken: apiMethods.invalidateWalletToken,
|
|
97
|
+
authorizeTransaction: apiMethods.authorizeTransaction,
|
|
133
98
|
async addNetworkToWallet(params) {
|
|
134
99
|
if (typeof window === 'undefined' || !window.ethereum) {
|
|
135
100
|
throw new Error('Wallet not detected. Please install a wallet extension to add network.');
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Address, LocalAccount } from 'viem';
|
|
2
|
+
import type { TokenManager } from './storage.js';
|
|
3
|
+
import type { TokenData } from './types.js';
|
|
4
|
+
export interface SiweAuthConfig {
|
|
5
|
+
account: LocalAccount;
|
|
6
|
+
prividiumApiBaseUrl: string;
|
|
7
|
+
domain: string;
|
|
8
|
+
tokenManager: TokenManager;
|
|
9
|
+
}
|
|
10
|
+
export declare class SiweAuth {
|
|
11
|
+
private config;
|
|
12
|
+
constructor(config: SiweAuthConfig);
|
|
13
|
+
get address(): Address;
|
|
14
|
+
authorize(): Promise<TokenData>;
|
|
15
|
+
unauthorize(): void;
|
|
16
|
+
isAuthorized(): boolean;
|
|
17
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const siweMessageResponseSchema = z.object({
|
|
3
|
+
msg: z.string()
|
|
4
|
+
});
|
|
5
|
+
const siweLoginResponseSchema = z.object({
|
|
6
|
+
token: z.string(),
|
|
7
|
+
expiresAt: z.string()
|
|
8
|
+
});
|
|
9
|
+
const mfaResponseSchema = z.object({
|
|
10
|
+
requiresMfa: z.literal(true)
|
|
11
|
+
});
|
|
12
|
+
export class SiweAuth {
|
|
13
|
+
config;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
}
|
|
17
|
+
get address() {
|
|
18
|
+
return this.config.account.address;
|
|
19
|
+
}
|
|
20
|
+
async authorize() {
|
|
21
|
+
// Step 1: Request SIWE message
|
|
22
|
+
const siweMessageUrl = new URL('/api/siwe-messages', this.config.prividiumApiBaseUrl).toString();
|
|
23
|
+
const siweResponse = await fetch(siweMessageUrl, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { 'Content-Type': 'application/json' },
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
address: this.config.account.address,
|
|
28
|
+
domain: this.config.domain
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
if (!siweResponse.ok) {
|
|
32
|
+
throw new Error(`Failed to get SIWE message: ${siweResponse.status} ${siweResponse.statusText}`);
|
|
33
|
+
}
|
|
34
|
+
const siweData = siweMessageResponseSchema.parse(await siweResponse.json());
|
|
35
|
+
// Step 2: Sign the message
|
|
36
|
+
const signature = await this.config.account.signMessage({ message: siweData.msg });
|
|
37
|
+
// Step 3: Login
|
|
38
|
+
const loginUrl = new URL('/auth/login/crypto-native', this.config.prividiumApiBaseUrl).toString();
|
|
39
|
+
const loginResponse = await fetch(loginUrl, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify({ message: siweData.msg, signature })
|
|
43
|
+
});
|
|
44
|
+
if (!loginResponse.ok) {
|
|
45
|
+
throw new Error(`SIWE login failed: ${loginResponse.status} ${loginResponse.statusText}`);
|
|
46
|
+
}
|
|
47
|
+
const loginJson = await loginResponse.json();
|
|
48
|
+
// Check for MFA requirement (admin users with passkeys)
|
|
49
|
+
const mfaParsed = mfaResponseSchema.safeParse(loginJson);
|
|
50
|
+
if (mfaParsed.success) {
|
|
51
|
+
throw new Error('SIWE login requires MFA which is not supported in programmatic auth');
|
|
52
|
+
}
|
|
53
|
+
const loginData = siweLoginResponseSchema.parse(loginJson);
|
|
54
|
+
// Step 4: Store token directly (login response includes expiresAt)
|
|
55
|
+
const tokenData = {
|
|
56
|
+
rawToken: loginData.token,
|
|
57
|
+
expiresAt: new Date(loginData.expiresAt)
|
|
58
|
+
};
|
|
59
|
+
this.config.tokenManager.setTokenDirect(tokenData);
|
|
60
|
+
return tokenData;
|
|
61
|
+
}
|
|
62
|
+
unauthorize() {
|
|
63
|
+
this.config.tokenManager.clearToken();
|
|
64
|
+
}
|
|
65
|
+
isAuthorized() {
|
|
66
|
+
return this.config.tokenManager.isAuthorized();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Address, type Chain, type LocalAccount, type Transport } from 'viem';
|
|
2
|
+
import type { AuthorizeTransactionParams, AuthorizeTransactionResponse, Storage, TokenData, UserProfile } from './types.js';
|
|
3
|
+
export interface PrividiumSiweConfig {
|
|
4
|
+
chain: Omit<Chain, 'rpcUrls'>;
|
|
5
|
+
prividiumApiBaseUrl: string;
|
|
6
|
+
account: LocalAccount;
|
|
7
|
+
domain: string;
|
|
8
|
+
storage?: Storage;
|
|
9
|
+
autoReauthenticate?: boolean;
|
|
10
|
+
onAuthExpiry?: () => void;
|
|
11
|
+
onReauthenticate?: () => void;
|
|
12
|
+
onReauthenticateError?: (error: Error) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface PrividiumSiweChain {
|
|
15
|
+
chain: Chain;
|
|
16
|
+
transport: Transport;
|
|
17
|
+
address: Address;
|
|
18
|
+
authorize(): Promise<TokenData>;
|
|
19
|
+
unauthorize(): void;
|
|
20
|
+
isAuthorized(): boolean;
|
|
21
|
+
getAuthHeaders(): Record<string, string> | null;
|
|
22
|
+
fetchUser(): Promise<UserProfile>;
|
|
23
|
+
getWalletToken(): Promise<string>;
|
|
24
|
+
getWalletRpcUrl(): Promise<string>;
|
|
25
|
+
invalidateWalletToken(): Promise<string>;
|
|
26
|
+
authorizeTransaction(params: AuthorizeTransactionParams): Promise<AuthorizeTransactionResponse>;
|
|
27
|
+
}
|
|
28
|
+
export declare function createPrividiumSiweChain(config: PrividiumSiweConfig): PrividiumSiweChain;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { http } from 'viem';
|
|
2
|
+
import { TokenManager } from './storage.js';
|
|
3
|
+
import { MemoryStorage } from './memory-storage.js';
|
|
4
|
+
import { SiweAuth } from './siwe-auth.js';
|
|
5
|
+
import { hasPrividiumUnauthorizedError } from './error-utils.js';
|
|
6
|
+
import { PrividiumSessionError } from './errors.js';
|
|
7
|
+
import { buildChainObject, createApiMethods, rpcUrl } from './chain-core.js';
|
|
8
|
+
export function createPrividiumSiweChain(config) {
|
|
9
|
+
const autoReauth = config.autoReauthenticate ?? true;
|
|
10
|
+
const storage = config.storage ?? new MemoryStorage();
|
|
11
|
+
const tokenManager = new TokenManager(storage, config.chain.id, config.prividiumApiBaseUrl, config.onAuthExpiry);
|
|
12
|
+
const siweAuth = new SiweAuth({
|
|
13
|
+
account: config.account,
|
|
14
|
+
prividiumApiBaseUrl: config.prividiumApiBaseUrl,
|
|
15
|
+
domain: config.domain,
|
|
16
|
+
tokenManager
|
|
17
|
+
});
|
|
18
|
+
// Deduplication: only one reauthentication flow at a time
|
|
19
|
+
let reauthPromise = null;
|
|
20
|
+
async function reauthenticate() {
|
|
21
|
+
if (reauthPromise) {
|
|
22
|
+
return reauthPromise;
|
|
23
|
+
}
|
|
24
|
+
reauthPromise = (async () => {
|
|
25
|
+
try {
|
|
26
|
+
const tokenData = await siweAuth.authorize();
|
|
27
|
+
config.onReauthenticate?.();
|
|
28
|
+
return tokenData;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
32
|
+
config.onReauthenticateError?.(err);
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
reauthPromise = null;
|
|
37
|
+
}
|
|
38
|
+
})();
|
|
39
|
+
return reauthPromise;
|
|
40
|
+
}
|
|
41
|
+
async function ensureAuthorized() {
|
|
42
|
+
if (!tokenManager.isAuthorized() && autoReauth) {
|
|
43
|
+
await reauthenticate();
|
|
44
|
+
}
|
|
45
|
+
if (!tokenManager.isAuthorized()) {
|
|
46
|
+
throw new PrividiumSessionError();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const getAuthHeaders = () => {
|
|
50
|
+
const tokenData = tokenManager.getToken();
|
|
51
|
+
if (!tokenData) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
Authorization: `Bearer ${tokenData.rawToken}`
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
async function prividiumApiCall(schema, url, method, body) {
|
|
59
|
+
await ensureAuthorized();
|
|
60
|
+
const headers = getAuthHeaders();
|
|
61
|
+
if (!headers) {
|
|
62
|
+
throw new PrividiumSessionError();
|
|
63
|
+
}
|
|
64
|
+
const response = await fetch(url, {
|
|
65
|
+
method,
|
|
66
|
+
headers: {
|
|
67
|
+
'Content-Type': 'application/json',
|
|
68
|
+
...headers
|
|
69
|
+
},
|
|
70
|
+
...(body && { body })
|
|
71
|
+
});
|
|
72
|
+
if (response.status === 401) {
|
|
73
|
+
tokenManager.clearToken();
|
|
74
|
+
// Attempt reauthentication and retry once
|
|
75
|
+
if (autoReauth) {
|
|
76
|
+
try {
|
|
77
|
+
await reauthenticate();
|
|
78
|
+
const retryHeaders = getAuthHeaders();
|
|
79
|
+
if (!retryHeaders)
|
|
80
|
+
throw new PrividiumSessionError();
|
|
81
|
+
const retryResponse = await fetch(url, {
|
|
82
|
+
method,
|
|
83
|
+
headers: { 'Content-Type': 'application/json', ...retryHeaders },
|
|
84
|
+
...(body && { body })
|
|
85
|
+
});
|
|
86
|
+
if (!retryResponse.ok) {
|
|
87
|
+
config.onAuthExpiry?.();
|
|
88
|
+
throw new PrividiumSessionError();
|
|
89
|
+
}
|
|
90
|
+
const parsed = schema.safeParse(await retryResponse.json());
|
|
91
|
+
if (!parsed.success) {
|
|
92
|
+
throw new Error(`Unexpected api response calling ${url}`);
|
|
93
|
+
}
|
|
94
|
+
return parsed.data;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
config.onAuthExpiry?.();
|
|
98
|
+
throw new PrividiumSessionError();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
config.onAuthExpiry?.();
|
|
102
|
+
throw new PrividiumSessionError();
|
|
103
|
+
}
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
throw new Error(`Error calling ${url}: ${response.status} ${response.statusText}`);
|
|
106
|
+
}
|
|
107
|
+
const parsed = schema.safeParse(await response.json());
|
|
108
|
+
if (!parsed.success) {
|
|
109
|
+
throw new Error(`Unexpected api response calling ${url}`);
|
|
110
|
+
}
|
|
111
|
+
return parsed.data;
|
|
112
|
+
}
|
|
113
|
+
// Create transport with auth integration using viem callbacks
|
|
114
|
+
const transport = http(rpcUrl(config.prividiumApiBaseUrl), {
|
|
115
|
+
batch: false,
|
|
116
|
+
fetchOptions: {
|
|
117
|
+
headers: getAuthHeaders() || {}
|
|
118
|
+
},
|
|
119
|
+
onFetchRequest(_request, init) {
|
|
120
|
+
if (!tokenManager.isAuthorized()) {
|
|
121
|
+
throw new PrividiumSessionError();
|
|
122
|
+
}
|
|
123
|
+
init.headers = {
|
|
124
|
+
...init.headers,
|
|
125
|
+
...getAuthHeaders()
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
onFetchResponse: async (response) => {
|
|
129
|
+
if (await hasPrividiumUnauthorizedError(response)) {
|
|
130
|
+
tokenManager.clearToken();
|
|
131
|
+
if (autoReauth) {
|
|
132
|
+
try {
|
|
133
|
+
await reauthenticate();
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
config.onAuthExpiry?.();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
config.onAuthExpiry?.();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
const apiMethods = createApiMethods({ prividiumApiCall, prividiumApiBaseUrl: config.prividiumApiBaseUrl });
|
|
146
|
+
return {
|
|
147
|
+
chain: buildChainObject(config),
|
|
148
|
+
transport,
|
|
149
|
+
address: config.account.address,
|
|
150
|
+
async authorize() {
|
|
151
|
+
return siweAuth.authorize();
|
|
152
|
+
},
|
|
153
|
+
unauthorize() {
|
|
154
|
+
siweAuth.unauthorize();
|
|
155
|
+
},
|
|
156
|
+
isAuthorized() {
|
|
157
|
+
return siweAuth.isAuthorized();
|
|
158
|
+
},
|
|
159
|
+
getAuthHeaders,
|
|
160
|
+
fetchUser: apiMethods.fetchUser,
|
|
161
|
+
getWalletToken: apiMethods.getWalletToken,
|
|
162
|
+
getWalletRpcUrl: apiMethods.getWalletRpcUrl,
|
|
163
|
+
invalidateWalletToken: apiMethods.invalidateWalletToken,
|
|
164
|
+
authorizeTransaction: apiMethods.authorizeTransaction
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { createPrividiumSiweChain } from './siwe-chain.js';
|
|
2
|
+
export type { PrividiumSiweConfig, PrividiumSiweChain } from './siwe-chain.js';
|
|
3
|
+
export { SiweAuth } from './siwe-auth.js';
|
|
4
|
+
export type { SiweAuthConfig } from './siwe-auth.js';
|
|
5
|
+
export { MemoryStorage } from './memory-storage.js';
|
|
6
|
+
export { TokenManager } from './storage.js';
|
|
7
|
+
export type { Storage, TokenData } from './types.js';
|
|
8
|
+
export { createPrividiumClient } from './create-prividium-client.js';
|
|
9
|
+
export type { UserProfile, UserRole, AuthorizeTransactionParams, AuthorizeTransactionResponse } from './types.js';
|
|
10
|
+
export { AUTH_ERRORS } from './types.js';
|
|
11
|
+
export { UNAUTHORIZED_ERROR_CODE, FORBIDDEN_ERROR_CODE } from './rpc-error-codes.js';
|
package/dist/sdk/siwe.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Core factory + types
|
|
2
|
+
export { createPrividiumSiweChain } from './siwe-chain.js';
|
|
3
|
+
// Auth strategy
|
|
4
|
+
export { SiweAuth } from './siwe-auth.js';
|
|
5
|
+
// Storage (MemoryStorage is the default for SIWE, but expose both + interface)
|
|
6
|
+
export { MemoryStorage } from './memory-storage.js';
|
|
7
|
+
export { TokenManager } from './storage.js';
|
|
8
|
+
// Viem client (reusable with SIWE chain)
|
|
9
|
+
export { createPrividiumClient } from './create-prividium-client.js';
|
|
10
|
+
export { AUTH_ERRORS } from './types.js';
|
|
11
|
+
export { UNAUTHORIZED_ERROR_CODE, FORBIDDEN_ERROR_CODE } from './rpc-error-codes.js';
|
package/dist/sdk/storage.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export declare class TokenManager {
|
|
|
16
16
|
getToken(): TokenData | null;
|
|
17
17
|
private getTokenExpiration;
|
|
18
18
|
setToken(rawToken: string): Promise<TokenData>;
|
|
19
|
+
/**
|
|
20
|
+
* Stores token data directly without fetching expiration from the API.
|
|
21
|
+
* Used by SIWE auth where the login response already includes expiresAt.
|
|
22
|
+
*/
|
|
23
|
+
setTokenDirect(tokenData: TokenData): void;
|
|
19
24
|
clearToken(): void;
|
|
20
25
|
isAuthorized(): boolean;
|
|
21
26
|
setState(state: string): void;
|
package/dist/sdk/storage.js
CHANGED
|
@@ -106,6 +106,14 @@ export class TokenManager {
|
|
|
106
106
|
throw error;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Stores token data directly without fetching expiration from the API.
|
|
111
|
+
* Used by SIWE auth where the login response already includes expiresAt.
|
|
112
|
+
*/
|
|
113
|
+
setTokenDirect(tokenData) {
|
|
114
|
+
this.storage.setItem(this.tokenKey, JSON.stringify(tokenData));
|
|
115
|
+
this.tokenCache = tokenData;
|
|
116
|
+
}
|
|
109
117
|
clearToken() {
|
|
110
118
|
this.tokenCache = null;
|
|
111
119
|
this.storage.removeItem(this.tokenKey);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/create-prividium-client.ts","../src/error-utils.ts","../src/errors.ts","../src/index.ts","../src/popup-auth.ts","../src/prividium-chain.ts","../src/rpc-error-codes.ts","../src/storage.ts","../src/test-utils.ts","../src/token-utils.ts","../src/types.ts"],"version":"5.8.3"}
|
|
1
|
+
{"root":["../src/chain-core.ts","../src/create-prividium-client.ts","../src/error-utils.ts","../src/errors.ts","../src/index.ts","../src/memory-storage.ts","../src/popup-auth.ts","../src/prividium-chain.ts","../src/rpc-error-codes.ts","../src/siwe-auth.ts","../src/siwe-chain.ts","../src/siwe.ts","../src/storage.ts","../src/test-utils.ts","../src/token-utils.ts","../src/types.ts"],"version":"5.8.3"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prividium",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"bin": {
|
|
5
5
|
"prividium": "bin/cli.js"
|
|
6
6
|
},
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
".": {
|
|
9
9
|
"import": "./dist/sdk/index.js",
|
|
10
10
|
"types": "./dist/sdk/index.d.ts"
|
|
11
|
+
},
|
|
12
|
+
"./siwe": {
|
|
13
|
+
"import": "./dist/sdk/siwe.js",
|
|
14
|
+
"types": "./dist/sdk/siwe.d.ts"
|
|
11
15
|
}
|
|
12
16
|
},
|
|
13
17
|
"license": "MIT OR Apache-2.0",
|