@covenant-rpc/server 0.3.0 → 0.4.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.
Files changed (61) hide show
  1. package/dist/adapters/vanilla.d.ts +3 -0
  2. package/dist/adapters/vanilla.d.ts.map +1 -0
  3. package/dist/adapters/vanilla.js +7 -0
  4. package/dist/adapters/vanilla.js.map +1 -0
  5. package/{index.ts → dist/index.d.ts} +1 -2
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +8 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/interfaces/direct.d.ts +4 -0
  10. package/dist/interfaces/direct.d.ts.map +1 -0
  11. package/dist/interfaces/direct.js +98 -0
  12. package/dist/interfaces/direct.js.map +1 -0
  13. package/dist/interfaces/empty.d.ts +3 -0
  14. package/dist/interfaces/empty.d.ts.map +1 -0
  15. package/dist/interfaces/empty.js +8 -0
  16. package/dist/interfaces/empty.js.map +1 -0
  17. package/dist/interfaces/http.d.ts +4 -0
  18. package/dist/interfaces/http.d.ts.map +1 -0
  19. package/dist/interfaces/http.js +92 -0
  20. package/dist/interfaces/http.js.map +1 -0
  21. package/dist/interfaces/mock.d.ts +4 -0
  22. package/dist/interfaces/mock.d.ts.map +1 -0
  23. package/dist/interfaces/mock.js +28 -0
  24. package/dist/interfaces/mock.js.map +1 -0
  25. package/dist/logger.d.ts +16 -0
  26. package/dist/logger.d.ts.map +1 -0
  27. package/dist/logger.js +60 -0
  28. package/dist/logger.js.map +1 -0
  29. package/dist/server.d.ts +46 -0
  30. package/dist/server.d.ts.map +1 -0
  31. package/dist/server.js +342 -0
  32. package/dist/server.js.map +1 -0
  33. package/dist/sidekick/handlers.d.ts +15 -0
  34. package/dist/sidekick/handlers.d.ts.map +1 -0
  35. package/dist/sidekick/handlers.js +143 -0
  36. package/dist/sidekick/handlers.js.map +1 -0
  37. package/dist/sidekick/index.d.ts +33 -0
  38. package/dist/sidekick/index.d.ts.map +1 -0
  39. package/dist/sidekick/index.js +72 -0
  40. package/dist/sidekick/index.js.map +1 -0
  41. package/dist/sidekick/socket.d.ts +4 -0
  42. package/dist/sidekick/socket.d.ts.map +1 -0
  43. package/dist/sidekick/socket.js +5 -0
  44. package/dist/sidekick/socket.js.map +1 -0
  45. package/package.json +13 -6
  46. package/adapters/vanilla.ts +0 -9
  47. package/interfaces/direct.ts +0 -116
  48. package/interfaces/empty.ts +0 -9
  49. package/interfaces/http.ts +0 -111
  50. package/interfaces/mock.ts +0 -33
  51. package/logger.ts +0 -79
  52. package/server.ts +0 -454
  53. package/sidekick/handlers.ts +0 -173
  54. package/sidekick/index.ts +0 -109
  55. package/sidekick/socket.ts +0 -5
  56. package/tests/channel-http.test.ts +0 -483
  57. package/tests/channel.test.ts +0 -689
  58. package/tests/procedure.test.ts +0 -238
  59. package/tests/sidekick.test.ts +0 -23
  60. package/tests/validation-types.test.ts +0 -122
  61. package/tests/validation.test.ts +0 -144
