Rxjs: ReferenceError: WebSocket is not defined when using Angular Universal for SSR

Created on 22 Jul 2018  路  12Comments  路  Source: ReactiveX/rxjs

Bug Report

Current Behavior
I use WebSocketSubject and webSocket in my angular universal 6.x project, which works fine on the browser platform. However, when running the node web server (that contains the SSR stuff (Server-Side Rendering)), an error is thrown:

ReferenceError: WebSocket is not defined

Reproduction

Example code:

import { WebSocketSubject, webSocket } from 'rxjs/webSocket';

const socket: WebSocketSubject<any> = webSocket('wss://echo.websocket.org');
socket.subscribe(msg => doSomething(msg));

Expected behavior
No error

Environment

  • Runtime: Angular 6
  • RxJS version: 6.2.0, 6.2.1, 6.2.2 (tested all)
  • Loader, build configuration:
# install dependencies
npm install

# build angular bundles for browser and server platforms
npm run build:client-and-server-bundles

# build the webserver
npm run webpack:server

# start the webserver
npm run serve:ssr

# the app is now being served at http://localhost:8081/
# open it in the browser and the error will occur: 'ReferenceError: WebSocket is not defined'

Additional info

Most helpful comment

Since webSocketSubject is a wrapper around websocket object from browsers, it makes perfectly sense not to be available in the server. No one browser APIs are available when using Angular/rxjs in the server.

All 12 comments

@trotyl Thanks for your suggestion.
I tried to fix it by:

  1. forking the rxjs repo => see https://github.com/takahser/rxjs
  2. fixing the bug => if (!config.WebSocketCtor && typeof WebSocket !== 'undefined') {
  3. replacing the dependency in my package.json => "rxjs": "takahser/rxjs#00d144092d421d00080a7b44c3672af2b4f3733d",
  4. running rm -rf node_modules/ && npm install

However, when checking rxjs/src/internal/observable/dom/WebSocketSubject.ts in my node_modules, it doesn't contain my changes... why?
I decided to manually change the file within node_modules and running the build commands, but it still throws me the error ReferenceError: WebSocket is not defined when accessing the running app...

At least you should tell which line of code (on which project) is causing this.

@trotyl WebSocketSubject is used here:

https://github.com/takahser/angular-universal-ssr-with-rxjs-websocket/blob/master/src/app/messaging.service.ts#L5

Please note that the error only occurs after

  1. building the server-rendered pages (npm run build:client-and-server-bundles)
  2. building the express web server (npm run webpack:server)
  3. starting the express web server (npm run serve:ssr)
  4. and open the app in the browser

The webserver bundle is generated using this webpack config:
https://github.com/takahser/angular-universal-ssr-with-rxjs-websocket/blob/master/webpack.server.config.js

Please let me know, if you need more information

@trotyl update:

  • I was able to modify the built WebSocketSubject.js file in the project dependencies (node_modules) on my machine
  • I applied the same changes: if (!config.WebSocketCtor && WebSocket) { => if (!config.WebSocketCtor && typeof WebSocket !== 'undefined') {
  • now I get a new error: Error: no WebSocket constructor can be found

now, looking at https://github.com/takahser/rxjs/commit/00d144092d421d00080a7b44c3672af2b4f3733d#diff-17e027a1b28f1cfb2d7a472e6a95ddb8R95, is this really correct? we assign the config.WebSocketCtor attribute to the WebSocket type. This is wrong, isn't it?

Instead, it should call the WebSocket constructor, similar to the specs: https://github.com/ReactiveX/rxjs/blob/2db0788eaeebe102c86af0132a985c131855ec67/spec/observables/dom/webSocket-spec.ts#L647

@takahser Could you solve that problem?

@Zaniyar No, but it seems as this is rather a problem with angular universal and webpack than RxJs. When using SSR, Angular Universal creates a bundle for running the angular app on the node server, but there is natively no websocket implementation on Node. Therefore, it has to be added manually as a dependency (npm package). I tried adding it to the js server bundle (const WebSocket = require('ws'); manually, which resolves the problem. However, when I add it to the typescript code that gets transcompiled into the js bundle later on, it won't work.

I'm trying to get WebSocketSubject working on my nestjs (express framework) backend. I'm getting the same error:

ReferenceError: WebSocket is not defined
    at new WebSocketSubject (.../node_modules/rxjs/src/internal/observable/dom/WebSocketSubject.ts:94:36)

I know WebSocketSubject depends on the dom/browser's native websocket library. I've made sure that "dom" is included in my tsconfig.

"ws": "6.1.0" and "rxjs": "6.3.3" are also installed.

I saw your post on stack overflow. Adding this line to the file I was using WebSocketSubject in fixed my problem:

// tslint:disable-next-line
(global as any).WebSocket = require('ws');

But, looks like a hack to me. Anyone know what is the root of this problem?

Since webSocketSubject is a wrapper around websocket object from browsers, it makes perfectly sense not to be available in the server. No one browser APIs are available when using Angular/rxjs in the server.

@CoreyCole exactly.
When running JavaScript in the browser, the JavaScript API provides us with various global objects, such as window, document, or WebSocket, which are not available on the server-side node implementation.
When running JS on the server (node), such as it is the case with Angular Prerendering, certain global browser web APIs won't be available. Therefore, we need to use certain npm packages, that provides us with the lacking functionality (such as ws in this case). When using JS, this would be sufficient, but TypeScript will complain, because the types on the WebSocket object are mising. Finally, we can convert the untyped object to the type any, which can be (as the name implies) anything ((global as any).WebSocket = require('ws');). This way, we can satisfy the TypeScript compiler. :)

You can find more examples global variables, that are different on the browser and server implementations here.

Alright, for the record, to solve this issue, cast your WebSocket object to any:

// tslint:disable-next-line
(global as any).WebSocket = require('ws');

See also on the stackoverflow post

Was this page helpful?
0 / 5 - 0 ratings