-
Notifications
You must be signed in to change notification settings - Fork 0
Core API
The handwritten surface: one function to start, one class to hold everything, four error types. The fifteen curated layers have their own pages (Workshop, Stats, Cloud, Leaderboards, Lobbies, Social, Overlay, Auth, System, Capture, Controllers, DLC, Inventory, P2P, Recording), and the generated interface classes are on Flat-API.
import { init, Steam, SteamInitError, SteamResultError, flat } from 'steamwand.js';function init(opts?: InitOptions): Steam;Initializes the Steam API and returns a ready Steam. In order:
- When
opts.appIdis given, setsprocess.env.SteamAppIdandprocess.env.SteamGameIdto that number as a string. - Loads the redistributable (
opts.libPath, or the bundled one for this platform). - Calls
SteamAPI_InitFlatwith a 1024-byte error buffer. - On failure, throws
SteamInitErrorcarrying Valve's own diagnostic text. - Calls
SteamAPI_ManualDispatch_Init, creates the dispatch pump on the pipe fromSteamAPI_GetHSteamPipe, and starts it.
steam.appId is opts.appId when given, otherwise
Number(process.env.SteamAppId ?? 0).
Throws. SteamInitError when SteamAPI_InitFlat returns anything other
than k_ESteamAPIInitResult_OK. err.initResult is the raw
ESteamAPIInitResult value, and err.message is Steam's text, or
SteamAPI_InitFlat failed (<result>) when Steam wrote nothing. A missing or
unloadable library throws from koffi before that point, and an unsupported
process.platform throws steamwand: unsupported platform <name>.
One session at a time. Manual dispatch owns the process-wide callback
queue, so a second session would pump the same queue twice and steal the first
one's callbacks. init guards against that: while a session is open it throws
Error: steamwand: a Steam session is already open (one init per process); call close() on it first
before touching the library. This is a plain Error, not SteamInitError.
Sequential sessions are fine: close releases the lock, and an init
after it starts a fresh session. That is what lets a test suite or a workbench
switch app ids without restarting the process.
| Option | Type | Default | Meaning |
|---|---|---|---|
appId |
number |
none | App id to initialize under. Omit to rely on steam_appid.txt or an existing SteamAppId environment variable. |
libPath |
string |
the bundled runtime/<platform>/steam_api* file |
Absolute path of the steam_api library to load. |
pumpIntervalMs |
number |
50 |
Manual-dispatch pump interval in milliseconds. |
function isSteamRunning(libPath?: string): boolean;Reports whether a Steam client is running on this machine. Loads the library
but does not start the Steam API, so it is safe to call on its own, before
init, to tell "Steam is not running" apart from the other init failures.
libPath overrides the bundled redistributable, as in InitOptions.
function restartAppIfNecessary(appId: number, libPath?: string): boolean;Wraps SteamAPI_RestartAppIfNecessary. Call it at the very top of the
process, before init. When it returns true, Steam is relaunching your app
under its own control, so exit at once and let that copy take over. It always
returns false when a steam_appid.txt sits next to the executable, which is
why that file belongs in development only.
Returned by init. The constructor is public
(new Steam(native, dispatch, appId)), but it expects an already initialized
SteamNative and a started SteamDispatch, so use init.
| Member | Type | Notes |
|---|---|---|
appId |
number |
The app id this instance was initialized with. Readonly. |
native |
SteamNative |
The loaded library and the symbol cache. Readonly. |
dispatch |
SteamDispatch |
The running callback pump. Readonly. |
Each getter builds its interface class on first access and caches it for the
lifetime of the Steam object. Construction calls the versioned accessor
(SteamAPI_SteamApps_v009 and friends) and throws
steamwand: <accessor> returned null (is Steam initialized?) if Steam hands
back nothing.
All 25 interfaces have one, named after the interface with the leading ISteam
dropped and the first letter lowercased:
| Getter | Type |
|---|---|
steam.user |
flat.ISteamUser |
steam.friends |
flat.ISteamFriends |
steam.utils |
flat.ISteamUtils |
steam.matchmaking |
flat.ISteamMatchmaking |
steam.matchmakingServers |
flat.ISteamMatchmakingServers |
steam.parties |
flat.ISteamParties |
steam.remoteStorage |
flat.ISteamRemoteStorage |
steam.userStats |
flat.ISteamUserStats |
steam.apps |
flat.ISteamApps |
steam.networking |
flat.ISteamNetworking |
steam.screenshots |
flat.ISteamScreenshots |
steam.music |
flat.ISteamMusic |
steam.http |
flat.ISteamHTTP |
steam.input |
flat.ISteamInput |
steam.controller |
flat.ISteamController |
steam.ugc |
flat.ISteamUGC |
steam.htmlSurface |
flat.ISteamHTMLSurface |
steam.inventory |
flat.ISteamInventory |
steam.timeline |
flat.ISteamTimeline |
steam.video |
flat.ISteamVideo |
steam.parentalSettings |
flat.ISteamParentalSettings |
steam.remotePlay |
flat.ISteamRemotePlay |
steam.networkingMessages |
flat.ISteamNetworkingMessages |
steam.networkingSockets |
flat.ISteamNetworkingSockets |
steam.networkingUtils |
flat.ISteamNetworkingUtils |
Fifteen task-level layers and the async wrapper sit on top, cached the same way.
Each is built on first access from the interface it wraps, this session's
dispatch pump, steam.on and steam.once where it needs callbacks, and (for
workshop) steam.appId.
| Getter | Type | Wraps | Page |
|---|---|---|---|
steam.workshop |
Workshop |
steam.ugc |
Workshop |
steam.stats |
Stats |
steam.userStats |
Stats |
steam.cloud |
Cloud |
steam.remoteStorage |
Cloud |
steam.leaderboards |
Leaderboards |
steam.userStats |
Leaderboards |
steam.lobbies |
Lobbies |
steam.matchmaking |
Lobbies |
steam.social |
Social |
steam.friends, steam.utils
|
Social |
steam.overlay |
Overlay |
steam.friends, steam.utils
|
Overlay |
steam.auth |
Auth |
steam.user |
Auth |
steam.system |
System |
steam.utils, steam.apps
|
System |
steam.capture |
Capture |
steam.screenshots |
Capture |
steam.controllers |
Controllers |
steam.input |
Controllers |
steam.dlc |
Apps |
steam.apps |
DLC |
steam.items |
Inventory |
steam.inventory |
Inventory |
steam.p2p |
P2P |
steam.networking |
P2P |
steam.recording |
Timeline |
steam.timeline |
Recording |
steam.async |
flat.SteamAsync |
every interface | Flat API |
Nine of those getters are named after the job rather than after the interface,
because the generated accessor already holds the obvious name: social and
overlay because steam.friends is ISteamFriends, auth because
steam.user is ISteamUser, system because steam.utils is ISteamUtils,
capture because steam.screenshots is ISteamScreenshots, controllers
because steam.input is ISteamInput, dlc because steam.apps is
ISteamApps, items because steam.inventory is ISteamInventory, and
recording because steam.timeline is ISteamTimeline. The classes behind
steam.dlc, steam.items, and steam.recording are still called Apps,
Inventory, and Timeline; only the getters are renamed.
steam.async is the generated layer, not a curated one: it returns every flat
call that yields a SteamAPICall_t as a promise of the decoded result struct,
grouped by interface, and a non-OK EResult inside that struct is not an error
there.
const r = await steam.async.userStats.FindLeaderboard('Quickest Win');
console.log(r.m_bLeaderboardFound, r.m_hSteamLeaderboard);steamId(): bigint;The local user's 64-bit Steam id, straight from ISteamUser.GetSteamID().
accountId(): number;The lower 32 bits of the Steam id, as a number
(Number(this.steamId() & 0xffffffffn)). This is the value the UGC query
functions want, for example steam.workshop.getUserItems(1, steam.accountId()).
on<T>(callbackName: string, listener: (data: T) => void): () => void;Subscribes to a plain Steam callback by its struct name, for example
'ItemInstalled_t' or 'DownloadItemResult_t'. The name is looked up in the
generated callback table, the payload is decoded with the layout for this
platform (the win64 table on Windows, posix elsewhere), and the decoded
object is handed to the listener. Fields keep their C names.
Returns an unsubscribe function. T is not checked against the layout, so
declare it from the struct's real fields.
const stop = steam.on<{ m_unAppID: number; m_nPublishedFileId: bigint }>(
'ItemInstalled_t',
(d) => console.log(d.m_unAppID, d.m_nPublishedFileId),
);
stop();Throws steamwand: unknown callback struct '<name>' for a name that is not
in the table. Call results (the reply to an async call, such as
CreateItemResult_t) do not arrive here; they resolve the promise returned by
the call instead.
once<K>(callbackName: K, match?: (data) => boolean): Promise<SteamCallbackMap[K]>;Awaits the first plain callback of callbackName that match accepts. Same
lookup and same per-platform decode as on, but it settles a promise
instead of calling a listener, and it unsubscribes itself once it does.
match defaults to accepting the first callback.
This is the awaitable form for the flat calls that answer through a broadcast
callback rather than a call result, which is a different mechanism: a call
result belongs to one SteamAPICall_t handle, a broadcast callback belongs to
nobody, so it has to be matched by hand. GetAuthSessionTicket is the
canonical case, and match is how you tell your ticket's confirmation from
another one that happens to be in flight.
const ticket = Buffer.alloc(1024);
const size = Buffer.alloc(4);
const handle = steam.user.GetAuthSessionTicket(ticket, ticket.length, size, null);
const r = await steam.once('GetAuthSessionTicketResponse_t', (e) => e.m_hAuthTicket === handle);
console.log(r.m_eResult);The pump keeps the process alive while the promise is pending, so a script that does nothing but await one of these will not exit early.
Throws steamwand: unknown callback struct '<name>' for an unknown name.
The promise rejects with
steamwand: dispatch stopped while waiting for a callback when
close runs while it is still waiting.
auth.getSessionTicket, auth.getWebApiTicket and system.showGamepadTextInput are built on this.
close(): void;Stops the pump and calls SteamAPI_Shutdown. Idempotent: the second call
returns immediately. Pending async calls reject with
steamwand: dispatch stopped while call was in flight, and pending
once promises with
steamwand: dispatch stopped while waiting for a callback.
It also releases the one-session-per-process lock, so init may run
again afterwards. Do not use this Steam, its interfaces, or its curated layers
after close; take fresh ones from the new session.
-
64-bit values are
bigint. Steam ids, published file ids, UGC query handles, update handles, and API call handles. Write literals with thensuffix:steam.workshop.getItem(3141592653n). Parameters that accept an id are typedbigint | numberso a small literal still works, but returns are alwaysbigint. -
Out parameters are
Buffers you allocate. The generated signature isBuffer | null, and the size argument is separate, so passbuf.lengthyourself. Read the result back with the normal Buffer methods.const name = Buffer.alloc(256); if (steam.apps.GetCurrentBetaName(name, name.length)) { console.log(name.toString('utf8', 0, name.indexOf(0))); }
-
const char *returns are plain strings. koffi copies them, so there is nothing to free. -
String array parameters use
stringArray. Anywhere the flat API takes aSteamParamStringArray_t *, passstringArray(['gameplay', 'ui']). -
Enums are plain const objects, not TypeScript enums:
flat.EResult.k_EResultOK,flat.EWorkshopFileType.k_EWorkshopFileTypeCommunity.
class SteamInitError extends Error {
readonly initResult: number; // ESteamAPIInitResult
}Thrown only by init. message is Valve's diagnostic text from the
SteamAPI_InitFlat error buffer, so it says things like which app id was
expected or that no Steam client is running.
class SteamResultError extends Error {
readonly operation: string; // e.g. 'CreateItem'
readonly result: number; // EResult
}Thrown by every curated layer when a call completes with a non-OK EResult.
The message is ${operation} failed: ${eResultName(result)}, for example
SubmitItemUpdate failed: k_EResultAccessDenied or
FileWriteAsync failed: k_EResultLimitExceeded. The generated layer never
throws it: steam.async hands the struct back with whatever EResult is in it.
class SteamApiCallError extends Error {
readonly callbackId: number;
}Thrown when the call itself never produced a usable result, which is different
from a call that completed with a bad EResult. Four messages:
| Message | Cause |
|---|---|
Steam returned an invalid API call handle |
The flat function returned handle 0. callbackId is 0. |
expected callback id <n> for this call, Steam completed it with id <m> |
The caller passed an expected callback id and the completion carried a different struct. Since 0.3.0, see How It Works. |
SteamAPI_ManualDispatch_GetAPICallResult failed |
Steam refused to hand over the completed result. |
Steam reported an IO failure for this API call |
Steam set the bFailed flag on the result. |
function eResultName(result: number): string;The EResult constant name for a number, for example 1 gives
'k_EResultOK'. Unknown values come back as `EResult(${result})`.
Not every failure has a class. These are thrown as Error, all prefixed with
steamwand:.
| Message | Thrown by |
|---|---|
a Steam session is already open (one init per process); call close() on it first |
init, while another session is open. |
unsupported platform <name> |
The loader, on a process.platform that is not win32, linux, or darwin. |
<accessor> returned null (is Steam initialized?) |
An interface constructor. |
unknown callback struct '<name>' |
steam.on and steam.once. |
dispatch stopped while call was in flight |
close(), for each pending call. |
dispatch stopped while waiting for a callback |
close(), for each pending steam.once. |
buffer too small for struct (<n> < <size>) |
decodeStruct, on a payload shorter than the layout. |
no generated layout for struct <name> |
flat.layoutOf, for a struct with no layout table (unions are excluded). |
Everything below is exported from the package root.
| Export | Kind | Purpose |
|---|---|---|
init |
function | Initialize Steam and start the pump. Returns Steam. |
Steam |
class | The handle returned by init: interfaces, callbacks, shutdown. |
isSteamRunning |
function | Whether a Steam client is running. Safe before init. |
restartAppIfNecessary |
function | Relaunch through Steam when started from the executable. Call before init. |
InitOptions |
type | Options accepted by init. |
SteamNative |
class | The loaded steam_api library, its core exports, and the symbol cache. |
SteamDispatch |
class | The manual dispatch pump: runFrame, callResult, on, start, stop. |
SteamApiCallError |
class | An async call that produced no usable result. |
SteamInitError |
class |
SteamAPI_InitFlat failed. |
SteamResultError |
class | A call completed with a non-OK EResult. |
eResultName |
function |
EResult number to constant name. |
decodeStruct |
function | Decode raw callback bytes with a StructLayout. |
StructLayout |
type |
{ size, fields } for one struct on one platform. |
FieldLayout |
type |
{ name, offset, type } for one field. |
FieldType |
type | The field type union used by FieldLayout. |
stringArray |
function | Build a SteamParamStringArray_t argument from a string[]. |
out |
object | Typed out-parameter buffers for the flat API: out.bool(), out.uint64(), and friends. |
OutParam |
type | What one out.*() call returns: a buffer plus a decoded value. |
Workshop |
class | The curated workshop layer. See Workshop. |
QueryOptions |
type | Language and description options for workshop queries. |
UpdateProgress |
type | Status and byte counts reported during an upload. |
WorkshopItem |
type | One workshop item, decoded and named. |
AdditionalPreview |
type | One extra preview on an item: url, file name, and EItemPreviewType. |
WorkshopItemUpdate |
type | The fields one submitUpdate writes. |
WorkshopStatistic |
type | Names of the item statistics that queries return. |
UserItemsPage |
type | One page of a user's items plus totalResults. |
Stats |
class | The curated achievements and stats layer. See Stats. |
AchievementState |
type |
achieved plus the unlock time, or null while locked. |
AchievementDisplay |
type | Localized name, description, and the hidden flag. |
Cloud |
class | The curated Steam Cloud layer. See Cloud. |
CloudFile |
type | One entry from listFiles: name and size. |
CloudFileInfo |
type | Size and modification time of one cloud file. |
CloudQuota |
type | Total and still free cloud bytes for this app. |
Leaderboards |
class | The curated leaderboards layer. See Leaderboards. |
LeaderboardInfo |
type | Handle, name, entry count, sort method, display type. |
ScoreUploadResult |
type | Whether the score changed, and the ranks around it. |
LeaderboardEntry |
type | One downloaded row: user, rank, score, details, UGC handle. |
DownloadOptions |
type | Which entries to download and how many details to read. |
Lobbies |
class | The curated lobbies layer. See Lobbies. |
LobbySearchOptions |
type | Filters for one lobby search. |
LobbyChatMessage |
type | Sender and text of one lobby chat message. |
Social |
class | The curated friends, presence, and avatar layer. See Social. |
Friend |
type | One friend list entry: id, name, state, relationship. |
Avatar |
type | Decoded avatar: size plus RGBA pixels. |
AvatarSize |
type |
'small' | 'medium' | 'large'. |
PersonaStateChange |
type | Which user changed, and which parts. |
RichPresenceJoinRequest |
type | A friend's "Join game" click and their connect string. |
LobbyJoinRequest |
type | An accepted lobby invite: lobby id and inviter. |
Overlay |
class | The curated Steam overlay layer. See Overlay. |
OverlayDialog |
type | Top level overlay dialog names, open to any string. |
OverlayUserDialog |
type | Per-user overlay dialog names, open to any string. |
OverlayActivation |
type | Whether the overlay just opened or closed. |
Auth |
class | The curated auth ticket layer. See Auth. |
AuthTicket |
type | Ticket handle, raw bytes, and their hex form. |
ValidateTicketResult |
type | One session validation answer, with the licence owner. |
System |
class | The curated machine and client facts layer. See System. |
SteamImage |
type | Decoded Steam image: size plus RGBA pixels. |
GamepadTextInputOptions |
type | Prompt, mode, and starting text for the gamepad keyboard. |
Capture |
class | The curated screenshot layer. See Capture. |
ScreenshotReady |
type | Handle and EResult of one finished screenshot. |
Controllers |
class | The curated Steam Input layer. See Controllers. |
DigitalAction |
type | Pressed state of one digital action, plus whether it is bound. |
AnalogAction |
type | Both axes of one analog action, its source mode, and whether it is bound. |
Apps |
class | The curated DLC layer, reached as steam.dlc. See DLC. |
DlcInfo |
type | One DLC: app id, availability, display name. |
flat |
namespace | The whole generated layer: 25 interface classes, enums, consts, struct and callback layouts. See Flat-API. |
SteamNative and SteamDispatch are exported because steam.native and
steam.dispatch have those types, and because an async flat call needs the
pump directly:
const call = steam.ugc.CreateItem(steam.appId, flat.EWorkshopFileType.k_EWorkshopFileTypeCommunity);
const result = await steam.dispatch.callResultStruct<flat.CreateItemResult_t>(
call,
flat.layoutOf('CreateItemResult_t'), // picks the win64 or posix table for you
flat.callbackIdByName.CreateItemResult_t, // optional: rejects a mismatched completion
);
console.log(result.m_eResult, result.m_nPublishedFileId);How-It-Works explains the pump, the loader, and the offset tables. Recipes has worked examples.