@tellescope/sdk 0.0.4 → 0.0.8
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/lib/cjs/enduser.d.ts +346 -13
- package/lib/cjs/enduser.d.ts.map +1 -1
- package/lib/cjs/enduser.js +135 -14
- package/lib/cjs/enduser.js.map +1 -1
- package/lib/cjs/sdk.d.ts +106 -19
- package/lib/cjs/sdk.d.ts.map +1 -1
- package/lib/cjs/sdk.js +116 -32
- package/lib/cjs/sdk.js.map +1 -1
- package/lib/cjs/session.d.ts +36 -175
- package/lib/cjs/session.d.ts.map +1 -1
- package/lib/cjs/session.js +131 -34
- package/lib/cjs/session.js.map +1 -1
- package/lib/cjs/tests/public_endpoint_tests.js +3 -3
- package/lib/cjs/tests/public_endpoint_tests.js.map +1 -1
- package/lib/cjs/tests/socket_tests.js +42 -31
- package/lib/cjs/tests/socket_tests.js.map +1 -1
- package/lib/cjs/tests/tests.js +196 -62
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/esm/enduser.d.ts +346 -13
- package/lib/esm/enduser.d.ts.map +1 -1
- package/lib/esm/enduser.js +132 -12
- package/lib/esm/enduser.js.map +1 -1
- package/lib/esm/sdk.d.ts +106 -19
- package/lib/esm/sdk.d.ts.map +1 -1
- package/lib/esm/sdk.js +116 -32
- package/lib/esm/sdk.js.map +1 -1
- package/lib/esm/session.d.ts +36 -175
- package/lib/esm/session.d.ts.map +1 -1
- package/lib/esm/session.js +130 -32
- package/lib/esm/session.js.map +1 -1
- package/lib/esm/tests/public_endpoint_tests.js +3 -3
- package/lib/esm/tests/public_endpoint_tests.js.map +1 -1
- package/lib/esm/tests/socket_tests.js +41 -30
- package/lib/esm/tests/socket_tests.js.map +1 -1
- package/lib/esm/tests/tests.js +192 -63
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -9
- package/src/enduser.ts +93 -20
- package/src/sdk.ts +70 -42
- package/src/session.ts +121 -33
- package/src/tests/public_endpoint_tests.ts +6 -4
- package/src/tests/socket_tests.ts +37 -16
- package/src/tests/tests.ts +117 -24
- package/tsconfig.json +0 -1
package/src/sdk.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { io } from 'socket.io-client'
|
|
2
|
-
|
|
3
1
|
import {
|
|
4
2
|
JourneyState,
|
|
5
3
|
UserSession,
|
|
@@ -11,7 +9,9 @@ import {
|
|
|
11
9
|
ClientModelForName_required,
|
|
12
10
|
ClientModelForName_updatesDisabled,
|
|
13
11
|
Enduser,
|
|
12
|
+
File,
|
|
14
13
|
} from "@tellescope/types-client"
|
|
14
|
+
import { CustomUpdateOptions, SortOption, S3PresignedPost } from "@tellescope/types-utilities"
|
|
15
15
|
import { url_safe_path } from "@tellescope/utilities"
|
|
16
16
|
|
|
17
17
|
import { Session as SessionManager, SessionOptions, Filter } from "./session"
|
|
@@ -30,29 +30,29 @@ export interface APIQuery<
|
|
|
30
30
|
createOne: (t: CREATE) => Promise<T>;
|
|
31
31
|
createSome: (ts: CREATE[]) => Promise<{ created: T[], errors: object[] }>;
|
|
32
32
|
getOne: (id: string, filter?: Filter<Partial<T>>) => Promise<T>;
|
|
33
|
-
getSome: (o?: { lastId?: string, limit?: number, sort?: SortOption, threadKey?: string
|
|
33
|
+
getSome: (o?: { lastId?: string, limit?: number, sort?: SortOption, threadKey?: string, filter?: Filter<Partial<T>> }) => Promise<T[]>
|
|
34
34
|
updateOne: (id: string, updates: UPDATE, options?: CustomUpdateOptions) => Promise<void>;
|
|
35
35
|
deleteOne: (id: string) => Promise<void>;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export const defaultQueries = <N extends keyof ClientModelForName>(
|
|
39
|
-
s:
|
|
39
|
+
s: Session, n: keyof ClientModelForName_required
|
|
40
40
|
): APIQuery<N> => {
|
|
41
41
|
|
|
42
42
|
const safeName = url_safe_path(n)
|
|
43
43
|
const singularName = (safeName).substring(0, safeName.length - 1)
|
|
44
44
|
|
|
45
45
|
return {
|
|
46
|
-
createOne: o => s.
|
|
47
|
-
createSome: os => s.
|
|
48
|
-
getOne: (id, filter) => s.
|
|
49
|
-
getSome: (o
|
|
50
|
-
updateOne: (id, updates, options) => s.
|
|
51
|
-
deleteOne: id => s.
|
|
46
|
+
createOne: o => s._POST(`/v1/${singularName}`, o),
|
|
47
|
+
createSome: os => s._POST(`/v1/${safeName}`, { create: os }),
|
|
48
|
+
getOne: (id, filter) => s._GET(`/v1/${singularName}/${id}`, { filter }),
|
|
49
|
+
getSome: (o) => s._GET(`/v1/${safeName}`, o),
|
|
50
|
+
updateOne: (id, updates, options) => s._PATCH(`/v1/${singularName}/${id}`, { updates, options }),
|
|
51
|
+
deleteOne: id => s._DELETE(`/v1/${singularName}/${id}`),
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
const loadDefaultQueries = (s:
|
|
55
|
+
const loadDefaultQueries = (s: Session): { [K in keyof ClientModelForName] : APIQuery<K> } => ({
|
|
56
56
|
endusers: defaultQueries(s, 'endusers'),
|
|
57
57
|
engagement_events: defaultQueries(s, 'engagement_events'),
|
|
58
58
|
journeys: defaultQueries(s, 'journeys'),
|
|
@@ -63,52 +63,86 @@ const loadDefaultQueries = (s: SessionManager): { [K in keyof ClientModelForName
|
|
|
63
63
|
chat_rooms: defaultQueries(s, 'chat_rooms'),
|
|
64
64
|
chats: defaultQueries(s, 'chats'),
|
|
65
65
|
users: defaultQueries(s, 'users'),
|
|
66
|
-
templates: defaultQueries(s, 'templates')
|
|
66
|
+
templates: defaultQueries(s, 'templates'),
|
|
67
|
+
files: defaultQueries(s, 'files'),
|
|
67
68
|
})
|
|
68
69
|
|
|
69
70
|
type Queries = { [K in keyof ClientModelForName]: APIQuery<K> } & {
|
|
70
71
|
journeys: {
|
|
71
|
-
updateState: (id: string, name: string, updates: JourneyState) => Promise<void>
|
|
72
|
+
updateState: (args: { id: string, name: string, updates: JourneyState }) => Promise<void>
|
|
72
73
|
},
|
|
73
74
|
endusers: {
|
|
74
|
-
setPassword: (id: string, password: string) => Promise<void>,
|
|
75
|
-
isAuthenticated: (id: string, authToken: string) => Promise<{ isAuthenticated: boolean, enduser: Enduser }>
|
|
76
|
-
}
|
|
75
|
+
setPassword: (args: { id: string, password: string }) => Promise<void>,
|
|
76
|
+
isAuthenticated: (args: { id: string, authToken: string }) => Promise<{ isAuthenticated: boolean, enduser: Enduser }>
|
|
77
|
+
},
|
|
78
|
+
users: {
|
|
79
|
+
display_names: () => Promise<{ fname: string, lname: string, id: string }[]>,
|
|
80
|
+
},
|
|
81
|
+
files: {
|
|
82
|
+
prepare_file_upload: (args: { name: string, size: number, type: string }) => Promise<{ presignedUpload: S3PresignedPost, file: File }>,
|
|
83
|
+
file_download_URL: (args: { secureName: string }) => Promise<{ downloadURL: string }>,
|
|
84
|
+
},
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
export class Session extends SessionManager{
|
|
87
|
+
export class Session extends SessionManager {
|
|
80
88
|
api: Queries;
|
|
81
|
-
userInfo
|
|
89
|
+
userInfo!: UserSession;
|
|
82
90
|
|
|
83
91
|
constructor(o?: SessionOptions) {
|
|
84
|
-
super(o)
|
|
85
|
-
this.userInfo = {} as UserSession
|
|
92
|
+
super({ ...o, cacheKey: o?.cacheKey || "tellescope_user" })
|
|
86
93
|
const queries = loadDefaultQueries(this) as Queries
|
|
87
94
|
|
|
88
|
-
queries.journeys.updateState = (id, name, updates) => this.
|
|
89
|
-
queries.endusers.setPassword = (id, password) => this.
|
|
90
|
-
queries.endusers.isAuthenticated = (id, authToken) => this.
|
|
95
|
+
queries.journeys.updateState = ({id, name, updates}) => this._PATCH(`/v1/journey/${id}/state/${name}`, { updates })
|
|
96
|
+
queries.endusers.setPassword = ({id, password}) => this._POST(`/v1/set-enduser-password`, { id, password })
|
|
97
|
+
queries.endusers.isAuthenticated = ({id, authToken}) => this._GET(`/v1/enduser-is-authenticated`, { id, authToken })
|
|
98
|
+
queries.users.display_names = () => this._GET<{}, { fname: string, lname: string, id: string }[]>(`/v1/user-display-names`),
|
|
99
|
+
queries.files.prepare_file_upload = (args) => this._POST(`/v1/prepare-file-upload`, args),
|
|
100
|
+
queries.files.file_download_URL = a => this._GET('/v1/file-download-URL', a),
|
|
91
101
|
|
|
92
102
|
this.api = queries
|
|
103
|
+
|
|
104
|
+
// if (this.userInfo) this.refresh_session()
|
|
93
105
|
}
|
|
94
106
|
|
|
107
|
+
_POST = async <A,R=void>(endpoint: string, args?: A, authenticated=true) => {
|
|
108
|
+
await this.refresh_session_if_expiring_soon()
|
|
109
|
+
return await this.POST<A,R>(endpoint, args, authenticated)
|
|
110
|
+
}
|
|
95
111
|
|
|
96
|
-
|
|
97
|
-
this.
|
|
98
|
-
this.
|
|
112
|
+
_GET = async <A,R=void>(endpoint: string, params?: A, authenticated=true) => {
|
|
113
|
+
await this.refresh_session_if_expiring_soon()
|
|
114
|
+
return await this.GET<A,R>(endpoint, params, authenticated)
|
|
115
|
+
}
|
|
99
116
|
|
|
100
|
-
|
|
101
|
-
this.
|
|
102
|
-
this.
|
|
117
|
+
_PATCH = async <A,R=void>(endpoint: string, params?: A, authenticated=true) => {
|
|
118
|
+
await this.refresh_session_if_expiring_soon()
|
|
119
|
+
return await this.PATCH<A,R>(endpoint, params, authenticated)
|
|
120
|
+
}
|
|
103
121
|
|
|
104
|
-
|
|
122
|
+
_DELETE = async <A,R=void>(endpoint: string, args?: A, authenticated=true) => {
|
|
123
|
+
await this.refresh_session_if_expiring_soon()
|
|
124
|
+
return await this.DELETE<A,R>(endpoint, args, authenticated)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
handle_new_session = async ({ authToken, ...userInfo }: UserSession & { authToken: string }) => {
|
|
128
|
+
this.sessionStart = Date.now()
|
|
129
|
+
this.setAuthToken(authToken)
|
|
130
|
+
this.setUserInfo(userInfo)
|
|
131
|
+
this.authenticate_socket()
|
|
105
132
|
|
|
106
133
|
return { authToken, ...userInfo }
|
|
107
134
|
}
|
|
108
135
|
|
|
109
136
|
refresh_session = async () => {
|
|
110
|
-
|
|
111
|
-
await this.
|
|
137
|
+
console.log('refreshing session')
|
|
138
|
+
const { user, authToken } = await this.POST<{}, { user: UserSession } & { authToken: string }>('/v1/refresh-session')
|
|
139
|
+
await this.handle_new_session({ ...user, authToken })
|
|
140
|
+
return { user, authToken }
|
|
141
|
+
}
|
|
142
|
+
refresh_session_if_expiring_soon = async () => {
|
|
143
|
+
const elapsedSessionMS = Date.now() - (this.sessionStart || Date.now())
|
|
144
|
+
|
|
145
|
+
if (this.AUTO_REFRESH_MS < elapsedSessionMS) { return await this.refresh_session()}
|
|
112
146
|
}
|
|
113
147
|
|
|
114
148
|
authenticate = async (email: string, password: string, url?: string) => {
|
|
@@ -118,17 +152,11 @@ export class Session extends SessionManager{
|
|
|
118
152
|
await this.POST<{email: string, password: string }, UserSession & { authToken: string }>('/submit-login', { email, password })
|
|
119
153
|
)
|
|
120
154
|
}
|
|
155
|
+
logout = async () => {
|
|
156
|
+
this.clearState()
|
|
157
|
+
await this.POST('/logout-api').catch(console.error)
|
|
158
|
+
}
|
|
121
159
|
|
|
122
|
-
subscribe = (rooms: { [index: string]: keyof ClientModelForName } ) => this.EMIT('join-rooms', { rooms })
|
|
123
|
-
|
|
124
|
-
handle_events = ( handlers: { [index: string]: (a: any) => void } ) => {
|
|
125
|
-
for (const handler in handlers) this.ON(handler, handlers[handler])
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
unsubscribe = (roomIds: string[]) => this.EMIT('leave-rooms', { roomIds })
|
|
129
|
-
|
|
130
|
-
socket_is_authenticated = () => this.socketAuthenticated
|
|
131
|
-
logout = () => this.POST('/logout-api')
|
|
132
160
|
reset_db = () => this.POST('/reset-demo')
|
|
133
161
|
test_online = () => this.GET<{}, string>('/v1')
|
|
134
162
|
test_authenticated = () => this.GET<{}, string>('/v1/test-authenticated')
|
package/src/session.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import axios from "axios"
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
|
|
2
|
+
import NodeFormData from "form-data"
|
|
3
|
+
import { Socket, io } from 'socket.io-client'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
CustomUpdateOptions,
|
|
7
|
+
FileBlob,
|
|
8
|
+
SortOption,
|
|
9
|
+
S3PresignedPost,
|
|
10
|
+
} from "@tellescope/types-utilities"
|
|
11
|
+
import {
|
|
12
|
+
ClientModelForName,
|
|
13
|
+
ClientModelForName_required,
|
|
14
|
+
ClientModelForName_readonly,
|
|
15
|
+
ClientModelForName_updatesDisabled
|
|
16
|
+
} from "@tellescope/types-client"
|
|
7
17
|
|
|
8
18
|
export const DEFAULT_HOST = 'https://api.tellescope.com'
|
|
9
19
|
|
|
@@ -11,6 +21,12 @@ export interface SessionOptions {
|
|
|
11
21
|
apiKey?: string;
|
|
12
22
|
authToken?: string;
|
|
13
23
|
host?: string;
|
|
24
|
+
cacheKey?: string;
|
|
25
|
+
handleUnauthenticated?: () => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface RequestOptions {
|
|
29
|
+
refresh_session?: () => Promise<any>,
|
|
14
30
|
}
|
|
15
31
|
|
|
16
32
|
export type Filter<T> = { [K in keyof T]: T[K] }
|
|
@@ -31,23 +47,6 @@ export interface APIQuery<
|
|
|
31
47
|
deleteOne: (id: string) => Promise<void>;
|
|
32
48
|
}
|
|
33
49
|
|
|
34
|
-
export const defaultQueries = <N extends keyof ClientModelForName>(
|
|
35
|
-
s: Session, n: keyof ClientModelForName_required
|
|
36
|
-
): APIQuery<N> => {
|
|
37
|
-
|
|
38
|
-
const safeName = url_safe_path(n)
|
|
39
|
-
const singularName = (safeName).substring(0, safeName.length - 1)
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
createOne: o => s.POST(`/v1/${singularName}`, o),
|
|
43
|
-
createSome: os => s.POST(`/v1/${safeName}`, { create: os }),
|
|
44
|
-
getOne: (id, filter) => s.GET(`/v1/${singularName}/${id}`, { filter }),
|
|
45
|
-
getSome: (o, filter) => s.GET(`/v1/${safeName}`, { ...o, filter }),
|
|
46
|
-
updateOne: (id, updates, options) => s.PATCH(`/v1/${singularName}/${id}`, { updates, options }),
|
|
47
|
-
deleteOne: id => s.DELETE(`/v1/${singularName}/${id}`),
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
50
|
const generateBearer = (authToken: string) => `Bearer ${authToken}`
|
|
52
51
|
|
|
53
52
|
const parseError = (err: any) => {
|
|
@@ -59,32 +58,80 @@ const parseError = (err: any) => {
|
|
|
59
58
|
return err
|
|
60
59
|
}
|
|
61
60
|
|
|
61
|
+
const DEFAULT_AUTHTOKEN_KEY = 'tellescope_authToken'
|
|
62
|
+
const IN_BROWSER = typeof window !== 'undefined'
|
|
63
|
+
const has_local_storage = () => typeof window !== 'undefined' && !!window.localStorage
|
|
64
|
+
const set_cache = (key: string, authToken: string) => has_local_storage() && (window.localStorage[key] = authToken)
|
|
65
|
+
const access_cache = (key=DEFAULT_AUTHTOKEN_KEY) => has_local_storage() ? window.localStorage[key] : undefined
|
|
66
|
+
|
|
62
67
|
export class Session {
|
|
63
68
|
host: string;
|
|
64
69
|
authToken: string;
|
|
70
|
+
cacheKey: string;
|
|
65
71
|
apiKey?: string;
|
|
66
72
|
socket?: Socket;
|
|
73
|
+
handleUnauthenticated?: SessionOptions['handleUnauthenticated']
|
|
67
74
|
socketAuthenticated: boolean;
|
|
75
|
+
userInfo: { businessId?: string };
|
|
76
|
+
sessionStart = Date.now();
|
|
77
|
+
AUTO_REFRESH_MS = 3600000 // 1hr elapsed
|
|
68
78
|
|
|
69
79
|
config: { headers: { Authorization: string }};
|
|
70
80
|
|
|
71
|
-
constructor(o={} as SessionOptions) {
|
|
81
|
+
constructor(o={} as SessionOptions & RequestOptions) {
|
|
72
82
|
this.host= o.host ?? DEFAULT_HOST
|
|
73
|
-
this.authToken = o.authToken ?? '';
|
|
74
|
-
this.config = { headers: { Authorization: generateBearer(o.authToken ?? '') } }
|
|
75
83
|
this.apiKey = o.apiKey ?? '';
|
|
76
84
|
this.socket = undefined as Socket | undefined
|
|
77
85
|
this.socketAuthenticated = false
|
|
86
|
+
this.handleUnauthenticated = o.handleUnauthenticated
|
|
87
|
+
|
|
88
|
+
this.cacheKey = o.cacheKey || DEFAULT_AUTHTOKEN_KEY
|
|
89
|
+
this.authToken = o.authToken ?? access_cache(o.cacheKey) ?? '';
|
|
90
|
+
this.userInfo = JSON.parse(access_cache(o.cacheKey + 'userInfo') || '{}');
|
|
91
|
+
if (this.authToken) {
|
|
92
|
+
set_cache(this.cacheKey, this.authToken)
|
|
93
|
+
this.authenticate_socket()
|
|
94
|
+
}
|
|
95
|
+
this.config = { headers: { Authorization: generateBearer(this.authToken ?? '') } } // initialize after authToken
|
|
78
96
|
}
|
|
97
|
+
|
|
98
|
+
resolve_field = async <T>(p: () => Promise<T>, field: keyof T) => (await p())[field]
|
|
79
99
|
|
|
80
100
|
setAuthToken = (a: string) => {
|
|
81
101
|
this.authToken = a;
|
|
82
102
|
this.config.headers.Authorization = generateBearer(a);
|
|
103
|
+
set_cache(this.cacheKey, a)
|
|
83
104
|
}
|
|
84
|
-
// setApiKey = (k: string) => apiKey = k
|
|
85
105
|
|
|
86
|
-
|
|
106
|
+
setUserInfo = (u: { businessId: string }) => {
|
|
107
|
+
this.userInfo = u;
|
|
108
|
+
set_cache(this.cacheKey + 'userInfo', JSON.stringify(u))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
clearCache = () => {
|
|
112
|
+
set_cache(this.cacheKey, '')
|
|
113
|
+
set_cache(this.cacheKey + 'userInfo', '')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
clearState = () => {
|
|
117
|
+
this.apiKey = ''
|
|
118
|
+
this.authToken = ''
|
|
119
|
+
this.userInfo = { }
|
|
120
|
+
this.clearCache()
|
|
121
|
+
}
|
|
87
122
|
|
|
123
|
+
getAuthInfo = (requiresAuth?: boolean) => requiresAuth && this.apiKey ? { apiKey: this.apiKey } : { }
|
|
124
|
+
|
|
125
|
+
errorHandler = async (_err: any) => {
|
|
126
|
+
const err = parseError(_err)
|
|
127
|
+
if (err === 'Unauthenticated') {
|
|
128
|
+
this.authToken = ''
|
|
129
|
+
this.clearCache()
|
|
130
|
+
await this.handleUnauthenticated?.()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return err
|
|
134
|
+
}
|
|
88
135
|
POST = async <A,R=void>(endpoint: string, args?: A, authenticated=true) => {
|
|
89
136
|
try {
|
|
90
137
|
return (await axios.post(
|
|
@@ -92,7 +139,7 @@ export class Session {
|
|
|
92
139
|
{ ...args, ...this.getAuthInfo(authenticated) },
|
|
93
140
|
this.config)
|
|
94
141
|
).data as R
|
|
95
|
-
} catch(err) { throw
|
|
142
|
+
} catch(err) { throw await this.errorHandler(err) }
|
|
96
143
|
}
|
|
97
144
|
|
|
98
145
|
GET = async <A,R=void>(endpoint: string, params?: A, authenticated=true) => {
|
|
@@ -102,7 +149,7 @@ export class Session {
|
|
|
102
149
|
{ params: { ...params, ...this.getAuthInfo(authenticated) },
|
|
103
150
|
headers: this.config.headers })
|
|
104
151
|
).data as R
|
|
105
|
-
} catch(err) { throw
|
|
152
|
+
} catch(err) { throw await this.errorHandler(err) }
|
|
106
153
|
}
|
|
107
154
|
|
|
108
155
|
PATCH = async <A,R=void>(endpoint: string, params?: A, authenticated=true) => {
|
|
@@ -112,7 +159,7 @@ export class Session {
|
|
|
112
159
|
{ ...params, ...this.getAuthInfo(authenticated) },
|
|
113
160
|
this.config)
|
|
114
161
|
).data as R
|
|
115
|
-
} catch(err) { throw
|
|
162
|
+
} catch(err) { throw await this.errorHandler(err) }
|
|
116
163
|
}
|
|
117
164
|
|
|
118
165
|
DELETE = async <A,R=void>(endpoint: string, args?: A, authenticated=true) => {
|
|
@@ -122,14 +169,55 @@ export class Session {
|
|
|
122
169
|
{ data: { ...args, ...this.getAuthInfo(authenticated) },
|
|
123
170
|
headers: this.config.headers })
|
|
124
171
|
).data as R
|
|
125
|
-
} catch(err) { throw
|
|
172
|
+
} catch(err) { throw await this.errorHandler(err) }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
UPLOAD = async (presigned: S3PresignedPost, file?: FileBlob, buffer?: FileBlob) => {
|
|
176
|
+
const formData = IN_BROWSER ? new FormData() : new NodeFormData();
|
|
177
|
+
Object.keys(presigned.fields).forEach(key => {
|
|
178
|
+
formData.append(key, presigned.fields[key]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// has to be appended last
|
|
182
|
+
if (!IN_BROWSER) file = buffer || file // use raw buffer if file is Node Blob
|
|
183
|
+
formData.append("file", file as Blob)
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
await axios.post(presigned.url, formData, {
|
|
187
|
+
headers: IN_BROWSER
|
|
188
|
+
? { 'Content-Type': 'multipart/form-data'}
|
|
189
|
+
: {
|
|
190
|
+
...(formData as NodeFormData).getHeaders(),
|
|
191
|
+
'Content-Length' : (formData as any).maxDataSize
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
catch(err) { console.error(err); throw err }
|
|
126
196
|
}
|
|
127
197
|
|
|
128
|
-
EMIT = async (route: string, args: object, authenticated=true) => {
|
|
198
|
+
EMIT = async (route: string, args: object, authenticated=true, options={} as RequestOptions) => {
|
|
129
199
|
this.socket?.emit(route, { ...args, ...authenticated ? { authToken: this.authToken } : {} } )
|
|
130
200
|
}
|
|
131
201
|
|
|
132
202
|
ON = <T={}>(s: string, callback: (a: T) => void) => this.socket?.on(s, callback)
|
|
133
203
|
|
|
134
|
-
|
|
204
|
+
subscribe = (rooms: { [index: string]: keyof ClientModelForName }, handlers?: { [index: string]: (a: any) => void } ) => {
|
|
205
|
+
if (handlers) { this.handle_events(handlers) }
|
|
206
|
+
this.EMIT('join-rooms', { rooms })
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
handle_events = ( handlers: { [index: string]: (a: any) => void } ) => {
|
|
210
|
+
for (const handler in handlers) this.ON(handler, handlers[handler])
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
unsubscribe = (roomIds: string[]) => this.EMIT('leave-rooms', { roomIds })
|
|
214
|
+
removeAllSocketListeners = (s: string) => this.socket?.removeAllListeners(s)
|
|
215
|
+
|
|
216
|
+
authenticate_socket = () => {
|
|
217
|
+
this.socket = io(`${this.host}/${this.userInfo.businessId}`, { transports: ['websocket'] }); // supporting polling requires sticky session at load balancer
|
|
218
|
+
this.socket.on('disconnect', () => { this.socketAuthenticated = false })
|
|
219
|
+
this.socket.on('authenticated', () => { this.socketAuthenticated = true })
|
|
220
|
+
|
|
221
|
+
this.socket.emit('authenticate', this.authToken)
|
|
222
|
+
}
|
|
135
223
|
}
|
|
@@ -2,9 +2,11 @@ import {
|
|
|
2
2
|
assert,
|
|
3
3
|
async_test,
|
|
4
4
|
log_header,
|
|
5
|
-
objects_equivalent,
|
|
6
5
|
wait,
|
|
7
6
|
} from "@tellescope/testing"
|
|
7
|
+
import {
|
|
8
|
+
objects_equivalent,
|
|
9
|
+
} from "@tellescope/utilities"
|
|
8
10
|
import { Session, /* APIQuery */ } from "../sdk"
|
|
9
11
|
import { PublicEndpoints } from "../public"
|
|
10
12
|
|
|
@@ -30,14 +32,14 @@ const enduser_login_tests = async () => {
|
|
|
30
32
|
)
|
|
31
33
|
await async_test(
|
|
32
34
|
'setPassword',
|
|
33
|
-
() => sdk.api.endusers.setPassword(e.id, password),
|
|
35
|
+
() => sdk.api.endusers.setPassword({ id: e.id, password }),
|
|
34
36
|
{ onResult: _ => true }
|
|
35
37
|
)
|
|
36
38
|
|
|
37
39
|
let authToken = 'placeholder'
|
|
38
40
|
await async_test(
|
|
39
41
|
'isAuthenticated (no)',
|
|
40
|
-
() => sdk.api.endusers.isAuthenticated(e.id, authToken),
|
|
42
|
+
() => sdk.api.endusers.isAuthenticated({ id: e.id, authToken }),
|
|
41
43
|
{ onResult: ({ isAuthenticated, enduser }) => isAuthenticated === false && enduser === null }
|
|
42
44
|
)
|
|
43
45
|
await async_test(
|
|
@@ -47,7 +49,7 @@ const enduser_login_tests = async () => {
|
|
|
47
49
|
)
|
|
48
50
|
await async_test(
|
|
49
51
|
'isAuthenticated (yes)',
|
|
50
|
-
() => sdk.api.endusers.isAuthenticated(e.id, authToken),
|
|
52
|
+
() => sdk.api.endusers.isAuthenticated({ id: e.id, authToken }),
|
|
51
53
|
{ onResult: ({ isAuthenticated, enduser }) => isAuthenticated === true && enduser?.id === e.id }
|
|
52
54
|
)
|
|
53
55
|
}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
assert,
|
|
3
|
+
async_test,
|
|
3
4
|
log_header,
|
|
4
|
-
objects_equivalent,
|
|
5
5
|
wait,
|
|
6
6
|
} from "@tellescope/testing"
|
|
7
|
+
import {
|
|
8
|
+
objects_equivalent,
|
|
9
|
+
} from "@tellescope/utilities"
|
|
10
|
+
import {
|
|
11
|
+
ChatMessage,
|
|
12
|
+
} from "@tellescope/types-client"
|
|
13
|
+
import {
|
|
14
|
+
Indexable,
|
|
15
|
+
} from "@tellescope/types-utilities"
|
|
7
16
|
import { EnduserSession } from "../enduser"
|
|
8
17
|
import { Session, /* APIQuery */ } from "../sdk"
|
|
9
18
|
|
|
@@ -23,8 +32,7 @@ if (!(email && password && email2 && password2)) {
|
|
|
23
32
|
const basic_tests = async () => {
|
|
24
33
|
const socket_events: Indexable[] = []
|
|
25
34
|
|
|
26
|
-
user2.subscribe({ 'endusers': 'endusers' }
|
|
27
|
-
user2.handle_events({
|
|
35
|
+
user2.subscribe({ 'endusers': 'endusers' }, {
|
|
28
36
|
'created-endusers': es => socket_events.push(es),
|
|
29
37
|
'updated-endusers': es => socket_events.push(es),
|
|
30
38
|
'deleted-endusers': es => socket_events.push(es),
|
|
@@ -116,11 +124,10 @@ const access_tests = async () => {
|
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
const enduser_tests = async () => {
|
|
119
|
-
const enduser = await user1.api.endusers.createOne({ email: "
|
|
120
|
-
await user1.api.endusers.setPassword(enduser.id, '
|
|
127
|
+
const enduser = await user1.api.endusers.createOne({ email: "enduser@tellescope.com" })
|
|
128
|
+
await user1.api.endusers.setPassword({ id: enduser.id, password: 'enduserPassword!' })
|
|
121
129
|
|
|
122
|
-
await enduserSDK.authenticate(enduser.email as string, '
|
|
123
|
-
enduserSDK.authenticate_socket()
|
|
130
|
+
await enduserSDK.authenticate(enduser.email as string, 'enduserPassword!')
|
|
124
131
|
await wait(undefined, 25)
|
|
125
132
|
|
|
126
133
|
const userEvents = [] as ChatMessage[]
|
|
@@ -140,18 +147,31 @@ const enduser_tests = async () => {
|
|
|
140
147
|
|
|
141
148
|
user1.handle_events({
|
|
142
149
|
'created-chats': rs => userEvents.push(...rs),
|
|
143
|
-
|
|
150
|
+
})
|
|
151
|
+
enduserSDK.handle_events({
|
|
152
|
+
'created-chats': rs => enduserEvents.push(...rs),
|
|
144
153
|
})
|
|
145
154
|
|
|
146
155
|
const messageToEnduser = await user1.api.chats.createOne({ roomId: room.id, message: "Hello!" })
|
|
147
156
|
const messageToUser = await enduserSDK.api.chats.createOne({ roomId: room.id, message: "Hello right back!" })
|
|
157
|
+
await wait(undefined, 25)
|
|
148
158
|
|
|
149
|
-
|
|
159
|
+
assert(objects_equivalent(userEvents[0], messageToUser), 'no message on socket', 'push message to user')
|
|
160
|
+
assert(objects_equivalent(enduserEvents[0], messageToEnduser), 'no message on socket', 'push message to enduser')
|
|
161
|
+
|
|
162
|
+
// test enduser logout
|
|
163
|
+
await enduserSDK.api.endusers.logout()
|
|
164
|
+
await async_test(
|
|
165
|
+
`verify enduser logout works`,
|
|
166
|
+
() => enduserSDK.api.chats.getSome({}),
|
|
167
|
+
{ shouldError: true, onError: (e: string) => e === 'Unauthenticated' }
|
|
168
|
+
)
|
|
150
169
|
|
|
170
|
+
// keep these models around for front-end testing
|
|
151
171
|
// cleanup
|
|
152
|
-
await user1.api.endusers.deleteOne(enduser.id)
|
|
153
|
-
await user1.api.chats.deleteOne(messageToEnduser.id)
|
|
154
|
-
await user1.api.chats.deleteOne(messageToUser.id)
|
|
172
|
+
// await user1.api.endusers.deleteOne(enduser.id)
|
|
173
|
+
// await user1.api.chats.deleteOne(messageToEnduser.id)
|
|
174
|
+
// await user1.api.chats.deleteOne(messageToUser.id)
|
|
155
175
|
}
|
|
156
176
|
|
|
157
177
|
(async () => {
|
|
@@ -160,10 +180,11 @@ const enduser_tests = async () => {
|
|
|
160
180
|
try {
|
|
161
181
|
await user1.authenticate(email, password, host)
|
|
162
182
|
await user1.reset_db()
|
|
163
|
-
await user2.authenticate(email2, password2, host) // generate authToken + socket connection for API
|
|
164
|
-
|
|
183
|
+
await user2.authenticate(email2, password2, host) // generate authToken + socket connection for API keyj
|
|
184
|
+
await wait(undefined, 25)
|
|
185
|
+
|
|
165
186
|
let loopCount = 0
|
|
166
|
-
while (!(user1.
|
|
187
|
+
while (!(user1.socketAuthenticated && user2.socketAuthenticated) && ++loopCount < 10) {
|
|
167
188
|
user2.authenticate_socket()
|
|
168
189
|
await wait(undefined, 100)
|
|
169
190
|
}
|
|
@@ -171,7 +192,7 @@ const enduser_tests = async () => {
|
|
|
171
192
|
console.log("Failed to authenticate")
|
|
172
193
|
process.exit()
|
|
173
194
|
}
|
|
174
|
-
|
|
195
|
+
|
|
175
196
|
await enduser_tests()
|
|
176
197
|
await basic_tests()
|
|
177
198
|
await access_tests()
|