Files
api/web/store/callback.ts

230 lines
6.5 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 DowngradeOs = 'downgradeOs';
export type Manage = 'manage';
export type MyKeys = 'myKeys';
export type LinkKey = 'linkKey';
export type AccountActionTypes = Troubleshoot | SignIn | SignOut | OemSignOut | Manage | MyKeys | LinkKey;
export type AccountKeyActionTypes = Recover | Replace | TrialExtend | TrialStart | UpdateOs | DowngradeOs;
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: DowngradeOs | 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', '/Tools'),
type: sendType ?? callbackActions.sendType,
});
const encryptedMessage = AES.encrypt(
stringifiedData,
callbackActions.encryptionKey,
).toString();
/**
* Build and go to url
*/
const destinationUrl = new URL(url.replace('/Tools/Update', '/Tools'));
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,
};
});