npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

com.robotsquid.squidhub

v0.5.13

Published

Cloud saves, analytics and the coin economy for Unity WebGL games published on SquidHub. Installs the C# wrapper, the .jslib half and the page bridge, and rewrites the exported index.html so it survives the platform's CSP.

Readme

SquidHub for Unity

Cloud saves, analytics and the coin economy for Unity WebGL games on SquidHub.

Install it, press Build. There is no bundling step, no WebGL template to pick and nothing to copy into Assets/.

Install

Add the registry to Packages/manifest.json:

{
  "scopedRegistries": [
    {
      "name": "RobotSquid",
      "url": "https://registry.npmjs.org",
      "scopes": ["com.robotsquid"]
    }
  ]
}

Then Window > Package Manager > My Registries > SquidHub > Install.

Unity 2021.3 or newer.

Use it

using System.Collections.Generic;
using SquidHub;

async void Start()
{
    await Sdk.WaitForReadyAsync();          // throws if the portal refuses

    var json = await Sdk.SavesGetAsync();   // JSON; parse with your own library
    // `data` inside it is base64 — Sdk.FromBase64(...) gives you the bytes.

    Sdk.AnalyticsTrack("level_done", new Dictionary<string, object> { ["level"] = 3 });

    await Sdk.SavesPutAsync(bytes, ifVersion: version);
}

Responses come back as JSON strings deliberately: your project already carries a JSON library, and a second one shipped in here would be a worse one.

The full API — capabilities, save conflicts, error codes, the manifest — is documented in @squidhub/game-sdk. This package is a wrapper around it and shares its behaviour exactly.

⚠️ Handle CONFLICT on SavesPutAsync. It is not an edge case — it is what happens whenever a player has your game open in two places, which is the situation cloud saves exist for. The server's copy rides along on the error so you can merge without a second round trip. The platform stores opaque bytes and cannot merge for you.

What happens when you build

Nothing to configure. On a WebGL build this package:

  1. Refuses the build if it could not possibly run — pthreads enabled, the .jslib not linked for WebGL, the bridge missing. Each of those otherwise fails silently in a browser rather than loudly in the Editor.
  2. Writes squidhub-bridge.js into the output and injects it ahead of the Unity loader.
  3. Hoists every inline <script> out of index.html into an external file.
  4. Makes the game fill the frame and hides Unity's own logo bar, build title and fullscreen button.

Step 3 is the one that matters. squidhub-gamehost serves script-src 'self' 'wasm-unsafe-eval' blob: with no 'unsafe-inline', and every stock Unity template inlines its bootstrap. Left alone, the page loads, all the markup is there, and nothing runs — the only signal is a CSP violation in the console. This is the single most common first-integration failure, and you should never see it.

⚠️ Step 4 is why your game is not a 960×600 box in the corner. The portal already letterboxes — it measures its box, clamps it to the aspect window you declared and hands the frame an element of exactly that size. Unity's stock template then renders a fixed 960×600 centred inside that, on a white page, under a Unity logo and a second fullscreen button that fights the portal's own. The footer is hidden rather than removed, because the stock bootstrap queries the fullscreen button and throws on null — which surfaces as an alert() where the game should be. Your loading bar is left alone: the portal draws none, so it is the only progress a player sees.

Then push the folder:

node src/cli.mjs push --game <slug> --dir <BuildFolder> --env=dev --verify

Declaring what your game is

SquidHub → Game Manifest… edits manifest.json beside Assets/ — the game's id and title, which capabilities it uses, its save schema version, the orientations and aspect window it wants, and whether it makes sound.

It is the same file squidhub-release build --manifest manifest.json reads, so the Editor and the CLI cannot disagree about what your game declares. It belongs in your repository.

The build writes it into the output folder for you. That is the only thing that makes a declaration take effect — the server reads manifest.json out of the uploaded bytes and ignores anything a client posts alongside the upload, so a build folder without one declares nothing at all: save schema 1, no display constraints, and a rollback guard that cannot fire.

⚠️ Ticking a capability does not grant it. Capabilities are granted by the platform at review. Declaring commerce.spend says your game uses it; until it is granted the broker still answers DENIED, and your game has to handle that. New games have saves and analytics.

⚠️ Keys the dialog does not model are preserved. skus, achievements and rewardTable live in the same file and survive a save untouched.

Uploading from Unity

SquidHub → Upload Build… does the whole publish — no console, no command line, no repository checkout. SquidHub → Build and Upload… picks a build profile, builds it and uploads the result in one action.

Once: in the publisher console open your game → Upload keys → create one, then paste it into SquidHub → Upload Settings.

