Source: chum-base.ts

/**
 * @author Michael Hasenstein <hasenstein@yahoo.com>
 * @author Maximilian Wisgickl <wisgicklma@gmail.com>
 * @copyright REFINIO GmbH & Maximilian Wisgickl 2017
 * @license CC-BY-NC-SA-2.5; portions MIT License
 * @version 0.0.1
 */

/**
 * @private
 * @module
 */

/**
 * Options object for Chum importer function
 * {@link
 * chum-base.sendAccessibleObjectsEventsRequest|chum-base.sendAccessibleObjectsEventsRequest}
 * @global
 * @typedef {object} AccessibleObjectsRequestOptions
 * @property {number} [lastChumTimestamp] - Numeric millisecond timestamp (since 1/1/1970) to only
 * list accessible objects created later than this
 * @property {boolean} [keepRunning=false] - Triggers the sending of update events while the
 * websocket connection and the Chum exchange remain active, informing the Chum importer of any
 * newly created objects that it has access to (may overlap with the initial list of accessible
 * objects depending on millisecond timing - but the Chum importer has double-download prevention)
 * @property {number} maxNotificationDelay - When `keepRunning` is true and the full
 * synchronization is over, what should be the maximum delay between new accessible objects
 * being created and notification events sent to us? The remote instance is going to collect all
 * new accessible hashes and only sends them after waiting for this amount of milliseconds. If
 * there are a lot of new hashes, exceeding a hard-coded threshold, it sends them right away though.
 * If you require fast notifications about rare new-accessible-object events you can set the
 * delay low (**1 ms is the minimum**), if it is not urgent you can set the delay higher and let
 * the remote exporter accumulate more hashes. The delay does not have to be higher than a few
 * seconds, because it will make less and less of a difference very quickly with increasing values.
 */
export interface AccessibleObjectsRequestOptions {
    lastChumTimestamp?: number;
    keepRunning?: boolean;
    maxNotificationDelay?: number;
}

/**
 * An array of these objects is the "list" property of AccessibleObjectsEventData.
 *
 * The data is stored using an array instead of an object with named properties because the data
 * will be JSON.stringified and sent over a websocket. Repeating string literals for the property
 * names for every single item wastes space needlessly.
 *
 * Element [0] - The hash or the ID hash (for ID objects)
 * Element [1] - The type of the object pointed to by the hash, and the type of hash (regular, ID)
 * Element [2] - The distance of the hash from the root node
 * Element [3] - The size of the object
 * @typedef {Array<SHA256Hash,HashTypes,number>} AccessibleObjectsTuple
 */
export type AccessibleObjectsTuple = [
    SHA256Hash<HashTypes> | SHA256IdHash,
    IteratorCbParam['type'],
    number,
    number
];

/**
 * @typedef {object} AccessibleObjectsEventData
 * @property {AccessibleObjectsTuple[]} list - Array of hash and type string pairs. Type can be
 * any ONE object type name or "BIN" for all other types of files. This type is used by the
 * {@link chum-exporter.ts|chum-exporter} to send events about accessible objects to the
 * connected ONE instance's {@link chum-importer.ts|chum-importer}. The importer uses the
 * type information to select the right method to request the hash: ONE objects are transferred
 * as microdata strings, everything else (type "BLOB") as a binary stream.
 * @property {number} timestamp - When a timestamp is sent it indicates that the hashes of all
 * accessible objects created up to this point in time have been sent. A timestamp is only sent
 * if graph iteration completed successfully.
 * If the importer manages to process all those hashes (obtain them or find that it already has
 * the file) it can use the timestamp as parameter for the next synchronization to tell the
 * remote exporter to only send hashes created after the given time.
 */
export interface AccessibleObjectsEventData {
    list: AccessibleObjectsTuple[];
    timestamp?: number;
}

/**
 * This event is sent when the chum-exporter has iterated through all accessible objects found
 * in the initial full scan. It is mostly useful for non-ending `keepRunning:true` chums,
 * although if the receiving importer's ONE instance still is not done with its own
 * chum-exporter task of sending accessible objects this event still appears before the chum
 * exchange is over. When both sides' chum-exporter sent this event a full-sync chum without
 * `keepRunning:true` will be over.
 * @global
 * @typedef {object} ExporterEventFullSync
 * @property {1} type
 */
