@tellescope/sdk 0.0.25 → 0.0.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellescope/sdk",
3
- "version": "0.0.25",
3
+ "version": "0.0.29",
4
4
  "description": "Code for interacting with the Tellescope API",
5
5
  "main": "./lib/cjs/sdk.js",
6
6
  "module": "./lib/esm/sdk.js",
@@ -32,14 +32,14 @@
32
32
  },
33
33
  "homepage": "https://github.com/tellescope-os/tellescope#readme",
34
34
  "dependencies": {
35
- "@tellescope/constants": "^0.0.25",
36
- "@tellescope/schema": "^0.0.25",
37
- "@tellescope/testing": "^0.0.25",
38
- "@tellescope/types-client": "^0.0.25",
39
- "@tellescope/types-models": "^0.0.25",
40
- "@tellescope/types-utilities": "^0.0.25",
41
- "@tellescope/utilities": "^0.0.25",
42
- "@tellescope/validation": "^0.0.25",
35
+ "@tellescope/constants": "^0.0.29",
36
+ "@tellescope/schema": "^0.0.29",
37
+ "@tellescope/testing": "^0.0.29",
38
+ "@tellescope/types-client": "^0.0.29",
39
+ "@tellescope/types-models": "^0.0.29",
40
+ "@tellescope/types-utilities": "^0.0.29",
41
+ "@tellescope/utilities": "^0.0.29",
42
+ "@tellescope/validation": "^0.0.29",
43
43
  "axios": "^0.21.1",
44
44
  "express": "^4.17.1",
45
45
  "form-data": "^4.0.0",
@@ -56,5 +56,5 @@
56
56
  "publishConfig": {
57
57
  "access": "public"
58
58
  },
59
- "gitHead": "79b392c815e70c22e96ab329f5e877bb140a7e90"
59
+ "gitHead": "7e0f3d4785c144284c5dda119d6bccafb6fe6bc4"
60
60
  }
package/src/enduser.ts CHANGED
@@ -4,7 +4,7 @@ import { Session, SessionOptions } from "./session"
4
4
  import { APIQuery } from "./sdk"
5
5
  import { url_safe_path } from "@tellescope/utilities"
6
6
 
7
- import { FileBlob, S3PresignedPost, UserIdentity } from "@tellescope/types-utilities"
7
+ import { FileBlob, FileBuffer, FileDetails, ReactNativeFile, S3PresignedPost, UserIdentity } from "@tellescope/types-utilities"
8
8
  import {
9
9
  Attendee,
10
10
  } from "@tellescope/types-models"
