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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public class MyScript : MonoBehaviour

## Installation

> You can also install Dissonity via [OpenUPM](https://openupm.com/packages/com.furnyr.dissonity/) or with each `.unitypackage` file found in the [Releases](https://github.com/Furnyr/Dissonity/releases) tab.

1. Create a new Unity project (Unity 2021.3 or later, Unity 6 recommended)
2. Open the package manager and install the package from this git URL: `https://github.com/Furnyr/Dissonity.git?path=/unity#v2`
3. Use the pop-up dialog to select a configuration file
Expand All @@ -71,7 +73,7 @@ Dissonity is now installed! But you still need to configure a few things:
## Configuration

1. Open the configuration file in Assets/Dissonity/DissonityConfiguration.cs
2. Set your app id in `<SdkConfiguration>.ClientId` (find it [here](https://discord.com/developers/applications))
2. Set your app id in `<SdkConfiguration>.ClientId` (find it or create a Discord app [here](https://discord.com/developers/applications))

Up and running! If you want to test your activity within Unity:

Expand All @@ -89,6 +91,9 @@ Dissonity helps in the process to make the game, but you will still need to host

If you're not sure how to continue, read the documentation.

> [!TIP]
> If you are also using a framework that provides Discord functionality, you should read [Third-party support](http://dissonity.dev/guides/v2/third-party-support).


## Documentation

Expand Down
2 changes: 1 addition & 1 deletion hirpc-interface/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dissonity/hirpc-interface",
"version": "0.4.0",
"version": "0.5.0",
"description": "A hiRPC implementation for Unity WebGL",
"scripts": {
"build": "pnpm tsc && node scripts/build.js",
Expand Down
41 changes: 21 additions & 20 deletions hirpc-interface/src/app_loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,9 @@ let baseUrl = `${window.location.protocol}//${window.location.host}${getPath()}`
if (!baseUrl.endsWith("/")) baseUrl += "/";

let outsideDiscord = false;
let proxyPrefixAdded = false;
let needsProxyPrefix = false;

let loaderPath = "Build/{{{ LOADER_FILENAME }}}";
const versionCheckPath = baseUrl + ".proxy/version.json";

const proxyBridgeImport = "dso_proxy_bridge/";
const normalBridgeImport = "dso_bridge/";
Expand All @@ -45,23 +43,27 @@ let initialHeight = window.innerHeight;
// Set up paths before anything
async function initialize() {

async function fileExists(url: string) {
const response = await fetch(url, { method: "HEAD" });
return response.ok;
}

async function updatePaths() {

let { pathname } = window.location;

// Handle URL override
const pathSegments = pathname.split("/"); // "/.proxy/staging" -> ["", ".proxy", "staging"]
pathSegments.shift();

proxyPrefixAdded = pathSegments[0] == ".proxy";
const prefixData = sessionStorage.getItem("dso_needs_prefix") as SessionStorage["dso_needs_prefix"];
needsProxyPrefix = !proxyPrefixAdded && prefixData != "false" && (prefixData == "true" || await fileExists(versionCheckPath));
outsideDiscord = !proxyPrefixAdded && !needsProxyPrefix;
//? Not outside Discord
if (window.location.hostname.endsWith(".discordsays.com")) {
sessionStorage.setItem("dso_outside_discord", "false" as NonNullable<SessionStorage["dso_outside_discord"]>);
}

else {
outsideDiscord = true;
sessionStorage.setItem("dso_outside_discord", "true" as NonNullable<SessionStorage["dso_outside_discord"]>);
}

//? Doesn't need prefix
if (outsideDiscord || window.location.pathname.startsWith("/.proxy")) {
sessionStorage.setItem("dso_needs_prefix", "false" as NonNullable<SessionStorage["dso_needs_prefix"]>);
}

else {
needsProxyPrefix = true;
sessionStorage.setItem("dso_needs_prefix", "true" as NonNullable<SessionStorage["dso_needs_prefix"]>);
}

// Add .proxy
if (needsProxyPrefix) {
Expand All @@ -87,10 +89,9 @@ async function initialize() {
async function handleHiRpc() {

//? Module already created

// Nested
const isNested = window.parent != window.parent.parent;
if (isNested || typeof window.parent?.dso_hirpc == "object") {
if (isNested && typeof window.parent?.dso_hirpc == "object") {

//\ Add shallow references to this window to use later
Object.defineProperty(window, "dso_hirpc", {
Expand Down Expand Up @@ -138,7 +139,7 @@ async function handleHiRpc() {
// window.dso_hirpc is defined after this line
const hiRpc = new window.Dissonity.HiRpc.default() as HiRpcModule;

await initialize(hiRpc, false);
await initialize(hiRpc, false || hiRpc.getBuildVariables().LAZY_HIRPC_LOAD);

resolve(hiRpc);
}
Expand Down
29 changes: 23 additions & 6 deletions hirpc-interface/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,27 @@ mergeInto(LibraryManager.library, {

const hiRpc = window.dso_hirpc as HiRpcModule;

hiRpc.openDownwardFlow((stringifiedData: string) => {

SendMessage("_DissonityBridge", "_HiRpcInput", stringifiedData);
});
// Load module now if LAZY_HIRPC_LOAD is set to true.
// This loads the module with zero hash accesses. Hash access is already locked at this point either way.
if (hiRpc.getBuildVariables().LAZY_HIRPC_LOAD) {
hiRpc.load(0)
.then(openFlow)
.catch(err => {

// So if something weird happens we can see what's going on
console.log(err);
});
}

else {
openFlow();
}

function openFlow() {
hiRpc.openDownwardFlow((stringifiedData: string) => {
SendMessage("_DissonityBridge", "_HiRpcInput", stringifiedData);
});
}
},

//@unity-bridge
Expand Down Expand Up @@ -112,12 +129,12 @@ mergeInto(LibraryManager.library, {
//@unity-api
DsoSendToRpc: function (stringifiedMessage: string): void {

const { data, app_hash } = JSON.parse(UTF8ToString(stringifiedMessage));
const { data } = JSON.parse(UTF8ToString(stringifiedMessage));

const hiRpc = window.dso_hirpc as HiRpcModule;

// The array is formed again at the hiRPC layer
hiRpc.sendToRpc(app_hash, data[0], data[1]);
hiRpc.sendToRpc(data[0], data[1]);
},

//@unity-api
Expand Down
6 changes: 6 additions & 0 deletions hirpc-kit/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes to this package will be documented in this file.

## [0.1.5] - 2025-06-10

### Added

- Support for LAZY_HIRPC_LOAD and hiRPC v0.6.0

## [0.1.4] - 2025-05-30

### Added
Expand Down
102 changes: 97 additions & 5 deletions hirpc-kit/dist/index.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ interface PatchUrlMappingsConfig {
*
* Each variable is separated by § (alt 21 win) (\u00A7)
*/
declare class BuildVariables$1 {
declare class BuildVariables$2 {
#private;
DISABLE_INFO_LOGS: boolean;
LAZY_HIRPC_LOAD: boolean;
CLIENT_ID: string;
DISABLE_CONSOLE_LOG_OVERRIDE: boolean;
MAPPINGS: Mapping[];
Expand All @@ -49,7 +50,7 @@ type RpcInputPayload = {
nonce?: string;
args?: unknown;
};
type BuildVariables = InstanceType<typeof BuildVariables$1>;
type BuildVariables$1 = InstanceType<typeof BuildVariables$2>;

/**
* Main hiRPC class. After instantiation, the instance will be located in window.dso_hirpc.
Expand All @@ -58,7 +59,7 @@ type BuildVariables = InstanceType<typeof BuildVariables$1>;
* - dso_bridge/
* - dso_proxy_bridge/
*/
declare class HiRpc {
declare class HiRpcV0_5 {
#private;
constructor();
/**
Expand Down Expand Up @@ -128,8 +129,99 @@ declare class HiRpc {
closeDownwardFlow(hash: string): void;
}

declare class _BuildVariables {
#private;
DISABLE_INFO_LOGS: boolean;
CLIENT_ID: string;
DISABLE_CONSOLE_LOG_OVERRIDE: boolean;
MAPPINGS: Mapping[];
PATCH_URL_MAPPINGS_CONFIG: PatchUrlMappingsConfig;
OAUTH_SCOPES: string[];
TOKEN_REQUEST_PATH: string;
SERVER_REQUEST: string;
constructor();
}
type BuildVariables = InstanceType<typeof _BuildVariables>;

/**
* Main hiRPC class. After instantiation, the instance will be located in window.dso_hirpc.
*
* Imports that must be defined:
* - dso_bridge/
* - dso_proxy_bridge/
*/
declare class HiRpc {
#private;
constructor();
/**
* Load the hiRPC module once per runtime.
*
* Depends on the external RPC state tracked via `sessionStorage`.
*
* ```js
* // Trigger a parallel load:
* sessionStorage.setItem("dso_connected", true);
*
* // Trigger a soft load:
* sessionStorage.setItem("dso_connected", true);
* sessionStorage.setItem("dso_authenticated", true);
* ```
*/
load(maxAccessCount?: number): Promise<void>;
getBuildVariables(): BuildVariables$1;
patchUrlMappings(hash: string, mappings: Mapping[], config?: PatchUrlMappingsConfig): void;
formatPrice(hash: string, price: {
amount: number;
currency: string;
}, locale?: string): string | undefined;
getQueryObject(): Record<string, string>;
getNonce(): string;
getVersions(): {
embedded_app_sdk: string;
hirpc: string;
};
/**
* Request a hash to access restricted functionality.
*
* Call this method after hiRPC `load` and before loading the game build.
*/
requestHash(): Promise<string | null>;
/**
* Send data to Discord through RPC.
*/
sendToRpc(opcode: Opcode | undefined, payload: RpcInputPayload): Promise<void>;
/**
* **Only used inside the game build.**
*
* Send data to the JavaScript layer.
*/
sendToJs(appHash: string, channel: string, payload: unknown): void;
/**
* Send data to the game build through a hiRPC channel.
*/
sendToApp(hash: string, channel: string, payload: unknown): Promise<void>;
addAppListener(hash: string, channel: string, listener: (data: unknown) => void): void;
removeAppListener(hash: string, channel: string, listener: (data: unknown) => void): void;
/**
* Lock hash access before opening the downward flow. After this call, `requestHash` will return null.
*/
lockHashAccess(): void;
/**
* Define the function that will be used to send data to the game build.
*
* The first message sent will be the `MultiEvent` after authentication. Depending on the RPC state, it could contain partial data.
*
* Hash access will be locked after this call, if it wasn't locked already.
*/
openDownwardFlow(appSender: (data: string) => void): Promise<void>;
/**
* Free the app sender. Hash access will continue to be locked.
*/
closeDownwardFlow(hash: string): void;
}

type StartWith<V extends string> = `${V}${string}`;
type HiRpcShape<V extends string> = V extends StartWith<"0.5"> ? HiRpc : UnknownHiRpc;
type HiRpcShape<V extends string> = V extends StartWith<"0.5"> ? HiRpcV0_5 : V extends StartWith<"0.6"> ? HiRpc : UnknownHiRpc;

declare enum RpcOpcode {
Handshake = 0,
Expand All @@ -148,4 +240,4 @@ declare function setupHiRpc<V extends string>(_hiRpcVersion: V): Promise<HiRpcSh
*/
declare function loadIframe(src: string, id: string): void;

export { RpcOpcode, loadIframe, setupHiRpc };
export { type HiRpcShape, RpcOpcode, loadIframe, setupHiRpc };
Loading