use-dcp-worker
Advanced tools
Comparing version 1.0.4 to 2.0.0
@@ -1,25 +0,16 @@ | ||
/** | ||
* @file useDCPWorker.jsx | ||
* @author Kirill Kirnichansky <kirill@distributive.network> | ||
* | ||
* @date January 2023 | ||
*/ | ||
import { EventHandler } from 'react'; | ||
/// <reference types="react" /> | ||
import BigNumber from 'bignumber.js'; | ||
declare global { | ||
interface Window { | ||
dcp: DCP; | ||
dcp: { | ||
wallet: { | ||
Keystore: typeof Keystore; | ||
Address: typeof Address; | ||
}; | ||
utils: any; | ||
worker: any; | ||
}; | ||
dcpConfig: any; | ||
} | ||
} | ||
declare interface DCP { | ||
wallet: Wallet; | ||
worker: { | ||
Worker: typeof Worker; | ||
}; | ||
} | ||
declare interface Wallet { | ||
Address: typeof Address; | ||
Keystore: typeof Keystore; | ||
} | ||
declare class Address { | ||
@@ -56,32 +47,14 @@ constructor(address: string | Address | undefined | null); | ||
jobAddresses?: Array<string>; | ||
cores?: { | ||
cpu?: number; | ||
gpu?: number; | ||
}; | ||
maxWorkingSandboxes?: number | undefined; | ||
paymentAddress?: Address | string | null; | ||
paymentAddress?: Keystore | Address | string | null; | ||
evaluatorOptions?: {}; | ||
shouldStopWorkingImmediately?: boolean; | ||
} | ||
declare class EventTarget<T> { | ||
on<E extends keyof T>(event: E, eventListener: T[E]): this; | ||
off<E extends keyof T>(event: E, eventListener: T[E]): this; | ||
} | ||
declare interface WorkerEvents { | ||
start: EventHandler<any>; | ||
fetchStart: EventHandler<any>; | ||
error: EventHandler<any>; | ||
fetchEnd: EventHandler<any>; | ||
stop: EventHandler<any>; | ||
payment: EventHandler<any>; | ||
} | ||
declare class Worker extends EventTarget<WorkerEvents> { | ||
constructor(identity: Keystore, options: IWorkerOptions); | ||
start: () => Promise<void>; | ||
stop: (shouldstopImmediately: boolean) => Promise<void>; | ||
supervisorOptions: any; | ||
workingSandboxes: Array<any>; | ||
} | ||
/** | ||
* Stores the worker's "state", whether it is loaded, fetching and/or submitting work, and | ||
* any recent error | ||
* Stores the worker's "state", whether it is fetching and/or submitting work, and any recent error | ||
* | ||
* @type {Object} | ||
* @property {boolean} isLoaded True once the worker has been established | ||
* @property {number} workingSandboxes Number of sandboxes currently working | ||
@@ -95,3 +68,2 @@ * @property {boolean} working True if worker has started to work, False otherwise | ||
interface IDefaultWorkerState { | ||
isLoaded: boolean; | ||
workingSandboxes: number; | ||
@@ -108,6 +80,2 @@ working: boolean; | ||
computeTime: number; | ||
options: { | ||
paymentAddress: string | null; | ||
maxWorkingSandboxes: number; | ||
}; | ||
} | ||
@@ -119,3 +87,3 @@ /** | ||
*/ | ||
export declare const WorkerProvider: (props: any) => JSX.Element; | ||
export declare function WorkerProvider(props: any): JSX.Element; | ||
interface IUseDCPWorkerParams { | ||
@@ -127,26 +95,26 @@ identity?: any; | ||
/** | ||
* This hook enables the use of a DCP web worker. A config object is accepted as a paremeter. This config object can have a | ||
* config.identity property which will be assigned as the worker's identity (must be of type dcp.wallet.Keystore). config.useLocalStorage | ||
* is another config property which determined wether browser localStorage is used to save workerOptions between sessions. The final property | ||
* for the config param is config.workerOptions which is used by the worker constructor to configure the worker. | ||
* This hook enables the use of a DCP web worker. Accepts a config object as a parameter used to configure local storage use and | ||
* configuration for the worker. Returns the dcp worker, which is `undefined` until it is constructed, the worker options used to | ||
* configure the worker (and to update options after), `workerState` describing dcp worker states and `workerStatistics` storing worker | ||
* session data. | ||
* | ||
* @param config.identity Value to set the identity of the worker. Must be of type dcp.wallet.Address | ||
* This hook will throw in the following cases: | ||
* - missing `dcp-client` dependency | ||
* - error in worker constructor | ||
* | ||
* Errors may also be emited by the worker, in which then, they are set to `workerState.error` and will not cause the hook to throw. | ||
* | ||
* @param config.identity Value to set the identity of the worker. Must be of type dcp.wallet.Address. Optional. | ||
* If not provided, an identity will be generated. | ||
* @param config.useLocalStorage Boolean flag to enable the use of localStorage to save workerOptions. | ||
* This will override any workerOptions passed in the config. | ||
* This will override `paymentAddress` and `maxWorkingSandboxes` properties defined in | ||
* `config.workerOptions`. Optional, set to `true` by default. | ||
* @param config.workerOptions WorkerOptions to configure the worker. | ||
* @returns `{workerState, workerStatistics, setWorkerOptions, startWorker, stopWorker, toggleWorker, workerOptionsState, sandboxes}` | ||
* workerState and workerStatisitics provide worker status and data information. The worker can be controlled by startWorker, | ||
* stopWorker and togglerWorker. workerOptionsState is a readonly object that describes how the worker is currently | ||
* configured. To mutate workerOptions, use setWorkerOptions. | ||
* @returns `{worker, workerOptions, workerState, workerStatistics}` | ||
*/ | ||
declare const useDCPWorker: ({ identity, useLocalStorage, workerOptions: userWorkerOptions, }: IUseDCPWorkerParams) => { | ||
worker: any; | ||
workerState: IDefaultWorkerState; | ||
workerStatistics: IDefaultWorkerStats; | ||
setWorkerOptions: (newWorkerOptions: IWorkerOptions) => void; | ||
startWorker: () => void; | ||
stopWorker: () => void; | ||
toggleWorker: () => void; | ||
workerOptionsState: IWorkerOptions; | ||
sandboxes: any[]; | ||
}; | ||
export default useDCPWorker; |
@@ -24,3 +24,2 @@ "use strict"; | ||
const defaultWorkerState = { | ||
isLoaded: false, | ||
workingSandboxes: 0, | ||
@@ -33,41 +32,57 @@ working: false, | ||
}; | ||
let hasStartedWorkerInit = false, optionsError = false; | ||
var WorkerStateActions; | ||
(function (WorkerStateActions) { | ||
WorkerStateActions["WORKER_LOADED"] = "WORKER_LOADED"; | ||
WorkerStateActions["SET_WORKER_SBX"] = "SET_WORKER_SANDBOXES"; | ||
WorkerStateActions["FETCHING_TRUE"] = "FETCHING_TRUE"; | ||
WorkerStateActions["FETCHING_FALSE"] = "FETCHING_FALSE"; | ||
WorkerStateActions["SUBMIT_TRUE"] = "SUBMIT_TRUE"; | ||
WorkerStateActions["SUBMIT_FALSE"] = "SUBMIT_FALSE"; | ||
WorkerStateActions["WORKING_TRUE"] = "WORKING_TRUE"; | ||
WorkerStateActions["WORKING_FALSE"] = "WORKING_FALSE"; | ||
WorkerStateActions["WILL_WORK_TRUE"] = "WILL_WORK_TRUE"; | ||
WorkerStateActions["WILL_WORK_FALSE"] = "WILL_WORK_FALSE"; | ||
WorkerStateActions["ERROR"] = "ERROR"; | ||
WorkerStateActions["TRIGGER_RERENDER"] = "TRIGGER_RERENDER"; | ||
})(WorkerStateActions || (WorkerStateActions = {})); | ||
const workerStateReducer = (state, action) => { | ||
const updatedState = Object.assign({}, state); | ||
if (action.type === 'WORKER_LOADED_TRUE') { | ||
updatedState.isLoaded = true; | ||
switch (action.type) { | ||
case WorkerStateActions.SET_WORKER_SBX: | ||
updatedState.workingSandboxes = action.data; | ||
break; | ||
case WorkerStateActions.FETCHING_TRUE: | ||
updatedState.fetching = true; | ||
break; | ||
case WorkerStateActions.FETCHING_FALSE: | ||
updatedState.fetching = false; | ||
break; | ||
case WorkerStateActions.SUBMIT_TRUE: | ||
updatedState.submitting = true; | ||
break; | ||
case WorkerStateActions.SUBMIT_FALSE: | ||
updatedState.submitting = false; | ||
break; | ||
case WorkerStateActions.WORKING_TRUE: | ||
updatedState.working = true; | ||
if (updatedState.willWork) | ||
updatedState.willWork = null; | ||
break; | ||
case WorkerStateActions.WORKING_FALSE: | ||
updatedState.working = false; | ||
if (!updatedState.willWork) | ||
updatedState.willWork = null; | ||
break; | ||
case WorkerStateActions.WILL_WORK_TRUE: | ||
updatedState.willWork = true; | ||
break; | ||
case WorkerStateActions.WILL_WORK_FALSE: | ||
updatedState.willWork = false; | ||
break; | ||
case WorkerStateActions.ERROR: | ||
console.error('use-dcp-worker: Worker error occured:', action.data); | ||
updatedState.error = action.data; | ||
break; | ||
} | ||
else if (action.type === 'SET_WORKING_SANDBOXES') { | ||
updatedState.workingSandboxes = action.data; | ||
} | ||
else if (action.type === 'FETCHING_TRUE') { | ||
updatedState.fetching = true; | ||
} | ||
else if (action.type === 'FETCHING_FALSE') { | ||
updatedState.fetching = false; | ||
} | ||
else if (action.type === 'SUBMITTING_TRUE') { | ||
updatedState.submitting = true; | ||
} | ||
else if (action.type === 'SUBMITTING_FALSE') { | ||
updatedState.fetching = false; | ||
} | ||
else if (action.type === 'WORKING_TRUE') { | ||
updatedState.working = true; | ||
if (updatedState.willWork) | ||
updatedState.willWork = null; | ||
} | ||
else if (action.type === 'WORKING_FALSE') { | ||
updatedState.working = false; | ||
if (!updatedState.willWork) | ||
updatedState.willWork = null; | ||
} | ||
else if (action.type === 'WILL_WORK_TRUE') { | ||
updatedState.willWork = true; | ||
} | ||
else if (action.type === 'WILL_WORK_FALSE') { | ||
updatedState.willWork = false; | ||
} | ||
else if (action.type === 'ERROR') { | ||
updatedState.error = action.data; | ||
} | ||
return updatedState; | ||
@@ -87,15 +102,22 @@ }; | ||
computeTime: 0, | ||
options: { paymentAddress: null, maxWorkingSandboxes: 0 }, | ||
}; | ||
var WorkerStatsActions; | ||
(function (WorkerStatsActions) { | ||
WorkerStatsActions["ADD_COMPUTE_TIME"] = "ADD_COMPUTE_TIME"; | ||
WorkerStatsActions["ADD_SLICE"] = "ADD_SLICE"; | ||
WorkerStatsActions["ADD_CREDITS"] = "ADD_CREDITS"; | ||
})(WorkerStatsActions || (WorkerStatsActions = {})); | ||
const workerStatsReducer = (state, action) => { | ||
let updatedStats = Object.assign({}, state); | ||
if (action.type === 'INCREMENT_COMPUTE_TIME') { | ||
updatedStats.computeTime += action.data; | ||
const updatedStats = Object.assign({}, state); | ||
switch (action.type) { | ||
case WorkerStatsActions.ADD_COMPUTE_TIME: | ||
updatedStats.computeTime += action.data; | ||
break; | ||
case WorkerStatsActions.ADD_SLICE: | ||
updatedStats.slices++; | ||
break; | ||
case WorkerStatsActions.ADD_CREDITS: | ||
updatedStats.credits = updatedStats.credits.plus(action.data); | ||
break; | ||
} | ||
else if (action.type === 'INCREMENT_SLICES') { | ||
updatedStats.slices++; | ||
} | ||
else if (action.type === 'INCREMENT_CREDITS') { | ||
updatedStats.credits = updatedStats.credits.plus(action.data); | ||
} | ||
return updatedStats; | ||
@@ -129,6 +151,4 @@ }; | ||
const defaultWorkerContext = { | ||
worker: null, | ||
worker: undefined, | ||
setWorker: () => { }, | ||
workerOptionsState: defaultWorkerOptions, | ||
setWorkerOptionsState: () => { }, | ||
workerState: defaultWorkerState, | ||
@@ -145,12 +165,9 @@ workerStatistics: defaultWorkerStats, | ||
*/ | ||
const WorkerProvider = (props) => { | ||
function WorkerProvider(props) { | ||
const [workerState, dispatchWorkerState] = (0, react_1.useReducer)(workerStateReducer, defaultWorkerState); | ||
const [workerStatistics, dispatchWorkerStats] = (0, react_1.useReducer)(workerStatsReducer, defaultWorkerStats); | ||
const [workerOptionsState, setWorkerOptionsState] = (0, react_1.useState)(defaultWorkerOptions); | ||
const [worker, setWorker] = (0, react_1.useState)(null); | ||
const [worker, setWorker] = (0, react_1.useState)(); | ||
return ((0, jsx_runtime_1.jsx)(WorkerContext.Provider, Object.assign({ value: { | ||
worker, | ||
setWorker, | ||
workerOptionsState, | ||
setWorkerOptionsState, | ||
workerState, | ||
@@ -161,3 +178,3 @@ workerStatistics, | ||
} }, { children: props.children }))); | ||
}; | ||
} | ||
exports.WorkerProvider = WorkerProvider; | ||
@@ -186,9 +203,9 @@ /** | ||
function loadWorkerOptions() { | ||
let options = window.localStorage.getItem('worker-options'); | ||
let storage = options === null ? {} : JSON.parse(options); | ||
const options = window.localStorage.getItem('dcp-worker-options'); | ||
const storage = options === null ? {} : JSON.parse(options); | ||
let loadedOptions; | ||
if (Object.prototype.hasOwnProperty.call(storage, getWorkerOptionsKey())) { | ||
if (Object.hasOwn(storage, getWorkerOptionsKey())) { | ||
loadedOptions = storage[getWorkerOptionsKey()]; | ||
} | ||
else if (Object.prototype.hasOwnProperty.call(storage, 'defaultMaxWorkers')) { | ||
else if (Object.hasOwn(storage, 'defaultMaxWorkers')) { | ||
loadedOptions = storage; | ||
@@ -198,3 +215,3 @@ } | ||
return null; | ||
if (Object.prototype.hasOwnProperty.call(loadedOptions, 'paymentAddress')) { | ||
if (Object.hasOwn(loadedOptions, 'paymentAddress')) { | ||
if (loadedOptions.paymentAddress instanceof window.dcp.wallet.Keystore) { | ||
@@ -208,3 +225,3 @@ loadedOptions.paymentAddress = new window.dcp.wallet.Address(loadedOptions.paymentAddress.address); | ||
// If the saved options have `defaultMaxWorkers`, change that to `defaultMaxSliceCount` | ||
if (Object.prototype.hasOwnProperty.call(loadedOptions, 'defaultMaxWorkers')) { | ||
if (Object.hasOwn(loadedOptions, 'defaultMaxWorkers')) { | ||
loadedOptions.defaultMaxSliceCount = loadedOptions.defaultMaxWorkers; | ||
@@ -216,68 +233,55 @@ delete loadedOptions.defaultMaxWorkers; | ||
/** | ||
* This hook enables the use of a DCP web worker. A config object is accepted as a paremeter. This config object can have a | ||
* config.identity property which will be assigned as the worker's identity (must be of type dcp.wallet.Keystore). config.useLocalStorage | ||
* is another config property which determined wether browser localStorage is used to save workerOptions between sessions. The final property | ||
* for the config param is config.workerOptions which is used by the worker constructor to configure the worker. | ||
* This hook enables the use of a DCP web worker. Accepts a config object as a parameter used to configure local storage use and | ||
* configuration for the worker. Returns the dcp worker, which is `undefined` until it is constructed, the worker options used to | ||
* configure the worker (and to update options after), `workerState` describing dcp worker states and `workerStatistics` storing worker | ||
* session data. | ||
* | ||
* @param config.identity Value to set the identity of the worker. Must be of type dcp.wallet.Address | ||
* This hook will throw in the following cases: | ||
* - missing `dcp-client` dependency | ||
* - error in worker constructor | ||
* | ||
* Errors may also be emited by the worker, in which then, they are set to `workerState.error` and will not cause the hook to throw. | ||
* | ||
* @param config.identity Value to set the identity of the worker. Must be of type dcp.wallet.Address. Optional. | ||
* If not provided, an identity will be generated. | ||
* @param config.useLocalStorage Boolean flag to enable the use of localStorage to save workerOptions. | ||
* This will override any workerOptions passed in the config. | ||
* This will override `paymentAddress` and `maxWorkingSandboxes` properties defined in | ||
* `config.workerOptions`. Optional, set to `true` by default. | ||
* @param config.workerOptions WorkerOptions to configure the worker. | ||
* @returns `{workerState, workerStatistics, setWorkerOptions, startWorker, stopWorker, toggleWorker, workerOptionsState, sandboxes}` | ||
* workerState and workerStatisitics provide worker status and data information. The worker can be controlled by startWorker, | ||
* stopWorker and togglerWorker. workerOptionsState is a readonly object that describes how the worker is currently | ||
* configured. To mutate workerOptions, use setWorkerOptions. | ||
* @returns `{worker, workerOptions, workerState, workerStatistics}` | ||
*/ | ||
const useDCPWorker = ({ identity = null, useLocalStorage = true, workerOptions: userWorkerOptions, }) => { | ||
var _a; | ||
const { worker, setWorker, workerOptionsState, setWorkerOptionsState, workerState, workerStatistics, dispatchWorkerState, dispatchWorkerStats, } = (0, react_1.useContext)(WorkerContext); | ||
function startWorker() { | ||
if (workerState.isLoaded && worker !== null) { | ||
dispatchWorkerState({ type: 'WILL_WORK_TRUE' }); | ||
worker.start().catch((error) => { | ||
console.error(`use-dcp-worker: starting the worker threw an unexpected error:`, error); | ||
return error; | ||
}); | ||
const { worker, setWorker, workerState, workerStatistics, dispatchWorkerState, dispatchWorkerStats, } = (0, react_1.useContext)(WorkerContext); | ||
/** | ||
* This method ensures that the paymentAddress prop in workerOptions is of valid type. | ||
* @returns `true` if valid, `false` otherwise | ||
*/ | ||
const ensurePaymentAddressType = (0, react_1.useCallback)(() => { | ||
if (!Object.hasOwn(workerOptions, 'paymentAddress')) { | ||
console.error('use-dcp-worker: workerOptions must contain a paymentAddress.'); | ||
optionsError = true; | ||
return; | ||
} | ||
else | ||
console.warn('use-dcp-worker: worker is not loaded.'); | ||
} | ||
function stopWorker() { | ||
var _a; | ||
dispatchWorkerState({ type: 'WILL_WORK_FALSE' }); | ||
if (workerState.isLoaded && worker !== null) { | ||
worker | ||
.stop((_a = workerOptions.shouldStopWorkingImmediately) !== null && _a !== void 0 ? _a : false) | ||
.catch((error) => { | ||
console.error(`use-dcp-worker: stopping the worker threw an unexpected error:`, error); | ||
return error; | ||
}); | ||
try { | ||
if (workerOptions.paymentAddress instanceof window.dcp.wallet.Keystore) | ||
workerOptions.paymentAddress = workerOptions.paymentAddress.address; | ||
else if (!(workerOptions.paymentAddress instanceof window.dcp.wallet.Address)) | ||
workerOptions.paymentAddress = new window.dcp.wallet.Address(workerOptions.paymentAddress); | ||
} | ||
else { | ||
console.error("use-dcp-worker: failed to stop worker, worker isn't loaded or is null."); | ||
catch (error) { | ||
console.error(`use-dcp-worker: Invalid type (${typeof workerOptions.paymentAddress}) of paymentAddress supplied for worker options.`, error); | ||
optionsError = true; | ||
} | ||
} | ||
function toggleWorker() { | ||
if (workerState.working) | ||
stopWorker(); | ||
else | ||
startWorker(); | ||
} | ||
}, []); | ||
/** | ||
* Apply a set of workerOptions (can come from loadWorkerOptions or | ||
* saveWorkerOptions) to the worker, then notify any listeners (using an | ||
* extension event name, `x-portal-options-updated`) | ||
* Performs a leaf merge onto the workerOptions object. | ||
* | ||
* @param {newWorkerOptions} newWorkerOptions WorkerOptions to apply | ||
* | ||
* @return {WorkerOptions} WorkerOptions as applied | ||
* @param {newWorkerOptions} newWorkerOptions options to apply | ||
*/ | ||
const applyWorkerOptions = (0, react_1.useCallback)((newWorkerOptions) => { | ||
if (!newWorkerOptions) | ||
return null; | ||
return; | ||
for (const prop in newWorkerOptions) { | ||
if (prop === 'paymentAddress') { | ||
if (typeof newWorkerOptions[prop] === 'string') | ||
newWorkerOptions[prop] = new window.dcp.wallet.Address(newWorkerOptions[prop]); | ||
} | ||
if (typeof newWorkerOptions[prop] === 'object') | ||
workerOptions[prop] = window.dcp.utils.leafMerge(workerOptions[prop], newWorkerOptions[prop]); | ||
workerOptions[prop] = newWorkerOptions[prop]; | ||
@@ -287,58 +291,105 @@ } | ||
/** | ||
* Saves the current maxWorkingSandboxes and paymentAddress configuration under worker-options in | ||
* their local storage to be loaded when they sign in next time. | ||
* Applies user specific options to workerOptions. First the options passed to the hook are applied, | ||
* followed by the options stored in local storage if enabled. | ||
*/ | ||
const applyUserOptions = (0, react_1.useCallback)(() => { | ||
// apply user passed options | ||
if (userWorkerOptions) | ||
applyWorkerOptions(userWorkerOptions); | ||
// apply local storage options | ||
let storageOptions; | ||
if (useLocalStorage) | ||
storageOptions = loadWorkerOptions(); | ||
if (storageOptions) | ||
applyWorkerOptions(storageOptions); // paymentAddress and maxWorkingSandboxes from localStorage applied onto workerOptions | ||
}, [userWorkerOptions]); | ||
/** | ||
* If local storage is enabled: | ||
* | ||
* Saves the current cores and paymentAddress configuration | ||
* under dcp-worker-options in local storage to be loaded in next time. | ||
*/ | ||
const saveWorkerOptions = (0, react_1.useCallback)(() => { | ||
const storageItem = window.localStorage.getItem('worker-options'); | ||
if (!useLocalStorage) | ||
return; | ||
const storageItem = window.localStorage.getItem('dcp-worker-options'); | ||
const storage = storageItem !== null ? JSON.parse(storageItem) : {}; | ||
// Save the worker options indexed by the user's Identity | ||
storage[getWorkerOptionsKey()] = { | ||
maxWorkingSandboxes: workerOptions.maxWorkingSandboxes, | ||
paymentAddress: workerOptions.paymentAddress | ||
cores: workerOptions.cores, | ||
paymentAddress: workerOptions.paymentAddress, | ||
}; | ||
localStorage.setItem('worker-options', JSON.stringify(storage)); | ||
localStorage.setItem('dcp-worker-options', JSON.stringify(storage)); | ||
}, []); | ||
/** | ||
* This desired method to use when changing workerOptions. First it calls applyWorkerOptions | ||
* which correctly mutates the workerOptions referenced in the worker. Depending if local storage | ||
* is enabled, changes are saved to local storage. The readonly wokerOptionsSate is updated accordingly as well. | ||
* Constructs/retrieves the worker options object. In the first pass, workerOptions is set by reference to | ||
* dcpConfig.worker, this is to ensure that the source of the option obj is always coming from dcpConfig. | ||
* Then user specific options are applied onto workerOptions. User specific option consist of options passed | ||
* to this hook (userWorkerOptions) and options stored in local storage, which are applied in that order. | ||
* The paymentAddress prop is then validated and coerced to the desired type. | ||
* | ||
* When worker is already set, the workerOptions is retrieved from the supervisor. | ||
* | ||
* The workerOptions object is returned by this hook. Modifying worker options is as simple as mutating the | ||
* workerOptions object returned. in the case local storage is enabled, properties. | ||
*/ | ||
const setWorkerOptions = (0, react_1.useCallback)((newWorkerOptions) => { | ||
applyWorkerOptions(newWorkerOptions); | ||
if (useLocalStorage) | ||
saveWorkerOptions(); | ||
setWorkerOptionsState(Object.assign({}, workerOptions)); | ||
}, [ | ||
applyWorkerOptions, | ||
useLocalStorage, | ||
saveWorkerOptions, | ||
setWorkerOptionsState, | ||
]); | ||
if (!workerOptions) { | ||
// if worker in loaded state, pass options reference from supervisor | ||
if (workerState.isLoaded && worker !== null) { | ||
workerOptions = worker.supervisorOptions; | ||
} | ||
else { | ||
// we can trust global config | ||
workerOptions = (_a = window.dcpConfig.worker) !== null && _a !== void 0 ? _a : defaultWorkerOptions; | ||
if (userWorkerOptions) | ||
applyWorkerOptions(userWorkerOptions); // apply user passed options | ||
let userOptions = null; | ||
if (useLocalStorage) | ||
userOptions = loadWorkerOptions(); | ||
if (userOptions !== null) | ||
applyWorkerOptions(userOptions); // paymentAddress and maxWorkingSandboxes from localStorage applied onto workerOptions | ||
// ensure that paymentAddress is of type dcp.wallet.Address | ||
try { | ||
if (workerOptions.paymentAddress instanceof window.dcp.wallet.Keystore) | ||
workerOptions.paymentAddress = new window.dcp.wallet.Address(workerOptions.paymentAddress.address); | ||
else if (typeof userWorkerOptions.paymentAddress === 'string') | ||
workerOptions.paymentAddress = new window.dcp.wallet.Address(workerOptions.paymentAddress); | ||
const constructWorkerOptions = (0, react_1.useCallback)(() => { | ||
var _a; | ||
// if optionsError -> an error happened in previous execution of this method, therefore, we can retry | ||
if (!workerOptions || optionsError) { | ||
// if worker in loaded state, pass options reference from supervisor | ||
if (worker) { | ||
workerOptions = worker.supervisorOptions; | ||
} | ||
catch (error) { | ||
console.error(`use-dcp-worker: Invalid type of paymentAddress supplied for worker options.`, error); | ||
else { | ||
// we can trust dcpConfig | ||
workerOptions = (_a = window.dcpConfig.worker) !== null && _a !== void 0 ? _a : defaultWorkerOptions; | ||
optionsError = false; | ||
applyUserOptions(); | ||
ensurePaymentAddressType(); | ||
// ensure computeGroups is array, {} by default from dcpConfig | ||
if (!(workerOptions.computeGroups instanceof Array)) | ||
workerOptions.computeGroups = []; | ||
// applicable when cores is saved to localStorage & maxWorkingSandboxes passed in userWorkerOptions to hook | ||
// deprecate when maxWorkingSandboxes is not supported at all | ||
if (Object.hasOwn(workerOptions, 'cores') && Object.hasOwn(workerOptions, 'maxWorkingSandboxes')) | ||
delete workerOptions.maxWorkingSandboxes; | ||
/** | ||
* Set up proxy so that when paymentAddress or cores is changed | ||
* it is saved to localStorage and triggers re-render to components using this hook. | ||
*/ | ||
const changeWatchList = ['paymentAddress', 'maxWorkingSandboxes', 'cores', 'cpu']; | ||
const workerOptionsHandler = { | ||
get(target, property) { | ||
if (typeof target[property] === 'object') | ||
return new Proxy(target[property], workerOptionsHandler); | ||
return target[property]; | ||
}, | ||
set(target, property, value) { | ||
target[property] = value; | ||
if (changeWatchList.includes(property)) { | ||
if (property === 'paymentAddress') | ||
ensurePaymentAddressType(); | ||
saveWorkerOptions(); | ||
/** | ||
* paymentAddress and cores are desired worker options that may | ||
* feature UI components, therefore, we want to trigger a re-render | ||
*/ | ||
dispatchWorkerState({ type: WorkerStateActions.TRIGGER_RERENDER }); | ||
} | ||
return true; | ||
} | ||
}; | ||
const workerOptionsProxy = new Proxy(workerOptions, workerOptionsHandler); | ||
workerOptions = workerOptionsProxy; | ||
} | ||
} | ||
}, [userWorkerOptions]); | ||
// Ensure window.dcp -> dcp-client library loaded | ||
if (!window.dcp) { | ||
console.error('use-dcp-worker: Missing dcp-client dependency.'); | ||
throw new Error('Missing dcp-client dependency.'); | ||
} | ||
// Worker Options Construction | ||
constructWorkerOptions(); | ||
// Worker Initialization | ||
@@ -348,24 +399,11 @@ (0, react_1.useEffect)(() => { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!window.dcp) { | ||
console.error('use-dcp-worker: Missing dcp-client dependency. '); | ||
// prevents race condition if hook is called multiple times || options error | ||
if (hasStartedWorkerInit || optionsError) | ||
return; | ||
} | ||
if (!Object.prototype.hasOwnProperty.call(workerOptions, 'paymentAddress')) { | ||
console.error('use-dcp-worker: workerOptions must contain a paymentAddress.'); | ||
return; | ||
} | ||
hasStartedWorkerInit = true; | ||
let workerId = identity; | ||
if (!workerId) | ||
workerId = yield new window.dcp.wallet.Keystore(null, false); | ||
let dcpWorker; | ||
try { | ||
delete workerOptions.shouldStopWorkingImmediately; | ||
dcpWorker = new window.dcp.worker.Worker(workerId, workerOptions); | ||
dispatchWorkerState({ type: 'WORKER_LOADED_TRUE' }); | ||
setWorkerOptionsState(workerOptions); | ||
} | ||
catch (error) { | ||
console.error('use-dcp-worker: something went wrong in the wallet.Worker constructor.', error); | ||
return error; | ||
} | ||
// DCP Worker constructor | ||
const dcpWorker = new window.dcp.worker.Worker(workerId, workerOptions); | ||
// Attach listeners | ||
@@ -375,3 +413,3 @@ dcpWorker.on('sandbox', (sandbox) => { | ||
dispatchWorkerState({ | ||
type: 'SET_WORKING_SANDBOXES', | ||
type: WorkerStateActions.SET_WORKER_SBX, | ||
data: dcpWorker.workingSandboxes.length, | ||
@@ -382,3 +420,3 @@ }); | ||
dispatchWorkerStats({ | ||
type: 'INCREMENT_COMPUTE_TIME', | ||
type: WorkerStatsActions.ADD_COMPUTE_TIME, | ||
data: measurements.elapsed, // seconds | ||
@@ -389,9 +427,9 @@ }); | ||
dcpWorker.on('payment', (payment) => { | ||
dispatchWorkerStats({ type: 'INCREMENT_SLICES' }); | ||
dispatchWorkerStats({ type: WorkerStatsActions.ADD_SLICE }); | ||
dispatchWorkerStats({ | ||
type: 'INCREMENT_CREDITS', | ||
type: WorkerStatsActions.ADD_CREDITS, | ||
data: payment, | ||
}); | ||
dispatchWorkerState({ | ||
type: 'SET_WORKING_SANDBOXES', | ||
type: WorkerStateActions.SET_WORKER_SBX, | ||
data: dcpWorker.workingSandboxes.length, | ||
@@ -401,25 +439,27 @@ }); | ||
dcpWorker.on('beforeFetch', () => { | ||
dispatchWorkerState({ type: 'FETCHING_TRUE' }); | ||
dispatchWorkerState({ type: WorkerStateActions.FETCHING_TRUE }); | ||
}); | ||
dcpWorker.on('fetch', (payload) => { | ||
if (payload instanceof Error) | ||
return dispatchWorkerState({ type: 'ERROR', data: payload }); | ||
return dispatchWorkerState({ type: WorkerStateActions.ERROR, data: payload }); | ||
// extra delay for cleaner UI visual updates between quick fetching states | ||
setTimeout(() => { | ||
dispatchWorkerState({ type: 'FETCHING_FALSE' }); | ||
dispatchWorkerState({ type: WorkerStateActions.FETCHING_FALSE }); | ||
}, 1000); | ||
}); | ||
dcpWorker.on('beforeReturn', () => { | ||
dispatchWorkerState({ type: 'SUBMITTING_TRUE' }); | ||
dispatchWorkerState({ type: WorkerStateActions.SUBMIT_TRUE }); | ||
}); | ||
dcpWorker.on('result', (payload) => { | ||
if (payload instanceof Error) | ||
return dispatchWorkerState({ type: 'ERROR', data: payload }); | ||
dispatchWorkerState({ type: 'SUBMITTING_FALSE' }); | ||
return dispatchWorkerState({ type: WorkerStateActions.ERROR, data: payload }); | ||
dispatchWorkerState({ type: WorkerStateActions.SUBMIT_FALSE }); | ||
}); | ||
dcpWorker.on('start', () => { | ||
dispatchWorkerState({ type: 'WORKING_TRUE' }); | ||
dispatchWorkerState({ type: WorkerStateActions.WILL_WORK_TRUE }); | ||
dispatchWorkerState({ type: WorkerStateActions.WORKING_TRUE }); | ||
}); | ||
dcpWorker.on('stop', () => { | ||
dispatchWorkerState({ type: 'WORKING_FALSE' }); | ||
dispatchWorkerState({ type: WorkerStateActions.WILL_WORK_FALSE }); | ||
dispatchWorkerState({ type: WorkerStateActions.WORKING_FALSE }); | ||
}); | ||
@@ -429,25 +469,11 @@ setWorker(dcpWorker); | ||
} | ||
if (worker === null) { | ||
initializeWorker(); | ||
} | ||
initializeWorker(); | ||
return () => { | ||
// might need to unhook dcpWorker listeners | ||
}; | ||
}, [ | ||
identity, | ||
}); | ||
return { | ||
worker, | ||
setWorker, | ||
dispatchWorkerState, | ||
dispatchWorkerStats, | ||
setWorkerOptionsState, | ||
]); | ||
return { | ||
workerState, | ||
workerStatistics, | ||
setWorkerOptions, | ||
startWorker, | ||
stopWorker, | ||
toggleWorker, | ||
workerOptionsState, | ||
sandboxes: worker ? worker.workingSandboxes : [], | ||
}; | ||
@@ -454,0 +480,0 @@ }; |
{ | ||
"name": "use-dcp-worker", | ||
"version": "1.0.4", | ||
"version": "2.0.0", | ||
"description": "Embed the DCP Worker in a React app", | ||
"keywords": [ | ||
"dcp", | ||
"dcp-worker", | ||
"react" | ||
], | ||
"homepage": "https://github.com/Distributive-Network/use-dcp-worker#readme", | ||
"bugs": { | ||
"url": "https://github.com/Distributive-Network/use-dcp-worker/issues" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/Distributive-Network/use-dcp-worker.git" | ||
}, | ||
"license": "MIT", | ||
"author": "Kirill Kirnichansky <kirill@distributive.network>", | ||
"contributors": [ | ||
"Robert Mirandola <robert@distributive.network>" | ||
], | ||
"main": "lib/useDCPWorker.js", | ||
@@ -18,20 +36,5 @@ "directories": { | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/Distributive-Network/useDCPWorker.git" | ||
"dependencies": { | ||
"bignumber.js": "^9.1.1" | ||
}, | ||
"keywords": [ | ||
"dcp", | ||
"dcp-worker", | ||
"react" | ||
], | ||
"author": "Robert Mirandola <robert@distributive.network>", | ||
"contributors": [ | ||
"Kirill Kirnichansky <kirill@kingsds.network>" | ||
], | ||
"license": "ISC", | ||
"bugs": { | ||
"url": "https://github.com/Distributive-Network/useDCPWorker/issues" | ||
}, | ||
"homepage": "https://github.com/Distributive-Network/useDCPWorker#readme", | ||
"devDependencies": { | ||
@@ -45,5 +48,11 @@ "@types/react": "^18.0.28", | ||
}, | ||
"dependencies": { | ||
"bignumber.js": "^9.1.1" | ||
"peerDependencies": { | ||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0", | ||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" | ||
}, | ||
"packageManager": "npm@9.6.7", | ||
"engines": { | ||
"node": ">=16", | ||
"npm": ">=7" | ||
} | ||
} |
103
README.md
@@ -41,10 +41,5 @@ # use-dcp-worker | ||
const { | ||
worker, | ||
workerState, | ||
workerStatistics, | ||
workerOptionsState, | ||
setWorkerOptions, | ||
startWorker, | ||
stopWorker, | ||
toggleWorker, | ||
sandboxes, | ||
} = useDCPWorker({ | ||
@@ -60,10 +55,13 @@ workerOptions: { | ||
- `useLocalStorage?: boolean = true`: A flag to toggle the use of the browser's local storage. The `workerOptions` object is the entity to be saved to local storage and is updated accordingly when calling `setWorkerOptions`. | ||
- `workerOptions: object`: This object is supplied to the Worker constructor as the `workerOptions` parameter (required). The only required property of the `workerOptions` object needed to provide is a `paymentAddress`. The rest of the properties will get default values. | ||
- `workerOptions: object`: The contents of this object will override the default values coming from the worker configuration coming from `dcpConfig`(provided by `dcp-client`, worker node sourced from `dcp-worker`). The only required property of `workerOptions` is `paymentAddress`. The following properties describe the worker options object configuring the DCP Worker: | ||
- `paymentAddress: string | Address | Keystore`: A string, Address (`dcp.wallet.Address`) or Keystore (`dcp.wallet.Keystore`) identifying a DCP Bank Account to deposit earned DCCs. | ||
__Note:__ Passing an invalid `paymentAddress` will be logged to the console but will not cause the hook to throw an error. The Worker will not be constructed (`worker === undefined`) and the hook will retry construction/initialization on each re-render. | ||
- `trustComputeGroupOrigins?: boolean = true`: Trust the scheduler to tell client about allowed origins for jobs in a compute group. | ||
- `allowOrigins?: object`: Allow list permitting network access beyond DCP messages to services. | ||
- `any: []`: A list of origins that are safe to communicate with. | ||
- `fetchWorkFunctions: []`: A list of work function URIs that are safe to communicate with. | ||
- `fetchArguments: []`: A list of argument datum URIs that are safe to communicate with. | ||
- `fetchData: []`: A list of input datum URIs that are safe to communicate with. | ||
- `sendResults: []`: A list of URIs that are safe to send job results to. | ||
- `allowOrigins?: object`: Allow list permitting network access beyond DCP messages to services. This list is used only in setting up the DCP Worker. After the worker is constructed/loaded, the `originManager` is responsible for managing origins (see Managing Origins). It's empty by default. | ||
- `any: []`: A list of origins that are allowed to communicate with, for all purposes. | ||
- `fetchWorkFunctions: []`: A list of origins that are allowed to fetch the work function from. | ||
- `fetchArguments: []`: A list of origins that are allowed to fetch work function arguments from. | ||
- `fetchData: []`: A list of origins that are allowed to fetch input set data from. | ||
- `sendResults: []`: A list of origins that are allowed to send results to. | ||
- `minimumWage?: object`: The minimum payout per slice the worker will accept from a job. Will default with the following structure: | ||
@@ -75,5 +73,5 @@ - `CPU: number = 0` | ||
- `computeGroups?: []`: List of compute groups the worker is in and the authorization to join them. A compute group is to be described as `{ joinKey: 'exampleGroup', joinSecret: 'password' }`. | ||
- `leavePublicGroup?: boolean = false`: A flag that controls if the worker should omit fetching work from the public compute group. If not defined, this flag is evaluated to _false_. | ||
- `jobAddresses?: []`: If populated, worker will only fetch slices from jobs corresponding to the job addresses in this list. | ||
- `maxWorkingSandboxes?: integer | undefined`: Maximum number of sandboxes allowed to do work. If `undefined`, then the Supervisor will determine a safe limit, based off of machine hardware. | ||
- `paymentAddress: Keystore | Address | String`: A Keystore or Address (`dcp.wallet.Address`) identifying a DCP Bank Account to deposit earned DCCs. An address string can also be supplied. | ||
- `maxWorkingSandboxes?: number | undefined`: Maximum number of sandboxes allowed to do work. If `undefined`, then the Supervisor will determine a safe limit, based off of machine hardware. | ||
- `shouldStopWorkerImmediately?: boolean`: If true, when the worker is called to stop, it will terminate all working sandboxes without waiting for them to finish. If false, the worker will wait for all sandboxes to finish computing before terminating. | ||
@@ -84,27 +82,64 @@ | ||
## Returns | ||
The `useDCPWorker` hook returns an object with the following properties: | ||
- `workerState: object`: Stores status of worker states. | ||
- `isLoaded: boolean`: True once worker is properly initialized. | ||
- `working: boolean`: True if worker is doing work, false otherwise. | ||
- `willWork: boolean`: True when worker is starting to do work, false when worker is stopping. | ||
- `fetching: boolean`: True when the worker is fetching for slices to compute. | ||
- `submitting: boolean`: True when the worker is submitting results to the scheduler. | ||
- `error: Error | boolean`: Set when a worker has occured, false otherwise. | ||
This hook returns an object with the following properties: | ||
- `worker: Worker`: Refer to the [Worker API documentation](https://docs.dcp.dev/specs/worker-api.html). | ||
- `workerState: object`: Stores status of worker states. Stored globally and preseved between component updates. | ||
- `isLoaded: boolean`: True once the worker is properly initialized. | ||
- `working: boolean`: True if the worker is doing work, false otherwise. | ||
- `willWork: boolean`: True when the worker is starting to do work, false when the worker is stopping. | ||
- `fetching: boolean`: True when the worker is fetching for slices to compute, false otherwise. | ||
- `submitting: boolean`: True when the worker is submitting results to the scheduler, false otherwise. | ||
- `error: Error | boolean`: Set when a worker error has occured, false otherwise. | ||
- `workingSandboxes: number`: Number of sandboxes currently doing work. | ||
- `workerStatistics: object`: Stores a global count of worker statistics for a browser session. | ||
- `workerStatistics: object`: Stores a global count of worker statistics for a browser session. Stored globally and preseved between component updates. | ||
- `slices: number`: Number of slices completed. | ||
- `credits: BigNumber`: Total credits earned. | ||
- `computeTime: number`: Total time computed in seconds. | ||
- `workerOptionsState: object`: Refer to `workerOptions` in Parameters. This is to be treated as a read-only object, mutating it will not update worker options. | ||
- `sandboxes: object`: List of Sandbox objects of sandboxes currently working. Sandbox objects consist of the properties: `id`, `isWorking`, `public`, `sliceStartTime`, and `progress`. | ||
- `setWorkerOptions: function`: This method updates the `workerOptions` object. The method accepts an object as a parameter and does a leaf merge on the original `workerOptions` object, however, only on the first layer of properties. For example, `setWorkerOptions({ paymentAddress: 'some address' })` will only update the `paymentAddress` property of `workerOptions` and preserve the rest of the object. `setWorkerOptions({ allowOrigins: { any: ['origin'] } })` will update the entirety of `allowOrigins` instead of just `allowOrigins.any`. | ||
- `startWorker: function`: This method starts the worker. | ||
- `stopWorker: function`: This method stops the worker. | ||
- `toggleWorker: function`: This method starts/stops the worker. | ||
- `computeTime: number`: Total time computed (ms). | ||
# Changelog | ||
## 1.0.0 | ||
- initial release. | ||
Note: Learn more about `Sandbox` in our [Sandbox API](https://docs.dcp.dev/specs/worker-api.html#sandbox-api) & [Compute API](https://docs.dcp.dev/specs/compute-api.html#definitions) docs. | ||
## How it works | ||
The Worker requires an options object for configuration. This hook passes in `dcpConfig.worker` defined in the global scope, with options passed to the hook and those saved in local storage overwritten on top, straight to the constructor. The hook was written to handle multiple insances of the hook defined in a React application, ensuring a single instance of a Worker is used/instanciated (including between component updates) - achieved using React state management. Once a Worker is instantiated, it is in a global context state that all instances of the hook will reference. The state and statistics the hook provides, `workerState` and `workerStatistics`, is also handled in a global React state context. Custom handling of state and statistics can always be achieved using the `worker` returned with an understanding of the Worker API and Worker events. | ||
## Editing Worker Options | ||
As part of the [Worker API](https://docs.dcp.dev/specs/worker-api.html), the `worker` has a `workerOptions` property that describe the current active options configuring the worker, and mutating this object will modify worker options. | ||
__Note:__ To achieve desired React component updates regarding changes to certain options that may be featured in a UI, such as _paymentAddress_ - `worker.workerOptions` is a Proxy object with custom handling to handle component updates and writes to local storage. | ||
## Managing Origins | ||
The `worker` returned has the `originManager` property, which is an instance of the `OriginAccessManager` class responsible for managing the worker's allowed origins. `originManager` is `undefined` until the worker is properly initialized. | ||
Upon construction of the worker, the worker options `allowOrigins` property is read into the construction of the `OriginAccessManager`. Properties of `allowOrigins` translate to a _purpose_ on the OAM, with their values, being a list of origins, are added under that _purpose_ (and `null` _key_). The `any` property translates to a `null` _purpose_, which matches any _purpose_. For example, `isAllowed` will return `true` for origins stored under a `null` _purpose_, regardless the _purpose_ and _key_ combination queried. | ||
### OriginAccessManager Methods | ||
- `add(origin, purpose, key):` Adds (allows) the _origin_ under the _purpose_ and _key_. A `null` _purpose_ and/or _key_ will match any _purpose_ and/or _key_, respectively. | ||
- `remove(origin, purpose, key):` Removes (un-allows) the _origin_ under the _purpose_ and _key_. A `null` purpose and/or key will __not__ match any _purpose_ and/or _key_. | ||
- `remove_byKey(key):` Removes all origins for all purposes under the _key_. A `null` _key_ is not accepted, must be a string. | ||
- `getAllowList(purpose, key):` returns a list of origins under the _purpose_ and _key_. A `null` _purpose_ is not accepted, must be a string. Previously added origins under `null` _purpose_ and/or _key_ will match any _purpose_ and/or _key_, respectively. | ||
- `isAllowed(origin, purpose, key):` returns `true` if _origin_ is allowed under the _purpose_ and _key_. A `null` _purpose_ is not accepted, must be a string. Previously added origins under `null` _purpose_ and/or _key_ will match any _purpose_ and/or _key_, respectively. | ||
# Change log | ||
- __2.0.0__ - Aug 2023 | ||
- now returns the worker | ||
- proper handling of race-condition in constructing the worker when/if the hook is executed multiple times at once | ||
- workerOptions passed to worker constructor is a Proxy now | ||
- needed reflect changes to paymentAddress and maxWorkingSandbox props to local storage and trigger component update/re-render | ||
- removed workerState.isLoaded since now we are returning the worker | ||
- !isLoaded === !worker | ||
- removed workerOptionsState, start/stop/toggleWorker, sandboxes, originManager | ||
- these are all accessible via Worker API | ||
- removed applyWorkerOptions since editing workerOptions should be done by directly mutating worker.workerOptions | ||
- improved error handling | ||
- quality of life improvements | ||
- __1.0.4__ - Aug 3, 2023 | ||
- compatibility update for new Worker events from dcp-client | ||
- __1.0.2__ - Mar 23, 2023 | ||
- local storage will only save paymentAddress and maxWorkingSandbox props | ||
- workerOptions source always coming from dcpConfig | ||
- added delay between quick worker fetching states | ||
- Quality of life + maintainability improvements | ||
- __1.0.1__ - Mar 1, 2023 | ||
- Added src/ directory to published files to support source map | ||
- __1.0.0__ - Feb 28, 2023 | ||
- Inital Release | ||
# License | ||
Please refer to the [LICENSE](LICENSE) file for more information. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
71076
0
142
3
1185