farvex 0.1.0 → 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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/index.ts"],"names":[],"mappings":";;;AAAO,IAAM,gBAAA,GAAmB","file":"index.cjs","sourcesContent":["export const reactPlaceholder = \"farvex/react\";\n"]}
1
+ {"version":3,"sources":["../../src/react/index.tsx"],"names":["createContext","useEffect","useContext","useSyncExternalStore","useState"],"mappings":"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","file":"index.cjs","sourcesContent":["import {\n createContext,\n useContext,\n useEffect,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from \"react\";\n\nimport type { Nullable } from \"../shared/types\";\nimport type {\n ClientStatus,\n HostClient,\n SessionAction,\n SessionClient,\n SessionDuration,\n SessionInvite,\n VoiceSession,\n} from \"../core/types\";\n\nconst CallpadContext = createContext<SessionClient | HostClient | null>(null);\n\nexport type CallpadProviderProps<TClient extends SessionClient = SessionClient> = {\n client: TClient;\n connect?: boolean;\n disposeOnUnmount?: boolean;\n children: ReactNode;\n};\n\nexport const CallpadProvider = <TClient extends SessionClient>({\n client,\n connect = false,\n disposeOnUnmount = false,\n children,\n}: CallpadProviderProps<TClient>) => {\n useEffect(() => {\n if (!connect) {\n return;\n }\n void client.connect().catch(() => undefined);\n return () => {\n if (disposeOnUnmount) {\n void client.dispose();\n return;\n }\n void client.disconnect();\n };\n }, [client, connect, disposeOnUnmount]);\n\n return <CallpadContext.Provider value={client}>{children}</CallpadContext.Provider>;\n};\n\nexport const useCallpad = <TClient extends SessionClient = SessionClient>(): TClient => {\n const client = useContext(CallpadContext);\n if (!client) {\n throw new Error(\"CallpadProvider is required\");\n }\n return client as TClient;\n};\n\nexport const useStatus = (): ClientStatus => {\n const client = useCallpad();\n return useSyncExternalStore(\n (notify) => client.subscribe(\"status\", notify),\n () => client.status,\n () => \"idle\",\n );\n};\n\nexport const useSessions = (): readonly VoiceSession[] => {\n const client = useCallpad();\n return useSyncExternalStore(\n (notify) => client.subscribe(\"sessions\", notify),\n () => client.sessions.list(),\n () => [],\n );\n};\n\nexport const useSession = (sessionId?: string): Nullable<VoiceSession> => {\n const client = useCallpad();\n return useSyncExternalStore(\n (notify) => client.subscribe(sessionId ? `session:${sessionId}` : \"sessions\", notify),\n () => client.sessions.get(sessionId),\n () => null,\n );\n};\n\nexport const useIncomingInvites = (): readonly SessionInvite[] => {\n const client = useCallpad();\n return useSyncExternalStore(\n (notify) => client.subscribe(\"invites\", notify),\n () => client.invites.list(),\n () => [],\n );\n};\n\nexport const useIncomingInvite = (): Nullable<SessionInvite> => {\n const invites = useIncomingInvites();\n return invites[0] ?? null;\n};\n\nexport const useCan = (sessionId: string | undefined, action: SessionAction): boolean => {\n const client = useCallpad();\n const session = useSession(sessionId);\n if (!session) {\n return false;\n }\n return client.sessions.can(session, action);\n};\n\nexport const useSessionDuration = (sessionId?: string): SessionDuration => {\n const session = useSession(sessionId);\n const [now, setNow] = useState(() => Date.now());\n\n useEffect(() => {\n if (!session || session.state === \"ended\") {\n return;\n }\n setNow(Date.now());\n const timer = window.setInterval(() => {\n setNow(Date.now());\n }, 1000);\n return () => {\n window.clearInterval(timer);\n };\n }, [session]);\n\n return durationOf(session, now);\n};\n\nconst durationOf = (session: Nullable<VoiceSession>, now: number): SessionDuration => {\n if (!session) {\n return { seconds: 0, startedAt: null, endedAt: null, running: false };\n }\n const startedAt = session.answeredAt ?? session.createdAt;\n const endedAt = session.endedAt;\n const end = endedAt ? Date.parse(endedAt) : now;\n return {\n seconds: Math.max(0, Math.floor((end - Date.parse(startedAt)) / 1000)),\n startedAt,\n endedAt,\n running: !endedAt && session.state !== \"ended\",\n };\n};\n"]}
@@ -1,3 +1,21 @@
1
- declare const reactPlaceholder = "farvex/react";
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { i as SessionClient, S as SessionAction, N as Nullable, l as SessionInvite, s as VoiceSession, k as SessionDuration, b as ClientStatus } from '../types-DhJEeeui.cjs';
2
4
 
