epicenter-libs 3.11.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.
- package/CHANGELOG.md +345 -0
- package/LICENSE.md +37 -0
- package/README.md +134 -0
- package/dist/browser/AckExtension-e67c6a28.js +129 -0
- package/dist/browser/AckExtension-e67c6a28.js.map +1 -0
- package/dist/browser/ReloadExtension-b1e50033.js +253 -0
- package/dist/browser/ReloadExtension-b1e50033.js.map +1 -0
- package/dist/browser/cometd-eeabdcd4.js +3438 -0
- package/dist/browser/cometd-eeabdcd4.js.map +1 -0
- package/dist/browser/epicenter-2cce2971.js +6086 -0
- package/dist/browser/epicenter-2cce2971.js.map +1 -0
- package/dist/browser/epicenter.js +2 -0
- package/dist/browser/epicenter.js.map +1 -0
- package/dist/cjs/AckExtension-f5178e19.js +131 -0
- package/dist/cjs/AckExtension-f5178e19.js.map +1 -0
- package/dist/cjs/ReloadExtension-65b036ba.js +255 -0
- package/dist/cjs/ReloadExtension-65b036ba.js.map +1 -0
- package/dist/cjs/cometd-473408f4.js +3441 -0
- package/dist/cjs/cometd-473408f4.js.map +1 -0
- package/dist/cjs/epicenter-12ceb814.js +7248 -0
- package/dist/cjs/epicenter-12ceb814.js.map +1 -0
- package/dist/cjs/epicenter.js +54 -0
- package/dist/cjs/epicenter.js.map +1 -0
- package/dist/epicenter.js +9895 -0
- package/dist/epicenter.js.map +1 -0
- package/dist/epicenter.min.js +2 -0
- package/dist/epicenter.min.js.map +1 -0
- package/dist/module/AckExtension-6181d8b5.js +129 -0
- package/dist/module/AckExtension-6181d8b5.js.map +1 -0
- package/dist/module/ReloadExtension-eaa8c42c.js +253 -0
- package/dist/module/ReloadExtension-eaa8c42c.js.map +1 -0
- package/dist/module/cometd-af78008d.js +3438 -0
- package/dist/module/cometd-af78008d.js.map +1 -0
- package/dist/module/epicenter-9b8c92a9.js +7213 -0
- package/dist/module/epicenter-9b8c92a9.js.map +1 -0
- package/dist/module/epicenter.js +7 -0
- package/dist/module/epicenter.js.map +1 -0
- package/dist/types/adapters/account.d.ts +44 -0
- package/dist/types/adapters/admin.d.ts +33 -0
- package/dist/types/adapters/asset.d.ts +46 -0
- package/dist/types/adapters/authentication.d.ts +62 -0
- package/dist/types/adapters/channel.d.ts +39 -0
- package/dist/types/adapters/chat.d.ts +105 -0
- package/dist/types/adapters/cometd.d.ts +25 -0
- package/dist/types/adapters/email.d.ts +86 -0
- package/dist/types/adapters/episode.d.ts +91 -0
- package/dist/types/adapters/group.d.ts +273 -0
- package/dist/types/adapters/index.d.ts +21 -0
- package/dist/types/adapters/leaderboard.d.ts +68 -0
- package/dist/types/adapters/presence.d.ts +35 -0
- package/dist/types/adapters/project.d.ts +99 -0
- package/dist/types/adapters/recaptcha.d.ts +1 -0
- package/dist/types/adapters/run.d.ts +253 -0
- package/dist/types/adapters/task.d.ts +154 -0
- package/dist/types/adapters/time.d.ts +2 -0
- package/dist/types/adapters/user.d.ts +38 -0
- package/dist/types/adapters/vault.d.ts +94 -0
- package/dist/types/adapters/world.d.ts +230 -0
- package/dist/types/epicenter.d.ts +10 -0
- package/dist/types/utils/config.d.ts +90 -0
- package/dist/types/utils/constants.d.ts +290 -0
- package/dist/types/utils/cookies.d.ts +16 -0
- package/dist/types/utils/error-manager.d.ts +21 -0
- package/dist/types/utils/error.d.ts +4 -0
- package/dist/types/utils/fault.d.ts +17 -0
- package/dist/types/utils/helpers.d.ts +4 -0
- package/dist/types/utils/identification.d.ts +47 -0
- package/dist/types/utils/index.d.ts +11 -0
- package/dist/types/utils/result.d.ts +6 -0
- package/dist/types/utils/router.d.ts +157 -0
- package/dist/types/utils/store.d.ts +31 -0
- package/package.json +103 -0
- package/src/adapters/account.ts +104 -0
- package/src/adapters/admin.ts +53 -0
- package/src/adapters/asset.ts +195 -0
- package/src/adapters/authentication.ts +173 -0
- package/src/adapters/channel.ts +83 -0
- package/src/adapters/chat.ts +186 -0
- package/src/adapters/cometd.ts +297 -0
- package/src/adapters/email.ts +146 -0
- package/src/adapters/episode.ts +163 -0
- package/src/adapters/group.ts +511 -0
- package/src/adapters/index.ts +43 -0
- package/src/adapters/leaderboard.ts +122 -0
- package/src/adapters/presence.ts +63 -0
- package/src/adapters/project.ts +123 -0
- package/src/adapters/recaptcha.ts +11 -0
- package/src/adapters/run.ts +726 -0
- package/src/adapters/task.ts +213 -0
- package/src/adapters/time.ts +36 -0
- package/src/adapters/user.ts +75 -0
- package/src/adapters/vault.ts +232 -0
- package/src/adapters/world.ts +412 -0
- package/src/epicenter.ts +96 -0
- package/src/globals.d.ts +16 -0
- package/src/utils/config.ts +168 -0
- package/src/utils/constants.ts +324 -0
- package/src/utils/cookies.ts +71 -0
- package/src/utils/error-manager.ts +66 -0
- package/src/utils/error.ts +9 -0
- package/src/utils/fault.ts +39 -0
- package/src/utils/helpers.ts +7 -0
- package/src/utils/identification.ts +128 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/result.ts +15 -0
- package/src/utils/router.ts +547 -0
- package/src/utils/store.ts +82 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { RoutingOptions } from '../utils/router';
|
|
2
|
+
import type { GenericScope } from '../utils/constants';
|
|
3
|
+
|
|
4
|
+
import fetch from 'cross-fetch';
|
|
5
|
+
import { Fault, Router, ROLE } from '../utils';
|
|
6
|
+
|
|
7
|
+
interface AssetScope extends GenericScope {
|
|
8
|
+
userKey?: string,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Asset {
|
|
12
|
+
file: string,
|
|
13
|
+
address: {
|
|
14
|
+
projectShortName: string,
|
|
15
|
+
groupName: string,
|
|
16
|
+
accountShortName: string,
|
|
17
|
+
worldKey: string,
|
|
18
|
+
episodeName: string,
|
|
19
|
+
},
|
|
20
|
+
scope: AssetScope,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface AssetTicket {
|
|
24
|
+
url: string,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export async function create(
|
|
29
|
+
file: string,
|
|
30
|
+
scope: AssetScope,
|
|
31
|
+
optionals: {
|
|
32
|
+
readLock?: keyof typeof ROLE,
|
|
33
|
+
writeLock?: keyof typeof ROLE,
|
|
34
|
+
ttlSeconds?: number,
|
|
35
|
+
} & RoutingOptions = {}
|
|
36
|
+
): Promise<AssetTicket> {
|
|
37
|
+
const { scopeBoundary, scopeKey, userKey } = scope;
|
|
38
|
+
const {
|
|
39
|
+
readLock, writeLock, ttlSeconds,
|
|
40
|
+
...routingOptions
|
|
41
|
+
} = optionals;
|
|
42
|
+
return await new Router()
|
|
43
|
+
.post('/asset', {
|
|
44
|
+
body: {
|
|
45
|
+
file,
|
|
46
|
+
scope: {
|
|
47
|
+
scopeBoundary,
|
|
48
|
+
scopeKey,
|
|
49
|
+
userKey,
|
|
50
|
+
},
|
|
51
|
+
permit: {
|
|
52
|
+
readLock: readLock ?? ROLE.USER,
|
|
53
|
+
writeLock: writeLock ?? ROLE.USER,
|
|
54
|
+
},
|
|
55
|
+
ttlSeconds,
|
|
56
|
+
},
|
|
57
|
+
...routingOptions,
|
|
58
|
+
}).then(({ body }) => body);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function update(
|
|
62
|
+
file: string,
|
|
63
|
+
scope: AssetScope,
|
|
64
|
+
optionals: {
|
|
65
|
+
readLock?: keyof typeof ROLE,
|
|
66
|
+
writeLock?: keyof typeof ROLE,
|
|
67
|
+
ttlSeconds?: number,
|
|
68
|
+
} & RoutingOptions = {}
|
|
69
|
+
): Promise<AssetTicket> {
|
|
70
|
+
const { scopeBoundary, scopeKey, userKey } = scope;
|
|
71
|
+
const {
|
|
72
|
+
readLock, writeLock, ttlSeconds,
|
|
73
|
+
...routingOptions
|
|
74
|
+
} = optionals;
|
|
75
|
+
return await new Router()
|
|
76
|
+
.patch('/asset', {
|
|
77
|
+
body: {
|
|
78
|
+
file,
|
|
79
|
+
scope: {
|
|
80
|
+
scopeBoundary,
|
|
81
|
+
scopeKey,
|
|
82
|
+
userKey,
|
|
83
|
+
},
|
|
84
|
+
permit: {
|
|
85
|
+
readLock: readLock ?? ROLE.USER,
|
|
86
|
+
writeLock: writeLock ?? ROLE.USER,
|
|
87
|
+
},
|
|
88
|
+
ttlSeconds,
|
|
89
|
+
},
|
|
90
|
+
...routingOptions,
|
|
91
|
+
}).then(({ body }) => body);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function remove(
|
|
95
|
+
assetKey: string,
|
|
96
|
+
optionals: RoutingOptions = {}
|
|
97
|
+
): Promise<void> {
|
|
98
|
+
return await new Router()
|
|
99
|
+
.delete(`/asset/${assetKey}`, optionals)
|
|
100
|
+
.then(({ body }) => body);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function removeFromScope(
|
|
104
|
+
scope: AssetScope,
|
|
105
|
+
optionals: RoutingOptions = {}
|
|
106
|
+
): Promise<void> {
|
|
107
|
+
const { scopeBoundary, scopeKey, userKey } = scope;
|
|
108
|
+
const uriComponent = userKey ? `/${userKey}` : '';
|
|
109
|
+
return await new Router()
|
|
110
|
+
.delete(`/asset/in/${scopeBoundary}/${scopeKey}${uriComponent}`, optionals)
|
|
111
|
+
.then(({ body }) => body);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function get(
|
|
115
|
+
assetKey: string,
|
|
116
|
+
optionals: RoutingOptions = {}
|
|
117
|
+
): Promise<Asset> {
|
|
118
|
+
const { server, accountShortName, projectShortName } = optionals;
|
|
119
|
+
return await new Router()
|
|
120
|
+
.withServer(server)
|
|
121
|
+
.withAccountShortName(accountShortName)
|
|
122
|
+
.withProjectShortName(projectShortName)
|
|
123
|
+
.get(`/asset/${assetKey}`)
|
|
124
|
+
.then(({ body }) => body);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function list(
|
|
128
|
+
scope: AssetScope,
|
|
129
|
+
optionals: {
|
|
130
|
+
filter?: string,
|
|
131
|
+
} & RoutingOptions = {}
|
|
132
|
+
): Promise<Asset[]> {
|
|
133
|
+
const { scopeBoundary, scopeKey, userKey } = scope;
|
|
134
|
+
const {
|
|
135
|
+
filter,
|
|
136
|
+
...routingOptions
|
|
137
|
+
} = optionals;
|
|
138
|
+
const uriComponent = userKey ? `/${userKey}` : '';
|
|
139
|
+
return await new Router()
|
|
140
|
+
.get(`/asset/in/${scopeBoundary}/${scopeKey}${uriComponent}/${filter ?? '*'}`, routingOptions)
|
|
141
|
+
.then(({ body }) => body);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function getURL(
|
|
145
|
+
assetKey: string,
|
|
146
|
+
optionals: RoutingOptions = {}
|
|
147
|
+
): Promise<string> {
|
|
148
|
+
return await new Router()
|
|
149
|
+
.get(`/asset/url/${assetKey}`, optionals)
|
|
150
|
+
.then(({ body }) => body);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function getURLWithScope(
|
|
154
|
+
file: string,
|
|
155
|
+
scope: AssetScope,
|
|
156
|
+
optionals: RoutingOptions = {}
|
|
157
|
+
): Promise<string> {
|
|
158
|
+
const { scopeBoundary, scopeKey, userKey } = scope;
|
|
159
|
+
const uriComponent = userKey ? `/${userKey}` : '';
|
|
160
|
+
return await new Router()
|
|
161
|
+
.get(`/asset/url/with/${scopeBoundary}/${scopeKey}${uriComponent}/${file}`, optionals)
|
|
162
|
+
.then(({ body }) => body);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const CONFLICT = 409;
|
|
166
|
+
export async function store(
|
|
167
|
+
file: File,
|
|
168
|
+
scope: AssetScope,
|
|
169
|
+
optionals: {
|
|
170
|
+
readLock?: keyof typeof ROLE,
|
|
171
|
+
writeLock?: keyof typeof ROLE,
|
|
172
|
+
ttlSeconds?: number,
|
|
173
|
+
overwrite?: boolean,
|
|
174
|
+
fileName?: string,
|
|
175
|
+
} & RoutingOptions = {}
|
|
176
|
+
): Promise<void> {
|
|
177
|
+
const { overwrite, fileName, ...remaining } = optionals;
|
|
178
|
+
const name = fileName ?? file.name;
|
|
179
|
+
let presignedUrl = '';
|
|
180
|
+
try {
|
|
181
|
+
const response = await create(name, scope, remaining);
|
|
182
|
+
presignedUrl = response.url;
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (error instanceof Fault) {
|
|
185
|
+
const shouldUpdate = error.status === CONFLICT && overwrite;
|
|
186
|
+
if (!shouldUpdate) throw error;
|
|
187
|
+
const response = await update(name, scope, remaining);
|
|
188
|
+
presignedUrl = response.url;
|
|
189
|
+
} else {
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
await fetch(presignedUrl, { method: 'PUT', body: file });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { RoutingOptions } from '../utils/router';
|
|
2
|
+
import type { Session } from '../utils/identification';
|
|
3
|
+
|
|
4
|
+
import { Router, identification } from '../utils';
|
|
5
|
+
import cometdAdapter from './cometd';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
interface UserCredentials {
|
|
9
|
+
handle: string,
|
|
10
|
+
password: string,
|
|
11
|
+
groupKey?: string,
|
|
12
|
+
}
|
|
13
|
+
interface AppCredentials {
|
|
14
|
+
secretKey: string,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Run API adapters -- use this to create, update, delete, and manage your runs
|
|
19
|
+
* @namespace authAdapter
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Logs out of current Epicenter session.
|
|
24
|
+
* @example
|
|
25
|
+
* epicenter.authAdapter.logout()
|
|
26
|
+
* @returns promise resolving to successful logout
|
|
27
|
+
*/
|
|
28
|
+
export async function logout(): Promise<void> {
|
|
29
|
+
identification.session = undefined;
|
|
30
|
+
await cometdAdapter.disconnect();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function login(
|
|
34
|
+
credentials: UserCredentials | AppCredentials,
|
|
35
|
+
optionals: { objectType?: string } & RoutingOptions = {}
|
|
36
|
+
): Promise<Session> {
|
|
37
|
+
const { objectType, ...routingOptions } = optionals;
|
|
38
|
+
let payload;
|
|
39
|
+
if (Object.prototype.hasOwnProperty.call(credentials, 'handle')) {
|
|
40
|
+
const { handle, password, groupKey } = credentials as UserCredentials;
|
|
41
|
+
payload = { objectType: objectType ?? 'user', handle, password, groupKey: groupKey || undefined };
|
|
42
|
+
}
|
|
43
|
+
if (Object.prototype.hasOwnProperty.call(credentials, 'secretKey')) {
|
|
44
|
+
const { secretKey } = credentials as AppCredentials;
|
|
45
|
+
payload = { objectType: objectType ?? 'account', secretKey };
|
|
46
|
+
}
|
|
47
|
+
const session = await new Router()
|
|
48
|
+
.post('/authentication', {
|
|
49
|
+
inert: true,
|
|
50
|
+
includeAuthorization: false,
|
|
51
|
+
body: payload,
|
|
52
|
+
...routingOptions,
|
|
53
|
+
}).then(({ body }) => body);
|
|
54
|
+
await logout();
|
|
55
|
+
identification.session = session;
|
|
56
|
+
return session;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Regenerates your epicenter session with the appropriate context. Allows users to update their session to the correct group, and admins to update their session with the correct account name. Will fail if the user/admin does not already belong to the group/account.
|
|
61
|
+
* @example
|
|
62
|
+
* // Changes the current user session to have a group context associated with the provided key
|
|
63
|
+
* epicenter.authAdapter.regenerate('00000165ad4e6a3cd22b993340b963820239');
|
|
64
|
+
* // Changes the current admin session to use the account context for the 'acme' account.
|
|
65
|
+
* epicenter.authAdapter.regenerate('acme', { objectType: 'admin' });
|
|
66
|
+
* @param groupOrAccount Group key or account name
|
|
67
|
+
* @param [optionals] Optional parameters
|
|
68
|
+
* @param [optionals.objectType] The object type to regenerate for. Uses objectType: 'user' by default.
|
|
69
|
+
* @returns promise resolving to the new session object
|
|
70
|
+
*/
|
|
71
|
+
export async function regenerate(
|
|
72
|
+
groupOrAccount: string,
|
|
73
|
+
optionals: {
|
|
74
|
+
objectType?: string,
|
|
75
|
+
} & RoutingOptions = {}
|
|
76
|
+
): Promise<Session> {
|
|
77
|
+
const {
|
|
78
|
+
objectType = 'user',
|
|
79
|
+
accountShortName,
|
|
80
|
+
...routingOptions
|
|
81
|
+
} = optionals;
|
|
82
|
+
|
|
83
|
+
const session = await new Router()
|
|
84
|
+
.patch('/authentication', {
|
|
85
|
+
accountShortName: objectType === 'admin' ?
|
|
86
|
+
groupOrAccount :
|
|
87
|
+
accountShortName,
|
|
88
|
+
body: {
|
|
89
|
+
objectType,
|
|
90
|
+
groupKey: objectType === 'user' ?
|
|
91
|
+
groupOrAccount :
|
|
92
|
+
undefined,
|
|
93
|
+
},
|
|
94
|
+
...routingOptions,
|
|
95
|
+
}).then(({ body }) => body);
|
|
96
|
+
|
|
97
|
+
await logout();
|
|
98
|
+
identification.session = session;
|
|
99
|
+
return session;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function sso(
|
|
103
|
+
optionals: RoutingOptions = {},
|
|
104
|
+
): Promise<Session> {
|
|
105
|
+
const session = await new Router()
|
|
106
|
+
.get('/registration/sso', optionals)
|
|
107
|
+
.then(({ body }) => body);
|
|
108
|
+
|
|
109
|
+
identification.session = session;
|
|
110
|
+
return session;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function getSession(): Promise<Session> {
|
|
114
|
+
const { body } = await new Router().get('/authentication');
|
|
115
|
+
identification.session = body;
|
|
116
|
+
return body;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getLocalSession(): Session | undefined {
|
|
120
|
+
return identification.session;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function setLocalSession(session: Session): Session {
|
|
124
|
+
return identification.session = session;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Sends a link to reset a user's password to their email
|
|
129
|
+
* @example
|
|
130
|
+
* const subject = 'Please reset your password for the Acme simulation';
|
|
131
|
+
* const url = 'https://forio.com/app/acme/simulations';
|
|
132
|
+
* const handle = 'testUser@test.com'
|
|
133
|
+
* epicenter.authAdapter.resetPassword(handle, { redirectURL, subject });
|
|
134
|
+
* @param handle Handle that user would use to login
|
|
135
|
+
* @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist.
|
|
136
|
+
* @param [optionals.redirectURL] Url to redirect to after password reset is completed. Must be in the forio domain otherwise an error will be thrown
|
|
137
|
+
* @param [optionals.subject] The subject of the email that will be sent
|
|
138
|
+
* @returns promise that resolves to undefined (indicating success)
|
|
139
|
+
*/
|
|
140
|
+
export async function resetPassword(
|
|
141
|
+
handle: string,
|
|
142
|
+
optionals: {
|
|
143
|
+
redirectURL?: string,
|
|
144
|
+
subject?: string,
|
|
145
|
+
} & RoutingOptions = {}
|
|
146
|
+
): Promise<void> {
|
|
147
|
+
const {
|
|
148
|
+
redirectURL, subject,
|
|
149
|
+
...routingOptions
|
|
150
|
+
} = optionals;
|
|
151
|
+
|
|
152
|
+
return await new Router()
|
|
153
|
+
.post(`/authentication/password/user/${handle}`, {
|
|
154
|
+
...routingOptions,
|
|
155
|
+
body: {
|
|
156
|
+
redirectUrl: redirectURL,
|
|
157
|
+
subject,
|
|
158
|
+
},
|
|
159
|
+
})
|
|
160
|
+
.then(({ body }) => body);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function verify(
|
|
164
|
+
token: string,
|
|
165
|
+
optionals: RoutingOptions = {},
|
|
166
|
+
): Promise<Session> {
|
|
167
|
+
return await new Router()
|
|
168
|
+
.get('/verification', {
|
|
169
|
+
authorization: `Bearer ${token}`,
|
|
170
|
+
...optionals,
|
|
171
|
+
})
|
|
172
|
+
.then(({ body }) => body);
|
|
173
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Callback, SubscriptionHandle, Message } from 'cometd';
|
|
2
|
+
import type { GenericScope } from '../utils/constants';
|
|
3
|
+
|
|
4
|
+
import { EpicenterError, SCOPE_BOUNDARY, PUSH_CATEGORY } from '../utils';
|
|
5
|
+
import cometdAdapter from './cometd';
|
|
6
|
+
|
|
7
|
+
interface ChannelScope extends GenericScope {
|
|
8
|
+
pushCategory: string,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const validateScope = (scope: ChannelScope) => {
|
|
12
|
+
if (!scope) throw new EpicenterError('No scope found where one was required');
|
|
13
|
+
const { scopeBoundary, scopeKey, pushCategory } = scope;
|
|
14
|
+
if (!scopeBoundary) throw new EpicenterError('Missing scope component: scopeBoundary');
|
|
15
|
+
if (!scopeKey) throw new EpicenterError('Missing scope component: scopeKey');
|
|
16
|
+
if (!pushCategory) throw new EpicenterError('Missing scope component: pushCategory');
|
|
17
|
+
if (!Object.prototype.hasOwnProperty.call(SCOPE_BOUNDARY, scopeBoundary)) throw new EpicenterError(`Invalid scope boundary: ${scopeBoundary}`);
|
|
18
|
+
if (!Object.prototype.hasOwnProperty.call(PUSH_CATEGORY, pushCategory)) throw new EpicenterError(`Invalid push category: ${pushCategory}`);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Used to subscribe to CometD channels. Pass in a channel scope to instantiate, if a subscription to that scope already exists it will use it.
|
|
23
|
+
* */
|
|
24
|
+
export default class Channel {
|
|
25
|
+
|
|
26
|
+
path: string;
|
|
27
|
+
update: Callback | undefined;
|
|
28
|
+
subscription: SubscriptionHandle | null = null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Channel constructor
|
|
32
|
+
* @param scope object with the scope boundary, scope key, and push category. Defines the namespace for the channel
|
|
33
|
+
*/
|
|
34
|
+
constructor(scope: ChannelScope) {
|
|
35
|
+
const { scopeBoundary, scopeKey, pushCategory } = scope;
|
|
36
|
+
validateScope(scope);
|
|
37
|
+
this.path = `/${scopeBoundary.toLowerCase()}/${scopeKey}/${pushCategory.toLowerCase()}`;
|
|
38
|
+
if (cometdAdapter.subscriptions.has(this.path)) {
|
|
39
|
+
this.subscription = cometdAdapter.subscriptions.get(this.path);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
publish(content: FIXME): Promise<Message | Message[]> {
|
|
44
|
+
return cometdAdapter.publish(this, content);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Subscribes to the CometD channel, attaching a handler for any channel updates. If a subscription already exists it will first unsubscribe, ensuring that only one subscription is ever attached to the channel.
|
|
49
|
+
* @example
|
|
50
|
+
* import { Channel, authAdapter, SCOPE_BOUNDARY, PUSH_CATEGORY } from 'epicenter';
|
|
51
|
+
* const session = authAdapter.getLocalSession();
|
|
52
|
+
* const channel = new Channel({
|
|
53
|
+
* scopeBoundary: SCOPE_BOUNDARY.GROUP,
|
|
54
|
+
* scopeKey: session.groupKey,
|
|
55
|
+
* pushCategory: PUSH_CATEGORY.CHAT,
|
|
56
|
+
* }).subscribe((data) => {
|
|
57
|
+
* console.log(data.content);
|
|
58
|
+
* })
|
|
59
|
+
* @param update function that is called whenever a channel update occurs.
|
|
60
|
+
* @returns the subscription object returned by CometD after a sucessful subscribe.
|
|
61
|
+
*/
|
|
62
|
+
async subscribe(
|
|
63
|
+
update: (data: unknown) => unknown,
|
|
64
|
+
options: { inert?: boolean } = {},
|
|
65
|
+
): Promise<SubscriptionHandle> {
|
|
66
|
+
if (this.subscription) await this.unsubscribe();
|
|
67
|
+
this.update = update;
|
|
68
|
+
return cometdAdapter.add(this, update, options).then((subscription) => {
|
|
69
|
+
if (Array.isArray(subscription)) subscription = subscription[0];
|
|
70
|
+
this.subscription = subscription;
|
|
71
|
+
return subscription;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async unsubscribe(): Promise<void> {
|
|
76
|
+
if (this.subscription) {
|
|
77
|
+
await cometdAdapter.remove(this.subscription);
|
|
78
|
+
this.subscription = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { GenericScope, Permit } from 'utils/constants';
|
|
2
|
+
import type { RoutingOptions, Page, GenericSearchOptions } from 'utils/router';
|
|
3
|
+
import Router from 'utils/router';
|
|
4
|
+
|
|
5
|
+
interface ChatMessage {
|
|
6
|
+
senderKey: string,
|
|
7
|
+
receiverKey: string,
|
|
8
|
+
created: string,
|
|
9
|
+
id: string,
|
|
10
|
+
message: string,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface Chat {
|
|
14
|
+
permit: Permit,
|
|
15
|
+
chatKey: string,
|
|
16
|
+
messages: ChatMessage[],
|
|
17
|
+
room: string,
|
|
18
|
+
scope: GenericScope,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Updates the permissions of a chat
|
|
23
|
+
* @param chatKey Key associated with the chat
|
|
24
|
+
* @param permit Permit object with the updated permissions
|
|
25
|
+
* @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist.
|
|
26
|
+
* @returns the newly updated chat
|
|
27
|
+
*/
|
|
28
|
+
export async function updatePermit(
|
|
29
|
+
chatKey: string,
|
|
30
|
+
permit: Permit,
|
|
31
|
+
optionals: RoutingOptions = {}
|
|
32
|
+
): Promise<Chat> {
|
|
33
|
+
return new Router()
|
|
34
|
+
.patch(`/chat/${chatKey}`, {
|
|
35
|
+
...optionals,
|
|
36
|
+
body: { permit },
|
|
37
|
+
}).then(({ body }) => body);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates a chat
|
|
42
|
+
* @example
|
|
43
|
+
* import { chatAdapter, SCOPE_BOUNDARY, ROLE } from 'epicenter';
|
|
44
|
+
* chatAdapter.create(
|
|
45
|
+
* 'my-chat-room',
|
|
46
|
+
* { scopeBoundary: SCOPE_BOUNDARY.GROUP, scopeKey: '00000165ad4e6a3cd22b993340b963820239' },
|
|
47
|
+
* { readLock: ROLE.PARTICIPANT, writeLock: ROLE.PARTICIPANT }
|
|
48
|
+
* );
|
|
49
|
+
* @param room Name of the chat
|
|
50
|
+
* @param scope Scope of the chat; will not accept user scope
|
|
51
|
+
* @param permit Permissions for the chat
|
|
52
|
+
* @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist.
|
|
53
|
+
* @returns The chat created
|
|
54
|
+
*/
|
|
55
|
+
export async function create(
|
|
56
|
+
room: string,
|
|
57
|
+
scope: GenericScope,
|
|
58
|
+
permit: Permit,
|
|
59
|
+
optionals: RoutingOptions = {}
|
|
60
|
+
): Promise<Chat> {
|
|
61
|
+
return new Router()
|
|
62
|
+
.post('/chat', {
|
|
63
|
+
body: {
|
|
64
|
+
scope: {
|
|
65
|
+
scopeBoundary: scope.scopeBoundary,
|
|
66
|
+
scopeKey: scope.scopeKey,
|
|
67
|
+
},
|
|
68
|
+
permit,
|
|
69
|
+
room,
|
|
70
|
+
},
|
|
71
|
+
...optionals,
|
|
72
|
+
}).then(({ body }) => body);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Gets a chat
|
|
78
|
+
* @example
|
|
79
|
+
* epicenter.chatAdapter.get('00000165ad4e6a3cd22b993340b963820239');
|
|
80
|
+
* @param chatKey Key of the associated chat
|
|
81
|
+
* @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist.
|
|
82
|
+
* @returns The chat corresponding with the provided key
|
|
83
|
+
*/
|
|
84
|
+
export async function get(
|
|
85
|
+
chatKey: string,
|
|
86
|
+
optionals: RoutingOptions = {}
|
|
87
|
+
): Promise<Chat> {
|
|
88
|
+
return new Router()
|
|
89
|
+
.get(`/chat/${chatKey}`, optionals)
|
|
90
|
+
.then(({ body }) => body);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Open search for chats, returns a page
|
|
96
|
+
* @example
|
|
97
|
+
* epicenter.chatAdapter.query({
|
|
98
|
+
* filter: [
|
|
99
|
+
* 'room|=my-chat-room|my-other-chat|room-three', // looks for any rooms with the names provided
|
|
100
|
+
* 'scopeBoundary=GROUP', // keeps the search within the group scope
|
|
101
|
+
* // 'scopeKey=00000165ad4e6a3cd22b993340b963820239', // used in conjunction with the scopeBoundary
|
|
102
|
+
* // 'chatKey=0000017dd3bf540e5ada5b1e058f08f20461', // searches for a specific chat
|
|
103
|
+
* // 'accountShortName=acme', // specifies the account, typically unnecessary
|
|
104
|
+
* // 'projectShortName=simulations', // specifies the project, typically unnecessary
|
|
105
|
+
* // 'groupName=my-group-name', // search based on group name
|
|
106
|
+
* // 'episodeName=my-episode-name', // search based on episode name
|
|
107
|
+
* 'created>=2022-01-03T20:30:53.054Z', // looks for any chats created after Jan 3rd 2022
|
|
108
|
+
* ],
|
|
109
|
+
* sort: ['+chat.created'], // sort all findings by the 'created' field (ascending)
|
|
110
|
+
* first: 3, // page should start with the 4th item found (defaults to 0)
|
|
111
|
+
* max: 10, // page should only include the first 10 items
|
|
112
|
+
* });
|
|
113
|
+
* @param searchOptions Search options -- for more on Epicenter search options go [here](NOOP link)
|
|
114
|
+
* @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist.
|
|
115
|
+
* @returns A page for the list of chats found
|
|
116
|
+
*/
|
|
117
|
+
export async function query(
|
|
118
|
+
searchOptions: GenericSearchOptions,
|
|
119
|
+
optionals: RoutingOptions = {}
|
|
120
|
+
): Promise<Page<Chat>> {
|
|
121
|
+
const { filter = [], sort = [], first = 0, max } = searchOptions;
|
|
122
|
+
const searchParams = {
|
|
123
|
+
filter: filter.join(';') || undefined,
|
|
124
|
+
sort: sort.join(';') || undefined,
|
|
125
|
+
first, max,
|
|
126
|
+
};
|
|
127
|
+
return await new Router()
|
|
128
|
+
.withSearchParams(searchParams)
|
|
129
|
+
.get('/chat', {
|
|
130
|
+
paginated: true,
|
|
131
|
+
...optionals,
|
|
132
|
+
})
|
|
133
|
+
.then(({ body }) => body);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Sends a message to a chat
|
|
138
|
+
* @example
|
|
139
|
+
* epicenter.chatAdapter.sendMessage('0000017dd3bf540e5ada5b1e058f08f20461', 'hello');
|
|
140
|
+
* epicenter.chatAdapter.sendMessage('0000017dd3bf540e5ada5b1e058f08f20461', 'hello, privately', { userKey: '000001796733eef0842f4d6d960997018a33' });
|
|
141
|
+
* @param chatKey Key associated with the chat
|
|
142
|
+
* @param message Message text to send
|
|
143
|
+
* @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist.
|
|
144
|
+
* @param [optionals.userKey] Key of the user to send the message to. If omitted, will send as a public message
|
|
145
|
+
* @returns The chat message created
|
|
146
|
+
*/
|
|
147
|
+
export async function sendMessage(
|
|
148
|
+
chatKey: string,
|
|
149
|
+
message: string,
|
|
150
|
+
optionals: { userKey?: string } & RoutingOptions = {}
|
|
151
|
+
): Promise<ChatMessage> {
|
|
152
|
+
const { userKey, ...routingOptions } = optionals;
|
|
153
|
+
const uriComponent = userKey ? `/${userKey}` : '';
|
|
154
|
+
return new Router()
|
|
155
|
+
.put(`/chat/message/${chatKey}${uriComponent}`, {
|
|
156
|
+
body: { message },
|
|
157
|
+
...routingOptions,
|
|
158
|
+
}).then(({ body }) => body);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Retrieves messages from for a given chat
|
|
163
|
+
* @example
|
|
164
|
+
* // gets the chat message with id: 5
|
|
165
|
+
* epicenter.chatAdapter.getMessages('0000017dd3bf540e5ada5b1e058f08f20461', { horizon: 5, maxRecords: 1 });
|
|
166
|
+
* // gets the 10 chat messages starting from id 5 (inclusive)
|
|
167
|
+
* epicenter.chatAdapter.getMessages('0000017dd3bf540e5ada5b1e058f08f20461', { horizon: 5, maxRecords: 10 });
|
|
168
|
+
* @param chatKey Key associated with the chat
|
|
169
|
+
* @param [optionals] Optional arguments; pass network call options overrides here. Special arguments specific to this method are listed below if they exist.
|
|
170
|
+
* @param [optionals.maxRecords] Maximum number of messages to get
|
|
171
|
+
* @param [optionals.horizon] The message ID from which to start with; works backwards so if `maxRecords=20` and `horizon=50`, it will get the 20 messages starting from message ID 50, working backwards (50, 49, 48..., etc.). If this value is omitted the platform assumes it is the most recent message in the chat
|
|
172
|
+
* @returns The list chat messages requested
|
|
173
|
+
*/
|
|
174
|
+
export async function getMessages(
|
|
175
|
+
chatKey: string,
|
|
176
|
+
optionals: {
|
|
177
|
+
maxRecords?: number,
|
|
178
|
+
horizon?: number,
|
|
179
|
+
} & RoutingOptions = {}
|
|
180
|
+
): Promise<ChatMessage[]> {
|
|
181
|
+
const { maxRecords, horizon, ...routingOptions } = optionals;
|
|
182
|
+
return new Router()
|
|
183
|
+
.withSearchParams({ maxRecords, horizon })
|
|
184
|
+
.get(`/chat/message/${chatKey}`, routingOptions)
|
|
185
|
+
.then(({ body }) => body);
|
|
186
|
+
}
|