mirror of
https://github.com/orbitdb/orbitdb.git
synced 2025-03-30 15:08:28 +00:00
refactor: Functionize identity provider. (#74)
* refactor: Functionize identity provider. * docs: Building a custom identity provider. * refactor: Functionize clock.
This commit is contained in:
parent
60fbe47ee3
commit
794136c762
@ -47,7 +47,7 @@ const type = 'ipfs'
|
||||
* @param {Array} [params.write] An array of identity ids who can write to the
|
||||
* database.
|
||||
* @param {module:Storage} [params.storage] An instance of a compatible storage.
|
||||
* @returns {module:AccessControllers.AccessControllers-IPFS} An
|
||||
* @return {module:AccessControllers.AccessControllers-IPFS} An
|
||||
* IPFSAccessController function.
|
||||
* @memberof module:AccessControllers
|
||||
*/
|
||||
@ -70,7 +70,7 @@ const IPFSAccessController = ({ write, storage } = {}) => async ({ orbitdb, iden
|
||||
/**
|
||||
* Verifies the write permission of an entry.
|
||||
* @param {module:Log~Entry} entry An entry to verify.
|
||||
* @returns {boolean} True if the entry's identity has write permission,
|
||||
* @return {boolean} True if the entry's identity has write permission,
|
||||
* false otherwise.
|
||||
* @memberof module:AccessControllers.AccessControllers-IPFS
|
||||
*/
|
||||
|
@ -27,7 +27,7 @@ const type = 'orbitdb'
|
||||
* IPFSAccessController.
|
||||
* @param {Array} [params.write] An array of identity ids who can write to the
|
||||
* database.
|
||||
* @returns {module:AccessControllers.AccessControllers-OrbitDB} An
|
||||
* @return {module:AccessControllers.AccessControllers-OrbitDB} An
|
||||
* IPFSAccessController function.
|
||||
* @memberof module:AccessControllers
|
||||
*/
|
||||
@ -42,7 +42,7 @@ const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities
|
||||
/**
|
||||
* Verifies the write permission of an entry.
|
||||
* @param {module:Log~Entry} entry An entry to verify.
|
||||
* @returns {boolean} True if the entry's identity has write permission,
|
||||
* @return {boolean} True if the entry's identity has write permission,
|
||||
* false otherwise.
|
||||
* @memberof module:AccessControllers.AccessControllers-OrbitDB
|
||||
* @instance
|
||||
@ -68,7 +68,7 @@ const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities
|
||||
*
|
||||
* The returned capabilities will be a mixture of admin and write access
|
||||
* addresses.
|
||||
* @returns {Array} A list of addresses with admin and write access.
|
||||
* @return {Array} A list of addresses with admin and write access.
|
||||
* @memberof module:AccessControllers.AccessControllers-OrbitDB
|
||||
* @instance
|
||||
*/
|
||||
@ -98,7 +98,7 @@ const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities
|
||||
/**
|
||||
* Gets a list of addresses with the specified capability.
|
||||
* @param {string} capability A capability (e.g. write).
|
||||
* @returns {Array} One or more addresses with the spcified capability.
|
||||
* @return {Array} One or more addresses with the spcified capability.
|
||||
* @memberof module:AccessControllers.AccessControllers-OrbitDB
|
||||
* @instance
|
||||
*/
|
||||
@ -120,7 +120,7 @@ const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities
|
||||
* Checks whether an address has a capability.
|
||||
* @param {string} capability A capability (e.g. write).
|
||||
* @param {string} key An address.
|
||||
* @returns {boolean} True if the address has the capability, false
|
||||
* @return {boolean} True if the address has the capability, false
|
||||
* otherwise.
|
||||
* @memberof module:AccessControllers.AccessControllers-OrbitDB
|
||||
* @instance
|
||||
|
@ -61,7 +61,7 @@ const parseAddress = (address) => {
|
||||
* Creates an instance of OrbitDBAddress.
|
||||
* @function
|
||||
* @param {OrbitDBAddress|string} address A valid OrbitDB database address.
|
||||
* @returns {OrbitDBAddress} An instance of OrbitDBAddress.
|
||||
* @return {OrbitDBAddress} An instance of OrbitDBAddress.
|
||||
* @instance
|
||||
*/
|
||||
const OrbitDBAddress = (address) => {
|
||||
@ -89,7 +89,7 @@ const OrbitDBAddress = (address) => {
|
||||
/**
|
||||
* Returns OrbitDBAddress as a string.
|
||||
* @function
|
||||
* @returns {string} The string form of OrbitDBAddress.
|
||||
* @return {string} The string form of OrbitDBAddress.
|
||||
* @memberof module:Address~OrbitDBAddress
|
||||
*/
|
||||
const toString = () => {
|
||||
|
@ -54,7 +54,7 @@ const DefaultOptions = { indexBy: '_id' }
|
||||
* Defines a Documents database.
|
||||
* @param {Object} options Various options for configuring the Document store.
|
||||
* @param {string} [params.indexBy=_id] An index.
|
||||
* @returns {module:Database.Database-Documents} A Documents function.
|
||||
* @return {module:Database.Database-Documents} A Documents function.
|
||||
* @memberof module:Database
|
||||
*/
|
||||
const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => {
|
||||
@ -66,7 +66,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add
|
||||
* Stores a document to the store.
|
||||
* @function
|
||||
* @param {Object} doc An object representing a key/value list of fields.
|
||||
* @returns {string} The hash of the new oplog entry.
|
||||
* @return {string} The hash of the new oplog entry.
|
||||
* @memberof module:Database.Database-Documents
|
||||
* @instance
|
||||
*/
|
||||
@ -82,7 +82,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add
|
||||
* Deletes a document from the store.
|
||||
* @function
|
||||
* @param {string} key The key of the doc to delete.
|
||||
* @returns {string} The hash of the new oplog entry.
|
||||
* @return {string} The hash of the new oplog entry.
|
||||
* @memberof module:Database.Database-Documents
|
||||
* @instance
|
||||
*/
|
||||
@ -96,7 +96,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add
|
||||
* Gets a document from the store by key.
|
||||
* @function
|
||||
* @param {string} key The key of the doc to get.
|
||||
* @returns {Object} The doc corresponding to key or null.
|
||||
* @return {Object} The doc corresponding to key or null.
|
||||
* @memberof module:Database.Database-Documents
|
||||
* @instance
|
||||
*/
|
||||
@ -113,7 +113,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add
|
||||
* @function
|
||||
* @param {function(Object)} findFn A function for querying for specific
|
||||
* results.
|
||||
* @returns {Array} Found documents.
|
||||
* @return {Array} Found documents.
|
||||
* @memberof module:Database.Database-Documents
|
||||
* @instance
|
||||
*/
|
||||
@ -132,8 +132,8 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add
|
||||
/**
|
||||
* Iterates over documents.
|
||||
* @function
|
||||
* @params {Object} [filters={}] Various filters to apply to the iterator.
|
||||
* @params {string} [filters.amount=-1] The number of results to fetch.
|
||||
* @param {Object} [filters={}] Various filters to apply to the iterator.
|
||||
* @param {string} [filters.amount=-1] The number of results to fetch.
|
||||
* @yields [string, string, string] The next document as hash/key/value.
|
||||
* @memberof module:Database.Database-Documents
|
||||
* @instance
|
||||
@ -160,7 +160,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add
|
||||
/**
|
||||
* Returns all documents.
|
||||
* @function
|
||||
* @returns [][string, string, string] An array of documents as hash/key
|
||||
* @return [][string, string, string] An array of documents as hash/key
|
||||
* value entries.
|
||||
* @memberof module:Database.Database-Documents
|
||||
* @instance
|
||||
|
@ -37,7 +37,7 @@ import Database from '../database.js'
|
||||
|
||||
/**
|
||||
* Defines an Events database.
|
||||
* @returns {module:Database.Database-Events} A Events function.
|
||||
* @return {module:Database.Database-Events} A Events function.
|
||||
* @memberof module:Database
|
||||
*/
|
||||
const Events = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => {
|
||||
@ -49,7 +49,7 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory,
|
||||
* Adds an event to the store.
|
||||
* @function
|
||||
* @param {*} value The event to be added.
|
||||
* @returns {string} The hash of the new oplog entry.
|
||||
* @return {string} The hash of the new oplog entry.
|
||||
* @memberof module:Database.Database-Events
|
||||
* @instance
|
||||
*/
|
||||
@ -61,7 +61,7 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory,
|
||||
* Gets an event from the store by hash.
|
||||
* @function
|
||||
* @param {string} hash The hash of the event to get.
|
||||
* @returns {*} The value corresponding to hash or null.
|
||||
* @return {*} The value corresponding to hash or null.
|
||||
* @memberof module:Database.Database-Events
|
||||
* @instance
|
||||
*/
|
||||
@ -73,16 +73,16 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory,
|
||||
/**
|
||||
* Iterates over events.
|
||||
* @function
|
||||
* @params {Object} [filters={}] Various filters to apply to the iterator.
|
||||
* @params {string} [filters.gt] All events which are greater than the
|
||||
* @param {Object} [filters={}] Various filters to apply to the iterator.
|
||||
* @param {string} [filters.gt] All events which are greater than the
|
||||
* given hash.
|
||||
* @params {string} [filters.gte] All events which are greater than or equal
|
||||
* @param {string} [filters.gte] All events which are greater than or equal
|
||||
* to the given hash.
|
||||
* @params {string} [filters.lt] All events which are less than the given
|
||||
* @param {string} [filters.lt] All events which are less than the given
|
||||
* hash.
|
||||
* @params {string} [filters.lte] All events which are less than or equal to
|
||||
* @param {string} [filters.lte] All events which are less than or equal to
|
||||
* the given hash.
|
||||
* @params {string} [filters.amount=-1] The number of results to fetch.
|
||||
* @param {string} [filters.amount=-1] The number of results to fetch.
|
||||
* @yields [string, string] The next event as hash/value.
|
||||
* @memberof module:Database.Database-Events
|
||||
* @instance
|
||||
@ -99,7 +99,7 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory,
|
||||
/**
|
||||
* Returns all events.
|
||||
* @function
|
||||
* @returns [][string, string] An array of events as hash/value entries.
|
||||
* @return [][string, string] An array of events as hash/value entries.
|
||||
* @memberof module:Database.Database-Events
|
||||
* @instance
|
||||
*/
|
||||
|
@ -54,7 +54,7 @@ const valueEncoding = 'json'
|
||||
* @param {Object} options Various options for configuring the KeyValueIndexed
|
||||
* store.
|
||||
* @param {module:Storage} [storage=LevelStorage] A compatible storage.
|
||||
* @returns {module:Database.Database-KeyValueIndexed} A KeyValueIndexed
|
||||
* @return {module:Database.Database-KeyValueIndexed} A KeyValueIndexed
|
||||
* function.
|
||||
* @memberof module:Database
|
||||
*/
|
||||
@ -93,7 +93,7 @@ const KeyValueIndexed = ({ storage } = {}) => async ({ ipfs, identity, address,
|
||||
* Gets a value from the store by key.
|
||||
* @function
|
||||
* @param {string} key The key of the value to get.
|
||||
* @returns {*} The value corresponding to key or null.
|
||||
* @return {*} The value corresponding to key or null.
|
||||
* @memberof module:Database.Database-KeyValueIndexed
|
||||
* @instance
|
||||
*/
|
||||
@ -108,8 +108,8 @@ const KeyValueIndexed = ({ storage } = {}) => async ({ ipfs, identity, address,
|
||||
/**
|
||||
* Iterates over keyvalue pairs.
|
||||
* @function
|
||||
* @params {Object} [filters={}] Various filters to apply to the iterator.
|
||||
* @params {string} [filters.amount=-1] The number of results to fetch.
|
||||
* @param {Object} [filters={}] Various filters to apply to the iterator.
|
||||
* @param {string} [filters.amount=-1] The number of results to fetch.
|
||||
* @yields [string, string, string] The next key/value as key/value/hash.
|
||||
* @memberof module:Database.Database-KeyValueIndexed
|
||||
* @instance
|
||||
|
@ -37,7 +37,7 @@ import Database from '../database.js'
|
||||
|
||||
/**
|
||||
* Defines an KeyValue database.
|
||||
* @returns {module:Database.Database-KeyValue} A KeyValue function.
|
||||
* @return {module:Database.Database-KeyValue} A KeyValue function.
|
||||
* @memberof module:Database
|
||||
*/
|
||||
const KeyValue = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => {
|
||||
@ -50,7 +50,7 @@ const KeyValue = () => async ({ ipfs, identity, address, name, access, directory
|
||||
* @function
|
||||
* @param {string} key The key to store.
|
||||
* @param {*} value The value to store.
|
||||
* @returns {string} The hash of the new oplog entry.
|
||||
* @return {string} The hash of the new oplog entry.
|
||||
* @memberof module:Database.Database-KeyValue
|
||||
* @instance
|
||||
*/
|
||||
@ -73,7 +73,7 @@ const KeyValue = () => async ({ ipfs, identity, address, name, access, directory
|
||||
* Gets a value from the store by key.
|
||||
* @function
|
||||
* @param {string} key The key of the value to get.
|
||||
* @returns {*} The value corresponding to key or null.
|
||||
* @return {*} The value corresponding to key or null.
|
||||
* @memberof module:Database.Database-KeyValue
|
||||
* @instance
|
||||
*/
|
||||
@ -91,8 +91,8 @@ const KeyValue = () => async ({ ipfs, identity, address, name, access, directory
|
||||
/**
|
||||
* Iterates over keyvalue pairs.
|
||||
* @function
|
||||
* @params {Object} [filters={}] Various filters to apply to the iterator.
|
||||
* @params {string} [filters.amount=-1] The number of results to fetch.
|
||||
* @param {Object} [filters={}] Various filters to apply to the iterator.
|
||||
* @param {string} [filters.amount=-1] The number of results to fetch.
|
||||
* @yields [string, string, string] The next key/value as key/value/hash.
|
||||
* @memberof module:Database.Database-KeyValue
|
||||
* @instance
|
||||
@ -119,7 +119,7 @@ const KeyValue = () => async ({ ipfs, identity, address, name, access, directory
|
||||
/**
|
||||
* Returns all key/value pairs.
|
||||
* @function
|
||||
* @returns [][string, string, string] An array of key/value pairs as
|
||||
* @return [][string, string, string] An array of key/value pairs as
|
||||
* key/value/hash entries.
|
||||
* @memberof module:Database.Database-KeyValue
|
||||
* @instance
|
||||
|
@ -13,7 +13,7 @@ import KeyStore, { signMessage, verifyMessage } from '../key-store.js'
|
||||
import { LRUStorage, IPFSBlockStorage, MemoryStorage, ComposedStorage } from '../storage/index.js'
|
||||
import pathJoin from '../utils/path-join.js'
|
||||
|
||||
const DefaultProviderType = PublicKeyIdentityProvider.type
|
||||
const DefaultProviderType = 'publickey'
|
||||
const DefaultIdentityKeysPath = pathJoin('./orbitdb', 'identities')
|
||||
|
||||
const supportedTypes = {
|
||||
@ -36,7 +36,7 @@ const supportedTypes = {
|
||||
* module.
|
||||
* @param {IPFS} [params.ipfs] An instance of IPFS. This param is not required
|
||||
* if storage is provided.
|
||||
* @returns {module:Identities~Identities} An instance of Identities.
|
||||
* @return {module:Identities~Identities} An instance of Identities.
|
||||
* @instance
|
||||
*/
|
||||
const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
|
||||
@ -58,7 +58,7 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
|
||||
/**
|
||||
* Gets an identity by hash.
|
||||
* @param {string} hash An identity hash.
|
||||
* @returns {Identity} An instance of identity.
|
||||
* @return {Identity} An instance of identity.
|
||||
* @memberof module:Identities~Identities
|
||||
* @instance
|
||||
*/
|
||||
@ -72,8 +72,8 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
|
||||
/**
|
||||
* Creates an identity, adding it to storage.
|
||||
* @param {Object} options Various options for configuring a new identity.
|
||||
* @param {string} [options.type=PublicKeyIdentityProvider.type] The type of provider to use for generating an identity.
|
||||
* @returns {Identity} An instance of identity.
|
||||
* @param {string} [options.type=publickey] The type of provider to use for generating an identity.
|
||||
* @return {Identity} An instance of identity.
|
||||
* @memberof module:Identities~Identities
|
||||
* @instance
|
||||
*/
|
||||
@ -81,8 +81,8 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
|
||||
options.keystore = keystore
|
||||
|
||||
const type = options.type || DefaultProviderType
|
||||
const Provider = getProviderFor(type)
|
||||
const identityProvider = new Provider(options)
|
||||
const Provider = getProviderFor(type).default
|
||||
const identityProvider = Provider(options)
|
||||
const id = await identityProvider.getId(options)
|
||||
const privateKey = await keystore.getKey(id) || await keystore.createKey(id)
|
||||
const publicKey = keystore.getPublic(privateKey)
|
||||
@ -103,7 +103,7 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
|
||||
/**
|
||||
* Verifies an identity using the identity's provider.
|
||||
* @param {Identity} identity The identity to verify.
|
||||
* @returns {boolean} True the identity is valid, false otherwise.
|
||||
* @return {boolean} True the identity is valid, false otherwise.
|
||||
* @memberof module:Identities~Identities
|
||||
*/
|
||||
const verifyIdentity = async (identity) => {
|
||||
@ -137,7 +137,7 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
|
||||
* Signs data using an identity.
|
||||
* @param {Identity} identity The identity to use for signing.
|
||||
* @param {string} data The data to sign.
|
||||
* @returns {string} The signed data.
|
||||
* @return {string} The signed data.
|
||||
* @throws Private signing key not fund from KeyStore when no signing key can
|
||||
* be retrieved.
|
||||
* @memberof module:Identities~Identities
|
||||
@ -170,7 +170,7 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
|
||||
/**
|
||||
* Checks whether an identity provider is supported.
|
||||
* @param {string} type The identity provider type.
|
||||
* @returns {boolean} True if the identity provider is supported, false
|
||||
* @return {boolean} True if the identity provider is supported, false
|
||||
* otherwise.
|
||||
* @static
|
||||
*/
|
||||
@ -181,7 +181,7 @@ const isProviderSupported = (type) => {
|
||||
/**
|
||||
* Gets an identity provider.
|
||||
* @param {string} type The identity provider type.
|
||||
* @returns {IdentityProvider} The IdentityProvider module corresponding to
|
||||
* @return {IdentityProvider} The IdentityProvider module corresponding to
|
||||
* type.
|
||||
* @throws IdentityProvider type is not supported if the identity provider is
|
||||
* not supported.
|
||||
|
@ -23,7 +23,7 @@ const hashStringEncoding = base58btc
|
||||
* @param {string} params.type The type of identity provider.
|
||||
* @param {function} params.sign A sign function.
|
||||
* @param {function} params.verify A verify function.
|
||||
* @returns {module:Identity~Identity} An instance of Identity.
|
||||
* @return {module:Identity~Identity} An instance of Identity.
|
||||
* @throws Identity id is required if id is not provided.
|
||||
* @throws Invalid public key if publicKey is not provided.
|
||||
* @throws Signatures object is required if signature is not provided.
|
||||
@ -62,7 +62,7 @@ const Identity = async ({ id, publicKey, signatures, type, sign, verify } = {})
|
||||
/**
|
||||
* Encode an Identity to a serializable form.
|
||||
* @param {Identity} identity Identity to encode,
|
||||
* @returns {Object} Object with fields hash and bytes.
|
||||
* @return {Object} Object with fields hash and bytes.
|
||||
* @static
|
||||
*/
|
||||
const _encodeIdentity = async (identity) => {
|
||||
@ -76,7 +76,7 @@ const _encodeIdentity = async (identity) => {
|
||||
/**
|
||||
* Decode an Identity from bytes
|
||||
* @param {Uint8Array} bytes Bytes from which to decode an Identity from
|
||||
* @returns {Identity}
|
||||
* @return {Identity}
|
||||
* @static
|
||||
*/
|
||||
const decodeIdentity = async (bytes) => {
|
||||
|
@ -1,4 +1,3 @@
|
||||
/** @module IdentityProviders */
|
||||
export {
|
||||
default as Identities,
|
||||
addIdentityProvider,
|
||||
@ -12,4 +11,4 @@ export {
|
||||
isEqual
|
||||
} from './identity.js'
|
||||
|
||||
export { default as PublicKeyIdentityProvider } from './providers/publickey.js'
|
||||
export { PublicKeyIdentityProvider } from './providers/index.js'
|
||||
|
@ -1,4 +1,52 @@
|
||||
/**
|
||||
* @module IdentityProviders
|
||||
* @description
|
||||
* Identity providers.
|
||||
*
|
||||
* ## Custom Providers
|
||||
*
|
||||
* An identity provider provides a method for signing and verifying an
|
||||
* identity using a particular cryptographic mechanism.
|
||||
*
|
||||
* A custom identity provider can be used provided the module takes the
|
||||
* following form:
|
||||
* ```javascript
|
||||
* // A unique name for the identity provider
|
||||
* const type = 'custom'
|
||||
*
|
||||
* // check whether the identity was signed by the identity's id.
|
||||
* const verifyIdentity = identity => {
|
||||
*
|
||||
* }
|
||||
*
|
||||
* // The identity provider.
|
||||
* const MyCustomIdentityProvider = ({ keystore }) => {
|
||||
* const getId = async ({ id } = {}) => {
|
||||
*
|
||||
* }
|
||||
*
|
||||
* const signIdentity = async (data, { id } = {}) => {
|
||||
*
|
||||
* }
|
||||
*
|
||||
* return {
|
||||
* getId,
|
||||
* signIdentity
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* export { MyCustomIdentityProvider as default, verifyIdentity, type }
|
||||
* ```
|
||||
*
|
||||
* To use it, add it to the list of known identity providers:
|
||||
* ```javascript
|
||||
* import * as MyCustomIdentityProvider from 'my-custom-identity-provider'
|
||||
* addIdentityProvider(MyCustomIdentityProvider)
|
||||
* ```
|
||||
*
|
||||
* where my-custom-identity-provider is the custom module.
|
||||
*/
|
||||
|
||||
// export { default as DIDIdentityProvider } from './did.js'
|
||||
// export { default as EthIdentityProvider } from './ethereum.js'
|
||||
export { default as IdentityProvider } from './interface.js'
|
||||
export { default as PublicKeyIdentityProvider } from './publickey.js'
|
||||
export * as PublicKeyIdentityProvider from './publickey.js'
|
||||
|
@ -1,26 +0,0 @@
|
||||
class IdentityProvider {
|
||||
/* Return id of identity (to be signed by orbit-db public key) */
|
||||
async getId (options) {}
|
||||
|
||||
/* Return signature of OrbitDB public key signature */
|
||||
async signIdentity (data, options) {}
|
||||
|
||||
/* Verify a signature of OrbitDB public key signature */
|
||||
static async verifyIdentity (identity) {}
|
||||
|
||||
/* Return the type for this identity provider */
|
||||
static get type () {
|
||||
throw new Error('\'static get type ()\' needs to be defined in the inheriting class')
|
||||
}
|
||||
|
||||
/*
|
||||
Return the type for this identity-proider
|
||||
NOTE! This is the only property of the interface that
|
||||
shouldn't be overridden in the inherited IdentityProvider
|
||||
*/
|
||||
get type () {
|
||||
return this.constructor.type
|
||||
}
|
||||
}
|
||||
|
||||
export default IdentityProvider
|
@ -1,41 +1,77 @@
|
||||
/**
|
||||
* @namespace module:IdentityProviders.IdentityProviders-PublicKey
|
||||
* @description PublicKey Identity Provider
|
||||
* @module PublicKeyIdentityProvider
|
||||
* @memberof module:IdentityProviders
|
||||
* @description
|
||||
* The PublicKey Identity Provider signs and verifies an identity using the
|
||||
* public key of a private/public key pair.
|
||||
*/
|
||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||
import IdentityProvider from './interface.js'
|
||||
import { signMessage, verifyMessage } from '../../key-store.js'
|
||||
|
||||
/**
|
||||
* The type of identity provider.
|
||||
* @return string
|
||||
* @const
|
||||
*/
|
||||
const type = 'publickey'
|
||||
|
||||
class PublicKeyIdentityProvider extends IdentityProvider {
|
||||
constructor ({ keystore }) {
|
||||
super()
|
||||
/**
|
||||
* Verifies an identity using the identity's id.
|
||||
* @param {module:Identity} identity
|
||||
* @return {boolean} True if the identity is valid, false otherwise.
|
||||
* @static
|
||||
*/
|
||||
const verifyIdentity = identity => {
|
||||
const { id, publicKey, signatures } = identity
|
||||
return verifyMessage(signatures.publicKey, id, publicKey + signatures.id)
|
||||
}
|
||||
|
||||
if (!keystore) {
|
||||
throw new Error('PublicKeyIdentityProvider requires a keystore parameter')
|
||||
}
|
||||
/**
|
||||
* Instantiates the publickey identity provider.
|
||||
* @return {module:IdentityProviders.IdentityProvider-PublicKey} A public key identity provider function.
|
||||
*/
|
||||
const PublicKeyIdentityProvider = ({ keystore }) => {
|
||||
/**
|
||||
* @namespace module:IdentityProviders.IdentityProvider-PublicKey
|
||||
* @memberof module:IdentityProviders
|
||||
* @description The instance returned by {@link module:IdentityProviders.IdentityProvider-PublicKey}.
|
||||
*/
|
||||
|
||||
this._keystore = keystore
|
||||
if (!keystore) {
|
||||
throw new Error('PublicKeyIdentityProvider requires a keystore parameter')
|
||||
}
|
||||
|
||||
// Returns the type of the identity provider
|
||||
static get type () { return type }
|
||||
|
||||
async getId ({ id } = {}) {
|
||||
/**
|
||||
* Gets the id.
|
||||
* @memberof module:IdentityProviders.IdentityProvider-PublicKey
|
||||
* @param {String} id The id to retrieve.
|
||||
* @return {String} The identity's id.
|
||||
* @instance
|
||||
*/
|
||||
const getId = async ({ id } = {}) => {
|
||||
if (!id) {
|
||||
throw new Error('id is required')
|
||||
}
|
||||
const key = await this._keystore.getKey(id) || await this._keystore.createKey(id)
|
||||
|
||||
const key = await keystore.getKey(id) || await keystore.createKey(id)
|
||||
return uint8ArrayToString(key.public.marshal(), 'base16')
|
||||
}
|
||||
|
||||
async signIdentity (data, { id } = {}) {
|
||||
/**
|
||||
* Signs an identity using the identity's id.
|
||||
* @memberof module:IdentityProviders.IdentityProvider-PublicKey
|
||||
* @param {*} data The identity data to sign.
|
||||
* @param {Object} params One or more parameters for configuring Database.
|
||||
* @param {string} [params.id] The identity's id.
|
||||
* @return {string} A signature.
|
||||
* @instance
|
||||
*/
|
||||
const signIdentity = async (data, { id } = {}) => {
|
||||
if (!id) {
|
||||
throw new Error('id is required')
|
||||
}
|
||||
|
||||
const key = await this._keystore.getKey(id)
|
||||
const key = await keystore.getKey(id)
|
||||
if (!key) {
|
||||
throw new Error(`Signing key for '${id}' not found`)
|
||||
}
|
||||
@ -43,11 +79,10 @@ class PublicKeyIdentityProvider extends IdentityProvider {
|
||||
return signMessage(key, data)
|
||||
}
|
||||
|
||||
static async verifyIdentity (identity) {
|
||||
const { id, publicKey, signatures } = identity
|
||||
// Verify that identity was signed by the ID
|
||||
return verifyMessage(signatures.publicKey, id, publicKey + signatures.id)
|
||||
return {
|
||||
getId,
|
||||
signIdentity
|
||||
}
|
||||
}
|
||||
|
||||
export default PublicKeyIdentityProvider
|
||||
export { PublicKeyIdentityProvider as default, verifyIdentity, type }
|
||||
|
@ -21,9 +21,9 @@ const unmarshalPubKey = crypto.keys.supportedKeys.secp256k1.unmarshalSecp256k1Pu
|
||||
|
||||
/**
|
||||
* Verifies a signature used for signing data.
|
||||
* @params {string} signature The generated signature.
|
||||
* @params {string} publicKey The derived public key of the key pair.
|
||||
* @params {string} data The data to be verified.
|
||||
* @param {string} signature The generated signature.
|
||||
* @param {string} publicKey The derived public key of the key pair.
|
||||
* @param {string} data The data to be verified.
|
||||
* @return {boolean} True if the signature is valid, false otherwise.
|
||||
* @throws No signature given if no signature is provided.
|
||||
* @throws Given publicKey was undefined if no publicKey is provided.
|
||||
@ -60,8 +60,8 @@ const verifySignature = async (signature, publicKey, data) => {
|
||||
|
||||
/**
|
||||
* Signs data using a key pair.
|
||||
* @params {string} key The key to use for signing data.
|
||||
* @params {string} data The data to sign.
|
||||
* @param {string} key The key to use for signing data.
|
||||
* @param {string} data The data to sign.
|
||||
* @return {string} A signature.
|
||||
* @throws No signing key given if no key is provided.
|
||||
* @throws Given input data was undefined if no data is provided.
|
||||
@ -87,9 +87,9 @@ const verifiedCachePromise = LRUStorage({ size: 1000 })
|
||||
|
||||
/**
|
||||
* Verifies input data against a cached version of the signed message.
|
||||
* @params {string} signature The generated signature.
|
||||
* @params {string} publicKey The derived public key of the key pair.
|
||||
* @params {string} data The data to be verified.
|
||||
* @param {string} signature The generated signature.
|
||||
* @param {string} publicKey The derived public key of the key pair.
|
||||
* @param {string} data The data to be verified.
|
||||
* @return {boolean} True if the the data and cache match, false otherwise.
|
||||
* @static
|
||||
*/
|
||||
|
@ -20,7 +20,7 @@ const hashStringEncoding = base58btc
|
||||
* @param {Object} params One or more parameters for configuring Manifest.
|
||||
* @param {IPFS} params.ipfs An instance of IPFS.
|
||||
* @param {module:Storage} [param.storage=module:Storage.Storage-ComposedStorage] An instance of Storage.
|
||||
* @returns {module:Manifest} An instance of Manifest.
|
||||
* @return {module:Manifest} An instance of Manifest.
|
||||
* @instance
|
||||
*/
|
||||
const Manifest = async ({ ipfs, storage } = {}) => {
|
||||
@ -37,7 +37,7 @@ const Manifest = async ({ ipfs, storage } = {}) => {
|
||||
/**
|
||||
* Gets the manifest data from the underlying storage.
|
||||
* @param {string} address The address of the manifest.
|
||||
* @returns {*} The manifest data.
|
||||
* @return {*} The manifest data.
|
||||
* @memberof module:Manifest~Manifest
|
||||
*/
|
||||
const get = async (address) => {
|
||||
@ -53,7 +53,7 @@ const Manifest = async ({ ipfs, storage } = {}) => {
|
||||
* @param {string} type The type of the database.
|
||||
* @param {string} accessController The type of access controller.
|
||||
* @param {Object} meta Metadata.
|
||||
* @returns {Object} A hash and manifest.
|
||||
* @return {Object} A hash and manifest.
|
||||
* @throws name is required if no name is provided.
|
||||
* @throws type is required if no type is provided.
|
||||
* @throws accessController is required if no access controller is provided.
|
||||
|
@ -1,33 +1,26 @@
|
||||
/* Lamport Clock */
|
||||
class Clock {
|
||||
constructor (id, time) {
|
||||
this.id = id
|
||||
this.time = time || 0
|
||||
}
|
||||
const compareClocks = (a, b) => {
|
||||
// Calculate the "distance" based on the clock, ie. lower or greater
|
||||
const dist = a.time - b.time
|
||||
|
||||
tick () {
|
||||
return new Clock(this.id, ++this.time)
|
||||
}
|
||||
// If the sequence number is the same (concurrent events),
|
||||
// and the IDs are different, take the one with a "lower" id
|
||||
if (dist === 0 && a.id !== b.id) return a.id < b.id ? -1 : 1
|
||||
|
||||
merge (clock) {
|
||||
this.time = Math.max(this.time, clock.time)
|
||||
return new Clock(this.id, this.time)
|
||||
}
|
||||
return dist
|
||||
}
|
||||
|
||||
clone () {
|
||||
return new Clock(this.id, this.time)
|
||||
}
|
||||
const tickClock = (clock) => {
|
||||
return Clock(clock.id, ++clock.time)
|
||||
}
|
||||
|
||||
static compare (a, b) {
|
||||
// Calculate the "distance" based on the clock, ie. lower or greater
|
||||
const dist = a.time - b.time
|
||||
const Clock = (id, time) => {
|
||||
time = time || 0
|
||||
|
||||
// If the sequence number is the same (concurrent events),
|
||||
// and the IDs are different, take the one with a "lower" id
|
||||
if (dist === 0 && a.id !== b.id) return a.id < b.id ? -1 : 1
|
||||
|
||||
return dist
|
||||
return {
|
||||
id,
|
||||
time
|
||||
}
|
||||
}
|
||||
|
||||
export default Clock
|
||||
export { Clock as default, compareClocks, tickClock }
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Clock from './clock.js'
|
||||
import { compareClocks } from './clock.js'
|
||||
|
||||
/**
|
||||
* Sort two entries as Last-Write-Wins (LWW).
|
||||
@ -8,7 +8,7 @@ import Clock from './clock.js'
|
||||
*
|
||||
* @param {Entry} a First entry
|
||||
* @param {Entry} b Second entry
|
||||
* @returns {number} 1 if a is latest, -1 if b is latest
|
||||
* @return {number} 1 if a is latest, -1 if b is latest
|
||||
*/
|
||||
function LastWriteWins (a, b) {
|
||||
// Ultimate conflict resolution (take the first/left arg)
|
||||
@ -27,11 +27,11 @@ function LastWriteWins (a, b) {
|
||||
* @param {Entry} a First entry to compare
|
||||
* @param {Entry} b Second entry to compare
|
||||
* @param {function(a, b)} resolveConflict A function to call if entries are concurrent (happened at the same time). The function should take in two entries and return 1 if the first entry should be chosen and -1 if the second entry should be chosen.
|
||||
* @returns {number} 1 if a is greater, -1 if b is greater
|
||||
* @return {number} 1 if a is greater, -1 if b is greater
|
||||
*/
|
||||
function SortByClocks (a, b, resolveConflict) {
|
||||
// Compare the clocks
|
||||
const diff = Clock.compare(a.clock, b.clock)
|
||||
const diff = compareClocks(a.clock, b.clock)
|
||||
// If the clocks are concurrent, use the provided
|
||||
// conflict resolution function to determine which comes first
|
||||
return diff === 0 ? resolveConflict(a, b) : diff
|
||||
@ -42,7 +42,7 @@ function SortByClocks (a, b, resolveConflict) {
|
||||
* @param {Entry} a First entry to compare
|
||||
* @param {Entry} b Second entry to compare
|
||||
* @param {function(a, b)} resolveConflict A function to call if the clocks ids are the same. The function should take in two entries and return 1 if the first entry should be chosen and -1 if the second entry should be chosen.
|
||||
* @returns {number} 1 if a is greater, -1 if b is greater
|
||||
* @return {number} 1 if a is greater, -1 if b is greater
|
||||
*/
|
||||
function SortByClockId (a, b, resolveConflict) {
|
||||
// Sort by ID if clocks are concurrent,
|
||||
@ -55,7 +55,7 @@ function SortByClockId (a, b, resolveConflict) {
|
||||
/**
|
||||
* A wrapper function to throw an error if the results of a passed function return zero
|
||||
* @param {function(a, b)} [tiebreaker] The tiebreaker function to validate.
|
||||
* @returns {function(a, b)} 1 if a is greater, -1 if b is greater
|
||||
* @return {function(a, b)} 1 if a is greater, -1 if b is greater
|
||||
* @throws {Error} if func ever returns 0
|
||||
*/
|
||||
function NoZeroes (func) {
|
||||
|
@ -21,7 +21,7 @@ const hashStringEncoding = base58btc
|
||||
* @param {Clock} [clock] The clock
|
||||
* @param {Array<string|Entry>} [next=[]] An array of CIDs as base58btc encoded strings
|
||||
* @param {Array<string|Entry>} [refs=[]] An array of CIDs as base58btc encoded strings
|
||||
* @returns {Promise<Entry>}
|
||||
* @return {Promise<Entry>}
|
||||
* @example
|
||||
* const entry = await Entry.create(identity, 'log1', 'hello')
|
||||
* console.log(entry)
|
||||
@ -33,7 +33,7 @@ const create = async (identity, id, payload, clock = null, next = [], refs = [])
|
||||
if (payload == null) throw new Error('Entry requires a payload')
|
||||
if (next == null || !Array.isArray(next)) throw new Error("'next' argument is not an array")
|
||||
|
||||
clock = clock || new Clock(identity.publicKey)
|
||||
clock = clock || Clock(identity.publicKey)
|
||||
|
||||
const entry = {
|
||||
id, // For determining a unique chain
|
||||
@ -84,7 +84,7 @@ const verify = async (identities, entry) => {
|
||||
/**
|
||||
* Check if an object is an Entry.
|
||||
* @param {Entry} obj
|
||||
* @returns {boolean}
|
||||
* @return {boolean}
|
||||
*/
|
||||
const isEntry = (obj) => {
|
||||
return obj && obj.id !== undefined &&
|
||||
@ -102,7 +102,7 @@ const isEqual = (a, b) => {
|
||||
/**
|
||||
* Decode a serialized Entry from bytes
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {Entry}
|
||||
* @return {Entry}
|
||||
*/
|
||||
const decode = async (bytes) => {
|
||||
const { value } = await Block.decode({ bytes, codec, hasher })
|
||||
@ -112,12 +112,12 @@ const decode = async (bytes) => {
|
||||
/**
|
||||
* Encode an Entry to a serializable form
|
||||
* @param {Entry} entry
|
||||
* @returns {TODO}
|
||||
* @return {TODO}
|
||||
*/
|
||||
const _encodeEntry = async (entry) => {
|
||||
const { cid, bytes } = await Block.encode({ value: entry, codec, hasher })
|
||||
const hash = cid.toString(hashStringEncoding)
|
||||
const clock = new Clock(entry.clock.id, entry.clock.time)
|
||||
const clock = Clock(entry.clock.id, entry.clock.time)
|
||||
return {
|
||||
...entry,
|
||||
clock,
|
||||
|
@ -76,7 +76,7 @@ const Heads = async ({ storage, heads }) => {
|
||||
* This function is private and not exposed in the Log API
|
||||
*
|
||||
* @param {Array<Entry>} entries Entries to search heads from
|
||||
* @returns {Array<Entry>}
|
||||
* @return {Array<Entry>}
|
||||
*/
|
||||
const findHeads = (entries) => {
|
||||
entries = new Set(entries)
|
||||
|
@ -9,7 +9,7 @@
|
||||
*/
|
||||
import LRU from 'lru'
|
||||
import Entry from './entry.js'
|
||||
import Clock from './clock.js'
|
||||
import Clock, { tickClock } from './clock.js'
|
||||
import Heads from './heads.js'
|
||||
import ConflictResolution from './conflict-resolution.js'
|
||||
import MemoryStorage from '../storage/memory.js'
|
||||
@ -80,20 +80,20 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
|
||||
|
||||
/**
|
||||
* Returns the clock of the log.
|
||||
* @returns {Clock}
|
||||
* @return {Clock}
|
||||
* @memberof module:Log~Log
|
||||
* @instance
|
||||
*/
|
||||
const clock = async () => {
|
||||
// Find the latest clock from the heads
|
||||
const maxTime = Math.max(0, (await heads()).reduce(maxClockTimeReducer, 0))
|
||||
return new Clock(identity.publicKey, maxTime)
|
||||
return Clock(identity.publicKey, maxTime)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current heads of the log
|
||||
*
|
||||
* @returns {Array<module:Log~Entry>}
|
||||
* @return {Array<module:Log~Entry>}
|
||||
* @memberof module:Log~Log
|
||||
* @instance
|
||||
*/
|
||||
@ -105,7 +105,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
|
||||
/**
|
||||
* Returns all entries in the log
|
||||
*
|
||||
* @returns {Array<module:Log~Entry>}
|
||||
* @return {Array<module:Log~Entry>}
|
||||
* @memberof module:Log~Log
|
||||
* @instance
|
||||
*/
|
||||
@ -121,7 +121,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
|
||||
* Retrieve an entry
|
||||
*
|
||||
* @param {string} hash The hash of the entry to retrieve
|
||||
* @returns {module:Log~Entry}
|
||||
* @return {module:Log~Entry}
|
||||
* @memberof module:Log~Log
|
||||
* @instance
|
||||
*/
|
||||
@ -169,7 +169,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
|
||||
identity,
|
||||
id,
|
||||
data,
|
||||
(await clock()).tick(),
|
||||
tickClock(await clock()),
|
||||
nexts,
|
||||
refs
|
||||
)
|
||||
@ -353,7 +353,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
|
||||
* @param {string} options.gte Beginning hash of the iterator, inclusive
|
||||
* @param {string} options.lt Ending hash of the iterator, non-inclusive
|
||||
* @param {string} options.lte Ending hash of the iterator, inclusive
|
||||
* @returns {Symbol.asyncIterator} Iterator object of log entries
|
||||
* @return {Symbol.asyncIterator} Iterator object of log entries
|
||||
*
|
||||
* @examples
|
||||
*
|
||||
@ -473,7 +473,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
|
||||
/**
|
||||
* Check if an object is a Log.
|
||||
* @param {Log} obj
|
||||
* @returns {boolean}
|
||||
* @return {boolean}
|
||||
* @memberof module:Log~Log
|
||||
* @instance
|
||||
*/
|
||||
|
@ -17,7 +17,7 @@
|
||||
* @function
|
||||
* @param {module:Storage} storage1 A storage instance.
|
||||
* @param {module:Storage} storage2 A storage instance.
|
||||
* @returns {module:Storage.Storage-Composed} An instance of ComposedStorage.
|
||||
* @return {module:Storage.Storage-Composed} An instance of ComposedStorage.
|
||||
* @memberof module:Storage
|
||||
* @instance
|
||||
*/
|
||||
|
@ -18,7 +18,7 @@ const defaultTimeout = 30000
|
||||
* @param {number} [params.timeout=defaultTimeout] A timeout in ms.
|
||||
* @param {boolean} [params.pin=false] True, if the block should be pinned,
|
||||
* false otherwise.
|
||||
* @returns {module:Storage.Storage-IPFS} An instance of IPFSBlockStorage.
|
||||
* @return {module:Storage.Storage-IPFS} An instance of IPFSBlockStorage.
|
||||
* @memberof module:Storage
|
||||
* @throw An instance of ipfs is required if params.ipfs is not specified.
|
||||
* @instance
|
||||
|
@ -18,7 +18,7 @@ const defaultValueEncoding = 'view'
|
||||
* LevelStorage.
|
||||
* @param {string} [params.path=defaultPath] The Level path.
|
||||
* @param {string} [params.valueEncoding=defaultValueEncoding] Value encoding.
|
||||
* @returns {module:Storage.Storage-Level} An instance of LevelStorage.
|
||||
* @return {module:Storage.Storage-Level} An instance of LevelStorage.
|
||||
* @memberof module:Storage
|
||||
* @instance
|
||||
*/
|
||||
|
@ -14,7 +14,7 @@ const defaultSize = 1000000
|
||||
* @param {Object} [params={}] One or more parameters for configuring
|
||||
* IPFSBlockStorage.
|
||||
* @param {string} [params.size=defaultSize] The number of elements to store.
|
||||
* @returns {module:Storage.Storage-LRU} An instance of LRUStorage.
|
||||
* @return {module:Storage.Storage-LRU} An instance of LRUStorage.
|
||||
* @memberof module:Storage
|
||||
* @instance
|
||||
*/
|
||||
|
@ -8,7 +8,7 @@
|
||||
/**
|
||||
* Creates an instance of MemoryStorage.
|
||||
* @function
|
||||
* @returns {module:Storage.Storage-Memory} An instance of LRUStorage.
|
||||
* @return {module:Storage.Storage-Memory} An instance of LRUStorage.
|
||||
* @memberof module:Storage
|
||||
* @instance
|
||||
*/
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Creates an id from an alphanumeric character list.
|
||||
* @param {number} [length=32] The length of the id.
|
||||
* @returns {string} An id.
|
||||
* @return {string} An id.
|
||||
* @see {@link https://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript}
|
||||
* @memberof module:Utils
|
||||
*/
|
||||
|
@ -4,7 +4,7 @@ import pathJoin from './path-join.js'
|
||||
* Checks that the given address has '/_access' as the last part.
|
||||
* @function
|
||||
* @param {string} address The address to check.
|
||||
* @returns {string} The address appended with /_access.
|
||||
* @return {string} The address appended with /_access.
|
||||
* @memberof module:Utils
|
||||
*/
|
||||
export default address => {
|
||||
|
@ -213,18 +213,30 @@ describe('Identities', function () {
|
||||
})
|
||||
|
||||
it('false signature doesn\'t verify', async () => {
|
||||
class IP {
|
||||
async getId () { return 'pubKey' }
|
||||
const IP = () => {
|
||||
const verifyIdentity = async (data) => { return false }
|
||||
|
||||
async signIdentity (data) { return `false signature '${data}'` }
|
||||
const FakeIdentityProvider = () => {
|
||||
const getId = () => { return 'pubKey' }
|
||||
|
||||
static async verifyIdentity (data) { return false }
|
||||
const signIdentity = (data) => { return `false signature '${data}'` }
|
||||
|
||||
static get type () { return 'fake' }
|
||||
return {
|
||||
getId,
|
||||
signIdentity,
|
||||
type: 'fake'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
default: FakeIdentityProvider,
|
||||
verifyIdentity,
|
||||
type: 'fake'
|
||||
}
|
||||
}
|
||||
|
||||
addIdentityProvider(IP)
|
||||
identity = await identities.createIdentity({ type: IP.type })
|
||||
addIdentityProvider(IP())
|
||||
identity = await identities.createIdentity({ type: IP().type })
|
||||
const verified = await identities.verifyIdentity(identity)
|
||||
assert.strictEqual(verified, false)
|
||||
})
|
||||
|
@ -1,11 +1,11 @@
|
||||
import Clock from '../../src/oplog/clock.js'
|
||||
import Clock, { tickClock, compareClocks } from '../../src/oplog/clock.js'
|
||||
import { strictEqual } from 'assert'
|
||||
|
||||
describe('Clock', () => {
|
||||
it('creates a new clock', () => {
|
||||
const id = 'A'
|
||||
const time = 0
|
||||
const clock = new Clock(id, time)
|
||||
const clock = Clock(id, time)
|
||||
strictEqual(clock.id, id)
|
||||
strictEqual(clock.time, time)
|
||||
})
|
||||
@ -13,7 +13,7 @@ describe('Clock', () => {
|
||||
it('creates a new clock with default time', () => {
|
||||
const id = 'A'
|
||||
const time = 0
|
||||
const clock = new Clock(id)
|
||||
const clock = Clock(id)
|
||||
strictEqual(clock.id, id)
|
||||
strictEqual(clock.time, time)
|
||||
})
|
||||
@ -21,7 +21,7 @@ describe('Clock', () => {
|
||||
it('creates a new clock with time starting at 1', () => {
|
||||
const id = 'A'
|
||||
const time = 1
|
||||
const clock = new Clock(id, time)
|
||||
const clock = Clock(id, time)
|
||||
strictEqual(clock.id, id)
|
||||
strictEqual(clock.time, time)
|
||||
})
|
||||
@ -29,144 +29,104 @@ describe('Clock', () => {
|
||||
it('advances clock forward 1 tick', () => {
|
||||
const id = 'A'
|
||||
const time = 1
|
||||
const clock = new Clock(id)
|
||||
clock.tick()
|
||||
const clock = tickClock(Clock(id))
|
||||
strictEqual(clock.time, time)
|
||||
})
|
||||
|
||||
it('advances clock forward 2 ticks', () => {
|
||||
const id = 'A'
|
||||
const time = 2
|
||||
const clock = new Clock(id)
|
||||
clock.tick()
|
||||
clock.tick()
|
||||
const clock = tickClock(tickClock(Clock(id)))
|
||||
strictEqual(clock.time, time)
|
||||
})
|
||||
|
||||
it('merges clock2 into clock1', () => {
|
||||
const id1 = 'A'
|
||||
const clock1 = new Clock(id1)
|
||||
const id2 = 'B'
|
||||
const time2 = 2
|
||||
const clock2 = new Clock(id2, time2)
|
||||
|
||||
clock1.merge(clock2)
|
||||
|
||||
strictEqual(clock1.id, id1)
|
||||
strictEqual(clock1.time, time2)
|
||||
})
|
||||
|
||||
it('merges two clocks with the same time', () => {
|
||||
const id1 = 'A'
|
||||
const time1 = 1
|
||||
const clock1 = new Clock(id1, time1)
|
||||
const id2 = 'B'
|
||||
const time2 = 1
|
||||
const clock2 = new Clock(id2, time2)
|
||||
|
||||
clock1.merge(clock2)
|
||||
|
||||
strictEqual(clock1.id, id1)
|
||||
strictEqual(clock1.time, time1)
|
||||
})
|
||||
|
||||
it('clones a clock', () => {
|
||||
const id = 'A'
|
||||
const time = 1
|
||||
const clock = new Clock(id, time)
|
||||
const clonedClock = clock.clone()
|
||||
|
||||
strictEqual(clonedClock.id, id)
|
||||
strictEqual(clonedClock.time, time)
|
||||
})
|
||||
|
||||
describe('Compare clocks', () => {
|
||||
it('compares clocks when clock1\'s time is 1 less than clock2\'s', () => {
|
||||
const id1 = 'A'
|
||||
const time1 = 1
|
||||
const clock1 = new Clock(id1, time1)
|
||||
const clock1 = Clock(id1, time1)
|
||||
const id2 = 'B'
|
||||
const time2 = 2
|
||||
const clock2 = new Clock(id2, time2)
|
||||
const clock2 = Clock(id2, time2)
|
||||
|
||||
const expected = -1
|
||||
|
||||
strictEqual(Clock.compare(clock1, clock2), expected)
|
||||
strictEqual(compareClocks(clock1, clock2), expected)
|
||||
})
|
||||
it('compares clocks when clock1\'s time is 3 less than clock2\'s', () => {
|
||||
const id1 = 'A'
|
||||
const time1 = 1
|
||||
const clock1 = new Clock(id1, time1)
|
||||
const clock1 = Clock(id1, time1)
|
||||
const id2 = 'B'
|
||||
const time2 = 4
|
||||
const clock2 = new Clock(id2, time2)
|
||||
const clock2 = Clock(id2, time2)
|
||||
|
||||
const expected = -3
|
||||
|
||||
strictEqual(Clock.compare(clock1, clock2), expected)
|
||||
strictEqual(compareClocks(clock1, clock2), expected)
|
||||
})
|
||||
|
||||
it('compares clocks when clock1\'s time is 1 more than clock2\'s', () => {
|
||||
const id1 = 'A'
|
||||
const time1 = 2
|
||||
const clock1 = new Clock(id1, time1)
|
||||
const clock1 = Clock(id1, time1)
|
||||
const id2 = 'B'
|
||||
const time2 = 1
|
||||
const clock2 = new Clock(id2, time2)
|
||||
const clock2 = Clock(id2, time2)
|
||||
|
||||
const expected = 1
|
||||
|
||||
strictEqual(Clock.compare(clock1, clock2), expected)
|
||||
strictEqual(compareClocks(clock1, clock2), expected)
|
||||
})
|
||||
|
||||
it('compares clocks when clock1\'s time is 3 more than clock2\'s', () => {
|
||||
const id1 = 'A'
|
||||
const time1 = 4
|
||||
const clock1 = new Clock(id1, time1)
|
||||
const clock1 = Clock(id1, time1)
|
||||
const id2 = 'B'
|
||||
const time2 = 1
|
||||
const clock2 = new Clock(id2, time2)
|
||||
const clock2 = Clock(id2, time2)
|
||||
|
||||
const expected = 3
|
||||
|
||||
strictEqual(Clock.compare(clock1, clock2), expected)
|
||||
strictEqual(compareClocks(clock1, clock2), expected)
|
||||
})
|
||||
|
||||
it('compares clocks when clock1\'s id is less than clock2\'s', () => {
|
||||
const id1 = 'A'
|
||||
const time1 = 1
|
||||
const clock1 = new Clock(id1, time1)
|
||||
const clock1 = Clock(id1, time1)
|
||||
const id2 = 'B'
|
||||
const time2 = 1
|
||||
const clock2 = new Clock(id2, time2)
|
||||
const clock2 = Clock(id2, time2)
|
||||
|
||||
const expected = -1
|
||||
|
||||
strictEqual(Clock.compare(clock1, clock2), expected)
|
||||
strictEqual(compareClocks(clock1, clock2), expected)
|
||||
})
|
||||
|
||||
it('compares clocks when clock1\'s id is more than clock2\'s', () => {
|
||||
const id1 = 'B'
|
||||
const time1 = 1
|
||||
const clock1 = new Clock(id1, time1)
|
||||
const clock1 = Clock(id1, time1)
|
||||
const id2 = 'A'
|
||||
const time2 = 1
|
||||
const clock2 = new Clock(id2, time2)
|
||||
const clock2 = Clock(id2, time2)
|
||||
|
||||
const expected = 1
|
||||
|
||||
strictEqual(Clock.compare(clock1, clock2), expected)
|
||||
strictEqual(compareClocks(clock1, clock2), expected)
|
||||
})
|
||||
|
||||
it('compares clocks when clock1 is the same as clock2', () => {
|
||||
const id = 'A'
|
||||
const time = 1
|
||||
const clock1 = new Clock(id, time)
|
||||
const clock2 = new Clock(id, time)
|
||||
const clock1 = Clock(id, time)
|
||||
const clock2 = Clock(id, time)
|
||||
|
||||
const expected = 0
|
||||
|
||||
strictEqual(Clock.compare(clock1, clock2), expected)
|
||||
strictEqual(compareClocks(clock1, clock2), expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -31,22 +31,22 @@ describe('ConflictResolution', () => {
|
||||
|
||||
it('returns -1 when first clock\'s id is less than second clock\'s', () => {
|
||||
const expected = -1
|
||||
const record1 = { clock: new Clock('A') }
|
||||
const record2 = { clock: new Clock('B') }
|
||||
const record1 = { clock: Clock('A') }
|
||||
const record2 = { clock: Clock('B') }
|
||||
strictEqual(ConflictResolution.SortByClockId(record1, record2, fallbackFn), expected)
|
||||
})
|
||||
|
||||
it('returns 1 when first clock\'s id is greater than second clock\'s', () => {
|
||||
const expected = 1
|
||||
const record1 = { clock: new Clock('B') }
|
||||
const record2 = { clock: new Clock('A') }
|
||||
const record1 = { clock: Clock('B') }
|
||||
const record2 = { clock: Clock('A') }
|
||||
strictEqual(ConflictResolution.SortByClockId(record1, record2, fallbackFn), expected)
|
||||
})
|
||||
|
||||
it('returns the clock when clocks have the same id', () => {
|
||||
const expected = { clock: new Clock('A') }
|
||||
const record1 = { clock: new Clock('A') }
|
||||
const record2 = { clock: new Clock('A') }
|
||||
const expected = { clock: Clock('A') }
|
||||
const record1 = { clock: Clock('A') }
|
||||
const record2 = { clock: Clock('A') }
|
||||
deepStrictEqual(ConflictResolution.SortByClockId(record1, record2, fallbackFn), expected)
|
||||
})
|
||||
})
|
||||
@ -59,22 +59,22 @@ describe('ConflictResolution', () => {
|
||||
|
||||
it('returns -1 when a\'s time is less than b\'s', () => {
|
||||
const expected = -1
|
||||
const record1 = { clock: new Clock('A', 1) }
|
||||
const record2 = { clock: new Clock('B', 2) }
|
||||
const record1 = { clock: Clock('A', 1) }
|
||||
const record2 = { clock: Clock('B', 2) }
|
||||
strictEqual(ConflictResolution.SortByClocks(record1, record2, fallbackFn), expected)
|
||||
})
|
||||
|
||||
it('returns 1 when a\'s time is greater than b\'s', () => {
|
||||
const expected = 1
|
||||
const record1 = { clock: new Clock('A', 2) }
|
||||
const record2 = { clock: new Clock('B', 1) }
|
||||
const record1 = { clock: Clock('A', 2) }
|
||||
const record2 = { clock: Clock('B', 1) }
|
||||
strictEqual(ConflictResolution.SortByClocks(record1, record2, fallbackFn), expected)
|
||||
})
|
||||
|
||||
it('returns -1 when a\'s time is equal to b\'s', () => {
|
||||
const expected = -1
|
||||
const record1 = { clock: new Clock('A', 1) }
|
||||
const record2 = { clock: new Clock('B', 1) }
|
||||
const record1 = { clock: Clock('A', 1) }
|
||||
const record2 = { clock: Clock('B', 1) }
|
||||
strictEqual(ConflictResolution.SortByClocks(record1, record2, fallbackFn), expected)
|
||||
})
|
||||
})
|
||||
@ -82,29 +82,29 @@ describe('ConflictResolution', () => {
|
||||
describe('Last write wins', () => {
|
||||
it('returns -1 when a\'s time is less than b\'s', () => {
|
||||
const expected = -1
|
||||
const record1 = { clock: new Clock('A', 1) }
|
||||
const record2 = { clock: new Clock('B', 2) }
|
||||
const record1 = { clock: Clock('A', 1) }
|
||||
const record2 = { clock: Clock('B', 2) }
|
||||
strictEqual(ConflictResolution.LastWriteWins(record1, record2), expected)
|
||||
})
|
||||
|
||||
it('returns 1 when a\'s time is greater than b\'s', () => {
|
||||
const expected = 1
|
||||
const record1 = { clock: new Clock('A', 2) }
|
||||
const record2 = { clock: new Clock('B', 1) }
|
||||
const record1 = { clock: Clock('A', 2) }
|
||||
const record2 = { clock: Clock('B', 1) }
|
||||
strictEqual(ConflictResolution.LastWriteWins(record1, record2), expected)
|
||||
})
|
||||
|
||||
it('returns -1 when a\'s time is equal to b\'s', () => {
|
||||
const expected = -1
|
||||
const record1 = { clock: new Clock('A', 1) }
|
||||
const record2 = { clock: new Clock('B', 1) }
|
||||
const record1 = { clock: Clock('A', 1) }
|
||||
const record2 = { clock: Clock('B', 1) }
|
||||
strictEqual(ConflictResolution.LastWriteWins(record1, record2), expected)
|
||||
})
|
||||
|
||||
it('returns the clock when a and b are the same', () => {
|
||||
const expected = { clock: new Clock('A') }
|
||||
const record1 = { clock: new Clock('A') }
|
||||
const record2 = { clock: new Clock('A') }
|
||||
const expected = { clock: Clock('A') }
|
||||
const record1 = { clock: Clock('A') }
|
||||
const record2 = { clock: Clock('A') }
|
||||
deepStrictEqual(ConflictResolution.LastWriteWins(record1, record2), expected)
|
||||
})
|
||||
})
|
||||
@ -112,17 +112,17 @@ describe('ConflictResolution', () => {
|
||||
describe('ConflictResolution records', () => {
|
||||
it('sorts by clock time', () => {
|
||||
const expected = [
|
||||
{ clock: new Clock('A', 1) },
|
||||
{ clock: new Clock('B', 2) },
|
||||
{ clock: new Clock('C', 3) },
|
||||
{ clock: new Clock('D', 4) }
|
||||
{ clock: Clock('A', 1) },
|
||||
{ clock: Clock('B', 2) },
|
||||
{ clock: Clock('C', 3) },
|
||||
{ clock: Clock('D', 4) }
|
||||
]
|
||||
|
||||
const records = [
|
||||
{ clock: new Clock('C', 3) },
|
||||
{ clock: new Clock('A', 1) },
|
||||
{ clock: new Clock('D', 4) },
|
||||
{ clock: new Clock('B', 2) }
|
||||
{ clock: Clock('C', 3) },
|
||||
{ clock: Clock('A', 1) },
|
||||
{ clock: Clock('D', 4) },
|
||||
{ clock: Clock('B', 2) }
|
||||
]
|
||||
|
||||
deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected)
|
||||
@ -130,17 +130,17 @@ describe('ConflictResolution', () => {
|
||||
|
||||
it('sorts by clock time when id is the same', () => {
|
||||
const expected = [
|
||||
{ clock: new Clock('A', 1) },
|
||||
{ clock: new Clock('A', 2) },
|
||||
{ clock: new Clock('A', 3) },
|
||||
{ clock: new Clock('A', 4) }
|
||||
{ clock: Clock('A', 1) },
|
||||
{ clock: Clock('A', 2) },
|
||||
{ clock: Clock('A', 3) },
|
||||
{ clock: Clock('A', 4) }
|
||||
]
|
||||
|
||||
const records = [
|
||||
{ clock: new Clock('A', 3) },
|
||||
{ clock: new Clock('A', 1) },
|
||||
{ clock: new Clock('A', 4) },
|
||||
{ clock: new Clock('A', 2) }
|
||||
{ clock: Clock('A', 3) },
|
||||
{ clock: Clock('A', 1) },
|
||||
{ clock: Clock('A', 4) },
|
||||
{ clock: Clock('A', 2) }
|
||||
]
|
||||
|
||||
deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected)
|
||||
@ -148,17 +148,17 @@ describe('ConflictResolution', () => {
|
||||
|
||||
it('sorts by clock id', () => {
|
||||
const expected = [
|
||||
{ clock: new Clock('A') },
|
||||
{ clock: new Clock('B') },
|
||||
{ clock: new Clock('C') },
|
||||
{ clock: new Clock('D') }
|
||||
{ clock: Clock('A') },
|
||||
{ clock: Clock('B') },
|
||||
{ clock: Clock('C') },
|
||||
{ clock: Clock('D') }
|
||||
]
|
||||
|
||||
const records = [
|
||||
{ clock: new Clock('C') },
|
||||
{ clock: new Clock('A') },
|
||||
{ clock: new Clock('D') },
|
||||
{ clock: new Clock('B') }
|
||||
{ clock: Clock('C') },
|
||||
{ clock: Clock('A') },
|
||||
{ clock: Clock('D') },
|
||||
{ clock: Clock('B') }
|
||||
]
|
||||
|
||||
deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected)
|
||||
@ -166,17 +166,17 @@ describe('ConflictResolution', () => {
|
||||
|
||||
it('sorts the same clock', () => {
|
||||
const expected = [
|
||||
{ clock: new Clock('A') },
|
||||
{ clock: new Clock('A') },
|
||||
{ clock: new Clock('B') },
|
||||
{ clock: new Clock('B') }
|
||||
{ clock: Clock('A') },
|
||||
{ clock: Clock('A') },
|
||||
{ clock: Clock('B') },
|
||||
{ clock: Clock('B') }
|
||||
]
|
||||
|
||||
const records = [
|
||||
{ clock: new Clock('B') },
|
||||
{ clock: new Clock('A') },
|
||||
{ clock: new Clock('B') },
|
||||
{ clock: new Clock('A') }
|
||||
{ clock: Clock('B') },
|
||||
{ clock: Clock('A') },
|
||||
{ clock: Clock('B') },
|
||||
{ clock: Clock('A') }
|
||||
]
|
||||
|
||||
deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected)
|
||||
|
@ -3,6 +3,7 @@ import rmrf from 'rimraf'
|
||||
import { copy } from 'fs-extra'
|
||||
import { Entry, Identities, KeyStore } from '../../src/index.js'
|
||||
import testKeysPath from '../fixtures/test-keys-path.js'
|
||||
import { tickClock } from '../../src/oplog/clock.js'
|
||||
|
||||
const { create, isEntry } = Entry
|
||||
const keysPath = './testkeys'
|
||||
@ -79,7 +80,7 @@ describe('Entry', function () {
|
||||
const payload1 = 'hello world'
|
||||
const payload2 = 'hello again'
|
||||
const entry1 = await create(testIdentity, 'A', payload1)
|
||||
entry1.clock.tick()
|
||||
entry1.clock = tickClock(entry1.clock)
|
||||
const entry2 = await create(testIdentity, 'A', payload2, entry1.clock, [entry1])
|
||||
strictEqual(entry2.payload, payload2)
|
||||
strictEqual(entry2.next.length, 1)
|
||||
|
@ -350,19 +350,19 @@ describe('Log - Join', async function () {
|
||||
strictEqual((await log4.clock()).time, 8)
|
||||
|
||||
const expectedData = [
|
||||
{ payload: 'helloA1', id: 'X', clock: new Clock(testIdentity.publicKey, 1) },
|
||||
{ payload: 'helloB1', id: 'X', clock: new Clock(testIdentity2.publicKey, 1) },
|
||||
{ payload: 'helloD1', id: 'X', clock: new Clock(testIdentity4.publicKey, 1) },
|
||||
{ payload: 'helloA2', id: 'X', clock: new Clock(testIdentity.publicKey, 2) },
|
||||
{ payload: 'helloB2', id: 'X', clock: new Clock(testIdentity2.publicKey, 2) },
|
||||
{ payload: 'helloD2', id: 'X', clock: new Clock(testIdentity4.publicKey, 2) },
|
||||
{ payload: 'helloC1', id: 'X', clock: new Clock(testIdentity3.publicKey, 3) },
|
||||
{ payload: 'helloC2', id: 'X', clock: new Clock(testIdentity3.publicKey, 4) },
|
||||
{ payload: 'helloD3', id: 'X', clock: new Clock(testIdentity4.publicKey, 5) },
|
||||
{ payload: 'helloD4', id: 'X', clock: new Clock(testIdentity4.publicKey, 6) },
|
||||
{ payload: 'helloA5', id: 'X', clock: new Clock(testIdentity.publicKey, 7) },
|
||||
{ payload: 'helloD5', id: 'X', clock: new Clock(testIdentity4.publicKey, 7) },
|
||||
{ payload: 'helloD6', id: 'X', clock: new Clock(testIdentity4.publicKey, 8) }
|
||||
{ payload: 'helloA1', id: 'X', clock: Clock(testIdentity.publicKey, 1) },
|
||||
{ payload: 'helloB1', id: 'X', clock: Clock(testIdentity2.publicKey, 1) },
|
||||
{ payload: 'helloD1', id: 'X', clock: Clock(testIdentity4.publicKey, 1) },
|
||||
{ payload: 'helloA2', id: 'X', clock: Clock(testIdentity.publicKey, 2) },
|
||||
{ payload: 'helloB2', id: 'X', clock: Clock(testIdentity2.publicKey, 2) },
|
||||
{ payload: 'helloD2', id: 'X', clock: Clock(testIdentity4.publicKey, 2) },
|
||||
{ payload: 'helloC1', id: 'X', clock: Clock(testIdentity3.publicKey, 3) },
|
||||
{ payload: 'helloC2', id: 'X', clock: Clock(testIdentity3.publicKey, 4) },
|
||||
{ payload: 'helloD3', id: 'X', clock: Clock(testIdentity4.publicKey, 5) },
|
||||
{ payload: 'helloD4', id: 'X', clock: Clock(testIdentity4.publicKey, 6) },
|
||||
{ payload: 'helloA5', id: 'X', clock: Clock(testIdentity.publicKey, 7) },
|
||||
{ payload: 'helloD5', id: 'X', clock: Clock(testIdentity4.publicKey, 7) },
|
||||
{ payload: 'helloD6', id: 'X', clock: Clock(testIdentity4.publicKey, 8) }
|
||||
]
|
||||
|
||||
const values = await log4.values()
|
||||
|
Loading…
x
Reference in New Issue
Block a user