@edge-markets/connect-node 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +314 -0
- package/dist/index.d.mts +387 -0
- package/dist/index.d.ts +387 -0
- package/dist/index.js +543 -0
- package/dist/index.mjs +528 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# @edgeboost/edge-connect-server
|
|
2
|
+
|
|
3
|
+
Server SDK for EDGE Connect token exchange and API calls.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 **Secure token exchange** - Exchange codes for tokens with PKCE
|
|
8
|
+
- 🔄 **Token refresh** - Automatic refresh token handling
|
|
9
|
+
- 📡 **Full API client** - User, balance, transfers
|
|
10
|
+
- 🛡️ **Typed errors** - Specific error classes for each scenario
|
|
11
|
+
- 📝 **TypeScript first** - Complete type definitions
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @edgeboost/edge-connect-server
|
|
17
|
+
# or
|
|
18
|
+
pnpm add @edgeboost/edge-connect-server
|
|
19
|
+
# or
|
|
20
|
+
yarn add @edgeboost/edge-connect-server
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { EdgeConnectServer } from '@edgeboost/edge-connect-server'
|
|
27
|
+
|
|
28
|
+
// Create instance once (reuse for all requests)
|
|
29
|
+
const edge = new EdgeConnectServer({
|
|
30
|
+
clientId: process.env.EDGE_CLIENT_ID!,
|
|
31
|
+
clientSecret: process.env.EDGE_CLIENT_SECRET!,
|
|
32
|
+
environment: 'staging',
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Exchange code from EdgeLink for tokens
|
|
36
|
+
const tokens = await edge.exchangeCode(code, codeVerifier)
|
|
37
|
+
|
|
38
|
+
// Make API calls
|
|
39
|
+
const user = await edge.getUser(tokens.accessToken)
|
|
40
|
+
const balance = await edge.getBalance(tokens.accessToken)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## ⚠️ Security
|
|
44
|
+
|
|
45
|
+
**This SDK requires your client secret. Use it ONLY on your backend server!**
|
|
46
|
+
|
|
47
|
+
Never expose your client secret to browsers or client-side code.
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
interface EdgeConnectServerConfig {
|
|
53
|
+
clientId: string // Your OAuth client ID
|
|
54
|
+
clientSecret: string // Your OAuth client secret (keep secret!)
|
|
55
|
+
environment: EdgeEnvironment // 'production' | 'staging' | 'sandbox'
|
|
56
|
+
|
|
57
|
+
// Optional
|
|
58
|
+
apiBaseUrl?: string // Custom API URL (dev only)
|
|
59
|
+
authDomain?: string // Custom Cognito domain (dev only)
|
|
60
|
+
timeout?: number // Request timeout (default: 30000ms)
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Token Exchange
|
|
65
|
+
|
|
66
|
+
After EdgeLink completes, exchange the code for tokens:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// In your /api/edge/exchange endpoint
|
|
70
|
+
export async function POST(req: Request) {
|
|
71
|
+
const { code, codeVerifier } = await req.json()
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const tokens = await edge.exchangeCode(code, codeVerifier)
|
|
75
|
+
|
|
76
|
+
// Store tokens securely (encrypted in database)
|
|
77
|
+
await db.edgeConnections.upsert({
|
|
78
|
+
userId: req.user.id,
|
|
79
|
+
accessToken: encrypt(tokens.accessToken),
|
|
80
|
+
refreshToken: encrypt(tokens.refreshToken),
|
|
81
|
+
expiresAt: new Date(tokens.expiresAt),
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
return Response.json({ success: true })
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error instanceof EdgeTokenExchangeError) {
|
|
87
|
+
// Code expired or already used
|
|
88
|
+
return Response.json({ error: 'Please try again' }, { status: 400 })
|
|
89
|
+
}
|
|
90
|
+
throw error
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Token Refresh
|
|
96
|
+
|
|
97
|
+
Refresh tokens before they expire:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
async function getValidAccessToken(userId: string): Promise<string> {
|
|
101
|
+
const connection = await db.edgeConnections.get(userId)
|
|
102
|
+
|
|
103
|
+
// Refresh 5 minutes before expiry
|
|
104
|
+
const BUFFER = 5 * 60 * 1000
|
|
105
|
+
|
|
106
|
+
if (Date.now() > connection.expiresAt.getTime() - BUFFER) {
|
|
107
|
+
const newTokens = await edge.refreshTokens(
|
|
108
|
+
decrypt(connection.refreshToken)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
await db.edgeConnections.update(userId, {
|
|
112
|
+
accessToken: encrypt(newTokens.accessToken),
|
|
113
|
+
refreshToken: encrypt(newTokens.refreshToken),
|
|
114
|
+
expiresAt: new Date(newTokens.expiresAt),
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
return newTokens.accessToken
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return decrypt(connection.accessToken)
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## API Methods
|
|
125
|
+
|
|
126
|
+
### User & Balance
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// Get user profile
|
|
130
|
+
const user = await edge.getUser(accessToken)
|
|
131
|
+
// Returns: { id, email, firstName, lastName, createdAt }
|
|
132
|
+
|
|
133
|
+
// Get balance
|
|
134
|
+
const balance = await edge.getBalance(accessToken)
|
|
135
|
+
// Returns: { userId, availableBalance, currency, asOf }
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Transfers
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// Initiate transfer (requires OTP verification)
|
|
142
|
+
const transfer = await edge.initiateTransfer(accessToken, {
|
|
143
|
+
type: 'debit', // 'debit' = pull from user, 'credit' = push to user
|
|
144
|
+
amount: '100.00',
|
|
145
|
+
idempotencyKey: `txn_${userId}_${Date.now()}`,
|
|
146
|
+
})
|
|
147
|
+
// Returns: { transferId, status: 'pending_verification', otpMethod }
|
|
148
|
+
|
|
149
|
+
// Verify with OTP
|
|
150
|
+
const result = await edge.verifyTransfer(accessToken, transfer.transferId, userOtp)
|
|
151
|
+
// Returns: { transferId, status: 'completed' | 'failed' }
|
|
152
|
+
|
|
153
|
+
// Get transfer status
|
|
154
|
+
const status = await edge.getTransfer(accessToken, transferId)
|
|
155
|
+
|
|
156
|
+
// List transfers
|
|
157
|
+
const { transfers, total } = await edge.listTransfers(accessToken, {
|
|
158
|
+
status: 'completed',
|
|
159
|
+
limit: 10,
|
|
160
|
+
offset: 0,
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Consent
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// Revoke consent (disconnect user)
|
|
168
|
+
await edge.revokeConsent(accessToken)
|
|
169
|
+
|
|
170
|
+
// Clean up stored tokens
|
|
171
|
+
await db.edgeConnections.delete(userId)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Error Handling
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import {
|
|
178
|
+
EdgeError,
|
|
179
|
+
EdgeAuthenticationError,
|
|
180
|
+
EdgeTokenExchangeError,
|
|
181
|
+
EdgeConsentRequiredError,
|
|
182
|
+
isEdgeError,
|
|
183
|
+
} from '@edgeboost/edge-connect-server'
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const balance = await edge.getBalance(accessToken)
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (error instanceof EdgeAuthenticationError) {
|
|
189
|
+
// Token expired - try refresh or reconnect
|
|
190
|
+
return { error: 'session_expired' }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (error instanceof EdgeConsentRequiredError) {
|
|
194
|
+
// User revoked consent - need to reconnect
|
|
195
|
+
return { error: 'reconnect_required' }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (isEdgeError(error)) {
|
|
199
|
+
// Some other SDK error
|
|
200
|
+
console.error(`Edge Error [${error.code}]: ${error.message}`)
|
|
201
|
+
return { error: error.code }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Unknown error
|
|
205
|
+
throw error
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Error Types
|
|
210
|
+
|
|
211
|
+
| Error | When | What to do |
|
|
212
|
+
|-------|------|------------|
|
|
213
|
+
| `EdgeAuthenticationError` | Token invalid/expired | Refresh token or reconnect |
|
|
214
|
+
| `EdgeTokenExchangeError` | Code exchange failed | Ask user to try again |
|
|
215
|
+
| `EdgeConsentRequiredError` | User hasn't granted consent | Open EdgeLink |
|
|
216
|
+
| `EdgeInsufficientScopeError` | Missing required scopes | Request more scopes |
|
|
217
|
+
| `EdgeNotFoundError` | Resource not found | Check ID |
|
|
218
|
+
| `EdgeApiError` | Other API error | Check error.code |
|
|
219
|
+
| `EdgeNetworkError` | Network failure | Retry request |
|
|
220
|
+
|
|
221
|
+
## NestJS Example
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { Injectable, Logger } from '@nestjs/common'
|
|
225
|
+
import { ConfigService } from '@nestjs/config'
|
|
226
|
+
import { EdgeConnectServer, EdgeConsentRequiredError } from '@edgeboost/edge-connect-server'
|
|
227
|
+
|
|
228
|
+
@Injectable()
|
|
229
|
+
export class EdgeService {
|
|
230
|
+
private readonly edge: EdgeConnectServer
|
|
231
|
+
private readonly logger = new Logger(EdgeService.name)
|
|
232
|
+
|
|
233
|
+
constructor(private config: ConfigService) {
|
|
234
|
+
this.edge = new EdgeConnectServer({
|
|
235
|
+
clientId: this.config.getOrThrow('EDGE_CLIENT_ID'),
|
|
236
|
+
clientSecret: this.config.getOrThrow('EDGE_CLIENT_SECRET'),
|
|
237
|
+
environment: this.config.get('EDGE_ENVIRONMENT', 'staging'),
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async exchangeCode(code: string, codeVerifier: string) {
|
|
242
|
+
return this.edge.exchangeCode(code, codeVerifier)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async getBalance(accessToken: string) {
|
|
246
|
+
try {
|
|
247
|
+
return await this.edge.getBalance(accessToken)
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (error instanceof EdgeConsentRequiredError) {
|
|
250
|
+
this.logger.warn('User consent required')
|
|
251
|
+
throw error
|
|
252
|
+
}
|
|
253
|
+
this.logger.error('Failed to get balance', error)
|
|
254
|
+
throw error
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Express Example
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import express from 'express'
|
|
264
|
+
import { EdgeConnectServer, isEdgeError } from '@edgeboost/edge-connect-server'
|
|
265
|
+
|
|
266
|
+
const edge = new EdgeConnectServer({
|
|
267
|
+
clientId: process.env.EDGE_CLIENT_ID!,
|
|
268
|
+
clientSecret: process.env.EDGE_CLIENT_SECRET!,
|
|
269
|
+
environment: 'staging',
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
const app = express()
|
|
273
|
+
app.use(express.json())
|
|
274
|
+
|
|
275
|
+
// Exchange code for tokens
|
|
276
|
+
app.post('/api/edge/exchange', async (req, res) => {
|
|
277
|
+
try {
|
|
278
|
+
const { code, codeVerifier } = req.body
|
|
279
|
+
const tokens = await edge.exchangeCode(code, codeVerifier)
|
|
280
|
+
|
|
281
|
+
// Store tokens for user...
|
|
282
|
+
|
|
283
|
+
res.json({ success: true })
|
|
284
|
+
} catch (error) {
|
|
285
|
+
if (isEdgeError(error)) {
|
|
286
|
+
res.status(400).json({ error: error.code, message: error.message })
|
|
287
|
+
} else {
|
|
288
|
+
res.status(500).json({ error: 'internal_error' })
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
// Get balance
|
|
294
|
+
app.get('/api/edge/balance', async (req, res) => {
|
|
295
|
+
try {
|
|
296
|
+
const accessToken = await getAccessTokenForUser(req.user.id)
|
|
297
|
+
const balance = await edge.getBalance(accessToken)
|
|
298
|
+
res.json(balance)
|
|
299
|
+
} catch (error) {
|
|
300
|
+
// Handle errors...
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Related Packages
|
|
306
|
+
|
|
307
|
+
- `@edgeboost/edge-connect-sdk` - Core types and utilities
|
|
308
|
+
- `@edgeboost/edge-connect-link` - Browser SDK for popup authentication
|
|
309
|
+
|
|
310
|
+
## License
|
|
311
|
+
|
|
312
|
+
MIT
|
|
313
|
+
|
|
314
|
+
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { EdgeEnvironment, EdgeTokens, User, Balance, Transfer, ListTransfersParams, TransferList } from '@edge-markets/connect';
|
|
2
|
+
export { Balance, EdgeApiError, EdgeAuthenticationError, EdgeConsentRequiredError, EdgeEnvironment, EdgeError, EdgeInsufficientScopeError, EdgeNetworkError, EdgeNotFoundError, EdgeTokenExchangeError, EdgeTokens, ListTransfersParams, Transfer, TransferList, TransferListItem, TransferStatus, TransferType, User, getEnvironmentConfig, isApiError, isAuthenticationError, isConsentRequiredError, isEdgeError, isNetworkError, isProductionEnvironment } from '@edge-markets/connect';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* EdgeConnectServer - Server-Side SDK for EDGE Connect
|
|
6
|
+
*
|
|
7
|
+
* This SDK handles operations that require your client secret:
|
|
8
|
+
* - Exchanging authorization codes for tokens
|
|
9
|
+
* - Refreshing access tokens
|
|
10
|
+
* - Making authenticated API calls
|
|
11
|
+
*
|
|
12
|
+
* **Security:** This SDK should ONLY run on your backend server.
|
|
13
|
+
* Never expose your client secret to the browser.
|
|
14
|
+
*
|
|
15
|
+
* @module @edge-markets/connect-node
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { EdgeConnectServer } from '@edge-markets/connect-node'
|
|
20
|
+
*
|
|
21
|
+
* const edge = new EdgeConnectServer({
|
|
22
|
+
* clientId: process.env.EDGE_CLIENT_ID!,
|
|
23
|
+
* clientSecret: process.env.EDGE_CLIENT_SECRET!,
|
|
24
|
+
* environment: 'staging',
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* // Exchange code from EdgeLink for tokens
|
|
28
|
+
* const tokens = await edge.exchangeCode(code, codeVerifier)
|
|
29
|
+
*
|
|
30
|
+
* // Make API calls
|
|
31
|
+
* const user = await edge.getUser(tokens.accessToken)
|
|
32
|
+
* const balance = await edge.getBalance(tokens.accessToken)
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Configuration for EdgeConnectServer.
|
|
38
|
+
*
|
|
39
|
+
* All fields except `environment` are required for production use.
|
|
40
|
+
*/
|
|
41
|
+
interface EdgeConnectServerConfig {
|
|
42
|
+
/**
|
|
43
|
+
* Your OAuth client ID from the EdgeBoost partner portal.
|
|
44
|
+
*/
|
|
45
|
+
clientId: string;
|
|
46
|
+
/**
|
|
47
|
+
* Your OAuth client secret.
|
|
48
|
+
* **Keep this secret!** Never expose in frontend code.
|
|
49
|
+
*/
|
|
50
|
+
clientSecret: string;
|
|
51
|
+
/**
|
|
52
|
+
* Environment to connect to.
|
|
53
|
+
* - `'production'` - Live environment with real money
|
|
54
|
+
* - `'staging'` - Test environment for development
|
|
55
|
+
* - `'sandbox'` - Isolated mock environment (coming soon)
|
|
56
|
+
*/
|
|
57
|
+
environment: EdgeEnvironment;
|
|
58
|
+
/**
|
|
59
|
+
* Custom API base URL (for local development).
|
|
60
|
+
* @default Derived from environment config
|
|
61
|
+
*/
|
|
62
|
+
apiBaseUrl?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Custom Cognito domain (for local development).
|
|
65
|
+
* @default Derived from environment config
|
|
66
|
+
*/
|
|
67
|
+
authDomain?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Request timeout in milliseconds.
|
|
70
|
+
* @default 30000 (30 seconds)
|
|
71
|
+
*/
|
|
72
|
+
timeout?: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Options for initiating a transfer.
|
|
76
|
+
*/
|
|
77
|
+
interface TransferOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Type of transfer.
|
|
80
|
+
* - `'debit'` - Pull funds FROM user's EdgeBoost TO partner
|
|
81
|
+
* - `'credit'` - Push funds FROM partner TO user's EdgeBoost
|
|
82
|
+
*/
|
|
83
|
+
type: 'debit' | 'credit';
|
|
84
|
+
/**
|
|
85
|
+
* Amount in USD as a string (preserves precision).
|
|
86
|
+
* @example '100.00'
|
|
87
|
+
*/
|
|
88
|
+
amount: string;
|
|
89
|
+
/**
|
|
90
|
+
* Unique key to prevent duplicate transfers.
|
|
91
|
+
* If a transfer with this key exists, its current status is returned.
|
|
92
|
+
*
|
|
93
|
+
* @example `txn_${userId}_${Date.now()}`
|
|
94
|
+
*/
|
|
95
|
+
idempotencyKey: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Server-side SDK for EDGE Connect.
|
|
99
|
+
*
|
|
100
|
+
* Handles token exchange and API calls that require your client secret.
|
|
101
|
+
* Create one instance and reuse it for all requests.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* // Create instance (do once, e.g., in module scope)
|
|
106
|
+
* const edge = new EdgeConnectServer({
|
|
107
|
+
* clientId: process.env.EDGE_CLIENT_ID!,
|
|
108
|
+
* clientSecret: process.env.EDGE_CLIENT_SECRET!,
|
|
109
|
+
* environment: 'staging',
|
|
110
|
+
* })
|
|
111
|
+
*
|
|
112
|
+
* // In your API route handler
|
|
113
|
+
* export async function POST(req: Request) {
|
|
114
|
+
* const { code, codeVerifier } = await req.json()
|
|
115
|
+
*
|
|
116
|
+
* // Exchange code for tokens
|
|
117
|
+
* const tokens = await edge.exchangeCode(code, codeVerifier)
|
|
118
|
+
*
|
|
119
|
+
* // Store tokens securely (e.g., encrypted in database)
|
|
120
|
+
* await saveTokens(userId, tokens)
|
|
121
|
+
*
|
|
122
|
+
* return Response.json({ success: true })
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
declare class EdgeConnectServer {
|
|
127
|
+
private readonly config;
|
|
128
|
+
private readonly apiBaseUrl;
|
|
129
|
+
private readonly oauthBaseUrl;
|
|
130
|
+
private readonly timeout;
|
|
131
|
+
/**
|
|
132
|
+
* Creates a new EdgeConnectServer instance.
|
|
133
|
+
*
|
|
134
|
+
* @param config - Server configuration
|
|
135
|
+
* @throws Error if required config is missing
|
|
136
|
+
*/
|
|
137
|
+
constructor(config: EdgeConnectServerConfig);
|
|
138
|
+
/**
|
|
139
|
+
* Exchanges an authorization code for tokens.
|
|
140
|
+
*
|
|
141
|
+
* Call this after receiving the code from EdgeLink's `onSuccess` callback.
|
|
142
|
+
* The code is single-use and expires in ~10 minutes.
|
|
143
|
+
*
|
|
144
|
+
* @param code - Authorization code from EdgeLink
|
|
145
|
+
* @param codeVerifier - PKCE code verifier from EdgeLink
|
|
146
|
+
* @returns Access token, refresh token, and metadata
|
|
147
|
+
* @throws EdgeTokenExchangeError if exchange fails
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* // In your /api/edge/exchange endpoint
|
|
152
|
+
* const { code, codeVerifier } = req.body
|
|
153
|
+
*
|
|
154
|
+
* try {
|
|
155
|
+
* const tokens = await edge.exchangeCode(code, codeVerifier)
|
|
156
|
+
*
|
|
157
|
+
* // Store tokens securely
|
|
158
|
+
* await db.edgeConnections.upsert({
|
|
159
|
+
* userId: req.user.id,
|
|
160
|
+
* accessToken: encrypt(tokens.accessToken),
|
|
161
|
+
* refreshToken: encrypt(tokens.refreshToken),
|
|
162
|
+
* expiresAt: new Date(tokens.expiresAt),
|
|
163
|
+
* })
|
|
164
|
+
*
|
|
165
|
+
* return { success: true }
|
|
166
|
+
* } catch (error) {
|
|
167
|
+
* if (error instanceof EdgeTokenExchangeError) {
|
|
168
|
+
* // Code expired or already used
|
|
169
|
+
* return { error: 'Please try connecting again' }
|
|
170
|
+
* }
|
|
171
|
+
* throw error
|
|
172
|
+
* }
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
exchangeCode(code: string, codeVerifier: string): Promise<EdgeTokens>;
|
|
176
|
+
/**
|
|
177
|
+
* Refreshes an access token using a refresh token.
|
|
178
|
+
*
|
|
179
|
+
* Call this when the access token is expired or about to expire.
|
|
180
|
+
* Check `tokens.expiresAt` to know when to refresh.
|
|
181
|
+
*
|
|
182
|
+
* @param refreshToken - Refresh token from previous exchange
|
|
183
|
+
* @returns New tokens (refresh token may or may not change)
|
|
184
|
+
* @throws EdgeAuthenticationError if refresh fails
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```typescript
|
|
188
|
+
* // Check if token needs refresh (with 5 minute buffer)
|
|
189
|
+
* const BUFFER_MS = 5 * 60 * 1000
|
|
190
|
+
*
|
|
191
|
+
* async function getValidAccessToken(userId: string): Promise<string> {
|
|
192
|
+
* const connection = await db.edgeConnections.get(userId)
|
|
193
|
+
*
|
|
194
|
+
* if (Date.now() > connection.expiresAt.getTime() - BUFFER_MS) {
|
|
195
|
+
* // Token expired or expiring soon - refresh it
|
|
196
|
+
* const newTokens = await edge.refreshTokens(decrypt(connection.refreshToken))
|
|
197
|
+
*
|
|
198
|
+
* // Update stored tokens
|
|
199
|
+
* await db.edgeConnections.update(userId, {
|
|
200
|
+
* accessToken: encrypt(newTokens.accessToken),
|
|
201
|
+
* refreshToken: encrypt(newTokens.refreshToken),
|
|
202
|
+
* expiresAt: new Date(newTokens.expiresAt),
|
|
203
|
+
* })
|
|
204
|
+
*
|
|
205
|
+
* return newTokens.accessToken
|
|
206
|
+
* }
|
|
207
|
+
*
|
|
208
|
+
* return decrypt(connection.accessToken)
|
|
209
|
+
* }
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
refreshTokens(refreshToken: string): Promise<EdgeTokens>;
|
|
213
|
+
/**
|
|
214
|
+
* Gets the connected user's profile.
|
|
215
|
+
*
|
|
216
|
+
* Requires scope: `user.read`
|
|
217
|
+
*
|
|
218
|
+
* @param accessToken - Valid access token
|
|
219
|
+
* @returns User profile information
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```typescript
|
|
223
|
+
* const user = await edge.getUser(accessToken)
|
|
224
|
+
* console.log(`Connected: ${user.firstName} ${user.lastName}`)
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
getUser(accessToken: string): Promise<User>;
|
|
228
|
+
/**
|
|
229
|
+
* Gets the connected user's EdgeBoost balance.
|
|
230
|
+
*
|
|
231
|
+
* Requires scope: `balance.read`
|
|
232
|
+
*
|
|
233
|
+
* @param accessToken - Valid access token
|
|
234
|
+
* @returns Balance information
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```typescript
|
|
238
|
+
* const balance = await edge.getBalance(accessToken)
|
|
239
|
+
* console.log(`Balance: $${balance.availableBalance.toFixed(2)} ${balance.currency}`)
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
getBalance(accessToken: string): Promise<Balance>;
|
|
243
|
+
/**
|
|
244
|
+
* Initiates a fund transfer.
|
|
245
|
+
*
|
|
246
|
+
* Requires scope: `transfer.write`
|
|
247
|
+
*
|
|
248
|
+
* **Transfer Types:**
|
|
249
|
+
* - `debit`: Pull funds FROM user's EdgeBoost TO your platform
|
|
250
|
+
* - `credit`: Push funds FROM your platform TO user's EdgeBoost
|
|
251
|
+
*
|
|
252
|
+
* **Idempotency:** Using the same `idempotencyKey` returns the existing
|
|
253
|
+
* transfer instead of creating a duplicate. Use a unique key per transaction.
|
|
254
|
+
*
|
|
255
|
+
* **OTP Verification:** Transfers require OTP verification before completion.
|
|
256
|
+
* The response includes `otpMethod` indicating how the user will receive the code.
|
|
257
|
+
*
|
|
258
|
+
* @param accessToken - Valid access token
|
|
259
|
+
* @param options - Transfer options
|
|
260
|
+
* @returns Transfer with status and OTP method
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```typescript
|
|
264
|
+
* const transfer = await edge.initiateTransfer(accessToken, {
|
|
265
|
+
* type: 'debit',
|
|
266
|
+
* amount: '100.00',
|
|
267
|
+
* idempotencyKey: `withdraw_${userId}_${Date.now()}`,
|
|
268
|
+
* })
|
|
269
|
+
*
|
|
270
|
+
* if (transfer.status === 'pending_verification') {
|
|
271
|
+
* // Show OTP input to user
|
|
272
|
+
* console.log(`Enter code sent via ${transfer.otpMethod}`)
|
|
273
|
+
* }
|
|
274
|
+
* ```
|
|
275
|
+
*/
|
|
276
|
+
initiateTransfer(accessToken: string, options: TransferOptions): Promise<Transfer>;
|
|
277
|
+
/**
|
|
278
|
+
* Verifies a pending transfer with OTP.
|
|
279
|
+
*
|
|
280
|
+
* Call this after the user enters the OTP code they received.
|
|
281
|
+
* The OTP is valid for ~5 minutes.
|
|
282
|
+
*
|
|
283
|
+
* @param accessToken - Valid access token
|
|
284
|
+
* @param transferId - Transfer ID from initiateTransfer
|
|
285
|
+
* @param otp - 6-digit OTP code from user
|
|
286
|
+
* @returns Updated transfer (status will be 'completed' or 'failed')
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```typescript
|
|
290
|
+
* const result = await edge.verifyTransfer(accessToken, transferId, userOtp)
|
|
291
|
+
*
|
|
292
|
+
* if (result.status === 'completed') {
|
|
293
|
+
* console.log('Transfer successful!')
|
|
294
|
+
* } else if (result.status === 'failed') {
|
|
295
|
+
* console.log('Transfer failed - possibly wrong OTP')
|
|
296
|
+
* }
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
verifyTransfer(accessToken: string, transferId: string, otp: string): Promise<Transfer>;
|
|
300
|
+
/**
|
|
301
|
+
* Gets the status of a transfer.
|
|
302
|
+
*
|
|
303
|
+
* Use for polling after initiating a transfer.
|
|
304
|
+
*
|
|
305
|
+
* @param accessToken - Valid access token
|
|
306
|
+
* @param transferId - Transfer ID
|
|
307
|
+
* @returns Current transfer status
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```typescript
|
|
311
|
+
* const transfer = await edge.getTransfer(accessToken, transferId)
|
|
312
|
+
* console.log(`Status: ${transfer.status}`)
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
getTransfer(accessToken: string, transferId: string): Promise<Transfer>;
|
|
316
|
+
/**
|
|
317
|
+
* Lists transfers for the connected user.
|
|
318
|
+
*
|
|
319
|
+
* Useful for reconciliation and showing transfer history.
|
|
320
|
+
*
|
|
321
|
+
* @param accessToken - Valid access token
|
|
322
|
+
* @param params - Pagination and filter options
|
|
323
|
+
* @returns Paginated list of transfers
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```typescript
|
|
327
|
+
* // Get first page of completed transfers
|
|
328
|
+
* const { transfers, total } = await edge.listTransfers(accessToken, {
|
|
329
|
+
* status: 'completed',
|
|
330
|
+
* limit: 10,
|
|
331
|
+
* offset: 0,
|
|
332
|
+
* })
|
|
333
|
+
*
|
|
334
|
+
* console.log(`Showing ${transfers.length} of ${total} transfers`)
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
listTransfers(accessToken: string, params?: ListTransfersParams): Promise<TransferList>;
|
|
338
|
+
/**
|
|
339
|
+
* Revokes the user's consent (disconnects their account).
|
|
340
|
+
*
|
|
341
|
+
* After revocation:
|
|
342
|
+
* - All API calls will fail with `consent_required` error
|
|
343
|
+
* - User must go through EdgeLink again to reconnect
|
|
344
|
+
* - Stored tokens become invalid
|
|
345
|
+
*
|
|
346
|
+
* Use this for "Disconnect" or "Unlink" features in your app.
|
|
347
|
+
*
|
|
348
|
+
* @param accessToken - Valid access token
|
|
349
|
+
* @returns Confirmation of revocation
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* ```typescript
|
|
353
|
+
* // Disconnect user's EdgeBoost account
|
|
354
|
+
* await edge.revokeConsent(accessToken)
|
|
355
|
+
*
|
|
356
|
+
* // Clean up stored tokens
|
|
357
|
+
* await db.edgeConnections.delete(userId)
|
|
358
|
+
*
|
|
359
|
+
* console.log('EdgeBoost disconnected')
|
|
360
|
+
* ```
|
|
361
|
+
*/
|
|
362
|
+
revokeConsent(accessToken: string): Promise<{
|
|
363
|
+
revoked: boolean;
|
|
364
|
+
}>;
|
|
365
|
+
/**
|
|
366
|
+
* Makes an authenticated API request.
|
|
367
|
+
*/
|
|
368
|
+
private apiRequest;
|
|
369
|
+
/**
|
|
370
|
+
* Fetch with timeout support.
|
|
371
|
+
*/
|
|
372
|
+
private fetchWithTimeout;
|
|
373
|
+
/**
|
|
374
|
+
* Parses token response from Cognito.
|
|
375
|
+
*/
|
|
376
|
+
private parseTokenResponse;
|
|
377
|
+
/**
|
|
378
|
+
* Handles token exchange errors.
|
|
379
|
+
*/
|
|
380
|
+
private handleTokenError;
|
|
381
|
+
/**
|
|
382
|
+
* Handles API errors.
|
|
383
|
+
*/
|
|
384
|
+
private handleApiError;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export { EdgeConnectServer, type EdgeConnectServerConfig, type TransferOptions };
|