opendesk-react-native 0.1.3 → 0.1.7
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 +25 -1
- package/assets/chime.wav +0 -0
- package/lib/api.js +25 -8
- package/lib/client.d.ts +9 -0
- package/lib/client.js +14 -0
- package/lib/errors.d.ts +6 -2
- package/lib/errors.js +6 -2
- package/lib/index.d.ts +2 -0
- package/lib/index.js +3 -1
- package/lib/sound.d.ts +22 -0
- package/lib/sound.js +33 -0
- package/lib/types.d.ts +2 -0
- package/lib/ui/OpenDeskChat.d.ts +5 -1
- package/lib/ui/OpenDeskChat.js +77 -12
- package/lib/ui/RichText.d.ts +15 -0
- package/lib/ui/RichText.js +59 -0
- package/lib/ui/keyboard.d.ts +15 -0
- package/lib/ui/keyboard.js +62 -0
- package/lib/ui/rich.d.ts +54 -0
- package/lib/ui/rich.js +444 -0
- package/lib/ui/strings.d.ts +2 -0
- package/lib/ui/strings.js +2 -0
- package/package.json +2 -1
- package/src/api.ts +195 -172
- package/src/assets.d.ts +5 -0
- package/src/client.ts +680 -659
- package/src/errors.ts +20 -16
- package/src/index.ts +27 -25
- package/src/sound.ts +40 -0
- package/src/types.ts +202 -200
- package/src/ui/OpenDeskChat.tsx +1476 -1318
- package/src/ui/RichText.tsx +129 -0
- package/src/ui/keyboard.ts +68 -0
- package/src/ui/rich.ts +507 -0
- package/src/ui/strings.ts +59 -57
package/src/errors.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
/**
|
|
2
|
+
* A failed openDesk request; `status` is 0 when the server could not be reached, and then
|
|
3
|
+
* `cause` holds what `fetch` threw.
|
|
4
|
+
*/
|
|
5
|
+
export class OpenDeskError extends Error {
|
|
6
|
+
constructor(
|
|
7
|
+
public readonly status: number,
|
|
8
|
+
public readonly code: string,
|
|
9
|
+
message: string,
|
|
10
|
+
public readonly cause?: unknown,
|
|
11
|
+
) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'OpenDeskError';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** The visitor token is unknown or revoked; a fresh session is needed. */
|
|
17
|
+
get isUnauthorized(): boolean {
|
|
18
|
+
return this.status === 401;
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
export { OpenDesk } from './client';
|
|
2
|
-
export type { AIReply, IncomingMessage, OpenDeskOptions, OpenDeskState } from './client';
|
|
3
|
-
export { OpenDeskApi } from './api';
|
|
4
|
-
export type { Fetcher, OpenDeskApiOptions } from './api';
|
|
5
|
-
export { OpenDeskError } from './errors';
|
|
6
|
-
export { OpenDeskRealtime, phoenixSocketFactory } from './realtime';
|
|
7
|
-
export type { RealtimeChannel, RealtimeSocket, SocketFactory } from './realtime';
|
|
8
|
-
export { keychainStorage, memoryStorage, secureStoreStorage } from './storage';
|
|
9
|
-
export type { ExpoSecureStoreModule, KeychainModule, TokenStorage } from './storage';
|
|
10
|
-
export {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export type {
|
|
21
|
-
export {
|
|
22
|
-
export type {
|
|
23
|
-
export {
|
|
24
|
-
export type {
|
|
25
|
-
export
|
|
1
|
+
export { OpenDesk } from './client';
|
|
2
|
+
export type { AIReply, IncomingMessage, OpenDeskOptions, OpenDeskState } from './client';
|
|
3
|
+
export { OpenDeskApi } from './api';
|
|
4
|
+
export type { Fetcher, OpenDeskApiOptions } from './api';
|
|
5
|
+
export { OpenDeskError } from './errors';
|
|
6
|
+
export { OpenDeskRealtime, phoenixSocketFactory } from './realtime';
|
|
7
|
+
export type { RealtimeChannel, RealtimeSocket, SocketFactory } from './realtime';
|
|
8
|
+
export { keychainStorage, memoryStorage, secureStoreStorage } from './storage';
|
|
9
|
+
export type { ExpoSecureStoreModule, KeychainModule, TokenStorage } from './storage';
|
|
10
|
+
export { expoAudioSound } from './sound';
|
|
11
|
+
export type { ExpoAudioModule, ExpoAudioPlayer } from './sound';
|
|
12
|
+
export {
|
|
13
|
+
OpenDeskProvider,
|
|
14
|
+
useConversation,
|
|
15
|
+
useConversations,
|
|
16
|
+
useOpenDesk,
|
|
17
|
+
useOpenDeskState,
|
|
18
|
+
useUnreadCount,
|
|
19
|
+
} from './react';
|
|
20
|
+
export type { ConversationView, OpenDeskProviderProps } from './react';
|
|
21
|
+
export { OpenDeskChat } from './ui/OpenDeskChat';
|
|
22
|
+
export type { OpenDeskChatProps } from './ui/OpenDeskChat';
|
|
23
|
+
export { OpenDeskLauncher } from './ui/OpenDeskLauncher';
|
|
24
|
+
export type { OpenDeskLauncherProps } from './ui/OpenDeskLauncher';
|
|
25
|
+
export { defaultStrings } from './ui/strings';
|
|
26
|
+
export type { ChatStrings } from './ui/strings';
|
|
27
|
+
export type * from './types';
|
package/src/sound.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import chime from '../assets/chime.wav';
|
|
2
|
+
|
|
3
|
+
/** The parts of `expo-audio` the adapter uses; pass the module itself. */
|
|
4
|
+
export interface ExpoAudioModule {
|
|
5
|
+
createAudioPlayer(source: number): ExpoAudioPlayer;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ExpoAudioPlayer {
|
|
9
|
+
volume: number;
|
|
10
|
+
play(): void;
|
|
11
|
+
seekTo(seconds: number): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Plays the web widget's reply chime with `expo-audio`. Takes the module as an argument, so the
|
|
16
|
+
* SDK has no dependency on Expo:
|
|
17
|
+
*
|
|
18
|
+
* import * as ExpoAudio from 'expo-audio';
|
|
19
|
+
* new OpenDesk({ …, sound: expoAudioSound(ExpoAudio) });
|
|
20
|
+
*
|
|
21
|
+
* The player is made on the first reply and kept for the next. The app's audio mode is left
|
|
22
|
+
* alone; under expo-audio's default the chime mixes with other audio instead of pausing it.
|
|
23
|
+
*/
|
|
24
|
+
export function expoAudioSound(ExpoAudio: ExpoAudioModule, options: { volume?: number } = {}): () => void {
|
|
25
|
+
let player: ExpoAudioPlayer | null = null;
|
|
26
|
+
|
|
27
|
+
return () => {
|
|
28
|
+
if (!player) {
|
|
29
|
+
player = ExpoAudio.createAudioPlayer(chime);
|
|
30
|
+
if (options.volume !== undefined) {
|
|
31
|
+
player.volume = options.volume;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const current = player;
|
|
35
|
+
void current
|
|
36
|
+
.seekTo(0)
|
|
37
|
+
.catch(() => undefined)
|
|
38
|
+
.then(() => current.play());
|
|
39
|
+
};
|
|
40
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,200 +1,202 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wire types for openDesk's visitor-facing widget API (`/v1/widget/*`), taken from
|
|
3
|
-
* openDesk's OpenAPI document. Only the fields a chat client needs are listed.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export type ConversationStatus = 'open' | 'pending' | 'on_hold' | 'solved' | 'closed';
|
|
7
|
-
|
|
8
|
-
export interface WidgetConfig {
|
|
9
|
-
workspace_name: string;
|
|
10
|
-
title: string;
|
|
11
|
-
subtitle: string;
|
|
12
|
-
greeting: string;
|
|
13
|
-
intro: string;
|
|
14
|
-
offline_message: string;
|
|
15
|
-
brand_color: string;
|
|
16
|
-
brand: { name: string; color: string };
|
|
17
|
-
theme: 'auto' | 'light' | 'dark';
|
|
18
|
-
radius: 'square' | 'soft' | 'round';
|
|
19
|
-
locale: string;
|
|
20
|
-
language: string;
|
|
21
|
-
open: boolean;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
user
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
export
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Wire types for openDesk's visitor-facing widget API (`/v1/widget/*`), taken from
|
|
3
|
+
* openDesk's OpenAPI document. Only the fields a chat client needs are listed.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type ConversationStatus = 'open' | 'pending' | 'on_hold' | 'solved' | 'closed';
|
|
7
|
+
|
|
8
|
+
export interface WidgetConfig {
|
|
9
|
+
workspace_name: string;
|
|
10
|
+
title: string;
|
|
11
|
+
subtitle: string;
|
|
12
|
+
greeting: string;
|
|
13
|
+
intro: string;
|
|
14
|
+
offline_message: string;
|
|
15
|
+
brand_color: string;
|
|
16
|
+
brand: { name: string; color: string };
|
|
17
|
+
theme: 'auto' | 'light' | 'dark';
|
|
18
|
+
radius: 'square' | 'soft' | 'round';
|
|
19
|
+
locale: string;
|
|
20
|
+
language: string;
|
|
21
|
+
open: boolean;
|
|
22
|
+
/** Whether replies play a sound while the visitor isn't looking at them. */
|
|
23
|
+
sound: boolean;
|
|
24
|
+
require_email: boolean;
|
|
25
|
+
chat_welcome: boolean;
|
|
26
|
+
help_center_url: string;
|
|
27
|
+
avatar_url: string | null;
|
|
28
|
+
attachments: boolean;
|
|
29
|
+
attachment_extensions: string[] | null;
|
|
30
|
+
voice_messages: boolean;
|
|
31
|
+
voice_max_seconds: number;
|
|
32
|
+
ai_answers: boolean;
|
|
33
|
+
suggestions: string[] | null;
|
|
34
|
+
topics: { id: string; label: string }[] | null;
|
|
35
|
+
team: { name: string; avatar_url: string | null }[] | null;
|
|
36
|
+
bot: { id: string; name: string; role: string; avatar_url: string; greeting: string; look?: BotLook | null } | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ContactRef {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
email: string | null;
|
|
43
|
+
avatar_url: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface UserRef {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
email: string;
|
|
50
|
+
avatar_url: string | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface BotRef {
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
avatar_url: string;
|
|
57
|
+
look?: BotLook | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** How the widget draws a bot that has no uploaded image. */
|
|
61
|
+
export interface BotLook {
|
|
62
|
+
kind?: 'built' | 'emoji' | 'image';
|
|
63
|
+
shape?: 'blob' | 'circle' | 'rounded' | 'squircle' | 'hexagon' | 'cloud';
|
|
64
|
+
fill?: { type?: 'solid' | 'gradient'; color?: string; to?: string };
|
|
65
|
+
eyes?: string;
|
|
66
|
+
mouth?: string;
|
|
67
|
+
emoji?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface WidgetSession {
|
|
71
|
+
visitor_token: string;
|
|
72
|
+
contact: ContactRef;
|
|
73
|
+
ws_token: string;
|
|
74
|
+
gateway_url: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A verified identity: `user_hash` is hex(HMAC-SHA256(widget_secret, user_id)) and must be
|
|
79
|
+
* computed on your server, never in the app.
|
|
80
|
+
*/
|
|
81
|
+
export interface Identity {
|
|
82
|
+
user_id?: string;
|
|
83
|
+
user_hash?: string;
|
|
84
|
+
email?: string;
|
|
85
|
+
name?: string;
|
|
86
|
+
avatar_url?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface Conversation {
|
|
90
|
+
id: string;
|
|
91
|
+
subject: string;
|
|
92
|
+
status: ConversationStatus;
|
|
93
|
+
last_message_at: string;
|
|
94
|
+
last_message_preview: string;
|
|
95
|
+
ai_handoff: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface Attachment {
|
|
99
|
+
id: string;
|
|
100
|
+
filename: string;
|
|
101
|
+
content_type: string;
|
|
102
|
+
size_bytes: number;
|
|
103
|
+
inline: boolean;
|
|
104
|
+
url: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface AIAction {
|
|
108
|
+
id: string;
|
|
109
|
+
status: 'pending' | 'confirmed' | 'declined' | 'expired' | 'failed';
|
|
110
|
+
title: string;
|
|
111
|
+
summary: string;
|
|
112
|
+
tool: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface Message {
|
|
116
|
+
id: string;
|
|
117
|
+
ticket_id: string;
|
|
118
|
+
kind: 'reply' | 'note';
|
|
119
|
+
direction: 'inbound' | 'outbound';
|
|
120
|
+
author: {
|
|
121
|
+
type: 'contact' | 'user' | 'system' | 'api';
|
|
122
|
+
contact?: ContactRef | null;
|
|
123
|
+
user?: UserRef | null;
|
|
124
|
+
bot?: BotRef | null;
|
|
125
|
+
};
|
|
126
|
+
body_text: string;
|
|
127
|
+
body_html: string;
|
|
128
|
+
attachments: Attachment[] | null;
|
|
129
|
+
created_at: string;
|
|
130
|
+
edited_at?: string | null;
|
|
131
|
+
deleted_at?: string | null;
|
|
132
|
+
ai?: boolean;
|
|
133
|
+
ai_action?: AIAction | null;
|
|
134
|
+
ai_rating?: 'up' | 'down' | '';
|
|
135
|
+
/**
|
|
136
|
+
* Set by the SDK, not openDesk: a render key that stays the same when the optimistic copy
|
|
137
|
+
* or the streaming AI answer is replaced by the stored message. Use `client_id ?? id` as a key.
|
|
138
|
+
*/
|
|
139
|
+
client_id?: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** A file picked on the device, in the shape React Native's FormData expects. */
|
|
143
|
+
export interface LocalFile {
|
|
144
|
+
uri: string;
|
|
145
|
+
name: string;
|
|
146
|
+
type: string;
|
|
147
|
+
/** Bytes, when the picker reports it; shown before the upload finishes. */
|
|
148
|
+
size?: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** What `upload` accepts: a picked file on iOS/Android, or a `File`/`Blob` on the web. */
|
|
152
|
+
export type UploadFile = LocalFile | Blob;
|
|
153
|
+
|
|
154
|
+
export interface StartConversationInput {
|
|
155
|
+
body?: string;
|
|
156
|
+
attachment_ids?: string[];
|
|
157
|
+
topic_id?: string;
|
|
158
|
+
page_url?: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface SendMessageInput {
|
|
162
|
+
body?: string;
|
|
163
|
+
attachment_ids?: string[];
|
|
164
|
+
page_url?: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The envelope the gateway pushes as `event` on `contact:<wid>:<contact_id>`. */
|
|
168
|
+
export interface RealtimeEvent {
|
|
169
|
+
id: string;
|
|
170
|
+
seq: number;
|
|
171
|
+
type: string;
|
|
172
|
+
visibility?: string;
|
|
173
|
+
occurred_at: string;
|
|
174
|
+
data: {
|
|
175
|
+
ticket_id?: string;
|
|
176
|
+
message_id?: string;
|
|
177
|
+
message?: Message;
|
|
178
|
+
[key: string]: unknown;
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Ephemeral AI streaming frames pushed as `live`. */
|
|
183
|
+
export interface LiveEvent {
|
|
184
|
+
type: 'ai.reply.started' | 'ai.reply.delta' | 'ai.reply.tool' | 'ai.reply.ended' | string;
|
|
185
|
+
data: {
|
|
186
|
+
reply_id?: string;
|
|
187
|
+
ticket_id?: string;
|
|
188
|
+
text?: string;
|
|
189
|
+
tool?: string;
|
|
190
|
+
status?: string;
|
|
191
|
+
message_id?: string;
|
|
192
|
+
handoff?: boolean;
|
|
193
|
+
action_id?: string;
|
|
194
|
+
bot?: BotRef;
|
|
195
|
+
[key: string]: unknown;
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface TypingEvent {
|
|
200
|
+
ticket_id: string;
|
|
201
|
+
user?: { name?: string } | null;
|
|
202
|
+
}
|