@ab-org/predicate-market-sdk 0.0.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/README.md +246 -0
- package/dist/auth/autoReconnect.d.ts +11 -0
- package/dist/auth/autoReconnect.js +36 -0
- package/dist/auth/bundledConfig.d.ts +2 -0
- package/dist/auth/bundledConfig.js +19 -0
- package/dist/auth/config.d.ts +29 -0
- package/dist/auth/config.js +53 -0
- package/dist/auth/google.d.ts +43 -0
- package/dist/auth/google.js +147 -0
- package/dist/auth/twitter.d.ts +7 -0
- package/dist/auth/twitter.js +94 -0
- package/dist/constants/chains.d.ts +22 -0
- package/dist/constants/chains.js +23 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +19 -0
- package/dist/modules/api.d.ts +144 -0
- package/dist/modules/api.js +93 -0
- package/dist/modules/balanceQuery.d.ts +20 -0
- package/dist/modules/balanceQuery.js +58 -0
- package/dist/modules/deposit.d.ts +31 -0
- package/dist/modules/deposit.js +57 -0
- package/dist/modules/marketData.d.ts +8 -0
- package/dist/modules/marketData.js +113 -0
- package/dist/modules/withdraw.d.ts +31 -0
- package/dist/modules/withdraw.js +60 -0
- package/dist/modules/withdrawExecutor.d.ts +47 -0
- package/dist/modules/withdrawExecutor.js +208 -0
- package/dist/policyAdapter.d.ts +11 -0
- package/dist/policyAdapter.js +38 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.js +1 -0
- package/dist/ui/DepositModal.d.ts +36 -0
- package/dist/ui/DepositModal.js +326 -0
- package/dist/ui/SignInModal.d.ts +22 -0
- package/dist/ui/SignInModal.js +74 -0
- package/dist/ui/SignInModal.sections.d.ts +33 -0
- package/dist/ui/SignInModal.sections.js +45 -0
- package/dist/ui/SignInModal.shared.d.ts +15 -0
- package/dist/ui/SignInModal.shared.js +87 -0
- package/dist/ui/WalletSelectionModal.d.ts +14 -0
- package/dist/ui/WalletSelectionModal.js +54 -0
- package/dist/ui/WithdrawModal.d.ts +47 -0
- package/dist/ui/WithdrawModal.js +528 -0
- package/dist/ui/components/CloseButton.d.ts +4 -0
- package/dist/ui/components/CloseButton.js +15 -0
- package/dist/ui/components/Countdown.d.ts +16 -0
- package/dist/ui/components/Countdown.js +42 -0
- package/dist/ui/components/DepositDetailsPanel.d.ts +8 -0
- package/dist/ui/components/DepositDetailsPanel.js +117 -0
- package/dist/ui/components/DropdownField.d.ts +19 -0
- package/dist/ui/components/DropdownField.js +81 -0
- package/dist/ui/components/Field.d.ts +10 -0
- package/dist/ui/components/Field.js +21 -0
- package/dist/ui/components/LoginRequiredOverlay.d.ts +6 -0
- package/dist/ui/components/LoginRequiredOverlay.js +31 -0
- package/dist/ui/components/ModalCard.d.ts +9 -0
- package/dist/ui/components/ModalCard.js +14 -0
- package/dist/ui/components/ModalFrame.d.ts +9 -0
- package/dist/ui/components/ModalFrame.js +18 -0
- package/dist/ui/components/PrimaryButton.d.ts +2 -0
- package/dist/ui/components/PrimaryButton.js +14 -0
- package/dist/ui/components/QRCodePanel.d.ts +4 -0
- package/dist/ui/components/QRCodePanel.js +43 -0
- package/dist/ui/components/Select.d.ts +12 -0
- package/dist/ui/components/Select.js +29 -0
- package/dist/ui/components/StepIndicator.d.ts +7 -0
- package/dist/ui/components/StepIndicator.js +35 -0
- package/dist/ui/components/Success.d.ts +1 -0
- package/dist/ui/components/Success.js +4 -0
- package/dist/ui/components/Toast.d.ts +8 -0
- package/dist/ui/components/Toast.js +51 -0
- package/dist/ui/hooks/useSession.d.ts +2 -0
- package/dist/ui/hooks/useSession.js +10 -0
- package/dist/ui/signInTypes.d.ts +25 -0
- package/dist/ui/signInTypes.js +1 -0
- package/dist/ui/theme.d.ts +31 -0
- package/dist/ui/theme.js +31 -0
- package/dist/ui/useSignInModalController.d.ts +25 -0
- package/dist/ui/useSignInModalController.js +173 -0
- package/dist/utils/env.d.ts +1 -0
- package/dist/utils/env.js +61 -0
- package/dist/utils/explorer.d.ts +3 -0
- package/dist/utils/explorer.js +47 -0
- package/dist/walletUtils.d.ts +3 -0
- package/dist/walletUtils.js +3 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# @ab-org/predicate-market-sdk
|
|
2
|
+
|
|
3
|
+
Prediction-market specific helpers built on top of `@ab-org/sdk-core`.
|
|
4
|
+
|
|
5
|
+
## Key features
|
|
6
|
+
- Built-in wallet connection modal (social + plugin wallets)
|
|
7
|
+
- Connection state via `createWalletConnectController()` and `createAccountController()` from `@ab-org/sdk-core`
|
|
8
|
+
- Unified smart-wallet session metadata with capability policy support
|
|
9
|
+
- High-level execution helpers via `createWalletExecutionController()`
|
|
10
|
+
- Deposit / withdraw controllers with USD1-first defaults
|
|
11
|
+
- **Dynamic token & chain lists** via `MarketDataProvider` (backend-driven, mock included)
|
|
12
|
+
- **Quote / slippage API** — fetch real-time pricing before confirming deposit or withdraw
|
|
13
|
+
- Predicate-market policy adapter for deposit / withdraw / trade flows
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
```bash
|
|
17
|
+
npm install @ab-org/sdk-core @ab-org/predicate-market-sdk
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
```tsx
|
|
22
|
+
import {
|
|
23
|
+
WalletConnector,
|
|
24
|
+
MetaMaskProvider,
|
|
25
|
+
createAccountController,
|
|
26
|
+
createWalletConnectController,
|
|
27
|
+
createWalletExecutionController,
|
|
28
|
+
} from "@ab-org/sdk-core";
|
|
29
|
+
import {
|
|
30
|
+
createDepositController,
|
|
31
|
+
createWithdrawController,
|
|
32
|
+
createMockMarketDataProvider,
|
|
33
|
+
createPredicateMarketPolicyAdapter,
|
|
34
|
+
} from "@ab-org/predicate-market-sdk";
|
|
35
|
+
|
|
36
|
+
const connector = new WalletConnector([
|
|
37
|
+
new MetaMaskProvider(),
|
|
38
|
+
// new CubistSocialProvider(cubistClient)
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const walletConnect = createWalletConnectController({ connector, defaultAdapterId: "metamask" });
|
|
42
|
+
const account = createAccountController();
|
|
43
|
+
const execution = createWalletExecutionController();
|
|
44
|
+
|
|
45
|
+
// Use the mock provider until real backend endpoints are ready
|
|
46
|
+
const marketData = createMockMarketDataProvider();
|
|
47
|
+
|
|
48
|
+
const deposit = createDepositController(custodyAdapter, marketData);
|
|
49
|
+
const withdraw = createWithdrawController(custodyAdapter, marketData);
|
|
50
|
+
const policy = createPredicateMarketPolicyAdapter({
|
|
51
|
+
appId: "prediction-market",
|
|
52
|
+
origin: window.location.origin,
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Connect wallet
|
|
57
|
+
```tsx
|
|
58
|
+
<button onClick={() => (walletConnect.isConnected ? walletConnect.disconnect() : walletConnect.openModal())}>
|
|
59
|
+
{walletConnect.isConnected ? "Disconnect" : "Connect Wallet"}
|
|
60
|
+
</button>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Configure SignInModal
|
|
64
|
+
|
|
65
|
+
The SDK ships with **bundled auth config** (Google client id, Twitter client id, CubeSigner env/org) so you can call `initSDK` with only `signIn` (and optionally `twitterRedirectUri` for your origin). Override via env (`NEXT_PUBLIC_*`) or by passing options to `initSDK`.
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { initSDK, SignInModal } from "@ab-org/predicate-market-sdk";
|
|
69
|
+
|
|
70
|
+
initSDK({
|
|
71
|
+
// optional: override redirect for Twitter (origin-dependent)
|
|
72
|
+
twitterRedirectUri: typeof window !== "undefined" ? `${window.location.origin}/auth/twitter-callback` : undefined,
|
|
73
|
+
signIn: {
|
|
74
|
+
socialProviders: [
|
|
75
|
+
{ id: "google", label: "Continue with Google" },
|
|
76
|
+
{ id: "x", label: "Continue with X" },
|
|
77
|
+
],
|
|
78
|
+
wallets: [
|
|
79
|
+
{ id: "metamask", name: "MetaMask" },
|
|
80
|
+
{ id: "okx", name: "OKX Wallet" },
|
|
81
|
+
{ id: "phantom", name: "Phantom" },
|
|
82
|
+
],
|
|
83
|
+
initialVisibleCount: 4,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
<SignInModal />;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Rules:
|
|
91
|
+
- `socialProviders: undefined` uses built-in defaults (`google`, `x`)
|
|
92
|
+
- `socialProviders: []` hides all social buttons
|
|
93
|
+
- known social ids like `google` and `x` automatically reuse built-in icons unless you override `icon`
|
|
94
|
+
- `wallets: undefined` uses the built-in wallet registry
|
|
95
|
+
- `wallets: []` hides all wallet buttons
|
|
96
|
+
- known wallet ids automatically reuse built-in metadata like `installUrl` and detected `installed` state unless you override them
|
|
97
|
+
- component props still override `initSDK({ signIn })` on a per-modal basis
|
|
98
|
+
|
|
99
|
+
## Address & balance
|
|
100
|
+
```tsx
|
|
101
|
+
if (!account.isConnected) return <p>Please connect wallet</p>;
|
|
102
|
+
return (
|
|
103
|
+
<div>
|
|
104
|
+
Address: {account.address?.slice(0, 6)}...{account.address?.slice(-4)}
|
|
105
|
+
<br />
|
|
106
|
+
Balance: {account.balance?.formatted} {account.balance?.symbol}
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Market data — token / chain lists & quotes
|
|
112
|
+
|
|
113
|
+
Both `deposit` and `withdraw` controllers expose market-data helpers
|
|
114
|
+
that delegate to the injected `MarketDataProvider`:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
// 1. Fetch available tokens
|
|
118
|
+
const tokens = await deposit.fetchTokens();
|
|
119
|
+
// → [{ symbol: "USDT", name: "Tether", decimals: 6, … }, …]
|
|
120
|
+
|
|
121
|
+
// 2. After the user picks a token, fetch the chains it supports
|
|
122
|
+
const chains = await deposit.fetchChains("USDT");
|
|
123
|
+
// → [{ id: "ETH", name: "Ethereum", estimatedTime: "~15 min" }, …]
|
|
124
|
+
|
|
125
|
+
// 3. After user picks token + chain, fetch the deposit address (backend-provided)
|
|
126
|
+
const { address, minimumDeposit } = await deposit.fetchDepositAddress("USDT", "ETH");
|
|
127
|
+
// QR code is generated client-side from the address — no backend QR needed
|
|
128
|
+
|
|
129
|
+
// 4. After user enters amount, fetch a quote
|
|
130
|
+
const quote = await deposit.fetchQuote("USDT", "ETH", "100");
|
|
131
|
+
// → { quoteId, estimatedAmount, slippage, fee, feeToken, exchangeRate, expiresAt }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The same token / chain / quote API is available on the withdraw controller:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const tokens = await withdraw.fetchTokens();
|
|
138
|
+
const chains = await withdraw.fetchChains("USDT");
|
|
139
|
+
const quote = await withdraw.fetchQuote("USDT", "ETH", "200");
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Implementing a real `MarketDataProvider`
|
|
143
|
+
|
|
144
|
+
Replace `createMockMarketDataProvider()` with your own implementation:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import type { MarketDataProvider } from "@ab-org/predicate-market-sdk";
|
|
148
|
+
|
|
149
|
+
const realMarketData: MarketDataProvider = {
|
|
150
|
+
async getSupportedTokens(direction) {
|
|
151
|
+
const res = await fetch(`/api/market/tokens?direction=${direction}`);
|
|
152
|
+
return res.json();
|
|
153
|
+
},
|
|
154
|
+
async getSupportedChains(token, direction) {
|
|
155
|
+
const res = await fetch(`/api/market/chains?token=${token}&direction=${direction}`);
|
|
156
|
+
return res.json();
|
|
157
|
+
},
|
|
158
|
+
async getQuote(request) {
|
|
159
|
+
const res = await fetch("/api/market/quote", {
|
|
160
|
+
method: "POST",
|
|
161
|
+
body: JSON.stringify(request),
|
|
162
|
+
});
|
|
163
|
+
return res.json();
|
|
164
|
+
},
|
|
165
|
+
async getDepositAddress(token, chain) {
|
|
166
|
+
const res = await fetch(`/api/market/deposit-address?token=${token}&chain=${chain}`);
|
|
167
|
+
return res.json();
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Deposit modal
|
|
173
|
+
```tsx
|
|
174
|
+
<button
|
|
175
|
+
onClick={() =>
|
|
176
|
+
deposit.open({
|
|
177
|
+
preferredToken: "USDC",
|
|
178
|
+
preferredChain: "ETH",
|
|
179
|
+
onStatusChange: (status) => console.log("deposit status", status),
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
>
|
|
183
|
+
Deposit
|
|
184
|
+
</button>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Smart-wallet session model
|
|
188
|
+
Social login and injected wallets now converge on the same `WalletSession` shape.
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
const account = createAccountController();
|
|
192
|
+
|
|
193
|
+
console.log(account.session?.walletType); // "injected" | "smart"
|
|
194
|
+
console.log(account.chainContext?.walletChainDescriptor.label);
|
|
195
|
+
console.log(account.capabilities); // provider capability matrix
|
|
196
|
+
console.log(account.capabilityPolicy); // optional scoped session policy
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Trading helpers
|
|
200
|
+
```ts
|
|
201
|
+
import { encodeFunctionData, erc20Abi } from "viem";
|
|
202
|
+
|
|
203
|
+
async function approveSpender(spender: string, value: bigint) {
|
|
204
|
+
if (!account.address) return;
|
|
205
|
+
|
|
206
|
+
const callData = encodeFunctionData({
|
|
207
|
+
abi: erc20Abi,
|
|
208
|
+
functionName: "approve",
|
|
209
|
+
args: [spender, value],
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await execution.sendTransaction({
|
|
213
|
+
from: account.address,
|
|
214
|
+
to: tokenAddress,
|
|
215
|
+
data: callData,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Use `execution.signTransaction()`, `execution.signMessage()`, and `execution.signTypedData()` instead of building raw EIP-1193 requests in app code.
|
|
221
|
+
|
|
222
|
+
## Capability policies
|
|
223
|
+
```ts
|
|
224
|
+
const depositPolicy = policy.deposit("USDC", "ETH", "1000000");
|
|
225
|
+
const withdrawPolicy = policy.withdraw("USD1", "AB_CORE", "500000");
|
|
226
|
+
const tradePolicy = policy.trade("ETH", ["eth_sendTransaction", "eth_signTypedData_v4"]);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
These policies can be passed into your smart-wallet authorization flow so app actions stay scoped by chain, method, token, and amount.
|
|
230
|
+
|
|
231
|
+
## Withdraw modal
|
|
232
|
+
```tsx
|
|
233
|
+
<button
|
|
234
|
+
onClick={() =>
|
|
235
|
+
withdraw.open({
|
|
236
|
+
defaultToken: "USD1",
|
|
237
|
+
defaultChain: "AB_CORE",
|
|
238
|
+
targetAddress: "0x...",
|
|
239
|
+
defaultAmount: "100",
|
|
240
|
+
onStatusChange: (status) => console.log("withdraw", status),
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
>
|
|
244
|
+
Withdraw
|
|
245
|
+
</button>
|
|
246
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type WalletSession } from "@ab-org/sdk-core";
|
|
2
|
+
/**
|
|
3
|
+
* Attempt to silently reconnect a wallet whose session was restored
|
|
4
|
+
* from localStorage cache. Creates a temporary `WalletConnector` with
|
|
5
|
+
* the default adapter registry (injected wallets + Cubist if configured)
|
|
6
|
+
* and delegates to `WalletConnector.tryAutoReconnect()`.
|
|
7
|
+
*
|
|
8
|
+
* Safe to call multiple times — concurrent calls share the same promise.
|
|
9
|
+
* Returns the fresh session on success, or `null` if reconnection failed.
|
|
10
|
+
*/
|
|
11
|
+
export declare function tryAutoReconnect(): Promise<WalletSession | null>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { sessionStore, WalletConnector, createDefaultInjectedWalletRegistry, CubistSocialProvider, } from "@ab-org/sdk-core";
|
|
2
|
+
import { getSDKConfig } from "./config.js";
|
|
3
|
+
let pending = null;
|
|
4
|
+
/**
|
|
5
|
+
* Attempt to silently reconnect a wallet whose session was restored
|
|
6
|
+
* from localStorage cache. Creates a temporary `WalletConnector` with
|
|
7
|
+
* the default adapter registry (injected wallets + Cubist if configured)
|
|
8
|
+
* and delegates to `WalletConnector.tryAutoReconnect()`.
|
|
9
|
+
*
|
|
10
|
+
* Safe to call multiple times — concurrent calls share the same promise.
|
|
11
|
+
* Returns the fresh session on success, or `null` if reconnection failed.
|
|
12
|
+
*/
|
|
13
|
+
export function tryAutoReconnect() {
|
|
14
|
+
const session = sessionStore.getState().session;
|
|
15
|
+
if (!session)
|
|
16
|
+
return Promise.resolve(null);
|
|
17
|
+
if (pending)
|
|
18
|
+
return pending;
|
|
19
|
+
pending = doAutoReconnect().finally(() => {
|
|
20
|
+
pending = null;
|
|
21
|
+
});
|
|
22
|
+
return pending;
|
|
23
|
+
}
|
|
24
|
+
async function doAutoReconnect() {
|
|
25
|
+
const config = getSDKConfig();
|
|
26
|
+
const registry = createDefaultInjectedWalletRegistry();
|
|
27
|
+
const adapters = [...registry.map((r) => r.provider)];
|
|
28
|
+
if (config.cubeSigner) {
|
|
29
|
+
adapters.push(new CubistSocialProvider({
|
|
30
|
+
...config.cubeSigner,
|
|
31
|
+
defaultSessionPolicy: config.signIn?.sessionPolicy ?? config.cubeSigner.defaultSessionPolicy,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
const connector = new WalletConnector(adapters);
|
|
35
|
+
return connector.tryAutoReconnect();
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getEnv } from "../utils/env.js";
|
|
2
|
+
function readOptionalEnv(key) {
|
|
3
|
+
const value = getEnv(key);
|
|
4
|
+
return value === "" ? undefined : value;
|
|
5
|
+
}
|
|
6
|
+
const relayOrigin = readOptionalEnv("RELAY_ORIGIN");
|
|
7
|
+
const cubeEnv = readOptionalEnv("CUBE_SIGNER_ENV");
|
|
8
|
+
const cubeOrgId = readOptionalEnv("CUBE_SIGNER_ORG_ID");
|
|
9
|
+
export const BUNDLED_AUTH_CONFIG = {
|
|
10
|
+
googleClientId: readOptionalEnv("GOOGLE_CLIENT_ID"),
|
|
11
|
+
twitterClientId: readOptionalEnv("X_CLIENT_ID"),
|
|
12
|
+
twitterRedirectUri: relayOrigin ? `${relayOrigin}/auth/twitter-callback` : undefined,
|
|
13
|
+
cubeSigner: cubeEnv && cubeOrgId
|
|
14
|
+
? {
|
|
15
|
+
env: cubeEnv,
|
|
16
|
+
orgId: cubeOrgId,
|
|
17
|
+
}
|
|
18
|
+
: undefined,
|
|
19
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CubeSignerConfig } from "@ab-org/sdk-core";
|
|
2
|
+
import type { SignInUiConfig } from "../ui/signInTypes.js";
|
|
3
|
+
export interface SDKConfig {
|
|
4
|
+
googleClientId?: string;
|
|
5
|
+
twitterClientId?: string;
|
|
6
|
+
twitterRedirectUri?: string;
|
|
7
|
+
cubeSigner?: CubeSignerConfig;
|
|
8
|
+
signIn?: SignInUiConfig;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Fixed auth config: built-in bundled defaults (see bundledConfig.ts) with optional
|
|
12
|
+
* overrides from environment (server / `NEXT_PUBLIC_*` client). Used as defaults in initSDK.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getFixedAuthConfig(): Partial<SDKConfig>;
|
|
15
|
+
/**
|
|
16
|
+
* Config for initializing the predicate SDK. Defaults come from bundled config
|
|
17
|
+
* (and env overrides); you may override any of them here. Optional top-level
|
|
18
|
+
* `registerUser` is merged into `cubeSigner.oidcLoginHooks` when cubeSigner is present.
|
|
19
|
+
*/
|
|
20
|
+
export interface PredicateSDKConfig extends Partial<SDKConfig> {
|
|
21
|
+
/**
|
|
22
|
+
* Optional hook to register the user on your backend when CubeSigner proof
|
|
23
|
+
* indicates the user is not initialized. Used when cubeSigner is available
|
|
24
|
+
* (from env or override).
|
|
25
|
+
*/
|
|
26
|
+
registerUser?: (oidcToken: string) => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export declare function initSDK(config?: PredicateSDKConfig): void;
|
|
29
|
+
export declare function getSDKConfig(): Readonly<SDKConfig>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { getEnv } from "../utils/env.js";
|
|
2
|
+
import { BUNDLED_AUTH_CONFIG } from "./bundledConfig.js";
|
|
3
|
+
import { tryAutoReconnect } from "./autoReconnect.js";
|
|
4
|
+
let sdkConfig = {};
|
|
5
|
+
function readOptionalEnv(key) {
|
|
6
|
+
const value = getEnv(key);
|
|
7
|
+
return value === "" ? undefined : value;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Fixed auth config: built-in bundled defaults (see bundledConfig.ts) with optional
|
|
11
|
+
* overrides from environment (server / `NEXT_PUBLIC_*` client). Used as defaults in initSDK.
|
|
12
|
+
*/
|
|
13
|
+
export function getFixedAuthConfig() {
|
|
14
|
+
const envGoogle = readOptionalEnv("GOOGLE_CLIENT_ID");
|
|
15
|
+
const envTwitter = readOptionalEnv("X_CLIENT_ID");
|
|
16
|
+
const relayOrigin = readOptionalEnv("RELAY_ORIGIN");
|
|
17
|
+
const envRedirect = relayOrigin ? `${relayOrigin}/auth/twitter-callback` : undefined;
|
|
18
|
+
const cubeEnv = readOptionalEnv("CUBE_SIGNER_ENV");
|
|
19
|
+
const cubeOrgId = readOptionalEnv("CUBE_SIGNER_ORG_ID");
|
|
20
|
+
const cubeSignerFromEnv = cubeEnv && cubeOrgId
|
|
21
|
+
? { env: cubeEnv, orgId: cubeOrgId }
|
|
22
|
+
: undefined;
|
|
23
|
+
return {
|
|
24
|
+
googleClientId: envGoogle || BUNDLED_AUTH_CONFIG.googleClientId,
|
|
25
|
+
twitterClientId: envTwitter || BUNDLED_AUTH_CONFIG.twitterClientId,
|
|
26
|
+
twitterRedirectUri: envRedirect || BUNDLED_AUTH_CONFIG.twitterRedirectUri,
|
|
27
|
+
cubeSigner: cubeSignerFromEnv ?? BUNDLED_AUTH_CONFIG.cubeSigner,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function initSDK(config = {}) {
|
|
31
|
+
const { registerUser, ...rest } = config;
|
|
32
|
+
const fixed = getFixedAuthConfig();
|
|
33
|
+
const merged = {
|
|
34
|
+
...sdkConfig,
|
|
35
|
+
...fixed,
|
|
36
|
+
...rest,
|
|
37
|
+
cubeSigner: rest.cubeSigner ?? fixed.cubeSigner,
|
|
38
|
+
};
|
|
39
|
+
if (registerUser != null && merged.cubeSigner) {
|
|
40
|
+
merged.cubeSigner = {
|
|
41
|
+
...merged.cubeSigner,
|
|
42
|
+
oidcLoginHooks: {
|
|
43
|
+
...merged.cubeSigner.oidcLoginHooks,
|
|
44
|
+
registerUser,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
sdkConfig = merged;
|
|
49
|
+
tryAutoReconnect().catch(() => { });
|
|
50
|
+
}
|
|
51
|
+
export function getSDKConfig() {
|
|
52
|
+
return sdkConfig;
|
|
53
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface Window {
|
|
3
|
+
google?: {
|
|
4
|
+
accounts: {
|
|
5
|
+
id: {
|
|
6
|
+
initialize(config: GISInitConfig): void;
|
|
7
|
+
prompt(momentListener?: (notification: GISPromptMoment) => void): void;
|
|
8
|
+
renderButton(parent: HTMLElement, options: GISButtonConfig): void;
|
|
9
|
+
revoke(hint: string, callback?: () => void): void;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
interface GISInitConfig {
|
|
16
|
+
client_id: string;
|
|
17
|
+
callback: (response: GISCredentialResponse) => void;
|
|
18
|
+
auto_select?: boolean;
|
|
19
|
+
cancel_on_tap_outside?: boolean;
|
|
20
|
+
use_fedcm_for_prompt?: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface GISCredentialResponse {
|
|
23
|
+
credential: string;
|
|
24
|
+
}
|
|
25
|
+
interface GISPromptMoment {
|
|
26
|
+
isSkippedMoment(): boolean;
|
|
27
|
+
isDismissedMoment(): boolean;
|
|
28
|
+
isNotDisplayed(): boolean;
|
|
29
|
+
getDismissedReason(): string;
|
|
30
|
+
}
|
|
31
|
+
interface GISButtonConfig {
|
|
32
|
+
type?: "standard" | "icon";
|
|
33
|
+
size?: "large" | "medium" | "small";
|
|
34
|
+
}
|
|
35
|
+
export interface GoogleCredential {
|
|
36
|
+
idToken: string;
|
|
37
|
+
email?: string;
|
|
38
|
+
name?: string;
|
|
39
|
+
picture?: string;
|
|
40
|
+
}
|
|
41
|
+
export declare function isFedCMSupported(): boolean;
|
|
42
|
+
export declare function signInWithGoogle(clientId: string): Promise<GoogleCredential>;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const GIS_URL = "https://accounts.google.com/gsi/client";
|
|
2
|
+
let loadPromise = null;
|
|
3
|
+
let currentCredentialHandler = null;
|
|
4
|
+
let initializedKey = null;
|
|
5
|
+
function loadGIS() {
|
|
6
|
+
if (loadPromise)
|
|
7
|
+
return loadPromise;
|
|
8
|
+
if (window.google?.accounts?.id) {
|
|
9
|
+
loadPromise = Promise.resolve();
|
|
10
|
+
return loadPromise;
|
|
11
|
+
}
|
|
12
|
+
loadPromise = new Promise((resolve, reject) => {
|
|
13
|
+
const script = document.createElement("script");
|
|
14
|
+
script.src = GIS_URL;
|
|
15
|
+
script.async = true;
|
|
16
|
+
script.defer = true;
|
|
17
|
+
script.onload = () => resolve();
|
|
18
|
+
script.onerror = () => reject(new Error("Failed to load Google Identity Services"));
|
|
19
|
+
document.head.appendChild(script);
|
|
20
|
+
});
|
|
21
|
+
return loadPromise;
|
|
22
|
+
}
|
|
23
|
+
function decodeJwtPayload(token) {
|
|
24
|
+
const parts = token.split(".");
|
|
25
|
+
if (parts.length !== 3)
|
|
26
|
+
throw new Error("Invalid JWT");
|
|
27
|
+
const payload = parts[1];
|
|
28
|
+
if (!payload)
|
|
29
|
+
throw new Error("Invalid JWT payload");
|
|
30
|
+
let padded = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
31
|
+
while (padded.length % 4 !== 0)
|
|
32
|
+
padded += "=";
|
|
33
|
+
return JSON.parse(atob(padded));
|
|
34
|
+
}
|
|
35
|
+
export function isFedCMSupported() {
|
|
36
|
+
if (typeof window === "undefined" || typeof navigator === "undefined")
|
|
37
|
+
return false;
|
|
38
|
+
if (!window.isSecureContext)
|
|
39
|
+
return false;
|
|
40
|
+
return typeof navigator.credentials?.get === "function";
|
|
41
|
+
}
|
|
42
|
+
export async function signInWithGoogle(clientId) {
|
|
43
|
+
await loadGIS();
|
|
44
|
+
const gid = window.google?.accounts?.id;
|
|
45
|
+
if (!gid)
|
|
46
|
+
throw new Error("Google Identity Services not available");
|
|
47
|
+
const SIGN_IN_TIMEOUT_MS = 90000;
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
let settled = false;
|
|
50
|
+
let fallbackContainer = null;
|
|
51
|
+
const cleanupFallback = () => {
|
|
52
|
+
if (!fallbackContainer)
|
|
53
|
+
return;
|
|
54
|
+
fallbackContainer.remove();
|
|
55
|
+
fallbackContainer = null;
|
|
56
|
+
};
|
|
57
|
+
const finish = (fn) => {
|
|
58
|
+
if (settled)
|
|
59
|
+
return;
|
|
60
|
+
settled = true;
|
|
61
|
+
clearTimeout(timeoutId);
|
|
62
|
+
cleanupFallback();
|
|
63
|
+
fn();
|
|
64
|
+
};
|
|
65
|
+
const timeoutId = setTimeout(() => {
|
|
66
|
+
finish(() => reject(new Error("Google sign-in timed out")));
|
|
67
|
+
}, SIGN_IN_TIMEOUT_MS);
|
|
68
|
+
const handleCredential = (response) => {
|
|
69
|
+
finish(() => {
|
|
70
|
+
try {
|
|
71
|
+
const payload = decodeJwtPayload(response.credential);
|
|
72
|
+
resolve({
|
|
73
|
+
idToken: response.credential,
|
|
74
|
+
email: payload.email,
|
|
75
|
+
name: payload.name,
|
|
76
|
+
picture: payload.picture,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
resolve({ idToken: response.credential });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
const useFedcmForPrompt = isFedCMSupported();
|
|
85
|
+
const initKey = `${clientId}\0${useFedcmForPrompt ? "1" : "0"}`;
|
|
86
|
+
currentCredentialHandler = handleCredential;
|
|
87
|
+
if (initializedKey !== initKey) {
|
|
88
|
+
initializedKey = initKey;
|
|
89
|
+
gid.initialize({
|
|
90
|
+
client_id: clientId,
|
|
91
|
+
callback: (response) => currentCredentialHandler?.(response),
|
|
92
|
+
auto_select: false,
|
|
93
|
+
cancel_on_tap_outside: true,
|
|
94
|
+
use_fedcm_for_prompt: useFedcmForPrompt,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const triggerFallback = () => {
|
|
98
|
+
if (settled)
|
|
99
|
+
return;
|
|
100
|
+
fallbackContainer = document.createElement("div");
|
|
101
|
+
Object.assign(fallbackContainer.style, {
|
|
102
|
+
position: "fixed",
|
|
103
|
+
top: "-9999px",
|
|
104
|
+
left: "-9999px",
|
|
105
|
+
opacity: "0",
|
|
106
|
+
pointerEvents: "none",
|
|
107
|
+
});
|
|
108
|
+
document.body.appendChild(fallbackContainer);
|
|
109
|
+
gid.renderButton(fallbackContainer, { type: "standard", size: "large" });
|
|
110
|
+
const tryClickButton = (attemptsLeft) => {
|
|
111
|
+
requestAnimationFrame(() => {
|
|
112
|
+
const btn = fallbackContainer?.querySelector('[role="button"]') ??
|
|
113
|
+
fallbackContainer?.querySelector("div[style]");
|
|
114
|
+
if (btn) {
|
|
115
|
+
Object.assign(fallbackContainer.style, { pointerEvents: "auto" });
|
|
116
|
+
btn.click();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (attemptsLeft > 0) {
|
|
120
|
+
tryClickButton(attemptsLeft - 1);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
finish(() => reject(new Error("Google sign-in unavailable")));
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
tryClickButton(8);
|
|
127
|
+
};
|
|
128
|
+
gid.prompt((moment) => {
|
|
129
|
+
if (settled)
|
|
130
|
+
return;
|
|
131
|
+
if (moment.isDismissedMoment()) {
|
|
132
|
+
const reason = moment.getDismissedReason();
|
|
133
|
+
if (reason === "credential_returned")
|
|
134
|
+
return;
|
|
135
|
+
finish(() => reject(new Error(`Google sign-in dismissed: ${reason}`)));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (moment.isSkippedMoment()) {
|
|
139
|
+
triggerFallback();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (moment.isNotDisplayed()) {
|
|
143
|
+
triggerFallback();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|