3
- export { reactPlaceholder };
5
+ type CallpadProviderProps<TClient extends SessionClient = SessionClient> = {
6
+ client: TClient;
7
+ connect?: boolean;
8
+ disposeOnUnmount?: boolean;
9
+ children: ReactNode;
10
+ };
11
+ declare const CallpadProvider: <TClient extends SessionClient>({ client, connect, disposeOnUnmount, children, }: CallpadProviderProps<TClient>) => react_jsx_runtime.JSX.Element;
12
+ declare const useCallpad: <TClient extends SessionClient = SessionClient>() => TClient;
13
+ declare const useStatus: () => ClientStatus;
14
+ declare const useSessions: () => readonly VoiceSession[];
15
+ declare const useSession: (sessionId?: string) => Nullable<VoiceSession>;
16
+ declare const useIncomingInvites: () => readonly SessionInvite[];
17
+ declare const useIncomingInvite: () => Nullable<SessionInvite>;
18
+ declare const useCan: (sessionId: string | undefined, action: SessionAction) => boolean;
19
+ declare const useSessionDuration: (sessionId?: string) => SessionDuration;
20
+
21
+ export { CallpadProvider, type CallpadProviderProps, useCallpad, useCan, useIncomingInvite, useIncomingInvites, useSession, useSessionDuration, useSessions, useStatus };
@@ -1,3 +1,21 @@
1
- declare const reactPlaceholder = "farvex/react";
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { i as SessionClient, S as SessionAction, N as Nullable, l as SessionInvite, s as VoiceSession, k as SessionDuration, b as ClientStatus } from '../types-DhJEeeui.js';
2
4
 
3
- export { reactPlaceholder };
5
+ type CallpadProviderProps<TClient extends SessionClient = SessionClient> = {
6
+ client: TClient;
7
+ connect?: boolean;
8
+ disposeOnUnmount?: boolean;
9
+ children: ReactNode;
10
+ };
11
+ declare const CallpadProvider: <TClient extends SessionClient>({ client, connect, disposeOnUnmount, children, }: CallpadProviderProps<TClient>) => react_jsx_runtime.JSX.Element;
12
+ declare const useCallpad: <TClient extends SessionClient = SessionClient>() => TClient;
13
+ declare const useStatus: () => ClientStatus;
14
+ declare const useSessions: () => readonly VoiceSession[];
15
+ declare const useSession: (sessionId?: string) => Nullable<VoiceSession>;
16
+ declare const useIncomingInvites: () => readonly SessionInvite[];
17
+ declare const useIncomingInvite: () => Nullable<SessionInvite>;
18
+ declare const useCan: (sessionId: string | undefined, action: SessionAction) => boolean;
19
+ declare const useSessionDuration: (sessionId?: string) => SessionDuration;
20
+
21
+ export { CallpadProvider, type CallpadProviderProps, useCallpad, useCan, useIncomingInvite, useIncomingInvites, useSession, useSessionDuration, useSessions, useStatus };
@@ -1,7 +1,113 @@
1
1
  "use client";
2
- // src/react/index.ts
3
- var reactPlaceholder = "farvex/react";
2
+ import { createContext, useEffect, useContext, useSyncExternalStore, useState } from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
4
 
