@alter-ai/connect 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +577 -0
- package/dist/alter-connect.cjs.js +2 -0
- package/dist/alter-connect.cjs.js.map +1 -0
- package/dist/alter-connect.esm.js +2 -0
- package/dist/alter-connect.esm.js.map +1 -0
- package/dist/alter-connect.umd.js +2 -0
- package/dist/alter-connect.umd.js.map +1 -0
- package/dist/types/api/client.d.ts +15 -0
- package/dist/types/api/client.d.ts.map +1 -0
- package/dist/types/core/alter-connect.d.ts +33 -0
- package/dist/types/core/alter-connect.d.ts.map +1 -0
- package/dist/types/core/config.d.ts +19 -0
- package/dist/types/core/config.d.ts.map +1 -0
- package/dist/types/core/events.d.ts +11 -0
- package/dist/types/core/events.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/oauth/handler.d.ts +23 -0
- package/dist/types/oauth/handler.d.ts.map +1 -0
- package/dist/types/state/manager.d.ts +25 -0
- package/dist/types/state/manager.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +48 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/ui/modal.d.ts +22 -0
- package/dist/types/ui/modal.d.ts.map +1 -0
- package/dist/types/ui/provider-list.d.ts +16 -0
- package/dist/types/ui/provider-list.d.ts.map +1 -0
- package/dist/types/ui/search-bar.d.ts +15 -0
- package/dist/types/ui/search-bar.d.ts.map +1 -0
- package/package.json +74 -0
package/README.md
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
# Alter Connect SDK
|
|
2
|
+
|
|
3
|
+
A lightweight JavaScript SDK for embedding OAuth integrations into your application. Connect your users to Google, Slack, Microsoft, and more with just a few lines of code.
|
|
4
|
+
|
|
5
|
+
## 🚀 Quick Start
|
|
6
|
+
|
|
7
|
+
### 1. Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @alter-vault/connect
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or use via CDN:
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<script src="https://cdn.jsdelivr.net/npm/@alter-vault/connect@latest/dist/alter-connect.umd.js"></script>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### 2. Get a Session Token from Your Backend
|
|
20
|
+
|
|
21
|
+
Your backend creates a short-lived session token using your API key:
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
// YOUR backend endpoint
|
|
25
|
+
const response = await fetch('https://api.alterai.dev/oauth/connect/session', {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: {
|
|
28
|
+
'x-api-key': process.env.ALTER_API_KEY, // Never expose this!
|
|
29
|
+
'Content-Type': 'application/json'
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
end_user: {
|
|
33
|
+
id: 'user_123',
|
|
34
|
+
email: 'user@example.com'
|
|
35
|
+
},
|
|
36
|
+
allowed_providers: ['google', 'slack', 'microsoft']
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const { session_token } = await response.json();
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 3. Open the Connect UI
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
import AlterConnect from '@alter-vault/connect';
|
|
47
|
+
|
|
48
|
+
// Initialize SDK
|
|
49
|
+
const alterConnect = AlterConnect.create();
|
|
50
|
+
|
|
51
|
+
// Get session token from YOUR backend
|
|
52
|
+
const { session_token } = await fetch('/api/alter/session').then(r => r.json());
|
|
53
|
+
|
|
54
|
+
// Open Connect UI
|
|
55
|
+
await alterConnect.open({
|
|
56
|
+
token: session_token,
|
|
57
|
+
onSuccess: (connection) => {
|
|
58
|
+
console.log('Connected!', connection);
|
|
59
|
+
// Save connection.connection_id to your database
|
|
60
|
+
},
|
|
61
|
+
onError: (error) => {
|
|
62
|
+
console.error('Failed:', error);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
That's it! The SDK handles the OAuth flow, popup windows, and all security.
|
|
68
|
+
|
|
69
|
+
## 📖 Framework Examples
|
|
70
|
+
|
|
71
|
+
### React
|
|
72
|
+
|
|
73
|
+
```jsx
|
|
74
|
+
import { useState } from 'react';
|
|
75
|
+
import AlterConnect from '@alter-vault/connect';
|
|
76
|
+
|
|
77
|
+
function ConnectButton() {
|
|
78
|
+
const [alterConnect] = useState(() => AlterConnect.create());
|
|
79
|
+
|
|
80
|
+
const handleConnect = async () => {
|
|
81
|
+
const { session_token } = await fetch('/api/alter/session')
|
|
82
|
+
.then(r => r.json());
|
|
83
|
+
|
|
84
|
+
await alterConnect.open({
|
|
85
|
+
token: session_token,
|
|
86
|
+
onSuccess: (connection) => {
|
|
87
|
+
console.log('Connected!', connection);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return <button onClick={handleConnect}>Connect Account</button>;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Vue
|
|
97
|
+
|
|
98
|
+
```vue
|
|
99
|
+
<template>
|
|
100
|
+
<button @click="handleConnect">Connect Account</button>
|
|
101
|
+
</template>
|
|
102
|
+
|
|
103
|
+
<script setup>
|
|
104
|
+
import { ref, onMounted } from 'vue';
|
|
105
|
+
import AlterConnect from '@alter-vault/connect';
|
|
106
|
+
|
|
107
|
+
const alterConnect = ref(null);
|
|
108
|
+
|
|
109
|
+
onMounted(() => {
|
|
110
|
+
alterConnect.value = AlterConnect.create();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
async function handleConnect() {
|
|
114
|
+
const { session_token } = await fetch('/api/alter/session')
|
|
115
|
+
.then(r => r.json());
|
|
116
|
+
|
|
117
|
+
await alterConnect.value.open({
|
|
118
|
+
token: session_token,
|
|
119
|
+
onSuccess: (connection) => console.log('Connected!', connection)
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
</script>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Vanilla JavaScript (CDN)
|
|
126
|
+
|
|
127
|
+
```html
|
|
128
|
+
<button id="connect-btn">Connect Account</button>
|
|
129
|
+
|
|
130
|
+
<script src="https://cdn.jsdelivr.net/npm/@alter-vault/connect@latest/dist/alter-connect.umd.js"></script>
|
|
131
|
+
<script>
|
|
132
|
+
const alterConnect = AlterConnect.create();
|
|
133
|
+
|
|
134
|
+
document.getElementById('connect-btn').addEventListener('click', async () => {
|
|
135
|
+
const { session_token } = await fetch('/api/alter/session')
|
|
136
|
+
.then(r => r.json());
|
|
137
|
+
|
|
138
|
+
await alterConnect.open({
|
|
139
|
+
token: session_token,
|
|
140
|
+
onSuccess: (connection) => console.log('Connected!', connection)
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
</script>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 🎨 Branding & Customization
|
|
147
|
+
|
|
148
|
+
**Branding is now managed through the Developer Portal** and automatically applied to the Connect UI at runtime.
|
|
149
|
+
|
|
150
|
+
### Managing Branding (Developer Portal)
|
|
151
|
+
|
|
152
|
+
Configure your app's branding through the Developer Portal API:
|
|
153
|
+
|
|
154
|
+
#### Initial Setup or Complete Rebrand (PUT)
|
|
155
|
+
|
|
156
|
+
Use PUT for initial branding setup or to completely replace existing branding:
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
// Create or fully replace branding
|
|
160
|
+
await fetch('https://api.alterai.dev/api/v1/developer/apps/{app_id}/branding', {
|
|
161
|
+
method: 'PUT',
|
|
162
|
+
headers: {
|
|
163
|
+
'Authorization': `Bearer ${DEVELOPER_TOKEN}`,
|
|
164
|
+
'Content-Type': 'application/json'
|
|
165
|
+
},
|
|
166
|
+
body: JSON.stringify({
|
|
167
|
+
logo_url: 'https://your-domain.com/logo.png', // Required
|
|
168
|
+
colors: { // Required
|
|
169
|
+
primary: '#6366f1',
|
|
170
|
+
text: '#1f2937',
|
|
171
|
+
background: '#ffffff'
|
|
172
|
+
},
|
|
173
|
+
font_family: 'Inter, sans-serif' // Optional
|
|
174
|
+
})
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### Partial Updates (PATCH)
|
|
179
|
+
|
|
180
|
+
Use PATCH to update specific branding fields:
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
// Update only font (logo + colors preserved)
|
|
184
|
+
await fetch('https://api.alterai.dev/api/v1/developer/apps/{app_id}/branding', {
|
|
185
|
+
method: 'PATCH',
|
|
186
|
+
headers: {
|
|
187
|
+
'Authorization': `Bearer ${DEVELOPER_TOKEN}`,
|
|
188
|
+
'Content-Type': 'application/json'
|
|
189
|
+
},
|
|
190
|
+
body: JSON.stringify({
|
|
191
|
+
font_family: 'Roboto, sans-serif'
|
|
192
|
+
})
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Update logo + colors (font preserved)
|
|
196
|
+
await fetch('https://api.alterai.dev/api/v1/developer/apps/{app_id}/branding', {
|
|
197
|
+
method: 'PATCH',
|
|
198
|
+
headers: {
|
|
199
|
+
'Authorization': `Bearer ${DEVELOPER_TOKEN}`,
|
|
200
|
+
'Content-Type': 'application/json'
|
|
201
|
+
},
|
|
202
|
+
body: JSON.stringify({
|
|
203
|
+
logo_url: 'https://your-domain.com/new-logo.png',
|
|
204
|
+
colors: {
|
|
205
|
+
primary: '#ff0000',
|
|
206
|
+
text: '#000000',
|
|
207
|
+
background: '#ffffff'
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Important Rules:**
|
|
214
|
+
- **PUT:** Requires `logo_url` and `colors`. Font is optional. Replaces all existing branding.
|
|
215
|
+
- **PATCH:** Requires existing branding (use PUT first). If updating logo or colors, both must be provided together. Font can be updated independently.
|
|
216
|
+
- **DELETE:** Removes all branding and reverts to Alter defaults.
|
|
217
|
+
|
|
218
|
+
### How It Works
|
|
219
|
+
|
|
220
|
+
1. **Configure branding** in the Developer Portal
|
|
221
|
+
2. **SDK fetches branding** automatically when `alterConnect.open()` is called
|
|
222
|
+
3. **Branding is applied** instantly to the Connect UI
|
|
223
|
+
4. **No redeployment needed** - changes take effect immediately
|
|
224
|
+
|
|
225
|
+
### Fallback Behavior
|
|
226
|
+
|
|
227
|
+
If no branding is configured or the API call fails, the SDK gracefully falls back to Alter's default branding.
|
|
228
|
+
|
|
229
|
+
### SDK Configuration Options
|
|
230
|
+
|
|
231
|
+
For local development or debugging, you can still configure some options:
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
const alterConnect = AlterConnect.create({
|
|
235
|
+
debug: true // Enable console logging
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## 📚 API Reference
|
|
240
|
+
|
|
241
|
+
### `AlterConnect.create(config?)`
|
|
242
|
+
|
|
243
|
+
Creates a new SDK instance.
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
const alterConnect = AlterConnect.create({
|
|
247
|
+
debug: false
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Config Options:**
|
|
252
|
+
|
|
253
|
+
| Option | Type | Description | Default |
|
|
254
|
+
|--------|------|-------------|---------|
|
|
255
|
+
| `debug` | `boolean` | Enable debug logging | `false` |
|
|
256
|
+
|
|
257
|
+
**Note:** Branding (colors, fonts, logo) is now managed through the Developer Portal and fetched automatically at runtime.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
### `alterConnect.open(options)`
|
|
262
|
+
|
|
263
|
+
Opens the Connect UI modal.
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
await alterConnect.open({
|
|
267
|
+
token: 'sess_abc123...',
|
|
268
|
+
onSuccess: (connection) => { /* ... */ },
|
|
269
|
+
onError: (error) => { /* ... */ },
|
|
270
|
+
onExit: () => { /* ... */ }
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Options:**
|
|
275
|
+
|
|
276
|
+
| Parameter | Type | Required | Description |
|
|
277
|
+
|-----------|------|----------|-------------|
|
|
278
|
+
| `token` | `string` | ✅ Yes | Session token from your backend |
|
|
279
|
+
| `baseURL` | `string` | No | Alter API URL (usually auto-detected) |
|
|
280
|
+
| `onSuccess` | `function` | ✅ Yes | Called when connection succeeds |
|
|
281
|
+
| `onError` | `function` | No | Called when connection fails |
|
|
282
|
+
| `onExit` | `function` | No | Called when user closes modal |
|
|
283
|
+
| `onEvent` | `function` | No | Called for analytics events |
|
|
284
|
+
|
|
285
|
+
**Connection Object (onSuccess):**
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
{
|
|
289
|
+
connection_id: string; // Unique ID - store this!
|
|
290
|
+
provider: string; // e.g., 'google', 'slack'
|
|
291
|
+
provider_name: string; // e.g., 'Google', 'Slack'
|
|
292
|
+
account_identifier: string; // e.g., 'user@gmail.com'
|
|
293
|
+
timestamp: string; // ISO 8601 timestamp
|
|
294
|
+
operation: 'creation' | 'reauth';
|
|
295
|
+
scopes: string[]; // Granted OAuth scopes
|
|
296
|
+
status: 'active' | 'pending' | 'error';
|
|
297
|
+
metadata?: object; // Additional provider data
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Error Object (onError):**
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
{
|
|
305
|
+
code: string; // e.g., 'invalid_token', 'popup_blocked'
|
|
306
|
+
message: string; // Human-readable message
|
|
307
|
+
details?: object; // Additional error context
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
### `alterConnect.close()`
|
|
314
|
+
|
|
315
|
+
Manually closes the Connect UI.
|
|
316
|
+
|
|
317
|
+
```javascript
|
|
318
|
+
alterConnect.close();
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
### `alterConnect.destroy()`
|
|
324
|
+
|
|
325
|
+
Destroys the SDK instance and cleans up resources.
|
|
326
|
+
|
|
327
|
+
```javascript
|
|
328
|
+
alterConnect.destroy();
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
### `alterConnect.isOpen()`
|
|
334
|
+
|
|
335
|
+
Checks if the Connect UI is currently open.
|
|
336
|
+
|
|
337
|
+
```javascript
|
|
338
|
+
if (alterConnect.isOpen()) {
|
|
339
|
+
console.log('Modal is open');
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**Returns:** `boolean`
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
### `alterConnect.getVersion()`
|
|
348
|
+
|
|
349
|
+
Gets the SDK version.
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
console.log(alterConnect.getVersion()); // "0.1.0"
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Returns:** `string`
|
|
356
|
+
|
|
357
|
+
## 🔒 Security Architecture
|
|
358
|
+
|
|
359
|
+
The SDK uses a **Plaid-style token architecture** for security:
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
Frontend (Public) Backend (Secure) Alter API
|
|
363
|
+
│ │ │
|
|
364
|
+
│ 1. User clicks │ │
|
|
365
|
+
│ "Connect" │ │
|
|
366
|
+
│─────────────────────────>│ │
|
|
367
|
+
│ │ │
|
|
368
|
+
│ │ 2. Create session │
|
|
369
|
+
│ │ (with API key) │
|
|
370
|
+
│ │───────────────────────>│
|
|
371
|
+
│ │ │
|
|
372
|
+
│ │ 3. { session_token } │
|
|
373
|
+
│ │<───────────────────────│
|
|
374
|
+
│ │ │
|
|
375
|
+
│ 4. { session_token } │ │
|
|
376
|
+
│<─────────────────────────│ │
|
|
377
|
+
│ │ │
|
|
378
|
+
│ 5. SDK.open({ token }) │ │
|
|
379
|
+
│──────────────────────────┼───────────────────────>│
|
|
380
|
+
│ │ 6. OAuth flow │
|
|
381
|
+
│<─────────────────────────┴────────────────────────│
|
|
382
|
+
│ 7. onSuccess(connection)│ │
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Key Security Features:**
|
|
386
|
+
|
|
387
|
+
✅ **API keys never touch frontend** - Only backend has API key
|
|
388
|
+
✅ **Session tokens are short-lived** - Expire after 10 minutes
|
|
389
|
+
✅ **Session tokens are single-use** - Can only create one connection
|
|
390
|
+
✅ **Session tokens are scoped** - Locked to specific user & providers
|
|
391
|
+
✅ **No secrets in browser** - SDK is purely a UI component
|
|
392
|
+
|
|
393
|
+
## 💡 Smart UX
|
|
394
|
+
|
|
395
|
+
### Single Provider Flow
|
|
396
|
+
|
|
397
|
+
When only 1 provider is allowed, the SDK **skips the selection UI** and opens OAuth directly:
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
// Backend
|
|
401
|
+
allowed_providers: ['google']
|
|
402
|
+
|
|
403
|
+
// SDK behavior: Opens Google OAuth immediately (no modal)
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Multiple Provider Flow
|
|
407
|
+
|
|
408
|
+
When 2+ providers are allowed, the SDK shows a **provider selection modal**:
|
|
409
|
+
|
|
410
|
+
```javascript
|
|
411
|
+
// Backend
|
|
412
|
+
allowed_providers: ['google', 'slack', 'microsoft']
|
|
413
|
+
|
|
414
|
+
// SDK behavior: Shows modal with provider grid
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## 🎯 Common Patterns
|
|
418
|
+
|
|
419
|
+
### Save Connection to Your Database
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
await alterConnect.open({
|
|
423
|
+
token: sessionToken,
|
|
424
|
+
onSuccess: async (connection) => {
|
|
425
|
+
// Save to YOUR database
|
|
426
|
+
await fetch('/api/connections', {
|
|
427
|
+
method: 'POST',
|
|
428
|
+
body: JSON.stringify({
|
|
429
|
+
connection_id: connection.connection_id,
|
|
430
|
+
user_id: currentUserId,
|
|
431
|
+
provider: connection.provider
|
|
432
|
+
})
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Validate Provider Type
|
|
439
|
+
|
|
440
|
+
```javascript
|
|
441
|
+
await alterConnect.open({
|
|
442
|
+
token: sessionToken,
|
|
443
|
+
onSuccess: async (connection) => {
|
|
444
|
+
// Ensure user connected the right provider
|
|
445
|
+
if (connection.provider !== 'google') {
|
|
446
|
+
alert('Please connect your Gmail account');
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
await saveConnection(connection);
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Validate Email Domain
|
|
456
|
+
|
|
457
|
+
```javascript
|
|
458
|
+
await alterConnect.open({
|
|
459
|
+
token: sessionToken,
|
|
460
|
+
onSuccess: async (connection) => {
|
|
461
|
+
// Ensure user connected a work email
|
|
462
|
+
if (!connection.account_identifier.endsWith('@company.com')) {
|
|
463
|
+
alert('Please connect your @company.com work email');
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
await saveConnection(connection);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Handle Errors Gracefully
|
|
473
|
+
|
|
474
|
+
```javascript
|
|
475
|
+
await alterConnect.open({
|
|
476
|
+
token: sessionToken,
|
|
477
|
+
onSuccess: (connection) => {
|
|
478
|
+
console.log('Connected!', connection);
|
|
479
|
+
},
|
|
480
|
+
onError: (error) => {
|
|
481
|
+
if (error.code === 'popup_blocked') {
|
|
482
|
+
alert('Please allow popups for this site');
|
|
483
|
+
} else if (error.code === 'session_expired') {
|
|
484
|
+
alert('Session expired. Please try again.');
|
|
485
|
+
} else {
|
|
486
|
+
alert(`Connection failed: ${error.message}`);
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
onExit: () => {
|
|
490
|
+
console.log('User cancelled');
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## 📦 TypeScript Support
|
|
496
|
+
|
|
497
|
+
Full TypeScript definitions included:
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
import AlterConnect, { Connection, AlterError } from '@alter-vault/connect';
|
|
501
|
+
|
|
502
|
+
const alterConnect = AlterConnect.create({
|
|
503
|
+
customization: { colors: { primary: '#6366f1' } }
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
await alterConnect.open({
|
|
507
|
+
token: sessionToken,
|
|
508
|
+
onSuccess: (connection: Connection) => {
|
|
509
|
+
// TypeScript knows all properties
|
|
510
|
+
console.log(connection.connection_id);
|
|
511
|
+
console.log(connection.provider);
|
|
512
|
+
},
|
|
513
|
+
onError: (error: AlterError) => {
|
|
514
|
+
console.error(error.code, error.message);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## 🌐 Browser Support
|
|
520
|
+
|
|
521
|
+
- Chrome/Edge 90+
|
|
522
|
+
- Firefox 88+
|
|
523
|
+
- Safari 14+
|
|
524
|
+
- Mobile browsers (iOS Safari 14+, Chrome Mobile)
|
|
525
|
+
|
|
526
|
+
## 📊 Bundle Size
|
|
527
|
+
|
|
528
|
+
- **UMD:** 21KB minified
|
|
529
|
+
- **ESM:** 21KB minified
|
|
530
|
+
- **CJS:** 21KB minified
|
|
531
|
+
- **Zero runtime dependencies**
|
|
532
|
+
|
|
533
|
+
## 🐛 Troubleshooting
|
|
534
|
+
|
|
535
|
+
### Popup Blocked
|
|
536
|
+
|
|
537
|
+
**Problem:** Browser blocks the OAuth popup
|
|
538
|
+
|
|
539
|
+
**Solution:** Ensure `alterConnect.open()` is called directly from a user interaction (click event):
|
|
540
|
+
|
|
541
|
+
```javascript
|
|
542
|
+
// ❌ Bad - may be blocked
|
|
543
|
+
button.addEventListener('click', async () => {
|
|
544
|
+
const token = await fetchToken(); // Async delay
|
|
545
|
+
alterConnect.open({ token }); // May be blocked
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// ✅ Good - no async delay before open()
|
|
549
|
+
button.addEventListener('click', () => {
|
|
550
|
+
fetchToken().then(token => {
|
|
551
|
+
alterConnect.open({ token }); // Called synchronously
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### Session Token Expired
|
|
557
|
+
|
|
558
|
+
**Problem:** `session_expired` error
|
|
559
|
+
|
|
560
|
+
**Solution:** Session tokens expire after 10 minutes. Create a new session token.
|
|
561
|
+
|
|
562
|
+
### CORS Errors
|
|
563
|
+
|
|
564
|
+
**Problem:** CORS error when calling Alter API
|
|
565
|
+
|
|
566
|
+
**Solution:** Session tokens should be created from your **backend**, not frontend. The SDK handles all frontend API calls.
|
|
567
|
+
|
|
568
|
+
## 🤝 Support
|
|
569
|
+
|
|
570
|
+
- **Documentation:** [https://docs.alterai.dev](https://docs.alterai.dev)
|
|
571
|
+
- **API Reference:** [https://api.alterai.dev/docs](https://api.alterai.dev/docs)
|
|
572
|
+
- **Issues:** [GitHub Issues](https://github.com/alter-ai/alter-vault/issues)
|
|
573
|
+
- **Email:** support@alterai.dev
|
|
574
|
+
|
|
575
|
+
## 📄 License
|
|
576
|
+
|
|
577
|
+
MIT License - See [LICENSE](LICENSE) file for details
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const t={colors:{primary:"#6366f1",text:"#1f2937",background:"#ffffff"},fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'},e="en",n=!1;function o(t){if(t.customization?.colors?.primary&&!r(t.customization.colors.primary))throw new Error(`Invalid primary color: ${t.customization.colors.primary}`);if(t.customization?.colors?.text&&!r(t.customization.colors.text))throw new Error(`Invalid text color: ${t.customization.colors.text}`);if(t.customization?.colors?.background&&!r(t.customization.colors.background))throw new Error(`Invalid background color: ${t.customization.colors.background}`)}function s(o){const s=t.colors,i=t.fontFamily;return{customization:{colors:{primary:o.customization?.colors?.primary||s.primary,text:o.customization?.colors?.text||s.text,background:o.customization?.colors?.background||s.background},fontFamily:o.customization?.fontFamily||i},locale:o.locale||e,debug:o.debug??n}}function i(t,...e){t.debug&&console.log("[Alter Connect]",...e)}function r(t){return!!/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(t)||(!!/^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(,\s*[\d.]+\s*)?\)$/.test(t)||(!!/^hsla?\(\s*\d+\s*,\s*[\d.]+%\s*,\s*[\d.]+%\s*(,\s*[\d.]+\s*)?\)$/.test(t)||!!/^[a-z]+$/.test(t.toLowerCase())))}class a{constructor(){this.events=new Map}on(t,e){return this.events.has(t)||this.events.set(t,new Set),this.events.get(t).add(e),()=>this.off(t,e)}off(t,e){const n=this.events.get(t);n&&n.delete(e)}emit(t,...e){const n=this.events.get(t);n&&n.forEach(n=>{try{n(...e)}catch(e){console.error(`[Alter Connect] Error in event handler for '${t}':`,e)}})}removeAllListeners(t){t?this.events.delete(t):this.events.clear()}}class l{constructor(){this.state={isOpen:!1,isLoading:!1,error:null,sessionToken:null,providers:[],selectedProvider:null,searchQuery:"",categoryFilter:null},this.listeners=new Set}getState(){return{...this.state}}get(t){return this.state[t]}setState(t){this.state={...this.state,...t},this.notifyListeners()}subscribe(t){return this.listeners.add(t),()=>{this.listeners.delete(t)}}clearListeners(){this.listeners.clear()}notifyListeners(){const t=this.getState();this.listeners.forEach(e=>{try{e(t)}catch(t){console.error("[Alter Connect] Error in state listener:",t)}})}}class c{constructor(t=!1){this.TIMEOUT=3e4,this.DEFAULT_BASE_URL="https://api.alterai.dev",this.debug=t}async fetchProviders(t,e){this.log("Fetching providers with session token");const n=await this.fetch("/oauth/providers",{method:"GET",sessionToken:t,baseURL:e||this.DEFAULT_BASE_URL});return this.log(`Fetched ${n.providers.length} providers`),n.providers}async fetchBranding(t,e){this.log("Fetching branding with session token");const n=await this.fetch("/oauth/branding",{method:"GET",sessionToken:t,baseURL:e||this.DEFAULT_BASE_URL});return this.log("Fetched branding:",n),n}buildAuthorizationURL(t,e,n){const o=`${n||this.DEFAULT_BASE_URL}/oauth/connect/initiate?provider=${t}&session=${e}`;return this.log("Authorization URL:",o),o}async fetch(t,e){const n=`${e.baseURL}${t}`,o={"Content-Type":"application/json"};e.sessionToken&&(o["X-Session-Token"]=e.sessionToken);const s=new AbortController,i=setTimeout(()=>s.abort(),this.TIMEOUT);try{const t=await fetch(n,{method:e.method,headers:o,body:e.body,signal:s.signal});if(clearTimeout(i),!t.ok){const e=await t.json().catch(()=>({error:"unknown_error",message:`HTTP ${t.status}: ${t.statusText}`}));throw this.createError(e.error||"api_error",e.message||e.error_description||`Request failed with status ${t.status}`,e)}return await t.json()}catch(t){if(clearTimeout(i),"AbortError"===t.name)throw this.createError("timeout",`Request timed out after ${this.TIMEOUT}ms`,{timeout:this.TIMEOUT});if(t instanceof TypeError)throw this.createError("network_error","Network request failed. Please check your internet connection.",{originalError:t.message});if(this.isAlterError(t))throw t;throw this.createError("unknown_error",t.message||"An unexpected error occurred",{originalError:t})}}createError(t,e,n){return{code:t,message:e,details:n}}isAlterError(t){return t&&"object"==typeof t&&"code"in t&&"message"in t}log(...t){this.debug&&console.log("[Alter API Client]",...t)}}class h{constructor(t){this.overlay=null,this.container=null,this.contentEl=null,this.closeButton=null,this.onClose=null,this.handleClose=()=>{this.onClose&&this.onClose()},this.handleEscape=t=>{"Escape"===t.key&&this.handleClose()},this.config=t}open(t){this.onClose=t.onClose,this.overlay=document.createElement("div"),this.overlay.className="alter-connect-overlay",this.overlay.style.cssText="\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 999999;\n animation: alter-connect-fade-in 0.2s ease-out;\n ",this.container=document.createElement("div"),this.container.className="alter-connect-modal",this.container.style.cssText=`\n background: #ffffff;\n border-radius: 12px;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\n width: 90%;\n max-width: 480px;\n max-height: 90vh;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n font-family: ${this.config.customization.fontFamily};\n animation: alter-connect-slide-up 0.3s ease-out;\n color: #1a1a1a;\n `,this.closeButton=document.createElement("button"),this.closeButton.className="alter-connect-close",this.closeButton.innerHTML="×",this.closeButton.setAttribute("aria-label","Close"),this.closeButton.style.cssText="\n position: absolute;\n top: 16px;\n right: 16px;\n background: transparent;\n border: none;\n font-size: 28px;\n line-height: 1;\n color: #6b7280;\n cursor: pointer;\n padding: 0;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n transition: background-color 0.2s, color 0.2s;\n ",this.closeButton.addEventListener("click",()=>this.handleClose()),this.closeButton.addEventListener("mouseenter",()=>{this.closeButton.style.backgroundColor="#f3f4f6",this.closeButton.style.color="#111827"}),this.closeButton.addEventListener("mouseleave",()=>{this.closeButton.style.backgroundColor="transparent",this.closeButton.style.color="#6b7280"}),this.contentEl=document.createElement("div"),this.contentEl.className="alter-connect-content",this.contentEl.style.cssText="\n padding: 40px 32px 32px 32px;\n overflow-y: auto;\n flex: 1;\n position: relative;\n ",this.container.appendChild(this.closeButton),this.container.appendChild(this.contentEl),this.overlay.appendChild(this.container),document.body.appendChild(this.overlay),this.overlay.addEventListener("click",t=>{t.target===this.overlay&&this.handleClose()}),document.addEventListener("keydown",this.handleEscape),document.body.style.overflow="hidden"}setContent(t){this.contentEl&&(this.contentEl.innerHTML="",this.contentEl.appendChild(t))}close(){this.overlay&&this.overlay.parentNode&&this.overlay.parentNode.removeChild(this.overlay),this.overlay=null,this.container=null,this.contentEl=null,this.closeButton=null,this.onClose=null,document.removeEventListener("keydown",this.handleEscape),document.body.style.overflow=""}destroy(){this.close()}}class d{constructor(t){this.container=null,this.onProviderClick=null,this.config=t}render(t,e){return this.onProviderClick=e,this.container=document.createElement("div"),this.container.className="alter-connect-provider-list",this.container.style.cssText="\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n gap: 12px;\n margin-bottom: 20px;\n ",this.renderProviders(t),this.container}update(t){this.container&&(this.container.innerHTML="",this.renderProviders(t))}destroy(){this.container=null,this.onProviderClick=null}renderProviders(t){if(this.container){if(0===t.length){const t=document.createElement("div");return t.style.cssText="\n text-align: center;\n padding: 40px 20px;\n color: white;\n font-size: 14px;\n ",t.textContent="No providers found",void this.container.appendChild(t)}t.forEach(t=>{const e=this.createProviderButton(t);this.container.appendChild(e)})}}createProviderButton(t){const e=document.createElement("button");e.className="alter-connect-provider-button",e.setAttribute("data-provider-id",t.id),e.style.cssText=`\n display: flex;\n align-items: center;\n padding: 16px;\n background: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.15s;\n text-decoration: none;\n color: inherit;\n gap: 12px;\n font-family: ${this.config.customization.fontFamily};\n width: 100%;\n `;const n=document.createElement("span");n.textContent="→",n.className="connect-arrow",n.style.cssText="\n color: #9ca3af;\n font-size: 18px;\n transition: all 0.15s;\n flex-shrink: 0;\n ",e.addEventListener("mouseenter",()=>{e.style.borderColor="#d1d5db",e.style.boxShadow="0 2px 8px rgba(0, 0, 0, 0.08)",e.style.transform="translateY(-1px)",n.style.color="#000000",n.style.transform="translateX(2px)"}),e.addEventListener("mouseleave",()=>{e.style.borderColor="#e5e7eb",e.style.boxShadow="none",e.style.transform="translateY(0)",n.style.color="#9ca3af",n.style.transform="translateX(0)"});const o=document.createElement("div");if(o.style.cssText="\n width: 40px;\n height: 40px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #f9fafb;\n border: 1px solid #e5e7eb;\n flex-shrink: 0;\n ",t.logo_url){const e=document.createElement("img");e.src=t.logo_url,e.alt=t.name,e.style.cssText="\n width: 28px;\n height: 28px;\n object-fit: contain;\n ",e.addEventListener("error",()=>{o.textContent=t.name.charAt(0).toUpperCase()}),o.appendChild(e)}else o.textContent=t.name.charAt(0).toUpperCase();const s=document.createElement("div");s.style.cssText="\n flex: 1;\n min-width: 0;\n text-align: left;\n ";const i=document.createElement("div");i.textContent=t.name,i.style.cssText="\n font-size: 14px;\n font-weight: 600;\n color: #1a1a1a;\n margin-bottom: 2px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n ";const r=document.createElement("div");return r.textContent=t.category?t.category.charAt(0).toUpperCase()+t.category.slice(1):"Other",r.style.cssText="\n font-size: 12px;\n color: #6b7280;\n font-weight: 400;\n ",s.appendChild(i),s.appendChild(r),e.appendChild(o),e.appendChild(s),e.appendChild(n),e.addEventListener("click",()=>{this.onProviderClick&&this.onProviderClick(t)}),e}}class p{constructor(t){this.popup=null,this.pollInterval=null,this.messageListener=null,this.options={onSuccess:t.onSuccess,onError:t.onError,onCancel:t.onCancel,popupWidth:t.popupWidth||450,popupHeight:t.popupHeight||600,debug:t.debug||!1}}openPopup(t){const e=window.screenX+(window.outerWidth-this.options.popupWidth)/2,n=window.screenY+(window.outerHeight-this.options.popupHeight)/2,o=[`width=${this.options.popupWidth}`,`height=${this.options.popupHeight}`,`left=${e}`,`top=${n}`,"resizable=yes","scrollbars=yes","status=yes"].join(",");this.log("Opening OAuth popup:",t),this.popup=window.open(t,"alter_oauth_popup",o),this.popup?(this.startPolling(),this.setupMessageListener()):this.options.onError({code:"popup_blocked",message:"Popup was blocked by browser. Please allow popups for this site."})}close(){this.log("Closing OAuth handler"),this.popup&&!this.popup.closed&&this.popup.close(),this.popup=null,null!==this.pollInterval&&(clearInterval(this.pollInterval),this.pollInterval=null),this.messageListener&&(window.removeEventListener("message",this.messageListener),this.messageListener=null)}startPolling(){this.pollInterval=window.setInterval(()=>{this.popup&&!this.popup.closed||(this.log("Popup closed by user"),this.close(),this.options.onCancel())},500)}setupMessageListener(){this.messageListener=t=>{this.log("Received message:",t.data);const e=t.data;if(e&&"object"==typeof e)if("alter_connect_success"===e.type){this.log("OAuth success");const t={connection_id:e.connection_id,provider:e.provider,provider_name:e.provider_name||e.provider,account_identifier:e.account_identifier,timestamp:e.timestamp,operation:e.operation||"creation",scopes:e.scopes||[],status:e.status||"active",metadata:e.metadata};this.close(),this.options.onSuccess(t)}else if("alter_connect_error"===e.type){this.log("OAuth error");const t={code:e.error||"oauth_error",message:e.error_description||"OAuth authorization failed",details:e};this.close(),this.options.onError(t)}},window.addEventListener("message",this.messageListener)}log(...t){this.options.debug&&console.log("[OAuth Handler]",...t)}}const u="0.1.0";class g{constructor(t={}){this._searchBar=null,this._providerList=null,this._oauthHandler=null,this._baseURL="https://api.alterai.dev",o(t),this.config=s(t),this.eventEmitter=new a,this.stateManager=new l,this.apiClient=new c(this.config.debug),this._modal=new h(this.config),this._isInitialized=!0,function(){if(document.getElementById("alter-connect-styles"))return;const t=document.createElement("style");t.id="alter-connect-styles",t.textContent="\n @keyframes alter-connect-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes alter-connect-slide-up {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .alter-connect-modal * {\n box-sizing: border-box;\n }\n ",document.head.appendChild(t)}(),i(this.config,"Alter Connect SDK initialized",{version:u})}static create(t){return new g(t)}async open(t){if(!this._isInitialized)throw this.createError("sdk_destroyed","Cannot call open() - SDK instance has been destroyed");if(i(this.config,"Opening Connect UI"),!t.token||"string"!=typeof t.token)throw this.createError("invalid_options","Session token is required. Create one from your backend using POST /oauth/connect/session");if(!t.onSuccess||"function"!=typeof t.onSuccess)throw this.createError("invalid_options","onSuccess callback is required");if(this.isOpen())i(this.config,"Connect UI is already open");else try{this._baseURL=t.baseURL||"https://api.alterai.dev",this.stateManager.setState({isOpen:!0,isLoading:!0,error:null}),this.stateManager.setState({sessionToken:t.token});const[e,n]=await Promise.all([this.apiClient.fetchProviders(t.token,this._baseURL),this.apiClient.fetchBranding(t.token,this._baseURL).catch(()=>null)]);if(n&&(i(this.config,"Applying branding from API"),n.colors&&(this.config.customization.colors={primary:n.colors.primary,text:n.colors.text,background:n.colors.background}),n.font_family&&(this.config.customization.fontFamily=n.font_family),this.config.customization.logoUrl=n.logo_url||void 0),this.stateManager.setState({providers:e,isLoading:!1}),this.registerEventHandlers(t),1===e.length)i(this.config,"Single provider detected - skipping selection UI"),this.handleProviderClick(e[0]),t.onEvent&&t.onEvent("single_provider_flow",{timestamp:(new Date).toISOString(),provider:e[0].id});else{i(this.config,"Multiple providers detected - showing selection UI");const n=this.buildUIContent(e);this._modal.open({onClose:()=>{this.eventEmitter.emit("exit")}}),this._modal.setContent(n),t.onEvent&&t.onEvent("modal_opened",{timestamp:(new Date).toISOString(),providerCount:e.length})}i(this.config,"Connect UI opened successfully")}catch(e){throw i(this.config,"Error opening Connect UI:",e),this.stateManager.setState({isLoading:!1,isOpen:!1,error:this.normalizeError(e)}),t.onError&&t.onError(this.normalizeError(e)),e}}close(){if(!this._isInitialized)throw this.createError("sdk_destroyed","Cannot call close() - SDK instance has been destroyed");i(this.config,"Closing Connect UI"),this._oauthHandler&&(this._oauthHandler.close(),this._oauthHandler=null),this._searchBar&&(this._searchBar.destroy(),this._searchBar=null),this._providerList&&(this._providerList.destroy(),this._providerList=null),this._modal.close(),this.stateManager.setState({isOpen:!1,selectedProvider:null,searchQuery:"",categoryFilter:null}),this.eventEmitter.emit("close")}destroy(){this._isInitialized&&(i(this.config,"Destroying SDK instance"),this._oauthHandler&&(this._oauthHandler.close(),this._oauthHandler=null),this._searchBar&&(this._searchBar.destroy(),this._searchBar=null),this._providerList&&(this._providerList.destroy(),this._providerList=null),this._modal.destroy(),this.stateManager.get("isOpen")&&this.stateManager.setState({isOpen:!1,selectedProvider:null,searchQuery:"",categoryFilter:null}),this.eventEmitter.removeAllListeners(),this.stateManager.clearListeners(),this._isInitialized=!1)}updateConfig(t){if(!this._isInitialized)throw this.createError("sdk_destroyed","Cannot call updateConfig() - SDK instance has been destroyed");i(this.config,"Updating config",t);const e={...this.config,...t};o(e),this.config=s(e),this.apiClient=new c(this.config.debug)}getConfig(){return{...this.config}}on(t,e){if(!this._isInitialized)throw this.createError("sdk_destroyed","Cannot call on() - SDK instance has been destroyed");return this.eventEmitter.on(t,e)}off(t,e){if(!this._isInitialized)throw this.createError("sdk_destroyed","Cannot call off() - SDK instance has been destroyed");this.eventEmitter.off(t,e)}isOpen(){if(!this._isInitialized)throw this.createError("sdk_destroyed","Cannot call isOpen() - SDK instance has been destroyed");return this.stateManager.get("isOpen")}getVersion(){return u}buildUIContent(t){const e=document.createElement("div");e.className="alter-connect-ui-container";const n=document.createElement("div");n.style.cssText="\n background: #000000;\n border-bottom: 1px solid #1a1a1a;\n padding: 20px 24px;\n margin: -32px -32px 32px -32px;\n display: flex;\n align-items: center;\n justify-content: center;\n ";const o=document.createElement("img");this.config.customization.logoUrl?(o.src=this.config.customization.logoUrl,o.alt="App Logo"):(o.src=`${this._baseURL}/static/Alter_Primary_Logo_name.png`,o.alt="Alter"),o.style.cssText="\n height: 28px;\n width: auto;\n max-width: 200px;\n object-fit: contain;\n ",o.addEventListener("error",()=>{const t=document.createElement("div");t.textContent="alter",t.style.cssText='\n font-size: 18px;\n font-weight: 600;\n color: #ffffff;\n letter-spacing: -0.02em;\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;\n ',o.replaceWith(t)}),n.appendChild(o);const s=document.createElement("div");s.style.cssText="\n margin-bottom: 32px;\n text-align: center;\n ";const i=document.createElement("h1");i.textContent="Connect an Integration",i.style.cssText='\n font-size: 24px;\n font-weight: 600;\n color: #1a1a1a;\n margin-bottom: 8px;\n letter-spacing: -0.02em;\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;\n ';const r=document.createElement("p");r.textContent="Choose a service to connect to your account",r.style.cssText='\n margin: 0;\n font-size: 15px;\n color: #6b7280;\n font-weight: 400;\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;\n ',s.appendChild(i),s.appendChild(r),this._providerList=new d(this.config);const a=this._providerList.render(t,t=>{this.handleProviderClick(t)});return e.appendChild(n),e.appendChild(s),e.appendChild(a),e}handleProviderClick(t){i(this.config,"Provider clicked:",t.id),this.stateManager.setState({selectedProvider:t});const e=this.stateManager.get("sessionToken");if(!e){const t=this.createError("no_session","No active session");return void this.eventEmitter.emit("error",t)}const n=this.apiClient.buildAuthorizationURL(t.id,e,this._baseURL);this._oauthHandler=new p({onSuccess:t=>{i(this.config,"OAuth success:",t),this.handleOAuthSuccess(t)},onError:t=>{i(this.config,"OAuth error:",t),this.handleOAuthError(t)},onCancel:()=>{i(this.config,"OAuth cancelled"),this.handleOAuthCancel()},popupWidth:450,popupHeight:600,debug:this.config.debug}),this._oauthHandler.openPopup(n),i(this.config,"OAuth flow started for provider:",t.id)}handleOAuthSuccess(t){this.stateManager.setState({selectedProvider:null}),this.eventEmitter.emit("success",t),this._oauthHandler&&(this._oauthHandler=null)}handleOAuthError(t){this.stateManager.setState({selectedProvider:null,error:t}),this.eventEmitter.emit("error",t),this._oauthHandler&&(this._oauthHandler=null)}handleOAuthCancel(){this.stateManager.setState({selectedProvider:null}),this.eventEmitter.emit("exit"),this._oauthHandler&&(this._oauthHandler=null)}registerEventHandlers(t){t.onSuccess&&this.eventEmitter.on("success",e=>{t.onSuccess(e),this.close()}),t.onExit&&this.eventEmitter.on("exit",()=>{t.onExit(),this.close()}),t.onError&&this.eventEmitter.on("error",e=>{t.onError(e)}),t.onEvent&&this.eventEmitter.on("event",(e,n)=>{t.onEvent(e,n)})}createError(t,e,n){const o=new Error(e);return o.code=t,o.details=n,o}normalizeError(t){if(t&&"object"==typeof t&&"code"in t&&"message"in t)return t;const e=this.createError("unknown_error",t?.message||String(t)||"An unexpected error occurred",{originalError:t});return{code:e.code,message:e.message,details:e.details}}}exports.default=g;
|
|
2
|
+
//# sourceMappingURL=alter-connect.cjs.js.map
|