1
0
mirror of https://github.com/byulmaru/quesdon synced 2024-11-27 06:18:02 +09:00

Merge branch 'misskey-support' into 'master'

Misskey support

Closes #2

See merge request chocological00/quesdon!3
This commit is contained in:
DW 2019-11-11 05:39:19 +00:00
commit 05fa99dea8
24 changed files with 587 additions and 202 deletions

View File

@ -1,25 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": [
"src"
]
}

View File

@ -3,10 +3,13 @@ import Router from 'koa-router';
import fetch from 'node-fetch';
import parseLinkHeader, { Link, Links } from 'parse-link-header';
import { Account } from 'megalodon';
import { User as MisskeyUser } from '../../utils/misskey_entities/user';
import { Following } from '../../utils/misskey_entities/following';
import { oneLineTrim, stripIndents } from 'common-tags';
import { QUESTION_TEXT_MAX_LENGTH } from '../../../common/const';
import { BASE_URL, NOTICE_ACCESS_TOKEN, PUSHBULLET_CLIENT_ID, PUSHBULLET_CLIENT_SECRET } from '../../config';
import { Question, User } from '../../db/index';
import detectInstance from '../../utils/detectInstance';
const router = new Router();
@ -24,8 +27,6 @@ router.get('/verify_credentials', async (ctx: Koa.ParameterizedContext): Promise
router.get('/followers', async (ctx: Koa.ParameterizedContext): Promise<never|void|{}> =>
{
if (null === /^\d+$/.exec(ctx.query.max_id || '0'))
return ctx.throw('max_id is num only', 400);
if (!ctx.session.user)
return ctx.throw('please login', 403);
@ -37,10 +38,46 @@ router.get('/followers', async (ctx: Koa.ParameterizedContext): Promise<never|vo
if (user.hostName === 'twitter.com')
return ctx.body = { max_id: undefined, accounts: [] };
// TODO: add logic for misskey
// mastodon
const instanceUrl = 'https://' + user.acct.split('@')[1];
const myInfo = await fetch(instanceUrl + '/api/v1/accounts/verify_credentials',
const instanceType = await detectInstance(instanceUrl);
if (instanceType === 'misskey')
{
// misskey
const fetchOptions =
{
method: 'POST',
headers: { 'Content-Type': 'application/json' }
};
const myInfo: MisskeyUser = await fetch(`${instanceUrl}/api/i`,
Object.assign({}, fetchOptions,
{
body: JSON.stringify( { i: user.accessToken })
})).then(r => r.json());
const body: { i: string; userId: string; limit: number; untilId?: string } =
{
i: user.accessToken,
userId: myInfo.id,
limit: 80
};
if (ctx.query.max_id)
body.untilId = ctx.query.max_id;
const followersRaw: Following[] = await fetch(`${instanceUrl}/api/users/followers`,
Object.assign({}, fetchOptions, { body: body })).then(r => r.json());
const followers = followersRaw
.map(follower => `${follower.follower?.username}@${follower.follower?.host ?? user.acct.split('@')[1]}`.toLowerCase());
const followersObject = await User.find({acctLower: {$in: followers}});
const max_id = followersRaw[followersRaw.length - 1]?.id ?? '';
return ctx.body =
{
accounts: followersObject,
max_id
};
}
// mastodon
const myInfo = await fetch(`${instanceUrl}/api/v1/accounts/verify_credentials`,
{
headers: { Authorization: 'Bearer ' + user.accessToken }
}).then((r) => r.json());
@ -57,7 +94,7 @@ router.get('/followers', async (ctx: Koa.ParameterizedContext): Promise<never|vo
const followersObject = await User.find({acctLower: {$in: followers}});
const max_id = ((parseLinkHeader(followersRes.headers.get('Link') ?? '') || {} as Links).next || {} as Link).max_id;
ctx.body =
return ctx.body =
{
accounts: followersObject,
max_id
@ -185,7 +222,7 @@ router.post('/:acct/question', async (ctx: Koa.ParameterizedContext): Promise<ne
if (!ctx.session.user)
return ctx.throw('please login', 403);
const questionUser = await User.findById(ctx.sessions.user);
const questionUser = await User.findById(ctx.session.user);
if (!questionUser)
return ctx.throw('not found', 404);

View File

@ -2,12 +2,16 @@ import Koa from 'koa';
import Router from 'koa-router';
import fetch from 'node-fetch';
import rndstr from 'rndstr';
import crypto from 'crypto';
import { URL } from 'url';
import { BASE_URL } from '../../config';
import { MastodonApp, User } from '../../db/index';
import QueryStringUtils from '../../utils/queryString';
import { requestOAuth } from '../../utils/requestOAuth';
import twitterClient from '../../utils/twitterClient';
import detectInstance from '../../utils/detectInstance';
import { App } from '../../utils/misskey_entities/app';
import { User as MisskeyUser } from '../../utils/misskey_entities/user';
const router = new Router();
@ -20,47 +24,7 @@ router.post('/get_url', async (ctx: Koa.ParameterizedContext): Promise<never|voi
const redirectUri = BASE_URL + '/api/web/oauth/redirect';
let url = '';
if (hostName !== 'twitter.com')
{
// Mastodon
// TODO: misskey support
let app = await MastodonApp.findOne( { hostName, appBaseUrl: BASE_URL, redirectUri } );
if (!app)
{
const res = await fetch('https://' + hostName + '/api/v1/apps',
{
method: 'POST',
body: JSON.stringify(
{
client_name: 'Quesdon',
redirect_uris: redirectUri,
scopes: 'read write',
website: BASE_URL
}),
headers: { 'Content-Type': 'application/json' }
}).then((r) => r.json());
app = new MastodonApp();
app.clientId = res.client_id;
app.clientSecret = res.client_secret;
app.hostName = hostName;
app.appBaseUrl = BASE_URL;
app.redirectUri = redirectUri;
await app.save();
}
ctx.session.loginState = `${rndstr()}_${app.id}`;
const params: {[key: string]: string} =
{
client_id: app.clientId,
scope: 'read+write',
redirect_uri: redirectUri,
response_type: 'code',
state: ctx.session.loginState
};
url = `https://${app.hostName}/oauth/authorize?${Object.entries(params).map((v) => v.join('=')).join('&')}`;
}
else // Twitter
if (hostName === 'twitter.com')
{
ctx.session.loginState = 'twitter';
const { TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET } = process.env;
@ -80,6 +44,85 @@ router.post('/get_url', async (ctx: Koa.ParameterizedContext): Promise<never|voi
ctx.session.twitterOAuth = requestToken;
url = `https://twitter.com/oauth/authenticate?oauth_token=${requestToken.token}`;
}
else
{
const instanceType = await detectInstance(`https://${hostName}`);
if (instanceType === 'misskey')
{
let app = await MastodonApp.findOne( { hostName, appBaseUrl: BASE_URL, redirectUri } );
if (!app) // if it's the first time user from this instance is using quesdon
{
const res: App = await fetch(`https://${hostName}/api/app/create`,
{
method: 'POST',
body: JSON.stringify(
{
name: 'Quesdon',
description: BASE_URL,
permission: ['read:following', 'write:notes'],
callbackUrl: redirectUri
}),
headers: { 'Content-Type': 'application/json' }
}).then(r => r.json());
app = new MastodonApp();
app.clientId = res.id,
app.clientSecret = res.secret as string;
app.hostName = hostName;
app.appBaseUrl = BASE_URL;
app.redirectUri = redirectUri;
await app.save();
}
ctx.session.loginState = `misskey_${app.id}`;
const res = await fetch(`https://${hostName}/api/auth/session/generate`, // get authentication url from misskey instance
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify( { appSecret: app.clientSecret } )
}).then(r => r.json());
url = res.url;
}
else
{
// Mastodon
let app = await MastodonApp.findOne( { hostName, appBaseUrl: BASE_URL, redirectUri } );
if (!app)
{
const res = await fetch('https://' + hostName + '/api/v1/apps',
{
method: 'POST',
body: JSON.stringify(
{
client_name: 'Quesdon',
redirect_uris: redirectUri,
scopes: 'read write',
website: BASE_URL
}),
headers: { 'Content-Type': 'application/json' }
}).then((r) => r.json());
app = new MastodonApp();
app.clientId = res.client_id;
app.clientSecret = res.client_secret;
app.hostName = hostName;
app.appBaseUrl = BASE_URL;
app.redirectUri = redirectUri;
await app.save();
}
ctx.session.loginState = `${rndstr()}_${app.id}`;
const params: {[key: string]: string} =
{
client_id: app.clientId,
scope: 'read+write',
redirect_uri: redirectUri,
response_type: 'code',
state: ctx.session.loginState
};
url = `https://${app.hostName}/oauth/authorize?${Object.entries(params).map((v) => v.join('=')).join('&')}`;
}
}
ctx.body = { url };
});
@ -96,11 +139,40 @@ router.get('/redirect', async (ctx: Koa.ParameterizedContext) =>
url: string;
acct: string;
};
if (ctx.session.loginState !== 'twitter')
if ((ctx.session.loginState as string).startsWith('misskey'))
{
// misskey
const app = await MastodonApp.findById(ctx.session.loginState.split('_')[1]);
if (app === null)
return ctx.redirect('/login?error=app_notfound');
const res: { accessToken: string; user: MisskeyUser } = await fetch(`https://${app.hostName}/api/auth/session/userkey`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(
{
appSecret: app.clientSecret,
token: ctx.query.token
})
}).then(r => r.json());
profile =
{
id: res.user.id,
name: res.user.name ?? res.user.username,
screenName: res.user.username,
hostName: app.hostName,
avatarUrl: res.user.avatarUrl as string,
accessToken: crypto.createHash('sha256').update(res.accessToken + app.clientSecret).digest('hex'),
url: `https://${res.user.host}/@${res.user.username}`,
acct: `${res.user.username}@${app.hostName}`
};
}
else if (ctx.session.loginState !== 'twitter')
{
// Mastodon
// TODO: handle misskey
if (ctx.query.state !== ctx.session.loginState)
return ctx.redirect('/login?error=invalid_state');
@ -207,14 +279,12 @@ router.get('/redirect', async (ctx: Koa.ParameterizedContext) =>
const acct = profile.acct;
let user;
if (profile.hostName !== 'twitter.com')
{
// Mastodon
// TODO: misskey
if (profile.hostName !== 'twitter.com') // Mastodon and misskey
user = await User.findOne({acctLower: acct.toLowerCase()});
}
else
user = await User.findOne({upstreamId: profile.id, hostName: profile.hostName});
if (user === null)
user = new User();

View File

@ -8,6 +8,7 @@ import { IMastodonApp, IUser, Question, QuestionLike, User } from '../../db/inde
import { cutText } from '../../utils/cutText';
import { requestOAuth } from '../../utils/requestOAuth';
import twitterClient from '../../utils/twitterClient';
import detectInstance from '../../utils/detectInstance';
const router = new Router();
@ -52,7 +53,7 @@ router.get('/latest', async (ctx) =>
ctx.body = questions;
});
router.post('/:id/answer', async (ctx: Koa.ParameterizedContext): Promise<never|void> =>
router.post('/:id/answer', async (ctx: Koa.ParameterizedContext): Promise<void|never> =>
{
if (!ctx.session.user)
return ctx.throw('please login', 403);
@ -87,57 +88,99 @@ router.post('/:id/answer', async (ctx: Koa.ParameterizedContext): Promise<never|
const isTwitter = user.hostName === 'twitter.com';
const answerCharMax = isTwitter ? (110 - question.question.length) : 200;
const answerUrl = `${BASE_URL}/@${user.acct}/questions/${question.id}`;
if (!isTwitter)
if (isTwitter)
{
// Mastodon
// TODO: misskey
const body =
{
spoiler_text: `Q. ${question.question} #quesdon`,
status: `A. ${question.answer.length > 200 ? `${question.answer.substring(0, 200)}...` : question.answer}
#quesdon ${answerUrl}`,
visibility: ctx.request.body.visibility
};
if (question.questionUser)
{
let questionUserAcct = `@${question.questionUser.acct}`;
if (question.questionUser.hostName === 'twitter.com')
questionUserAcct = `https://twitter.com/${question.questionUser.acct.replace(/:.+/, '')}`;
body.status = stripIndents`질문자: ${questionUserAcct}
${body.status}`;
}
if (question.isNSFW)
{
body.status = `Q. ${question.question}
${body.status}`;
body.spoiler_text = '⚠ 이 질문은 답변자가 NSFW하다고 했어요. #quesdon';
}
fetch('https://' + user.acct.split('@')[1] + '/api/v1/statuses',
{
method: 'POST',
body: JSON.stringify(body),
headers:
{
'Authorization': 'Bearer ' + user.accessToken,
'Content-Type': 'application/json'
}
});
}
else
{
const strQ = cutText(question.question, 60);
const strA = cutText(question.answer, 120 - strQ.length);
const [key, secret] = user.accessToken.split(':');
const body = `Q. ${strQ}
A. ${strA}
#quesdon ${answerUrl}`;
requestOAuth(twitterClient,
await requestOAuth(twitterClient,
{
url: 'https://api.twitter.com/1.1/statuses/update.json',
method: 'POST',
data: { status: body }
}, { key, secret });
return;
}
// misskey
const instanceUrl = 'https://' + user.acct.split('@')[1];
const instanceType = await detectInstance(instanceUrl);
const status =
{
title: `Q. ${question.question} #quesdon`,
text: stripIndents`
A. ${question.answer.length > answerCharMax ? `${question.answer.substring(0, answerCharMax)}...` : question.answer}
#quesdon ${answerUrl}`
};
if (question.questionUser)
{
let questionUserAcct = `@${question.questionUser.acct}`;
if (question.questionUser.hostName === 'twitter.com')
questionUserAcct = `https://twitter.com/${question.questionUser.acct.replace(/:.+/, '')}`;
status.text = stripIndents`
질문자: ${questionUserAcct}
${status.text}`;
}
if (question.isNSFW)
{
status.text = stripIndents`
Q. ${question.question}
${status.text}`;
status.title = '⚠ 이 질문은 답변자가 NSFW하다고 했어요. #quesdon';
}
if (instanceType === 'misskey')
{
let visibility;
switch(ctx.request.body.visibility)
{
case 'public': visibility = 'public'; break;
case 'unlisted': visibility = 'home'; break;
case 'private': visibility = 'followers'; break;
default: visibility = 'home'; break;
}
const body =
{
i: user.accessToken,
visibility: visibility,
cw: status.title,
text: status.text
};
await fetch(`${instanceUrl}/api/notes/create`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
return;
}
// Mastodon
const body =
{
spoiler_text: status.title,
status: status.text,
visibility: ctx.request.body.visibility
};
await fetch(instanceUrl + '/api/v1/statuses',
{
method: 'POST',
body: JSON.stringify(body),
headers:
{
'Authorization': 'Bearer ' + user.accessToken,
'Content-Type': 'application/json'
}
});
return;
});
router.post('/:id/delete', async (ctx: Koa.ParameterizedContext): Promise<never|void> =>

View File

@ -14,7 +14,7 @@ import { User } from './db/index';
const app = new Koa();
app.keys = [SECRET_KEY];
const pug = new Pug( { viewPath: path.resolve(__dirname, '../../views'), app: app } );
new Pug( { viewPath: path.resolve(__dirname, '../../views'), app: app } );
app.use(koaBody( { multipart: true } ));
app.use(session({}, app));

View File

@ -0,0 +1,36 @@
import fetch from 'node-fetch';
type nodeinfoMeta =
{
links: nodeinfoVersionList[];
};
type nodeinfoVersionList =
{
rel: string;
href: string;
}
type nodeinfo =
{
version: string;
software: { name: string; version: string };
// irrelevent properties skipped
}
export default async function detectInstance(url: string): Promise<string|undefined>
{
const parsedURL = new URL(url);
if(parsedURL.hostname === 'twitter.com')
return 'twitter';
// fediverse
const nodeinfoMeta: nodeinfoMeta = await fetch(`${parsedURL.origin}/.well-known/nodeinfo`).then(r => r.json());
const nodeinfoLink = nodeinfoMeta.links.find(elem => elem.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0');
// TODO: add support for 1.0 as a fallback? All latest versions of major AP softwares seem to support 2.0 tho
if(!nodeinfoLink)
return undefined;
const nodeinfo: nodeinfo = await fetch(`${nodeinfoLink.href}`).then(r => r.json());
return nodeinfo.software.name;
}

View File

@ -0,0 +1,8 @@
export type App =
{
id: string;
name: string;
callbackUrl: string | null;
permission?: Array<string>;
secret?: string;
}

View File

@ -0,0 +1,9 @@
import { User } from './user';
export type Blocking =
{
id: string;
createdAt: string;
blockeeId: string;
blockee: User;
}

View File

@ -0,0 +1,12 @@
export type DriveFile =
{
id: string;
createdAt: string;
name: string;
type: string;
md5: string;
size: number;
url: string | null;
folderId: string | null;
isSensitive: boolean;
}

View File

@ -0,0 +1,10 @@
export type DriveFolder =
{
id: string;
createdAt: string;
name: string;
foldersCount?: number;
filesCount?: number;
parentId: string | null;
parent?: DriveFolder | null;
}

View File

@ -0,0 +1,9 @@
export type Error =
{
error:
{
code: string;
message: string;
id: string;
};
}

View File

@ -0,0 +1,11 @@
import { User } from './user';
export type Following =
{
id: string;
createdAt: string;
followeeId: string;
followee?: User;
followerId: string;
follower?: User;
}

View File

@ -0,0 +1,10 @@
export type Hashtag =
{
tag: string;
mentionedUsersCount: number;
mentionedLocalUsersCount: number;
mentionedRemoteUsersCount: number;
attachedUsersCount: number;
attahedLocalUsersCount: number;
attachedRemoteUsersCount: number;
}

View File

@ -0,0 +1,20 @@
import { User } from './user';
import { UserGroup } from './usergroup';
import { DriveFile } from './drivefile';
export type MessagingMessage =
{
id: string;
createdAt: string;
userId: string;
user?: User;
text: string | null;
fileId?: string | null;
file?: DriveFile | null;
recipientId: string | null;
recipient?: User | null;
groupId: string | null;
group?: UserGroup | null;
isRead?: boolean;
reads?: Array<string>;
}

View File

@ -0,0 +1,9 @@
import { User } from './user';
export type Muting =
{
id: string;
createdAt: string;
muteeId: string;
mutee: User;
}

View File

@ -0,0 +1,26 @@
import { User } from './user';
import { DriveFile } from './drivefile';
export type Note =
{
id: string;
createdAt: string;
text: string | null;
cw?: string | null;
userId: string;
user: User;
replyId?: string | null;
renoteId?: string | null;
reply?: Note | null;
renote?: Note | null;
viaMobile?: boolean;
isHidden?: boolean;
visibility: string;
mentions?: Array<string>;
visibleUserIds?: Array<string>;
fileIds?: Array<string>;
files?: Array<DriveFile>;
tags?: Array<string>;
poll?: object | null; // FIXME: poll
geo?: object | null; // FIXME: geo
}

View File

@ -0,0 +1,7 @@
export type NoteFavorite =
{
id: string;
createdAt: string;
note: string;
noteId: string;
}

View File

@ -0,0 +1,9 @@
import { User } from './user';
export type NoteReaction =
{
id: string;
createdAt: string;
user: User;
type: string;
}

View File

@ -0,0 +1,22 @@
import { User } from './user';
enum NotificationType // TODO: bring this out
{
follow = 'follow',
receiveFollowRequest = 'receiveFollowRequest',
mention = 'mention',
reply = 'reply',
renote = 'renote',
quote = 'quote',
reaction = 'reaction',
pollVote = 'pollVote'
}
export type Notification =
{
id: string;
createdAt: string;
type: NotificationType;
userId?: string | null;
user?: User | null;
}

View File

@ -0,0 +1,15 @@
import { User } from './user';
export type Page =
{
id: string;
createdAt: string;
updatedAt: string;
title: string;
name: string;
summary: string | null;
content: Array<void>;
variables: Array<void>;
userId: string;
user: User;
}

View File

@ -0,0 +1,32 @@
import { Note } from './note';
export type User =
{
id: string;
username: string;
name: string | null;
url?: string | null;
avatarUrl: string | null;
avatarColor: any | null; // eslint-disable-line @typescript-eslint/no-explicit-any
bannerUrl?: string | null;
bannerColor?: any | null; // eslint-disable-line @typescript-eslint/no-explicit-any
emojis: any | null; // eslint-disable-line @typescript-eslint/no-explicit-any
host: string | null;
description?: string | null;
birthday?: string | null;
createdAt?: string;
updatedAt?: string | null;
location?: string | null;
followersCount?: number;
followingCount?: number;
notesCount?: number;
isBot?: boolean;
pinnedNoteIds?: Array<string>;
pinnedNotes?: Array<Note>;
isCat?: boolean;
isAdmin?: boolean;
isModerator?: boolean;
isLocked?: boolean;
hasUnreadSpecifiedNotes?: boolean;
hasUnreadMentions?: boolean;
}

View File

@ -0,0 +1,8 @@
export type UserGroup =
{
id: string;
createdAt: string;
name: string;
ownerId: string;
userIds?: Array<string>;
}

View File

@ -0,0 +1,7 @@
export type UserList =
{
id: string;
createdAt: string;
name: string;
userIds?: Array<string>;
}

162
yarn.lock
View File

@ -19,9 +19,9 @@
js-tokens "^4.0.0"
"@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.4.0":
version "7.7.1"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.1.tgz#b223497bbfbcbbb38116673904debc71470ca528"
integrity sha512-SQ0sS7KUJDvgCI2cpZG0nJygO6002oTbhgSuw4WcocsnbxLwL5Q8I3fqbJdyBAc3uFrWZiR2JomseuxSuci3SQ==
version "7.7.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.2.tgz#111a78002a5c25fc8e3361bedc9529c696b85a6a"
integrity sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==
dependencies:
regenerator-runtime "^0.13.2"
@ -158,9 +158,9 @@
integrity sha1-/1QEYtL7TQqIRBzq8n0oewHD2Hg=
"@types/koa-compose@*":
version "3.2.4"
resolved "https://registry.yarnpkg.com/@types/koa-compose/-/koa-compose-3.2.4.tgz#76a461634a59c3e13449831708bb9b355fb1548e"
integrity sha512-ioou0rxkuWL+yBQYsHUQAzRTfVxAg8Y2VfMftU+Y3RA03/MzuFL0x/M2sXXj3PkfnENbHsjeHR1aMdezLYpTeA==
version "3.2.5"
resolved "https://registry.yarnpkg.com/@types/koa-compose/-/koa-compose-3.2.5.tgz#85eb2e80ac50be95f37ccf8c407c09bbe3468e9d"
integrity sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==
dependencies:
"@types/koa" "*"
@ -216,9 +216,9 @@
"@types/koa-send" "*"
"@types/koa@*", "@types/koa@^2.0.51":
version "2.0.51"
resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.0.51.tgz#b08a57dc49e34aaf6b5cc004b5fef4b16ebe32e1"
integrity sha512-L5e/l6Z+SR9Jk6HM0wNYdkvWhSUBOvi+7Q5Uwn7kE/VmBXX7NIxARMigARWAyXAtXiv5Ry1P2HmebolFdvuIVg==
version "2.0.52"
resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.0.52.tgz#7dd11de4189ab339ad66c4ccad153716b14e525f"
integrity sha512-cp/GTOhOYwomlSKqEoG0kaVEVJEzP4ojYmfa7EKaGkmkkRwJ4B/1VBLbQZ49Z+WJNvzXejQB/9GIKqMo9XLgFQ==
dependencies:
"@types/accepts" "*"
"@types/cookies" "*"
@ -238,17 +238,17 @@
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/mongodb@*":
version "3.3.8"
resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.3.8.tgz#a6023ff343a90478c0e22b2b5d35e56964c3ba60"
integrity sha512-hsLcKRBFVeam4FyJOU6bwklvsNHzmUBI5SIxQ2meZu+RZgTDzrv+W19YAHgDIuiTn6UqHrVolweLMk0RpKEbxg==
version "3.3.10"
resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.3.10.tgz#ef8255cc78b32ecd8afd901796e458f4379d9bc2"
integrity sha512-0irlIjqs0UVcs+1ih7qdA9Rs4gkN9hBX7PI6IBWYu/kcMQ52L+oLW19gKT8wBJD2uMBkgT4mVKDmEo6ygObnHA==
dependencies:
"@types/bson" "*"
"@types/node" "*"
"@types/mongoose@^5.5.29":
version "5.5.29"
resolved "https://registry.yarnpkg.com/@types/mongoose/-/mongoose-5.5.29.tgz#611cb2eaed58d8d12d3f6338d8811290a4407f0f"
integrity sha512-bIlJfCIg/pPhORc35hGs14gmDjPLIPffcs20UgMZFB8bw0WPIAkDRTZJlBLpuZ0UgHTOOxcSt56exkUv7f/1Yw==
version "5.5.30"
resolved "https://registry.yarnpkg.com/@types/mongoose/-/mongoose-5.5.30.tgz#8b6b06e4142771204ae1abec5773157b8a94ec53"
integrity sha512-LQ7Q/MoRLhtYn1zGefsTrobeg4hieXe6MGYM2plbZHhqf4Ud1zRuEwjDQIjCgDIyMPnACiyCvPMTyzHaoSp0SA==
dependencies:
"@types/mongodb" "*"
"@types/node" "*"
@ -261,9 +261,9 @@
"@types/node" "*"
"@types/node@*":
version "12.12.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.6.tgz#a47240c10d86a9a57bb0c633f0b2e0aea9ce9253"
integrity sha512-FjsYUPzEJdGXjwKqSpE0/9QEh6kzhTAeObA54rn6j3rR4C/mzpI9L0KNfoeASSPMMdxIsoJuCLDWcM/rVjIsSA==
version "12.12.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11"
integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w==
"@types/oauth@^0.9.0":
version "0.9.1"
@ -293,9 +293,9 @@
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
"@types/react-dom@^16.9.3":
version "16.9.3"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.3.tgz#4006ff0e13958af91313869077c04cb20d9b9d04"
integrity sha512-FUuZKXPr9qlzUT9lhuzrZgLjH63TvNn28Ch3MvKG4B+F52zQtO8DtE0Opbncy3xaucNZM2WIPfuNTgkbKx5Brg==
version "16.9.4"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.4.tgz#0b58df09a60961dcb77f62d4f1832427513420df"
integrity sha512-fya9xteU/n90tda0s+FtN5Ym4tbgxpq/hb/Af24dvs6uYnYn+fspaxw5USlw0R8apDNwxsqumdRoCoKitckQqw==
dependencies:
"@types/react" "*"
@ -1613,9 +1613,9 @@ browserslist@^3.2.6:
electron-to-chromium "^1.3.47"
bson@^1.1.1, bson@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.1.tgz#4330f5e99104c4e751e7351859e2d408279f2f13"
integrity sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg==
version "1.1.3"
resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.3.tgz#aa82cb91f9a453aaa060d6209d0675114a8154d3"
integrity sha512-TdiJxMVnodVS7r0BdL42y/pqC9cL2iKynVwA0Ho3qbsQYr428veL3l7BQyuqiw+Q5SqqoT0m4srSY/BlZ9AxXg==
buffer-from@^1.0.0:
version "1.1.1"
@ -1633,9 +1633,9 @@ buffer-xor@^1.0.3:
integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=
buffer@^4.3.0:
version "4.9.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=
version "4.9.2"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8"
integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==
dependencies:
base64-js "^1.0.2"
ieee754 "^1.1.4"
@ -2513,9 +2513,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.47:
version "1.3.304"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.304.tgz#49b47d961f8143116174c2f70fbfee3aabf43015"
integrity sha512-a5mqa13jCdBc+Crgk3Gyr7vpXCiFWfFq23YDCEmrPYeiDOQKZDVE6EX/Q4Xdv97n3XkcjiSBDOY0IS19yP2yeA==
version "1.3.306"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.306.tgz#e8265301d053d5f74e36cb876486830261fbe946"
integrity sha512-frDqXvrIROoYvikSKTIKbHbzO6M3/qC6kCIt/1FOa9kALe++c4VAJnwjSFvf1tYLEUsP2n9XZ4XSCyqc3l7A/A==
elliptic@^6.0.0:
version "6.5.1"
@ -2611,9 +2611,9 @@ es-abstract@^1.4.3:
string.prototype.trimright "^2.1.0"
es-to-primitive@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==
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"
@ -2933,7 +2933,7 @@ fast-json-stable-stringify@^2.0.0:
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
fast-levenshtein@~2.0.4:
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=
@ -3319,9 +3319,9 @@ glob2base@^0.0.12:
find-index "^0.1.1"
glob@^7.0.3, glob@^7.0.5, glob@^7.1.3, glob@^7.1.4:
version "7.1.5"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.5.tgz#6714c69bee20f3c3e64c4dd905553e532b40cdc0"
integrity sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==
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"
@ -3806,7 +3806,7 @@ ip-regex@^2.1.0:
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
ip@^1.1.0, ip@^1.1.5:
ip@1.1.5, ip@^1.1.0, ip@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
@ -4532,9 +4532,9 @@ lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4:
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
loglevel@^1.6.4:
version "1.6.4"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.4.tgz#f408f4f006db8354d0577dcf6d33485b3cb90d56"
integrity sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g==
version "1.6.6"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.6.tgz#0ee6300cc058db6b3551fa1c4bf73b83bb771312"
integrity sha512-Sgr5lbboAUBo3eXCSPL4/KoVz3ROKquOjcctxmHIt+vol2DrqTQe3SwkKKuYhEiWB5kYa13YyopJ69deJ1irzQ==
longest@^1.0.1:
version "1.0.1"
@ -4892,9 +4892,9 @@ mongoose-legacy-pluralize@1.0.2:
integrity sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==
mongoose@^5.7.8:
version "5.7.8"
resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.7.8.tgz#ae86bff72687e8950dd95690a5eb3365ad9d57a0"
integrity sha512-GsFXYo7z3ebnIDdnqlDJYQXQFqEGS+yxfdY9G0B7fxpvUJchDpwLGuGJdVd1QbGxu2l0DscCYBO0ntl3G6OACA==
version "5.7.9"
resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.7.9.tgz#c7b7684e079749a26cdcc59b7027aae5d2b8f0f8"
integrity sha512-wXYY4+IEvplbEEeOxLVOHBGosBDNn/DYgwKzBFgsamCTvRQZHbdw88m9xUH8Srza+jdKND73/4XbQLynPseRAQ==
dependencies:
bson "~1.1.1"
kareem "2.3.1"
@ -5301,16 +5301,16 @@ opn@^5.5.0:
is-wsl "^1.1.0"
optionator@^0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=
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.4"
fast-levenshtein "~2.0.6"
levn "~0.3.0"
prelude-ls "~1.1.2"
type-check "~0.3.2"
wordwrap "~1.0.0"
word-wrap "~1.2.3"
original@^1.0.0:
version "1.0.2"
@ -5532,9 +5532,9 @@ path-to-regexp@0.1.7:
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
path-to-regexp@^1.1.1, path-to-regexp@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=
version "1.8.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
dependencies:
isarray "0.0.1"
@ -5578,9 +5578,9 @@ performance-now@^2.1.0:
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
picomatch@^2.0.5:
version "2.1.0"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.0.tgz#0fd042f568d08b1ad9ff2d3ec0f0bfb3cb80e177"
integrity sha512-uhnEDzAbrcJ8R3g2fANnSuXZMBtkpSjxTTgn2LeSiQlfmq72enQJWdQllXW24MBLYnA1SBD2vfvx2o0Zw3Ielw==
version "2.1.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.1.tgz#ecdfbea7704adb5fe6fb47f9866c4c0e15e905c5"
integrity sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==
pidtree@^0.3.0:
version "0.3.0"
@ -5928,9 +5928,9 @@ qs@6.7.0:
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
qs@^6.4.0:
version "6.9.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.0.tgz#d1297e2a049c53119cb49cca366adbbacc80b409"
integrity sha512-27RP4UotQORTpmNQDX8BHPukOnBP3p1uUJY5UnDhaJB+rMt9iMsok724XL+UHU23bEFOHRMQ2ZhI99qOWUMGFA==
version "6.9.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.1.tgz#20082c65cb78223635ab1a9eaca8875a29bf8ec9"
integrity sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==
qs@~6.5.2:
version "6.5.2"
@ -6037,9 +6037,9 @@ react-lifecycles-compat@^3.0.4:
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-popper@^1.3.3:
version "1.3.4"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.4.tgz#f0cd3b0d30378e1f663b0d79bcc8614221652ced"
integrity sha512-9AcQB29V+WrBKk6X7p0eojd1f25/oJajVdMZkywIoAV6Ag7hzE1Mhyeup2Q1QnvFRtGQFQvtqfhlEoDAPfKAVA==
version "1.3.6"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.6.tgz#32122f83af8fda01bdd4f86625ddacaf64fdd06d"
integrity sha512-kLTfa9z8n+0jJvRVal9+vIuirg41rObg4Bbrvv/ZfsGPQDN9reyVVSxqnHF1ZNgXgV7x11PeUfd5ItF8DZnqhg==
dependencies:
"@babel/runtime" "^7.1.2"
create-react-context "^0.3.0"
@ -6679,10 +6679,10 @@ sliced@1.0.1:
resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41"
integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=
smart-buffer@4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.2.tgz#5207858c3815cc69110703c6b94e46c15634395d"
integrity sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw==
smart-buffer@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba"
integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==
snapdragon-node@^2.0.1:
version "2.1.1"
@ -6742,12 +6742,12 @@ socks-proxy-agent@h3poteto/node-socks-proxy-agent#master:
socks "~2.3.2"
socks@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.2.tgz#ade388e9e6d87fdb11649c15746c578922a5883e"
integrity sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ==
version "2.3.3"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.3.tgz#01129f0a5d534d2b897712ed8aceab7ee65d78e3"
integrity sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==
dependencies:
ip "^1.1.5"
smart-buffer "4.0.2"
ip "1.1.5"
smart-buffer "^4.1.0"
source-list-map@^2.0.0:
version "2.0.1"
@ -7137,9 +7137,9 @@ terser-webpack-plugin@^1.4.1:
worker-farm "^1.7.0"
terser@^4.1.2:
version "4.3.9"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.9.tgz#e4be37f80553d02645668727777687dad26bbca8"
integrity sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==
version "4.4.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.4.0.tgz#22c46b4817cf4c9565434bfe6ad47336af259ac3"
integrity sha512-oDG16n2WKm27JO8h4y/w3iqBGAOSCtq7k8dRmrn4Wf9NouL0b2WpMHGChFGZq4nFAQy1FsNJrVQHfurXOSTmOA==
dependencies:
commander "^2.20.0"
source-map "~0.6.1"
@ -7367,9 +7367,9 @@ uglify-js@^2.6.1:
uglify-to-browserify "~1.0.0"
uglify-js@^3.6.0:
version "3.6.7"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.7.tgz#15f49211df6b8a01ee91322bbe46fa33223175dc"
integrity sha512-4sXQDzmdnoXiO+xvmTzQsfIiwrjUCSA95rSP4SEd8tDb51W2TiDOlL76Hl+Kw0Ie42PSItCW8/t6pBNCF2R48A==
version "3.6.8"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.8.tgz#5edcbcf9d49cbb0403dc49f856fe81530d65145e"
integrity sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==
dependencies:
commander "~2.20.3"
source-map "~0.6.1"
@ -7741,16 +7741,16 @@ with@^5.0.0:
acorn "^3.1.0"
acorn-globals "^3.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==
wordwrap@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=
wordwrap@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
worker-farm@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"