@@ -19,7 +19,7 @@ import {
19
19
 
20
20
  export interface EnduserSessionOptions extends SessionOptions {}
21
21
 
22
- type EnduserAccessibleModels = "chat_rooms" | 'chats' | 'files' | 'tickets'
22
+ type EnduserAccessibleModels = 'endusers' | "chat_rooms" | 'chats' | 'files' | 'tickets'
23
23
 
24
24
  export const defaultQueries = <N extends keyof ClientModelForName>(
25
25
  s: EnduserSession, n: keyof ClientModelForName_required
@@ -47,7 +47,7 @@ type EnduserQueries = { [K in EnduserAccessibleModels]: APIQuery<K> } & {
47
47
  display_info: () => Promise<UserDisplayInfo[]>
48
48
  },
49
49
  files: {
50
- prepare_file_upload: (args: { name: string, size: number, type: string }) => Promise<{ presignedUpload: S3PresignedPost, file: File }>,
50
+ prepare_file_upload: (args: FileDetails) => Promise<{ presignedUpload: S3PresignedPost, file: File }>,
51
51
  file_download_URL: (args: { secureName: string }) => Promise<{ downloadURL: string }>,
52
52
  },
53
53
  meetings: {
@@ -60,6 +60,7 @@ type EnduserQueries = { [K in EnduserAccessibleModels]: APIQuery<K> } & {
60
60
  const loadDefaultQueries = (s: EnduserSession): { [K in EnduserAccessibleModels] : APIQuery<K> } => ({
61
61
  chat_rooms: defaultQueries(s, 'chat_rooms'),
62
62
  chats: defaultQueries(s, 'chats'),
63
+ endusers: defaultQueries(s, 'endusers'),
63
64
  files: defaultQueries(s, 'files'),
64
65
  tickets: defaultQueries(s, 'tickets'),
65
66
  })
@@ -75,9 +76,8 @@ export class EnduserSession extends Session {
75
76
 
76
77
  this.api = loadDefaultQueries(this) as EnduserQueries
77
78
 
78
- this.api.endusers = {
79
- logout: () => this._POST('/v1/logout-enduser'),
80
- }
79
+ this.api.endusers.logout = () => this._POST('/v1/logout-enduser'),
80
+
81
81
  this.api.users = {
82
82
  display_info: () => this._GET<{}, UserDisplayInfo[] >(`/v1/user-display-info`),
83
83
  }
@@ -113,8 +113,8 @@ export class EnduserSession extends Session {
113
113
  return await this.DELETE<A,R>(endpoint, args, authenticated)
114
114
  }
115
115
 
116
- prepare_and_upload_file = async (file: FileBlob) => {
117
- const { name, size, type } = file
116
+ prepare_and_upload_file = async (details: FileDetails, file: Blob | Buffer | ReactNativeFile) => {
117
+ const { name, size, type } = details
118
118
  const { presignedUpload, file: { secureName } } = await this.api.files.prepare_file_upload({ name, size, type })
119
119
  await this.UPLOAD(presignedUpload, file)
120
120
  return { secureName }
package/src/sdk.ts CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  File,
18
18
  Meeting,
19
19
  } from "@tellescope/types-client"
20
- import { CustomUpdateOptions, SortOption, S3PresignedPost, UserIdentity, FileBlob } from "@tellescope/types-utilities"
20
+ import { CustomUpdateOptions, SortOption, S3PresignedPost, UserIdentity, FileBlob, FileBuffer, FileDetails, ReactNativeFile } from "@tellescope/types-utilities"
21
21
  import { url_safe_path } from "@tellescope/utilities"
22
22
 
23
23
  import { Session as SessionManager, SessionOptions } from "./session"
@@ -92,14 +92,14 @@ type Queries = { [K in keyof ClientModelForName]: APIQuery<K> } & {
92
92
  },
93
93
  endusers: {
94
94
  set_password: (args: { id: string, password: string }) => Promise<void>,
95
- is_authenticated: (args: { id: string, authToken: string }) => Promise<{ isAuthenticated: boolean, enduser: Enduser }>
95
+ is_authenticated: (args: { id?: string, authToken: string }) => Promise<{ isAuthenticated: boolean, enduser: Enduser }>
96
96
  generate_auth_token: (args: { id?: string, phone?: string, email?: string, externalId?: string }) => Promise<{ authToken: string, enduser: Enduser }>
97
97
  },
98
98
  users: {
99
99
  display_names: () => Promise<{ fname: string, lname: string, id: string }[]>,
100
100
  },
101
101
  files: {
102
- prepare_file_upload: (args: { name: string, size: number, type: string }) => Promise<{ presignedUpload: S3PresignedPost, file: File }>,
102
+ prepare_file_upload: (args: FileDetails) => Promise<{ presignedUpload: S3PresignedPost, file: File }>,
103
103
  file_download_URL: (args: { secureName: string }) => Promise<{ downloadURL: string }>,
104
104
  },
105
105
  meetings: {
@@ -146,18 +146,7 @@ export class Session extends SessionManager {
146
146
  queries.webhooks.configure = a => this._POST('/v1/configure-webhooks', a)
147
147
  queries.webhooks.update = a => this._PATCH('/v1/update-webhooks', a)
148
148
 
149
- const updateUser = queries.users.updateOne
150
- queries.users.updateOne = async (...a: Parameters<typeof updateUser>) => {
151
- const updated = await updateUser(...a)
152
- if (a[0] === this.userInfo.id) { // ensure session userInfo is updated as well
153
- await this.refresh_session()
154
- }
155
- return updated
156
- }
157
-
158
149
  this.api = queries
159
-
160
- // if (this.userInfo) this.refresh_session()
161
150
  }
162
151
 
163
152
  _POST = async <A,R=void>(endpoint: string, args?: A, authenticated=true) => {
@@ -213,8 +202,8 @@ export class Session extends SessionManager {
213
202
  await this.POST('/logout-api').catch(console.error)
214
203
  }
215
204
 
216
- prepare_and_upload_file = async (file: FileBlob) => {
217
- const { name, size, type } = file
205
+ prepare_and_upload_file = async (details: FileDetails, file: Blob | Buffer | ReactNativeFile) => {
206
+ const { name, size, type } = details
218
207
  const { presignedUpload, file: { secureName } } = await this.api.files.prepare_file_upload({ name, size, type })
219
208
  await this.UPLOAD(presignedUpload, file)
220
209
  return { secureName }
package/src/session.ts CHANGED
@@ -4,6 +4,7 @@ import { Socket, io } from 'socket.io-client'
4
4
 
5
5
  import {
6
6
  FileBlob,
7
+ ReactNativeFile,
7
8
  S3PresignedPost,
8
9
  } from "@tellescope/types-utilities"
9
10
  import {
@@ -152,27 +153,17 @@ export class Session {
152
153
  } catch(err) { throw await this.errorHandler(err) }
153
154
  }
154
155
 
155
- UPLOAD = async (presigned: S3PresignedPost, file?: FileBlob, buffer?: FileBlob) => {
156
- const formData = IN_BROWSER ? new FormData() : new NodeFormData();
156
+ UPLOAD = async (presigned: S3PresignedPost, file: Blob | Buffer | ReactNativeFile) => {
157
+ const formData = new NodeFormData();
157
158
  Object.keys(presigned.fields).forEach(key => {
158
- formData.append(key, presigned.fields[key]);
159
+ formData.append(key, presigned.fields[key as keyof S3PresignedPost['fields']]);
159
160
  });
161
+ formData.append("file", file) // must be appended last
160
162
 
161
- // has to be appended last
162
- if (!IN_BROWSER) file = buffer || file // use raw buffer if file is Node Blob
163
- formData.append("file", file as Blob)
163
+ const headers = formData.getHeaders ? { ...formData.getHeaders(), 'Content-Length' : (formData as any).maxDataSize }
164
+ : { 'Content-Type': 'multipart/form-data' }
164
165
 
165
- try {
166
- await axios.post(presigned.url, formData, {
167
- headers: IN_BROWSER
168
- ? { 'Content-Type': 'multipart/form-data'}
169
- : {
170
- ...(formData as NodeFormData).getHeaders(),
171
- 'Content-Length' : (formData as any).maxDataSize
172
- }
173
- })
174
- }
175
- catch(err) { console.error(err); throw err }
166
+ await axios.post(presigned.url, formData, { headers })
176
167
  }
177
168
 
178
169
  DOWNLOAD = async (downloadURL: string) => {
@@ -181,6 +172,9 @@ export class Session {
181
172
  }
182
173
 
183
174
  EMIT = async (route: string, args: object, authenticated=true, options={} as RequestOptions) => {
175
+ if (!this.socket) {
176
+ this.authenticate_socket() // sets namespace correctly
177
+ }
184
178
  this.socket?.emit(route, { ...args, ...authenticated ? { authToken: this.authToken } : {} } )
185
179
  }
186
180
 
@@ -199,6 +193,7 @@ export class Session {
199
193
  removeAllSocketListeners = (s: string) => this.socket?.removeAllListeners(s)
200
194
 
201
195
  authenticate_socket = () => {
196
+ if (!this.userInfo?.businessId) return
202
197
  this.socket = io(`${this.host}/${this.userInfo.businessId}`, { transports: ['websocket'] }); // supporting polling requires sticky session at load balancer
203
198
  this.socket.on('disconnect', () => { this.socketAuthenticated = false })
204
199
  this.socket.on('authenticated', () => { this.socketAuthenticated = true })
@@ -295,8 +295,13 @@ const generateEnduserAuthTests = async () => {
295
295
 
296
296
  const { authToken, enduser } = await sdk.api.endusers.generate_auth_token({ id: e.id })
297
297
  assert(!!authToken && !!enduser, 'invalid returned values', 'Generate authToken and get enduser')
298
- const { isAuthenticated } = await sdk.api.endusers.is_authenticated({ id: enduser.id, authToken })
298
+ let { isAuthenticated } = await sdk.api.endusers.is_authenticated({ id: enduser.id, authToken })
299
299
  assert(isAuthenticated, 'invalid authToken generated for enduser', 'Generate authToken for enduser is valid')
300
+ assert(
301
+ (await sdk.api.endusers.is_authenticated({ authToken })).isAuthenticated,
302
+ 'id omitted results in failed authentication',
303
+ 'id optional for is_authenticated'
304
+ )
300
305
 
301
306
  const { authToken: authTokenUID, enduser: enduser2 } = await sdk.api.endusers.generate_auth_token({ externalId })
302
307
  assert(!!authTokenUID && !!enduser2, 'invalid returned values eid', 'Generate authToken and get enduser eid')
@@ -969,6 +974,7 @@ const enduserAccessTests = async () => {
969
974
  const password = 'testpassword'
970
975
 
971
976
  const enduser = await sdk.api.endusers.createOne({ email })
977
+ const enduser2 = await sdk.api.endusers.createOne({ email: 'hi' + email })
972
978
  await sdk.api.endusers.set_password({ id: enduser.id, password }).catch(console.error)
973
979
  await enduserSDK.authenticate(email, password).catch(console.error)
974
980
 
@@ -1021,6 +1027,17 @@ const enduserAccessTests = async () => {
1021
1027
  }
1022
1028
  }
1023
1029
 
1030
+ await async_test(
1031
+ `enduser can update self`,
1032
+ () => enduserSDK.api.endusers.updateOne(enduser.id, { fname: "Sebastian", lname: "Coates" }),
1033
+ { onResult: e => e.id === enduser.id && e.fname === 'Sebastian' && e.lname === "Coates" }
1034
+ )
1035
+ await async_test(
1036
+ `enduser can't update other enduser`,
1037
+ () => enduserSDK.api.endusers.updateOne(enduser2.id, { fname: "Shouldn't Work"}),
1038
+ { shouldError: true, onError: e => e.message === "Could not find a record for the given id" }
1039
+ )
1040
+
1024
1041
  const ticketAccessible = await sdk.api.tickets.createOne({ enduserId: enduser.id, title: "Accessible ticket" })
1025
1042
  const ticketInaccessible = await sdk.api.tickets.createOne({ enduserId: PLACEHOLDER_ID, title: "Inaccessible ticket" })
1026
1043
  await async_test(
@@ -1047,6 +1064,7 @@ const enduserAccessTests = async () => {
1047
1064
  await sdk.api.tickets.deleteOne(ticketAccessible.id)
1048
1065
  await sdk.api.tickets.deleteOne(ticketInaccessible.id)
1049
1066
  await sdk.api.endusers.deleteOne(enduser.id)
1067
+ await sdk.api.endusers.deleteOne(enduser2.id)
1050
1068
  }
1051
1069
 
1052
1070
  const files_tests = async () => {
@@ -1055,12 +1073,15 @@ const files_tests = async () => {
1055
1073
  const { presignedUpload, file } = await sdk.api.files.prepare_file_upload({
1056
1074
  name: 'Test File', size: buff.byteLength, type: 'text/plain'
1057
1075
  })
1058
- await sdk.UPLOAD(presignedUpload, undefined, buff as any)
1076
+ await sdk.UPLOAD(presignedUpload, buff)
1059
1077
 
1060
1078
  const { downloadURL } = await sdk.api.files.file_download_URL({ secureName: file.secureName })
1061
1079
  const downloaded: string = await sdk.DOWNLOAD(downloadURL)
1062
1080
 
1063
1081
  assert(downloaded === buff.toString(), 'downloaded file does not match uploaded file', 'upload, download comparison')
1082
+
1083
+ const { downloadURL: cachedURL } = await sdk.api.files.file_download_URL({ secureName: file.secureName })
1084
+ assert(downloadURL === cachedURL, 'cache download url failed', 'download url cache')
1064
1085
  }
1065
1086
 
1066
1087
  const enduser_session_tests = async () => {
@@ -1097,14 +1118,15 @@ const users_tests = async () => {
1097
1118
  )
1098
1119
 
1099
1120
  // reset fname to "Non" if this test throws, otherwise will falsely pass on next run
1100
- assert(sdkNonAdmin.userInfo.fname === 'Updated', 'refresh_session not called on self after update', 'sdk updated on user update')
1121
+ // NOT Supported behavior any more
1122
+ // assert(sdkNonAdmin.userInfo.fname === 'Updated', 'refresh_session not called on self after update', 'sdk updated on user update')
1101
1123
 
1102
1124
  await async_test(
1103
1125
  `update user (admin, other user)`,
1104
1126
  () => sdk.api.users.updateOne(sdkNonAdmin.userInfo.id, { fname: 'Non' }), // change back
1105
1127
  { onResult: u => u.id === sdkNonAdmin.userInfo.id && u.fname === "Non" }
1106
1128
  )
1107
- sdkNonAdmin.userInfo.fname = 'Non' // update back in sdk instance as well
1129
+ // sdkNonAdmin.userInfo.fname = 'Non' // update back in sdk instance as well
1108
1130
 
1109
1131
  await async_test(
1110
1132
  `verify user update with admin get`,