⚠️ The key never enters your project. It lives in EditorPrefs, per machine and per project path, so it cannot reach your repository. It can upload a build for the one game it was minted for, and on dev publish that build — it cannot read anything, touch another game, or publish on prod. If it leaks, revoke it in the console.

Publishing on upload

Publish the build is ticked by default in SquidHub → Build and Upload….

⚠️ It works on dev only. On prod the server refuses it and says so; the upload still succeeds and the build arrives registered and not live. Promoting a build for every player is an owner decision made by a person in the console, and an upload key is not a person — it sits in CI configs and Editor preferences. That asymmetry is deliberate: it is what keeps a leaked key from replacing the live build for everyone.

⚠️ It does not ask. Choosing the action is the decision, so there is no "upload N files?" confirmation to dismiss — the file count, size, destination and any warnings go to the Console instead, where they survive being read. A build that cannot upload still stops with a dialog naming the fix.

The upload is refused before a byte moves if the build is precompressed or has no index.html at its top level, and OS metadata is dropped rather than sent — which is also what makes a build pushed from Unity hash identically to the same bytes dragged into the console.

In the Editor

Sdk compiles on every platform and reports Status() == "failed" anywhere that is not a WebGL build — pressing Play does not throw.

⚠️ It never simulates a session. There is no offline player, no fake save and no pretend balance, because a game built against a player who cannot exist gets its first honest answer from production. Your game must start when a platform call fails; treat every verb as something that can fail and keep a local fallback.

Run SquidHub > Check WebGL Settings any time to audit the project without waiting for a build.

Player Settings

The package checks these for you, but for reference:

| Setting | Value | Why | |---|---|---| | Native C/C++ Multithreading | off | SquidHub's default tier is not cross-origin isolated, so SharedArrayBuffer does not exist. A pthreads build cannot start. Ask about the isolated tier if you genuinely need threads — it carries no ads | | Run In Background | on | ⚠️ Unity WebGL pauses when the canvas loses focus, and every control on SquidHub — mute, fullscreen, back — belongs to the portal, not the game. Left off, clicking any of them stops the game; fullscreen then keeps rendering at the pre-fullscreen resolution, so the game is blurry until the player clicks into it | | Compression Format | Disabled | ⚠️ Required, and the build is refused otherwise. SquidHub stores assets uncompressed and Cloudflare compresses them at the edge, per client. A precompressed build cannot be served correctly at all |

Things that will otherwise bite

⚠️ Your game only works when the portal launches it. The launch nonce arrives in the URL fragment (#s=…), so opening index.html directly, or reloading without the fragment, fails the handshake with UNTRUSTED_EMBEDDER. That is intended, not a bug to work around.

⚠️ No external resources, at all. The CSP is default-src 'self' blob: data:. A CDN font, a remote texture or a third-party analytics snippet is blocked at load and shows up as a console error, not as a missing style. Everything must ship inside the build.

⚠️ Fullscreen needs two things, and neither is the Unity fullscreen button. Tick Fullscreen button in SquidHub → Game Manifest…display.fullscreen defaults to false and the portal draws no control without it — and turn on Run In Background, or the game pauses the moment the player clicks that control and stays at the old resolution while the frame grows. The portal fullscreens its own container rather than asking your game to do it, so your build needs no fullscreen code at all; Unity's own button is hidden for you.

⚠️ Do not write your own rotate prompt or call screen.orientation.lock(). Declare the orientations you support in SquidHub → Game Manifest… and the portal draws the prompt outside your frame. Two prompts fight, and yours is the one the player sees first.

⚠️ The mute button is the portal's, but the muting happens in your frame. A cross-origin iframe has no muted property, so the SDK interposes a gain node on your AudioContext. It works without you doing anything — but only because the bridge is injected ahead of the loader, which is why step 2 above is not merely tidy. Tick Sound in SquidHub → Game Manifest… or no button appears.

⚠️ Editing manifest.json alone registers a new version. It used not to — the manifest is excluded from the build hash, so the build looked identical and the edit was discarded. A version is now identified by its bytes and its declaration, so a manifest-only change ships. The bytes are still deduped: the two versions share one copy in R2.

To change display on a build that is already live, the publisher console's override is still the faster path — it needs no upload at all.

⚠️ Status

Verified end to end on 2026-09-04. A real Unity WebGL build completed the portal handshake, received its session, read a save, and had commerce.purchase correctly refused as an ungranted capability.

⚠️ Upload your build UNCOMPRESSED — Player Settings → Publishing Settings → Compression Format → Disabled. Cloudflare compresses at the edge and sets the header; a precompressed build cannot be served correctly and fails in the loader with a message naming the wrong cause.

Support

Contact your SquidHub publisher representative.

MIT licensed.