@service-broker/webclient 1.0.2 → 2.0.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/README.md CHANGED
@@ -1,15 +1,111 @@
1
- # service-broker-webclient
2
- Browser JavaScript library for communicating with the service broker
1
+ ## @service-broker/webclient
2
+ Browser ESM client library for communicating with a [Service Broker](https://github.com/service-broker/service-broker/wiki/specification)
3
3
 
4
- ## Install
5
- `npm install @service-broker/webclient`
6
4
 
7
- ## Usage
5
+ ### Install
6
+ ```bash
7
+ npm install @service-broker/webclient
8
+ ```
9
+
10
+
11
+ ### Connect
12
+ Connect to the Service Broker at the specified WebSocket URL.
13
+
8
14
  ```javascript
9
15
  import { ServiceBroker } from "@service-broker/webclient"
10
16
 
11
- const sb = new ServiceBroker("wss://sb.mydomain.com", console)
17
+ const sb = new ServiceBroker("wss://sb.mydomain.com")
18
+ ```
19
+
20
+
21
+ ### Request
22
+ Send a service request. The broker will select a qualified provider based on service `name` and requested `capabilities`. The parameter `request` contains the actual message that'll be delivered to the service provider.
23
+
24
+ ```typescript
25
+ interface Message {
26
+ header: {
27
+ from: string // the endpointId of the sender
28
+ to: string // the endpointId of the recipient
29
+ },
30
+ payload: string // the message payload, usually JSON
31
+ }
32
+
33
+ sb.request(
34
+ service: {
35
+ name: string,
36
+ capabilities?: string[]
37
+ },
38
+ request: Message,
39
+ timeout?: number
40
+ ): Promise<Message>
41
+ ```
42
+
43
+
44
+ ### Notify
45
+ A notification is like a request except no response will be sent back.
46
+
47
+ ```typescript
48
+ sb.notify(
49
+ service: {
50
+ name: string,
51
+ capabilities?: string[]
52
+ },
53
+ notification: Message
54
+ ): Promise<void>
55
+ ```
56
+
57
+
58
+ ### RequestTo
59
+ Send a service request directly to an endpoint.
60
+
61
+ ```typescript
62
+ sb.requestTo(
63
+ endpointId: string,
64
+ serviceName: string,
65
+ request: Message,
66
+ timeout?: number
67
+ ): Promise<Message>
68
+ ```
69
+
70
+
71
+ ### NotifyTo
72
+ Send a notification directly to an endpoint.
73
+
74
+ ```typescript
75
+ sb.notifyTo(
76
+ endpointId: string,
77
+ serviceName: string,
78
+ notification: Message
79
+ ): Promise<void>
80
+ ```
81
+
82
+
83
+ ### SetServiceHandler
84
+ The `requestTo` and `notifyTo` methods can be used to send direct messages to an endpoint. For example, a chat service provider may publish a client's endpointId to other clients and allow them to send direct messages to one another.
85
+
86
+ This method sets a handler for incoming requests and notifications.
87
+
88
+ ```typescript
89
+ sb.setServiceHandler(
90
+ serviceName: string,
91
+ handler: (request: Message) => Message|void|Promise<Message|void>
92
+ ): void
93
+ ```
94
+
95
+
96
+ ### Publish/Subscribe
97
+ ```typescript
98
+ sb.publish(
99
+ topic: string,
100
+ text: string
101
+ ): Promise<void>
102
+
103
+ sb.subscribe(
104
+ topic: string,
105
+ handler: (text: string) => void
106
+ ): Promise<void>
12
107
 
13
- sb.request({name: "my-service"}, {payload: "my-request"})
14
- .then(({payload}) => handleResult(payload))
108
+ sb.unsubscribe(
109
+ topic: string
110
+ ): Promise<void>
15
111
  ```
package/dist/index.d.ts CHANGED
@@ -1,34 +1,39 @@
1
-
2
1
  interface ServiceFilter {
3
- name: string
4
- capabilities?: string[]
2
+ name: string;
3
+ capabilities?: string[];
5
4
  }
6
-
7
- export interface ServiceAdvert {
8
- name: string
9
- capabilities?: string[]
10
- priority: number
11
- }
12
-
13
5
  export interface ServiceHandler {
14
- (msg: Message): Partial<Message>|void|Promise<Partial<Message>|void>
6
+ (request: Message): Message | void | Promise<Message | void>;
15
7
  }
16
-
17
8
  interface Message {
18
- header: any
19
- payload: any
9
+ header?: Record<string, unknown>;
10
+ payload?: string;
20
11
  }
21
-
22
12
  export declare class ServiceBroker {
23
- constructor(url: string, logger: {error: Console["error"], debug: Console["debug"]});
24
- request(service: ServiceFilter, req: Partial<Message>): Promise<Message>;
25
- requestTo(endpointId: string, service: ServiceFilter, req: Partial<Message>): Promise<Message>;
26
- advertise(service: ServiceAdvert, handler: ServiceHandler): Promise<void>;
27
- unadvertise(serviceName: string): Promise<void>;
28
- setHandler(serviceName: string, handler: ServiceHandler): void;
29
- publish(topic: string, text: string): Promise<void>;
30
- subscribe(topic: string, handler: (text: string) => void): Promise<void>;
31
- unsubscribe(topic: string): Promise<void>;
32
- isConnected(): boolean;
33
- addConnectListener(listener: () => void): void;
13
+ private readonly url;
14
+ private readonly providers;
15
+ private ws;
16
+ private readonly connectListeners;
17
+ private pendingSend;
18
+ private readonly pendingResponses;
19
+ private pendingIdGen;
20
+ constructor(url: string);
21
+ private connect;
22
+ private onOpen;
23
+ private onClose;
24
+ private onMessage;
25
+ private onServiceResponse;
26
+ private onServiceRequest;
27
+ private send;
28
+ request(service: ServiceFilter, req: Message): Promise<Message>;
29
+ requestTo(endpointId: string | null, service: ServiceFilter, req: Message): Promise<Message>;
30
+ advertise(service: ServiceFilter, handler: ServiceHandler): void;
31
+ unadvertise(serviceName: string): void;
32
+ setServiceHandler(serviceName: string, handler: ServiceHandler): void;
33
+ publish(topic: string, text: string): void;
34
+ subscribe(topic: string, handler: (text: string) => void): void;
35
+ unsubscribe(topic: string): void;
36
+ isConnected(): boolean;
37
+ addConnectListener(listener: Function): void;
34
38
  }
39
+ export {};
package/dist/index.js CHANGED
@@ -1,184 +1,185 @@
1
- exports.ServiceBroker = ServiceBroker;
2
-
3
- function ServiceBroker(url, logger) {
4
- var pending = {};
5
- var pendingIdGen = 0;
6
- var providers = {};
7
- var ws;
8
- var connectListeners = [];
9
- var pendingSend = [];
10
- connect();
11
-
12
- function connect() {
13
- var conn = new WebSocket(url);
14
- conn.onopen = onOpen.bind(null, conn);
15
- conn.onerror = function() {
16
- logger.error("Failed to connect to service broker, retrying in 15");
17
- setTimeout(connect, 15000);
18
- };
19
- }
20
-
21
- function onOpen(conn) {
22
- ws = conn;
23
- ws.onerror = logger.error;
24
- ws.onclose = onClose;
25
- ws.onmessage = onMessage;
26
- for (var i=0; i<connectListeners.length; i++) connectListeners[i]();
27
- for (var i=0; i<pendingSend.length; i++) send(pendingSend[i].header, pendingSend[i].payload);
28
- pendingSend = [];
29
- }
30
-
31
- function onClose() {
32
- ws = null;
33
- logger.error("Lost connection to service broker, reconnecting");
34
- setTimeout(connect, 0);
35
- }
36
-
37
- function onMessage(e) {
38
- var msg = messageFromString(e.data);
39
- logger.debug("<<", msg.header, msg.payload);
40
- if (msg.header.type == "ServiceResponse") onServiceResponse(msg);
41
- else if (msg.header.type == "ServiceRequest") onServiceRequest(msg);
42
- else if (msg.header.type == "SbStatusResponse") onServiceResponse(msg);
43
- else if (msg.header.error) onServiceResponse(msg);
44
- else logger.error("Unhandled", msg.header);
45
- }
46
-
47
- function messageFromString(text) {
48
- var index = text.indexOf('\n');
49
- if (index == -1) return {header: JSON.parse(text)};
50
- else return {header: JSON.parse(text.substr(0,index)), payload: text.substr(index+1)};
51
- }
52
-
53
- function onServiceResponse(msg) {
54
- if (pending[msg.header.id]) {
55
- if (msg.header.error) pending[msg.header.id].reject(new Error(msg.header.error));
56
- else pending[msg.header.id].fulfill(msg);
57
- delete pending[msg.header.id];
58
- }
59
- else logger.error("Response received but no pending request", msg.header);
60
- }
61
-
62
- function onServiceRequest(msg) {
63
- if (providers[msg.header.service.name]) {
64
- Promise.resolve(providers[msg.header.service.name].handler(msg))
65
- .then(function(res) {
66
- if (!res) res = {};
67
- if (msg.header.id) {
68
- var header = {
69
- to: msg.header.from,
70
- id: msg.header.id,
71
- type: "ServiceResponse"
72
- };
73
- send(Object.assign({}, res.header, header), res.payload);
74
- }
75
- })
76
- .catch(function(err) {
77
- if (msg.header.id) {
78
- send({
79
- to: msg.header.from,
80
- id: msg.header.id,
81
- type: "ServiceResponse",
82
- error: err.message
1
+ function messageFromString(text) {
2
+ const index = text.indexOf('\n');
3
+ if (index == -1) {
4
+ return {
5
+ header: JSON.parse(text)
6
+ };
7
+ }
8
+ else {
9
+ return {
10
+ header: JSON.parse(text.slice(0, index)),
11
+ payload: text.slice(index + 1)
12
+ };
13
+ }
14
+ }
15
+ export class ServiceBroker {
16
+ constructor(url) {
17
+ this.url = url;
18
+ this.providers = new Map();
19
+ this.ws = null;
20
+ this.connectListeners = [];
21
+ this.pendingSend = [];
22
+ this.pendingResponses = new Map();
23
+ this.pendingIdGen = 0;
24
+ this.connect();
25
+ }
26
+ connect() {
27
+ const conn = new WebSocket(this.url);
28
+ conn.onopen = () => this.onOpen(conn);
29
+ conn.onerror = () => {
30
+ console.error("Failed to connect to service broker, retrying in 15");
31
+ setTimeout(() => this.connect(), 15000);
32
+ };
33
+ }
34
+ onOpen(conn) {
35
+ this.ws = conn;
36
+ this.ws.onerror = console.error;
37
+ this.ws.onclose = () => this.onClose();
38
+ this.ws.onmessage = event => this.onMessage(event);
39
+ for (const listener of this.connectListeners)
40
+ listener();
41
+ for (const { header, payload } of this.pendingSend)
42
+ this.send(header, payload);
43
+ this.pendingSend = [];
44
+ }
45
+ onClose() {
46
+ this.ws = null;
47
+ console.error("Lost connection to service broker, reconnecting");
48
+ setTimeout(() => this.connect(), 0);
49
+ }
50
+ onMessage(e) {
51
+ const msg = messageFromString(e.data);
52
+ console.debug("<<", msg.header, msg.payload);
53
+ if (msg.header.type == "ServiceResponse")
54
+ this.onServiceResponse(msg);
55
+ else if (msg.header.type == "ServiceRequest")
56
+ this.onServiceRequest(msg);
57
+ else if (msg.header.type == "SbStatusResponse")
58
+ this.onServiceResponse(msg);
59
+ else if (msg.header.error)
60
+ this.onServiceResponse(msg);
61
+ else
62
+ console.error("Unhandled", msg.header);
63
+ }
64
+ onServiceResponse(message) {
65
+ const id = message.header.id;
66
+ const pendingResponse = this.pendingResponses.get(id);
67
+ if (pendingResponse) {
68
+ this.pendingResponses.delete(id);
69
+ if (message.header.error) {
70
+ pendingResponse.reject(new Error(message.header.error));
71
+ }
72
+ else {
73
+ pendingResponse.fulfill(message);
74
+ }
75
+ }
76
+ else {
77
+ console.error("Response received but no pending request", message.header);
78
+ }
79
+ }
80
+ onServiceRequest(msg) {
81
+ const service = msg.header.service;
82
+ const provider = this.providers.get(service.name);
83
+ if (provider) {
84
+ Promise.resolve(provider.handler(msg))
85
+ .then(res => {
86
+ if (msg.header.id) {
87
+ this.send(Object.assign(Object.assign({}, res === null || res === void 0 ? void 0 : res.header), { to: msg.header.from, id: msg.header.id, type: "ServiceResponse" }), res === null || res === void 0 ? void 0 : res.payload);
88
+ }
83
89
  })
84
- }
85
- else logger.error(err.message, msg.header);
86
- })
87
- }
88
- else logger.error("No handler for service " + msg.header.service.name);
89
- }
90
-
91
-
92
-
93
- this.request = function(service, req) {
94
- return this.requestTo(null, service, req);
95
- }
96
-
97
- this.requestTo = function(endpointId, service, req) {
98
- var id = ++pendingIdGen;
99
- var promise = new Promise(function(fulfill, reject) {
100
- pending[id] = {fulfill: fulfill, reject: reject};
101
- })
102
- var header = {
103
- id: id,
104
- type: "ServiceRequest",
105
- service: service
106
- };
107
- if (endpointId) header.to = endpointId;
108
- send(Object.assign({}, req.header, header), req.payload);
109
- return promise;
110
- }
111
-
112
- function send(header, payload) {
113
- if (!ws) {
114
- pendingSend.push({header: header, payload: payload});
115
- return;
116
- }
117
- logger.debug(">>", header, payload);
118
- ws.send(JSON.stringify(header) + (payload ? "\n"+payload : ""));
119
- }
120
-
121
-
122
-
123
- this.advertise = function(service, handler) {
124
- if (providers[service.name]) throw new Error(service.name + " provider already exists");
125
- providers[service.name] = {
126
- advertisedService: service,
127
- handler: handler
128
- }
129
- return send({
130
- type: "SbAdvertiseRequest",
131
- services: Object.keys(providers)
132
- .map(function(x) {return providers[x].advertisedService})
133
- .filter(function(x) {return x})
134
- })
135
- }
136
-
137
- this.unadvertise = function(serviceName) {
138
- if (!providers[serviceName]) throw new Error(serviceName + " provider not exists");
139
- delete providers[serviceName];
140
- return send({
141
- type: "SbAdvertiseRequest",
142
- services: Object.keys(providers)
143
- .map(function(x) {return providers[x].advertisedService})
144
- .filter(function(x) {return x})
145
- })
146
- }
147
-
148
- this.setHandler = function(serviceName, handler) {
149
- if (providers[serviceName]) throw new Error("Handler already exists");
150
- providers[serviceName] = {
151
- handler: handler
152
- }
153
- }
154
-
155
-
156
-
157
- this.publish = function(topic, text) {
158
- return send({
159
- type: "ServiceRequest",
160
- service: {name: "#"+topic}
161
- },
162
- text);
163
- }
164
-
165
- this.subscribe = function(topic, handler) {
166
- return this.advertise({name: "#"+topic}, function(msg) {
167
- handler(msg.payload);
168
- return null;
169
- })
170
- }
171
-
172
- this.unsubscribe = function(topic) {
173
- return this.unadvertise("#"+topic);
174
- }
175
-
176
- this.isConnected = function() {
177
- return ws != null;
178
- }
179
-
180
- this.addConnectListener = function(listener) {
181
- connectListeners.push(listener);
182
- if (this.isConnected()) listener();
183
- }
90
+ .catch(err => {
91
+ if (msg.header.id) {
92
+ this.send({
93
+ to: msg.header.from,
94
+ id: msg.header.id,
95
+ type: "ServiceResponse",
96
+ error: err.message || err
97
+ });
98
+ }
99
+ else {
100
+ console.error(err.message, msg.header);
101
+ }
102
+ });
103
+ }
104
+ else {
105
+ console.error("No handler for service " + service.name);
106
+ }
107
+ }
108
+ send(header, payload) {
109
+ if (!this.ws) {
110
+ this.pendingSend.push({ header, payload });
111
+ return;
112
+ }
113
+ console.debug(">>", header, payload);
114
+ if (payload) {
115
+ this.ws.send(JSON.stringify(header) + "\n" + payload);
116
+ }
117
+ else {
118
+ this.ws.send(JSON.stringify(header));
119
+ }
120
+ }
121
+ request(service, req) {
122
+ return this.requestTo(null, service, req);
123
+ }
124
+ requestTo(endpointId, service, req) {
125
+ const id = ++this.pendingIdGen;
126
+ const promise = new Promise((fulfill, reject) => {
127
+ this.pendingResponses.set(id, { fulfill, reject });
128
+ });
129
+ const header = {
130
+ id: id,
131
+ type: "ServiceRequest",
132
+ service
133
+ };
134
+ if (endpointId)
135
+ header.to = endpointId;
136
+ this.send(Object.assign(Object.assign({}, req.header), header), req.payload);
137
+ return promise;
138
+ }
139
+ advertise(service, handler) {
140
+ if (this.providers.has(service.name)) {
141
+ throw new Error(service.name + " provider already exists");
142
+ }
143
+ this.providers.set(service.name, { advertisedService: service, handler });
144
+ return this.send({
145
+ type: "SbAdvertiseRequest",
146
+ services: Array.from(this.providers.values())
147
+ .filter(x => x.advertisedService)
148
+ .map(x => x.advertisedService)
149
+ });
150
+ }
151
+ unadvertise(serviceName) {
152
+ if (!this.providers.delete(serviceName)) {
153
+ throw new Error(serviceName + " provider not exists");
154
+ }
155
+ return this.send({
156
+ type: "SbAdvertiseRequest",
157
+ services: Array.from(this.providers.values())
158
+ .filter(x => x.advertisedService)
159
+ .map(x => x.advertisedService)
160
+ });
161
+ }
162
+ setServiceHandler(serviceName, handler) {
163
+ if (this.providers.has(serviceName)) {
164
+ throw new Error("Handler already exists");
165
+ }
166
+ this.providers.set(serviceName, { handler });
167
+ }
168
+ publish(topic, text) {
169
+ return this.send({ type: "ServiceRequest", service: { name: "#" + topic } }, text);
170
+ }
171
+ subscribe(topic, handler) {
172
+ return this.advertise({ name: "#" + topic }, msg => handler(msg.payload));
173
+ }
174
+ unsubscribe(topic) {
175
+ return this.unadvertise("#" + topic);
176
+ }
177
+ isConnected() {
178
+ return this.ws != null;
179
+ }
180
+ addConnectListener(listener) {
181
+ this.connectListeners.push(listener);
182
+ if (this.isConnected())
183
+ listener();
184
+ }
184
185
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@service-broker/webclient",
3
- "version": "1.0.2",
4
- "description": "Browser JavaScript library for communicating with the service broker",
3
+ "version": "2.0.1",
4
+ "description": "Browser ESM client library for communicating with the service broker",
5
+ "type": "module",
5
6
  "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/service-broker/service-broker-webclient.git"
@@ -13,5 +13,8 @@
13
13
  "bugs": {
14
14
  "url": "https://github.com/service-broker/service-broker-webclient/issues"
15
15
  },
16
- "homepage": "https://github.com/service-broker/service-broker-webclient#readme"
16
+ "homepage": "https://github.com/service-broker/service-broker-webclient#readme",
17
+ "devDependencies": {
18
+ "typescript": "^5.8.3"
19
+ }
17
20
  }
package/src/index.ts ADDED
@@ -0,0 +1,225 @@
1
+
2
+ interface ServiceFilter {
3
+ name: string
4
+ capabilities?: string[]
5
+ }
6
+
7
+ export interface ServiceHandler {
8
+ (request: Message): Message | void | Promise<Message | void>
9
+ }
10
+
11
+ interface MessageWithHeader {
12
+ header: Record<string, unknown>
13
+ payload?: string
14
+ }
15
+
16
+ interface Message {
17
+ header?: Record<string, unknown>
18
+ payload?: string
19
+ }
20
+
21
+ interface PendingResponse {
22
+ fulfill(response: Message): void
23
+ reject(err: unknown): void
24
+ }
25
+
26
+ function messageFromString(text: string): MessageWithHeader {
27
+ const index = text.indexOf('\n')
28
+ if (index == -1) {
29
+ return {
30
+ header: JSON.parse(text)
31
+ }
32
+ } else {
33
+ return {
34
+ header: JSON.parse(text.slice(0, index)),
35
+ payload: text.slice(index + 1)
36
+ }
37
+ }
38
+ }
39
+
40
+
41
+ export class ServiceBroker {
42
+
43
+ private readonly providers = new Map<string, {
44
+ advertisedService?: ServiceFilter
45
+ handler: ServiceHandler
46
+ }>()
47
+ private ws: WebSocket | null = null
48
+ private readonly connectListeners: Function[] = []
49
+ private pendingSend: Message[] = []
50
+ private readonly pendingResponses = new Map<number, PendingResponse>()
51
+ private pendingIdGen = 0
52
+
53
+
54
+ constructor(private readonly url: string) {
55
+ this.connect()
56
+ }
57
+
58
+ private connect() {
59
+ const conn = new WebSocket(this.url)
60
+ conn.onopen = () => this.onOpen(conn)
61
+ conn.onerror = () => {
62
+ console.error("Failed to connect to service broker, retrying in 15")
63
+ setTimeout(() => this.connect(), 15000)
64
+ }
65
+ }
66
+
67
+ private onOpen(conn: WebSocket) {
68
+ this.ws = conn
69
+ this.ws.onerror = console.error
70
+ this.ws.onclose = () => this.onClose()
71
+ this.ws.onmessage = event => this.onMessage(event)
72
+ for (const listener of this.connectListeners) listener()
73
+ for (const { header, payload } of this.pendingSend) this.send(header, payload)
74
+ this.pendingSend = []
75
+ }
76
+
77
+ private onClose() {
78
+ this.ws = null
79
+ console.error("Lost connection to service broker, reconnecting")
80
+ setTimeout(() => this.connect(), 0)
81
+ }
82
+
83
+ private onMessage(e: MessageEvent) {
84
+ const msg = messageFromString(e.data);
85
+ console.debug("<<", msg.header, msg.payload);
86
+ if (msg.header.type == "ServiceResponse") this.onServiceResponse(msg)
87
+ else if (msg.header.type == "ServiceRequest") this.onServiceRequest(msg)
88
+ else if (msg.header.type == "SbStatusResponse") this.onServiceResponse(msg)
89
+ else if (msg.header.error) this.onServiceResponse(msg)
90
+ else console.error("Unhandled", msg.header)
91
+ }
92
+
93
+ private onServiceResponse(message: MessageWithHeader) {
94
+ const id = message.header.id as number
95
+ const pendingResponse = this.pendingResponses.get(id)
96
+ if (pendingResponse) {
97
+ this.pendingResponses.delete(id)
98
+ if (message.header.error) {
99
+ pendingResponse.reject(new Error(message.header.error as string))
100
+ } else {
101
+ pendingResponse.fulfill(message)
102
+ }
103
+ } else {
104
+ console.error("Response received but no pending request", message.header)
105
+ }
106
+ }
107
+
108
+ private onServiceRequest(msg: MessageWithHeader) {
109
+ const service = msg.header.service as ServiceFilter
110
+ const provider = this.providers.get(service.name)
111
+ if (provider) {
112
+ Promise.resolve(provider.handler(msg))
113
+ .then(res => {
114
+ if (msg.header.id) {
115
+ this.send({
116
+ ...res?.header,
117
+ to: msg.header.from,
118
+ id: msg.header.id,
119
+ type: "ServiceResponse"
120
+ }, res?.payload)
121
+ }
122
+ })
123
+ .catch(err => {
124
+ if (msg.header.id) {
125
+ this.send({
126
+ to: msg.header.from,
127
+ id: msg.header.id,
128
+ type: "ServiceResponse",
129
+ error: err.message || err
130
+ })
131
+ } else {
132
+ console.error(err.message, msg.header)
133
+ }
134
+ })
135
+ } else {
136
+ console.error("No handler for service " + service.name)
137
+ }
138
+ }
139
+
140
+ private send(header: Message['header'], payload?: Message['payload']) {
141
+ if (!this.ws) {
142
+ this.pendingSend.push({ header, payload })
143
+ return;
144
+ }
145
+ console.debug(">>", header, payload);
146
+ if (payload) {
147
+ this.ws.send(JSON.stringify(header) + "\n" + payload)
148
+ } else {
149
+ this.ws.send(JSON.stringify(header))
150
+ }
151
+ }
152
+
153
+
154
+ request(service: ServiceFilter, req: Message) {
155
+ return this.requestTo(null, service, req);
156
+ }
157
+
158
+ requestTo(endpointId: string | null, service: ServiceFilter, req: Message) {
159
+ const id = ++this.pendingIdGen
160
+ const promise = new Promise<Message>((fulfill, reject) => {
161
+ this.pendingResponses.set(id, { fulfill, reject })
162
+ })
163
+ const header: Message['header'] = {
164
+ id: id,
165
+ type: "ServiceRequest",
166
+ service
167
+ };
168
+ if (endpointId) header.to = endpointId;
169
+ this.send({...req.header, ...header}, req.payload)
170
+ return promise;
171
+ }
172
+
173
+ advertise(service: ServiceFilter, handler: ServiceHandler) {
174
+ if (this.providers.has(service.name)) {
175
+ throw new Error(service.name + " provider already exists")
176
+ }
177
+ this.providers.set(service.name, { advertisedService: service, handler })
178
+ return this.send({
179
+ type: "SbAdvertiseRequest",
180
+ services: Array.from(this.providers.values())
181
+ .filter(x => x.advertisedService)
182
+ .map(x => x.advertisedService)
183
+ })
184
+ }
185
+
186
+ unadvertise(serviceName: string) {
187
+ if (!this.providers.delete(serviceName)) {
188
+ throw new Error(serviceName + " provider not exists")
189
+ }
190
+ return this.send({
191
+ type: "SbAdvertiseRequest",
192
+ services: Array.from(this.providers.values())
193
+ .filter(x => x.advertisedService)
194
+ .map(x => x.advertisedService)
195
+ })
196
+ }
197
+
198
+ setServiceHandler(serviceName: string, handler: ServiceHandler) {
199
+ if (this.providers.has(serviceName)) {
200
+ throw new Error("Handler already exists")
201
+ }
202
+ this.providers.set(serviceName, { handler })
203
+ }
204
+
205
+ publish(topic: string, text: string) {
206
+ return this.send({ type: "ServiceRequest", service: { name: "#" + topic } }, text)
207
+ }
208
+
209
+ subscribe(topic: string, handler: (text: string) => void) {
210
+ return this.advertise({ name: "#" + topic }, msg => handler(msg.payload!))
211
+ }
212
+
213
+ unsubscribe(topic: string) {
214
+ return this.unadvertise("#" + topic)
215
+ }
216
+
217
+ isConnected() {
218
+ return this.ws != null
219
+ }
220
+
221
+ addConnectListener(listener: Function) {
222
+ this.connectListeners.push(listener)
223
+ if (this.isConnected()) listener()
224
+ }
225
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "esnext",
4
+ "target": "es2017",
5
+ "strict": true,
6
+ "moduleResolution": "bundler",
7
+ "declaration": true,
8
+ "newLine": "LF",
9
+ "rootDir": "src",
10
+ "outDir": "dist"
11
+ },
12
+ "include": [
13
+ "src/**/*"
14
+ ]
15
+ }