Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@cap-js-community/websocket

Package Overview
Dependencies
Maintainers
7
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cap-js-community/websocket

WebSocket adapter for CDS

  • 0.2.0
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
123
decreased by-3.15%
Maintainers
7
Weekly downloads
 
Created
Source

@cap-js-community/websocket

npm version monthly downloads REUSE status Main CI

WebSocket adapter for CDS

Exposes a WebSocket protocol via WebSocket standard or Socket.IO for CDS services. Runs in context of the SAP Cloud Application Programming Model (CAP) using @sap/cds (CDS Node.js).

Getting Started

  • Run npm add @cap-js-community/websocket in @sap/cds project
  • Annotate a service, that shall be exposed via WebSocket using one of the following annotations:
    @ws
    @websocket
    @protocol: 'ws'
    @protocol: 'websocket'
    @protocol: [{ kind: 'websocket', path: 'chat' }]
    @protocol: [{ kind: 'ws', path: 'chat' }]
    
  • Execute cds-serve to start server
  • Access the service endpoint via WebSocket

Usage

Server

  • Run npm add @cap-js-community/websocket in @sap/cds project
  • Create a service to be exposed as websocket protocol: srv/chat-service.cds
    @protocol: 'websocket'
    service ChatService {
      function message(text: String) returns String;
      event received {
        text: String;
      }
    }
    
  • Implement CDS websocket service: srv/chat-service.js
    module.exports = (srv) => {
      srv.on("message", async (req) => {
        srv.emit("received", req.data);
        return req.data.text;
      });
    };
    

Client

In browser environment implement the websocket client: index.html

WebSocket Standard
  • Connect with WebSocket
    const protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
    const socket = new WebSocket(protocol + window.location.host + "/ws/chat");
    
  • Emit event
    socket.send(
      JSON.stringify({
        event: "message",
        data: { text: input.value },
      }),
    );
    
  • Listen to event
    socket.addEventListener("message", (message) => {
      const payload = JSON.parse(message.data);
      switch (payload.event) {
        case "received":
          console.log(payload.data.text);
          break;
      }
    });
    
Socket.IO
  • Connect with Socket.IO client
    const socket = io("/chat", { path: "/ws" });
    
  • Emit event
    socket.emit("message", { text: "Hello World" });
    
  • Listen to event
    socket.on("received", (message) => {
      console.log(message.text);
    });
    

Documentation

WebSocket Server

The websocket server is exposed on cds object implementation-independent at cds.ws and implementation-specific at cds.io or cds.wss. Additional listeners can be registered bypassing CDS definitions and runtime. WebSocket server options can be provided via cds.requires.websocket.options.

Default protocol path is /ws and can be overwritten via cds.env.protocols.websocket.path resp. cds.env.protocols.ws.path;

WebSocket Service

Annotated services with websocket protocol are exposed at endpoint: /ws/<service-path>:

Examples:

  • WS: const socket = new WebSocket("ws://localhost:4004/ws/chat");
  • Socket.IO: const socket = io("/chat", { path: "/ws" })
WebSocket Event

Non-websocket services can contain events that are exposed as websocket events:

  @protocol: 'odata'
  @path: 'chat'
  service ChatService {
    entity Chat as projection on chat.Chat;
    function message(text: String) returns String;
    @websocket
    event received {
      text: String;
    }
  }

Although the service is exposed as an OData protocol at /odata/v4/chat, the service events annotated with @websocket or @ws are exposed as websocket events under the websocket protocol path as follows: /ws/chat. Entities and operations are not exposed, as the service itself is not marked as websocket protocol.

Non-websocket service events are only active when at least one websocket enabled service is available (=> websocket protocol adapter active).

Server Socket

Each CDS handler request context is extended to hold the current server socket instance of the event. It can be accessed via req.context.socket or cds.context.socket. Events can be directly emitted via the socket bypassing CDS runtime.

Authentication & Authorization

Authentication only works via AppRouter using a UAA configuration, as the auth token is forwarded via authorization header bearer token by AppRouter to backend instance. CDS middlewares process the auth token and set the auth info accordingly. Authorization scopes are checked as defined in the CDS services @requires annotations and authorization restrictions are checked as defined in the CDS services @restrict annotations.

