@4players/odin-common 1.1.2 → 1.2.1

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.1
4
+
5
+ - Updated npm ignore patterns
6
+
7
+ ## 1.2.0
8
+
9
+ - Added both typed and untyped rpc connection class
10
+
3
11
  ## 1.1.2
4
12
 
5
13
  - Added missing export
@@ -1,6 +1,16 @@
1
1
  "use strict";
2
+ /* eslint-disable no-dupe-class-members */
3
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5
+ return new (P || (P = Promise))(function (resolve, reject) {
6
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
7
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
8
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
10
+ });
11
+ };
2
12
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isKnownRpcMessage = exports.parseRpcMessage = exports.parseRpc = exports.msgpackDecode = exports.msgpackEncode = void 0;
13
+ exports.isKnownRpcMessage = exports.parseRpcMessage = exports.parseRpc = exports.TypedRpcConnection = exports.RpcConnection = exports.msgpackDecode = exports.msgpackEncode = void 0;
4
14
  const msgpack = require("@msgpack/msgpack");
5
15
  const serialization_1 = require("../schema/serialization");
6
16
  const result_1 = require("./result");
@@ -12,6 +22,181 @@ function msgpackDecode(buffer) {
12
22
  return msgpack.decode(buffer);
13
23
  }
14
24
  exports.msgpackDecode = msgpackDecode;
