├── .changeset
├── angry-bags-relax.md
├── blue-actors-change.md
├── chatty-feet-train.md
├── cold-beds-guess.md
├── config.json
├── easy-ravens-rhyme.md
├── late-snails-care.md
├── legal-ways-pick.md
├── lucky-hands-obey.md
├── moody-nails-begin.md
├── plain-steaks-wash.md
├── plenty-banks-dress.md
├── rich-bugs-own.md
├── seven-owls-run.md
├── small-wasps-carry.md
├── soft-shoes-bathe.md
├── spotty-showers-cough.md
├── tasty-buckets-check.md
├── tasty-dodos-study.md
├── witty-nights-vanish.md
└── yummy-mice-sing.md
├── .editorconfig
├── .github
├── actions
│ ├── build
│ │ └── action.yml
│ ├── install
│ │ └── action.yml
│ ├── playwright
│ │ └── action.yml
│ ├── report-test-result
│ │ └── action.yml
│ ├── setup-js-app
│ │ └── action.yml
│ ├── setup-node-app
│ │ └── action.yml
│ └── zrok
│ │ └── action.yml
├── pull_request_template.md
└── workflows
│ ├── browser-js-production.yml
│ ├── browser-js-staging.yml
│ ├── realtime-api-production.yml
│ ├── realtime-api-staging.yml
│ ├── release-dev.yml
│ ├── unit-tests.yml
│ └── update-playgrounds.yml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── babel.config.js
├── internal
├── e2e-js
│ ├── .env.example
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── assets
│ │ └── sw-docs.y4m
│ ├── fixtures.ts
│ ├── global-setup.ts
│ ├── package.json
│ ├── playwright.config.ts
│ ├── templates
│ │ └── blank
│ │ │ ├── index.html
│ │ │ ├── index.js
│ │ │ └── v2vanilla.html
│ ├── tests
│ │ ├── buildVideoWithFabricSDK.spec.ts
│ │ ├── buildVideoWithVideoSDK.spec.ts
│ │ ├── callfabric
│ │ │ ├── address.spec.ts
│ │ │ ├── agent_customer.spec.ts
│ │ │ ├── cleanup.spec.ts
│ │ │ ├── conversation.spec.ts
│ │ │ ├── deviceEvent.spec.ts
│ │ │ ├── deviceState.spec.ts
│ │ │ ├── holdunhold.spec.ts
│ │ │ ├── mirrorVideo.spec.ts
│ │ │ ├── mirrorVideo.spec.ts-snapshots
│ │ │ │ ├── video-flipped.png
│ │ │ │ └── video-normal.png
│ │ │ ├── raiseHand.spec.ts
│ │ │ ├── reattach.spec.ts
│ │ │ ├── relayApp.spec.ts
│ │ │ ├── renegotiateAudio.spec.ts
│ │ │ ├── renegotiateVideo.spec.ts
│ │ │ ├── swml.spec.ts
│ │ │ ├── videoRoom.spec.ts
│ │ │ ├── videoRoomLayout.spec.ts
│ │ │ └── websocket_reconnect.spec.ts
│ │ ├── chat.spec.ts
│ │ ├── pubSub.spec.ts
│ │ ├── roomSession.spec.ts
│ │ ├── roomSessionAudienceCount.spec.ts
│ │ ├── roomSessionAutomaticStream.spec.ts
│ │ ├── roomSessionBadNetwork.spec.ts
│ │ ├── roomSessionCleanup.spec.ts
│ │ ├── roomSessionDemote.spec.ts
│ │ ├── roomSessionDemoteAudience.spec.ts
│ │ ├── roomSessionDemotePromote.spec.ts
│ │ ├── roomSessionDemoteReattachPromote.spec.ts
│ │ ├── roomSessionDevices.spec.ts
│ │ ├── roomSessionFollowLeader.spec.ts
│ │ ├── roomSessionJoinFrom.spec.ts
│ │ ├── roomSessionJoinUntil.spec.ts
│ │ ├── roomSessionLocalStream.spec.ts
│ │ ├── roomSessionLockUnlock.spec.ts
│ │ ├── roomSessionMaxMembers.spec.ts
│ │ ├── roomSessionMethodsOnNonExistingMembers.spec.ts
│ │ ├── roomSessionMultipleStreams.spec.ts
│ │ ├── roomSessionPromoteDemote.spec.ts
│ │ ├── roomSessionPromoteMeta.spec.ts
│ │ ├── roomSessionPromoteParticipant.spec.ts
│ │ ├── roomSessionPromoteReattachDemote.spec.ts
│ │ ├── roomSessionRaiseHand.spec.ts
│ │ ├── roomSessionReattach.spec.ts
│ │ ├── roomSessionReattachBadAuth.spec.ts
│ │ ├── roomSessionReattachMultiple.spec.ts
│ │ ├── roomSessionReattachScreenshare.spec.ts
│ │ ├── roomSessionReattachWrongCallId.spec.ts
│ │ ├── roomSessionReattachWrongProtocol.spec.ts
│ │ ├── roomSessionRemoveAfterSecondsElapsed.spec.ts
│ │ ├── roomSessionRemoveAllMembers.spec.ts
│ │ ├── roomSessionRemoveAt.spec.ts
│ │ ├── roomSessionStreaming.spec.ts
│ │ ├── roomSessionStreamingAPI.spec.ts
│ │ ├── roomSessionTalkingEventsParticipant.spec.ts
│ │ ├── roomSessionTalkingEventsToAudience.spec.ts
│ │ ├── roomSessionUnauthorized.spec.ts
│ │ ├── roomSessionUpdateMedia.spec.ts
│ │ ├── roomSettings.spec.ts
│ │ └── v2Webrtc
│ │ │ ├── v2WebrtcFromRest.spec.ts
│ │ │ └── webrtcCalling.spec.ts
│ ├── tsconfig.json
│ └── utils.ts
├── e2e-realtime-api
│ ├── .env.example
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── global-setup.ts
│ ├── package.json
│ ├── playwright.config.ts
│ ├── src
│ │ ├── chat.test.ts
│ │ ├── messaging.test.ts
│ │ ├── playwright
│ │ │ ├── video.test.ts
│ │ │ ├── videoHandRaise.test.ts
│ │ │ └── videoUtils.ts
│ │ ├── pubSub.test.ts
│ │ ├── task.test.ts
│ │ ├── utils.ts
│ │ ├── voice.test.ts
│ │ ├── voiceCollect
│ │ │ ├── withAllListeners.test.ts
│ │ │ ├── withCallListeners.test.ts
│ │ │ ├── withCollectListeners.test.ts
│ │ │ ├── withContinuousFalsePartialFalse.test.ts
│ │ │ ├── withContinuousFalsePartialTrue&EarlyHangup.test.ts
│ │ │ ├── withContinuousFalsePartialTrue.test.ts
│ │ │ ├── withContinuousTruePartialFalse.test.ts
│ │ │ ├── withContinuousTruePartialTrue.test.ts
│ │ │ └── withDialListeners.test.ts
│ │ ├── voiceDetect
│ │ │ ├── withAllListeners.test.ts
│ │ │ ├── withCallListeners.test.ts
│ │ │ ├── withDetectListeners.test.ts
│ │ │ └── withDialListeners.test.ts
│ │ ├── voicePass.test.ts
│ │ ├── voicePlayback
│ │ │ ├── withAllListeners.test.ts
│ │ │ ├── withCallListeners.test.ts
│ │ │ ├── withDialListeners.test.ts
│ │ │ ├── withMultiplePlaybacks.test.ts
│ │ │ └── withPlaybackListeners.test.ts
│ │ ├── voicePrompt
│ │ │ ├── withAllListeners.test.ts
│ │ │ ├── withCallListeners.test.ts
│ │ │ ├── withDialListeners.test.ts
│ │ │ └── withPromptListeners.test.ts
│ │ ├── voiceRecord
│ │ │ ├── withAllListeners.test.ts
│ │ │ ├── withCallListeners.test.ts
│ │ │ ├── withDialListeners.test.ts
│ │ │ ├── withMultipleRecords.test.ts
│ │ │ └── withRecordListeners.test.ts
│ │ └── voiceTapAllListeners.test.ts
│ ├── templates
│ │ └── blank
│ │ │ ├── index.html
│ │ │ └── index.js
│ └── utils.ts
├── playground-js
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── example.env
│ ├── index.html
│ ├── package.json
│ ├── src
│ │ ├── chat
│ │ │ ├── index.html
│ │ │ └── index.js
│ │ ├── fabric-callee
│ │ │ ├── firebase-messaging-sw.js
│ │ │ ├── index.html
│ │ │ ├── index.js
│ │ │ └── sw.js
│ │ ├── fabric-http
│ │ │ ├── index.html
│ │ │ └── index.js
│ │ ├── fabric
│ │ │ ├── index.html
│ │ │ └── index.js
│ │ ├── pubSub
│ │ │ ├── index.html
│ │ │ └── index.js
│ │ ├── video
│ │ │ ├── index.html
│ │ │ └── index.js
│ │ └── videoManager
│ │ │ ├── index.html
│ │ │ └── index.js
│ └── vite.config.ts
├── playground-realtime-api
│ ├── .env.example
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── index.js
│ ├── package.json
│ └── src
│ │ ├── basic
│ │ └── index.ts
│ │ ├── chat
│ │ └── index.ts
│ │ ├── messaging
│ │ └── index.ts
│ │ ├── pubSub
│ │ └── index.ts
│ │ ├── task
│ │ └── index.ts
│ │ ├── voice-dtmf-loop
│ │ └── index.ts
│ │ ├── voice-inbound
│ │ └── index.ts
│ │ ├── voice
│ │ └── index.ts
│ │ └── with-events
│ │ └── index.ts
├── playground-swaig
│ ├── CHANGELOG.md
│ ├── index.js
│ ├── package.json
│ └── src
│ │ └── index.ts
└── stack-tests
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── index.js
│ ├── package.json
│ └── src
│ ├── chat
│ ├── app.ts
│ └── package.json
│ ├── close-event
│ ├── app.ts
│ └── package.json
│ ├── messaging
│ ├── app.ts
│ └── package.json
│ ├── pubSub
│ ├── app.ts
│ └── package.json
│ ├── task
│ ├── app.ts
│ └── package.json
│ ├── video
│ ├── app.ts
│ └── package.json
│ └── voice
│ ├── app.ts
│ └── package.json
├── package-lock.json
├── package.json
├── packages
├── compatibility-api
│ └── README.md
├── core
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ │ ├── BaseClient.ts
│ │ ├── BaseComponent.test.ts
│ │ ├── BaseComponent.ts
│ │ ├── BaseConsumer.test.ts
│ │ ├── BaseConsumer.ts
│ │ ├── BaseJWTSession.test.ts
│ │ ├── BaseJWTSession.ts
│ │ ├── BaseSession.test.ts
│ │ ├── BaseSession.ts
│ │ ├── CustomErrors.ts
│ │ ├── RPCMessages
│ │ │ ├── RPCConnect.ts
│ │ │ ├── RPCDisconnect.ts
│ │ │ ├── RPCEventAck.ts
│ │ │ ├── RPCExecute.ts
│ │ │ ├── RPCPing.ts
│ │ │ ├── RPCReauthenticate.ts
│ │ │ ├── VertoMessages.ts
│ │ │ ├── helpers.ts
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── chat
│ │ │ ├── BaseChat.ts
│ │ │ ├── ChatMember.ts
│ │ │ ├── ChatMessage.ts
│ │ │ ├── applyCommonMethods.ts
│ │ │ ├── index.ts
│ │ │ ├── methods.ts
│ │ │ ├── utils
│ │ │ │ ├── index.ts
│ │ │ │ ├── toInternalChatChannels.test.ts
│ │ │ │ └── toInternalChatChannels.ts
│ │ │ └── workers
│ │ │ │ ├── chatWorker.ts
│ │ │ │ └── index.ts
│ │ ├── index.test.ts
│ │ ├── index.ts
│ │ ├── memberPosition
│ │ │ ├── index.ts
│ │ │ ├── workers.test.ts
│ │ │ └── workers.ts
│ │ ├── pubSub
│ │ │ ├── BasePubSub.ts
│ │ │ ├── PubSubMessage.ts
│ │ │ ├── index.ts
│ │ │ └── workers
│ │ │ │ ├── index.ts
│ │ │ │ └── pubSubWorker.ts
│ │ ├── redux
│ │ │ ├── actions.ts
│ │ │ ├── connect.test.ts
│ │ │ ├── connect.ts
│ │ │ ├── features
│ │ │ │ ├── component
│ │ │ │ │ ├── componentSaga.test.ts
│ │ │ │ │ ├── componentSaga.ts
│ │ │ │ │ ├── componentSelectors.test.ts
│ │ │ │ │ ├── componentSelectors.ts
│ │ │ │ │ ├── componentSlice.test.ts
│ │ │ │ │ └── componentSlice.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── session
│ │ │ │ │ ├── sessionSaga.ts
│ │ │ │ │ ├── sessionSelectors.ts
│ │ │ │ │ ├── sessionSlice.test.ts
│ │ │ │ │ └── sessionSlice.ts
│ │ │ ├── index.ts
│ │ │ ├── interfaces.ts
│ │ │ ├── rootReducer.ts
│ │ │ ├── rootSaga.test.ts
│ │ │ ├── rootSaga.ts
│ │ │ ├── toolkit
│ │ │ │ ├── configureStore.ts
│ │ │ │ ├── createAction.ts
│ │ │ │ ├── createReducer.ts
│ │ │ │ ├── createSlice.ts
│ │ │ │ ├── devtoolsExtension.ts
│ │ │ │ ├── getDefaultMiddleware.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── isPlainObject.ts
│ │ │ │ ├── mapBuilders.ts
│ │ │ │ ├── tsHelpers.ts
│ │ │ │ └── utils.ts
│ │ │ └── utils
│ │ │ │ ├── createDestroyableSlice.test.ts
│ │ │ │ ├── createDestroyableSlice.ts
│ │ │ │ ├── sagaHelpers.ts
│ │ │ │ ├── useInstanceMap.ts
│ │ │ │ └── useSession.ts
│ │ ├── rooms
│ │ │ ├── RoomSessionPlayback.test.ts
│ │ │ ├── RoomSessionPlayback.ts
│ │ │ ├── RoomSessionRecording.test.ts
│ │ │ ├── RoomSessionRecording.ts
│ │ │ ├── RoomSessionStream.test.ts
│ │ │ ├── RoomSessionStream.ts
│ │ │ ├── index.ts
│ │ │ ├── methods.test.ts
│ │ │ └── methods.ts
│ │ ├── setupTests.ts
│ │ ├── testUtils.ts
│ │ ├── types
│ │ │ ├── cantina.ts
│ │ │ ├── chat.ts
│ │ │ ├── common.ts
│ │ │ ├── conversation.ts
│ │ │ ├── fabric.ts
│ │ │ ├── fabricLayout.ts
│ │ │ ├── fabricMember.ts
│ │ │ ├── fabricRoomSession.ts
│ │ │ ├── index.ts
│ │ │ ├── messaging.ts
│ │ │ ├── pubSub.ts
│ │ │ ├── task.ts
│ │ │ ├── utils.ts
│ │ │ ├── video.ts
│ │ │ ├── videoLayout.ts
│ │ │ ├── videoMember.ts
│ │ │ ├── videoPlayback.ts
│ │ │ ├── videoRecording.ts
│ │ │ ├── videoRoomDevice.ts
│ │ │ ├── videoRoomSession.ts
│ │ │ ├── videoStream.ts
│ │ │ ├── voice.ts
│ │ │ ├── voiceCall.ts
│ │ │ ├── voiceCollect.ts
│ │ │ ├── voiceConnect.ts
│ │ │ ├── voiceDetect.ts
│ │ │ ├── voicePlayback.ts
│ │ │ ├── voicePrompt.ts
│ │ │ ├── voiceRecording.ts
│ │ │ ├── voiceSendDigits.ts
│ │ │ └── voiceTap.ts
│ │ ├── utils
│ │ │ ├── EventEmitter.test.ts
│ │ │ ├── EventEmitter.ts
│ │ │ ├── SWCloseEvent.ts
│ │ │ ├── asyncRetry.test.ts
│ │ │ ├── asyncRetry.ts
│ │ │ ├── common.ts
│ │ │ ├── constants.ts
│ │ │ ├── debounce.ts
│ │ │ ├── eventUtils.ts
│ │ │ ├── extendComponent.test.ts
│ │ │ ├── extendComponent.ts
│ │ │ ├── index.test.ts
│ │ │ ├── index.ts
│ │ │ ├── interfaces.ts
│ │ │ ├── logger.test.ts
│ │ │ ├── logger.ts
│ │ │ ├── parseRPCResponse.test.ts
│ │ │ ├── parseRPCResponse.ts
│ │ │ ├── storage
│ │ │ │ ├── index.native.ts
│ │ │ │ └── index.ts
│ │ │ ├── toExternalJSON.test.ts
│ │ │ ├── toExternalJSON.ts
│ │ │ ├── toInternalAction.test.ts
│ │ │ ├── toInternalAction.ts
│ │ │ ├── toInternalEventName.test.ts
│ │ │ ├── toInternalEventName.ts
│ │ │ ├── toSnakeCaseKeys.test.ts
│ │ │ └── toSnakeCaseKeys.ts
│ │ └── workers
│ │ │ ├── executeActionWorker.ts
│ │ │ └── index.ts
│ └── tsconfig.build.json
├── js
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ │ ├── BaseRoomSession.ts
│ │ ├── Client.ts
│ │ ├── JWTSession.test.ts
│ │ ├── JWTSession.ts
│ │ ├── RoomSessionDevice.test.ts
│ │ ├── RoomSessionDevice.ts
│ │ ├── RoomSessionScreenShare.test.ts
│ │ ├── RoomSessionScreenShare.ts
│ │ ├── VideoOverlays.ts
│ │ ├── buildVideoElement.test.ts
│ │ ├── buildVideoElement.ts
│ │ ├── cantina
│ │ │ ├── VideoManager.test.ts
│ │ │ ├── VideoManager.ts
│ │ │ ├── index.ts
│ │ │ └── workers
│ │ │ │ ├── index.ts
│ │ │ │ ├── videoManagerRoomWorker.ts
│ │ │ │ ├── videoManagerRoomsWorker.ts
│ │ │ │ └── videoManagerWorker.ts
│ │ ├── chat
│ │ │ ├── Client.test.ts
│ │ │ ├── Client.ts
│ │ │ └── index.ts
│ │ ├── createClient.test.ts
│ │ ├── createClient.ts
│ │ ├── createRoomObject.ts
│ │ ├── fabric
│ │ │ ├── Conversation.test.ts
│ │ │ ├── Conversation.ts
│ │ │ ├── ConversationAPI.ts
│ │ │ ├── FabricRoomSession.test.ts
│ │ │ ├── FabricRoomSession.ts
│ │ │ ├── FabricRoomSessionMember.ts
│ │ │ ├── HTTPClient.test.ts
│ │ │ ├── HTTPClient.ts
│ │ │ ├── IncomingCallManager.test.ts
│ │ │ ├── IncomingCallManager.ts
│ │ │ ├── SATSession.test.ts
│ │ │ ├── SATSession.ts
│ │ │ ├── SignalWire.test.ts
│ │ │ ├── SignalWire.ts
│ │ │ ├── WSClient.ts
│ │ │ ├── createHttpClient.ts
│ │ │ ├── createWSClient.ts
│ │ │ ├── index.ts
│ │ │ ├── interfaces
│ │ │ │ ├── address.ts
│ │ │ │ ├── capabilities.ts
│ │ │ │ ├── conversation.ts
│ │ │ │ ├── device.ts
│ │ │ │ ├── httpClient.ts
│ │ │ │ ├── incomingCallManager.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── wsClient.ts
│ │ │ ├── utils
│ │ │ │ ├── capabilitiesHelpers.test.ts
│ │ │ │ ├── capabilitiesHelpers.ts
│ │ │ │ ├── constants.ts
│ │ │ │ ├── eventMappers.ts
│ │ │ │ ├── typeGuard.ts
│ │ │ │ ├── validationProxy.ts
│ │ │ │ └── validators.ts
│ │ │ └── workers
│ │ │ │ ├── callJoinWorker.ts
│ │ │ │ ├── callLeftWorker.ts
│ │ │ │ ├── callSegmentWorker.ts
│ │ │ │ ├── conversationWorker.ts
│ │ │ │ ├── fabricMemberWorker.ts
│ │ │ │ ├── fabricWorker.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── wsClientWorker.ts
│ │ ├── features
│ │ │ ├── actions.ts
│ │ │ └── mediaElements
│ │ │ │ └── mediaElementsSagas.ts
│ │ ├── index.test.ts
│ │ ├── index.ts
│ │ ├── joinRoom.ts
│ │ ├── pubSub
│ │ │ ├── Client.test.ts
│ │ │ ├── Client.ts
│ │ │ └── index.ts
│ │ ├── setupTests.ts
│ │ ├── testUtils.ts
│ │ ├── utils
│ │ │ ├── CloseEvent.ts
│ │ │ ├── audioElement.ts
│ │ │ ├── constants.ts
│ │ │ ├── interfaces
│ │ │ │ ├── base.ts
│ │ │ │ ├── fabric.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── video.ts
│ │ │ ├── makeQueryParamsUrl.ts
│ │ │ ├── paginatedResult.ts
│ │ │ ├── roomSession.test.ts
│ │ │ ├── roomSession.ts
│ │ │ ├── storage.test.ts
│ │ │ ├── storage.ts
│ │ │ └── videoElement.ts
│ │ ├── video
│ │ │ ├── RoomSession.test.ts
│ │ │ ├── RoomSession.ts
│ │ │ ├── VideoRoomSession.test.ts
│ │ │ ├── VideoRoomSession.ts
│ │ │ ├── index.ts
│ │ │ └── workers
│ │ │ │ ├── childMemberJoinedWorker.test.ts
│ │ │ │ ├── childMemberJoinedWorker.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── memberListUpdatedWorker.ts
│ │ │ │ ├── videoPlaybackWorker.ts
│ │ │ │ ├── videoRecordWorker.ts
│ │ │ │ ├── videoStreamWorker.ts
│ │ │ │ └── videoWorker.ts
│ │ └── webrtc.ts
│ ├── test
│ │ └── resolver.js
│ └── tsconfig.build.json
├── node
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ │ ├── index.test.ts
│ │ └── index.ts
│ └── tsconfig.build.json
├── realtime-api
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── __mocks__
│ │ └── ws.js
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ │ ├── BaseNamespace.test.ts
│ │ ├── BaseNamespace.ts
│ │ ├── ListenSubscriber.test.ts
│ │ ├── ListenSubscriber.ts
│ │ ├── SWClient.test.ts
│ │ ├── SWClient.ts
│ │ ├── Session.ts
│ │ ├── SignalWire.ts
│ │ ├── chat
│ │ │ ├── BaseChat.test.ts
│ │ │ ├── BaseChat.ts
│ │ │ ├── Chat.test.ts
│ │ │ ├── Chat.ts
│ │ │ └── workers
│ │ │ │ ├── chatWorker.ts
│ │ │ │ └── index.ts
│ │ ├── client
│ │ │ ├── Client.ts
│ │ │ ├── clientConnect.ts
│ │ │ ├── clientProxyFactory.test.ts
│ │ │ ├── clientProxyFactory.ts
│ │ │ ├── createClient.test.ts
│ │ │ ├── createClient.ts
│ │ │ ├── getClient.test.ts
│ │ │ ├── getClient.ts
│ │ │ ├── index.ts
│ │ │ └── setupClient.ts
│ │ ├── common
│ │ │ └── clientContext.ts
│ │ ├── configure
│ │ │ └── index.ts
│ │ ├── decoratePromise.test.ts
│ │ ├── decoratePromise.ts
│ │ ├── index.ts
│ │ ├── messaging
│ │ │ ├── Message.ts
│ │ │ ├── Messaging.test.ts
│ │ │ ├── Messaging.ts
│ │ │ └── workers
│ │ │ │ ├── index.ts
│ │ │ │ └── messagingWorker.ts
│ │ ├── pubSub
│ │ │ ├── PubSub.test.ts
│ │ │ ├── PubSub.ts
│ │ │ └── workers
│ │ │ │ ├── index.ts
│ │ │ │ └── pubSubWorker.ts
│ │ ├── setupTests.ts
│ │ ├── task
│ │ │ ├── Task.test.ts
│ │ │ ├── Task.ts
│ │ │ └── workers
│ │ │ │ ├── index.ts
│ │ │ │ └── taskWorker.ts
│ │ ├── testUtils.ts
│ │ ├── types
│ │ │ ├── chat.ts
│ │ │ ├── index.ts
│ │ │ ├── messaging.ts
│ │ │ ├── pubSub.ts
│ │ │ ├── task.ts
│ │ │ ├── video.ts
│ │ │ └── voice.ts
│ │ ├── utils
│ │ │ ├── internals.test.ts
│ │ │ └── internals.ts
│ │ ├── video
│ │ │ ├── BaseVideo.ts
│ │ │ ├── RoomSession.test.ts
│ │ │ ├── RoomSession.ts
│ │ │ ├── RoomSessionMember
│ │ │ │ ├── RoomSessionMember.test.ts
│ │ │ │ ├── RoomSessionMember.ts
│ │ │ │ └── index.ts
│ │ │ ├── RoomSessionPlayback
│ │ │ │ ├── RoomSessionPlayback.test.ts
│ │ │ │ ├── RoomSessionPlayback.ts
│ │ │ │ ├── decoratePlaybackPromise.ts
│ │ │ │ └── index.ts
│ │ │ ├── RoomSessionRecording
│ │ │ │ ├── RoomSessionRecording.test.ts
│ │ │ │ ├── RoomSessionRecording.ts
│ │ │ │ ├── decorateRecordingPromise.ts
│ │ │ │ └── index.ts
│ │ │ ├── RoomSessionStream
│ │ │ │ ├── RoomSessionStream.test.ts
│ │ │ │ ├── RoomSessionStream.ts
│ │ │ │ ├── decorateStreamPromise.ts
│ │ │ │ └── index.ts
│ │ │ ├── Video.test.ts
│ │ │ ├── Video.ts
│ │ │ ├── methods
│ │ │ │ ├── index.ts
│ │ │ │ ├── methods.test.ts
│ │ │ │ └── methods.ts
│ │ │ └── workers
│ │ │ │ ├── index.ts
│ │ │ │ ├── videoCallingWorker.ts
│ │ │ │ ├── videoLayoutWorker.ts
│ │ │ │ ├── videoMemberWorker.ts
│ │ │ │ ├── videoPlaybackWorker.ts
│ │ │ │ ├── videoRecordingWorker.ts
│ │ │ │ ├── videoRoomAudienceWorker.ts
│ │ │ │ ├── videoRoomWorker.ts
│ │ │ │ └── videoStreamWorker.ts
│ │ └── voice
│ │ │ ├── Call.test.ts
│ │ │ ├── Call.ts
│ │ │ ├── CallCollect
│ │ │ ├── CallCollect.test.ts
│ │ │ ├── CallCollect.ts
│ │ │ ├── decorateCollectPromise.ts
│ │ │ └── index.ts
│ │ │ ├── CallDetect
│ │ │ ├── CallDetect.test.ts
│ │ │ ├── CallDetect.ts
│ │ │ ├── decorateDetectPromise.ts
│ │ │ └── index.ts
│ │ │ ├── CallPlayback
│ │ │ ├── CallPlayback.test.ts
│ │ │ ├── CallPlayback.ts
│ │ │ ├── decoratePlaybackPromise.ts
│ │ │ └── index.ts
│ │ │ ├── CallPrompt
│ │ │ ├── CallPrompt.test.ts
│ │ │ ├── CallPrompt.ts
│ │ │ ├── decoratePromptPromise.ts
│ │ │ └── index.ts
│ │ │ ├── CallRecording
│ │ │ ├── CallRecording.test.ts
│ │ │ ├── CallRecording.ts
│ │ │ ├── decorateRecordingPromise.ts
│ │ │ └── index.ts
│ │ │ ├── CallTap
│ │ │ ├── CallTap.test.ts
│ │ │ ├── CallTap.ts
│ │ │ ├── decorateTapPromise.ts
│ │ │ └── index.ts
│ │ │ ├── DeviceBuilder.test.ts
│ │ │ ├── DeviceBuilder.ts
│ │ │ ├── Playlist.test.ts
│ │ │ ├── Playlist.ts
│ │ │ ├── Voice.test.ts
│ │ │ ├── Voice.ts
│ │ │ ├── utils.test.ts
│ │ │ ├── utils.ts
│ │ │ └── workers
│ │ │ ├── VoiceCallSendDigitWorker.ts
│ │ │ ├── handlers
│ │ │ ├── callConnectEventsHandler.ts
│ │ │ ├── callDialEventsHandler.ts
│ │ │ ├── callStateEventsHandler.ts
│ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ ├── voiceCallCollectWorker.ts
│ │ │ ├── voiceCallConnectWorker.ts
│ │ │ ├── voiceCallDetectWorker.ts
│ │ │ ├── voiceCallDialWorker.ts
│ │ │ ├── voiceCallPlayWorker.ts
│ │ │ ├── voiceCallReceiveWorker.ts
│ │ │ ├── voiceCallRecordWorker.ts
│ │ │ └── voiceCallTapWorker.ts
│ └── tsconfig.build.json
├── swaig
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ │ ├── Server.ts
│ │ ├── index.test.ts
│ │ ├── index.ts
│ │ ├── schemas.ts
│ │ ├── swaig.test.ts
│ │ ├── swaig.ts
│ │ ├── types.ts
│ │ └── utils.ts
│ └── tsconfig.build.json
├── web-api
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.js
│ ├── mocks
│ │ ├── rest.ts
│ │ └── server.ts
│ ├── package.json
│ ├── src
│ │ ├── createRestClient.test.ts
│ │ ├── createRestClient.ts
│ │ ├── index.ts
│ │ ├── rooms
│ │ │ ├── createRoomFactory.ts
│ │ │ ├── createVRTFactory.ts
│ │ │ ├── deleteRoomFactory.ts
│ │ │ ├── getRoomFactory.ts
│ │ │ ├── listAllRoomsFactory.ts
│ │ │ └── updateRoomFactory.ts
│ │ ├── types.ts
│ │ ├── utils
│ │ │ ├── getConfig.test.ts
│ │ │ ├── getConfig.ts
│ │ │ ├── httpClient.test.ts
│ │ │ └── httpClient.ts
│ │ ├── validateRequest.test.ts
│ │ └── validateRequest.ts
│ └── tsconfig.build.json
└── webrtc
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ ├── BaseConnection.ts
│ ├── RTCPeer.ts
│ ├── index.test.ts
│ ├── index.ts
│ ├── setupTests.ts
│ ├── utils
│ │ ├── constants.ts
│ │ ├── deviceHelpers.test.ts
│ │ ├── deviceHelpers.ts
│ │ ├── enumerateDevices.ts
│ │ ├── getDisplayMedia.ts
│ │ ├── getUserMedia.ts
│ │ ├── helpers.test.ts
│ │ ├── helpers.ts
│ │ ├── index.ts
│ │ ├── interfaces.ts
│ │ ├── permissions.ts
│ │ ├── primitives.native.ts
│ │ ├── primitives.ts
│ │ ├── requestPermissions.ts
│ │ ├── sdpHelpers.test.ts
│ │ ├── sdpHelpers.ts
│ │ ├── watchRTCPeerMediaPackets.ts
│ │ └── webrtcHelpers.test.ts
│ ├── webrtcMocks.ts
│ └── workers
│ │ ├── index.ts
│ │ ├── promoteDemoteWorker.ts
│ │ ├── roomSubscribedWorker.ts
│ │ ├── sessionAuthWorker.ts
│ │ ├── vertoEventWorker.test.ts
│ │ └── vertoEventWorker.ts
│ ├── tsconfig.build.json
│ └── tsconfig.cjs.json
├── prepare_public_folder.sh
├── scripts
├── sw-build-all
│ ├── CHANGELOG.md
│ ├── bin
│ │ └── cli.js
│ ├── index.js
│ └── package.json
├── sw-build
│ ├── CHANGELOG.md
│ ├── bin
│ │ └── cli.js
│ ├── index.js
│ └── package.json
├── sw-common
│ ├── CHANGELOG.md
│ ├── index.js
│ └── package.json
├── sw-release
│ ├── CHANGELOG.md
│ ├── bin
│ │ └── cli.js
│ ├── common.js
│ ├── index.js
│ ├── modes
│ │ ├── beta.js
│ │ ├── development.js
│ │ ├── prepareProd.js
│ │ └── production.js
│ └── package.json
└── sw-test
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── bin
│ └── cli.js
│ ├── index.js
│ ├── package.json
│ └── runNodeScript.js
├── sw.config.js
├── tsconfig.docs.json
└── tsconfig.json
/.changeset/angry-bags-relax.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@signalwire/js': patch
3 | ---
4 |
5 | CF SDK: Optional `incomingCallHandler` parameter to `handlePushNotification()`
6 |
--------------------------------------------------------------------------------
/.changeset/blue-actors-change.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@sw-internal/e2e-js': patch
3 | ---
4 |
5 | Temporarily disabled convo api tests
6 |
--------------------------------------------------------------------------------
/.changeset/chatty-feet-train.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@sw-internal/e2e-js': patch
3 | ---
4 |
5 | Re-enable Conversation Room e2e tests
6 |
--------------------------------------------------------------------------------
/.changeset/cold-beds-guess.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@signalwire/core': minor
3 | '@signalwire/js': minor
4 | ---
5 |
6 | Added API request retries by default
7 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@1.5.0/schema.json",
3 | "changelog": [
4 | "@changesets/changelog-github",
5 | { "repo": "signalwire/signalwire-js" }
6 | ],
7 | "commit": false,
8 | "linked": [],
9 | "access": "restricted",
10 | "baseBranch": "main",
11 | "updateInternalDependencies": "patch",
12 | "ignore": []
13 | }
14 |
--------------------------------------------------------------------------------
/.changeset/easy-ravens-rhyme.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@signalwire/webrtc': patch
3 | '@signalwire/core': patch
4 | '@signalwire/js': patch
5 | ---
6 |
7 | Video & CF SDK:
8 |
9 | - Exposes a `cameraConstraints` and `microphoneConstraints` on the room/call object.
10 |
11 | CF SDK:
12 |
13 | - Introduces a validation proxy for the `FabricRoomSession` class.
14 | - Introduces a `CapabilityError` for the errors based on the missing capability.
15 | - Fixes the `setOutputVolume` API for Call Fabric.
16 | - Fixes the `setInputSensitivity` API param for Call Fabric.
17 |
--------------------------------------------------------------------------------
/.changeset/late-snails-care.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@signalwire/realtime-api': patch
3 | '@signalwire/core': patch
4 | ---
5 |
6 | Realtime-API: Bug Fix - Resubscribe topics/channels after WS reconnection
7 |
8 | Realtime-API Chat: Fix type interfaces for `getMessages` and `getMembers`.
9 |
--------------------------------------------------------------------------------
/.changeset/legal-ways-pick.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@signalwire/js': patch
3 | ---
4 |
5 | CF SDK: Fix Raise Hand call and member capability.
6 |
--------------------------------------------------------------------------------
/.changeset/lucky-hands-obey.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@signalwire/js': minor
3 | '@signalwire/webrtc': patch
4 | '@signalwire/core': patch
5 | ---
6 |
7 | CF SDK: Allow users to pass the `fromFabricAddressId` while dialing
8 |
9 | ```ts
10 | const call = await client.dial({
11 | .....,
12 | to: .....,
13 | fromFabricAddressId: 'valid_subscriber_id', // Optional
14 | ...
15 | })
16 | ```
17 |
--------------------------------------------------------------------------------
/.changeset/moody-nails-begin.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@signalwire/webrtc': patch
3 | '@signalwire/core': patch
4 | '@signalwire/js': patch
5 | ---
6 |
7 | CF SDK: Fix the `getAddresses` TS contract with internal refactoring
8 |
--------------------------------------------------------------------------------
/.changeset/plain-steaks-wash.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@signalwire/js': patch
3 | ---
4 |
5 | resolves the user_name from the getChatMessages API
6 |
--------------------------------------------------------------------------------
/.changeset/plenty-banks-dress.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@signalwire/core': patch
3 | '@signalwire/js': patch
4 | ---
5 |
6 | CF SDK: Changed the type of the error param for the `expectedErrorHandler` internal handler
7 |
--------------------------------------------------------------------------------
/.changeset/rich-bugs-own.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@sw-internal/e2e-js': patch
3 | ---
4 |
5 | Fix conversation spec by making sure promise doesn't resolve on call logs conversation.message and also allow for GET messages response assert to include more than 2 messages in case they include call logs
6 |
--------------------------------------------------------------------------------
/.changeset/seven-owls-run.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@signalwire/web-api': patch
3 | ---
4 |
5 | Web-API: Upgrade @signalwire/compatibility-api package to the latest version
6 |
--------------------------------------------------------------------------------
/.changeset/small-wasps-carry.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@signalwire/js': patch
3 | ---
4 |
5 | CF SDK(bug): Include the missing Pagination type
6 |
--------------------------------------------------------------------------------
/.changeset/soft-shoes-bathe.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@sw-internal/e2e-js': patch
3 | ---
4 |
5 | Temporarily disable status webhook callback tests
6 |
--------------------------------------------------------------------------------
/.changeset/spotty-showers-cough.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@sw-internal/e2e-js': patch
3 | ---
4 |
5 | Added call status webhook test in v2Webrtc suite
6 |
--------------------------------------------------------------------------------
/.changeset/tasty-buckets-check.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@sw-internal/e2e-js': patch
3 | ---
4 |
5 | Renabled callfabric/conversation.spec.ts test suite
6 |
--------------------------------------------------------------------------------
/.changeset/tasty-dodos-study.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@signalwire/js': minor
3 | ---
4 |
5 | CF SDK: Support default media params
6 |
7 | ```ts
8 | await call.dial({
9 | applyLocalVideoOverlay: false, // Should the SDK apply local video overlay? Default: true
10 | applyMemberOverlay: true, // Should the SDK apply member video overlays? Default: true
11 | stopCameraWhileMuted: true, // Should the SDK stop the camera when muted? Default: true
12 | stopMicrophoneWhileMuted: true, // Should the SDK stop the mic when muted? Default: true
13 | mirrorLocalVideoOverlay: false // Should the SDK mirror the local video overlay? Default: true
14 | })
15 | ```
16 |
--------------------------------------------------------------------------------
/.changeset/witty-nights-vanish.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@sw-internal/e2e-js': patch
3 | ---
4 |
5 | Re-enable v2WebrtcFromRest's status callback tests and added better error handling and retry logic for zrok process
6 |
--------------------------------------------------------------------------------
/.changeset/yummy-mice-sing.md:
--------------------------------------------------------------------------------
1 | ---
2 | '@signalwire/js': patch
3 | ---
4 |
5 | refactored gatChatMeassages to prevent multiple lookup of the same address
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
--------------------------------------------------------------------------------
/.github/actions/build/action.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | description: Build all the SDKs in the monorepo
3 |
4 | inputs:
5 | NODE_VERSION:
6 | required: false
7 | default: 20.x
8 | description: Node version to use
9 |
10 | runs:
11 | using: composite
12 | steps:
13 | - name: Cache dist folders
14 | id: cache
15 | uses: actions/cache@v4
16 | with:
17 | key: ${{ github.ref }}-${{ github.sha }}
18 | path: packages/*/dist
19 | - if: steps.cache.outputs.cache-hit != 'true'
20 | name: Build
21 | shell: bash
22 | run: npm run build
23 |
--------------------------------------------------------------------------------
/.github/actions/install/action.yml:
--------------------------------------------------------------------------------
1 | name: Install
2 | description: Install dependencies for the monorepo
3 |
4 | inputs:
5 | NODE_VERSION:
6 | required: false
7 | default: 20.x
8 | description: Node version to use
9 |
10 | runs:
11 | using: composite
12 | steps:
13 | - name: Setup node
14 | uses: actions/setup-node@v4
15 | with:
16 | node-version: ${{ inputs.NODE_VERSION }}
17 | cache: npm
18 | - name: Cache node_modules
19 | id: cache_node_modules
20 | uses: actions/cache@v4
21 | with:
22 | key: ${{ runner.os }}-node-modules-${{ inputs.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
23 | path: |
24 | node_modules
25 | packages/*/node_modules
26 | scripts/*/node_modules
27 | internal/*/node_modules
28 | - if: steps.cache_node_modules.outputs.cache-hit != 'true'
29 | name: Install Dependencies
30 | shell: bash
31 | run: npm ci
32 |
--------------------------------------------------------------------------------
/.github/actions/playwright/action.yml:
--------------------------------------------------------------------------------
1 | name: Playwright
2 | description: Install playwright and required browsers
3 |
4 | inputs:
5 | PLAYWRIGHT_VERSION:
6 | required: true
7 | description: Playwright version to use
8 |
9 | runs:
10 | using: composite
11 | steps:
12 | - name: Cache playwright binaries
13 | uses: actions/cache@v4
14 | id: playwright-cache
15 | with:
16 | path: |
17 | ~/.cache/ms-playwright
18 | key: ${{ runner.os }}-playwright-${{ inputs.PLAYWRIGHT_VERSION }}
19 |
20 | - if: steps.playwright-cache.outputs.cache-hit != 'true'
21 | name: Install Playwright
22 | shell: bash
23 | run: npx playwright install --with-deps chromium
24 |
--------------------------------------------------------------------------------
/.github/actions/setup-js-app/action.yml:
--------------------------------------------------------------------------------
1 | name: Setup and Build JS
2 | description: Setup app, install JS dependencies and build
3 |
4 | inputs:
5 | NODE_VERSION:
6 | required: false
7 | default: "20.x"
8 | description: "Default node version"
9 |
10 | runs:
11 | using: "composite"
12 | steps:
13 | - name: Setup node
14 | uses: actions/setup-node@v4
15 | with:
16 | node-version: ${{ inputs.NODE_VERSION }}
17 | cache: 'npm'
18 | cache-dependency-path: '**/package-lock.json'
19 | - name: Install Dependencies
20 | shell: bash
21 | run: npm ci
22 | - name: Install Playwright
23 | shell: bash
24 | run: npx playwright install --with-deps chromium
25 | - name: Build
26 | shell: bash
27 | run: npm run build
28 |
--------------------------------------------------------------------------------
/.github/actions/setup-node-app/action.yml:
--------------------------------------------------------------------------------
1 | name: Setup and Build Node
2 | description: Setup app, install node dependencies and build
3 |
4 | inputs:
5 | NODE_VERSION:
6 | required: false
7 | default: "20.x"
8 | description: "Default node version"
9 |
10 | runs:
11 | using: "composite"
12 | steps:
13 | - name: Setup node
14 | uses: actions/setup-node@v4
15 | with:
16 | node-version: ${{ inputs.NODE_VERSION }}
17 | cache: 'npm'
18 | cache-dependency-path: '**/package-lock.json'
19 | - name: Install Dependencies
20 | shell: bash
21 | run: npm install
22 | - name: Build
23 | shell: bash
24 | run: npm run build
25 |
--------------------------------------------------------------------------------
/.github/actions/zrok/action.yml:
--------------------------------------------------------------------------------
1 | name: Zrok
2 | description: Install zrok
3 |
4 | inputs:
5 | ZROK_VERSION:
6 | required: false
7 | description: Zrok version to use
8 | default: 1.0.4
9 | ZROK_API_ENDPOINT:
10 | required: true
11 | description: Zrok apiEndpoint
12 | ZROK_TOKEN:
13 | required: true
14 | description: Zrok token
15 | runs:
16 | using: composite
17 | steps:
18 | - name: Install Zrok
19 | shell: bash
20 | run: |
21 | curl -L -O https://github.com/openziti/zrok/releases/download/v1.0.4/zrok_${{ inputs.ZROK_VERSION }}_linux_amd64.tar.gz
22 | mkdir /tmp/zrok && tar xvzf ./zrok_${{ inputs.ZROK_VERSION }}_linux_amd64.tar.gz -C /tmp/zrok
23 | mkdir -p /usr/local/bin && install /tmp/zrok/zrok /usr/local/bin/
24 | zrok version
25 | zrok config set apiEndpoint ${{ inputs.ZROK_API_ENDPOINT }}
26 | zrok enable ${{ inputs.ZROK_TOKEN }} --headless
27 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | Please include a summary of the change and which issue is fixed.
4 |
5 | ## Type of change
6 |
7 | - [ ] Internal refactoring
8 | - [ ] Bug fix (bugfix - non-breaking)
9 | - [ ] New feature (non-breaking change which adds functionality)
10 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
11 |
12 | ## Code snippets
13 |
14 | In case of new feature or breaking changes, please include code snippets.
15 |
--------------------------------------------------------------------------------
/.github/workflows/release-dev.yml:
--------------------------------------------------------------------------------
1 | name: Release dev tag to NPM
2 |
3 | on:
4 | workflow_run:
5 | workflows: [Tests]
6 | branches: [main]
7 | types:
8 | - completed
9 |
10 | concurrency:
11 | group: ${{ github.workflow }}-${{ github.ref }}
12 | cancel-in-progress: true
13 |
14 | jobs:
15 | release_dev:
16 | if: ${{ github.event.workflow_run.conclusion == 'success' }}
17 | runs-on: ubuntu-latest
18 | strategy:
19 | matrix:
20 | node-version: [20.x]
21 | steps:
22 | - uses: actions/checkout@v4
23 | - uses: actions/setup-node@v4
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 | registry-url: 'https://registry.npmjs.org'
27 | - run: npm i
28 | - run: npm run release:dev
29 | env:
30 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # misc
2 | *~
3 | *.sw[mnpcod]
4 | *.log
5 | *.lock
6 | *.tmp
7 | *.tmp.*
8 | log.txt
9 | *.sublime-project
10 | *.sublime-workspace
11 |
12 | .idea/
13 | .vscode/
14 | .sass-cache/
15 | .versions/
16 | .devcontainer/
17 | node_modules/
18 | $RECYCLE.BIN/
19 |
20 | .DS_Store
21 | Thumbs.db
22 | UserInterfaceState.xcuserstate
23 | .env
24 | .env.local
25 | .env.development.local
26 | .env.test.local
27 | .env.production.local
28 | .env.test
29 |
30 | npm-debug.log*
31 | yarn-debug.log*
32 | yarn-error.log*
33 |
34 | coverage/
35 | public/
36 | .devcontainer/
37 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 14
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | build/*
2 | coverage
3 | dist/*
4 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "trailingComma": "es5",
4 | "useTabs": false,
5 | "tabWidth": 2,
6 | "semi": false,
7 | "singleQuote": true,
8 | "jsxSingleQuote": true,
9 | "jsxBracketSameLine": false,
10 | "arrowParens": "always"
11 | }
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 SignalWire
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', { targets: { node: 'current' } }],
4 | '@babel/preset-typescript',
5 | ],
6 | }
7 |
--------------------------------------------------------------------------------
/internal/e2e-js/.env.example:
--------------------------------------------------------------------------------
1 | SW_TEST_CONFIG='{
2 | "ignoreFiles": [],
3 | "env": {
4 | "API_HOST": "xyz.signalwire.com",
5 | "RELAY_HOST": "relay.signalwire.com",
6 | "RELAY_PROJECT": "xyz",
7 | "RELAY_TOKEN": "PTxyz",
8 | "PLAYBACK_URL": "http://xyz.test.mp4",
9 | "STREAMING_URL": "rtmp://a.rtmp.youtube.com/live2/111",
10 | "STREAM_CHECK_URL": "https://rtmp.example.com/stats",
11 | "RTMP_SERVER": "rtmp://a.rtmp.youtube.com/live2/",
12 | "RTMP_STREAM_NAME": "someName",
13 | "SAT_REFERENCE": "oauthReference",
14 | "VERTO_DOMAIN": "dev-1111.verto.example.com"
15 | }
16 | }'
--------------------------------------------------------------------------------
/internal/e2e-js/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | test-results/
--------------------------------------------------------------------------------
/internal/e2e-js/assets/sw-docs.y4m:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/signalwire/signalwire-js/9d2638af57ccf52be93e3cc29224d5fb6c8ea6b5/internal/e2e-js/assets/sw-docs.y4m
--------------------------------------------------------------------------------
/internal/e2e-js/global-setup.ts:
--------------------------------------------------------------------------------
1 | import type { FullConfig } from '@playwright/test'
2 | import { createTestServer } from './utils'
3 |
4 | async function globalSetup(_config: FullConfig) {
5 | const server = await createTestServer()
6 | await server.start()
7 |
8 | return async () => {
9 | await server.close()
10 | }
11 | }
12 |
13 | export default globalSetup
14 |
--------------------------------------------------------------------------------
/internal/e2e-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sw-internal/e2e-js",
3 | "version": "0.0.19",
4 | "private": true,
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "sw-test --mode=playwright --pass-with-no-tests",
8 | "test:update:snapshots": "npm run dev --update-snapshots",
9 | "test": ""
10 | },
11 | "dependencies": {
12 | "@sw-internal/playground-js": "0.0.15",
13 | "express": "^5.1.0",
14 | "node-fetch": "^2.6.7"
15 | },
16 | "devDependencies": {
17 | "@playwright/test": "^1.52.0",
18 | "@types/express": "^5.0.1",
19 | "playwrigth-ws-inspector": "^1.0.0",
20 | "vite": "^6.2.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/internal/e2e-js/templates/blank/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Signalwire Local Template
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/internal/e2e-js/templates/blank/index.js:
--------------------------------------------------------------------------------
1 | import * as SWJS from '@signalwire/js'
2 |
3 | window._SWJS = SWJS
4 |
--------------------------------------------------------------------------------
/internal/e2e-js/tests/callfabric/mirrorVideo.spec.ts-snapshots/video-flipped.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/signalwire/signalwire-js/9d2638af57ccf52be93e3cc29224d5fb6c8ea6b5/internal/e2e-js/tests/callfabric/mirrorVideo.spec.ts-snapshots/video-flipped.png
--------------------------------------------------------------------------------
/internal/e2e-js/tests/callfabric/mirrorVideo.spec.ts-snapshots/video-normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/signalwire/signalwire-js/9d2638af57ccf52be93e3cc29224d5fb6c8ea6b5/internal/e2e-js/tests/callfabric/mirrorVideo.spec.ts-snapshots/video-normal.png
--------------------------------------------------------------------------------
/internal/e2e-js/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "emitDeclarationOnly": true,
5 | "outDir": "dist",
6 | "rootDir": "../../"
7 | },
8 | "include": ["./**/*.ts", "../../packages/js/src/**/*.ts", "./utils.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/internal/e2e-realtime-api/.env.example:
--------------------------------------------------------------------------------
1 | SW_TEST_CONFIG='{
2 | "ignoreFiles": [],
3 | "ignoreDirectories": [],
4 | "env": {
5 | "API_HOST": "xyz.signalwire.com",
6 | "RELAY_HOST": "relay.signalwire.com",
7 | "RELAY_PROJECT": "xyz",
8 | "RELAY_TOKEN": "PTxyz",
9 | "MESSAGING_FROM_NUMBER": "+1111",
10 | "MESSAGING_TO_NUMBER": "+1111",
11 | "MESSAGING_CONTEXT": "someContext",
12 | "DAPP_DOMAIN": "abc.signalwire.com",
13 | "PLAYBACK_URL": "http://xyz.test.mp4"
14 | }
15 | }'
--------------------------------------------------------------------------------
/internal/e2e-realtime-api/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | node_modules/
3 | /test-results/
4 | /playwright-report/
5 | /playwright/.cache/
6 |
--------------------------------------------------------------------------------
/internal/e2e-realtime-api/global-setup.ts:
--------------------------------------------------------------------------------
1 | import type { FullConfig } from '@playwright/test'
2 | import { createTestServer } from './utils'
3 |
4 | async function globalSetup(_config: FullConfig) {
5 | const server = await createTestServer()
6 | await server.start()
7 |
8 | return async () => {
9 | await server.close()
10 | }
11 | }
12 |
13 | export default globalSetup
14 |
--------------------------------------------------------------------------------
/internal/e2e-realtime-api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sw-internal/e2e-realtime-api",
3 | "version": "0.1.14",
4 | "private": true,
5 | "main": "index.js",
6 | "scripts": {
7 | "dev:playwright": "sw-test --mode=playwright",
8 | "dev": "sw-test --mode=custom-node && npm run dev:playwright",
9 | "dev:rtonly": "sw-test --mode=custom-node",
10 | "test": ""
11 | },
12 | "devDependencies": {
13 | "@playwright/test": "^1.35.1",
14 | "@types/tap": "^15.0.8",
15 | "esbuild-register": "^3.6.0",
16 | "vite": "^6.2.0"
17 | },
18 | "dependencies": {
19 | "@mapbox/node-pre-gyp": "^1.0.10",
20 | "tap": "^18.7.2",
21 | "ws": "^8.17.1"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/internal/e2e-realtime-api/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, devices } from '@playwright/test'
2 |
3 | require('dotenv').config()
4 |
5 | const useDesktopChrome = {
6 | ...devices['Desktop Chrome'],
7 | launchOptions: {
8 | args: [
9 | '--use-fake-ui-for-media-stream',
10 | '--use-fake-device-for-media-stream',
11 | ],
12 | },
13 | }
14 |
15 | const config = defineConfig({
16 | testDir: './src/playwright',
17 | reporter: process.env.CI ? 'github' : 'list',
18 | globalSetup: require.resolve('./global-setup'),
19 | testMatch: undefined,
20 | testIgnore: undefined,
21 | timeout: 60_000,
22 | expect: {
23 | timeout: 10_000,
24 | },
25 | fullyParallel: true,
26 | // forbidOnly: !!process.env.CI,
27 | workers: 1,
28 | projects: [
29 | {
30 | name: 'default',
31 | use: useDesktopChrome,
32 | },
33 | ],
34 | })
35 |
36 | export default config
37 |
--------------------------------------------------------------------------------
/internal/e2e-realtime-api/templates/blank/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Signalwire Local Template
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/internal/e2e-realtime-api/templates/blank/index.js:
--------------------------------------------------------------------------------
1 | import * as SWJS from '@signalwire/js'
2 |
3 | window._SWJS = SWJS
4 |
--------------------------------------------------------------------------------
/internal/e2e-realtime-api/utils.ts:
--------------------------------------------------------------------------------
1 | import { createServer } from 'vite'
2 |
3 | export const SERVER_URL = 'http://localhost:1338'
4 |
5 | export const createTestServer = async () => {
6 | const targetOptions = { path: './templates/blank', port: 1338 }
7 | const server = await createServer({
8 | configFile: false,
9 | root: targetOptions.path,
10 | server: {
11 | port: targetOptions.port,
12 | },
13 | logLevel: 'silent',
14 | })
15 |
16 | return {
17 | start: async () => {
18 | await server.listen()
19 | },
20 | close: async () => {
21 | await server.close()
22 | },
23 | url: `http://localhost:${targetOptions.port}`,
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/internal/playground-js/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 |
--------------------------------------------------------------------------------
/internal/playground-js/README.md:
--------------------------------------------------------------------------------
1 | # Playground
2 |
3 | After you've built the SDK at root, you can `cd` to this folder and run the playground applications with:
4 |
5 | ```
6 | npm run dev
7 | ```
8 |
9 | which will run a `vite` server on localhost (default port: 3000).
10 |
11 | Browse to the provided local URL (e.g. `http://localhost:3000/`) and select a playground app to use.
12 |
--------------------------------------------------------------------------------
/internal/playground-js/example.env:
--------------------------------------------------------------------------------
1 | #Firebase Initilization Params
2 | VITE_FB_API_KEY=
3 | VITE_FB_AUTH_DOMAIN=
4 | VITE_FB_PROJECT_ID=
5 | VITE_FB_STORAGE_BUCKET=
6 | VITE_FB_MESSAGING_SENDER_ID=
7 | VITE_FB_APP_ID=
8 | VITE_FB_MEASUREMENT_ID=
9 | VITE_FB_VAPI_KEY=
10 |
--------------------------------------------------------------------------------
/internal/playground-js/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 | SignalWire Playground
12 |
13 |
14 |
15 |
16 |
17 |
18 |
Playgrounds
19 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/internal/playground-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sw-internal/playground-js",
3 | "version": "0.0.15",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "serve": "vite preview",
9 | "test": ""
10 | },
11 | "dependencies": {
12 | "firebase": "^11.3.1",
13 | "pako": "^2.1.0"
14 | },
15 | "devDependencies": {
16 | "vite": "^6.2.0"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/internal/playground-js/src/pubSub/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Signalwire Chat Demo
5 |
6 |
10 |
14 |
15 |
16 |
17 | PubSub Test
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/internal/playground-js/src/pubSub/index.js:
--------------------------------------------------------------------------------
1 | import { Chat as PubSub } from '@signalwire/js'
2 |
3 | window.connect = async ({ channels, host, token }) => {
4 | const pubSubClient = new PubSub.Client({
5 | host,
6 | token,
7 | })
8 |
9 | // .subscribe should be after .on but i left here for test.
10 | await pubSubClient.subscribe(channels)
11 |
12 | pubSubClient.on('message', (message) => {
13 | console.log(
14 | 'Received',
15 | message.content,
16 | 'on',
17 | message.channel,
18 | 'at',
19 | message.publishedAt
20 | )
21 | })
22 |
23 | await pubSubClient.publish({
24 | channel: channels[0],
25 | content: 'Hello World',
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/internal/playground-js/src/videoManager/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Signalwire VideoManager Demo
5 |
6 |
10 |
14 |
15 |
16 |
17 | VideoManager Test
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/internal/playground-realtime-api/.env.example:
--------------------------------------------------------------------------------
1 | HOST=example.domain.com
2 | PROJECT=xxx
3 | TOKEN=yyy
4 | RELAY_CONTEXT=default
5 | FROM_NUMBER=+1xxx
6 | TO_NUMBER=+1yyy
7 |
--------------------------------------------------------------------------------
/internal/playground-realtime-api/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 |
--------------------------------------------------------------------------------
/internal/playground-realtime-api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sw-internal/playground-realtime-api",
3 | "version": "0.3.5",
4 | "private": true,
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "node -r dotenv/config index.js",
8 | "test": ""
9 | },
10 | "devDependencies": {
11 | "esbuild-register": "^3.6.0",
12 | "inquirer": "^8.2.5",
13 | "node-watch": "^0.7.3"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/internal/playground-realtime-api/src/basic/index.ts:
--------------------------------------------------------------------------------
1 | import { Video } from '@signalwire/realtime-api'
2 |
3 | async function run() {
4 | try {
5 | const video = new Video.Client({
6 | // @ts-expect-error
7 | host: process.env.HOST,
8 | project: process.env.PROJECT as string,
9 | token: process.env.TOKEN as string,
10 | // debug: { logWsTraffic: true },
11 | })
12 |
13 | video.on('room.started', async (roomSession) => {
14 | console.log('Room started', roomSession.id)
15 |
16 | roomSession.on('member.updated', async (member) => {
17 | console.log(member)
18 | })
19 | })
20 |
21 | video.on('room.ended', async (roomSession) => {
22 | console.log('Room ended', roomSession.id)
23 | })
24 |
25 | const { roomSessions } = await video.getRoomSessions()
26 |
27 | roomSessions.forEach(async (roomSession) => {
28 | console.log('RoomSession:', roomSession.id)
29 | const { recordings } = await roomSession.getRecordings()
30 | console.log('Recordings', recordings.length)
31 | recordings.forEach(async (rec) => {
32 | console.log('REC', rec.id, rec.roomSessionId, rec.state, rec.startedAt)
33 | if (rec.state === 'recording') {
34 | await rec.stop()
35 | }
36 | })
37 | })
38 |
39 | console.log('Client Running..')
40 | } catch (error) {
41 | console.log('', error)
42 | }
43 | }
44 |
45 | run()
46 |
--------------------------------------------------------------------------------
/internal/playground-swaig/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @sw-internal/playground-swaig
2 |
3 | ## 0.1.1
4 |
5 | ### Patch Changes
6 |
7 | - [#1001](https://github.com/signalwire/signalwire-js/pull/1001) [`968d226b`](https://github.com/signalwire/signalwire-js/commit/968d226ba2791f44dea4bd1b0d173aefaf103bda) Thanks [@ayeminag](https://github.com/ayeminag)! - - API to fetch address by id and tests
8 |
--------------------------------------------------------------------------------
/internal/playground-swaig/index.js:
--------------------------------------------------------------------------------
1 | const inquirer = require('inquirer')
2 | const watch = require('node-watch')
3 | const path = require('node:path')
4 | const fs = require('node:fs')
5 | const { spawn } = require('node:child_process')
6 |
7 | const runScript = (scriptFile) => {
8 | const child = spawn(...['node', ['-r', 'esbuild-register', scriptFile]])
9 |
10 | child.stdout.on('data', (data) => {
11 | console.log(data.toString())
12 | })
13 |
14 | child.stderr.on('data', (data) => {
15 | console.error(`stderr: ${data}`)
16 | })
17 |
18 | return child
19 | }
20 |
21 | async function main() {
22 | const playground = path.join(__dirname, './src/index.ts')
23 |
24 | let child
25 | watch(
26 | [
27 | path.join(__dirname, '../../', 'packages/swaig/dist'),
28 | path.join(__dirname, 'src'),
29 | ],
30 | { recursive: true },
31 | (_evt, name) => {
32 | if (name.endsWith('map')) {
33 | return
34 | }
35 |
36 | const newChild = runScript(playground)
37 |
38 | if (child) {
39 | child.stdin.pause()
40 | child.kill()
41 | }
42 |
43 | child = newChild
44 | }
45 | )
46 |
47 | child = runScript(playground)
48 | }
49 |
50 | main(process.argv)
51 |
--------------------------------------------------------------------------------
/internal/playground-swaig/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sw-internal/playground-swaig",
3 | "description": "Playground for SWAIG",
4 | "author": "SignalWire Team ",
5 | "version": "0.1.1",
6 | "private": true,
7 | "main": "index.js",
8 | "scripts": {
9 | "dev": "node -r dotenv/config index.js",
10 | "test": ""
11 | },
12 | "keywords": [
13 | "signalwire",
14 | "swaig",
15 | "ai",
16 | "gateway"
17 | ],
18 | "devDependencies": {
19 | "node-watch": "^0.7.4"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/internal/stack-tests/.gitignore:
--------------------------------------------------------------------------------
1 | **/dist
2 | .env
3 |
--------------------------------------------------------------------------------
/internal/stack-tests/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sw-internal/stack-tests",
3 | "version": "1.0.5",
4 | "description": "",
5 | "main": "index.js",
6 | "type": "module",
7 | "scripts": {
8 | "dev": "node index.js",
9 | "test": ""
10 | },
11 | "dependencies": {
12 | "execa": "^7.1.1",
13 | "tap": "^18.7.2"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/chat/app.ts:
--------------------------------------------------------------------------------
1 | import { SignalWire } from '@signalwire/realtime-api'
2 | import tap from 'tap'
3 |
4 | async function run() {
5 | try {
6 | const client = await SignalWire({
7 | host: process.env.RELAY_HOST || 'relay.swire.io',
8 | project: process.env.RELAY_PROJECT as string,
9 | token: process.env.RELAY_TOKEN as string,
10 | })
11 |
12 | tap.ok(client.chat, 'client.chat is defined')
13 | tap.ok(client.chat.listen, 'client.chat.listen is defined')
14 | tap.ok(client.chat.publish, 'client.chat.publish is defined')
15 | tap.ok(client.chat.getMessages, 'client.chat.getMessages is defined')
16 | tap.ok(client.chat.getMembers, 'client.chat.getMembers is defined')
17 | tap.ok(client.chat.getMemberState, 'client.chat.getMemberState is defined')
18 | tap.ok(client.chat.setMemberState, 'client.chat.setMemberState is defined')
19 |
20 | process.exit(0)
21 | } catch (error) {
22 | console.log('', error)
23 | process.exit(1)
24 | }
25 | }
26 |
27 | run()
28 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/chat/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stack-tests-chat",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "build:cjs": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module CommonJS --outDir dist/cjs",
7 | "build:esm": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module ESNext --outDir dist/esm"
8 | },
9 | "dependencies": {
10 | "@signalwire/realtime-api": "^3.1.1"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/close-event/app.ts:
--------------------------------------------------------------------------------
1 | import { SWCloseEvent } from '@signalwire/core'
2 | import tap from 'tap'
3 |
4 | async function run() {
5 | try {
6 | const evt = new SWCloseEvent('close', {
7 | reason: 'Client-side closed',
8 | })
9 |
10 | tap.equal(evt.type, 'close')
11 | tap.equal(evt.code, 0)
12 | tap.equal(evt.reason, 'Client-side closed')
13 | tap.equal(evt.wasClean, false)
14 |
15 | process.exit(0)
16 | } catch (error) {
17 | console.log('', error)
18 | process.exit(1)
19 | }
20 | }
21 |
22 | run()
23 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/close-event/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stack-tests-close-event",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "",
6 | "scripts": {
7 | "build:cjs": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module CommonJS --outDir dist/cjs",
8 | "build:esm": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module ESNext --outDir dist/esm"
9 | },
10 | "dependencies": {
11 | "@signalwire/core": "^3.18.2"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/messaging/app.ts:
--------------------------------------------------------------------------------
1 | import { SignalWire } from '@signalwire/realtime-api'
2 | import tap from 'tap'
3 |
4 | async function run() {
5 | try {
6 | const client = await SignalWire({
7 | host: process.env.RELAY_HOST || 'relay.swire.io',
8 | project: process.env.RELAY_PROJECT as string,
9 | token: process.env.RELAY_TOKEN as string,
10 | })
11 |
12 | tap.ok(client.messaging, 'client.messaging is defined')
13 | tap.ok(client.messaging.listen, 'client.messaging.listen is defined')
14 | tap.ok(client.messaging.send, 'message.send is defined')
15 | tap.ok(client.disconnect, 'client.disconnect is defined')
16 |
17 | process.exit(0)
18 | } catch (error) {
19 | console.log('', error)
20 | process.exit(1)
21 | }
22 | }
23 |
24 | run()
25 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/messaging/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stack-tests-messaging",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "build:cjs": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module CommonJS --outDir dist/cjs",
7 | "build:esm": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module ESNext --outDir dist/esm"
8 | },
9 | "dependencies": {
10 | "@signalwire/realtime-api": "^3.1.1"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/pubSub/app.ts:
--------------------------------------------------------------------------------
1 | import { SignalWire } from '@signalwire/realtime-api'
2 | import tap from 'tap'
3 |
4 | async function run() {
5 | try {
6 | const client = await SignalWire({
7 | host: process.env.RELAY_HOST || 'relay.swire.io',
8 | project: process.env.RELAY_PROJECT as string,
9 | token: process.env.RELAY_TOKEN as string,
10 | })
11 |
12 | tap.ok(client.pubSub, 'client.pubSub is defined')
13 | tap.ok(client.pubSub.listen, 'client.pubSub.listen is defined')
14 | tap.ok(client.pubSub.publish, 'client.pubSub.publish is defined')
15 |
16 | process.exit(0)
17 | } catch (error) {
18 | console.log('', error)
19 | process.exit(1)
20 | }
21 | }
22 |
23 | run()
24 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/pubSub/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stack-tests-pubsub",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "build:cjs": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module CommonJS --outDir dist/cjs",
7 | "build:esm": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module ESNext --outDir dist/esm"
8 | },
9 | "dependencies": {
10 | "@signalwire/realtime-api": "^3.1.1"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/task/app.ts:
--------------------------------------------------------------------------------
1 | import { SignalWire } from '@signalwire/realtime-api'
2 | import tap from 'tap'
3 |
4 | async function run() {
5 | try {
6 | const client = await SignalWire({
7 | host: process.env.RELAY_HOST || 'relay.swire.io',
8 | project: process.env.RELAY_PROJECT as string,
9 | token: process.env.RELAY_TOKEN as string,
10 | })
11 |
12 | tap.ok(client.task, 'client.task is defined')
13 | tap.ok(client.task.listen, 'client.task.listen is defined')
14 | tap.ok(client.task.send, 'client.task.send is defined')
15 |
16 | process.exit(0)
17 | } catch (error) {
18 | console.log('', error)
19 | process.exit(1)
20 | }
21 | }
22 |
23 | run()
24 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/task/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stack-tests-task",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "build:cjs": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module CommonJS --outDir dist/cjs",
7 | "build:esm": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module ESNext --outDir dist/esm"
8 | },
9 | "dependencies": {
10 | "@signalwire/realtime-api": "^3.1.1"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/video/app.ts:
--------------------------------------------------------------------------------
1 | import { SignalWire } from '@signalwire/realtime-api'
2 | import tap from 'tap'
3 |
4 | async function run() {
5 | try {
6 | const client = await SignalWire({
7 | host: process.env.RELAY_HOST || 'relay.swire.io',
8 | project: process.env.RELAY_PROJECT as string,
9 | token: process.env.RELAY_TOKEN as string,
10 | })
11 |
12 | tap.ok(client.video, 'client.video is defined')
13 | tap.ok(client.video.listen, 'client.video.listen is defined')
14 | tap.ok(client.video.getRoomSessions, 'video.getRoomSessions is defined')
15 | tap.ok(
16 | client.video.getRoomSessionById,
17 | 'video.getRoomSessionById is defined'
18 | )
19 | tap.ok(client.disconnect, 'video.disconnect is defined')
20 |
21 | process.exit(0)
22 | } catch (error) {
23 | console.log('', error)
24 | process.exit(1)
25 | }
26 | }
27 |
28 | run()
29 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/video/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stack-tests-video",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "build:cjs": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module CommonJS --outDir dist/cjs",
7 | "build:esm": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module ESNext --outDir dist/esm"
8 | },
9 | "dependencies": {
10 | "@signalwire/realtime-api": "^3.1.1"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/voice/app.ts:
--------------------------------------------------------------------------------
1 | import { SignalWire } from '@signalwire/realtime-api'
2 | import tap from 'tap'
3 |
4 | async function run() {
5 | try {
6 | const client = await SignalWire({
7 | host: process.env.RELAY_HOST || 'relay.swire.io',
8 | project: process.env.RELAY_PROJECT as string,
9 | token: process.env.RELAY_TOKEN as string,
10 | })
11 |
12 | tap.ok(client.voice, 'client.voice is defined')
13 | tap.ok(client.voice.listen, 'client.voice.listen is defined')
14 | tap.ok(client.voice.dial, 'voice.dial is defined')
15 | tap.ok(client.voice.dialPhone, 'voice.dialPhone is defined')
16 | tap.ok(client.voice.dialSip, 'voice.dialSip is defined')
17 | tap.ok(client.disconnect, 'voice.disconnect is defined')
18 |
19 | process.exit(0)
20 | } catch (error) {
21 | console.log('', error)
22 | process.exit(1)
23 | }
24 | }
25 |
26 | run()
27 |
--------------------------------------------------------------------------------
/internal/stack-tests/src/voice/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stack-tests-voice",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "build:cjs": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module CommonJS --outDir dist/cjs",
7 | "build:esm": "tsc app.ts --rootDir ./ --skipLibCheck --esModuleInterop --lib DOM --lib ES2015 --target ESNext --moduleResolution node --module ESNext --outDir dist/esm"
8 | },
9 | "dependencies": {
10 | "@signalwire/realtime-api": "^3.1.1"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@signalwire/root",
3 | "version": "0.0.0",
4 | "private": true,
5 | "workspaces": [
6 | "./packages/*",
7 | "./scripts/*",
8 | "./internal/*"
9 | ],
10 | "scripts": {
11 | "changeset": "changeset",
12 | "clean": "npm exec --ws -- npx rimraf node_modules && npm exec --ws -- npx rimraf dist && npx rimraf node_modules",
13 | "test": "npm exec --ws npm run test",
14 | "build": "npm exec --ws -- npx rimraf dist && sw-build-all",
15 | "prettier": "prettier --write .",
16 | "release:dev": "sw-release --development",
17 | "release:beta": "sw-release --beta",
18 | "prepare:prod": "sw-release --prepare-prod",
19 | "release:prod": "sw-release --production"
20 | },
21 | "dependencies": {
22 | "@babel/core": "^7.26.0",
23 | "@babel/preset-env": "^7.26.0",
24 | "@babel/preset-typescript": "^7.26.0",
25 | "@changesets/changelog-github": "^0.4.8",
26 | "@changesets/cli": "^2.26.2",
27 | "@types/jest": "^29.5.2",
28 | "babel-jest": "^29.7.0",
29 | "concurrently": "^8.2.0",
30 | "esbuild": "0.25.0",
31 | "jest": "^29.7.0",
32 | "jest-environment-jsdom": "^29.7.0",
33 | "prettier": "^2.8.8"
34 | },
35 | "overrides": {
36 | "axios": "1.8.4"
37 | },
38 | "devDependencies": {
39 | "@types/node": "^20.3.2",
40 | "dotenv": "^16.3.1",
41 | "jest-websocket-mock": "^2.5.0",
42 | "mock-socket": "^9.3.1",
43 | "ts-jest": "^29.2.5",
44 | "typescript": "^5.6.3"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/compatibility-api/README.md:
--------------------------------------------------------------------------------
1 | # @signalwire/compatibility-api
2 |
3 | The `@signalwire/compatibility-api` SDK has been moved in the [compatibility-api-js](https://github.com/signalwire/compatibility-api-js) repository.
4 |
--------------------------------------------------------------------------------
/packages/core/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | dist
5 | docs
6 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # @signalwire/core
2 |
3 | The SignalWire Core package contains lower level objects and shared utilities for various `@signalwire` packages.
4 |
5 | **Important**: This package is intended for internal use by the SignalWire libraries. You should not use it directly in your production projects, as the APIs can and will change often. Usage of this package directly is currently not supported.
6 |
7 | ## Related
8 |
9 | - [Get Started](https://developer.signalwire.com/)
10 | - [`@signalwire/js`](https://www.npmjs.com/package/@signalwire/js)
11 | - [`@signalwire/realtime-api`](https://www.npmjs.com/package/@signalwire/realtime-api)
12 |
13 | ## License
14 |
15 | `@signalwire/core` is copyright © 2018-2023 [SignalWire](http://signalwire.com). It is free software, and may be redistributed under the terms specified in the [MIT-LICENSE](https://github.com/signalwire/signalwire-js/blob/master/LICENSE) file.
16 |
--------------------------------------------------------------------------------
/packages/core/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | '\\.[jt]sx?$': ['babel-jest', { configFile: './../../babel.config.js' }],
4 | },
5 | setupFiles: ['./src/setupTests.ts'],
6 | }
7 |
--------------------------------------------------------------------------------
/packages/core/src/BaseClient.ts:
--------------------------------------------------------------------------------
1 | import { destroyAction, initAction } from './redux'
2 | import { BaseClientOptions } from './utils/interfaces'
3 | import { BaseComponent } from './BaseComponent'
4 | import { EventEmitter } from './utils/EventEmitter'
5 | import { getAuthStatus } from './redux/features/session/sessionSelectors'
6 |
7 | export class BaseClient<
8 | EventTypes extends EventEmitter.ValidEventTypes
9 | > extends BaseComponent {
10 | constructor(public options: BaseClientOptions) {
11 | super(options)
12 | }
13 |
14 | /**
15 | * Connect the underlay WebSocket connection to the SignalWire network.
16 | *
17 | * @returns Promise that will resolve with the Client object.
18 | */
19 | connect(): Promise {
20 | const authStatus = getAuthStatus(this.store.getState())
21 |
22 | if (authStatus === 'unknown' || authStatus === 'unauthorized') {
23 | this.store.dispatch(initAction())
24 | }
25 |
26 | return this._waitUntilSessionAuthorized()
27 | }
28 |
29 | /**
30 | * Disconnect the Client from the SignalWire network.
31 | */
32 | disconnect() {
33 | this.store.dispatch(destroyAction())
34 | }
35 |
36 | override removeAllListeners>(
37 | event?: T
38 | ) {
39 | this.sessionEventNames().forEach((eventName) => {
40 | this.sessionEmitter.off(eventName)
41 | })
42 |
43 | return super.removeAllListeners(event)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/core/src/BaseConsumer.test.ts:
--------------------------------------------------------------------------------
1 | import { BaseConsumer, connect } from '.'
2 | import { configureFullStack } from './testUtils'
3 |
4 | describe('BaseConsumer', () => {
5 | describe('subscribe', () => {
6 | let instance: any
7 | let fullStack: ReturnType
8 |
9 | beforeEach(() => {
10 | fullStack = configureFullStack()
11 |
12 | instance = connect({
13 | store: fullStack.store,
14 | Component: BaseConsumer,
15 | })({
16 | emitter: fullStack.emitter,
17 | })
18 | instance.execute = jest.fn()
19 | })
20 |
21 | afterEach(() => {
22 | fullStack.destroy()
23 | })
24 |
25 | it('should be idempotent', async () => {
26 | instance.on('something-1', () => {})
27 | instance.on('something-2', () => {})
28 | instance.on('something-2', () => {})
29 |
30 | await instance.subscribe()
31 | await instance.subscribe()
32 | await instance.subscribe()
33 | await instance.subscribe()
34 | await instance.subscribe()
35 | expect(instance.execute).toHaveBeenCalledTimes(1)
36 | })
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/packages/core/src/CustomErrors.ts:
--------------------------------------------------------------------------------
1 | export class AuthError extends Error {
2 | name = 'AuthError'
3 |
4 | constructor(public code: number, public message: string) {
5 | super(message)
6 | Object.setPrototypeOf(this, AuthError.prototype)
7 | }
8 | }
9 |
10 | export class HttpError extends Error {
11 | name = 'HttpError'
12 |
13 | constructor(
14 | public code: number,
15 | public message: string,
16 | public response?: Record
17 | ) {
18 | super(message)
19 | Object.setPrototypeOf(this, HttpError.prototype)
20 | }
21 | }
22 |
23 | export class CapabilityError extends Error {
24 | constructor(message: string) {
25 | super(message)
26 | this.name = 'CapabilityError'
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/core/src/RPCMessages/RPCConnect.ts:
--------------------------------------------------------------------------------
1 | import { makeRPCRequest } from './helpers'
2 |
3 | type WithToken = { token: string; jwt_token?: never }
4 | type WithJWT = { token?: never; jwt_token: string }
5 | type RPCConnectAuthentication = { project?: string } & (WithToken | WithJWT)
6 | export type RPCConnectParams = {
7 | authentication: RPCConnectAuthentication
8 | version?: typeof DEFAULT_CONNECT_VERSION
9 | agent?: string
10 | protocol?: string
11 | authorization_state?: string
12 | contexts?: string[]
13 | topics?: string[]
14 | eventing?: string[]
15 | event_acks?: boolean
16 | }
17 |
18 | export const DEFAULT_CONNECT_VERSION = {
19 | major: 3,
20 | minor: 0,
21 | revision: 0,
22 | }
23 |
24 | export const UNIFIED_CONNECT_VERSION = {
25 | major: 4,
26 | minor: 0,
27 | revision: 0,
28 | }
29 |
30 | export const RPCConnect = (params: RPCConnectParams) => {
31 | return makeRPCRequest({
32 | method: 'signalwire.connect',
33 | params: {
34 | version: DEFAULT_CONNECT_VERSION,
35 | event_acks: true,
36 | ...params,
37 | },
38 | })
39 | }
40 |
--------------------------------------------------------------------------------
/packages/core/src/RPCMessages/RPCDisconnect.ts:
--------------------------------------------------------------------------------
1 | import { makeRPCResponse } from './helpers'
2 |
3 | export const RPCDisconnectResponse = (id: string) => {
4 | return makeRPCResponse({
5 | id,
6 | result: {},
7 | })
8 | }
9 |
--------------------------------------------------------------------------------
/packages/core/src/RPCMessages/RPCEventAck.ts:
--------------------------------------------------------------------------------
1 | import { makeRPCResponse } from './helpers';
2 |
3 |
4 | export const RPCEventAckResponse = (id: string) => makeRPCResponse({ id, result: {} })
5 |
--------------------------------------------------------------------------------
/packages/core/src/RPCMessages/RPCExecute.ts:
--------------------------------------------------------------------------------
1 | import { makeRPCRequest } from './helpers'
2 | import { JSONRPCMethod } from '../utils/interfaces'
3 |
4 | type RPCExecuteParams = {
5 | id?: string
6 | method: JSONRPCMethod
7 | params: Record
8 | }
9 |
10 | export const RPCExecute = ({ method, params }: RPCExecuteParams) => {
11 | return makeRPCRequest({
12 | method,
13 | params,
14 | })
15 | }
16 |
--------------------------------------------------------------------------------
/packages/core/src/RPCMessages/RPCPing.ts:
--------------------------------------------------------------------------------
1 | import { makeRPCRequest, makeRPCResponse } from './helpers'
2 |
3 | export const RPCPing = () => {
4 | return makeRPCRequest({
5 | method: 'signalwire.ping',
6 | params: {
7 | timestamp: Date.now() / 1000,
8 | },
9 | })
10 | }
11 |
12 | export const RPCPingResponse = (id: string, timestamp?: number) => {
13 | return makeRPCResponse({
14 | id,
15 | result: {
16 | timestamp: timestamp || Date.now() / 1000,
17 | },
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/RPCMessages/RPCReauthenticate.ts:
--------------------------------------------------------------------------------
1 | import { makeRPCRequest } from './helpers'
2 |
3 | export type RPCReauthenticateParams = { project: string; jwt_token: string }
4 |
5 | export const RPCReauthenticate = (authentication: RPCReauthenticateParams) => {
6 | return makeRPCRequest({
7 | method: 'signalwire.reauthenticate',
8 | params: {
9 | authentication,
10 | },
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/packages/core/src/RPCMessages/helpers.ts:
--------------------------------------------------------------------------------
1 | import { uuid } from '../utils'
2 | import { JSONRPCMethod } from '../utils/interfaces'
3 |
4 | interface MakeRPCRequestParams {
5 | id?: string
6 | method: JSONRPCMethod
7 | params: {
8 | // TODO: use list of types?
9 | [key: string]: any
10 | }
11 | }
12 | export const makeRPCRequest = (params: MakeRPCRequestParams) => {
13 | return {
14 | jsonrpc: '2.0' as const,
15 | id: params.id ?? uuid(),
16 | ...params,
17 | }
18 | }
19 |
20 | interface MakeRPCResponseParams {
21 | id: string
22 | result: {
23 | // TODO: use list of types?
24 | [key: string]: any
25 | }
26 | }
27 | export const makeRPCResponse = (params: MakeRPCResponseParams) => {
28 | return {
29 | jsonrpc: '2.0' as const,
30 | ...params,
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/core/src/RPCMessages/index.ts:
--------------------------------------------------------------------------------
1 | export * from './helpers'
2 | export * from './RPCConnect'
3 | export * from './RPCReauthenticate'
4 | export * from './RPCPing'
5 | export * from './RPCExecute'
6 | export * from './RPCDisconnect'
7 | export * from './VertoMessages'
8 | export * from './RPCEventAck'
9 |
--------------------------------------------------------------------------------
/packages/core/src/chat/ChatMember.ts:
--------------------------------------------------------------------------------
1 | import { ChatMemberContract } from '..'
2 |
3 | /**
4 | * Represents a member in a chat.
5 | */
6 | export class ChatMember implements ChatMemberContract {
7 | constructor(private payload: ChatMemberContract) {}
8 |
9 | /** The id of this member */
10 | get id(): string {
11 | return this.payload.id
12 | }
13 |
14 | /** The channel of this member */
15 | get channel(): string {
16 | return this.payload.channel
17 | }
18 |
19 | /** The state of this member */
20 | get state(): any {
21 | return this.payload.state ?? {}
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/chat/ChatMessage.ts:
--------------------------------------------------------------------------------
1 | import { ChatMessageContract, ChatMemberContract } from '..'
2 | import { PubSubMessage } from '../pubSub'
3 |
4 | /**
5 | * Represents a message in a chat.
6 | */
7 | export class ChatMessage extends PubSubMessage {
8 | /** The member which sent this message */
9 | get member(): ChatMemberContract {
10 | return this.payload.member
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/core/src/chat/index.ts:
--------------------------------------------------------------------------------
1 | export * from './methods'
2 | export * from './BaseChat'
3 | export * from './ChatMessage'
4 | export * from './ChatMember'
5 | export * from './applyCommonMethods'
6 |
--------------------------------------------------------------------------------
/packages/core/src/chat/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './toInternalChatChannels'
2 |
3 | export const isValidChannels = (input: unknown): input is string | string[] => {
4 | return Array.isArray(input) || typeof input === 'string'
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/src/chat/utils/toInternalChatChannels.test.ts:
--------------------------------------------------------------------------------
1 | import { toInternalChatChannels } from './toInternalChatChannels'
2 |
3 | describe('toInternalChatChannels', () => {
4 | it('should convert a single string channel to array of objects', () => {
5 | const result = toInternalChatChannels('watercooler')
6 |
7 | expect(result).toStrictEqual([
8 | {
9 | name: 'watercooler',
10 | },
11 | ])
12 | })
13 |
14 | it('should convert multiple channels to array of objects', () => {
15 | const result = toInternalChatChannels(['channel1', 'channel2'])
16 |
17 | expect(result).toStrictEqual([
18 | {
19 | name: 'channel1',
20 | },
21 | {
22 | name: 'channel2',
23 | },
24 | ])
25 | })
26 |
27 | it('should return empty array when undefined', () => {
28 | const result = toInternalChatChannels(undefined)
29 |
30 | expect(result).toStrictEqual([])
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/packages/core/src/chat/utils/toInternalChatChannels.ts:
--------------------------------------------------------------------------------
1 | import { InternalChatChannel } from '../..'
2 |
3 | export const toInternalChatChannels = (
4 | channels: string | string[] | undefined
5 | ): InternalChatChannel[] => {
6 | const list = !channels || Array.isArray(channels) ? channels : [channels]
7 |
8 | if (Array.isArray(list)) {
9 | return list.map((name) => {
10 | return {
11 | name,
12 | }
13 | })
14 | }
15 |
16 | return []
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/src/chat/workers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './chatWorker'
2 |
--------------------------------------------------------------------------------
/packages/core/src/index.test.ts:
--------------------------------------------------------------------------------
1 | describe('Fake', () => {
2 | it('works', () => {
3 | expect(2).toEqual(2)
4 | })
5 | })
6 |
--------------------------------------------------------------------------------
/packages/core/src/memberPosition/index.ts:
--------------------------------------------------------------------------------
1 | export * from './workers'
2 |
--------------------------------------------------------------------------------
/packages/core/src/pubSub/PubSubMessage.ts:
--------------------------------------------------------------------------------
1 | import { PubSubMessageContract } from '..'
2 |
3 | /**
4 | * Represents a message in a pubSub context.
5 | */
6 | export class PubSubMessage<
7 | PayloadType extends PubSubMessageContract = PubSubMessageContract
8 | > implements PubSubMessageContract
9 | {
10 | constructor(protected payload: PayloadType) {}
11 |
12 | /** The id of this message */
13 | get id(): string {
14 | return this.payload.id
15 | }
16 |
17 | /** The channel in which this message was sent */
18 | get channel(): string {
19 | return this.payload.channel
20 | }
21 |
22 | /** The content of this message */
23 | get content(): string {
24 | return this.payload.content
25 | }
26 |
27 | /** Any metadata associated to this message */
28 | get meta(): any {
29 | return this.payload.meta
30 | }
31 |
32 | /** The date at which this message was published */
33 | get publishedAt(): Date {
34 | return this.payload.publishedAt
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/core/src/pubSub/index.ts:
--------------------------------------------------------------------------------
1 | export * from './BasePubSub'
2 | export * from './PubSubMessage'
3 |
--------------------------------------------------------------------------------
/packages/core/src/pubSub/workers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './pubSubWorker'
2 |
--------------------------------------------------------------------------------
/packages/core/src/redux/features/component/componentSaga.ts:
--------------------------------------------------------------------------------
1 | import { SagaIterator } from '@redux-saga/core'
2 | import { delay, fork, put, select } from '@redux-saga/core/effects'
3 | import { componentActions } from '..'
4 | import { getComponentsToCleanup } from './componentSelectors'
5 |
6 | export function* componentCleanupSagaWorker(): SagaIterator {
7 | const toCleanup = yield select(getComponentsToCleanup)
8 |
9 | if (toCleanup.length) {
10 | yield put(
11 | componentActions.cleanup({
12 | ids: toCleanup,
13 | })
14 | )
15 | }
16 | }
17 |
18 | export function* componentCleanupSaga(): SagaIterator {
19 | while (true) {
20 | yield delay(3600_000) // 1 hour
21 | yield fork(componentCleanupSagaWorker)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/redux/features/component/componentSelectors.test.ts:
--------------------------------------------------------------------------------
1 | import { Store } from 'redux'
2 | import { configureJestStore } from '../../../testUtils'
3 | import { getComponentsToCleanup } from './componentSelectors'
4 |
5 | describe('componentSelectors', () => {
6 | let store: Store
7 |
8 | beforeEach(() => {
9 | store = configureJestStore({
10 | preloadedState: {
11 | components: {
12 | byId: {
13 | '268b4cf8-a3c5-4003-8666-3b7a4f0a5af9': {
14 | id: '268b4cf8-a3c5-4003-8666-3b7a4f0a5af9',
15 | },
16 | 'faa63915-3a64-4c39-acbb-06dac0758f8a': {
17 | id: 'faa63915-3a64-4c39-acbb-06dac0758f8a',
18 | responses: {},
19 | },
20 | 'zfaa63915-3a64-4c39-acbb-06dac0758f8a': {
21 | id: 'zfaa63915-3a64-4c39-acbb-06dac0758f8a',
22 | errors: {},
23 | },
24 | },
25 | },
26 | },
27 | })
28 | })
29 |
30 | describe('getComponentsToCleanup', () => {
31 | it('should return the list of components to cleanup', () => {
32 | expect(getComponentsToCleanup(store.getState())).toEqual([
33 | 'faa63915-3a64-4c39-acbb-06dac0758f8a',
34 | 'zfaa63915-3a64-4c39-acbb-06dac0758f8a',
35 | ])
36 | })
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/packages/core/src/redux/features/component/componentSelectors.ts:
--------------------------------------------------------------------------------
1 | import { ReduxComponent, SDKState } from '../../interfaces'
2 |
3 | export const getComponent = ({ components }: SDKState, id: string) => {
4 | return components.byId?.[id]
5 | }
6 |
7 | export const getComponentsById = ({ components }: SDKState) => {
8 | return components.byId
9 | }
10 |
11 | export const getComponentsToCleanup = (state: SDKState) => {
12 | const components = getComponentsById(state)
13 |
14 | let toCleanup: Array = []
15 | Object.keys(components).forEach((id) => {
16 | if (components[id].responses || components[id].errors) {
17 | toCleanup.push(id)
18 | }
19 | })
20 |
21 | return toCleanup
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/redux/features/index.ts:
--------------------------------------------------------------------------------
1 | export * from './component/componentSlice'
2 | export * from './session/sessionSlice'
3 |
--------------------------------------------------------------------------------
/packages/core/src/redux/features/session/sessionSelectors.ts:
--------------------------------------------------------------------------------
1 | import { SDKState } from '../../interfaces'
2 |
3 | export const getIceServers = ({ session }: SDKState) => {
4 | return session?.iceServers ?? []
5 | }
6 |
7 | export const getSession = (store: SDKState) => {
8 | return store.session
9 | }
10 |
11 | export const getAuthStatus = ({ session }: SDKState) => {
12 | return session.authStatus
13 | }
14 |
15 | export const getAuthError = ({ session }: SDKState) => {
16 | return session.authError
17 | }
18 |
19 | export const getAuthorization = ({ session }: SDKState) => {
20 | return session.authorization
21 | }
22 |
23 | export const getAuthorizationState = ({ session }: SDKState) => {
24 | return session.authorizationState
25 | }
26 |
27 | export const getProtocol = ({ session }: SDKState) => {
28 | return session.protocol
29 | }
30 |
--------------------------------------------------------------------------------
/packages/core/src/redux/rootReducer.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from './toolkit'
2 | import { componentReducer, sessionReducer } from './features'
3 |
4 | export const rootReducer = combineReducers({
5 | components: componentReducer,
6 | session: sessionReducer,
7 | })
8 |
--------------------------------------------------------------------------------
/packages/core/src/redux/toolkit/getDefaultMiddleware.ts:
--------------------------------------------------------------------------------
1 | import { Middleware } from 'redux'
2 | import { MiddlewareArray } from './utils'
3 |
4 | interface GetDefaultMiddlewareOptions {}
5 |
6 | export type CurriedGetDefaultMiddleware = <
7 | O extends Partial = {
8 | thunk: true
9 | immutableCheck: true
10 | serializableCheck: true
11 | }
12 | >(
13 | options?: O
14 | ) => MiddlewareArray>
15 |
16 | export function curryGetDefaultMiddleware<
17 | S = any
18 | >(): CurriedGetDefaultMiddleware {
19 | return function curriedGetDefaultMiddleware() {
20 | return [] as unknown as MiddlewareArray>
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/redux/toolkit/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Cherry-picked (and adapted) version of Redux Toolkit. API
3 | * wise it remains fully compatible but we changed a couple
4 | * of things to reduce the overall bundle size. Most
5 | * important is that our version doesn't depend on Immer and
6 | * we don't include any of the default middleares included
7 | * by RTK like thunks or inmutable state (we do this through
8 | * TS).
9 | */
10 | export * from 'redux'
11 | export * from './createAction'
12 | export * from './configureStore'
13 |
--------------------------------------------------------------------------------
/packages/core/src/redux/toolkit/isPlainObject.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns true if the passed value is "plain" object, i.e. an object whose
3 | * prototype is the root `Object.prototype`. This includes objects created
4 | * using object literals, but not for instance for class instances.
5 | *
6 | * @param {any} value The value to inspect.
7 | * @returns {boolean} True if the argument appears to be a plain object.
8 | *
9 | * @public
10 | */
11 | export default function isPlainObject(value: unknown): value is object {
12 | if (typeof value !== 'object' || value === null) return false
13 |
14 | let proto = Object.getPrototypeOf(value)
15 | if (proto === null) return true
16 |
17 | let baseProto = proto
18 | while (Object.getPrototypeOf(baseProto) !== null) {
19 | baseProto = Object.getPrototypeOf(baseProto)
20 | }
21 |
22 | return proto === baseProto
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/redux/utils/createDestroyableSlice.ts:
--------------------------------------------------------------------------------
1 | import {
2 | SliceCaseReducers,
3 | ValidateSliceCaseReducers,
4 | createSlice,
5 | } from '../toolkit/createSlice'
6 | import { ActionReducerMapBuilder } from '../toolkit/mapBuilders'
7 | import { destroyAction } from '../actions'
8 |
9 | export const createDestroyableSlice = <
10 | T,
11 | Reducers extends SliceCaseReducers
12 | >({
13 | name = '',
14 | initialState,
15 | reducers,
16 | extraReducers,
17 | }: {
18 | name: string
19 | initialState: T
20 | reducers: ValidateSliceCaseReducers
21 | extraReducers?: (builder: ActionReducerMapBuilder) => void
22 | }) => {
23 | return createSlice({
24 | name,
25 | initialState,
26 | reducers,
27 | extraReducers: (builder) => {
28 | builder.addCase(destroyAction.type, () => {
29 | return initialState
30 | })
31 |
32 | if (typeof extraReducers === 'function') {
33 | extraReducers(builder)
34 | }
35 | },
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/packages/core/src/redux/utils/sagaHelpers.ts:
--------------------------------------------------------------------------------
1 | import { Saga } from '@redux-saga/core'
2 | import { call, spawn } from '@redux-saga/core/effects'
3 | import { getLogger } from '../../utils'
4 |
5 | export const createRestartableSaga = (saga: Saga) => {
6 | return function* () {
7 | spawn(function* () {
8 | while (true) {
9 | try {
10 | getLogger().debug('Run a restartable saga')
11 | yield call(saga)
12 | getLogger().debug(
13 | 'One of the restartable saga has ended. Restarting..'
14 | )
15 | } catch (error) {
16 | getLogger().error('Restartable Saga Error', error)
17 | }
18 | }
19 | })
20 | }
21 | }
22 |
23 | const defaultCatchHandler = (error: any) =>
24 | getLogger().error('Catchable Saga Error', error)
25 |
26 | export const createCatchableSaga = (
27 | saga: Saga,
28 | errorHandler = defaultCatchHandler
29 | ) => {
30 | return function* (...params: Args[]) {
31 | try {
32 | yield call(saga, ...params)
33 | } catch (error) {
34 | errorHandler(error)
35 | }
36 | }
37 | }
38 |
39 | export { eventChannel } from '@redux-saga/core'
40 |
--------------------------------------------------------------------------------
/packages/core/src/redux/utils/useInstanceMap.ts:
--------------------------------------------------------------------------------
1 | import { InstanceMap } from '../../utils/interfaces'
2 |
3 | export const useInstanceMap = (): InstanceMap => {
4 | /**
5 | * Generic map stores multiple instance
6 | * For eg;
7 | * callId => CallInstance
8 | * controlId => PlaybackInstance | RecordingInstance
9 | */
10 | const instanceMap = new Map()
11 |
12 | const getInstance = (key: string): T => {
13 | return instanceMap.get(key) as T
14 | }
15 |
16 | const setInstance = (key: string, value: T) => {
17 | instanceMap.set(key, value)
18 | return instanceMap as Map
19 | }
20 |
21 | const deleteInstance = (key: string) => {
22 | instanceMap.delete(key)
23 | return instanceMap as Map
24 | }
25 |
26 | const getAllInstances = () => {
27 | return Array.from(instanceMap.entries())
28 | }
29 |
30 | const deleteAllInstances = () => {
31 | instanceMap.clear()
32 | return instanceMap
33 | }
34 |
35 | return {
36 | get: getInstance,
37 | set: setInstance,
38 | remove: deleteInstance,
39 | getAll: getAllInstances,
40 | deleteAll: deleteAllInstances,
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/core/src/redux/utils/useSession.ts:
--------------------------------------------------------------------------------
1 | import { BaseSession } from '../../BaseSession'
2 | import { getLogger } from '../../utils'
3 | import { getEventEmitter } from '../../utils/EventEmitter'
4 | import {
5 | ClientEvents,
6 | InternalUserOptions,
7 | SessionConstructor,
8 | } from '../../utils/interfaces'
9 | import { SessionChannel } from '../interfaces'
10 |
11 | interface UseSessionOptions {
12 | userOptions: InternalUserOptions
13 | SessionConstructor: SessionConstructor
14 | sessionChannel: SessionChannel
15 | }
16 |
17 | export const useSession = (options: UseSessionOptions) => {
18 | const { SessionConstructor, userOptions, sessionChannel } = options
19 |
20 | const sessionEmitter = getEventEmitter()
21 |
22 | let session: BaseSession | null = null
23 |
24 | const initSession = () => {
25 | session = new SessionConstructor({
26 | ...userOptions,
27 | sessionChannel,
28 | })
29 | return session
30 | }
31 |
32 | const getSession = () => {
33 | if (!session) {
34 | getLogger().warn('Session does not exist!')
35 | }
36 | return session
37 | }
38 |
39 | return { session, initSession, getSession, sessionEmitter }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/core/src/rooms/RoomSessionStream.test.ts:
--------------------------------------------------------------------------------
1 | import { configureJestStore } from '../testUtils'
2 | import {
3 | createRoomSessionStreamObject,
4 | RoomSessionStream,
5 | } from './RoomSessionStream'
6 |
7 | describe('RoomSessionStream', () => {
8 | describe('createRoomSessionStreamObject', () => {
9 | let instance: RoomSessionStream
10 | beforeEach(() => {
11 | instance = createRoomSessionStreamObject({
12 | store: configureJestStore(),
13 | payload: {
14 | // @ts-expect-error
15 | stream: {
16 | id: 'c22d7223-5a01-49fe-8da0-46bec8e75e32',
17 | },
18 | room_session_id: 'room-session-id',
19 | },
20 | })
21 | // @ts-expect-error
22 | instance.execute = jest.fn()
23 | })
24 |
25 | it('should control an active stream', async () => {
26 | const baseExecuteParams = {
27 | method: '',
28 | params: {
29 | room_session_id: 'room-session-id',
30 | stream_id: 'c22d7223-5a01-49fe-8da0-46bec8e75e32',
31 | },
32 | }
33 |
34 | await instance.stop()
35 | // @ts-expect-error
36 | expect(instance.execute).toHaveBeenLastCalledWith({
37 | ...baseExecuteParams,
38 | method: 'video.stream.stop',
39 | })
40 | })
41 | })
42 | })
43 |
--------------------------------------------------------------------------------
/packages/core/src/rooms/index.ts:
--------------------------------------------------------------------------------
1 | import { BaseComponent, EventEmitter } from '..'
2 |
3 | export interface BaseRoomInterface<
4 | EventTypes extends EventEmitter.ValidEventTypes
5 | > extends BaseComponent {
6 | roomId: string
7 | roomSessionId: string
8 | memberId: string
9 | }
10 |
11 | export * from './methods'
12 | export * from './RoomSessionRecording'
13 | export * from './RoomSessionPlayback'
14 | export * from './RoomSessionStream'
15 |
--------------------------------------------------------------------------------
/packages/core/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | jest.mock('./utils', () => {
2 | return {
3 | ...(jest.requireActual('./utils') as any),
4 | logger: {
5 | error: jest.fn(),
6 | warn: jest.fn(),
7 | log: jest.fn(),
8 | info: jest.fn(),
9 | debug: jest.fn(),
10 | trace: jest.fn(),
11 | levels: jest.fn(),
12 | setLevel: jest.fn(),
13 | },
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/packages/core/src/types/common.ts:
--------------------------------------------------------------------------------
1 | export type PaginationCursor =
2 | | {
3 | before: string
4 | after?: never
5 | }
6 | | {
7 | before?: never
8 | after: string
9 | }
10 |
11 | export type ClientContextMethod = 'signalwire.receive' | 'signalwire.unreceive'
12 |
13 | /**
14 | * Private common event types
15 | */
16 | export type CallPlay = 'call.play'
17 | export type CallRecord = 'call.record'
18 |
19 | /**
20 | * Public common event types
21 | */
22 | export type PlaybackStarted = 'playback.started'
23 | export type PlaybackUpdated = 'playback.updated'
24 | export type PlaybackEnded = 'playback.ended'
25 | export type PlaybackFailed = 'playback.failed'
26 | export type RecordingStarted = 'recording.started'
27 | export type RecordingUpdated = 'recording.updated'
28 | export type RecordingEnded = 'recording.ended'
29 | export type RecordingFailed = 'recording.failed'
30 |
--------------------------------------------------------------------------------
/packages/core/src/types/conversation.ts:
--------------------------------------------------------------------------------
1 | import { SwEvent } from '..'
2 |
3 | export type ConversationMessageEventName = 'conversation.message'
4 |
5 | export interface ConversationMessageEventParams {
6 | conversation_id: string
7 | conversation_name: string
8 | details: Record
9 | hidden: boolean
10 | id: string
11 | kind?: string
12 | metadata: Record
13 | subtype: string
14 | text?: string
15 | ts: number
16 | type: string
17 | user_id: string
18 | user_name: string
19 | address_id: string
20 | from_address_id: string
21 | }
22 |
23 | export interface ConversationMessageEvent extends SwEvent {
24 | event_type: ConversationMessageEventName
25 | params: ConversationMessageEventParams
26 | }
27 |
28 | export type ConversationEvent = ConversationMessageEvent
29 |
30 | export type ConversationEventParams = ConversationMessageEventParams
31 |
--------------------------------------------------------------------------------
/packages/core/src/types/fabric.ts:
--------------------------------------------------------------------------------
1 | import { MapToPubSubShape } from '../redux/interfaces'
2 | import {
3 | FabricLayoutEvent,
4 | FabricLayoutEventNames,
5 | FabricLayoutEventParams,
6 | } from './fabricLayout'
7 | import {
8 | FabricMemberEvent,
9 | FabricMemberEventNames,
10 | FabricMemberEventParams,
11 | } from './fabricMember'
12 | import {
13 | FabricRoomEvent,
14 | FabricRoomEventNames,
15 | FabricRoomEventParams,
16 | } from './fabricRoomSession'
17 |
18 | export * from './fabricRoomSession'
19 | export * from './fabricMember'
20 | export * from './fabricLayout'
21 |
22 | /**
23 | * List of all call fabric events
24 | */
25 | export type FabricEventNames =
26 | | FabricRoomEventNames
27 | | FabricMemberEventNames
28 | | FabricLayoutEventNames
29 |
30 | export type FabricEvent =
31 | | FabricRoomEvent
32 | | FabricMemberEvent
33 | | FabricLayoutEvent
34 |
35 | export type FabricEventParams =
36 | | FabricRoomEventParams
37 | | FabricMemberEventParams
38 | | FabricLayoutEventParams
39 |
40 | export type FabricAction = MapToPubSubShape
41 |
--------------------------------------------------------------------------------
/packages/core/src/types/fabricLayout.ts:
--------------------------------------------------------------------------------
1 | import { SwEvent } from '.'
2 | import { InternalVideoLayout, LayoutChanged } from './videoLayout'
3 |
4 | /**
5 | * ==========
6 | * ==========
7 | * Server-Side Events
8 | * ==========
9 | * ==========
10 | */
11 |
12 | /**
13 | * layout.changed
14 | */
15 | export interface FabricLayoutChangedEventParams {
16 | room_id: string
17 | room_session_id: string
18 | layout: InternalVideoLayout
19 | }
20 |
21 | export interface FabricLayoutChangedEvent extends SwEvent {
22 | event_type: LayoutChanged
23 | params: FabricLayoutChangedEventParams
24 | }
25 |
26 | export type FabricLayoutEventNames = LayoutChanged
27 |
28 | export type FabricLayoutEvent = FabricLayoutChangedEvent
29 |
30 | export type FabricLayoutEventParams = FabricLayoutChangedEventParams
31 |
--------------------------------------------------------------------------------
/packages/core/src/types/task.ts:
--------------------------------------------------------------------------------
1 | import type { OnlyStateProperties, OnlyFunctionProperties } from '..'
2 |
3 | export type TaskReceivedEventName = 'task.received'
4 |
5 | export type TaskEventNames = TaskReceivedEventName
6 |
7 | export interface TaskContract {}
8 |
9 | export type TaskEntity = OnlyStateProperties
10 | export type TaskMethods = Omit<
11 | OnlyFunctionProperties,
12 | 'subscribe' | 'unsubscribe' | 'updateToken'
13 | >
14 |
15 | /**
16 | * ==========
17 | * ==========
18 | * Server-Side Events
19 | * ==========
20 | * ==========
21 | */
22 |
23 | /**
24 | * 'queuing.relay.tasks'
25 | */
26 | export interface TaskInboundEvent {
27 | event_type: 'queuing.relay.tasks'
28 | context: string
29 | message: Record
30 | timestamp: number
31 | space_id: string
32 | project_id: string
33 | }
34 |
35 | export type TaskEvent = TaskInboundEvent
36 |
37 | /**
38 | * TODO: update MapToPubSubShape in another PR
39 | * not used MapToPubSubShape because queuing.relay.tasks
40 | * has a different shape
41 | */
42 | export type TaskAction = {
43 | type: TaskReceivedEventName
44 | payload: TaskInboundEvent
45 | }
46 |
--------------------------------------------------------------------------------
/packages/core/src/types/videoRoomDevice.ts:
--------------------------------------------------------------------------------
1 | export type CameraUpdated = 'camera.updated'
2 | export type CameraDisconnected = 'camera.disconnected'
3 | export type MicrophoneUpdated = 'microphone.updated'
4 | export type MicrophoneDisconnected = 'microphone.disconnected'
5 | export type SpeakerUpdated = 'speaker.updated'
6 | export type SpeakerDisconnected = 'speaker.disconnected'
7 |
8 | /**
9 | * List of public event names
10 | */
11 |
12 | export type VideoRoomDeviceUpdatedEventNames =
13 | | CameraUpdated
14 | | MicrophoneUpdated
15 | | SpeakerUpdated
16 |
17 | export type VideoRoomDeviceDisconnectedEventNames =
18 | | CameraDisconnected
19 | | MicrophoneDisconnected
20 | | SpeakerDisconnected
21 |
22 | export type VideoRoomDeviceEventNames =
23 | | VideoRoomDeviceUpdatedEventNames
24 | | VideoRoomDeviceDisconnectedEventNames
25 |
26 | export interface VideoRoomMediaDeviceInfo {
27 | deviceId: MediaDeviceInfo['deviceId'] | undefined
28 | label: MediaDeviceInfo['label'] | undefined
29 | }
30 |
31 | export interface DeviceUpdatedEventParams {
32 | previous: VideoRoomMediaDeviceInfo
33 | current: VideoRoomMediaDeviceInfo
34 | }
35 |
36 | export type DeviceDisconnectedEventParams = VideoRoomMediaDeviceInfo
37 |
38 | export type VideoRoomDeviceEventParams =
39 | | DeviceUpdatedEventParams
40 | | DeviceDisconnectedEventParams
41 |
--------------------------------------------------------------------------------
/packages/core/src/types/voice.ts:
--------------------------------------------------------------------------------
1 | import { PRODUCT_PREFIX_VOICE_CALL } from '../utils/constants'
2 |
3 | export type VoiceNamespace = typeof PRODUCT_PREFIX_VOICE_CALL
4 | export type ToInternalVoiceEvent = `${VoiceNamespace}.${T}`
5 |
6 | export interface NestedArray extends Array> {}
7 |
8 | export * from './voiceCall'
9 | export * from './voicePlayback'
10 | export * from './voiceRecording'
11 | export * from './voiceDetect'
12 | export * from './voiceTap'
13 | export * from './voiceCollect'
14 | export * from './voicePrompt'
15 | export * from './voiceSendDigits'
16 | export * from './voiceConnect'
17 |
--------------------------------------------------------------------------------
/packages/core/src/utils/EventEmitter.ts:
--------------------------------------------------------------------------------
1 | import EventEmitter from 'eventemitter3'
2 |
3 | const REQUIRED_EMITTER_METHODS = [
4 | 'on',
5 | 'off',
6 | 'once',
7 | 'removeAllListeners',
8 | 'emit',
9 | ]
10 |
11 | /**
12 | * Checks the shape of the emitter at runtime. This is useful for when
13 | * the user is using the SDK without TS
14 | */
15 | const assertEventEmitter = (emitter: unknown): emitter is EventEmitter => {
16 | if (
17 | emitter &&
18 | typeof emitter === 'object' &&
19 | REQUIRED_EMITTER_METHODS.every((name) => name in emitter)
20 | ) {
21 | return true
22 | }
23 |
24 | return false
25 | }
26 |
27 | const getEventEmitter = () => {
28 | return new EventEmitter()
29 | }
30 |
31 | export { assertEventEmitter, EventEmitter, getEventEmitter }
32 |
--------------------------------------------------------------------------------
/packages/core/src/utils/SWCloseEvent.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Class representing a close event.
3 | * The `ws` package does not expose it so we can easily create one in here.
4 | *
5 | * @extends Event
6 | */
7 | export class SWCloseEvent {
8 | public code: number
9 | public reason: string
10 | public wasClean: boolean
11 | constructor(
12 | public type: string,
13 | options: { code?: number; reason?: string; wasClean?: boolean } = {}
14 | ) {
15 | this.code = options.code === undefined ? 0 : options.code
16 | this.reason = options.reason === undefined ? '' : options.reason
17 | this.wasClean = options.wasClean === undefined ? false : options.wasClean
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/utils/common.ts:
--------------------------------------------------------------------------------
1 | import { WebRTCEventType } from '..'
2 |
3 | const UPPERCASE_REGEX = /[A-Z]/g
4 | /**
5 | * Converts values from camelCase to snake_case
6 | * @internal
7 | */
8 | export const fromCamelToSnakeCase = (event: T): T => {
9 | // @ts-ignore
10 | return event.replace(UPPERCASE_REGEX, (letter) => {
11 | return `_${letter.toLowerCase()}`
12 | }) as T
13 | }
14 |
15 | export const WEBRTC_EVENT_TYPES: WebRTCEventType[] = [
16 | 'webrtc.message',
17 | // 'webrtc.verto',
18 | ]
19 | export const isWebrtcEventType = (
20 | eventType: string
21 | ): eventType is WebRTCEventType => {
22 | // @ts-expect-error
23 | return WEBRTC_EVENT_TYPES.includes(eventType)
24 | }
25 |
--------------------------------------------------------------------------------
/packages/core/src/utils/eventUtils.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export const stripNamespacePrefix = (
4 | event: string,
5 | namespace?: string
6 | ): string => {
7 | if (namespace && typeof namespace === 'string') {
8 | const regex = new RegExp(`^${namespace}\.`)
9 | return event.replace(regex, '')
10 | }
11 | const items = event.split('.')
12 | if (items.length > 1) {
13 | items.shift()
14 | return items.join('.')
15 | }
16 | return event
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/src/utils/extendComponent.test.ts:
--------------------------------------------------------------------------------
1 | import { extendComponent } from './extendComponent'
2 |
3 | describe('extendComponent', () => {
4 | it('should allow us to extend an classes with new methods', () => {
5 | class A {}
6 | const ExtendedClass = extendComponent(A, {
7 | methodA: {},
8 | methodB: {},
9 | methodC: {},
10 | methodD: {},
11 | })
12 |
13 | const instance = new ExtendedClass()
14 |
15 | expect(instance).toHaveProperty('methodA')
16 | expect(instance).toHaveProperty('methodB')
17 | expect(instance).toHaveProperty('methodC')
18 | expect(instance).toHaveProperty('methodD')
19 | })
20 |
21 | it('should throw if the base class already has the method defined', () => {
22 | expect(() =>
23 | extendComponent(
24 | class {
25 | methodA() {}
26 | },
27 | {
28 | methodA: {},
29 | methodB: {},
30 | methodC: {},
31 | methodD: {},
32 | }
33 | )
34 | ).toThrow()
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/packages/core/src/utils/extendComponent.ts:
--------------------------------------------------------------------------------
1 | import type { APIMethodsMap } from './interfaces'
2 | import type { ConstructableType } from '../types/utils'
3 |
4 | export const extendComponent = (
5 | klass: any,
6 | methods: APIMethodsMap
7 | ) => {
8 | Object.keys(methods).forEach((methodName) => {
9 | if (klass.prototype.hasOwnProperty(methodName)) {
10 | throw new Error(`[extendComponent] Duplicated method name: ${methodName}`)
11 | }
12 | })
13 |
14 | Object.defineProperties(klass.prototype, methods)
15 |
16 | return klass as ConstructableType
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/src/utils/storage/index.native.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 |
3 | // TODO: Review ReactNative support
4 |
5 | import AsyncStorage from '@react-native-community/async-storage'
6 | import { mutateStorageKey, safeParseJson } from '../'
7 |
8 | const getItem = async (key: string): Promise => {
9 | try {
10 | const res = await AsyncStorage.getItem(mutateStorageKey(key))
11 | return safeParseJson(res)
12 | } catch (error) {
13 | return null
14 | }
15 | }
16 |
17 | const setItem = (key: string, value: any): Promise => {
18 | if (typeof value === 'object') {
19 | value = JSON.stringify(value)
20 | }
21 | return AsyncStorage.setItem(mutateStorageKey(key), value)
22 | }
23 |
24 | const removeItem = (key: string): Promise =>
25 | AsyncStorage.removeItem(mutateStorageKey(key))
26 |
27 | export const localStorage = { getItem, setItem, removeItem }
28 | export const sessionStorage = { getItem, setItem, removeItem }
29 |
--------------------------------------------------------------------------------
/packages/core/src/utils/toInternalAction.ts:
--------------------------------------------------------------------------------
1 | import { JSONRPCRequest } from '..'
2 | import { MapToPubSubShape } from '../redux/interfaces'
3 | import { isWebrtcEventType } from './common'
4 |
5 | export const toInternalAction = <
6 | T extends { event_type: string; params?: unknown; node_id?: string }
7 | >(
8 | event: T
9 | ) => {
10 | const { event_type, params, node_id } = event
11 |
12 | /**
13 | * queuing.relay.tasks has a slightly different shape:
14 | * no nested "params" so we return the whole event.
15 | */
16 | if (event_type === 'queuing.relay.tasks') {
17 | return {
18 | type: event_type,
19 | payload: event,
20 | } as MapToPubSubShape
21 | }
22 |
23 | /**
24 | * `webrtc.*` events need to carry the node_id with them
25 | */
26 | if (isWebrtcEventType(event_type) && (params as JSONRPCRequest)?.jsonrpc) {
27 | const vertoRPC = params as JSONRPCRequest
28 | if (vertoRPC.params) {
29 | vertoRPC.params.nodeId = node_id
30 | }
31 | return {
32 | type: event_type,
33 | payload: vertoRPC,
34 | } as MapToPubSubShape
35 | }
36 |
37 | return {
38 | type: event_type,
39 | payload: params,
40 | } as MapToPubSubShape
41 | }
42 |
--------------------------------------------------------------------------------
/packages/core/src/utils/toInternalEventName.ts:
--------------------------------------------------------------------------------
1 | import { fromCamelToSnakeCase } from './common'
2 | import { EVENT_NAMESPACE_DIVIDER } from './constants'
3 | import { EventEmitter } from './EventEmitter'
4 |
5 | type ToInternalEventNameParams<
6 | EventTypes extends EventEmitter.ValidEventTypes
7 | > = {
8 | event: EventEmitter.EventNames
9 | namespace?: string
10 | }
11 |
12 | export const toInternalEventName = <
13 | EventTypes extends EventEmitter.ValidEventTypes
14 | >({
15 | event,
16 | namespace,
17 | }: ToInternalEventNameParams) => {
18 | // TODO: improve types of getNamespacedEvent and fromCamelToSnakeCase
19 | if (typeof event === 'string') {
20 | // other transforms here..
21 | event = getNamespacedEvent({
22 | event,
23 | namespace,
24 | }) as EventEmitter.EventNames
25 | event = fromCamelToSnakeCase>(event)
26 | }
27 |
28 | return event
29 | }
30 |
31 | const getNamespacedEvent = ({
32 | namespace,
33 | event,
34 | }: {
35 | event: string
36 | namespace?: string
37 | }) => {
38 | /**
39 | * If getNamespacedEvent is called with the `namespace` already
40 | * present in the `event` or with a falsy namespace we'll return it
41 | * as is
42 | */
43 | if (!namespace || event.startsWith(namespace)) {
44 | return event
45 | }
46 |
47 | return `${namespace}${EVENT_NAMESPACE_DIVIDER}${event}`
48 | }
49 |
--------------------------------------------------------------------------------
/packages/core/src/utils/toSnakeCaseKeys.ts:
--------------------------------------------------------------------------------
1 | import { CamelToSnakeCase } from '../types/utils'
2 | import { fromCamelToSnakeCase } from './common'
3 |
4 | type ToSnakeCaseKeys = {
5 | [Property in NonNullable as CamelToSnakeCase<
6 | Extract
7 | >]: T[Property]
8 | }
9 |
10 | export const toSnakeCaseKeys = >(
11 | obj: T,
12 | transform: (value: string) => any = (value: string) => value,
13 | result: Record = {}
14 | ) => {
15 | if (Array.isArray(obj)) {
16 | result = obj.map((item: any, index: number) => {
17 | if (typeof item === 'object') {
18 | return toSnakeCaseKeys(item, transform, result[index])
19 | }
20 | return item
21 | })
22 | } else {
23 | Object.keys(obj).forEach((key) => {
24 | const newKey = fromCamelToSnakeCase(key)
25 | // Both 'object's and arrays will enter this branch
26 | if (obj[key] && typeof obj[key] === 'object') {
27 | result[newKey] = toSnakeCaseKeys(obj[key], transform, result[newKey])
28 | } else {
29 | result[newKey] = transform(obj[key])
30 | }
31 | })
32 | }
33 |
34 | return result as ToSnakeCaseKeys
35 | }
36 |
--------------------------------------------------------------------------------
/packages/core/src/workers/executeActionWorker.ts:
--------------------------------------------------------------------------------
1 | import { call } from '@redux-saga/core/effects'
2 | import { SagaIterator } from '@redux-saga/core'
3 | import { getLogger } from '../utils/logger'
4 | import { RPCExecute } from '../RPCMessages/RPCExecute'
5 | import type { SDKWorker } from '../utils/interfaces'
6 | import type { ExecuteActionParams } from '../redux/interfaces'
7 |
8 | /**
9 | * Send a JSONRPC over the wire using session.execute and resolve/reject the promise
10 | */
11 | export const executeActionWorker: SDKWorker = function* (
12 | options
13 | ): SagaIterator {
14 | const { initialState, onDone, onFail, getSession } = options
15 |
16 | const { requestId: id, method, params } = initialState
17 |
18 | const session = getSession()
19 |
20 | if (!session) {
21 | const error = new Error('Session does not exist!')
22 | getLogger().error(error)
23 | onFail?.(error)
24 | return
25 | }
26 |
27 | try {
28 | let message = RPCExecute({ id, method, params })
29 |
30 | const response = yield call(session.execute, message)
31 | onDone?.(response)
32 | } catch (error) {
33 | getLogger().warn('Execute error: ', error)
34 | onFail?.(error)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/core/src/workers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './executeActionWorker'
2 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "emitDeclarationOnly": true,
5 | "outDir": "dist"
6 | },
7 | "include": ["src"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/js/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | dist
5 | .parcel-cache
6 | docs
7 |
--------------------------------------------------------------------------------
/packages/js/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@jest/types').Config.InitialOptions} */
2 | module.exports = {
3 | transform: {
4 | '\\.[jt]sx?$': ['babel-jest', { configFile: './../../babel.config.js' }],
5 | },
6 | resolver: '/test/resolver.js',
7 | setupFiles: ['./src/setupTests.ts'],
8 | testMatch: ['/src/**/*.test.ts'],
9 | }
10 |
--------------------------------------------------------------------------------
/packages/js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@signalwire/js",
3 | "description": "SignalWire JS SDK",
4 | "author": "SignalWire Team ",
5 | "license": "MIT",
6 | "version": "3.28.1",
7 | "main": "dist/index.js",
8 | "module": "dist/index.esm.js",
9 | "unpkg": "dist/index.umd.js",
10 | "files": [
11 | "dist",
12 | "src"
13 | ],
14 | "engines": {
15 | "node": ">=14"
16 | },
17 | "keywords": [
18 | "signalwire",
19 | "video",
20 | "rtc",
21 | "real time communication",
22 | "webrtc",
23 | "relay",
24 | "sip"
25 | ],
26 | "repository": {
27 | "type": "git",
28 | "url": "https://github.com/signalwire/signalwire-js"
29 | },
30 | "bugs": {
31 | "url": "https://github.com/signalwire/signalwire-js/issues"
32 | },
33 | "homepage": "https://github.com/signalwire/signalwire-js/tree/master/packages/js",
34 | "scripts": {
35 | "start": "sw-build --dev --web",
36 | "build": "tsc --project tsconfig.build.json && sw-build --web && sw-build --umd",
37 | "test": "NODE_ENV=test jest --detectOpenHandles --forceExit",
38 | "example": "(cd examples && npm run dev)",
39 | "prepublishOnly": "npm run build"
40 | },
41 | "dependencies": {
42 | "@signalwire/core": "4.2.1",
43 | "@signalwire/webrtc": "3.13.1",
44 | "jwt-decode": "^3.1.2"
45 | },
46 | "devDependencies": {
47 | "@types/object-path": "^0.11.4"
48 | },
49 | "types": "dist/js/src/index.d.ts"
50 | }
51 |
--------------------------------------------------------------------------------
/packages/js/src/RoomSessionDevice.test.ts:
--------------------------------------------------------------------------------
1 | import { RoomSessionDeviceAPI } from './RoomSessionDevice'
2 | import type { RoomSessionDevice } from './RoomSessionDevice'
3 | import { configureJestStore } from './testUtils'
4 |
5 | describe('RoomDevice Object', () => {
6 | let roomDevice: RoomSessionDevice
7 |
8 | beforeEach(() => {
9 | roomDevice = new RoomSessionDeviceAPI({
10 | store: configureJestStore(),
11 | }) as any as RoomSessionDevice
12 | // @ts-expect-error
13 | roomDevice.execute = jest.fn()
14 | })
15 |
16 | it('should have all the custom methods defined', () => {
17 | expect(roomDevice.audioMute).toBeDefined()
18 | expect(roomDevice.audioUnmute).toBeDefined()
19 | expect(roomDevice.videoMute).toBeDefined()
20 | expect(roomDevice.videoUnmute).toBeDefined()
21 | expect(roomDevice.setMicrophoneVolume).toBeDefined()
22 | expect(roomDevice.setInputVolume).toBeDefined()
23 | expect(roomDevice.setInputSensitivity).toBeDefined()
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/packages/js/src/RoomSessionScreenShare.test.ts:
--------------------------------------------------------------------------------
1 | import { RoomSessionScreenShareAPI } from './RoomSessionScreenShare'
2 | import type { RoomSessionScreenShare } from './RoomSessionScreenShare'
3 | import { configureJestStore } from './testUtils'
4 |
5 | describe('RoomScreenShare Object', () => {
6 | let roomScreenShare: RoomSessionScreenShare
7 |
8 | beforeEach(() => {
9 | roomScreenShare = new RoomSessionScreenShareAPI({
10 | store: configureJestStore(),
11 | }) as any as RoomSessionScreenShare
12 | // @ts-expect-error
13 | roomScreenShare.execute = jest.fn()
14 | })
15 |
16 | it('should have all the custom methods defined', () => {
17 | expect(roomScreenShare.audioMute).toBeDefined()
18 | expect(roomScreenShare.audioUnmute).toBeDefined()
19 | expect(roomScreenShare.videoMute).toBeDefined()
20 | expect(roomScreenShare.videoUnmute).toBeDefined()
21 | expect(roomScreenShare.setMicrophoneVolume).toBeDefined()
22 | expect(roomScreenShare.setInputVolume).toBeDefined()
23 | expect(roomScreenShare.setInputSensitivity).toBeDefined()
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/packages/js/src/cantina/index.ts:
--------------------------------------------------------------------------------
1 | export * from './VideoManager'
2 |
--------------------------------------------------------------------------------
/packages/js/src/cantina/workers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './videoManagerWorker'
2 |
--------------------------------------------------------------------------------
/packages/js/src/cantina/workers/videoManagerRoomWorker.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getLogger,
3 | SagaIterator,
4 | MapToPubSubShape,
5 | VideoManagerRoomEvent,
6 | toExternalJSON,
7 | VideoManagerRoomEventNames,
8 | stripNamespacePrefix,
9 | } from '@signalwire/core'
10 | import { VideoManagerWorkerParams } from './videoManagerWorker'
11 |
12 | export const videoManagerRoomWorker = function* (
13 | options: VideoManagerWorkerParams>
14 | ): SagaIterator {
15 | getLogger().trace('videoManagerRoomWorker started')
16 | const {
17 | instance: client,
18 | action: { type, payload },
19 | } = options
20 |
21 | // For now we expose the transformed payload and not a RoomSession
22 | client.emit(
23 | stripNamespacePrefix(type) as VideoManagerRoomEventNames,
24 | toExternalJSON(payload)
25 | )
26 |
27 | getLogger().trace('videoManagerRoomWorker ended')
28 | }
29 |
--------------------------------------------------------------------------------
/packages/js/src/cantina/workers/videoManagerRoomsWorker.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getLogger,
3 | SagaIterator,
4 | MapToPubSubShape,
5 | VideoManagerRoomsSubscribedEvent,
6 | toExternalJSON,
7 | VideoManagerRoomEventNames,
8 | stripNamespacePrefix,
9 | } from '@signalwire/core'
10 | import { VideoManagerWorkerParams } from './videoManagerWorker'
11 |
12 | export const videoManagerRoomsWorker = function* (
13 | options: VideoManagerWorkerParams<
14 | MapToPubSubShape
15 | >
16 | ): SagaIterator {
17 | getLogger().trace('videoManagerRoomsWorker started')
18 | const {
19 | instance: client,
20 | action: { type, payload },
21 | } = options
22 |
23 | // For now we expose the transformed payload and not a RoomSession
24 | const modPayload = {
25 | rooms: payload.rooms.map((row) => toExternalJSON(row)),
26 | }
27 | client.emit(
28 | stripNamespacePrefix(type) as VideoManagerRoomEventNames,
29 | // @ts-expect-error
30 | modPayload
31 | )
32 |
33 | getLogger().trace('videoManagerRoomsWorker ended')
34 | }
35 |
--------------------------------------------------------------------------------
/packages/js/src/chat/index.ts:
--------------------------------------------------------------------------------
1 | export { Client, ClientOptions, ClientApiEvents } from './Client'
2 |
3 | export { ChatMember, ChatMessage } from '@signalwire/core'
4 |
5 | export type { PagingCursor } from '../utils/interfaces'
6 |
7 | export type {
8 | ChatAction,
9 | ChatChannel,
10 | ChatChannelMessageEvent,
11 | ChatChannelMessageEventParams,
12 | ChatChannelState,
13 | ChatEvent,
14 | ChatGetMembersParams,
15 | ChatGetMemberStateParams,
16 | ChatGetMessagesParams,
17 | ChatMemberContract,
18 | ChatMemberEntity,
19 | ChatMemberJoinedEvent,
20 | ChatMemberJoinedEventParams,
21 | ChatMemberLeftEvent,
22 | ChatMemberLeftEventParams,
23 | ChatMemberUpdatedEvent,
24 | ChatMemberUpdatedEventParams,
25 | ChatMessageContract,
26 | ChatMessageEntity,
27 | ChatSetMemberStateParams,
28 | InternalChatMemberEntity,
29 | InternalChatMessageEntity,
30 | PaginationCursor,
31 | } from '@signalwire/core'
32 |
--------------------------------------------------------------------------------
/packages/js/src/createClient.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ClientEvents,
3 | configureStore,
4 | connect,
5 | getEventEmitter,
6 | UserOptions,
7 | } from '@signalwire/core'
8 | import { ClientAPI, Client } from './Client'
9 | import { JWTSession } from './JWTSession'
10 |
11 | /**
12 | * With Video.createClient() you can establish a WebSocket connection
13 | * with SignalWire and interact with the client.
14 | *
15 | * ## Examples
16 | * Create a client
17 | *
18 | * @example
19 | * ```js
20 | * try {
21 | * const client = Video.createClient({
22 | * token: '',
23 | * })
24 | *
25 | * await client.connect()
26 | * // Your client is ready now..
27 | * } catch (error) {
28 | * console.error('Error', error)
29 | * }
30 | * ```
31 | * @internal
32 | */
33 | export const createClient = (userOptions: UserOptions) => {
34 | const baseUserOptions = {
35 | ...userOptions,
36 | emitter: getEventEmitter(),
37 | }
38 | const store = configureStore({
39 | userOptions: baseUserOptions,
40 | SessionConstructor: JWTSession,
41 | })
42 | const client = connect>({
43 | store,
44 | Component: ClientAPI,
45 | })(baseUserOptions)
46 |
47 | return client
48 | }
49 |
--------------------------------------------------------------------------------
/packages/js/src/fabric/ConversationAPI.ts:
--------------------------------------------------------------------------------
1 | import { Conversation } from './Conversation'
2 | import {
3 | ConversationAPIGetMessagesParams,
4 | ConversationAPISendMessageParams,
5 | ConversationContract,
6 | ConversationResponse,
7 | } from './interfaces'
8 |
9 | export class ConversationAPI implements ConversationContract {
10 | constructor(
11 | private conversation: Conversation,
12 | private payload: ConversationResponse
13 | ) {}
14 |
15 | get id() {
16 | return this.payload.id
17 | }
18 |
19 | get addressId() {
20 | return this.payload.address_id
21 | }
22 |
23 | get createdAt() {
24 | return this.payload.created_at
25 | }
26 |
27 | get lastMessageAt() {
28 | return this.payload.last_message_at
29 | }
30 |
31 | get metadata() {
32 | return this.payload.metadata
33 | }
34 |
35 | get name() {
36 | return this.payload.name
37 | }
38 |
39 | sendMessage(params: ConversationAPISendMessageParams) {
40 | return this.conversation.sendMessage({
41 | addressId: this.id,
42 | text: params.text,
43 | })
44 | }
45 |
46 | getMessages(params?: ConversationAPIGetMessagesParams) {
47 | return this.conversation.getConversationMessages({
48 | addressId: this.id,
49 | ...params,
50 | })
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/packages/js/src/fabric/createWSClient.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@signalwire/core'
2 | import { SATSession } from './SATSession'
3 | import { WSClientOptions } from './interfaces'
4 |
5 | export const createWSClient = (options: WSClientOptions) => {
6 | const store = configureStore({
7 | userOptions: options,
8 | SessionConstructor: SATSession,
9 | })
10 |
11 | return { ...options, store }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/js/src/fabric/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * ============ CAUTION ============
3 | *
4 | * Anything we export from here in available on the Public interface.
5 | */
6 |
7 | export * from './SignalWire'
8 | export * from './interfaces/address'
9 | export * from './interfaces/capabilities'
10 | export * from './interfaces/conversation'
11 | export * from './interfaces/device'
12 | export * from './interfaces/incomingCallManager'
13 |
14 | export {
15 | OnlineParams,
16 | HandlePushNotificationParams,
17 | HandlePushNotificationResult,
18 | DialParams,
19 | } from './interfaces/wsClient'
20 | export {
21 | SignalWireClient,
22 | SignalWireContract,
23 | SignalWireClientParams,
24 | GetSubscriberInfoResponse,
25 | GetSubscriberInfoResult,
26 | PaginatedResponse,
27 | PaginatedResult,
28 | } from './interfaces'
29 | export { FabricRoomSession, isFabricRoomSession } from './FabricRoomSession'
30 |
--------------------------------------------------------------------------------
/packages/js/src/fabric/interfaces/address.ts:
--------------------------------------------------------------------------------
1 | import { PaginatedResponse, PaginatedResult } from '.'
2 |
3 | export type ResourceType = 'app' | 'call' | 'room' | 'subscriber'
4 |
5 | export interface GetAddressResponse {
6 | id: string
7 | display_name: string
8 | name: string
9 | preview_url?: string
10 | cover_url?: string
11 | resource_id: string
12 | type: ResourceType
13 | channels: {
14 | audio?: string
15 | messaging?: string
16 | video?: string
17 | }
18 | locked: boolean
19 | created_at: string
20 | }
21 |
22 | export type Address = GetAddressResponse
23 |
24 | export interface GetAddressesParams {
25 | type?: string
26 | displayName?: string
27 | pageSize?: number
28 | sortBy?: 'name' | 'created_at'
29 | sortOrder?: 'asc' | 'desc'
30 | }
31 |
32 | export interface GetAddressByIdParams {
33 | id: string
34 | }
35 |
36 | export interface GetAddressByNameParams {
37 | name: string
38 | }
39 |
40 | export type GetAddressParams = GetAddressByIdParams | GetAddressByNameParams
41 |
42 | export type GetAddressResult = GetAddressResponse
43 |
44 | export type GetAddressesResponse = PaginatedResponse
45 |
46 | export type GetAddressesResult = PaginatedResult
47 |
--------------------------------------------------------------------------------
/packages/js/src/fabric/interfaces/capabilities.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface CapabilityOnOffStateContract {
3 | on: boolean
4 | off: boolean
5 | }
6 |
7 | export interface MemberCapabilityContract {
8 | muteAudio: CapabilityOnOffStateContract
9 | muteVideo: CapabilityOnOffStateContract
10 | microphoneVolume: boolean
11 | microphoneSensitivity: boolean
12 | speakerVolume: boolean
13 | deaf: CapabilityOnOffStateContract
14 | raisehand: CapabilityOnOffStateContract
15 | position: boolean
16 | meta: boolean
17 | remove: boolean
18 | }
19 |
20 | export interface CallCapabilitiesContract {
21 | self: MemberCapabilityContract
22 | member: MemberCapabilityContract
23 | end: boolean
24 | setLayout: boolean
25 | sendDigit: boolean
26 | vmutedHide: CapabilityOnOffStateContract
27 | lock: CapabilityOnOffStateContract
28 | device: boolean
29 | screenshare: boolean
30 | }
31 |
--------------------------------------------------------------------------------
/packages/js/src/fabric/interfaces/device.ts:
--------------------------------------------------------------------------------
1 | export type RegisterDeviceType = 'iOS' | 'Android' | 'Desktop'
2 |
3 | export interface RegisterDeviceParams {
4 | deviceType: RegisterDeviceType
5 | deviceToken: string
6 | }
7 |
8 | export interface UnregisterDeviceParams {
9 | id: string
10 | }
11 |
12 | export interface RegisterDeviceResponse {
13 | date_registered: Date
14 | device_name?: string
15 | device_token: string
16 | device_type: RegisterDeviceType
17 | id: string
18 | push_notification_key: string
19 | }
20 |
21 | export type RegisterDeviceResult = RegisterDeviceResponse
22 |
--------------------------------------------------------------------------------
/packages/js/src/fabric/interfaces/incomingCallManager.ts:
--------------------------------------------------------------------------------
1 | import { FabricRoomSession } from '../FabricRoomSession'
2 | import { CallParams } from './wsClient'
3 |
4 | export type IncomingInviteSource = 'websocket' | 'pushNotification'
5 |
6 | export interface IncomingInvite {
7 | callID: string
8 | sdp: string
9 | caller_id_name: string
10 | caller_id_number: string
11 | callee_id_name: string
12 | callee_id_number: string
13 | display_direction: string
14 | nodeId: string
15 | }
16 |
17 | export interface IncomingInviteWithSource extends IncomingInvite {
18 | source: IncomingInviteSource
19 | }
20 |
21 | export interface IncomingCallNotification {
22 | invite: {
23 | details: IncomingInvite
24 | accept: (param: CallParams) => Promise
25 | reject: () => Promise
26 | }
27 | }
28 | export type IncomingCallHandler = (
29 | notification: IncomingCallNotification
30 | ) => void
31 |
32 | export interface IncomingCallHandlers {
33 | all?: IncomingCallHandler
34 | pushNotification?: IncomingCallHandler
35 | websocket?: IncomingCallHandler
36 | }
37 |
--------------------------------------------------------------------------------
/packages/js/src/fabric/utils/constants.ts:
--------------------------------------------------------------------------------
1 | export const PREVIOUS_CALLID_STORAGE_KEY = 'ci-SAT';
2 | export const DEFAULT_API_REQUEST_RETRIES = 10
3 | export const DEFAULT_API_REQUEST_RETRIES_DELAY = 300
4 | export const DEFAULT_API_REQUEST_RETRIES_DELAY_INCREMENT = 100
--------------------------------------------------------------------------------
/packages/js/src/fabric/utils/typeGuard.ts:
--------------------------------------------------------------------------------
1 | import {
2 | GetAddressParams,
3 | GetAddressByNameParams,
4 | GetAddressByIdParams,
5 | GetAddressResponse,
6 | GetAddressesResponse,
7 | } from '../interfaces'
8 |
9 | export const isGetAddressByNameParams = (
10 | obj: GetAddressParams
11 | ): obj is GetAddressByNameParams => {
12 | return obj && 'name' in obj
13 | }
14 |
15 | export const isGetAddressByIdParams = (
16 | obj: GetAddressParams
17 | ): obj is GetAddressByIdParams => {
18 | return obj && 'id' in obj
19 | }
20 |
21 | export const isGetAddressResponse = (
22 | obj: GetAddressResponse | GetAddressesResponse
23 | ): obj is GetAddressResponse => {
24 | return obj && 'id' in obj && 'name' in obj
25 | }
26 |
27 | export const isGetAddressesResponse = (
28 | obj: GetAddressResponse | GetAddressesResponse
29 | ): obj is GetAddressesResponse => {
30 | return obj && 'data' in obj
31 | }
32 |
--------------------------------------------------------------------------------
/packages/js/src/fabric/workers/callLeftWorker.ts:
--------------------------------------------------------------------------------
1 | import { getLogger, SagaIterator, CallLeftEvent } from '@signalwire/core'
2 | import { FabricWorkerParams } from './fabricWorker'
3 | import { FabricRoomSessionMemberAPI } from '../FabricRoomSessionMember'
4 |
5 | export const callLeftWorker = function* (
6 | options: FabricWorkerParams
7 | ): SagaIterator {
8 | getLogger().trace('callLeftWorker started')
9 |
10 | const {
11 | action: { payload },
12 | instance: cfRoomSession,
13 | instanceMap,
14 | } = options
15 |
16 | const { room_session_id } = payload
17 |
18 | // Remove all the member instance where roomSessionId matches
19 | instanceMap.getAll().forEach(([key, obj]) => {
20 | if (
21 | obj instanceof FabricRoomSessionMemberAPI &&
22 | obj.roomSessionId === room_session_id
23 | ) {
24 | instanceMap.remove(key)
25 | }
26 | })
27 |
28 | cfRoomSession.emit('call.left', payload)
29 | cfRoomSession.emit('room.left', payload)
30 |
31 | getLogger().trace('callLeftWorker ended')
32 | }
33 |
--------------------------------------------------------------------------------
/packages/js/src/fabric/workers/conversationWorker.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getLogger,
3 | sagaEffects,
4 | SagaIterator,
5 | SDKWorker,
6 | SDKActions,
7 | MapToPubSubShape,
8 | ConversationEvent,
9 | } from '@signalwire/core'
10 | import { Conversation } from '../Conversation'
11 | import { WSClient } from '../WSClient'
12 |
13 | interface ConversationWorkerInitialState {
14 | conversation: Conversation
15 | }
16 |
17 | export const conversationWorker: SDKWorker = function* (
18 | options
19 | ): SagaIterator {
20 | getLogger().debug('conversationWorker started')
21 | const {
22 | channels: { swEventChannel },
23 | initialState,
24 | } = options
25 |
26 | const { conversation } = initialState as ConversationWorkerInitialState
27 |
28 | const isConversationEvent = (action: SDKActions) => {
29 | return action.type.startsWith('conversation.')
30 | }
31 |
32 | while (true) {
33 | const action: MapToPubSubShape = yield sagaEffects.take(
34 | swEventChannel,
35 | isConversationEvent
36 | )
37 | conversation.handleEvent(action.payload)
38 | }
39 |
40 | getLogger().trace('conversationWorker ended')
41 | }
42 |
--------------------------------------------------------------------------------
/packages/js/src/fabric/workers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './wsClientWorker'
2 | export * from './conversationWorker'
3 | export * from './fabricWorker'
4 |
--------------------------------------------------------------------------------
/packages/js/src/features/actions.ts:
--------------------------------------------------------------------------------
1 | import { actions } from '@signalwire/core'
2 |
3 | export const audioSetSpeakerAction = actions.createAction(
4 | 'swJs/audioSetSpeakerAction'
5 | )
6 |
--------------------------------------------------------------------------------
/packages/js/src/index.test.ts:
--------------------------------------------------------------------------------
1 | describe('Web Entry Point', () => {
2 | it('should work', () => {
3 | expect(1).toEqual(1)
4 | })
5 | })
6 |
--------------------------------------------------------------------------------
/packages/js/src/joinRoom.ts:
--------------------------------------------------------------------------------
1 | import { CreateRoomObjectOptions, createRoomObject } from './createRoomObject'
2 |
3 | /**
4 | * Using Video.joinRoom() you can automatically join a room.
5 | *
6 | * @example
7 | * With an HTMLDivElement with id="root" in the DOM.
8 | * ```js
9 | * //
10 | *
11 | * try {
12 | * const roomObj = await Video.joinRoom({
13 | * token: '',
14 | * rootElementId: 'root',
15 | * })
16 | *
17 | * // You have joined the room..
18 | * } catch (error) {
19 | * console.error('Error', error)
20 | * }
21 | * ```
22 | * @deprecated Use {@link RoomSession} instead.
23 | */
24 | export const joinRoom = (roomOptions: CreateRoomObjectOptions) => {
25 | return createRoomObject({
26 | ...roomOptions,
27 | autoJoin: true,
28 | })
29 | }
30 |
--------------------------------------------------------------------------------
/packages/js/src/pubSub/index.ts:
--------------------------------------------------------------------------------
1 | import { PubSub } from '@signalwire/core'
2 | import { PagingCursor } from '../utils/interfaces'
3 | import PubSubMessage = PubSub.PubSubMessage
4 |
5 | export * from './Client'
6 |
7 | export {
8 | PubSubMessage,
9 | PagingCursor
10 | }
11 |
--------------------------------------------------------------------------------
/packages/js/src/utils/CloseEvent.ts:
--------------------------------------------------------------------------------
1 | import { SWCloseEvent as CoreCloseEvent } from '@signalwire/core'
2 |
3 | const SwCloseEvent =
4 | typeof CloseEvent === 'function' ? CloseEvent : CoreCloseEvent
5 |
6 | export { SwCloseEvent }
7 |
--------------------------------------------------------------------------------
/packages/js/src/utils/audioElement.ts:
--------------------------------------------------------------------------------
1 | const setAudioMediaTrack = ({
2 | track,
3 | element,
4 | }: {
5 | track: MediaStreamTrack
6 | element: HTMLAudioElement
7 | }) => {
8 | element.autoplay = true
9 | // @ts-ignore
10 | element.playsinline = true
11 | element.srcObject = new MediaStream([track])
12 |
13 | track.addEventListener('ended', () => {
14 | element.srcObject = null
15 | element.remove()
16 | })
17 |
18 | return element
19 | }
20 |
21 | export { setAudioMediaTrack }
22 |
--------------------------------------------------------------------------------
/packages/js/src/utils/constants.ts:
--------------------------------------------------------------------------------
1 | export const SCREENSHARE_AUDIO_CONSTRAINTS: MediaTrackConstraints = {
2 | echoCancellation: true,
3 | noiseSuppression: false,
4 | autoGainControl: false,
5 | // @ts-expect-error
6 | googAutoGainControl: false,
7 | }
8 |
--------------------------------------------------------------------------------
/packages/js/src/utils/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export * from './base'
2 | export * from './video'
3 | export * from './fabric'
4 |
--------------------------------------------------------------------------------
/packages/js/src/utils/makeQueryParamsUrl.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Constructs a URL with query parameters.
3 | *
4 | * @param path The base path of the URL.
5 | * @param queryParams An instance of URLSearchParams containing all query parameters.
6 | * @returns The constructed URL with query parameters.
7 | */
8 | export function makeQueryParamsUrls(
9 | path: string,
10 | queryParams: URLSearchParams
11 | ): string {
12 | const queryString = queryParams.toString()
13 | return queryString ? `${path}?${queryString}` : path
14 | }
15 |
--------------------------------------------------------------------------------
/packages/js/src/utils/paginatedResult.ts:
--------------------------------------------------------------------------------
1 | import { PaginatedResponse } from '../fabric/interfaces'
2 | import { CreateHttpClient } from '../fabric/createHttpClient'
3 |
4 | export function buildPaginatedResult(
5 | body: PaginatedResponse,
6 | client: CreateHttpClient
7 | ) {
8 | const anotherPage = async (url?: string) => {
9 | if (!url) return Promise.resolve(undefined)
10 | const { body } = await client>(url)
11 | return buildPaginatedResult(body, client)
12 | }
13 |
14 | return {
15 | data: body.data,
16 | self: async () => {
17 | const { self } = body.links
18 | return anotherPage(self)
19 | },
20 | nextPage: async () => {
21 | const { next } = body.links
22 | return anotherPage(next)
23 | },
24 | prevPage: async () => {
25 | const { prev } = body.links
26 | return anotherPage(prev)
27 | },
28 | firstPage: async () => {
29 | const { first } = body.links
30 | return anotherPage(first)
31 | },
32 | hasNext: Boolean(body.links.next),
33 | hasPrev: Boolean(body.links.prev),
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/js/src/utils/storage.test.ts:
--------------------------------------------------------------------------------
1 | import { sessionStorageManager } from './storage'
2 |
3 | describe('sessionStorageManager', () => {
4 | it('should return expected keys', () => {
5 | const token =
6 | 'eyJ0eXBiOiJWUlQiLCJjaCI6InJlbGF5LnNpZ25hbHdpcmUuY29tIiwiYWxnIjoiPOE1MTIifQ.eyJpYXQiOjE2ODg0NjE3MDMsImp0aSI6IjA1YWZkODczLTI4MGUtNDYzNC1iNmQzLWMwMzk4YzE2NGVlMSIsInN1YiI6IjRiN2FlNzhhLWQwMmUtNDg4OS1hNjNiLTA4YjE1NmQ1OTE2ZSIsInUiOiJqZXN0IiwiamEiOiJtZW1iZXIiLCJyIjoidGVhbS10ZXN0IiwicyI6W10sImFjciI6dHJ1ZSwibWEiOiJhbGwiLCJtdGEiOnt9LCJybXRhIjp7fX0.r1JWiEckaids8mm9ESUraCuXWc6Ysa6qMMmuNzRiOf94PO8VwoL_gIr1LDopDtffk-EFUUB4ZsOl8gK-u-qU7A'
7 | const manager = sessionStorageManager(token)
8 | expect(manager).toStrictEqual({
9 | authStateKey: 'as-team-test',
10 | protocolKey: 'pt-team-test',
11 | callIdKey: 'ci-team-test',
12 | })
13 | })
14 |
15 | it('should return falsy values', () => {
16 | const token = 'foo'
17 | const manager = sessionStorageManager(token)
18 | expect(manager).toStrictEqual({
19 | authStateKey: false,
20 | protocolKey: false,
21 | callIdKey: false,
22 | })
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/packages/js/src/utils/storage.ts:
--------------------------------------------------------------------------------
1 | import jwtDecode from 'jwt-decode'
2 | import { getLogger } from '@signalwire/core'
3 |
4 | /**
5 | * Note: ready to support RN with a "storage.native.ts" file.
6 | */
7 | export const getStorage = () => {
8 | if (window && window.sessionStorage) {
9 | return window.sessionStorage
10 | }
11 | return undefined
12 | }
13 |
14 | export const sessionStorageManager = (token: string) => {
15 | let roomName: string = ''
16 | try {
17 | const jwtPayload = jwtDecode<{ r: string; ja: string }>(token)
18 | roomName = jwtPayload?.r ?? ''
19 | } catch (e) {
20 | try {
21 | const jwtPayload = jwtDecode<{ typ: string}>(token, {header: true})
22 | roomName = jwtPayload.typ || ''
23 | } catch {
24 | if (process.env.NODE_ENV !== 'production') {
25 | getLogger().error('[sessionStorageManager] error decoding JWT', token)
26 | }
27 | roomName = ''
28 | }
29 | }
30 |
31 | const valid = Boolean(roomName)
32 | return {
33 | authStateKey: valid && `as-${roomName}`,
34 | protocolKey: valid && `pt-${roomName}`,
35 | callIdKey: valid && `ci-${roomName}`,
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/js/src/video/RoomSession.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment jsdom
3 | */
4 | import { RoomSession, UNSAFE_PROP_ACCESS } from './RoomSession'
5 |
6 | describe('RoomSession Object', () => {
7 | it('should control which properties the user can access before connecting the room.', async () => {
8 | const roomSession = new RoomSession({
9 | token: '',
10 | })
11 |
12 | expect(() => roomSession.active).not.toThrow()
13 | expect(() => roomSession.memberId).not.toThrow()
14 | expect(() => roomSession.join()).not.toThrow()
15 | UNSAFE_PROP_ACCESS.map((prop) => {
16 | // @ts-expect-error
17 | expect(() => roomSession[prop]).toThrow()
18 | })
19 |
20 | // @ts-expect-error
21 | roomSession.state = 'active'
22 |
23 | expect(() => roomSession.active).not.toThrow()
24 | expect(() => roomSession.memberId).not.toThrow()
25 | expect(() => roomSession.join()).not.toThrow()
26 | UNSAFE_PROP_ACCESS.map((prop) => {
27 | // @ts-expect-error
28 | expect(() => roomSession[prop]).not.toThrow()
29 | })
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/packages/js/src/video/index.ts:
--------------------------------------------------------------------------------
1 | import { createRoomObject, Room } from '../createRoomObject'
2 | import { createClient } from '../createClient'
3 | import { joinRoom } from '../joinRoom'
4 | import { MakeRoomOptions } from '../Client'
5 | import { RoomSession, RoomSessionOptions } from './RoomSession'
6 | import { RoomSessionDevice, RoomDevice } from '../RoomSessionDevice'
7 | import {
8 | RoomSessionScreenShare,
9 | RoomScreenShare,
10 | } from '../RoomSessionScreenShare'
11 |
12 | export {
13 | RoomSession,
14 | RoomSessionDevice,
15 | RoomSessionScreenShare,
16 | // Just to keep backwards compatibility.
17 | createRoomObject,
18 | joinRoom,
19 | Room,
20 | RoomDevice,
21 | RoomScreenShare,
22 | createClient,
23 | }
24 |
25 | /** @ignore */
26 | export type { MakeRoomOptions, RoomSessionOptions }
27 |
28 | /** @ignore */
29 | export type {
30 | DeprecatedMemberUpdatableProps,
31 | DeprecatedVideoMemberHandlerParams,
32 | VideoMemberHandlerParams,
33 | VideoMemberListUpdatedParams,
34 | } from '../utils/interfaces'
35 |
36 | export type { CreateRoomObjectOptions } from '../createRoomObject'
37 |
38 | export type {
39 | RoomSessionRecording,
40 | RoomSessionPlayback,
41 | } from '@signalwire/core'
42 |
43 | export { VideoRoomSession, isVideoRoomSession } from './VideoRoomSession'
44 |
--------------------------------------------------------------------------------
/packages/js/src/video/workers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './memberListUpdatedWorker'
2 | export * from './childMemberJoinedWorker'
3 | export * from './videoWorker'
4 | export * from './videoPlaybackWorker'
5 | export * from './videoRecordWorker'
6 | export * from './videoStreamWorker'
7 |
--------------------------------------------------------------------------------
/packages/js/src/video/workers/videoStreamWorker.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getLogger,
3 | SagaIterator,
4 | MapToPubSubShape,
5 | VideoStreamEvent,
6 | RoomSessionStream,
7 | Rooms,
8 | } from '@signalwire/core'
9 | import { VideoWorkerParams } from './videoWorker'
10 |
11 | export const videoStreamWorker = function* (
12 | options: VideoWorkerParams>
13 | ): SagaIterator {
14 | getLogger().trace('videoStreamWorker started')
15 | const {
16 | instance: roomSession,
17 | action: { type, payload },
18 | instanceMap: { get, set, remove },
19 | } = options
20 |
21 | // For now, we are not storing the RoomSession object in the instance map
22 |
23 | let streamInstance = get(payload.stream.id)
24 | if (!streamInstance) {
25 | streamInstance = Rooms.createRoomSessionStreamObject({
26 | store: roomSession.store,
27 | payload,
28 | })
29 | } else {
30 | streamInstance.setPayload(payload)
31 | }
32 | set(payload.stream.id, streamInstance)
33 |
34 | switch (type) {
35 | case 'video.stream.started':
36 | roomSession.emit('stream.started', streamInstance)
37 | break
38 | case 'video.stream.ended':
39 | roomSession.emit('stream.ended', streamInstance)
40 | remove(payload.stream.id)
41 | break
42 | default:
43 | getLogger().warn(`Unknown video.stream event: "${type}"`)
44 | break
45 | }
46 |
47 | getLogger().trace('videoStreamWorker ended')
48 | }
49 |
--------------------------------------------------------------------------------
/packages/js/src/webrtc.ts:
--------------------------------------------------------------------------------
1 | export {
2 | getDevices,
3 | getCameraDevices,
4 | getMicrophoneDevices,
5 | getSpeakerDevices,
6 | getDevicesWithPermissions,
7 | getCameraDevicesWithPermissions,
8 | getMicrophoneDevicesWithPermissions,
9 | getSpeakerDevicesWithPermissions,
10 | checkPermissions,
11 | checkCameraPermissions,
12 | checkMicrophonePermissions,
13 | checkSpeakerPermissions,
14 | requestPermissions,
15 | createDeviceWatcher,
16 | createCameraDeviceWatcher,
17 | createMicrophoneDeviceWatcher,
18 | createSpeakerDeviceWatcher,
19 | supportsMediaDevices,
20 | supportsGetUserMedia,
21 | supportsGetDisplayMedia,
22 | getUserMedia,
23 | getDisplayMedia,
24 | enumerateDevices,
25 | enumerateDevicesByKind,
26 | getSupportedConstraints,
27 | supportsMediaOutput,
28 | setMediaElementSinkId,
29 | stopStream,
30 | stopTrack,
31 | createMicrophoneAnalyzer,
32 | } from '@signalwire/webrtc'
33 | export type { MicrophoneAnalyzer } from '@signalwire/webrtc'
34 |
--------------------------------------------------------------------------------
/packages/js/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "emitDeclarationOnly": true,
5 | "outDir": "dist"
6 | },
7 | "include": ["./src/**/*.ts", "../core/src/**/*.ts", "../webrtc/src/**/*.ts"],
8 | "exclude": ["**/*.test.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/node/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | dist
5 | docs
6 |
--------------------------------------------------------------------------------
/packages/node/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | '\\.[jt]sx?$': ['babel-jest', { configFile: './../../babel.config.js' }],
4 | },
5 | }
6 |
--------------------------------------------------------------------------------
/packages/node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@signalwire/node",
3 | "description": "SignalWire Node.js SDK",
4 | "author": "SignalWire Team ",
5 | "license": "MIT",
6 | "version": "3.0.3",
7 | "main": "dist/index.node.js",
8 | "exports": {
9 | "require": "./dist/index.node.js",
10 | "default": "./dist/index.node.mjs"
11 | },
12 | "files": [
13 | "dist",
14 | "src"
15 | ],
16 | "engines": {
17 | "node": ">=14"
18 | },
19 | "keywords": [
20 | "signalwire",
21 | "video",
22 | "relay",
23 | "sip"
24 | ],
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/signalwire/signalwire-js"
28 | },
29 | "bugs": {
30 | "url": "https://github.com/signalwire/signalwire-js/issues"
31 | },
32 | "homepage": "https://github.com/signalwire/signalwire-js/tree/master/packages/node",
33 | "scripts": {
34 | "start": "sw-build --dev --node",
35 | "build": "tsc --project tsconfig.build.json && sw-build --node",
36 | "test": "NODE_ENV=test jest --detectOpenHandles --forceExit",
37 | "prepublishOnly": "npm run build"
38 | },
39 | "dependencies": {
40 | "@signalwire/realtime-api": "^4.0.1",
41 | "@signalwire/web-api": "^3.1.2"
42 | },
43 | "types": "dist/node/src/index.d.ts"
44 | }
45 |
--------------------------------------------------------------------------------
/packages/node/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import { WebAPI } from './index'
2 |
3 | describe('WebAPI', () => {
4 | it('should expose validat', () => {
5 | expect(WebAPI.validateRequest).toBeDefined()
6 | expect(typeof WebAPI.validateRequest).toBe('function')
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/packages/node/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as RealtimeAPI from '@signalwire/realtime-api'
2 | import * as WebAPI from '@signalwire/web-api'
3 |
4 | export { RealtimeAPI, WebAPI }
5 |
--------------------------------------------------------------------------------
/packages/node/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "emitDeclarationOnly": true,
5 | "outDir": "dist"
6 | },
7 | "include": ["./src/**/*.ts", "../core/src/**/*.ts"],
8 | "exclude": ["**/*.test.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/realtime-api/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | dist
5 | docs
6 |
--------------------------------------------------------------------------------
/packages/realtime-api/__mocks__/ws.js:
--------------------------------------------------------------------------------
1 | export { WebSocket as default } from 'mock-socket'
2 |
--------------------------------------------------------------------------------
/packages/realtime-api/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleNameMapper: {
3 | // Force module uuid to resolve with the CJS entry point, because Jest does not support package.json.exports. See https://github.com/uuidjs/uuid/issues/451
4 | uuid: require.resolve('uuid'),
5 | },
6 | transform: {
7 | '\\.[jt]sx?$': ['babel-jest', { configFile: './../../babel.config.js' }],
8 | },
9 | setupFiles: ['./src/setupTests.ts'],
10 | }
11 |
--------------------------------------------------------------------------------
/packages/realtime-api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@signalwire/realtime-api",
3 | "description": "SignalWire RealTime SDK for Node.js",
4 | "author": "SignalWire Team ",
5 | "license": "MIT",
6 | "version": "4.1.1",
7 | "main": "dist/index.node.js",
8 | "exports": {
9 | "types": "./dist/realtime-api/src/index.d.ts",
10 | "require": "./dist/index.node.js",
11 | "default": "./dist/index.node.mjs"
12 | },
13 | "files": [
14 | "dist",
15 | "src"
16 | ],
17 | "engines": {
18 | "node": ">=14"
19 | },
20 | "keywords": [
21 | "signalwire",
22 | "video",
23 | "relay",
24 | "sip"
25 | ],
26 | "repository": {
27 | "type": "git",
28 | "url": "https://github.com/signalwire/signalwire-js"
29 | },
30 | "bugs": {
31 | "url": "https://github.com/signalwire/signalwire-js/issues"
32 | },
33 | "homepage": "https://github.com/signalwire/signalwire-js/tree/master/packages/realtime-api",
34 | "scripts": {
35 | "start": "sw-build --dev --node --watchFormat=cjs",
36 | "build": "tsc --project tsconfig.build.json && sw-build --node",
37 | "test": "NODE_ENV=test jest --detectOpenHandles --forceExit",
38 | "prepublishOnly": "npm run build"
39 | },
40 | "dependencies": {
41 | "@signalwire/core": "4.2.1",
42 | "ws": "^8.17.1"
43 | },
44 | "devDependencies": {
45 | "@types/ws": "^8.5.10"
46 | },
47 | "types": "dist/realtime-api/src/index.d.ts"
48 | }
49 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/Session.ts:
--------------------------------------------------------------------------------
1 | import { BaseSession, SWCloseEvent } from '@signalwire/core'
2 | import WebSocket from 'ws'
3 |
4 | export class Session extends BaseSession {
5 | public WebSocketConstructor = WebSocket
6 | public CloseEventConstructor = SWCloseEvent
7 | public agent = process.env.SDK_PKG_AGENT!
8 | }
9 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/SignalWire.ts:
--------------------------------------------------------------------------------
1 | import { SWClient, SWClientOptions } from './SWClient'
2 |
3 | export const SignalWire = (options: SWClientOptions): Promise => {
4 | return new Promise(async (resolve, reject) => {
5 | const swClient = new SWClient(options)
6 |
7 | try {
8 | await swClient.connect()
9 | resolve(swClient)
10 | } catch (error) {
11 | reject(error)
12 | }
13 | })
14 | }
15 |
16 | export type { SWClient } from './SWClient'
17 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/chat/Chat.test.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from '@signalwire/core'
2 | import { Chat } from './Chat'
3 | import { createClient } from '../client/createClient'
4 |
5 | describe('Chat', () => {
6 | let chat: Chat
7 | const userOptions = {
8 | host: 'example.com',
9 | project: 'example.project',
10 | token: 'example.token',
11 | }
12 | const swClientMock = {
13 | userOptions,
14 | client: createClient(userOptions),
15 | }
16 |
17 | beforeEach(() => {
18 | //@ts-expect-error
19 | chat = new Chat(swClientMock)
20 | })
21 |
22 | afterEach(() => {
23 | jest.clearAllMocks()
24 | })
25 |
26 | it('should have an event emitter', () => {
27 | expect(chat['emitter']).toBeInstanceOf(EventEmitter)
28 | })
29 |
30 | it('should declare the correct event map', () => {
31 | const expectedEventMap = {
32 | onMessageReceived: 'chat.message',
33 | onMemberJoined: 'chat.member.joined',
34 | onMemberUpdated: 'chat.member.updated',
35 | onMemberLeft: 'chat.member.left',
36 | }
37 | expect(chat['_eventMap']).toEqual(expectedEventMap)
38 | })
39 | })
40 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/chat/workers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './chatWorker'
2 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/client/Client.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Note: This file will eventually replace
3 | * packages/realtime-api/src/Client.ts
4 | */
5 |
6 | import {
7 | BaseClient as CoreBaseClient,
8 | ClientContract,
9 | ClientEvents,
10 | } from '@signalwire/core'
11 |
12 | export interface RealtimeClient
13 | extends ClientContract {
14 | /**
15 | * Connects this client to the SignalWire network.
16 | *
17 | * As a general best practice, it is suggested to connect the event listeners
18 | * *before* connecting the client, so that no events are lost.
19 | *
20 | * @returns Upon connection, asynchronously returns an instance of this same
21 | * object.
22 | *
23 | * @example
24 | * ```typescript
25 | * const client = await createClient({project, token})
26 | * client.video.on('room.started', async (roomSession) => { }) // connect events
27 | * await client.connect()
28 | * ```
29 | */
30 | connect(): Promise
31 |
32 | /**
33 | * Disconnects this client from the SignalWire network.
34 | */
35 | disconnect(): void
36 | }
37 |
38 | export class Client extends CoreBaseClient {}
39 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/client/clientConnect.ts:
--------------------------------------------------------------------------------
1 | import { RealtimeClient } from './Client'
2 |
3 | export const clientConnect = (client: RealtimeClient) => {
4 | /**
5 | * We swallow the (possible) error here to avoid polluting
6 | * the stdout. The error itself won't be swallowed from
7 | * the user (it will be handled by our `rootSaga`) and we
8 | * can extend that behavior by adding the following
9 | * listener:
10 | * client.on('session.auth_error', () => { ... })
11 | */
12 | return client.connect().catch(() => {})
13 | }
14 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/client/clientProxyFactory.test.ts:
--------------------------------------------------------------------------------
1 | import { configureFullStack } from '../testUtils'
2 | import { clientProxyFactory } from './clientProxyFactory'
3 | import { getClient } from './getClient'
4 |
5 | describe('clientProxyFactory', () => {
6 | it('should handle the automatic connection every time the user attach an event', () => {
7 | const { store, emitter, destroy } = configureFullStack()
8 | const options = {
9 | project: 'a-proj',
10 | token: 'a-proj',
11 | store,
12 | emitter,
13 | cache: new Map(),
14 | }
15 | const { client } = getClient(options)
16 | const connectMock = jest.fn()
17 | const proxiedClient = clientProxyFactory(client, { connect: connectMock })
18 |
19 | expect(connectMock).toHaveBeenCalledTimes(0)
20 |
21 | proxiedClient.on('session.connected', () => {})
22 | proxiedClient.on('session.reconnecting', () => {})
23 |
24 | expect(connectMock).toHaveBeenCalledTimes(2)
25 |
26 | destroy()
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/client/clientProxyFactory.ts:
--------------------------------------------------------------------------------
1 | import { RealtimeClient } from './Client'
2 | import { clientConnect as baseClientConnect } from './clientConnect'
3 |
4 | interface ClientInterceptors {
5 | connect?: (client: RealtimeClient) => Promise
6 | }
7 |
8 | const defaultInterceptors: ClientInterceptors = {
9 | connect: baseClientConnect,
10 | }
11 |
12 | export const clientProxyFactory = (
13 | client: RealtimeClient,
14 | interceptors: ClientInterceptors = defaultInterceptors
15 | ) => {
16 | const clientConnect = interceptors.connect || baseClientConnect
17 |
18 | // Client interceptors
19 | const clientOn: RealtimeClient['on'] = (...args) => {
20 | clientConnect(client)
21 |
22 | return client.on(...args)
23 | }
24 | const clientOnce: RealtimeClient['once'] = (...args) => {
25 | clientConnect(client)
26 |
27 | return client.once(...args)
28 | }
29 |
30 | return new Proxy(client, {
31 | get(target: RealtimeClient, prop: keyof RealtimeClient, receiver: any) {
32 | if (prop === 'on') {
33 | return clientOn
34 | } else if (prop === 'once') {
35 | return clientOnce
36 | }
37 |
38 | return Reflect.get(target, prop, receiver)
39 | },
40 | })
41 | }
42 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/client/createClient.ts:
--------------------------------------------------------------------------------
1 | import { connect, ClientEvents, SDKStore } from '@signalwire/core'
2 | import { setupInternals } from '../utils/internals'
3 | import { Client } from './Client'
4 |
5 | export const createClient = (userOptions: {
6 | project: string
7 | token: string
8 | logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent'
9 | store?: SDKStore
10 | }) => {
11 | const { emitter, store } = setupInternals(userOptions)
12 | const client = connect({
13 | store: userOptions.store ?? store,
14 | Component: Client,
15 | })({ ...userOptions, store, emitter })
16 |
17 | return client
18 | }
19 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/client/getClient.test.ts:
--------------------------------------------------------------------------------
1 | import { configureFullStack } from '../testUtils'
2 | import { getClient } from './getClient'
3 |
4 | describe('getClient', () => {
5 | it.skip('should cache clients by project and key', () => {
6 | const { store, emitter, destroy } = configureFullStack()
7 | const options = {
8 | project: 'a-proj',
9 | token: 'a-proj',
10 | store,
11 | emitter,
12 | cache: new Map(),
13 | }
14 | const clientA = getClient(options)
15 | const clientB = getClient(options)
16 | const clientC = getClient({
17 | ...options,
18 | project: 'c-project',
19 | })
20 | const clientD = getClient({
21 | ...options,
22 | token: 'd-project',
23 | })
24 |
25 | expect(clientA).toEqual(clientB)
26 | expect(clientA).not.toEqual(clientC)
27 | expect(clientA).not.toEqual(clientD)
28 | expect(options.cache.size).toEqual(3)
29 |
30 | destroy()
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/client/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Client'
2 | export * from './clientConnect'
3 | export * from './setupClient'
4 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/client/setupClient.ts:
--------------------------------------------------------------------------------
1 | import { getLogger, UserOptions } from '@signalwire/core'
2 | import { getCredentials } from '../utils/internals'
3 | import { clientProxyFactory } from './clientProxyFactory'
4 | import { getClient, ClientConfig, ClientCache } from './getClient'
5 |
6 | interface SetupClientOptions {
7 | project?: string
8 | token?: string
9 | logLevel?: UserOptions['logLevel']
10 | cache?: ClientCache
11 | }
12 |
13 | export const setupClient = (userOptions?: SetupClientOptions): ClientConfig => {
14 | const credentials = getCredentials({
15 | token: userOptions?.token,
16 | project: userOptions?.project,
17 | })
18 | const { client, store, emitter } = getClient({
19 | ...userOptions,
20 | ...credentials,
21 | })
22 |
23 | // @ts-expect-error
24 | client.session.on('session.auth_error', () => {
25 | getLogger().error("Wrong credentials: couldn't connect the client.")
26 |
27 | // TODO: we can execute the future `onConnectError` from here.
28 | })
29 |
30 | const proxiedClient = clientProxyFactory(client)
31 |
32 | return {
33 | client: proxiedClient,
34 | store,
35 | emitter,
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/common/clientContext.ts:
--------------------------------------------------------------------------------
1 | import { RealtimeClient, clientConnect } from '../client/index'
2 | import type { ExecuteParams } from '@signalwire/core'
3 |
4 | export const clientContextInterceptorsFactory = (client: RealtimeClient) => {
5 | return {
6 | async addContexts(contexts: string[]) {
7 | await clientConnect(client)
8 | const executeParams: ExecuteParams = {
9 | method: 'signalwire.receive',
10 | params: {
11 | contexts,
12 | },
13 | }
14 |
15 | // @ts-expect-error
16 | return client.execute(executeParams)
17 | },
18 | async removeContexts(contexts: string[]) {
19 | await clientConnect(client)
20 | const executeParams: ExecuteParams = {
21 | method: 'signalwire.unreceive',
22 | params: {
23 | contexts,
24 | },
25 | }
26 |
27 | // @ts-expect-error
28 | return client.execute(executeParams)
29 | },
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/configure/index.ts:
--------------------------------------------------------------------------------
1 | export interface GlobalConfig {
2 | token?: string
3 | project?: string
4 | }
5 |
6 | let GLOBAL_CONFIG: GlobalConfig = {}
7 |
8 | export const getConfig = (): GlobalConfig => {
9 | return GLOBAL_CONFIG
10 | }
11 |
12 | /** @ignore */
13 | export interface ConfigOptions extends GlobalConfig {
14 | /** @internal */
15 | cache?: GlobalConfig
16 | }
17 |
18 | /** @internal */
19 | export const config = ({
20 | cache = GLOBAL_CONFIG,
21 | ...options
22 | }: ConfigOptions) => {
23 | if (cache) {
24 | GLOBAL_CONFIG = cache
25 | }
26 |
27 | Object.entries(options).forEach(([key, value]) => {
28 | // TODO: filter out properties
29 | // @ts-expect-error
30 | GLOBAL_CONFIG[key] = value
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/index.ts:
--------------------------------------------------------------------------------
1 | /** @ignore */
2 | export * from './configure'
3 |
4 | export * as Messaging from './messaging/Messaging'
5 |
6 | export * as Chat from './chat/Chat'
7 |
8 | export * as PubSub from './pubSub/PubSub'
9 |
10 | export * as Task from './task/Task'
11 |
12 | export * as Voice from './voice/Voice'
13 |
14 | export * as Video from './video/Video'
15 |
16 | /**
17 | * Access all the SignalWire APIs with a single instance. You can initiate a {@link SignalWire} to
18 | * use Messaging, Chat, PubSub, Task, Voice, and Video APIs.
19 | *
20 | * @example
21 | *
22 | * The following example creates a single client and uses Task and Voice APIs.
23 | *
24 | * ```javascript
25 | * const client = await SignalWire({
26 | * project: "",
27 | * token: "",
28 | * })
29 | *
30 | * await client.task.listen({
31 | * topics: ['office'],
32 | * onTaskReceived: (payload) => {
33 | * console.log('message.received', payload)}
34 | * })
35 | *
36 | * await client.task.send({
37 | * topic: 'office',
38 | * message: '+1yyy',
39 | * })
40 | *
41 | * await client.voice.listen({
42 | * topics: ['office'],
43 | * onCallReceived: (call) => {
44 | * console.log('call.received', call)}
45 | * })
46 | *
47 | * await client.voice.dialPhone({
48 | * from: '+1xxx',
49 | * to: '+1yyy',
50 | * })
51 | * ```
52 | */
53 | export * from './SignalWire'
54 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/messaging/workers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './messagingWorker'
2 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/pubSub/PubSub.test.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from '@signalwire/core'
2 | import { PubSub } from './PubSub'
3 | import { createClient } from '../client/createClient'
4 |
5 | describe('PubSub', () => {
6 | let pubSub: PubSub
7 | const userOptions = {
8 | host: 'example.com',
9 | project: 'example.project',
10 | token: 'example.token',
11 | }
12 | const swClientMock = {
13 | userOptions,
14 | client: createClient(userOptions),
15 | }
16 |
17 | beforeEach(() => {
18 | //@ts-expect-error
19 | pubSub = new PubSub(swClientMock)
20 | })
21 |
22 | afterEach(() => {
23 | jest.clearAllMocks()
24 | })
25 |
26 | it('should have an event emitter', () => {
27 | expect(pubSub['emitter']).toBeInstanceOf(EventEmitter)
28 | })
29 |
30 | it('should declare the correct event map', () => {
31 | const expectedEventMap = {
32 | onMessageReceived: 'chat.message',
33 | }
34 | expect(pubSub['_eventMap']).toEqual(expectedEventMap)
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/pubSub/PubSub.ts:
--------------------------------------------------------------------------------
1 | import {
2 | PubSubMessageEventName,
3 | PubSubNamespace,
4 | PubSubMessage,
5 | } from '@signalwire/core'
6 | import { SWClient } from '../SWClient'
7 | import { pubSubWorker } from './workers'
8 | import { BaseChat } from '../chat/BaseChat'
9 | import { RealTimePubSubEvents } from '../types/pubSub'
10 |
11 | interface PubSubListenOptions {
12 | channels: string[]
13 | onMessageReceived?: (message: PubSubMessage) => unknown
14 | }
15 |
16 | type PubSubListenersKeys = keyof Omit<
17 | PubSubListenOptions,
18 | 'channels' | 'topics'
19 | >
20 |
21 | export class PubSub extends BaseChat<
22 | PubSubListenOptions,
23 | RealTimePubSubEvents
24 | > {
25 | protected _eventMap: Record<
26 | PubSubListenersKeys,
27 | `${PubSubNamespace}.${PubSubMessageEventName}`
28 | > = {
29 | onMessageReceived: 'chat.message',
30 | }
31 |
32 | constructor(options: SWClient) {
33 | super(options)
34 |
35 | this._client.runWorker('pubSubWorker', {
36 | worker: pubSubWorker,
37 | initialState: {
38 | pubSub: this,
39 | },
40 | })
41 | }
42 | }
43 |
44 | export type { PubSubMessageContract } from '@signalwire/core'
45 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/pubSub/workers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './pubSubWorker'
2 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | jest.mock('@signalwire/core', () => {
2 | return {
3 | ...(jest.requireActual('@signalwire/core') as any),
4 | logger: {
5 | error: jest.fn(),
6 | warn: jest.fn(),
7 | log: jest.fn(),
8 | info: jest.fn(),
9 | debug: jest.fn(),
10 | trace: jest.fn(),
11 | levels: jest.fn(),
12 | setLevel: jest.fn(),
13 | },
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/task/workers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './taskWorker'
2 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/task/workers/taskWorker.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getLogger,
3 | sagaEffects,
4 | SagaIterator,
5 | SDKWorker,
6 | SDKActions,
7 | TaskAction,
8 | } from '@signalwire/core'
9 | import { prefixEvent } from '../../utils/internals'
10 | import type { Client } from '../../client/Client'
11 | import { Task } from '../Task'
12 |
13 | interface TaskWorkerInitialState {
14 | task: Task
15 | }
16 |
17 | export const taskWorker: SDKWorker = function* (options): SagaIterator {
18 | getLogger().trace('taskWorker started')
19 | const {
20 | channels: { swEventChannel },
21 | initialState,
22 | } = options
23 |
24 | const { task } = initialState as TaskWorkerInitialState
25 |
26 | function* worker(action: TaskAction) {
27 | const { context } = action.payload
28 |
29 | // @ts-expect-error
30 | task.emit(prefixEvent(context, 'task.received'), action.payload.message)
31 | }
32 |
33 | const isTaskEvent = (action: SDKActions) =>
34 | action.type === 'queuing.relay.tasks'
35 |
36 | while (true) {
37 | const action = yield sagaEffects.take(swEventChannel, isTaskEvent)
38 |
39 | yield sagaEffects.fork(worker, action)
40 | }
41 |
42 | getLogger().trace('taskWorker ended')
43 | }
44 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/types/chat.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | ChatMember,
3 | ChatMemberEventNames,
4 | ChatMessage,
5 | ChatMessageEventName,
6 | ChatNamespace,
7 | } from '@signalwire/core'
8 |
9 | export type RealTimeChatApiEventsHandlerMapping = Record<
10 | `${ChatNamespace}.${ChatMessageEventName}`,
11 | (message: ChatMessage) => void
12 | > &
13 | Record<
14 | `${ChatNamespace}.${ChatMemberEventNames}`,
15 | (member: ChatMember) => void
16 | >
17 |
18 | export type RealTimeChatEvents = {
19 | [k in keyof RealTimeChatApiEventsHandlerMapping]: RealTimeChatApiEventsHandlerMapping[k]
20 | }
21 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './video'
2 | export * from './task'
3 | export * from './messaging'
4 | export * from './voice'
5 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/types/messaging.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | MessageReceivedEventName,
3 | MessageUpdatedEventName,
4 | } from '@signalwire/core'
5 | import type { MessageContract } from '../messaging/Message'
6 |
7 | export type RealTimeMessagingApiEventsHandlerMapping = Record<
8 | MessageReceivedEventName | MessageUpdatedEventName,
9 | (message: MessageContract) => void
10 | >
11 |
12 | export type MessagingClientApiEvents = {
13 | [k in keyof RealTimeMessagingApiEventsHandlerMapping]: RealTimeMessagingApiEventsHandlerMapping[k]
14 | }
15 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/types/pubSub.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | PubSubMessage,
3 | PubSubMessageEventName,
4 | PubSubNamespace,
5 | } from '@signalwire/core'
6 |
7 | export type RealTimePubSubApiEventsHandlerMapping = Record<
8 | `${PubSubNamespace}.${PubSubMessageEventName}`,
9 | (message: PubSubMessage) => void
10 | >
11 |
12 | export type RealTimePubSubEvents = {
13 | [k in keyof RealTimePubSubApiEventsHandlerMapping]: RealTimePubSubApiEventsHandlerMapping[k]
14 | }
15 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/types/task.ts:
--------------------------------------------------------------------------------
1 | import type { TaskReceivedEventName } from '@signalwire/core'
2 |
3 | export type RealTimeTaskApiEventsHandlerMapping = Record<
4 | TaskReceivedEventName,
5 | (params: Record) => void
6 | >
7 |
8 | export type TaskClientApiEvents = {
9 | [k in keyof RealTimeTaskApiEventsHandlerMapping]: RealTimeTaskApiEventsHandlerMapping[k]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/video/RoomSessionMember/index.ts:
--------------------------------------------------------------------------------
1 | export * from './RoomSessionMember'
2 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/video/RoomSessionPlayback/index.ts:
--------------------------------------------------------------------------------
1 | export * from './RoomSessionPlayback'
2 | export * from './decoratePlaybackPromise'
3 | export { decoratePlaybackPromise } from './decoratePlaybackPromise'
4 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/video/RoomSessionRecording/index.ts:
--------------------------------------------------------------------------------
1 | export * from './RoomSessionRecording'
2 | export * from './decorateRecordingPromise'
3 | export { decorateRecordingPromise } from './decorateRecordingPromise'
4 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/video/RoomSessionStream/index.ts:
--------------------------------------------------------------------------------
1 | export * from './RoomSessionStream'
2 | export * from './decorateStreamPromise'
3 | export { decorateStreamPromise } from './decorateStreamPromise'
4 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/video/methods/index.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from '@signalwire/core'
2 | import { ListenSubscriber } from '../../ListenSubscriber'
3 | import type { Client } from '../../client/Client'
4 | import {
5 | RealTimeRoomEventsHandlerMapping,
6 | RealTimeRoomListeners,
7 | } from '../../types'
8 |
9 | // @ts-expect-error
10 | export interface BaseRoomInterface
11 | extends ListenSubscriber<
12 | RealTimeRoomListeners,
13 | RealTimeRoomEventsHandlerMapping
14 | > {
15 | _client: Client
16 | roomId: string
17 | roomSessionId: string
18 | memberId: string
19 | once>(
20 | event: T,
21 | fn: EventEmitter.EventListener
22 | ): void
23 | off>(
24 | event: T,
25 | fn: EventEmitter.EventListener
26 | ): void
27 | }
28 |
29 | export * as RoomMethods from './methods'
30 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/video/workers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './videoCallingWorker'
2 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/video/workers/videoLayoutWorker.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getLogger,
3 | SagaIterator,
4 | MapToPubSubShape,
5 | VideoLayoutChangedEvent,
6 | toExternalJSON,
7 | stripNamespacePrefix,
8 | VideoLayoutEventNames,
9 | } from '@signalwire/core'
10 | import { RoomSession } from '../RoomSession'
11 | import { VideoCallWorkerParams } from './videoCallingWorker'
12 |
13 | export const videoLayoutWorker = function* (
14 | options: VideoCallWorkerParams>
15 | ): SagaIterator {
16 | getLogger().trace('videoLayoutWorker started')
17 | const {
18 | action: { type, payload },
19 | instanceMap: { get },
20 | } = options
21 |
22 | const roomSessionInstance = get(payload.room_session_id)
23 | if (!roomSessionInstance) {
24 | throw new Error('Missing room session instance for playback')
25 | }
26 |
27 | // TODO: Implement a Layout object when we have a proper payload from the backend
28 | // Create a layout instance and emit that instance
29 |
30 | const event = stripNamespacePrefix(type) as VideoLayoutEventNames
31 |
32 | switch (type) {
33 | case 'video.layout.changed':
34 | roomSessionInstance.emit(event, toExternalJSON(payload))
35 | break
36 | default:
37 | break
38 | }
39 |
40 | getLogger().trace('videoLayoutWorker ended')
41 | }
42 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/video/workers/videoRoomAudienceWorker.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getLogger,
3 | SagaIterator,
4 | MapToPubSubShape,
5 | toExternalJSON,
6 | VideoRoomAudienceCountEvent,
7 | } from '@signalwire/core'
8 | import { RoomSession } from '../RoomSession'
9 | import { VideoCallWorkerParams } from './videoCallingWorker'
10 |
11 | export const videoRoomAudienceWorker = function* (
12 | options: VideoCallWorkerParams>
13 | ): SagaIterator {
14 | getLogger().trace('videoRoomAudienceWorker started')
15 | const {
16 | action: { type, payload },
17 | instanceMap: { get },
18 | } = options
19 |
20 | const roomSessionInstance = get(payload.room_session_id)
21 | if (!roomSessionInstance) {
22 | throw new Error('Missing room session instance for playback')
23 | }
24 |
25 | switch (type) {
26 | case 'video.room.audience_count':
27 | // @ts-expect-error
28 | roomSessionInstance.emit('room.audienceCount', toExternalJSON(payload))
29 | break
30 | default:
31 | break
32 | }
33 |
34 | getLogger().trace('videoRoomAudienceWorker ended')
35 | }
36 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/voice/CallCollect/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CallCollect'
2 | export * from './decorateCollectPromise'
3 | export { decorateCollectPromise } from './decorateCollectPromise'
4 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/voice/CallDetect/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CallDetect'
2 | export * from './decorateDetectPromise'
3 | export { decorateDetectPromise } from './decorateDetectPromise'
4 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/voice/CallPlayback/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CallPlayback'
2 | export * from './decoratePlaybackPromise'
3 | export { decoratePlaybackPromise } from './decoratePlaybackPromise'
4 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/voice/CallPrompt/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CallPrompt'
2 | export * from './decoratePromptPromise'
3 | export { decoratePromptPromise } from './decoratePromptPromise'
4 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/voice/CallRecording/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CallRecording'
2 | export * from './decorateRecordingPromise'
3 | export { decorateRecordingPromise } from './decorateRecordingPromise'
4 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/voice/CallTap/decorateTapPromise.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CallingCallTapEndState,
3 | CallingCallTapState,
4 | Promisify,
5 | } from '@signalwire/core'
6 | import { Call } from '../Call'
7 | import { CallTap } from './CallTap'
8 | import { decoratePromise } from '../../decoratePromise'
9 | import { CallTapListeners } from '../../types'
10 |
11 | export interface CallTapEnded {
12 | id: string
13 | callId: string
14 | nodeId: string
15 | controlId: string
16 | state: CallingCallTapEndState
17 | }
18 |
19 | export interface CallTapPromise
20 | extends Promise,
21 | Omit, 'state'> {
22 | onStarted: () => Promise
23 | onEnded: () => Promise
24 | listen: (listeners: CallTapListeners) => Promise<() => Promise>
25 | stop: () => Promise
26 | ended: () => Promise
27 | state: Promise
28 | }
29 |
30 | export const getters = ['id', 'callId', 'nodeId', 'controlId', 'state']
31 |
32 | export const methods = ['stop', 'ended']
33 |
34 | export function decorateTapPromise(this: Call, innerPromise: Promise) {
35 | // prettier-ignore
36 | return (decoratePromise).call(this, {
37 | promise: innerPromise,
38 | namespace: 'tap',
39 | methods,
40 | getters,
41 | }) as CallTapPromise
42 | }
43 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/voice/CallTap/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CallTap'
2 | export * from './decorateTapPromise'
3 | export { decorateTapPromise } from './decorateTapPromise'
4 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/voice/workers/handlers/callDialEventsHandler.ts:
--------------------------------------------------------------------------------
1 | import { CallingCallDialEventParams, InstanceMap } from '@signalwire/core'
2 | import { Call } from '../../Call'
3 | import { Voice } from '../../Voice'
4 |
5 | interface CallDialEventsHandlerOptions {
6 | payload: CallingCallDialEventParams
7 | instanceMap: InstanceMap
8 | voice: Voice
9 | }
10 |
11 | export function handleCallDialEvents(options: CallDialEventsHandlerOptions) {
12 | const { payload, instanceMap, voice } = options
13 | const { get } = instanceMap
14 |
15 | switch (payload.dial_state) {
16 | case 'failed': {
17 | // @ts-expect-error
18 | voice.emit('dial.failed', payload)
19 | return true
20 | }
21 | case 'answered': {
22 | const callInstance = get(payload.call.call_id)
23 | callInstance.setPayload(payload.call)
24 | // @ts-expect-error
25 | voice.emit('dial.answered', callInstance)
26 | return true
27 | }
28 | default:
29 | return false
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/voice/workers/handlers/callStateEventsHandler.ts:
--------------------------------------------------------------------------------
1 | import { CallingCallStateEventParams, InstanceMap } from '@signalwire/core'
2 | import { RealTimeCallListeners } from '../../../types'
3 | import { Call } from '../../Call'
4 | import { Voice } from '../../Voice'
5 |
6 | interface CallStateEventsHandlerOptions {
7 | payload: CallingCallStateEventParams
8 | voice: Voice
9 | instanceMap: InstanceMap
10 | listeners?: RealTimeCallListeners
11 | }
12 |
13 | export function handleCallStateEvents(options: CallStateEventsHandlerOptions) {
14 | const {
15 | payload,
16 | voice,
17 | listeners,
18 | instanceMap: { get, set, remove },
19 | } = options
20 |
21 | let callInstance = get(payload.call_id)
22 | if (!callInstance) {
23 | callInstance = new Call({
24 | voice,
25 | payload,
26 | listeners,
27 | })
28 | } else {
29 | callInstance.setPayload(payload)
30 | }
31 | set(payload.call_id, callInstance)
32 |
33 | switch (payload.call_state) {
34 | case 'ended': {
35 | callInstance.emit('call.state', callInstance)
36 |
37 | // Resolves the promise when user disconnects using a peer call instance
38 | // @ts-expect-error
39 | callInstance.emit('connect.disconnected', callInstance)
40 | remove(payload.call_id)
41 |
42 | return true
43 | }
44 | default:
45 | callInstance.emit('call.state', callInstance)
46 | return false
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/voice/workers/handlers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './callStateEventsHandler'
2 | export * from './callConnectEventsHandler'
3 | export * from './callDialEventsHandler'
4 |
--------------------------------------------------------------------------------
/packages/realtime-api/src/voice/workers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './voiceCallReceiveWorker'
2 | export * from './voiceCallPlayWorker'
3 | export * from './voiceCallRecordWorker'
4 | export * from './voiceCallCollectWorker'
5 | export * from './voiceCallTapWorker'
6 | export * from './voiceCallConnectWorker'
7 | export * from './voiceCallDialWorker'
8 | export * from './VoiceCallSendDigitWorker'
9 | export * from './voiceCallDetectWorker'
10 | export * from './voiceCallCollectWorker'
11 |
--------------------------------------------------------------------------------
/packages/realtime-api/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "emitDeclarationOnly": true,
5 | "outDir": "dist"
6 | },
7 | "include": ["./src/**/*.ts", "../core/src/**/*.ts"],
8 | "exclude": ["**/*.test.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/swaig/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | dist
5 | docs
6 |
--------------------------------------------------------------------------------
/packages/swaig/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleNameMapper: {
3 | // Force module uuid to resolve with the CJS entry point, because Jest does not support package.json.exports. See https://github.com/uuidjs/uuid/issues/451
4 | uuid: require.resolve('uuid'),
5 | },
6 | transform: {
7 | '\\.[jt]sx?$': ['babel-jest', { configFile: './../../babel.config.js' }],
8 | },
9 | // setupFiles: ['./src/setupTests.ts'],
10 | }
11 |
--------------------------------------------------------------------------------
/packages/swaig/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@signalwire/swaig",
3 | "description": "SignalWire SWAIG SDK",
4 | "author": "SignalWire Team ",
5 | "license": "MIT",
6 | "version": "0.0.1",
7 | "main": "dist/index.node.js",
8 | "exports": {
9 | "require": "./dist/index.node.js",
10 | "default": "./dist/index.node.mjs"
11 | },
12 | "files": [
13 | "dist",
14 | "src"
15 | ],
16 | "engines": {
17 | "node": ">=18"
18 | },
19 | "keywords": [
20 | "signalwire",
21 | "video",
22 | "relay",
23 | "swaig"
24 | ],
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/signalwire/signalwire-js"
28 | },
29 | "bugs": {
30 | "url": "https://github.com/signalwire/signalwire-js/issues"
31 | },
32 | "homepage": "https://github.com/signalwire/signalwire-js/tree/master/packages/swaig",
33 | "scripts": {
34 | "start": "sw-build --dev --node --watchFormat=cjs",
35 | "build": "tsc --project tsconfig.build.json && sw-build --node",
36 | "test": "NODE_ENV=test jest --detectOpenHandles --forceExit",
37 | "prepublishOnly": "npm run build"
38 | },
39 | "dependencies": {
40 | "@fastify/basic-auth": "^6.0.1",
41 | "@fastify/swagger": "^9.2.0",
42 | "@fastify/swagger-ui": "^5.1.0",
43 | "fastify": "^5.1.0"
44 | },
45 | "types": "dist/swaig/src/index.d.ts",
46 | "devDependencies": {
47 | "json-schema-to-ts": "^3.1.1"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/swaig/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import * as SDK from './index'
2 |
3 | describe('SWAIG SDK', () => {
4 | it('should export a SWAIG function', () => {
5 | expect(SDK.SWAIG).toBeDefined()
6 | expect(typeof SDK.SWAIG).toBe('function')
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/packages/swaig/src/index.ts:
--------------------------------------------------------------------------------
1 | export { SWAIG } from './swaig'
2 |
3 | export * from './types'
4 |
--------------------------------------------------------------------------------
/packages/swaig/src/swaig.ts:
--------------------------------------------------------------------------------
1 | import { Server } from './Server'
2 | import type { ServerOptions } from './types'
3 |
4 | export interface SWAIGOptions extends ServerOptions {}
5 |
6 | export interface SWAIG {
7 | server: Server['instance']
8 | addFunction: Server['defineRoute']
9 | run: Server['run']
10 | close: Server['close']
11 | }
12 |
13 | export async function SWAIG(options: SWAIGOptions): Promise {
14 | const server = new Server(options)
15 |
16 | await server.init()
17 |
18 | const service = {
19 | server: server.instance,
20 | addFunction: server.defineRoute.bind(server),
21 | run: server.run.bind(server),
22 | close: server.close.bind(server),
23 | }
24 | return service
25 | }
26 |
--------------------------------------------------------------------------------
/packages/swaig/src/types.ts:
--------------------------------------------------------------------------------
1 | import { FastifySwaggerUiOptions } from '@fastify/swagger-ui'
2 | import { OpenAPIV3, OpenAPIV2 } from 'openapi-types'
3 | import type { JSONSchema } from 'json-schema-to-ts'
4 |
5 | export interface Signature {
6 | function: string
7 | purpose: string
8 | argument: JSONSchema
9 | web_hook_url: string
10 | web_hook_auth_user?: string
11 | web_hook_auth_password?: string
12 | meta_data_token?: string
13 | }
14 |
15 | export interface ServerRunParams {
16 | port?: number
17 | host?: string
18 | }
19 | export interface ServerDefineRouteParams {
20 | name: string
21 | purpose: string
22 | argument: T
23 | username?: string
24 | password?: string
25 | token?: string
26 | description?: string
27 | summary?: string
28 | tags?: string[]
29 | }
30 | export interface CustomRouteHandlerResponse {
31 | response: string
32 | action?: Record[]
33 | }
34 | export type CustomRouteHandler = (
35 | params: T,
36 | extra: any
37 | ) => Promise
38 |
39 | export interface ServerOptions {
40 | baseUrl: string
41 | username?: string
42 | password?: string
43 | token?: string
44 | documentation?: {
45 | openapi?: OpenAPIV3.Document
46 | swagger?: OpenAPIV2.Document
47 | route?: string
48 | ui?: FastifySwaggerUiOptions
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/swaig/src/utils.ts:
--------------------------------------------------------------------------------
1 | import type { FastifyRequest, FastifyReply } from 'fastify'
2 |
3 | export const swLogoUrl =
4 | process.env.SWAIG_SDK_LOGO ??
5 | 'https://developer.signalwire.com/landing-assets/images/logo.svg'
6 |
7 | export async function validate(
8 | username: string,
9 | password: string,
10 | req: FastifyRequest,
11 | _reply: FastifyReply
12 | ) {
13 | if (
14 | (req.webHookUsername && req.webHookUsername !== username) ||
15 | (req.webHookPassword && req.webHookPassword !== password)
16 | ) {
17 | throw new Error('Unauthorized')
18 | }
19 | }
20 |
21 | export const fetchSWLogo = async (): Promise => {
22 | try {
23 | const response = await fetch(swLogoUrl)
24 | const arrayBuffer = await response.arrayBuffer()
25 | return Buffer.from(arrayBuffer)
26 | } catch (error) {
27 | console.error('Error fetching the logo', error)
28 | return undefined
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/swaig/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "emitDeclarationOnly": true,
5 | "outDir": "dist",
6 | "resolveJsonModule": true
7 | },
8 | "include": [
9 | "./src/**/*.ts",
10 | "./package.json"
11 | ],
12 | "exclude": [
13 | "**/*.test.ts"
14 | ]
15 | }
--------------------------------------------------------------------------------
/packages/web-api/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | dist
5 | docs
6 |
--------------------------------------------------------------------------------
/packages/web-api/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | '\\.[jt]sx?$': ['babel-jest', { configFile: './../../babel.config.js' }],
4 | },
5 | }
6 |
--------------------------------------------------------------------------------
/packages/web-api/mocks/rest.ts:
--------------------------------------------------------------------------------
1 | import { http as rest, HttpResponse} from 'msw'
2 |
3 | export const handlers = [
4 | rest.get(`*/video/rooms/:id`, async ({request: req, params, }) => {
5 |
6 | if (params.id === 'timeout') {
7 | await new Promise((r) => setTimeout(r, 100000))
8 | } else if (req.headers.get('authorization')?.includes('invalid')) {
9 | return HttpResponse.text('Unauthorized',
10 | {
11 | status: 401,
12 | statusText: 'Unauthorized',
13 | },
14 | )
15 | } else if (params.id === 'non-existing-id') {
16 | return HttpResponse.text('Not Found',
17 | {
18 | status: 404,
19 | statusText: 'Not Found',
20 | },
21 | )
22 | }
23 |
24 | return HttpResponse.json({
25 | id: '63df00d5-85a7-4633-82e2-6d61d8143dfb',
26 | name: 'karaoke_night',
27 | display_name: 'Mandatory Fun Activities',
28 | max_participants: 5,
29 | delete_on_end: false,
30 | starts_at: '2021-04-27T19:40:21Z',
31 | ends_at: '2021-04-27T19:55:21Z',
32 | created_at: '2021-04-27T19:35:21Z',
33 | updated_at: '2021-04-27T19:35:21Z',
34 | })
35 |
36 | }),
37 | ]
38 |
--------------------------------------------------------------------------------
/packages/web-api/mocks/server.ts:
--------------------------------------------------------------------------------
1 | import { setupServer } from 'msw/node'
2 | import { handlers } from './rest'
3 |
4 | export const server = setupServer(...handlers)
5 |
--------------------------------------------------------------------------------
/packages/web-api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@signalwire/web-api",
3 | "description": "SignalWire Web-API SDK for Node.js",
4 | "author": "SignalWire Team ",
5 | "license": "MIT",
6 | "version": "3.1.3",
7 | "main": "dist/index.node.js",
8 | "exports": {
9 | "require": "./dist/index.node.js",
10 | "default": "./dist/index.node.mjs"
11 | },
12 | "files": [
13 | "dist",
14 | "src"
15 | ],
16 | "engines": {
17 | "node": ">=14"
18 | },
19 | "keywords": [
20 | "signalwire",
21 | "video",
22 | "relay",
23 | "sip"
24 | ],
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/signalwire/signalwire-js"
28 | },
29 | "bugs": {
30 | "url": "https://github.com/signalwire/signalwire-js/issues"
31 | },
32 | "homepage": "https://github.com/signalwire/signalwire-js/tree/master/packages/web-api",
33 | "scripts": {
34 | "start": "sw-build --dev --node --watchFormat=cjs",
35 | "build": "tsc --project tsconfig.build.json && sw-build --node",
36 | "test": "NODE_ENV=test jest --detectOpenHandles --forceExit",
37 | "prepublishOnly": "npm run build"
38 | },
39 | "dependencies": {
40 | "@signalwire/compatibility-api": "^3.1.4",
41 | "@signalwire/core": "4.2.1",
42 | "node-abort-controller": "^3.1.1",
43 | "node-fetch": "^2.6.11"
44 | },
45 | "devDependencies": {
46 | "@types/node-fetch": "^2.5.10",
47 | "msw": "^2.6.6"
48 | },
49 | "types": "dist/web-api/src/index.d.ts"
50 | }
51 |
--------------------------------------------------------------------------------
/packages/web-api/src/createRestClient.test.ts:
--------------------------------------------------------------------------------
1 | import { createRestClient } from './createRestClient'
2 |
3 | describe('createRestClient', () => {
4 | it('should return an object with all the available methods', () => {
5 | const client = createRestClient({
6 | projectId: '',
7 | projectToken: '',
8 | spaceHost: 'space.host.io',
9 | })
10 |
11 | expect(client).toHaveProperty('createRoom')
12 | expect(client).toHaveProperty('deleteRoom')
13 | expect(client).toHaveProperty('getRoomById')
14 | expect(client).toHaveProperty('getRoomByName')
15 | expect(client).toHaveProperty('listAllRooms')
16 | expect(client).toHaveProperty('updateRoom')
17 | expect(client).toHaveProperty('createVRT')
18 | })
19 |
20 | it('should throw when required options are missing', () => {
21 | expect(() => createRestClient()).toThrow('Missing required options')
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/packages/web-api/src/createRestClient.ts:
--------------------------------------------------------------------------------
1 | import { createRoomFactory } from './rooms/createRoomFactory'
2 | import { createVRTFactory } from './rooms/createVRTFactory'
3 | import { deleteRoomFactory } from './rooms/deleteRoomFactory'
4 | import { getRoomFactory } from './rooms/getRoomFactory'
5 | import { listAllRoomsFactory } from './rooms/listAllRoomsFactory'
6 | import { updateRoomFactory } from './rooms/updateRoomFactory'
7 | import { Client } from './types'
8 | import { getConfig } from './utils/getConfig'
9 | import { createHttpClient } from './utils/httpClient'
10 |
11 | /**
12 | *
13 | * NOT USED
14 | *
15 | */
16 | export const createRestClient: Client = (options = {}) => {
17 | const config = getConfig(options)
18 |
19 | const client = createHttpClient({
20 | baseUrl: config.baseUrl,
21 | headers: {
22 | Authorization: `Basic ${Buffer.from(config.authCreds).toString(
23 | 'base64'
24 | )}`,
25 | },
26 | })
27 |
28 | const createRoom = createRoomFactory(client)
29 | const createVRT = createVRTFactory(client)
30 | const deleteRoom = deleteRoomFactory(client)
31 | const getRoom = getRoomFactory(client)
32 | const listAllRooms = listAllRoomsFactory(client)
33 | const updateRoom = updateRoomFactory(client)
34 |
35 | return {
36 | createRoom,
37 | deleteRoom,
38 | ...getRoom,
39 | listAllRooms,
40 | updateRoom,
41 | createVRT,
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/web-api/src/index.ts:
--------------------------------------------------------------------------------
1 | import { validateRequest } from './validateRequest'
2 |
3 | export { validateRequest }
4 |
--------------------------------------------------------------------------------
/packages/web-api/src/rooms/createRoomFactory.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient, RoomResponse } from '../types'
2 | interface CreateRoomOptions {
3 | name: string
4 | displayName?: string
5 | maxParticipants?: number
6 | deleteOnEnd?: boolean
7 | startsAt?: string
8 | endsAt?: string
9 | }
10 |
11 | export type CreateRoom = (options: CreateRoomOptions) => Promise
12 |
13 | type CreateRoomFactory = (client: HttpClient) => CreateRoom
14 |
15 | export const createRoomFactory: CreateRoomFactory = (client) => async (
16 | options
17 | ) => {
18 | const {
19 | name,
20 | displayName: display_name,
21 | maxParticipants: max_participants,
22 | deleteOnEnd: delete_on_end,
23 | startsAt: starts_at,
24 | endsAt: ends_at,
25 | } = options
26 |
27 | const { body } = await client('video/rooms', {
28 | method: 'POST',
29 | body: {
30 | name,
31 | display_name,
32 | max_participants,
33 | delete_on_end,
34 | starts_at,
35 | ends_at,
36 | },
37 | })
38 |
39 | return body
40 | }
41 |
--------------------------------------------------------------------------------
/packages/web-api/src/rooms/createVRTFactory.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '../types'
2 |
3 | interface CreateVRTOptions {
4 | roomName: string
5 | userName: string
6 | scopes?: string[]
7 | }
8 |
9 | interface CreateVRTResponse {
10 | token: string
11 | }
12 |
13 | export type CreateVRT = (
14 | options: CreateVRTOptions
15 | ) => Promise
16 |
17 | type CreateVRTFactory = (client: HttpClient) => CreateVRT
18 |
19 | export const createVRTFactory: CreateVRTFactory = (client) => async (
20 | options
21 | ) => {
22 | const { roomName: room_name, userName: user_name, scopes } = options
23 | const { body } = await client('video/room_tokens', {
24 | method: 'POST',
25 | body: {
26 | room_name,
27 | user_name,
28 | scopes,
29 | },
30 | })
31 |
32 | return body
33 | }
34 |
--------------------------------------------------------------------------------
/packages/web-api/src/rooms/deleteRoomFactory.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '../types'
2 | interface DeleteRoomOptions {
3 | id: string
4 | }
5 |
6 | export type DeleteRoom = (options: DeleteRoomOptions) => Promise
7 |
8 | type DeleteRoomFactory = (client: HttpClient) => DeleteRoom
9 |
10 | export const deleteRoomFactory: DeleteRoomFactory = (client) => async (
11 | options
12 | ) => {
13 | const { id } = options
14 |
15 | await client(`video/rooms/${id}`, {
16 | method: 'DELETE',
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/packages/web-api/src/rooms/getRoomFactory.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient, RoomResponse } from '../types'
2 |
3 | export interface GetRoomByIdOptions {
4 | id: string
5 | }
6 |
7 | export interface GetRoomByNameOptions {
8 | name: string
9 | }
10 |
11 | export type GetRoom = (options: T) => Promise
12 |
13 | type getRoomFactory = (
14 | client: HttpClient
15 | ) => {
16 | getRoomById: GetRoom
17 | getRoomByName: GetRoom
18 | }
19 |
20 | export const getRoomFactory: getRoomFactory = (client) => {
21 | const getRoomPath = 'video/rooms'
22 | const getRoom = async ({ id }: { id: string }) => {
23 | try {
24 | const { body } = await client(`${getRoomPath}/${id}`, {
25 | method: 'GET',
26 | })
27 |
28 | return body
29 | } catch (e) {
30 | if (e.code === 404) {
31 | return null
32 | }
33 |
34 | throw e
35 | }
36 | }
37 |
38 | return {
39 | async getRoomById(options: GetRoomByIdOptions) {
40 | return getRoom({ id: options.id })
41 | },
42 | async getRoomByName(options: GetRoomByNameOptions) {
43 | return getRoom({ id: options.name })
44 | },
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/web-api/src/rooms/listAllRoomsFactory.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient, ListAllRoomsResponse } from '../types'
2 |
3 | interface ListAllRoomsOptions {
4 | startsAfter?: string
5 | startsBefore?: string
6 | pageNumber?: number
7 | pageToken?: string
8 | pageSize?: number
9 | }
10 |
11 | export type ListAllRooms = (
12 | options?: ListAllRoomsOptions
13 | ) => Promise
14 |
15 | type ListAllRoomsFactory = (client: HttpClient) => ListAllRooms
16 |
17 | export const listAllRoomsFactory: ListAllRoomsFactory = (client) => async (
18 | options = {}
19 | ) => {
20 | const {
21 | startsAfter: starts_at,
22 | startsBefore: starts_before,
23 | pageNumber: page_number,
24 | pageToken: page_token,
25 | pageSize: page_size,
26 | } = options
27 | const { body } = await client('video/rooms', {
28 | method: 'GET',
29 | searchParams: {
30 | starts_at,
31 | starts_before,
32 | page_number,
33 | page_token,
34 | page_size,
35 | },
36 | })
37 |
38 | return body
39 | }
40 |
--------------------------------------------------------------------------------
/packages/web-api/src/rooms/updateRoomFactory.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient, RoomResponse } from '../types'
2 | interface UpdateRoomOptions {
3 | name: string
4 | displayName?: string
5 | maxParticipants?: number
6 | deleteOnEnd?: boolean
7 | startsAt?: string
8 | endsAt?: string
9 | }
10 |
11 | export type UpdateRoom = (options: UpdateRoomOptions) => Promise
12 |
13 | type UpdateRoomFactory = (client: HttpClient) => UpdateRoom
14 |
15 | export const updateRoomFactory: UpdateRoomFactory = (client) => async (
16 | options
17 | ) => {
18 | const {
19 | name,
20 | displayName: display_name,
21 | maxParticipants: max_participants,
22 | deleteOnEnd: delete_on_end,
23 | startsAt: starts_at,
24 | endsAt: ends_at,
25 | } = options
26 |
27 | const { body } = await client('video/rooms', {
28 | method: 'PUT',
29 | body: {
30 | name,
31 | display_name,
32 | max_participants,
33 | delete_on_end,
34 | starts_at,
35 | ends_at,
36 | },
37 | })
38 |
39 | return body
40 | }
41 |
--------------------------------------------------------------------------------
/packages/web-api/src/types.ts:
--------------------------------------------------------------------------------
1 | import { CreateRoom } from './rooms/createRoomFactory'
2 | import { CreateVRT } from './rooms/createVRTFactory'
3 | import {
4 | GetRoom,
5 | GetRoomByIdOptions,
6 | GetRoomByNameOptions,
7 | } from './rooms/getRoomFactory'
8 | import { ListAllRooms } from './rooms/listAllRoomsFactory'
9 | import { UpdateRoom } from './rooms/updateRoomFactory'
10 | import { ConfigParamaters } from './utils/getConfig'
11 | import { createHttpClient } from './utils/httpClient'
12 |
13 | export type HttpClient = ReturnType
14 |
15 | interface VideoSDKClient {
16 | createRoom: CreateRoom
17 | createVRT: CreateVRT
18 | listAllRooms: ListAllRooms
19 | getRoomByName: GetRoom
20 | getRoomById: GetRoom
21 | updateRoom: UpdateRoom
22 | }
23 |
24 | export interface ListAllRoomsResponse {
25 | links: {
26 | self: string
27 | first: string
28 | // TODO: validate this
29 | next: string | null
30 | }
31 | rooms: RoomResponse[]
32 | }
33 |
34 | export interface RoomResponse {
35 | id: string
36 | name: string
37 | display_name: string
38 | max_participants: number
39 | delete_on_end: boolean
40 | starts_at: string
41 | ends_at: string
42 | created_at: string
43 | updated_at: string
44 | }
45 |
46 | export type Client = (options?: ConfigParamaters) => VideoSDKClient
47 |
--------------------------------------------------------------------------------
/packages/web-api/src/utils/getConfig.ts:
--------------------------------------------------------------------------------
1 | export interface ConfigParamaters {
2 | projectId?: string
3 | projectToken?: string
4 | spaceHost?: string
5 | }
6 |
7 | export type ConfigOptions = {
8 | projectId: string
9 | projectToken: string
10 | authCreds: string
11 | baseUrl: string
12 | }
13 |
14 | type GetConfig = (options?: ConfigParamaters) => ConfigOptions
15 |
16 | const getBaseUrl = (spaceHost: string) => {
17 | return `https://${spaceHost}/api/`
18 | }
19 |
20 | export const getConfig: GetConfig = (options = {}) => {
21 | const {
22 | projectId = process.env.PROJECT_ID,
23 | projectToken = process.env.PROJECT_TOKEN,
24 | spaceHost = process.env.SPACE_HOST,
25 | } = options
26 |
27 | if (!projectId || !projectToken || !spaceHost) {
28 | throw new TypeError('Missing required options')
29 | }
30 |
31 | const authCreds = `${projectId}:${projectToken}`
32 |
33 | return {
34 | projectId,
35 | projectToken,
36 | baseUrl: getBaseUrl(spaceHost),
37 | authCreds,
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/web-api/src/validateRequest.ts:
--------------------------------------------------------------------------------
1 | import { createHmac } from 'node:crypto'
2 | import { RestClient } from '@signalwire/compatibility-api'
3 |
4 | /**
5 | * Utility function to validate an incoming request is indeed from SignalWire
6 | *
7 | * @param {string} privateKey - The "SIGNING KEY", as seen in the SignalWire API page
8 | * @param {string} header - The value of the X-SignalWire-Signature header from the request
9 | * @param {string} url - The full URL (with query string) you configured to handle this request
10 | * @param {string} rawBody - The raw body of the request (JSON string)
11 | * @returns {boolean} - Whether the request is valid or not
12 | */
13 | export const validateRequest = (
14 | privateKey: string,
15 | header: string,
16 | url: string,
17 | rawBody: string
18 | ): boolean => {
19 | if (typeof rawBody !== 'string') {
20 | throw new TypeError(
21 | `"rawBody" is not a string. You may need to JSON.stringify the request body.`
22 | )
23 | }
24 | const hmac = createHmac('sha1', privateKey)
25 | hmac.update(`${url}${rawBody}`)
26 | const valid = hmac.digest('hex') === header
27 |
28 | if (valid) {
29 | return true
30 | }
31 |
32 | const parsedBody = JSON.parse(rawBody)
33 | // @ts-expect-error - add suppressWarning: true
34 | return RestClient.validateRequest(privateKey, header, url, parsedBody, true)
35 | }
36 |
--------------------------------------------------------------------------------
/packages/web-api/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "emitDeclarationOnly": true,
5 | "outDir": "dist",
6 | },
7 | "include": ["./src/**/*.ts", "../core/src/**/*.ts"],
8 | "exclude": ["**/*.test.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/webrtc/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | dist
5 | docs
6 |
--------------------------------------------------------------------------------
/packages/webrtc/README.md:
--------------------------------------------------------------------------------
1 | # @signalwire/webrtc
2 |
3 | The SignalWire WebRTC package contains the various WebRTC object used by other SignalWire packages.
4 |
5 | ## Related
6 |
7 | - [Get Started](https://developer.signalwire.com/)
8 | - [`@signalwire/js`](https://www.npmjs.com/package/@signalwire/js)
9 |
10 | ## License
11 |
12 | `@signalwire/webrtc` is copyright © 2018-2023 [SignalWire](http://signalwire.com). It is free software, and may be redistributed under the terms specified in the [MIT-LICENSE](https://github.com/signalwire/signalwire-js/blob/master/LICENSE) file.
13 |
--------------------------------------------------------------------------------
/packages/webrtc/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleNameMapper: {
3 | // Force module uuid to resolve with the CJS entry point, because Jest does not support package.json.exports. See https://github.com/uuidjs/uuid/issues/451
4 | uuid: require.resolve('uuid'),
5 | },
6 | transform: {
7 | '\\.[jt]sx?$': ['babel-jest', { configFile: './../../babel.config.js' }],
8 | },
9 | testMatch: ['/src/**/*.test.ts'],
10 | setupFiles: ['./src/setupTests.ts'],
11 | }
12 |
--------------------------------------------------------------------------------
/packages/webrtc/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@signalwire/webrtc",
3 | "description": "SignalWire WebRTC library",
4 | "author": "SignalWire Team ",
5 | "license": "MIT",
6 | "version": "3.13.1",
7 | "main": "dist/cjs/webrtc/src/index.js",
8 | "module": "dist/mjs/webrtc/src/index.js",
9 | "files": [
10 | "dist",
11 | "src"
12 | ],
13 | "engines": {
14 | "node": ">=14"
15 | },
16 | "keywords": [
17 | "signalwire",
18 | "audio",
19 | "video",
20 | "rtc",
21 | "real time communication",
22 | "webrtc",
23 | "relay"
24 | ],
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/signalwire/signalwire-js"
28 | },
29 | "bugs": {
30 | "url": "https://github.com/signalwire/signalwire-js/issues"
31 | },
32 | "homepage": "https://github.com/signalwire/signalwire-js/tree/master/packages/webrtc",
33 | "scripts": {
34 | "start": "tsc --watch --project tsconfig.build.json",
35 | "build": "tsc --project tsconfig.build.json && tsc --project tsconfig.cjs.json",
36 | "test": "NODE_ENV=test jest",
37 | "prepublishOnly": "npm run build"
38 | },
39 | "dependencies": {
40 | "@signalwire/core": "4.2.1",
41 | "sdp": "^3.2.0"
42 | },
43 | "types": "dist/cjs/webrtc/src/index.d.ts"
44 | }
45 |
--------------------------------------------------------------------------------
/packages/webrtc/src/index.test.ts:
--------------------------------------------------------------------------------
1 | describe('WebRTC Entry Point', () => {
2 | it('should work', () => {
3 | expect(1).toEqual(1)
4 | })
5 | })
6 |
--------------------------------------------------------------------------------
/packages/webrtc/src/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | getDevices,
3 | getCameraDevices,
4 | getMicrophoneDevices,
5 | getSpeakerDevices,
6 | getSpeakerById,
7 | getDevicesWithPermissions,
8 | getCameraDevicesWithPermissions,
9 | getMicrophoneDevicesWithPermissions,
10 | getSpeakerDevicesWithPermissions,
11 | assureDeviceId,
12 | assureVideoDevice,
13 | assureAudioInDevice,
14 | assureAudioOutDevice,
15 | createDeviceWatcher,
16 | createMicrophoneDeviceWatcher,
17 | createSpeakerDeviceWatcher,
18 | createCameraDeviceWatcher,
19 | createMicrophoneAnalyzer,
20 | } from './utils/deviceHelpers'
21 | export type { MicrophoneAnalyzer } from './utils/deviceHelpers'
22 | export {
23 | supportsMediaDevices,
24 | supportsGetUserMedia,
25 | supportsGetDisplayMedia,
26 | getUserMedia,
27 | getDisplayMedia,
28 | enumerateDevices,
29 | enumerateDevicesByKind,
30 | getSupportedConstraints,
31 | streamIsValid,
32 | supportsMediaOutput,
33 | setMediaElementSinkId,
34 | stopStream,
35 | stopTrack,
36 | checkPermissions,
37 | checkCameraPermissions,
38 | checkMicrophonePermissions,
39 | checkSpeakerPermissions,
40 | requestPermissions,
41 | } from './utils'
42 | export * from './utils/interfaces'
43 | export { BaseConnection, BaseConnectionOptions } from './BaseConnection'
44 |
--------------------------------------------------------------------------------
/packages/webrtc/src/utils/constants.ts:
--------------------------------------------------------------------------------
1 | import { ConnectionOptions } from './interfaces'
2 |
3 | export const INVITE_VERSION = 1000
4 |
5 | export const AUDIO_CONSTRAINTS: MediaTrackConstraints = {
6 | echoCancellation: true,
7 | noiseSuppression: true,
8 | autoGainControl: true,
9 | }
10 |
11 | export const AUDIO_CONSTRAINTS_SCREENSHARE: MediaTrackConstraints = {
12 | ...AUDIO_CONSTRAINTS,
13 | noiseSuppression: false,
14 | autoGainControl: false,
15 | // @ts-expect-error
16 | googAutoGainControl: false,
17 | }
18 |
19 | export const VIDEO_CONSTRAINTS: MediaTrackConstraints = {
20 | width: { ideal: 1280, min: 320 },
21 | height: { ideal: 720, min: 180 },
22 | aspectRatio: { ideal: 16 / 9 },
23 | }
24 |
25 | export const DEFAULT_CALL_OPTIONS: ConnectionOptions = {
26 | destinationNumber: 'room',
27 | remoteCallerName: 'Outbound Call',
28 | remoteCallerNumber: '',
29 | callerName: '',
30 | callerNumber: '',
31 | audio: AUDIO_CONSTRAINTS,
32 | video: VIDEO_CONSTRAINTS,
33 | useStereo: false,
34 | attach: false,
35 | screenShare: false,
36 | additionalDevice: false,
37 | userVariables: {},
38 | requestTimeout: 10 * 1000,
39 | autoApplyMediaParams: true,
40 | iceGatheringTimeout: 2 * 1000,
41 | maxIceGatheringTimeout: 5 * 1000,
42 | maxConnectionStateTimeout: 3 * 1000,
43 | watchMediaPackets: true,
44 | watchMediaPacketsTimeout: 2 * 1000,
45 | }
46 |
--------------------------------------------------------------------------------
/packages/webrtc/src/utils/enumerateDevices.ts:
--------------------------------------------------------------------------------
1 | import { getMediaDevicesApi } from './primitives'
2 |
3 | /**
4 | * Enumerates the media input and output devices available on this device.
5 | *
6 | * > 📘
7 | * > Depending on the browser, some information (such as the `label` and
8 | * > `deviceId` attributes) could be hidden until permission is granted, for
9 | * > example by calling {@link getUserMedia}.
10 | *
11 | * @example
12 | * ```typescript
13 | * await SignalWire.WebRTC.enumerateDevices()
14 | * // [
15 | * // {
16 | * // "deviceId": "Rug5Bk...4TMhY=",
17 | * // "kind": "videoinput",
18 | * // "label": "HD FaceTime Camera",
19 | * // "groupId": "EEX/N2...AjrOs="
20 | * // },
21 | * // ...
22 | * // ]
23 | * ```
24 | */
25 | export const enumerateDevices = () => {
26 | return getMediaDevicesApi().enumerateDevices()
27 | }
28 |
29 | export const enumerateDevicesByKind = async (
30 | filterByKind?: MediaDeviceKind
31 | ) => {
32 | let devices: MediaDeviceInfo[] = await enumerateDevices().catch(
33 | (_error) => []
34 | )
35 | if (filterByKind) {
36 | devices = devices.filter(({ kind }) => kind === filterByKind)
37 | }
38 | return devices
39 | }
40 |
--------------------------------------------------------------------------------
/packages/webrtc/src/utils/getDisplayMedia.ts:
--------------------------------------------------------------------------------
1 | import { getMediaDevicesApi } from './primitives'
2 |
3 | /**
4 | * Prompts the user to share the screen and asynchronously returns a
5 | * [MediaStream](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream)
6 | * object associated with a display or part of it.
7 | *
8 | * @param constraints an optional
9 | * [MediaStreamConstraints](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints)
10 | * object specifying requirements for the returned [MediaStream](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream).
11 | *
12 | * @example
13 | * ```typescript
14 | * await SignalWire.WebRTC.getDisplayMedia()
15 | * // MediaStream {id: "HCXy...", active: true, ...}
16 | * ```
17 | */
18 | export const getDisplayMedia = (constraints?: MediaStreamConstraints) => {
19 | return getMediaDevicesApi().getDisplayMedia(constraints)
20 | }
21 |
--------------------------------------------------------------------------------
/packages/webrtc/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './primitives'
2 | export * from './enumerateDevices'
3 | export * from './getUserMedia'
4 | export * from './getDisplayMedia'
5 | export * from './permissions'
6 | export * from './requestPermissions'
7 |
--------------------------------------------------------------------------------
/packages/webrtc/src/utils/requestPermissions.ts:
--------------------------------------------------------------------------------
1 | import { _getMediaDeviceKindByName, stopStream } from './primitives'
2 | import { getUserMedia } from './getUserMedia'
3 |
4 | /**
5 | * Prompts the user to grant permissions for the devices matching the specified set of constraints.
6 | * @param constraints an optional [MediaStreamConstraints](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints)
7 | * object specifying requirements for the returned [MediaStream](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream).
8 | *
9 | * @example
10 | * To only request audio permissions:
11 | *
12 | * ```typescript
13 | * await SignalWire.WebRTC.requestPermissions({audio: true, video: false})
14 | * ```
15 | *
16 | * @example
17 | * To request permissions for both audio and video, specifying constraints for the video:
18 | * ```typescript
19 | * const constraints = {
20 | * audio: true,
21 | * video: {
22 | * width: { min: 1024, ideal: 1280, max: 1920 },
23 | * height: { min: 576, ideal: 720, max: 1080 }
24 | * }
25 | * }
26 | * await SignalWire.WebRTC.requestPermissions(constraints)
27 | * ```
28 | */
29 | export const requestPermissions = async (
30 | constraints: MediaStreamConstraints
31 | ) => {
32 | try {
33 | const stream = await getUserMedia(constraints)
34 | stopStream(stream)
35 | } catch (error) {
36 | throw error
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/webrtc/src/workers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './vertoEventWorker'
2 | export * from './roomSubscribedWorker'
3 | export * from './promoteDemoteWorker'
4 | export * from './sessionAuthWorker'
5 |
--------------------------------------------------------------------------------
/packages/webrtc/src/workers/sessionAuthWorker.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getLogger,
3 | sagaEffects,
4 | actions,
5 | SagaIterator,
6 | SDKWorker,
7 | SDKWorkerHooks,
8 | } from '@signalwire/core'
9 |
10 | import { BaseConnection } from '../BaseConnection'
11 |
12 | type SessionAuthWorkerOnDone = (args: BaseConnection) => void
13 | type SessionAuthWorkerOnFail = (args: { error: Error }) => void
14 |
15 | export type SessionAuthWorkerHooks = SDKWorkerHooks<
16 | SessionAuthWorkerOnDone,
17 | SessionAuthWorkerOnFail
18 | >
19 |
20 | type Action = typeof actions.authSuccessAction | typeof actions.authErrorAction
21 |
22 | export const sessionAuthWorker: SDKWorker<
23 | BaseConnection,
24 | SessionAuthWorkerHooks
25 | > = function* (options): SagaIterator {
26 | getLogger().debug('sessionAuthWorker started')
27 | const { instance } = options
28 | const action: Action = yield sagaEffects.take([
29 | actions.authSuccessAction.type,
30 | actions.authErrorAction.type,
31 | ])
32 |
33 | switch (action.type) {
34 | case actions.authSuccessAction.type:
35 | yield sagaEffects.call([instance, instance.resume])
36 | break
37 | case actions.authErrorAction.type:
38 | yield sagaEffects.call([instance, instance.setState], 'hangup')
39 | break
40 | }
41 |
42 | getLogger().debug('sessionAuthWorker ended')
43 | }
44 |
--------------------------------------------------------------------------------
/packages/webrtc/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es2017",
5 | "outDir": "dist/mjs"
6 | },
7 | "include": ["./src/**/*.ts", "../core/src/**/*.ts", "../webrtc/src/**/*.ts"],
8 | "exclude": ["**/*.test.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/webrtc/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "module": "CommonJS",
5 | "target": "es2015",
6 | "outDir": "dist/cjs"
7 | },
8 | "include": ["./src/**/*.ts", "../core/src/**/*.ts", "../webrtc/src/**/*.ts"],
9 | "exclude": ["**/*.test.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/scripts/sw-build-all/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @sw-internal/build-all
2 |
3 | ## 0.0.3
4 |
5 | ### Patch Changes
6 |
7 | - [#1001](https://github.com/signalwire/signalwire-js/pull/1001) [`968d226b`](https://github.com/signalwire/signalwire-js/commit/968d226ba2791f44dea4bd1b0d173aefaf103bda) Thanks [@ayeminag](https://github.com/ayeminag)! - - API to fetch address by id and tests
8 |
9 | ## 0.0.2
10 |
11 | ### Patch Changes
12 |
13 | - [#434](https://github.com/signalwire/signalwire-js/pull/434) [`0677540c`](https://github.com/signalwire/signalwire-js/commit/0677540c65211570b0762ff56ed4b85b8d66e1d0) - Bubble up build errors.
14 |
15 | ## 0.0.1
16 |
17 | ### Patch Changes
18 |
19 | - [#388](https://github.com/signalwire/signalwire-js/pull/388) [`62c25d8`](https://github.com/signalwire/signalwire-js/commit/62c25d8468c37711f37c6674c24251755a4ada39) - Upgrade dependencies, add dependency visualizer to UMD build.
20 |
--------------------------------------------------------------------------------
/scripts/sw-build-all/bin/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { cli } from '../index.js'
4 |
5 | cli(process.argv)
6 |
--------------------------------------------------------------------------------
/scripts/sw-build-all/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sw-internal/build-all",
3 | "version": "0.0.3",
4 | "description": "A CLI for creating the build tree and concurrently building each of them",
5 | "main": "index.js",
6 | "type": "module",
7 | "private": true,
8 | "scripts": {
9 | "test": ""
10 | },
11 | "bin": {
12 | "sw-build-all": "bin/cli.js"
13 | },
14 | "dependencies": {
15 | "concurrently": "^8.2.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/scripts/sw-build/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @sw-internal/build
2 |
3 | ## 0.0.4
4 |
5 | ### Patch Changes
6 |
7 | - [#1001](https://github.com/signalwire/signalwire-js/pull/1001) [`968d226b`](https://github.com/signalwire/signalwire-js/commit/968d226ba2791f44dea4bd1b0d173aefaf103bda) Thanks [@ayeminag](https://github.com/ayeminag)! - - API to fetch address by id and tests
8 |
9 | ## 0.0.3
10 |
11 | ### Patch Changes
12 |
13 | - [#605](https://github.com/signalwire/signalwire-js/pull/605) [`2f909c9e`](https://github.com/signalwire/signalwire-js/commit/2f909c9ef670eeaed7b3444b9d4bf703bfbc3a1b) - Change how the SDK agent is defined.
14 |
15 | ## 0.0.2
16 |
17 | ### Patch Changes
18 |
19 | - [#464](https://github.com/signalwire/signalwire-js/pull/464) [`2c8fc597`](https://github.com/signalwire/signalwire-js/commit/2c8fc59719e7f40c1d9b01ebf67190d358dcea46) - Upgrade dependencies.
20 |
21 | ## 0.0.1
22 |
23 | ### Patch Changes
24 |
25 | - [#388](https://github.com/signalwire/signalwire-js/pull/388) [`62c25d8`](https://github.com/signalwire/signalwire-js/commit/62c25d8468c37711f37c6674c24251755a4ada39) - Upgrade dependencies, add dependency visualizer to UMD build.
26 |
--------------------------------------------------------------------------------
/scripts/sw-build/bin/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { cli } from '../index.js'
4 |
5 | cli(process.argv)
6 |
--------------------------------------------------------------------------------
/scripts/sw-build/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sw-internal/build",
3 | "version": "0.0.4",
4 | "description": "A CLI for building packages using esbuild",
5 | "main": "index.js",
6 | "type": "module",
7 | "private": true,
8 | "scripts": {
9 | "test": ""
10 | },
11 | "bin": {
12 | "sw-build": "bin/cli.js"
13 | },
14 | "dependencies": {
15 | "@rollup/plugin-commonjs": "^22.0.0",
16 | "@rollup/plugin-node-resolve": "^13.3.0",
17 | "@rollup/plugin-replace": "^4.0.0",
18 | "@rollup/plugin-terser": "^0.4.4",
19 | "esbuild-node-externals": "^1.18.0",
20 | "rollup": "^2.74.1",
21 | "rollup-plugin-license": "^2.7.0",
22 | "rollup-plugin-typescript2": "^0.31.2",
23 | "rollup-plugin-visualizer": "^5.6.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/scripts/sw-common/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @sw-internal/common
2 |
3 | ## 0.0.4
4 |
5 | ### Patch Changes
6 |
7 | - [#1001](https://github.com/signalwire/signalwire-js/pull/1001) [`968d226b`](https://github.com/signalwire/signalwire-js/commit/968d226ba2791f44dea4bd1b0d173aefaf103bda) Thanks [@ayeminag](https://github.com/ayeminag)! - - API to fetch address by id and tests
8 |
9 | ## 0.0.3
10 |
11 | ### Patch Changes
12 |
13 | - [#573](https://github.com/signalwire/signalwire-js/pull/573) [`eaed3706`](https://github.com/signalwire/signalwire-js/commit/eaed3706aff7ba009885b13f845096a3b21eca03) - [internal] Add infra for testing usages of the SDK with node
14 |
15 | ## 0.0.2
16 |
17 | ### Patch Changes
18 |
19 | - [#464](https://github.com/signalwire/signalwire-js/pull/464) [`2c8fc597`](https://github.com/signalwire/signalwire-js/commit/2c8fc59719e7f40c1d9b01ebf67190d358dcea46) - Upgrade dependencies.
20 |
21 | ## 0.0.1
22 |
23 | ### Patch Changes
24 |
25 | - [#388](https://github.com/signalwire/signalwire-js/pull/388) [`62c25d8`](https://github.com/signalwire/signalwire-js/commit/62c25d8468c37711f37c6674c24251755a4ada39) - Upgrade dependencies, add dependency visualizer to UMD build.
26 |
--------------------------------------------------------------------------------
/scripts/sw-common/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sw-internal/common",
3 | "version": "0.0.4",
4 | "description": "CLI utilities",
5 | "main": "index.js",
6 | "type": "module",
7 | "private": true,
8 | "dependencies": {
9 | "execa": "^7.1.1"
10 | },
11 | "scripts": {
12 | "test": ""
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/scripts/sw-release/bin/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { cli } from '../index.js'
4 |
5 | cli(process.argv)
6 |
--------------------------------------------------------------------------------
/scripts/sw-release/modes/production.js:
--------------------------------------------------------------------------------
1 | import { isCleanGitStatus } from '@sw-internal/common'
2 | import { getBuildTask, getTestTask, publishTaskFactory } from '../common.js'
3 |
4 | const getProductionTasks = ({ flags, executer, dryRun }) => {
5 | return [
6 | {
7 | title: '🔍 Checking Git status',
8 | task: async (_ctx, task) => {
9 | try {
10 | await isCleanGitStatus({ executer })
11 | task.title = '🔍 Git: clean working tree!'
12 | } catch (e) {
13 | task.title = `🛑 ${e.message}`
14 |
15 | return Promise.reject('')
16 | }
17 | },
18 | },
19 | ...getBuildTask({ dryRun, executer }),
20 | ...getTestTask({ dryRun, executer }),
21 | ...publishTaskFactory({
22 | npmOptions: [],
23 | publishGitTag: true,
24 | executer,
25 | dryRun,
26 | }),
27 | ]
28 | }
29 |
30 | export { getProductionTasks }
31 |
--------------------------------------------------------------------------------
/scripts/sw-release/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sw-internal/release",
3 | "version": "0.0.6",
4 | "description": "A CLI for publishing releases on npm.",
5 | "main": "index.js",
6 | "type": "module",
7 | "private": true,
8 | "scripts": {
9 | "test": ""
10 | },
11 | "bin": {
12 | "sw-release": "bin/cli.js"
13 | },
14 | "dependencies": {
15 | "@sw-internal/common": "^0.0.4",
16 | "execa": "^7.1.1",
17 | "listr2": "^4.0.5"
18 | },
19 | "devDependencies": {
20 | "inquirer": "^8.2.5"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/scripts/sw-test/bin/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const { cli } = require('../index.js')
4 |
5 | cli(process.argv)
6 |
--------------------------------------------------------------------------------
/scripts/sw-test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sw-internal/test",
3 | "version": "0.0.5",
4 | "description": "A CLI for running tests using Jest",
5 | "main": "index.js",
6 | "private": true,
7 | "scripts": {
8 | "test": ""
9 | },
10 | "bin": {
11 | "sw-test": "bin/cli.js"
12 | },
13 | "devDependencies": {
14 | "esbuild-register": "^3.6.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/sw.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | agents: {
3 | // Maps packages to agent-name. Take into account that
4 | // these names are just a part within the fully
5 | // qualified agent sent to the server:
6 | // @signalwire///
7 | byName: {
8 | '@signalwire/realtime-api': 'nodejs/realtime-api',
9 | '@signalwire/js': 'js/browser',
10 | '@signalwire/web-api': 'nodejs/web-api',
11 | '@signalwire/node': 'nodejs/node',
12 | '@signalwire/swaig': 'nodejs/swaig',
13 | },
14 | },
15 | utilityPackages: ['@signalwire/core', '@signalwire/webrtc'],
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.docs.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "esnext"],
4 | "module": "commonjs",
5 | "target": "es2020",
6 |
7 | "strict": true,
8 | "strictNullChecks": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "strictPropertyInitialization": false,
12 | "useUnknownInCatchVariables": false,
13 |
14 | "baseUrl": ".",
15 | "paths": {
16 | "@signalwire/js": ["packages/js/src"],
17 | "@signalwire/core": ["packages/core/src"],
18 | "@signalwire/webrtc": ["packages/webrtc/src"],
19 | "@signalwire/node": ["packages/node/src"]
20 | },
21 | },
22 | "exclude": ["**/*.test.ts", "**/*.native.ts"]
23 | }
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "esnext",
4 | "lib": ["dom", "esnext"],
5 | "importHelpers": true,
6 | "declaration": true,
7 | "sourceMap": true,
8 | "strict": true,
9 | "strictNullChecks": true,
10 | "target": "ESNext",
11 | "noUnusedLocals": true,
12 | "noUnusedParameters": true,
13 | "noImplicitReturns": true,
14 | "noFallthroughCasesInSwitch": true,
15 | "moduleResolution": "node",
16 | "jsx": "react",
17 | "esModuleInterop": true,
18 | "allowJs": false,
19 | "baseUrl": ".",
20 | "rootDir": "packages",
21 | "typeRoots": ["./node_modules/@types", "./types"],
22 | "paths": {
23 | "@signalwire/js": ["packages/js/src"],
24 | "@signalwire/core": ["packages/core/src"],
25 | "@signalwire/webrtc": ["packages/webrtc/src"],
26 | "@signalwire/node": ["packages/node/src"]
27 | },
28 | "composite": true,
29 | "declarationMap": true,
30 | "strictPropertyInitialization": false,
31 | "useUnknownInCatchVariables": false,
32 | "skipLibCheck": true,
33 | "forceConsistentCasingInFileNames": true
34 | },
35 | "include": ["packages"]
36 | }
37 |
--------------------------------------------------------------------------------