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 undertest/.
- 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; HEAD5c694a9, “ci: [fkitdev-8448] use github actions (#38)”, 2026-05-19)
README is nearly empty.
README.mdis 4 lines (title + achrome://flags/#allow-insecure-localhostlocalhost-testing tip). Purpose below is reconstructed frompackage.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
createsession 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-3756ESM migration,FKITDEV-4638old-iPhone bug,FKITDEV-8448CI).
Tech stack & runtime
- Language: plain ES2020+ JavaScript (
.js, ES modules). No TypeScript.tssource — TS is used only as the build compiler to emit dual CJS/ESM output and.d.tsdeclarations (allowJs: trueintsconfig-base.json:3). - Module system:
"type": "module"(package.json:34); source usesimport/export. - Runtime target: Node
>=18(package.json:18-20). CHANGELOG marks the ESM migration + Node-18 floor as the7.0.0breaking change. - Package manager: Yarn (classic, v1) —
yarn.lockpresent (102.8 KB), CI runsyarn install --frozen-lockfile(.github/workflows/pull-request.yaml:34). The maintenance/security logs were produced byyarn 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)
| Script | Command | What it does |
|---|---|---|
test | eslint . | There is no unit-test runner. yarn test only lints the repo with ESLint. |
build-test | node ./test/buildtest.js | Bundles the six browser demo apps with esbuild and serves ./test/ over HTTPS on :8080 (see Tests). |
build | rm -fr dist/* && tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && ./fixup.sh | Clears 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
binfield, no CLI.dist/is git-ignored (.gitignore:5) and absent from this source-only clone — you mustyarn buildto produce it.
Build mechanics
tsconfig-base.jsonis 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.shwritesdist/cjs/package.json({"type":"commonjs"}) anddist/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
startcommand exist in this repo.Verified: no
Dockerfile, nodocker-compose, nobin/, noscripts/start. The only first-party shell script isfixup.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 nopackage.json"bin"field.The only standalone executable scripts in the repo are
fixup.sh(build post-step, documented above) andtest/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 aJanusConfig-shaped object and an injectedlogger(any object with.error/.warn/.debug; the demos passconsole). Setsprotocol = 'janus-protocol',sendCreate = true(Janus.js:10-23).connect()— opens WS with subprotocol +config.options, on open sends{janus:'create'}unlesssendCreateis false, storessessionId, starts keepalive (Janus.js:25-83).transaction(type, payload, replyType='ack', timeoutMs)— assigns auuidtransaction id, registers a resolver keyed by id+replyType, optionally rejects aftertimeoutMs(Janus.js:110-137).addPlugin(plugin)/destroyPlugin(plugin)—attach/detachaJanusPluginhandle, tracked inpluginHandlesby Janus handle id (Janus.js:90-108,194-218).onMessage()— the big dispatcher: matchesjson.janusagainstkeepalive/ack/success/webrtcup/hangup/detached/media/slowlink/error/eventand forwards to the owning plugin handle or resolves the pending transaction (Janus.js:220-398).keepAlive()— recursivelysetTimeouts akeepalivetransaction everyconfig.keepAliveIntervalMs(Janus.js:409-428).cleanup()— tears down plugins, WS, and rejects all pending transactions; invoked on WSclose(Janus.js:442-477).- Ignores Janus error codes
458(session not found) and459(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 thevideom-section keeping only H264 rtpmaps whoseprofile-level-idmatches an allowed value (array | string | RegExp) (SdpHelper.js:12-65).filterDirectCandidates(sdp, force=false)— dropsa=candidatelines whose type is host/srflx (forces relay/TURN); refuses to strip if it would leave no candidates unlessforce(SdpHelper.js:67-96).isDirectCandidate(line)— type ∈ {host, srflx} viasdp.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, …) withtoJanusConfig()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 matchingdescription === String(config.id)(:146-165);createRoom()builds the body fromconfig.toJanusConfig()(:192-209);configure(offer,…)exchanges SDP and storesofferSdp/answerSdp(:211-241);startRTPForward/stopRTPForwardpush 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),onmessageemitsremoteMemberUnpublished/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); emitsjsep/updated.StreamingJanusPlugin(janus.plugin.streaming) —create/destroy/list/watch/start/stop/pause/info/switchover the streaming plugin; emitsstatusChange+jsep. JSDoc enumerates rtp/rtsp/live/ondemand params (:13-39).VideoCallPlugin(janus.plugin.videocall) —onlineList/register/doCall/doAnswer/configure; emitsaccepted/incomingcall;hangup()self-destroys.EchoJanusPlugin(janus.plugin.echotest) —connect/mute/consume; self-destroys onresult === 'done'. Used by the echo demo.RecordPlayJanusPlugin(janus.plugin.recordplay) —configure(videoBitrateMax, videoKeyframeInterval)+consume(record/stop); emitsrecordingId/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 aloggerinto the constructors. There are no environment variables read anywhere insrc/.
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.iceServerspoints atturnserver.techteamer.com:443(STUN + TURN udp/tcp, demo credsdemo/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 testrunseslint .only (package.json:14). Thetest/directory contains manual browser demos, not a test suite, and is excluded from the npm package (.npmignore:6) and lint scope (.eslintignore:2,.gitignore:4ignore the generated*.bundle.js).
- Demo harness:
test/buildtest.jsesbuild-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 viayarn build-test. - Demo apps: each
test/<feature>/<feature>.js+index.htmlopens a realRTCPeerConnectionagainst the configured Janus and exercises one plugin. Example:test/echo/echo.jsconnects, attachesEchoJanusPlugin, does getUserMedia → offer →echo.consume, sets the remote answer (test/echo/echo.js). They importwebrtc-adapterand../common.js.
Demo harness hard-codes absolute paths.
test/buildtest.jsrequires TLS certs at/workspace/cert/dev.key|crt, andtest/common.jsreferences/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:
- lint —
yarn install --frozen-lockfilethenyarn test(ESLint). - audit —
yarn audit --groups dependenciesthenimproved-yarn-audit --min-severity critical. - sonar —
SonarSource/sonarqube-scan-action@v6(projectTechTeamer_janus-api, orgtechteamer, personar-project.properties). - build —
yarn 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 viaJanusConfig.url). The demo config points atwss://localhost:8989(Janus default WS port) andturnserver.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.2vs latest14,eslint 8vs10,typescript 5.9vs6) — each marked “Update scheduled”. - Security (
security/2026-05-04.md): one moderate advisory —uuidmissing 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.lockis the lockfile.- Logger is injected, not created — every class needs a
loggerarg (demos passconsole, which would actually violate the repo’s ownno-consolelint rule if used insrc/).fixup.shis mandatory aftertsc— without it Node mis-resolves CJS vs ESM indist/./workspace/...absolute paths intest/(certs, records dir) make demos environment-specific.uuid@8carries a known moderate advisory (replacement scheduled).
Unverified / gaps
- Consumers: which FaceKom repo/service imports
@techteamer/janus-apiis not determinable from this repo — confirm in the consuming service (architecture-overview). - Published
dist/contents:dist/cjs/*anddist/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 declareddependencies/devDependenciesranges inpackage.jsonand 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 bybuildtest.js); onlytest/echo/echo.jsandtest/common.jswere read line-by-line as the representative sample. - LFS / binaries: none — no
.gitattributes, no tracked binary/model/pointer files (verified viagit ls-files). Nothing to flag. .github/: onlyworkflows/pull-request.yamlexists; no other workflow/template files tracked.
Sources
package.json,index.js,README.md,CHANGELOG.md,fixup.shtsconfig.json,tsconfig-base.json,tsconfig-cjs.json.eslintrc.json,.eslintignore,.gitignore,.npmignore,sonar-project.properties.github/workflows/pull-request.yamlsrc/Janus.js,src/JanusAdmin.js,src/JanusPlugin.js,src/SdpHelper.js,src/Config.jssrc/plugin/VideoRoomPublisherJanusPlugin.js,src/plugin/VideoRoomListenerJanusPlugin.js,src/plugin/StreamingJanusPlugin.js,src/plugin/VideoCallPlugin.js,src/plugin/EchoJanusPlugin.js,src/plugin/RecordPlayJanusPlugin.jstest/buildtest.js,test/common.js,test/echo/echo.jsmaintenance/2026-05-04.md,security/2026-05-04.mdgit -C … log/ls-tree/ls-files/branch(HEAD5c694a9, branchmaster)