janus-api

@techteamer/janus-api — a thin JavaScript client library for the Janus WebRTC Gateway by Meetecho. It speaks Janus’s JSON-over-WebSocket signalling protocol and exposes one class per Janus plugin (videoroom, streaming, videocall, echotest, recordplay). It is isomorphic — designed to run both server-side (Node ≥18) and in the browser (isomorphic-ws abstracts the WebSocket).

This is a published npm package, not a deployable service.

It has no server, no bin/, no Dockerfile, no main process. Other FaceKom services import it as a dependency to talk to a Janus gateway. The repo’s only runtime entrypoints are the published ESM/CJS bundles and a set of browser demo pages under test/.

  • Package name: @techteamer/janus-api (package.json:2)
  • Version: 7.0.1-beta.1 (package.json:3)
  • License: MIT (package.json:30)
  • Author: TechTeamer (package.json:29)
  • Upstream: git+https://github.com/TechTeamer/janus-api.git (package.json:23)
  • Base branch: master (only branch present; HEAD 5c694a9, “ci: [fkitdev-8448] use github actions (#38)”, 2026-05-19)

README is nearly empty.

README.md is 4 lines (title + a chrome://flags/#allow-insecure-localhost localhost-testing tip). Purpose below is reconstructed from package.json, index.js, and source — not from README prose.

Purpose & role in FaceKom

The library wraps the Janus signalling lifecycle so callers don’t hand-roll the protocol:

  • Open a WebSocket to Janus, perform the create session handshake, run keepalives, and demultiplex async events back to the right plugin handle (src/Janus.js).
  • Attach/detach plugin handles and route Janus messages (event, webrtcup, hangup, media, slowlink, error, …) to per-plugin objects (src/Janus.js:220-398).
  • Provide high-level methods per Janus plugin: join/configure a videoroom, publish/subscribe, RTP-forward to ffmpeg, watch a stream, place a video call, run echo/recordplay tests.
  • Munge SDP: filter H.264 profiles and strip “direct” (host/srflx) ICE candidates to force TURN relaying (src/SdpHelper.js).
  • Drive the Janus Admin API over a separate protocol for session/handle introspection (src/JanusAdmin.js).

Exactly which FaceKom service consumes this is not evidenced in this repo. Cross-repo wiring (which service imports @techteamer/janus-api) must be confirmed elsewhere — see "Unverified / gaps". Within FaceKom this is the video-conferencing/recording signalling layer; FaceKom Jira keys appear in history (FKITDEV-3756 ESM migration, FKITDEV-4638 old-iPhone bug, FKITDEV-8448 CI).

Tech stack & runtime

  • Language: plain ES2020+ JavaScript (.js, ES modules). No TypeScript .ts source — TS is used only as the build compiler to emit dual CJS/ESM output and .d.ts declarations (allowJs: true in tsconfig-base.json:3).
  • Module system: "type": "module" (package.json:34); source uses import/export.
  • Runtime target: Node >=18 (package.json:18-20). CHANGELOG marks the ESM migration + Node-18 floor as the 7.0.0 breaking change.
  • Package manager: Yarn (classic, v1)yarn.lock present (102.8 KB), CI runs yarn install --frozen-lockfile (.github/workflows/pull-request.yaml:34). The maintenance/security logs were produced by yarn v1.22.22.
  • Key runtime deps (package.json:36-42): isomorphic-ws@^4.0.1 (WS abstraction), ws@^8.2.0 (Node WS impl), sdp@^3.0.1 (SDP parsing), uuid@^8.2.0 (transaction IDs), improved-yarn-audit@^3.0.4.
  • Key dev deps (package.json:43-56): typescript@^5.4.5 (build), esbuild@^0.28.0 (bundles browser demos), eslint@^8.8.0 + eslint-config-standard + typescript-eslint (lint = the “test”), http-server@^14.1.0 (serves demos), webrtc-adapter@^8.1.1 (browser shim for demos).

Build & run

package.json scripts (verbatim — package.json:13-17)

ScriptCommandWhat it does
testeslint .There is no unit-test runner. yarn test only lints the repo with ESLint.
build-testnode ./test/buildtest.jsBundles the six browser demo apps with esbuild and serves ./test/ over HTTPS on :8080 (see Tests).
buildrm -fr dist/* && tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && ./fixup.shClears dist/, compiles ESM → dist/mjs and CJS → dist/cjs via two tsconfigs, then runs fixup.sh.

Entrypoints

  • Source aggregator: index.js — re-exports every public class (named + a default object) (index.js:12-37).
  • Published main (CJS): dist/cjs/index.js (package.json:5).
  • Published module (ESM): dist/mjs/index.js (package.json:6).
  • Conditional exports (package.json:7-12): import./dist/mjs/index.js, require./dist/cjs/index.js.
  • No bin field, no CLI. dist/ is git-ignored (.gitignore:5) and absent from this source-only clone — you must yarn build to produce it.

Build mechanics

  • tsconfig-base.json is the shared base: allowJs, declaration: true (emits .d.ts), strict: true, types: ["node"], include: ["src", "index.js"], exclude: ["node_modules", "dist"].
  • tsconfig.json → ESM: module/target: esnext, outDir: dist/mjs (tsconfig.json).
  • tsconfig-cjs.json → CJS: module: commonjs, target: es2022, outDir: dist/cjs (tsconfig-cjs.json).
  • fixup.sh writes dist/cjs/package.json ({"type":"commonjs"}) and dist/mjs/package.json ({"type":"module"}) so Node resolves each subtree with the correct module type despite the root being "type":"module" (fixup.sh).

No Dockerfile and no start command exist in this repo.

Verified: no Dockerfile, no docker-compose, no bin/, no scripts/start. The only first-party shell script is fixup.sh (a build step, not an entrypoint).

Top-level structure

janus-api/
├── index.js                    # public API barrel — re-exports all classes
├── package.json                # manifest (name, scripts, deps, engines)
├── src/                        # library source: 5 top-level files + plugin/ subdir (11 .js total)
│   ├── Janus.js                # core session: WS connect, transactions, event demux
│   ├── JanusAdmin.js           # Janus Admin API client (extends Janus)
│   ├── JanusPlugin.js          # abstract plugin base (EventEmitter)
│   ├── SdpHelper.js            # SDP munging (H264 filter, direct-candidate filter)
│   ├── Config.js               # JanusConfig / JanusAdminConfig / JanusRoomConfig DTOs
│   └── plugin/                 # one class per Janus plugin (6 files)
│       ├── VideoRoomPublisherJanusPlugin.js
│       ├── VideoRoomListenerJanusPlugin.js
│       ├── StreamingJanusPlugin.js
│       ├── VideoCallPlugin.js
│       ├── EchoJanusPlugin.js
│       └── RecordPlayJanusPlugin.js
├── test/                       # browser demo apps (NOT unit tests) + buildtest harness
│   ├── buildtest.js            # esbuild bundler + https://:8080 dev server
│   ├── common.js               # shared janus + peerConnection demo config
│   └── {echo,recordplay,streaming,videocall,videoroom}/  # *.js + index.html per demo
├── maintenance/                # dated dependency-update audit logs (.md)
├── security/                   # dated `yarn audit` logs (.md)
├── .github/workflows/          # pull-request.yaml CI (lint, audit, sonar, build)
├── tsconfig*.json              # dual ESM/CJS TypeScript build configs
├── fixup.sh                    # post-build: stamp module type into dist subdirs
├── .eslintrc.json              # lint config (= the test gate)
├── sonar-project.properties    # SonarCloud project metadata
├── CHANGELOG.md                # hand-maintained changelog
└── README.md                   # 4 lines (title + localhost tip)

(verified by git ls-tree -r HEAD + sampling; node_modules, dist absent/ignored.)

Helper scripts under bin/

None. There is no first-party bin/ directory and no package.json "bin" field.

The only standalone executable scripts in the repo are fixup.sh (build post-step, documented above) and test/buildtest.js (demo bundler/server, documented under Tests). Both are covered above.

Key modules / entities

src/Janus.js — core session manager

The heart of the library. A Janus instance wraps one WebSocket session.

  • constructor(config, logger) — takes a JanusConfig-shaped object and an injected logger (any object with .error/.warn/.debug; the demos pass console). Sets protocol = 'janus-protocol', sendCreate = true (Janus.js:10-23).
  • connect() — opens WS with subprotocol + config.options, on open sends {janus:'create'} unless sendCreate is false, stores sessionId, starts keepalive (Janus.js:25-83).
  • transaction(type, payload, replyType='ack', timeoutMs) — assigns a uuid transaction id, registers a resolver keyed by id+replyType, optionally rejects after timeoutMs (Janus.js:110-137).
  • addPlugin(plugin) / destroyPlugin(plugin)attach/detach a JanusPlugin handle, tracked in pluginHandles by Janus handle id (Janus.js:90-108, 194-218).
  • onMessage() — the big dispatcher: matches json.janus against keepalive/ack/success/webrtcup/hangup/detached/media/slowlink/error/event and forwards to the owning plugin handle or resolves the pending transaction (Janus.js:220-398).
  • keepAlive() — recursively setTimeouts a keepalive transaction every config.keepAliveIntervalMs (Janus.js:409-428).
  • cleanup() — tears down plugins, WS, and rejects all pending transactions; invoked on WS close (Janus.js:442-477).
  • Ignores Janus error codes 458 (session not found) and 459 (handle not found) when logging (Janus.js:5-8).

src/JanusAdmin.js — Admin API client

Extends Janus but sets protocol = 'janus-admin-protocol' and sendCreate = false (no session create on connect) (JanusAdmin.js:9-14). Overrides transaction() to inject admin_secret: config.secret and default replyType to 'success' (JanusAdmin.js:16-21). Exposes listSessions(), listHandles(sessionId), handleInfo(sessionId, handleId) (JanusAdmin.js:23-38).

src/JanusPlugin.js — abstract plugin base

Extends Node’s EventEmitter. Holds id (uuid → sent as opaque_id), janus, janusHandleId, pluginName (JanusPlugin.js:4-13). transaction() proxies to the parent Janus adding handle_id (JanusPlugin.js:19-27). Defines the event-bridge no-ops the core calls back into: hangup, slowLink, mediaState, webrtcState (emit events) and lifecycle stubs success/error/onmessage/oncleanup/detached/detach (JanusPlugin.js:29-73).

src/SdpHelper.js — SDP munging

  • filterH264Profiles(sdp, allowedProfiles) — rewrites the video m-section keeping only H264 rtpmaps whose profile-level-id matches an allowed value (array | string | RegExp) (SdpHelper.js:12-65).
  • filterDirectCandidates(sdp, force=false) — drops a=candidate lines whose type is host/srflx (forces relay/TURN); refuses to strip if it would leave no candidates unless force (SdpHelper.js:67-96).
  • isDirectCandidate(line) — type ∈ {host, srflx} via sdp.parseCandidate (SdpHelper.js:98-101).

src/Config.js — config DTOs

Three plain data classes (Config.js):

  • JanusConfig{ url, keepAliveIntervalMs, options } (:1-9).
  • JanusAdminConfig extends JanusConfig — adds { secret, sessionListIntervalMs } (:11-19).
  • JanusRoomConfig — ~30 videoroom fields (id, codec, record, bitrate, publishers, secret, pin, requireE2ee, …) with toJanusConfig() mapping camelCase → Janus snake_case body keys (e.g. firSeconds → fir_freq, recordDirectory → rec_dir, requireE2ee → require_e2ee), emitting only set fields (:21-165). Inline comments document Janus defaults.

src/plugin/* — Janus plugin wrappers (each sets a fixed pluginName)

  • VideoRoomPublisherJanusPlugin (pluginName: janus.plugin.videoroom) — the richest plugin. connect() lists rooms and joins-or-creates by matching description === String(config.id) (:146-165); createRoom() builds the body from config.toJanusConfig() (:192-209); configure(offer,…) exchanges SDP and stores offerSdp/answerSdp (:211-241); startRTPForward/stopRTPForward push RTP to an external host (with an ffmpeg invocation documented in the JSDoc) and synthesize an RTP SDP file (:35-131); setRoomBitrate, listParticipants, candidate (trickle), onmessage emits remoteMemberUnpublished/remoteMemberLeaving/publishersUpdated/keyframe/talking/stoppedTalking/slowlink (:251-315).
  • VideoRoomListenerJanusPlugin (janus.plugin.videoroom, ptype: subscriber) — join() subscribes to a remote feed and retries up to 10× at 1 s on failure (:34-74); setAnswer() starts playback; hangup() self-destroys the plugin (:108-113); emits jsep/updated.
  • StreamingJanusPlugin (janus.plugin.streaming) — create/destroy/list/watch/start/stop/pause/info/switch over the streaming plugin; emits statusChange + jsep. JSDoc enumerates rtp/rtsp/live/ondemand params (:13-39).
  • VideoCallPlugin (janus.plugin.videocall) — onlineList/register/doCall/doAnswer/configure; emits accepted/incomingcall; hangup() self-destroys.
  • EchoJanusPlugin (janus.plugin.echotest) — connect/mute/consume; self-destroys on result === 'done'. Used by the echo demo.
  • RecordPlayJanusPlugin (janus.plugin.recordplay) — configure(videoBitrateMax, videoKeyframeInterval) + consume (record/stop); emits recordingId/jsep.

Configuration

No config loader, no AJV/getconfig schema, no .env.

The library is dependency-injected: callers construct JanusConfig/JanusAdminConfig/JanusRoomConfig (src/Config.js) and pass them plus a logger into the constructors. There are no environment variables read anywhere in src/.

The only concrete config values in the repo are demo values in test/common.js (not production):

  • janus.url = 'wss://localhost:8989', keepAliveIntervalMs = 30000, options.rejectUnauthorized = false, filterDirectCandidates = true, recordDirectory = '/workspace/records/', bitrate = 774144, firSeconds = 10, publishers = 20 (test/common.js:1-12).
  • peerConnectionConfig.iceServers points at turnserver.techteamer.com:443 (STUN + TURN udp/tcp, demo creds demo/secret) (test/common.js:13-19).

Lint config (.eslintrc.json): extends standard + plugin:n/recommended + @typescript-eslint/stylistic; notable rules no-console: error, no-debugger: error, radix: error, no-empty-function: error; ignores dist/ and *.d.ts.

Tests

There are no automated unit/integration tests.

yarn test runs eslint . only (package.json:14). The test/ directory contains manual browser demos, not a test suite, and is excluded from the npm package (.npmignore:6) and lint scope (.eslintignore:2, .gitignore:4 ignore the generated *.bundle.js).

  • Demo harness: test/buildtest.js esbuild-bundles six browser apps (echo, recordplay, videoroom/publisher, videoroom/listener, streaming, videocall) into *.bundle.js, then serves ./test/ over HTTPS on :8080 using certs at /workspace/cert/dev.{key,crt} (test/buildtest.js:15-29). Run via yarn build-test.
  • Demo apps: each test/<feature>/<feature>.js + index.html opens a real RTCPeerConnection against the configured Janus and exercises one plugin. Example: test/echo/echo.js connects, attaches EchoJanusPlugin, does getUserMedia → offer → echo.consume, sets the remote answer (test/echo/echo.js). They import webrtc-adapter and ../common.js.

Demo harness hard-codes absolute paths.

test/buildtest.js requires TLS certs at /workspace/cert/dev.key|crt, and test/common.js references /workspace/records/. These /workspace/... paths imply the demos were meant to run inside a specific container/CI image; they will fail elsewhere without those files.

CI (.github/workflows/pull-request.yaml)

Runs on every pull_request (Node 24 via env.NODE_VERSION, despite the engines floor of 18). Jobs:

  1. lintyarn install --frozen-lockfile then yarn test (ESLint).
  2. audityarn audit --groups dependencies then improved-yarn-audit --min-severity critical.
  3. sonarSonarSource/sonarqube-scan-action@v6 (project TechTeamer_janus-api, org techteamer, per sonar-project.properties).
  4. buildyarn build (needs lint+audit+sonar).

The committed CI is the only workflow; it migrated to GitHub Actions in the HEAD commit ( 5c694a9, FKITDEV-8448). No publish/release workflow is present in the repo.

Dependencies on other FaceKom services

None are wired into this code.

Grep/read of src/ shows no RabbitMQ, no HTTP REST client, no Redis, no SQL/DB — and no imports of other @techteamer/* packages. The library’s only external contract is a WebSocket to a Janus Gateway (URL supplied by the caller via JanusConfig.url). The demo config points at wss://localhost:8989 (Janus default WS port) and turnserver.techteamer.com (TURN/STUN), but those are demo constants, not service bindings.

The real integration is inbound: other FaceKom services depend on this package to drive Janus. That direction is not provable from this repo.

Maintenance & security logs (in-repo)

maintenance/ and security/ hold dated markdown audit snapshots (2026-04-07.md, 2026-05-04.md):

  • Maintenance (maintenance/2026-05-04.md): tracks intentionally-pinned outdated deps (e.g. uuid 8.3.2 vs latest 14, eslint 8 vs 10, typescript 5.9 vs 6) — each marked “Update scheduled”.
  • Security (security/2026-05-04.md): one moderate advisory — uuid missing buffer bounds check in v3/v5/v6 (patched in ≥14.0.0, advisory 1116970), marked “Replace is scheduled”.

Verified gotchas (summary)

Quick reference

  • Not a service — published library; no server/Dockerfile/bin/start. Build with yarn build; dist/ is git-ignored & not in this clone.
  • yarn test = lint only. No unit tests exist.
  • Yarn v1 (classic), not npm/pnpm — yarn.lock is the lockfile.
  • Logger is injected, not created — every class needs a logger arg (demos pass console, which would actually violate the repo’s own no-console lint rule if used in src/).
  • fixup.sh is mandatory after tsc — without it Node mis-resolves CJS vs ESM in dist/.
  • /workspace/... absolute paths in test/ (certs, records dir) make demos environment-specific.
  • uuid@8 carries a known moderate advisory (replacement scheduled).

Unverified / gaps

  • Consumers: which FaceKom repo/service imports @techteamer/janus-api is not determinable from this repo — confirm in the consuming service (architecture-overview).
  • Published dist/ contents: dist/cjs/* and dist/mjs/* are git-ignored and absent in this source-only clone; described from the build config, not from emitted artifacts.
  • yarn.lock (102.8 KB) was not read line-by-line — only the declared dependencies/devDependencies ranges in package.json and the version snapshots in the maintenance log are quoted.
  • Per-demo subtrees (test/recordplay, test/streaming, test/videocall, test/videoroom): surveyed structurally (each has <name>.js + index.html, bundled identically by buildtest.js); only test/echo/echo.js and test/common.js were read line-by-line as the representative sample.
  • LFS / binaries: none — no .gitattributes, no tracked binary/model/pointer files (verified via git ls-files). Nothing to flag.
  • .github/: only workflows/pull-request.yaml exists; no other workflow/template files tracked.

Sources

  • package.json, index.js, README.md, CHANGELOG.md, fixup.sh
  • tsconfig.json, tsconfig-base.json, tsconfig-cjs.json
  • .eslintrc.json, .eslintignore, .gitignore, .npmignore, sonar-project.properties
  • .github/workflows/pull-request.yaml
  • src/Janus.js, src/JanusAdmin.js, src/JanusPlugin.js, src/SdpHelper.js, src/Config.js
  • src/plugin/VideoRoomPublisherJanusPlugin.js, src/plugin/VideoRoomListenerJanusPlugin.js, src/plugin/StreamingJanusPlugin.js, src/plugin/VideoCallPlugin.js, src/plugin/EchoJanusPlugin.js, src/plugin/RecordPlayJanusPlugin.js
  • test/buildtest.js, test/common.js, test/echo/echo.js
  • maintenance/2026-05-04.md, security/2026-05-04.md
  • git -C … log/ls-tree/ls-files/branch (HEAD 5c694a9, branch master)