/**
* @author Michael Hasenstein <hasenstein@yahoo.com>
* @copyright REFINIO GmbH 2017
* @license CC-BY-NC-SA-2.5; portions MIT License
* @version 0.0.1
*/
/**
* This module creates the initial {@link Instance} and {@link Person} (for the instance owner)
* objects.
* @module
*/
/**
* @typedef {object} InstanceUpdateOptions
* @property {string} name - The name given to the instance is part of the instance ID
* @property {string} email - The uniquely identifying email address of the instance owner
* @property {string} [ownerName] - The name of the owner (optional)
* @property {KeyPair} [personEncryptionKeyPair] - Encryption keypair used for instance owner.
* If provided, **all four keys** must be provided together. In that case no new keys will be
* created upon instance creation, instead these keys will be used. The keys are only used for a
* new instance. If they are provided but an instance already exists nothing will happen.
* @property {SignKeyPair} [personSignKeyPair] - Sign keypair used for instance owner.
* Also see the description for `personEncryptionKeyPair`.
* @property {KeyPair} [instanceEncryptionKeyPair] - Encryption keypair used for instance.
* Also see the description for `personEncryptionKeyPair`.
* @property {SignKeyPair} [instanceSignKeyPair] - Sign keypair used for instance.
* Also see the description for `personEncryptionKeyPair`.
* @property {string} secret - A secret known to the owner. It is used to derive a key to
* encrypt the private keys created (only!) during initial instance creation. The string will be
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize|normalized}.
* @property {Recipe[]} [recipes=[]]
* @property {Map<OneObjectTypeNames,undefined|string[]>} [enabledReverseMapTypes=new Map()]
* @property {Map<OneObjectTypeNames,undefined|string[]>}
* [enabledReverseMapTypesForIdObjects=new Map()]
*/
export interface InstanceCreatorOptions {
name: InstanceOptions['name'];
email: string;
ownerName?: string;
personEncryptionKeyPair?: KeyPair;
personSignKeyPair?: SignKeyPair;
instanceEncryptionKeyPair?: KeyPair;
instanceSignKeyPair?: SignKeyPair;
initialRecipes: InstanceOptions['initialRecipes'];
initiallyEnabledReverseMapTypes: InstanceOptions['initiallyEnabledReverseMapTypes'];
initiallyEnabledReverseMapTypesForIdObjects: InstanceOptions['initiallyEnabledReverseMapTypesForIdObjects'];
}
import {addRecipesToRuntimeAndStorage} from './crdt-recipes';
import type {KeyPair} from './crypto/encryption';
import type {SignKeyPair} from './crypto/sign';
import {createError} from './errors';
import type {InstanceOptions} from './instance';
import {createDefaultKeysIfNotExist} from './keychain/keychain';
import type {Instance} from './recipes';
import type {VersionedObjectResult} from './storage-versioned-objects';
import {storeVersionedObject} from './storage-versioned-objects';
/**
* Use this module through {@link instance.module:ts.registerRecipes|instance.registerRecipes} for
* your convenience. It provides the `name` and `owner` from the active instance
* and registers any given {@link Recipe|Recipes} with the currently running instance, which
* this function does not do since you could create or update an inactive instance.
* Note that recipes are *not* added to the runtime, since you might be updating an inactive
* Instance object.
* @static
* @async
* @param {InstanceUpdaterOptions} options
* @returns {Promise<ObjectCreation[]>} Returns the result of creating the Instance object and,
* if provided Recipe and Module objects. The Instance object creation result always is in the
* first position and always exists. The {@link Person} object creation result is in second
* place, but it only exists if the Person object had to be created. {@link Recipe} and
* {@link Module} creation results only exist if any recipes or modules were provided.
*/
export async function createInstance({
name,
email,
ownerName,
personEncryptionKeyPair,
personSignKeyPair,
instanceEncryptionKeyPair,
instanceSignKeyPair,
initialRecipes = [],
initiallyEnabledReverseMapTypes = new Map(),
initiallyEnabledReverseMapTypesForIdObjects = new Map()
}: InstanceCreatorOptions): Promise<VersionedObjectResult<Instance>> {
if (
// If any key is provided...
(personEncryptionKeyPair !== undefined || personSignKeyPair !== undefined) &&
// ... then *all* must be provided.
(personEncryptionKeyPair === undefined || personSignKeyPair === undefined)
) {
throw createError('INCR-WPK2', {
iName: name,
encType: typeof personEncryptionKeyPair,
sigType: typeof personSignKeyPair
});
}
if (
// If any key is provided...
(instanceEncryptionKeyPair !== undefined || instanceSignKeyPair !== undefined) &&
// ... then *all* must be provided.
(instanceEncryptionKeyPair === undefined || instanceSignKeyPair === undefined)
) {
throw createError('INCR-WPK3', {
iName: name,
encType: typeof instanceEncryptionKeyPair,
sigType: typeof instanceSignKeyPair
});
}
// ----------------------------------------------------
// PERSON (instance owner)
// ----------------------------------------------------
const owner = await storeVersionedObject({
$type$: 'Person',
email,
name: ownerName
});
await createDefaultKeysIfNotExist(owner.idHash, personEncryptionKeyPair, personSignKeyPair);
// ----------------------------------------------------
// INSTANCE
// ----------------------------------------------------
const recipeResults = await addRecipesToRuntimeAndStorage(initialRecipes);
const instance = await storeVersionedObject({
$type$: 'Instance',
name,
owner: owner.idHash,
recipe: recipeResults.map(r => r.hash),
module: [],
enabledReverseMapTypes: initiallyEnabledReverseMapTypes,
enabledReverseMapTypesForIdObjects: initiallyEnabledReverseMapTypesForIdObjects
} as Instance);
await createDefaultKeysIfNotExist(
instance.idHash,
instanceEncryptionKeyPair,
instanceSignKeyPair
);
return instance;
}