Files
api/web/store/callback.ts
2023-11-06 13:18:28 -08:00

227 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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,
};
});