fauna-api 1.0.681 → 1.0.686

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.
@@ -1,31 +1,25 @@
1
- import { Platform } from "../integrations.js";
2
- import { Profile, Credentials, ApiKey } from "../authentification.js";
3
- export type Discord = {
4
- displayName: string;
5
- handle: string;
6
- userId?: string;
7
- };
8
- export type Slack = {
9
- displayName: string;
10
- email: string;
11
- headline: string;
12
- };
13
- export type Email = {
14
- email: string;
1
+ import { Credentials, ApiKey } from "../authentification.js";
2
+ export type AddressKind = 'email' | 'phone' | 'discord';
3
+ export type Address = {
4
+ kind: AddressKind;
5
+ /** Lowercase email, E.164 phone (+12395551234), or Discord handle without @. */
6
+ value: string;
15
7
  };
16
- export type SMS = {
17
- phone: string;
18
- };
19
- export type Identity = Discord | Slack | Email | SMS;
20
8
  export type Contact = {
21
- profile: Profile;
9
+ id: string;
10
+ displayName: string;
11
+ addresses: Address[];
12
+ /** One of its emails belongs to a Fauna account: the verified badge. */
22
13
  registered: boolean;
23
- platforms: Identity[];
14
+ /** That Fauna account's picture. */
15
+ icon?: string;
24
16
  };
17
+ /** Every contact, sorted by display name. */
25
18
  export declare function list(authorization: Credentials | ApiKey): Promise<Contact[]>;
26
- export declare function create(authorization: Credentials | ApiKey, params: {
27
- email: string;
28
- displayName?: string;
29
- platform: Platform;
30
- identity?: string;
19
+ /** Creates a contact, or with `id` renames one and replaces its addresses. */
20
+ export declare function save(authorization: Credentials | ApiKey, contact: {
21
+ id?: string;
22
+ displayName: string;
23
+ addresses: Address[];
31
24
  }): Promise<Contact>;
25
+ export declare function remove(authorization: Credentials | ApiKey, id: string): Promise<void>;
@@ -1,4 +1,5 @@
1
1
  import { Cicada } from "./index.js";
2
+ /** Every contact, sorted by display name. */
2
3
  export async function list(authorization) {
3
4
  const response = await fetch(`${Cicada.getURL()}/cicada/contacts`, {
4
5
  method: "POST",
@@ -11,18 +12,29 @@ export async function list(authorization) {
11
12
  throw new Error(await response.text());
12
13
  return await response.json();
13
14
  }
14
- // Promote an anonymous sender into a contact. Email is mandatory; passing the
15
- // platform + raw identity (phone / handle / email) binds existing messages to it.
16
- export async function create(authorization, params) {
17
- const response = await fetch(`${Cicada.getURL()}/cicada/contacts/create`, {
15
+ /** Creates a contact, or with `id` renames one and replaces its addresses. */
16
+ export async function save(authorization, contact) {
17
+ const response = await fetch(`${Cicada.getURL()}/cicada/contacts/save`, {
18
18
  method: "POST",
19
19
  headers: {
20
20
  "Content-Type": "application/json",
21
21
  "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
22
22
  },
23
- body: JSON.stringify(params)
23
+ body: JSON.stringify(contact)
24
24
  });
25
25
  if (!response.ok)
26
26
  throw new Error(await response.text());
27
27
  return await response.json();
28
28
  }
29
+ export async function remove(authorization, id) {
30
+ const response = await fetch(`${Cicada.getURL()}/cicada/contacts/delete`, {
31
+ method: "POST",
32
+ headers: {
33
+ "Content-Type": "application/json",
34
+ "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
35
+ },
36
+ body: JSON.stringify({ id })
37
+ });
38
+ if (!response.ok)
39
+ throw new Error(await response.text());
40
+ }
@@ -12,6 +12,8 @@ export type Inbox = {
12
12
  name: string;
13
13
  createdAt: Date;
14
14
  group?: InboxGroup;
15
+ /** An agent is on for this inbox. */
16
+ agent?: boolean;
15
17
  };
16
18
  export type InboxAccount = {
17
19
  platform: Platform;
@@ -46,3 +48,17 @@ export declare function setAgent(authorization: Credentials | ApiKey, inbox: str
46
48
  dailyLimit: number;
47
49
  }): Promise<AgentSettings>;
48
50
  export declare function clearAgent(authorization: Credentials | ApiKey, inbox: string): Promise<void>;
51
+ /**
52
+ * Has the inbox's agent phone `to` from one of the inbox's lines (`from`, or its first)
53
+ * and follow `directions`. Returns once the call is placed; the transcript lands on the
54
+ * thread with that number. Owners only; counts toward the agent's daily limit.
55
+ */
56
+ export declare function agentCall(authorization: Credentials, inbox: string, call: {
57
+ to: string;
58
+ directions: string;
59
+ from?: string;
60
+ }): Promise<{
61
+ calling: string;
62
+ from: string;
63
+ callId: string;
64
+ }>;
@@ -25,6 +25,7 @@ function wrapInbox(raw) {
25
25
  name: raw.name,
26
26
  createdAt: new Date(raw.createdAt),
27
27
  ...(group ? { group } : {}),
28
+ ...(raw.agent ? { agent: true } : {}),
28
29
  };
29
30
  }
30
31
  export async function list(authorization) {
@@ -80,3 +81,12 @@ export async function setAgent(authorization, inbox, settings) {
80
81
  export async function clearAgent(authorization, inbox) {
81
82
  await post(authorization, '/cicada/inboxes/agent/clear', { inbox });
82
83
  }
84
+ /**
85
+ * Has the inbox's agent phone `to` from one of the inbox's lines (`from`, or its first)
86
+ * and follow `directions`. Returns once the call is placed; the transcript lands on the
87
+ * thread with that number. Owners only; counts toward the agent's daily limit.
88
+ */
89
+ export async function agentCall(authorization, inbox, call) {
90
+ const response = await post(authorization, '/cicada/inboxes/agent/call', { inbox, ...call });
91
+ return await response.json();
92
+ }
@@ -48,5 +48,8 @@ export declare class WhatsApp extends Account {
48
48
  readonly phone: string;
49
49
  constructor(authorization: Credentials, raw: any);
50
50
  }
51
+ /** Opens the provider's consent page in a popup and returns it, or `null` when the browser
52
+ * blocked the popup. The popup opens before the request so the browser still counts it as
53
+ * part of the click. */
51
54
  export declare function linkPlatform(credentials: Credentials, integration: Platform): Promise<WindowProxy | null>;
52
55
  export declare function getAccounts(credentials: Credentials): Promise<Account[]>;
@@ -176,20 +176,32 @@ function wrapAccount(authorization, platform, raw) {
176
176
  case Platform.WHATSAPP: return new WhatsApp(authorization, raw);
177
177
  }
178
178
  }
179
+ /** Opens the provider's consent page in a popup and returns it, or `null` when the browser
180
+ * blocked the popup. The popup opens before the request so the browser still counts it as
181
+ * part of the click. */
179
182
  export async function linkPlatform(credentials, integration) {
180
- const response = await fetch(`${Fauna.getURL()}/integrations/link`, {
181
- method: "POST",
182
- headers: {
183
- "Content-Type": "application/json",
184
- "Authorization": `Token ${credentials.token}`
185
- },
186
- body: JSON.stringify({ integration })
187
- });
188
- if (!response.ok) {
189
- return null;
183
+ const popup = window.open("", "_blank", "width=500,height=600");
184
+ try {
185
+ const response = await fetch(`${Fauna.getURL()}/integrations/link`, {
186
+ method: "POST",
187
+ headers: {
188
+ "Content-Type": "application/json",
189
+ "Authorization": `Token ${credentials.token}`
190
+ },
191
+ body: JSON.stringify({ integration })
192
+ });
193
+ if (!response.ok) {
194
+ throw new Error(await response.text());
195
+ }
196
+ const { url } = await response.json();
197
+ if (popup)
198
+ popup.location.href = url;
199
+ return popup;
200
+ }
201
+ catch (err) {
202
+ popup?.close();
203
+ throw err;
190
204
  }
191
- const { url } = await response.json();
192
- return window.open(url, "_blank", "width=500,height=600");
193
205
  }
194
206
  export async function getAccounts(credentials) {
195
207
  const response = await fetch(`${Fauna.getURL()}/integrations/accounts`, {
@@ -1,4 +1,5 @@
1
1
  import { Fauna } from "fauna-api";
2
+ import { sendInformaticToast } from "./toast.js";
2
3
  import gsap from "gsap";
3
4
  // Brand logos as inline SVG (currentColor-free, real brand marks) so the
4
5
  // connections panel reads as a polished product surface rather than generic
@@ -50,13 +51,17 @@ export function createConnections(credentials, options = {}) {
50
51
  grid.className = 'connections-grid';
51
52
  root.appendChild(grid);
52
53
  function fail(err) {
53
- options.onError?.(err instanceof Error ? err : new Error(String(err)));
54
+ const error = err instanceof Error ? err : new Error(String(err));
55
+ if (options.onError)
56
+ options.onError(error);
57
+ else
58
+ void sendInformaticToast(error.message || 'Could not connect.', 'error');
54
59
  }
55
60
  async function connect(platform) {
56
61
  try {
57
62
  const popup = await Fauna.Integrations.linkPlatform(credentials, platform);
58
63
  if (!popup)
59
- return;
64
+ return fail(new Error('Allow popups for this site to connect.'));
60
65
  // Refresh once the consent popup closes.
61
66
  const timer = setInterval(async () => {
62
67
  if (popup.closed) {
@@ -70,6 +75,77 @@ export function createConnections(credentials, options = {}) {
70
75
  fail(err);
71
76
  }
72
77
  }
78
+ // Twilio delivers new texts only to the number's own "A message comes in" webhook,
79
+ // which Fauna doesn't set, so a linked number shows where to point it.
80
+ function buildTwilioWebhookNote() {
81
+ const url = `${new URL(Fauna.getURL()).origin}/integrations/twilio/webhook`;
82
+ const note = document.createElement('div');
83
+ note.className = 'connection-card-note';
84
+ const head = document.createElement('div');
85
+ head.className = 'connection-card-note-head';
86
+ head.innerHTML = '<span class="material-symbols-outlined connection-card-note-icon">error</span>';
87
+ const title = document.createElement('span');
88
+ title.className = 'connection-card-note-title';
89
+ title.textContent = 'Set the webhook in Twilio';
90
+ title.title = 'New texts only reach Fauna once Twilio sends them to this webhook.';
91
+ head.appendChild(title);
92
+ const open = document.createElement('a');
93
+ open.className = 'connection-card-note-link';
94
+ open.href = 'https://console.twilio.com/us1/develop/phone-numbers/manage/incoming';
95
+ open.target = '_blank';
96
+ open.rel = 'noopener';
97
+ open.innerHTML = 'Open<span class="material-symbols-outlined">open_in_new</span>';
98
+ head.appendChild(open);
99
+ note.appendChild(head);
100
+ const steps = document.createElement('ol');
101
+ steps.className = 'connection-card-note-steps';
102
+ for (const path of [
103
+ ['Active numbers', 'your number'],
104
+ ['Messaging', 'A message comes in'],
105
+ ['Webhook, HTTP POST, this URL'],
106
+ ]) {
107
+ const step = document.createElement('li');
108
+ path.forEach((part, i) => {
109
+ if (i) {
110
+ const arrow = document.createElement('span');
111
+ arrow.className = 'material-symbols-outlined connection-card-note-arrow';
112
+ arrow.textContent = 'arrow_forward';
113
+ step.appendChild(arrow);
114
+ }
115
+ const text = document.createElement('span');
116
+ text.textContent = part;
117
+ step.appendChild(text);
118
+ });
119
+ steps.appendChild(step);
120
+ }
121
+ note.appendChild(steps);
122
+ const copy = document.createElement('button');
123
+ copy.type = 'button';
124
+ copy.className = 'connection-card-note-url';
125
+ copy.title = `Copy ${url}`;
126
+ const code = document.createElement('code');
127
+ code.textContent = url;
128
+ const icon = document.createElement('span');
129
+ icon.className = 'material-symbols-outlined';
130
+ icon.textContent = 'content_copy';
131
+ copy.append(code, icon);
132
+ copy.onclick = async () => {
133
+ try {
134
+ await navigator.clipboard.writeText(url);
135
+ icon.textContent = 'check';
136
+ copy.classList.add('copied');
137
+ setTimeout(() => {
138
+ icon.textContent = 'content_copy';
139
+ copy.classList.remove('copied');
140
+ }, 1600);
141
+ }
142
+ catch (err) {
143
+ fail(err);
144
+ }
145
+ };
146
+ note.appendChild(copy);
147
+ return note;
148
+ }
73
149
  // One card per platform. `accounts` are the already-linked accounts for this
74
150
  // platform (possibly several); each gets a labeled row with an unlink action.
75
151
  function buildCard(platform, accounts) {
@@ -136,6 +212,9 @@ export function createConnections(credentials, options = {}) {
136
212
  }
137
213
  card.appendChild(accountsBox);
138
214
  }
215
+ if (platform === Fauna.Integrations.Platform.TWILIO && accounts.length) {
216
+ card.appendChild(buildTwilioWebhookNote());
217
+ }
139
218
  // Connect / add-another action.
140
219
  const connectBtn = document.createElement('button');
141
220
  connectBtn.type = 'button';
@@ -2417,6 +2417,143 @@ button.chat-message-file {
2417
2417
  color: color-mix(in srgb, var(--text, #1a1a18) 70%, transparent);
2418
2418
  }
2419
2419
 
2420
+ .connection-card-note {
2421
+ display: flex;
2422
+ flex-direction: column;
2423
+ gap: 8px;
2424
+
2425
+ padding: 10px 12px 12px;
2426
+
2427
+ border: 1px solid color-mix(in srgb, #f59e0b 34%, transparent);
2428
+ border-radius: 10px;
2429
+ background: color-mix(in srgb, #f59e0b 9%, transparent);
2430
+ }
2431
+ .connection-card-note-head {
2432
+ display: flex;
2433
+ align-items: center;
2434
+ gap: 7px;
2435
+ }
2436
+ .connection-card-note-icon {
2437
+ flex: 0 0 auto;
2438
+ font-size: 18px !important;
2439
+ color: #d97706;
2440
+ }
2441
+ .connection-card-note-title {
2442
+ flex: 1;
2443
+ min-width: 0;
2444
+
2445
+ font-size: 12.5px;
2446
+ font-weight: 600;
2447
+ line-height: 1.3;
2448
+ color: var(--text, #1a1a18);
2449
+ }
2450
+ .connection-card-note-link {
2451
+ display: inline-flex;
2452
+ align-items: center;
2453
+ gap: 2px;
2454
+
2455
+ font-size: 12px;
2456
+ font-weight: 600;
2457
+ white-space: nowrap;
2458
+ text-decoration: none;
2459
+ color: var(--connection-note-accent, #b45309);
2460
+ }
2461
+ .connection-card-note-link span {
2462
+ font-size: 14px !important;
2463
+ }
2464
+ .connection-card-note-link:hover {
2465
+ text-decoration: underline;
2466
+ }
2467
+ .connection-card-note-steps {
2468
+ display: flex;
2469
+ flex-direction: column;
2470
+ gap: 5px;
2471
+
2472
+ margin: 0;
2473
+ padding: 0 0 0 25px;
2474
+
2475
+ list-style: none;
2476
+ counter-reset: connection-step;
2477
+ }
2478
+ .connection-card-note-steps li {
2479
+ position: relative;
2480
+
2481
+ display: flex;
2482
+ flex-wrap: wrap;
2483
+ align-items: center;
2484
+ gap: 2px 4px;
2485
+
2486
+ font-size: 12px;
2487
+ line-height: 16px;
2488
+ color: color-mix(in srgb, var(--text, #1a1a18) 78%, transparent);
2489
+
2490
+ counter-increment: connection-step;
2491
+ }
2492
+ .connection-card-note-steps li::before {
2493
+ content: counter(connection-step);
2494
+
2495
+ position: absolute;
2496
+ left: -25px;
2497
+ top: 0;
2498
+
2499
+ display: flex;
2500
+ align-items: center;
2501
+ justify-content: center;
2502
+
2503
+ width: 16px;
2504
+ height: 16px;
2505
+
2506
+ border-radius: 50%;
2507
+ background: color-mix(in srgb, #f59e0b 20%, transparent);
2508
+ color: var(--connection-note-accent, #b45309);
2509
+
2510
+ font-size: 10px;
2511
+ font-weight: 700;
2512
+ }
2513
+ .connection-card-note-arrow {
2514
+ font-size: 13px !important;
2515
+ color: color-mix(in srgb, var(--text, #1a1a18) 38%, transparent);
2516
+ }
2517
+ .connection-card-note-url {
2518
+ display: flex;
2519
+ align-items: center;
2520
+ gap: 8px;
2521
+
2522
+ width: 100%;
2523
+ padding: 6px 8px 6px 10px;
2524
+
2525
+ border: 1px solid color-mix(in srgb, var(--text, #1a1a18) 12%, transparent);
2526
+ border-radius: 8px;
2527
+ background: var(--surface, #fff);
2528
+ color: var(--text, #1a1a18);
2529
+
2530
+ cursor: pointer;
2531
+ text-align: left;
2532
+ transition: border-color 0.15s ease;
2533
+ }
2534
+ .connection-card-note-url code {
2535
+ flex: 1;
2536
+ min-width: 0;
2537
+
2538
+ overflow: hidden;
2539
+ text-overflow: ellipsis;
2540
+ white-space: nowrap;
2541
+
2542
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
2543
+ font-size: 11.5px;
2544
+ }
2545
+ .connection-card-note-url span {
2546
+ font-size: 15px !important;
2547
+ color: color-mix(in srgb, var(--text, #1a1a18) 50%, transparent);
2548
+ transition: color 0.15s ease;
2549
+ }
2550
+ .connection-card-note-url:hover {
2551
+ border-color: color-mix(in srgb, var(--text, #1a1a18) 26%, transparent);
2552
+ }
2553
+ .connection-card-note-url.copied span {
2554
+ color: #16a34a;
2555
+ }
2556
+
2420
2557
 
2421
2558
  /* Social sign-in buttons (UI.Authentification.createOAuthButtons).
2422
2559
  Themed via --oauth-* custom properties so each app can recolor without
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fauna-api",
3
- "version": "1.0.681",
3
+ "version": "1.0.686",
4
4
  "description": "The Fauna SDK: accounts, Drive, groups, calendar, chats, payments and every Fauna app (Parrot, Marmot, Magpie, Cicada, Ant, Termite), plus React UI.",
5
5
  "homepage": "https://docs.faunakit.com",
6
6
  "repository": {