jam-core
Client-side JavaScript/TypeScript library for 🍓 Jam.
npm i jam-core
import {createJam} from 'jam-core';
let roomId = 'my-jam-1234';
let jamConfig = {domain: 'jam.systems'};
const [state, {setProps, enterRoom, createRoom}] = createJam({jamConfig});
createRoom(roomId, {name: 'My Jam', stageOnly: true});
setProps({roomId});
await enterRoom(roomId);
console.log(state.inRoom === roomId);
Embedding this example in a HTML page and running it in two different browsers is enough to let two people speak with each other over WebRTC 🎧
You can use jam-core
to build custom UIs and bots for Jam that run in a Browser. Find out more about Jam on our Gitlab page: https://gitlab.com/jam-systems/jam
jam-core
is compatible with Jam as a Service where we manage Jam servers for you. This lets you build powerful connected audio apps while only worrying about the UI 🚀
jam-core
is independent of any JavaScript/TypeScript framework. You might want to check out the companion library jam-core-react for integrating jam-core
into a React app.
This library works in all modern browsers.
Documentation
import {createJam} from 'jam-core';
The API essentially consists of a single function, createJam()
, which returns an array of two objects:
const [state, api] = createJam(options);
state
and api
together represent a single user in a single Jam room (which can be switched), as in the official Jam app. If this is your use case, you probably only need to call createJam()
once, at the beginning. If you want your page to act as multiple users, or be in multiple Jam rooms at the same time, call createJam()
multiple times.
The options
parameter is mainly a way of specifying the domain where Jam is hosted on, and whether this domain has an SFU (server-side streaming) enabled:
let options = {
jamConfig: {
domain: 'jam.example.com',
sfu: false,
},
cachedRooms: {
'my-hardcoded-room': {
name: 'My hardcoded Room',
speakers: [],
moderators: [],
},
},
debug: false,
};
state
state
holds information about current client-side state, the room, discovered peers and various others. state
is only for reading -- it will be mutated by Jam as the application evolves.
As an example, one property that will be on your state
immediately is state.myIdentity
, which is stored in the Browser's localStorage and auto-created if not found there:
const [state, api] = createJam();
console.log(state.myIdentity);
This is a cryptographic key pair that represents your user account. The public key simultaneously acts as your user id; it's available as a shortcut on state.myId
.
console.log(state.myId);
Some more examples:
let {
roomId,
inRoom,
room,
iAmSpeaker,
peers,
identities,
myAudio,
micMuted,
peerState,
} = state;
The full up-to-date specification of state
is exposed via TypeScript and can be inspected in the source code.
api
api
holds various methods to interact with Jam. It lets you do all things programmatically that you can do interactively in the official Jam UI.
Let's unpack these methods by grouping them into three categories:
1. Manage rooms and identities on the backend
The first kinds of methods are wrappers around REST API calls that will modify state on the Jam server. They are all async and return true
if the operation succeeded, false
otherwise.
Example:
const [state, api] = createJam();
let {createRoom, addSpeaker, removeSpeaker, updateIdentity} = api;
let roomId = 'my-jam-1234';
let ok = await createRoom(roomId, {name: 'My new room'});
if (!ok) console.warn('the room possibly already existed');
console.log(state.roomId);
ok = await addSpeaker(roomId, 'kXISM4HDE4CXugmALs02mUEr4vKPK6DFJgGmhCVV7hY');
ok = await removeSpeaker(roomId, state.myId);
ok = await updateInfo({name: 'Thomas'});
console.log(state.myIdentity.info);
There are currently no JavaScript methods for fetching information about rooms/identities from the server, because Jam will do that automatically as needed, and will listen to most information in real-time by connecting via WebSocket.
However, you could use our REST API to check that the calls above succeeded:
let roomId = 'my-jam-1234';
let response = await fetch(
`https://jam.systems/_/pantry/api/v1/rooms/${roomId}`
);
let room = await response.json();
console.log(room);
Full API for managing rooms and identities on the backend:
let {
createRoom,
updateRoom,
addSpeaker,
addModerator,
removeSpeaker,
removeModerator,
updateInfo,
addAdmin,
removeAdmin,
} = api;
2. Operate the current Jam room
These methods correspond to some user action. They are async and are batched automatically. To interrupt batching and see changes reflected, just await
the returned Promise.
Example:
const [state, api] = createJam();
let {
setProps,
enterRoom,
leaveRoom,
sendReaction,
startRecording,
stopRecording,
} = api;
let roomId = 'my-jam-1234';
setProps({roomId});
console.log(state.roomId);
await setProps({roomId});
console.log(state.roomId);
setProps({userInteracted: true});
await enterRoom(roomId);
console.log(state.inRoom);
await leaveRoom();
console.log(state.inRoom);
await enterRoom(roomId);
sendReaction('❤️');
sendReaction('❤️');
await sendReaction('❤️');
console.log(state.reactions);
await new Promise(r => setTimeout(r, 6000));
console.log(state.reactions);
await startRecording();
console.log(state.isRecording);
await new Promise(r => setTimeout(r, 10000));
await stopRecording();
console.log(state.isRecording);
await new Promise(r => setTimeout(r, 100));
console.log(state.recordedAudio);
If you're wondering when to do setProps({roomId})
: In the official Jam UI, we sync this to the current URL, i.e. it is called on navigation to https://jam.systems/<roomId>
. In that state, the library eagerly fetches the room and tries to connect with other peers, but only if the room exists.
Full API for operating the current Jam room:
let {
setProps,
enterRoom,
leaveRoom,
leaveStage,
sendReaction,
retryMic,
retryAudio,
autoJoinOnce,
startRecording,
stopRecording,
downloadRecording,
} = api;
Several ways to operate the current Jam room are combined in a single setProps()
method which takes a partial object of stuff that should be changed:
setProps({
roomId: 'my-jam-1234',
userInteracted: true,
micMuted: true,
handRaised: true,
});
3. Listen to changes
Finally, there is one method that lets you listen to state changes, which may for example be triggered by other users interacting with the room:
let {onState} = api;
onState('peers', peers => {
console.log('peers changed:', peers);
});
onState((key, value) => {
console.log('state change:', key, value);
});
onState()
returns a callback that lets you unsubscribe the listener:
let {onState, setProps} = api;
let unsubscribe = onState('micMuted', muted =>
console.log(`mic muted? ${muted}!`)
);
await setProps({micMuted: true});
unsubscribe();
await setProps({micMuted: false});
Note that you probably don't need onState()
when using our React integration jam-core-react, which will give you React hooks to subscribe to state changes from components.
Additional API for identities
Sometimes you might need to manually specify the identity of your user, rather than let us create a random one. For this, we give you two functions which are best called before createJam()
. Both are async:
importDefaultIdentity(identity)
lets you replace the current default identity stored in the browser:
import {importDefaultIdentity, createJam} from 'jam-core';
await importDefaultIdentity({
publicKey: '...',
secretKey: '...',
info: {name: 'Christoph'},
});
await importDefaultIdentity({secretKey: '...'});
await importDefaultIdentity({
seed: 'arbitrary string!',
info: {name: 'Christoph'},
});
await importDefaultIdentity({info: {name: 'Christoph'}});
let [state, api] = createJam();
console.log(state.myIdentity.info.name);
importRoomIdentity(roomId, identity)
lets you specify an identity that only applies in one specific room:
import {importRoomIdentity, createJam} from 'jam-core';
let roomId = 'the-simpsons-jam';
await importRoomIdentity(roomId, {
secretKey: '...',
info: {name: 'Homer Simpson'},
});
await importRoomIdentity(roomId, {secretKey: '...'});
await importRoomIdentity(roomId, {
seed: 'arbitrary string!',
info: {name: 'Homer Simpson'},
});
await importRoomIdentity(roomId, {info: {name: 'Homer Simpson'}});
let [state, api] = createJam();
console.log(state.myIdentity.info.name);
await setProps({roomId: 'the-simpsons-jam'});
console.log(state.myIdentity.info.name);
Examples
Example code for a super simple custom UI with manual DOM-updating: https://gitlab.com/jam-systems/jam/-/blob/master/ui/examples/tiny-jam/index.html
Real-life examples for using jam-core
for bots (we used this for load-testing Jam!): https://github.com/mitschabaude/jam-bots
Also, the official Jam UI is built entirely on jam-core
(but mostly accesses it through the React integration): https://gitlab.com/jam-systems/jam/-/blob/master/ui/Jam.jsx