AppHandle
public sealed class AppHandleNamespace Sideload.Api
Handle to a registered app: the two-way channel between your C# and the app’s JavaScript. Kept deliberately thin; every method stays a no-op when Sideload is absent.
Properties
Section titled “Properties”public string Id { get; }The id this app was registered under.
IsOnScreen
Section titled “IsOnScreen”public bool IsOnScreen { get; }Whether this app is the one the phone is showing right now. Ask before interrupting: an event the player is already watching happen does not deserve a notification, and the same event with the phone in their pocket does. False when Sideload is absent or the app is not on a phone yet.
if (!app.IsOnScreen) app.Notify(sender, text);IsOpen
Section titled “IsOpen”public bool IsOpen { get; }Added in 1.1.0
Whether this app is the one the phone has open - true even with the phone in the player’s pocket. For “can they actually see it”, use IsOnScreen.
CanOpenProgrammatically
Section titled “CanOpenProgrammatically”public static bool CanOpenProgrammatically { get; }Added in 1.1.0
Whether the installed Sideload understands NoIcon and Open. False against an older host, where both are silent no-ops.
A mod that provides its own entry point must check this before registering anything: on an older host it would hide nothing, open nothing, and leave the player with apps they cannot reach. Refuse to set up and say which version is needed - that is a fixable message, an unreachable app is not.
if (!AppHandle.CanOpenProgrammatically) { Log.Error("needs Sideload 1.1.0 or newer"); return; }CanClaimKeys
Section titled “CanClaimKeys”public static bool CanClaimKeys { get; }Added in 1.10.0
Whether the installed Sideload can hand an app a key. False against anything older than 1.10.0, where OnKey is a silent no-op.
Worth checking only when the key is the ONLY way into your app - pair it with NoIcon and a host that ignores both leaves the player with an app they can neither see nor reach.
Methods
Section titled “Methods”OnCall
Section titled “OnCall”public AppHandle OnCall(string name, Func<string, string> handler)Answer s1.call("<name>", arg) from this app’s page. The handler runs on the Unity main thread in the same frame as the call, so it may touch game state directly; whatever it returns is the call’s value. Strings cross the boundary - send JSON for anything structured.
app.OnCall("chat.threads", _ => Json.Of(Chat.Threads));app.OnCall("chat.send", text => { Chat.Send(text); return "ok"; });public void Emit(string name, string payload = "")Push an event at this app’s page, where s1.on("<name>", fn) is waiting for it. Use it when the game changes something the page did not ask for - a message arriving, a timer expiring.
AllowHost
Section titled “AllowHost”public AppHandle AllowHost(string host)Let this app’s page reach one host with fetch. Without at least one of these the page reaches nothing: the allowlist starts empty and only the app’s own mod can add to it, so a web bundle edited in the Mods folder can never talk to somewhere you did not name here.
Give a bare host name. *.example.com covers any single label under it, but not example.com itself. Ports are ignored, and the scheme must be https unless the host is 127.0.0.1 or localhost.
Apps.Register("mystash", "MyMod.Assets.mystash") .AllowHost("api.example.com") .AllowHost("*.cdn.example.com");Orientation
Section titled “Orientation”public AppHandle Orientation(params string[] supported)Which ways round the phone may hold this app, in preference order. The FIRST one is what the app opens in; naming a second is what lets the player turn it, with the rotate keys the game already binds. Say nothing and the app is landscape only, which is the only safe reading of silence - an app that never styled portrait must not be turned into it.
Both are worth styling. Sideload evaluates @media (orientation: portrait|landscape) against the real viewport shape, so one stylesheet covers both without any script. The player’s choice is remembered per app; you do not have to store it.
.Orientation("landscape") // landscape only, the phone never turns.Orientation("portrait") // portrait only.Orientation("landscape", "portrait") // both, opens landscape.Orientation("portrait", "landscape") // both, opens portraitpublic AppHandle Badge(int count)The unread count on this app’s home-screen icon - the same red badge the vanilla apps use. Zero clears it. Counts above 99 read as “99+”.
Set it whenever your own count changes, not on a timer: the value is remembered across a phone rebuild, so setting it once is enough and setting it again is cheap.
app.Badge(unreadMessages);Notify
Section titled “Notify”public AppHandle Notify( string title, string subtitle = "", float seconds = 0f)Added in 1.4.0
Raise one of the game’s own phone notifications - the slide-in the vanilla apps use, carrying this app’s icon. Nothing happens if the app is not on a phone yet.
This interrupts whatever the player is doing, so spend it on what they would want to be interrupted for. A count that can wait belongs in Badge.
app.Notify("Jessi Waters", "on my way");| Parameter | |
|---|---|
seconds | How long it stays up. Leave it at zero for Sideload’s own timing, which suits a headline plus a sentence. Raise it for something the player has to act on, lower it for a passing remark. Clamped to between 2 and 30 seconds - the slide-in cannot be dismissed, so an app does not get to hold the corner of the screen. Ignored, without failing, on a Sideload too old to have it. |
public AppHandle Image(string name, byte[] png)Hand a picture your mod produced at runtime to the page, which draws it with <img src="s1://<name>">. Null or empty bytes remove it, which is how you say “there is no picture for this one” and let the page fall back to whatever it draws without one.
PNG bytes rather than a texture, because this file references no Unity type and is not going to start. Supplying the same name again replaces the picture.
app.Image("avatar/" + steamId, pngBytes);public AppHandle Icon(bool visible)Added in 1.6.0
Show or hide this app’s home-screen icon while the game is running.
For an app whose way in is a key rather than a square, but only sometimes: hash puts an icon there exactly while the game’s console is switched on, because that is the only time it can run anything, and that setting is a live toggle. Safe to call with the same value repeatedly.
Unlike NoIcon this is NOT queued until Sideload binds. A caller polling a condition would otherwise pile up one queued call per check against a host that never arrives; an unbound host simply ignores it, which is the right answer for a decision that is re-stated anyway.
NoIcon
Section titled “NoIcon”public AppHandle NoIcon()Added in 1.1.0
Give this app no home-screen icon. For an app whose way in already exists somewhere else - a vanilla icon your mod has taken over, a world object, another app handing off.
With no icon, Open is the ONLY way in: call it from wherever your entry point is, or the app is unreachable. Check CanOpenProgrammatically first - against an older Sideload this is a no-op and the app would get an icon you did not plan for.
Apps.Register("reflash-messages", "Reflash.Assets.reflash-messages", "Messages").NoIcon();public AppHandle Open()Added in 1.1.0
Open this app as if the player had pressed its icon: whatever else is open closes first, and the phone turns to this app’s orientation. Does nothing while the app is not on a phone - before the home screen exists there is nothing to open.
if (playerPressedTheHijackedIcon) app.Open();public AppHandle Close()Added in 1.1.0
Close this app, returning the phone to its home screen. Does nothing if it is not the open one.
public bool Show()Added in 1.5.0
Take the phone out AND open this app - what a key that opens an app has to mean, because Open on its own opens it on a phone that is still in the player’s pocket.
The order matters and is why this exists rather than two calls at the call site: the page is built the first time it is opened, and a page built while its panel is hidden measures every line about ten times too short. Raising first means the very first frame is laid out against the real viewport.
Returns false when the game refused the phone - paused, asleep, dead, arrested - in which case nothing was opened either.
if (consoleKeyPressed) app.Show();public AppHandle OnKey(string keys, Func<string, bool> handler)Added in 1.10.0
Ask for a key that reaches this app with the phone still in the player’s pocket - the way IN, as opposed to data-keys, which only ever reaches a focused field in an already-open page.
Spell keys the way the DOM does, several separated by spaces or commas: Enter, F8, Ctrl+Shift+K. Modifiers match exactly, so Enter does not fire for Shift+Enter. Escape is refused - it is the game’s own exit action.
Your handler returns whether it TOOK the press. Return false and the key goes to the next app that wants it, which is how you decline a key you cannot use right now - a chat with no lobby behind it should not open, and should not swallow the key on its way past. The argument is the key that fired, so one handler can serve several.
When two apps want the same key, the one that notified most recently gets it. Two messengers installed together then behave the way a phone should: the key answers the conversation that is actually waiting. An app that has never notified still wins a key nobody else claimed - it simply sorts last.
Sideload only reads the key where the game would let the player take their phone out anyway: never while they are typing, paused, asleep, arrested, or standing at a station, a shop or the developer console. While one of your apps is on screen it owns every key it claimed, and no other app is offered them.
app.OnKey("Enter", _ => { if (!Online) return false; return app.Show(); });| Parameter | |
|---|---|
keys | One or more key declarations, separated by whitespace or commas. |
handler | Runs on the Unity main thread. Null gives back every key this app holds. |
public AppHandle Hide()Added in 1.5.0
Close this app and put the phone away. The mirror of Show.