@edge-markets/connect-node
v1.4.0
Published
Server SDK for EDGE Connect token exchange and API calls
Readme
@edge-markets/connect-node
Server SDK for EDGE Connect token exchange and API calls.
Features
- 🔐 Secure token exchange - Exchange codes for tokens with PKCE
- 🔄 Token refresh - Automatic refresh token handling
- 📡 Full API client - User, balance, transfers
- 🛡️ Typed errors - Specific error classes for each scenario
- 📝 TypeScript first - Complete type definitions
Installation
npm install @edge-markets/connect-node
# or
pnpm add @edge-markets/connect-node
# or
yarn add @edge-markets/connect-nodeQuick Start
import { EdgeConnectServer } from '@edgeboost/edge-connect-server'
// Create instance once (reuse for all requests)
const edge = new EdgeConnectServer({
clientId: process.env.EDGE_CLIENT_ID!,
clientSecret: process.env.EDGE_CLIENT_SECRET!,
environment: 'staging',
})
// Exchange code from EdgeLink for tokens
const tokens = await edge.exchangeCode(code, codeVerifier)
// Make API calls
const user = await edge.getUser(tokens.accessToken)
const balance = await edge.getBalance(tokens.accessToken)⚠️ Security
This SDK requires your client secret. Use it ONLY on your backend server!
Never expose your client secret to browsers or client-side code.
Configuration
interface EdgeConnectServerConfig {
clientId: string // Your OAuth client ID
clientSecret: string // Your OAuth client secret (keep secret!)
environment: EdgeEnvironment // 'production' | 'staging' | 'sandbox'
// Optional
apiBaseUrl?: string // Custom API URL (dev only)
authDomain?: string // Custom Cognito domain (dev only)
timeout?: number // Request timeout (default: 30000ms)
// Optional: Message Level Encryption (Connect endpoints only)
mle?: {
enabled: boolean
edgePublicKey: string // EDGE public encryption key (PEM)
edgeKeyId: string // EDGE key ID (kid) used for requests
partnerPrivateKey: string // Your private key (PEM) to decrypt responses
partnerKeyId: string // Your key ID (kid) expected in response headers
strictResponseEncryption?: boolean // default true
}
}Message Level Encryption (MLE)
const edge = new EdgeConnectServer({
clientId: process.env.EDGE_CLIENT_ID!,
clientSecret: process.env.EDGE_CLIENT_SECRET!,
environment: 'staging',
mle: {
enabled: true,
edgePublicKey: process.env.EDGE_MLE_EDGE_PUBLIC_KEY!,
edgeKeyId: process.env.EDGE_MLE_EDGE_KEY_ID!,
partnerPrivateKey: process.env.EDGE_MLE_PARTNER_PRIVATE_KEY!,
partnerKeyId: process.env.EDGE_MLE_PARTNER_KEY_ID!,
},
})When enabled, the SDK sends X-Edge-MLE: v1, encrypts request bodies, and decrypts encrypted Connect responses.
Token Exchange
After EdgeLink completes, exchange the code for tokens:
// In your /api/edge/exchange endpoint
export async function POST(req: Request) {
const { code, codeVerifier } = await req.json()
try {
const tokens = await edge.exchangeCode(code, codeVerifier)
// Store tokens securely (encrypted in database)
await db.edgeConnections.upsert({
userId: req.user.id,
accessToken: encrypt(tokens.accessToken),
refreshToken: encrypt(tokens.refreshToken),
expiresAt: new Date(tokens.expiresAt),
})
return Response.json({ success: true })
} catch (error) {
if (error instanceof EdgeTokenExchangeError) {
// Code expired or already used
return Response.json({ error: 'Please try again' }, { status: 400 })
}
throw error
}
}Token Refresh
Refresh tokens before they expire:
async function getValidAccessToken(userId: string): Promise<string> {
const connection = await db.edgeConnections.get(userId)
// Refresh 5 minutes before expiry
const BUFFER = 5 * 60 * 1000
if (Date.now() > connection.expiresAt.getTime() - BUFFER) {
const newTokens = await edge.refreshTokens(
decrypt(connection.refreshToken)
)
await db.edgeConnections.update(userId, {
accessToken: encrypt(newTokens.accessToken),
refreshToken: encrypt(newTokens.refreshToken),
expiresAt: new Date(newTokens.expiresAt),
})
return newTokens.accessToken
}
return decrypt(connection.accessToken)
}API Methods
User & Balance
// Get user profile
const user = await edge.getUser(accessToken)
// Returns: { id, email, firstName, lastName, createdAt }
// Get balance
const balance = await edge.getBalance(accessToken)
// Returns: { userId, availableBalance, currency, asOf }Transfers
// Initiate transfer (requires OTP verification)
const transfer = await edge.initiateTransfer(accessToken, {
type: 'debit', // 'debit' = pull from user, 'credit' = push to user
amount: '100.00',
idempotencyKey: `txn_${userId}_${Date.now()}`,
})
// Returns: { transferId, status: 'pending_verification', otpMethod }
// Verify with OTP
const result = await edge.verifyTransfer(accessToken, transfer.transferId, userOtp)
// Returns: { transferId, status: 'completed' | 'failed' }
// Get transfer status
const status = await edge.getTransfer(accessToken, transferId)
// List transfers
const { transfers, total } = await edge.listTransfers(accessToken, {
status: 'completed',
limit: 10,
offset: 0,
})Consent
// Revoke consent (disconnect user)
await edge.revokeConsent(accessToken)
// Clean up stored tokens
await db.edgeConnections.delete(userId)Error Handling
import {
EdgeError,
EdgeAuthenticationError,
EdgeTokenExchangeError,
EdgeConsentRequiredError,
isEdgeError,
} from '@edge-markets/connect-node'
try {
const balance = await edge.getBalance(accessToken)
} catch (error) {
if (error instanceof EdgeAuthenticationError) {
// Token expired - try refresh or reconnect
return { error: 'session_expired' }
}
if (error instanceof EdgeConsentRequiredError) {
// User revoked consent - need to reconnect
return { error: 'reconnect_required' }
}
if (isEdgeError(error)) {
// Some other SDK error
console.error(`Edge Error [${error.code}]: ${error.message}`)
return { error: error.code }
}
// Unknown error
throw error
}Error Types
| Error | When | What to do |
|-------|------|------------|
| EdgeAuthenticationError | Token invalid/expired | Refresh token or reconnect |
| EdgeTokenExchangeError | Code exchange failed | Ask user to try again |
| EdgeConsentRequiredError | User hasn't granted consent | Open EdgeLink |
| EdgeInsufficientScopeError | Missing required scopes | Request more scopes |
| EdgeNotFoundError | Resource not found | Check ID |
| EdgeApiError | Other API error | Check error.code |
| EdgeNetworkError | Network failure | Retry request |
NestJS Example
import { Injectable, Logger } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { EdgeConnectServer, EdgeConsentRequiredError } from '@edge-markets/connect-node'
@Injectable()
export class EdgeService {
private readonly edge: EdgeConnectServer
private readonly logger = new Logger(EdgeService.name)
constructor(private config: ConfigService) {
this.edge = new EdgeConnectServer({
clientId: this.config.getOrThrow('EDGE_CLIENT_ID'),
clientSecret: this.config.getOrThrow('EDGE_CLIENT_SECRET'),
environment: this.config.get('EDGE_ENVIRONMENT', 'staging'),
})
}
async exchangeCode(code: string, codeVerifier: string) {
return this.edge.exchangeCode(code, codeVerifier)
}
async getBalance(accessToken: string) {
try {
return await this.edge.getBalance(accessToken)
} catch (error) {
if (error instanceof EdgeConsentRequiredError) {
this.logger.warn('User consent required')
throw error
}
this.logger.error('Failed to get balance', error)
throw error
}
}
}Express Example
import express from 'express'
import { EdgeConnectServer, isEdgeError } from '@edge-markets/connect-node'
const edge = new EdgeConnectServer({
clientId: process.env.EDGE_CLIENT_ID!,
clientSecret: process.env.EDGE_CLIENT_SECRET!,
environment: 'staging',
})
const app = express()
app.use(express.json())
// Exchange code for tokens
app.post('/api/edge/exchange', async (req, res) => {
try {
const { code, codeVerifier } = req.body
const tokens = await edge.exchangeCode(code, codeVerifier)
// Store tokens for user...
res.json({ success: true })
} catch (error) {
if (isEdgeError(error)) {
res.status(400).json({ error: error.code, message: error.message })
} else {
res.status(500).json({ error: 'internal_error' })
}
}
})
// Get balance
app.get('/api/edge/balance', async (req, res) => {
try {
const accessToken = await getAccessTokenForUser(req.user.id)
const balance = await edge.getBalance(accessToken)
res.json(balance)
} catch (error) {
// Handle errors...
}
})Related Packages
@edge-markets/connect- Core types and utilities@edge-markets/connect-link- Browser SDK for popup authentication
License
MIT
