aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYarmo Mackenbach <yarmo@yarmo.eu>2022-04-05 08:33:30 +0200
committerYarmo Mackenbach <yarmo@yarmo.eu>2022-04-05 08:33:30 +0200
commit53bf124fc825b7393ec9abe522d3e0505199a22f (patch)
tree7739f0bbd6297ae110ccdf3117c64e672ef7c029
Initial commit
-rw-r--r--.gitignore3
-rw-r--r--.prettierignore2
-rw-r--r--.prettierrc6
-rw-r--r--Dockerfile12
-rw-r--r--package.json22
-rw-r--r--src/db.js159
-rw-r--r--src/index.js372
-rw-r--r--yarn.lock489
8 files changed, 1065 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3f10206
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+data
+.env \ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..aa15018
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,2 @@
+node_modules
+data \ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..e74ed9f
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "trailingComma": "es5",
+ "tabWidth": 4,
+ "semi": false,
+ "singleQuote": true
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..f38598e
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,12 @@
+FROM node:16-alpine
+
+RUN mkdir /app
+RUN mkdir /data
+WORKDIR /app
+
+COPY ./src /app
+COPY ./package.json /app
+COPY ./yarn.lock /app
+RUN yarn --production --pure-lockfile
+
+CMD yarn start \ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..c7eb170
--- /dev/null
+++ b/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "matrix-shared-expenses-bot",
+ "version": "0.0.1",
+ "description": "Shared expenses bot for Matrix",
+ "main": "./src/index.js",
+ "type": "module",
+ "scripts": {
+ "start": "node ./",
+ "prettier:check": "prettier --check .",
+ "prettier:write": "prettier --write ."
+ },
+ "author": "Yarmo Mackenbach <yarmo@yarmo.eu>",
+ "license": "MIT",
+ "dependencies": {
+ "dotenv": "^16.0.0",
+ "lowdb": "^3.0.0",
+ "matrix-js-sdk": "^16.0.1"
+ },
+ "devDependencies": {
+ "prettier": "^2.6.2"
+ }
+}
diff --git a/src/db.js b/src/db.js
new file mode 100644
index 0000000..f1d41fc
--- /dev/null
+++ b/src/db.js
@@ -0,0 +1,159 @@
+import { join, dirname, resolve } from 'path'
+import { Low, JSONFile } from 'lowdb'
+import { fileURLToPath } from 'url'
+import crypto from 'crypto'
+
+const __dirname = dirname(fileURLToPath(import.meta.url))
+const file = join(__dirname, '../data', 'db.json')
+let adapter
+let db
+
+const init = async () => {
+ adapter = new JSONFile(file)
+ db = new Low(adapter)
+
+ await db.read()
+ db.data ||= { rooms: {} }
+ await db.write()
+}
+
+const read = async () => {
+ await db.read()
+ return db.data
+}
+
+const write = async (obj) => {
+ await db.read()
+ let roomHash
+
+ switch (obj.type) {
+ case 'introduction':
+ case 'expense':
+ roomHash = _getHash(obj.roomId)
+ db.data.rooms[roomHash] ||= { events: [] }
+ db.data.rooms[roomHash].events.push(obj)
+ break
+
+ default:
+ throw new Error('Invalid type for db.write()')
+ break
+ }
+
+ await db.write()
+}
+
+const undo = async (roomId) => {
+ const roomHash = _getHash(roomId)
+ await db.read()
+
+ db.data.rooms[roomHash] ||= { events: [] }
+
+ db.data.rooms[roomHash].events.pop()
+
+ await db.write()
+}
+
+const reset = async (roomId) => {
+ const roomHash = _getHash(roomId)
+ await db.read()
+
+ db.data.rooms[roomHash] ||= { events: [] }
+
+ db.data.rooms[roomHash].events = db.data.rooms[roomHash].events.filter(
+ (e) => {
+ return !(e.type === 'expense' || e.type === 'transaction')
+ }
+ )
+
+ await db.write()
+}
+
+const fullreset = async (roomId) => {
+ const roomHash = _getHash(roomId)
+ await db.read()
+
+ db.data.rooms[roomHash] ||= { events: [] }
+
+ delete db.data.rooms[roomHash]
+
+ await db.write()
+}
+
+const getUsers = async (roomId) => {
+ const roomHash = _getHash(roomId)
+ const data = await read()
+
+ let userIds = []
+
+ data.rooms[roomHash] ||= { events: [] }
+
+ data.rooms[roomHash].events.forEach((event) => {
+ if (event.type === 'introduction') {
+ userIds.push(event.userId)
+ }
+ })
+
+ return userIds
+}
+
+const getEventLog = async (roomId, limit) => {
+ const roomHash = _getHash(roomId)
+ const data = await read()
+
+ data.rooms[roomHash] ||= { events: [] }
+
+ return data.rooms[roomHash].events.slice(-(limit || 4))
+}
+
+const getBalance = async (roomId) => {
+ const roomHash = _getHash(roomId)
+ const data = await read()
+
+ let balance = {
+ userIds: [],
+ }
+
+ data.rooms[roomHash] ||= { events: [] }
+
+ data.rooms[roomHash].events.forEach((event) => {
+ switch (event.type) {
+ case 'introduction':
+ balance.userIds.push(event.userId)
+ balance[event.userId] = {
+ totalSpent: 0,
+ spentForUserId: {},
+ }
+ break
+
+ case 'expense':
+ balance[event.userId].totalSpent += event.data.amount
+ balance.userIds.forEach((userId) => {
+ balance[event.userId].spentForUserId[userId] ||= 0
+ balance[event.userId].spentForUserId[userId] +=
+ event.data.amount / balance.userIds.length
+ })
+ break
+
+ default:
+ break
+ }
+ })
+
+ return balance
+}
+
+const _getHash = (string) => {
+ return crypto.createHash('sha256').update(string, 'utf8').digest('hex')
+}
+
+export default {
+ init: init,
+ read: read,
+ write: write,
+ undo: undo,
+ reset: reset,
+ fullreset: fullreset,
+ getUsers: getUsers,
+ getEventLog: getEventLog,
+ getBalance: getBalance,
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..13296e0
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,372 @@
+import 'dotenv/config'
+import * as sdk from 'matrix-js-sdk'
+import db from './db.js'
+
+// Initialize database
+db.init()
+
+// Prepare Matrix client
+const botUserId = process.env.MATRIX_USERID
+const matrixClient = sdk.createClient({
+ baseUrl: process.env.MATRIX_BASEURL,
+ accessToken: process.env.MATRIX_ACCESS_TOKEN,
+ userId: botUserId,
+})
+
+// Prepare Matrix functions
+const sendMessage = (roomId, message, formattedMessage) => {
+ let content = {
+ body: message,
+ msgtype: 'm.text',
+ }
+
+ if (formattedMessage) {
+ content.format = 'org.matrix.custom.html'
+ content.formatted_body = formattedMessage
+ }
+
+ matrixClient.sendEvent(
+ roomId,
+ 'm.room.message',
+ content,
+ '',
+ (err, res) => {
+ console.log(err)
+ }
+ )
+}
+
+// Automatically join new rooms
+matrixClient.on('RoomMember.membership', function (event, member) {
+ if (member.membership === 'invite' && member.userId === botUserId) {
+ matrixClient.joinRoom(member.roomId).then(function () {
+ console.log('Auto-joined %s', member.roomId)
+ })
+ }
+})
+
+// Listen for new messages
+matrixClient.on('Room.timeline', async (event, room, toStartOfTimeline) => {
+ // Don't print paginated results
+ if (toStartOfTimeline) {
+ return
+ }
+
+ // Ignore non-messages
+ if (event.getType() !== 'm.room.message') {
+ return
+ }
+
+ // Ignore own messages
+ if (event.getSender() === botUserId) {
+ return
+ }
+
+ // Ignore old messages
+ if (event.getAge() && event.getAge() > 10 * 1000) {
+ // in milliseconds
+ return
+ }
+
+ // Get the message
+ const message = `${event.getContent().body}`
+
+ // Log the message
+ console.log('(%s) %s :: %s', room.name, event.getSender(), message)
+
+ // Try and find a command in the message
+ const match = message.match(/^!([a-zA-Z]*)(?: (.*))?/)
+ if (match) {
+ const command = match[1]
+ const args = (match[2] || '').split(' ')
+
+ let users = await db.getUsers(room.roomId)
+
+ switch (command) {
+ case 'h':
+ case 'help':
+ sendMessage(
+ room.roomId,
+ '!hi: introduce yourself to Seb',
+ '<b>!hi</b>: introduce yourself to Seb'
+ )
+ sendMessage(
+ room.roomId,
+ '!e 42: add a new expense',
+ '<b>!e 42</b>: add a new expense'
+ )
+ sendMessage(
+ room.roomId,
+ '!e 42 Saturday market: describe the expense',
+ '<b>!e 42 Saturday market</b>: describe the expense'
+ )
+ sendMessage(
+ room.roomId,
+ '!b: get the balance',
+ '<b>!b</b>: get the balance'
+ )
+ sendMessage(
+ room.roomId,
+ '!u: get the list of users',
+ '<b>!u</b>: get the list of users'
+ )
+ sendMessage(
+ room.roomId,
+ "!l: list the last 4 actions",
+ "<b>!l</b>: list the last 4 actions"
+ )
+ sendMessage(
+ room.roomId,
+ '!l 6: request a specific number of actions',
+ '<b>!l 6</b>: request a specific number of actions'
+ )
+ sendMessage(
+ room.roomId,
+ '!undo: undo the last action',
+ '<b>!undo</b>: undo the last action'
+ )
+ sendMessage(
+ room.roomId,
+ '!reset: remove all expenses and transactions (cannot be undone!)',
+ '<b>!reset</b>: remove all expenses and transactions (cannot be undone!)'
+ )
+ sendMessage(
+ room.roomId,
+ '!fullreset: remove all data (cannot be undone!)',
+ '<b>!fullreset</b>: remove all data (cannot be undone!)'
+ )
+ break
+
+ case 'hi':
+ if (
+ (await db.getUsers(room.roomId)).includes(event.getSender())
+ ) {
+ sendMessage(
+ room.roomId,
+ `👋 We've already met, ${event.getSender()}`
+ )
+ return
+ }
+
+ db.write({
+ type: 'introduction',
+ timestamp: event.getDate(),
+ roomId: room.roomId,
+ userId: event.getSender(),
+ data: {},
+ })
+ .then(() => {
+ sendMessage(
+ room.roomId,
+ `👋 Pleased to meet you, ${event.getSender()}`
+ )
+ })
+ .catch((e) => {
+ sendMessage(
+ room.roomId,
+ `❌ Whoops, something went wrong (${e})`
+ )
+ })
+ break
+
+ case 'u':
+ case 'users':
+ if (users.length === 0) {
+ sendMessage(
+ room.roomId,
+ "🤷 I don't know anyone here. Do say !hi to get started"
+ )
+ }
+ users.forEach((user) => {
+ sendMessage(room.roomId, user)
+ })
+ break
+
+ case 'l':
+ case 'log':
+ case 'list':
+ ;(await db.getEventLog(room.roomId, parseInt(args[0]))).forEach(
+ (event) => {
+ const ts = new Date(event.timestamp)
+ const formattedTs = `${ts
+ .getDate()
+ .toString()
+ .padStart(2, '0')}-${(ts.getMonth() + 1)
+ .toString()
+ .padStart(2, '0')}-${ts
+ .getFullYear()
+ .toString()} ${ts
+ .getHours()
+ .toString()
+ .padStart(2, '0')}:${ts
+ .getMinutes()
+ .toString()
+ .padStart(2, '0')}`
+
+ switch (event.type) {
+ case 'introduction':
+ sendMessage(
+ room.roomId,
+ `⌚ ${formattedTs} → ${event.type}\n 👋 ${event.userId}`
+ )
+ break
+
+ case 'expense':
+ sendMessage(
+ room.roomId,
+ `⌚ ${formattedTs} → ${
+ event.type
+ }\n 🪙 ${event.data.currency} ${
+ event.data.amount
+ }\n 🧑 ${event.userId}\n 📜 ${
+ event.data.description ||
+ 'No description'
+ }`
+ )
+ break
+
+ default:
+ break
+ }
+ }
+ )
+ break
+
+ case 'e':
+ case 'exp':
+ case 'expense':
+ let amount = parseFloat(args[0])
+ let description = args
+ .slice(1)
+ .filter((a) => {
+ console.log(a, a[0])
+ return a[0] !== '?'
+ })
+ .join(' ')
+
+ if (!amount) {
+ sendMessage(
+ room.roomId,
+ '❌ That expense is invalid, need !help ?'
+ )
+ return
+ }
+
+ db.write({
+ type: 'expense',
+ timestamp: event.getDate(),
+ roomId: room.roomId,
+ userId: event.getSender(),
+ data: {
+ amount: amount,
+ currency: 'EUR',
+ description: description,
+ },
+ })
+ .then(() => {
+ sendMessage(room.roomId, '👍 Expense noted')
+ })
+ .catch((e) => {
+ sendMessage(
+ room.roomId,
+ `❌ Whoops, something went wrong (${e})`
+ )
+ })
+ break
+
+ case 'b':
+ case 'balance':
+ if (users.length === 0) {
+ sendMessage(
+ room.roomId,
+ '🤷 No users to make a balance for'
+ )
+ }
+ let balance = await db.getBalance(room.roomId)
+
+ // number of combinations = (n^2 + n)/2
+ for (let index = 0; index < balance.userIds.length; index++) {
+ const userId1 = balance.userIds[index]
+
+ for (
+ let index2 = index + 1;
+ index2 < balance.userIds.length;
+ index2++
+ ) {
+ const userId2 = balance.userIds[index2]
+
+ const diff =
+ (balance[userId2].spentForUserId[userId1] || 0) -
+ (balance[userId1].spentForUserId[userId2] || 0)
+
+ sendMessage(
+ room.roomId,
+ `${userId1} ${
+ diff > 0 ? '→' : '←'
+ } ${userId2}: ${Math.abs(diff).toFixed(2)}`
+ )
+ }
+ }
+ break
+
+ case 'undo':
+ db.undo(room.roomId)
+ .then(() => {
+ sendMessage(room.roomId, '👍 Last action was undone')
+ })
+ .catch((e) => {
+ sendMessage(
+ room.roomId,
+ `❌ Whoops, something went wrong (${e})`
+ )
+ })
+ break
+
+ case 'reset':
+ db.reset(room.roomId)
+ .then(() => {
+ sendMessage(
+ room.roomId,
+ '👍 All expenses and transactions have been erased'
+ )
+ })
+ .catch((e) => {
+ sendMessage(
+ room.roomId,
+ `❌ Whoops, something went wrong (${e})`
+ )
+ })
+ break
+
+ case 'fullreset':
+ db.fullreset(room.roomId)
+ .then(() => {
+ sendMessage(room.roomId, '👍 All data has been erased')
+ })
+ .catch((e) => {
+ sendMessage(
+ room.roomId,
+ `❌ Whoops, something went wrong (${e})`
+ )
+ })
+ break
+
+ default:
+ sendMessage(
+ room.roomId,
+ '🤷 Not sure what you are asking for. Need !help ?'
+ )
+ break
+ }
+ return
+ }
+
+ // Reply to cookie
+ if (message.toLowerCase().includes('cookie')) {
+ sendMessage(room.roomId, 'Did someone say cookie?')
+ return
+ }
+})
+
+// Start the Matrix client
+matrixClient.startClient()
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 0000000..7543818
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,489 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/runtime@^7.12.5":
+ version "7.17.8"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2"
+ integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
+"@types/retry@^0.12.0":
+ version "0.12.1"
+ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065"
+ integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==
+
+ajv@^6.12.3:
+ version "6.12.6"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ 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"
+
+another-json@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/another-json/-/another-json-0.2.0.tgz#b5f4019c973b6dd5c6506a2d93469cb6d32aeedc"
+ integrity sha1-tfQBnJc7bdXGUGotk0acttMq7tw=
+
+asn1@~0.2.3:
+ version "0.2.6"
+ resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d"
+ integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
+ dependencies:
+ safer-buffer "~2.1.0"
+
+assert-plus@1.0.0, assert-plus@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
+ integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
+
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+ integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
+
+aws-sign2@~0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
+ integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
+
+aws4@^1.8.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
+ integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
+
+base-x@^3.0.2:
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320"
+ integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==
+ dependencies:
+ safe-buffer "^5.0.1"
+
+bcrypt-pbkdf@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
+ integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
+ dependencies:
+ tweetnacl "^0.14.3"
+
+browser-request@^0.3.3:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/browser-request/-/browser-request-0.3.3.tgz#9ece5b5aca89a29932242e18bf933def9876cc17"
+ integrity sha1-ns5bWsqJopkyJC4Yv5M975h2zBc=
+
+bs58@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
+ integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo=
+ dependencies:
+ base-x "^3.0.2"
+
+call-bind@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
+ integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
+ dependencies:
+ function-bind "^1.1.1"
+ get-intrinsic "^1.0.2"
+
+caseless@~0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
+ integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
+
+combined-stream@^1.0.6, combined-stream@~1.0.6:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+ integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+ dependencies:
+ delayed-stream "~1.0.0"
+
+content-type@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
+ integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
+
+core-util-is@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+ integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+
+dashdash@^1.12.0:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
+ integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
+ dependencies:
+ assert-plus "^1.0.0"
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+ integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
+
+dotenv@^16.0.0:
+ version "16.0.0"
+ resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411"
+ integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==
+
+ecc-jsbn@~0.1.1:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
+ integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
+ dependencies:
+ jsbn "~0.1.0"
+ safer-buffer "^2.1.0"
+
+extend@~3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+ integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+
+extsprintf@1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
+ integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
+
+extsprintf@^1.2.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07"
+ integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==
+
+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==
+
+forever-agent@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
+ integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
+
+form-data@~2.3.2:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
+ integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.6"
+ mime-types "^2.1.12"
+
+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==
+
+get-intrinsic@^1.0.2:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
+ integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
+ dependencies:
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-symbols "^1.0.1"
+
+getpass@^0.1.1:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
+ integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
+ dependencies:
+ assert-plus "^1.0.0"
+
+har-schema@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
+ integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
+
+har-validator@~5.1.3:
+ version "5.1.5"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
+ integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
+ dependencies:
+ ajv "^6.12.3"
+ har-schema "^2.0.0"
+
+has-symbols@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
+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"
+
+http-signature@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
+ integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
+ dependencies:
+ assert-plus "^1.0.0"
+ jsprim "^1.2.2"
+ sshpk "^1.7.0"
+
+is-typedarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+ integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
+
+isstream@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
+ integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
+
+jsbn@~0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
+ integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
+
+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-schema@0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5"
+ integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==
+
+json-stringify-safe@~5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+ integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
+
+jsprim@^1.2.2:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
+ integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==
+ dependencies:
+ assert-plus "1.0.0"
+ extsprintf "1.3.0"
+ json-schema "0.4.0"
+ verror "1.10.0"
+
+loglevel@^1.7.1:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114"
+ integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==
+
+lowdb@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lowdb/-/lowdb-3.0.0.tgz#c10ab4e7eb86f1cbe255e35e60ffb0c6f42049e0"
+ integrity sha512-9KZRulmIcU8fZuWiaM0d5e2/nPnrFyXkeXVpqT+MJS+vgbgOf1EbtvgQmba8HwUFgDl1oeZR6XqEJnkJmQdKmg==
+ dependencies:
+ steno "^2.1.0"
+
+matrix-events-sdk@^0.0.1-beta.7:
+ version "0.0.1-beta.7"
+ resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1-beta.7.tgz#5ffe45eba1f67cc8d7c2377736c728b322524934"
+ integrity sha512-9jl4wtWanUFSy2sr2lCjErN/oC8KTAtaeaozJtrgot1JiQcEI4Rda9OLgQ7nLKaqb4Z/QUx/fR3XpDzm5Jy1JA==
+
+matrix-js-sdk@^16.0.1:
+ version "16.0.1"
+ resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-16.0.1.tgz#9b389ef16677ba648efad2929a7802af6f1dc81f"
+ integrity sha512-GRYZY7JZRqsVFa2nKO2qJbU4gQail2+1PgX2QDcibWizTL5Gh8YS384twprpIKqzdLHJ3d7H7A0L+uqc562ZsQ==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+ another-json "^0.2.0"
+ browser-request "^0.3.3"
+ bs58 "^4.0.1"
+ content-type "^1.0.4"
+ loglevel "^1.7.1"
+ matrix-events-sdk "^0.0.1-beta.7"
+ p-retry "^4.5.0"
+ qs "^6.9.6"
+ request "^2.88.2"
+ unhomoglyph "^1.0.6"
+
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12, mime-types@~2.1.19:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+oauth-sign@~0.9.0:
+ version "0.9.0"
+ resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
+ integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
+
+object-inspect@^1.9.0:
+ version "1.12.0"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
+ integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==
+
+p-retry@^4.5.0:
+ version "4.6.1"
+ resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c"
+ integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==
+ dependencies:
+ "@types/retry" "^0.12.0"
+ retry "^0.13.1"
+
+performance-now@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
+ integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
+
+prettier@^2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032"
+ integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==
+
+psl@^1.1.28:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
+ integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
+
+punycode@^2.1.0, punycode@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
+ integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+
+qs@^6.9.6:
+ version "6.10.3"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
+ integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
+ dependencies:
+ side-channel "^1.0.4"
+
+qs@~6.5.2:
+ version "6.5.3"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
+ integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
+
+regenerator-runtime@^0.13.4:
+ version "0.13.9"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
+ integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
+
+request@^2.88.2:
+ version "2.88.2"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
+ integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
+ dependencies:
+ aws-sign2 "~0.7.0"
+ aws4 "^1.8.0"
+ caseless "~0.12.0"
+ combined-stream "~1.0.6"
+ extend "~3.0.2"
+ forever-agent "~0.6.1"
+ form-data "~2.3.2"
+ har-validator "~5.1.3"
+ http-signature "~1.2.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.19"
+ oauth-sign "~0.9.0"
+ performance-now "^2.1.0"
+ qs "~6.5.2"
+ safe-buffer "^5.1.2"
+ tough-cookie "~2.5.0"
+ tunnel-agent "^0.6.0"
+ uuid "^3.3.2"
+
+retry@^0.13.1:
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
+ integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==
+
+safe-buffer@^5.0.1, safe-buffer@^5.1.2:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+side-channel@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
+ integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+ dependencies:
+ call-bind "^1.0.0"
+ get-intrinsic "^1.0.2"
+ object-inspect "^1.9.0"
+
+sshpk@^1.7.0:
+ version "1.17.0"
+ resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5"
+ integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==
+ dependencies:
+ asn1 "~0.2.3"
+ assert-plus "^1.0.0"
+ bcrypt-pbkdf "^1.0.0"
+ dashdash "^1.12.0"
+ ecc-jsbn "~0.1.1"
+ getpass "^0.1.1"
+ jsbn "~0.1.0"
+ safer-buffer "^2.0.2"
+ tweetnacl "~0.14.0"
+
+steno@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/steno/-/steno-2.1.0.tgz#05a9c378ce42ed04f642cda6fcb41787a10e4e33"
+ integrity sha512-mauOsiaqTNGFkWqIfwcm3y/fq+qKKaIWf1vf3ocOuTdco9XoHCO2AGF1gFYXuZFSWuP38Q8LBHBGJv2KnJSXyA==
+
+tough-cookie@~2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
+ integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
+ dependencies:
+ psl "^1.1.28"
+ punycode "^2.1.1"
+
+tunnel-agent@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
+ integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
+ dependencies:
+ safe-buffer "^5.0.1"
+
+tweetnacl@^0.14.3, tweetnacl@~0.14.0:
+ version "0.14.5"
+ resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
+ integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
+
+unhomoglyph@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/unhomoglyph/-/unhomoglyph-1.0.6.tgz#ea41f926d0fcf598e3b8bb2980c2ddac66b081d3"
+ integrity sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==
+
+uri-js@^4.2.2:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
+uuid@^3.3.2:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
+ integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+
+verror@1.10.0:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
+ integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
+ dependencies:
+ assert-plus "^1.0.0"
+ core-util-is "1.0.2"
+ extsprintf "^1.2.0"