diff --git a/package-lock.json b/package-lock.json index f747006..a384283 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "is-plain-object": "^5.0.0", "lz-string": "^1.4.4", "nanoevents": "^6.0.2", + "unofficial-galaxy-sdk": "git+https://code.incremental.social/thepaperpilot/unofficial-galaxy-sdk.git#1.0.0", "vite": "^2.9.12", "vite-plugin-pwa": "^0.12.0", "vite-tsconfig-paths": "^3.5.0", @@ -6878,6 +6879,10 @@ "node": ">= 4.0.0" } }, + "node_modules/unofficial-galaxy-sdk": { + "version": "1.0", + "resolved": "git+https://code.incremental.social/thepaperpilot/unofficial-galaxy-sdk.git#431b1f16bcbe9d9e5c9fd08c73c14e0f0fe4ebfd" + }, "node_modules/upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", diff --git a/package.json b/package.json index 3c1c415..956251a 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "is-plain-object": "^5.0.0", "lz-string": "^1.4.4", "nanoevents": "^6.0.2", + "unofficial-galaxy-sdk": "git+https://code.incremental.social/thepaperpilot/unofficial-galaxy-sdk.git#1.0.0", "vite": "^2.9.12", "vite-plugin-pwa": "^0.12.0", "vite-tsconfig-paths": "^3.5.0", diff --git a/src/components/Nav.vue b/src/components/Nav.vue index e7a5a04..e2cc798 100644 --- a/src/components/Nav.vue +++ b/src/components/Nav.vue @@ -121,7 +121,7 @@ function openDiscord() { } const needsSync = computed( - () => galaxy.value?.loggedIn && !syncedSaves.value.includes(settings.active) + () => galaxy.value?.loggedIn === true && !syncedSaves.value.includes(settings.active) ); diff --git a/src/components/saves/CloudSaveResolver.vue b/src/components/saves/CloudSaveResolver.vue index 2c0188a..9a2b823 100644 --- a/src/components/saves/CloudSaveResolver.vue +++ b/src/components/saves/CloudSaveResolver.vue @@ -135,7 +135,7 @@ function close() { ?.save( slot, LZString.compressToUTF16(stringifySave(setupInitialStore(local))), - cloud.name ?? null + cloud.name ) .catch(console.error); break; @@ -152,7 +152,7 @@ function close() { ?.save( slot, LZString.compressToUTF16(stringifySave(setupInitialStore(local))), - cloud.name ?? null + cloud.name ) .catch(console.error); break; diff --git a/src/components/saves/Save.vue b/src/components/saves/Save.vue index 93cfee2..8c1809b 100644 --- a/src/components/saves/Save.vue +++ b/src/components/saves/Save.vue @@ -120,7 +120,10 @@ const currentTime = computed(() => isActive.value ? player.time : (save.value != null && save.value.time) ?? 0 ); const synced = computed( - () => !unref(readonly) && galaxy.value?.loggedIn && syncedSaves.value.includes(save.value.id) + () => + !unref(readonly) && + galaxy.value?.loggedIn === true && + syncedSaves.value.includes(save.value.id) ); function changeName() { diff --git a/src/components/saves/SavesManager.vue b/src/components/saves/SavesManager.vue index 9e32a9a..4edb2e0 100644 --- a/src/components/saves/SavesManager.vue +++ b/src/components/saves/SavesManager.vue @@ -159,7 +159,7 @@ const saves = computed(() => ); const showNotSyncedWarning = computed( - () => galaxy.value?.loggedIn && settings.saves.length < syncedSaves.value.length + () => galaxy.value?.loggedIn === true && settings.saves.length < syncedSaves.value.length ); function exportSave(id: string) { @@ -204,7 +204,7 @@ function duplicateSave(id: string) { } function deleteSave(id: string) { - if (galaxy.value?.loggedIn) { + if (galaxy.value?.loggedIn === true) { galaxy.value.getSaveList().then(list => { const slot = Object.keys(list).find(slot => { const content = list[slot as unknown as number].content; diff --git a/src/lib/galaxy.js b/src/lib/galaxy.js deleted file mode 100644 index 11db155..0000000 --- a/src/lib/galaxy.js +++ /dev/null @@ -1,248 +0,0 @@ -/** - * The Galaxy API defines actions and responses for interacting with the Galaxy platform. - * - * @typedef {object} SupportsAction - * @property {"supports"} action - The action type. - * @property {boolean} saving - If your game auto-saves or allows the user to make/load game saves from within the UI. - * @property {boolean} save_manager - If your game has a complete save manager integrated into it. - */ - -/** - * The save list action sends a retrieval request to Galaxy to get the player's cloud save list. - * - * @typedef {object} SaveListAction - * @property {"save_list"} action - The action type. - */ - -/** - * The save action creates a cloud save and puts it into a certain save slot. - * - * @typedef {object} SaveAction - * @property {"save"} action - The action type. - * @property {number} slot - The save slot number. Must be an integer between 0 and 10, inclusive. - * @property {string} [label] - The optional label of the save file. - * @property {string} data - The actual save data. - */ - -/** - * The load action sends a retrieval request to Galaxy to get the cloud save data inside a certain save slot. - * - * @typedef {object} LoadAction - * @property {"load"} action - The action type. - * @property {number} slot - The save slot number. - */ - -/** - * The Galaxy action can be one of SupportsAction, SaveListAction, SaveAction, or LoadAction. - * - * @typedef {SupportsAction | SaveListAction | SaveAction | LoadAction} GalaxyAction - */ - -/** - * The info response is sent when the page loads. - * - * @typedef {object} InfoResponse - * @property {"info"} type - The response type. - * @property {boolean} galaxy - Whether you're talking to Galaxy. - * @property {number} api_version - The version of the API. - * @property {string} theme_preference - The player's theme preference. - * @property {boolean} logged_in - Whether the player is logged in. - */ - -/** - * The save list response is requested by the save_list action. - * - * @typedef {object} SaveListResponse - * @property {"save_list"} type - The response type. - * @property {Record} list - A list of saves. - * @property {boolean} error - Whether the action encountered an error. - * @property {("no_account" | "server_error")} [message] - Reason for the error. - */ - -/** - * The save content response is requested by the load action. - * - * @typedef {object} SaveContentResponse - * @property {"save_content"} type - The response type. - * @property {boolean} error - Whether the action encountered an error. - * @property {("no_account" | "empty_slot" | "invalid_slot" | "server_error")} [message] - Reason for the error. - * @property {number} slot - The save slot number. - * @property {string} [label] - The save's label. - * @property {string} [content] - The save's actual data. - */ - -/** - * The saved response is requested by the save action. - * - * @typedef {object} SavedResponse - * @property {"saved"} type - The response type. - * @property {boolean} error - Whether the action encountered an error. - * @property {number} slot - The save slot number. - * @property {("no_account" | "too_big" | "invalid_slot" | "server_error")} [message] - Reason for the error. - */ - -/** - * The GalaxyResponse can be one of InfoResponse, SaveListResponse, SaveContentResponse, or SavedResponse. - * - * @typedef {InfoResponse | SaveListResponse | SaveContentResponse | SavedResponse} GalaxyResponse - */ - -/** - * The GalaxyApi interface defines methods and properties for interacting with the Galaxy platform. - * - * @typedef {object} GalaxyApi - * @property {string[]} acceptedOrigins - Accepted origins. - * @property {boolean} [supportsSaving] - Whether saving is supported. - * @property {boolean} [supportsSaveManager] - Whether save manager is supported. - * @property {boolean} [ignoreApiVersion] - Whether to ignore API version. - * @property {function(GalaxyApi): void} [onLoggedInChanged] - Function to handle logged in changes. - * @property {string} origin - Origin of the API. - * @property {number} apiVersion - Version of the API. - * @property {boolean} loggedIn - Whether the player is logged in. - * @property {function(GalaxyAction): void} postMessage - Method to post a message. - * @property {function(): Promise>} getSaveList - Method to get the save list. - * @property {function(number, string, string?): Promise} save - Method to save data. - * @property {function(number): Promise<{ content: string; label?: string; slot: number }>} load - Method to load data. - */ - - -/** - * Initialize the Galaxy API. - * @param {Object} [options] - An object of options that configure the API - * @param {string[]} [options.acceptedOrigins] - A list of domains that the API trusts messages from. Defaults to `['https://galaxy.click']`. - * @param {boolean} [options.supportsSaving] - Indicates to Galaxy that this game supports saving. Defaults to false. - * @param {boolean} [options.supportsSaveManager] - Indicates to Galaxy that this game supports a saves manager. Defaults to false. - * @param {boolean} [options.ignoreApiVersion] - Ignores the api_version property received from Galaxy. By default this value is false, meaning if an unknown API version is encountered, the API will fail to initialize. - * @param {(galaxy: GalaxyApi) => void} [options.onLoggedInChanged] - A callback for when the logged in status of the player changes after the initialization. - * @returns {Promise} - */ -export function initGalaxy({ - acceptedOrigins, - supportsSaving, - supportsSaveManager, - ignoreApiVersion, - onLoggedInChanged -}) { - return new Promise((accept, reject) => { - acceptedOrigins = acceptedOrigins ?? ["https://galaxy.click"]; - if (acceptedOrigins.includes(window.origin)) { - // Callbacks to resolve promises - /** @type function(SaveListResponse["list"]):void */ - let saveListAccept, - /** @type function(string?):void */ - saveListReject; - /** @type Record */ - const saveCallbacks = {}; - /** @type Record */ - const loadCallbacks = {}; - - /** @type GalaxyApi */ - const galaxy = { - acceptedOrigins, - supportsSaving, - supportsSaveManager, - ignoreApiVersion, - onLoggedInChanged, - origin: window.origin, - apiVersion: 0, - loggedIn: false, - postMessage: function (message) { - window.top?.postMessage(message, galaxy.origin); - }, - getSaveList: function () { - if (saveListAccept != null || saveListReject != null) { - return Promise.reject("save_list action already in progress."); - } - galaxy.postMessage({ action: "save_list" }); - return new Promise((accept, reject) => { - saveListAccept = accept; - saveListReject = reject; - }); - }, - save: function (slot, content, label) { - if (slot in saveCallbacks) { - return Promise.reject(`save action for slot ${slot} already in progress.`); - } - galaxy.postMessage({ action: "save", slot, content, label }); - return new Promise((accept, reject) => { - saveCallbacks[slot] = { accept, reject }; - }); - }, - load: function (slot) { - if (slot in loadCallbacks) { - return Promise.reject(`load action for slot ${slot} already in progress.`); - } - galaxy.postMessage({ action: "load", slot }); - return new Promise((accept, reject) => { - loadCallbacks[slot] = { accept, reject }; - }); - } - }; - - window.addEventListener("message", e => { - if (e.origin === galaxy.origin) { - console.log("Received message from Galaxy", e.data); - /** @type GalaxyResponse */ - const data = e.data; - - switch (data.type) { - case "info": { - const { galaxy: isGalaxy, api_version, logged_in } = data; - // Ignoring isGalaxy check in case other accepted origins send it as false - if (api_version !== 1 && galaxy.ignoreApiVersion !== true) { - reject(`API version not recognized: ${api_version}`); - } else { - // Info responses may be sent again if the information gets updated - // Specifically, we care if logged_in gets changed - // We can use the api_version to determine if this is the first - // info response or a new one. - const firstInfoResponse = galaxy.apiVersion === 0; - galaxy.apiVersion = api_version; - galaxy.loggedIn = logged_in; - galaxy.origin = e.origin; - if (firstInfoResponse) { - accept(galaxy); - } else { - galaxy.onLoggedInChanged?.(galaxy); - } - } - break; - } - case "save_list": { - const { list, error, message } = data; - if (error === true) { - saveListReject(message); - } else { - saveListAccept(list); - } - saveListAccept = saveListReject = null; - break; - } - case "save_content": { - const { content, label, slot, error, message } = data; - if (error === true) { - loadCallbacks[slot]?.reject(message); - } else { - loadCallbacks[slot]?.accept({ slot, content, label }); - } - delete loadCallbacks[slot]; - break; - } - case "saved": { - const { slot, error, message } = data; - if (error === true) { - saveCallbacks[slot]?.reject(message); - } else { - saveCallbacks[slot]?.accept(slot); - } - delete saveCallbacks[slot]; - break; - } - } - } - }); - } else { - reject(`Project is not running on an accepted origin: ${window.origin}`); - } - }); -} diff --git a/src/util/galaxy.ts b/src/util/galaxy.ts index ab79d82..e5fadc3 100644 --- a/src/util/galaxy.ts +++ b/src/util/galaxy.ts @@ -1,7 +1,7 @@ import { LoadablePlayerData } from "components/saves/SavesManager.vue"; import player, { Player, stringifySave } from "game/player"; import settings from "game/settings"; -import { GalaxyApi, initGalaxy } from "lib/galaxy"; +import { GalaxyApi, initGalaxy } from "unofficial-galaxy-sdk"; import LZString from "lz-string"; import { ref } from "vue"; import { decodeSave, loadSave, save, setupInitialStore } from "./save"; @@ -13,7 +13,7 @@ export const conflictingSaves = ref< export const syncedSaves = ref([]); export function sync() { - if (galaxy.value == null || !galaxy.value.loggedIn) { + if (galaxy.value?.loggedIn !== true) { return; } if (conflictingSaves.value.length > 0) { @@ -138,7 +138,7 @@ function syncSaves( LZString.compressToUTF16( stringifySave(setupInitialStore(cloudSave.content)) ), - cloudSave.label ?? null + cloudSave.label ) .catch(console.error); // Update cloud save content for the return value