commit e0fea190ce1fd8228bf7acf7a6d97eb73d781ec4 Author: Sven Slootweg Date: Wed May 12 22:17:17 2021 +0200 WIP diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..1a8e790 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,11 @@ +{ + "extends": "@joepie91/eslint-config", + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "script" + }, + "parser": "babel-eslint", + "plugins": [ + "babel" + ] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ddfe6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +private diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 0000000..b7191e7 --- /dev/null +++ b/babel.config.json @@ -0,0 +1 @@ +{ "presets": ["@babel/preset-env"] } diff --git a/index.js b/index.js new file mode 100644 index 0000000..b48bb3c --- /dev/null +++ b/index.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + createClient: function (hostname, options) { + return { + // FIXME + }; + } +}; diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..d16eb5e --- /dev/null +++ b/notes.md @@ -0,0 +1,437 @@ +IDEA: Add a strictValidation flag that strictly validates all inputs on the client library, to catch errors early? Default enabled, allow disabling for eg. people working on protocol extensions +IDEA: Bot(?) library with E2EE support + +Packages: +- create-session (for each auth method! produce a {homeserver, accessToken, protocolVersions, unstableFeatures} object named a 'session') +- sync-to-events (parse sync response, turn into list of events) +- event-stream (return ppstream with events that keeps polling /sync) +- various method packages for individual operations, that take a 'session' as their argument +- client, which combines all(?) of the above into a single API... or maybe just the method packages and event-stream? and let a `client` be initialized with a 'session' object from elsewhere + +TODO: Write a package for generating a filename based on description + mimetype? + - If description is already a filename with a valid extension for the mimetype (or no mimetype), return as-is + - Otherwise, if mimetype is set, return description + primary extension for mimetype + - If neither a valid filename nor a mimetype is set, return `undefined` + +How to deal with event validation: it's probably easiest to have a two-pass validatorm, where the first pass does any normalization and critical validation, and the second pass does "fail-able" validation - ie. validators that can safely fail and should just produce a warning. + +TODO: Figure out a failsafe mechanism to prevent unencrypted file uploads in service of an encrypted m.file/m.video/etc. event. Maybe default-reject unencrypted files in makeEncryptedMessage wrapper, unless a permitUnencryptedAttachments flag is set (eg. for forwarding attachments from an unencrypted room)? +FIXME: Split makeMessage stuff out of send* modules once we need to encrypt events for E2EE +TODO: Find a way to reduce validation schema duplication for this +FIXME: Gracefully handle 500 M_UNKNOWN: Internal server error (use exponential backoff?) + +Reading magic bytes from a blob: https://gist.github.com/topalex/ad13f76150e0b36de3c4a3d5ba8dc63a + +------ + +# PaginatedResource (cursor) interface + +items: the items in the current chunk +getNext({ [limit] }): retrieves the next chunk of items, in the 'direction of travel' +getPrevious({ [limit] }): retrieves the previous chunk of items, in the 'direction of travel' + +getNext and getPrevious may reject with a NoMoreItems error if the end of pagination in that direction has been reached; either immediately, or after making another speculative request, depending on the underlying technical requirements + +not every implementation may support the `limit` override; in those cases, there is either no fixed limit, or the limit can only be set during the initial request + +------- + +# messages endpoint + +Forwards: + Request initial set somehow + Request again, dir f, from + Response: + start: + +-------- + +# parjs combinators + +## Characters + +digit ASCII(?) digit in +hex ASCII(?) digit in base 16 (hex) +uniDecimal unicode digit in base 10 (decimal) + +letter ASCII letter +uniLetter unicode letter + +lower ASCII lower-case letter +uniLower unicode lower-case letter + +upper ASCII upper-case letter +uniUpper unicode upper-case letter + +space single ASCII space +spaces1 one or more ASCII spaces (ie. space+) +whitespace zero or more ASCII "whitespace characters", whatever that means + +anyChar a single character of any kind +anyCharOf a single character in the specified +noCharOf a single character NOT in the specified + +## Control flow + +replaceState apply the specified parser, but within an isolated scope (which may also be specified as a function(parentState) -> scopedState) +backtrack apply the specified parser and return the result, but do not advance the parser position +later placeholder parser that does nothing, and on which the `.init` method needs to be called later, to (mutably!) fill in the actual logic + +between apply the , and then the specified parser, and then , and return the result of the middle one +thenq apply the specified parser and then the parser, and return the result of the former +qthen apply the specified parser and then the parser, and return the result of the latter +then apply the specified parser and then the parser, and return array containing the results of the [former, latter] +thenPick apply the specified parser and then call the , which dynamically returns the next parser to apply + +## Boolean operations + +not invert the result of the specified parser +or attempt each of the specified until one succeeds or all fail + +## Repeats + +exactly apply the specified parser times, and return an array of results +many apply the specified parser until it runs out of matches, and return an array of results +manyBetween apply , then the specified parser, until the is encountered, then call +manySepBy apply the specified parser until it runs out of matches, and expect each match to be separated with ; return an array of results +manyTill apply the specified parser until the is encountered, and return the results of the specified parser + +## Result and state handling + +must apply the specified parser, and expect its result to pass +mustCapture apply the specified parser, and expect the parser position to have advanced + +each apply the specified parser, then call , and return the *original* result (userState may be mutated) +map apply the specified parser, then call , and return the result of the projection function +mapConst apply the specified parser, then throw away the result and return instead +maybe apply the specified parser, and return its result if it succeeds, or otherwise (ie. a default value) + +flatten like map((result) => result.flat()) +stringify like map((result) => String(result)), but with extra stringification logic + +## Error handling + +recover apply the specified parser, and if it hard-fails, call and, if a new parserState is returned from it, return that instead of the original + +-------- + +Message history access patterns: +- Window seek (fetch N messages before/after marker/ID) +- Insert N messages before/after marker/ID +- Update (message with given ID) +- Purge (messages that have not been accessed for N time / that have been least recently accessed) + +Each 'message' should have an internal list of all applicable events which modify it, eg. edits and reactions + +Maybe have the data structure expose an async 'seek' API which will transparently fetch messages if not locally available? + +-------- + +# /sync response + +Sync response: + +```js +{ + // Required. The batch token to supply in the since param of the next /sync request. + next_batch: "token", + // Updates to rooms. + rooms: { // Rooms + // The rooms that the user has joined. + join: { + "!room_id:example.com": { // Joined Room + // Information about the room which clients may need to correctly render it to users. + summary: { // RoomSummary + // The users which can be used to generate a room name if the room does not have one. Required if the room's m.room.name or m.room.canonical_alias state events are unset or empty. + "m.heroes": ["@foo:example.com", "@bar:example.com"], + // The number of users with membership of join, including the client's own user ID. If this field has not changed since the last sync, it may be omitted. Required otherwise. + "m.joined_member_count": 10, + // The number of users with membership of invite. If this field has not changed since the last sync, it may be omitted. Required otherwise. + "m.invited_member_count": 10 + }, + // Updates to the state, between the time indicated by the since parameter, and the start of the timeline (or all state up to the start of the timeline, if since is not given, or full_state is true). + state: { // State + // List of events. + events: [ stateEvent, stateEvent, stateEvent ] + }, + // The timeline of messages and state changes in the room. + timeline: { // Timeline + // List of events. + events: [ roomEvent, roomEvent, roomEvent ], + // True if the number of events returned was limited by the limit on the filter. + limited: true, + // A token that can be supplied to the from parameter of the rooms/{roomId}/messages endpoint. + prev_batch: "string" + }, + // The ephemeral events in the room that aren't recorded in the timeline or state of the room. e.g. typing. + ephemeral: { // Ephemeral + // List of events. + events: [ event, event, event ] + }, + // The private data that this user has attached to this room. + account_data: { // Account Data + // List of events. + events: [ event, event, event ] + }, + // Counts of unread notifications for this room. Servers MUST include the number of unread notifications in a client's /sync stream, and MUST update it as it changes. Notifications are determined by the push rules which apply to an event. When the user updates their read receipt (either by using the API or by sending an event), notifications prior to and including that event MUST be marked as read. + unread_notifications: { // Unread Notification Counts + // The number of unread notifications for this room with the highlight flag set + highlight_count: 10, + // The total number of unread notifications for this room + notification_count: 10 + }, + } + }, + // The rooms that the user has been invited to. + invite: { + "!room_id:example.com": { // Invited Room + // The state of a room that the user has been invited to. These state events may only have the sender, type, state_key and content keys present. These events do not replace any state that the client already has for the room, for example if the client has archived the room. Instead the client should keep two separate copies of the state: the one from the invite_state and one from the archived state. If the client joins the room then the current state will be given as a delta against the archived state not the invite_state. + invite_state: { // InviteState + // The StrippedState events that form the invite state. + events: [ strippedEvent, strippedEvent, strippedEvent ] + } + } + }, + // The rooms that the user has left or been banned from. + leave: { + "!room_id:example.com": { // Left Room + // The state updates for the room up to the start of the timeline. + state: { // State + // List of events. + events: [ stateEvent, stateEvent, stateEvent ] + }, + // The timeline of messages and state changes in the room up to the point when the user left. + timeline: { // Timeline + // List of events. + events: [ roomEvent, roomEvent, roomEvent ], + // True if the number of events returned was limited by the limit on the filter. + limited: true, + // A token that can be supplied to the from parameter of the rooms/{roomId}/messages endpoint. + prev_batch: "string" + }, + // The private data that this user has attached to this room. + account_data: { // Account Data + // List of events. + events: [ event, event, event ] + }, + } + } + }, + // The updates to the presence status of other users. + presence: { // Presence + // List of events. + events: [ event, event, event ] + }, + // The global private data created by this user. + account_data: { // Account Data + // List of events. + events: [ event, event, event ] + }, + // Optional. Information on the send-to-device messages for the client device. + to_device: { // ToDevice + // List of send-to-device messages. + events: [ toDeviceEvent, toDeviceEvent, toDeviceEvent ] + }, + // Optional. Information on e2e device updates. Note: only present on an incremental sync. + device_lists: { // DeviceLists + // List of users who have updated their device identity keys, or who now share an encrypted room with the client since the previous sync response. + changed: [ "string", "string", "string" ], + // List of users with whom we do not share any encrypted rooms anymore since the previous sync response. + left: [ "string", "string", "string" ] + }, + // Optional. For each key algorithm, the number of unclaimed one-time keys currently held on the server for this device. + device_one_time_keys_count: { + [algorithmName]: 10, + [algorithmName]: 10 + } +} +``` + +Event: + +```js +{ + // Required. The fields in this object will vary depending on the type of event. When interacting with the REST API, this is the HTTP body. + content: varyingObject, + // Required. The type of event. This SHOULD be namespaced similar to Java package naming conventions e.g. 'com.example.subdomain.event.type' + type: "string", +} +``` + +Room event: + +```js +{ + ... Event, // inherits + // Required. The globally unique event identifier. + event_id: "string", + // Required. The type of event. This SHOULD be namespaced similar to Java package naming conventions e.g. 'com.example.subdomain.event.type' + type: "string", + // Required. Contains the fully-qualified ID of the user who sent this event. + sender: "string", + // Required. The fields in this object will vary depending on the type of event. When interacting with the REST API, this is the HTTP body. + content: varyingObject, + // Required. Timestamp in milliseconds on originating homeserver when this event was sent. + origin_server_ts: 10, + // Required. The ID of the room associated with this event. Will not be present on events that arrive through /sync, despite being required everywhere else. + room_id: "string", + // Contains optional extra information about the event. + unsigned: { // UnsignedData + // The time in milliseconds that has elapsed since the event was sent. This field is generated by the local homeserver, and may be incorrect if the local time on at least one of the two servers is out of sync, which can cause the age to either be negative or greater than it actually is. + age: 10, + // Optional. The event that redacted this event, if any. + redacted_because: event, + // The client-supplied transaction ID, if the client being given the event is the same one which sent it. + transaction_id: "string" + } +} +``` + +State event: + +```js +{ + ... RoomEvent, // inherits + // Required. A unique key which defines the overwriting semantics for this piece of room state. This value is often a zero-length string. The presence of this key makes this event a State Event. State keys starting with an @ are reserved for referencing user IDs, such as room members. With the exception of a few events, state events set with a given user's ID as the state key MUST only be set by that user. + state_key: "string" + // Optional. The previous content for this event. If there is no previous content, this key will be missing. + prev_content: varyingObject, +} +``` + +Stripped event: + +```js +{ + // Required. The type for the event. + type: "string", + // Required. The content for the event. + content: varyingObject, + // Required. The state_key for the event. + state_key: "string", + // Required. The sender for the event. + sender: "string" +} +``` + +To-device event: + +```js +{ + // The content of this event. The fields in this object will vary depending on the type of event. + content: varyingObject, + // The Matrix user ID of the user who sent this event. + sender: "string", + // The type of event. + type: "string" +} +``` + +----------- + +Filter creation request: + +```js +{ + // List of event fields to include. If this list is absent then all fields are included. The entries may include '.' characters to indicate sub-fields. So ['content.body'] will include the 'body' field of the 'content' object. A literal '.' character in a field name may be escaped using a '\'. A server may include more fields than were requested. + event_fields: [ "field_path", "field_path", "field_path" ], + // The format to use for events. 'client' will return the events in a format suitable for clients. 'federation' will return the raw event as received over federation. The default is 'client'. + event_format: ( "client" || "federation" ), + // The presence updates to include. + presence: EventFilter, + // The user account data that isn't associated with rooms to include. + account_data: EventFilter, + // Filters to be applied to room data. + room: { + // A list of room IDs to exclude. If this list is absent then no rooms are excluded. A matching room will be excluded even if it is listed in the 'rooms' filter. This filter is applied before the filters in ephemeral, state, timeline or account_data + not_rooms: [ "room_id", "room_id", "room_id" ], + // A list of room IDs to include. If this list is absent then all rooms are included. This filter is applied before the filters in ephemeral, state, timeline or account_data + rooms: [ "room_id", "room_id", "room_id" ], + // Include rooms that the user has left in the sync, default false + include_leave: false, + // The state events to include for rooms. + state: StateFilter, + // The events that aren't recorded in the room history, e.g. typing and receipts, to include for rooms. + ephemeral: StateFilter, + // The message and state update events to include for rooms. + timeline: StateFilter, + // The per user account data to include for rooms. + account_data: StateFilter + } +} +``` + +EventFilter: + +```js +{ + // The maximum number of events to return. + limit: 10, + // A list of event types to include. If this list is absent then all event types are included. A '*' can be used as a wildcard to match any sequence of characters. + types: [ "m.type", "m.type", "m.type" ], + // A list of event types to exclude. If this list is absent then no event types are excluded. A matching type will be excluded even if it is listed in the 'types' filter. A '*' can be used as a wildcard to match any sequence of characters. + not_types: [ "m.type", "m.type", "m.type" ], + // A list of senders IDs to include. If this list is absent then all senders are included. + senders: [ "user_id", "user_id", "user_id" ], + // A list of sender IDs to exclude. If this list is absent then no senders are excluded. A matching sender will be excluded even if it is listed in the 'senders' filter. + not_senders: [ "user_id", "user_id", "user_id" ], +} +``` + +StateFilter: + +```js +{ + ... EventFilter, // inherits + // A list of room IDs to include. If this list is absent then all rooms are included. + rooms: [ "room_id", "room_id", "room_id" ], + // A list of room IDs to exclude. If this list is absent then no rooms are excluded. A matching room will be excluded even if it is listed in the 'rooms' filter. + not_rooms: [ "room_id", "room_id", "room_id" ], + // If true, enables lazy-loading of membership events. Defaults to false. + lazy_load_members: false, + // If true, sends all membership events for all events, even if they have already been sent to the client. Does not apply unless lazy_load_members is true. Defaults to false. + include_redundant_members: false, + // If true, includes only events with a url key in their content. If false, excludes those events. If omitted, url key is not considered for filtering. + contains_url: ( true || false || null ) +} +``` + +----------- + +Junk +---- + + // Optional. This key will only be present for state events. A unique key which defines the overwriting semantics for this piece of room state. + state_key: "string", + // The MXID of the user who sent this event. + sender: "string", + // The content of this event. The fields in this object will vary depending on the type of event. + content: varyingObject, + // Timestamp in milliseconds on originating homeserver when this event was sent. + origin_server_ts: 10, + // Information about this event which was not sent by the originating homeserver + unsigned: { // Unsigned + // Time in milliseconds since the event was sent. + age: 10, + // Optional. The event that redacted this event, if any. + redacted_because: event + // Optional. The transaction ID set when this message was sent. This key will only be present for message events sent by the device calling this API. + transaction_id: "string", + // Optional. The previous content for this state. This will be present only for state events appearing in the timeline. If this is not a state event, or there is no previous content, this key will be missing. + prev_content: varyingObject, + } + + +item 14 + +Filter: + +```json +{ + "room": { + "state": { + "lazy_load_members": true + } + } +} +``` diff --git a/package.json b/package.json new file mode 100644 index 0000000..a02851f --- /dev/null +++ b/package.json @@ -0,0 +1,77 @@ +{ + "name": "@modular-matrix/client", + "description": "A Matrix client library", + "version": "0.1.0", + "main": "index.js", + "repository": "http://git.cryto.net/modular-matrix/client.git", + "author": "Sven Slootweg ", + "license": "WTFPL OR CC0-1.0", + "devDependencies": { + "@babel/core": "^7.8.6", + "@babel/node": "^7.8.4", + "@babel/preset-env": "^7.8.6", + "@joepie91/eslint-config": "^1.1.0", + "babel-eslint": "^10.1.0", + "eslint": "^6.2.2", + "eslint-plugin-babel": "^5.3.0" + }, + "dependencies": { + "@joepie91/unreachable": "^1.0.0", + "@modular-matrix/is-homeserver-url": "^0.1.0", + "@modular-matrix/is-mxc-url": "^0.1.0", + "@modular-matrix/parse-mxc": "^1.0.1", + "@promistream/buffer": "^0.1.0", + "@promistream/collect": "^0.1.0", + "@promistream/end-of-stream": "^0.1.0", + "@promistream/filter": "^0.1.1", + "@promistream/map": "^0.1.1", + "@promistream/pipe": "^0.1.0", + "@promistream/sequentialize": "^0.1.0", + "@promistream/simple-sink": "^0.1.0", + "@promistream/simple-source": "^0.1.1", + "@validatem/allow-extra-properties": "^0.1.0", + "@validatem/any-property": "^0.1.3", + "@validatem/anything": "^0.1.0", + "@validatem/array-of": "^0.1.2", + "@validatem/core": "^0.3.11", + "@validatem/default-to": "^0.1.0", + "@validatem/either": "^0.1.9", + "@validatem/error": "^1.1.0", + "@validatem/ignore-result": "^0.1.1", + "@validatem/is-boolean": "^0.1.1", + "@validatem/is-function": "^0.1.0", + "@validatem/is-integer": "^0.1.0", + "@validatem/is-non-empty-string": "^0.1.0", + "@validatem/is-number": "^0.1.3", + "@validatem/is-plain-object": "^0.1.1", + "@validatem/is-string": "^0.1.1", + "@validatem/is-url": "^0.1.0", + "@validatem/matches-format": "^0.1.0", + "@validatem/one-of": "^0.1.1", + "@validatem/require-either": "^0.1.0", + "@validatem/required": "^0.1.1", + "@validatem/wrap-error": "^0.1.3", + "as-expression": "^1.0.0", + "assure-array": "^1.0.0", + "axios": "^0.19.0", + "bluebird": "^3.7.2", + "concat-arrays": "^2.0.0", + "create-event-emitter": "^1.0.0", + "default-value": "^1.0.0", + "dotty": "^0.1.0", + "flatten": "^1.0.3", + "image-size": "^0.8.3", + "is-negative-zero": "^2.0.0", + "make-url": "^0.0.1", + "match-value": "^1.1.0", + "p-defer": "^3.0.0", + "p-try": "^2.2.0", + "parjs": "^0.12.7", + "split-filter": "^1.1.3", + "split-limit": "^1.0.4", + "supports-color": "^7.1.0", + "syncpipe": "^1.0.0", + "url-join": "^4.0.1", + "validatem": "^0.2.0" + } +} diff --git a/repro-bug.js b/repro-bug.js new file mode 100644 index 0000000..c3b4fbc --- /dev/null +++ b/repro-bug.js @@ -0,0 +1,14 @@ +"use strict"; + +const { letter } = require("parjs"); +const { then } = require("parjs/combinators"); + +function combine([ previous, current ]) { + return previous.concat([ current ]); +} + +let test = letter().pipe( + then(combine) +); + +console.log(test.parse(process.argv[2])); diff --git a/spec-bugs-to-file.md b/spec-bugs-to-file.md new file mode 100644 index 0000000..7fcbe90 --- /dev/null +++ b/spec-bugs-to-file.md @@ -0,0 +1,2 @@ +- Seems that in a `/sync` response, `timeline` can contain both room *and* state events (ie. it can contain events with state-event-specific keys), but this is not actually mentioned anywhere? +- For `/sync`, `Timeline` specifies for `prev_batch`: "can be supplied to the from parameter of the rooms/{roomId}/messages endpoint" -- but it is not specified what direction this is relevant to. diff --git a/spec-bugs.md b/spec-bugs.md new file mode 100644 index 0000000..5c7d8a2 --- /dev/null +++ b/spec-bugs.md @@ -0,0 +1,7 @@ +# Error types + +## PUT /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId} +- No error types specified at all + +## POST /_matrix/media/r0/upload +- No way to distinguish between different 'forbidden' errors; no specific error code defined for 'forbidden filetype', 'exceeded quota', etc. diff --git a/src/login.js b/src/login.js new file mode 100644 index 0000000..75b9d4b --- /dev/null +++ b/src/login.js @@ -0,0 +1,24 @@ +"use strict"; + +const Promise = require("bluebird"); +const axios = require("axios"); + +module.exports = function createLoginAgent({ homeserver }) { + return { + getMethods: function () { + return Promise.try(() => { + // axios get /login + }).then((response) => { + // extract available login methods + }); + }, + login: function ({ token, username, password }) { + // validate either token or (username, password) supplied + return Promise.try(() => { + // axios post /login + }).then((response) => { + // TODO + }); + } + }; +}; diff --git a/src/match-value.js b/src/match-value.js new file mode 100644 index 0000000..6db20e8 --- /dev/null +++ b/src/match-value.js @@ -0,0 +1,32 @@ +"use strict"; + +// FIXME: Switch to `match-value` package + +function getValue(value, functionsAreLiterals) { + if (typeof value === "function" && !functionsAreLiterals) { + return value(); + } else { + return value; + } +} + +function doMatchValue(value, arms, functionsAreLiterals) { + if (value == null) { + return value; + } else if (arms[value] !== undefined) { + // NOTE: We intentionally only check for `undefined` here (and below), since we want to allow the mapped-to value to be an explicit `null`. + return getValue(arms[value], functionsAreLiterals); + } else if (arms._ !== undefined) { + return getValue(arms._, functionsAreLiterals); + } else { + throw new Error(`No match arm found for value '${value}'`); + } +} + +module.exports = function matchValue(value, arms) { + return doMatchValue(value, arms, true); +}; + +module.exports.literal = function matchValueLiteral(value, arms) { + return doMatchValue(value, arms, false); +}; diff --git a/src/packages/await-image-element-load/index.js b/src/packages/await-image-element-load/index.js new file mode 100644 index 0000000..19868c1 --- /dev/null +++ b/src/packages/await-image-element-load/index.js @@ -0,0 +1,15 @@ +"use strict"; + +module.exports = function awaitImageLoad(imageElement) { + // FIXME: Validation + return new Promise((resolve, reject) => { + imageElement.addEventListener("load", (_event) => { + resolve(); + }); + + imageElement.addEventListener("error", (_event) => { + // The event does not have an actual error stored on it + reject(new Error("Could not load image")); // FIXME: Error type + }); + }); +}; diff --git a/src/packages/await-video-element-load/index.js b/src/packages/await-video-element-load/index.js new file mode 100644 index 0000000..550fcb5 --- /dev/null +++ b/src/packages/await-video-element-load/index.js @@ -0,0 +1,15 @@ +"use strict"; + +module.exports = function awaitVideoElementLoad(videoElement) { + // FIXME: Validation + return new Promise((resolve, reject) => { + videoElement.addEventListener("loadeddata", (_event) => { + resolve(); + }); + + videoElement.addEventListener("error", (event) => { + // FIXME: Check that this actually works as expected + reject(event.error); + }); + }); +}; diff --git a/src/packages/calculate-thumbnail-size/index.js b/src/packages/calculate-thumbnail-size/index.js new file mode 100644 index 0000000..d390cb7 --- /dev/null +++ b/src/packages/calculate-thumbnail-size/index.js @@ -0,0 +1,19 @@ +"use strict"; + +module.exports = function calculateThumbnailSize({ sourceWidth, sourceHeight, targetWidth, targetHeight }) { + // FIXME: Validation + // TODO: crop method, currently only implements scale/fit (crop would probably require inverting match arms, ceil instead of floor, and providing a drawing offset) + let sourceAspectRatio = sourceWidth / sourceHeight; + let targetAspectRatio = targetWidth / targetHeight; + + let resizeFactor = (targetAspectRatio > sourceAspectRatio) + // Target is wider than source, constrain by height + ? targetHeight / sourceHeight + // Target is taller than or equally-ratioed to source, constrain by width + : targetWidth / sourceWidth; + + return { + width: Math.floor(sourceWidth * resizeFactor), + height: Math.floor(sourceHeight * resizeFactor) + }; +}; diff --git a/src/packages/calculate-thumbnail-size/test.js b/src/packages/calculate-thumbnail-size/test.js new file mode 100644 index 0000000..ee1770b --- /dev/null +++ b/src/packages/calculate-thumbnail-size/test.js @@ -0,0 +1,12 @@ +"use strict"; + +const calculateThumbnailSize = require("./"); + +let calculated = calculateThumbnailSize({ + sourceWidth: 200, + sourceHeight: 100, + targetWidth: 80, + targetHeight: 60 +}); + +console.log(calculated); diff --git a/src/packages/canvas-to-blob/index.js b/src/packages/canvas-to-blob/index.js new file mode 100644 index 0000000..b6518c9 --- /dev/null +++ b/src/packages/canvas-to-blob/index.js @@ -0,0 +1,10 @@ +"use strict"; + +module.exports = function canvasToBlob(canvas, mimeType) { + // FIXME: Validation + return new Promise((resolve, _reject) => { + canvas.toBlob(function(blob) { + resolve(blob); + }, mimeType); + }); +}; diff --git a/src/packages/diff-lists/index.js b/src/packages/diff-lists/index.js new file mode 100644 index 0000000..8cf85ed --- /dev/null +++ b/src/packages/diff-lists/index.js @@ -0,0 +1,14 @@ +"use strict"; + +// TODO: Can probably be made more performant by precomputing a key -> item mapping, at the cost of multiple items with the same key becoming impossible + +module.exports = function diffLists(oldArray, newArray, keyFunction = (item) => item) { + // NOTE: This only detects additions and removals, *not* order changes! The order is not relevant for our usecase. + let oldSet = new Set(oldArray.map((item) => keyFunction(item))); + let newSet = new Set(newArray.map((item) => keyFunction(item))); + + let removed = oldArray.filter((item) => !newSet.has(keyFunction(item))); + let added = newArray.filter((item) => !oldSet.has(keyFunction(item))); + + return { removed, added }; +}; diff --git a/src/packages/element-to-thumbnail/index.js b/src/packages/element-to-thumbnail/index.js new file mode 100644 index 0000000..a5fa57b --- /dev/null +++ b/src/packages/element-to-thumbnail/index.js @@ -0,0 +1,57 @@ +"use strict"; + +const Promise = require("bluebird"); +const asExpression = require("as-expression"); +const unreachable = require("@joepie91/unreachable")("@modular-matrix/client"); // FIXME when packaging +const getImageDimensions = require("../get-image-dimensions"); +const getVideoDimensions = require("../get-video-dimensions"); +const calculateThumbnailSize = require("../calculate-thumbnail-size"); +const canvasToBlob = require("../canvas-to-blob"); + +const { validateOptions } = require("@validatem/core"); +const required = require("@validatem/required"); +const isInteger = require("@validatem/is-integer"); +const oneOf = require("@validatem/one-of"); + +module.exports = function elementToThumbnail(_options) { + // TODO: Add a 'crop' method in the future? + let { element, maximumWidth, maximumHeight, mimetype } = validateOptions(arguments, { + element: [ required ], // FIXME: Element type validation + maximumWidth: [ required, isInteger ], // FIXME: Positive integer + maximumHeight: [ required, isInteger ], // FIXME: Positive integer + mimetype: [ required, oneOf([ "image/png", "image/jpeg" ]) ] + }); + + let dimensions = asExpression(() => { + if (element instanceof HTMLImageElement) { + return getImageDimensions(element); + } else if (element instanceof HTMLVideoElement) { + return getVideoDimensions(element); + } else { + unreachable("Unrecognized element type"); + } + }); + + let { width, height } = calculateThumbnailSize({ + sourceWidth: dimensions.width, + sourceHeight: dimensions.height, + targetWidth: maximumWidth, + targetHeight: maximumHeight + }); + + let canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + + canvas.getContext("2d").drawImage(element, 0, 0, width, height); + + return Promise.try(() => { + return canvasToBlob(canvas, mimetype); + }).then((blob) => { + return { + width: width, + height: height, + blob: blob + }; + }); +}; diff --git a/src/packages/ensure-valid-dimension/index.js b/src/packages/ensure-valid-dimension/index.js new file mode 100644 index 0000000..56a3177 --- /dev/null +++ b/src/packages/ensure-valid-dimension/index.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = function ensureValidDimension(value) { + if (value > 0) { + return value; + } else { + throw new Error(`Encountered invalid dimension value; this is probably a bug, please report it!`); + } +}; diff --git a/src/packages/event-add-common-fields/index.js b/src/packages/event-add-common-fields/index.js new file mode 100644 index 0000000..a646f66 --- /dev/null +++ b/src/packages/event-add-common-fields/index.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = function addCommonFields(protocolEvent, newEvent) { + return Object.assign({ + id: protocolEvent.event_id, // FIXME: Make sure that all derived timeline events set this correctly, like with power levels + sourceEventID: protocolEvent.event_id, + timestamp: protocolEvent.origin_server_ts + }, newEvent); +}; diff --git a/src/packages/event-deduplicator/index.js b/src/packages/event-deduplicator/index.js new file mode 100644 index 0000000..36ddf37 --- /dev/null +++ b/src/packages/event-deduplicator/index.js @@ -0,0 +1,7 @@ +"use strict"; + +const itemDeduplicator = require("../item-deduplicator"); + +module.exports = function createEventDeduplicator() { + return itemDeduplicator((event) => event.event_id); +}; diff --git a/src/packages/file-to-data-url/index.js b/src/packages/file-to-data-url/index.js new file mode 100644 index 0000000..9eb5250 --- /dev/null +++ b/src/packages/file-to-data-url/index.js @@ -0,0 +1,18 @@ +"use strict"; + +module.exports = function fileToDataURL(dataUrl) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + + reader.onload = function(event) { + resolve(event.target.result); + }; + + reader.onerror = function(event) { + // FIXME: Verify that this works + reject(event.error); + }; + + reader.readAsDataURL(dataUrl); + }); +}; diff --git a/src/packages/generate-transaction-id/index.js b/src/packages/generate-transaction-id/index.js new file mode 100644 index 0000000..465e345 --- /dev/null +++ b/src/packages/generate-transaction-id/index.js @@ -0,0 +1,7 @@ +"use strict"; + +const nanoid = require("nanoid").nanoid; + +module.exports = function generateTransactionID() { + return nanoid(); +}; diff --git a/src/packages/get-file-url/index.js b/src/packages/get-file-url/index.js new file mode 100644 index 0000000..2a97955 --- /dev/null +++ b/src/packages/get-file-url/index.js @@ -0,0 +1,45 @@ +"use strict"; + +const url = require("url"); + +const makeURL = require("make-url"); +const parseMXC = require("@modular-matrix/parse-mxc"); + +const { validateOptions } = require("@validatem/core"); +const required = require("@validatem/required"); +const requireEither = require("@validatem/require-either"); +const isString = require("@validatem/is-string"); +const isMXC = require("@modular-matrix/is-mxc-url"); +const isSession = require("@modular-matrix/is-session"); +const isHomeserverURL = require("@modular-matrix/is-homeserver-url"); + +module.exports = function getFileURL(_session, _options) { + let options = validateOptions(arguments, [ + required, + requireEither([ "session", "homeserver" ]), + { + session: [ isSession ], + homeserver: [ isHomeserverURL ], + url: [ required, isMXC ], + filename: [ isString ] + } + ]); + + let homeserverURL = (options.session != null) + ? options.session.homeserver + : options.homeserver; + + let parsedMXC = parseMXC.parse(options.url); + + let urlTemplate = (options.filename != null) + ? "/_matrix/media/r0/download/:serverName/:mediaID/:filename" + : "/_matrix/media/r0/download/:serverName/:mediaID"; + + let path = makeURL(urlTemplate, { + serverName: parsedMXC.homeserver, + mediaID: parsedMXC.id, + filename: options.filename + }); + + return url.resolve(homeserverURL, path); +}; diff --git a/src/packages/get-file-url/test.js b/src/packages/get-file-url/test.js new file mode 100644 index 0000000..6459247 --- /dev/null +++ b/src/packages/get-file-url/test.js @@ -0,0 +1,18 @@ +"use strict"; + +const Promise = require("bluebird"); +const createSession = require("@modular-matrix/create-session"); +const getFileURL = require("."); + +return Promise.try(() => { + return createSession("https://pixie.town/", { accessToken: require("../../../private/access-token") }); +}).then((session) => { + let url = getFileURL({ + session: session, +// url: "mxc://pixie.town/NKsNxCkItRtbpRunyNYHCxsW", + url: "mxc://matrix.org/qmhOpvpttfkmucqyycSzcfvk", + filename: "image.jpg" + }); + + console.log(url); +}); diff --git a/src/packages/get-filesize/index.js b/src/packages/get-filesize/index.js new file mode 100644 index 0000000..54ad62f --- /dev/null +++ b/src/packages/get-filesize/index.js @@ -0,0 +1,39 @@ +"use strict"; + +const Promise = require("bluebird"); +const fs = require("fs"); + +function analyzeBuffer(buffer) { + return buffer.length; +} + +function analyzeBlob(blob) { + return blob.size; +} + +function analyzeStream(stream) { + return Promise.try(() => { + return fs.promises.stat(stream.path); + }).then((stat) => { + return stat.size; + }); +} + +function analyzeString(string) { + // NOTE: string.length would produce the size in *code units*, but we want the size in *bytes* + return (new TextEncoder().encode(string)).length; +} + +module.exports = function getFilesize(file) { + if (typeof file === "string") { + return analyzeString(file); + } else if (typeof Blob !== "undefined" && file instanceof Blob) { + return analyzeBlob(file); + } else if (Buffer.isBuffer(file)) { + return analyzeBuffer(file); + } else if (file._readableState != null && file.path != null) { + return analyzeStream(file); + } else { + throw new Error(`Invalid file passed`); // FIXME: Validate + } +}; diff --git a/src/packages/get-image-dimensions/index.js b/src/packages/get-image-dimensions/index.js new file mode 100644 index 0000000..1cb7778 --- /dev/null +++ b/src/packages/get-image-dimensions/index.js @@ -0,0 +1,12 @@ +"use strict"; + +const ensureValidDimension = require("../ensure-valid-dimension"); + +module.exports = function getImageDimensions(imageElement) { + // FIXME: Validation + + return { + width: ensureValidDimension(imageElement.naturalWidth), + height: ensureValidDimension(imageElement.naturalHeight) + }; +}; diff --git a/src/packages/get-thumbnail-url copy/index.js b/src/packages/get-thumbnail-url copy/index.js new file mode 100644 index 0000000..29a3dc0 --- /dev/null +++ b/src/packages/get-thumbnail-url copy/index.js @@ -0,0 +1,50 @@ +"use strict"; + +const url = require("url"); + +const makeURL = require("make-url"); +const parseMXC = require("@modular-matrix/parse-mxc"); + +const { validateOptions } = require("@validatem/core"); +const required = require("@validatem/required"); +const requireEither = require("../require-either"); +const isInteger = require("@validatem/is-integer"); +const oneOf = require("@validatem/one-of"); +const isMXC = require("@modular-matrix/is-mxc-url"); +const isSession = require("@modular-matrix/is-session"); +const isHomeserverURL = require("@modular-matrix/is-homeserver-url"); + +module.exports = function getThumbnailURL(_session, _options) { + let options = validateOptions(arguments, [ + required, + requireEither([ "session", "homeserver" ]), + { + session: [ isSession ], + homeserver: [ isHomeserverURL ], + url: [ required, isMXC ], + method: [ required, oneOf([ "crop", "scale" ]) ], + minimumWidth: [ required, isInteger ], + minimumHeight: [ required, isInteger ] + } + ]); + + let homeserverURL = (options.session != null) + ? options.session.homeserver + : options.homeserver; + + let parsedMXC = parseMXC.parse(options.url); + + let path = makeURL("/_matrix/media/r0/thumbnail/:serverName/:mediaID", { + serverName: parsedMXC.homeserver, + mediaID: parsedMXC.id + }); + + return url.resolve(homeserverURL, url.format({ + pathname: path, + query: { + method: options.method, + width: options.minimumWidth, + height: options.minimumHeight + } + })); +}; diff --git a/src/packages/get-thumbnail/index.js b/src/packages/get-thumbnail/index.js new file mode 100644 index 0000000..08b3cb4 --- /dev/null +++ b/src/packages/get-thumbnail/index.js @@ -0,0 +1,66 @@ +"use strict"; + +const Promise = require("bluebird"); +const mmAxios = require("@modular-matrix/axios"); +const getThumbnailURL = require("../get-thumbnail-url"); +const withoutKeys = require("../without-keys"); +const universalImageMetadata = require("../universal-image-metadata"); +const defaultValue = require("default-value"); + +const { validateOptions } = require("@validatem/core"); +const required = require("@validatem/required"); +const requireEither = require("@validatem/require-either"); +const isInteger = require("@validatem/is-integer"); +const isBoolean = require("@validatem/is-boolean"); +const defaultTo = require("@validatem/default-to"); +const oneOf = require("@validatem/one-of"); +const isMXC = require("@modular-matrix/is-mxc-url"); +const isSession = require("@modular-matrix/is-session"); +const isHomeserverURL = require("@modular-matrix/is-homeserver-url"); + +module.exports = function getThumbnail(_options) { + let options = validateOptions(arguments, [ + required, + requireEither([ "session", "homeserver" ]), + { + session: [ isSession ], + homeserver: [ isHomeserverURL ], + url: [ required, isMXC ], + method: [ required, oneOf([ "fit", "fill" ]) ], + minimumWidth: [ required, isInteger ], + minimumHeight: [ required, isInteger ], + stream: [ isBoolean, defaultTo(false) ] + } + ]); + + let axios = mmAxios({ + session: options.session, + homeserver: options.homeserver + }); + + let thumbnailOptions = withoutKeys(options, [ "stream" ]); + let url = getThumbnailURL(thumbnailOptions); + + return Promise.try(() => { + return axios.get(url, { + // TODO: Support "blob" in browsers? + responseType: (options.stream === true) ? "stream" : "arraybuffer" + }); + }).then((response) => { + return Promise.try(() => { + if (options.stream === false) { + return universalImageMetadata(response.data); + } else { + return { width: undefined, height: undefined, mimetype: undefined }; + } + }).then((metadata) => { + return { + buffer: (options.stream === false) ? response.data : undefined, + stream: (options.stream === true) ? response.data : undefined, + mimetype: defaultValue(metadata.mimetype, response.headers["content-type"]), + width: metadata.width, + height: metadata.height + }; + }); + }); +}; diff --git a/src/packages/get-upload-size-limit/index.js b/src/packages/get-upload-size-limit/index.js new file mode 100644 index 0000000..f44ac2e --- /dev/null +++ b/src/packages/get-upload-size-limit/index.js @@ -0,0 +1,22 @@ +"use strict"; + +const Promise = require("bluebird"); +const mmAxios = require("@modular-matrix/axios"); + +const { validateArguments } = require("@validatem/core"); +const required = require("@validatem/required"); +const isSession = require("@modular-matrix/is-session"); + +module.exports = function getThumbnail(_session) { + let [ session ] = validateArguments(arguments, { + session: [ required, isSession ] + }); + + let axios = mmAxios({ session: session }); + + return Promise.try(() => { + return axios.get("/media/r0/config"); + }).then((response) => { + return { limit: response.data["m.upload.size"] }; + }); +}; diff --git a/src/packages/get-upload-size-limit/test.js b/src/packages/get-upload-size-limit/test.js new file mode 100644 index 0000000..b437162 --- /dev/null +++ b/src/packages/get-upload-size-limit/test.js @@ -0,0 +1,15 @@ +"use strict"; + +const Promise = require("bluebird"); +const createSession = require("@modular-matrix/create-session"); +const getUploadSizeLimit = require("."); + +return Promise.try(() => { + return createSession("https://pixie.town/", { accessToken: require("../../../private/access-token") }); +}).then((session) => { + return Promise.try(() => { + return getUploadSizeLimit(session); + }).then((result) => { + console.log(result); + }); +}); diff --git a/src/packages/get-video-dimensions/index.js b/src/packages/get-video-dimensions/index.js new file mode 100644 index 0000000..9392d8c --- /dev/null +++ b/src/packages/get-video-dimensions/index.js @@ -0,0 +1,12 @@ +"use strict"; + +const ensureValidDimension = require("../ensure-valid-dimension"); + +module.exports = function getVideoDimensions(videoElement) { + // FIXME: Validation + + return { + width: ensureValidDimension(videoElement.videoWidth), + height: ensureValidDimension(videoElement.videoHeight) + }; +}; diff --git a/src/packages/is-device-event/index.js b/src/packages/is-device-event/index.js new file mode 100644 index 0000000..2a43619 --- /dev/null +++ b/src/packages/is-device-event/index.js @@ -0,0 +1,11 @@ +"use strict"; + +const required = require("@validatem/required"); + +const isEvent = require("../is-event"); +const isMatrixID = require("../is-matrix-id"); + +module.exports = { + ... isEvent, + sender: [ required, isMatrixID ] +}; diff --git a/src/packages/is-event-id/index.js b/src/packages/is-event-id/index.js new file mode 100644 index 0000000..fc15beb --- /dev/null +++ b/src/packages/is-event-id/index.js @@ -0,0 +1,6 @@ +"use strict"; + +const isString = require("@validatem/is-string"); + +// FIXME: Improve validation +module.exports = isString; diff --git a/src/packages/is-event/index.js b/src/packages/is-event/index.js new file mode 100644 index 0000000..7ff9669 --- /dev/null +++ b/src/packages/is-event/index.js @@ -0,0 +1,10 @@ +"use strict"; + +const required = require("@validatem/required"); +const isString = require("@validatem/is-string"); +const isPlainObject = require("@validatem/is-plain-object"); + +module.exports = { + type: [ required, isString ], + content: [ required, isPlainObject ] +}; diff --git a/src/packages/is-homeserver-url/index.js b/src/packages/is-homeserver-url/index.js new file mode 100644 index 0000000..3921b20 --- /dev/null +++ b/src/packages/is-homeserver-url/index.js @@ -0,0 +1,8 @@ +"use strict"; + +const isURL = require("@validatem/is-url"); + +// FIXME: Enforce that the path component is empty! Homeservers must always exist at the root of their hostname +// FIXME: Replace all isString instances where this should be used instead +module.exports = isURL(["https"]); +// MARKER: Package and fix this diff --git a/src/packages/is-matrix-id/index.js b/src/packages/is-matrix-id/index.js new file mode 100644 index 0000000..fc15beb --- /dev/null +++ b/src/packages/is-matrix-id/index.js @@ -0,0 +1,6 @@ +"use strict"; + +const isString = require("@validatem/is-string"); + +// FIXME: Improve validation +module.exports = isString; diff --git a/src/packages/is-messages-response/index.js b/src/packages/is-messages-response/index.js new file mode 100644 index 0000000..885f881 --- /dev/null +++ b/src/packages/is-messages-response/index.js @@ -0,0 +1,19 @@ +"use strict"; + +const required = require("@validatem/required"); +const isString = require("@validatem/is-string"); +const arrayOf = require("@validatem/array-of"); + +const isTimelineEvent = require("../is-timeline-event"); +const isStateEvent = require("../is-state-event"); +const optionalArray = require("../optional-array"); + +let isTimelineList = arrayOf([ required, isTimelineEvent ]); +let isStateList = arrayOf([ required, isStateEvent ]); + +module.exports = { + start: [ required, isString ], + end: [ isString ], + chunk: optionalArray(isTimelineList), + state: optionalArray(isStateList) +}; diff --git a/src/packages/is-paginated-chunk-of/index.js b/src/packages/is-paginated-chunk-of/index.js new file mode 100644 index 0000000..90f97b2 --- /dev/null +++ b/src/packages/is-paginated-chunk-of/index.js @@ -0,0 +1,13 @@ +"use strict"; + +const arrayOf = require("@validatem/array-of"); +const isBoolean = require("@validatem/is-boolean"); +const isInteger = require("@validatem/is-integer"); + +module.exports = function isPaginatedChunkOf(rules) { + return { + chunk: arrayOf(rules), + limited: isBoolean, + count: isInteger + }; +}; diff --git a/src/packages/is-presence-event/index.js b/src/packages/is-presence-event/index.js new file mode 100644 index 0000000..6d91c67 --- /dev/null +++ b/src/packages/is-presence-event/index.js @@ -0,0 +1,12 @@ +"use strict"; + +const required = require("@validatem/required"); + +const isEvent = require("../is-event"); +const isMatrixID = require("../is-matrix-id"); + +// NOTE: Unspecced, see https://github.com/matrix-org/matrix-doc/issues/2680 - this would normally just be `isEvent` as per the spec +module.exports = { + ... isEvent, + sender: [ required, isMatrixID ] +}; diff --git a/src/packages/is-room-event/index.js b/src/packages/is-room-event/index.js new file mode 100644 index 0000000..ae83292 --- /dev/null +++ b/src/packages/is-room-event/index.js @@ -0,0 +1,68 @@ +"use strict"; + +const required = require("@validatem/required"); +const isString = require("@validatem/is-string"); +const isInteger = require("@validatem/is-integer"); +const isPlainObject = require("@validatem/is-plain-object"); + +const isEvent = require("../is-event"); +const isMatrixID = require("../is-matrix-id"); +const isEventID = require("../is-event-id"); +const isRoomID = require("../is-room-id"); +const optionalObject = require("../optional-object"); +const isPaginatedChunkOf = require("../is-paginated-chunk-of"); + +module.exports = { + ... isEvent, + event_id: [ required, isString ], + sender: [ required, isMatrixID ], + origin_server_ts: [ required, isInteger ], + // In spec, but missing from Room Event format: https://github.com/matrix-org/matrix-doc/issues/2684 + redacts: isEventID, // FIXME: Make required when redaction-type event + // Spec omission: https://github.com/matrix-org/matrix-doc/issues/2685 + age_ts: isInteger, + room_id: [ isRoomID ], // FIXME: Not present on /sync, but will need to be required-checked for event validation elsewhere + // Synapse bug: https://github.com/matrix-org/synapse/issues/7925 + age: isInteger, + // Synapse bug: https://github.com/matrix-org/synapse/issues/7924 + user_id: isMatrixID, + // Synapse bug: https://github.com/matrix-org/synapse/issues/7925#issuecomment-662089208 + replaces_state: isEventID, + // Synapse bug: https://github.com/matrix-org/synapse/issues/7925#issuecomment-663247760 + redacted_because: isPlainObject, + // Obsolete field originating from a now-defunct Synapse fork running on ponies.im + origin_server_ipts: [ isInteger ], + unsigned: optionalObject({ + age: isInteger, + transaction_id: isString, + redacted_because: isPlainObject, // FIXME: Cannot do recursion with isRoomEvent (/isStateEvent) -- fixable via `dynamic` wrapper, so that the rules are only generated *after* the validator has finished being declared? + // Spec omission: https://github.com/matrix-org/matrix-doc/issues/2690 + redacted_by: isEventID, + // Spec omission: https://github.com/matrix-org/matrix-doc/issues/1167 + replaces_state: isEventID, + // Spec omission and/or Synapse bug: https://github.com/matrix-org/matrix-doc/issues/877 + prev_content: isPlainObject, + // Spec omission: https://github.com/matrix-org/matrix-doc/issues/684 + prev_sender: isMatrixID, + // MSC 1849, not merged yet: https://github.com/matrix-org/matrix-doc/blob/matthew/msc1849/proposals/1849-aggregations.md + "m.relations": { + "m.annotation": isPaginatedChunkOf({ + type: [ required, isString ], + key: [ required, isString ], + // Should be required according to MSC, but currently missing in Synapse: https://github.com/matrix-org/synapse/issues/7941#issuecomment-663238820 + origin_server_ts: [ /* required, */ isInteger ], + count: [ required, isInteger ] + }), + "m.reference": isPaginatedChunkOf({ + // Should be required according to MSC, but currently missing in Synapse: https://github.com/matrix-org/synapse/issues/7941 + type: [ /* required, */ isString ], + event_id: [ required, isEventID ] + }), + "m.replace": { + event_id: [ required, isEventID ], + origin_server_ts: [ required, isInteger ], + sender: [ required, isMatrixID ] + } + } + }) +}; diff --git a/src/packages/is-room-id/index.js b/src/packages/is-room-id/index.js new file mode 100644 index 0000000..fc15beb --- /dev/null +++ b/src/packages/is-room-id/index.js @@ -0,0 +1,6 @@ +"use strict"; + +const isString = require("@validatem/is-string"); + +// FIXME: Improve validation +module.exports = isString; diff --git a/src/packages/is-state-event/index.js b/src/packages/is-state-event/index.js new file mode 100644 index 0000000..442e634 --- /dev/null +++ b/src/packages/is-state-event/index.js @@ -0,0 +1,16 @@ +"use strict"; + +const required = require("@validatem/required"); +const isString = require("@validatem/is-string"); +const isPlainObject = require("@validatem/is-plain-object"); +const isInteger = require("@validatem/is-integer"); + +const isRoomEvent = require("../is-room-event"); + +module.exports = { + ... isRoomEvent, + state_key: [ required, isString ], + prev_content: isPlainObject, + // Spec violation by Synapse: https://github.com/matrix-org/synapse/issues/6226 + membership: [ isString ] +}; diff --git a/src/packages/is-stream-token/index.js b/src/packages/is-stream-token/index.js new file mode 100644 index 0000000..f6894ca --- /dev/null +++ b/src/packages/is-stream-token/index.js @@ -0,0 +1,8 @@ +"use strict"; + +const wrapError = require("@validatem/wrap-error"); +const matchesFormat = require("@validatem/matches-format"); + +module.exports = wrapError("Must be a validly-formatted stream token", "modular-matrix.is-stream-token", [ + matchesFormat(/^[a-zA-Z0-9.=_-]+$/) +]); diff --git a/src/packages/is-stripped-event/index.js b/src/packages/is-stripped-event/index.js new file mode 100644 index 0000000..82f3a2d --- /dev/null +++ b/src/packages/is-stripped-event/index.js @@ -0,0 +1,13 @@ +"use strict"; + +const required = require("@validatem/required"); +const isString = require("@validatem/is-string"); + +const isEvent = require("../is-event"); +const isMatrixID = require("../is-matrix-id"); + +module.exports = { + ... isEvent, + state_key: [ required, isString ], + sender: [ required, isMatrixID ] +}; diff --git a/src/packages/is-sync-response/index.js b/src/packages/is-sync-response/index.js new file mode 100644 index 0000000..7d243ba --- /dev/null +++ b/src/packages/is-sync-response/index.js @@ -0,0 +1,112 @@ +"use strict"; +// FIXME: own package + +const required = require("@validatem/required"); +const anyProperty = require("@validatem/any-property"); +const arrayOf = require("@validatem/array-of"); +const isString = require("@validatem/is-string"); +const isInteger = require("@validatem/is-integer"); +const isBoolean = require("@validatem/is-boolean"); +const anything = require("@validatem/anything"); + +const isEvent = require("../is-event"); +const isPresenceEvent = require("../is-presence-event"); +const isStateEvent = require("../is-state-event"); +const isDeviceEvent = require("../is-device-event"); +const isStrippedEvent = require("../is-stripped-event"); +const isTimelineEvent = require("../is-timeline-event"); +const isRoomID = require("../is-room-id"); +const isMatrixID = require("../is-matrix-id"); + +const optionalObject = require("../optional-object"); +const optionalArray = require("../optional-array"); + +let isStateList = arrayOf([ required, isStateEvent ]); +let isEventList = arrayOf([ required, isEvent ]); +let isPresenceEventList = arrayOf([ required, isPresenceEvent ]); +let isStrippedEventList = arrayOf([ required, isStrippedEvent ]); +let isDeviceEventList = arrayOf([ required, isDeviceEvent ]); +let isTimelineList = arrayOf([ required, isTimelineEvent ]); + +module.exports = { + next_batch: [ required, isString ], + // FIXME: also optionalObject for `rooms` + rooms: { + join: optionalObject(anyProperty({ + key: [ required, isRoomID ], + value: { + summary: optionalObject({ + "m.heroes": arrayOf([ required, isString ]), + "m.joined_member_count": isInteger, + "m.invited_member_count": isInteger, + }), + // NOTE: Despite what the spec currently says, state.events *can* contain membership events when the timeline isn't limited, when lazy-loading is enabled + state: optionalObject({ + events: optionalArray(isStateList) + }), + timeline: optionalObject({ + events: optionalArray(isTimelineList), + limited: isBoolean, + prev_batch: isString + }), + ephemeral: optionalObject({ + events: optionalArray(isEventList) + }), + account_data: optionalObject({ + events: optionalArray(isEventList) + }), + unread_notifications: { + highlight_count: isInteger, + notification_count: isInteger + }, + // FIXME: expose the below + "org.matrix.msc2654.unread_count": [ isInteger ], // NOTE: https://github.com/matrix-org/matrix-doc/pull/2654 + } + })), + invite: optionalObject(anyProperty({ + key: [ required, isRoomID ], + value: { + // NOTE: This state needs to be maintained separately from known room state (see spec). FIXME: Represent this in the event list output? + invite_state: optionalObject({ + events: optionalArray(isStrippedEventList) + }) + } + })), + leave: optionalObject(anyProperty({ + key: [ required, isRoomID ], + value: { + state: optionalObject({ + events: optionalArray(isStateList) + }), + timeline: optionalObject({ + events: optionalArray(isTimelineList), + limited: isBoolean, + prev_batch: isString + }), + account_data: optionalObject({ + events: optionalArray(isEventList) + }), + } + })) + }, + presence: optionalObject({ + events: optionalArray(isPresenceEventList) + }), + account_data: optionalObject({ + events: optionalArray(isEventList) + }), + to_device: optionalObject({ + events: optionalArray(isDeviceEventList) + }), + device_lists: optionalObject({ + changed: arrayOf([ required, isMatrixID ]), + left: arrayOf([ required, isMatrixID ]) + }), + device_one_time_keys_count: optionalObject(anyProperty({ + key: [ required, isString ], // algorithm name + value: [ required, isInteger ] // key count + })), + groups: anything, // NOTE: Non-standard + // TODO: Validate algorithm names below? + "org.matrix.msc2732.device_unused_fallback_key_types": optionalArray(arrayOf([ required, isString ])), // NOTE: https://github.com/matrix-org/matrix-doc/pull/2732 +}; diff --git a/src/packages/is-thumbnail-options/index.js b/src/packages/is-thumbnail-options/index.js new file mode 100644 index 0000000..10b4960 --- /dev/null +++ b/src/packages/is-thumbnail-options/index.js @@ -0,0 +1,13 @@ +"use strict"; + +const isNonEmptyString = require("@validatem/is-non-empty-string"); +const isInteger = require("@validatem/is-integer"); +const isMXC = require("@modular-matrix/is-mxc-url"); + +module.exports = { + url: [ isMXC ], + displayWidth: [ isInteger ], + displayHeight: [ isInteger ], + mimetype: [ isNonEmptyString ], + filesize: [ isInteger ], +}; diff --git a/src/packages/is-timeline-event/index.js b/src/packages/is-timeline-event/index.js new file mode 100644 index 0000000..78f7456 --- /dev/null +++ b/src/packages/is-timeline-event/index.js @@ -0,0 +1,12 @@ +"use strict"; + +const either = require("@validatem/either"); + +const isRoomEvent = require("../is-room-event"); +const isStateEvent = require("../is-state-event"); + +// State events are a more specific version of room events: https://github.com/matrix-org/matrix-doc/issues/2681 +module.exports = either([ + [ isRoomEvent ], + [ isStateEvent ] +]); diff --git a/src/packages/item-deduplicator/index.js b/src/packages/item-deduplicator/index.js new file mode 100644 index 0000000..c7b1356 --- /dev/null +++ b/src/packages/item-deduplicator/index.js @@ -0,0 +1,21 @@ +"use strict"; + +// FIXME: Publish as separate non-modular-matrix package + +module.exports = function createItemDeduplicator(getKey) { + let items = new Map(); + + return function deduplicateItem(item) { + let key = getKey(item); + + if (key == null) { + // We cannot deduplicate an item that doesn't have a key + return item; + } else if (items.has(key)) { + return items.get(key); + } else { + items.set(key, item); + return item; + } + }; +}; diff --git a/src/packages/load-image-file/index.js b/src/packages/load-image-file/index.js new file mode 100644 index 0000000..ed447e1 --- /dev/null +++ b/src/packages/load-image-file/index.js @@ -0,0 +1,20 @@ +"use strict"; + +const Promise = require("bluebird"); +const awaitImageElementLoad = require("../await-image-element-load"); + +module.exports = function loadImageFile(file) { + // FIXME: Validation + let objectURL = URL.createObjectURL(file); + + let element = document.createElement("img"); + element.src = objectURL; + + return Promise.try(() => { + return awaitImageElementLoad(element); + }).then(() => { + URL.revokeObjectURL(objectURL); + + return element; + }); +}; diff --git a/src/packages/load-video-file/index.js b/src/packages/load-video-file/index.js new file mode 100644 index 0000000..3b24525 --- /dev/null +++ b/src/packages/load-video-file/index.js @@ -0,0 +1,44 @@ +"use strict"; + +const Promise = require("bluebird"); +const awaitVideoElementLoad = require("../await-video-element-load"); + +function trySetSrcObject(element, file) { + if ("srcObject" in element) { + try { + element.srcObject = file; + return true; + } catch (error) { + if (error.name === "TypeError") { + return false; + } else { + throw error; + } + } + } else { + return false; + } +} + +module.exports = function loadVideoFile(file) { + // FIXME: Validation + let cleanupFunction; + let element = document.createElement("video"); + + if (!trySetSrcObject(element, file)) { + let objectURL = URL.createObjectURL(file); + element.src = objectURL; + // FIXME: Verify that this works correctly, and doesn't prematurely kill the video! + cleanupFunction = () => URL.revokeObjectURL(objectURL); + } + + return Promise.try(() => { + return awaitVideoElementLoad(element); + }).then(() => { + if (cleanupFunction != null) { + cleanupFunction(); + } + + return element; + }); +}; diff --git a/src/packages/make-url/index.js b/src/packages/make-url/index.js new file mode 100644 index 0000000..e60d86e --- /dev/null +++ b/src/packages/make-url/index.js @@ -0,0 +1,13 @@ +"use strict"; + +// FIXME: Publish this some day, and switch to it instead of the `make-url` package (which has a magic API) + +module.exports = function makeURL(template, options) { + return template.replace(/:([^\/]+)/gi, (_, property) => { + if (options[property] != null) { + return encodeURIComponent(options[property]); + } else { + throw new Error(`Missing property: ${property}`); + } + }); +}; diff --git a/src/packages/map-canonical-alias-event/index.js b/src/packages/map-canonical-alias-event/index.js new file mode 100644 index 0000000..65efa2a --- /dev/null +++ b/src/packages/map-canonical-alias-event/index.js @@ -0,0 +1,14 @@ +"use strict"; + +const addCommonFields = require("../event-add-common-fields"); +const mapMaybeRedacted = require("../map-maybe-redacted"); + +module.exports = function mapCanonicalAliasEvent(event, _context) { + return addCommonFields(event, { + type: "canonicalAliasChanged", + sender: event.sender, + ... mapMaybeRedacted(event, () => ({ + alias: event.content.alias + })) + }); +}; diff --git a/src/packages/map-cross-signing-master-event/index.js b/src/packages/map-cross-signing-master-event/index.js new file mode 100644 index 0000000..16dd7e1 --- /dev/null +++ b/src/packages/map-cross-signing-master-event/index.js @@ -0,0 +1,10 @@ +"use strict"; + +const mapEncryptedWrapper = require("../map-encrypted-wrapper"); + +module.exports = function mapCrossSigningMasterEvent(event, _context) { + return { + type: "crossSigningMasterKey", + ... mapEncryptedWrapper(event.content.encrypted) + }; +}; diff --git a/src/packages/map-cross-signing-self-signing-event/index.js b/src/packages/map-cross-signing-self-signing-event/index.js new file mode 100644 index 0000000..14c9916 --- /dev/null +++ b/src/packages/map-cross-signing-self-signing-event/index.js @@ -0,0 +1,10 @@ +"use strict"; + +const mapEncryptedWrapper = require("../map-encrypted-wrapper"); + +module.exports = function mapCrossSigningSelfSigningEvent(event, _context) { + return { + type: "crossSigningSelfSigningKey", + ... mapEncryptedWrapper(event.content.encrypted) + }; +}; diff --git a/src/packages/map-cross-signing-user-signing-event/index.js b/src/packages/map-cross-signing-user-signing-event/index.js new file mode 100644 index 0000000..7ccb5b0 --- /dev/null +++ b/src/packages/map-cross-signing-user-signing-event/index.js @@ -0,0 +1,10 @@ +"use strict"; + +const mapEncryptedWrapper = require("../map-encrypted-wrapper"); + +module.exports = function mapCrossSigningUserSigningEvent(event, _context) { + return { + type: "crossSigningUserSigningKey", + ... mapEncryptedWrapper(event.content.encrypted) + }; +}; diff --git a/src/packages/map-direct-event/index.js b/src/packages/map-direct-event/index.js new file mode 100644 index 0000000..7fb8021 --- /dev/null +++ b/src/packages/map-direct-event/index.js @@ -0,0 +1,27 @@ +"use strict"; + +function invertMapping(mapping) { + let invertedMapping = {}; + + for (let [ key, values ] of Object.entries(mapping)) { + for (let value of values) { + if (invertedMapping[value] == null) { + invertedMapping[value] = []; + } + + invertedMapping[value].push(key); + } + } + + return invertedMapping; +} + +module.exports = function mapDirectEvent(event, _context) { + // Context: account data + + return { + type: "directMessageRoomsChanged", + userToRooms: event.content, + roomToUsers: invertMapping(event.content) + }; +}; diff --git a/src/packages/map-encrypted-file-object/index.js b/src/packages/map-encrypted-file-object/index.js new file mode 100644 index 0000000..0b4f6c6 --- /dev/null +++ b/src/packages/map-encrypted-file-object/index.js @@ -0,0 +1,18 @@ +"use strict"; + +// FIXME: Rename to map-encrypted-object or map-encrypted-string-object, depending on what it represents? + +module.exports = function mapEncryptedFileObject(object) { + // FIXME: Proper validation + if (object.key.alg !== "A256CTR") { + throw new Error(`Invalid algorithm: ${object.key.alg}`); + } + + return { + protocolVersion: object.v, + url: object.url, + hashes: object.hashes, + iv: object.iv, + key: object.key // NOTE: JWK format, so we don't map these + }; +}; diff --git a/src/packages/map-encrypted-message-event/index.js b/src/packages/map-encrypted-message-event/index.js new file mode 100644 index 0000000..f369ed8 --- /dev/null +++ b/src/packages/map-encrypted-message-event/index.js @@ -0,0 +1,44 @@ +"use strict"; + +const matchValue = require("match-value"); + +const addCommonFields = require("../event-add-common-fields"); +const normalizeEncryptionAlgorithmName = require("../normalize-encryption-algorithm-name"); +const mapMaybeRedacted = require("../map-maybe-redacted"); + +function mapFields(event) { + let algorithm = normalizeEncryptionAlgorithmName(event.content.algorithm); + + return { + // FIXME: decrypt method + type: "encryptedMessage", + algorithm: algorithm, + senderKey: event.content.sender_key, + ... matchValue(algorithm, { + "olm.curve25519.aes-cbc-256.sha-256": () => ({ + ciphertexts: Object.entries(event.content.ciphertext).map(([ deviceKey, payload ]) => { + return { + deviceKey: deviceKey, + isPreKeyMessage: (payload.type === 0), + ciphertext: payload.body + }; + }) + }), + "megolm.ed25519.aes-cbc-256.hmac-sha-256": { + ciphertext: event.content.ciphertext, + deviceID: event.content.device_id, + sessionID: event.content.session_id + } + }) + }; +} + +module.exports = function mapEncryptedMessage(event, context) { + if (context === "toDeviceEvent") { + return mapFields(event); + } else { + return addCommonFields(mapMaybeRedacted(event, () => { + return mapFields(event); + })); + } +}; diff --git a/src/packages/map-encrypted-wrapper/index.js b/src/packages/map-encrypted-wrapper/index.js new file mode 100644 index 0000000..9f7e135 --- /dev/null +++ b/src/packages/map-encrypted-wrapper/index.js @@ -0,0 +1,29 @@ +"use strict"; + +const withoutKeys = require("../without-keys"); + +module.exports = function mapEncryptedWrapper(wrapperObject, permitPassthrough = false) { + return { + // TODO: Add a `decrypt` method? + encryptedPayloads: Object.entries(wrapperObject).map(([ keyID, data ]) => { + if (data.passthrough === true) { + if (permitPassthrough) { + return { + keyID: keyID, + isKeyPassthrough: true, + encryptionConfiguration: {} + }; + } else { + throw new Error(`Encountered a 'passthrough' encryption object where it is not allowed`); + } + } else { + return { + keyID: keyID, + isKeyPassthrough: false, + ciphertext: data.ciphertext, + encryptionConfiguration: withoutKeys(data, [ "ciphertext" ]) + }; + } + }) + }; +}; diff --git a/src/packages/map-encryption-event/index.js b/src/packages/map-encryption-event/index.js new file mode 100644 index 0000000..440344b --- /dev/null +++ b/src/packages/map-encryption-event/index.js @@ -0,0 +1,16 @@ +"use strict"; + +const addCommonFields = require("../event-add-common-fields"); +const normalizeEncryptionAlgorithmName = require("../normalize-encryption-algorithm-name"); + +module.exports = function mapEncryptionEvent(event, _context) { + return addCommonFields(event, { + type: "encryptionEnabled", + sender: event.sender, + algorithm: normalizeEncryptionAlgorithmName(event.content.algorithm), + encryptionConfiguration: { + rotateSessionAfterTime: event.content.rotation_period_ms, + rotateSessionAfterMessages: event.content.rotation_period_msgs + } + }); +}; diff --git a/src/packages/map-event/index.js b/src/packages/map-event/index.js new file mode 100644 index 0000000..4909754 --- /dev/null +++ b/src/packages/map-event/index.js @@ -0,0 +1,90 @@ +"use strict"; + +const matchValue = require("match-value"); +const supportsColor = require("supports-color").stderr; + +let unmappedTypes = new Set([ + "im.vector.setting.breadcrumbs", + "im.vector.setting.integration_provisioning", + "im.vector.riot.breadcrumb_rooms", // Room selection history? + "im.vector.web.settings", + "m.room.bot.options", // Missing from spec: https://github.com/matrix-org/matrix-doc/issues/1409 + "opsdroid.database", + "m.room.third_party_invite", // FIXME + "m.room.tombstone", // FIXME + "m.room.bridging", // FIXME + "m.room.plumbing", // FIXME + "org.matrix.room.preview_urls", // FIXME + "im.vector.modular.widgets", // FIXME? + ":type", // FIXME: Temporary hack to work around invalid test event, until we have unknown-event-type warning infrastructure in place +]); + +module.exports = function mapEvent(event, context) { + // FIXME: Allow customization of this through a factor that accepts additional/overriding event mappers for different types; prefix those additional entries so that they take precedence over the default ones + let mapper = matchValue.literal(event.type, { + "m.room.message": require("../map-message-event"), + "m.sticker": require("../map-message-event"), // This is really just an m.image-like message, so we handle it in one place + "m.room.member": require("../map-member-event"), + "m.room.encrypted": require("../map-encrypted-message-event"), + "m.room.name": require("../map-room-name-event"), + "m.room.topic": require("../map-topic-event"), + "m.room.canonical_alias": require("../map-canonical-alias-event"), + "m.presence": require("../map-presence-event"), + "m.receipt": require("../map-receipt-event"), + "m.typing": require("../map-typing-event"), + "m.fully_read": require("../map-fully-read-event"), + "m.tag": require("../map-tag-event"), + "m.push_rules": require("../map-push-rules-event"), + "m.room.encryption": require("../map-encryption-event"), + "m.secret_storage.default_key": require("../map-secret-storage-default-key-event"), + "m.cross_signing.master": require("../map-cross-signing-master-event"), + "m.cross_signing.user_signing": require("../map-cross-signing-user-signing-event"), + "m.cross_signing.self_signing": require("../map-cross-signing-self-signing-event"), + "m.megolm_backup.v1": require("../map-megolm-backup-v1-event"), + "m.room_key_request": require("../map-key-request-event"), + "m.direct": require("../map-direct-event"), + "m.reaction": require("../map-reaction-event"), + "m.room.create": require("../map-room-create-event"), // FIXME: Tombstones + "m.room.power_levels": require("../map-power-levels-event"), + "m.room.history_visibility": require("../map-history-visibility-event"), + "m.room.join_rules": require("../map-join-rules-event"), + "m.room.guest_access": require("../map-guest-access-event"), + "m.room.avatar": require("../map-room-avatar-event"), + "m.room.related_groups": require("../map-related-groups-event"), + "m.room.redaction": require("../map-redaction-event"), + "m.room.server_acl": require("../map-server-acl-event"), + _: (event, context) => { + function call(mapper) { + return mapper(event, context); + } + + // Workarounds for API design hacks (eg. keyed event types for account data) + if (event.type.startsWith("m.secret_storage.key.")) { + return call(require("../map-secret-storage-key-event")); + } else { + return event; + } + } + }); + + let mappedEvent = mapper(event, context); + + if (mappedEvent === event && !unmappedTypes.has(event.type)) { + throw new Error(`Event was not mapped: ${require("util").inspect(event, { depth: null, colors: supportsColor })}`); + } + + let tests = [ + // (event.type === "m.room.message" && event.content.msgtype === "m.image"), + // (event.type === "m.reaction"), + (event.type === "m.room.notice"), + ]; + + if (tests.some((result) => result === true)) { + // if (event.type.startsWith("m.secret_storage.key.")) { + // if (event.type === "m.cross_signing.master") { + console.log("mapped event:", require("util").inspect(mappedEvent, { colors: supportsColor, depth: null })); + throw new Error(`Break`); + } + + return mappedEvent; +}; diff --git a/src/packages/map-events/index.js b/src/packages/map-events/index.js new file mode 100644 index 0000000..2dd1b48 --- /dev/null +++ b/src/packages/map-events/index.js @@ -0,0 +1,47 @@ +"use strict"; + +const syncpipe = require("syncpipe"); +const flatten = require("flatten"); + +module.exports = function mapEvents(events, eventMapper) { + // TODO: Do we need to deduplicate mapped events here? + return syncpipe(events, [ + (_) => _.map((event) => { + // FIXME: Remove + // console.log(require("util").inspect(event, { colors: require("supports-color").stderr, depth: null })); + + if (event.event != null) { + let mappedEvents = (eventMapper != null) + ? eventMapper(event.event, event.type) + : event.event; + + if (Array.isArray(mappedEvents)) { + if (mappedEvents.length > 0) { + return mappedEvents.map((mappedEvent) => { + // TODO: Could mutability provide a significant performance improvement here? (also in stream-backlog) + return { + ... event, + protocolEvent: event.event, + event: mappedEvent + }; + }); + } else { + return null; + } + } else if (mappedEvents != null) { + return { + ... event, + protocolEvent: event.event, + event: mappedEvents + }; + } else { + return null; + } + } else { + return event; + } + }), + (_) => _.filter((event) => event != null), + (_) => flatten(_) + ]); +}; diff --git a/src/packages/map-fully-read-event/index.js b/src/packages/map-fully-read-event/index.js new file mode 100644 index 0000000..9cfc908 --- /dev/null +++ b/src/packages/map-fully-read-event/index.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = function mapFullyReadEvent(event, _context) { + return { + type: "fullyReadMarker", + eventID: event.content.event_id + }; +}; diff --git a/src/packages/map-guest-access-event/index.js b/src/packages/map-guest-access-event/index.js new file mode 100644 index 0000000..4fd7708 --- /dev/null +++ b/src/packages/map-guest-access-event/index.js @@ -0,0 +1,16 @@ +"use strict"; + +const matchValue = require("match-value"); + +const addCommonFields = require("../event-add-common-fields"); + +module.exports = function mapGuestAccessEvent(event, _context) { + return addCommonFields(event, { + type: "guestAccessChanged", + sender: event.sender, + guestsCanJoin: matchValue(event.content.guest_access, { + can_join: true, + forbidden: false + }) + }); +}; diff --git a/src/packages/map-history-visibility-event/index.js b/src/packages/map-history-visibility-event/index.js new file mode 100644 index 0000000..a0664de --- /dev/null +++ b/src/packages/map-history-visibility-event/index.js @@ -0,0 +1,18 @@ +"use strict"; + +const matchValue = require("match-value"); + +const addCommonFields = require("../event-add-common-fields"); + +module.exports = function mapHistoryVisibilityEvent(event, _context) { + return addCommonFields(event, { + type: "historyVisibilityChanged", + sender: event.sender, + visibility: matchValue(event.content.history_visibility, { + world_readable: "public", + shared: "membersOnly", + invited: "membersSinceInvited", + joined: "membersSinceJoined" + }) + }); +}; diff --git a/src/packages/map-join-rules-event/index.js b/src/packages/map-join-rules-event/index.js new file mode 100644 index 0000000..f4f6161 --- /dev/null +++ b/src/packages/map-join-rules-event/index.js @@ -0,0 +1,16 @@ +"use strict"; + +const matchValue = require("match-value"); + +const addCommonFields = require("../event-add-common-fields"); + +module.exports = function mapJoinRulesEvent(event, _context) { + return addCommonFields(event, { + type: "joinRuleChanged", + sender: event.sender, + access: matchValue(event.content.join_rule, { + public: "public", + invite: "inviteOnly" + }) + }); +}; diff --git a/src/packages/map-key-request-event/index.js b/src/packages/map-key-request-event/index.js new file mode 100644 index 0000000..939c77b --- /dev/null +++ b/src/packages/map-key-request-event/index.js @@ -0,0 +1,29 @@ +"use strict"; + +const unreachable = require("@joepie91/unreachable"); + +const normalizeEncryptionAlgorithmName = require("../normalize-encryption-algorithm-name"); + +module.exports = function mapKeyRequestEvent(event, _context) { + if (event.content.action === "request") { + return { + type: "keyRequested", + user: event.sender, + deviceID: event.content.requesting_device_id, + requestID: event.content.request_id, + algorithm: normalizeEncryptionAlgorithmName(event.content.body.algorithm), + senderKey: event.content.body.sender_key, // TODO: Better name for senderKey? also elsewhere + roomID: event.content.body.room_id, + sessionID: event.content.body.session_id + }; + } else if (event.content.action === "request_cancellation") { + return { + type: "keyRequestCancelled", + user: event.sender, + deviceID: event.content.requesting_device_id, + requestID: event.content.request_id, + }; + } else { + unreachable(`Unrecognized action '${event.content.action}' for room_key_request`); + } +}; diff --git a/src/packages/map-maybe-redacted/index.js b/src/packages/map-maybe-redacted/index.js new file mode 100644 index 0000000..30ac90d --- /dev/null +++ b/src/packages/map-maybe-redacted/index.js @@ -0,0 +1,37 @@ +"use strict"; + +const dotty = require("dotty"); + +const addCommonFields = require("../event-add-common-fields"); + +function noop() { + return {}; +} + +module.exports = function mapMaybeRedacted(event, handler) { + let { redacted, notRedacted } = (typeof handler === "function") + ? { redacted: noop, notRedacted: handler } + : handler; + + let redactionData = dotty.get(event, ["unsigned", "redacted_because"]); + + if (redactionData != null) { + return { + ... redacted(), + isRedacted: true, + redaction: addCommonFields(redactionData, { + user: redactionData.sender, + isVoluntary: (event.sender != null) + ? (redactionData.sender === event.sender) + : undefined, + reason: redactionData.content.reason + }) + }; + } else { + return { + ... notRedacted(), + isRedacted: false, + redaction: {}, + }; + } +}; diff --git a/src/packages/map-megolm-backup-v1-event/index.js b/src/packages/map-megolm-backup-v1-event/index.js new file mode 100644 index 0000000..d19cb4d --- /dev/null +++ b/src/packages/map-megolm-backup-v1-event/index.js @@ -0,0 +1,10 @@ +"use strict"; + +const mapEncryptedWrapper = require("../map-encrypted-wrapper"); + +module.exports = function mapMegolmBackupV1Event(event, _context) { + return { + type: "megolmKeyBackup", + ... mapEncryptedWrapper(event.content.encrypted, true) + }; +}; diff --git a/src/packages/map-member-event/index.js b/src/packages/map-member-event/index.js new file mode 100644 index 0000000..e21d749 --- /dev/null +++ b/src/packages/map-member-event/index.js @@ -0,0 +1,160 @@ +"use strict"; + +const defaultValue = require("default-value"); +const flatten = require("flatten"); +const addCommonFields = require("../event-add-common-fields"); +const mapMaybeRedacted = require("../map-maybe-redacted"); +const numberDerivedEvents = require("../number-derived-events"); + +function mapProfile(eventContent) { + return { + avatar: normalizeProfileField(eventContent.avatar_url), + displayName: normalizeProfileField(eventContent.displayname) + }; +} + +function normalizeProfileField(value) { + if (value === null || value === undefined) { + // We make this an *explicit* null, as this makes it easier for the consuming code to distinguish between "this user has unset the profile field" and "we just haven't seen a profile field yet". + return null; + } else { + return value; + } +} + +module.exports = function mapMemberEvent(event, _context) { + let previousContent = defaultValue(event.unsigned.prev_content, { membership: "leave" }); + + let oldState = previousContent.membership; + let newState = event.content.membership; + let oldProfile = mapProfile(previousContent); + let newProfile = mapProfile(event.content); + + let voluntaryChange = (event.state_key === event.sender); + + let transitions = { + invite: { + invite: null, + join: "_userAcceptedInviteAndJoined", + leave: (voluntaryChange) + ? "userRejectedInvite" + : "userInviteWasRevoked", + ban: "userWasBanned" + }, + join: { + join: "_userChangedProfile", + leave: (voluntaryChange) + ? "userLeft" + : "userWasKicked", + ban: "_userWasKickedAndBanned" + }, + leave: { + invite: "userWasInvited", + join: "userJoined", + leave: null, + ban: "userWasBanned" + }, + ban: { + leave: "userWasUnbanned", + ban: null + } + }; + + // userChangedProfile -> userChangedDisplayName / userChangedAvatar + // use prev_content to determine diff + + let typesWithProfileData = new Set([ "_userChangedProfile", "userJoined", "_userAcceptedInviteAndJoined" ]); + + if (transitions[oldState] !== undefined && transitions[oldState][newState] !== undefined) { + let type = transitions[oldState][newState]; + let hasProfileData = typesWithProfileData.has(type); + let deltaEvents = []; + + if (hasProfileData) { + if (oldProfile.avatar != newProfile.avatar) { + deltaEvents.push({ + type: "userChangedAvatar", + user: event.state_key, + sender: event.sender, + ... mapMaybeRedacted(event, () => { + return { + url: newProfile.avatar, + previousURL: oldProfile.avatar + }; + }) + }); + } + + if (oldProfile.displayName != newProfile.displayName) { + deltaEvents.push({ + type: "userChangedDisplayName", + user: event.state_key, + sender: event.sender, + ... mapMaybeRedacted(event, () => { + return { + name: newProfile.displayName, + previousName: oldProfile.displayName + }; + }) + }); + } + } + + // We ignore any "no change" transitions, as well as any profile changes (since those will have been handled in full above) + if (type !== null && type !== "_userChangedProfile") { + let membershipFields = { + user: event.state_key, + sender: event.sender, + ... mapMaybeRedacted(event, () => { + return { + reason: event.content.reason, + // FIXME: is_direct flag, translate into "is direct message" event if prev_content does not contain it + // FIXME: third_party_invite.display_name + }; + }) + }; + + // NOTE: We have a few special cases below, where certain membership changes result in multiple logical actions. To leave it up to the consumer whether to care about the cause of eg. a kick or join, we represent the logical actions as individual events, annotating them with metadata so that a cause-interested client can ignore the implicitly-generated events. + + if (type === "_userWasKickedAndBanned") { + deltaEvents.push([ + addCommonFields(event, { + type: "userWasKicked", + causedByBan: true, + ... membershipFields + }), + addCommonFields(event, { + type: "userWasBanned", + ... membershipFields + }), + ]); + } else if (type === "_userAcceptedInviteAndJoined") { + deltaEvents.push([ + addCommonFields(event, { + type: "userAcceptedInvite", + ... membershipFields + }), + addCommonFields(event, { + type: "userJoined", + causedByInvite: true, + ... membershipFields + }), + ]); + } else { + deltaEvents.push(addCommonFields(event, { + type: type, + ... membershipFields + })); + } + } + + let flattenedDeltaEvents = flatten(deltaEvents); + + numberDerivedEvents(event.event_id, flattenedDeltaEvents); + + return flattenedDeltaEvents; + } else { + // FIXME: Error type + throw new Error(`Membership transition not allowed: ${oldState} => ${newState}`); + } +}; diff --git a/src/packages/map-message-event/index.js b/src/packages/map-message-event/index.js new file mode 100644 index 0000000..2457752 --- /dev/null +++ b/src/packages/map-message-event/index.js @@ -0,0 +1,154 @@ +"use strict"; + +const dotty = require("dotty"); +const unreachable = require("@joepie91/unreachable")("@modular-matrix/client"); // FIXME: Change name when packaging separately + +const addCommonFields = require("../event-add-common-fields"); +const mapEncryptedFileObject = require("../map-encrypted-file-object"); +const stripHTMLReplyFallback = require("../strip-html-reply-fallback"); +const stripPlaintextReplyFallback = require("../strip-plaintext-reply-fallback"); +const mapMaybeRedacted = require("../map-maybe-redacted"); + +function mapThumbnail(event) { + return { + hasThumbnail: (event.content.info.thumbnail_file != null || event.content.info.thumbnail_url != null), + thumbnail: { + filesize: dotty.get(event.content.info, [ "thumbnail_info", "size" ]), + displayHeight: dotty.get(event.content.info, [ "thumbnail_info", "h" ]), + displayWidth: dotty.get(event.content.info, [ "thumbnail_info", "w" ]), + mimetype: dotty.get(event.content.info, [ "thumbnail_info", "mimetype" ]), + ... (event.content.info.thumbnail_file != null) + ? { isEncrypted: true, encryptedFile: mapEncryptedFileObject(event.content.info.thumbnail_file) } + : { isEncrypted: false, url: event.content.info.thumbnail_url } + } + }; +} + +module.exports = function mapMessageEvent(event, _context) { + // NOTE: We have some custom redaction handling logic here, separate from the usual `mapMaybeRedacted` logic, because the `m.room.message` event gets split into several different types depending on the msgtype - but that msgtype is *also* redacted when a message is, which means we need a single dedicated "encrypted message" type. This does not mix well with mapMaybeRedacted + the requirement to return an event entirely unchanged when it is of an unrecognized msgtype, as mapMaybeRedacted would *necessarily* always return a new object. + let isRedacted = dotty.exists(event, ["unsigned", "redacted_because"]); + + if (!isRedacted) { + // This works around the weird inconsistency that stickers are not `m.room.message` events, despite exactly matching the structure of an `m.room.message` -> `m.image`. + let messageType = (event.type === "m.sticker") + ? "m.sticker" + : event.content.msgtype; + + // TODO: Replace with ?. once that is available in all supported Node + let replyToID = dotty.get(event, [ "content", "m.relates_to", "m.in_reply_to", "event_id" ]); + let isReply = (replyToID != null); + + let replyFields = (isReply) + ? { inReplyToID: replyToID } + : {}; + + if (messageType === "m.text" || messageType === "m.notice" || messageType === "m.emote") { + return addCommonFields(event, { + type: "message", + sender: event.sender, + isNotice: (messageType === "m.notice"), + isEmote: (messageType === "m.emote"), + text: (isReply === true) + ? stripPlaintextReplyFallback(event.content.body) + : event.content.body, + html: (event.content.format === "org.matrix.custom.html") + ? (isReply === true) + ? stripHTMLReplyFallback(event.content.formatted_body) + : event.content.formatted_body + : undefined, + ... replyFields, + }); + } else if (messageType === "m.image" || messageType === "m.sticker") { + return addCommonFields(event, { + type: "image", + sender: event.sender, + isSticker: (messageType === "m.sticker"), + description: event.content.body, + image: { + filesize: event.content.info.size, + displayHeight: event.content.info.h, + displayWidth: event.content.info.w, + mimetype: event.content.info.mimetype, + ... (event.content.file != null) + ? { isEncrypted: true, encryptedFile: mapEncryptedFileObject(event.content.file) } + : { isEncrypted: false, url: event.content.url } + }, + ... mapThumbnail(event), + ... replyFields, + }); + } else if (messageType === "m.video") { + return addCommonFields(event, { + type: "video", + sender: event.sender, + description: event.content.body, + video: { + filesize: event.content.info.size, + displayHeight: event.content.info.h, + displayWidth: event.content.info.w, + mimetype: event.content.info.mimetype, + ... (event.content.file != null) + ? { isEncrypted: true, encryptedFile: mapEncryptedFileObject(event.content.file) } + : { isEncrypted: false, url: event.content.url } + }, + ... mapThumbnail(event), + ... replyFields, + }); + } else if (messageType === "m.audio") { + return addCommonFields(event, { + type: "audio", + sender: event.sender, + description: event.content.body, + audio: { + // We nest the audio-related data inside of an object like for m.image, even though we don't have thumbnails to deal with yet, because something like cover art might be added in the future - and this way, we can keep the API consistent in that case. + filesize: event.content.info.size, + duration: event.content.info.duration, + mimetype: event.content.info.mimetype, + ... (event.content.file != null) + ? { isEncrypted: true, encryptedFile: mapEncryptedFileObject(event.content.file) } + : { isEncrypted: false, url: event.content.url } + }, + ... replyFields, + }); + } else if (messageType === "m.file") { + return addCommonFields(event, { + type: "file", + sender: event.sender, + description: event.content.body, + filename: event.content.filename, + file: { + filesize: event.content.info.size, + mimetype: event.content.info.mimetype, + ... (event.content.file != null) + ? { isEncrypted: true, encryptedFile: mapEncryptedFileObject(event.content.file) } + : { isEncrypted: false, url: event.content.url } + }, + ... mapThumbnail(event), + ... replyFields, + }); + } else if (messageType === "m.location") { + return addCommonFields(event, { + type: "location", + sender: event.sender, + description: event.content.body, + url: event.content.geo_uri, + ... replyFields, + }); + } else if (messageType === ":type") { + // FIXME: Temporary hack to ignore an erroneous testing event, until there's unrecognized-event logging infrastructure + return null; + } else { + // NOTE: The event should remain *completely* unaltered in this case! + return event; + } + } else { + return addCommonFields({ + sender: event.sender, + ... mapMaybeRedacted(event, { + redacted: () => ({ type: "redactedMessage" }), + notRedacted: () => { + unreachable("Reached notRedacted handler for redacted message event"); + } + }) + }); + } +}; diff --git a/src/packages/map-power-levels-event/index.js b/src/packages/map-power-levels-event/index.js new file mode 100644 index 0000000..5280102 --- /dev/null +++ b/src/packages/map-power-levels-event/index.js @@ -0,0 +1,138 @@ +"use strict"; + +const defaultValue = require("default-value"); +const numberDerivedEvents = require("../number-derived-events"); + +function mapLevels(eventContent) { + if (eventContent != null) { + return { + defaultRequiredPowerLevels: { + stateChange: defaultValue(eventContent.state_default, 50), + message: defaultValue(eventContent.events_default, 0), + }, + requiredPowerLevels: { + kick: defaultValue(eventContent.kick, 50), + ban: defaultValue(eventContent.ban, 50), + invite: defaultValue(eventContent.invite, 50), + redact: defaultValue(eventContent.redact, 50) + }, + requiredEventPowerLevels: eventContent.events, + requiredNotificationPowerLevels: { room: 50, ... eventContent.notifications }, + defaultUserPowerLevel: defaultValue(eventContent.users_default, 0), + currentPowerLevels: eventContent.users + }; + } else { + return { + defaultRequiredPowerLevels: { + stateChange: 0, + message: 0, + }, + requiredPowerLevels: { + kick: 50, + ban: 50, + invite: 50, + redact: 50 + }, + requiredEventPowerLevels: {}, + requiredNotificationPowerLevels: { + room: 50 + }, + defaultUserPowerLevel: 0, + currentPowerLevels: {} + }; + } +} + +// FIXME: Option for parsing as a whole, rather than as a diff? Maybe package `mapLevels` as a separate `map-power-levels-event-full` package or so? +// FIXME: Do above + emit as a whole event, for later use in powerlevel-modifying event creation; same for ACLs (maybe build an abstraction for automatically tracking this stuff internally?) -- and make sure to document that the user should use either the full event OR the derived event, not both, for the same purpose! +module.exports = function mapPowerLevelsEvent(event, _context) { + let oldState = mapLevels(event.unsigned.prev_content); + let newState = mapLevels(event.content); + + let deltaEvents = []; + + if (oldState.defaultUserPowerLevel !== newState.defaultUserPowerLevel) { + deltaEvents.push({ + type: "defaultUserPowerLevelChanged", + sender: event.sender, + oldLevel: oldState.defaultUserPowerLevel, + newLevel: newState.defaultUserPowerLevel + }); + } + + for (let [ eventCategory, newLevel ] of Object.entries(newState.defaultRequiredPowerLevels)) { + let oldLevel = oldState.defaultRequiredPowerLevels[eventCategory]; + + if (newLevel !== oldLevel) { + deltaEvents.push({ + type: "defaultRequiredPowerLevelChanged", + sender: event.sender, + eventCategory: eventCategory, + newLevel: newLevel, + oldLevel: oldLevel + }); + } + } + + for (let [ action, newLevel ] of Object.entries(newState.requiredPowerLevels)) { + let oldLevel = oldState.requiredPowerLevels[action]; + + if (newLevel !== oldLevel) { + deltaEvents.push({ + type: "requiredPowerLevelChanged", + sender: event.sender, + action: action, + newLevel: newLevel, + oldLevel: oldLevel + }); + } + } + + for (let [ eventType, newLevel ] of Object.entries(newState.requiredEventPowerLevels)) { + let oldLevel = oldState.requiredEventPowerLevels[eventType]; // FIXME: Default power level? + + if (newLevel !== oldLevel) { + deltaEvents.push({ + type: "requiredEventPowerLevelChanged", + sender: event.sender, + eventType: eventType, + newLevel: newLevel, + oldLevel: oldLevel + }); + } + } + + for (let [ notificationType, newLevel ] of Object.entries(newState.requiredNotificationPowerLevels)) { + let oldLevel = oldState.requiredNotificationPowerLevels[notificationType]; + + if (newLevel !== oldLevel) { + deltaEvents.push({ + type: "requiredNotificationPowerLevelChanged", + sender: event.sender, + notificationType: notificationType, + newLevel: newLevel, + oldLevel: oldLevel + }); + } + } + + for (let [ matrixID, newLevel ] of Object.entries(newState.currentPowerLevels)) { + let oldLevel = oldState.currentPowerLevels[matrixID]; + + if (newLevel !== oldLevel) { + deltaEvents.push({ + type: "userPowerLevelChanged", + sender: event.sender, + user: matrixID, + newLevel: defaultValue(newLevel, newState.defaultUserPowerLevel), + oldLevel: defaultValue(oldLevel, oldState.defaultUserPowerLevel), + newLevelIsExplicit: (newLevel != null), + oldLevelIsExplicit: (oldLevel != null) + }); + } + } + + numberDerivedEvents(event.event_id, deltaEvents); + + return deltaEvents; +}; diff --git a/src/packages/map-presence-event/index.js b/src/packages/map-presence-event/index.js new file mode 100644 index 0000000..bfd8e1e --- /dev/null +++ b/src/packages/map-presence-event/index.js @@ -0,0 +1,16 @@ +"use strict"; + +module.exports = function mapPresenceEvent(event, _context) { + return { + type: "userChangedStatus", + user: event.sender, + displayName: event.content.displayname, + avatar: event.content.avatar_url, + status: event.content.presence, + statusMessage: event.content.status_msg, + isActive: event.content.currently_active, + lastActive: (event.content.last_active_ago != null) + ? Date.now() - (event.content.last_active_ago * 1000) + : undefined, + }; +}; diff --git a/src/packages/map-push-rules-event/index.js b/src/packages/map-push-rules-event/index.js new file mode 100644 index 0000000..3346312 --- /dev/null +++ b/src/packages/map-push-rules-event/index.js @@ -0,0 +1,162 @@ +"use strict"; + +const matchValue = require("match-value"); +const defaultValue = require("default-value"); +const syncpipe = require("syncpipe"); +const splitFilter = require("split-filter"); +const flatten = require("flatten"); +const unreachable = require("@joepie91/unreachable")("@modular-matrix/client"); // FIXME + +function parsePredicate(predicate) { + let match = /^(>|>=|<|<=|==)?([0-9]+)$/.exec(predicate); + + if (match != null) { + return { + operator: defaultValue(match[1], "=="), + value: match[2] + }; + } else { + unreachable("Failed to parse predicate"); + } +} + +function mapCondition(condition) { + if (condition.kind === "event_match") { + return { + type: "matchEventField", + field: condition.key, + pattern: condition.pattern + }; + } else if (condition.kind === "contains_display_name") { + return { + type: "matchDisplayName" + }; + } else if (condition.kind === "room_member_count") { + let { operator, value } = parsePredicate(condition.is); + + return { + type: "matchRoomMemberCount", + operator: operator, + value: value + }; + } else if (condition.kind === "sender_notification_permission") { + return { + type: "requireEventPowerLevel", + notificationType: condition.key + }; + } else { + // Spec: "Unrecognised conditions MUST NOT match any events, effectively making the push rule disabled." + // FIXME: Log warning? + return { + type: "neverMatch" + }; + } +} + +function mapAction(action) { + if (action === "notify") { + return { + type: "notification" + }; + } else if (action === "dont_notify") { + return { + type: "preventNotification" + }; + } else if (action === "coalesce") { + return { + type: "digestNotification" + }; + } else if (action.set_tweak != null) { + let key = action.set_tweak; + + return { + type: "setTweak", + key: key, + value: matchValue(key, { + sound: () => (action.value === "default") ? true : action.value, + highlight: () => defaultValue(action.value, true), + _: () => action.value + }) + }; + } else { + unreachable(`Unrecognized action type`); + } +} + +function mapRule(rule) { + let mappedActions = rule.actions.map(mapAction); + let [ tweaks, actualActions ] = splitFilter(mappedActions, (action) => action.type === "setTweak"); + + return { + id: rule.rule_id, + isDefault: rule.default, + isEnabled: rule.enabled, + actions: actualActions, + // FIXME: Clearly document that these 'options' are called 'tweaks' on the push gateway side! + notificationOptions: Object.fromEntries(tweaks.map(({ key, value }) => [ key, value ])) + }; +} + +function mapRuleset(rules, key) { + if (rules == null) { + return []; + } else if (key === "content") { + return rules.map((rule) => { + return { + ... mapRule(rule), + conditions: [{ + type: "matchContent", + pattern: rule.pattern + }] + }; + }); + } else if (key === "room") { + return rules.map((rule) => { + return { + ... mapRule(rule), + conditions: [{ + type: "matchRoom", + roomID: rule.rule_id + }] + }; + }); + } else if (key === "sender") { + return rules.map((rule) => { + return { + ... mapRule(rule), + conditions: [{ + type: "matchSender", + userID: rule.rule_id + }] + }; + }); + } else if (key === "override" || key === "underride") { + return rules.map((rule) => { + return { + ... mapRule(rule), + conditions: rule.conditions.map(mapCondition) + }; + }); + } else { + throw unreachable("Unrecognized ruleset key"); + } +} + +function mapRulesets(rulesets) { + if (rulesets != null) { + return syncpipe([ "override", "content", "room", "sender", "underride" ], [ + (_) => _.map((key) => mapRuleset(rulesets[key], key)), + (_) => _.filter((ruleset) => ruleset.length > 0), + (_) => flatten(_) + ]); + } else { + return []; + } +} + +module.exports = function mapPushRulesEvent(event, _context) { + return { + type: "pushRulesChanged", + rulesets: mapRulesets(event.content.global) + }; +}; diff --git a/src/packages/map-reaction-event/index.js b/src/packages/map-reaction-event/index.js new file mode 100644 index 0000000..302b938 --- /dev/null +++ b/src/packages/map-reaction-event/index.js @@ -0,0 +1,15 @@ +"use strict"; + +const addCommonFields = require("../event-add-common-fields"); +const mapMaybeRedacted = require("../map-maybe-redacted"); + +module.exports = function mapReactionEvent(event, _context) { + return addCommonFields({ + type: "reaction", + user: event.sender, + ... mapMaybeRedacted(event, () => ({ + body: event.content["m.relates_to"].key, + reactionTo: event.content["m.relates_to"].event_id + })) + }); +}; diff --git a/src/packages/map-receipt-event/index.js b/src/packages/map-receipt-event/index.js new file mode 100644 index 0000000..c40e295 --- /dev/null +++ b/src/packages/map-receipt-event/index.js @@ -0,0 +1,29 @@ +"use strict"; + +const syncpipe = require("syncpipe"); +const flatten = require("flatten"); +const matchValue = require("match-value"); + +module.exports = function mapReceiptEvent(event, _context) { + return syncpipe(event, [ + (_) => Object.entries(_.content), + (_) => _.map(([ eventID, receiptSets ]) => syncpipe(receiptSets, [ + (_) => Object.entries(_), + (_) => _.map(([ receiptType, receipts ]) => syncpipe(receipts, [ + (_) => Object.entries(_), + (_) => _.map(([ user, receiptData ]) => { + return { + type: "receiptReceived", + receiptType: matchValue(receiptType, { + "m.read": "readReceipt" + }), + user: user, + timestamp: receiptData.ts, + eventID: eventID + }; + }) + ])) + ])), + (_) => flatten(_) + ]); +}; diff --git a/src/packages/map-redaction-event/index.js b/src/packages/map-redaction-event/index.js new file mode 100644 index 0000000..a23e8b2 --- /dev/null +++ b/src/packages/map-redaction-event/index.js @@ -0,0 +1,12 @@ +"use strict"; + +const addCommonFields = require("../event-add-common-fields"); + +module.exports = function mapRedactionEvent(event, _context) { + return addCommonFields(event, { + type: "redaction", + sender: event.sender, + redactedEvent: event.redacts, + reason: event.content.reason + }); +}; diff --git a/src/packages/map-related-groups-event/index.js b/src/packages/map-related-groups-event/index.js new file mode 100644 index 0000000..a107d55 --- /dev/null +++ b/src/packages/map-related-groups-event/index.js @@ -0,0 +1,16 @@ +"use strict"; + +const addCommonFields = require("../event-add-common-fields"); +const mapMaybeRedacted = require("../map-maybe-redacted"); + +// Not in spec, documentation at https://docs.google.com/document/d/1wCLXwUT3r4gVFuQpwWMHxl-nEf_Kx2pv34vZQQVb_Bc/edit# + +module.exports = function mapRelatedGroupsEvent(event, _context) { + return addCommonFields(event, { + type: "associatedGroupsChanged", + sender: event.sender, + ... mapMaybeRedacted(event, () => ({ + groups: event.content.groups + })) + }); +}; diff --git a/src/packages/map-room-avatar-event/index.js b/src/packages/map-room-avatar-event/index.js new file mode 100644 index 0000000..c631b4a --- /dev/null +++ b/src/packages/map-room-avatar-event/index.js @@ -0,0 +1,15 @@ +"use strict"; + +const addCommonFields = require("../event-add-common-fields"); +const mapMaybeRedacted = require("../map-maybe-redacted"); + +module.exports = function mapRoomAvatarEvent(event, _context) { + // TODO: This event can optionally include image metadata and a thumbnail, like an m.image message! + return addCommonFields(event, { + type: "roomAvatarChanged", + sender: event.sender, + ... mapMaybeRedacted(event, () => ({ + url: event.content.url + })) + }); +}; diff --git a/src/packages/map-room-create-event/index.js b/src/packages/map-room-create-event/index.js new file mode 100644 index 0000000..1594797 --- /dev/null +++ b/src/packages/map-room-create-event/index.js @@ -0,0 +1,17 @@ +"use strict"; + +// FIXME: Tombstones + +const defaultValue = require("default-value"); + +const addCommonFields = require("../event-add-common-fields"); + +module.exports = function mapRoomCreateEvent(event, _context) { + // FIXME: Redactable + return addCommonFields(event, { + type: "roomCreated", + sender: event.content.creator, // TODO: Verify that this is always equal to `event.sender` + isFederated: defaultValue(event.content["m.federate"], true), + roomVersion: event.content.room_version, + }); +}; diff --git a/src/packages/map-room-name-event/index.js b/src/packages/map-room-name-event/index.js new file mode 100644 index 0000000..37bd2ef --- /dev/null +++ b/src/packages/map-room-name-event/index.js @@ -0,0 +1,14 @@ +"use strict"; + +const addCommonFields = require("../event-add-common-fields"); +const mapMaybeRedacted = require("../map-maybe-redacted"); + +module.exports = function mapRoomNameEvent(event, _context) { + return addCommonFields(event, { + type: "roomNameChanged", + sender: event.sender, + ... mapMaybeRedacted(event, () => ({ + name: event.content.name + })) + }); +}; diff --git a/src/packages/map-secret-storage-default-key-event/index.js b/src/packages/map-secret-storage-default-key-event/index.js new file mode 100644 index 0000000..6ade342 --- /dev/null +++ b/src/packages/map-secret-storage-default-key-event/index.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = function mapSecretStorageDefaultKeyEvent(event, _context) { + return { + type: "secretStorageDefaultKeyChanged", + keyID: event.content.key + }; +}; diff --git a/src/packages/map-secret-storage-key-event/index.js b/src/packages/map-secret-storage-key-event/index.js new file mode 100644 index 0000000..007b0b4 --- /dev/null +++ b/src/packages/map-secret-storage-key-event/index.js @@ -0,0 +1,66 @@ +"use strict"; + +const unreachable = require("@joepie91/unreachable"); +const matchValue = require("match-value"); +const defaultValue = require("default-value"); + +const normalizeEncryptionAlgorithmName = require("../normalize-encryption-algorithm-name"); +const normalizePassphraseAlgorithmName = require("../normalize-passphrase-algorithm-name"); + +let keyIDRegex = /^m\.secret_storage\.key\.(.+)$/; + +function getKeyID(type) { + let match = keyIDRegex.exec(type); + + if (match != null) { + return match[1]; + } else { + unreachable("Event type did not match key ID regex"); + } +} + +module.exports = function mapSecretStorageKeyEvent(event, _context) { + let isDerived = (event.content.passphrase != null); + let encryptionAlgorithm = normalizeEncryptionAlgorithmName(event.content.algorithm); + + let baseProperties = { + type: "secretStorageKey", + keyID: getKeyID(event.type), + name: event.content.name, + encryptionAlgorithm: encryptionAlgorithm, + isDerivedFromPassphrase: isDerived + }; + + if (isDerived) { + let passphraseData = event.content.passphrase; + let passphraseAlgorithm = normalizePassphraseAlgorithmName(passphraseData.algorithm); + + let passphraseConfiguration = matchValue(passphraseAlgorithm, { + "pbkdf2-sha512": { + salt: passphraseData.salt, + iterations: passphraseData.iterations, + bitsToGenerate: defaultValue(passphraseData.bits, 256) + } + }); + + return { + ... baseProperties, + passphraseAlgorithm: passphraseData.algorithm, + passphraseConfiguration: passphraseConfiguration, + encryptionConfiguration: {} + }; + } else { + let encryptionConfiguration = matchValue(encryptionAlgorithm, { + "aes-ctr-256.hmac-sha-256": { + iv: event.content.iv, + mac: event.content.mac + } + }); + + return { + ... baseProperties, + passphraseConfiguration: {}, + encryptionConfiguration: encryptionConfiguration + }; + } +}; diff --git a/src/packages/map-server-acl-event/index.js b/src/packages/map-server-acl-event/index.js new file mode 100644 index 0000000..c41e2ed --- /dev/null +++ b/src/packages/map-server-acl-event/index.js @@ -0,0 +1,62 @@ +"use strict"; + +const defaultValue = require("default-value"); + +const diffLists = require("../diff-lists"); + +function mapFields(eventContent) { + if (eventContent != null) { + let ipLiteralSetting = eventContent.allow_ip_literals; + + return { + // Weird logic to implement this spec requirement: "Defaults to true if missing or otherwise not a boolean." + ipLiteralsAllowed: (ipLiteralSetting === false) + ? false + : true, + allowedHosts: defaultValue(eventContent.allow, []), + deniedHosts: defaultValue(eventContent.deny, []) + }; + } else { + return { + ipLiteralsAllowed: true, + allowedHosts: [], + deniedHosts: [] + }; + } +} + +module.exports = function mapServerACLEvent(event, _context) { + // FIXME: Number + full event + let newFields = mapFields(event.content); + let oldFields = mapFields(event.unsigned.prev_content); + let deltaEvents = []; + + if (newFields.ipLiteralsAllowed !== oldFields.ipLiteralsAllowed) { + deltaEvents.push({ + type: "accessRulesChanged", + sender: event.sender, + ipLiteralsAllowed: newFields.ipLiteralsAllowed + }); + } + + let { removed: removedAllowed, added: addedAllowed } = diffLists(oldFields.allowedHosts, newFields.allowedHosts); + let { removed: removedDenied, added: addedDenied } = diffLists(oldFields.deniedHosts, newFields.deniedHosts); + + for (let host of removedAllowed) { + deltaEvents.push({ type: "allowedHostRemoved", sender: event.sender, hostmask: host }); + } + + for (let host of removedDenied) { + deltaEvents.push({ type: "deniedHostRemoved", sender: event.sender, hostmask: host }); + } + + for (let host of addedAllowed) { + deltaEvents.push({ type: "allowedHostAdded", sender: event.sender, hostmask: host }); + } + + for (let host of addedDenied) { + deltaEvents.push({ type: "deniedHostAdded", sender: event.sender, hostmask: host }); + } + + return deltaEvents; +}; diff --git a/src/packages/map-tag-event/index.js b/src/packages/map-tag-event/index.js new file mode 100644 index 0000000..e10fef9 --- /dev/null +++ b/src/packages/map-tag-event/index.js @@ -0,0 +1,53 @@ +"use strict"; + +const matchValue = require("match-value"); + +let standardRegex = /^m\.([\s\S]+)$/u; +let userRegex = /^u\.([\s\S]+)$/u; +let clientRegex = /^((?:[^.]+\.){2,})([\s\S]+)$/u; + +function parseTag(tag) { + let match; + + if (match = standardRegex.exec(tag)) { + return { + type: "standard", + name: matchValue(match[1], { + lowpriority: "isLowPriority", + favourite: "isFavourite", + server_notice: "isServerNoticeRoom" + }) + }; + } else if (match = userRegex.exec(tag)) { + return { + type: "user", + name: match[1] + }; + } else if (match = clientRegex.exec(tag)) { + return { + type: "client", + namespace: match[1], + name: match[2] + }; + } else { + // Legacy, un-namespaced format + return { + type: "user", + name: tag + }; + } +} + +module.exports = function mapTagEvent(event, _context) { + return { + type: "tagsChanged", + tags: Object.entries(event.content.tags).map(([ tagString, options ]) => { + return { + ... parseTag(tagString), + tagString: tagString, + // TODO: Explicitly parse options? + options: options + }; + }) + }; +}; diff --git a/src/packages/map-topic-event/index.js b/src/packages/map-topic-event/index.js new file mode 100644 index 0000000..d357d83 --- /dev/null +++ b/src/packages/map-topic-event/index.js @@ -0,0 +1,14 @@ +"use strict"; + +const addCommonFields = require("../event-add-common-fields"); +const mapMaybeRedacted = require("../map-maybe-redacted"); + +module.exports = function mapTopicEvent(event, _context) { + return addCommonFields(event, { + type: "topicChanged", + sender: event.sender, + ... mapMaybeRedacted(event, () => ({ + topic: event.content.topic + })) + }); +}; diff --git a/src/packages/map-typing-event/index.js b/src/packages/map-typing-event/index.js new file mode 100644 index 0000000..0a164bb --- /dev/null +++ b/src/packages/map-typing-event/index.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = function mapTypingEvent(event, _context) { + return { + type: "typingUserListChanged", + users: event.content.user_ids + }; +}; diff --git a/src/packages/normalize-encryption-algorithm-name/index.js b/src/packages/normalize-encryption-algorithm-name/index.js new file mode 100644 index 0000000..e8d8a54 --- /dev/null +++ b/src/packages/normalize-encryption-algorithm-name/index.js @@ -0,0 +1,11 @@ +"use strict"; + +const matchValue = require("match-value"); + +module.exports = function normalizeEncryptionAlgorithmName(algorithm) { + return matchValue(algorithm, { + "m.secret_storage.v1.aes-hmac-sha2": "aes-ctr-256.hmac-sha-256", + "m.olm.v1.curve25519-aes-sha2": "olm.curve25519.aes-cbc-256.sha-256", + "m.megolm.v1.aes-sha2": "megolm.ed25519.aes-cbc-256.hmac-sha-256" + }); +}; diff --git a/src/packages/normalize-passphrase-algorithm-name/index.js b/src/packages/normalize-passphrase-algorithm-name/index.js new file mode 100644 index 0000000..0d8aede --- /dev/null +++ b/src/packages/normalize-passphrase-algorithm-name/index.js @@ -0,0 +1,9 @@ +"use strict"; + +const matchValue = require("match-value"); + +module.exports = function normalizePassphraseAlgorithmName(algorithm) { + return matchValue(algorithm, { + "m.pbkdf2": "pbkdf2-sha512" + }); +}; diff --git a/src/packages/number-derived-events/index.js b/src/packages/number-derived-events/index.js new file mode 100644 index 0000000..5ecbf89 --- /dev/null +++ b/src/packages/number-derived-events/index.js @@ -0,0 +1,15 @@ +"use strict"; + +// TODO: If the derivation logic changes, these IDs may not be stable. Need to figure out whether that's going to be an issue or not, and whether we may need to replace it with a deterministic algorithm that eg. takes a key-returning callback (which uses the event data to generate it). + +module.exports = function numberDerivedEvents(id, events) { + // NOTE: Mutates `events`! + let i = 0; + + for (let event of events) { + // NOTE: We do this so that each derived event has a unique ID, but it is still possible to refer back to the original event ID + event.id = `${id}_${i}`; + event.sourceEventID = id; + i += 1; + } +}; diff --git a/src/packages/optional-array/index.js b/src/packages/optional-array/index.js new file mode 100644 index 0000000..e1bab5d --- /dev/null +++ b/src/packages/optional-array/index.js @@ -0,0 +1,9 @@ +"use strict"; + +const defaultTo = require("@validatem/default-to"); + +// FIXME: Package for @validatem + +module.exports = function optionalArray(rules) { + return [ defaultTo([]), rules ]; +}; diff --git a/src/packages/optional-object/index.js b/src/packages/optional-object/index.js new file mode 100644 index 0000000..1d91a31 --- /dev/null +++ b/src/packages/optional-object/index.js @@ -0,0 +1,9 @@ +"use strict"; + +const defaultTo = require("@validatem/default-to"); + +// FIXME: Package for @validatem + +module.exports = function optionalObject(rules) { + return [ defaultTo({}), rules ]; +}; diff --git a/src/packages/parse-identifier/index.js b/src/packages/parse-identifier/index.js new file mode 100644 index 0000000..4a8d129 --- /dev/null +++ b/src/packages/parse-identifier/index.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = function parseIdentifier(identifier) { + // FIXME +}; diff --git a/src/packages/parse-messages-response/index.js b/src/packages/parse-messages-response/index.js new file mode 100644 index 0000000..0a71568 --- /dev/null +++ b/src/packages/parse-messages-response/index.js @@ -0,0 +1,37 @@ +"use strict"; + +const { validateArguments } = require("@validatem/core"); +const required = require("@validatem/required"); +const concatArrays = require("concat-arrays"); +const itemDeduplicator = require("../item-deduplicator"); + +const isMessagesResponse = require("../is-messages-response"); + +module.exports = function parseMessagesResponse(_response) { + // FIXME: Figure out a way to soft-fail, and turn the validation error into a warning event + let [ response ] = validateArguments(arguments, { + response: [ required, isMessagesResponse ] + }); + + let deduplicateEvent = itemDeduplicator((event) => event.event_id); + + function toTimestampedEvent(event, type) { + return { + type: type, + event: deduplicateEvent(event), + timestamp: event.origin_server_ts + }; + } + + if (response.chunk.length > 0) { + return { + events: concatArrays( + response.chunk.map((event) => toTimestampedEvent(event, "roomTimelineEvent")), + response.state.map((event) => toTimestampedEvent(event, "roomStateUpdate")), + ), + paginationToken: response.end + }; + } else { + return { events: [] }; + } +}; diff --git a/src/packages/parse-sync-response/index.js b/src/packages/parse-sync-response/index.js new file mode 100644 index 0000000..dbcf63d --- /dev/null +++ b/src/packages/parse-sync-response/index.js @@ -0,0 +1,247 @@ +"use strict"; + +const defaultValue = require("default-value"); +const assureArray = require("assure-array"); +const matchValue = require("match-value"); +const concatArrays = require("concat-arrays"); +const syncpipe = require("syncpipe"); +const flatten = require("flatten"); + +const { validateArguments } = require("@validatem/core"); +const required = require("@validatem/required"); + +const eventDeduplicator = require("../event-deduplicator"); +const isSyncResponse = require("../is-sync-response"); +const removeStateDuplication = require("./remove-state-duplication"); + +function maybeMap(array, mappingFunction) { + if (array == null) { + return []; + } else { + return array.map(mappingFunction); + } +} + +function maybeMapObject(object, mappingFunction) { + if (object == null) { + return []; + } else { + return Object.entries(object).map(mappingFunction); + } +} + + +module.exports = function syncResponseToEvents(_syncResponse) { + // require("fs").writeFileSync("private/dump.json", JSON.stringify(_syncResponse)); + let [ syncResponse ] = validateArguments(arguments, { + syncResponseBody: [ required, isSyncResponse ], // TODO: Validate and normalize the response body, including setting defaults, and allowing extra properties + }); + + // We keep an event ID -> event body mapping, to ensure that the same event in different places in the response maps to the same in-memory object in our resulting event list; this is useful both to save memory, and to make equality-checking operations work + // FIXME: Check if we need to deep-compare objects here to detect abbreviated versions of events? Otherwise we might end up replacing a full event with an abbreviated version. + let deduplicateEvent = eventDeduplicator(); + + function toTimestampedEvent(event, type) { + return { + type: type, + event: deduplicateEvent(event), + timestamp: event.origin_server_ts + }; + } + + function toUntimestampedEvent(event, type) { + return { + type: type, + event: deduplicateEvent(event) + }; + } + + function toRoomSummaryEvent(key, value) { + return { + type: "roomSummaryUpdate", + key: key, + value: value + }; + } + + function toNotificationCountsEvent(notificationCount) { + return { + type: "roomNotificationCounts", + data: notificationCount + }; + } + + function toDeviceListUserChangedEvent(user) { + return { + type: "deviceListUserChanged", + user: user + }; + } + + function toDeviceListUserLeftEvent(user) { + return { + type: "deviceListUserLeft", + user: user + }; + } + + function toMemberStateEvent(room, memberState) { + return { + type: "memberState", + room: room, // FIXME: Rename all room to roomID for consistency? + state: memberState + }; + } + + let roomParsingRules = [ + { types: [ "joined", "left", "invited" ], parser: (room, memberState) => { + return toMemberStateEvent(room, memberState); + } }, + { types: [ "joined", "left" ], parser: (room) => { + return maybeMap(room.state.events, (event) => toTimestampedEvent(event, "roomStateUpdate")); + } }, + { types: [ "joined", "left" ], parser: (room) => { + // NOTE: Can still contain state events! But they are a part of the timeline, not a 'summarized' state delta like `roomStateUpdate`s. + return maybeMap(room.timeline.events, (event) => toTimestampedEvent(event, "roomTimelineEvent")); + } }, + { types: [ "joined", "left" ], parser: (room) => { + return maybeMap(room.account_data.events, (event) => toUntimestampedEvent(event, "roomAccountData")); + } }, + { types: [ "joined" ], parser: (room) => { + return maybeMap(room.ephemeral.events, (event) => toUntimestampedEvent(event, "roomEphemeralEvent")); + } }, + { types: [ "joined" ], parser: (room) => { + return maybeMapObject(room.summary, ([ key, value ]) => toRoomSummaryEvent(key, value)); + } }, + { types: [ "joined" ], parser: (room) => { + if (room.unread_notifications != null) { + return toNotificationCountsEvent(room.unread_notifications); + } + } }, + { types: [ "invited" ], parser: (room) => { + return maybeMap(room.invite_state.events, (event) => toUntimestampedEvent(event, "roomInviteState")); + } }, + ]; + + let globalParsingRules = [ + { key: "device_lists", parser: (deviceLists) => { + return concatArrays([ + maybeMap(deviceLists.changed, (user) => toDeviceListUserChangedEvent(user)), + maybeMap(deviceLists.left, (user) => toDeviceListUserLeftEvent(user)), + ]); + } }, + { key: "device_one_time_keys_count", parser: (oneTimeKeysCounts) => { + /* QUESTION: Always received, or only when one value updates? And if the latter, only the delta or the full list of algorithms? */ + return { + type: "deviceOneTimeKeysCount", + keyCounts: oneTimeKeysCounts + }; + } }, + ]; + + let limitedRooms = []; + let previousBatchTokens = {}; + + let roomsWithTimelines = concatArrays( + Object.entries(syncResponse.rooms.join), + Object.entries(syncResponse.rooms.leave), + ); + + for (let [ roomId, room ] of roomsWithTimelines) { + if (room.timeline.prev_batch != null) { + previousBatchTokens[roomId] = room.timeline.prev_batch; + } + + if (room.timeline.limited === true) { + limitedRooms.push(roomId); + } + } + + function parseRoom({ room, roomId, memberState }) { + return roomParsingRules.map((rule) => { + if (rule.types.includes(memberState)) { + return assureArray(rule.parser(room, memberState)) + .filter((event) => event != null) + .map((event) => { + return { + ... event, + room: roomId, + // memberState: memberState + }; + }); + } + }); + } + + let roomEvents = ["join", "leave", "invite"].map((roomType) => { + if (syncResponse.rooms[roomType] != null) { + let memberState = matchValue(roomType, { + join: "joined", + leave: "left", + invite: "invited" + }); + + return Object.entries(syncResponse.rooms[roomType]) + .filter(([ _roomId, room ]) => room != null) + .map(([ roomId, room ]) => { + return parseRoom({ room, roomId, memberState }); + }); + } else { + return []; + } + }); + + let globalEvents = globalParsingRules.map((rule) => { + let data = syncResponse[rule.key]; + + if (data != null) { + return syncpipe(data, [ + (_) => rule.parser(_), + (_) => assureArray(_) + ]); + } else { + return []; + } + }); + + let globalTimelineMapping = { + presence: "presenceEvent", + account_data: "accountData", + to_device: "toDeviceEvent" + }; + + let globalTimelineEvents = syncpipe(globalTimelineMapping, [ + (_) => Object.entries(_), + (_) => _.map(([ source, eventType ]) => { + let events = defaultValue(syncResponse[source].events, []); + + return events.map((event) => { + return { + type: eventType, + event: deduplicateEvent(event) + }; + }); + }) + ]); + + let events = syncpipe(null, [ + (_) => concatArrays( + globalEvents, + globalTimelineEvents, + roomEvents + ), + (_) => flatten(_), + (_) => _.filter((event) => event !== undefined), + (_) => removeStateDuplication(_) + ]); + + // FIXME: In the stream API, translate the metadata into events in and of themselves; probably combining the 'limited' markers and previous-batch tokens into one event, to allow clients to backfill based on that + // -> Do we need to emit such events for rooms that are *not* limited? Is there an actual purpose to that? + + return { + syncToken: syncResponse.next_batch, + limitedRooms: limitedRooms, + previousBatchTokens: previousBatchTokens, + events: events + }; +}; diff --git a/src/packages/parse-sync-response/remove-state-duplication.js b/src/packages/parse-sync-response/remove-state-duplication.js new file mode 100644 index 0000000..55661e3 --- /dev/null +++ b/src/packages/parse-sync-response/remove-state-duplication.js @@ -0,0 +1,21 @@ +"use strict"; + +const syncpipe = require("syncpipe"); + +// NOTE: This is a workaround for https://github.com/matrix-org/synapse/issues/1597 + +module.exports = function removeStateDuplication(events) { + let seenTimelineEvents = syncpipe(events, [ + (_) => _.filter((event) => event.type === "roomTimelineEvent"), + (_) => _.map((event) => event.event.event_id), + (_) => _.filter((id) => id != null), + (_) => new Set(_) + ]); + + return events.filter((event) => { + let isStateEvent = event.type === "roomStateUpdate"; + let isDuplicatedState = isStateEvent && seenTimelineEvents.has(event.event.event_id); + + return !isDuplicatedState; + }); +}; diff --git a/src/packages/send-image-event/index.js b/src/packages/send-image-event/index.js new file mode 100644 index 0000000..a0e887d --- /dev/null +++ b/src/packages/send-image-event/index.js @@ -0,0 +1,41 @@ +"use strict"; + +const sendMessageEvent = require("../send-message-event"); +const unmapImageOptions = require("../unmap-image-options"); + +const { validateArguments } = require("@validatem/core"); +const required = require("@validatem/required"); +const optionalObject = require("../optional-object"); +const isNonEmptyString = require("@validatem/is-non-empty-string"); +const isInteger = require("@validatem/is-integer"); +const isRoomID = require("../is-room-id"); +const isThumbnailOptions = require("../is-thumbnail-options"); +const isSession = require("@modular-matrix/is-session"); +const isMXC = require("@modular-matrix/is-mxc-url"); + +module.exports = function sendImageEvent(_session, _options) { + let [ session, options ] = validateArguments(arguments, { + session: [ required, isSession ], + options: [ required, { + roomID: [ required, isRoomID ], + description: [ required, isNonEmptyString ], + image: [ required, { + url: [ required, isMXC ], + displayWidth: [ isInteger ], + displayHeight: [ isInteger ], + mimetype: [ isNonEmptyString ], + filesize: [ isInteger ], + }], + thumbnail: optionalObject(isThumbnailOptions) + }] + }); + + return sendMessageEvent(session, { + roomID: options.roomID, + type: "m.room.message", + content: { + msgtype: "m.image", + ... unmapImageOptions(options) + } + }); +}; diff --git a/src/packages/send-image-event/test.js b/src/packages/send-image-event/test.js new file mode 100644 index 0000000..8b0a25d --- /dev/null +++ b/src/packages/send-image-event/test.js @@ -0,0 +1,32 @@ +"use strict"; + +const Promise = require("bluebird"); +const createSession = require("@modular-matrix/create-session"); +const sendImageEvent = require("./"); + +return Promise.try(() => { + return createSession("https://pixie.town/", { accessToken: require("../../../private/access-token") }); +}).then((session) => { + return Promise.try(() => { + return sendImageEvent(session, { + roomID: "!TFtSgVEJlHMAhDyHOk:pixie.town", + description: "potato.jpg", + image: { + url: "mxc://pixie.town/ZgoXjZXGehIIVhoyrlwOyEnH", + displayWidth: 400, + displayHeight: 300, + mimetype: "image/jpeg", + filesize: 113924 + }, + thumbnail: { + url: "mxc://pixie.town/ZgoXjZXGehIIVhoyrlwOyEnH", + displayWidth: 400, + displayHeight: 300, + mimetype: "image/jpeg", + filesize: 113924 + }, + }); + }).then((result) => { + console.log(result); + }); +}); diff --git a/src/packages/send-image/index.js b/src/packages/send-image/index.js new file mode 100644 index 0000000..4e26ed9 --- /dev/null +++ b/src/packages/send-image/index.js @@ -0,0 +1,105 @@ +"use strict"; + +const Promise = require("bluebird"); +const path = require("path"); +const matchValue = require("match-value"); +const defaultValue = require("default-value"); +const thumbnailImage = require("../thumbnail-image"); +const uploadFile = require("../upload-file"); +const sendImageEvent = require("../send-image-event"); +const universalImageMetadata = require("../universal-image-metadata"); + +const { validateArguments } = require("@validatem/core"); +const required = require("@validatem/required"); +const isInteger = require("@validatem/is-integer"); +const isNonEmptyString = require("@validatem/is-non-empty-string"); +const defaultTo = require("@validatem/default-to"); +const isSession = require("@modular-matrix/is-session"); +const isRoomID = require("../is-room-id"); +const isPositive = require("../is-positive"); + +function thumbnailFilename(filename, mimetype) { + let extension = matchValue(mimetype, { + "image/png": "png", + "image/jpeg": "jpeg" + }); + + if (filename != null) { + let baseName = path.basename(filename, path.extname(filename)); + return `thumbnail-${baseName}.${extension}`; + } else { + return `thumbnail.${extension}`; + } +} + +module.exports = function sendImage(_session, _options) { + let [ session, options ] = validateArguments(arguments, { + session: [ required, isSession ], + options: [ required, { + roomID: [ required, isRoomID ], + description: [ required, isNonEmptyString ], + file: [ required ], // FIXME: Validate more strictly, something like isUploadable + filename: [ isNonEmptyString ], + thumbnail: [{ + maximumWidth: [ required, isInteger, isPositive ], + maximumHeight: [ required, isInteger, isPositive ], + }, defaultTo({ maximumWidth: 800, maximumHeight: 600 }) ] + }] + }); + + return Promise.all([ + universalImageMetadata(options.file), + uploadFile(session, { + file: options.file, + filename: options.filename + }) + ]).then(([ metadata, uploadResult ]) => { + let fullSizeMXC = uploadResult.url; + + let desiredThumbnailMimetype = (metadata.mimetype === "image/jpeg") + ? "image/jpeg" + : "image/png"; + + return Promise.try(() => { + return thumbnailImage({ + session: session, + mxc: fullSizeMXC, + maximumWidth: options.thumbnail.maximumWidth, + maximumHeight: options.thumbnail.maximumHeight, + file: options.file, + mimetype: desiredThumbnailMimetype + }); + }).then((thumbnail) => { + let thumbnailFile = defaultValue(thumbnail.blob, thumbnail.buffer); + let thumbnailFilesize = (thumbnail.buffer != null) ? thumbnail.buffer.length : thumbnail.blob.size; + + return Promise.try(() => { + return uploadFile(session, { + file: thumbnailFile, + filename: thumbnailFilename(options.filename) + }); + }).then((result) => { + let thumbnailMXC = result.url; + + return sendImageEvent(session, { + roomID: options.roomID, + description: options.description, + image: { + url: fullSizeMXC, + displayWidth: metadata.width, + displayHeight: metadata.height, + mimetype: metadata.mimetype, + filesize: metadata.filesize + }, + thumbnail: { + url: thumbnailMXC, + displayWidth: thumbnail.width, + displayHeight: thumbnail.height, + mimetype: defaultValue(thumbnail.mimetype, desiredThumbnailMimetype), + filesize: thumbnailFilesize + } + }); + }); + }); + }); +}; diff --git a/src/packages/send-image/potato.jpg b/src/packages/send-image/potato.jpg new file mode 100644 index 0000000..353631d Binary files /dev/null and b/src/packages/send-image/potato.jpg differ diff --git a/src/packages/send-image/test.js b/src/packages/send-image/test.js new file mode 100644 index 0000000..58f946a --- /dev/null +++ b/src/packages/send-image/test.js @@ -0,0 +1,22 @@ +"use strict"; + +const Promise = require("bluebird"); +const fs = require("fs"); +const path = require("path"); +const createSession = require("@modular-matrix/create-session"); +const sendImage = require("./"); + +return Promise.try(() => { + return createSession("https://pixie.town/", { accessToken: require("../../../private/access-token") }); +}).then((session) => { + return Promise.try(() => { + return sendImage(session, { + roomID: "!TFtSgVEJlHMAhDyHOk:pixie.town", + description: "potato.jpg", + file: fs.createReadStream(path.join(__dirname, "./potato.jpg")), + filename: "potato.jpg", + }); + }).then((result) => { + console.log(result); + }); +}); diff --git a/src/packages/send-message-event/index.js b/src/packages/send-message-event/index.js new file mode 100644 index 0000000..86a9c00 --- /dev/null +++ b/src/packages/send-message-event/index.js @@ -0,0 +1,49 @@ +"use strict"; + +const Promise = require("bluebird"); + +const mmAxios = require("@modular-matrix/axios"); +const generateTransactionID = require("../generate-transaction-id"); +const makeURL = require("make-url"); + +const { validateArguments } = require("@validatem/core"); +const required = require("@validatem/required"); +const isPlainObject = require("@validatem/is-plain-object"); +const isString = require("@validatem/is-string"); +const isRoomID = require("../is-room-id"); +const isSession = require("@modular-matrix/is-session"); + +/* TODO: +- Retries +- Send queue +*/ + +module.exports = function sendMessageEvent(_session, _options) { + let [ session, options ] = validateArguments(arguments, { + session: [ required, isSession ], + options: [ required, { + roomID: [ required, isRoomID ], + type: [ required, isString ], + content: [ required, isPlainObject ] + }] + }); + + let axios = mmAxios({ session: session }); + let transactionID = generateTransactionID(); + + return Promise.try(() => { + let url = makeURL("/client/r0/rooms/:roomID/send/:type/:transactionID", { + roomID: options.roomID, + type: options.type, + transactionID: transactionID + }); + + return axios.put(url, options.content, { + sendQueueKey: options.roomID + }); + }).then((response) => { + return { + eventID: response.data.event_id + }; + }); +}; diff --git a/src/packages/send-message/index.js b/src/packages/send-message/index.js new file mode 100644 index 0000000..a97d8b2 --- /dev/null +++ b/src/packages/send-message/index.js @@ -0,0 +1,33 @@ +"use strict"; + +const sendMessageEvent = require("../send-message-event"); + +const { validateArguments } = require("@validatem/core"); +const required = require("@validatem/required"); +const isNonEmptyString = require("@validatem/is-non-empty-string"); +const isRoomID = require("../is-room-id"); +const isSession = require("@modular-matrix/is-session"); + +module.exports = function sendMessage(_session, _options) { + let [ session, options ] = validateArguments(arguments, { + session: [ required, isSession ], + options: [ required, { + roomID: [ required, isRoomID ], + text: [ required, isNonEmptyString ], + html: [ isNonEmptyString ] + }] + }); + + return sendMessageEvent(session, { + roomID: options.roomID, + type: "m.room.message", + content: { + msgtype: "m.text", + body: options.text, + format: (options.html != null) + ? "org.matrix.custom.html" + : undefined, + formatted_body: options.html + } + }); +}; diff --git a/src/packages/send-message/test.js b/src/packages/send-message/test.js new file mode 100644 index 0000000..3157d48 --- /dev/null +++ b/src/packages/send-message/test.js @@ -0,0 +1,58 @@ +"use strict"; + +const Promise = require("bluebird"); +const createSession = require("@modular-matrix/create-session"); +const sendMessage = require("./"); + +function logError(error) { + console.error("caught:", require("util").inspect(error, { colors: true, depth: 0 })); +} + +return Promise.try(() => { + return createSession("https://pixie.town/", { accessToken: require("../../../private/access-token") }); +}).then((session) => { + return Promise.try(() => { + // return sendMessage(session, { + // roomID: "!TFtSgVEJlHMAhDyHOk:pixie.town", + // text: "---", + // html: "---" + // }); + }).then(() => { + // return Promise.all([ + // sendMessage(session, { + // roomID: "!TFtSgVEJlHMAhDyHOk:pixie.town", + // text: "Hello world 1!", + // html: "Hello world 1!" + // }), + // sendMessage(session, { + // roomID: "!TFtSgVEJlHMAhDyHOk:pixie.town", + // text: "Hello world 2!", + // html: "Hello world 2!" + // }), + // sendMessage(session, { + // roomID: "!TFtSgVEJlHMAhDyHOk:pixie.town", + // text: "Hello world 3!", + // html: "Hello world 3!" + // }), + // ]); + return Promise.all([ + sendMessage(session, { + roomID: "!TFtSgVEJlHMAhDyHOk:pixie.town", + text: "Hello world 1!", + html: "Hello world 1!" + }).catch((err) => logError(err)), + sendMessage(session, { + roomID: "!TFtSgVEJlHMAhDyHOk:pixie.town", + text: "Hello world 2!", + html: "Hello world 2!" + }).catch((err) => logError(err)), + sendMessage(session, { + roomID: "!TFtSgVEJlHMAhDyHOk:pixie.town", + text: "Hello world 3!", + html: "Hello world 3!" + }).catch((err) => logError(err)), + ]); + }).then((result) => { + console.log(result); + }); +}); diff --git a/src/packages/send-sticker-event/index.js b/src/packages/send-sticker-event/index.js new file mode 100644 index 0000000..500865d --- /dev/null +++ b/src/packages/send-sticker-event/index.js @@ -0,0 +1,38 @@ +"use strict"; + +const sendMessageEvent = require("../send-message-event"); +const unmapImageOptions = require("../unmap-image-options"); + +const { validateArguments } = require("@validatem/core"); +const required = require("@validatem/required"); +const optionalObject = require("../optional-object"); +const isNonEmptyString = require("@validatem/is-non-empty-string"); +const isInteger = require("@validatem/is-integer"); +const isRoomID = require("../is-room-id"); +const isThumbnailOptions = require("../is-thumbnail-options"); +const isSession = require("@modular-matrix/is-session"); +const isMXC = require("@modular-matrix/is-mxc-url"); + +module.exports = function sendStickerEvent(_session, _options) { + let [ session, options ] = validateArguments(arguments, { + session: [ required, isSession ], + options: [ required, { + roomID: [ required, isRoomID ], + description: [ required, isNonEmptyString ], + image: [ required, { + url: [ required, isMXC ], + displayWidth: [ isInteger ], + displayHeight: [ isInteger ], + mimetype: [ isNonEmptyString ], + filesize: [ isInteger ], + }], + thumbnail: optionalObject(isThumbnailOptions) + }] + }); + + return sendMessageEvent(session, { + roomID: options.roomID, + type: "m.sticker", + content: unmapImageOptions(options) + }); +}; diff --git a/src/packages/send-sticker-event/test.js b/src/packages/send-sticker-event/test.js new file mode 100644 index 0000000..8fcdd81 --- /dev/null +++ b/src/packages/send-sticker-event/test.js @@ -0,0 +1,32 @@ +"use strict"; + +const Promise = require("bluebird"); +const createSession = require("@modular-matrix/create-session"); +const sendStickerEvent = require("./"); + +return Promise.try(() => { + return createSession("https://pixie.town/", { accessToken: require("../../../private/access-token") }); +}).then((session) => { + return Promise.try(() => { + return sendStickerEvent(session, { + roomID: "!TFtSgVEJlHMAhDyHOk:pixie.town", + description: "Test sticker", + image: { + url: "mxc://pixie.town/ZgoXjZXGehIIVhoyrlwOyEnH", + displayWidth: 400, + displayHeight: 300, + mimetype: "image/jpeg", + filesize: 113924 + }, + thumbnail: { + url: "mxc://pixie.town/ZgoXjZXGehIIVhoyrlwOyEnH", + displayWidth: 400, + displayHeight: 300, + mimetype: "image/jpeg", + filesize: 113924 + }, + }); + }).then((result) => { + console.log(result); + }); +}); diff --git a/src/packages/send-video-event/index.js b/src/packages/send-video-event/index.js new file mode 100644 index 0000000..6b01b98 --- /dev/null +++ b/src/packages/send-video-event/index.js @@ -0,0 +1,52 @@ +"use strict"; + +const sendMessageEvent = require("../send-message-event"); +const unmapImageInfo = require("../unmap-image-info"); + +const { validateArguments } = require("@validatem/core"); +const required = require("@validatem/required"); +const optionalObject = require("../optional-object"); +const isNonEmptyString = require("@validatem/is-non-empty-string"); +const isInteger = require("@validatem/is-integer"); +const isRoomID = require("../is-room-id"); +const isThumbnailOptions = require("../is-thumbnail-options"); +const isSession = require("@modular-matrix/is-session"); +const isMXC = require("@modular-matrix/is-mxc-url"); + +module.exports = function sendVideoEvent(_session, _options) { + let [ session, options ] = validateArguments(arguments, { + session: [ required, isSession ], + options: [ required, { + roomID: [ required, isRoomID ], + description: [ required, isNonEmptyString ], + video: [ required, { + url: [ required, isMXC ], + displayWidth: [ isInteger ], + displayHeight: [ isInteger ], + mimetype: [ isNonEmptyString ], + filesize: [ isInteger ], + }], + thumbnail: optionalObject(isThumbnailOptions) + }] + }); + + return sendMessageEvent(session, { + roomID: options.roomID, + type: "m.room.message", + content: { + msgtype: "m.video", + body: options.description, + url: options.video.url, + info: { + w: options.video.displayWidth, + h: options.video.displayHeight, + mimetype: options.video.mimetype, + size: options.video.filesize, + thumbnail_url: options.thumbnail.url, + thumbnail_info: (options.thumbnail.url != null) + ? unmapImageInfo(options.thumbnail) + : undefined + } + } + }); +}; diff --git a/src/packages/send-video-event/test.js b/src/packages/send-video-event/test.js new file mode 100644 index 0000000..a1f2683 --- /dev/null +++ b/src/packages/send-video-event/test.js @@ -0,0 +1,32 @@ +"use strict"; + +const Promise = require("bluebird"); +const createSession = require("@modular-matrix/create-session"); +const sendImageEvent = require("."); + +return Promise.try(() => { + return createSession("https://pixie.town/", { accessToken: require("../../../private/access-token") }); +}).then((session) => { + return Promise.try(() => { + return sendImageEvent(session, { + roomID: "!TFtSgVEJlHMAhDyHOk:pixie.town", + description: "video.mp4", + video: { + url: "mxc://pixie.town/nOXbufEBytEHyTFPydqLBbsW", + displayWidth: 176, + displayHeight: 234, + mimetype: "video/mp4", + filesize: 81770 + }, + thumbnail: { + url: "mxc://pixie.town/ZgoXjZXGehIIVhoyrlwOyEnH", + displayWidth: 176, + displayHeight: 234, + mimetype: "image/jpeg", + filesize: 113924 + }, + }); + }).then((result) => { + console.log(result); + }); +}); diff --git a/src/packages/sort-event-comparator/index.js b/src/packages/sort-event-comparator/index.js new file mode 100644 index 0000000..80cad71 --- /dev/null +++ b/src/packages/sort-event-comparator/index.js @@ -0,0 +1,20 @@ +"use strict"; + +module.exports = function (a, b) { + if (a.type === "roomStateUpdate" && b.type === "roomTimelineEvent") { + // Sort roomStateUpdates towards the front + return -1; + } else if (a.type === "roomTimelineEvent" && b.type === "roomStateUpdate") { + return 1; + } else if (a.timestamp == null && b.timestamp != null) { + // Sort timestamp-less events towards the front + return -1; + } else if (a.timestamp != null && b.timestamp == null) { + return 1; + } else if (a.timestamp == null && b.timestamp == null) { + // Keep timestamp-less events stable, relative to each other + return 0; + } else { + return (a.timestamp - b.timestamp); + } +}; diff --git a/src/packages/stream-backlog/index.js b/src/packages/stream-backlog/index.js new file mode 100644 index 0000000..b072d29 --- /dev/null +++ b/src/packages/stream-backlog/index.js @@ -0,0 +1,77 @@ +"use strict"; + +const Promise = require("bluebird"); +const { chain } = require("error-chain"); + +const mmAxios = require("@modular-matrix/axios"); +const matchProtocolError = require("@modular-matrix/match-protocol-error"); +const errors = require("@modular-matrix/errors"); +const mmParseMessagesResponse = require("../parse-messages-response"); +const mmMapEvents = require("../map-events"); + +const pipe = require("@promistream/pipe"); +const simpleSource = require("@promistream/simple-source"); +const buffer = require("@promistream/buffer"); +const EndOfStream = require("@promistream/end-of-stream"); + +const { validateArguments } = require("@validatem/core"); +const required = require("@validatem/required"); +const isString = require("@validatem/is-string"); +const isInteger = require("@validatem/is-integer"); +const isFunction = require("@validatem/is-function"); + +// FIXME: Ensure that there is a way to define a limit, and get back a token that lets us continue from the point where that limit was reached. + +module.exports = function streamBacklog(_session, _roomID, _options) { + let [ session, roomID, options ] = validateArguments(arguments, { + session: [ required ], // FIXME: Session object validation + roomID: [ required, isString ], // FIXME: Strict validation + options: [ required, { + start: [ required, isString ], + end: [ isString ], + chunkSize: [ isInteger ], + eventMapper: [ isFunction ] + }] + }); + + let lastToken = options.start; + + let axios = mmAxios({ session: session }); + + return pipe([ + simpleSource(() => { + if (lastToken != null) { + return Promise.try(() => { + // FIXME: Write urlEncodedInput`` abstraction + return axios.get(`/client/r0/rooms/${encodeURIComponent(roomID)}/messages`, { params: { + from: lastToken, + to: options.end, + dir: "b", + limit: options.chunkSize + // FIXME: filter? + }}); + }).then((response) => { + // console.log(require("util").inspect(response.data.chunk, { colors: true, depth: null })); + + let { events, paginationToken } = mmParseMessagesResponse(response.data); + + // Currently the only way to detect the end of pagination + if (events.length > 0) { + lastToken = paginationToken; + + // FIXME: Return type:event items and a type:token item + return mmMapEvents(events, options.eventMapper); + } else { + throw new EndOfStream; + } + }).catch(matchProtocolError(403, "M_FORBIDDEN"), (error) => { + throw chain(error, errors.AccessDenied, "You cannot access the messages in that room, or the room does not exist"); + }); + } else { + // For supporting the proposed future end-of-pagination detection method (https://github.com/matrix-org/matrix-doc/issues/2251) + throw new EndOfStream; + } + }), + buffer() + ]); +}; diff --git a/src/packages/stream-backlog/test.js b/src/packages/stream-backlog/test.js new file mode 100644 index 0000000..a4f4a81 --- /dev/null +++ b/src/packages/stream-backlog/test.js @@ -0,0 +1,25 @@ +"use strict"; + +const Promise = require("bluebird"); +const mmStreamBacklog = require("./"); +const pipe = require("@promistream/pipe"); +const simpleSink = require("@promistream/simple-sink"); + +let session = { + homeserver: "https://pixie.town", + accessToken: require("../../../private/access-token") +}; + +return Promise.try(() => { + return pipe([ + mmStreamBacklog(session, "!yjVgJUsMLYLAONsoii:jki.re", { + start: "s14964921_63289774_419108_6402604_769480_404_26267_3427116_36", + end: "s14886247_62932075_392219_6368720_764854_404_25467_3352546_36" // original token used for /sync + }), + simpleSink((event) => console.log("EVENT:", event)) + ]).read(); +}).then(() => { + console.log("Done"); +}); + +// let emitter = mmStreamEvents(session, "s14011802_60514432_167714_6077759_745604_385_21833_2919406_36"); diff --git a/src/packages/stream-events/index.js b/src/packages/stream-events/index.js new file mode 100644 index 0000000..4e0b00b --- /dev/null +++ b/src/packages/stream-events/index.js @@ -0,0 +1,152 @@ +"use strict"; + +const Promise = require("bluebird"); +const concatArrays = require("concat-arrays"); +const flatten = require("flatten"); + +const mmParseSyncResponse = require("../parse-sync-response"); +const mmAxios = require("@modular-matrix/axios"); +const mmStreamBacklog = require("../stream-backlog"); +const isSession = require("@modular-matrix/is-session"); +const sortEventComparator = require("../sort-event-comparator"); +const mapEvents = require("../map-events"); + +const pipe = require("@promistream/pipe"); +const simpleSource = require("@promistream/simple-source"); +const buffer = require("@promistream/buffer"); +const collect = require("@promistream/collect"); + +const { validateArguments } = require("@validatem/core"); +const isString = require("@validatem/is-string"); +const isInteger = require("@validatem/is-integer"); +const isFunction = require("@validatem/is-function"); +const optionalObject = require("../optional-object"); +const defaultTo = require("@validatem/default-to"); + +/* pstreams implementation: +track last sync token in state, on every call make a new sync request +buffer stream + +suggest using a time-based batching stream for batch handling +*/ + +// FIXME: Can we deduplicate between stream-events and stream-backlog? Is this a useful thing to do in the first place? +// FIXME: Accept a decryption implementation as an argument, to allow decryption to occur transparently, so that only the underlying plaintext events (or decryption errors or their recoveries) actually end up in the event stream + +/* TODO: Add limited/lazy mode +- for each limited room, return a 'paginator' object; this object internally stores the start and end of the pagination, and so represents a finite set of items +- when invoked, the paginator should produce a pstreams stream, which should be aborted when the desired amount of items has been obtained (and will then internally stop making requests for them) + +different settings: +- limited/lazy mode for initial sync +- limited/lazy mode for subsequent syncs + +*/ + +function generateLimitFilter(limit) { + return { + room: { + timeline: { limit: limit } + } + }; +} + +function combineEvents(arrays) { + return concatArrays(... arrays).sort(sortEventComparator); +} + +module.exports = function streamEvents(_session, _initialSyncToken, _options) { + let [ session, initialSyncToken, options ] = validateArguments(arguments, { + session: isSession, + initialSyncToken: [ isString ], + options: optionalObject({ + initialLimit: [ isInteger ], + updateLimit: [ isInteger ], + eventMapper: [ isFunction ], + timeout: [ defaultTo(10000), isInteger ] + }) + }); + + let lastSyncToken = initialSyncToken; + let isInitialSync = true; + + let axios = mmAxios({ session: session }); + + return pipe([ + simpleSource(() => { + let applicableLimit = (isInitialSync) + ? options.initialLimit + : options.updateLimit; + + let filter = (applicableLimit != null) + ? generateLimitFilter(applicableLimit) + : {}; + + if (isInitialSync) { + isInitialSync = false; + } + + return Promise.try(() => { + return axios.get("/client/r0/sync", { + params: { + since: lastSyncToken, + filter: JSON.stringify(filter), + timeout: options.timeout + } + }); + }).then((response) => { + let { events, syncToken, limitedRooms, previousBatchTokens } = mmParseSyncResponse(response.data); + + let limitedRoomMarkers = limitedRooms.map((room) => { + let backlogStart = previousBatchTokens[room]; + let backlogEnd = lastSyncToken; + + return { + type: "limitedRoom", + room: room, + backlogStart: backlogStart, + backlogEnd: backlogEnd, + streamBacklog: function () { + return mmStreamBacklog(session, room, { + start: backlogStart, + end: backlogEnd, + // chunkSize: applicableLimit // FIXME: Make configurable separately? + chunkSize: 200, + eventMapper: options.eventMapper + }); + } + }; + }); + + lastSyncToken = syncToken; + + let syncTokenEvent = { + type: "syncToken", + token: syncToken + }; + + let mappedEvents = mapEvents(events, options.eventMapper); + + if (limitedRooms.length > 0 && applicableLimit == null) { + return Promise.map(limitedRoomMarkers, (marker) => { + return pipe([ + marker.streamBacklog(), + collect() + ]).read(); + }).then((backlogs) => { + return combineEvents([ + flatten(backlogs), + mappedEvents + ]).concat([ syncTokenEvent ]); + }); + } else { + return combineEvents([ + limitedRoomMarkers, + mappedEvents + ]).concat([ syncTokenEvent ]); + } + }); + }), + buffer() + ]); +}; diff --git a/src/packages/stream-events/test.js b/src/packages/stream-events/test.js new file mode 100644 index 0000000..43fd365 --- /dev/null +++ b/src/packages/stream-events/test.js @@ -0,0 +1,29 @@ +"use strict"; + +const Promise = require("bluebird"); +const mmStreamEvents = require("./"); +const pipe = require("@promistream/pipe"); +const simpleSink = require("@promistream/simple-sink"); +const filter = require("@promistream/filter"); + +const mapEvent = require("../map-event"); + +let session = { + homeserver: "https://pixie.town", + accessToken: require("../../../private/access-token") +}; + +// let since = "s14011802_60514432_167714_6077759_745604_385_21833_2919406_36"; +// let since = "s14886247_62932075_392219_6368720_764854_404_25467_3352546_36"; +// let since = "s15392233_64465400_3129_6507625_778028_404_26822_3668302_36"; +let since = "s15508519_65086739_41448_6548141_783165_404_27201_3802507_36"; + +return Promise.try(() => { + return pipe([ + mmStreamEvents(session, since, { initialLimit: 50, eventMapper: mapEvent }), + filter((event) => event.type === "roomTimelineEvent"), + simpleSink((event) => console.log(event)) + ]).read(); +}).then(() => { + console.log("Done"); +}); diff --git a/src/packages/strip-html-reply-fallback/index.js b/src/packages/strip-html-reply-fallback/index.js new file mode 100644 index 0000000..7654efd --- /dev/null +++ b/src/packages/strip-html-reply-fallback/index.js @@ -0,0 +1,7 @@ +"use strict"; + +let mxReplyRegex = /]*)?>[\s\S]*<\/mx-reply\s*>/u; + +module.exports = function stripHTMLReplyFallback(html) { + return html.replace(mxReplyRegex, ""); +}; diff --git a/src/packages/strip-plaintext-reply-fallback/index.js b/src/packages/strip-plaintext-reply-fallback/index.js new file mode 100644 index 0000000..3b11fa4 --- /dev/null +++ b/src/packages/strip-plaintext-reply-fallback/index.js @@ -0,0 +1,20 @@ +"use strict"; + +module.exports = function stripPlaintextReplyFallback(plaintext) { + let lines = plaintext.split("\n"); + let firstNonReplyLine = lines.findIndex((line) => !line.startsWith("> ")); + + if (firstNonReplyLine !== -1) { + let nonReplyLines = lines.slice(firstNonReplyLine); + let firstNonEmptyLine = nonReplyLines.findIndex((line) => line.length > 0); + + if (firstNonEmptyLine !== -1) { + let nonEmptyLines = nonReplyLines.slice(firstNonEmptyLine); + return nonEmptyLines.join("\n"); + } else { + return ""; + } + } else { + return ""; + } +}; diff --git a/src/packages/thumbnail-image-browser/index.js b/src/packages/thumbnail-image-browser/index.js new file mode 100644 index 0000000..97240ac --- /dev/null +++ b/src/packages/thumbnail-image-browser/index.js @@ -0,0 +1,30 @@ +"use strict"; + +const Promise = require("bluebird"); +const loadImageFile = require("../load-image-file"); +const elementToThumbnail = require("../element-to-thumbnail"); + +const { validateOptions } = require("@validatem/core"); +const required = require("@validatem/required"); +const isInteger = require("@validatem/is-integer"); +const isString = require("@validatem/is-string"); + +module.exports = function thumbnailImageBrowser(_options) { + let { file, width, height, mimetype } = validateOptions(arguments, { + file: [ required ], // FIXME + maximumWidth: [ required, isInteger ], // FIXME: positive + maximumHeight: [ required, isInteger ], // FIXME: positive + mimetype: [ required, isString ] + }); + + return Promise.try(() => { + return loadImageFile(file); + }).then((element) => { + return elementToThumbnail({ + element: element, + maximumWidth: width, + maximumHeight: height, + mimetype: mimetype + }); + }); +}; diff --git a/src/packages/thumbnail-image/index.js b/src/packages/thumbnail-image/index.js new file mode 100644 index 0000000..233809a --- /dev/null +++ b/src/packages/thumbnail-image/index.js @@ -0,0 +1,27 @@ +"use strict"; + +const thumbnailImageBrowser = require("../thumbnail-image-browser"); +const getThumbnail = require("../get-thumbnail"); + +module.exports = function thumbnailImage({ session, mxc, maximumWidth, maximumHeight, file, mimetype }) { + // TODO: localOnly option for encrypted images + if (typeof window !== "undefined" && window.HTMLImageElement != null) { + // Browser environments + return thumbnailImageBrowser({ + file: file, + mimetype: mimetype, + maximumWidth: maximumWidth, + maximumHeight: maximumHeight, + }); + } else { + // Server environments + return getThumbnail({ + session: session, + url: mxc, + method: "fit", + minimumWidth: maximumWidth, + minimumHeight: maximumHeight, + stream: false + }); + } +}; diff --git a/src/packages/thumbnail-video-browser/index.js b/src/packages/thumbnail-video-browser/index.js new file mode 100644 index 0000000..6e2fd30 --- /dev/null +++ b/src/packages/thumbnail-video-browser/index.js @@ -0,0 +1,30 @@ +"use strict"; + +const Promise = require("bluebird"); +const loadVideoFile = require("../load-video-file"); +const elementToThumbnail = require("../element-to-thumbnail"); + +const { validateOptions } = require("@validatem/core"); +const required = require("@validatem/required"); +const isInteger = require("@validatem/is-integer"); +const isString = require("@validatem/is-string"); + +module.exports = function thumbnailVideoBrowser(_options) { + let { file, width, height, mimetype } = validateOptions(arguments, { + file: [ required ], // FIXME + maximumWidth: [ required, isInteger ], // FIXME: positive + maximumHeight: [ required, isInteger ], // FIXME: positive + mimetype: [ required, isString ] + }); + + return Promise.try(() => { + return loadVideoFile(file); + }).then((element) => { + return elementToThumbnail({ + element: element, + maximumWidth: width, + maximumHeight: height, + mimetype: mimetype + }); + }); +}; diff --git a/src/packages/track-user-profiles/index.js b/src/packages/track-user-profiles/index.js new file mode 100644 index 0000000..c6c31ea --- /dev/null +++ b/src/packages/track-user-profiles/index.js @@ -0,0 +1,90 @@ +"use strict"; + +const defaultValue = require("default-value"); +const asExpression = require("as-expression"); +const pipe = require("@promistream/pipe"); +const map = require("@promistream/map"); +const sequentialize = require("@promistream/sequentialize"); + +// FIXME: Accept initial state +module.exports = function createUserProfileTrackingStream() { + let currentUserProfiles = new Map( /* userID -> profileData */ ); + + function getUserProfileData(userID) { + return defaultValue(currentUserProfiles.get(userID), {}); + // return currentUserProfiles.get(userID); + } + + function setUserProfileData(userID, newProfileData) { + let currentProfileData = getUserProfileData(userID); + + currentUserProfiles.set(userID, { + ... currentProfileData, + ... newProfileData + }); + } + + // FIXME: This linear approach does not work for backlog retrieval! + + return { + stream: function () { + return pipe([ + map((item) => { + // FIXME: Track per individual room, always sort roomStateUpdate before roomTimelineEvent + if (item.event != null && ["roomTimelineEvent", "roomStateUpdate"].includes(item.type)) { + let sender = item.event.sender; + let knownProfileData = getUserProfileData(sender); + + // This is hacky, but needed to deal with the edgecase where a user's first event in the response is a profile change; in that case, the server won't have sent a roomStateUpdate for this user's previous membership, and so we need to backfill from the prev_content. We can't use the mapped events here because avatar and display name changes arrive separately, and so we can't fully process the former until the latter has been handled, which is of course impossible. + let profileData = asExpression(() => { + let isProfileEvent = ["userChangedAvatar", "userChangedDisplayName"].includes(item.event.type); + let hasPreviousState = (item.protocolEvent.unsigned.prev_content != null); + + if (isProfileEvent && hasPreviousState) { + return { + avatarURL: defaultValue(knownProfileData.avatarURL, item.protocolEvent.unsigned.prev_content.avatar_url), + displayName: defaultValue(knownProfileData.displayName, item.protocolEvent.unsigned.prev_content.displayname), + }; + } else { + return knownProfileData; + } + }); + + // We only attach profile data to roomTimelineEvents, since roomStateUpdates are conceptually unordered and should not be displayed anyway. + let newItem = (item.type === "roomTimelineEvent") + ? { + ... item, + event: { + ... item.event, + userProfile: profileData + } + } + : item; + + // NOTE: We update the tracked profile data *after* generating the modified item, because at the time of a profile-data-change event, the old profile data still applies. The new profile data only conceptually starts applying *after* that event. + if (item.event.type === "userChangedAvatar") { + setUserProfileData(sender, { avatarURL: item.event.url }); + } else if (item.event.type === "userChangedDisplayName") { + setUserProfileData(sender, { displayName: item.event.name }); + + // console.log(`Display name change for ${(newItem.event.userProfile || {}).displayName}: ${newItem.event.previousName} -> ${newItem.event.name}`); + } + + // MARKER: f0x -> f0x displayname change // "$15948192607987cjWRn:pixie.town_0" // "!XTcFGXYoSwwJPindtt:feneas.org" + if (item.event.sender.includes("f0x")) { + // console.log("f0x", newItem); + } + + return newItem; + } else { + return item; + } + }), + sequentialize() + ]); + }, + getCurrentState: function () { + return currentUserProfiles; + } + }; +}; diff --git a/src/packages/universal-image-metadata/index.js b/src/packages/universal-image-metadata/index.js new file mode 100644 index 0000000..33e5c79 --- /dev/null +++ b/src/packages/universal-image-metadata/index.js @@ -0,0 +1,72 @@ +"use strict"; + +const Promise = require("bluebird"); +const matchValue = require("match-value"); +const imageSize = require("image-size"); +const fs = require("fs"); +const getFilesize = require("../get-filesize"); + +const imageSizeAsync = Promise.promisify(imageSize); + +function typeToMimetype(type) { + return matchValue(type, { + psd: "image/vnd.adobe.photoshop", + bmp: "image/bmp", + dds: "image/vnd-ms.dds", + gif: "image/gif", + tiff: "image/tiff", + webp: "image/webp", + icns: "image/icns", + png: "image/png", + jpg: "image/jpeg", + cur: "image/x-icon", + ico: "image/x-icon", + j2c: "image/jp2", + jp2: "image/jp2", + ktx: "image/ktx", + svg: "image/svg+xml" + }); +} + +function analyzeBuffer(buffer) { + return imageSize(buffer); +} + +function analyzeBlob(blob) { + return Promise.try(() => { + return blob.arrayBuffer(); + }).then((buffer) => { + return analyzeBuffer(buffer); + }); +} + +function analyzeStream(stream) { + return imageSizeAsync(stream.path); +} + +function getImageData(image) { + if (typeof Blob !== "undefined" && image instanceof Blob) { + return analyzeBlob(image); + } else if (Buffer.isBuffer(image)) { + return analyzeBuffer(image); + } else if (image._readableState != null && image.path != null) { + return analyzeStream(image); + } else { + throw new Error(`Invalid file passed`); // FIXME: Validate + } +} + +module.exports = function getImageMetadata(image) { + // image: buffer, arraybuffer, blob/file, fs stream + return Promise.all([ + getFilesize(image), + getImageData(image) + ]).then(([ filesize, { width, height, type } ]) => { + return { + width: width, + height: height, + filesize: filesize, + mimetype: typeToMimetype(type) + }; + }); +}; diff --git a/src/packages/unmap-image-info/index.js b/src/packages/unmap-image-info/index.js new file mode 100644 index 0000000..80e5128 --- /dev/null +++ b/src/packages/unmap-image-info/index.js @@ -0,0 +1,10 @@ +"use strict"; + +module.exports = function unmapImageInfo(options) { + return { + w: options.displayWidth, + h: options.displayHeight, + mimetype: options.mimetype, + size: options.filesize + }; +}; diff --git a/src/packages/unmap-image-options/index.js b/src/packages/unmap-image-options/index.js new file mode 100644 index 0000000..64153ed --- /dev/null +++ b/src/packages/unmap-image-options/index.js @@ -0,0 +1,17 @@ +"use strict"; + +const unmapImageInfo = require("../unmap-image-info"); + +module.exports = function unmapImageOptions(options) { + return { + body: options.description, + url: options.image.url, + info: { + ... unmapImageInfo(options.image), + thumbnail_url: options.thumbnail.url, + thumbnail_info: (options.thumbnail.url != null) + ? unmapImageInfo(options.thumbnail) + : undefined + } + }; +}; diff --git a/src/packages/upload-file/index.js b/src/packages/upload-file/index.js new file mode 100644 index 0000000..0ead8be --- /dev/null +++ b/src/packages/upload-file/index.js @@ -0,0 +1,48 @@ +"use strict"; + +const Promise = require("bluebird"); +const { chain } = require("error-chain"); + +const mmAxios = require("@modular-matrix/axios"); +const matchProtocolError = require("@modular-matrix/match-protocol-error"); +const errors = require("@modular-matrix/errors"); +const getUploadSizeLimit = require("../get-upload-size-limit"); +const getFilesize = require("../get-filesize"); + +const { validateArguments } = require("@validatem/core"); +const required = require("@validatem/required"); +const isString = require("@validatem/is-string"); +const isSession = require("@modular-matrix/is-session"); + +module.exports = function uploadFile(_session, _options) { + let [ session, options ] = validateArguments(arguments, { + session: [ required, isSession ], + options: [ required, { + filename: [ isString ], + file: [ required ], // FIXME: Stream or blob or buffer or string + }] + }); + + let axios = mmAxios({ session }); + + return Promise.all([ + getUploadSizeLimit(session), + getFilesize(options.file) + ]).then(([ sizeLimit, filesize ]) => { + if (sizeLimit == null || filesize <= sizeLimit.limit) { + return axios.post("/media/r0/upload", options.file, { + params: options.filename != null + ? { filename: options.filename } + : undefined + }); + } else { + throw new errors.FilesizeLimitExceeded("The file will exceed the upload filesize limit for this server", { limit: sizeLimit }); + } + }).then((response) => { + return { url: response.data.content_uri }; + }).catch(matchProtocolError(403, "M_FORBIDDEN"), (error) => { + throw chain(error, errors.AccessDenied, "The uploaded file was rejected"); + }).catch(matchProtocolError(413, "M_TOO_LARGE"), (error) => { + throw chain(error, errors.FilesizeLimitExceeded, "The uploaded file exceeds the filesize limit for this server", { limit: undefined }); + }); +}; diff --git a/src/packages/upload-file/test.js b/src/packages/upload-file/test.js new file mode 100644 index 0000000..902b7fc --- /dev/null +++ b/src/packages/upload-file/test.js @@ -0,0 +1,20 @@ +"use strict"; + +const Promise = require("bluebird"); +const fs = require("fs"); +const path = require("path"); +const createSession = require("@modular-matrix/create-session"); +const uploadFile = require("./"); + +return Promise.try(() => { + return createSession("https://pixie.town/", { accessToken: require("../../../private/access-token") }); +}).then((session) => { + return Promise.try(() => { + return uploadFile(session, { + filename: "index.js", + file: fs.createReadStream(path.join(__dirname, "index.js")) + }); + }).then((result) => { + console.log(result); + }); +}); diff --git a/src/packages/validate-filter/index.js b/src/packages/validate-filter/index.js new file mode 100644 index 0000000..faaec50 --- /dev/null +++ b/src/packages/validate-filter/index.js @@ -0,0 +1,168 @@ +"use strict"; + +const url = require("url"); +const net = require("net"); +const asExpression = require("as-expression"); +const { validateValue, arrayOf, isString, isBoolean, oneOf, ValidationError, either } = require("validatem"); +const splitLimit = require("split-limit"); + +// FIXME: a 'wrap error' utility, for providing the cause in nested validation chains (eg. server name failed because port number failed because not numeric integer)? or just rely on chained errors for this? need some sort of 'context' feature/wrapper for describing 'virtual' properties of eg. parsed strings, so that we can say `someObject -> roomID -> [serverName] -> [portNumber]: Must be a valid port number` +// FIXME: When validating parsed strings, we're duplicating a lot of code between parsing and validation... need to find a model to remove this duplication, while still integrating into validatem well. Maybe using PEG.js? + +// MARKER: virtual properties in validatem, parser combinator design for validatem? + +function isNumericInteger(value) { + isString(value); + + if (value.match(/[^0-9]/)) { + throw new ValidationError(`Must be a numeric integer`); + } +} + +function isPortNumber(value) { + isNumericInteger(value); + + if (parseInt(value) > 65535) { + throw new ValidationError(`Must be a valid port number`); + } +} + +function isIPv4Address(value) { + isString(value); + + if(!net.isIPv4(value)) { + throw new ValidationError(`Must be a valid IPv4 address`); + } +} + +function isIPv6Address(value) { + isString(value); + + if(!net.isIPv6(value)) { + throw new ValidationError(`Must be a valid IPv6 address`); + } +} + +function isIPAddress(value) { + isString(value); + + if(net.isIP(value) === 0) { + throw new ValidationError(`Must be a valid IPv4 or IPv6 address`); + } +} + +function isDNSName(value) { + isString(value); + + // TODO: Fix to use a more accurate regex (or library) for DNS names + let dnsNameRegex = /^[0-9a-z.-]{1,255}$/i; + + if (!value.match(dnsNameRegex)) { + throw new ValidationError(`Must be a valid DNS name`); + } +} + +function isHost(value) { + either(isIPv4Address, isIPv6Address, isDNSName); +} + +function isServerName(value) { + isString(value); + + let split = value.split(":"); + + if (split.length > 2) { + throw new ValidationError(`Must be a valid server name`); + } else { + let [ host, port ] = split; + + if (port != null) { + // NOTE: We are actually a bit stricter than the spec here, and don't just require a 5-digit number; it also needs to be a valid port number (ie. <= 65535) + isPortNumber(port); + } + + isHost(host); + } +} + +function isNotEmptyString(value) { + isString(value); + + if (value.length === 0) { + throw new ValidationError(`Must not be empty`); + } +} + +let isSigil = oneOf([ "@", "!", "$", "+", "#" ]); + +function isIdentifier(value) { + isString(value); + + if (value.length > 255) { + // https://github.com/matrix-org/matrix-doc/issues/1190 + throw new ValidationError(`May not be longer than 255 characters`); + } else { + let sigil = value[0]; + // FIXME: parsing context + isSigil(sigil); + + let [ localPart, domain ] = splitLimit(value.slice(1), ":", 2); + + // FIXME: Parsing context + isNotEmptyString(localPart); + isServerName(domain); + } +} + +function parseIdentifier(identifier) { + // https://matrix.org/docs/spec/appendices#common-identifier-format + // FIXME: Does needing to use validateValue mean that exported validation functions are *not* composable into custom validators, because validateValue is sitting inbetween, and presumably the validation function would not have behaved as expected *without* validateValue? + validateValue(identifier, isIdentifier); + + let sigil = identifier[0]; + let string = identifier.slice(1); + + let [ localPart, domain ] = splitLimit(string, ":", 2); + + return { + localPart: localPart, + domain: domain, + type: matchValue(sigil, { + "@": "user", + "!": "room", + "$": "event", + "+": "group", + "#": "roomAlias" + }) + }; +} + +function isRoomID(value) { + isString(value); + isIdentifier(value); + + +} + +// MARKER: Rewrite this all with the new version of validatem, and separate it out into packages (validatem and modular-matrix) + +let eventFilter = { + limit: isNumber, // FIXME: is positive number + types: arrayOf(isString), // FIXME: Do validation rules exist for types? + not_types: arrayOf(isString), + senders: arrayOf(isUserID), + not_senders: arrayOf(isUserID) +}; + +let stateFilter = { + ... eventFilter, + rooms: arrayOf(isRoomID), + not_rooms: arrayOf(isRoomID), + lazy_load_members: isBoolean, + include_redundant_members: isBoolean, + contains_url: oneOf([ true, false, null ]) +}; + +module.exports = function validateFilter(filter) { + +}; diff --git a/src/packages/without-keys/index.js b/src/packages/without-keys/index.js new file mode 100644 index 0000000..f994b25 --- /dev/null +++ b/src/packages/without-keys/index.js @@ -0,0 +1,15 @@ +"use strict"; + +module.exports = function withoutKeys(object, keys) { + let omitKeys = new Set(keys); + let newObject = {}; + + // TODO: Include Symbol keys as well? + for (let key of Object.keys(object)) { + if (!omitKeys.has(key)) { + newObject[key] = object[key]; + } + } + + return newObject; +}; diff --git a/src/test-events2.js b/src/test-events2.js new file mode 100644 index 0000000..0252da4 --- /dev/null +++ b/src/test-events2.js @@ -0,0 +1,11 @@ +"use strict"; + +const util = require("util"); +const parseSyncResponse = require("./packages/parse-sync-response"); + +try { + console.log(util.inspect(parseSyncResponse(require("../private/test4.json")), { colors: true, depth: null })); +} catch (error) { + console.log(error.message); + +} diff --git a/test-parse.js b/test-parse.js new file mode 100644 index 0000000..cb3fb96 --- /dev/null +++ b/test-parse.js @@ -0,0 +1,65 @@ +"use strict"; + +const { regexp: regexp_, anyCharOf, letter, digit } = require("parjs"); +const { many, must, map, or, manySepBy, then, each } = require("parjs/combinators"); + +let first = map((items) => items[0]); + +// TODO: +// label combinator, for error labeling +// named regexes +// named 'must' predicates + +function sequence(items) { + let mappedItems = items.map((item) => { + if (typeof item === "string") { + return string(item); + } else if (item instanceof RegExp) { + return regexp(item); + } else { + return item; + } + }); +} + +function regexp(regex) { + // The `.call` is a workaround for https://github.com/GregRos/parjs/pull/30 + return regexp_.call({}, regex); +} + +let ipv4Digit = regexp(/[0-9]{1,3}/).pipe(first); + +let ipv6Address = regexp(/[a-f0-9:.]{2,45}/i).pipe( + first +); + +// FIXME: Find a way to compose `manySepBy` and `exactly` +let ipv4Address = ipv4Digit.pipe( + manySepBy("."), + must((results) => results.length === 4), + map((octets) => octets.join(".")) +); + +let ipAddress = ipv4Address.pipe( + or(ipv6Address) +); + +// let ipv4Address = regexp.call(); + +// console.log(ipAddress.parse(process.argv[2])); + +function combine([ previous, current ]) { + return previous.concat([ current ]); +} + +let test = letter().pipe( + then(digit()), + then(letter()), + combine, + // then(combine), + then(digit()), + then(combine), + then(letter()) +); + +console.log(test.parse(process.argv[2])); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..7781108 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2963 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/compat-data@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.5.tgz#d38425e67ea96b1480a3f50404d1bf85676301a6" + integrity sha512-mPVoWNzIpYJHbWje0if7Ck36bpbtTvIxOi9+6WSK9wjGEXearAqlwBoTQvVjsAY2VIwgcs8V940geY3okzRCEw== + dependencies: + browserslist "^4.12.0" + invariant "^2.2.4" + semver "^5.5.0" + +"@babel/core@^7.8.6": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.5.tgz#1f15e2cca8ad9a1d78a38ddba612f5e7cdbbd330" + integrity sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.10.5" + "@babel/helper-module-transforms" "^7.10.5" + "@babel/helpers" "^7.10.4" + "@babel/parser" "^7.10.5" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.5" + "@babel/types" "^7.10.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.5.tgz#1b903554bc8c583ee8d25f1e8969732e6b829a69" + integrity sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig== + dependencies: + "@babel/types" "^7.10.5" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" + integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" + integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-compilation-targets@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz#804ae8e3f04376607cc791b9d47d540276332bd2" + integrity sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ== + dependencies: + "@babel/compat-data" "^7.10.4" + browserslist "^4.12.0" + invariant "^2.2.4" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/helper-create-class-features-plugin@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz#9f61446ba80e8240b0a5c85c6fdac8459d6f259d" + integrity sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-member-expression-to-functions" "^7.10.5" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + +"@babel/helper-create-regexp-features-plugin@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" + integrity sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-regex" "^7.10.4" + regexpu-core "^4.7.0" + +"@babel/helper-define-map@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz#b53c10db78a640800152692b13393147acb9bb30" + integrity sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/types" "^7.10.5" + lodash "^4.17.19" + +"@babel/helper-explode-assignable-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz#40a1cd917bff1288f699a94a75b37a1a2dbd8c7c" + integrity sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A== + dependencies: + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" + integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== + dependencies: + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-get-function-arity@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-hoist-variables@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e" + integrity sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-member-expression-to-functions@^7.10.4", "@babel/helper-member-expression-to-functions@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz#172f56e7a63e78112f3a04055f24365af702e7ee" + integrity sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA== + dependencies: + "@babel/types" "^7.10.5" + +"@babel/helper-module-imports@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" + integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-module-transforms@^7.10.4", "@babel/helper-module-transforms@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz#120c271c0b3353673fcdfd8c053db3c544a260d6" + integrity sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.5" + lodash "^4.17.19" + +"@babel/helper-optimise-call-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" + integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" + integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== + +"@babel/helper-regex@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0" + integrity sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg== + dependencies: + lodash "^4.17.19" + +"@babel/helper-remap-async-to-generator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz#fce8bea4e9690bbe923056ded21e54b4e8b68ed5" + integrity sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-wrap-function" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-replace-supers@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf" + integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.10.4" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-simple-access@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" + integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw== + dependencies: + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-split-export-declaration@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz#2c70576eaa3b5609b24cb99db2888cc3fc4251d1" + integrity sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + +"@babel/helper-wrap-function@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87" + integrity sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helpers@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044" + integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== + dependencies: + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/node@^7.8.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.10.5.tgz#30866322aa2c0251a9bdd73d07a9167bd1f4ed64" + integrity sha512-suosS7zZ2roj+fYVCnDuVezUbRc0sdoyF0Gj/1FzWxD4ebbGiBGtL5qyqHH4NO34B5m4vWWYWgyNhSsrqS8vwA== + dependencies: + "@babel/register" "^7.10.5" + commander "^4.0.1" + core-js "^3.2.1" + lodash "^4.17.19" + node-environment-flags "^1.0.5" + regenerator-runtime "^0.13.4" + resolve "^1.13.1" + v8flags "^3.1.1" + +"@babel/parser@^7.10.4", "@babel/parser@^7.10.5", "@babel/parser@^7.7.0": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b" + integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ== + +"@babel/plugin-proposal-async-generator-functions@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz#3491cabf2f7c179ab820606cec27fed15e0e8558" + integrity sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-remap-async-to-generator" "^7.10.4" + "@babel/plugin-syntax-async-generators" "^7.8.0" + +"@babel/plugin-proposal-class-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807" + integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-proposal-dynamic-import@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz#ba57a26cb98b37741e9d5bca1b8b0ddf8291f17e" + integrity sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + +"@babel/plugin-proposal-json-strings@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz#593e59c63528160233bd321b1aebe0820c2341db" + integrity sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.0" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a" + integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + +"@babel/plugin-proposal-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz#ce1590ff0a65ad12970a609d78855e9a4c1aef06" + integrity sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-proposal-object-rest-spread@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz#50129ac216b9a6a55b3853fdd923e74bf553a4c0" + integrity sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.10.4" + +"@babel/plugin-proposal-optional-catch-binding@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz#31c938309d24a78a49d68fdabffaa863758554dd" + integrity sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + +"@babel/plugin-proposal-optional-chaining@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz#750f1255e930a1f82d8cdde45031f81a0d0adff7" + integrity sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + +"@babel/plugin-proposal-private-methods@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz#b160d972b8fdba5c7d111a145fc8c421fc2a6909" + integrity sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-proposal-unicode-property-regex@^7.10.4", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz#4483cda53041ce3413b7fe2f00022665ddfaa75d" + integrity sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-async-generators@^7.8.0": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c" + integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-dynamic-import@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-json-strings@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz#4bbeb8917b54fcf768364e0a81f560e33a3ef57d" + integrity sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-arrow-functions@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz#e22960d77e697c74f41c501d44d73dbf8a6a64cd" + integrity sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-async-to-generator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz#41a5017e49eb6f3cda9392a51eef29405b245a37" + integrity sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-remap-async-to-generator" "^7.10.4" + +"@babel/plugin-transform-block-scoped-functions@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz#1afa595744f75e43a91af73b0d998ecfe4ebc2e8" + integrity sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-block-scoping@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.5.tgz#b81b8aafefbfe68f0f65f7ef397b9ece68a6037d" + integrity sha512-6Ycw3hjpQti0qssQcA6AMSFDHeNJ++R6dIMnpRqUjFeBBTmTDPa8zgF90OVfTvAo11mXZTlVUViY1g8ffrURLg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-classes@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz#405136af2b3e218bc4a1926228bc917ab1a0adc7" + integrity sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-define-map" "^7.10.4" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz#9ded83a816e82ded28d52d4b4ecbdd810cdfc0eb" + integrity sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-destructuring@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz#70ddd2b3d1bea83d01509e9bb25ddb3a74fc85e5" + integrity sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-dotall-regex@^7.10.4", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz#469c2062105c1eb6a040eaf4fac4b488078395ee" + integrity sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-duplicate-keys@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz#697e50c9fee14380fe843d1f306b295617431e47" + integrity sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-exponentiation-operator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz#5ae338c57f8cf4001bdb35607ae66b92d665af2e" + integrity sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-for-of@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz#c08892e8819d3a5db29031b115af511dbbfebae9" + integrity sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz#6a467880e0fc9638514ba369111811ddbe2644b7" + integrity sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz#9f42ba0841100a135f22712d0e391c462f571f3c" + integrity sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-member-expression-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz#b1ec44fcf195afcb8db2c62cd8e551c881baf8b7" + integrity sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-modules-amd@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz#1b9cddaf05d9e88b3aad339cb3e445c4f020a9b1" + integrity sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw== + dependencies: + "@babel/helper-module-transforms" "^7.10.5" + "@babel/helper-plugin-utils" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-commonjs@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz#66667c3eeda1ebf7896d41f1f16b17105a2fbca0" + integrity sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w== + dependencies: + "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-systemjs@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz#6270099c854066681bae9e05f87e1b9cadbe8c85" + integrity sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw== + dependencies: + "@babel/helper-hoist-variables" "^7.10.4" + "@babel/helper-module-transforms" "^7.10.5" + "@babel/helper-plugin-utils" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-umd@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz#9a8481fe81b824654b3a0b65da3df89f3d21839e" + integrity sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA== + dependencies: + "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz#78b4d978810b6f3bcf03f9e318f2fc0ed41aecb6" + integrity sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + +"@babel/plugin-transform-new-target@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz#9097d753cb7b024cb7381a3b2e52e9513a9c6888" + integrity sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-object-super@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz#d7146c4d139433e7a6526f888c667e314a093894" + integrity sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + +"@babel/plugin-transform-parameters@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz#59d339d58d0b1950435f4043e74e2510005e2c4a" + integrity sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw== + dependencies: + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-property-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz#f6fe54b6590352298785b83edd815d214c42e3c0" + integrity sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-regenerator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63" + integrity sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw== + dependencies: + regenerator-transform "^0.14.2" + +"@babel/plugin-transform-reserved-words@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz#8f2682bcdcef9ed327e1b0861585d7013f8a54dd" + integrity sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-shorthand-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6" + integrity sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-spread@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz#4e2c85ea0d6abaee1b24dcfbbae426fe8d674cff" + integrity sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-sticky-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz#8f3889ee8657581130a29d9cc91d7c73b7c4a28d" + integrity sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-regex" "^7.10.4" + +"@babel/plugin-transform-template-literals@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz#78bc5d626a6642db3312d9d0f001f5e7639fde8c" + integrity sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-typeof-symbol@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz#9509f1a7eec31c4edbffe137c16cc33ff0bc5bfc" + integrity sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-unicode-escapes@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz#feae523391c7651ddac115dae0a9d06857892007" + integrity sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-unicode-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz#e56d71f9282fac6db09c82742055576d5e6d80a8" + integrity sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/preset-env@^7.8.6": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.4.tgz#fbf57f9a803afd97f4f32e4f798bb62e4b2bef5f" + integrity sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw== + dependencies: + "@babel/compat-data" "^7.10.4" + "@babel/helper-compilation-targets" "^7.10.4" + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-proposal-async-generator-functions" "^7.10.4" + "@babel/plugin-proposal-class-properties" "^7.10.4" + "@babel/plugin-proposal-dynamic-import" "^7.10.4" + "@babel/plugin-proposal-json-strings" "^7.10.4" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" + "@babel/plugin-proposal-numeric-separator" "^7.10.4" + "@babel/plugin-proposal-object-rest-spread" "^7.10.4" + "@babel/plugin-proposal-optional-catch-binding" "^7.10.4" + "@babel/plugin-proposal-optional-chaining" "^7.10.4" + "@babel/plugin-proposal-private-methods" "^7.10.4" + "@babel/plugin-proposal-unicode-property-regex" "^7.10.4" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-class-properties" "^7.10.4" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.10.4" + "@babel/plugin-transform-arrow-functions" "^7.10.4" + "@babel/plugin-transform-async-to-generator" "^7.10.4" + "@babel/plugin-transform-block-scoped-functions" "^7.10.4" + "@babel/plugin-transform-block-scoping" "^7.10.4" + "@babel/plugin-transform-classes" "^7.10.4" + "@babel/plugin-transform-computed-properties" "^7.10.4" + "@babel/plugin-transform-destructuring" "^7.10.4" + "@babel/plugin-transform-dotall-regex" "^7.10.4" + "@babel/plugin-transform-duplicate-keys" "^7.10.4" + "@babel/plugin-transform-exponentiation-operator" "^7.10.4" + "@babel/plugin-transform-for-of" "^7.10.4" + "@babel/plugin-transform-function-name" "^7.10.4" + "@babel/plugin-transform-literals" "^7.10.4" + "@babel/plugin-transform-member-expression-literals" "^7.10.4" + "@babel/plugin-transform-modules-amd" "^7.10.4" + "@babel/plugin-transform-modules-commonjs" "^7.10.4" + "@babel/plugin-transform-modules-systemjs" "^7.10.4" + "@babel/plugin-transform-modules-umd" "^7.10.4" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4" + "@babel/plugin-transform-new-target" "^7.10.4" + "@babel/plugin-transform-object-super" "^7.10.4" + "@babel/plugin-transform-parameters" "^7.10.4" + "@babel/plugin-transform-property-literals" "^7.10.4" + "@babel/plugin-transform-regenerator" "^7.10.4" + "@babel/plugin-transform-reserved-words" "^7.10.4" + "@babel/plugin-transform-shorthand-properties" "^7.10.4" + "@babel/plugin-transform-spread" "^7.10.4" + "@babel/plugin-transform-sticky-regex" "^7.10.4" + "@babel/plugin-transform-template-literals" "^7.10.4" + "@babel/plugin-transform-typeof-symbol" "^7.10.4" + "@babel/plugin-transform-unicode-escapes" "^7.10.4" + "@babel/plugin-transform-unicode-regex" "^7.10.4" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.10.4" + browserslist "^4.12.0" + core-js-compat "^3.6.2" + invariant "^2.2.2" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/preset-modules@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" + integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/register@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.10.5.tgz#354f3574895f1307f79efe37a51525e52fd38d89" + integrity sha512-eYHdLv43nyvmPn9bfNfrcC4+iYNwdQ8Pxk1MFJuU/U5LpSYl/PH4dFMazCYZDFVi8ueG3shvO+AQfLrxpYulQw== + dependencies: + find-cache-dir "^2.0.0" + lodash "^4.17.19" + make-dir "^2.1.0" + pirates "^4.0.0" + source-map-support "^0.5.16" + +"@babel/runtime@^7.8.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c" + integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" + integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/traverse@^7.10.4", "@babel/traverse@^7.10.5", "@babel/traverse@^7.7.0": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.5.tgz#77ce464f5b258be265af618d8fddf0536f20b564" + integrity sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.10.5" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/parser" "^7.10.5" + "@babel/types" "^7.10.5" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + +"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.4.4", "@babel/types@^7.7.0": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.5.tgz#d88ae7e2fde86bfbfe851d4d81afa70a997b5d15" + integrity sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + +"@joepie91/eslint-config@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@joepie91/eslint-config/-/eslint-config-1.1.0.tgz#9397e6ce0a010cb57dcf8aef8754d3a5ce0ae36a" + integrity sha512-XliasRSUfOz1/bAvTBaUlCjWDbceCW4y1DnvFfW7Yw9p2FbNRR0w8WoPdTxTCjKuoZ7/OQMeBxIe2y9Qy6rbYw== + +"@joepie91/unreachable@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@joepie91/unreachable/-/unreachable-1.0.0.tgz#8032bb8a5813e81bbbe516cb3031d60818526687" + integrity sha512-vZRJ5UDq4mqP1vgSrcOLD3aIfS/nzwsvGFOOHv5sj5fa1Ss0dT1xnIzrXKLD9pu5EcUvF3K6n6jdaMW8uXpNEQ== + +"@modular-matrix/is-homeserver-url@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@modular-matrix/is-homeserver-url/-/is-homeserver-url-0.1.0.tgz#e97c0625dd2c2d9a373fb2ee9675f820f0573f48" + integrity sha512-nndxH6KmSdV5ziripQvHnNY+2Xz0E6Gx/AVEha7XccvZO8rqE+jEja9onheNrHVrEJJE8v9wzU952P65KsLNBg== + dependencies: + "@validatem/error" "^1.1.0" + "@validatem/ignore-result" "^0.1.1" + "@validatem/is-url" "^0.2.0" + "@validatem/wrap-error" "^0.2.0" + +"@modular-matrix/is-mxc-url@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@modular-matrix/is-mxc-url/-/is-mxc-url-0.1.0.tgz#cee18f8ff5bd2933e17e12e79ad5f3b70d6af975" + integrity sha512-+zavcZLCR2pywyJF8d8rEh3WSRosmUgeEje9Thr8ETt4YV+sg/EVnS/Pf85XfERqJBd8yHsr4iI9iOA/umvjdw== + dependencies: + "@validatem/error" "^1.1.0" + "@validatem/ignore-result" "^0.1.1" + "@validatem/is-url" "^0.1.1" + "@validatem/wrap-error" "^0.2.0" + +"@modular-matrix/parse-mxc@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@modular-matrix/parse-mxc/-/parse-mxc-1.0.1.tgz#73df45a4b45c2657535dc0c7302b98044b17c5d1" + integrity sha512-KnX+WjB79qmzWvz5KlmtPT5bNYUayCR6y2uN/sMCM7POXH4aOywMSlTTeKXvmRfqot0Qo8pI4UzHNddYEtfPwQ== + dependencies: + create-error "^0.3.1" + +"@promistream/aborted@^0.1.1": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@promistream/aborted/-/aborted-0.1.2.tgz#49a5c57fa346db14b7cb84e000d473e0852138c6" + integrity sha512-rLQgZTFr0r7yWtDbqA8zT5F4TKDiOiyowlAxSKiSY4XM+XVYeNq7k9SXIxVhLczjKh1Cv0nlvvZ7cZ41UjZPwQ== + dependencies: + default-value "^1.0.0" + error-chain "^0.1.0" + +"@promistream/buffer@^0.1.0": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@promistream/buffer/-/buffer-0.1.2.tgz#73c63476aa5cfeb111298b791a33d8008384721b" + integrity sha512-EquWW7HTpjngNkMxHhxww0rvODWAaEN715BXQWP9zXkm+CXdhYmadeol7G2kMPTTYuYjKFmiNUdjjfm/z2puyg== + dependencies: + "@joepie91/unreachable" "^1.0.0" + "@promistream/propagate-abort" "^0.1.2" + bluebird "^3.5.4" + +"@promistream/collect@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@promistream/collect/-/collect-0.1.1.tgz#085360a66c5cab7616833542009212be34a447ff" + integrity sha512-zXnp8SFm2dFsvJBASLfYMUxfaNkvzyhU56WT1iAXxlN5w2Rb0vArP2pIXvpbiSVrWkUObNeZ8t715nGxqsWEow== + dependencies: + "@promistream/simple-sink" "^0.1.0" + +"@promistream/end-of-stream@^0.1.0", "@promistream/end-of-stream@^0.1.1": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@promistream/end-of-stream/-/end-of-stream-0.1.2.tgz#45820c8d29353c480c0219920db95ba075396438" + integrity sha512-rOeAIkcVZW6oYox2Jc1z/00iLVx0w0cIlcD/TbR798Qg5M5/nhErtjSG08QAtuaPSxAFKNl5ipAD8HHGV5esJw== + dependencies: + default-value "^1.0.0" + error-chain "^0.1.0" + +"@promistream/filter@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@promistream/filter/-/filter-0.1.1.tgz#126f2d581c801d0e8e30e1392700384f00da94be" + integrity sha512-rE4mTC8I7FiwH5HZTcqzfwHTRXAAShBuNoLMhhWcKPvqPIjAbEoqGg8klEs4fNDl8LXrd0Txm7PeBQ8cgZYwmg== + dependencies: + "@promistream/propagate-abort" "^0.1.3" + "@promistream/propagate-peek" "^0.1.0" + "@validatem/core" "^0.3.12" + "@validatem/is-function" "^0.1.0" + "@validatem/required" "^0.1.1" + bluebird "^3.7.2" + +"@promistream/is-aborted@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@promistream/is-aborted/-/is-aborted-0.1.1.tgz#58d714dbd5f20bf851c77339c2213128ac50f0bf" + integrity sha512-2AYo+MFu0wNKXCEDHexaFWoESiUzHfGZgWpazbdA6OyU/AJsHRfMwKzE7awmgi1u0T43k5nLwwJXIiTypajSiw== + +"@promistream/is-end-of-stream@^0.1.0", "@promistream/is-end-of-stream@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@promistream/is-end-of-stream/-/is-end-of-stream-0.1.1.tgz#7f84e630c9e49a92739df6a8c574eff99dd4c09d" + integrity sha512-GZn7W0wrUen7kkgWCcwFFgr0g/ftfuddnuK/Tp0MLWCCJA4hyAboglCZP0JzEJdi34gClEP8lCfDwGekw18LHg== + +"@promistream/map@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@promistream/map/-/map-0.1.1.tgz#2f771372e5d1dd12f41b6efd57874014d406f123" + integrity sha512-ggyNqWlvNXVY9Gf/pLUgbHROK8mEqu46hbpJftmN9etPr724YPhL+vxA7+9b6bBmTLAU1Tw4Th3BWG5EHVBn1g== + dependencies: + "@promistream/propagate-abort" "^0.1.2" + "@promistream/propagate-peek" "^0.1.0" + "@validatem/core" "^0.3.12" + "@validatem/is-function" "^0.1.0" + "@validatem/required" "^0.1.1" + bluebird "^3.5.4" + +"@promistream/pipe@^0.1.0": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@promistream/pipe/-/pipe-0.1.5.tgz#c802048afafd9aed8c3395419e17f277b83cb90e" + integrity sha512-xIdka0MOaCRy2RA5nxOoMPxXKDnzhGvd6f1WgXifLMqT5gPi2cIjdbrrS9Mn6yMrN+tPLIOFNSl1OHipIyd1QQ== + dependencies: + "@validatem/allow-extra-properties" "^0.1.0" + "@validatem/anything" "^0.1.0" + "@validatem/array-of" "^0.1.2" + "@validatem/core" "^0.3.15" + "@validatem/error" "^1.1.0" + "@validatem/remove-nullish-items" "^0.1.0" + "@validatem/required" "^0.1.1" + "@validatem/wrap-error" "^0.3.0" + +"@promistream/propagate-abort@^0.1.2", "@promistream/propagate-abort@^0.1.3", "@promistream/propagate-abort@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@promistream/propagate-abort/-/propagate-abort-0.1.6.tgz#dfc3c78c2e22662b9e5d548afce2180c40584ef5" + integrity sha512-Ap4eDFiIcLb4yuJdin2tQM1+2ZJZm78sYWkKVdqECJY0UGkwNsbaMMeYyfZpFRpJGmW8mCCuOkWs0fQl5H9DGA== + +"@promistream/propagate-peek@^0.1.0", "@promistream/propagate-peek@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@promistream/propagate-peek/-/propagate-peek-0.1.1.tgz#c7dd69efcd894c408d7a3e9713b6a9036f70a501" + integrity sha512-4xfkSmtPQzlvL4+KCquPHX7sPXiAACGJac/y7fB3Sv6ZKXAT/cjTfms1nEjlDGn1nroN0MzReBza2HnpF59deg== + +"@promistream/sequentialize@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@promistream/sequentialize/-/sequentialize-0.1.0.tgz#8cab499c2518ee856fcb1e13943859ca5b77ba71" + integrity sha512-lm7wJmlOSmBvHq49zLfs3cghOt9kcRhLezCbuhXQUXhhiaKLCvYuyA1AGId0kiJDPX2SggrU3Ojb+TOcxPEAqw== + dependencies: + "@joepie91/unreachable" "^1.0.0" + "@promistream/propagate-abort" "^0.1.2" + bluebird "^3.5.4" + p-defer "^3.0.0" + +"@promistream/simple-sink@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@promistream/simple-sink/-/simple-sink-0.1.1.tgz#e3808179102ffe4bc10d70d681f19c649e1f3811" + integrity sha512-M6dQMUIPjFTRB+xIwBMqIrPghWORoreGoNAl2F/5oshBlX6+X2F+RAeUuz6plDymwq2eoVL5pvSUR4zYdMdRjQ== + dependencies: + "@promistream/is-aborted" "^0.1.1" + "@promistream/is-end-of-stream" "^0.1.1" + "@promistream/propagate-abort" "^0.1.6" + "@promistream/propagate-peek" "^0.1.1" + "@validatem/core" "^0.3.11" + "@validatem/default-to" "^0.1.0" + "@validatem/is-function" "^0.1.0" + "@validatem/required" "^0.1.1" + "@validatem/wrap-value-as-option" "^0.1.0" + bluebird "^3.5.4" + +"@promistream/simple-source@^0.1.1": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@promistream/simple-source/-/simple-source-0.1.4.tgz#a0029040660cd577e51eede1978b49b04b1e5ed0" + integrity sha512-dMAVpcX2WC40IVMA6zvSAcgwxXjDVj4QIQzOyDdXgOnKjCRnGPhtUvK2ST5Jiw8/lVpEYx0bviqgezttU3IaFg== + dependencies: + "@joepie91/unreachable" "^1.0.0" + "@promistream/aborted" "^0.1.1" + "@promistream/end-of-stream" "^0.1.1" + "@promistream/is-end-of-stream" "^0.1.0" + "@validatem/core" "^0.3.12" + "@validatem/is-function" "^0.1.0" + "@validatem/required" "^0.1.1" + "@validatem/wrap-value-as-option" "^0.1.0" + bluebird "^3.7.2" + error-chain "^0.1.0" + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@validatem/allow-extra-properties@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/allow-extra-properties/-/allow-extra-properties-0.1.0.tgz#e8c434818d6fd74b8cb237cfaa4d548295de13c1" + integrity sha512-9jihpYxw1vp4FdjnbN0bTVZMLYv//9OjFNTsVLG5OV4xHESwtgkgQEF5/N5rY1iBwoH/pcKuRl44MBZ8eMdrKw== + dependencies: + "@validatem/with-context" "^0.1.0" + +"@validatem/annotate-errors@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@validatem/annotate-errors/-/annotate-errors-0.1.2.tgz#fa9152bb30f4f42b69496b527e38f0c31ff605a9" + integrity sha512-EuX7pzdYI/YpTmZcgdPG481Oi3elAg8JWh/LYXuE1h6MaZk3A8eP5DD33/l7EoKzrysn6y8nCsqNa1ngei562w== + dependencies: + "@validatem/match-validation-error" "^0.1.0" + +"@validatem/any-property@^0.1.0", "@validatem/any-property@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@validatem/any-property/-/any-property-0.1.3.tgz#fc7768c1922a8bacff9369ae48913672e5350f52" + integrity sha512-jYWxif5ff9pccu7566LIQ/4+snlApXEJUimBywzAriBgS3r4eDBbz3oZFHuiPmhxNK/NNof5YUS+L6Sk3zaMfg== + dependencies: + "@validatem/annotate-errors" "^0.1.2" + "@validatem/combinator" "^0.1.0" + "@validatem/error" "^1.0.0" + "@validatem/validation-result" "^0.1.1" + "@validatem/virtual-property" "^0.1.0" + default-value "^1.0.0" + +"@validatem/anything@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/anything/-/anything-0.1.0.tgz#09b57720476b9f7ab072c3e5d0a3d4234b721435" + integrity sha512-VJcygPpLw2fAhh29m2qL1AybHY7Ewl7xpvVgNIZpqUwMsSZXWSmzmbZhqE4Sr6Wy2n6FbZVzVoUFREO589SPcQ== + +"@validatem/array-of@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@validatem/array-of/-/array-of-0.1.2.tgz#59c09879fb41c583e45b210e7f7c78fd7f86ac33" + integrity sha512-3YjrZOxxlburFfRdJyPWbNoAA7a72E3/2nPCyVGTE8lekQy9NZSyrPjntMozwE14rsnGGLWFLCgNWKu73cyhxQ== + dependencies: + "@validatem/annotate-errors" "^0.1.2" + "@validatem/combinator" "^0.1.0" + "@validatem/is-array" "^0.1.0" + "@validatem/validation-result" "^0.1.1" + +"@validatem/combinator@^0.1.0", "@validatem/combinator@^0.1.1", "@validatem/combinator@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@validatem/combinator/-/combinator-0.1.2.tgz#eab893d55f1643b9c6857eaf6ff7ed2a728e89ff" + integrity sha512-vE8t1tNXknmN62FlN6LxQmA2c6TwVKZ+fl/Wit3H2unFdOhu7SZj2kRPGjAXdK/ARh/3svYfUBeD75pea0j1Sw== + +"@validatem/core@^0.3.10", "@validatem/core@^0.3.15": + version "0.3.15" + resolved "https://registry.yarnpkg.com/@validatem/core/-/core-0.3.15.tgz#645a0734dbc6efa3a5c39c62c5f2d8fa773f89f3" + integrity sha512-4nBLGzgpPrPsZ5DDXDXwL5p+GUEvpAFt6I3/YUHoah+ckYmKNh9qwmWKkFZHxJVdRrTewGFRj0FPw5fqje1yxA== + dependencies: + "@validatem/annotate-errors" "^0.1.2" + "@validatem/any-property" "^0.1.0" + "@validatem/error" "^1.0.0" + "@validatem/match-validation-error" "^0.1.0" + "@validatem/match-versioned-special" "^0.1.0" + "@validatem/match-virtual-property" "^0.1.0" + "@validatem/normalize-rules" "^0.1.0" + "@validatem/required" "^0.1.0" + "@validatem/validation-result" "^0.1.1" + "@validatem/virtual-property" "^0.1.0" + as-expression "^1.0.0" + assure-array "^1.0.0" + create-error "^0.3.1" + default-value "^1.0.0" + execall "^2.0.0" + flatten "^1.0.3" + indent-string "^4.0.0" + is-arguments "^1.0.4" + supports-color "^7.1.0" + syncpipe "^1.0.0" + +"@validatem/core@^0.3.11", "@validatem/core@^0.3.12": + version "0.3.12" + resolved "https://registry.yarnpkg.com/@validatem/core/-/core-0.3.12.tgz#e4e8a566850571bf55412862e88a3b06e75c8072" + integrity sha512-ngrFk6PT/pPZntpleG6q55SByongNxRk7wJhUiCihyv4yqIqqG+bNGH4wb6yW33IHefreWxkkJ53yM1Yj9srNA== + dependencies: + "@validatem/annotate-errors" "^0.1.2" + "@validatem/any-property" "^0.1.0" + "@validatem/error" "^1.0.0" + "@validatem/match-validation-error" "^0.1.0" + "@validatem/match-versioned-special" "^0.1.0" + "@validatem/match-virtual-property" "^0.1.0" + "@validatem/normalize-rules" "^0.1.0" + "@validatem/required" "^0.1.0" + "@validatem/validation-result" "^0.1.1" + "@validatem/virtual-property" "^0.1.0" + as-expression "^1.0.0" + assure-array "^1.0.0" + create-error "^0.3.1" + default-value "^1.0.0" + execall "^2.0.0" + flatten "^1.0.3" + indent-string "^4.0.0" + is-arguments "^1.0.4" + supports-color "^7.1.0" + syncpipe "^1.0.0" + +"@validatem/default-to@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/default-to/-/default-to-0.1.0.tgz#62766a3ca24d2f61a96c713bcb629a5b3c6427c5" + integrity sha512-UE/mJ6ZcHFlBLUhX75PQHDRYf80GFFhB+vZfIcsEWduh7Nm6lTMDnCPj4MI+jd9E/A7HV5D1yCZhaRSwoWo4vg== + dependencies: + is-callable "^1.1.5" + +"@validatem/dynamic@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@validatem/dynamic/-/dynamic-0.1.2.tgz#70e4b238631328874f03ffa1f062f41b71512230" + integrity sha512-TNZMUO9McL2kFYdLWTYSD+zxxZ9fbK9Si+3X5u/JngOWAq7PFxbU7o2oxREkwiSIZi5cjBCK/hvrZMWyl+FWEA== + dependencies: + "@validatem/combinator" "^0.1.1" + +"@validatem/either@^0.1.9": + version "0.1.9" + resolved "https://registry.yarnpkg.com/@validatem/either/-/either-0.1.9.tgz#0d753ef8fe04486d2b7122de3dd3ac51b3acaacf" + integrity sha512-cUqlRjy02qDcZ166/D6duk8lrtqrHynHuSakU0TvMGMBiLzjWpMJ+3beAWHe+kILB5/dlXVyc68ZIjSNhBi8Kw== + dependencies: + "@validatem/combinator" "^0.1.1" + "@validatem/error" "^1.0.0" + "@validatem/match-validation-error" "^0.1.0" + "@validatem/validation-result" "^0.1.2" + flatten "^1.0.3" + +"@validatem/error@^1.0.0", "@validatem/error@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@validatem/error/-/error-1.1.0.tgz#bef46e7066c39761b494ebe3eec2ecdc7348f4ed" + integrity sha512-gZJEoZq1COi/8/5v0fVKQ9uX54x5lb5HbV7mzIOhY6dqjmLNfxdQmpECZPQrCAOpcRkRMJ7zaFhq4UTslpY9yA== + +"@validatem/forbidden@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/forbidden/-/forbidden-0.1.0.tgz#f96a5ac38e322a411eb74c9be1458f80e30348a0" + integrity sha512-5GpgXt33z15oXZJwd+BKzEcX56YrU1Ysqe3NM20L9OzuSCJYbWU6xR6mMHkYVfF3TDLfkC8csOiKG2UnduCLhw== + dependencies: + "@validatem/error" "^1.0.0" + +"@validatem/has-shape@^0.1.0": + version "0.1.8" + resolved "https://registry.yarnpkg.com/@validatem/has-shape/-/has-shape-0.1.8.tgz#dff0f0449c12b96d150091b7a980154d810ae63d" + integrity sha512-x2i8toW1uraFF2Vl6WBl4CScbBeg5alrtoCKMyXbJkHf2B5QxL/ftUh2RQRcBzx6U0i7KUb8vdShcWAa+fehRQ== + dependencies: + "@validatem/annotate-errors" "^0.1.2" + "@validatem/combinator" "^0.1.0" + "@validatem/error" "^1.0.0" + "@validatem/validation-result" "^0.1.1" + array-union "^2.1.0" + as-expression "^1.0.0" + assure-array "^1.0.0" + default-value "^1.0.0" + flatten "^1.0.3" + +"@validatem/ignore-result@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@validatem/ignore-result/-/ignore-result-0.1.1.tgz#e999b66ebc4bdfbc6c6228b6116fceb6c14aabaa" + integrity sha512-Wt0sF1fIuQG21yzXVdS7cXUz5m7wAGw5JbvyPorrISriDuTv6C18Zepb5zPhCVgupp3+KZm8M7wkeJh3XxPnIw== + dependencies: + "@validatem/combinator" "^0.1.2" + "@validatem/validation-result" "^0.1.2" + "@validatem/with-context" "^0.1.2" + +"@validatem/is-array@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@validatem/is-array/-/is-array-0.1.1.tgz#fbe15ca8c97c30b622a5bbeb536d341e99cfc2c5" + integrity sha512-XD3C+Nqfpnbb4oO//Ufodzvui7SsCIW/stxZ39dP/fyRsBHrdERinkFATH5HepegtDlWMQswm5m1XFRbQiP2oQ== + dependencies: + "@validatem/error" "^1.0.0" + +"@validatem/is-boolean@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@validatem/is-boolean/-/is-boolean-0.1.1.tgz#b7fafd4143ab6d23bca597c86d8c4e0ba6f6cacf" + integrity sha512-eIFq+mCBEDgAp4ezaPn1mbVZd2H+IkQG3CcEFnLSlqfg1XKY5uv8AOI08+UqeWS+C7AIFk3rEqRg63+OuPCpsg== + dependencies: + "@validatem/error" "^1.0.0" + is-boolean-object "^1.0.1" + +"@validatem/is-function@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/is-function/-/is-function-0.1.0.tgz#15a2e95259dc5e32256e8c21872455661437d069" + integrity sha512-UtVrwTGhaIdIJ0mPG5XkAmYZUeWgRoMP1G9ZEHbKvAZJ4+SXf/prC0jPgE0pw+sPjdQG4hblsXSfo/9Bf3PGdQ== + dependencies: + "@validatem/error" "^1.0.0" + is-callable "^1.1.5" + +"@validatem/is-integer@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/is-integer/-/is-integer-0.1.0.tgz#52c544063aaeabb630854e1822298f5c043196a0" + integrity sha512-sSp66uxfirIFMqro64DAdfM+UKo+IICmHdy/x3ZJXUM9F4byz/GyFmhR4wfcQswywwF1fqKw9458GE38fozjOQ== + dependencies: + "@validatem/error" "^1.0.0" + "@validatem/is-number" "^0.1.2" + +"@validatem/is-non-empty-string@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/is-non-empty-string/-/is-non-empty-string-0.1.0.tgz#40cfe2d47fc196b069c8e01ce4e25a9fd60003f4" + integrity sha512-U6TmKLH6LfhQKldRE4xNZi8ovNrodSXgUiiEY9C4UZ8334CdhOXs5lxIyOqMRvM++Ex5NmpQIAFDOYGCr4RtoQ== + dependencies: + "@validatem/error" "^1.0.0" + "@validatem/is-string" "^0.1.1" + +"@validatem/is-number@^0.1.2", "@validatem/is-number@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@validatem/is-number/-/is-number-0.1.3.tgz#0f8ce8c72970dbedbbd04d12942e5ab48a44cda6" + integrity sha512-GjnbKYfYa0cTCJmsr5OUbylxTKHHZ6FDtJixWl+lEuXzeELDoYRp2UAjzfjTXJ9g2BumESqI/t0hap5rw5tEyQ== + dependencies: + "@validatem/error" "^1.0.0" + is-number-object "^1.0.4" + +"@validatem/is-plain-object@^0.1.0", "@validatem/is-plain-object@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@validatem/is-plain-object/-/is-plain-object-0.1.1.tgz#b7a3ef8ef960882c7c41e84ed709fa0bfb932e93" + integrity sha512-aNGbNIbKRpYI0lRBczlTBbiA+nqN52ADAASdySKg2/QeSCVtYS4uOIeCNIJRAgXe/5sUnLTuL4pgq628uAl7Kw== + dependencies: + "@validatem/error" "^1.0.0" + is-plain-obj "^2.1.0" + +"@validatem/is-string@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@validatem/is-string/-/is-string-0.1.1.tgz#0710d8cebedd4d6861b4a8c63d7803ed6d2f9d6c" + integrity sha512-iyRVYRPgRt2ZlWyc7pzN1WkO6apzE8at39XQa4WUr8qRPfJn12V4khS9MumWbZs8N2qqajrxMigB2LJUCKOCRg== + dependencies: + "@validatem/error" "^1.0.0" + is-string "^1.0.5" + +"@validatem/is-url@^0.1.0", "@validatem/is-url@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@validatem/is-url/-/is-url-0.1.1.tgz#80e4794da8bbcd776f3aae6edaf7c98df1539c91" + integrity sha512-PGgt70ZiAdFOB7r2Vb2n2/aZ7gCexLs1FZar9vfc/lTWEiNrRBelA7hDL+HbUQquRM8h3SGIOK0aPoTbMmgOgQ== + dependencies: + "@validatem/error" "^1.0.0" + "@validatem/is-string" "^0.1.1" + +"@validatem/is-url@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@validatem/is-url/-/is-url-0.2.0.tgz#b51f5de0c3fde91a68fc90b6b69c34e9bdbd82a2" + integrity sha512-ZzP3e6a/m8yKTLIqKF3MRAGqcPxx0UhOCdItbLHlyJZZiJUalCBfBBSzVq0mnV9jXuTaE90kVRzP3qByFXkmBw== + dependencies: + "@validatem/error" "^1.0.0" + "@validatem/is-string" "^0.1.1" + +"@validatem/match-special@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/match-special/-/match-special-0.1.0.tgz#4e0c28f1aee5bf53c1ef30bbf8c755d4946ae0ff" + integrity sha512-TFiq9Wk/1Hoja4PK85WwNYnwBXk3+Lgoj59ZIMxm2an1qmNYp8j+BnSvkKBflba451yIn6V1laU9NJf+/NYZgw== + +"@validatem/match-validation-error@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/match-validation-error/-/match-validation-error-0.1.0.tgz#fa87f5f1836e7c1d9bf6b75b2addf0a5b21e4c1e" + integrity sha512-6akGTk7DdulOreyqDiGdikwRSixQz/AlvARSX18dcWaTFc79KxCLouL2hyoFcor9IIUhu5RTY4/i756y4T1yxA== + dependencies: + "@validatem/match-versioned-special" "^0.1.0" + +"@validatem/match-versioned-special@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/match-versioned-special/-/match-versioned-special-0.1.0.tgz#2eacc48debecdbbe7e3d02f0c0a665afaea9bedf" + integrity sha512-xoOTY0bdA2ELj+ntcDVJ8YyMEFIJpjZ4HNPL9lGcbnRFwJBhQcHUAhUpZwkMxu02zH9wkNM1FvYGHxPz40745Q== + +"@validatem/match-virtual-property@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/match-virtual-property/-/match-virtual-property-0.1.0.tgz#4de2de1075987b5f3b356d3f2bcf6c0be5b5fb83" + integrity sha512-ssd3coFgwbLuqvZftLZTy3eHN0TFST8oTS2XTViQdXJPXVoJmwEKBpFhXgwnb5Ly1CE037R/KWpjhd1TP/56kQ== + +"@validatem/matches-format@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/matches-format/-/matches-format-0.1.0.tgz#cb4ac6144c9769a6db3a0b36637b090b49f0142b" + integrity sha512-V3w6ajCNUx4qEsib5G+Bl1zGwXFm0COosg4dtz6lHr9m8mkP4CajzHZES6eSSojOlSrKvP/OAG3hzv77d1OTEQ== + dependencies: + "@validatem/error" "^1.0.0" + is-regex "^1.0.5" + +"@validatem/normalize-rules@^0.1.0": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@validatem/normalize-rules/-/normalize-rules-0.1.3.tgz#59fd6193b1091ff97b5c723b32c9bb1fe2a9dc9c" + integrity sha512-HHPceAP2ce9NWymIZrgLCTzpdwXNRBCCB5H6ZPc5ggOrbmh4STpT83fLazleHtvYNlqgXZ4GjQOvCwrjaM+qEA== + dependencies: + "@validatem/has-shape" "^0.1.0" + "@validatem/is-plain-object" "^0.1.0" + "@validatem/match-special" "^0.1.0" + assure-array "^1.0.0" + default-value "^1.0.0" + flatten "^1.0.3" + is-plain-obj "^2.1.0" + +"@validatem/one-of@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@validatem/one-of/-/one-of-0.1.1.tgz#df40f6d2780021b8557b640b99c7b217bda10b95" + integrity sha512-lIgxnkNRouPx5Ydddi8OaAxmzp1ox44OJnrJPRrJkU4ccz9Yb7GSJ+wQJNVkAZCar+DGTDMoXoy51NwDnsf4sw== + dependencies: + "@validatem/error" "^1.0.0" + +"@validatem/remove-nullish-items@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/remove-nullish-items/-/remove-nullish-items-0.1.0.tgz#fe1a8b64d11276b506fae2bd2c41da4985a5b5ff" + integrity sha512-cs4YSF47TA/gHnV5muSUUqGi5PwybP5ztu5SYnPKxQVTyubvcbrFat51nOvJ2PmUasyrIccoYMmATiviXkTi6g== + +"@validatem/require-either@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/require-either/-/require-either-0.1.0.tgz#250e35ab06f124ea90f3925d74b5f53a083923b0" + integrity sha512-UyZtJieT3aJhO9tj1OJp47V9jpHCE7RSohue9jg3FyDGwmIBVYXCfASeM19mWg9W0lp6IevsqTmaGQhqQOQYJg== + dependencies: + "@validatem/allow-extra-properties" "^0.1.0" + "@validatem/either" "^0.1.9" + "@validatem/forbidden" "^0.1.0" + "@validatem/required" "^0.1.1" + assure-array "^1.0.0" + flatten "^1.0.3" + +"@validatem/required@^0.1.0", "@validatem/required@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@validatem/required/-/required-0.1.1.tgz#64f4a87333fc5955511634036b7f8948ed269170" + integrity sha512-vI4NzLfay4RFAzp7xyU34PHb8sAo6w/3frrNh1EY9Xjnw2zxjY5oaxwmbFP1jVevBE6QQEnKogtzUHz/Zuvh6g== + +"@validatem/validation-result@^0.1.1", "@validatem/validation-result@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@validatem/validation-result/-/validation-result-0.1.2.tgz#4e75cfd87305fc78f8d05ac84921a2c99a0348e0" + integrity sha512-okmP8JarIwIgfpaVcvZGuQ1yOsLKT3Egt49Ynz6h1MAeGsP/bGHXkkXtbiWOVsk5Tzku5vDVFSrFnF+5IEHKxw== + dependencies: + default-value "^1.0.0" + +"@validatem/virtual-property@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/virtual-property/-/virtual-property-0.1.0.tgz#880540dfd149f98ecf1095d93912e34443381fe4" + integrity sha512-JUUvWtdqoSkOwlsl20oB3qFHYIL05a/TAfdY4AJcs55QeVTiX5iI1b8IoQW644sIWWooBuLv+XwoxjRsQFczlQ== + +"@validatem/with-context@^0.1.0", "@validatem/with-context@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@validatem/with-context/-/with-context-0.1.2.tgz#3645c04897664f70995104961277e07b61b4f615" + integrity sha512-noAWf4CsmU+BCz+KOg3GPq9+R9BQLWOQnOgWVfkYHFdLnnbLhl8w/ONdzvFzUYGHIZGKZwsWVCp+Kwz/tAfMnA== + dependencies: + "@validatem/combinator" "^0.1.1" + +"@validatem/wrap-error@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@validatem/wrap-error/-/wrap-error-0.1.3.tgz#2470d24c17325ad97d852a21be6c0227da908d3c" + integrity sha512-86ANJACPGbH8jD/C/tUTZNgQh9xCePUKq4wf5ZRcwOvtIDaZO98FI9cdoT2/zS1CzQCp3VWlwz16YT6FNjJJJA== + dependencies: + "@validatem/combinator" "^0.1.1" + "@validatem/error" "^1.0.0" + "@validatem/match-validation-error" "^0.1.0" + "@validatem/validation-result" "^0.1.2" + as-expression "^1.0.0" + default-value "^1.0.0" + split-filter-n "^1.1.2" + +"@validatem/wrap-error@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@validatem/wrap-error/-/wrap-error-0.2.0.tgz#ce3c85f847eaafb0e3e99a72a5d210d59d128ac4" + integrity sha512-v7lrXvkvDCrAPveLO4RzkcMo97ocOeq2qx1HxttJs/uWSHipAtyaXgmWoKEEG1nsLx40C+1bkddwKdGYZLJAxA== + dependencies: + "@validatem/combinator" "^0.1.1" + "@validatem/error" "^1.0.0" + "@validatem/match-validation-error" "^0.1.0" + "@validatem/validation-result" "^0.1.2" + as-expression "^1.0.0" + default-value "^1.0.0" + split-filter-n "^1.1.2" + +"@validatem/wrap-error@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@validatem/wrap-error/-/wrap-error-0.3.0.tgz#f8d170e79b6fdd68321d82c60581ad345be7d6b9" + integrity sha512-km5v6F/Xm7j8W/tmCmht2BTzxMLSpBUJ5MdhJD7ABEut/fdO0tNca1u1imTnWCULCJcdDHbNtpSmDMvXFg3E7Q== + dependencies: + "@validatem/combinator" "^0.1.1" + "@validatem/error" "^1.0.0" + "@validatem/match-validation-error" "^0.1.0" + "@validatem/validation-result" "^0.1.2" + as-expression "^1.0.0" + default-value "^1.0.0" + split-filter-n "^1.1.2" + +"@validatem/wrap-value-as-option@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/wrap-value-as-option/-/wrap-value-as-option-0.1.0.tgz#57fa8d535f6cdf40cf8c8846ad45f4dd68f44568" + integrity sha512-gWDkfyU0DOsbinE9iqvRSJ+NxuynChyueJsC+AFm3EYbe8+s7V2gRs3qkJ4mq7hOlUbEh8tgCWQfZZvr+IdVFw== + dependencies: + "@validatem/either" "^0.1.9" + "@validatem/is-plain-object" "^0.1.1" + +acorn-jsx@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" + integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== + +acorn@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" + integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== + +ajv@^6.10.0, ajv@^6.10.2: + version "6.12.3" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706" + integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + dependencies: + type-fest "^0.11.0" + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +as-expression@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/as-expression/-/as-expression-1.0.0.tgz#7bc620ca4cb2fe0ee90d86729bd6add33b8fd831" + integrity sha512-Iqh4GxNUfxbJdGn6b7/XMzc8m1Dz2ZHouBQ9DDTzyMRO3VPPIAXeoY/sucRxxxXKbUtzwzWZSN6jPR3zfpYHHA== + +assure-array@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assure-array/-/assure-array-1.0.0.tgz#4f4ad16a87659d6200a4fb7103462033d216ec1f" + integrity sha1-T0rRaodlnWIApPtxA0YgM9IW7B8= + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +axios@^0.19.0: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + +babel-eslint@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +bluebird@^3.5.4, bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +browserslist@^4.12.0, browserslist@^4.8.5: + version "4.13.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.13.0.tgz#42556cba011e1b0a2775b611cba6a8eca18e940d" + integrity sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ== + dependencies: + caniuse-lite "^1.0.30001093" + electron-to-chromium "^1.3.488" + escalade "^3.0.1" + node-releases "^1.1.58" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +caniuse-lite@^1.0.30001093: + version "1.0.30001104" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001104.tgz#4e3d5b3b1dd3c3529f10cb7f519c62ba3e579f5d" + integrity sha512-pkpCg7dmI/a7WcqM2yfdOiT4Xx5tzyoHAXWsX5/HxZ3TemwDZs0QXdqbE0UPLPVy/7BeK7693YfzfRYfu1YVpg== + +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-info@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/char-info/-/char-info-0.3.2.tgz#d4c4d034245c864d1ab17152cd31746b3bd4f0d0" + integrity sha512-6P6KW8crZx+N857wZalB4FreR+PhheSLmAk22c8zbQsFhsDxZP1aTDfmOjrzddgp1IBOl53b0Z8kDQrwh4B//A== + dependencies: + node-interval-tree "^1.3.3" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +clone-regexp@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f" + integrity sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q== + dependencies: + is-regexp "^2.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +concat-arrays@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/concat-arrays/-/concat-arrays-2.0.0.tgz#268628e3e0639a8f6a60fdb70d47c484f7fc3fd9" + integrity sha512-KqWFprduDocx4A7M8tzz+fvtjX550Q9RnCspro1E7ZFGatdD7HhIiAFo4B4J5kkIJi/rSJcMnAUBrRuxZMzJzw== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +core-js-compat@^3.6.2: + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" + integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng== + dependencies: + browserslist "^4.8.5" + semver "7.0.0" + +core-js@^3.2.1: + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" + integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== + +create-error@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/create-error/-/create-error-0.3.1.tgz#69810245a629e654432bf04377360003a5351a23" + integrity sha1-aYECRaYp5lRDK/BDdzYAA6U1GiM= + +create-event-emitter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/create-event-emitter/-/create-event-emitter-1.0.0.tgz#a1e4aa75ee410dfc09226e032f9ceac23eb93e11" + integrity sha1-oeSqde5BDfwJIm4DL5zqwj65PhE= + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +debug@=3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +debug@^4.0.1, debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +default-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-value/-/default-value-1.0.0.tgz#8c6f52a5a1193fe78fdc9f86eb71d16c9757c83a" + integrity sha1-jG9SpaEZP+eP3J+G63HRbJdXyDo= + dependencies: + es6-promise-try "0.0.1" + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dotty@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/dotty/-/dotty-0.1.0.tgz#da371ccd931a37282f8f7f77adada7d54539708a" + integrity sha512-VJzcXJZEckXowvj6yGJC2JH66DLEEm1d1QOB0hik1EvlbUpULvcYt411JeFuy8rNC96FG8V2N7pMkyjvK8LYwQ== + +electron-to-chromium@^1.3.488: + version "1.3.501" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.501.tgz#faa17a2cb0105ee30d5e1ca87eae7d8e85dd3175" + integrity sha512-tyzuKaV2POw2mtqBBzQGNBojMZzH0MRu8bT8T/50x+hWeucyG/9pkgAATy+PcM2ySNM9+8eG2VllY9c6j4i+bg== + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-chain@^0.1.0: + version "0.1.3" + resolved "https://registry.yarnpkg.com/error-chain/-/error-chain-0.1.3.tgz#5575bdeca295224f6301123bb85b52a79547bdcd" + integrity sha512-Hx/Yd7w6ku+bTIGzPxdgKAoZSADCf4EnM9CEcIyr75vw/FH/wbZ23YnKKv7ZQB80F4s7ZSVJ/9UPXk03SQsACQ== + dependencies: + "@validatem/allow-extra-properties" "^0.1.0" + "@validatem/core" "^0.3.10" + "@validatem/default-to" "^0.1.0" + "@validatem/dynamic" "^0.1.2" + "@validatem/error" "^1.1.0" + "@validatem/forbidden" "^0.1.0" + "@validatem/is-boolean" "^0.1.1" + "@validatem/is-function" "^0.1.0" + "@validatem/is-plain-object" "^0.1.1" + "@validatem/is-string" "^0.1.1" + "@validatem/one-of" "^0.1.1" + "@validatem/required" "^0.1.1" + "@validatem/wrap-error" "^0.1.3" + chalk "^2.4.2" + fromentries "^1.2.0" + is.object "^1.0.0" + syncpipe "^1.0.0" + +es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: + version "1.17.6" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" + integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.0" + is-regex "^1.1.0" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es6-promise-try@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/es6-promise-try/-/es6-promise-try-0.0.1.tgz#10f140dad27459cef949973e5d21a087f7274b20" + integrity sha1-EPFA2tJ0Wc75SZc+XSGgh/cnSyA= + +escalade@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" + integrity sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-plugin-babel@^5.3.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz#75a2413ffbf17e7be57458301c60291f2cfbf560" + integrity sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g== + dependencies: + eslint-rule-composer "^0.3.0" + +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== + +eslint-scope@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" + integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint@^6.2.2: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.3" + eslint-visitor-keys "^1.1.0" + espree "^6.1.2" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.3" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" + integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== + dependencies: + acorn "^7.1.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.1.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + dependencies: + estraverse "^4.1.0" + +estraverse@^4.1.0, estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" + integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execall@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/execall/-/execall-2.0.0.tgz#16a06b5fe5099df7d00be5d9c06eecded1663b45" + integrity sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow== + dependencies: + clone-regexp "^2.1.0" + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +find-cache-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +flatten@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" + integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== + +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + +fromentries@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +glob-parent@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +homedir-polyfill@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + dependencies: + parse-passwd "^1.0.0" + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +image-size@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.8.3.tgz#f0b568857e034f29baffd37013587f2c0cad8b46" + integrity sha512-SMtq1AJ+aqHB45c3FsB4ERK0UCiA2d3H1uq8s+8T0Pf8A3W4teyBQyaFaktH6xvZqh+npwlKU7i4fJo0r7TYTg== + dependencies: + queue "6.0.1" + +import-fresh@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" + integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@^7.0.0: + version "7.3.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.2.tgz#25245d2e32dc9f33dbe26eeaada231daa66e9c7c" + integrity sha512-DF4osh1FM6l0RJc5YWYhSDB6TawiBRlbV9Cox8MWlidU218Tb7fm3lQTULyUJDfJ0tjbzl0W4q651mrCCEM55w== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.16" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +invariant@^2.2.2, invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-arguments@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" + integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== + +is-boolean-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" + integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== + +is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" + integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-negative-zero@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" + integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= + +is-number-object@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" + integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== + +is-plain-obj@^2.0.0, is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-regex@^1.0.5: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== + dependencies: + has-symbols "^1.0.1" + +is-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" + integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== + dependencies: + has-symbols "^1.0.1" + +is-regexp@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d" + integrity sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA== + +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +is.object@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is.object/-/is.object-1.0.0.tgz#e4f4117e9f083b35c8df5cf817ea3efb0452fdfa" + integrity sha1-5PQRfp8IOzXI31z4F+o++wRS/fo= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json5@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== + dependencies: + minimist "^1.2.5" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levenary@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" + integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== + dependencies: + leven "^3.1.0" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.16, lodash@^4.17.19: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +make-dir@^2.0.0, make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-url@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/make-url/-/make-url-0.0.1.tgz#43dc9f7e8f1393982b67e411f754300fe5ae9da0" + integrity sha1-Q9yffo8Tk5grZ+QR91QwD+WunaA= + +match-value@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/match-value/-/match-value-1.1.0.tgz#ad311ef8bbe2d344a53ec3104e28fe221984b98e" + integrity sha512-NOvpobcmkX+l9Eb6r2s3BkR1g1ZwzExDFdXA9d6p1r1O1olLbo88KuzMiBmg43xSpodfm7I6Hqlx2OoySquEgg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mkdirp@^0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-environment-flags@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" + integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== + dependencies: + object.getownpropertydescriptors "^2.0.3" + semver "^5.7.0" + +node-interval-tree@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/node-interval-tree/-/node-interval-tree-1.3.3.tgz#15ffb904cde08270214acace8dc7653e89ae32b7" + integrity sha512-K9vk96HdTK5fEipJwxSvIIqwTqr4e3HRJeJrNxBSeVMNSC/JWARRaX7etOLOuTmrRMeOI/K5TCJu3aWIwZiNTw== + dependencies: + shallowequal "^1.0.2" + +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + +node-releases@^1.1.58: + version "1.1.59" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.59.tgz#4d648330641cec704bff10f8e4fe28e453ab8e8e" + integrity sha512-H3JrdUczbdiwxN5FuJPyCHnGHIFqQ0wWxo+9j1kAXAzqNMAHlo+4I/sYYxpyK0irQ73HgdiyzD32oqQDcU2Osw== + +object-inspect@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.getownpropertydescriptors@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" + integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +p-defer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-3.0.0.tgz#d1dceb4ee9b2b604b1d94ffec83760175d4e6f83" + integrity sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw== + +p-limit@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-try@^2.0.0, p-try@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parjs@^0.12.7: + version "0.12.7" + resolved "https://registry.yarnpkg.com/parjs/-/parjs-0.12.7.tgz#9dde4975dbbb2f48d4b484524cb359d02395fb72" + integrity sha512-aKw+vsSMBdZNdxcetIwi5mnmFckuwxTz7dKu07wEzrNjKYtvhRlrlla3b4GAEmGMyAWhrocLrUcbSGvmMZMU3Q== + dependencies: + char-info "^0.3.1" + lodash "^4.17.13" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pirates@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +queue@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.1.tgz#abd5a5b0376912f070a25729e0b6a7d565683791" + integrity sha512-AJBQabRCCNr9ANq8v77RJEv73DPbn55cdTb+Giq4X0AVnNVZvMHlYp7XlQiN+1npCZj1DuSmaA2hYVUUDgxFDg== + dependencies: + inherits "~2.0.3" + +regenerate-unicode-properties@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" + integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f" + integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A== + +regenerator-runtime@^0.13.4: + version "0.13.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" + integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + +regenerator-transform@^0.14.2: + version "0.14.5" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" + integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== + dependencies: + "@babel/runtime" "^7.8.4" + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +regexpu-core@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" + integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.2.0" + +regjsgen@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" + integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== + +regjsparser@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" + integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== + dependencies: + jsesc "~0.5.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3.2: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +rxjs@^6.6.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.0.tgz#af2901eedf02e3a83ffa7f886240ff9018bbec84" + integrity sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg== + dependencies: + tslib "^1.9.0" + +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.1.2: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +shallowequal@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +source-map-support@^0.5.16: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +split-filter-n@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/split-filter-n/-/split-filter-n-1.1.2.tgz#268be1ec9c4d93dfb27b030c06165ac1b6f70f66" + integrity sha512-+hXSQYpKe1uyXPXI4zQtAJAlaF2EzEc+BaF2goMeNL5oUD5YLqrVcpjxELJxpomXfwMCUaYLAszEbdY9gKVdHQ== + +split-filter@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/split-filter/-/split-filter-1.1.3.tgz#c68cc598783d88f60d16e7b452dacfe95ba60539" + integrity sha512-2xXwhWeJUFrYE8CL+qoy9mCohu5/E+uglvpqL1FVXz1XbvTwivafVC6oTDeg/9ksOAxg6DvyCF44Dvf5crFU0w== + +split-limit@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/split-limit/-/split-limit-1.0.4.tgz#efdca7c042b3289f8304f339fc31e46915ad532d" + integrity sha1-79ynwEKzKJ+DBPM5/DHkaRWtUy0= + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string.prototype.trimend@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trimstart@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-json-comments@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +syncpipe@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/syncpipe/-/syncpipe-1.0.0.tgz#170340f813150bc8fcb8878b1b9c71ea0ccd3727" + integrity sha512-cdiAFTnFJRvUaNPDc2n9CqoFvtIL3+JUMJZrC3kA3FzpugHOqu0TvkgNwmnxPZ5/WjAzMcfMS3xm+AO7rg/j/w== + dependencies: + assure-array "^1.0.0" + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +tslib@^1.9.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" + integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-fest@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" + integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" + integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +url-join@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" + integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== + +v8-compile-cache@^2.0.3: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" + integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== + +v8flags@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656" + integrity sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg== + dependencies: + homedir-polyfill "^1.0.1" + +validatem@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/validatem/-/validatem-0.2.1.tgz#fd85d7b035a6c3cf553e8d9292ff584743e25e98" + integrity sha512-1K/jV6NJe7+9+ukxjhoDVtaAXzqswjUKVw8iGiWJn8QNFYltteprrg+f9Q3cueo6kBNlybeYYiDs/PS14bffrA== + dependencies: + assure-array "^1.0.0" + create-error "^0.3.1" + default-value "^1.0.0" + is-plain-obj "^2.0.0" + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1"