Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: CI
on:
pull_request:
push:
branches: [master, main]
permissions:
contents: read
jobs:
offline:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.4.0
- run: bun install --frozen-lockfile
- run: npm run lint
- run: npm test
- run: npm run test:types
- run: npm run generate-wire-schema && npm run generate-response-types
- run: git diff --exit-code -- lib/wire-schema.json types/responses.d.ts
packed-consumers:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: npm run test:consumer
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
node_modules/*
test/*
node_modules/

# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## v2.6.0 - Request lifecycle and verification

- Added a reusable fake Steam transport, independent outgoing/incoming wire fixtures,
and a shared randomized inspect corpus. New tests are no longer hidden by the test-directory ignore rule.
- Centralized request cleanup and same-key serialization; added `cancelPendingRequests()`,
`dispose()`, and `RequestError` codes with mutation uncertainty. Failed sent keys remain
quarantined for the client lifetime and are never automatically retried.
- Fixed subscription-after-send races, wrong notification correlation, discarded additional
profiles, absent wear coerced to zero, duplicate/reordered inventory changes, unsigned
storage-unit IDs, and malformed nested protobuf/craft payload handling.
- Added schema-generated public response/event types and compile-time consumer examples.
Corrected Promise overload inference and the numeric `ItemInfo.accountid` declaration.
- Added an explicit [feature support matrix](FEATURE_SUPPORT.md) and a safe, opt-in
authenticated live runner with individually designated mutations and inventory assertions.
- Added packed consumer CI across Node 14/16/18/20/22/24 and steam-user 4.2.0/4.29.3/5.3.0.

Migration: existing calls remain compatible. Handle request rejections, reconcile uncertain
mutations before any manual retry, and dispose unused clients. Read FEATURE_SUPPORT.md
for quarantine semantics and callback differences. Live Steam/GC behavior remains unverified;
schema-correct sends and offline notifications do not establish server-side feature support.


This changelog tracks `node-cs2` releases. Dates through `2.2.4` use npm publish timestamps; `2.3.0` is the prepared package version in this repository.

Legacy git tags from the upstream `globaloffensive` package are intentionally omitted when they were not published as `node-cs2`.
Expand Down
131 changes: 131 additions & 0 deletions FEATURE_SUPPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Feature support and verification

Evidence date: **2026-10-02**. Protocol snapshot: SteamDatabase/GameTracking-CS2
`3fc98e763328f7d1627405b389d1b6b69c5b0e38` (2026-09-25), checked into `protobufs/`.

“Payload” means actual transport bytes agree with separately written wire fixtures.
“Decode” means independent incoming wire bytes reach the public event/result.
“Lifecycle” means an offline fake Steam transport exercises real dispatch and cleanup;
it does **not** establish server acceptance. “Partial” lists the tested subset.
No authenticated live evidence or sanitized account capture was available for this release.
The fixture corpus is synthetic and independently encoded; it is not described as a Steam replay.

| Feature / public API | Outgoing payload | Response decoding | Offline lifecycle | Live behavior / prerequisites |
|---|---|---|---|---|
| Legacy `inspectItem` | Verified | Verified | Concurrent IDs, same-ID queue, timeout, late/duplicate replies, cancel, disconnect, callback/Promise | Unverified; logged-in account, GC session, valid inspect link |
| Embedded `inspectItem` | Local; no send | Verified: masked/unmasked, uint64, float32, Unicode, repeated names, opaque bytes, unknown fields | Callback/Promise parity | Local operation; Steam not required |
| `requestPlayersProfile` | Verified | Verified, including multiple profiles | Success, callback/Promise timeout cleanup | Unverified; GC session, valid profile |
| `requestRecurringMissionSchedule` | Verified | Verified | Shared request lifecycle; response dispatch | Unverified; account/region mission availability |
| `getCasketContents` | Verified | Customization notification verified | Cached/empty behavior retained; shared request lifecycle | Unverified; owned storage unit and accessible contents |
| `loadVolatileItemContents` | Verified | Assumes `CasketContents` notification | Synchronous response, unrelated notification rejection, shared item queue | Unverified; owned volatile item. Notification association remains a hypothesis |
| `extractSticker`, `encapsulateSticker` | Verified against schema | Notification item/type decoding verified | Same-item serialization, wrong item/type, duplicate reply, cleanup | Unverified; eligible sticker/item, any required tool. Notification association not live-proven |
| `applyPatch`, `removePatch`, `applyKeychain` | Verified against schema | Notification item/type decoding verified | Same-item serialization, mixed operations, timeout uncertainty, callback/Promise parity | Unverified; eligible designated items/slots. Notification association not live-proven |
| `removeKeychain` | Verified | No response awaited | Fire-and-forget only | Unverified; attached charm and applicable removal prerequisites |
| `openCrate` | Verified, including explicit zero/false | Notification decoder verified; subject-ID association assumed | Shared item queue; existing callback behavior tested | Unverified; owned crate/key, account eligibility. A response that omits the subject ID will time out |
| `redeemFreeReward`, `claimVolatileItemReward` | Verified | Free-reward notification decoded | Shared global reward lane; no request ID; sequential replies | Unverified; eligible reward/balance; volatile-to-free-reward association assumed |
| `redeemMissionReward` | Verified | Customization decoder verified | Shared mission-reward lane | Unverified; eligible mission/cost/balance |
| Inventory create/update/destroy/multiple | Incoming only | Verified | Duplicate events, reordered versions, tombstones, truncated attributes | Unverified; live inventory cache traffic |
| `requestGame`, `requestLiveGames`, `requestRecentGames`, `requestLiveGameForUser` | Verified | Match-list event verified | Event-only; no request Promise | Unverified; valid sharecode/user, available matches |
| `nameItem`, `deleteItem`, `craft`, `addToCasket`, `removeFromCasket` | Verified | Craft parser rejects truncation; inventory decoder verified | Fire-and-forget; no operation success assertion | Unverified; owned eligible items/tools; destructive operations excluded from runner |
| `commendPlayer`, `setLeaderboardSafeName`, `acknowledgeRentalExpiration`, `acknowledgeXPShopTracks`, `ackPetEvent` | Verified | No operation response awaited | Fire-and-forget only | Unverified; relevant account, item, or match prerequisites |
| Account data, XP shop, mission, match, search-stat events | Incoming only | Verified with independent bytes | Event dispatch | Unverified; event-specific account activity |
| Premier season summary | Incoming only | Schema-derived type; not independently fixture-verified | Existing handler only | Unverified; eligible Premier season history |
| GC connect/reconnect | Existing hello encoding; not independently verified | Welcome/status parser exercised | Timer teardown, reconnect scheduling, disposal | Unverified; Steam and GC availability |

Evidence is in `test/payloads.test.mjs`, `test/lifecycle.test.mjs`,
`test/corpus.test.mjs`, `test/live-runner.test.mjs`, `test/types/usage.ts`, and
`scripts/verify-consumers.js`. CI installs packed artifacts in separate consumer
projects and exercises real steam-user 4.2.0, 4.29.3, and 5.3.0 constructors on
Node 14, 16, 18, 20, 22, and 24. Runtime support does not imply upstream security
maintenance of old Node versions. Test/lint tooling itself requires Node 24.

## Lifecycle and migration notes

Existing callback and Promise call signatures remain supported. Inspect callbacks
retain their historical success-only signature; use `inspectItemTimedOut` for their
timeout event, or use the Promise overload for structured failure handling. Profile
callbacks continue receiving either a profile or an Error. Mutation callbacks remain
error-first. Promise overload declarations now correctly infer Promise return types.

`cancelPendingRequests()` rejects active and queued requests and clears their timers
and listeners. `dispose()` also permanently removes this instance's Steam transport
listeners. Neither method retries a request. External event listeners are preserved.

Promise rejections and error-first callbacks use `NodeCS2.RequestError` with `code`
(`TIMEOUT`, `CANCELLED`, `DISCONNECTED`, `DISPOSED`, `NOT_CONNECTED`, `SEND_FAILED`,
`UNCERTAIN_PREVIOUS_RESULT`) and `uncertain`. A sent mutation that times out or loses
its connection may already have happened. Reconcile the inventory before deciding
what to do; **never automatically retry it**.

Requests with the same inspect/profile key or item-mutation key are serialized.
Rewards without a request identifier are serialized globally within their notification
class. Once a sent request fails or is cancelled, its key is quarantined for that
client's lifetime: subsequent queued/new work receives `UNCERTAIN_PREVIOUS_RESULT`.
A reconnect does not reset quarantine. After independently reconciling the result,
dispose the client and construct a new one to begin a new correlation lifetime.
Unsent failures do not quarantine a key. Different keys remain usable.

Serialization prevents simultaneous ambiguous requests and a synchronous duplicate
response from completing queued work. The protocol cannot distinguish an arbitrarily
late duplicate of a successful operation from the response to a later operation on
the same key. Do not interpret a notification alone as proof of a mutation; verify
inventory state. Inventory messages lacking versions can be deduplicated by content,
but cannot be reliably reordered. Version tombstones last until a fresh welcome cache.
Fire-and-forget APIs have no request lifecycle and remain caller-coordinated.

Decoded event types are generated from the checked-in schema in `types/responses.d.ts`.
64-bit integers are decimal strings, absent scalars/messages are null, and repeated
fields are arrays. `ItemInfo.accountid` correctly reflects the wire's uint32 number.
Absent paintwear now stays null instead of being coerced to zero. Malformed wire
payloads (including nested truncations) are rejected before protobuf decoding; attach
an `error` listener to observe incoming decoding failures as with other GC errors.

## Opt-in authenticated live runner

Nothing authenticates during `npm test`. To run the default read-only self-profile
case, provide credentials through your environment and explicitly enable the runner:

```sh
CS2_LIVE=1 STEAM_REFRESH_TOKEN=... npm run test:live
```

Alternatively set `STEAM_ACCOUNT_NAME`, `STEAM_PASSWORD`, and, if needed,
`STEAM_TWO_FACTOR_CODE`. Do not commit credentials. No refresh token is written to
disk. Select a single named case with `-- --case NAME`; there is no “all mutations”
option. `CS2_LIVE_CONFIG` points to a local JSON file, for example:

```json
{
"testItemIds": ["111", "222"],
"cases": {
"inspect-test-item": { "method": "inspect", "args": ["M1A111D3"] },
"apply-test-patch": {
"method": "applyPatch", "args": ["111", "222", 0],
"expectedInventory": [{ "id": "222", "present": false }]
}
}
}
```

Replace these illustrative IDs with sacrificial items in your own inventory. All
consumed/modified IDs must appear in `testItemIds`, exist before the operation, and
have an explicit postcondition which does not already hold. For a present-item
postcondition supply `properties` (exact values of normalized inventory properties).
Use a postcondition specific enough to demonstrate the intended result; patch tool
consumption alone may be weaker evidence than also asserting the target's attributes.
The runner requires both the method's correlated notification and that postcondition.
It invokes a mutation exactly once; failure to observe the postcondition is uncertain.

Supported read methods: `profile`, `inspect`, `missions`, `casket`. Individually
selectable mutations: `applyPatch`, `removePatch`, `extractSticker`,
`encapsulateSticker`, `applyKeychain`, `openCrate`. Other mutations remain excluded
until a meaningful automated state assertion is available. There are no automatic
retries, including after Steam/GC timeout, disconnect, or ambiguous notification.

The sanitized JSON report records UTC time, case name, method, protocol snapshot,
and outcome. Exit codes: 0 passed; 1 assertion failure; 2 unavailable account/config
prerequisite; 3 Steam/GC outage; 4 uncertain mutation. Missing acknowledgements can
also indicate a protocol mismatch: an “outage” result is not proof of its cause.
A successful send never counts as a pass. Retain reports alongside sanitized inventory
evidence before upgrading any matrix entry to live-verified; include account
prerequisites, verification date, exact snapshot, and the operation-specific assertion.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,15 @@ MIT License - see [LICENSE](LICENSE) file for details.
Use Node.js 24+ for the current lint and test tooling. The published library retains Node.js 14+ runtime support. The development dependency uses `steam-user` 5; consumers may continue using the supported `steam-user` 4 API.

To refresh protocols, run `npm run update-protos`. Each download uses one upstream commit; set `PROTO_REF=<commit>` to reproduce a specific snapshot.

## Verification and request lifecycle

See the [feature support matrix and migration notes](FEATURE_SUPPORT.md) for separate
payload, decoding, offline lifecycle, and live evidence. Version 2.6 adds
`cancelPendingRequests()`, `dispose()`, typed response events, and structured
`NodeCS2.RequestError` failures. Sent failures quarantine their correlation key; mutations
are never automatically retried. Existing callback and Promise signatures are retained.

Run `npm test`, `npm run test:types`, and `npm run test:consumer` for offline verification.
Authenticated checks are explicitly opt-in through `npm run test:live`; see the matrix
for credential setup and individual mutation selection. No live behavior is claimed here.
22 changes: 22 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Release Notes

## v2.6.0 - Request lifecycle and verification

- Added a reusable fake Steam transport, independent outgoing/incoming wire fixtures,
and a shared randomized inspect corpus. New tests are no longer hidden by the test-directory ignore rule.
- Centralized request cleanup and same-key serialization; added `cancelPendingRequests()`,
`dispose()`, and `RequestError` codes with mutation uncertainty. Failed sent keys remain
quarantined for the client lifetime and are never automatically retried.
- Fixed subscription-after-send races, wrong notification correlation, discarded additional
profiles, absent wear coerced to zero, duplicate/reordered inventory changes, unsigned
storage-unit IDs, and malformed nested protobuf/craft payload handling.
- Added schema-generated public response/event types and compile-time consumer examples.
Corrected Promise overload inference and the numeric `ItemInfo.accountid` declaration.
- Added an explicit [feature support matrix](FEATURE_SUPPORT.md) and a safe, opt-in
authenticated live runner with individually designated mutations and inventory assertions.
- Added packed consumer CI across Node 14/16/18/20/22/24 and steam-user 4.2.0/4.29.3/5.3.0.

Migration: existing calls remain compatible. Handle request rejections, reconcile uncertain
mutations before any manual retry, and dispose unused clients. Read FEATURE_SUPPORT.md
for quarantine semantics and callback differences. Live Steam/GC behavior remains unverified;
schema-correct sends and offline notifications do not establish server-side feature support.


## v2.5.0 - September CS2 Protocol Refresh

### Added
Expand Down
Loading
Loading