25
+ class RpcConnection {
26
+ get closed() {
27
+ return this._Close.signal.aborted;
28
+ }
29
+ get closeSignal() {
30
+ return this._Close.signal;
31
+ }
32
+ constructor(webSocket) {
33
+ this.webSocket = webSocket;
34
+ this._SendQueue = [];
35
+ this._RecvQueue = [];
36
+ this._Requests = new Map();
37
+ this._Close = new AbortController();
38
+ this._NextId = 1;
39
+ this.webSocket.addEventListener('open', this._OnWebSocketOpen.bind(this));
40
+ this.webSocket.addEventListener('close', this.close.bind(this));
41
+ this.webSocket.addEventListener('message', this._OnWebSocketMessage.bind(this));
42
+ this.webSocket.binaryType = 'arraybuffer';
43
+ this._Close.signal.addEventListener('abort', this._Cleanup.bind(this), {
44
+ once: true,
45
+ });
46
+ }
47
+ request(name, properties, timeout) {
48
+ return __awaiter(this, void 0, void 0, function* () {
49
+ if (this.closed) {
50
+ return Promise.resolve('closed');
51
+ }
52
+ const id = this._NextId;
53
+ const timeoutId = timeout === undefined
54
+ ? undefined
55
+ : setTimeout(() => this._TimeoutRequest(id), timeout);
56
+ this._Send([0, id, name, properties]);
57
+ return new Promise((resolve) => {
58
+ this._Requests.set(id, { resolve, timeoutId });
59
+ this._NextId += 1;
60
+ });
61
+ });
62
+ }
63
+ notify(name, properties) {
64
+ if (this.closed === false) {
65
+ this._Send([2, name, properties]);
66
+ }
67
+ }
68
+ recv() {
69
+ if (this.closed === false) {
70
+ return new Promise((resolve) => this._RecvQueue.push(resolve));
71
+ }
72
+ else {
73
+ return Promise.resolve('closed');
74
+ }
75
+ }
76
+ close() {
77
+ this._Close.abort();
78
+ }
79
+ _TimeoutRequest(requestId) {
80
+ const request = this._Requests.get(requestId);
81
+ if (request === undefined)
82
+ return;
83
+ this._Requests.delete(requestId);
84
+ clearTimeout(request.timeoutId);
85
+ request.resolve('timeout');
86
+ }
87
+ _Send(message) {
88
+ this.webSocket.send(msgpack.encode(message));
89
+ }
90
+ _OnWebSocketOpen(ev) {
91
+ for (const data in this._SendQueue) {
92
+ this.webSocket.send(data);
93
+ }
94
+ this._SendQueue.length = 0;
95
+ }
96
+ _OnWebSocketMessage(ev) {
97
+ const rpc = parseRpc(ev.data);
98
+ if (rpc.type !== 'Success') {
99
+ this.webSocket.close();
100
+ return;
101
+ }
102
+ switch (rpc.value[0]) {
103
+ case 0:
104
+ this._Send([1, rpc.value[1], 'not supported', undefined]);
105
+ break;
106
+ case 1:
107
+ this._OnResponse(rpc.value[1], rpc.value[2], rpc.value[3]);
108
+ break;
109
+ case 2:
110
+ this._OnEvent(rpc.value[1], rpc.value[2]);
111
+ break;
112
+ }
113
+ }
114
+ _OnResponse(id, error, result) {
115
+ const request = this._Requests.get(id);
116
+ if (request === undefined)
117
+ return;
118
+ clearTimeout(request.timeoutId);
119
+ request.resolve(error === null ? (0, result_1.success)(result) : (0, result_1.failure)(error));
120
+ }
121
+ _OnEvent(name, properties) {
122
+ const receiver = this._RecvQueue.shift();
123
+ if (receiver !== undefined) {
124
+ receiver({ name, properties });
125
+ }
126
+ }
127
+ _Cleanup() {
128
+ for (const request of this._Requests.values()) {
129
+ clearTimeout(request.timeoutId);
130
+ request.resolve('closed');
131
+ }
132
+ this._Requests.clear();
133
+ for (const accept of this._RecvQueue) {
134
+ accept('closed');
135
+ }
136
+ this._RecvQueue.length = 0;
137
+ this._SendQueue.length = 0;
138
+ this.webSocket.close();
139
+ }
140
+ }
141
+ exports.RpcConnection = RpcConnection;
142
+ class TypedRpcConnection extends RpcConnection {
143
+ constructor(webSocket, _Commands, _Events) {
144
+ super(webSocket);
145
+ this._Commands = _Commands;
146
+ this._Events = _Events;
147
+ }
148
+ request(name, properties, timeout) {
149
+ const _super = Object.create(null, {
150
+ request: { get: () => super.request }
151
+ });
152
+ return __awaiter(this, void 0, void 0, function* () {
153
+ properties = this._Commands[name].request.parse(properties);
154
+ const response = yield (timeout !== undefined
155
+ ? _super.request.call(this, name, properties, timeout)
156
+ : _super.request.call(this, name, properties));
157
+ if (response === 'closed' ||
158
+ response === 'timeout' ||
159
+ response.type === 'Failure') {
160
+ return response;
161
+ }
162
+ const parsed = this._Commands[name].response.safeParse(response.value);
163
+ if (parsed.success) {
164
+ response.value = parsed.data;
165
+ return response;
166
+ }
167
+ else {
168
+ return (0, result_1.failure)(`response schema failed validation: ${parsed.error}`);
169
+ }
170
+ });
171
+ }
172
+ notify(name, properties) {
173
+ properties = this._Commands[name].request.parse(properties);
174
+ super.notify(name, properties);
175
+ }
176
+ recv() {
177
+ const _super = Object.create(null, {
178
+ recv: { get: () => super.recv }
179
+ });
180
+ return __awaiter(this, void 0, void 0, function* () {
181
+ const event = yield _super.recv.call(this);
182
+ if (event === 'closed')
183
+ return event;
184
+ const parsed = parseRpcMessage(this._Events, event);
185
+ if (parsed !== undefined) {
186
+ return parsed;
187
+ }
188
+ if (!isKnownRpcMessage(this._Events, event.name)) {
189
+ console.warn(`dropping unknown event ${event.name}`);
190
+ }
191
+ else {
192
+ console.error(`schema validation for ${event.name} failed; closing connection`);
193
+ this.close();
194
+ }
195
+ return 'closed';
196
+ });
197
+ }
198
+ }
199
+ exports.TypedRpcConnection = TypedRpcConnection;
15
200
  function parseRpc(buffer) {
16
201
  try {
17
202
  return (0, result_1.success)(serialization_1.MessagePackRpcSchema.parse(msgpackDecode(buffer)));
@@ -11,14 +11,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.Selector = void 0;
13
13
  class Selector {
14
- constructor(generators) {
15
- this.generators = generators;
16
- this.futures = generators.map(Selector.addIndex);
14
+ constructor(_Generators) {
15
+ this._Generators = _Generators;
16
+ this._Futures = _Generators.map(Selector.addIndex);
17
17
  }
18
18
  next() {
19
19
  return __awaiter(this, void 0, void 0, function* () {
20
- const [result, index] = yield Promise.race(this.futures);
21
- this.futures[index] = Selector.addIndex(this.generators[index], index);
20
+ const [result, index] = yield Promise.race(this._Futures);
21
+ this._Futures[index] = Selector.addIndex(this._Generators[index], index);
22
22
  return result;
23
23
  });
24
24
  }
@@ -12,8 +12,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.Strand = void 0;
13
13
  class Strand {
14
14
  constructor() {
15
- this.tasks = [];
16
- this.running = false;
15
+ this._Tasks = [];
16
+ this._Running = false;
17
17
  }
18
18
  enqueue(task) {
19
19
  return new Promise((resolve, reject) => {
@@ -26,22 +26,22 @@ class Strand {
26
26
  reject(error);
27
27
  }
28
28
  });
29
- this.tasks.push(wrapped);
30
- if (!this.running) {
29
+ this._Tasks.push(wrapped);
30
+ if (!this._Running) {
31
31
  this.execute();
32
32
  }
33
33
  });
34
34
  }
35
35
  execute() {
36
36
  return __awaiter(this, void 0, void 0, function* () {
37
- this.running = true;
37
+ this._Running = true;
38
38
  while (true) {
39
- const task = this.tasks.shift();
39
+ const task = this._Tasks.shift();
40
40
  if (task === undefined)
41
41
  break;
42
42
  yield task();
43
43
  }
44
- this.running = false;
44
+ this._Running = false;
45
45
  });
46
46
  }
47
47
  }
@@ -1,3 +1,13 @@
1
+ /* eslint-disable no-dupe-class-members */
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
1
11
  import * as msgpack from '@msgpack/msgpack';
2
12
  import { MessagePackRpcSchema } from '../schema/serialization';
3
13
  import { failure, success } from './result';
@@ -7,6 +17,179 @@ export function msgpackEncode(value) {
7
17
  export function msgpackDecode(buffer) {
8
18
  return msgpack.decode(buffer);
9
19
  }
20
+ export class RpcConnection {
21
+ get closed() {
22
+ return this._Close.signal.aborted;
23
+ }
24
+ get closeSignal() {
25
+ return this._Close.signal;
26
+ }
27
+ constructor(webSocket) {
28
+ this.webSocket = webSocket;
29
+ this._SendQueue = [];
30
+ this._RecvQueue = [];
31
+ this._Requests = new Map();
32
+ this._Close = new AbortController();
33
+ this._NextId = 1;
34
+ this.webSocket.addEventListener('open', this._OnWebSocketOpen.bind(this));
35
+ this.webSocket.addEventListener('close', this.close.bind(this));
36
+ this.webSocket.addEventListener('message', this._OnWebSocketMessage.bind(this));
37
+ this.webSocket.binaryType = 'arraybuffer';
38
+ this._Close.signal.addEventListener('abort', this._Cleanup.bind(this), {
39
+ once: true,
40
+ });
41
+ }
42
+ request(name, properties, timeout) {
43
+ return __awaiter(this, void 0, void 0, function* () {
44
+ if (this.closed) {
45
+ return Promise.resolve('closed');
46
+ }
47
+ const id = this._NextId;
48
+ const timeoutId = timeout === undefined
49
+ ? undefined
50
+ : setTimeout(() => this._TimeoutRequest(id), timeout);
51
+ this._Send([0, id, name, properties]);
52
+ return new Promise((resolve) => {
53
+ this._Requests.set(id, { resolve, timeoutId });
54
+ this._NextId += 1;
55
+ });
56
+ });
57
+ }
58
+ notify(name, properties) {
59
+ if (this.closed === false) {
60
+ this._Send([2, name, properties]);
61
+ }
62
+ }
63
+ recv() {
64
+ if (this.closed === false) {
65
+ return new Promise((resolve) => this._RecvQueue.push(resolve));
66
+ }
67
+ else {
68
+ return Promise.resolve('closed');
69
+ }
70
+ }
71
+ close() {
72
+ this._Close.abort();
73
+ }
74
+ _TimeoutRequest(requestId) {
75
+ const request = this._Requests.get(requestId);
76
+ if (request === undefined)
77
+ return;
78
+ this._Requests.delete(requestId);
79
+ clearTimeout(request.timeoutId);
80
+ request.resolve('timeout');
81
+ }
82
+ _Send(message) {
83
+ this.webSocket.send(msgpack.encode(message));
84
+ }
85
+ _OnWebSocketOpen(ev) {
86
+ for (const data in this._SendQueue) {
87
+ this.webSocket.send(data);
88
+ }
89
+ this._SendQueue.length = 0;
90
+ }
91
+ _OnWebSocketMessage(ev) {
92
+ const rpc = parseRpc(ev.data);
93
+ if (rpc.type !== 'Success') {
94
+ this.webSocket.close();
95
+ return;
96
+ }
97
+ switch (rpc.value[0]) {
98
+ case 0:
99
+ this._Send([1, rpc.value[1], 'not supported', undefined]);
100
+ break;
101
+ case 1:
102
+ this._OnResponse(rpc.value[1], rpc.value[2], rpc.value[3]);
103
+ break;
104
+ case 2:
105
+ this._OnEvent(rpc.value[1], rpc.value[2]);
106
+ break;
107
+ }
108
+ }
109
+ _OnResponse(id, error, result) {
110
+ const request = this._Requests.get(id);
111
+ if (request === undefined)
112
+ return;
113
+ clearTimeout(request.timeoutId);
114
+ request.resolve(error === null ? success(result) : failure(error));
115
+ }
116
+ _OnEvent(name, properties) {
117
+ const receiver = this._RecvQueue.shift();
118
+ if (receiver !== undefined) {
119
+ receiver({ name, properties });
120
+ }
121
+ }
122
+ _Cleanup() {
123
+ for (const request of this._Requests.values()) {
124
+ clearTimeout(request.timeoutId);
125
+ request.resolve('closed');
126
+ }
127
+ this._Requests.clear();
128
+ for (const accept of this._RecvQueue) {
129
+ accept('closed');
130
+ }
131
+ this._RecvQueue.length = 0;
132
+ this._SendQueue.length = 0;
133
+ this.webSocket.close();
134
+ }
135
+ }
136
+ export class TypedRpcConnection extends RpcConnection {
137
+ constructor(webSocket, _Commands, _Events) {
138
+ super(webSocket);
139
+ this._Commands = _Commands;
140
+ this._Events = _Events;
141
+ }
142
+ request(name, properties, timeout) {
143
+ const _super = Object.create(null, {
144
+ request: { get: () => super.request }
145
+ });
146
+ return __awaiter(this, void 0, void 0, function* () {
147
+ properties = this._Commands[name].request.parse(properties);
148
+ const response = yield (timeout !== undefined
149
+ ? _super.request.call(this, name, properties, timeout)
150
+ : _super.request.call(this, name, properties));
151
+ if (response === 'closed' ||
152
+ response === 'timeout' ||
153
+ response.type === 'Failure') {
154
+ return response;
155
+ }
156
+ const parsed = this._Commands[name].response.safeParse(response.value);
157
+ if (parsed.success) {
158
+ response.value = parsed.data;
159
+ return response;
160
+ }
161
+ else {
162
+ return failure(`response schema failed validation: ${parsed.error}`);
163
+ }
164
+ });
165
+ }
166
+ notify(name, properties) {
167
+ properties = this._Commands[name].request.parse(properties);
168
+ super.notify(name, properties);
169
+ }
170
+ recv() {
171
+ const _super = Object.create(null, {
172
+ recv: { get: () => super.recv }
173
+ });
174
+ return __awaiter(this, void 0, void 0, function* () {
175
+ const event = yield _super.recv.call(this);
176
+ if (event === 'closed')
177
+ return event;
178
+ const parsed = parseRpcMessage(this._Events, event);
179
+ if (parsed !== undefined) {
180
+ return parsed;
181
+ }
182
+ if (!isKnownRpcMessage(this._Events, event.name)) {
183
+ console.warn(`dropping unknown event ${event.name}`);
184
+ }
185
+ else {
186
+ console.error(`schema validation for ${event.name} failed; closing connection`);
187
+ this.close();
188
+ }
189
+ return 'closed';
190
+ });
191
+ }
192
+ }
10
193
  export function parseRpc(buffer) {
11
194
  try {
12
195
  return success(MessagePackRpcSchema.parse(msgpackDecode(buffer)));
@@ -8,14 +8,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  export class Selector {
11
- constructor(generators) {
12
- this.generators = generators;
13
- this.futures = generators.map(Selector.addIndex);
11
+ constructor(_Generators) {
12
+ this._Generators = _Generators;
13
+ this._Futures = _Generators.map(Selector.addIndex);
14
14
  }
15
15
  next() {
16
16
  return __awaiter(this, void 0, void 0, function* () {
17
- const [result, index] = yield Promise.race(this.futures);
18
- this.futures[index] = Selector.addIndex(this.generators[index], index);
17
+ const [result, index] = yield Promise.race(this._Futures);
18
+ this._Futures[index] = Selector.addIndex(this._Generators[index], index);
19
19
  return result;
20
20
  });
21
21
  }
@@ -9,8 +9,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  export class Strand {
11
11
  constructor() {
12
- this.tasks = [];
13
- this.running = false;
12
+ this._Tasks = [];
13
+ this._Running = false;
14
14
  }
15
15
  enqueue(task) {
16
16
  return new Promise((resolve, reject) => {
@@ -23,22 +23,22 @@ export class Strand {
23
23
  reject(error);
24
24
  }
25
25
  });
26
- this.tasks.push(wrapped);
27
- if (!this.running) {
26
+ this._Tasks.push(wrapped);
27
+ if (!this._Running) {
28
28
  this.execute();
29
29
  }
30
30
  });
31
31
  }
32
32
  execute() {
33
33
  return __awaiter(this, void 0, void 0, function* () {
34
- this.running = true;
34
+ this._Running = true;
35
35
  while (true) {
36
- const task = this.tasks.shift();
36
+ const task = this._Tasks.shift();
37
37
  if (task === undefined)
38
38
  break;
39
39
  yield task();
40
40
  }
41
- this.running = false;
41
+ this._Running = false;
42
42
  });
43
43
  }
44
44
  }