package/server.ts DELETED
@@ -1,454 +0,0 @@
1
- import type { ChannelMap, Covenant, ProcedureMap } from "@covenant-rpc/core";
2
- import { procedureRequestBodySchema, type ProcedureDefinition, type ProcedureInputs, type ProcedureRequest, type ProcedureResponse } from "@covenant-rpc/core/procedure";
3
- import type { StandardSchemaV1 } from "@standard-schema/spec";
4
- import { err, issuesToString, ok, type ArrayToMap, type AsyncResult, type MaybePromise } from "@covenant-rpc/core/utils";
5
- import type { ServerToSidekickConnection } from "@covenant-rpc/core/interfaces";
6
- import { channelConnectionRequestSchema, serverMessageWithContext, type ChannelConnectionResponse, type ChannelDefinition } from "@covenant-rpc/core/channel";
7
- import { v } from "@covenant-rpc/core/validation";
8
- import { procedureErrorFromUnknown, ThrowableProcedureError, ThrowableChannelError, channelErrorFromUnknown } from "@covenant-rpc/core/errors";
9
- import { Logger } from "./logger";
10
- import ION from "@covenant-rpc/ion";
11
- import type { LoggerLevel } from "@covenant-rpc/core/logger";
12
-
13
-
14
- export type ProcedureDefinitionMap<T extends ProcedureMap, Context, Derivation> = {
15
- [key in keyof T]: ProcedureDefinition<T[key], Context, Derivation> | undefined
16
- }
17
-
18
- export type ChannelDefinitionMap<T extends ChannelMap> = {
19
- [key in keyof T]: ChannelDefinition<T[key]>
20
- }
21
-
22
- export type ContextGenerator<Context> =
23
- (i: ProcedureInputs<unknown, undefined, undefined>) => MaybePromise<Context>
24
-
25
- export type Derivation<Context, Derived> = (i: ProcedureInputs<undefined, Context, undefined>) => MaybePromise<Derived>;
26
-
27
-
28
- export class CovenantServer<
29
- P extends ProcedureMap,
30
- C extends ChannelMap,
31
- Context,
32
- Derived,
33
- > {
34
- private covenant: Covenant<P, C>;
35
- private contextGenerator: ContextGenerator<Context>;
36
- private derivation: Derivation<Context, Derived>;
37
- private sidekickConnection: ServerToSidekickConnection
38
-
39
- private procedureDefinitions: ProcedureDefinitionMap<P, Context, Derived>;
40
- private channelDefinitions: ChannelDefinitionMap<C>;
41
- private logger: Logger;
42
-
43
- constructor(covenant: Covenant<P, C>, {
44
- contextGenerator,
45
- derivation,
46
- sidekickConnection,
47
- logLevel,
48
- }: {
49
- contextGenerator: ContextGenerator<Context>,
50
- derivation: Derivation<Context, Derived>,
51
- sidekickConnection: ServerToSidekickConnection,
52
- logLevel?: LoggerLevel,
53
- }) {
54
- this.covenant = covenant;
55
- this.contextGenerator = contextGenerator;
56
- this.derivation = derivation;
57
- this.sidekickConnection = sidekickConnection;
58
- this.logger = new Logger(logLevel ?? "info", [
59
- () => new Date().toUTCString(),
60
- ]);
61
-
62
-
63
- // both of these fail. We leave them emtpy and let the user
64
- // define them later. The `assertAllDefined can be used to do`
65
- // a check to ensure all channels and procedures are defined
66
- //
67
- //@ts-expect-error see above
68
- this.procedureDefinitions = {};
69
- //@ts-expect-error see above
70
- this.channelDefinitions = {};
71
- }
72
-
73
- defineProcedure<N extends keyof P>(name: N, definition: ProcedureDefinition<P[N], Context, Derived>) {
74
- if (this.procedureDefinitions[name] !== undefined) {
75
- throw new Error(`Tried to define ${String(name)} twice!`);
76
- }
77
-
78
- this.procedureDefinitions[name] = definition;
79
- }
80
-
81
- defineChannel<N extends keyof C>(name: N, definition: ChannelDefinition<C[N]>) {
82
- if (this.channelDefinitions[name] !== undefined) {
83
- throw new Error(`Tried to define ${String(name)} twice!`);
84
- }
85
-
86
- this.channelDefinitions[name] = definition;
87
- }
88
-
89
-
90
- async sendMessage<N extends keyof C>(
91
- name: N,
92
- params: ArrayToMap<C[N]["params"]>,
93
- message: StandardSchemaV1.InferOutput<C[N]["serverMessage"]>
94
- ): Promise<Error | null> {
95
- this.logger.info(`Sending message to ${String(name)} with params ${JSON.stringify(params)}`);
96
- return await this.sidekickConnection.postMessage({
97
- channel: String(name),
98
- params,
99
- data: message,
100
- });
101
- }
102
-
103
- async postChannelMessage<N extends keyof C>(
104
- name: N,
105
- params: ArrayToMap<C[N]["params"]>,
106
- message: StandardSchemaV1.InferOutput<C[N]["serverMessage"]>
107
- ): Promise<Error | null> {
108
- return await this.sendMessage(name, params, message);
109
- }
110
-
111
- async processChannelMessage(channelName: string, params: Record<string, string>, data: any, context: any): Promise<{ fault: "client" | "server"; message: string } | null> {
112
- let l = this.logger.sublogger(`CHANNEL ${channelName}`);
113
- try {
114
- const declaration = this.covenant.channels[channelName];
115
- const definition = this.channelDefinitions[channelName];
116
-
117
- if (!declaration || !definition) {
118
- return {
119
- fault: "server",
120
- message: `Channel ${channelName} not found`,
121
- };
122
- }
123
-
124
- // Validate client message
125
- const validation = await declaration.clientMessage["~standard"].validate(data);
126
- if (validation.issues) {
127
- return {
128
- fault: "client",
129
- message: `Invalid message data: ${issuesToString(validation.issues)}`,
130
- };
131
- }
132
-
133
- // Call onMessage handler
134
- try {
135
- await definition.onMessage({
136
- inputs: validation.value,
137
- params: params as any,
138
- context,
139
- error(reason: string, cause: "client" | "server"): never {
140
- throw new ThrowableChannelError(reason, channelName, params, cause);
141
- },
142
- });
143
-
144
- l.info(`Processed message successfully`);
145
- return null;
146
- } catch (e) {
147
- const error = channelErrorFromUnknown(e, channelName, params);
148
- l.error(`Message processing failed: ${error.message}`);
149
- return {
150
- fault: error.fault === "sidekick" ? "server" : error.fault,
151
- message: error.message,
152
- };
153
- }
154
- } catch (e) {
155
- const error = e instanceof Error ? e.message : String(e);
156
- l.error(`Unexpected error: ${error}`);
157
- return {
158
- fault: "server",
159
- message: error,
160
- };
161
- }
162
- }
163
-
164
- assertAllDefined(): void {
165
- for (const p of Object.keys(this.covenant.procedures)) {
166
- if (this.procedureDefinitions[p] === undefined) {
167
- this.logger.fatal(`Procedure ${p} was not defined`);
168
- }
169
- }
170
-
171
- for (const c of Object.keys(this.covenant.channels)) {
172
- if (this.channelDefinitions[c] === undefined) {
173
- this.logger.fatal(`Channel ${c} was not defined`);
174
- }
175
- }
176
- }
177
-
178
- private async processProcedure(request: ProcedureRequest, newHeaders: Headers): Promise<ProcedureResponse> {
179
- let l = this.logger.sublogger(`PROCEDURE ${request.procedure}`);
180
- try {
181
- const declaration = this.covenant.procedures[request.procedure];
182
- const definition = this.procedureDefinitions[request.procedure];
183
-
184
- if (!declaration || !definition) {
185
- throw new ThrowableProcedureError(`Procedure ${request.procedure} not found`, 404);
186
- }
187
-
188
- const validationResult = await declaration.input["~standard"].validate(request.input);
189
-
190
- if (validationResult.issues) {
191
- throw new ThrowableProcedureError(`Error parsing procedure inputs: ${issuesToString(validationResult.issues)}`, 404);
192
- }
193
-
194
- const initialInputs: ProcedureInputs<any, undefined, undefined> = {
195
- inputs: validationResult.value,
196
- request,
197
- ctx: undefined,
198
- derived: undefined,
199
- logger: l,
200
- setHeader(name: string, value: string) {
201
- newHeaders.set(name, value);
202
- },
203
- deleteHeader(name: string) {
204
- newHeaders.delete(name);
205
- },
206
- error(message, code) {
207
- throw new ThrowableProcedureError(message, code);
208
- }
209
- }
210
-
211
- const ctx: Context = await this.contextGenerator(initialInputs);
212
- const derived: Derived = await this.derivation({ ...initialInputs, ctx });
213
- const result = await definition.procedure({ ...initialInputs, ctx, derived });
214
- const resources = await definition.resources({ inputs: validationResult.value, ctx, outputs: result, logger: l });
215
-
216
- if (declaration.type === "mutation") {
217
- this.sidekickConnection.update(resources).then((e) => {
218
- if (e !== null) {
219
- l.error(`Failed to send resource updates for ${resources.toString()} - ${e.message}`);
220
- }
221
- });
222
- }
223
-
224
- l.info("Returning OK")
225
- return {
226
- status: "OK",
227
- data: result,
228
- resources,
229
- }
230
-
231
- } catch (e) {
232
- const error = procedureErrorFromUnknown(e);
233
- l.error(`Returning ERR ${error.code} - ${error.message}`);
234
- return {
235
- status: "ERR",
236
- error,
237
- }
238
- }
239
- }
240
-
241
- private async handleProcedure(request: Request): Promise<Response> {
242
- const { data: parsed, error, success } = await parseRequest(request);
243
-
244
- if (!success) {
245
- this.logger.error(`Failed parsing procedure request: ${error.message}`);
246
- return new Response(`Error parsing request body. If you're a dev seeing this then this is probably my bad not yours. Create an issue on the covenant rpc github: ${error.message}`);
247
- }
248
-
249
- const headers = new Headers();
250
- headers.set("Content-Type", "application/json");
251
-
252
- const res = await this.processProcedure(parsed, headers);
253
-
254
- const status = res.status === "OK" ? 201 : res.error.code;
255
-
256
- return new Response(ION.stringify(res), {
257
- headers,
258
- status,
259
- });
260
- }
261
-
262
- private async handleChannelMessage(request: Request): Promise<Response> {
263
- let l = this.logger.sublogger(`CHANNEL_MESSAGE`);
264
- try {
265
- const bodyText = await request.text();
266
- const body = ION.parse(bodyText);
267
- const validation = v.parseSafe(body, serverMessageWithContext);
268
-
269
- if (validation === null) {
270
- throw new Error(`Invalid channel message: ${JSON.stringify(body)}`);
271
- }
272
-
273
- const { channel, params, data, context } = validation;
274
-
275
- const result = await this.processChannelMessage(channel, params, data, context);
276
-
277
- if (result !== null) {
278
- l.error(`Channel message processing failed: ${result.fault} - ${result.message}`);
279
- return new Response(ION.stringify(result), {
280
- status: 400,
281
- headers: { "Content-Type": "application/json" },
282
- });
283
- }
284
-
285
- return new Response(null, {
286
- status: 204,
287
- });
288
- } catch (e) {
289
- const error = e instanceof Error ? e.message : String(e);
290
- l.error(`Channel message failed: ${error}`);
291
- return new Response(ION.stringify({ error }), {
292
- status: 500,
293
- headers: { "Content-Type": "application/json" },
294
- });
295
- }
296
- }
297
-
298
- private async handleConnectionRequest(request: Request): Promise<Response> {
299
- let l = this.logger.sublogger(`CONNECTION`);
300
-
301
- let channelName = "unknown";
302
- let params: Record<string, string> = {};
303
-
304
- try {
305
- const bodyText = await request.text();
306
- const body = ION.parse(bodyText);
307
- const validation = v.parseSafe(body, channelConnectionRequestSchema);
308
-
309
- if (validation === null) {
310
- throw new Error(`Invalid connection request: ${JSON.stringify(body)}`);
311
- }
312
-
313
- channelName = validation.channel;
314
- params = validation.params;
315
- const data = validation.data;
316
-
317
- const declaration = this.covenant.channels[channelName];
318
- const definition = this.channelDefinitions[channelName];
319
-
320
- if (!declaration || !definition) {
321
- throw new ThrowableChannelError(
322
- `Channel ${channelName} not found`,
323
- channelName,
324
- params,
325
- "server"
326
- );
327
- }
328
-
329
- // Validate connection request data
330
- const connectionRequestValidation = await declaration.connectionRequest["~standard"].validate(data);
331
- if (connectionRequestValidation.issues) {
332
- throw new ThrowableChannelError(
333
- `Invalid connection request data: ${issuesToString(connectionRequestValidation.issues)}`,
334
- channelName,
335
- params,
336
- "client"
337
- );
338
- }
339
-
340
- // Call onConnect handler
341
- const context = await definition.onConnect({
342
- inputs: connectionRequestValidation.value,
343
- params: params as any,
344
- reject(reason: string, cause: "client" | "server"): never {
345
- throw new ThrowableChannelError(reason, channelName, params, cause);
346
- },
347
- });
348
-
349
- // Generate token
350
- const token = globalThis.crypto.randomUUID();
351
-
352
- // Add connection to sidekick
353
- await this.sidekickConnection.addConnection({
354
- token,
355
- channel: channelName,
356
- params,
357
- context,
358
- });
359
-
360
- l.info(`Connection established for ${channelName} with token ${token}`);
361
-
362
- const response: ChannelConnectionResponse = {
363
- channel: channelName,
364
- params,
365
- result: {
366
- type: "OK",
367
- token,
368
- },
369
- };
370
-
371
- return new Response(ION.stringify(response), {
372
- status: 200,
373
- headers: { "Content-Type": "application/json" },
374
- });
375
- } catch (e) {
376
- if (e instanceof ThrowableChannelError) {
377
- const error = e.toChannelError();
378
- l.error(`Connection rejected: ${error.message}`);
379
-
380
- const response: ChannelConnectionResponse = {
381
- channel: channelName,
382
- params,
383
- result: {
384
- type: "ERROR",
385
- error,
386
- },
387
- };
388
-
389
- return new Response(ION.stringify(response), {
390
- status: 400,
391
- headers: { "Content-Type": "application/json" },
392
- });
393
- }
394
-
395
- const error = e instanceof Error ? e.message : String(e);
396
- l.error(`Connection failed: ${error}`);
397
- return new Response(ION.stringify({ error }), {
398
- status: 500,
399
- headers: { "Content-Type": "application/json" },
400
- });
401
- }
402
- }
403
-
404
- async handle(request: Request): Promise<Response> {
405
- const url = new URL(request.url);
406
- const type = url.searchParams.get("type") ?? "procedure";
407
-
408
- if (request.method !== "POST") {
409
- return new Response("Covenant servers only handle POST requests", { status: 404 });
410
- }
411
-
412
- let response = new Response();
413
-
414
- switch (type) {
415
- case "channel":
416
- response = await this.handleChannelMessage(request);
417
- break;
418
- case "procedure":
419
- response = await this.handleProcedure(request);
420
- break;
421
- case "connect":
422
- response = await this.handleConnectionRequest(request);
423
- break;
424
- }
425
-
426
- return response;
427
- }
428
- }
429
-
430
-
431
- export async function parseRequest(request: Request): AsyncResult<ProcedureRequest> {
432
- try {
433
- const bodyText = await request.text();
434
- const body = ION.parse(bodyText);
435
- const result = v.parseSafe(body, procedureRequestBodySchema);
436
-
437
- if (result === null) {
438
- throw new Error(`Failed to parse body as a ProcedureRequestBody: ${JSON.stringify(body)}`);
439
- }
440
- const url = new URL(request.url);
441
-
442
- return ok({
443
- headers: request.headers,
444
- input: result.inputs,
445
- procedure: result.procedure,
446
- path: url.pathname,
447
- url: url.toString(),
448
- req: request
449
- });
450
- } catch (e) {
451
- return err(e instanceof Error ? e : new Error(`Unknown error: ${e}`));
452
- }
453
-
454
- }
@@ -1,173 +0,0 @@
1
- import type { PublishFunction, SidekickClient, SidekickState } from ".";
2
- import type { Logger } from "@covenant-rpc/core/logger";
3
- import type { ListenMessage, SendMessage, SubscribeMessage, UnlistenMessage, UnsubscribeMessage } from "@covenant-rpc/core/sidekick/protocol";
4
- import { getChannelTopicName, getMapId, getResourceTopicName } from "@covenant-rpc/core/sidekick/protocol";
5
-
6
-
7
- export interface SidekickHandlerContext {
8
- state: SidekickState;
9
- client: SidekickClient;
10
- publish: PublishFunction;
11
- logger: Logger;
12
- }
13
-
14
- export async function handleListenMessage(message: ListenMessage, { client, logger }: SidekickHandlerContext) {
15
- for (const resource of message.resources) {
16
- client.subscribe(getResourceTopicName(resource));
17
- }
18
-
19
- logger.info(`Listened to ${message.resources.toString()}`);
20
-
21
- client.directMessage({
22
- type: "listening",
23
- resources: message.resources,
24
- });
25
- }
26
-
27
- export async function handleUnlistenMessage(message: UnlistenMessage, { client, logger }: SidekickHandlerContext) {
28
- for (const resource of message.resources) {
29
- client.unsubscribe(getResourceTopicName(resource));
30
- }
31
- logger.info(`Unlistened to ${message.resources.toString()}`);
32
-
33
- client.directMessage({
34
- type: "unlistening",
35
- resources: message.resources,
36
- });
37
- }
38
-
39
- export async function handleSendMessage(message: SendMessage, { client, logger, state }: SidekickHandlerContext) {
40
- // Check if token exists in unused tokens (not yet subscribed)
41
- let payload = state.tokenMap.get(message.token);
42
- let context: unknown = undefined;
43
-
44
- if (payload) {
45
- // Token exists but client hasn't subscribed yet - use the context from the payload
46
- context = payload.context;
47
-
48
- // Verify the channel and params match
49
- if (payload.channel !== message.channel) {
50
- logger.error(`Token is for channel ${payload.channel} but message is for ${message.channel}`);
51
- client.directMessage({
52
- type: "error",
53
- error: {
54
- channel: message.channel,
55
- params: message.params,
56
- fault: "client",
57
- message: "Token channel mismatch",
58
- }
59
- });
60
- return;
61
- }
62
- } else {
63
- // Check if token has been used (client is subscribed)
64
- const usedToken = state.usedTokenMap.get(message.token);
65
- if (!usedToken || usedToken.id !== client.getId()) {
66
- logger.error(`Invalid or unauthorized token for send`);
67
- client.directMessage({
68
- type: "error",
69
- error: {
70
- channel: message.channel,
71
- params: message.params,
72
- fault: "client",
73
- message: "Invalid or unauthorized token",
74
- }
75
- });
76
- return;
77
- }
78
-
79
- // Get context from context map
80
- const topic = getChannelTopicName(usedToken.channel, usedToken.params);
81
- const mapId = getMapId(client.getId(), topic);
82
- context = state.contextMap.get(mapId);
83
- }
84
-
85
- const result = await state.serverConnection.sendMessage({
86
- params: message.params,
87
- channel: message.channel,
88
- data: message.data,
89
- context,
90
- });
91
-
92
- if (result !== null) {
93
- logger.error(`Got bad response sending message: ${result.fault} - ${result.message}`);
94
- client.directMessage({
95
- type: "error",
96
- error: result,
97
- });
98
- return;
99
- }
100
-
101
- logger.info(`Processed message successfully`);
102
- }
103
-
104
- export async function handleSubscribeMessage(message: SubscribeMessage, { client, logger, state }: SidekickHandlerContext) {
105
- const payload = state.tokenMap.get(message.token);
106
- if (!payload) {
107
- logger.error("Failed to subscribe: bad input token");
108
- // TODO - add a time delay here to avoid brute forcing input tokens?
109
- // probably not. This level of abstraction doesn't feel right for that
110
- client.directMessage({
111
- type: "error",
112
- error: {
113
- fault: "client",
114
- message: "Inputted invalid token for subscription",
115
- channel: "???unknown",
116
- params: {},
117
- }
118
- });
119
- return;
120
- }
121
-
122
- const topic = getChannelTopicName(payload.channel, payload.params);
123
- const mapId = getMapId(client.getId(), topic);
124
-
125
- state.tokenMap.delete(message.token);
126
- state.contextMap.set(mapId, payload.context);
127
- client.subscribe(topic);
128
- state.usedTokenMap.set(message.token, {
129
- id: client.getId(),
130
- channel: payload.channel,
131
- params: payload.params,
132
- })
133
-
134
-
135
- logger.info(`Subscribed to ${topic}`);
136
- client.directMessage({
137
- type: "subscribed",
138
- channel: payload.channel,
139
- params: payload.params,
140
- });
141
- }
142
-
143
- export async function handleUnsubscribeMessage(message: UnsubscribeMessage, { client, logger, state }: SidekickHandlerContext) {
144
- const data = state.usedTokenMap.get(message.token);
145
- const id = client.getId();
146
- if (!data || data.id !== id) {
147
- logger.error("Failed to unsubscribe: bad input token");
148
- client.directMessage({
149
- type: "error",
150
- error: {
151
- fault: "client",
152
- message: "Inputted invalid token for unsubscription",
153
- channel: "???unknown",
154
- params: {},
155
- }
156
- })
157
- return;
158
- }
159
- const topic = getChannelTopicName(data.channel, data.params);
160
- const mapId = getMapId(id, topic);
161
-
162
- state.usedTokenMap.delete(message.token);
163
- state.contextMap.delete(mapId);
164
- client.unsubscribe(topic);
165
-
166
- logger.info(`Unsubscribed from ${topic}`);
167
- client.directMessage({
168
- type: "unsubscribed",
169
- channel: data.channel,
170
- params: data.params,
171
- });
172
-
173
- }