Approuter

Authorization in provided in production by approuter component vis XSUAA auth. Valid UAA bindings for approuter and backend are necessary, so that the authorization flow is working. Locally, the following default environment files need to exist:

  • test/_env/default-env.json
    {
      "VCAP_SERVICES": {
        "xsuaa": [{}]
      }
    }
    
  • test/_env/approuter/default-services.json
    {
      "uaa": {}
    }
    

Approuter is configured to support websockets in xs-app.json according to @sap/approuter - websockets property:

{
  "websockets": {
    "enabled": true
  }
}
Local

For local testing a mocked basic authorization is hardcoded in flp.html/index.html.

Operations

Operations comprise actions and function in the CDS service that are exposed by CDS service either unbound (static level) or bound (entity instance level). Operations are exposed as part of the websocket protocol as described below. Operation results will be provided via optional websocket acknowledgement callback.

Operation results are only supported with Socket.IO (kind: socket.io) using acknowledgement callbacks.

Unbound

Each unbound function and action is exposed as websocket event. The signature (parameters and return type) is passed through without additional modification. Operation result will be provided as part of acknowledgment callback.

Special operations

The websocket adapter tries to call the following special operations on the service, if available in service:

  • wsConnect: Callback to notify that a socket was connected
  • wsDisconnect: Callback to notify that a socket was disconnected
Bound

Each service entity is exposed as CRUD interface via as special events as proposed here. The event is prefixed with the entity name and has the CRUD operation as suffix, e.g. Books:create. In addition, also bound functions and actions are included into these schema, e.g. Books:sell. The signature (parameters and return type) is passed through without additional modification. It is expected, that the event payload contains the primary key information. CRUD/action/function result will be provided as part of acknowledgment callback.

CRUD

Create, Read, Update and Delete (CRUD) actions are mapped to websocket events as follows:

  • <entity>:create: Create an entity instance
  • <entity>:read: Read an entity instance by key
  • <entity>:readDeep: Read an entity instance deep (incl. deep compositions) by key
  • <entity>:update: Update an entity instance by key
  • <entity>:delete: Delete an entity instance by key
  • <entity>:list: List all entity instances
  • <entity>:<operation>: Call a bound entity operation (action/function)

Events can be emitted and the response can be retrieved via acknowledgment callback.

CRUD Broadcast Events

CRUD events that modify entities automatically emit another event after successful processing:

  • <entity>:create => <entity>:created: Entity instance has been updated
  • <entity>:update => <entity>:updated: Entity instance has been created
  • <entity>:delete => <entity>:deleted: Entity instance has been deleted

Because of security concerns, it can be controlled which data of those events is broadcast, via annotations @websocket.broadcast or @ws.broadcast.

  • Propagate only key (default): @websocket.broadcast = 'key', @ws.broadcast = 'key'
  • Propagate complete entity data: @websocket.broadcast = 'data', @ws.broadcast = 'data'

Examples

Todo (UI5)

The example UI5 todo application using Socket.IO can be found at test/_env/app/todo.

Example application can be started by:

Chat (HTML)

An example chat application using Socket.IO can be found at test/_env/app/chat.

Example application can be started by:

Unit-Tests

Unit-test can be found in folder test and can be executed via npm test; The basic unit-test setup for WebSockets in CDS context looks as follows:

WS

"use strict";

const cds = require("@sap/cds");
const WebSocket = require("ws");

cds.test(__dirname + "/..");

const authorization = `Basic ${Buffer.from("alice:alice").toString("base64")}`;

describe("WebSocket", () => {
  let socket;

  beforeAll((done) => {
    const port = cds.app.server.address().port;
    socket = new WebSocket(`ws://localhost:${port}/ws/chat`, {
      headers: {
        authorization,
      },
    });
  });

  afterAll(() => {
    cds.ws.close();
    socket.close();
  });

  test("Test", (done) => {
    socket.send(
      JSON.stringify({
        event: "event",
        data: {},
      }),
    );
  });
});
Socket.io
"use strict";

