Real-time channels
phantom.aj.realtimeOther players
A plugin's real-time channels.
Scripts and plugins
A script has all of it, as phantom.aj.realtime, and so does a plugin, from ctx.phantom(): the SDK runs in the plugin's own frame. Each call is marked with what it needs: game:read for what the area knows and hears, which "game": "read" in the manifest grants, and game:act for doing things in it as the player, which needs "game": "act" (see Permissions).
Overview
| Member | What it does | Plugins |
|---|---|---|
available | Whether real-time channels can be used here: false in a copy of the SDK without them. | game:read |
limits | What a channel will carry: the sizes and rates Phantom holds a plugin to. | game:read |
answer() | Answers an ask by its id, once. | game:act |
ask() | Asks one member of a channel something, by ids, and resolves with the answer; rejects with a RealtimeAskError. | game:act |
attach() | Attaches a picture beside a channel, by id, and resolves with the RealtimeAttachment to pass around. | game:act |
attachmentBytes() | An attachment's bytes, fetched from Phantom; rejects for one expired or taken down. | game:read |
attachmentUrl() | An attachment as a blob: address an <img> can show, made here once per attachment and good for as long as the page is open. | game:read |
channels() | The channels the player is in, each with who is there now. | game:read |
decline() | Turns an ask down at once (failed, or no-handler), rather than leaving it to time out. | game:act |
giveHost() | Hands a channel's host to another member, by ids. | game:act |
host() | A channel's host, by id; null for none, an empty channel, or a host the player hid. | game:read |
join() | Joins a channel and gives it back, to send on and hear. | game:act |
leave() | Leaves a channel by id; resolves with whether the player was in it. | game:act |
members() | Everyone else in a channel, by id; empty for one the player is not in. | game:read |
open() | Joins a channel without the object join gives, by its id. | game:act |
presence() | What the plugin set about the player in a channel, by id; null for none. | game:read |
send() | Sends to everyone else in a channel, by id. | game:act |
sendTo() | Sends to one member of a channel alone, by ids. | game:act |
setPresence() | Replaces what everyone in a channel sees about the player, by id. | game:act |
setState() | Replaces a channel's shared state, by id; what landed comes back through onState. | game:act |
state() | A channel's shared state, by id; null for none or a channel the player is not in. | game:read |
status() | How the connection behind the channels is doing; off where there are none. | game:read |
stayIn() | Joins a channel and keeps the player in it, joining again whenever the connection drops for longer than a channel rides out by itself. | game:act |
onAsk() | Hears what other members ask, on every channel. | game:read |
onClose() | Hears a channel close, and why. | game:read |
onHost() | Hears a host change, on every channel. | game:read |
onJoin() | Hears someone arrive in any of the player's channels. | game:read |
onLeave() | Hears someone leave any of the player's channels. | game:read |
onMessage() | Hears what others send, on every channel the player is in. | game:read |
onPresence() | Hears another member's presence change, on every channel. | game:read |
onReconnect() | Hears the connection come back after it dropped, once each time: the moment to set up again what lives outside a channel, such as a call's peers signalled over it. | game:read |
onState() | Hears a shared state change, on every channel, the player's own writes among them. | game:read |
onStatus() | Hears the connection's status change: connecting, online, offline (dropped, and Phantom is reconnecting by itself), refused, or off with no channel to connect for. | game:read |
Properties
available
available: booleanWhether real-time channels can be used here: false in a copy of the SDK without them.
limits
limits: RealtimeLimitsWhat a channel will carry: the sizes and rates Phantom holds a plugin to.
Types
Methods
answer
answer(askId: string, answer: unknown) => Promise<void>Answers an ask by its id, once.
Parameters
| Name | Type | |
|---|---|---|
askId | string | |
answer | unknown |
Returns
Promise<void>
ask
ask(channelId: string, memberId: string, question: unknown, options?: { timeout?: number | undefined; } | undefined) => Promise<unknown>Asks one member of a channel something, by ids, and resolves with the answer; rejects with a RealtimeAskError. options.timeout is the host's own (milliseconds); a channel from join takes { timeoutMs, signal } instead.
Parameters
| Name | Type | |
|---|---|---|
channelId | string | |
memberId | string | |
question | unknown | |
options optional | { timeout?: number | undefined; } | undefined |
Returns
Promise<unknown>
attach
attach(channelId: string, data: RealtimeAttachData, options?: RealtimeAttachOptions | undefined) => Promise<RealtimeAttachment>Attaches a picture beside a channel, by id, and resolves with the RealtimeAttachment to pass around. A channel from join has attach without the id.
Parameters
| Name | Type | |
|---|---|---|
channelId | string | |
data | RealtimeAttachData | |
options optional | RealtimeAttachOptions | undefined |
Returns
Promise<RealtimeAttachment>
Types
attachmentBytes
attachmentBytes(attachment: RealtimeAttachment) => Promise<Uint8Array<ArrayBufferLike>>An attachment's bytes, fetched from Phantom; rejects for one expired or taken down.
Parameters
| Name | Type | |
|---|---|---|
attachment | RealtimeAttachment |
Returns
Promise<Uint8Array<ArrayBufferLike>>
attachmentUrl
attachmentUrl(attachment: RealtimeAttachment) => Promise<string>An attachment as a blob: address an <img> can show, made here once per attachment and good for as long as the page is open.
Parameters
| Name | Type | |
|---|---|---|
attachment | RealtimeAttachment |
Returns
Promise<string>
Example
const lobby = await phantom.aj.realtime.join({ scope: 'named', name: 'gallery' });
const bytes = new Uint8Array(await (await fetch('/art.png')).arrayBuffer());
const picture = await lobby.attach(bytes);
const img = document.createElement('img');
img.src = await phantom.aj.realtime.attachmentUrl(picture);channels
channels() => RealtimeChannelInfo[]The channels the player is in, each with who is there now.
Returns
RealtimeChannelInfo[]
Types
decline
decline(askId: string, reason?: RealtimeAskFailure | undefined) => Promise<void>Turns an ask down at once (failed, or no-handler), rather than leaving it to time out.
Parameters
| Name | Type | |
|---|---|---|
askId | string | |
reason optional | RealtimeAskFailure | undefined |
Returns
Promise<void>
giveHost
giveHost(channelId: string, memberId: string) => Promise<void>Hands a channel's host to another member, by ids. Only the host may.
Parameters
| Name | Type | |
|---|---|---|
channelId | string | |
memberId | string |
Returns
Promise<void>
host
host(channelId: string) => RealtimeMember | nullA channel's host, by id; null for none, an empty channel, or a host the player hid.
Parameters
| Name | Type | |
|---|---|---|
channelId | string |
Returns
RealtimeMember \| null
join
join<Data = unknown, State extends RealtimeObject = RealtimeObject, Presence extends RealtimeObject = RealtimeObject>(spec: RealtimeChannelSpec, options?: RealtimeJoinOptions<Presence> | undefined) => Promise<RealtimeChannel<Data, State, Presence>>Joins a channel and gives it back, to send on and hear. Rejects, saying why, for a plugin not from the store nor in Developer mode, one switched off by staff, a full named channel that did not ask to split, or a player not signed in to Phantom. Joining one already joined gives the same channel; it is left once every join has left it.
Parameters
| Name | Type | |
|---|---|---|
spec | RealtimeChannelSpec | |
options optional | RealtimeJoinOptions<Presence> | undefined |
Returns
Promise<RealtimeChannel<Data, State, Presence>>
Types
Example
const lobby = await phantom.aj.realtime.join<{ move: number }>({ scope: 'named', name: 'ttt' });
lobby.onMessage(({ from, data }) => console.log(from.screenName, 'played', data.move));leave
leave(channelId: string) => Promise<boolean>Leaves a channel by id; resolves with whether the player was in it.
Parameters
| Name | Type | |
|---|---|---|
channelId | string |
Returns
Promise<boolean>
members
members(channelId: string) => RealtimeMember[]Everyone else in a channel, by id; empty for one the player is not in.
Parameters
| Name | Type | |
|---|---|---|
channelId | string |
Returns
RealtimeMember[]
open
open(spec: RealtimeChannelSpec, options?: RealtimeJoinOptions | undefined) => Promise<RealtimeChannelInfo>Joins a channel without the object join gives, by its id. Most plugins want join.
Parameters
| Name | Type | |
|---|---|---|
spec | RealtimeChannelSpec | |
options optional | RealtimeJoinOptions | undefined |
Returns
Promise<RealtimeChannelInfo>
Types
presence
presence(channelId: string) => RealtimeObject | nullWhat the plugin set about the player in a channel, by id; null for none.
Parameters
| Name | Type | |
|---|---|---|
channelId | string |
Returns
RealtimeObject \| null
send
send(channelId: string, data: unknown) => Promise<void>Sends to everyone else in a channel, by id.
Parameters
| Name | Type | |
|---|---|---|
channelId | string | |
data | unknown |
Returns
Promise<void>
sendTo
sendTo(channelId: string, memberId: string, data: unknown) => Promise<void>Sends to one member of a channel alone, by ids.
Parameters
| Name | Type | |
|---|---|---|
channelId | string | |
memberId | string | |
data | unknown |
Returns
Promise<void>
setPresence
setPresence(channelId: string, presence: RealtimeObject | null) => Promise<void>Replaces what everyone in a channel sees about the player, by id.
Parameters
| Name | Type | |
|---|---|---|
channelId | string | |
presence | RealtimeObject | null |
Returns
Promise<void>
setState
setState(channelId: string, state: RealtimeObject | null) => Promise<void>Replaces a channel's shared state, by id; what landed comes back through onState.
Parameters
| Name | Type | |
|---|---|---|
channelId | string | |
state | RealtimeObject | null |
Returns
Promise<void>
state
state(channelId: string) => RealtimeObject | nullA channel's shared state, by id; null for none or a channel the player is not in.
Parameters
| Name | Type | |
|---|---|---|
channelId | string |
Returns
RealtimeObject \| null
status
status() => RealtimeStatusHow the connection behind the channels is doing; off where there are none.
Returns
RealtimeStatus
Types
stayIn
stayIn<Data = unknown, State extends RealtimeObject = RealtimeObject, Presence extends RealtimeObject = RealtimeObject>(spec: RealtimeChannelSpec, ready: (channel: RealtimeChannel<Data, State, Presence>) => void, options?: RealtimeStayOptions<Presence> | undefined) => Promise<() => void>Joins a channel and keeps the player in it, joining again whenever the connection drops for longer than a channel rides out by itself. ready is called with the first channel and every one a rejoin makes, so wire the listeners up there. The first join's refusal is thrown; after that only a channel that closed offline is rejoined. Resolves with what stops it.
Parameters
| Name | Type | |
|---|---|---|
spec | RealtimeChannelSpec | |
ready | (channel: RealtimeChannel<Data, State, Presence>) => void | |
options optional | RealtimeStayOptions<Presence> | undefined |
Returns
Promise<() => void>
Types
Example
const stop = await phantom.aj.realtime.stayIn({ scope: 'room' }, (room) => {
room.onMembers((members) => console.log(members.length, 'here'));
});Events
onAsk
onAsk(listener: (ask: RealtimeAsk) => void) => () => voidHears what other members ask, on every channel. Answer each with answer or decline; a channel from join answers its own through its onAsk.
Parameters
| Name | Type | |
|---|---|---|
listener | (ask: RealtimeAsk) => void |
Returns
() => void. Call it to stop listening.
Types
onClose
onClose(listener: (event: RealtimeClose) => void) => () => voidHears a channel close, and why.
Parameters
| Name | Type | |
|---|---|---|
listener | (event: RealtimeClose) => void |
Returns
() => void. Call it to stop listening.
Types
onHost
onHost(listener: (event: RealtimeHostEvent) => void) => () => voidHears a host change, on every channel.
Parameters
| Name | Type | |
|---|---|---|
listener | (event: RealtimeHostEvent) => void |
Returns
() => void. Call it to stop listening.
Types
onJoin
onJoin(listener: (event: RealtimeMemberEvent) => void) => () => voidHears someone arrive in any of the player's channels.
Parameters
| Name | Type | |
|---|---|---|
listener | (event: RealtimeMemberEvent) => void |
Returns
() => void. Call it to stop listening.
Types
onLeave
onLeave(listener: (event: RealtimeMemberEvent) => void) => () => voidHears someone leave any of the player's channels.
Parameters
| Name | Type | |
|---|---|---|
listener | (event: RealtimeMemberEvent) => void |
Returns
() => void. Call it to stop listening.
Types
onMessage
onMessage(listener: (message: RealtimeMessage) => void) => () => voidHears what others send, on every channel the player is in.
Parameters
| Name | Type | |
|---|---|---|
listener | (message: RealtimeMessage) => void |
Returns
() => void. Call it to stop listening.
Types
onPresence
onPresence(listener: (event: RealtimePresenceEvent) => void) => () => voidHears another member's presence change, on every channel.
Parameters
| Name | Type | |
|---|---|---|
listener | (event: RealtimePresenceEvent) => void |
Returns
() => void. Call it to stop listening.
Types
onReconnect
onReconnect(listener: () => void) => () => voidHears the connection come back after it dropped, once each time: the moment to set up again what lives outside a channel, such as a call's peers signalled over it. A channel from join rejoins by itself; its own onRejoin says when it is back.
Parameters
| Name | Type | |
|---|---|---|
listener | () => void |
Returns
() => void. Call it to stop listening.
Example
const peers = new Map<string, RTCPeerConnection>();
phantom.aj.realtime.onReconnect(() => {
for (const peer of peers.values()) peer.restartIce();
});onState
onState(listener: (event: RealtimeStateEvent) => void) => () => voidHears a shared state change, on every channel, the player's own writes among them.
Parameters
| Name | Type | |
|---|---|---|
listener | (event: RealtimeStateEvent) => void |
Returns
() => void. Call it to stop listening.
Types
onStatus
onStatus(listener: (status: RealtimeStatus) => void) => () => voidHears the connection's status change: connecting, online, offline (dropped, and Phantom is reconnecting by itself), refused, or off with no channel to connect for.
Parameters
| Name | Type | |
|---|---|---|
listener | (status: RealtimeStatus) => void |
Returns
() => void. Call it to stop listening.
Types
Example
phantom.aj.realtime.onStatus(({ state, error }) => {
console.log(state === 'offline' ? 'Reconnecting…' : (error ?? state));
});Types
The types the calls above take and give, with every field. Import any of them in a plugin from @phantomclient/plugin/sdk, or use them as they are in a script.
RealtimeLimits
What a channel will carry: the sizes and rates Phantom's server holds a plugin to. Fixed numbers, so they can be read before anything is joined and used to size what a plugin sends.
| Field | Type | |
|---|---|---|
messageBytes | number | The most one message may be, as JSON, in bytes. An ask and its answer are each a message. |
stateBytes | number | The most a channel's shared state may be, as JSON, in bytes. |
presenceBytes | number | The most one member's presence may be, as JSON, in bytes. |
messagesPerSecond | number | Messages a plugin may send a second, across its channels; states and presences count. |
updatesPerSecond | number | How often a state or a presence actually goes, a second; publish holds to it. |
members | number | How many members one channel holds before it fills, or opens another part. |
channels | number | How many channels one player's connection holds at once, every plugin's counted. |
nameLength | number | The longest a named channel's name may be. |
attachmentBytes | number | |
attachmentTypes | readonly ("image/png" | "image/jpeg" | "image/webp" | "image/gif")[] | |
attachmentLifetimeMs | number | How long an attachment is kept after it was last attached, in milliseconds. |
attachmentsPerMinute | number | |
rejoinQueue | number | How many sends a channel holds while it rejoins, to go once it is back. |
RealtimeStatus
| Field | Type | |
|---|---|---|
state | RealtimeState | |
error | string | null | Why it is offline or refused, as a sentence; null when all is well. |
RealtimeState
How the player's side of real-time channels is doing.
type RealtimeState = | 'off'
| 'connecting'
| 'online'
/** The connection dropped; Phantom is reconnecting. */
| 'offline'
/** Phantom's server refused the player: signed out, no plan, or barred from the community. */
| 'refused'RealtimeJoinOptions
What to join a channel with: the presence to arrive holding.
| Field | Type | |
|---|---|---|
presence optional | Presence | null | undefined | What everyone else sees about the player the moment they arrive. Up to 512 bytes as JSON. |
RealtimeStayOptions
What to keep a channel with: what join takes, and who hears a rejoin that failed.
type RealtimeStayOptions<Presence extends RealtimeObject = RealtimeObject> = RealtimeJoinOptions<Presence> & {
/** Told whatever a rejoin failed with; the plugin's own log otherwise. */
onProblem?: (error: unknown) => void;
}RealtimeChannelInfo
A channel the player is in: what it is, who they are in it, and everyone else there.
| Field | Type | |
|---|---|---|
id | string | Phantom's id for it, which the id-based members take. |
scope | "room" | "named" | "plugin" | |
name | string | null | A named channel's name; null for the other kinds. |
part | number | null | Which part the player is in, from 1, for a channel in parts; null for a room channel. |
you | RealtimeMember | The player, as the others see them. |
members | RealtimeMember[] | Everyone else in it now. |
state | RealtimeObject | null | The shared state as it stood at the join; null when nobody has set one. |
host | string | null | The host's id; null with nobody in it, or a host hidden from the player (hostHidden). |
hostHidden optional | boolean | undefined | The channel has a host the player cannot be told the name of: they hid each other. |
RealtimeAttachData
A file to attach, as its bytes.
type RealtimeAttachData = Uint8Array | ArrayBufferRealtimeAttachOptions
What to attach with: type has Phantom refuse a file that is not that type.
| Field | Type | |
|---|---|---|
type optional | "image/png" | "image/jpeg" | "image/webp" | "image/gif" | undefined |
RealtimeMessage
Something another member sent.
| Field | Type | |
|---|---|---|
channel | string | |
from | RealtimeMember | |
data | Data | |
at | number | When Phantom's server passed it on, in milliseconds since the epoch. |
direct | boolean | Sent to the player alone (sendTo). |
RealtimeMemberEvent
Someone arriving in a channel or leaving it.
| Field | Type | |
|---|---|---|
channel | string | |
member | RealtimeMember |
RealtimeStateEvent
A channel's shared state, now it has changed.
| Field | Type | |
|---|---|---|
channel | string | |
state | State | null | What it is now; null when cleared. |
from | string | null | Who set it; null for the state found on arriving, and for somebody the player hid. |
at | number |
RealtimePresenceEvent
A member's presence, now it has changed. member.presence is the new value.
| Field | Type | |
|---|---|---|
channel | string | |
member | RealtimeMember<Presence> |
RealtimeHostEvent
The channel's host, now Phantom has named another one.
| Field | Type | |
|---|---|---|
channel | string | |
host | RealtimeMember<Presence> | null | The player or one of the members; null with nobody there, or a host the player hid. |
you | boolean | Whether the player is the host. |
hosted | boolean | Whether the channel has a host at all, one the player is never told of included. |
RealtimeAsk
A question another member asked, waiting on an answer. id is what answer takes.
| Field | Type | |
|---|---|---|
channel | string | |
id | string | |
from | RealtimeMember | |
data | Question | |
at | number |
RealtimeClose
A channel closing, and why.
| Field | Type | |
|---|---|---|
channel | string | |
reason | RealtimeCloseReason | |
message | string | The same, as a sentence to show the player. |
code optional | string | undefined | Phantom's word for why, when it gave one: OFFLINE for a dropped connection, the server's own (BANNED, NOT_LISTED) for a refusal. A channel from join closes as offline only for a refusal; a dropped connection it rejoins by itself. |