5
- export { reactPlaceholder };
5
+ // src/react/index.tsx
6
+ var CallpadContext = createContext(null);
7
+ var CallpadProvider = ({
8
+ client,
9
+ connect = false,
10
+ disposeOnUnmount = false,
11
+ children
12
+ }) => {
13
+ useEffect(() => {
14
+ if (!connect) {
15
+ return;
16
+ }
17
+ void client.connect().catch(() => void 0);
18
+ return () => {
19
+ if (disposeOnUnmount) {
20
+ void client.dispose();
21
+ return;
22
+ }
23
+ void client.disconnect();
24
+ };
25
+ }, [client, connect, disposeOnUnmount]);
26
+ return /* @__PURE__ */ jsx(CallpadContext.Provider, { value: client, children });
27
+ };
28
+ var useCallpad = () => {
29
+ const client = useContext(CallpadContext);
30
+ if (!client) {
31
+ throw new Error("CallpadProvider is required");
32
+ }
33
+ return client;
34
+ };
35
+ var useStatus = () => {
36
+ const client = useCallpad();
37
+ return useSyncExternalStore(
38
+ (notify) => client.subscribe("status", notify),
39
+ () => client.status,
40
+ () => "idle"
41
+ );
42
+ };
43
+ var useSessions = () => {
44
+ const client = useCallpad();
45
+ return useSyncExternalStore(
46
+ (notify) => client.subscribe("sessions", notify),
47
+ () => client.sessions.list(),
48
+ () => []
49
+ );
50
+ };
51
+ var useSession = (sessionId) => {
52
+ const client = useCallpad();
53
+ return useSyncExternalStore(
54
+ (notify) => client.subscribe(sessionId ? `session:${sessionId}` : "sessions", notify),
55
+ () => client.sessions.get(sessionId),
56
+ () => null
57
+ );
58
+ };
59
+ var useIncomingInvites = () => {
60
+ const client = useCallpad();
61
+ return useSyncExternalStore(
62
+ (notify) => client.subscribe("invites", notify),
63
+ () => client.invites.list(),
64
+ () => []
65
+ );
66
+ };
67
+ var useIncomingInvite = () => {
68
+ const invites = useIncomingInvites();
69
+ return invites[0] ?? null;
70
+ };
71
+ var useCan = (sessionId, action) => {
72
+ const client = useCallpad();
73
+ const session = useSession(sessionId);
74
+ if (!session) {
75
+ return false;
76
+ }
77
+ return client.sessions.can(session, action);
78
+ };
79
+ var useSessionDuration = (sessionId) => {
80
+ const session = useSession(sessionId);
81
+ const [now, setNow] = useState(() => Date.now());
82
+ useEffect(() => {
83
+ if (!session || session.state === "ended") {
84
+ return;
85
+ }
86
+ setNow(Date.now());
87
+ const timer = window.setInterval(() => {
88
+ setNow(Date.now());
89
+ }, 1e3);
90
+ return () => {
91
+ window.clearInterval(timer);
92
+ };
93
+ }, [session]);
94
+ return durationOf(session, now);
95
+ };
96
+ var durationOf = (session, now) => {
97
+ if (!session) {
98
+ return { seconds: 0, startedAt: null, endedAt: null, running: false };
99
+ }
100
+ const startedAt = session.answeredAt ?? session.createdAt;
101
+ const endedAt = session.endedAt;
102
+ const end = endedAt ? Date.parse(endedAt) : now;
103
+ return {
104
+ seconds: Math.max(0, Math.floor((end - Date.parse(startedAt)) / 1e3)),
105
+ startedAt,
106
+ endedAt,
107
+ running: !endedAt && session.state !== "ended"
108
+ };
109
+ };
110
+
111
+ export { CallpadProvider, useCallpad, useCan, useIncomingInvite, useIncomingInvites, useSession, useSessionDuration, useSessions, useStatus };
6
112
  //# sourceMappingURL=index.js.map