export interface ExporterEventFullSync {
    type: 1;
}

/**
 * @global
 * @typedef {object} ExporterEventError
 * @property {2} type
 * @property {string} code
 * @property {string} message
 */
export interface ExporterEventError {
    type: 2;
    code: string;
    message: string;
}

/**
 * The union of all event types that the chum-exporter can send to the chum-importer.
 * @global
 * @typedef {object} ExporterEvent
 */
export type ExporterEvent = ExporterEventFullSync | ExporterEventError;

import type {IteratorCbParam} from './object-graph-bottom-up-iterator';
import type {HashTypes} from './recipes';
import type {SHA256Hash, SHA256IdHash} from './util/type-checks';
import {countEnumerableProperties} from './util/type-checks';
import {isObject, isString} from './util/type-checks-basic';

/**
 * The chum-exporter may send event notifications of this type to the chum-importer through the
 * websocket connection.
 * @global
 * @typedef {object} EXPORTER_EVENT_TYPES
 * @property {1} FULL_SYNC
 * @property {2} ERROR
 */
export const EXPORTER_EVENT_TYPES = {
    FULL_SYNC: 1,
    ERROR: 2
} as const;

/**
 * Runtime type check function for {@link EXPORTER_EVENT_TYPES} chum-exporter event message
 * objects. Anything received over the network needs to be fully type-checked.
 * @static
 * @param {*} thing
 * @returns {boolean} Returns `true`if thing is an {@link ExporterEvent} object, `false` otherwise
 */
export function isExporterEventObj(thing: unknown): thing is ExporterEvent {
    if (!isObject(thing)) {
        return false;
    }

    switch (thing.type) {
        case 1:
            return countEnumerableProperties(thing) === 1;
        case 2:
            return (
                countEnumerableProperties(thing) === 3 &&
                isString(thing.code) &&
                isString(thing.message)
            );
        default:
            return false;
    }
}

/**
 * @static
 * @type {object}
 * @property {2} ACCESSIBLE_OBJECTS_EVENTS_REQUEST - 2
 * @property {2} EXPORTER_EVENT - 3
 * @property {4} ACCESSIBLE_OBJECTS_EVENT - 4
 * @property {6} GET_OBJECT - 6
 * @property {7} GET_ID_OBJECT -7
 * @property {8} GET_BLOB - 8
 * @property {9} ACKNOWLEDGE - 9
 * @property {10} GET_CRDT_META_OBJECT - 10
 */
export const MESSAGE_TYPES = {
    // Chum protocol version check to check if both sides are compatible
    VERSION_CHECK: 1,

    // Ask the exporter to send lists of accessible objects as events to the importer
    // EXPORTER handles this event
    ACCESSIBLE_OBJECTS_EVENTS_REQUEST: 2,

    // The exporter uses this message type to send event notifications to the importer. An
    // example is the event telling the importer the initial "full sync" is finished in a
    // keepRunning:true chum.
    // IMPORTER handles this event
    EXPORTER_EVENT: 3,

    // EVENT: Chum exporter sends a list of accessible object hashes to the Chum importer one or
    // more times during a Chum exchange
    // IMPORTER handles this event
    ACCESSIBLE_OBJECTS_EVENT: 4,

    // Get ONE microdata (UTF-8 string) object with hash, path (path needed for the server to check
    // if the access is legit)
    // EXPORTER handles this event
    GET_OBJECT: 6,

    // Get ONE microdata (UTF-8 string) object with hash, path (path needed for the server to check
    // if the access is legit)
    // EXPORTER handles this event
    GET_ID_OBJECT: 7,

    // Get BLOB with hash, path (path needed for the server to check if the access is legit)
    // EXPORTER handles this event
    GET_BLOB: 8,

    // Acknowledge reception of objects (after they have been saved locally)
    // EXPORTER handles this event
    ACKNOWLEDGE: 9,

    // Get ONE microdata (UTF-8 string) for metadata object of a crdt object.
    // EXPORTER handles this event
    GET_CRDT_META_OBJECT: 10
} as const;