[6a3a178] | 1 | import { WebSocketSubject, WebSocketSubjectConfig } from './WebSocketSubject';
|
---|
| 2 |
|
---|
| 3 | /**
|
---|
| 4 | * Wrapper around the w3c-compatible WebSocket object provided by the browser.
|
---|
| 5 | *
|
---|
| 6 | * <span class="informal">{@link Subject} that communicates with a server via WebSocket</span>
|
---|
| 7 | *
|
---|
| 8 | * `webSocket` is a factory function that produces a `WebSocketSubject`,
|
---|
| 9 | * which can be used to make WebSocket connection with an arbitrary endpoint.
|
---|
| 10 | * `webSocket` accepts as an argument either a string with url of WebSocket endpoint, or an
|
---|
| 11 | * {@link WebSocketSubjectConfig} object for providing additional configuration, as
|
---|
| 12 | * well as Observers for tracking lifecycle of WebSocket connection.
|
---|
| 13 | *
|
---|
| 14 | * When `WebSocketSubject` is subscribed, it attempts to make a socket connection,
|
---|
| 15 | * unless there is one made already. This means that many subscribers will always listen
|
---|
| 16 | * on the same socket, thus saving resources. If however, two instances are made of `WebSocketSubject`,
|
---|
| 17 | * even if these two were provided with the same url, they will attempt to make separate
|
---|
| 18 | * connections. When consumer of a `WebSocketSubject` unsubscribes, socket connection is closed,
|
---|
| 19 | * only if there are no more subscribers still listening. If after some time a consumer starts
|
---|
| 20 | * subscribing again, connection is reestablished.
|
---|
| 21 | *
|
---|
| 22 | * Once connection is made, whenever a new message comes from the server, `WebSocketSubject` will emit that
|
---|
| 23 | * message as a value in the stream. By default, a message from the socket is parsed via `JSON.parse`. If you
|
---|
| 24 | * want to customize how deserialization is handled (if at all), you can provide custom `resultSelector`
|
---|
| 25 | * function in {@link WebSocketSubject}. When connection closes, stream will complete, provided it happened without
|
---|
| 26 | * any errors. If at any point (starting, maintaining or closing a connection) there is an error,
|
---|
| 27 | * stream will also error with whatever WebSocket API has thrown.
|
---|
| 28 | *
|
---|
| 29 | * By virtue of being a {@link Subject}, `WebSocketSubject` allows for receiving and sending messages from the server. In order
|
---|
| 30 | * to communicate with a connected endpoint, use `next`, `error` and `complete` methods. `next` sends a value to the server, so bear in mind
|
---|
| 31 | * that this value will not be serialized beforehand. Because of This, `JSON.stringify` will have to be called on a value by hand,
|
---|
| 32 | * before calling `next` with a result. Note also that if at the moment of nexting value
|
---|
| 33 | * there is no socket connection (for example no one is subscribing), those values will be buffered, and sent when connection
|
---|
| 34 | * is finally established. `complete` method closes socket connection. `error` does the same,
|
---|
| 35 | * as well as notifying the server that something went wrong via status code and string with details of what happened.
|
---|
| 36 | * Since status code is required in WebSocket API, `WebSocketSubject` does not allow, like regular `Subject`,
|
---|
| 37 | * arbitrary values being passed to the `error` method. It needs to be called with an object that has `code`
|
---|
| 38 | * property with status code number and optional `reason` property with string describing details
|
---|
| 39 | * of an error.
|
---|
| 40 | *
|
---|
| 41 | * Calling `next` does not affect subscribers of `WebSocketSubject` - they have no
|
---|
| 42 | * information that something was sent to the server (unless of course the server
|
---|
| 43 | * responds somehow to a message). On the other hand, since calling `complete` triggers
|
---|
| 44 | * an attempt to close socket connection. If that connection is closed without any errors, stream will
|
---|
| 45 | * complete, thus notifying all subscribers. And since calling `error` closes
|
---|
| 46 | * socket connection as well, just with a different status code for the server, if closing itself proceeds
|
---|
| 47 | * without errors, subscribed Observable will not error, as one might expect, but complete as usual. In both cases
|
---|
| 48 | * (calling `complete` or `error`), if process of closing socket connection results in some errors, *then* stream
|
---|
| 49 | * will error.
|
---|
| 50 | *
|
---|
| 51 | * **Multiplexing**
|
---|
| 52 | *
|
---|
| 53 | * `WebSocketSubject` has an additional operator, not found in other Subjects. It is called `multiplex` and it is
|
---|
| 54 | * used to simulate opening several socket connections, while in reality maintaining only one.
|
---|
| 55 | * For example, an application has both chat panel and real-time notifications about sport news. Since these are two distinct functions,
|
---|
| 56 | * it would make sense to have two separate connections for each. Perhaps there could even be two separate services with WebSocket
|
---|
| 57 | * endpoints, running on separate machines with only GUI combining them together. Having a socket connection
|
---|
| 58 | * for each functionality could become too resource expensive. It is a common pattern to have single
|
---|
| 59 | * WebSocket endpoint that acts as a gateway for the other services (in this case chat and sport news services).
|
---|
| 60 | * Even though there is a single connection in a client app, having the ability to manipulate streams as if it
|
---|
| 61 | * were two separate sockets is desirable. This eliminates manually registering and unregistering in a gateway for
|
---|
| 62 | * given service and filter out messages of interest. This is exactly what `multiplex` method is for.
|
---|
| 63 | *
|
---|
| 64 | * Method accepts three parameters. First two are functions returning subscription and unsubscription messages
|
---|
| 65 | * respectively. These are messages that will be sent to the server, whenever consumer of resulting Observable
|
---|
| 66 | * subscribes and unsubscribes. Server can use them to verify that some kind of messages should start or stop
|
---|
| 67 | * being forwarded to the client. In case of the above example application, after getting subscription message with proper identifier,
|
---|
| 68 | * gateway server can decide that it should connect to real sport news service and start forwarding messages from it.
|
---|
| 69 | * Note that both messages will be sent as returned by the functions, they are by default serialized using JSON.stringify, just
|
---|
| 70 | * as messages pushed via `next`. Also bear in mind that these messages will be sent on *every* subscription and
|
---|
| 71 | * unsubscription. This is potentially dangerous, because one consumer of an Observable may unsubscribe and the server
|
---|
| 72 | * might stop sending messages, since it got unsubscription message. This needs to be handled
|
---|
| 73 | * on the server or using {@link publish} on a Observable returned from 'multiplex'.
|
---|
| 74 | *
|
---|
| 75 | * Last argument to `multiplex` is a `messageFilter` function which should return a boolean. It is used to filter out messages
|
---|
| 76 | * sent by the server to only those that belong to simulated WebSocket stream. For example, server might mark these
|
---|
| 77 | * messages with some kind of string identifier on a message object and `messageFilter` would return `true`
|
---|
| 78 | * if there is such identifier on an object emitted by the socket. Messages which returns `false` in `messageFilter` are simply skipped,
|
---|
| 79 | * and are not passed down the stream.
|
---|
| 80 | *
|
---|
| 81 | * Return value of `multiplex` is an Observable with messages incoming from emulated socket connection. Note that this
|
---|
| 82 | * is not a `WebSocketSubject`, so calling `next` or `multiplex` again will fail. For pushing values to the
|
---|
| 83 | * server, use root `WebSocketSubject`.
|
---|
| 84 | *
|
---|
| 85 | * ### Examples
|
---|
| 86 | * #### Listening for messages from the server
|
---|
| 87 | * ```ts
|
---|
| 88 | * import { webSocket } from "rxjs/webSocket";
|
---|
| 89 | * const subject = webSocket("ws://localhost:8081");
|
---|
| 90 | *
|
---|
| 91 | * subject.subscribe(
|
---|
| 92 | * msg => console.log('message received: ' + msg), // Called whenever there is a message from the server.
|
---|
| 93 | * err => console.log(err), // Called if at any point WebSocket API signals some kind of error.
|
---|
| 94 | * () => console.log('complete') // Called when connection is closed (for whatever reason).
|
---|
| 95 | * );
|
---|
| 96 | * ```
|
---|
| 97 | *
|
---|
| 98 | * #### Pushing messages to the server
|
---|
| 99 | * ```ts
|
---|
| 100 | * import { webSocket } from "rxjs/webSocket";
|
---|
| 101 | * const subject = webSocket('ws://localhost:8081');
|
---|
| 102 | *
|
---|
| 103 | * subject.subscribe();
|
---|
| 104 | * // Note that at least one consumer has to subscribe to the created subject - otherwise "nexted" values will be just buffered and not sent,
|
---|
| 105 | * // since no connection was established!
|
---|
| 106 | *
|
---|
| 107 | * subject.next({message: 'some message'});
|
---|
| 108 | * // This will send a message to the server once a connection is made. Remember value is serialized with JSON.stringify by default!
|
---|
| 109 | *
|
---|
| 110 | * subject.complete(); // Closes the connection.
|
---|
| 111 | *
|
---|
| 112 | * subject.error({code: 4000, reason: 'I think our app just broke!'});
|
---|
| 113 | * // Also closes the connection, but let's the server know that this closing is caused by some error.
|
---|
| 114 | * ```
|
---|
| 115 | *
|
---|
| 116 | * #### Multiplexing WebSocket
|
---|
| 117 | * ```ts
|
---|
| 118 | * import { webSocket } from "rxjs/webSocket";
|
---|
| 119 | * const subject = webSocket('ws://localhost:8081');
|
---|
| 120 | *
|
---|
| 121 | * const observableA = subject.multiplex(
|
---|
| 122 | * () => ({subscribe: 'A'}), // When server gets this message, it will start sending messages for 'A'...
|
---|
| 123 | * () => ({unsubscribe: 'A'}), // ...and when gets this one, it will stop.
|
---|
| 124 | * message => message.type === 'A' // If the function returns `true` message is passed down the stream. Skipped if the function returns false.
|
---|
| 125 | * );
|
---|
| 126 | *
|
---|
| 127 | * const observableB = subject.multiplex( // And the same goes for 'B'.
|
---|
| 128 | * () => ({subscribe: 'B'}),
|
---|
| 129 | * () => ({unsubscribe: 'B'}),
|
---|
| 130 | * message => message.type === 'B'
|
---|
| 131 | * );
|
---|
| 132 | *
|
---|
| 133 | * const subA = observableA.subscribe(messageForA => console.log(messageForA));
|
---|
| 134 | * // At this moment WebSocket connection is established. Server gets '{"subscribe": "A"}' message and starts sending messages for 'A',
|
---|
| 135 | * // which we log here.
|
---|
| 136 | *
|
---|
| 137 | * const subB = observableB.subscribe(messageForB => console.log(messageForB));
|
---|
| 138 | * // Since we already have a connection, we just send '{"subscribe": "B"}' message to the server. It starts sending messages for 'B',
|
---|
| 139 | * // which we log here.
|
---|
| 140 | *
|
---|
| 141 | * subB.unsubscribe();
|
---|
| 142 | * // Message '{"unsubscribe": "B"}' is sent to the server, which stops sending 'B' messages.
|
---|
| 143 | *
|
---|
| 144 | * subA.unsubscribe();
|
---|
| 145 | * // Message '{"unsubscribe": "A"}' makes the server stop sending messages for 'A'. Since there is no more subscribers to root Subject,
|
---|
| 146 | * // socket connection closes.
|
---|
| 147 | * ```
|
---|
| 148 | *
|
---|
| 149 | *
|
---|
| 150 | * @param {string|WebSocketSubjectConfig} urlConfigOrSource The WebSocket endpoint as an url or an object with
|
---|
| 151 | * configuration and additional Observers.
|
---|
| 152 | * @return {WebSocketSubject} Subject which allows to both send and receive messages via WebSocket connection.
|
---|
| 153 | */
|
---|
| 154 | export function webSocket<T>(urlConfigOrSource: string | WebSocketSubjectConfig<T>): WebSocketSubject<T> {
|
---|
| 155 | return new WebSocketSubject<T>(urlConfigOrSource);
|
---|
| 156 | }
|
---|