mirror of
https://github.com/unraid/api.git
synced 2026-01-07 09:10:05 -06:00
227 lines
6.4 KiB
TypeScript
227 lines
6.4 KiB
TypeScript
/**
|
||
* This file is used to handle callbacks from the server.
|
||
* It is used in the following apps:
|
||
* - auth
|
||
* - craft-unraid
|
||
* - connect @todo
|
||
* - connect-components
|
||
*/
|
||
import AES from 'crypto-js/aes';
|
||
import Utf8 from 'crypto-js/enc-utf8';
|
||
import { defineStore, createPinia, setActivePinia } from 'pinia';
|
||
|
||
export type SignIn = 'signIn';
|
||
export type SignOut = 'signOut';
|
||
export type OemSignOut = 'oemSignOut';
|
||
export type Troubleshoot = 'troubleshoot';
|
||
export type Recover = 'recover';
|
||
export type Replace = 'replace';
|
||
export type TrialExtend = 'trialExtend';
|
||
export type TrialStart = 'trialStart';
|
||
export type Purchase = 'purchase';
|
||
export type Redeem = 'redeem';
|
||
export type Renew = 'renew';
|
||
export type Upgrade = 'upgrade';
|
||
export type UpdateOs = 'updateOs';
|
||
export type AccountActionTypes = Troubleshoot | SignIn | SignOut | OemSignOut;
|
||
export type AccountKeyActionTypes = Recover | Replace | TrialExtend | TrialStart | UpdateOs;
|
||
export type PurchaseActionTypes = Purchase | Redeem | Renew | Upgrade;
|
||
|
||
export type ServerActionTypes = AccountActionTypes | AccountKeyActionTypes | PurchaseActionTypes;
|
||
|
||
export type ServerState = 'BASIC'
|
||
| 'PLUS'
|
||
| 'PRO'
|
||
| 'TRIAL'
|
||
| 'EEXPIRED'
|
||
| 'ENOKEYFILE'
|
||
| 'EGUID'
|
||
| 'EGUID1'
|
||
| 'ETRIAL'
|
||
| 'ENOKEYFILE2'
|
||
| 'ENOKEYFILE1'
|
||
| 'ENOFLASH'
|
||
| 'ENOFLASH1'
|
||
| 'ENOFLASH2'
|
||
| 'ENOFLASH3'
|
||
| 'ENOFLASH4'
|
||
| 'ENOFLASH5'
|
||
| 'ENOFLASH6'
|
||
| 'ENOFLASH7'
|
||
| 'EBLACKLISTED'
|
||
| 'EBLACKLISTED1'
|
||
| 'EBLACKLISTED2'
|
||
| 'ENOCONN'
|
||
| 'STARTER'
|
||
| 'UNLEASHED'
|
||
| 'LIFETIME'
|
||
| 'STALE'
|
||
| undefined;
|
||
|
||
export interface ServerData {
|
||
description?: string;
|
||
deviceCount?: number;
|
||
expireTime?: number;
|
||
flashProduct?: string;
|
||
flashVendor?: string;
|
||
guid?: string;
|
||
keyfile?: string;
|
||
locale?: string;
|
||
name?: string;
|
||
osVersion?: string;
|
||
osVersionBranch?: 'stable' | 'next' | 'preview' | 'test';
|
||
registered: boolean;
|
||
regExp?: number;
|
||
regUpdatesExpired?: boolean;
|
||
regGen?: number;
|
||
regGuid?: string;
|
||
regTy?: string;
|
||
state: ServerState;
|
||
wanFQDN?: string;
|
||
}
|
||
|
||
export interface UserInfo {
|
||
'custom:ips_id'?: string;
|
||
email?: string;
|
||
email_verifed?: 'true' | 'false';
|
||
preferred_username?: string;
|
||
sub?: string;
|
||
username?: string;
|
||
/**
|
||
* @param identities {string} JSON string containing @type Identity[]
|
||
*/
|
||
identities?: string;
|
||
/**
|
||
* @param cognito:groups {string[]} JSON string containing @type string[]
|
||
*
|
||
* Will contain all groups for the signed in user, used for determining which branch to use
|
||
* @example ["download-preview", "unraidPOOLID_Google"]
|
||
*/
|
||
'cognito:groups'?: string[];
|
||
}
|
||
|
||
export interface ExternalSignIn {
|
||
type: SignIn;
|
||
apiKey: string;
|
||
user: UserInfo;
|
||
}
|
||
|
||
export interface ExternalSignOut {
|
||
type: SignOut | OemSignOut;
|
||
}
|
||
|
||
export interface ExternalKeyActions {
|
||
type: PurchaseActionTypes | AccountKeyActionTypes;
|
||
keyUrl: string;
|
||
}
|
||
|
||
export interface ExternalUpdateOsAction {
|
||
type: UpdateOs;
|
||
sha256: string;
|
||
}
|
||
|
||
export interface ServerPayload {
|
||
type: ServerActionTypes;
|
||
server: ServerData;
|
||
}
|
||
|
||
export interface ServerTroubleshoot {
|
||
type: Troubleshoot;
|
||
server: ServerData;
|
||
}
|
||
|
||
export type ExternalActions = ExternalSignIn | ExternalSignOut | ExternalKeyActions | ExternalUpdateOsAction;
|
||
|
||
export type UpcActions = ServerPayload | ServerTroubleshoot;
|
||
|
||
export type SendPayloads = ExternalActions[] | UpcActions[];
|
||
|
||
/**
|
||
* Payload containing all actions that are sent from account.unraid.net to the server
|
||
*/
|
||
export interface ExternalPayload {
|
||
type: 'forUpc';
|
||
actions: ExternalActions[];
|
||
sender: string;
|
||
}
|
||
|
||
/**
|
||
* Payload containing all actions that are sent from a server to account.unraid.net
|
||
*/
|
||
export interface UpcPayload {
|
||
actions: UpcActions[];
|
||
sender: string;
|
||
type: 'fromUpc';
|
||
}
|
||
|
||
export type QueryPayloads = ExternalPayload | UpcPayload;
|
||
|
||
export interface CallbackActionsStore {
|
||
saveCallbackData: (decryptedData: QueryPayloads) => void;
|
||
encryptionKey: string;
|
||
sendType: 'fromUpc' | 'forUpc';
|
||
}
|
||
|
||
/**
|
||
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
|
||
* @see https://github.com/vuejs/pinia/discussions/1085
|
||
*/
|
||
setActivePinia(createPinia());
|
||
|
||
export const useCallbackStoreGeneric = (
|
||
useCallbackActions: () => CallbackActionsStore
|
||
) =>
|
||
defineStore('callback', () => {
|
||
const callbackActions = useCallbackActions();
|
||
|
||
const send = (url: string, payload: SendPayloads, redirectType?: 'newTab' | 'replace', sendType?: string) => {
|
||
console.debug('[callback.send]');
|
||
const stringifiedData = JSON.stringify({
|
||
actions: [...payload],
|
||
sender: window.location.href.replace('/Tools/Update', '/Main'),
|
||
type: sendType ?? callbackActions.sendType,
|
||
});
|
||
const encryptedMessage = AES.encrypt(
|
||
stringifiedData,
|
||
callbackActions.encryptionKey,
|
||
).toString();
|
||
/**
|
||
* Build and go to url
|
||
* @note – /Tools/Update redirects to account.unraid.net/server/update-os we need to prevent any callback sends to that url to prevent redirect loops
|
||
*/
|
||
const destinationUrl = new URL(url.replace('/Tools/Update', '/Main'));
|
||
destinationUrl.searchParams.set('data', encodeURI(encryptedMessage));
|
||
console.debug('[callback.send]', encryptedMessage, destinationUrl);
|
||
if (redirectType === 'newTab') { // helpful when webgui is in an iframe and callbacks need to be opened in a new tab
|
||
window.open(destinationUrl.toString(), '_blank');
|
||
return;
|
||
}
|
||
if (redirectType === 'replace') { // helpful when autoredirecting and we want to replace the current url to prevent back button issues with auto redirect loops
|
||
window.location.replace(destinationUrl.toString());
|
||
return;
|
||
}
|
||
window.location.href = destinationUrl.toString();
|
||
};
|
||
|
||
const watcher = () => {
|
||
console.debug('[callback.watcher]');
|
||
const currentUrl = new URL(window.location.toString());
|
||
const callbackValue = decodeURI(currentUrl.searchParams.get('data') ?? '');
|
||
console.debug('[callback.watcher]', { callbackValue });
|
||
if (!callbackValue) {
|
||
return console.debug('[callback.watcher] no callback to handle');
|
||
}
|
||
|
||
const decryptedMessage = AES.decrypt(callbackValue, callbackActions.encryptionKey);
|
||
const decryptedData: QueryPayloads = JSON.parse(decryptedMessage.toString(Utf8));
|
||
console.debug('[callback.watcher]', decryptedMessage, decryptedData);
|
||
// Parse the data and perform actions
|
||
callbackActions.saveCallbackData(decryptedData);
|
||
};
|
||
|
||
return {
|
||
send,
|
||
watcher,
|
||
};
|
||
});
|