7
113
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/index.ts"],"names":[],"mappings":";AAAO,IAAM,gBAAA,GAAmB","file":"index.js","sourcesContent":["export const reactPlaceholder = \"farvex/react\";\n"]}
1
+ {"version":3,"sources":["../../src/react/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import {\n createContext,\n useContext,\n useEffect,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from \"react\";\n\nimport type { Nullable } from \"../shared/types\";\nimport type {\n ClientStatus,\n HostClient,\n SessionAction,\n SessionClient,\n SessionDuration,\n SessionInvite,\n VoiceSession,\n} from \"../core/types\";\n\nconst CallpadContext = createContext<SessionClient | HostClient | null>(null);\n\nexport type CallpadProviderProps<TClient extends SessionClient = SessionClient> = {\n client: TClient;\n connect?: boolean;\n disposeOnUnmount?: boolean;\n children: ReactNode;\n};\n\nexport const CallpadProvider = <TClient extends SessionClient>({\n client,\n connect = false,\n disposeOnUnmount = false,\n children,\n}: CallpadProviderProps<TClient>) => {\n useEffect(() => {\n if (!connect) {\n return;\n }\n void client.connect().catch(() => undefined);\n return () => {\n if (disposeOnUnmount) {\n void client.dispose();\n return;\n }\n void client.disconnect();\n };\n }, [client, connect, disposeOnUnmount]);\n\n return <CallpadContext.Provider value={client}>{children}</CallpadContext.Provider>;\n};\n\nexport const useCallpad = <TClient extends SessionClient = SessionClient>(): TClient => {\n const client = useContext(CallpadContext);\n if (!client) {\n throw new Error(\"CallpadProvider is required\");\n }\n return client as TClient;\n};\n\nexport const useStatus = (): ClientStatus => {\n const client = useCallpad();\n return useSyncExternalStore(\n (notify) => client.subscribe(\"status\", notify),\n () => client.status,\n () => \"idle\",\n );\n};\n\nexport const useSessions = (): readonly VoiceSession[] => {\n const client = useCallpad();\n return useSyncExternalStore(\n (notify) => client.subscribe(\"sessions\", notify),\n () => client.sessions.list(),\n () => [],\n );\n};\n\nexport const useSession = (sessionId?: string): Nullable<VoiceSession> => {\n const client = useCallpad();\n return useSyncExternalStore(\n (notify) => client.subscribe(sessionId ? `session:${sessionId}` : \"sessions\", notify),\n () => client.sessions.get(sessionId),\n () => null,\n );\n};\n\nexport const useIncomingInvites = (): readonly SessionInvite[] => {\n const client = useCallpad();\n return useSyncExternalStore(\n (notify) => client.subscribe(\"invites\", notify),\n () => client.invites.list(),\n () => [],\n );\n};\n\nexport const useIncomingInvite = (): Nullable<SessionInvite> => {\n const invites = useIncomingInvites();\n return invites[0] ?? null;\n};\n\nexport const useCan = (sessionId: string | undefined, action: SessionAction): boolean => {\n const client = useCallpad();\n const session = useSession(sessionId);\n if (!session) {\n return false;\n }\n return client.sessions.can(session, action);\n};\n\nexport const useSessionDuration = (sessionId?: string): SessionDuration => {\n const session = useSession(sessionId);\n const [now, setNow] = useState(() => Date.now());\n\n useEffect(() => {\n if (!session || session.state === \"ended\") {\n return;\n }\n setNow(Date.now());\n const timer = window.setInterval(() => {\n setNow(Date.now());\n }, 1000);\n return () => {\n window.clearInterval(timer);\n };\n }, [session]);\n\n return durationOf(session, now);\n};\n\nconst durationOf = (session: Nullable<VoiceSession>, now: number): SessionDuration => {\n if (!session) {\n return { seconds: 0, startedAt: null, endedAt: null, running: false };\n }\n const startedAt = session.answeredAt ?? session.createdAt;\n const endedAt = session.endedAt;\n const end = endedAt ? Date.parse(endedAt) : now;\n return {\n seconds: Math.max(0, Math.floor((end - Date.parse(startedAt)) / 1000)),\n startedAt,\n endedAt,\n running: !endedAt && session.state !== \"ended\",\n };\n};\n"]}
@@ -0,0 +1,206 @@
1
+ type Auth = {
2
+ getAccessToken: () => string | Promise<string>;
3
+ onUnauthorized?: () => void | Promise<void>;
4
+ };
5
+ declare class CallpadError extends Error {
6
+ readonly code: string;
7
+ readonly status?: number | undefined;
8
+ constructor(code: string, message: string, status?: number | undefined);
9
+ }
10
+
11
+ type RealtimeToken = {
12
+ url: string;
13
+ token: string;
14
+ channel: string;
15
+ expiresAt: string;
16
+ };
17
+ type VoiceDirection = 'inbound' | 'outbound';
18
+ type VoiceSessionState = 'ringing' | 'active' | 'ended';
19
+ type VoiceEndReason = 'completed' | 'declined' | 'closed' | 'no_route';
20
+ type VoiceParticipantKind = 'agent' | 'vendor_user' | 'customer' | 'contact' | 'phone';
21
+ type VoiceParticipantRole = 'host' | 'participant';
22
+ type VoiceTransport = 'webrtc' | 'sip';
23
+ type VoiceParticipantState = 'invited' | 'accepted' | 'joined' | 'left' | 'rejected' | 'removed';
24
+ type VoiceParticipant = {
25
+ id: string;
26
+ kind: VoiceParticipantKind;
27
+ role: VoiceParticipantRole;
28
+ transport: VoiceTransport;
29
+ state: VoiceParticipantState;
30
+ identity: string;
31
+ userId: number | null;
32
+ agentId: string | null;
33
+ contactId: string | null;
34
+ phone: string | null;
35
+ firstName: string | null;
36
+ lastName: string | null;
37
+ profilePhoto: string | null;
38
+ inviteExpiresAt: string | null;
39
+ acceptedAt: string | null;
40
+ joinedAt: string | null;
41
+ leftAt: string | null;
42
+ };
43
+ type VoiceSession = {
44
+ id: string;
45
+ vendor: string;
46
+ version: number;
47
+ direction: VoiceDirection;
48
+ state: VoiceSessionState;
49
+ createdAt: string;
50
+ answeredAt: string | null;
51
+ endedAt: string | null;
52
+ endReason: VoiceEndReason | null;
53
+ recording: SessionRecording;
54
+ participants: Array<VoiceParticipant>;
55
+ };
56
+ type SessionRecordingStatus = 'off' | 'starting' | 'recording' | 'stopping';
57
+ type SessionRecording = {
58
+ status: SessionRecordingStatus;
59
+ startedAt: string | null;
60
+ };
61
+ type SessionRecordingControl = {
62
+ sessionId: string;
63
+ sessionVersion: number;
64
+ recording: SessionRecording;
65
+ };
66
+ type VoiceJoinGrant = {
67
+ url: string;
68
+ roomName: string;
69
+ token: string;
70
+ participantIdentity: string;
71
+ participantId: string;
72
+ expiresAt: string;
73
+ };
74
+ type StartedSession = {
75
+ session: VoiceSession;
76
+ join: VoiceJoinGrant | null;
77
+ };
78
+ type ListSessionsResponse = {
79
+ items: Array<VoiceSession>;
80
+ };
81
+ type ListSessionsData = {
82
+ body?: never;
83
+ path?: never;
84
+ query?: {
85
+ vendor?: string;
86
+ state?: 'active' | 'ended' | 'all';
87
+ before?: string;
88
+ limit?: number;
89
+ };
90
+ url: '/api/v1/sessions';
91
+ };
92
+
93
+ type Nullable<T> = T | null;
94
+
95
+ type ClientStatus = "idle" | "connecting" | "ready" | "reconnecting" | "offline" | "disposed";
96
+ type SessionAction = "accept" | "reject" | "join" | "leave" | "end" | "addParticipant" | "removeParticipant" | "startRecording" | "stopRecording";
97
+ type Unsubscribe = () => void;
98
+ type StoreTopic = "status" | "sessions" | "invites" | `session:${string}`;
99
+ type SessionInvite = {
100
+ session: VoiceSession;
101
+ participant: VoiceParticipant;
102
+ };
103
+ type SessionDuration = {
104
+ seconds: number;
105
+ startedAt: Nullable<string>;
106
+ endedAt: Nullable<string>;
107
+ running: boolean;
108
+ };
109
+ type ClientEvents = {
110
+ status: ClientStatus;
111
+ "session.added": VoiceSession;
112
+ "session.updated": VoiceSession;
113
+ "session.removed": {
114
+ sessionId: string;
115
+ vendor?: string;
116
+ version?: number;
117
+ };
118
+ error: Error;
119
+ };
120
+ type SessionClientConfig = {
121
+ apiUrl: string;
122
+ userId: number;
123
+ auth: Auth;
124
+ fetch?: typeof fetch;
125
+ };
126
+ type HostClientConfig = SessionClientConfig & {
127
+ vendor: string;
128
+ };
129
+ type ListQuery = NonNullable<ListSessionsData["query"]>;
130
+ type CustomerStartInput = {
131
+ vendor: string;
132
+ target: {
133
+ type: "vendor_user";
134
+ userId: number;
135
+ };
136
+ };
137
+ type HostStartInput = {
138
+ target: {
139
+ type: "customer";
140
+ userId: number;
141
+ } | {
142
+ type: "contact";
143
+ contactId: string;
144
+ } | {
145
+ type: "phone";
146
+ phone: string;
147
+ };
148
+ };
149
+ type ParticipantRef = {
150
+ sessionId: string;
151
+ participantId: string;
152
+ };
153
+ type HostParticipantInput = {
154
+ sessionId: string;
155
+ target: {
156
+ type: "contact";
157
+ contactId: string;
158
+ } | {
159
+ type: "phone";
160
+ phone: string;
161
+ };
162
+ };
163
+ type Invites = {
164
+ list: () => readonly SessionInvite[];
165
+ get: (sessionId: string) => Nullable<SessionInvite>;
166
+ };
167
+ type Sessions = {
168
+ list: () => readonly VoiceSession[];
169
+ get: (sessionId?: string) => Nullable<VoiceSession>;
170
+ sync: (query?: ListQuery) => Promise<readonly VoiceSession[]>;
171
+ start: (input: CustomerStartInput) => Promise<StartedSession>;
172
+ join: (sessionId: string) => Promise<VoiceJoinGrant>;
173
+ accept: (input: ParticipantRef) => Promise<StartedSession>;
174
+ reject: (input: ParticipantRef) => Promise<VoiceSession>;
175
+ leave: (sessionId: string) => Promise<VoiceSession>;
176
+ can: (session: VoiceSession, action: SessionAction) => boolean;
177
+ };
178
+ type HostSessions = Omit<Sessions, "start"> & {
179
+ start: (input: HostStartInput) => Promise<StartedSession>;
180
+ };
181
+ type HostControls = {
182
+ end: (sessionId: string) => Promise<void>;
183
+ addParticipant: (input: HostParticipantInput) => Promise<StartedSession>;
184
+ removeParticipant: (input: ParticipantRef) => Promise<VoiceSession>;
185
+ startRecording: (sessionId: string) => Promise<SessionRecordingControl>;
186
+ stopRecording: (sessionId: string) => Promise<SessionRecordingControl>;
187
+ };
188
+ type SessionClient = {
189
+ readonly kind: "session";
190
+ readonly status: ClientStatus;
191
+ readonly sessions: Sessions;
192
+ readonly invites: Invites;
193
+ connect: () => Promise<void>;
194
+ disconnect: () => Promise<void>;
195
+ dispose: () => Promise<void>;
196
+ on: <K extends keyof ClientEvents>(event: K, fn: (payload: ClientEvents[K]) => void) => Unsubscribe;
197
+ subscribe: (topic: StoreTopic, fn: () => void) => Unsubscribe;
198
+ };
199
+ type HostClient = Omit<SessionClient, "kind" | "sessions"> & {
200
+ readonly kind: "host";
201
+ readonly vendor: string;
202
+ readonly sessions: HostSessions;
203
+ readonly host: HostControls;
204
+ };
205
+
206
+ export { CallpadError as C, type HostClient as H, type Invites as I, type ListQuery as L, type Nullable as N, type ParticipantRef as P, type RealtimeToken as R, type SessionAction as S, type Unsubscribe as U, type VoiceJoinGrant as V, type ClientEvents as a, type ClientStatus as b, type CustomerStartInput as c, type HostClientConfig as d, type HostControls as e, type HostParticipantInput as f, type HostSessions as g, type ListSessionsResponse as h, type SessionClient as i, type SessionClientConfig as j, type SessionDuration as k, type SessionInvite as l, type SessionRecording as m, type SessionRecordingControl as n, type Sessions as o, type StartedSession as p, type StoreTopic as q, type VoiceParticipant as r, type VoiceSession as s };
@@ -0,0 +1,206 @@
1
+ type Auth = {
2
+ getAccessToken: () => string | Promise<string>;
3
+ onUnauthorized?: () => void | Promise<void>;
4
+ };
5
+ declare class CallpadError extends Error {
6
+ readonly code: string;
7
+ readonly status?: number | undefined;
8
+ constructor(code: string, message: string, status?: number | undefined);
9
+ }
10
+
11
+ type RealtimeToken = {
12
+ url: string;
13
+ token: string;
14
+ channel: string;
15
+ expiresAt: string;
16
+ };
17
+ type VoiceDirection = 'inbound' | 'outbound';
18
+ type VoiceSessionState = 'ringing' | 'active' | 'ended';
19
+ type VoiceEndReason = 'completed' | 'declined' | 'closed' | 'no_route';
20
+ type VoiceParticipantKind = 'agent' | 'vendor_user' | 'customer' | 'contact' | 'phone';
21
+ type VoiceParticipantRole = 'host' | 'participant';
22
+ type VoiceTransport = 'webrtc' | 'sip';
23
+ type VoiceParticipantState = 'invited' | 'accepted' | 'joined' | 'left' | 'rejected' | 'removed';
24
+ type VoiceParticipant = {
25
+ id: string;
26
+ kind: VoiceParticipantKind;
27
+ role: VoiceParticipantRole;
28
+ transport: VoiceTransport;
29
+ state: VoiceParticipantState;
30
+ identity: string;
31
+ userId: number | null;
32
+ agentId: string | null;
33
+ contactId: string | null;
34
+ phone: string | null;
35
+ firstName: string | null;
36
+ lastName: string | null;
37
+ profilePhoto: string | null;
38
+ inviteExpiresAt: string | null;
39
+ acceptedAt: string | null;
40
+ joinedAt: string | null;
41
+ leftAt: string | null;
42
+ };
43
+ type VoiceSession = {
44
+ id: string;
45
+ vendor: string;
46
+ version: number;
47
+ direction: VoiceDirection;
48
+ state: VoiceSessionState;
49
+ createdAt: string;
50
+ answeredAt: string | null;
51
+ endedAt: string | null;
52
+ endReason: VoiceEndReason | null;
53
+ recording: SessionRecording;
54
+ participants: Array<VoiceParticipant>;
55
+ };
56
+ type SessionRecordingStatus = 'off' | 'starting' | 'recording' | 'stopping';
57
+ type SessionRecording = {
58
+ status: SessionRecordingStatus;
59
+ startedAt: string | null;
60
+ };
61
+ type SessionRecordingControl = {
62
+ sessionId: string;
63
+ sessionVersion: number;
64
+ recording: SessionRecording;
65
+ };
66
+ type VoiceJoinGrant = {
67
+ url: string;
68
+ roomName: string;
69
+ token: string;
70
+ participantIdentity: string;
71
+ participantId: string;
72
+ expiresAt: string;
73
+ };
74
+ type StartedSession = {
75
+ session: VoiceSession;
76
+ join: VoiceJoinGrant | null;
77
+ };
78
+ type ListSessionsResponse = {
79
+ items: Array<VoiceSession>;
80
+ };
81
+ type ListSessionsData = {
82
+ body?: never;
83
+ path?: never;
84
+ query?: {
85
+ vendor?: string;
86
+ state?: 'active' | 'ended' | 'all';
87
+ before?: string;
88
+ limit?: number;
89
+ };
90
+ url: '/api/v1/sessions';
91
+ };
92
+
93
+ type Nullable<T> = T | null;
94
+
95
+ type ClientStatus = "idle" | "connecting" | "ready" | "reconnecting" | "offline" | "disposed";
96
+ type SessionAction = "accept" | "reject" | "join" | "leave" | "end" | "addParticipant" | "removeParticipant" | "startRecording" | "stopRecording";
97
+ type Unsubscribe = () => void;
98
+ type StoreTopic = "status" | "sessions" | "invites" | `session:${string}`;
99
+ type SessionInvite = {
100
+ session: VoiceSession;
101
+ participant: VoiceParticipant;
102
+ };
103
+ type SessionDuration = {
104
+ seconds: number;
105
+ startedAt: Nullable<string>;
106
+ endedAt: Nullable<string>;
107
+ running: boolean;
108
+ };
109
+ type ClientEvents = {
110
+ status: ClientStatus;
111
+ "session.added": VoiceSession;
112
+ "session.updated": VoiceSession;
113
+ "session.removed": {
114
+ sessionId: string;
115
+ vendor?: string;
116
+ version?: number;
117
+ };
118
+ error: Error;
119
+ };
120
+ type SessionClientConfig = {
121
+ apiUrl: string;
122
+ userId: number;
123
+ auth: Auth;
124
+ fetch?: typeof fetch;
125
+ };
126
+ type HostClientConfig = SessionClientConfig & {
127
+ vendor: string;
128
+ };
129
+ type ListQuery = NonNullable<ListSessionsData["query"]>;
130
+ type CustomerStartInput = {
131
+ vendor: string;
132
+ target: {
133
+ type: "vendor_user";
134
+ userId: number;
135
+ };
136
+ };
137
+ type HostStartInput = {
138
+ target: {
139
+ type: "customer";
140
+ userId: number;
141
+ } | {
142
+ type: "contact";
143
+ contactId: string;
144
+ } | {
145
+ type: "phone";
146
+ phone: string;
147
+ };
148
+ };
149
+ type ParticipantRef = {
150
+ sessionId: string;
151
+ participantId: string;
152
+ };
153
+ type HostParticipantInput = {
154
+ sessionId: string;
155
+ target: {
156
+ type: "contact";
157
+ contactId: string;
158
+ } | {
159
+ type: "phone";
160
+ phone: string;
161
+ };
162
+ };
163
+ type Invites = {
164
+ list: () => readonly SessionInvite[];
165
+ get: (sessionId: string) => Nullable<SessionInvite>;
166
+ };
167
+ type Sessions = {
168
+ list: () => readonly VoiceSession[];
169
+ get: (sessionId?: string) => Nullable<VoiceSession>;
170
+ sync: (query?: ListQuery) => Promise<readonly VoiceSession[]>;
171
+ start: (input: CustomerStartInput) => Promise<StartedSession>;
172
+ join: (sessionId: string) => Promise<VoiceJoinGrant>;
173
+ accept: (input: ParticipantRef) => Promise<StartedSession>;
174
+ reject: (input: ParticipantRef) => Promise<VoiceSession>;
175
+ leave: (sessionId: string) => Promise<VoiceSession>;
176
+ can: (session: VoiceSession, action: SessionAction) => boolean;
177
+ };
178
+ type HostSessions = Omit<Sessions, "start"> & {
179
+ start: (input: HostStartInput) => Promise<StartedSession>;
180
+ };
181
+ type HostControls = {
182
+ end: (sessionId: string) => Promise<void>;
183
+ addParticipant: (input: HostParticipantInput) => Promise<StartedSession>;
184
+ removeParticipant: (input: ParticipantRef) => Promise<VoiceSession>;
185
+ startRecording: (sessionId: string) => Promise<SessionRecordingControl>;
186
+ stopRecording: (sessionId: string) => Promise<SessionRecordingControl>;
187
+ };
188
+ type SessionClient = {
189
+ readonly kind: "session";
190
+ readonly status: ClientStatus;
191
+ readonly sessions: Sessions;
192
+ readonly invites: Invites;
193
+ connect: () => Promise<void>;
194
+ disconnect: () => Promise<void>;
195
+ dispose: () => Promise<void>;
196
+ on: <K extends keyof ClientEvents>(event: K, fn: (payload: ClientEvents[K]) => void) => Unsubscribe;
197
+ subscribe: (topic: StoreTopic, fn: () => void) => Unsubscribe;
198
+ };
199
+ type HostClient = Omit<SessionClient, "kind" | "sessions"> & {
200
+ readonly kind: "host";
201
+ readonly vendor: string;
202
+ readonly sessions: HostSessions;
203
+ readonly host: HostControls;
204
+ };
205
+
206
+ export { CallpadError as C, type HostClient as H, type Invites as I, type ListQuery as L, type Nullable as N, type ParticipantRef as P, type RealtimeToken as R, type SessionAction as S, type Unsubscribe as U, type VoiceJoinGrant as V, type ClientEvents as a, type ClientStatus as b, type CustomerStartInput as c, type HostClientConfig as d, type HostControls as e, type HostParticipantInput as f, type HostSessions as g, type ListSessionsResponse as h, type SessionClient as i, type SessionClientConfig as j, type SessionDuration as k, type SessionInvite as l, type SessionRecording as m, type SessionRecordingControl as n, type Sessions as o, type StartedSession as p, type StoreTopic as q, type VoiceParticipant as r, type VoiceSession as s };