const cds = require("@sap/cds");
const ioc = require("socket.io-client");

cds.test(__dirname + "/..");

const authorization = `Basic ${Buffer.from("alice:alice").toString("base64")}`;

describe("WebSocket", () => {
  let socket;

  beforeAll((done) => {
    const port = cds.app.server.address().port;
    socket = ioc(`http://localhost:${port}/chat`, {
      path: "/ws",
      extraHeaders: {
        authorization,
      },
    });
    socket.on("connect", done);
  });

  afterAll(() => {
    cds.ws.close();
    socket.disconnect();
  });

  test("Test", (done) => {
    socket.emit("event", {}, (result) => {
      expect(result).toBeDefined();
      done();
    });
  });
});

Adapters (Socket.IO)

An Adapter is a server-side component which is responsible for broadcasting events to all or a subset of clients.

Redis

Every event that is sent to multiple clients is sent to all matching clients connected to the current server and published in a Redis channel, and received by the other websocket servers of the cluster. The app needs to be bound to a Redis service instance to set up and connect Redis client.

WS Standard

The following adapters for WS Standard are supported out-of-the-box.

Redis

To use the Redis Adapter (basic publish/subscribe), the following steps have to be performed:

  • Set cds.requires.websocket.adapter.impl: "redis"
  • Application needs to be bound to a Redis instance
    • Locally a default-env.json file need to exist with index configuration
  • Redis Adapter options can be specified via cds.requires.websocket.adapter.options
  • Redis channel key can be specified via cds.requires.websocket.adapter.options.key. Default value is websocket.
Socket.IO

The following adapters for Socket.IO are supported out-of-the-box.

Redis Adapter

To use the Redis Adapter, the following steps have to be performed:

  • Install Redis Adapter dependency: npm install @socket.io/index-adapter
  • Set cds.requires.websocket.adapter.impl: "@socket.io/index-adapter"
  • Application needs to be bound to a Redis instance
    • Locally a default-env.json file need to exist with index configuration
  • Redis Adapter options can be specified via cds.requires.websocket.adapter.options
  • Redis channel key can be specified via cds.requires.websocket.adapter.options.key. Default value is socket.io.

Details: https://socket.io/docs/v4/index-adapter/

Redis Streams Adapter

To use the Redis Stream Adapter, the following steps have to be performed:

  • Install Redis Streams Adapter dependency: npm install @socket.io/index-streams-adapter
  • Set cds.requires.websocket.adapter.impl: "@socket.io/index-streams-adapter"
  • Application needs to be bound to a Redis instance
    • Locally a default-env.json file need to exist with index configuration
  • Redis Streams Adapter options can be specified via cds.requires.websocket.adapter.options
  • Redis channel key can be specified via cds.requires.websocket.adapter.options.streamName. Default value is socket.io.

Details: https://socket.io/docs/v4/index-streams-adapter/

Deployment

This module also works on a deployed infrastructure like Cloud Foundry (CF) or Kubernetes (K8s).

An example Cloud Foundry deployment can be found in test/_env:

  • cd test/_env:
  • npm run cf:push
    • Prepares modules approuter and backend in test/_env and pushes to Cloud Foundry
      • Approuter performs authentication flow with XSUAA and forwards to backend
      • Backend serves endpoints (websocket, odata) and UI apps (runs on an in-memory SQLite3 database)

In deployed infrastructure, websocket protocol is exposed via Web Socket Secure (WSS) at wss:// over an encrypted TLS connection. For WebSocket standard the following setup in browser environment is recommended to cover deployed and local use-case:

const protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
const socket = new WebSocket(protocol + window.location.host + "/ws/chat");

Support, Feedback, Contributing

This project is open to feature requests/suggestions, bug reports etc. via GitHub issues. Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our Contribution Guidelines.

Code of Conduct

We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its Code of Conduct at all times.

Licensing

Copyright 2023 SAP SE or an SAP affiliate company and websocket contributors. Please see our LICENSE for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available via the REUSE tool.

Keywords

FAQs

Package last updated on 11 Jan 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc