From 0b9aa74ae80273d927c72c993d2c4b44bcbca310 Mon Sep 17 00:00:00 2001 From: Abhishek Pathak <16938826+akpx@users.noreply.github.com> Date: Sun, 3 Feb 2019 23:33:27 -0500 Subject: [PATCH] Update (#1) * update background-jobs documentation * Fix chatInput focussing * Fix sending plaintext messages * Remove unnecessary double-read of attached media * Resolve React warnings * Fix creating new DM thread * Revert "Fix creating new DM thread" This reverts commit d8e9cc83d4c4896fea61f9dd5c145143e5f3ac17. * Fix flow * Update background-jobs.md * Don't include the req path in the datadog key This is an anti-pattern, as it'll blow up our DataDog storage costs. Instead, we should be using a logging service like Splunk to dig into specific slow requests after noticing abnormalities in the metrics. Will tackle that next most likely, but this should be a good start! Thanks to the folks in #observability for pointing that out! * Dont track connection pool size in DataDog, track query response times and sizes * Properly flow-type statsd * Add statsd logging in dev * Log instance hostname * Upgrade apollo-cache-inmemory * Better network disabled styling on input * Fix styling if quote-replying and uploading media at same time * Dont use alpha version of new apollo inmemory * Add codesandbox to explore * Add codesandbox url regex for better embeds * Add packages for deploys * Add the rest * Update react-modal to version 3.8.1 * Update electron-builder to version 20.38.4 * Focus chatinput on quote message click * Fix media messages arriving after text messages * Handle websocket connection states * Track job queue metrics * Update react-transition-group to version 2.5.2 * Eliminate warning in console due to incorrect html element nesting * Update debug to version 4.1.1 * Update debug to version 4.1.1 * Update debug to version 4.1.1 * Update debug to version 4.1.1 * Update debug to version 4.1.1 * Update debug to version 4.1.1 * Update debug to version 4.1.1 * Update validator to version 10.10.0 * Update serialize-javascript to version 1.6.1 * Remove use of Google+ APIs * Downrank watercooler threads in digests * Nicer reputation string in digest emails * Add function to check if localStorage is allowed * Trigger login modal in the joinChannel upsell if no currentUser * Remove localStorage check * Make logged out users sign in before seeing chat input * Remove unused timeInUTC variable * Fix e2e tests * Better string, fix test snapshots * Update rimraf to version 2.6.3 * Update aws-sdk to version 2.383.0 * Update aws-sdk to version 2.383.0 * Update aws-sdk to version 2.383.0 * Update aws-sdk to version 2.383.0 * Update aws-sdk to version 2.383.0 * Add missing username property in onValidationResult object `onValidationResult` function takes an object with prop error, success and username. * Make username optional * Fix flow * Remove unused variables from chatInput * Explicitly target chat input from e2e tests * Move data-cy="chat-input" to actual input * Use .clear() instead of .type() in e2e tests * Remove .only in thread_spec, fix /new/thread tests * Upgrade to draft-js-import-markdown@latest to fix code blocks * Make plaintext message editing work * edit in plaintext on the frontend * Fix flow * Fix editing e2e test * Match input font weight to rendered message font weight * Remove invalid dataCy dom node property * Change editor to allow multiline messages * Remove empty line while editing * Fix incorrect focussing on chat input while editing message * Make single line breaks work! * Upgrade to apollo-server-express@2.4.0-alpha.0 This implements query parsing and validation caching, reference https://github.com/apollographql/apollo-server/pull/2111, and should thusly provide a nice speedup for our frontend's queries! * add delete cover feature to edit community form * add coverphoto flow type * Add extra checks at send time for valid user recipient * Async await * Remove unnecessary code The code block for the `SET_SEARCH_VALUE` type and the `SET_SEARCH_VALUE_FOR_SERVER ` type are indentical. And, the `SET_SEARCH_VALUE` type isnt used in any action in the codebase. * Better keycode management * Enable flow in keycodes file * Fix flow * Focus the edit message input when edit message button is clicked * Move keycodes.js to src/helpers * Update source-map-support to version 0.5.10 * Update view error message * Fenced code blocks while editing * WIP: Implement GraphQL Rate Limiting * Make graphql-rate-limit work! * Show rate limit errors to end users * Adjust createChannel/createCommunity rate limits * Make flow happy * Use the job queue redis instance for rate limit data * Log rate limit errors to sentry in prod * Update to new version of gql-rate-limit, use string syntax * Make flow happy * Add flow stub for request-ip * Update react-transition-group to version 2.5.3 * Remove draftjs block type validation in messages This is confusing now that we have the plaintext input, and doesn't add anything as invalid blocks are treated as text blocks anyway on the frontend. * Disable rate limiting unless in prod * Gender-neutral rate limit error message, better addMessage rate limit * Update electron-builder to version 20.38.5 * Support hyperlinks in messages * Make flow happy * Update validator to version 10.11.0 * WIP: Add mention suggestions to chat input * add delete cover feature to createCommunityForm * Update validate-draft-js-input.js * Stop submitting message during IME is composing * Fix oopsie * Search users on type * It works! * Fix flow * Cleanup unnecessary code changes * Fix mentionsuggestions props * Fix dm chatinput onfocus and onblur handlers * Fix typo * Upgrade graphql-rate-limit This fixes a critical bug that is preventing some folks from posting messages * Update bull to version 3.5.3 * Update bull to version 3.5.3 * Update bull to version 3.5.3 * Update bull to version 3.5.3 * Update bull to version 3.6.0 * Update bull to version 3.6.0 * Update bull to version 3.6.0 * Update bull to version 3.6.0 * Hotfix Android thread creation * Fix hyperion memory leak Closes #4573 * Polish styles and prepopulate suggestions box * Update moment to version 2.24.0 * Update immutability-helper to version 2.9.1 * Show participants and search results in mention suggestions 1. Enter `@` 2. Participants are shown 3. Enter `@b` 4. Participants filtered and sorted by whether they have a `b` in them are shown 5. In the background, we do an API req 6. If there are users that come back from the API, we append them to the list of suggestions This is a much nicer experience than hiding the participants if you just want to filter them * Fix bug with null participant * Update graphql-tools to version 4.0.4 * Fix login redirects * Only show 10 suggestions, make sure suggestions have usernames * Pass participants to chatinput for SSR views * Darker shadow for contrast * Return original participants if user removes search query * If switched thread has no messages, remove participant suggestions * Ensure author is a suggested participant * Better styling, fewer suggestion results * Improves suggestion sort to factor in name * Fix capitalization for rendering and filtering * Fix flow * Ensure that local results will appear even if no remote results exist * Fix the default sort if search query isn't at the 0 index * Properly transform author username suggestion * Refactor to only perform suggestion transformations in one place * Provide transformation function in DM sorting * Fix flow * Workaround react-mentions bug with markup option Ideally, we would fix mentions by setting `markup="@__display__"` for the `MentionInput`. Unfortunately due to a bug in react-mentions, this doesn't work: https://github.com/signavio/react-mentions/pull/299 This works around the issue by inserting `@[username]` into the value, and then removing the brackets before sending to the server! Closes #4587 * Add component * Fix dataCy passthrough to mentionsinput * Update graphql-cost-analysis to version 1.0.3 * Update draft-js-export-markdown to fix escaped code chars * Update draft-js-import-markdown to fix link swallowing * Update draft-js-import-markdown to version 1.3.0 * Update highlight.js to version 9.14.1 * Update graphql-rate-limit to version 1.2.3 * Update aws-sdk to version 2.395.0 * Update aws-sdk to version 2.395.0 * Update aws-sdk to version 2.395.0 * Update slate to version 0.44.10 --- .circleci/config.yml | 113 +- .editorconfig | 13 - .eslintignore | 4 - .eslintrc.js | 23 +- .eslintrc.json | 33 - .flowconfig | 22 +- .github/PULL_REQUEST_TEMPLATE.md | 8 +- .gitignore | 11 +- .npmignore | 46 + .prettierignore | 2 + .prettierrc | 4 + README.md | 78 +- admin/README.md | 40 +- admin/package.json | 10 +- admin/src/components/globals/index.js | 4 +- admin/src/index.js | 2 +- .../communities/components/search/index.js | 25 +- .../views/users/components/search/index.js | 32 +- admin/yarn.lock | 4270 +++++-- analytics/index.js | 35 + analytics/models/channel.js | 10 + analytics/models/community.js | 10 + analytics/models/message.js | 10 + analytics/models/notification.js | 12 + analytics/models/reaction.js | 18 + analytics/models/thread.js | 10 + analytics/models/usersChannels.js | 26 + analytics/models/usersCommunities.js | 25 + analytics/models/usersThreads.js | 17 + analytics/package.json | 29 + analytics/queues/constants.js | 3 + analytics/queues/identify-analytics.js | 29 + analytics/queues/track-analytics.js | 32 + analytics/utils/amplitude.js | 15 + analytics/utils/getContext.js | 254 + analytics/utils/hash.js | 4 + analytics/utils/identify.js | 21 + analytics/utils/index.js | 9 + analytics/utils/track.js | 39 + analytics/utils/transformations.js | 244 + analytics/yarn.lock | 732 ++ api/apollo-server.js | 125 + api/authentication.js | 55 +- api/index.js | 117 +- api/loaders/channel.js | 11 +- api/loaders/community.js | 14 +- api/loaders/create-loader.js | 3 +- api/loaders/directMessageThread.js | 6 +- api/loaders/index.js | 26 +- api/loaders/message.js | 14 + api/loaders/reaction.js | 6 +- api/loaders/stripe.js | 7 - api/loaders/thread.js | 5 - api/loaders/threadReaction.js | 14 + api/loaders/types.js | 2 +- api/loaders/user.js | 9 +- .../20180411183454-lowercase-all-the-slugs.js | 27 + ...180428001543-reset-slack-import-records.js | 14 + ...80504003702-encrypt-existing-slack-data.js | 67 + ...180517180716-enable-private-communities.js | 17 + ...15503-add-ispending-to-userscommunities.js | 17 + ...add-join-settings-to-community-settings.js | 20 + .../20180621001409-thread-likes-table.js | 22 + ...823115847-add-users-communities-indexes.js | 61 + ...1061156-thread-metadata-denormalization.js | 57 + ...4151-fix-thread-metadata-message-counts.js | 29 + .../20181002060237-remove-payments.js | 57 + ...hread-reactions-useridandthreadid-index.js | 13 + ...rmalize-channel-community-member-counts.js | 52 + ...fications-useridandnotificationid-index.js | 16 + ...ers-notifications-userIdAndIsSeen-index.js | 13 + ...60027-update-denormalized-member-counts.js | 39 + .../20181024173616-indexes-for-digests.js | 31 + ...52-remove-attachments-from-thread-model.js | 13 + ...02025454-fix-old-image-urls-in-messages.js | 33 + ...102040518-fix-old-image-urls-in-threads.js | 71 + ...44407-fix-old-image-urls-in-communities.js | 73 + ...81102045821-fix-old-image-urls-in-users.js | 76 + ...523-fix-aws-static-url-community-photos.js | 54 + ...-add-terms-last-accepted-field-to-users.js | 17 + ...21054300-resync-community-member-counts.js | 39 + ...users-communities-useridandmember-index.js | 13 + .../20181126094455-users-channels-roles.js | 59 + ...27090014-communities-member-count-index.js | 13 + ...05171559-remove-old-users-notifications.js | 61 + ...rsthreads-user-id-and-participant-index.js | 19 + api/migrations/config.js | 22 +- api/migrations/seed/default/channels.js | 22 + api/migrations/seed/default/communities.js | 21 + api/migrations/seed/default/constants.js | 4 + .../seed/default/directMessageThreads.js | 6 + api/migrations/seed/default/index.js | 10 +- api/migrations/seed/default/messages.js | 13 - api/migrations/seed/default/notifications.js | 35 + api/migrations/seed/default/reactions.js | 13 + api/migrations/seed/default/threads.js | 130 +- api/migrations/seed/default/usersChannels.js | 39 +- .../seed/default/usersCommunities.js | 40 + .../seed/default/usersDirectMessageThreads.js | 20 +- .../seed/default/usersNotifications.js | 15 + api/migrations/seed/default/usersSettings.js | 33 + api/migrations/seed/generate.js | 2 - api/migrations/seed/index.js | 11 +- api/models/channel.js | 364 +- api/models/channelSettings.js | 154 +- api/models/community.js | 454 +- api/models/communitySettings.js | 394 +- api/models/curatedContent.js | 7 +- api/models/db.js | 52 - api/models/directMessageThread.js | 38 +- api/models/expo-push-subscription.js | 34 - api/models/invoice.js | 86 - api/models/message.js | 268 +- api/models/meta.js | 7 +- api/models/notification.js | 32 +- api/models/reaction.js | 136 +- api/models/recurringPayment.js | 126 - api/models/reputationEvents.js | 9 +- api/models/search.js | 88 +- api/models/session.js | 8 +- api/models/slackImport.js | 47 +- api/models/stripeCustomers.js | 90 - api/models/stripeInvoices.js | 20 - api/models/stripeSources.js | 29 - api/models/stripeSubscriptions.js | 21 - .../test/__snapshots__/channel.test.js.snap | 201 + api/models/test/channel.test.js | 175 + api/models/thread.js | 439 +- api/models/threadReaction.js | 162 + api/models/user.js | 469 - api/models/usersChannels.js | 456 +- api/models/usersCommunities.js | 568 +- api/models/usersDirectMessageThreads.js | 43 +- api/models/usersNotifications.js | 174 - api/models/usersSettings.js | 127 +- api/models/usersThreads.js | 144 +- api/models/utils.js | 19 +- api/models/web-push-subscription.js | 8 +- api/mutations/channel/archiveChannel.js | 100 +- api/mutations/channel/createChannel.js | 116 +- api/mutations/channel/deleteChannel.js | 116 +- .../channel/disableChannelTokenJoin.js | 51 +- api/mutations/channel/editChannel.js | 83 +- .../channel/enableChannelTokenJoin.js | 53 +- api/mutations/channel/index.js | 2 + api/mutations/channel/joinChannelWithToken.js | 92 +- .../channel/resetChannelJoinToken.js | 54 +- api/mutations/channel/restoreChannel.js | 89 +- .../channel/sendChannelEmailInvites.js | 61 - .../channel/toggleChannelNotifications.js | 69 +- .../channel/toggleChannelSubscription.js | 102 +- api/mutations/channel/togglePendingUser.js | 166 +- api/mutations/channel/unblockUser.js | 90 +- .../channel/updateChannelSlackBotLinks.js | 38 + api/mutations/community/addPaymentSource.js | 79 - api/mutations/community/cancelSubscription.js | 69 - api/mutations/community/createCommunity.js | 158 +- api/mutations/community/deleteCommunity.js | 76 +- .../community/disableBrandedLogin.js | 40 +- .../community/disableCommunityAnalytics.js | 43 - .../community/disableCommunityTokenJoin.js | 41 + api/mutations/community/editCommunity.js | 65 +- api/mutations/community/enableBrandedLogin.js | 42 +- .../community/enableCommunityAnalytics.js | 43 - .../community/enableCommunityTokenJoin.js | 41 + api/mutations/community/importSlackMembers.js | 50 + api/mutations/community/index.js | 20 +- .../community/makePaymentSourceDefault.js | 67 - api/mutations/community/pinThread.js | 78 +- .../community/removePaymentSource.js | 71 - .../community/resetCommunityJoinToken.js | 41 + .../community/saveBrandedLoginSettings.js | 56 +- api/mutations/community/sendEmailInvites.js | 77 +- api/mutations/community/sendSlackInvites.js | 133 +- .../community/updateAdministratorEmail.js | 38 +- .../communityMember/addCommunityMember.js | 81 +- .../addCommunityMemberWithToken.js | 115 + .../communityMember/addCommunityMembers.js | 65 + .../communityMember/addCommunityModerator.js | 120 +- .../addPendingCommunityMember.js | 145 + .../approvePendingCommunityMember.js | 141 + .../communityMember/blockCommunityMember.js | 145 +- .../blockPendingCommunityMember.js | 144 + api/mutations/communityMember/index.js | 12 + .../communityMember/removeCommunityMember.js | 110 +- .../removeCommunityModerator.js | 148 +- .../removePendingCommunityMember.js | 58 + .../communityMember/unblockCommunityMember.js | 128 +- .../createDirectMessageThread.js | 202 +- .../directMessageThread/setLastSeen.js | 13 +- api/mutations/message/addMessage.js | 256 +- api/mutations/message/deleteMessage.js | 111 +- api/mutations/message/editMessage.js | 130 + api/mutations/message/index.js | 2 + api/mutations/notification/index.js | 2 - .../notification/markAllNotificationsRead.js | 10 - .../notification/markAllNotificationsSeen.js | 14 +- .../markDirectMessageNotificationsSeen.js | 14 +- .../markSingleNotificationSeen.js | 14 +- api/mutations/reaction/toggleReaction.js | 22 +- .../recurringPayment/downgradeCommunity.js | 112 - .../recurringPayment/downgradeFromPro.js | 76 - api/mutations/recurringPayment/index.js | 14 - .../recurringPayment/upgradeCommunity.js | 141 - .../recurringPayment/upgradeToPro.js | 124 - api/mutations/recurringPayment/utils.js | 107 - api/mutations/thread/addThreadReaction.js | 36 + api/mutations/thread/deleteThread.js | 53 +- api/mutations/thread/editThread.js | 170 +- api/mutations/thread/index.js | 4 + api/mutations/thread/moveThread.js | 90 +- api/mutations/thread/publishThread.js | 519 +- api/mutations/thread/removeThreadReaction.js | 40 + api/mutations/thread/setThreadLock.js | 66 +- .../thread/toggleThreadNotifications.js | 35 +- api/mutations/user/banUser.js | 69 + api/mutations/user/deleteCurrentUser.js | 25 + api/mutations/user/editUser.js | 106 +- api/mutations/user/index.js | 6 + api/mutations/user/reportUser.js | 64 + api/mutations/user/subscribeExpoPush.js | 21 - api/mutations/user/subscribeWebPush.js | 30 +- .../user/toggleNotificationSettings.js | 48 +- api/mutations/user/unsubscribeWebPush.js | 28 +- api/mutations/user/updateUserEmail.js | 23 +- api/package.json | 143 +- api/queries/channel/blockedUsers.js | 21 +- api/queries/channel/channelPermissions.js | 46 +- api/queries/channel/community.js | 22 +- api/queries/channel/communityPermissions.js | 31 +- api/queries/channel/index.js | 2 + api/queries/channel/isArchived.js | 2 +- api/queries/channel/joinSettings.js | 16 +- api/queries/channel/memberConnection.js | 14 +- api/queries/channel/memberCount.js | 14 +- api/queries/channel/metaData.js | 56 +- api/queries/channel/moderators.js | 10 +- api/queries/channel/owners.js | 10 +- api/queries/channel/pendingUsers.js | 35 +- api/queries/channel/rootChannel.js | 25 +- api/queries/channel/slackSettings.js | 18 + api/queries/channel/threadConnection.js | 17 +- api/queries/channelSlackSettings/botLinks.js | 11 + api/queries/channelSlackSettings/index.js | 8 + api/queries/community/billingSettings.js | 58 - api/queries/community/brandedLogin.js | 2 +- api/queries/community/channelConnection.js | 35 +- api/queries/community/communityPermissions.js | 27 +- api/queries/community/conversationGrowth.js | 45 +- api/queries/community/coverPhoto.js | 9 + api/queries/community/hasChargeableSource.js | 25 - api/queries/community/hasFeatures.js | 50 - api/queries/community/index.js | 38 +- api/queries/community/invoices.js | 13 - api/queries/community/isPro.js | 13 - api/queries/community/joinSettings.js | 18 + api/queries/community/memberConnection.js | 30 +- api/queries/community/memberGrowth.js | 19 +- api/queries/community/members.js | 62 +- api/queries/community/metaData.js | 96 +- api/queries/community/pinnedThread.js | 20 +- api/queries/community/profilePhoto.js | 9 + api/queries/community/recurringPayments.js | 42 - .../community/rootSearchCommunities.js | 2 +- .../community/rootSearchCommunityThreads.js | 2 +- api/queries/community/slackSettings.js | 18 + api/queries/community/threadConnection.js | 31 +- api/queries/community/topAndNewThreads.js | 75 +- api/queries/community/topMembers.js | 25 +- api/queries/community/watercooler.js | 11 +- api/queries/communityMember/roles.js | 8 +- .../communitySlackSettings/hasSentInvites.js | 11 + api/queries/communitySlackSettings/index.js | 18 + .../communitySlackSettings/invitesSentAt.js | 11 + .../communitySlackSettings/isConnected.js | 11 + .../communitySlackSettings/memberCount.js | 11 + .../slackChannelList.js | 22 + .../communitySlackSettings/teamName.js | 14 + api/queries/directMessageThread/index.js | 2 + .../directMessageThread/messageConnection.js | 4 +- .../directMessageThread/participants.js | 22 +- .../rootDirectMessageThread.js | 4 +- .../rootDirectMessageThreadByUserId.js | 21 + api/queries/directMessageThread/snippet.js | 10 +- api/queries/directMessageThread/utils.js | 19 - api/queries/message/content.js | 9 + api/queries/message/index.js | 4 + api/queries/message/parent.js | 12 + api/queries/message/rootMessage.js | 4 +- api/queries/meta/isAdmin.js | 2 +- api/queries/search/search.js | 2 +- api/queries/search/searchCommunities.js | 18 +- api/queries/search/searchCommunityMembers.js | 18 +- api/queries/search/searchThreads.js | 145 +- api/queries/search/searchUsers.js | 24 +- api/queries/thread/attachments.js | 14 +- api/queries/thread/content.js | 12 + api/queries/thread/index.js | 11 +- api/queries/thread/messageConnection.js | 17 +- api/queries/thread/messageCount.js | 9 - api/queries/thread/metaImage.js | 24 + api/queries/thread/reactions.js | 36 + api/queries/thread/rootSearchThreads.js | 2 +- api/queries/thread/rootThread.js | 42 +- api/queries/user/communityConnection.js | 40 +- api/queries/user/coverPhoto.js | 15 +- api/queries/user/email.js | 7 +- api/queries/user/everything.js | 2 +- api/queries/user/index.js | 15 +- api/queries/user/invoices.js | 12 - api/queries/user/isPro.js | 14 - api/queries/user/profilePhoto.js | 15 +- api/queries/user/recurringPayments.js | 31 - api/queries/user/rootCurrentUser.js | 8 +- api/queries/user/rootSearchUsers.js | 2 +- api/queries/user/settings.js | 11 +- api/routes/api/email.js | 69 +- api/routes/api/export-user-data.js | 25 + api/routes/api/graphiql.js | 9 - api/routes/api/graphql.js | 34 - api/routes/api/index.js | 20 +- api/routes/api/slackImporter.js | 90 +- api/routes/api/stripe.js | 76 - api/routes/auth/create-signin-routes.js | 30 +- api/routes/auth/github.js | 1 + api/routes/auth/google.js | 5 +- api/routes/auth/logout.js | 2 +- api/routes/create-subscription-server.js | 30 +- api/routes/middlewares/index.js | 57 +- api/routes/webhooks/index.js | 41 - api/schema.js | 78 +- api/subscriptions/directMessageThread.js | 4 +- api/subscriptions/message.js | 3 +- api/subscriptions/notification.js | 5 +- api/subscriptions/thread.js | 6 +- api/test/__snapshots__/community.test.js.snap | 20 +- .../channel/mutations/deleteChannel.test.js | 23 +- .../__snapshots__/blockedUsers.test.js.snap | 76 +- .../memberConnection.test.js.snap | 12 +- .../__snapshots__/pendingUsers.test.js.snap | 75 +- .../__snapshots__/slackSettings.test.js.snap | 74 + api/test/channel/queries/blockedUsers.test.js | 131 +- .../channel/queries/channelSettings.test.js | 16 +- api/test/channel/queries/pendingUsers.test.js | 133 +- .../channel/queries/slackSettings.test.js | 128 + api/test/community.test.js | 4 - .../__snapshots__/slackSettings.test.js.snap | 77 + .../community/queries/slackSettings.test.js | 138 + api/test/utils.js | 11 +- api/test/utils/__mocks__/debug.js | 15 + ...reate-graphql-error-formatter.test.js.snap | 37 + .../create-graphql-error-formatter.test.js | 106 + api/types/Channel.js | 263 +- api/types/ChannelSlackSettings.js | 26 + api/types/Community.js | 607 +- api/types/CommunityMember.js | 48 +- api/types/CommunitySlackSettings.js | 18 + api/types/DirectMessageThread.js | 110 +- api/types/Invoice.js | 15 +- api/types/Message.js | 109 +- api/types/Notification.js | 137 +- api/types/Search.js | 18 +- api/types/Thread.js | 237 +- api/types/User.js | 343 +- api/types/custom-scalars/LowercaseString.js | 23 + api/types/general.js | 110 +- api/types/scalars.js | 8 +- api/utils/create-graphql-error-formatter.js | 51 +- api/utils/file-storage.js | 27 + api/utils/file-system.js | 40 + .../generate-thread-meta-image-from-text.js | 78 + api/utils/get-page-meta.js | 4 +- api/utils/get-random-default-photo.js | 4 +- api/utils/is-spectrum-url.js | 9 +- api/utils/permissions.js | 237 + api/utils/rate-limit-directive.js | 15 + api/utils/s3.js | 59 +- api/utils/session-store.js | 26 +- api/utils/validate-draft-js-input.js | 13 + api/yarn.lock | 6896 +++++++---- athena/index.js | 32 +- athena/models/channel.js | 2 +- athena/models/channelSettings.js | 13 + athena/models/community.js | 2 +- athena/models/communitySettings.js | 73 + athena/models/db.js | 30 - athena/models/directMessageThread.js | 2 +- athena/models/message.js | 2 +- athena/models/notification.js | 13 +- athena/models/recurringPayment.js | 16 - athena/models/slackImports.js | 39 - athena/models/thread.js | 2 +- athena/models/user.js | 37 - athena/models/usersChannels.js | 27 +- athena/models/usersCommunities.js | 29 +- athena/models/usersDirectMessageThreads.js | 2 +- athena/models/usersNotifications.js | 74 - athena/models/usersSettings.js | 2 +- athena/models/usersThreads.js | 15 +- athena/models/web-push-subscription.js | 2 +- athena/package.json | 31 +- athena/queues/channel-notification.js | 7 +- athena/queues/community-invite.js | 27 +- athena/queues/community-invoice-paid.js | 81 - athena/queues/community-notification.js | 7 +- athena/queues/constants.js | 19 +- .../create-thread-notification-email.js | 30 +- athena/queues/direct-message-notification.js | 28 +- athena/queues/mention-notification.js | 79 +- athena/queues/moderationEvents/message.js | 59 +- athena/queues/moderationEvents/perspective.js | 9 +- athena/queues/moderationEvents/spectrum.js | 34 - athena/queues/moderationEvents/thread.js | 58 +- .../new-message-in-thread/buffer-email.js | 416 +- .../new-message-in-thread/format-data.js | 102 +- .../new-message-in-thread/group-replies.js | 105 +- athena/queues/new-message-in-thread/index.js | 66 +- .../private-channel-request-approved.js | 52 +- athena/queues/private-channel-request-sent.js | 69 +- .../private-community-request-approved.js | 68 + .../queues/private-community-request-sent.js | 92 + athena/queues/pro-invoice-paid.js | 66 - athena/queues/reaction-notification.js | 13 +- athena/queues/send-push-notifications.js | 14 +- athena/queues/send-slack-invitations.js | 119 + athena/queues/slack-import.js | 55 - athena/queues/thread-notification.js | 196 +- athena/queues/thread-reaction-notification.js | 116 + athena/queues/track-user-thread-last-seen.js | 30 +- athena/utils/addQueue.js | 14 - athena/utils/get-email-status.js | 5 +- athena/utils/payloads.js | 4 +- athena/utils/push-notifications/index.js | 29 +- .../send-expo-push-notifications.js | 81 - athena/utils/slack/index.js | 30 + .../utils/truncateString.js | 3 +- athena/utils/types.js | 2 + athena/yarn.lock | 963 +- .../adminActiveCommunityReport.html | 69 + .../adminCommunityCreated.html | 59 + .../adminSlackImportProcessed.html | 59 + built-email-templates/adminToxicContent.html | 64 + built-email-templates/adminUserReported.html | 53 + .../adminUserSpammingThreadsNotification.html | 67 + built-email-templates/communityInvite.html | 142 + built-email-templates/mentionInMessage.html | 195 + built-email-templates/mentionInThread.html | 190 + .../newCommunityWelcome.html | 168 + built-email-templates/newDirectMessage.html | 149 + .../newRepliesInThreads.html | 232 + .../newThreadNotification.html | 197 + built-email-templates/newUserWelcome.html | 129 + .../privateChannelRequestApproved.html | 142 + .../privateChannelRequestSent.html | 154 + .../privateCommunityRequestApproved.html | 141 + .../privateCommunityRequestSent.html | 148 + ...alidate-community-administrator-email.html | 121 + built-email-templates/validate-email.html | 123 + built-email-templates/weeklyDigest.html | 225 + cacert | 21 - chronos/index.js | 72 +- chronos/jobs/index.js | 87 +- chronos/jobs/utils.js | 38 +- chronos/models/channel.js | 9 - chronos/models/community.js | 80 +- chronos/models/coreMetrics.js | 118 +- chronos/models/db.js | 30 - chronos/models/message.js | 36 +- chronos/models/reputationEvent.js | 8 +- chronos/models/thread.js | 57 +- chronos/models/usersChannels.js | 12 +- chronos/models/usersCommunities.js | 26 +- chronos/models/usersNotifications.js | 22 + chronos/models/usersSettings.js | 46 +- chronos/models/utils.js | 11 + chronos/package.json | 30 +- chronos/queues/constants.js | 17 +- .../coreMetrics/activeCommunityAdminReport.js | 78 +- chronos/queues/coreMetrics/index.js | 124 +- chronos/queues/digests/dailyDigest.js | 15 + chronos/queues/digests/getReputationString.js | 50 + .../queues/digests/getUpsellCommunities.js | 23 + chronos/queues/digests/index.js | 82 - chronos/queues/digests/processDigest.js | 62 + chronos/queues/digests/processDigestEmail.js | 85 - .../queues/digests/processIndividualDigest.js | 90 + chronos/queues/digests/processReputation.js | 33 - chronos/queues/digests/processThreads.js | 224 +- chronos/queues/digests/processUsers.js | 60 - .../message-count-string.test.js.snap | 9 + .../reputation-string.test.js.snap | 13 + .../digests/test/message-count-string.test.js | 20 + .../digests/test/reputation-string.test.js | 64 + chronos/queues/digests/types.js | 60 - chronos/queues/digests/weeklyDigest.js | 15 + .../queues/remove-seen-usersNotifications.js | 70 + chronos/types.js | 31 + chronos/yarn.lock | 893 +- config-overrides.js | 88 +- cypress.json | 9 +- cypress/integration/apps_page_spec.js | 11 + .../channel/settings/create_spec.js | 19 +- .../channel/settings/delete_spec.js | 27 +- .../integration/channel/settings/edit_spec.js | 66 +- .../channel/settings/members_spec.js | 48 + .../settings/private_invite_link_spec.js | 56 +- .../integration/channel/view/composer_spec.js | 23 +- .../integration/channel/view/members_spec.js | 11 +- .../channel/view/membership_spec.js | 74 +- .../channel/view/notifications_spec.js | 28 +- .../integration/channel/view/profile_spec.js | 91 +- .../integration/channel/view/search_spec.js | 2 +- .../integration/channel/view/threads_spec.js | 13 +- .../community/settings/create_spec.js | 126 + .../settings/private_invite_link_spec.js | 59 + .../community/view/profile_spec.js | 284 + .../community_settings_billing_spec.js | 277 - .../community_settings_overview_spec.js | 49 +- cypress/integration/community_spec.js | 40 - cypress/integration/faq_page_spec.js | 12 + cypress/integration/home_spec.js | 2 +- cypress/integration/inbox_spec.js | 9 +- cypress/integration/login_spec.js | 31 +- cypress/integration/messages_spec.js | 160 + cypress/integration/navbar_spec.js | 126 + cypress/integration/pricing_spec.js | 38 - cypress/integration/privacy_page_spec.js | 4 +- cypress/integration/terms_page_spec.js | 4 +- cypress/integration/thread/action_bar_spec.js | 171 +- cypress/integration/thread/chat_input_spec.js | 105 +- cypress/integration/thread/create_spec.js | 0 cypress/integration/thread/delete_spec.js | 0 cypress/integration/thread/edit_spec.js | 0 cypress/integration/thread/view_spec.js | 133 +- cypress/integration/thread_spec.js | 464 +- cypress/integration/user/delete_user_spec.js | 49 + cypress/integration/user/edit_user_spec.js | 55 + cypress/integration/user/me_redirect_spec.js | 39 + cypress/integration/user_spec.js | 8 +- cypress/plugins/index.js | 18 +- cypress/support/commands.js | 10 +- cypress/support/index.js | 15 +- dangerfile.js | 57 - desktop/README.md | 19 + desktop/deployment.md | 25 + desktop/package.json | 99 + desktop/resources/background.tiff | Bin 0 -> 53084 bytes desktop/resources/icons/mac/icon.icns | Bin 0 -> 1085991 bytes desktop/resources/icons/png/icon-512x512.png | Bin 0 -> 143184 bytes desktop/resources/icons/win/icon.ico | Bin 0 -> 64846 bytes desktop/resources/release-notes.md | 12 + desktop/src/autoUpdate.js | 7 + desktop/src/config.js | 29 + desktop/src/main.js | 108 + desktop/src/menu.js | 203 + desktop/src/preload.js | 33 + desktop/yarn.lock | 3255 +++++ docs/admin/intro.md | 5 + docs/api/graphql/fragments.md | 152 + docs/api/graphql/intro.md | 31 + docs/api/graphql/pagination.md | 104 + docs/api/graphql/testing.md | 40 + docs/api/graphql/tips-and-tricks.md | 20 + docs/api/intro.md | 16 + docs/backend/{graphql => api}/README.md | 0 docs/backend/{graphql => api}/fragments.md | 0 docs/backend/{graphql => api}/pagination.md | 0 docs/backend/{graphql => api}/testing.md | 0 .../{graphql => api}/tips-and-tricks.md | 2 +- docs/backend/athena/README.md | 3 - docs/backend/background-jobs.md | 7 - docs/backend/chronos/README.md | 3 - docs/backend/hermes/README.md | 3 - docs/backend/hyperion/README.md | 3 - docs/backend/mercury/README.md | 3 - docs/backend/vulcan/README.md | 3 - docs/deletingUsers.md | 54 - .../{backend/deployment.md => deployments.md} | 218 +- .../development.md} | 10 +- .../hyperion (server side rendering)/intro.md | 10 + .../importing-rethinkdb-backups.md} | 4 +- docs/operations/intro.md | 8 + docs/readme.md | 34 +- docs/testing.md | 87 - docs/testing/integration.md | 71 + docs/testing/intro.md | 10 + docs/testing/unit.md | 17 + docs/workers/analytics/intro.md | 12 + docs/workers/athena/intro.md | 9 + docs/workers/background-jobs.md | 9 + docs/workers/chronos/intro.md | 12 + docs/workers/hermes/intro.md | 8 + docs/{backend/README.md => workers/intro.md} | 31 +- docs/workers/mercury/intro.md | 7 + docs/workers/vulcan/intro.md | 7 + email-template-scripts/README.md | 23 + email-template-scripts/inline-html-emails.py | 43 + email-template-scripts/send-test-emails.js | 118 + email-template-scripts/sendgrid-sync.js | 114 + .../admin-active-community-report.json | 14 + .../admin-community-created-notification.json | 39 + .../admin-slack-import-completed.json | 23 + .../test-email-data/admin-toxic-content.json | 81 + .../admin-user-reported-alert.json | 48 + .../admin-user-spamming-threads-alert.json | 85 + .../community-admin-email-validation.json | 20 + .../test-email-data/community-created.json | 38 + .../test-email-data/community-invitation.json | 48 + .../test-email-data/digest.json | 49 + .../direct-message-received.json | 45 + .../test-email-data/mention-in-message.json | 145 + .../test-email-data/mention-in-thread.json | 114 + .../test-email-data/message-in-threads.json | 100 + .../test-email-data/new-user-welcome.json | 21 + .../private-channel-request-approved.json | 54 + .../private-channel-request-sent.json | 77 + .../private-community-request-approved.json | 46 + .../private-community-request-sent.json | 68 + .../test-email-data/thread-created.json | 101 + .../user-email-validation.json | 4 + .../adminActiveCommunityReport.html | 55 +- email-templates/adminCommunityCreated.html | 19 +- .../adminSlackImportProcessed.html | 19 +- email-templates/adminToxicContent.html | 25 +- email-templates/adminUserReported.html | 53 + .../adminUserSpammingThreadsNotification.html | 78 + .../communityCardExpiringWarning.html | 432 - email-templates/communityInvite.html | 468 +- email-templates/communityInvoiceReceipt.html | 459 - email-templates/communityPaymentFailed.html | 432 - .../communityPaymentSucceeded.html | 528 - email-templates/mentionInMessage.html | 463 +- email-templates/mentionInThread.html | 449 +- email-templates/newCommunityWelcome.html | 381 +- email-templates/newDirectMessage.html | 341 +- email-templates/newRepliesInThreads.html | 516 +- email-templates/newThreadNotification.html | 462 +- email-templates/newUserWelcome.html | 461 +- .../privateChannelRequestApproved.html | 310 +- .../privateChannelRequestSent.html | 331 +- .../privateCommunityRequestApproved.html | 431 + .../privateCommunityRequestSent.html | 439 + email-templates/proInvoiceReceipt.html | 459 - ...alidate-community-administrator-email.html | 448 +- email-templates/validate-email.html | 452 +- email-templates/weeklyDigest.html | 509 +- flow-typed/npm/@sendgrid/mail_vx.x.x.js | 52 + flow-typed/npm/amplitude_vx.x.x.js | 32 + flow-typed/npm/apollo-link-schema_vx.x.x.js | 53 + .../npm/apollo-server-express_vx.x.x.js | 53 + flow-typed/npm/b2a_vx.x.x.js | 32 + flow-typed/npm/bluebird_vx.x.x.js | 312 + flow-typed/npm/cryptr_vx.x.x.js | 38 + flow-typed/npm/datadog-metrics_vx.x.x.js | 59 + flow-typed/npm/dataloader_vx.x.x.js | 33 + flow-typed/npm/decode-uri-component_vx.x.x.js | 33 + .../npm/draft-js-export-markdown_vx.x.x.js | 39 + ...x.x.js => electron-context-menu_vx.x.x.js} | 8 +- flow-typed/npm/electron-is-dev_vx.x.x.js | 33 + flow-typed/npm/electron-updater_vx.x.x.js | 186 + ...x.x.js => electron-window-state_vx.x.x.js} | 8 +- flow-typed/npm/electron_vx.x.x.js | 45 + flow-typed/npm/express-enforces-ssl_vx.x.x.js | 33 + flow-typed/npm/express-hot-shots_vx.x.x.js | 53 + flow-typed/npm/graphql-rate-limit_vx.x.x.js | 130 + flow-typed/npm/helmet_vx.x.x.js | 33 + flow-typed/npm/host-validation_vx.x.x.js | 45 + flow-typed/npm/hot-shots_vx.x.x.js | 52 + flow-typed/npm/hpp_vx.x.x.js | 32 + flow-typed/npm/hsts_vx.x.x.js | 33 + flow-typed/npm/idx_v2.x.x.js | 8 + flow-typed/npm/ioredis_v3.x.x.js | 765 -- flow-typed/npm/ioredis_vx.x.x.js | 205 +- flow-typed/npm/is-electron_vx.x.x.js | 33 + flow-typed/npm/markdown-draft-js_vx.x.x.js | 95 + flow-typed/npm/ms_vx.x.x.js | 33 + flow-typed/npm/query-string_v5.x.x.js | 21 - flow-typed/npm/query-string_vx.x.x.js | 33 + flow-typed/npm/raf_vx.x.x.js | 52 + flow-typed/npm/react-hot-loader_vx.x.x.js | 73 + flow-typed/npm/react-image_vx.x.x.js | 88 + flow-typed/npm/react-mentions_vx.x.x.js | 151 + .../react-navigation-props-mapper_vx.x.x.js | 18 - flow-typed/npm/react-popper_vx.x.x.js | 123 + .../npm/react-visibility-sensor_vx.x.x.js | 116 + flow-typed/npm/redis-tag-cache_vx.x.x.js | 36 + flow-typed/npm/request-ip_vx.x.x.js | 32 + flow-typed/npm/rethinkhaberdashery_vx.x.x.js | 480 + flow-typed/npm/sanitize-filename_vx.x.x.js | 38 + .../npm/{abab_vx.x.x.js => sha1_vx.x.x.js} | 26 +- flow-typed/npm/string-similarity_vx.x.x.js | 46 + flow-typed/react-native.js | 2 +- hermes/README.md | 27 +- hermes/events/index.js | 37 + hermes/index.js | 94 +- hermes/models/usersSettings.js | 32 + hermes/package.json | 30 +- hermes/queues/constants.js | 102 +- ...end-admin-active-community-report-email.js | 53 +- .../send-admin-community-created-email.js | 25 +- .../queues/send-admin-slack-import-email.js | 26 +- .../queues/send-admin-toxic-content-email.js | 39 +- .../queues/send-admin-user-reported-email.js | 47 + ...ser-spamming-threads-notification-email.js | 61 + ...nd-administrator-email-validation-email.js | 43 +- ...d-community-card-expiring-warning-email.js | 45 - hermes/queues/send-community-invite-email.js | 53 +- .../send-community-invoice-receipt-email.js | 32 - .../send-community-payment-failed-email.js | 53 - .../send-community-payment-succeeded-email.js | 115 - hermes/queues/send-digest-email.js | 93 +- hermes/queues/send-email-validation-email.js | 34 +- hermes/queues/send-mention-message-email.js | 35 +- hermes/queues/send-mention-thread-email.js | 34 +- .../send-new-community-welcome-email.js | 17 +- .../queues/send-new-direct-message-email.js | 53 +- hermes/queues/send-new-message-email.js | 86 +- hermes/queues/send-new-thread-email.js | 26 +- ...-private-channel-request-approved-email.js | 39 +- ...send-private-channel-request-sent-email.js | 43 +- ...rivate-community-request-approved-email.js | 48 + ...nd-private-community-request-sent-email.js | 50 + .../queues/send-pro-invoice-receipt-email.js | 29 - hermes/queues/send-user-welcome-email.js | 64 +- hermes/queues/sendgrid-webhook-events.js | 32 + hermes/send-email.js | 118 +- hermes/user-can-receive-email.js | 38 + hermes/utils/smarten-string.js | 9 + hermes/yarn.lock | 1061 +- hyperion/cache.js | 46 - hyperion/create-cache-stream.js | 24 - hyperion/index.js | 108 +- hyperion/redis.js | 32 - hyperion/renderer/browser-shim.js | 1 + hyperion/renderer/html-template.js | 53 +- hyperion/renderer/index.js | 80 +- index.html | 54 - jest.config.js | 3 +- mercury/constants.js | 6 + mercury/functions/processMessageCreated.js | 4 +- mercury/functions/processMessageDeleted.js | 4 +- mercury/functions/processReactionCreated.js | 4 +- mercury/functions/processReactionDeleted.js | 4 +- mercury/functions/processThreadCreated.js | 4 +- mercury/functions/processThreadDeleted.js | 4 +- .../processThreadDeletedByModeration.js | 5 +- .../functions/processThreadReactionCreated.js | 24 + .../functions/processThreadReactionDeleted.js | 24 + mercury/functions/types.js | 6 - mercury/index.js | 17 +- mercury/models/db.js | 32 - mercury/models/message.js | 2 +- mercury/models/reaction.js | 2 +- mercury/models/reputationEvent.js | 2 +- mercury/models/thread.js | 98 +- mercury/models/usersCommunities.js | 5 +- mercury/package.json | 20 +- mercury/queues/calculateThreadScore.js | 87 + mercury/queues/processReputationEvent.js | 23 +- mercury/yarn.lock | 472 +- mobile/.expo/Exponent.app/.gitkeep | 0 mobile/.expo/settings.json | 7 - mobile/.gitignore | 3 - mobile/.idea/mobile.iml | 9 - mobile/.idea/modules.xml | 8 - mobile/.idea/workspace.xml | 180 - mobile/App.js | 95 - mobile/actions/authentication.js | 21 - mobile/app.json | 37 - mobile/assets/icon.png | Bin 2976 -> 0 bytes mobile/assets/splash.png | Bin 7178 -> 0 bytes mobile/components/.gitkeep | 0 mobile/components/Anchor/index.js | 32 - mobile/components/Avatar/image.js | 191 - .../components/Avatar/img/default_avatar.svg | 73 - .../Avatar/img/default_community.svg | 34 - mobile/components/Avatar/index.js | 44 - mobile/components/Avatar/style.js | 48 - mobile/components/Avatar/utils.js | 24 - mobile/components/Codeblock/index.js | 15 - mobile/components/IFrame/index.js | 18 - mobile/components/InfiniteList/index.js | 101 - mobile/components/Login/index.js | 56 - mobile/components/Messages/index.js | 123 - mobile/components/SafeAreaView/index.js | 18 - mobile/components/Text/index.js | 44 - mobile/components/ThreadContent/index.js | 13 - mobile/components/ThreadContent/renderer.js | 92 - mobile/components/ThreadFeed/index.js | 136 - mobile/components/ThreadFeed/style.js | 0 mobile/components/ThreadItem/Facepile.js | 113 - .../ThreadItem/ThreadCommunityInfo.js | 2 - mobile/components/ThreadItem/index.js | 82 - mobile/components/ThreadItem/style.js | 88 - mobile/components/ViewNetworkHandler/index.js | 92 - mobile/components/theme/index.js | 76 - mobile/jest.config.js | 23 - mobile/package.json | 57 - mobile/reducers/authentication.js | 30 - mobile/reducers/index.js | 11 - mobile/rn-cli.config.js | 5 - mobile/setupTests.js | 16 - mobile/utils/get-push-notification-token.js | 29 - mobile/utils/platform.js | 16 - mobile/views/Channel/index.js | 83 - mobile/views/Channel/style.js | 9 - mobile/views/Community/index.js | 86 - mobile/views/Community/style.js | 9 - mobile/views/Notifications/index.js | 169 - mobile/views/Splash/index.js | 51 - mobile/views/Splash/style.js | 9 - mobile/views/TabBar/BaseStack.js | 40 - mobile/views/TabBar/HomeStack.js | 28 - mobile/views/TabBar/NotificationsStack.js | 23 - mobile/views/TabBar/index.js | 32 - mobile/views/Thread/index.js | 71 - mobile/views/Thread/style.js | 9 - mobile/views/User/index.js | 107 - mobile/views/User/style.js | 9 - mobile/views/__e2e__/splash.spec.js | 13 - mobile/views/__tests__/.gitkeep | 0 mobile/yarn.lock | 7135 ----------- now-secrets.example.json | 3 +- now.json | 93 +- package.json | 281 +- pluto/changefeeds/analytics.js | 27 - pluto/changefeeds/community.js | 72 - pluto/changefeeds/index.js | 50 - pluto/changefeeds/moderator.js | 29 - pluto/changefeeds/openSourceStatus.js | 45 - pluto/changefeeds/privateChannel.js | 102 - pluto/changefeeds/support.js | 28 - pluto/create-worker-with-express.js | 17 - pluto/index.js | 160 - pluto/models/channel.js | 10 - pluto/models/community.js | 53 - pluto/models/stripeCustomers.js | 27 - pluto/models/usersCommunities.js | 11 - pluto/models/utils.js | 65 - pluto/package.json | 25 - pluto/queues/constants.js | 64 - .../processAdministratorEmailChanged.js | 52 - pluto/queues/processAnalyticsAdded.js | 77 - pluto/queues/processAnalyticsRemoved.js | 74 - pluto/queues/processCardExpiringWarning.js | 78 - pluto/queues/processCommunityCreated.js | 45 - pluto/queues/processCommunityDeleted.js | 39 - pluto/queues/processCommunityEdited.js | 44 - pluto/queues/processModeratorAdded.js | 126 - pluto/queues/processModeratorRemoved.js | 104 - pluto/queues/processOssStatusActivated.js | 166 - pluto/queues/processOssStatusDisabled.js | 176 - pluto/queues/processOssStatusEnabled.js | 166 - pluto/queues/processPaymentFailed.js | 87 - pluto/queues/processPaymentSucceeded.js | 94 - pluto/queues/processPrioritySupportAdded.js | 76 - pluto/queues/processPrioritySupportRemoved.js | 75 - pluto/queues/processPrivateChannelAdded.js | 131 - pluto/queues/processPrivateChannelRemoved.js | 104 - pluto/queues/processStripeChargeWebhook.js | 32 - pluto/queues/processStripeCustomerWebhook.js | 57 - pluto/queues/processStripeDiscountWebhook.js | 19 - pluto/queues/processStripeInvoiceWebhook.js | 67 - pluto/queues/processStripeSourceWebhook.js | 32 - .../processStripeSubscriptionWebhook.js | 19 - pluto/queues/removeAllPaidFeatures.js | 20 - pluto/webhooks/index.js | 97 - pluto/yarn.lock | 683 -- public/_redirects | 1 - public/img/payment-methods/amex.svg | 11 - public/img/payment-methods/diners-club.svg | 11 - public/img/payment-methods/discover.svg | 12 - public/img/payment-methods/jcb.svg | 15 - public/img/payment-methods/mastercard.svg | 12 - public/img/payment-methods/visa.svg | 11 - public/img/slack_colored.png | Bin 0 -> 6141 bytes public/img/stripe-logo.png | Bin 1170 -> 0 bytes public/img/waterfall.png | Bin 0 -> 84634 bytes public/index.html | 69 +- shared/analytics/README.md | 66 + shared/analytics/event-types/channel.js | 40 + shared/analytics/event-types/community.js | 44 + shared/analytics/event-types/directMessage.js | 10 + .../event-types/directMessageThreads.js | 10 + shared/analytics/event-types/emails.js | 3 + shared/analytics/event-types/index.js | 52 + shared/analytics/event-types/message.js | 11 + shared/analytics/event-types/misc.js | 3 + shared/analytics/event-types/navigation.js | 8 + shared/analytics/event-types/notifications.js | 5 + shared/analytics/event-types/page-apps.js | 11 + shared/analytics/event-types/page-explore.js | 7 + shared/analytics/event-types/page-features.js | 4 + shared/analytics/event-types/page-home.js | 8 + shared/analytics/event-types/page-inbox.js | 7 + shared/analytics/event-types/page-login.js | 5 + shared/analytics/event-types/page-support.js | 8 + shared/analytics/event-types/pwa.js | 5 + shared/analytics/event-types/reaction.js | 6 + shared/analytics/event-types/search.js | 6 + shared/analytics/event-types/thread.js | 48 + .../analytics/event-types/threadReaction.js | 6 + shared/analytics/event-types/user.js | 101 + .../analytics/event-types/userOnboarding.js | 9 + .../event-types/web-push-notification.js | 9 + shared/analytics/index.js | 4 + shared/bull/create-queue.js | 56 +- shared/bull/create-worker.js | 39 +- shared/bull/queues.js | 206 +- shared/bull/types.js | 486 +- shared/cache/redis.js | 13 + shared/changefeed-utils/index.js | 12 +- shared/clients/analytics/index.js | 29 + shared/clients/analytics/setUser.js | 23 + shared/clients/analytics/track.js | 29 + shared/clients/analytics/transformations.js | 94 + shared/clients/analytics/unsetUser.js | 23 + .../clients/draft-js/links-decorator/core.js | 9 +- .../draft-js/links-decorator/index.native.js | 3 - .../draft-js/mentions-decorator/core.js | 45 +- .../mentions-decorator/index.native.js | 26 - .../draft-js/mentions-decorator/index.web.js | 2 +- .../mentions-decorator/test/core.test.js | 36 + .../clients/draft-js/message/renderer.web.js | 47 +- .../test/__snapshots__/renderer.test.js.snap | 8 +- .../draft-js/message/test/renderer.test.js | 8 +- shared/clients/draft-js/message/types.js | 13 + shared/clients/draft-js/utils/isShort.js | 14 + shared/clients/group-messages.js | 18 +- .../test/__snapshots__/messages.test.js.snap | 7 +- shared/db/constants.js | 17 + shared/db/create-query.js | 91 + shared/db/db.js | 92 + shared/db/index.js | 5 + shared/db/queries/channel.js | 14 + shared/db/queries/community.js | 26 + shared/db/queries/message.js | 8 + shared/db/queries/notifications.js | 11 + shared/db/queries/thread.js | 8 + shared/db/queries/user.js | 757 ++ shared/db/queries/usersNotifications.js | 179 + shared/db/queries/utils/trackNotifications.js | 20 + shared/db/query-cache.js | 24 + shared/draft-utils.js | 5 + shared/encryption/index.js | 34 + shared/get-mentions.js | 5 +- shared/graphql-cache-keys.js | 7 + shared/graphql/constants.js | 10 + .../channel/channelMemberConnection.js | 3 +- .../fragments/channel/channelMetaData.js | 2 + ...unityChannelConnectionWithSlackSettings.js | 39 + .../fragments/community/communityInfo.js | 4 + .../fragments/community/communityMetaData.js | 2 + .../fragments/community/communitySettings.js | 83 +- .../community/communityThreadConnection.js | 2 +- .../communityMember/communityMemberInfo.js | 2 + .../directMessageThreadInfo.js | 18 +- .../graphql/fragments/invoice/invoiceInfo.js | 22 - .../fragments/message/directMessageInfo.js | 28 + .../graphql/fragments/message/messageInfo.js | 26 +- .../notification/notificationInfo.js | 11 +- shared/graphql/fragments/thread/threadInfo.js | 23 +- .../thread/threadMessageConnection.js | 2 +- .../fragments/user/userCommunityConnection.js | 6 - shared/graphql/fragments/user/userInfo.js | 4 +- .../fragments/user/userRecurringPayments.js | 24 - shared/graphql/index.js | 15 +- .../mutations/channel/createChannel.js | 7 + .../channel/toggleChannelSubscription.js | 6 + .../channel/updateChannelSlackBotLinks.js | 48 + .../mutations/channel/updateSlackSettings.js | 46 + .../mutations/community/addPaymentSource.js | 45 - .../mutations/community/cancelSubscription.js | 68 - .../community/disableCommunityAnalytics.js | 90 - .../community/disableCommunityTokenJoin.js | 48 + .../mutations/community/downgradeCommunity.js | 53 - .../community/enableCommunityAnalytics.js | 70 - .../community/enableCommunityTokenJoin.js | 48 + .../community/makePaymentSourceDefault.js | 48 - .../community/removePaymentSource.js | 45 - .../community/resetCommunityJoinToken.js | 48 + .../mutations/community/sendSlackInvites.js | 47 + .../community/updateAdministratorEmail.js | 8 - .../mutations/community/upgradeCommunity.js | 55 - .../communityMember/addCommunityMember.js | 6 + .../addCommunityMemberWithToken.js | 43 + .../communityMember/addCommunityMembers.js | 51 + .../addPendingCommunityMember.js | 38 + .../approvePendingCommunityMember.js | 39 + .../blockPendingCommunityMember.js | 39 + .../communityMember/removeCommunityMember.js | 6 + .../removePendingCommunityMember.js | 40 + .../createDirectMessageThread.js | 13 +- .../mutations/message/deleteMessage.js | 9 +- .../graphql/mutations/message/editMessage.js | 44 + .../mutations/message/sendDirectMessage.js | 15 +- .../graphql/mutations/message/sendMessage.js | 34 +- .../mutations/reaction/toggleReaction.js | 63 +- .../slackImport/sendSlackInvitations.js | 46 - .../mutations/thread/addThreadReaction.js | 81 + .../graphql/mutations/thread/publishThread.js | 39 +- .../mutations/thread/removeThreadReaction.js | 78 + shared/graphql/mutations/user/banUser.js | 27 + .../mutations/user/deleteCurrentUser.js | 17 + .../mutations/user/downgradeFromPro.js | 31 - shared/graphql/mutations/user/editUser.js | 7 + shared/graphql/mutations/user/reportUser.js | 27 + .../mutations/user/subscribeExpoPush.js | 22 - .../graphql/mutations/user/updateUserEmail.js | 2 +- shared/graphql/mutations/user/upgradeToPro.js | 36 - shared/graphql/queries/channel/getChannel.js | 18 +- .../queries/channel/getChannelBlockedUsers.js | 2 +- .../channel/getChannelMemberConnection.js | 7 +- .../channel/getChannelThreadConnection.js | 5 +- .../queries/community/getCommunities.js | 12 +- .../graphql/queries/community/getCommunity.js | 8 +- .../getCommunityChannelConnection.js | 6 - .../getCommunityChannelsSlackSettings.js | 52 + .../queries/community/getCommunityInvoices.js | 40 - .../queries/community/getCommunityMembers.js | 2 + .../getCommunityRecurringPayments.js | 50 - .../queries/community/getCommunitySettings.js | 4 +- .../community/getCommunitySlackSettings.js | 42 + .../community/getCommunityThreadConnection.js | 28 +- .../community/getCommunityTopAndNewThreads.js | 5 +- .../community/getCommunityTopMembers.js | 32 +- .../communityMember/getCommunityMember.js | 8 +- .../getComposerCommunitiesAndChannels.js | 2 - .../getCurrentUserDMThreadConnection.js | 110 +- .../getDirectMessageThreadByUserId.js | 32 + ...getDirectMessageThreadMessageConnection.js | 15 +- shared/graphql/queries/message/getMessage.js | 31 + .../getDirectMessageNotifications.js | 6 +- .../queries/notification/getNotifications.js | 3 +- .../graphql/queries/search/searchThreads.js | 1 + .../queries/slackImport/getSlackImport.js | 37 - .../thread/getThreadMessageConnection.js | 59 +- .../user/getCurrentUserEverythingFeed.js | 7 +- .../queries/user/getCurrentUserInvoices.js | 40 - .../user/getCurrentUserRecurringPayments.js | 27 - shared/graphql/queries/user/getUser.js | 2 +- .../user/getUserCommunityConnection.js | 1 - .../queries/user/getUserThreadConnection.js | 21 +- shared/graphql/schema.json | 15 +- shared/imgix/getDefaultExpires.js | 17 + shared/imgix/index.js | 24 + shared/imgix/sign.js | 72 + shared/imgix/signCommunity.js | 14 + shared/imgix/signMessage.js | 18 + shared/imgix/signThread.js | 51 + shared/imgix/signUser.js | 21 + shared/install-dependencies.js | 2 +- shared/middlewares/cors.js | 17 +- shared/middlewares/csrf.js | 30 + shared/middlewares/error-handler.js | 21 + shared/middlewares/logging.js | 6 +- shared/middlewares/security.js | 134 + shared/middlewares/session.js | 8 +- shared/middlewares/statsd.js | 19 + shared/middlewares/thread-param.js | 36 +- shared/middlewares/toobusy.js | 13 +- shared/normalize-url.js | 39 +- .../notification-to-text.js | 57 +- shared/only-contains-emoji.js | 45 +- shared/raven/index.js | 20 +- shared/regexps.js | 2 +- shared/slug-blacklists.js | 11 + shared/statsd.js | 100 + shared/stripe/index.js | 5 - shared/stripe/types/charge.js | 58 - shared/stripe/types/chargeEvent.js | 11 - shared/stripe/types/customer.js | 37 - shared/stripe/types/customerEvent.js | 11 - shared/stripe/types/discount.js | 17 - shared/stripe/types/invoice.js | 45 - shared/stripe/types/invoiceEvent.js | 11 - shared/stripe/types/invoiceItem.js | 24 - shared/stripe/types/plan.js | 15 - shared/stripe/types/source.js | 46 - shared/stripe/types/sourceEvent.js | 11 - shared/stripe/types/subscription.js | 45 - shared/stripe/types/subscriptionEvent.js | 11 - shared/stripe/types/subscriptionItem.js | 11 - shared/stripe/utils.js | 407 - .../notification-to-text.test.js.snap | 24 +- shared/test/encryption.test.js | 30 + .../test/fixtures/CHANNEL_CREATED.json | 0 .../test/fixtures/COMMUNITY_INVITE.json | 0 .../test/fixtures/DIRECT_MESSAGE_CREATED.json | 0 .../test/fixtures/MEDIA_MESSAGE_CREATED.json | 28 + .../test/fixtures/MENTION_MESSAGE.json | 0 .../test/fixtures/MENTION_THREAD.json | 0 .../test/fixtures/MESSAGE_CREATED.json | 0 .../test/fixtures/REACTION_CREATED.json | 0 .../test/fixtures/THREAD_CREATED.json | 0 .../fixtures/THREAD_REACTION_CREATED.json | 28 + .../test/fixtures/USER_JOINED_COMMUNITY.json | 0 shared/test/get-mentions.test.js | 16 + shared/test/normalize-url.test.js | 4 + .../test/notification-to-text.test.js | 18 +- shared/testing/data.js | 4 + shared/testing/db.js | 2 +- shared/testing/setup-test-framework.js | 2 +- shared/testing/setup.js | 25 +- shared/testing/teardown.js | 5 +- {src/components => shared}/theme/index.js | 22 +- shared/time-difference.js | 114 +- shared/time-formatting.js | 49 + shared/truthy-values.js | 9 + shared/types.js | 143 +- src/actions/authentication.js | 69 +- src/actions/composer.js | 4 +- src/actions/message.js | 16 + src/actions/modals.js | 6 +- src/actions/toasts.js | 3 +- src/api/constants.js | 24 +- src/components/appViewWrapper/index.js | 5 +- src/components/avatar/communityAvatar.js | 77 + src/components/avatar/hoverProfile.js | 154 - src/components/avatar/image.js | 221 +- src/components/avatar/index.js | 87 +- src/components/avatar/style.js | 124 +- src/components/avatar/userAvatar.js | 142 + src/components/badges/index.js | 92 +- src/components/badges/style.js | 52 +- src/components/buttons/index.js | 32 +- src/components/buttons/style.js | 11 +- src/components/card/index.js | 3 +- .../chatInput/components/mediaUploader.js | 74 +- src/components/chatInput/components/style.js | 8 +- src/components/chatInput/index.js | 751 +- src/components/chatInput/input.js | 125 - src/components/chatInput/style.js | 220 +- src/components/composer/index.js | 282 +- src/components/composer/style.js | 23 +- src/components/conditionalWrap/index.js | 24 + .../draft-js-plugins-editor/index.js | 97 + src/components/dropdown/index.js | 3 +- src/components/editForm/index.js | 23 - src/components/editForm/style.js | 53 +- src/components/emailInvitationForm/index.js | 22 +- src/components/emailInvitationForm/style.js | 17 +- src/components/error/ErrorBoundary.js | 43 + src/components/error/SettingsFallback.js | 44 + src/components/error/index.js | 5 +- src/components/flyout/index.js | 11 +- src/components/formElements/index.js | 81 +- src/components/formElements/style.js | 74 +- src/components/fullscreenView/index.js | 4 +- src/components/fullscreenView/style.js | 4 +- src/components/gallery/browser.js | 35 +- src/components/gallery/index.js | 2 +- src/components/gallery/style.js | 11 +- src/components/githubProfile/index.js | 13 +- src/components/globals/index.js | 92 +- src/components/goop/index.js | 2 +- src/components/granularUserProfile/index.js | 164 +- src/components/granularUserProfile/style.js | 114 +- src/components/head/index.js | 10 +- .../hoverProfile/channelContainer.js | 123 + src/components/hoverProfile/channelProfile.js | 121 + .../hoverProfile/communityContainer.js | 133 + .../hoverProfile/communityProfile.js | 115 + src/components/hoverProfile/index.js | 6 + .../hoverProfile/loadingHoverProfile.js | 23 + src/components/hoverProfile/style.js | 139 + src/components/hoverProfile/userContainer.js | 144 + src/components/hoverProfile/userProfile.js | 109 + src/components/icons/index.js | 309 +- src/components/illustrations/index.js | 1414 ++- .../infiniteScroll/deduplicateChildren.js | 11 + src/components/infiniteScroll/index.js | 225 +- .../infiniteScroll/tallViewports.js | 33 + src/components/link/index.js | 35 - src/components/linkPreview/index.js | 76 - src/components/linkPreview/style.js | 188 - src/components/listItems/channel/index.js | 38 + src/components/listItems/channel/style.js | 56 + src/components/listItems/index.js | 199 +- src/components/listItems/style.js | 33 +- src/components/loading/index.js | 247 +- src/components/loading/style.js | 165 +- src/components/loginButtonSet/github.js | 2 +- src/components/loginButtonSet/index.js | 2 + src/components/loginButtonSet/style.js | 69 +- src/components/maintenance/index.js | 9 +- src/components/mediaInput/style.js | 7 +- src/components/mentionsInput/index.js | 141 + .../mentionsInput/mentionSuggestion.js | 28 + src/components/mentionsInput/style.js | 50 + src/components/menu/index.js | 23 +- src/components/menu/style.js | 34 +- src/components/message/authorByline.js | 81 + src/components/message/editingBody.js | 129 + src/components/message/index.js | 452 +- .../message/messageErrorFallback.js | 27 + src/components/message/style.js | 539 +- src/components/message/view.js | 194 +- src/components/messageGroup/index.js | 277 +- src/components/messageGroup/style.js | 75 +- .../index.js | 135 - .../style.js | 17 - src/components/modals/BanUserModal/index.js | 149 + src/components/modals/BanUserModal/style.js | 30 + .../modals/ChangeChannelModal/index.js | 7 +- .../modals/ChangeChannelModal/style.js | 5 +- .../modals/ChatInputLoginModal/index.js | 36 +- .../modals/ChatInputLoginModal/style.js | 18 + .../modals/CreateChannelModal/index.js | 48 +- .../modals/DeleteDoubleCheckModal/index.js | 59 +- .../modals/RepExplainerModal/index.js | 31 +- .../modals/RepExplainerModal/style.js | 17 +- .../modals/ReportUserModal/index.js | 163 + .../modals/ReportUserModal/style.js | 21 + .../modals/RestoreChannelModal/index.js | 46 +- .../modals/UpgradeAnalyticsModal/index.js | 119 - .../modals/UpgradeAnalyticsModal/style.js | 24 - src/components/modals/UpgradeModal/index.js | 166 - src/components/modals/UpgradeModal/style.js | 186 - .../modals/UpgradeModeratorSeatModal/index.js | 117 - .../modals/UpgradeModeratorSeatModal/style.js | 24 - .../modals/components/poweredByStripe.js | 23 - src/components/modals/modalRoot.js | 16 +- src/components/modals/styles.js | 42 +- src/components/newActivityIndicator/index.js | 15 +- src/components/nextPageButton/index.js | 2 +- src/components/nextPageButton/style.js | 7 +- src/components/outsideClickHandler/index.js | 10 +- src/components/profile/channel.js | 256 - src/components/profile/community.js | 291 +- src/components/profile/coverPhoto.js | 15 +- src/components/profile/index.js | 8 - src/components/profile/style.js | 106 +- src/components/profile/thread.js | 40 +- src/components/profile/user.js | 204 +- src/components/reaction/index.js | 107 +- src/components/reaction/style.js | 130 +- src/components/redirectHandler/index.js | 36 + src/components/reputation/index.js | 13 +- src/components/reputation/style.js | 5 +- .../Embed.js | 21 +- .../Image.js | 5 +- .../rich-text-editor/LanguageSelect.js | 62 + .../container.js} | 187 +- src/components/rich-text-editor/index.js | 11 + .../prism-theme.css | 0 .../style.js | 104 +- .../toolbar.js | 13 +- src/components/scrollRow/index.js | 2 +- src/components/scrollRow/style.js | 18 +- src/components/segmentedControl/index.js | 11 +- src/components/settingsViews/header.js | 24 +- src/components/settingsViews/style.js | 93 +- src/components/settingsViews/subnav.js | 2 +- src/components/stripeCardForm/form.js | 78 - src/components/stripeCardForm/index.js | 88 - src/components/stripeCardForm/modalWell.js | 144 - src/components/stripeCardForm/style.js | 54 - src/components/themedSection/index.js | 72 +- .../thirdPartyContextSetting/index.js | 45 + .../threadComposer/components/composer.js | 261 +- .../threadComposer/components/placeholder.js | 3 +- src/components/threadComposer/style.js | 37 +- src/components/threadFeed/index.js | 258 +- src/components/threadFeed/style.js | 27 +- src/components/threadFeedCard/facePile.js | 49 - .../threadFeedCard/formattedThreadLocation.js | 70 - src/components/threadFeedCard/index.js | 95 - src/components/threadFeedCard/style.js | 41 +- src/components/threadLikes/index.js | 94 + src/components/threadLikes/style.js | 54 + src/components/toasts/index.js | 10 +- src/components/toasts/style.js | 23 +- .../toggleChannelMembership/index.js | 7 +- .../toggleChannelNotifications/index.js | 3 +- .../toggleCommunityMembership/index.js | 40 +- src/components/upsell/index.js | 195 +- src/components/upsell/joinChannel.js | 46 +- src/components/upsell/newUserUpsell.js | 15 +- src/components/upsell/newUserUpsellStyles.js | 7 +- src/components/upsell/requestToJoinChannel.js | 15 +- src/components/upsell/style.js | 120 +- src/components/userEmailConfirmation/index.js | 6 +- src/components/usernameSearch/index.js | 167 + src/components/usernameSearch/style.js | 91 + src/components/viewError/style.js | 7 +- src/components/visuallyHidden/index.js | 12 + src/components/withCurrentUser/index.js | 72 + src/helpers/analytics/index.js | 16 + src/helpers/consolidate-streamed-styles.js | 5 - src/helpers/desktop-app-utils.js | 23 + src/helpers/directMessageThreads.js | 1 + src/helpers/events.js | 61 - src/helpers/get-thread-link.js | 11 + src/helpers/images.js | 19 - src/helpers/is-admin.js | 9 + src/helpers/is-os.js | 12 + src/helpers/is-viewing-marketing-page.js | 25 + src/helpers/keycodes.js | 9 + src/helpers/localStorage.js | 6 + src/helpers/notifications.js | 5 +- src/helpers/realtimeThreads.js | 3 +- src/helpers/regexps.js | 6 +- .../render-text-with-markdown-links.js | 12 + src/helpers/sentry-redux-middleware.js | 28 +- src/helpers/signed-out-fallback.js | 38 +- src/helpers/utils.js | 167 +- src/hooks/useConnectionRestored.js | 54 + src/hot-routes.js | 7 + src/index.js | 107 +- src/reducers/connectionStatus.js | 23 +- src/reducers/dashboardFeed.js | 8 - src/reducers/index.js | 9 +- src/reducers/message.js | 25 + src/reducers/newUserOnboarding.js | 2 +- src/reducers/users.js | 16 - src/reset.css.js | 31 +- src/routes.js | 165 +- src/store/index.js | 35 +- src/subscribe-to-desktop-push.js | 54 + src/views/authViewHandler/index.js | 96 +- src/views/channel/components/memberGrid.js | 79 +- .../channel/components/notificationsToggle.js | 2 +- .../components/pendingUsersNotification.js | 2 +- src/views/channel/components/search.js | 2 +- src/views/channel/components/style.js | 5 +- src/views/channel/index.js | 318 +- src/views/channel/style.js | 117 +- .../channelSettings/components/archiveForm.js | 41 +- .../components/blockedUsers.js | 147 +- .../components/channelMembers.js | 88 +- .../components/editDropdown.js | 41 + .../channelSettings/components/editForm.js | 59 +- ...nTokenSettings.js => joinTokenSettings.js} | 5 +- ...loginTokenToggle.js => joinTokenToggle.js} | 3 +- .../channelSettings/components/overview.js | 78 +- .../components/pendingUsers.js | 172 +- .../components/resetJoinToken.js | 3 +- src/views/channelSettings/index.js | 60 +- src/views/channelSettings/style.js | 27 +- src/views/community/components/channelList.js | 226 +- src/views/community/components/memberGrid.js | 78 +- .../community/components/moderatorList.js | 99 +- .../components/requestToJoinCommunity.js | 121 + src/views/community/components/search.js | 2 +- src/views/community/index.js | 340 +- src/views/community/style.js | 101 +- .../components/analyticsUpsell/index.js | 127 - .../components/analyticsUpsell/style.js | 109 - .../components/threadListItem.js | 6 +- .../components/topAndNewThreads.js | 19 +- .../components/topMembers.js | 113 +- src/views/communityAnalytics/index.js | 54 +- src/views/communityAnalytics/style.js | 36 +- .../components/administratorEmailForm.js | 146 - .../components/editSourceDropdown.js | 137 - .../communityBilling/components/invoice.js | 56 - .../components/mutationWrapper.js | 70 - .../communityBilling/components/source.js | 61 - .../components/subscription.js | 282 - src/views/communityBilling/index.js | 197 - src/views/communityBilling/style.js | 141 - src/views/communityBilling/utils.js | 23 - src/views/communityLogin/index.js | 46 +- src/views/communityLogin/style.js | 17 +- .../components/communityMembers.js | 107 +- .../components/editDropdown.js | 102 +- .../components/importSlack.js | 297 - .../components/joinTokenSettings.js | 103 + .../components/joinTokenToggle.js | 72 + .../components/mutationWrapper.js | 3 +- .../components/resetJoinToken.js | 68 + src/views/communityMembers/index.js | 56 +- src/views/communityMembers/style.js | 69 +- .../components/brandedLogin.js | 14 +- .../components/brandedLoginToggle.js | 7 +- .../components/channelList.js | 43 +- .../communitySettings/components/editForm.js | 71 +- .../communitySettings/components/overview.js | 25 +- .../components/slack/channelConnection.js | 96 + .../components/slack/channelSlackManager.js | 86 + .../components/slack/connectSlack.js | 59 + .../components/slack/index.js | 101 + .../components/slack/sendInvitations.js | 135 + .../components/slack/style.js | 54 + src/views/communitySettings/index.js | 30 +- src/views/communitySettings/style.js | 41 +- src/views/compose/index.js | 20 - src/views/compose/style.js | 10 - .../dashboard/components/communityList.js | 106 +- .../components/desktopAppUpsell/index.js | 83 + .../components/desktopAppUpsell/style.js | 49 + .../dashboard/components/emptyThreadFeed.js | 2 +- .../dashboard/components/errorThreadFeed.js | 2 +- src/views/dashboard/components/facepile.js | 98 - src/views/dashboard/components/inboxThread.js | 248 - .../components/inboxThread/activity.js | 33 + .../components/inboxThread/header/index.js | 33 + .../components/inboxThread/header/style.js | 145 + .../inboxThread/header/threadHeader.js | 110 + .../inboxThread/header/timestamp.js | 38 + .../header/userProfileThreadHeader.js | 105 + .../dashboard/components/inboxThread/index.js | 140 + .../components/inboxThread/messageCount.js | 38 + .../dashboard/components/inboxThread/style.js | 154 + .../components/newActivityIndicator.js | 15 +- .../dashboard/components/sidebarChannels.js | 117 +- .../components/threadCommunityInfo.js | 73 - src/views/dashboard/components/threadFeed.js | 332 +- .../dashboard/components/threadSearch.js | 15 +- .../components/threadSelectorHeader.js | 5 +- .../components/upsellExploreCommunities.js | 22 +- src/views/dashboard/index.js | 215 +- src/views/dashboard/style.js | 485 +- src/views/dashboardThread/index.js | 4 +- src/views/dashboardThread/style.js | 7 +- .../directMessages/components/avatars.js | 49 +- src/views/directMessages/components/header.js | 12 +- .../directMessages/components/loading.js | 2 +- .../components/messageThreadListItem.js | 27 +- .../directMessages/components/messages.js | 45 +- src/views/directMessages/components/style.js | 72 +- .../directMessages/components/threadsList.js | 197 +- .../containers/existingThread.js | 107 +- src/views/directMessages/containers/index.js | 87 + .../directMessages/containers/newThread.js | 128 +- src/views/directMessages/index.js | 190 +- src/views/directMessages/style.js | 51 +- src/views/explore/collections.js | 36 +- .../components/communitySearchWrapper.js | 20 +- src/views/explore/components/search.js | 132 +- src/views/explore/index.js | 77 +- src/views/explore/style.js | 71 +- src/views/explore/view.js | 115 +- src/views/login/index.js | 29 +- src/views/login/style.js | 42 +- src/views/navbar/components/messagesTab.js | 116 +- .../navbar/components/notificationDropdown.js | 23 +- .../navbar/components/notificationsTab.js | 91 +- .../navbar/components/profileDropdown.js | 101 +- src/views/navbar/index.js | 278 +- src/views/navbar/style.js | 202 +- .../components/createCommunityForm/index.js | 167 +- .../components/createCommunityForm/style.js | 112 +- .../components/editCommunityForm/index.js | 24 +- .../newCommunity/components/share/index.js | 12 +- .../newCommunity/components/share/style.js | 11 +- .../newCommunity/components/stepper/style.js | 3 +- src/views/newCommunity/index.js | 21 +- src/views/newCommunity/style.js | 13 +- .../components/appsUpsell/index.js | 60 + .../components/appsUpsell/style.js | 13 + .../components/communitySearch/index.js | 188 +- .../components/communitySearch/style.js | 27 +- .../components/discoverCommunities/index.js | 8 + .../components/discoverCommunities/style.js | 13 +- .../components/joinFirstCommunity/index.js | 18 +- .../components/setUsername/index.js | 144 +- .../components/setUsername/style.js | 89 +- .../components/userInfo/index.js | 353 - .../components/userInfo/style.js | 135 - src/views/newUserOnboarding/index.js | 130 +- src/views/newUserOnboarding/style.js | 19 +- .../notifications/components/actorsRow.js | 12 +- .../components/browserNotificationRequest.js | 2 +- .../components/getCommunityMember.js | 25 + .../components/mentionMessageNotification.js | 34 +- .../components/newChannelNotification.js | 51 +- .../components/newMessageNotification.js | 69 +- .../components/newReactionNotification.js | 50 +- .../components/newThreadNotification.js | 71 +- .../newThreadReactionNotification.js | 106 + .../components/notificationDropdownList.js | 204 +- ...ivateChannelRequestApprovedNotification.js | 10 +- .../privateChannelRequestSentNotification.js | 14 +- ...ateCommunityRequestApprovedNotification.js | 111 + ...privateCommunityRequestSentNotification.js | 221 + .../sortAndGroupNotificationMessages.js | 2 +- src/views/notifications/index.js | 438 +- src/views/notifications/style.js | 145 +- src/views/notifications/utils.js | 91 +- src/views/pages/apps/index.js | 62 + src/views/pages/apps/style.js | 67 + src/views/pages/components/footer.js | 88 +- src/views/pages/components/nav.js | 91 +- src/views/pages/faq/index.js | 81 + src/views/pages/faq/style.js | 17 + src/views/pages/features/index.js | 421 +- src/views/pages/features/style.js | 301 + src/views/pages/home/index.js | 5 +- src/views/pages/index.js | 28 +- .../pages/pricing/components/communityList.js | 164 - .../pages/pricing/components/discount.js | 31 - src/views/pages/pricing/components/faq.js | 57 - src/views/pages/pricing/components/feature.js | 39 - src/views/pages/pricing/components/intro.js | 60 - src/views/pages/pricing/components/paid.js | 94 - src/views/pages/pricing/index.js | 45 - src/views/pages/pricing/style.js | 109 +- src/views/pages/privacy/index.js | 560 +- src/views/pages/style.js | 299 +- src/views/pages/support/index.js | 30 +- src/views/pages/terms/index.js | 62 +- src/views/pages/terms/style.js | 3 +- src/views/pages/view.js | 124 +- src/views/privateChannelJoin/index.js | 16 +- src/views/privateCommunityJoin/index.js | 109 + src/views/search/searchInput.js | 1 - src/views/search/style.js | 23 +- src/views/status/index.js | 49 +- src/views/thread/components/actionBar.js | 451 +- .../components/desktopAppUpsell/index.js | 71 + .../components/desktopAppUpsell/style.js | 38 + src/views/thread/components/loading.js | 6 +- src/views/thread/components/messages.js | 431 +- src/views/thread/components/sidebar.js | 294 +- src/views/thread/components/threadByline.js | 26 +- .../components/threadCommunityBanner.js | 139 +- src/views/thread/components/threadDetail.js | 296 +- .../thread/components/watercoolerActionBar.js | 167 + src/views/thread/container.js | 622 + src/views/thread/index.js | 586 +- src/views/thread/redirect-old-route.js | 36 + src/views/thread/style.js | 308 +- src/views/threadSlider/index.js | 87 +- src/views/threadSlider/style.js | 13 +- src/views/titlebar/index.js | 22 +- src/views/titlebar/style.js | 16 +- src/views/user/components/communityList.js | 18 +- src/views/user/components/search.js | 2 +- src/views/user/index.js | 220 +- src/views/user/style.js | 39 +- .../components/deleteAccountForm.js | 169 + .../components/downloadDataForm.js | 58 + .../userSettings/components/editForm.js} | 339 +- .../userSettings/components/emailSettings.js | 44 +- src/views/userSettings/components/invoices.js | 53 - .../components/notificationSettings.js | 53 +- src/views/userSettings/components/overview.js | 52 + .../components/recurringPaymentsList.js | 83 - src/views/userSettings/index.js | 164 +- src/views/userSettings/style.js | 121 +- vulcan/index.js | 80 +- vulcan/models/community.js | 62 - vulcan/models/db.js | 32 - vulcan/models/message.js | 47 - vulcan/models/thread.js | 118 - vulcan/models/user.js | 82 - vulcan/package.json | 30 +- vulcan/queues/constants.js | 2 + vulcan/queues/events/created.js | 47 + vulcan/queues/events/deleted.js | 25 + vulcan/queues/events/edited.js | 44 + vulcan/queues/index.js | 28 + vulcan/queues/types/community.js | 17 + vulcan/queues/types/message.js | 17 + vulcan/queues/types/thread/index.js | 17 + vulcan/queues/types/thread/moved.js | 52 + vulcan/queues/types/user.js | 19 + vulcan/utils/get-queue-from-type.js | 20 + vulcan/{models/utils.js => utils/index.js} | 18 +- vulcan/{models => utils}/text-parsing.js | 0 vulcan/yarn.lock | 874 +- yarn.lock | 10085 +++++++++++----- 1564 files changed, 82557 insertions(+), 58282 deletions(-) delete mode 100644 .editorconfig delete mode 100644 .eslintrc.json create mode 100644 .npmignore create mode 100644 .prettierrc create mode 100644 analytics/index.js create mode 100644 analytics/models/channel.js create mode 100644 analytics/models/community.js create mode 100644 analytics/models/message.js create mode 100644 analytics/models/notification.js create mode 100644 analytics/models/reaction.js create mode 100644 analytics/models/thread.js create mode 100644 analytics/models/usersChannels.js create mode 100644 analytics/models/usersCommunities.js create mode 100644 analytics/models/usersThreads.js create mode 100644 analytics/package.json create mode 100644 analytics/queues/constants.js create mode 100644 analytics/queues/identify-analytics.js create mode 100644 analytics/queues/track-analytics.js create mode 100644 analytics/utils/amplitude.js create mode 100644 analytics/utils/getContext.js create mode 100644 analytics/utils/hash.js create mode 100644 analytics/utils/identify.js create mode 100644 analytics/utils/index.js create mode 100644 analytics/utils/track.js create mode 100644 analytics/utils/transformations.js create mode 100644 analytics/yarn.lock create mode 100644 api/apollo-server.js create mode 100644 api/loaders/message.js delete mode 100644 api/loaders/stripe.js create mode 100644 api/loaders/threadReaction.js create mode 100644 api/migrations/20180411183454-lowercase-all-the-slugs.js create mode 100644 api/migrations/20180428001543-reset-slack-import-records.js create mode 100644 api/migrations/20180504003702-encrypt-existing-slack-data.js create mode 100644 api/migrations/20180517180716-enable-private-communities.js create mode 100644 api/migrations/20180517215503-add-ispending-to-userscommunities.js create mode 100644 api/migrations/20180518135040-add-join-settings-to-community-settings.js create mode 100644 api/migrations/20180621001409-thread-likes-table.js create mode 100644 api/migrations/20180823115847-add-users-communities-indexes.js create mode 100644 api/migrations/20181001061156-thread-metadata-denormalization.js create mode 100644 api/migrations/20181001064151-fix-thread-metadata-message-counts.js create mode 100644 api/migrations/20181002060237-remove-payments.js create mode 100644 api/migrations/20181003233411-thread-reactions-useridandthreadid-index.js create mode 100644 api/migrations/20181004222636-denormalize-channel-community-member-counts.js create mode 100644 api/migrations/20181005143053-users-notifications-useridandnotificationid-index.js create mode 100644 api/migrations/20181005144259-users-notifications-userIdAndIsSeen-index.js create mode 100644 api/migrations/20181023160027-update-denormalized-member-counts.js create mode 100644 api/migrations/20181024173616-indexes-for-digests.js create mode 100644 api/migrations/20181027050052-remove-attachments-from-thread-model.js create mode 100644 api/migrations/20181102025454-fix-old-image-urls-in-messages.js create mode 100644 api/migrations/20181102040518-fix-old-image-urls-in-threads.js create mode 100644 api/migrations/20181102044407-fix-old-image-urls-in-communities.js create mode 100644 api/migrations/20181102045821-fix-old-image-urls-in-users.js create mode 100644 api/migrations/20181102054523-fix-aws-static-url-community-photos.js create mode 100644 api/migrations/20181116173949-add-terms-last-accepted-field-to-users.js create mode 100644 api/migrations/20181121054300-resync-community-member-counts.js create mode 100644 api/migrations/20181122162921-users-communities-useridandmember-index.js create mode 100644 api/migrations/20181126094455-users-channels-roles.js create mode 100644 api/migrations/20181127090014-communities-member-count-index.js create mode 100644 api/migrations/20181205171559-remove-old-users-notifications.js create mode 100644 api/migrations/20181211181146-add-usersthreads-user-id-and-participant-index.js create mode 100644 api/migrations/seed/default/notifications.js create mode 100644 api/migrations/seed/default/reactions.js create mode 100644 api/migrations/seed/default/usersNotifications.js create mode 100644 api/migrations/seed/default/usersSettings.js delete mode 100644 api/models/db.js delete mode 100644 api/models/expo-push-subscription.js delete mode 100644 api/models/invoice.js delete mode 100644 api/models/recurringPayment.js delete mode 100644 api/models/stripeCustomers.js delete mode 100644 api/models/stripeInvoices.js delete mode 100644 api/models/stripeSources.js delete mode 100644 api/models/stripeSubscriptions.js create mode 100644 api/models/test/__snapshots__/channel.test.js.snap create mode 100644 api/models/test/channel.test.js create mode 100644 api/models/threadReaction.js delete mode 100644 api/models/user.js delete mode 100644 api/models/usersNotifications.js delete mode 100644 api/mutations/channel/sendChannelEmailInvites.js create mode 100644 api/mutations/channel/updateChannelSlackBotLinks.js delete mode 100644 api/mutations/community/addPaymentSource.js delete mode 100644 api/mutations/community/cancelSubscription.js delete mode 100644 api/mutations/community/disableCommunityAnalytics.js create mode 100644 api/mutations/community/disableCommunityTokenJoin.js delete mode 100644 api/mutations/community/enableCommunityAnalytics.js create mode 100644 api/mutations/community/enableCommunityTokenJoin.js create mode 100644 api/mutations/community/importSlackMembers.js delete mode 100644 api/mutations/community/makePaymentSourceDefault.js delete mode 100644 api/mutations/community/removePaymentSource.js create mode 100644 api/mutations/community/resetCommunityJoinToken.js create mode 100644 api/mutations/communityMember/addCommunityMemberWithToken.js create mode 100644 api/mutations/communityMember/addCommunityMembers.js create mode 100644 api/mutations/communityMember/addPendingCommunityMember.js create mode 100644 api/mutations/communityMember/approvePendingCommunityMember.js create mode 100644 api/mutations/communityMember/blockPendingCommunityMember.js create mode 100644 api/mutations/communityMember/removePendingCommunityMember.js create mode 100644 api/mutations/message/editMessage.js delete mode 100644 api/mutations/notification/markAllNotificationsRead.js delete mode 100644 api/mutations/recurringPayment/downgradeCommunity.js delete mode 100644 api/mutations/recurringPayment/downgradeFromPro.js delete mode 100644 api/mutations/recurringPayment/index.js delete mode 100644 api/mutations/recurringPayment/upgradeCommunity.js delete mode 100644 api/mutations/recurringPayment/upgradeToPro.js delete mode 100644 api/mutations/recurringPayment/utils.js create mode 100644 api/mutations/thread/addThreadReaction.js create mode 100644 api/mutations/thread/removeThreadReaction.js create mode 100644 api/mutations/user/banUser.js create mode 100644 api/mutations/user/deleteCurrentUser.js create mode 100644 api/mutations/user/reportUser.js delete mode 100644 api/mutations/user/subscribeExpoPush.js create mode 100644 api/queries/channel/slackSettings.js create mode 100644 api/queries/channelSlackSettings/botLinks.js create mode 100644 api/queries/channelSlackSettings/index.js delete mode 100644 api/queries/community/billingSettings.js create mode 100644 api/queries/community/coverPhoto.js delete mode 100644 api/queries/community/hasChargeableSource.js delete mode 100644 api/queries/community/hasFeatures.js delete mode 100644 api/queries/community/invoices.js delete mode 100644 api/queries/community/isPro.js create mode 100644 api/queries/community/joinSettings.js create mode 100644 api/queries/community/profilePhoto.js delete mode 100644 api/queries/community/recurringPayments.js create mode 100644 api/queries/community/slackSettings.js create mode 100644 api/queries/communitySlackSettings/hasSentInvites.js create mode 100644 api/queries/communitySlackSettings/index.js create mode 100644 api/queries/communitySlackSettings/invitesSentAt.js create mode 100644 api/queries/communitySlackSettings/isConnected.js create mode 100644 api/queries/communitySlackSettings/memberCount.js create mode 100644 api/queries/communitySlackSettings/slackChannelList.js create mode 100644 api/queries/communitySlackSettings/teamName.js create mode 100644 api/queries/directMessageThread/rootDirectMessageThreadByUserId.js delete mode 100644 api/queries/directMessageThread/utils.js create mode 100644 api/queries/message/content.js create mode 100644 api/queries/message/parent.js create mode 100644 api/queries/thread/content.js delete mode 100644 api/queries/thread/messageCount.js create mode 100644 api/queries/thread/metaImage.js create mode 100644 api/queries/thread/reactions.js delete mode 100644 api/queries/user/invoices.js delete mode 100644 api/queries/user/isPro.js delete mode 100644 api/queries/user/recurringPayments.js create mode 100644 api/routes/api/export-user-data.js delete mode 100644 api/routes/api/graphiql.js delete mode 100644 api/routes/api/graphql.js delete mode 100644 api/routes/api/stripe.js delete mode 100644 api/routes/webhooks/index.js create mode 100644 api/test/channel/queries/__snapshots__/slackSettings.test.js.snap create mode 100644 api/test/channel/queries/slackSettings.test.js create mode 100644 api/test/community/queries/__snapshots__/slackSettings.test.js.snap create mode 100644 api/test/community/queries/slackSettings.test.js create mode 100644 api/test/utils/__mocks__/debug.js create mode 100644 api/test/utils/__snapshots__/create-graphql-error-formatter.test.js.snap create mode 100644 api/test/utils/create-graphql-error-formatter.test.js create mode 100644 api/types/ChannelSlackSettings.js create mode 100644 api/types/CommunitySlackSettings.js create mode 100644 api/types/custom-scalars/LowercaseString.js create mode 100644 api/utils/file-storage.js create mode 100644 api/utils/file-system.js create mode 100644 api/utils/generate-thread-meta-image-from-text.js create mode 100644 api/utils/rate-limit-directive.js create mode 100644 api/utils/validate-draft-js-input.js create mode 100644 athena/models/channelSettings.js create mode 100644 athena/models/communitySettings.js delete mode 100644 athena/models/db.js delete mode 100644 athena/models/recurringPayment.js delete mode 100644 athena/models/slackImports.js delete mode 100644 athena/models/user.js delete mode 100644 athena/models/usersNotifications.js delete mode 100644 athena/queues/community-invoice-paid.js delete mode 100644 athena/queues/moderationEvents/spectrum.js create mode 100644 athena/queues/private-community-request-approved.js create mode 100644 athena/queues/private-community-request-sent.js delete mode 100644 athena/queues/pro-invoice-paid.js create mode 100644 athena/queues/send-slack-invitations.js delete mode 100644 athena/queues/slack-import.js create mode 100644 athena/queues/thread-reaction-notification.js delete mode 100644 athena/utils/addQueue.js delete mode 100644 athena/utils/push-notifications/send-expo-push-notifications.js create mode 100644 athena/utils/slack/index.js rename mobile/components/ThreadItem/utils.js => athena/utils/truncateString.js (72%) create mode 100644 built-email-templates/adminActiveCommunityReport.html create mode 100644 built-email-templates/adminCommunityCreated.html create mode 100644 built-email-templates/adminSlackImportProcessed.html create mode 100644 built-email-templates/adminToxicContent.html create mode 100644 built-email-templates/adminUserReported.html create mode 100644 built-email-templates/adminUserSpammingThreadsNotification.html create mode 100644 built-email-templates/communityInvite.html create mode 100644 built-email-templates/mentionInMessage.html create mode 100644 built-email-templates/mentionInThread.html create mode 100644 built-email-templates/newCommunityWelcome.html create mode 100644 built-email-templates/newDirectMessage.html create mode 100644 built-email-templates/newRepliesInThreads.html create mode 100644 built-email-templates/newThreadNotification.html create mode 100644 built-email-templates/newUserWelcome.html create mode 100644 built-email-templates/privateChannelRequestApproved.html create mode 100644 built-email-templates/privateChannelRequestSent.html create mode 100644 built-email-templates/privateCommunityRequestApproved.html create mode 100644 built-email-templates/privateCommunityRequestSent.html create mode 100644 built-email-templates/validate-community-administrator-email.html create mode 100644 built-email-templates/validate-email.html create mode 100644 built-email-templates/weeklyDigest.html delete mode 100644 cacert delete mode 100644 chronos/models/channel.js delete mode 100644 chronos/models/db.js create mode 100644 chronos/models/usersNotifications.js create mode 100644 chronos/models/utils.js create mode 100644 chronos/queues/digests/dailyDigest.js create mode 100644 chronos/queues/digests/getReputationString.js create mode 100644 chronos/queues/digests/getUpsellCommunities.js delete mode 100644 chronos/queues/digests/index.js create mode 100644 chronos/queues/digests/processDigest.js delete mode 100644 chronos/queues/digests/processDigestEmail.js create mode 100644 chronos/queues/digests/processIndividualDigest.js delete mode 100644 chronos/queues/digests/processReputation.js delete mode 100644 chronos/queues/digests/processUsers.js create mode 100644 chronos/queues/digests/test/__snapshots__/message-count-string.test.js.snap create mode 100644 chronos/queues/digests/test/__snapshots__/reputation-string.test.js.snap create mode 100644 chronos/queues/digests/test/message-count-string.test.js create mode 100644 chronos/queues/digests/test/reputation-string.test.js delete mode 100644 chronos/queues/digests/types.js create mode 100644 chronos/queues/digests/weeklyDigest.js create mode 100644 chronos/queues/remove-seen-usersNotifications.js create mode 100644 chronos/types.js create mode 100644 cypress/integration/apps_page_spec.js create mode 100644 cypress/integration/channel/settings/members_spec.js create mode 100644 cypress/integration/community/settings/create_spec.js create mode 100644 cypress/integration/community/settings/private_invite_link_spec.js create mode 100644 cypress/integration/community/view/profile_spec.js delete mode 100644 cypress/integration/community_settings_billing_spec.js delete mode 100644 cypress/integration/community_spec.js create mode 100644 cypress/integration/faq_page_spec.js create mode 100644 cypress/integration/messages_spec.js create mode 100644 cypress/integration/navbar_spec.js delete mode 100644 cypress/integration/pricing_spec.js delete mode 100644 cypress/integration/thread/create_spec.js delete mode 100644 cypress/integration/thread/delete_spec.js delete mode 100644 cypress/integration/thread/edit_spec.js create mode 100644 cypress/integration/user/delete_user_spec.js create mode 100644 cypress/integration/user/edit_user_spec.js create mode 100644 cypress/integration/user/me_redirect_spec.js delete mode 100644 dangerfile.js create mode 100644 desktop/README.md create mode 100644 desktop/deployment.md create mode 100644 desktop/package.json create mode 100644 desktop/resources/background.tiff create mode 100644 desktop/resources/icons/mac/icon.icns create mode 100644 desktop/resources/icons/png/icon-512x512.png create mode 100644 desktop/resources/icons/win/icon.ico create mode 100644 desktop/resources/release-notes.md create mode 100644 desktop/src/autoUpdate.js create mode 100644 desktop/src/config.js create mode 100644 desktop/src/main.js create mode 100644 desktop/src/menu.js create mode 100644 desktop/src/preload.js create mode 100644 desktop/yarn.lock create mode 100644 docs/admin/intro.md create mode 100644 docs/api/graphql/fragments.md create mode 100644 docs/api/graphql/intro.md create mode 100644 docs/api/graphql/pagination.md create mode 100644 docs/api/graphql/testing.md create mode 100644 docs/api/graphql/tips-and-tricks.md create mode 100644 docs/api/intro.md rename docs/backend/{graphql => api}/README.md (100%) rename docs/backend/{graphql => api}/fragments.md (100%) rename docs/backend/{graphql => api}/pagination.md (100%) rename docs/backend/{graphql => api}/testing.md (100%) rename docs/backend/{graphql => api}/tips-and-tricks.md (92%) delete mode 100644 docs/backend/athena/README.md delete mode 100644 docs/backend/background-jobs.md delete mode 100644 docs/backend/chronos/README.md delete mode 100644 docs/backend/hermes/README.md delete mode 100644 docs/backend/hyperion/README.md delete mode 100644 docs/backend/mercury/README.md delete mode 100644 docs/backend/vulcan/README.md delete mode 100644 docs/deletingUsers.md rename docs/{backend/deployment.md => deployments.md} (92%) rename docs/{backend/hyperion/server-side-rendering.md => hyperion (server side rendering)/development.md} (83%) create mode 100644 docs/hyperion (server side rendering)/intro.md rename docs/{rethinkdb.md => operations/importing-rethinkdb-backups.md} (83%) create mode 100644 docs/operations/intro.md delete mode 100644 docs/testing.md create mode 100644 docs/testing/integration.md create mode 100644 docs/testing/intro.md create mode 100644 docs/testing/unit.md create mode 100644 docs/workers/analytics/intro.md create mode 100644 docs/workers/athena/intro.md create mode 100644 docs/workers/background-jobs.md create mode 100644 docs/workers/chronos/intro.md create mode 100644 docs/workers/hermes/intro.md rename docs/{backend/README.md => workers/intro.md} (50%) create mode 100644 docs/workers/mercury/intro.md create mode 100644 docs/workers/vulcan/intro.md create mode 100644 email-template-scripts/README.md create mode 100644 email-template-scripts/inline-html-emails.py create mode 100644 email-template-scripts/send-test-emails.js create mode 100644 email-template-scripts/sendgrid-sync.js create mode 100644 email-template-scripts/test-email-data/admin-active-community-report.json create mode 100644 email-template-scripts/test-email-data/admin-community-created-notification.json create mode 100644 email-template-scripts/test-email-data/admin-slack-import-completed.json create mode 100644 email-template-scripts/test-email-data/admin-toxic-content.json create mode 100644 email-template-scripts/test-email-data/admin-user-reported-alert.json create mode 100644 email-template-scripts/test-email-data/admin-user-spamming-threads-alert.json create mode 100644 email-template-scripts/test-email-data/community-admin-email-validation.json create mode 100644 email-template-scripts/test-email-data/community-created.json create mode 100644 email-template-scripts/test-email-data/community-invitation.json create mode 100644 email-template-scripts/test-email-data/digest.json create mode 100644 email-template-scripts/test-email-data/direct-message-received.json create mode 100644 email-template-scripts/test-email-data/mention-in-message.json create mode 100644 email-template-scripts/test-email-data/mention-in-thread.json create mode 100644 email-template-scripts/test-email-data/message-in-threads.json create mode 100644 email-template-scripts/test-email-data/new-user-welcome.json create mode 100644 email-template-scripts/test-email-data/private-channel-request-approved.json create mode 100644 email-template-scripts/test-email-data/private-channel-request-sent.json create mode 100644 email-template-scripts/test-email-data/private-community-request-approved.json create mode 100644 email-template-scripts/test-email-data/private-community-request-sent.json create mode 100644 email-template-scripts/test-email-data/thread-created.json create mode 100644 email-template-scripts/test-email-data/user-email-validation.json create mode 100644 email-templates/adminUserReported.html create mode 100644 email-templates/adminUserSpammingThreadsNotification.html delete mode 100644 email-templates/communityCardExpiringWarning.html delete mode 100644 email-templates/communityInvoiceReceipt.html delete mode 100644 email-templates/communityPaymentFailed.html delete mode 100644 email-templates/communityPaymentSucceeded.html create mode 100644 email-templates/privateCommunityRequestApproved.html create mode 100644 email-templates/privateCommunityRequestSent.html delete mode 100644 email-templates/proInvoiceReceipt.html create mode 100644 flow-typed/npm/@sendgrid/mail_vx.x.x.js create mode 100644 flow-typed/npm/amplitude_vx.x.x.js create mode 100644 flow-typed/npm/apollo-link-schema_vx.x.x.js create mode 100644 flow-typed/npm/apollo-server-express_vx.x.x.js create mode 100644 flow-typed/npm/b2a_vx.x.x.js create mode 100644 flow-typed/npm/bluebird_vx.x.x.js create mode 100644 flow-typed/npm/cryptr_vx.x.x.js create mode 100644 flow-typed/npm/datadog-metrics_vx.x.x.js create mode 100644 flow-typed/npm/dataloader_vx.x.x.js create mode 100644 flow-typed/npm/decode-uri-component_vx.x.x.js create mode 100644 flow-typed/npm/draft-js-export-markdown_vx.x.x.js rename flow-typed/npm/{react-navigation_vx.x.x.js => electron-context-menu_vx.x.x.js} (60%) create mode 100644 flow-typed/npm/electron-is-dev_vx.x.x.js create mode 100644 flow-typed/npm/electron-updater_vx.x.x.js rename flow-typed/npm/{react-native-typography_vx.x.x.js => electron-window-state_vx.x.x.js} (59%) create mode 100644 flow-typed/npm/electron_vx.x.x.js create mode 100644 flow-typed/npm/express-enforces-ssl_vx.x.x.js create mode 100644 flow-typed/npm/express-hot-shots_vx.x.x.js create mode 100644 flow-typed/npm/graphql-rate-limit_vx.x.x.js create mode 100644 flow-typed/npm/helmet_vx.x.x.js create mode 100644 flow-typed/npm/host-validation_vx.x.x.js create mode 100644 flow-typed/npm/hot-shots_vx.x.x.js create mode 100644 flow-typed/npm/hpp_vx.x.x.js create mode 100644 flow-typed/npm/hsts_vx.x.x.js create mode 100644 flow-typed/npm/idx_v2.x.x.js delete mode 100644 flow-typed/npm/ioredis_v3.x.x.js create mode 100644 flow-typed/npm/is-electron_vx.x.x.js create mode 100644 flow-typed/npm/markdown-draft-js_vx.x.x.js create mode 100644 flow-typed/npm/ms_vx.x.x.js delete mode 100644 flow-typed/npm/query-string_v5.x.x.js create mode 100644 flow-typed/npm/query-string_vx.x.x.js create mode 100644 flow-typed/npm/raf_vx.x.x.js create mode 100644 flow-typed/npm/react-hot-loader_vx.x.x.js create mode 100644 flow-typed/npm/react-image_vx.x.x.js create mode 100644 flow-typed/npm/react-mentions_vx.x.x.js delete mode 100644 flow-typed/npm/react-navigation-props-mapper_vx.x.x.js create mode 100644 flow-typed/npm/react-popper_vx.x.x.js create mode 100644 flow-typed/npm/react-visibility-sensor_vx.x.x.js create mode 100644 flow-typed/npm/redis-tag-cache_vx.x.x.js create mode 100644 flow-typed/npm/request-ip_vx.x.x.js create mode 100644 flow-typed/npm/rethinkhaberdashery_vx.x.x.js create mode 100644 flow-typed/npm/sanitize-filename_vx.x.x.js rename flow-typed/npm/{abab_vx.x.x.js => sha1_vx.x.x.js} (50%) create mode 100644 flow-typed/npm/string-similarity_vx.x.x.js create mode 100644 hermes/events/index.js create mode 100644 hermes/models/usersSettings.js create mode 100644 hermes/queues/send-admin-user-reported-email.js create mode 100644 hermes/queues/send-admin-user-spamming-threads-notification-email.js delete mode 100644 hermes/queues/send-community-card-expiring-warning-email.js delete mode 100644 hermes/queues/send-community-invoice-receipt-email.js delete mode 100644 hermes/queues/send-community-payment-failed-email.js delete mode 100644 hermes/queues/send-community-payment-succeeded-email.js create mode 100644 hermes/queues/send-private-community-request-approved-email.js create mode 100644 hermes/queues/send-private-community-request-sent-email.js delete mode 100644 hermes/queues/send-pro-invoice-receipt-email.js create mode 100644 hermes/queues/sendgrid-webhook-events.js create mode 100644 hermes/user-can-receive-email.js create mode 100644 hermes/utils/smarten-string.js delete mode 100644 hyperion/cache.js delete mode 100644 hyperion/create-cache-stream.js delete mode 100644 hyperion/redis.js delete mode 100644 index.html create mode 100644 mercury/functions/processThreadReactionCreated.js create mode 100644 mercury/functions/processThreadReactionDeleted.js delete mode 100644 mercury/functions/types.js delete mode 100644 mercury/models/db.js create mode 100644 mercury/queues/calculateThreadScore.js delete mode 100644 mobile/.expo/Exponent.app/.gitkeep delete mode 100644 mobile/.expo/settings.json delete mode 100644 mobile/.gitignore delete mode 100644 mobile/.idea/mobile.iml delete mode 100644 mobile/.idea/modules.xml delete mode 100644 mobile/.idea/workspace.xml delete mode 100644 mobile/App.js delete mode 100644 mobile/actions/authentication.js delete mode 100644 mobile/app.json delete mode 100644 mobile/assets/icon.png delete mode 100644 mobile/assets/splash.png delete mode 100644 mobile/components/.gitkeep delete mode 100644 mobile/components/Anchor/index.js delete mode 100644 mobile/components/Avatar/image.js delete mode 100644 mobile/components/Avatar/img/default_avatar.svg delete mode 100644 mobile/components/Avatar/img/default_community.svg delete mode 100644 mobile/components/Avatar/index.js delete mode 100644 mobile/components/Avatar/style.js delete mode 100644 mobile/components/Avatar/utils.js delete mode 100644 mobile/components/Codeblock/index.js delete mode 100644 mobile/components/IFrame/index.js delete mode 100644 mobile/components/InfiniteList/index.js delete mode 100644 mobile/components/Login/index.js delete mode 100644 mobile/components/Messages/index.js delete mode 100644 mobile/components/SafeAreaView/index.js delete mode 100644 mobile/components/Text/index.js delete mode 100644 mobile/components/ThreadContent/index.js delete mode 100644 mobile/components/ThreadContent/renderer.js delete mode 100644 mobile/components/ThreadFeed/index.js delete mode 100644 mobile/components/ThreadFeed/style.js delete mode 100644 mobile/components/ThreadItem/Facepile.js delete mode 100644 mobile/components/ThreadItem/ThreadCommunityInfo.js delete mode 100644 mobile/components/ThreadItem/index.js delete mode 100644 mobile/components/ThreadItem/style.js delete mode 100644 mobile/components/ViewNetworkHandler/index.js delete mode 100644 mobile/components/theme/index.js delete mode 100644 mobile/jest.config.js delete mode 100644 mobile/package.json delete mode 100644 mobile/reducers/authentication.js delete mode 100644 mobile/reducers/index.js delete mode 100644 mobile/rn-cli.config.js delete mode 100644 mobile/setupTests.js delete mode 100644 mobile/utils/get-push-notification-token.js delete mode 100644 mobile/utils/platform.js delete mode 100644 mobile/views/Channel/index.js delete mode 100644 mobile/views/Channel/style.js delete mode 100644 mobile/views/Community/index.js delete mode 100644 mobile/views/Community/style.js delete mode 100644 mobile/views/Notifications/index.js delete mode 100644 mobile/views/Splash/index.js delete mode 100644 mobile/views/Splash/style.js delete mode 100644 mobile/views/TabBar/BaseStack.js delete mode 100644 mobile/views/TabBar/HomeStack.js delete mode 100644 mobile/views/TabBar/NotificationsStack.js delete mode 100644 mobile/views/TabBar/index.js delete mode 100644 mobile/views/Thread/index.js delete mode 100644 mobile/views/Thread/style.js delete mode 100644 mobile/views/User/index.js delete mode 100644 mobile/views/User/style.js delete mode 100644 mobile/views/__e2e__/splash.spec.js delete mode 100644 mobile/views/__tests__/.gitkeep delete mode 100644 mobile/yarn.lock delete mode 100644 pluto/changefeeds/analytics.js delete mode 100644 pluto/changefeeds/community.js delete mode 100644 pluto/changefeeds/index.js delete mode 100644 pluto/changefeeds/moderator.js delete mode 100644 pluto/changefeeds/openSourceStatus.js delete mode 100644 pluto/changefeeds/privateChannel.js delete mode 100644 pluto/changefeeds/support.js delete mode 100644 pluto/create-worker-with-express.js delete mode 100644 pluto/index.js delete mode 100644 pluto/models/channel.js delete mode 100644 pluto/models/community.js delete mode 100644 pluto/models/stripeCustomers.js delete mode 100644 pluto/models/usersCommunities.js delete mode 100644 pluto/models/utils.js delete mode 100644 pluto/package.json delete mode 100644 pluto/queues/constants.js delete mode 100644 pluto/queues/processAdministratorEmailChanged.js delete mode 100644 pluto/queues/processAnalyticsAdded.js delete mode 100644 pluto/queues/processAnalyticsRemoved.js delete mode 100644 pluto/queues/processCardExpiringWarning.js delete mode 100644 pluto/queues/processCommunityCreated.js delete mode 100644 pluto/queues/processCommunityDeleted.js delete mode 100644 pluto/queues/processCommunityEdited.js delete mode 100644 pluto/queues/processModeratorAdded.js delete mode 100644 pluto/queues/processModeratorRemoved.js delete mode 100644 pluto/queues/processOssStatusActivated.js delete mode 100644 pluto/queues/processOssStatusDisabled.js delete mode 100644 pluto/queues/processOssStatusEnabled.js delete mode 100644 pluto/queues/processPaymentFailed.js delete mode 100644 pluto/queues/processPaymentSucceeded.js delete mode 100644 pluto/queues/processPrioritySupportAdded.js delete mode 100644 pluto/queues/processPrioritySupportRemoved.js delete mode 100644 pluto/queues/processPrivateChannelAdded.js delete mode 100644 pluto/queues/processPrivateChannelRemoved.js delete mode 100644 pluto/queues/processStripeChargeWebhook.js delete mode 100644 pluto/queues/processStripeCustomerWebhook.js delete mode 100644 pluto/queues/processStripeDiscountWebhook.js delete mode 100644 pluto/queues/processStripeInvoiceWebhook.js delete mode 100644 pluto/queues/processStripeSourceWebhook.js delete mode 100644 pluto/queues/processStripeSubscriptionWebhook.js delete mode 100644 pluto/queues/removeAllPaidFeatures.js delete mode 100644 pluto/webhooks/index.js delete mode 100644 pluto/yarn.lock delete mode 100644 public/_redirects delete mode 100644 public/img/payment-methods/amex.svg delete mode 100644 public/img/payment-methods/diners-club.svg delete mode 100644 public/img/payment-methods/discover.svg delete mode 100644 public/img/payment-methods/jcb.svg delete mode 100644 public/img/payment-methods/mastercard.svg delete mode 100644 public/img/payment-methods/visa.svg create mode 100644 public/img/slack_colored.png delete mode 100644 public/img/stripe-logo.png create mode 100644 public/img/waterfall.png create mode 100644 shared/analytics/README.md create mode 100644 shared/analytics/event-types/channel.js create mode 100644 shared/analytics/event-types/community.js create mode 100644 shared/analytics/event-types/directMessage.js create mode 100644 shared/analytics/event-types/directMessageThreads.js create mode 100644 shared/analytics/event-types/emails.js create mode 100644 shared/analytics/event-types/index.js create mode 100644 shared/analytics/event-types/message.js create mode 100644 shared/analytics/event-types/misc.js create mode 100644 shared/analytics/event-types/navigation.js create mode 100644 shared/analytics/event-types/notifications.js create mode 100644 shared/analytics/event-types/page-apps.js create mode 100644 shared/analytics/event-types/page-explore.js create mode 100644 shared/analytics/event-types/page-features.js create mode 100644 shared/analytics/event-types/page-home.js create mode 100644 shared/analytics/event-types/page-inbox.js create mode 100644 shared/analytics/event-types/page-login.js create mode 100644 shared/analytics/event-types/page-support.js create mode 100644 shared/analytics/event-types/pwa.js create mode 100644 shared/analytics/event-types/reaction.js create mode 100644 shared/analytics/event-types/search.js create mode 100644 shared/analytics/event-types/thread.js create mode 100644 shared/analytics/event-types/threadReaction.js create mode 100644 shared/analytics/event-types/user.js create mode 100644 shared/analytics/event-types/userOnboarding.js create mode 100644 shared/analytics/event-types/web-push-notification.js create mode 100644 shared/analytics/index.js create mode 100644 shared/cache/redis.js create mode 100644 shared/clients/analytics/index.js create mode 100644 shared/clients/analytics/setUser.js create mode 100644 shared/clients/analytics/track.js create mode 100644 shared/clients/analytics/transformations.js create mode 100644 shared/clients/analytics/unsetUser.js delete mode 100644 shared/clients/draft-js/links-decorator/index.native.js delete mode 100644 shared/clients/draft-js/mentions-decorator/index.native.js create mode 100644 shared/clients/draft-js/mentions-decorator/test/core.test.js create mode 100644 shared/clients/draft-js/message/types.js create mode 100644 shared/clients/draft-js/utils/isShort.js create mode 100644 shared/db/constants.js create mode 100644 shared/db/create-query.js create mode 100644 shared/db/db.js create mode 100644 shared/db/index.js create mode 100644 shared/db/queries/channel.js create mode 100644 shared/db/queries/community.js create mode 100644 shared/db/queries/message.js create mode 100644 shared/db/queries/notifications.js create mode 100644 shared/db/queries/thread.js create mode 100644 shared/db/queries/user.js create mode 100644 shared/db/queries/usersNotifications.js create mode 100644 shared/db/queries/utils/trackNotifications.js create mode 100644 shared/db/query-cache.js create mode 100644 shared/encryption/index.js create mode 100644 shared/graphql-cache-keys.js create mode 100644 shared/graphql/constants.js create mode 100644 shared/graphql/fragments/community/communityChannelConnectionWithSlackSettings.js delete mode 100644 shared/graphql/fragments/invoice/invoiceInfo.js delete mode 100644 shared/graphql/fragments/user/userRecurringPayments.js create mode 100644 shared/graphql/mutations/channel/updateChannelSlackBotLinks.js create mode 100644 shared/graphql/mutations/channel/updateSlackSettings.js delete mode 100644 shared/graphql/mutations/community/addPaymentSource.js delete mode 100644 shared/graphql/mutations/community/cancelSubscription.js delete mode 100644 shared/graphql/mutations/community/disableCommunityAnalytics.js create mode 100644 shared/graphql/mutations/community/disableCommunityTokenJoin.js delete mode 100644 shared/graphql/mutations/community/downgradeCommunity.js delete mode 100644 shared/graphql/mutations/community/enableCommunityAnalytics.js create mode 100644 shared/graphql/mutations/community/enableCommunityTokenJoin.js delete mode 100644 shared/graphql/mutations/community/makePaymentSourceDefault.js delete mode 100644 shared/graphql/mutations/community/removePaymentSource.js create mode 100644 shared/graphql/mutations/community/resetCommunityJoinToken.js create mode 100644 shared/graphql/mutations/community/sendSlackInvites.js delete mode 100644 shared/graphql/mutations/community/upgradeCommunity.js create mode 100644 shared/graphql/mutations/communityMember/addCommunityMemberWithToken.js create mode 100644 shared/graphql/mutations/communityMember/addCommunityMembers.js create mode 100644 shared/graphql/mutations/communityMember/addPendingCommunityMember.js create mode 100644 shared/graphql/mutations/communityMember/approvePendingCommunityMember.js create mode 100644 shared/graphql/mutations/communityMember/blockPendingCommunityMember.js create mode 100644 shared/graphql/mutations/communityMember/removePendingCommunityMember.js create mode 100644 shared/graphql/mutations/message/editMessage.js delete mode 100644 shared/graphql/mutations/slackImport/sendSlackInvitations.js create mode 100644 shared/graphql/mutations/thread/addThreadReaction.js create mode 100644 shared/graphql/mutations/thread/removeThreadReaction.js create mode 100644 shared/graphql/mutations/user/banUser.js create mode 100644 shared/graphql/mutations/user/deleteCurrentUser.js delete mode 100644 shared/graphql/mutations/user/downgradeFromPro.js create mode 100644 shared/graphql/mutations/user/reportUser.js delete mode 100644 shared/graphql/mutations/user/subscribeExpoPush.js delete mode 100644 shared/graphql/mutations/user/upgradeToPro.js create mode 100644 shared/graphql/queries/community/getCommunityChannelsSlackSettings.js delete mode 100644 shared/graphql/queries/community/getCommunityInvoices.js delete mode 100644 shared/graphql/queries/community/getCommunityRecurringPayments.js create mode 100644 shared/graphql/queries/community/getCommunitySlackSettings.js create mode 100644 shared/graphql/queries/directMessageThread/getDirectMessageThreadByUserId.js create mode 100644 shared/graphql/queries/message/getMessage.js delete mode 100644 shared/graphql/queries/slackImport/getSlackImport.js delete mode 100644 shared/graphql/queries/user/getCurrentUserInvoices.js delete mode 100644 shared/graphql/queries/user/getCurrentUserRecurringPayments.js create mode 100644 shared/imgix/getDefaultExpires.js create mode 100644 shared/imgix/index.js create mode 100644 shared/imgix/sign.js create mode 100644 shared/imgix/signCommunity.js create mode 100644 shared/imgix/signMessage.js create mode 100644 shared/imgix/signThread.js create mode 100644 shared/imgix/signUser.js create mode 100644 shared/middlewares/csrf.js create mode 100644 shared/middlewares/error-handler.js create mode 100644 shared/middlewares/security.js create mode 100644 shared/middlewares/statsd.js rename athena/utils/push-notifications/notification-formatting.js => shared/notification-to-text.js (84%) create mode 100644 shared/statsd.js delete mode 100644 shared/stripe/index.js delete mode 100644 shared/stripe/types/charge.js delete mode 100644 shared/stripe/types/chargeEvent.js delete mode 100644 shared/stripe/types/customer.js delete mode 100644 shared/stripe/types/customerEvent.js delete mode 100644 shared/stripe/types/discount.js delete mode 100644 shared/stripe/types/invoice.js delete mode 100644 shared/stripe/types/invoiceEvent.js delete mode 100644 shared/stripe/types/invoiceItem.js delete mode 100644 shared/stripe/types/plan.js delete mode 100644 shared/stripe/types/source.js delete mode 100644 shared/stripe/types/sourceEvent.js delete mode 100644 shared/stripe/types/subscription.js delete mode 100644 shared/stripe/types/subscriptionEvent.js delete mode 100644 shared/stripe/types/subscriptionItem.js delete mode 100644 shared/stripe/utils.js rename athena/utils/push-notifications/test/__snapshots__/notification-formatting.test.js.snap => shared/test/__snapshots__/notification-to-text.test.js.snap (72%) create mode 100644 shared/test/encryption.test.js rename {athena/utils/push-notifications => shared}/test/fixtures/CHANNEL_CREATED.json (100%) rename {athena/utils/push-notifications => shared}/test/fixtures/COMMUNITY_INVITE.json (100%) rename {athena/utils/push-notifications => shared}/test/fixtures/DIRECT_MESSAGE_CREATED.json (100%) create mode 100644 shared/test/fixtures/MEDIA_MESSAGE_CREATED.json rename {athena/utils/push-notifications => shared}/test/fixtures/MENTION_MESSAGE.json (100%) rename {athena/utils/push-notifications => shared}/test/fixtures/MENTION_THREAD.json (100%) rename {athena/utils/push-notifications => shared}/test/fixtures/MESSAGE_CREATED.json (100%) rename {athena/utils/push-notifications => shared}/test/fixtures/REACTION_CREATED.json (100%) rename {athena/utils/push-notifications => shared}/test/fixtures/THREAD_CREATED.json (100%) create mode 100644 shared/test/fixtures/THREAD_REACTION_CREATED.json rename {athena/utils/push-notifications => shared}/test/fixtures/USER_JOINED_COMMUNITY.json (100%) rename athena/utils/push-notifications/test/notification-formatting.test.js => shared/test/notification-to-text.test.js (80%) rename {src/components => shared}/theme/index.js (81%) create mode 100644 shared/time-formatting.js create mode 100644 shared/truthy-values.js create mode 100644 src/actions/message.js create mode 100644 src/components/avatar/communityAvatar.js delete mode 100644 src/components/avatar/hoverProfile.js create mode 100644 src/components/avatar/userAvatar.js delete mode 100644 src/components/chatInput/input.js create mode 100644 src/components/conditionalWrap/index.js create mode 100644 src/components/draft-js-plugins-editor/index.js delete mode 100644 src/components/editForm/index.js create mode 100644 src/components/error/ErrorBoundary.js create mode 100644 src/components/error/SettingsFallback.js create mode 100644 src/components/hoverProfile/channelContainer.js create mode 100644 src/components/hoverProfile/channelProfile.js create mode 100644 src/components/hoverProfile/communityContainer.js create mode 100644 src/components/hoverProfile/communityProfile.js create mode 100644 src/components/hoverProfile/index.js create mode 100644 src/components/hoverProfile/loadingHoverProfile.js create mode 100644 src/components/hoverProfile/style.js create mode 100644 src/components/hoverProfile/userContainer.js create mode 100644 src/components/hoverProfile/userProfile.js create mode 100644 src/components/infiniteScroll/deduplicateChildren.js create mode 100644 src/components/infiniteScroll/tallViewports.js delete mode 100644 src/components/link/index.js delete mode 100644 src/components/linkPreview/index.js delete mode 100644 src/components/linkPreview/style.js create mode 100644 src/components/listItems/channel/index.js create mode 100644 src/components/listItems/channel/style.js create mode 100644 src/components/mentionsInput/index.js create mode 100644 src/components/mentionsInput/mentionSuggestion.js create mode 100644 src/components/mentionsInput/style.js create mode 100644 src/components/message/authorByline.js create mode 100644 src/components/message/editingBody.js create mode 100644 src/components/message/messageErrorFallback.js delete mode 100644 src/components/modals/AdminEmailAddressVerificationModal/index.js delete mode 100644 src/components/modals/AdminEmailAddressVerificationModal/style.js create mode 100644 src/components/modals/BanUserModal/index.js create mode 100644 src/components/modals/BanUserModal/style.js create mode 100644 src/components/modals/ReportUserModal/index.js create mode 100644 src/components/modals/ReportUserModal/style.js delete mode 100644 src/components/modals/UpgradeAnalyticsModal/index.js delete mode 100644 src/components/modals/UpgradeAnalyticsModal/style.js delete mode 100644 src/components/modals/UpgradeModal/index.js delete mode 100644 src/components/modals/UpgradeModal/style.js delete mode 100644 src/components/modals/UpgradeModeratorSeatModal/index.js delete mode 100644 src/components/modals/UpgradeModeratorSeatModal/style.js delete mode 100644 src/components/modals/components/poweredByStripe.js delete mode 100644 src/components/profile/channel.js create mode 100644 src/components/redirectHandler/index.js rename src/components/{draftjs-editor => rich-text-editor}/Embed.js (91%) rename src/components/{draftjs-editor => rich-text-editor}/Image.js (94%) create mode 100644 src/components/rich-text-editor/LanguageSelect.js rename src/components/{draftjs-editor/index.js => rich-text-editor/container.js} (67%) create mode 100644 src/components/rich-text-editor/index.js rename src/components/{draftjs-editor => rich-text-editor}/prism-theme.css (100%) rename src/components/{draftjs-editor => rich-text-editor}/style.js (56%) rename src/components/{draftjs-editor => rich-text-editor}/toolbar.js (87%) delete mode 100644 src/components/stripeCardForm/form.js delete mode 100644 src/components/stripeCardForm/index.js delete mode 100644 src/components/stripeCardForm/modalWell.js delete mode 100644 src/components/stripeCardForm/style.js create mode 100644 src/components/thirdPartyContextSetting/index.js delete mode 100644 src/components/threadFeedCard/facePile.js delete mode 100644 src/components/threadFeedCard/formattedThreadLocation.js delete mode 100644 src/components/threadFeedCard/index.js create mode 100644 src/components/threadLikes/index.js create mode 100644 src/components/threadLikes/style.js create mode 100644 src/components/usernameSearch/index.js create mode 100644 src/components/usernameSearch/style.js create mode 100644 src/components/visuallyHidden/index.js create mode 100644 src/components/withCurrentUser/index.js create mode 100644 src/helpers/analytics/index.js delete mode 100644 src/helpers/consolidate-streamed-styles.js create mode 100644 src/helpers/desktop-app-utils.js delete mode 100644 src/helpers/events.js create mode 100644 src/helpers/get-thread-link.js create mode 100644 src/helpers/is-admin.js create mode 100644 src/helpers/is-os.js create mode 100644 src/helpers/is-viewing-marketing-page.js create mode 100644 src/helpers/keycodes.js create mode 100644 src/helpers/render-text-with-markdown-links.js create mode 100644 src/hooks/useConnectionRestored.js create mode 100644 src/hot-routes.js create mode 100644 src/reducers/message.js delete mode 100644 src/reducers/users.js create mode 100644 src/subscribe-to-desktop-push.js create mode 100644 src/views/channelSettings/components/editDropdown.js rename src/views/channelSettings/components/{loginTokenSettings.js => joinTokenSettings.js} (95%) rename src/views/channelSettings/components/{loginTokenToggle.js => joinTokenToggle.js} (96%) create mode 100644 src/views/community/components/requestToJoinCommunity.js delete mode 100644 src/views/communityAnalytics/components/analyticsUpsell/index.js delete mode 100644 src/views/communityAnalytics/components/analyticsUpsell/style.js delete mode 100644 src/views/communityBilling/components/administratorEmailForm.js delete mode 100644 src/views/communityBilling/components/editSourceDropdown.js delete mode 100644 src/views/communityBilling/components/invoice.js delete mode 100644 src/views/communityBilling/components/mutationWrapper.js delete mode 100644 src/views/communityBilling/components/source.js delete mode 100644 src/views/communityBilling/components/subscription.js delete mode 100644 src/views/communityBilling/index.js delete mode 100644 src/views/communityBilling/style.js delete mode 100644 src/views/communityBilling/utils.js delete mode 100644 src/views/communityMembers/components/importSlack.js create mode 100644 src/views/communityMembers/components/joinTokenSettings.js create mode 100644 src/views/communityMembers/components/joinTokenToggle.js create mode 100644 src/views/communityMembers/components/resetJoinToken.js create mode 100644 src/views/communitySettings/components/slack/channelConnection.js create mode 100644 src/views/communitySettings/components/slack/channelSlackManager.js create mode 100644 src/views/communitySettings/components/slack/connectSlack.js create mode 100644 src/views/communitySettings/components/slack/index.js create mode 100644 src/views/communitySettings/components/slack/sendInvitations.js create mode 100644 src/views/communitySettings/components/slack/style.js delete mode 100644 src/views/compose/index.js delete mode 100644 src/views/compose/style.js create mode 100644 src/views/dashboard/components/desktopAppUpsell/index.js create mode 100644 src/views/dashboard/components/desktopAppUpsell/style.js delete mode 100644 src/views/dashboard/components/facepile.js delete mode 100644 src/views/dashboard/components/inboxThread.js create mode 100644 src/views/dashboard/components/inboxThread/activity.js create mode 100644 src/views/dashboard/components/inboxThread/header/index.js create mode 100644 src/views/dashboard/components/inboxThread/header/style.js create mode 100644 src/views/dashboard/components/inboxThread/header/threadHeader.js create mode 100644 src/views/dashboard/components/inboxThread/header/timestamp.js create mode 100644 src/views/dashboard/components/inboxThread/header/userProfileThreadHeader.js create mode 100644 src/views/dashboard/components/inboxThread/index.js create mode 100644 src/views/dashboard/components/inboxThread/messageCount.js create mode 100644 src/views/dashboard/components/inboxThread/style.js delete mode 100644 src/views/dashboard/components/threadCommunityInfo.js create mode 100644 src/views/directMessages/containers/index.js create mode 100644 src/views/newUserOnboarding/components/appsUpsell/index.js create mode 100644 src/views/newUserOnboarding/components/appsUpsell/style.js delete mode 100644 src/views/newUserOnboarding/components/userInfo/index.js delete mode 100644 src/views/newUserOnboarding/components/userInfo/style.js create mode 100644 src/views/notifications/components/getCommunityMember.js create mode 100644 src/views/notifications/components/newThreadReactionNotification.js create mode 100644 src/views/notifications/components/privateCommunityRequestApprovedNotification.js create mode 100644 src/views/notifications/components/privateCommunityRequestSentNotification.js create mode 100644 src/views/pages/apps/index.js create mode 100644 src/views/pages/apps/style.js create mode 100644 src/views/pages/faq/index.js create mode 100644 src/views/pages/faq/style.js delete mode 100644 src/views/pages/pricing/components/communityList.js delete mode 100644 src/views/pages/pricing/components/discount.js delete mode 100644 src/views/pages/pricing/components/faq.js delete mode 100644 src/views/pages/pricing/components/feature.js delete mode 100644 src/views/pages/pricing/components/intro.js delete mode 100644 src/views/pages/pricing/components/paid.js delete mode 100644 src/views/pages/pricing/index.js create mode 100644 src/views/privateCommunityJoin/index.js create mode 100644 src/views/thread/components/desktopAppUpsell/index.js create mode 100644 src/views/thread/components/desktopAppUpsell/style.js create mode 100644 src/views/thread/components/watercoolerActionBar.js create mode 100644 src/views/thread/container.js create mode 100644 src/views/thread/redirect-old-route.js create mode 100644 src/views/userSettings/components/deleteAccountForm.js create mode 100644 src/views/userSettings/components/downloadDataForm.js rename src/{components/editForm/user.js => views/userSettings/components/editForm.js} (56%) delete mode 100644 src/views/userSettings/components/invoices.js create mode 100644 src/views/userSettings/components/overview.js delete mode 100644 src/views/userSettings/components/recurringPaymentsList.js delete mode 100644 vulcan/models/community.js delete mode 100644 vulcan/models/db.js delete mode 100644 vulcan/models/message.js delete mode 100644 vulcan/models/thread.js delete mode 100644 vulcan/models/user.js create mode 100644 vulcan/queues/constants.js create mode 100644 vulcan/queues/events/created.js create mode 100644 vulcan/queues/events/deleted.js create mode 100644 vulcan/queues/events/edited.js create mode 100644 vulcan/queues/index.js create mode 100644 vulcan/queues/types/community.js create mode 100644 vulcan/queues/types/message.js create mode 100644 vulcan/queues/types/thread/index.js create mode 100644 vulcan/queues/types/thread/moved.js create mode 100644 vulcan/queues/types/user.js create mode 100644 vulcan/utils/get-queue-from-type.js rename vulcan/{models/utils.js => utils/index.js} (92%) rename vulcan/{models => utils}/text-parsing.js (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6a65eef7ea..6e4a0bcac2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,14 +12,13 @@ aliases: - &save-yarn-cache paths: - node_modules - - mobile/node_modules - ~/.cache/yarn key: v1-yarn-{{ arch }}-{{ checksum "package.json" }} - &yarn | yarn - cd ./mobile && yarn && yarn setup + cd ./desktop && yarn && cd .. - &install-rethinkdb name: Install RethinkDB 2.3.5 @@ -36,16 +35,19 @@ aliases: background: true - &setup-and-build-web - name: Setup and build + name: Setup and build web command: | - node -e "const setup = require('./shared/testing/setup.js')().then(() => process.exit())" + cp now-secrets.example.json now-secrets.json yarn run build:web - yarn run build:api + + - &build-api + name: Build API + command: yarn run build:api - &start-api name: Start the API in the background - command: TEST_DB=true yarn run dev:api + command: yarn run start:api:test background: true - &start-web @@ -61,11 +63,6 @@ js_defaults: &js_defaults docker: - image: circleci/node:8 -macos_defaults: &macos_defaults - <<: *defaults - macos: - xcode: "9.2.0" - version: 2 jobs: @@ -90,28 +87,53 @@ jobs: - image: circleci/node:8-browsers - image: redis:3.2.7 - image: cypress/base:6 + - image: rethinkdb:2.3.5 environment: TERM: xterm steps: - attach_workspace: at: ~/spectrum - - run: *install-rethinkdb - - run: *start-rethinkdb - - run: sleep 10 + - run: yarn run db:migrate + - run: yarn run db:seed + - run: + name: Run Unit Tests + command: yarn run test:ci - run: *setup-and-build-web + - run: *build-api - run: *start-api - run: *start-web - - run: sleep 60 + # Wait for the API and webserver to start + - run: ./node_modules/.bin/wait-on http://localhost:3000 http://localhost:3001 - run: name: Run Unit Tests command: yarn run test:ci + - run: + name: Install Cypress + command: yarn run cypress:install - run: name: Run E2E Tests - command: yarn run test:e2e + command: test $CYPRESS_RECORD_KEY && yarn run test:e2e -- --record || yarn run test:e2e - run: - name: Danger - when: always - command: yarn run danger ci + name: Test desktop apps + command: yarn run test:desktop + + # deploy_alpha: + # <<: *js_defaults + # docker: + # - image: circleci/node:8-browsers + # steps: + # - attach_workspace: + # at: ~/spectrum + # - run: *build-api + # - run: + # name: Deploy and alias API + # command: | + # cd build-api + # npx now-cd --alias "alpha=api.alpha.spectrum.chat" --team space-program + # cd .. + # - run: + # name: Deploy and alias Hyperion + # command: npx now-cd --alias "alpha=hyperion.alpha.spectrum.chat" --team space-program # Run eslint, flow etc. test_static_js: @@ -126,57 +148,26 @@ jobs: name: Run ESLint command: yarn run lint - # Tests js of the mobile app - test_mobile_js: - <<: *js_defaults - steps: - - attach_workspace: - at: ~/spectrum - - run: cd ./mobile && yarn test:unit - - # Tests native code of the mobile app - test_mobile_native: - <<: *macos_defaults - steps: - - attach_workspace: - at: ~/spectrum - - run: - name: Install Expo CLI - command: npm install -g exp - - run: - name: Install Detox dependencies - command: | - brew tap wix/brew - brew install applesimutils --HEAD - npm install -g detox-cli - - run: - name: Rebuild Detox frameworks - command: | - cd ./mobile - detox clean-framework-cache - detox build-framework-cache - - run: - name: Start Packager in the background - command: cd ./mobile && exp start - background: true - - run: cd ./mobile && yarn test:e2e - workflows: version: 2 test: jobs: - checkout_environment - - test_mobile_js: - requires: - - checkout_environment - # Disabled as Expo is fixing their critical issue with Detox - # - test_mobile_native: - # requires: - # - checkout_environment + + # Testing - test_web: requires: - checkout_environment - test_static_js: requires: - checkout_environment + + # Deployment + # - deploy_alpha: + # requires: + # - test_static_js + # - test_web + # filters: + # branches: + # only: alpha diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 952e3b9b7e..0000000000 --- a/.editorconfig +++ /dev/null @@ -1,13 +0,0 @@ - -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore index c214a50fe2..db347ab95e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -7,7 +7,6 @@ npm-debug.log build .DS_Store src/config/FirebaseConfig.js -npm-debug.log rethinkdb_data debug now-secrets.json @@ -19,7 +18,6 @@ build-chronos build-mercury build-vulcan build-hyperion -build-pluto package-lock.json .vscode dump.rdb @@ -30,6 +28,4 @@ test-extend.js stats.json iris/.env api/.env -.expo -mobile/.expo test-results.json diff --git a/.eslintrc.js b/.eslintrc.js index 1383a7ba4f..a1e3ac97d4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,12 +1,25 @@ module.exports = { - extends: 'react-app', + extends: 'eslint:recommended', + parser: 'babel-eslint', env: { - node: false, + es6: true, + node: true, }, + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + plugins: ['eslint-plugin-flowtype'], rules: { - 'no-unused-vars': 0, 'no-undef': 0, - radix: 0, - 'import/first': 0, + 'no-console': ['warn', { allow: ['warn', 'error'] }], + 'no-unused-vars': 0, + 'no-empty': 0, + 'no-useless-escape': 1, + 'no-fallthrough': 1, + 'no-extra-boolean-cast': 1, }, }; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 510dfaf1d3..0000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "env": { - "browser": true, - "commonjs": true, - "es6": true, - "node": true - }, - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "sourceType": "module" - }, - "rules": { - "no-const-assign": 1, - "no-extra-semi": 0, - "semi": 0, - "no-fallthrough": 0, - "no-empty": 0, - "no-mixed-spaces-and-tabs": 0, - "no-redeclare": 0, - "no-this-before-super": 1, - "no-undef": 0, - "no-unreachable": 1, - "no-unused-vars": 1, - "no-use-before-define": 0, - "constructor-super": 1, - "curly": 0, - "eqeqeq": 0, - "func-names": 0, - "valid-typeof": 1 - } -} \ No newline at end of file diff --git a/.flowconfig b/.flowconfig index 29cbf4e011..45bff64eb6 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,22 +1,22 @@ [ignore] -.*/node_modules/.* .*/build.* .*/*.test.js -.*/.expo - -[include] - -[libs] +.*/node_modules/cypress +.*/node_modules/draft-js +.*/node_modules/graphql +.*/node_modules/protobufjs-no-cli +.*/node_modules/reqwest +.*/node_modules/react-apollo +.*/node_modules/dataloader +/node_modules/* +/email-template-scripts/* [options] suppress_comment=.*\\$FlowFixMe suppress_comment=.*\\$FlowIssue esproposal.class_instance_fields=enable module.system.node.resolve_dirname=node_modules -module.system.node.resolve_dirname=spectrum -module.file_ext=.ios.js -module.file_ext=.android.js -module.file_ext=.native.js +module.system.node.resolve_dirname=. module.file_ext=.web.js module.file_ext=.js module.file_ext=.jsx @@ -29,4 +29,4 @@ unclear-type=warn unsafe-getters-setters=error [version] -0.66.0 +0.66.0 \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 56f09d8c26..77970e62c4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,12 +7,13 @@ **Deploy after merge (delete what needn't be deployed)** - api - hyperion (frontend) +- desktop - athena - vulcan - mercury - hermes - chronos -- mobile +- analytics **Run database migrations (delete if no migration was added)** YES @@ -20,3 +21,8 @@ YES **Release notes for users (delete if codebase-only change)** - +**Related issues (delete if you don't know of any)** +Closes # + + + diff --git a/.gitignore b/.gitignore index 0acb427541..405378a4f0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build .DS_Store src/config/FirebaseConfig.js npm-debug.log +yarn-error.log rethinkdb_data debug now-secrets.json @@ -16,7 +17,8 @@ build-chronos build-mercury build-vulcan build-hyperion -build-pluto +build-electron +build-analytics package-lock.json .vscode dump.rdb @@ -27,6 +29,9 @@ test-extend.js stats.json iris/.env api/.env -.expo -mobile/.expo test-results.json +desktop/release +public/uploads +cypress/screenshots/ +cypress/videos/ +cacert diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000000..032ff45d57 --- /dev/null +++ b/.npmignore @@ -0,0 +1,46 @@ +# This is used by now when deploying hyperion, replacing .gitignore +# NOTE(@mxstbr): The important change is that `cacert` is NOT ignored! +node_modules +.sass-cache +npm-debug.log +build +.DS_Store +src/config/FirebaseConfig.js +npm-debug.log +yarn-error.log +rethinkdb_data +debug +now-secrets.json +build-iris +build-api +build-athena +build-hermes +build-chronos +build-mercury +build-vulcan +build-hyperion +build-electron +build-analytics +package-lock.json +.vscode +dump.rdb +*.swp +queries-by-response-size.js +queries-by-time.js +test-extend.js +stats.json +iris/.env +api/.env +test-results.json +public/uploads +cypress/screenshots/ +cypress/videos/ + +# This is hyperion-now-specific, do not copy to .gitignore +desktop +docs +cypress +admin +.circleci +.github + diff --git a/.prettierignore b/.prettierignore index c6417d938a..d70da09d9d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,3 @@ flow-typed +package.json +shared/analytics/event-types/* \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..c1a6f66713 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "es5" +} diff --git a/README.md b/README.md index f434f8de46..834b7a687a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Spectrum](./public/img/media.png)](https://spectrum.chat) ### Simple, powerful online communities. - + This is the main monorepo codebase of [Spectrum](https://spectrum.chat). Every single line of code that's not packaged into a reusable library is in this repository. @@ -16,7 +16,7 @@ It is difficult to grow, manage and measure the impact of online communities. Co Spectrum aims to be the best platform to build any kind of community online by combining the best of web 2.0 forums and real-time chat apps. With best-in-class moderation tooling, a single platform for all your communities, threaded conversations by default, community health monitoring (and much more to come), we think that we will be able to help more people start and grow the best online communities. -> "[Spectrum] will take the place that Reddit used to have a long time ago for communities (especially tech) to freely share ideas and iteract. Except realtime and trolling-free." +> "[Spectrum] will take the place that Reddit used to have a long time ago for communities (especially tech) to freely share ideas and interact. Except realtime and trolling-free." > > \- [Guillermo Rauch (@rauchg)](https://twitter.com/rauchg/status/930946768841228288) @@ -39,17 +39,17 @@ Spectrum has been under full-time development since March, 2017. See [the roadma - [First time setup](#first-time-setup) - [Running the app locally](#running-the-app-locally) - [Roadmap](https://github.com/withspectrum/spectrum/projects/19) -- Technical - - [Testing](docs/testing.md) - - [Background Jobs](docs/backend/background-jobs.md) - - [Deployment](docs/backend/deployment.md) - - [GraphQL](docs/backend/api/) +- [Technical](docs/) + - [Testing](docs/testing/intro.md) + - [Background Jobs](docs/workers/background-jobs.md) + - [Deployment](docs/deployments.md) + - [API](docs/backend/api/) - [Fragments](docs/backend/api/fragments.md) - [Pagination](docs/backend/api/pagination.md) - [Testing](docs/backend/api/testing.md) - [Tips and Tricks](docs/backend/api/tips-and-tricks.md) -## Contributing +## Contributing **We heartily welcome any and all contributions that match [our product roadmap](https://github.com/withspectrum/spectrum/projects/19) and engineering standards!** @@ -67,7 +67,7 @@ If you found a technical bug on Spectrum or have ideas for features we should im #### Fixing a bug or implementing a new feature -If you find a bug on Spectrum and open a PR that fixes it we'll review it as soon as possible to ensure it matches our engineering standards. If you want implement a new feature, open an issue first to discuss what it'd look like and to ensure it fits in [our roadmap](https://github.com/withspectrum/spectrum/projects/19) and plans for the app. +If you find a bug on Spectrum and open a PR that fixes it we'll review it as soon as possible to ensure it matches our engineering standards. If you want to implement a new feature, open an issue first to discuss what it'd look like and to ensure it fits in [our roadmap](https://github.com/withspectrum/spectrum/projects/19) and plans for the app. If you want to contribute but are unsure to start, we have [a "good first issue" label](https://github.com/withspectrum/spectrum/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) which is applied to newcomer-friendly issues. Take a look at [the full list of good first issues](https://github.com/withspectrum/spectrum/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and pick something you like! There is also [an "open" channel in the Spectrum community on Spectrum](https://spectrum.chat/spectrum/open) (how meta), if you run into troubles while trying to contribute that is the best place to talk to us. @@ -83,7 +83,7 @@ Want to fix a bug or implement an agreed-upon feature? Great, jump to the [local With the ground rules out of the way, let's talk about the coarse architecture of this mono repo: -- **Full-stack JavaScript**: We use Node.js to power our servers, and React to power our frontend and mobile apps. Almost all of the code you'll touch in this codebase will be JavaScript. +- **Full-stack JavaScript**: We use Node.js to power our servers, and React to power our frontend apps. Almost all of the code you'll touch in this codebase will be JavaScript. - **Background Jobs**: We leverage background jobs (powered by [`bull`](https://github.com/OptimalBits/bull) and Redis) a lot. These jobs are handled by a handful of small worker servers, each with its own purpose. Here is a list of all the big technologies we use: @@ -93,8 +93,7 @@ Here is a list of all the big technologies we use: - **GraphQL**: API, powered by the entire Apollo toolchain - **Flowtype**: Type-safe JavaScript - **PassportJS**: Authentication -- **React**: Frontend and mobile apps -- **Expo**: Mobile apps +- **React**: Frontend React app - **DraftJS**: WYSIWYG writing experience on the web #### Folder structure @@ -104,13 +103,12 @@ spectrum/ ├── api # API server ├── athena # Worker server (notifications and general processing) ├── chronos # Worker server (cron jobs) +├── desktop # desktop apps (build with electron) ├── docs ├── email-templates ├── hermes # Worker server (email sending) ├── hyperion # Server rendering server ├── mercury # Worker server (reputation) -├── mobile # Mobile apps -├── pluto # Worker server (payments; syncing with Stripe) ├── public # Public files used on the frontend ├── shared # Shared JavaScript code ├── src # Frontend SPA @@ -151,6 +149,11 @@ The first step to running Spectrum locally is downloading the code by cloning th ```sh git clone git@github.com:withspectrum/spectrum.git +``` + If you get `Permission denied` error using `ssh` refer [here](https://help.github.com/articles/error-permission-denied-publickey/) +or use `https` link as a fallback. +```sh +git clone https://github.com/withspectrum/spectrum.git ``` #### Installation @@ -185,6 +188,19 @@ yarn run db:seed # ⚠️ To empty the database (e.g. if there's faulty data) run yarn run db:drop ``` +There's a shortcut for dropping, migrating and seeding the database too: +```sh +yarn run db:reset +``` + +The `testing` database used in end to end tests is managed separately. It is built, migrated, and seeded when you run: + +```sh +yarn run start:api:test +``` + +To drop the `testing` database, go to http://localhost:8080/#tables while `rethinkdb` is running, and click Delete Database on the appropriate database. + #### Getting the secrets While the app will run without any secrets set up, you won't be able to sign in locally. To get that set up, copy the provided example secrets file to the real location: @@ -225,38 +241,26 @@ yarn run dev:api #### Develop the web UI -To develop the frontend and web UI run +To develop the frontend and web UI run ``` yarn run dev:web ``` -#### Develop the mobile apps +#### Develop the desktop app -To start the mobile apps run: +To develop the desktop app you have to have the dev web server running in the background (`yarn run dev:web`) and then, in another terminal tab, run: ``` -yarn run dev:mobile -``` - -And then open either the iOS simulator or the Android simulator with - -```sh -yarn run open:ios -# or -yarn run open:android +yarn run dev:desktop ``` -Refer to [the Expo documentation on how to install the simulators](https://docs.expo.io/versions/v25.0.0/guides/debugging.html#using-a-simulator--emulator). - -> Note: If something didn't work or you ran into troubles please submit PRs to improve this doc and keep it up to date! - -
-
- -
- -## License +> Note: If something didn't work or you ran into troubles please submit PRs to improve this doc and keep it up to date! -BSD 3-Clause, see the [LICENSE](./LICENSE) file. +
+
+ +
+## License +BSD 3-Clause, see the [LICENSE](./LICENSE) file. \ No newline at end of file diff --git a/admin/README.md b/admin/README.md index a04686c9ce..5971086785 100644 --- a/admin/README.md +++ b/admin/README.md @@ -213,8 +213,8 @@ To configure the syntax highlighting in your favorite text editor, head to the [ ## Displaying Lint Output in the Editor ->Note: this feature is available with `react-scripts@0.2.0` and higher.
->It also only works with npm 3 or higher. +> Note: this feature is available with `react-scripts@0.2.0` and higher.
+> It also only works with npm 3 or higher. Some editors, including Sublime Text, Atom, and Visual Studio Code, provide plugins for ESLint. @@ -464,7 +464,7 @@ Then in `package.json`, add the following lines to `scripts`: "test": "react-scripts test --env=jsdom", ``` ->Note: To use a different preprocessor, replace `build-css` and `watch-css` commands according to your preprocessor’s documentation. +> Note: To use a different preprocessor, replace `build-css` and `watch-css` commands according to your preprocessor’s documentation. Now you can rename `src/App.css` to `src/App.scss` and run `npm run watch-css`. The watcher will find every Sass file in `src` subdirectories, and create a corresponding CSS file next to it, in our case overwriting `src/App.css`. Since `src/App.js` still imports `src/App.css`, the styles become a part of your application. You can now edit `src/App.scss`, and `src/App.css` will be regenerated. @@ -565,7 +565,7 @@ An alternative way of handling static assets is described in the next section. ## Using the `public` Folder ->Note: this feature is available with `react-scripts@0.5.0` and higher. +> Note: this feature is available with `react-scripts@0.5.0` and higher. ### Changing the HTML @@ -586,7 +586,7 @@ This mechanism provides a number of benefits: However there is an **escape hatch** that you can use to add an asset outside of the module system. -If you put a file into the `public` folder, it will **not** be processed by Webpack. Instead it will be copied into the build folder untouched. To reference assets in the `public` folder, you need to use a special variable called `PUBLIC_URL`. +If you put a file into the `public` folder, it will **not** be processed by Webpack. Instead it will be copied into the build folder untouched. To reference assets in the `public` folder, you need to use a special variable called `PUBLIC_URL`. Inside `index.html`, you can use it like this: @@ -701,7 +701,7 @@ To learn more about Flow, check out [its documentation](https://flowtype.org/). ## Adding Custom Environment Variables ->Note: this feature is available with `react-scripts@0.2.3` and higher. +> Note: this feature is available with `react-scripts@0.2.3` and higher. Your project can consume variables declared in your environment as if they were declared locally in your JS files. By default you will have `NODE_ENV` defined for you, and any other environment variables starting with @@ -709,7 +709,7 @@ default you will have `NODE_ENV` defined for you, and any other environment vari **The environment variables are embedded during the build time**. Since Create React App produces a static HTML/CSS/JS bundle, it can’t possibly read them at runtime. To read them at runtime, you would need to load HTML into memory on the server and replace placeholders in runtime, just like [described here](#injecting-data-from-the-server-into-the-page). Alternatively you can rebuild the app on the server anytime you change them. ->Note: You must create custom environment variables beginning with `REACT_APP_`. Any other variables except `NODE_ENV` will be ignored to avoid accidentally [exposing a private key on the machine that could have the same name](https://github.com/facebookincubator/create-react-app/issues/865#issuecomment-252199527). Changing any environment variables will require you to restart the development server if it is running. +> Note: You must create custom environment variables beginning with `REACT_APP_`. Any other variables except `NODE_ENV` will be ignored to avoid accidentally [exposing a private key on the machine that could have the same name](https://github.com/facebookincubator/create-react-app/issues/865#issuecomment-252199527). Changing any environment variables will require you to restart the development server if it is running. These environment variables will be defined for you on `process.env`. For example, having an environment variable named `REACT_APP_SECRET_CODE` will be exposed in your JS as `process.env.REACT_APP_SECRET_CODE`. @@ -764,7 +764,7 @@ When you compile the app with `npm run build`, the minification step will strip ### Referencing Environment Variables in the HTML ->Note: this feature is available with `react-scripts@0.9.0` and higher. +> Note: this feature is available with `react-scripts@0.9.0` and higher. You can also access the environment variables starting with `REACT_APP_` in the `public/index.html`. For example: @@ -798,7 +798,7 @@ REACT_APP_SECRET_CODE=abcdef npm start ### Adding Development Environment Variables In `.env` ->Note: this feature is available with `react-scripts@0.5.0` and higher. +> Note: this feature is available with `react-scripts@0.5.0` and higher. To define permanent environment variables, create a file called `.env` in the root of your project: @@ -810,7 +810,7 @@ REACT_APP_SECRET_CODE=abcdef #### What other `.env` files are can be used? ->Note: this feature is **available with `react-scripts@1.0.0` and higher**. +> Note: this feature is **available with `react-scripts@1.0.0` and higher**. * `.env`: Default. * `.env.local`: Local overrides. **This file is loaded for all environments except test.** @@ -826,7 +826,7 @@ Files on the left have more priority than files on the right: These variables will act as the defaults if the machine does not explicitly set them.
Please refer to the [dotenv documentation](https://github.com/motdotla/dotenv) for more details. ->Note: If you are defining environment variables for development, your CI and/or hosting platform will most likely need +> Note: If you are defining environment variables for development, your CI and/or hosting platform will most likely need these defined as well. Consult their documentation how to do this. For example, see the documentation for [Travis CI](https://docs.travis-ci.com/user/environment-variables/) or [Heroku](https://devcenter.heroku.com/articles/config-vars). ## Can I Use Decorators? @@ -862,7 +862,7 @@ You can find the companion GitHub repository [here](https://github.com/fullstack ## Proxying API Requests in Development ->Note: this feature is available with `react-scripts@0.2.3` and higher. +> Note: this feature is available with `react-scripts@0.2.3` and higher. People often serve the front-end React app from the same host and port as their backend implementation.
For example, a production setup might look like this after the app is deployed: @@ -904,7 +904,7 @@ When you enable the `proxy` option, you opt into a more strict set of host check This shouldn’t affect you when developing on `localhost`, but if you develop remotely like [described here](https://github.com/facebookincubator/create-react-app/issues/2271), you will see this error in the browser after enabling the `proxy` option: ->Invalid Host header +> Invalid Host header To work around it, you can specify your public development host in a file called `.env.development` in the root of your project: @@ -926,7 +926,7 @@ We don’t recommend this approach. ### Configuring the Proxy Manually ->Note: this feature is available with `react-scripts@1.0.0` and higher. +> Note: this feature is available with `react-scripts@1.0.0` and higher. If the `proxy` option is **not** flexible enough for you, you can specify an object in the following form (in `package.json`).
You may also specify any configuration value [`http-proxy-middleware`](https://github.com/chimurai/http-proxy-middleware#options) or [`http-proxy`](https://github.com/nodejitsu/node-http-proxy#options) supports. @@ -984,7 +984,7 @@ You may also narrow down matches using `*` and/or `**`, to match the path exactl ## Using HTTPS in Development ->Note: this feature is available with `react-scripts@0.4.0` and higher. +> Note: this feature is available with `react-scripts@0.4.0` and higher. You may require the dev server to serve pages over HTTPS. One particular case where this could be useful is when using [the "proxy" feature](#proxying-api-requests-in-development) to proxy requests to an API server when that API server is itself serving HTTPS. @@ -1049,7 +1049,7 @@ Then, on the server, you can replace `__SERVER_DATA__` with a JSON of real data ## Running Tests ->Note: this feature is available with `react-scripts@0.3.0` and higher.
+> Note: this feature is available with `react-scripts@0.3.0` and higher.
>[Read the migration guide to learn how to enable it in older projects!](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md#migrating-from-023-to-030) Create React App uses [Jest](https://facebook.github.io/jest/) as its test runner. To prepare for this integration, we did a [major revamp](https://facebook.github.io/jest/blog/2016/09/01/jest-15.html) of Jest so if you heard bad things about it years ago, give it another try. @@ -1198,7 +1198,7 @@ and then use them in your tests like you normally do. ### Initializing Test Environment ->Note: this feature is available with `react-scripts@0.4.0` and higher. +> Note: this feature is available with `react-scripts@0.4.0` and higher. If your app uses a browser API that you need to mock in your tests or if you just need a global setup before running your tests, add a `src/setupTests.js` to your project. It will be automatically executed before running your tests. @@ -1281,7 +1281,7 @@ CI=true npm run build The test command will force Jest to run tests once instead of launching the watcher. -> If you find yourself doing this often in development, please [file an issue](https://github.com/facebookincubator/create-react-app/issues/new) to tell us about your use case because we want to make watcher the best experience and are open to changing how it works to accommodate more workflows. +> If you find yourself doing this often in development, please [file an issue](https://github.com/facebookincubator/create-react-app/issues/new) to tell us about your use case because we want to make watcher the best experience and are open to changing how it works to accommodate more workflows. The build command will check for linter warnings and fail if any are found. @@ -1561,7 +1561,7 @@ This will let Create React App correctly infer the root path to use in the gener #### Serving the Same Build from Different Paths ->Note: this feature is available with `react-scripts@0.9.0` and higher. +> Note: this feature is available with `react-scripts@0.9.0` and higher. If you are not using the HTML5 `pushState` history API or not using client-side routing at all, it is unnecessary to specify the URL from which your app will be served. Instead, you can put this in your `package.json`: @@ -1638,7 +1638,7 @@ For more information see [Add Firebase to your JavaScript Project](https://fireb ### GitHub Pages ->Note: this feature is available with `react-scripts@0.2.0` and higher. +> Note: this feature is available with `react-scripts@0.2.0` and higher. #### Step 1: Add `homepage` to `package.json` diff --git a/admin/package.json b/admin/package.json index ad9a021deb..2ce71dfe0f 100644 --- a/admin/package.json +++ b/admin/package.json @@ -25,7 +25,6 @@ "apollo-link-http": "^1.3.2", "apollo-link-ws": "^1.0.4", "apollo-upload-client": "6.x", - "apollo-upload-server": "^2.0.4", "apollo-utilities": "^1.0.4", "d3-array": "^1.2.0", "graphql": "^0.12.3", @@ -44,11 +43,8 @@ "subscriptions-transport-ws": "^0.9.5" }, "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject", - "predeploy": "npm run build", - "deploy": "now build" + "start": "NODE_PATH=../ react-scripts start", + "build": "NODE_PATH=../ react-scripts build", + "test": "NODE_PATH=../ react-scripts test --env=jsdom" } } diff --git a/admin/src/components/globals/index.js b/admin/src/components/globals/index.js index 81aef7ae22..b4d054bc65 100644 --- a/admin/src/components/globals/index.js +++ b/admin/src/components/globals/index.js @@ -437,7 +437,7 @@ export const Tooltip = props => css` &:hover:after, &:hover:before { opacity: 1; - transition: all 0.1s ease-in 0.1s; + transition: opacity 0.1s ease-in 0.1s; } `; @@ -487,7 +487,7 @@ export const Onboarding = props => css` &:after, &:before { opacity: 1; - transition: all 0.1s ease-in 0.1s; + transition: opacity 0.1s ease-in 0.1s; } `; diff --git a/admin/src/index.js b/admin/src/index.js index 6139b08a37..c57c23f537 100644 --- a/admin/src/index.js +++ b/admin/src/index.js @@ -5,7 +5,7 @@ import { Router } from 'react-router'; import { ApolloProvider } from 'react-apollo'; import { ThemeProvider } from 'styled-components'; import { Provider } from 'react-redux'; -import { theme } from './components/theme'; +import { theme } from 'shared/theme'; import { client } from './api'; import { initStore } from './store'; import Routes from './routes'; diff --git a/admin/src/views/communities/components/search/index.js b/admin/src/views/communities/components/search/index.js index 8a082a34d2..68ff9f0c74 100644 --- a/admin/src/views/communities/components/search/index.js +++ b/admin/src/views/communities/components/search/index.js @@ -5,6 +5,7 @@ import pure from 'recompose/pure'; import { connect } from 'react-redux'; import { withApollo } from 'react-apollo'; import { withRouter } from 'react-router'; +import { ESC, BACKSPACE, ARROW_DOWN, ARROW_UP } from 'src/helpers/keycodes'; import { Spinner } from '../../../../components/globals'; import { throttle } from '../../../../helpers/utils'; import { SEARCH_COMMUNITIES_QUERY } from '../../../../api/queries'; @@ -99,7 +100,7 @@ class Search extends Component { ); // if person presses esc, clear all results, stop loading - if (e.keyCode === 27) { + if (e.keyCode === ESC) { this.setState({ searchResults: [], searchIsLoading: false, @@ -108,14 +109,12 @@ class Search extends Component { return; } - // backspace - if (e.keyCode === 8) { + if (e.keyCode === BACKSPACE) { if (searchString.length > 0) return; return input && input.focus(); } - //escape - if (e.keyCode === 27) { + if (e.keyCode === ESC) { this.setState({ searchResults: [], searchIsLoading: false, @@ -124,8 +123,7 @@ class Search extends Component { return input && input.focus(); } - // down - if (e.keyCode === 40) { + if (e.keyCode === ARROW_DOWN) { if (indexOfFocusedSearchResult === searchResults.length - 1) return; if (searchResults.length === 1) return; @@ -137,8 +135,7 @@ class Search extends Component { return; } - // up - if (e.keyCode === 38) { + if (e.keyCode === ARROW_UP) { // 1 if (indexOfFocusedSearchResult === 0) return; if (searchResults.length === 1) return; @@ -151,8 +148,7 @@ class Search extends Component { return; } - // enter - if (e.keyCode === 13) { + if (e.keyCode === ENTER) { if (!searchResults[indexOfFocusedSearchResult]) return; return this.goToCommunity(searchResults[indexOfFocusedSearchResult].slug); } @@ -261,4 +257,9 @@ class Search extends Component { } } -export default compose(withApollo, withRouter, connect(), pure)(Search); +export default compose( + withApollo, + withRouter, + connect(), + pure +)(Search); diff --git a/admin/src/views/users/components/search/index.js b/admin/src/views/users/components/search/index.js index 4f29d584e1..8dd7f9e72b 100644 --- a/admin/src/views/users/components/search/index.js +++ b/admin/src/views/users/components/search/index.js @@ -6,6 +6,13 @@ import pure from 'recompose/pure'; import { connect } from 'react-redux'; import { withApollo } from 'react-apollo'; import { withRouter } from 'react-router'; +import { + ESC, + BACKSPACE, + ENTER, + ARROW_DOWN, + ARROW_UP, +} from 'src/helpers/keycodes'; import { Spinner } from '../../../../components/globals'; import { throttle } from '../../../../helpers/utils'; import { searchUsersQuery } from 'shared/graphql/queries/search/searchUsers'; @@ -104,8 +111,7 @@ class Search extends React.Component { focusedSearchResult ); - // if person presses esc, clear all results, stop loading - if (e.keyCode === 27) { + if (e.keyCode === ESC) { this.setState({ searchResults: [], searchIsLoading: false, @@ -114,15 +120,13 @@ class Search extends React.Component { return; } - // backspace - if (e.keyCode === 8) { + if (e.keyCode === BACKSPACE) { if (searchString.length > 0) return; // $FlowFixMe return input && input.focus(); } - //escape - if (e.keyCode === 27) { + if (e.keyCode === ESC) { this.setState({ searchResults: [], searchIsLoading: false, @@ -132,8 +136,7 @@ class Search extends React.Component { return input && input.focus(); } - // down - if (e.keyCode === 40) { + if (e.keyCode === ARROW_DOWN) { if (indexOfFocusedSearchResult === searchResults.length - 1) return; if (searchResults.length === 1) return; @@ -145,8 +148,7 @@ class Search extends React.Component { return; } - // up - if (e.keyCode === 38) { + if (e.keyCode === ARROW_UP) { // 1 if (indexOfFocusedSearchResult === 0) return; if (searchResults.length === 1) return; @@ -159,8 +161,7 @@ class Search extends React.Component { return; } - // enter - if (e.keyCode === 13) { + if (e.keyCode === ENTER) { if (!searchResults[indexOfFocusedSearchResult]) return; return this.goToUser(searchResults[indexOfFocusedSearchResult].username); } @@ -269,4 +270,9 @@ class Search extends React.Component { } } -export default compose(withApollo, withRouter, connect(), pure)(Search); +export default compose( + withApollo, + withRouter, + connect(), + pure +)(Search); diff --git a/admin/yarn.lock b/admin/yarn.lock index 148d316e8a..875affb37e 100644 --- a/admin/yarn.lock +++ b/admin/yarn.lock @@ -3,30 +3,34 @@ "@babel/polyfill@^7.0.0-beta.32": - version "7.0.0-beta.38" - resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.0.0-beta.38.tgz#df9ca32ffe4f6de0b99ab495c197837f422bb12e" + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.0.0.tgz#c8ff65c9ec3be6a1ba10113ebd40e8750fb90bff" + integrity sha512-dnrMRkyyr74CRelJwvgnnSUDh2ge2NCTyHVwpOdvRMHtJUyxLtMAfhBN3s64pY41zdw0kgiLPh6S20eb1NcX6Q== dependencies: - core-js "^2.4.0" + core-js "^2.5.7" regenerator-runtime "^0.11.1" -"@babel/runtime@^7.0.0-beta.32", "@babel/runtime@^7.0.0-beta.37": - version "7.0.0-beta.38" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0-beta.38.tgz#8b7f16245b1f86fc168a1846ab6d77a238f6d16c" +"@babel/runtime@^7.0.0-beta.32", "@babel/runtime@^7.0.0-beta.37", "@babel/runtime@^7.1.2": + version "7.1.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.5.tgz#4170907641cf1f61508f563ece3725150cc6fe39" + integrity sha512-xKnPpXG/pvK1B90JkwwxSGii90rQGKtzcMt2gI5G6+M0REXaq6rOHsGC2ay6/d0Uje7zzvSzjEzfR3ENhFlrfA== dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.1" + regenerator-runtime "^0.12.0" -"@types/async@2.0.46": - version "2.0.46" - resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.46.tgz#f13a6c336a6582b95d0c0269e796b709488fd54d" +"@types/async@2.0.50": + version "2.0.50" + resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.50.tgz#117540e026d64e1846093abbd5adc7e27fda7bcb" + integrity sha512-VMhZMMQgV1zsR+lX/0IBfAk+8Eb7dPVMWiQGFAt3qjo5x7Ml6b77jUo0e1C3ToD+XRDXqtrfw+6AB0uUsPEr3Q== -"@types/zen-observable@0.5.3", "@types/zen-observable@^0.5.3": - version "0.5.3" - resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.5.3.tgz#91b728599544efbb7386d8b6633693a3c2e7ade5" +"@types/zen-observable@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" + integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== "@vx/bounds@0.0.129": version "0.0.129" resolved "https://registry.yarnpkg.com/@vx/bounds/-/bounds-0.0.129.tgz#7ecbdfaa3f5041e3f87c851bf4981547e2d19ffe" + integrity sha1-fsvfqj9QQeP4fIUb9JgVR+LRn/4= dependencies: prop-types "^15.5.10" react-dom "^15.0.0 || 15.x" @@ -34,18 +38,21 @@ "@vx/curve@0.0.127", "@vx/curve@^0.0.127": version "0.0.127" resolved "https://registry.yarnpkg.com/@vx/curve/-/curve-0.0.127.tgz#f05ea9871a97e1e1d59129b847f216751ffb31c3" + integrity sha1-8F6phxqX4eHVkSm4R/IWdR/7McM= dependencies: d3-shape "^1.0.6" "@vx/event@^0.0.127": version "0.0.127" resolved "https://registry.yarnpkg.com/@vx/event/-/event-0.0.127.tgz#8b0079a63cf0aeefd884123dd99ee18efef0af69" + integrity sha1-iwB5pjzwru/YhBI92Z7hjv7wr2k= dependencies: "@vx/point" "0.0.127" "@vx/gradient@^0.0.129": version "0.0.129" resolved "https://registry.yarnpkg.com/@vx/gradient/-/gradient-0.0.129.tgz#2c6f6a41085533166ba6e1c9cdbb3e52d4dd7e85" + integrity sha1-LG9qQQhVMxZrpuHJzbs+UtTdfoU= dependencies: classnames "^2.2.5" prop-types "^15.5.7" @@ -53,6 +60,7 @@ "@vx/grid@^0.0.131": version "0.0.131" resolved "https://registry.yarnpkg.com/@vx/grid/-/grid-0.0.131.tgz#572885166f0a3e05769903f1a4002ef1ade9a252" + integrity sha1-VyiFFm8KPgV2mQPxpAAu8a3polI= dependencies: "@vx/group" "0.0.127" "@vx/point" "0.0.127" @@ -62,22 +70,26 @@ "@vx/group@0.0.127": version "0.0.127" resolved "https://registry.yarnpkg.com/@vx/group/-/group-0.0.127.tgz#d607c957119cdb787aa709818532f40e7e04eb8f" + integrity sha1-1gfJVxGc23h6pwmBhTL0Dn4E648= dependencies: classnames "^2.2.5" "@vx/point@0.0.127": version "0.0.127" resolved "https://registry.yarnpkg.com/@vx/point/-/point-0.0.127.tgz#51ef7f648488feed758a6ec1cc8c71319602e3e7" + integrity sha1-Ue9/ZISI/u11im7BzIxxMZYC4+c= "@vx/scale@^0.0.127": version "0.0.127" resolved "https://registry.yarnpkg.com/@vx/scale/-/scale-0.0.127.tgz#2f81530c89b1ad837be387aaccebedd507f16549" + integrity sha1-L4FTDImxrYN744eqzOvt1QfxZUk= dependencies: d3-scale "^1.0.5" "@vx/shape@0.0.131", "@vx/shape@^0.0.131": version "0.0.131" resolved "https://registry.yarnpkg.com/@vx/shape/-/shape-0.0.131.tgz#38585e93319c9f958d317485b0b6520ff295f179" + integrity sha1-OFhekzGcn5WNMXSFsLZSD/KV8Xk= dependencies: "@vx/curve" "0.0.127" "@vx/group" "0.0.127" @@ -89,6 +101,7 @@ "@vx/tooltip@^0.0.134": version "0.0.134" resolved "https://registry.yarnpkg.com/@vx/tooltip/-/tooltip-0.0.134.tgz#8337f0876a98b3eec8e9636b4694126789eba60c" + integrity sha1-gzfwh2qYs+7I6WNrRpQSZ4nrpgw= dependencies: "@vx/bounds" "0.0.129" classnames "^2.2.5" @@ -97,79 +110,109 @@ abab@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" + integrity sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4= abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" +accepts@~1.3.4, accepts@~1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" + integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I= dependencies: - mime-types "~2.1.16" + mime-types "~2.1.18" negotiator "0.6.1" acorn-dynamic-import@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" + integrity sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ= dependencies: acorn "^4.0.3" acorn-globals@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf" + integrity sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8= dependencies: acorn "^4.0.4" acorn-jsx@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + integrity sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s= dependencies: acorn "^3.0.4" acorn@^3.0.4: version "3.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + integrity sha1-ReN/s56No/JbruP/U2niu18iAXo= acorn@^4.0.3, acorn@^4.0.4: version "4.0.13" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" + integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c= -acorn@^5.0.0, acorn@^5.2.1: - version "5.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822" +acorn@^5.0.0, acorn@^5.5.0: + version "5.7.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" + integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== add@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/add/-/add-2.0.6.tgz#248f0a9f6e5a528ef2295dbeec30532130ae2235" + integrity sha1-JI8Kn25aUo7yKV2+7DBTITCuIjU= address@1.0.3, address@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9" + integrity sha512-z55ocwKBRLryBs394Sm3ushTtBeg6VAeuku7utSoSnsJKvKcnXFIyC6vh27n3rXyxSgkJBBCAvyOn7gSUcTYjg== -ajv-keywords@^2.0.0, ajv-keywords@^2.1.0: +ajv-keywords@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" + integrity sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I= + +ajv-keywords@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" + integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= ajv@^4.9.1: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY= dependencies: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5, ajv@^5.2.0, ajv@^5.2.3: +ajv@^5.0.0, ajv@^5.1.5, ajv@^5.2.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= dependencies: co "^4.6.0" fast-deep-equal "^1.0.0" fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" +ajv@^6.0.1, ajv@^6.5.5: + version "6.5.5" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.5.tgz#cf97cdade71c6399a92c6d6c4177381291b781a1" + integrity sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + integrity sha1-DNkKVhCT810KmSVsIrcGlDP60Rc= dependencies: kind-of "^3.0.2" longest "^1.0.1" @@ -178,213 +221,265 @@ align-text@^0.1.1, align-text@^0.1.3: alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - -ansi-align@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-1.1.0.tgz#2f0c1658829739add5ebb15e6b0c6e3423f016ba" +ansi-align@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" + integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= dependencies: - string-width "^1.0.1" + string-width "^2.0.0" ansi-escapes@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= ansi-escapes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" + version "3.1.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" + integrity sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw== ansi-html@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" + integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= ansi-regex@^2.0.0, ansi-regex@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= -ansi-styles@^3.0.0, ansi-styles@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" +ansi-styles@^3.0.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" anymatch@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" + integrity sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA== dependencies: micromatch "^2.1.5" normalize-path "^2.0.0" +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + apollo-cache-inmemory@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.1.5.tgz#74111367fd59caee120197ef663dcb2516971300" + version "1.3.10" + resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.3.10.tgz#2e5375ad7ac0d30d59aaae3a2b5967673d0cf968" + integrity sha512-cJL8xxX2iytludvxY4goxYJ41n8avXirAjQkEwgSvGqrEzC0PG2DwtHZZh1QTnRRuM1rgj4MKiUiX/Ykhc5L+Q== dependencies: - apollo-cache "^1.1.0" - apollo-utilities "^1.0.4" - graphql-anywhere "^4.1.1" + apollo-cache "^1.1.20" + apollo-utilities "^1.0.25" + optimism "^0.6.6" -apollo-cache@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.1.0.tgz#281b6a0fb6ca2c5bce69825642f685de92d05f3c" +apollo-cache@1.1.20, apollo-cache@^1.1.20: + version "1.1.20" + resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.1.20.tgz#6152cc4baf6a63e376efee79f75de4f5c84bf90e" + integrity sha512-+Du0/4kUSuf5PjPx0+pvgMGV12ezbHA8/hubYuqRQoy/4AWb4faa61CgJNI6cKz2mhDd9m94VTNKTX11NntwkQ== dependencies: - apollo-utilities "^1.0.4" + apollo-utilities "^1.0.25" apollo-client@2.x: - version "2.2.0" - resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.2.0.tgz#e22c4ba3a3c01aec6dc916a5dbc0f39c58d277fb" + version "2.4.6" + resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.4.6.tgz#ba24a2def6ea9d487b41672652ca967cc7c05e4a" + integrity sha512-RsZVMYone7mu3Wj4sr7ehctN8pdaHsP4X1Sv6Ly4gZ/YDetCCVnhbmnk5q7kvDtfoo0jhhHblxgFyA3FLLImtA== dependencies: - "@types/zen-observable" "^0.5.3" - apollo-cache "^1.1.0" + "@types/zen-observable" "^0.8.0" + apollo-cache "1.1.20" apollo-link "^1.0.0" apollo-link-dedup "^1.0.0" - apollo-utilities "^1.0.4" + apollo-utilities "1.0.25" symbol-observable "^1.0.2" - zen-observable "^0.7.0" + zen-observable "^0.8.0" optionalDependencies: - "@types/async" "2.0.46" + "@types/async" "2.0.50" -apollo-engine-binary-darwin@0.2018.01-17-g9c203510f: - version "0.2018.1-17-g9c203510f" - resolved "https://registry.yarnpkg.com/apollo-engine-binary-darwin/-/apollo-engine-binary-darwin-0.2018.1-17-g9c203510f.tgz#0a920de1af104a02a3d095baa49c01c2d18f60e4" +apollo-engine-binary-darwin@0.2018.2-37-g678cbb68b: + version "0.2018.2-37-g678cbb68b" + resolved "https://registry.yarnpkg.com/apollo-engine-binary-darwin/-/apollo-engine-binary-darwin-0.2018.2-37-g678cbb68b.tgz#a7dbf5fd56d5090ebe955e28f9574228a10500fe" + integrity sha512-RyaZSEl4NV0BUuvq7Y2v2OyeYHIpvRGCHI/QTAgMh7XLZkxLeqKFR1HJh7SjnG/YU5PPsovPQ/YrFu3mUoBroA== -apollo-engine-binary-linux@0.2018.01-17-g9c203510f: - version "0.2018.1-17-g9c203510f" - resolved "https://registry.yarnpkg.com/apollo-engine-binary-linux/-/apollo-engine-binary-linux-0.2018.1-17-g9c203510f.tgz#745b29887904f1a5e28cf68606817d8d723eac27" +apollo-engine-binary-linux@0.2018.2-37-g678cbb68b: + version "0.2018.2-37-g678cbb68b" + resolved "https://registry.yarnpkg.com/apollo-engine-binary-linux/-/apollo-engine-binary-linux-0.2018.2-37-g678cbb68b.tgz#c348a89e0c3164f08687fa1701e71f378e862e57" + integrity sha512-nr/KJRwdGIeqjwRKA2JXAiHTNSCTrAxqSTYesaV+OcWGSk5I+G6Sto5XTIx75p+deIf5FDTj5Et46XBQkUWQ1Q== -apollo-engine-binary-windows@0.2018.01-17-g9c203510f: - version "0.2018.1-17-g9c203510f" - resolved "https://registry.yarnpkg.com/apollo-engine-binary-windows/-/apollo-engine-binary-windows-0.2018.1-17-g9c203510f.tgz#89cde03a7d714c6f283310853b8bd2b81733ce0a" +apollo-engine-binary-windows@0.2018.2-37-g678cbb68b: + version "0.2018.2-37-g678cbb68b" + resolved "https://registry.yarnpkg.com/apollo-engine-binary-windows/-/apollo-engine-binary-windows-0.2018.2-37-g678cbb68b.tgz#f6541b7b670b42b54efed4781abc724977009234" + integrity sha512-c+7ErUko4XbRFv8mCMUfQGzsw8Wg+qsjp2pngiAXmI/KBi90ZrshogpyEEWnbOMU78paEVoRulq+B30e0s294Q== apollo-engine@^0.8.3: - version "0.8.5" - resolved "https://registry.yarnpkg.com/apollo-engine/-/apollo-engine-0.8.5.tgz#d5ac00cef4018d754969581dd2998b5ce173713f" + version "0.8.10" + resolved "https://registry.yarnpkg.com/apollo-engine/-/apollo-engine-0.8.10.tgz#1f57260fee31997eb4abed6bd3b6ac11a5c17340" + integrity sha512-o0ttX4uaCfOckX2F0qwBOIDTgl7MuXMtuIKOdnQYpLwC8OwROgoslTWBoW8zAlTSJmdqXWZ2xQPo2ibLmewiwQ== dependencies: request "^2.81.0" stream-json "^0.5.2" optionalDependencies: - apollo-engine-binary-darwin "0.2018.01-17-g9c203510f" - apollo-engine-binary-linux "0.2018.01-17-g9c203510f" - apollo-engine-binary-windows "0.2018.01-17-g9c203510f" + apollo-engine-binary-darwin "0.2018.2-37-g678cbb68b" + apollo-engine-binary-linux "0.2018.2-37-g678cbb68b" + apollo-engine-binary-windows "0.2018.2-37-g678cbb68b" apollo-link-dedup@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/apollo-link-dedup/-/apollo-link-dedup-1.0.5.tgz#1c213d7ebbe48e74b016fffacd7e6a53dc309e32" + version "1.0.10" + resolved "https://registry.yarnpkg.com/apollo-link-dedup/-/apollo-link-dedup-1.0.10.tgz#7b94589fe7f969777efd18a129043c78430800ae" + integrity sha512-tpUI9lMZsidxdNygSY1FxflXEkUZnvKRkMUsXXuQUNoSLeNtEvUX7QtKRAl4k9ubLl8JKKc9X3L3onAFeGTK8w== + dependencies: + apollo-link "^1.2.3" + +apollo-link-http-common@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.5.tgz#d094beb7971523203359bf830bfbfa7b4e7c30ed" + integrity sha512-6FV1wr5AqAyJ64Em1dq5hhGgiyxZE383VJQmhIoDVc3MyNcFL92TkhxREOs4rnH2a9X2iJMko7nodHSGLC6d8w== dependencies: - apollo-link "^1.0.7" + apollo-link "^1.2.3" apollo-link-http@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.3.2.tgz#63537ee5ecf9c004efb0317f1222b7dbc6f21559" + version "1.5.5" + resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.5.tgz#7dbe851821771ad67fa29e3900c57f38cbd80da8" + integrity sha512-C5N6N/mRwmepvtzO27dgMEU3MMtRKSqcljBkYNZmWwH11BxkUQ5imBLPM3V4QJXNE7NFuAQAB5PeUd4ligivTQ== dependencies: - apollo-link "^1.0.7" + apollo-link "^1.2.3" + apollo-link-http-common "^0.2.5" apollo-link-ws@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/apollo-link-ws/-/apollo-link-ws-1.0.4.tgz#d0067aa0204470dbd3955aa5204f8dd72d389350" + version "1.0.9" + resolved "https://registry.yarnpkg.com/apollo-link-ws/-/apollo-link-ws-1.0.9.tgz#e2198abd6d3900e83fd842fdee335a28b347ea2d" + integrity sha512-CtKwLE61bCJTW5jrucOMm5PubeAlCl/9i45pg4GKKlAbl0zR4i2dww8TJkYoIY6iCyj4qjKW/uqGD6v5/aVwhg== dependencies: - apollo-link "^1.0.7" + apollo-link "^1.2.3" -apollo-link@^1.0.0, apollo-link@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.0.7.tgz#42cd38a7378332fc3e41a214ff6a6e5e703a556f" +apollo-link@^1.0.0, apollo-link@^1.0.7, apollo-link@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.3.tgz#9bd8d5fe1d88d31dc91dae9ecc22474d451fb70d" + integrity sha512-iL9yS2OfxYhigme5bpTbmRyC+Htt6tyo2fRMHT3K1XRL/C5IQDDz37OjpPy4ndx7WInSvfSZaaOTKFja9VWqSw== dependencies: - "@types/zen-observable" "0.5.3" apollo-utilities "^1.0.0" - zen-observable "^0.6.0" + zen-observable-ts "^0.8.10" apollo-upload-client@6.x: version "6.0.3" resolved "https://registry.yarnpkg.com/apollo-upload-client/-/apollo-upload-client-6.0.3.tgz#9f3249910ede32b7336968b1ae9fa438ed48a247" + integrity sha512-gJSaLg1KWtzshwX6lnhN05M1oFVoW9h66IuwwEjbuoLHz6NpVNXbP5ZZ1IemJhlB3n91XBNAkQd2mRVDx+lHmA== dependencies: "@babel/polyfill" "^7.0.0-beta.32" "@babel/runtime" "^7.0.0-beta.32" extract-files "^2.0.1" -apollo-upload-server@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/apollo-upload-server/-/apollo-upload-server-2.0.4.tgz#5105081b6c061638ef7a04ef848758d30f3ba96b" +apollo-utilities@1.0.25, apollo-utilities@^1.0.0, apollo-utilities@^1.0.25, apollo-utilities@^1.0.4: + version "1.0.25" + resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.25.tgz#899b00f5f990fb451675adf84cb3de82eb6372ea" + integrity sha512-AXvqkhni3Ir1ffm4SA1QzXn8k8I5BBl4PVKEyak734i4jFdp+xgfUyi2VCqF64TJlFTA/B73TRDUvO2D+tKtZg== dependencies: - formidable "^1.1.1" - mkdirp "^0.5.1" - object-path "^0.11.4" - -apollo-utilities@^1.0.0, apollo-utilities@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.4.tgz#560009ea5541b9fdc4ee07ebb1714ee319a76c15" + fast-json-stable-stringify "^2.0.0" append-transform@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" + integrity sha1-126/jKlNJ24keja61EpLdKthGZE= dependencies: default-require-extensions "^1.0.0" aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== are-we-there-yet@~1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== dependencies: delegates "^1.0.0" readable-stream "^2.0.6" argparse@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + 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" aria-query@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-0.7.0.tgz#4af10a1e61573ddea0cf3b99b51c52c05b424d24" + version "0.7.1" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-0.7.1.tgz#26cbb5aff64144b0a825be1846e0b16cfa00b11e" + integrity sha1-Jsu1r/ZBRLCoJb4YRuCxbPoAsR4= dependencies: ast-types-flow "0.0.7" + commander "^2.11.0" arr-diff@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8= dependencies: arr-flatten "^1.0.1" -arr-flatten@^1.0.1: +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.0.1, arr-flatten@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= array-filter@~0.0.0: version "0.0.1" resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" + integrity sha1-fajPLiZijtcygDWB/SH2fKzS7uw= array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= array-flatten@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296" + integrity sha1-Qmu52oQJDBg42BLIFQryCoMx4pY= array-includes@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" + integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0= dependencies: define-properties "^1.1.2" es-abstract "^1.7.0" @@ -392,88 +487,124 @@ array-includes@^3.0.3: array-map@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" + integrity sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI= array-reduce@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" + integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys= array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= dependencies: array-uniq "^1.0.1" array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= array-unique@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= asn1.js@^4.0.0: - version "4.9.2" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.2.tgz#8117ef4f7ed87cd8f89044b5bff97ac243a16c9a" + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== dependencies: bn.js "^4.0.0" inherits "^2.0.1" minimalistic-assert "^1.0.0" asn1@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + 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= assert-plus@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + integrity sha1-104bh+ev/A24qttwIfP+SBAasjQ= assert@^1.1.1: version "1.4.1" resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" + integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE= dependencies: util "0.10.3" +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + ast-types-flow@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" + integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + integrity sha1-GdOGodntxufByF04iu28xW0zYC0= async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== -async@^1.4.0, async@^1.5.2: +async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.1.2, async@^2.1.4, async@^2.4.1: - version "2.6.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" +async@^2.1.2, async@^2.1.4, async@^2.4.1, async@^2.5.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" + integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ== dependencies: - lodash "^4.14.0" + lodash "^4.17.10" asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +atob@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== autoprefixer@7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.6.tgz#fb933039f74af74a83e71225ce78d9fd58ba84d7" + integrity sha512-C9yv/UF3X+eJTi/zvfxuyfxmLibYrntpF3qoJYrMeQwgUJOZrZvpJiMG2FMQ3qnhWtF/be4pYONBBw95ZGe3vA== dependencies: browserslist "^2.5.1" caniuse-lite "^1.0.30000748" @@ -485,6 +616,7 @@ autoprefixer@7.1.6: autoprefixer@^6.3.1: version "6.7.7" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" + integrity sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ= dependencies: browserslist "^1.7.6" caniuse-db "^1.0.30000634" @@ -496,32 +628,38 @@ autoprefixer@^6.3.1: aws-sign2@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + integrity sha1-FDQt0428yU0OW4fXY81jYSwOeU8= 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.2.1, aws4@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" +aws4@^1.2.1, aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== axobject-query@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-0.1.0.tgz#62f59dbc59c9f9242759ca349960e7a2fe3c36c0" + integrity sha1-YvWdvFnJ+SQnWco0mWDnov48NsA= dependencies: ast-types-flow "0.0.7" babel-code-frame@6.26.0, babel-code-frame@^6.11.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= dependencies: chalk "^1.1.3" esutils "^2.0.2" js-tokens "^3.0.2" -babel-core@6.26.0, babel-core@^6.0.0, babel-core@^6.26.0: +babel-core@6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" + integrity sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g= dependencies: babel-code-frame "^6.26.0" babel-generator "^6.26.0" @@ -543,9 +681,35 @@ babel-core@6.26.0, babel-core@^6.0.0, babel-core@^6.26.0: slash "^1.0.0" source-map "^0.5.6" +babel-core@^6.0.0, babel-core@^6.26.0: + version "6.26.3" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" + integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== + dependencies: + babel-code-frame "^6.26.0" + babel-generator "^6.26.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.26.0" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + convert-source-map "^1.5.1" + debug "^2.6.9" + json5 "^0.5.1" + lodash "^4.17.4" + minimatch "^3.0.4" + path-is-absolute "^1.0.1" + private "^0.1.8" + slash "^1.0.0" + source-map "^0.5.7" + babel-eslint@7.2.3: version "7.2.3" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.3.tgz#b2fe2d80126470f5c19442dc757253a897710827" + integrity sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc= dependencies: babel-code-frame "^6.22.0" babel-traverse "^6.23.1" @@ -553,8 +717,9 @@ babel-eslint@7.2.3: babylon "^6.17.0" babel-generator@^6.18.0, babel-generator@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" + integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== dependencies: babel-messages "^6.23.0" babel-runtime "^6.26.0" @@ -562,12 +727,13 @@ babel-generator@^6.18.0, babel-generator@^6.26.0: detect-indent "^4.0.0" jsesc "^1.3.0" lodash "^4.17.4" - source-map "^0.5.6" + source-map "^0.5.7" trim-right "^1.0.1" babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" + integrity sha1-zORReto1b0IgvK6KAsKzRvmlZmQ= dependencies: babel-helper-explode-assignable-expression "^6.24.1" babel-runtime "^6.22.0" @@ -576,6 +742,7 @@ babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: babel-helper-builder-react-jsx@^6.24.1: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0" + integrity sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA= dependencies: babel-runtime "^6.26.0" babel-types "^6.26.0" @@ -584,6 +751,7 @@ babel-helper-builder-react-jsx@^6.24.1: babel-helper-call-delegate@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" + integrity sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340= dependencies: babel-helper-hoist-variables "^6.24.1" babel-runtime "^6.22.0" @@ -593,6 +761,7 @@ babel-helper-call-delegate@^6.24.1: babel-helper-define-map@^6.24.1: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" + integrity sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8= dependencies: babel-helper-function-name "^6.24.1" babel-runtime "^6.26.0" @@ -602,6 +771,7 @@ babel-helper-define-map@^6.24.1: babel-helper-explode-assignable-expression@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" + integrity sha1-8luCz33BBDPFX3BZLVdGQArCLKo= dependencies: babel-runtime "^6.22.0" babel-traverse "^6.24.1" @@ -610,6 +780,7 @@ babel-helper-explode-assignable-expression@^6.24.1: babel-helper-function-name@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + integrity sha1-00dbjAPtmCQqJbSDUasYOZ01gKk= dependencies: babel-helper-get-function-arity "^6.24.1" babel-runtime "^6.22.0" @@ -620,6 +791,7 @@ babel-helper-function-name@^6.24.1: babel-helper-get-function-arity@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + integrity sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0= dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" @@ -627,6 +799,7 @@ babel-helper-get-function-arity@^6.24.1: babel-helper-hoist-variables@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" + integrity sha1-HssnaJydJVE+rbyZFKc/VAi+enY= dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" @@ -634,6 +807,7 @@ babel-helper-hoist-variables@^6.24.1: babel-helper-optimise-call-expression@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" + integrity sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc= dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" @@ -641,6 +815,7 @@ babel-helper-optimise-call-expression@^6.24.1: babel-helper-regex@^6.24.1: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" + integrity sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI= dependencies: babel-runtime "^6.26.0" babel-types "^6.26.0" @@ -649,6 +824,7 @@ babel-helper-regex@^6.24.1: babel-helper-remap-async-to-generator@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" + integrity sha1-XsWBgnrXI/7N04HxySg5BnbkVRs= dependencies: babel-helper-function-name "^6.24.1" babel-runtime "^6.22.0" @@ -659,6 +835,7 @@ babel-helper-remap-async-to-generator@^6.24.1: babel-helper-replace-supers@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" + integrity sha1-v22/5Dk40XNpohPKiov3S2qQqxo= dependencies: babel-helper-optimise-call-expression "^6.24.1" babel-messages "^6.23.0" @@ -670,6 +847,7 @@ babel-helper-replace-supers@^6.24.1: babel-helpers@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI= dependencies: babel-runtime "^6.22.0" babel-template "^6.24.1" @@ -677,6 +855,7 @@ babel-helpers@^6.24.1: babel-jest@20.0.3, babel-jest@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-20.0.3.tgz#e4a03b13dc10389e140fc645d09ffc4ced301671" + integrity sha1-5KA7E9wQOJ4UD8ZF0J/8TO0wFnE= dependencies: babel-core "^6.0.0" babel-plugin-istanbul "^4.0.0" @@ -685,6 +864,7 @@ babel-jest@20.0.3, babel-jest@^20.0.3: babel-loader@7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.2.tgz#f6cbe122710f1aa2af4d881c6d5b54358ca24126" + integrity sha512-jRwlFbINAeyDStqK6Dd5YuY0k5YuzQUvlz2ZamuXrXmxav3pNqe9vfJ402+2G+OmlJSXxCOpB6Uz0INM7RQe2A== dependencies: find-cache-dir "^1.0.0" loader-utils "^1.0.2" @@ -693,70 +873,85 @@ babel-loader@7.1.2: babel-messages@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= dependencies: babel-runtime "^6.22.0" babel-plugin-check-es2015-constants@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + integrity sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o= dependencies: babel-runtime "^6.22.0" babel-plugin-dynamic-import-node@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-1.1.0.tgz#bd1d88ac7aaf98df4917c384373b04d971a2b37a" + integrity sha512-tTfZbM9Ecwj3GK50mnPrUpinTwA4xXmDiQGCk/aBYbvl1+X8YqldK86wZ1owVJ4u3mrKbRlXMma80J18qwiaTQ== dependencies: babel-plugin-syntax-dynamic-import "^6.18.0" babel-template "^6.26.0" babel-types "^6.26.0" babel-plugin-istanbul@^4.0.0: - version "4.1.5" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz#6760cdd977f411d3e175bb064f2bc327d99b2b6e" + version "4.1.6" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" + integrity sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ== dependencies: + babel-plugin-syntax-object-rest-spread "^6.13.0" find-up "^2.1.0" - istanbul-lib-instrument "^1.7.5" - test-exclude "^4.1.1" + istanbul-lib-instrument "^1.10.1" + test-exclude "^4.2.1" babel-plugin-jest-hoist@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-20.0.3.tgz#afedc853bd3f8dc3548ea671fbe69d03cc2c1767" + integrity sha1-r+3IU70/jcNUjqZx++adA8wsF2c= babel-plugin-syntax-async-functions@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + integrity sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU= babel-plugin-syntax-class-properties@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" + integrity sha1-1+sjt5oxf4VDlixQW4J8fWysJ94= babel-plugin-syntax-dynamic-import@6.18.0, babel-plugin-syntax-dynamic-import@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" + integrity sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo= babel-plugin-syntax-exponentiation-operator@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + integrity sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4= babel-plugin-syntax-flow@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" + integrity sha1-TDqyCiryaqIM0lmVw5jE63AxDI0= babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= -babel-plugin-syntax-object-rest-spread@^6.8.0: +babel-plugin-syntax-object-rest-spread@^6.13.0, babel-plugin-syntax-object-rest-spread@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= babel-plugin-syntax-trailing-function-commas@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + integrity sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM= babel-plugin-transform-async-to-generator@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" + integrity sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E= dependencies: babel-helper-remap-async-to-generator "^6.24.1" babel-plugin-syntax-async-functions "^6.8.0" @@ -765,6 +960,7 @@ babel-plugin-transform-async-to-generator@^6.22.0: babel-plugin-transform-class-properties@6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac" + integrity sha1-anl2PqYdM9NvN7YRqp3vgagbRqw= dependencies: babel-helper-function-name "^6.24.1" babel-plugin-syntax-class-properties "^6.8.0" @@ -774,18 +970,21 @@ babel-plugin-transform-class-properties@6.24.1: babel-plugin-transform-es2015-arrow-functions@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" + integrity sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" + integrity sha1-u8UbSflk1wy42OC5ToICRs46YUE= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-block-scoping@^6.23.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" + integrity sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8= dependencies: babel-runtime "^6.26.0" babel-template "^6.26.0" @@ -796,6 +995,7 @@ babel-plugin-transform-es2015-block-scoping@^6.23.0: babel-plugin-transform-es2015-classes@^6.23.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" + integrity sha1-WkxYpQyclGHlZLSyo7+ryXolhNs= dependencies: babel-helper-define-map "^6.24.1" babel-helper-function-name "^6.24.1" @@ -810,6 +1010,7 @@ babel-plugin-transform-es2015-classes@^6.23.0: babel-plugin-transform-es2015-computed-properties@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" + integrity sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM= dependencies: babel-runtime "^6.22.0" babel-template "^6.24.1" @@ -817,12 +1018,14 @@ babel-plugin-transform-es2015-computed-properties@^6.22.0: babel-plugin-transform-es2015-destructuring@6.23.0, babel-plugin-transform-es2015-destructuring@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" + integrity sha1-mXux8auWf2gtKwh2/jWNYOdlxW0= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-duplicate-keys@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" + integrity sha1-c+s9MQypaePvnskcU3QabxV2Qj4= dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" @@ -830,12 +1033,14 @@ babel-plugin-transform-es2015-duplicate-keys@^6.22.0: babel-plugin-transform-es2015-for-of@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" + integrity sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-function-name@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" + integrity sha1-g0yJhTvDaxrw86TF26qU/Y6sqos= dependencies: babel-helper-function-name "^6.24.1" babel-runtime "^6.22.0" @@ -844,20 +1049,23 @@ babel-plugin-transform-es2015-function-name@^6.22.0: babel-plugin-transform-es2015-literals@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" + integrity sha1-T1SgLWzWbPkVKAAZox0xklN3yi4= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" + integrity sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ= dependencies: babel-plugin-transform-es2015-modules-commonjs "^6.24.1" babel-runtime "^6.22.0" babel-template "^6.24.1" babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" + version "6.26.2" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" + integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q== dependencies: babel-plugin-transform-strict-mode "^6.24.1" babel-runtime "^6.26.0" @@ -867,6 +1075,7 @@ babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-e babel-plugin-transform-es2015-modules-systemjs@^6.23.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" + integrity sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM= dependencies: babel-helper-hoist-variables "^6.24.1" babel-runtime "^6.22.0" @@ -875,6 +1084,7 @@ babel-plugin-transform-es2015-modules-systemjs@^6.23.0: babel-plugin-transform-es2015-modules-umd@^6.23.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" + integrity sha1-rJl+YoXNGO1hdq22B9YCNErThGg= dependencies: babel-plugin-transform-es2015-modules-amd "^6.24.1" babel-runtime "^6.22.0" @@ -883,6 +1093,7 @@ babel-plugin-transform-es2015-modules-umd@^6.23.0: babel-plugin-transform-es2015-object-super@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" + integrity sha1-JM72muIcuDp/hgPa0CH1cusnj40= dependencies: babel-helper-replace-supers "^6.24.1" babel-runtime "^6.22.0" @@ -890,6 +1101,7 @@ babel-plugin-transform-es2015-object-super@^6.22.0: babel-plugin-transform-es2015-parameters@^6.23.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" + integrity sha1-V6w1GrScrxSpfNE7CfZv3wpiXys= dependencies: babel-helper-call-delegate "^6.24.1" babel-helper-get-function-arity "^6.24.1" @@ -901,6 +1113,7 @@ babel-plugin-transform-es2015-parameters@^6.23.0: babel-plugin-transform-es2015-shorthand-properties@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" + integrity sha1-JPh11nIch2YbvZmkYi5R8U3jiqA= dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" @@ -908,12 +1121,14 @@ babel-plugin-transform-es2015-shorthand-properties@^6.22.0: babel-plugin-transform-es2015-spread@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + integrity sha1-1taKmfia7cRTbIGlQujdnxdG+NE= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-sticky-regex@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" + integrity sha1-AMHNsaynERLN8M9hJsLta0V8zbw= dependencies: babel-helper-regex "^6.24.1" babel-runtime "^6.22.0" @@ -922,18 +1137,21 @@ babel-plugin-transform-es2015-sticky-regex@^6.22.0: babel-plugin-transform-es2015-template-literals@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" + integrity sha1-qEs0UPfp+PH2g51taH2oS7EjbY0= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-typeof-symbol@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" + integrity sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-unicode-regex@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" + integrity sha1-04sS9C6nMj9yk4fxinxa4frrNek= dependencies: babel-helper-regex "^6.24.1" babel-runtime "^6.22.0" @@ -942,6 +1160,7 @@ babel-plugin-transform-es2015-unicode-regex@^6.22.0: babel-plugin-transform-exponentiation-operator@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" + integrity sha1-KrDJx/MJj6SJB3cruBP+QejeOg4= dependencies: babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" babel-plugin-syntax-exponentiation-operator "^6.8.0" @@ -950,6 +1169,7 @@ babel-plugin-transform-exponentiation-operator@^6.22.0: babel-plugin-transform-flow-strip-types@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" + integrity sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988= dependencies: babel-plugin-syntax-flow "^6.18.0" babel-runtime "^6.22.0" @@ -957,6 +1177,7 @@ babel-plugin-transform-flow-strip-types@^6.22.0: babel-plugin-transform-object-rest-spread@6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" + integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY= dependencies: babel-plugin-syntax-object-rest-spread "^6.8.0" babel-runtime "^6.26.0" @@ -964,18 +1185,21 @@ babel-plugin-transform-object-rest-spread@6.26.0: babel-plugin-transform-react-constant-elements@6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-constant-elements/-/babel-plugin-transform-react-constant-elements-6.23.0.tgz#2f119bf4d2cdd45eb9baaae574053c604f6147dd" + integrity sha1-LxGb9NLN1F65uqrldAU8YE9hR90= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-react-display-name@^6.23.0: version "6.25.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1" + integrity sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-react-jsx-self@6.22.0, babel-plugin-transform-react-jsx-self@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e" + integrity sha1-322AqdomEqEh5t3XVYvL7PBuY24= dependencies: babel-plugin-syntax-jsx "^6.8.0" babel-runtime "^6.22.0" @@ -983,6 +1207,7 @@ babel-plugin-transform-react-jsx-self@6.22.0, babel-plugin-transform-react-jsx-s babel-plugin-transform-react-jsx-source@6.22.0, babel-plugin-transform-react-jsx-source@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6" + integrity sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY= dependencies: babel-plugin-syntax-jsx "^6.8.0" babel-runtime "^6.22.0" @@ -990,6 +1215,7 @@ babel-plugin-transform-react-jsx-source@6.22.0, babel-plugin-transform-react-jsx babel-plugin-transform-react-jsx@6.24.1, babel-plugin-transform-react-jsx@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3" + integrity sha1-hAoCjn30YN/DotKfDA2R9jduZqM= dependencies: babel-helper-builder-react-jsx "^6.24.1" babel-plugin-syntax-jsx "^6.8.0" @@ -998,18 +1224,21 @@ babel-plugin-transform-react-jsx@6.24.1, babel-plugin-transform-react-jsx@^6.24. babel-plugin-transform-regenerator@6.26.0, babel-plugin-transform-regenerator@^6.22.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" + integrity sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8= dependencies: regenerator-transform "^0.10.0" babel-plugin-transform-runtime@6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz#88490d446502ea9b8e7efb0fe09ec4d99479b1ee" + integrity sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-strict-mode@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + integrity sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g= dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" @@ -1017,6 +1246,7 @@ babel-plugin-transform-strict-mode@^6.24.1: babel-preset-env@1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.1.tgz#a18b564cc9b9afdf4aae57ae3c1b0d99188e6f48" + integrity sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA== dependencies: babel-plugin-check-es2015-constants "^6.22.0" babel-plugin-syntax-trailing-function-commas "^6.22.0" @@ -1052,18 +1282,21 @@ babel-preset-env@1.6.1: babel-preset-flow@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d" + integrity sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0= dependencies: babel-plugin-transform-flow-strip-types "^6.22.0" babel-preset-jest@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-20.0.3.tgz#cbacaadecb5d689ca1e1de1360ebfc66862c178a" + integrity sha1-y6yq3stdaJyh4d4TYOv8ZoYsF4o= dependencies: babel-plugin-jest-hoist "^20.0.3" babel-preset-react-app@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/babel-preset-react-app/-/babel-preset-react-app-3.1.1.tgz#d3f06a79742f0e89d7afcb72e282d9809c850920" + version "3.1.2" + resolved "https://registry.yarnpkg.com/babel-preset-react-app/-/babel-preset-react-app-3.1.2.tgz#49ba3681b917c4e5c73a5249d3ef4c48fae064e2" + integrity sha512-/sh5Qd5T08PYa6t4kuCdKh9tXp6/m/Jwyx7PJTqugsYMfsDUJMlBXOs5EwFODHprzjWrmQ0SydnMZu9FY4MZYg== dependencies: babel-plugin-dynamic-import-node "1.1.0" babel-plugin-syntax-dynamic-import "6.18.0" @@ -1082,6 +1315,7 @@ babel-preset-react-app@^3.1.1: babel-preset-react@6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380" + integrity sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A= dependencies: babel-plugin-syntax-jsx "^6.3.13" babel-plugin-transform-react-display-name "^6.23.0" @@ -1093,6 +1327,7 @@ babel-preset-react@6.24.1: babel-register@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" + integrity sha1-btAhFz4vy0htestFxgCahW9kcHE= dependencies: babel-core "^6.26.0" babel-runtime "^6.26.0" @@ -1105,6 +1340,7 @@ babel-register@^6.26.0: babel-runtime@6.26.0, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= dependencies: core-js "^2.4.0" regenerator-runtime "^0.11.0" @@ -1112,6 +1348,7 @@ babel-runtime@6.26.0, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtim babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= dependencies: babel-runtime "^6.26.0" babel-traverse "^6.26.0" @@ -1122,6 +1359,7 @@ babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: babel-traverse@^6.18.0, babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= dependencies: babel-code-frame "^6.26.0" babel-messages "^6.23.0" @@ -1136,6 +1374,7 @@ babel-traverse@^6.18.0, babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-tr babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= dependencies: babel-runtime "^6.26.0" esutils "^2.0.2" @@ -1145,73 +1384,100 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24 babylon@^6.17.0, babylon@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== backo2@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= balanced-match@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" + integrity sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg= 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= base64-js@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" + version "1.3.0" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" + integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= bcrypt-pbkdf@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + 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" big.js@^3.1.3: version "3.2.0" resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" + integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== binary-extensions@^1.0.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" + version "1.12.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" + integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg== block-stream@*: version "0.0.9" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= dependencies: inherits "~2.0.0" bluebird@^3.4.7: - version "3.5.1" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -body-parser@1.18.2: - version "1.18.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" +body-parser@1.18.3: + version "1.18.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" + integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= dependencies: bytes "3.0.0" content-type "~1.0.4" debug "2.6.9" - depd "~1.1.1" - http-errors "~1.6.2" - iconv-lite "0.4.19" + depd "~1.1.2" + http-errors "~1.6.3" + iconv-lite "0.4.23" on-finished "~2.3.0" - qs "6.5.1" - raw-body "2.3.2" - type-is "~1.6.15" + qs "6.5.2" + raw-body "2.3.3" + type-is "~1.6.16" bonjour@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= dependencies: array-flatten "^2.1.0" deep-equal "^1.0.1" @@ -1223,42 +1489,32 @@ bonjour@^3.5.0: boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= boom@2.x.x: version "2.10.1" resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + integrity sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8= dependencies: hoek "2.x.x" -boom@4.x.x: - version "4.3.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" - dependencies: - hoek "4.x.x" - -boom@5.x.x: - version "5.2.0" - resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" - dependencies: - hoek "4.x.x" - -boxen@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-0.6.0.tgz#8364d4248ac34ff0ef1b2f2bf49a6c60ce0d81b6" +boxen@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" + integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== dependencies: - ansi-align "^1.1.0" - camelcase "^2.1.0" - chalk "^1.1.1" + ansi-align "^2.0.0" + camelcase "^4.0.0" + chalk "^2.0.1" cli-boxes "^1.0.0" - filled-array "^1.0.0" - object-assign "^4.0.1" - repeating "^2.0.0" - string-width "^1.0.1" - widest-line "^1.0.0" + string-width "^2.0.0" + term-size "^1.2.0" + widest-line "^2.0.0" brace-expansion@^1.0.0, brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + 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" @@ -1266,24 +1522,44 @@ brace-expansion@^1.0.0, brace-expansion@^1.1.7: braces@^1.8.2: version "1.8.5" resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= dependencies: expand-range "^1.8.1" preserve "^0.2.0" repeat-element "^1.1.2" +braces@^2.3.0, braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= browser-resolve@^1.11.2: - version "1.11.2" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" + integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== dependencies: resolve "1.1.7" browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f" + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== dependencies: buffer-xor "^1.0.3" cipher-base "^1.0.0" @@ -1293,24 +1569,28 @@ browserify-aes@^1.0.0, browserify-aes@^1.0.4: safe-buffer "^5.0.1" browserify-cipher@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a" + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== dependencies: browserify-aes "^1.0.4" browserify-des "^1.0.0" evp_bytestokey "^1.0.0" browserify-des@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd" + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== dependencies: cipher-base "^1.0.1" des.js "^1.0.0" inherits "^2.0.1" + safe-buffer "^5.1.2" browserify-rsa@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= dependencies: bn.js "^4.1.0" randombytes "^2.0.1" @@ -1318,6 +1598,7 @@ browserify-rsa@^4.0.0: browserify-sign@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= dependencies: bn.js "^4.1.1" browserify-rsa "^4.0.0" @@ -1330,12 +1611,14 @@ browserify-sign@^4.0.0: browserify-zlib@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== dependencies: pako "~1.0.5" browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: version "1.7.7" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" + integrity sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk= dependencies: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" @@ -1343,6 +1626,7 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: browserslist@^2.1.2, browserslist@^2.5.1: version "2.11.3" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.11.3.tgz#fe36167aed1bbcde4827ebfe71347a2cc70b99b2" + integrity sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA== dependencies: caniuse-lite "^1.0.30000792" electron-to-chromium "^1.3.30" @@ -1350,34 +1634,45 @@ browserslist@^2.1.2, browserslist@^2.5.1: bser@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bser/-/bser-1.0.2.tgz#381116970b2a6deea5646dd15dd7278444b56169" + integrity sha1-OBEWlwsqbe6lZG3RXdcnhES1YWk= dependencies: node-int64 "^0.4.0" bser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" + integrity sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk= dependencies: node-int64 "^0.4.0" +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== + buffer-indexof@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + 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= dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" isarray "^1.0.0" buffer@^5.0.3: - version "5.0.8" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.0.8.tgz#84daa52e7cf2fa8ce4195bc5cf0f7809e0930b24" + version "5.2.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" + integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg== dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" @@ -1385,32 +1680,54 @@ buffer@^5.0.3: builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" caller-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + integrity sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8= dependencies: callsites "^0.2.0" callsites@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= callsites@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= camel-case@3.0.x: version "3.0.0" resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" + integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M= dependencies: no-case "^2.2.0" upper-case "^1.1.1" @@ -1418,6 +1735,7 @@ camel-case@3.0.x: camelcase-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= dependencies: camelcase "^2.0.0" map-obj "^1.0.0" @@ -1425,22 +1743,27 @@ camelcase-keys@^2.0.0: camelcase@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + integrity sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk= -camelcase@^2.0.0, camelcase@^2.1.0: +camelcase@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= -camelcase@^4.1.0: +camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= caniuse-api@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" + integrity sha1-tTTnxzTE+B7F++isoq0kNUuWLGw= dependencies: browserslist "^1.3.6" caniuse-db "^1.0.30000529" @@ -1448,39 +1771,42 @@ caniuse-api@^1.5.2: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000794" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000794.tgz#bbe71104fa277ce4b362387d54905e8b88e52f35" + version "1.0.30000907" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000907.tgz#2b4b8dff95e0b284dae24ef76ac2d96aa42f42d6" + integrity sha512-OKtlTmEPR9GgCxnKMlvdHTT2QD6n4eIovcVqEnjoR8iB9l6rk4abKnjsDSyTD36an/ebgigl8T2CSdwSf4JoGw== caniuse-lite@^1.0.30000748, caniuse-lite@^1.0.30000792: - version "1.0.30000792" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000792.tgz#d0cea981f8118f3961471afbb43c9a1e5bbf0332" + version "1.0.30000907" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000907.tgz#0b9899bde53fb1c30e214fb12402361e02ff5c42" + integrity sha512-No5sQ/OB2Nmka8MNOOM6nJx+Hxt6MQ6h7t7kgJFu9oTuwjykyKRSBP/+i/QAyFHxeHB+ddE0Da1CG5ihx9oehQ== capture-stack-trace@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" + version "1.0.1" + resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" + integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== case-sensitive-paths-webpack-plugin@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.1.1.tgz#3d29ced8c1f124bf6f53846fb3f5894731fdc909" + integrity sha1-PSnO2MHxJL9vU4Rvs/WJRzH9yQk= caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= center-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + integrity sha1-qg0yYptu6XIgBBHL1EYckHvCt60= dependencies: align-text "^0.1.3" lazy-cache "^1.0.3" -chain-function@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc" - -chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: +chalk@1.1.3, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= dependencies: ansi-styles "^2.2.1" escape-string-regexp "^1.0.2" @@ -1488,25 +1814,29 @@ chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" + integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== dependencies: - ansi-styles "^3.1.0" + ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" - supports-color "^4.0.0" + supports-color "^5.3.0" change-emitter@^0.1.2: version "0.1.6" resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" + integrity sha1-6LL+PX8at9aaMhma/5HqaTFAlRU= chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" + integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= -chokidar@^1.6.0, chokidar@^1.7.0: +chokidar@^1.6.0: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" + integrity sha1-eY5ol3gVHIB2tLNg5e3SjNortGg= dependencies: anymatch "^1.3.0" async-each "^1.0.0" @@ -1519,13 +1849,40 @@ chokidar@^1.6.0, chokidar@^1.7.0: optionalDependencies: fsevents "^1.0.0" -ci-info@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4" +chokidar@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" + integrity sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.0" + braces "^2.3.0" + glob-parent "^3.1.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + lodash.debounce "^4.0.8" + normalize-path "^2.1.1" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + upath "^1.0.5" + optionalDependencies: + fsevents "^1.2.2" + +chownr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" + integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== + +ci-info@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" + integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" @@ -1533,40 +1890,58 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: circular-json@^0.3.1: version "0.3.3" resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" + integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== clap@^1.0.9: version "1.2.3" resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" + integrity sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA== dependencies: chalk "^1.1.3" -classnames@2.2.5, classnames@^2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +classnames@^2.2.5, classnames@~2.2.5: + version "2.2.6" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" + integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== -clean-css@4.1.x: - version "4.1.9" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.9.tgz#35cee8ae7687a49b98034f70de00c4edd3826301" +clean-css@4.2.x: + version "4.2.1" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" + integrity sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g== dependencies: - source-map "0.5.x" + source-map "~0.6.0" cli-boxes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" + integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= dependencies: restore-cursor "^2.0.0" cli-width@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + integrity sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE= dependencies: center-align "^0.1.1" right-align "^0.1.1" @@ -1575,48 +1950,70 @@ cliui@^2.1.0: cliui@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= dependencies: string-width "^1.0.1" strip-ansi "^3.0.1" wrap-ansi "^2.0.0" clone@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= coa@~1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd" + integrity sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0= dependencies: q "^1.1.2" code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" color-convert@^1.3.0, color-convert@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" + 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.1" + color-name "1.1.3" -color-name@^1.0.0, color-name@^1.1.1: +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.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" + integrity sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE= dependencies: color-name "^1.0.0" color@^0.11.0: version "0.11.4" resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" + integrity sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q= dependencies: clone "^1.0.2" color-convert "^1.3.0" @@ -1625,6 +2022,7 @@ color@^0.11.0: colormin@^1.0.5: version "1.1.2" resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133" + integrity sha1-6i90IKcrlogaOKrlnsEkpvcpgTM= dependencies: color "^0.11.0" css-color-names "0.0.4" @@ -1633,134 +2031,165 @@ colormin@^1.0.5: colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM= -combined-stream@^1.0.5, combined-stream@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" +combined-stream@^1.0.5, combined-stream@^1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" + integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== dependencies: delayed-stream "~1.0.0" -commander@2.12.x: - version "2.12.2" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555" +commander@2.17.x, commander@~2.17.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" + integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== -commander@~2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" +commander@^2.11.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" + integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +component-emitter@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= -compressible@~2.0.11: - version "2.0.12" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66" +compressible@~2.0.14: + version "2.0.15" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.15.tgz#857a9ab0a7e5a07d8d837ed43fe2defff64fe212" + integrity sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw== dependencies: - mime-db ">= 1.30.0 < 2" + mime-db ">= 1.36.0 < 2" compression@^1.5.2: - version "1.7.1" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.1.tgz#eff2603efc2e22cf86f35d2eb93589f9875373db" + version "1.7.3" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.3.tgz#27e0e176aaf260f7f2c2813c3e440adb9f1993db" + integrity sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg== dependencies: - accepts "~1.3.4" + accepts "~1.3.5" bytes "3.0.0" - compressible "~2.0.11" + compressible "~2.0.14" debug "2.6.9" on-headers "~1.0.1" - safe-buffer "5.1.1" + safe-buffer "5.1.2" vary "~1.1.2" 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= concat-stream@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== dependencies: + buffer-from "^1.0.0" inherits "^2.0.3" readable-stream "^2.2.2" typedarray "^0.0.6" -configstore@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1" +configstore@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" + integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw== dependencies: - dot-prop "^3.0.0" + dot-prop "^4.1.0" graceful-fs "^4.1.2" - mkdirp "^0.5.0" - object-assign "^4.0.1" - os-tmpdir "^1.0.0" - osenv "^0.1.0" - uuid "^2.0.1" - write-file-atomic "^1.1.2" - xdg-basedir "^2.0.0" + make-dir "^1.0.0" + unique-string "^1.0.0" + write-file-atomic "^2.0.0" + xdg-basedir "^3.0.0" connect-history-api-fallback@^1.3.0: version "1.5.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a" + integrity sha1-sGhzk0vF40T+9hGhlqb6rgruAVo= console-browserify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" + integrity sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA= dependencies: date-now "^0.1.4" console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= contains-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= content-disposition@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= content-type-parser@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7" + integrity sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ== 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== -convert-source-map@^1.4.0, convert-source-map@^1.5.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" +convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" + integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== + dependencies: + safe-buffer "~5.1.1" cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= -core-js@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-js@^1.0.0: version "1.2.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-js@^2.4.0, core-js@^2.5.0: - version "2.5.3" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" +core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0, core-js@^2.5.7, core-js@~2.5.1: + version "2.5.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" + integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: version "2.2.2" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.2.2.tgz#6173cebd56fac042c1f4390edf7af6c07c7cb892" + integrity sha512-GiNXLwAFPYHy25XmTPpafYvn3CLAkJ8FLsscq78MQd1Kh0OU6Yzhn4eV2MVF4G9WEQZoWEGltatdR+ntGPMl5A== dependencies: is-directory "^0.3.1" js-yaml "^3.4.3" @@ -1771,30 +2200,35 @@ cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: require-from-string "^1.1.0" create-ecdh@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d" + version "4.0.3" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" + integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== dependencies: bn.js "^4.1.0" elliptic "^6.0.0" -create-error-class@^3.0.1: +create-error-class@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" + integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= dependencies: capture-stack-trace "^1.0.0" create-hash@^1.1.0, create-hash@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== dependencies: cipher-base "^1.0.1" inherits "^2.0.1" - ripemd160 "^2.0.0" + md5.js "^1.3.4" + ripemd160 "^2.0.1" sha.js "^2.4.0" create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.6" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06" + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== dependencies: cipher-base "^1.0.3" create-hash "^1.1.0" @@ -1806,6 +2240,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: cross-spawn@5.1.0, cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= dependencies: lru-cache "^4.0.1" shebang-command "^1.2.0" @@ -1814,18 +2249,14 @@ cross-spawn@5.1.0, cross-spawn@^5.0.1, cross-spawn@^5.1.0: cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + integrity sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g= dependencies: boom "2.x.x" -cryptiles@3.x.x: - version "3.1.2" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" - dependencies: - boom "5.x.x" - crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== dependencies: browserify-cipher "^1.0.0" browserify-sign "^4.0.0" @@ -1839,17 +2270,25 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-random-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" + integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= + css-color-keywords@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" + integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU= css-color-names@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= css-loader@0.28.7: version "0.28.7" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.7.tgz#5f2ee989dd32edd907717f953317656160999c1b" + integrity sha512-GxMpax8a/VgcfRrVy0gXD6yLd5ePYbXX/5zGgTVYp4wXtJklS8Z2VaUArJgc//f6/Dzil7BaJObdSv8eKKCPgg== dependencies: babel-code-frame "^6.11.0" css-selector-tokenizer "^0.7.0" @@ -1869,6 +2308,7 @@ css-loader@0.28.7: css-select@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= dependencies: boolbase "~1.0.0" css-what "2.1" @@ -1876,32 +2316,37 @@ css-select@^1.1.0: nth-check "~1.0.1" css-selector-tokenizer@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86" + version "0.7.1" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz#a177271a8bca5019172f4f891fc6eed9cbf68d5d" + integrity sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA== dependencies: cssesc "^0.1.0" fastparse "^1.1.1" regexpu-core "^1.0.0" css-to-react-native@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.0.4.tgz#cf4cc407558b3474d4ba8be1a2cd3b6ce713101b" + version "2.2.2" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.2.2.tgz#c077d0f7bf3e6c915a539e7325821c9dd01f9965" + integrity sha512-w99Fzop1FO8XKm0VpbQp3y5mnTnaS+rtCvS+ylSEOK76YXO5zoHQx/QMB1N54Cp+Ya9jB9922EHrh14ld4xmmw== dependencies: css-color-keywords "^1.0.0" fbjs "^0.8.5" postcss-value-parser "^3.3.0" css-what@2.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" + version "2.1.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.2.tgz#c0876d9d0480927d7d4920dcd72af3595649554d" + integrity sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ== cssesc@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" + integrity sha1-yBSQPkViM3GgR3tAEJqq++6t27Q= "cssnano@>=2.6.1 <4": version "3.10.0" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" + integrity sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg= dependencies: autoprefixer "^6.3.1" decamelize "^1.1.2" @@ -1939,55 +2384,66 @@ cssesc@^0.1.0: csso@~2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" + integrity sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U= dependencies: clap "^1.0.9" source-map "^0.5.3" cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": - version "0.3.2" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" + version "0.3.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797" + integrity sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog== "cssstyle@>= 0.2.37 < 0.3.0": version "0.2.37" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" + integrity sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ= dependencies: cssom "0.3.x" currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= dependencies: array-find-index "^1.0.1" d3-array@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc" + version "1.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" + integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw== d3-collection@1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2" + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e" + integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A== d3-color@1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b" + version "1.2.3" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.2.3.tgz#6c67bb2af6df3cc8d79efcc4d3a3e83e28c8048f" + integrity sha512-x37qq3ChOTLd26hnps36lexMRhNXEtVxZ4B25rL0DVdDsGQIJGB18S7y9XDwlDD6MD/ZBzITCf4JjGMM10TZkw== d3-format@1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a" + version "1.3.2" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.3.2.tgz#6a96b5e31bcb98122a30863f7d92365c00603562" + integrity sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ== -d3-interpolate@1, d3-interpolate@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.1.6.tgz#2cf395ae2381804df08aa1bf766b7f97b5f68fb6" +d3-interpolate@1, d3-interpolate@~1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.3.2.tgz#417d3ebdeb4bc4efcc8fd4361c55e4040211fd68" + integrity sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w== dependencies: d3-color "1" d3-path@1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764" + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.7.tgz#8de7cd693a75ac0b5480d3abaccd94793e58aae8" + integrity sha512-q0cW1RpvA5c5ma2rch62mX8AYaiLX0+bdaSM2wxSU9tXjU4DNvkx9qiUvjkuWCj3p22UO/hlPivujqMiR9PDzA== -d3-scale@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.6.tgz#bce19da80d3a0cf422c9543ae3322086220b34ed" +d3-scale@^1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d" + integrity sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw== dependencies: d3-array "^1.2.0" d3-collection "1" @@ -1997,102 +2453,157 @@ d3-scale@1.0.6: d3-time "1" d3-time-format "2" -d3-scale@^1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d" +d3-scale@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.1.2.tgz#4e932b7b60182aee9073ede8764c98423e5f9a94" + integrity sha512-bESpd64ylaKzCDzvULcmHKZTlzA/6DGSVwx7QSDj/EnX9cpSevsdiwdHFYI9ouo9tNBbV3v5xztHS2uFeOzh8Q== dependencies: d3-array "^1.2.0" d3-collection "1" - d3-color "1" d3-format "1" d3-interpolate "1" d3-time "1" d3-time-format "2" -d3-shape@1.2.0, d3-shape@^1.0.6, d3-shape@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" +d3-shape@^1.0.6, d3-shape@^1.2.0, d3-shape@~1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.2.tgz#f9dba3777a5825f9a8ce8bc928da08c17679e9a7" + integrity sha512-hUGEozlKecFZ2bOSNt7ENex+4Tk9uc/m0TtTEHBvitCBxUNjhzm5hS2GrrVRD/ae4IylSmxGeqX5tWC2rASMlQ== dependencies: d3-path "1" d3-time-format@2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.1.1.tgz#85b7cdfbc9ffca187f14d3c456ffda268081bb31" + version "2.1.3" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.1.3.tgz#ae06f8e0126a9d60d6364eac5b1533ae1bac826b" + integrity sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA== dependencies: d3-time "1" d3-time@1: - version "1.0.8" - resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84" + version "1.0.10" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.10.tgz#8259dd71288d72eeacfd8de281c4bf5c7393053c" + integrity sha512-hF+NTLCaJHF/JqHN5hE8HVGAXPStEq6/omumPE/SxyHVrR7/qQxusFDo0t0c/44+sCGHthC7yNGFZIEgju0P8g== d@1: version "1.0.0" resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" + integrity sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8= dependencies: es5-ext "^0.10.9" damerau-levenshtein@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514" + integrity sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ= 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" date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" + integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= -debug@2.6.9, debug@^2.2.0, debug@^2.6.0, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@^3.0.1, debug@^3.1.0: +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@^3.0.1, debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decimal.js-light@^2.4.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.0.tgz#ca7faf504c799326df94b0ab920424fdfc125348" + integrity sha512-b3VJCbd2hwUpeRGG3Toob+CRo8W22xplipNhP3tN7TSVB/cyMX71P1vM2Xjc9H74uV6dS2hDDmo/rHq8L87Upg== + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= -deep-extend@~0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== 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-require-extensions@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" + integrity sha1-836hXT4T/9m0N9M+GnW1+5eHTLg= dependencies: strip-bom "^2.0.0" define-properties@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + 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" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== dependencies: - foreach "^2.0.5" - object-keys "^1.0.8" + is-descriptor "^1.0.2" + isobject "^3.0.1" defined@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= -del@^2.0.2, del@^2.2.2: +del@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + integrity sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag= dependencies: globby "^5.0.0" is-path-cwd "^1.0.0" @@ -2105,6 +2616,7 @@ del@^2.0.2, del@^2.2.2: del@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" + integrity sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU= dependencies: globby "^6.1.0" is-path-cwd "^1.0.0" @@ -2116,22 +2628,22 @@ del@^3.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= delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= -depd@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" - -depd@~1.1.1: +depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" + integrity sha1-wHTS4qpqipoH29YfmhXCzYPsjsw= dependencies: inherits "^2.0.1" minimalistic-assert "^1.0.0" @@ -2139,35 +2651,42 @@ des.js@^1.0.0: destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= detect-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= dependencies: repeating "^2.0.0" detect-libc@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= detect-node@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" + version "2.0.4" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" + integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== -detect-port-alt@1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.5.tgz#a1aa8fc805a4a5df9b905b7ddc7eed036bcce889" +detect-port-alt@1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" + integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== dependencies: address "^1.0.1" debug "^2.6.0" diff@^3.2.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== diffie-hellman@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e" + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== dependencies: bn.js "^4.1.0" miller-rabin "^4.0.0" @@ -2176,10 +2695,12 @@ diffie-hellman@^5.0.0: dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= -dns-packet@^1.0.1: +dns-packet@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" + integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== dependencies: ip "^1.1.0" safe-buffer "^5.0.1" @@ -2187,12 +2708,14 @@ dns-packet@^1.0.1: dns-txt@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= dependencies: buffer-indexof "^1.0.0" doctrine@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= dependencies: esutils "^2.0.2" isarray "^1.0.0" @@ -2200,22 +2723,28 @@ doctrine@1.5.0: doctrine@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== dependencies: esutils "^2.0.2" -dom-converter@~0.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.1.4.tgz#a45ef5727b890c9bffe6d7c876e7b19cb0e17f3b" +dom-converter@~0.2: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== dependencies: - utila "~0.3" + utila "~0.4" -dom-helpers@^3.2.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6" +dom-helpers@^3.3.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" + integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== + dependencies: + "@babel/runtime" "^7.1.2" dom-serializer@0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" + integrity sha1-BzxpdUbOB4DOI75KKOKT5AvDDII= dependencies: domelementtype "~1.1.1" entities "~1.1.1" @@ -2223,81 +2752,96 @@ dom-serializer@0: dom-urls@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/dom-urls/-/dom-urls-1.1.0.tgz#001ddf81628cd1e706125c7176f53ccec55d918e" + integrity sha1-AB3fgWKM0ecGElxxdvU8zsVdkY4= dependencies: urijs "^1.16.1" domain-browser@^1.1.1: - version "1.1.7" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== domelementtype@1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" + version "1.2.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.2.1.tgz#578558ef23befac043a1abb0db07635509393479" + integrity sha512-SQVCLFS2E7G5CRCMdn6K9bIhRj1bS6QBWZfF0TUPh4V/BbqrQ619IdSS3/izn0FZ+9l+uODzaZjb08fjOfablA== domelementtype@~1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" + integrity sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs= domhandler@2.1: version "2.1.0" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594" + integrity sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ= dependencies: domelementtype "1" domutils@1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" + integrity sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU= dependencies: domelementtype "1" domutils@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= dependencies: dom-serializer "0" domelementtype "1" -dot-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177" +dot-prop@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" + integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== dependencies: is-obj "^1.0.0" dotenv-expand@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-4.0.1.tgz#68fddc1561814e0a10964111057ff138ced7d7a8" + integrity sha1-aP3cFWGBTgoQlkERBX/xOM7X16g= dotenv@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" + integrity sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0= -duplexer2@^0.1.4: +duplexer3@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - dependencies: - readable-stream "^2.0.2" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= duplexer@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" + integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= ecc-jsbn@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + 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" ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.30: - version "1.3.31" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.31.tgz#00d832cba9fe2358652b0c48a8816c8e3a037e9f" + version "1.3.84" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.84.tgz#2e55df59e818f150a9f61b53471ebf4f0feecc65" + integrity sha512-IYhbzJYOopiTaNWMBp7RjbecUBsbnbDneOP86f3qvS0G0xfzwNSvMJpTrvi5/Y1gU7tg2NAgeg8a8rCYvW9Whw== elliptic@^6.0.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" + version "6.4.1" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a" + integrity sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ== dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -2310,24 +2854,29 @@ elliptic@^6.0.0: emoji-regex@^6.1.0: version "6.5.1" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2" + integrity sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ== emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" + integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= -encodeurl@~1.0.1: +encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= encoding@^0.1.11: version "0.1.12" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= dependencies: iconv-lite "~0.4.13" enhanced-resolve@^3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" + integrity sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24= dependencies: graceful-fs "^4.1.2" memory-fs "^0.4.0" @@ -2335,24 +2884,28 @@ enhanced-resolve@^3.4.0: tapable "^0.2.7" entities@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== -errno@^0.1.3, errno@^0.1.4: - version "0.1.6" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.6.tgz#c386ce8a6283f14fc09563b71560908c9bf53026" +errno@^0.1.3, errno@~0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" + integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== dependencies: prr "~1.0.1" error-ex@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" es-abstract@^1.7.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" + version "1.12.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" + integrity sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA== dependencies: es-to-primitive "^1.1.1" function-bind "^1.1.1" @@ -2361,23 +2914,27 @@ es-abstract@^1.7.0: is-regex "^1.0.4" es-to-primitive@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" + 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== dependencies: - is-callable "^1.1.1" + is-callable "^1.1.4" is-date-object "^1.0.1" - is-symbol "^1.0.1" + is-symbol "^1.0.2" es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: - version "0.10.38" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.38.tgz#fa7d40d65bbc9bb8a67e1d3f9cc656a00530eed3" + version "0.10.46" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.46.tgz#efd99f67c5a7ec789baa3daa7f79870388f7f572" + integrity sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw== dependencies: es6-iterator "~2.0.3" es6-symbol "~3.1.1" + next-tick "1" es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= dependencies: d "1" es5-ext "^0.10.35" @@ -2386,6 +2943,7 @@ es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3: es6-map@^0.1.3: version "0.1.5" resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" + integrity sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA= dependencies: d "1" es5-ext "~0.10.14" @@ -2395,12 +2953,14 @@ es6-map@^0.1.3: event-emitter "~0.3.5" es6-promise@^4.0.5: - version "4.2.4" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" + version "4.2.5" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054" + integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg== es6-set@~0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" + integrity sha1-0rPsXU2ADO2BjbU40ol02wpzzLE= dependencies: d "1" es5-ext "~0.10.14" @@ -2411,6 +2971,7 @@ es6-set@~0.1.5: es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc= dependencies: d "1" es5-ext "~0.10.14" @@ -2418,6 +2979,7 @@ es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: es6-weak-map@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" + integrity sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8= dependencies: d "1" es5-ext "^0.10.14" @@ -2427,25 +2989,29 @@ es6-weak-map@^2.0.1: escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, 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= escodegen@^1.6.1: - version "1.9.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852" + version "1.11.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589" + integrity sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw== dependencies: esprima "^3.1.3" estraverse "^4.2.0" esutils "^2.0.2" optionator "^0.8.1" optionalDependencies: - source-map "~0.5.6" + source-map "~0.6.1" escope@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" + integrity sha1-4Bl16BJ4GhY6ba392AOY3GTIicM= dependencies: es6-map "^0.1.3" es6-weak-map "^2.0.1" @@ -2455,10 +3021,12 @@ escope@^3.6.0: eslint-config-react-app@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-2.1.0.tgz#23c909f71cbaff76b945b831d2d814b8bde169eb" + integrity sha512-8QZrKWuHVC57Fmu+SsKAVxnI9LycZl7NFQ4H9L+oeISuCXhYdXqsOOIVSjQFW6JF5MXZLFE+21Syhd7mF1IRZQ== eslint-import-resolver-node@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" + integrity sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q== dependencies: debug "^2.6.9" resolve "^1.5.0" @@ -2466,6 +3034,7 @@ eslint-import-resolver-node@^0.3.1: eslint-loader@1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-1.9.0.tgz#7e1be9feddca328d3dcfaef1ad49d5beffe83a13" + integrity sha512-40aN976qSNPyb9ejTqjEthZITpls1SVKtwguahmH1dzGCwQU/vySE+xX33VZmD8csU0ahVNCtFlsPgKqRBiqgg== dependencies: loader-fs-cache "^1.0.0" loader-utils "^1.0.2" @@ -2474,8 +3043,9 @@ eslint-loader@1.9.0: rimraf "^2.6.1" eslint-module-utils@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz#abaec824177613b8a95b299639e1b6facf473449" + version "2.2.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746" + integrity sha1-snA2LNiLGkitMIl2zn+lTphBF0Y= dependencies: debug "^2.6.8" pkg-dir "^1.0.0" @@ -2483,12 +3053,14 @@ eslint-module-utils@^2.1.1: eslint-plugin-flowtype@2.39.1: version "2.39.1" resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.39.1.tgz#b5624622a0388bcd969f4351131232dcb9649cd5" + integrity sha512-RiQv+7Z9QDJuzt+NO8sYgkLGT+h+WeCrxP7y8lI7wpU41x3x/2o3PGtHk9ck8QnA9/mlbNcy/hG0eKvmd7npaA== dependencies: lodash "^4.15.0" eslint-plugin-import@2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.8.0.tgz#fa1b6ef31fcb3c501c09859c1b86f1fc5b986894" + integrity sha512-Rf7dfKJxZ16QuTgVv1OYNxkZcsu/hULFnC+e+w0Gzi6jMC3guQoWQgxYxc54IDRinlb6/0v5z/PxxIKmVctN+g== dependencies: builtin-modules "^1.1.1" contains-path "^0.1.0" @@ -2504,6 +3076,7 @@ eslint-plugin-import@2.8.0: eslint-plugin-jsx-a11y@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-5.1.1.tgz#5c96bb5186ca14e94db1095ff59b3e2bd94069b1" + integrity sha512-5I9SpoP7gT4wBFOtXT8/tXNPYohHBVfyVfO17vkbC7r9kEIxYJF12D3pKqhk8+xnk12rfxKClS3WCFpVckFTPQ== dependencies: aria-query "^0.7.0" array-includes "^3.0.3" @@ -2516,6 +3089,7 @@ eslint-plugin-jsx-a11y@5.1.1: eslint-plugin-react@7.4.0: version "7.4.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.4.0.tgz#300a95861b9729c087d362dd64abcc351a74364a" + integrity sha512-tvjU9u3VqmW2vVuYnE8Qptq+6ji4JltjOjJ9u7VAOxVYkUkyBZWRvNYKbDv5fN+L6wiA+4we9+qQahZ0m63XEA== dependencies: doctrine "^2.0.0" has "^1.0.1" @@ -2523,8 +3097,9 @@ eslint-plugin-react@7.4.0: prop-types "^15.5.10" eslint-scope@^3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + version "3.7.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535" + integrity sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA== dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" @@ -2532,6 +3107,7 @@ eslint-scope@^3.7.1: eslint@4.10.0: version "4.10.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.10.0.tgz#f25d0d7955c81968c2309aa5c9a229e045176bb7" + integrity sha512-MMVl8P/dYUFZEvolL8PYt7qc5LNdS2lwheq9BYa5Y07FblhcZqFyaUqlS8TW5QITGex21tV4Lk0a3fK8lsJIkA== dependencies: ajv "^5.2.0" babel-code-frame "^6.22.0" @@ -2572,90 +3148,101 @@ eslint@4.10.0: text-table "~0.2.0" espree@^3.5.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca" + version "3.5.4" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" + integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A== dependencies: - acorn "^5.2.1" + acorn "^5.5.0" acorn-jsx "^3.0.0" esprima@^2.6.0: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= esprima@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= esprima@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + 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.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" + version "1.0.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" + integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== dependencies: estraverse "^4.0.0" esrecurse@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" + 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" - object-assign "^4.0.1" estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= event-emitter@~0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= dependencies: d "1" es5-ext "~0.10.14" -eventemitter3@1.x.x: - version "1.2.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" - -eventemitter3@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" +eventemitter3@^3.0.0, eventemitter3@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" + integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== events@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= eventsource@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232" + integrity sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI= dependencies: original ">=0.0.5" evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== dependencies: md5.js "^1.3.4" safe-buffer "^5.1.1" exec-sh@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38" + version "0.2.2" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.2.tgz#2a5e7ffcbd7d0ba2755bdecb16e5a427dfbdec36" + integrity sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw== dependencies: - merge "^1.1.3" + merge "^1.2.0" execa@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= dependencies: cross-spawn "^5.0.1" get-stream "^3.0.0" @@ -2668,63 +3255,97 @@ execa@^0.7.0: expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s= dependencies: is-posix-bracket "^0.1.0" +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + expand-range@^1.8.1: version "1.8.2" resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= dependencies: fill-range "^2.1.0" expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= dependencies: homedir-polyfill "^1.0.1" express@^4.13.3: - version "4.16.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" + version "4.16.4" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e" + integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg== dependencies: - accepts "~1.3.4" + accepts "~1.3.5" array-flatten "1.1.1" - body-parser "1.18.2" + body-parser "1.18.3" content-disposition "0.5.2" content-type "~1.0.4" cookie "0.3.1" cookie-signature "1.0.6" debug "2.6.9" - depd "~1.1.1" - encodeurl "~1.0.1" + depd "~1.1.2" + encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "1.1.0" + finalhandler "1.1.1" fresh "0.5.2" merge-descriptors "1.0.1" methods "~1.1.2" on-finished "~2.3.0" parseurl "~1.3.2" path-to-regexp "0.1.7" - proxy-addr "~2.0.2" - qs "6.5.1" + proxy-addr "~2.0.4" + qs "6.5.2" range-parser "~1.2.0" - safe-buffer "5.1.1" - send "0.16.1" - serve-static "1.13.1" + safe-buffer "5.1.2" + send "0.16.2" + serve-static "1.13.2" setprototypeof "1.1.0" - statuses "~1.3.1" - type-is "~1.6.15" + statuses "~1.4.0" + type-is "~1.6.16" utils-merge "1.0.1" vary "~1.1.2" -extend@~3.0.0, extend@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.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== external-editor@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.1.0.tgz#3d026a21b7f95b5726387d4200ac160d372c3b48" + version "2.2.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" + integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== dependencies: chardet "^0.4.0" iconv-lite "^0.4.17" @@ -2733,18 +3354,35 @@ external-editor@^2.0.4: extglob@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE= dependencies: is-extglob "^1.0.0" +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + extract-files@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-2.1.1.tgz#3e76eaeeccb5789fc369bfc22bdf9c0e6c5d8b1b" + integrity sha512-dijx+TFYPeI9RbDUj54D+jE7emaxCKXWinkzxrW7EiaGDhm46SPlQRH3n6MksCON3p1T6vprO33kzCa5U0ZzOA== dependencies: "@babel/runtime" "^7.0.0-beta.37" extract-text-webpack-plugin@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz#5f043eaa02f9750a9258b78c0a6e0dc1408fb2f7" + integrity sha512-bt/LZ4m5Rqt/Crl2HiKuAl/oqg0psx1tsTLkvWbJen1CtD+fftkZhMaQ9HOtY2gWsl2Wq+sABmMVi9z3DhKWQQ== dependencies: async "^2.4.1" loader-utils "^1.1.0" @@ -2754,54 +3392,75 @@ extract-text-webpack-plugin@3.0.2: 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.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= fast-deep-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" + integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= fast-json-stable-stringify@^2.0.0: version "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: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= fastparse@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" + version "1.1.2" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" + integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== faye-websocket@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" + integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ= dependencies: websocket-driver ">=0.5.1" faye-websocket@~0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38" + integrity sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg= dependencies: websocket-driver ">=0.5.1" fb-watchman@^1.8.0: version "1.9.2" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-1.9.2.tgz#a24cf47827f82d38fb59a69ad70b76e3b6ae7383" + integrity sha1-okz0eCf4LTj7Waaa1wt247auc4M= dependencies: bser "1.0.2" fb-watchman@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" + integrity sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg= dependencies: bser "^2.0.0" +fbjs-css-vars@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.1.tgz#836d876e887d702f45610f5ebd2fbeef649527fc" + integrity sha512-IM+v/C40MNZWqsLErc32e0TyIk/NhkkQZL0QmjBh6zi1eXv0/GeVKmKmueQX7nn9SXQBQbTUcB8zuexIF3/88w== + fbjs@^0.8.1, fbjs@^0.8.16, fbjs@^0.8.5, fbjs@^0.8.9: - version "0.8.16" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= dependencies: core-js "^1.0.0" isomorphic-fetch "^2.1.1" @@ -2809,17 +3468,33 @@ fbjs@^0.8.1, fbjs@^0.8.16, fbjs@^0.8.5, fbjs@^0.8.9: object-assign "^4.1.0" promise "^7.1.1" setimmediate "^1.0.5" - ua-parser-js "^0.7.9" + ua-parser-js "^0.7.18" + +fbjs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a" + integrity sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA== + dependencies: + core-js "^2.4.1" + fbjs-css-vars "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= dependencies: escape-string-regexp "^1.0.5" file-entry-cache@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E= dependencies: flat-cache "^1.2.1" object-assign "^4.0.1" @@ -2827,6 +3502,7 @@ file-entry-cache@^2.0.0: file-loader@1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.5.tgz#91c25b6b6fbe56dae99f10a425fd64933b5c9daa" + integrity sha512-RzGHDatcVNpGISTvCpfUfOGpYuSR7HSsSg87ki+wF6rw1Hm0RALPTiAdsxAq1UwLf0RRhbe22/eHK6nhXspiOQ== dependencies: loader-utils "^1.0.2" schema-utils "^0.3.0" @@ -2834,10 +3510,12 @@ file-loader@1.1.5: filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= fileset@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" + integrity sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA= dependencies: glob "^7.0.3" minimatch "^3.0.3" @@ -2845,36 +3523,46 @@ fileset@^2.0.2: filesize@3.5.11: version "3.5.11" resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.11.tgz#1919326749433bb3cf77368bd158caabcc19e9ee" + integrity sha512-ZH7loueKBoDb7yG9esn1U+fgq7BzlzW6NRi5/rMdxIZ05dj7GFD/Xc5rq2CDt5Yq86CyfSYVyx4242QQNZbx1g== fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + version "2.2.4" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" + integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== dependencies: is-number "^2.1.0" isobject "^2.0.0" - randomatic "^1.1.3" + randomatic "^3.0.0" repeat-element "^1.1.2" repeat-string "^1.5.2" -filled-array@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filled-array/-/filled-array-1.1.0.tgz#c3c4f6c663b923459a9aa29912d2d031f1507f84" +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" -finalhandler@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" +finalhandler@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" + integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg== dependencies: debug "2.6.9" - encodeurl "~1.0.1" + encodeurl "~1.0.2" escape-html "~1.0.3" on-finished "~2.3.0" parseurl "~1.3.2" - statuses "~1.3.1" + statuses "~1.4.0" unpipe "~1.0.0" find-cache-dir@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" + integrity sha1-yN765XyKUqinhPnjHFfHQumToLk= dependencies: commondir "^1.0.1" mkdirp "^0.5.1" @@ -2883,6 +3571,7 @@ find-cache-dir@^0.1.1: find-cache-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" + integrity sha1-kojj6ePMN0hxfTnq3hfPcfww7m8= dependencies: commondir "^1.0.1" make-dir "^1.0.0" @@ -2891,6 +3580,7 @@ find-cache-dir@^1.0.0: find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= dependencies: path-exists "^2.0.0" pinkie-promise "^2.0.0" @@ -2898,75 +3588,93 @@ find-up@^1.0.0: find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= dependencies: locate-path "^2.0.0" flat-cache@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" + version "1.3.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" + integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== dependencies: circular-json "^0.3.1" - del "^2.0.2" graceful-fs "^4.1.2" + rimraf "~2.6.2" write "^0.2.1" flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" + integrity sha1-2uRqnXj74lKSJYzB54CkHZXAN4I= flow-bin@^0.57.3: version "0.57.3" resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.57.3.tgz#843fb80a821b6d0c5847f7bb3f42365ffe53b27b" + integrity sha512-bbB7KLR1bLS0CvHSsKseOGFF6iI2N9ocL14EQv8Hng28ZksD0gNRzR2JopqA3WGrQYJukDU1W4ZuKoBaRuElFA== -for-in@^1.0.1: +follow-redirects@^1.0.0: + version "1.5.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.9.tgz#c9ed9d748b814a39535716e531b9196a845d89c6" + integrity sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w== + dependencies: + debug "=3.1.0" + +for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= for-own@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= dependencies: for-in "^1.0.1" -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - 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.1.1: version "2.1.4" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + integrity sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE= dependencies: asynckit "^0.4.0" combined-stream "^1.0.5" mime-types "^2.1.12" -form-data@~2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" +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.5" + combined-stream "^1.0.6" mime-types "^2.1.12" -formidable@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" - forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= fs-extra@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291" + integrity sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE= dependencies: graceful-fs "^4.1.2" jsonfile "^3.0.0" @@ -2975,6 +3683,7 @@ fs-extra@3.0.1: fs-extra@^0.30.0: version "0.30.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" + integrity sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A= dependencies: graceful-fs "^4.1.2" jsonfile "^2.1.0" @@ -2982,27 +3691,38 @@ fs-extra@^0.30.0: path-is-absolute "^1.0.0" rimraf "^2.2.8" +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== + dependencies: + minipass "^2.2.1" + 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= fsevents@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" + integrity sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw== dependencies: nan "^2.3.0" node-pre-gyp "^0.6.36" -fsevents@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" +fsevents@^1.0.0, fsevents@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" + integrity sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg== dependencies: - nan "^2.3.0" - node-pre-gyp "^0.6.39" + nan "^2.9.2" + node-pre-gyp "^0.10.0" fstream-ignore@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" + integrity sha1-nDHa40dnAY/h0kmyTa2mfQktoQU= dependencies: fstream "^1.0.0" inherits "2" @@ -3011,23 +3731,27 @@ fstream-ignore@^1.0.5: fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE= dependencies: graceful-fs "^4.1.2" inherits "~2.0.0" mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.0.2, function-bind@^1.1.1: +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= gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= dependencies: aproba "^1.0.3" console-control-strings "^1.0.0" @@ -3039,26 +3763,36 @@ gauge@~2.7.3: wide-align "^1.1.0" get-caller-file@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= 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" glob-base@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q= dependencies: glob-parent "^2.0.0" is-glob "^2.0.0" @@ -3066,12 +3800,22 @@ glob-base@^0.3.0: glob-parent@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= dependencies: is-glob "^2.0.0" +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -3080,9 +3824,17 @@ glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" +global-dirs@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" + integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= + dependencies: + ini "^1.3.4" + global-modules@1.0.0, global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" + integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== dependencies: global-prefix "^1.0.1" is-windows "^1.0.1" @@ -3091,6 +3843,7 @@ global-modules@1.0.0, global-modules@^1.0.0: global-prefix@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" + integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= dependencies: expand-tilde "^2.0.2" homedir-polyfill "^1.0.1" @@ -3101,10 +3854,12 @@ global-prefix@^1.0.1: globals@^9.17.0, globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== globby@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + integrity sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0= dependencies: array-union "^1.0.1" arrify "^1.0.0" @@ -3116,6 +3871,7 @@ globby@^5.0.0: globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= dependencies: array-union "^1.0.1" glob "^7.0.3" @@ -3123,161 +3879,199 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -got@^5.0.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" +got@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" + integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= dependencies: - create-error-class "^3.0.1" - duplexer2 "^0.1.4" + create-error-class "^3.0.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" is-redirect "^1.0.0" is-retry-allowed "^1.0.0" is-stream "^1.0.0" lowercase-keys "^1.0.0" - node-status-codes "^1.0.0" - object-assign "^4.0.1" - parse-json "^2.1.0" - pinkie-promise "^2.0.0" - read-all-stream "^3.0.0" - readable-stream "^2.0.5" - timed-out "^3.0.0" - unzip-response "^1.0.2" + safe-buffer "^5.0.1" + timed-out "^4.0.0" + unzip-response "^2.0.1" url-parse-lax "^1.0.0" graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" - -graphql-anywhere@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.1.1.tgz#67924b67b053d1a23bc30d0e4c8db943c1d791a8" - dependencies: - apollo-utilities "^1.0.4" + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== graphql-tag@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.6.1.tgz#4788d509f6e29607d947fc47a40c4e18f736d34a" + version "2.10.0" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.0.tgz#87da024be863e357551b2b8700e496ee2d4353ae" + integrity sha512-9FD6cw976TLLf9WYIUPCaaTpniawIjHWZSwIRZSjrfufJamcXbVVYfN2TWvJYbw0Xf2JjYbl1/f2+wDnBVw3/w== graphql@^0.12.3: version "0.12.3" resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.12.3.tgz#11668458bbe28261c0dcb6e265f515ba79f6ce07" + integrity sha512-Hn9rdu4zacplKXNrLCvR8YFiTGnbM4Zw/UH8FDmzBDsH7ou40lSNH4tIlsxcYnz2TGNVJCpu1WxCM23yd6kzhA== dependencies: iterall "1.1.3" growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= gzip-size@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-3.0.0.tgz#546188e9bdc337f673772f81660464b389dce520" + integrity sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA= dependencies: duplexer "^0.1.1" handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" + integrity sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ= handlebars@^4.0.3: - version "4.0.11" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" + version "4.0.12" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5" + integrity sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA== dependencies: - async "^1.4.0" + async "^2.5.0" optimist "^0.6.1" - source-map "^0.4.4" + source-map "^0.6.1" optionalDependencies: - uglify-js "^2.6" + uglify-js "^3.1.4" har-schema@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + integrity sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4= 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@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + integrity sha1-M0gdDxu/9gDdID11gSpqX7oALio= dependencies: ajv "^4.9.1" har-schema "^1.0.5" -har-validator@~5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" +har-validator@~5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== dependencies: - ajv "^5.1.0" + ajv "^6.5.5" har-schema "^2.0.0" has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= dependencies: ansi-regex "^2.0.0" has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + integrity sha1-6CB68cx7MNRGzHC3NLXovhj4jVE= + +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-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= -has@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= dependencies: - function-bind "^1.0.2" + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" -hash-base@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= dependencies: - inherits "^2.0.1" + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.1: + 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" hash-base@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" + version "1.1.5" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.5.tgz#e38ab4b85dfb1e0c40fe9265c0e9b54854c23812" + integrity sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA== dependencies: inherits "^2.0.3" - minimalistic-assert "^1.0.0" + minimalistic-assert "^1.0.1" hawk@3.1.3, hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + integrity sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ= dependencies: boom "2.x.x" cryptiles "2.x.x" hoek "2.x.x" sntp "1.x.x" -hawk@~6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" - dependencies: - boom "4.x.x" - cryptiles "3.x.x" - hoek "4.x.x" - sntp "2.x.x" - -he@1.1.x: - version "1.1.1" - resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" +he@1.2.x: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== history@^4.7.2: version "4.7.2" resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" + integrity sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA== dependencies: invariant "^2.2.1" loose-envify "^1.2.0" @@ -3288,6 +4082,7 @@ history@^4.7.2: hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= dependencies: hash.js "^1.0.3" minimalistic-assert "^1.0.0" @@ -3296,22 +4091,24 @@ hmac-drbg@^1.0.0: hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0= -hoek@4.x.x: - version "4.2.0" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" - -hoist-non-react-statics@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" +hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0: + version "2.5.5" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" + integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== -hoist-non-react-statics@^2.2.0, hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.1.0.tgz#42414ccdfff019cd2168168be998c7b3bd5245c0" + integrity sha512-MYcYuROh7SBM69xHGqXEwQqDux34s9tz+sCnxJmN18kgWh6JFdTw/5YdZtqsOdZJXddE/wUpCzfEdDrJj8p0Iw== + dependencies: + react-is "^16.3.2" home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg= dependencies: os-homedir "^1.0.0" os-tmpdir "^1.0.1" @@ -3319,16 +4116,19 @@ home-or-tmp@^2.0.0: homedir-polyfill@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" + integrity sha1-TCu8inWJmP7r9e1oWA921GdotLw= dependencies: parse-passwd "^1.0.0" hosted-git-info@^2.1.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + version "2.7.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" + integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= dependencies: inherits "^2.0.1" obuf "^1.0.0" @@ -3336,35 +4136,39 @@ hpack.js@^2.1.6: wbuf "^1.1.0" html-comment-regex@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" + version "1.1.2" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" + integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== html-encoding-sniffer@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== dependencies: whatwg-encoding "^1.0.1" html-entities@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" + integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8= html-minifier@^3.2.3: - version "3.5.8" - resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.8.tgz#5ccdb1f73a0d654e6090147511f6e6b2ee312700" + version "3.5.21" + resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.21.tgz#d0040e054730e354db008463593194015212d20c" + integrity sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA== dependencies: camel-case "3.0.x" - clean-css "4.1.x" - commander "2.12.x" - he "1.1.x" - ncname "1.0.x" + clean-css "4.2.x" + commander "2.17.x" + he "1.2.x" param-case "2.1.x" relateurl "0.2.x" - uglify-js "3.3.x" + uglify-js "3.4.x" html-webpack-plugin@2.29.0: version "2.29.0" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-2.29.0.tgz#e987f421853d3b6938c8c4c8171842e5fd17af23" + integrity sha1-6Yf0IYU9O2k4yMTIFxhC5f0XryM= dependencies: bluebird "^3.4.7" html-minifier "^3.2.3" @@ -3376,6 +4180,7 @@ html-webpack-plugin@2.29.0: htmlparser2@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" + integrity sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4= dependencies: domelementtype "1" domhandler "2.1" @@ -3385,23 +4190,27 @@ htmlparser2@~3.3.0: http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= -http-errors@1.6.2, http-errors@~1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" +http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= dependencies: - depd "1.1.1" + depd "~1.1.2" inherits "2.0.3" - setprototypeof "1.0.3" - statuses ">= 1.3.1 < 2" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" http-parser-js@>=0.4.0: - version "0.4.9" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.9.tgz#ea1a04fb64adff0242e9974f297dd4c3cad271e1" + version "0.5.0" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.0.tgz#d65edbede84349d0dc30320815a15d39cc3cbbd8" + integrity sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w== http-proxy-middleware@~0.17.4: version "0.17.4" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833" + integrity sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM= dependencies: http-proxy "^1.16.2" is-glob "^3.1.0" @@ -3409,15 +4218,18 @@ http-proxy-middleware@~0.17.4: micromatch "^2.3.11" http-proxy@^1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742" + version "1.17.0" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" + integrity sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g== dependencies: - eventemitter3 "1.x.x" - requires-port "1.x.x" + eventemitter3 "^3.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + integrity sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8= dependencies: assert-plus "^0.2.0" jsprim "^1.2.2" @@ -3426,6 +4238,7 @@ http-signature@~1.1.0: 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" @@ -3434,32 +4247,65 @@ http-signature@~1.2.0: https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + +iconv-lite@0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA== + dependencies: + safer-buffer ">= 2.1.2 < 3" -iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@~0.4.13: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.4, iconv-lite@~0.4.13: + 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" icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= icss-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.1.0.tgz#83f0a0ec378bf3246178b6c2ad9136f135b1c962" + integrity sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI= dependencies: postcss "^6.0.1" ieee754@^1.1.4: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + version "1.1.12" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" + integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== + +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== + dependencies: + minimatch "^3.0.4" ignore@^3.3.3: - version "3.3.7" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" + version "3.3.10" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" + integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== + +immutable-tuple@^0.4.9: + version "0.4.9" + resolved "https://registry.yarnpkg.com/immutable-tuple/-/immutable-tuple-0.4.9.tgz#473ebdd6c169c461913a454bf87ef8f601a20ff0" + integrity sha512-LWbJPZnidF8eczu7XmcnLBsumuyRBkpwIRPCZxlojouhBo5jEBO4toj6n7hMy6IxHU/c+MqDSWkvaTpPlMQcyA== + +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= import-local@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/import-local/-/import-local-0.1.1.tgz#b1179572aacdc11c6a91009fb430dbcab5f668a8" + integrity sha1-sReVcqrNwRxqkQCftDDbyrX2aKg= dependencies: pkg-dir "^2.0.0" resolve-cwd "^2.0.0" @@ -3467,24 +4313,29 @@ import-local@^0.1.1: 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@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= dependencies: repeating "^2.0.0" indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= 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" @@ -3492,18 +4343,22 @@ inflight@^1.0.4: inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== inquirer@3.3.0, inquirer@^3.0.6: version "3.3.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" + integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ== dependencies: ansi-escapes "^3.0.0" chalk "^2.0.0" @@ -3523,258 +4378,388 @@ inquirer@3.3.0, inquirer@^3.0.6: internal-ip@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" + integrity sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w= dependencies: meow "^3.3.0" interpret@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" + integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ= -invariant@^2.0.0, invariant@^2.2.1, invariant@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" +invariant@^2.2.1, 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" invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= 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= -ipaddr.js@1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" +ipaddr.js@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" + integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4= is-absolute-url@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= dependencies: binary-extensions "^1.0.0" is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-builtin-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74= dependencies: builtin-modules "^1.0.0" -is-callable@^1.1.1, is-callable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" +is-callable@^1.1.3, is-callable@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== is-ci@^1.0.10: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" + integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== + dependencies: + ci-info "^1.5.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= dependencies: - ci-info "^1.0.0" + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" is-date-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" is-directory@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= is-dotfile@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE= is-equal-shallow@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ= dependencies: is-primitive "^2.0.0" -is-extendable@^0.1.1: +is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" is-extglob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= -is-extglob@^2.1.0: +is-extglob@^2.1.0, 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-finite@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= dependencies: number-is-nan "^1.0.0" is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= dependencies: number-is-nan "^1.0.0" 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-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= dependencies: is-extglob "^1.0.0" is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= dependencies: is-extglob "^2.1.0" +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= + dependencies: + is-extglob "^2.1.1" + +is-installed-globally@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" + integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= + dependencies: + global-dirs "^0.1.0" + is-path-inside "^1.0.0" + is-npm@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" + integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= dependencies: kind-of "^3.0.2" is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= dependencies: kind-of "^3.0.2" +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== + is-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0= is-path-in-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" + integrity sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ== dependencies: is-path-inside "^1.0.0" is-path-inside@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" + integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= dependencies: path-is-inside "^1.0.1" is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= -is-plain-object@^2.0.1: +is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: isobject "^3.0.1" is-posix-bracket@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q= is-primitive@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= is-promise@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= is-redirect@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" + integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= is-regex@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= dependencies: has "^1.0.1" is-resolvable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== is-retry-allowed@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" + integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ= is-root@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-root/-/is-root-1.0.0.tgz#07b6c233bc394cd9d02ba15c966bd6660d6342d5" + integrity sha1-B7bCM7w5TNnQK6FclmvWZg1jQtU= is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= is-svg@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" + integrity sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk= dependencies: html-comment-regex "^1.1.0" -is-symbol@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" +is-symbol@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" + integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + dependencies: + has-symbols "^1.0.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= is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= -is-windows@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" +is-windows@^1.0.1, is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 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= isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= dependencies: isarray "1.0.0" -isobject@^3.0.1: +isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= isomorphic-fetch@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= dependencies: node-fetch "^1.0.1" whatwg-fetch ">=0.10.0" @@ -3782,81 +4767,97 @@ isomorphic-fetch@^2.1.1: 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= istanbul-api@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.1.tgz#0c60a0515eb11c7d65c6b50bba2c6e999acd8620" + version "1.3.7" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.7.tgz#a86c770d2b03e11e3f778cd7aedd82d2722092aa" + integrity sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA== dependencies: async "^2.1.4" fileset "^2.0.2" - istanbul-lib-coverage "^1.1.1" - istanbul-lib-hook "^1.1.0" - istanbul-lib-instrument "^1.9.1" - istanbul-lib-report "^1.1.2" - istanbul-lib-source-maps "^1.2.2" - istanbul-reports "^1.1.3" + istanbul-lib-coverage "^1.2.1" + istanbul-lib-hook "^1.2.2" + istanbul-lib-instrument "^1.10.2" + istanbul-lib-report "^1.1.5" + istanbul-lib-source-maps "^1.2.6" + istanbul-reports "^1.5.1" js-yaml "^3.7.0" mkdirp "^0.5.1" once "^1.4.0" -istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" +istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0" + integrity sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ== -istanbul-lib-hook@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz#8538d970372cb3716d53e55523dd54b557a8d89b" +istanbul-lib-hook@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz#bc6bf07f12a641fbf1c85391d0daa8f0aea6bf86" + integrity sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw== dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e" +istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2, istanbul-lib-instrument@^1.4.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca" + integrity sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A== dependencies: babel-generator "^6.18.0" babel-template "^6.16.0" babel-traverse "^6.18.0" babel-types "^6.18.0" babylon "^6.18.0" - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.2.1" semver "^5.3.0" -istanbul-lib-report@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425" +istanbul-lib-report@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz#f2a657fc6282f96170aaf281eb30a458f7f4170c" + integrity sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw== dependencies: - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.2.1" mkdirp "^0.5.1" path-parse "^1.0.5" supports-color "^3.1.2" -istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz#750578602435f28a0c04ee6d7d9e0f2960e62c1c" +istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz#37b9ff661580f8fca11232752ee42e08c6675d8f" + integrity sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg== dependencies: debug "^3.1.0" - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.2.1" mkdirp "^0.5.1" rimraf "^2.6.1" source-map "^0.5.3" -istanbul-reports@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.3.tgz#3b9e1e8defb6d18b1d425da8e8b32c5a163f2d10" +istanbul-reports@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.1.tgz#97e4dbf3b515e8c484caea15d6524eebd3ff4e1a" + integrity sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw== dependencies: handlebars "^4.0.3" -iterall@1.1.3, iterall@^1.1.1: +iterall@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.1.3.tgz#1cbbff96204056dde6656e2ed2e2226d0e6d72c9" + integrity sha512-Cu/kb+4HiNSejAPhSaN1VukdNTTi/r4/e+yykqjlG/IW+1gZH5b4+Bq3whDX4tvbYugta3r8KTMUiqT3fIGxuQ== + +iterall@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" + integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA== jest-changed-files@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-20.0.3.tgz#9394d5cc65c438406149bef1bf4d52b68e03e3f8" + integrity sha1-k5TVzGXEOEBhSb7xv01Sto4D4/g= jest-cli@^20.0.4: version "20.0.4" resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-20.0.4.tgz#e532b19d88ae5bc6c417e8b0593a6fe954b1dc93" + integrity sha1-5TKxnYiuW8bEF+iwWTpv6VSx3JM= dependencies: ansi-escapes "^1.4.0" callsites "^2.0.0" @@ -3892,6 +4893,7 @@ jest-cli@^20.0.4: jest-config@^20.0.4: version "20.0.4" resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-20.0.4.tgz#e37930ab2217c913605eff13e7bd763ec48faeea" + integrity sha1-43kwqyIXyRNgXv8T5712PsSPruo= dependencies: chalk "^1.1.3" glob "^7.1.1" @@ -3907,6 +4909,7 @@ jest-config@^20.0.4: jest-diff@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-20.0.3.tgz#81f288fd9e675f0fb23c75f1c2b19445fe586617" + integrity sha1-gfKI/Z5nXw+yPHXxwrGURf5YZhc= dependencies: chalk "^1.1.3" diff "^3.2.0" @@ -3916,10 +4919,12 @@ jest-diff@^20.0.3: jest-docblock@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-20.0.3.tgz#17bea984342cc33d83c50fbe1545ea0efaa44712" + integrity sha1-F76phDQswz2DxQ++FUXqDvqkRxI= jest-environment-jsdom@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-20.0.3.tgz#048a8ac12ee225f7190417713834bb999787de99" + integrity sha1-BIqKwS7iJfcZBBdxODS7mZeH3pk= dependencies: jest-mock "^20.0.3" jest-util "^20.0.3" @@ -3928,6 +4933,7 @@ jest-environment-jsdom@^20.0.3: jest-environment-node@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-20.0.3.tgz#d488bc4612af2c246e986e8ae7671a099163d403" + integrity sha1-1Ii8RhKvLCRumG6K52caCZFj1AM= dependencies: jest-mock "^20.0.3" jest-util "^20.0.3" @@ -3935,6 +4941,7 @@ jest-environment-node@^20.0.3: jest-haste-map@^20.0.4: version "20.0.5" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-20.0.5.tgz#abad74efb1a005974a7b6517e11010709cab9112" + integrity sha512-0IKAQjUvuZjMCNi/0VNQQF74/H9KB67hsHJqGiwTWQC6XO5Azs7kLWm+6Q/dwuhvDUvABDOBMFK2/FwZ3sZ07Q== dependencies: fb-watchman "^2.0.0" graceful-fs "^4.1.11" @@ -3946,6 +4953,7 @@ jest-haste-map@^20.0.4: jest-jasmine2@^20.0.4: version "20.0.4" resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-20.0.4.tgz#fcc5b1411780d911d042902ef1859e852e60d5e1" + integrity sha1-/MWxQReA2RHQQpAu8YWehS5g1eE= dependencies: chalk "^1.1.3" graceful-fs "^4.1.11" @@ -3960,6 +4968,7 @@ jest-jasmine2@^20.0.4: jest-matcher-utils@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-20.0.3.tgz#b3a6b8e37ca577803b0832a98b164f44b7815612" + integrity sha1-s6a443yld4A7CDKpixZPRLeBVhI= dependencies: chalk "^1.1.3" pretty-format "^20.0.3" @@ -3967,6 +4976,7 @@ jest-matcher-utils@^20.0.3: jest-matchers@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-matchers/-/jest-matchers-20.0.3.tgz#ca69db1c32db5a6f707fa5e0401abb55700dfd60" + integrity sha1-ymnbHDLbWm9wf6XgQBq7VXAN/WA= dependencies: jest-diff "^20.0.3" jest-matcher-utils "^20.0.3" @@ -3976,6 +4986,7 @@ jest-matchers@^20.0.3: jest-message-util@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-20.0.3.tgz#6aec2844306fcb0e6e74d5796c1006d96fdd831c" + integrity sha1-auwoRDBvyw5udNV5bBAG2W/dgxw= dependencies: chalk "^1.1.3" micromatch "^2.3.11" @@ -3984,20 +4995,24 @@ jest-message-util@^20.0.3: jest-mock@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-20.0.3.tgz#8bc070e90414aa155c11a8d64c869a0d5c71da59" + integrity sha1-i8Bw6QQUqhVcEajWTIaaDVxx2lk= jest-regex-util@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-20.0.3.tgz#85bbab5d133e44625b19faf8c6aa5122d085d762" + integrity sha1-hburXRM+RGJbGfr4xqpRItCF12I= jest-resolve-dependencies@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-20.0.3.tgz#6e14a7b717af0f2cb3667c549de40af017b1723a" + integrity sha1-bhSntxevDyyzZnxUneQK8Bexcjo= dependencies: jest-regex-util "^20.0.3" jest-resolve@^20.0.4: version "20.0.4" resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-20.0.4.tgz#9448b3e8b6bafc15479444c6499045b7ffe597a5" + integrity sha1-lEiz6La6/BVHlETGSZBFt//ll6U= dependencies: browser-resolve "^1.11.2" is-builtin-module "^1.0.0" @@ -4006,6 +5021,7 @@ jest-resolve@^20.0.4: jest-runtime@^20.0.4: version "20.0.4" resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-20.0.4.tgz#a2c802219c4203f754df1404e490186169d124d8" + integrity sha1-osgCIZxCA/dU3xQE5JAYYWnRJNg= dependencies: babel-core "^6.0.0" babel-jest "^20.0.3" @@ -4026,6 +5042,7 @@ jest-runtime@^20.0.4: jest-snapshot@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-20.0.3.tgz#5b847e1adb1a4d90852a7f9f125086e187c76566" + integrity sha1-W4R+GtsaTZCFKn+fElCG4YfHZWY= dependencies: chalk "^1.1.3" jest-diff "^20.0.3" @@ -4037,6 +5054,7 @@ jest-snapshot@^20.0.3: jest-util@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-20.0.3.tgz#0c07f7d80d82f4e5a67c6f8b9c3fe7f65cfd32ad" + integrity sha1-DAf32A2C9OWmfG+LnD/n9lz9Mq0= dependencies: chalk "^1.1.3" graceful-fs "^4.1.11" @@ -4049,6 +5067,7 @@ jest-util@^20.0.3: jest-validate@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-20.0.3.tgz#d0cfd1de4f579f298484925c280f8f1d94ec3cab" + integrity sha1-0M/R3k9XnymEhJJcKA+PHZTsPKs= dependencies: chalk "^1.1.3" jest-matcher-utils "^20.0.3" @@ -4058,20 +5077,29 @@ jest-validate@^20.0.3: jest@20.0.4: version "20.0.4" resolved "https://registry.yarnpkg.com/jest/-/jest-20.0.4.tgz#3dd260c2989d6dad678b1e9cc4d91944f6d602ac" + integrity sha1-PdJgwpidba1nix6cxNkZRPbWAqw= dependencies: jest-cli "^20.0.4" js-base64@^2.1.9: - version "2.4.2" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.2.tgz#1896da010ef8862f385d8887648e9b6dc4a7a2e9" + version "2.4.9" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.9.tgz#748911fb04f48a60c4771b375cac45a80df11c03" + integrity sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ== + +"js-tokens@^3.0.0 || ^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-tokens@^3.0.0, js-tokens@^3.0.2: +js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= js-yaml@^3.4.3, js-yaml@^3.7.0, js-yaml@^3.9.1: - version "3.10.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" + version "3.12.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" + integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -4079,6 +5107,7 @@ js-yaml@^3.4.3, js-yaml@^3.7.0, js-yaml@^3.9.1: js-yaml@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" + integrity sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A= dependencies: argparse "^1.0.7" esprima "^2.6.0" @@ -4086,10 +5115,12 @@ js-yaml@~3.7.0: jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= jsdom@^9.12.0: version "9.12.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" + integrity sha1-6MVG//ywbADUgzyoRBD+1/igl9Q= dependencies: abab "^1.0.3" acorn "^4.0.4" @@ -4114,60 +5145,78 @@ jsdom@^9.12.0: jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= 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-loader@^0.5.4: version "0.5.7" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" + integrity sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w== json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= + +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.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= json-stable-stringify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= dependencies: jsonify "~0.0.0" 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= json3@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" + integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE= json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= jsonfile@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" + integrity sha1-NzaitCi4e72gzIO1P6PWM6NcKug= optionalDependencies: graceful-fs "^4.1.6" jsonfile@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66" + integrity sha1-pezG9l9T9mLEQVx2daAzHQmS7GY= optionalDependencies: graceful-fs "^4.1.6" jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= dependencies: assert-plus "1.0.0" extsprintf "1.3.0" @@ -4177,62 +5226,79 @@ jsprim@^1.2.2: jsx-ast-utils@^1.4.0: version "1.4.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1" + integrity sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE= jsx-ast-utils@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f" + integrity sha1-6AGxs5mF4g//yHtA43SAgOLcrH8= dependencies: array-includes "^3.0.3" killable@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.0.tgz#da8b84bd47de5395878f95d64d02f2449fe05e6b" + version "1.0.1" + resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" + integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== -kind-of@^3.0.2: +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= dependencies: is-buffer "^1.1.5" kind-of@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= dependencies: is-buffer "^1.1.5" +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + klaw@^1.0.0: version "1.3.1" resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" + integrity sha1-QIhDO0azsbolnXh4XY6W9zugJDk= optionalDependencies: graceful-fs "^4.1.9" -latest-version@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b" +latest-version@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" + integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= dependencies: - package-json "^2.0.0" + package-json "^4.0.0" lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" - -lazy-req@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac" + integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= dependencies: invert-kv "^1.0.0" leven@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" + integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA= 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" @@ -4240,6 +5306,7 @@ levn@^0.3.0, levn@~0.3.0: load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= dependencies: graceful-fs "^4.1.2" parse-json "^2.2.0" @@ -4250,6 +5317,7 @@ load-json-file@^1.0.0: load-json-file@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= dependencies: graceful-fs "^4.1.2" parse-json "^2.2.0" @@ -4259,17 +5327,20 @@ load-json-file@^2.0.0: loader-fs-cache@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz#56e0bf08bd9708b26a765b68509840c8dec9fdbc" + integrity sha1-VuC/CL2XCLJqdltoUJhAyN7J/bw= dependencies: find-cache-dir "^0.1.1" mkdirp "0.5.1" loader-runner@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" + version "2.3.1" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.1.tgz#026f12fe7c3115992896ac02ba022ba92971b979" + integrity sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw== loader-utils@^0.2.16: version "0.2.17" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" + integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g= dependencies: big.js "^3.1.3" emojis-list "^2.0.0" @@ -4279,6 +5350,7 @@ loader-utils@^0.2.16: loader-utils@^1.0.2, loader-utils@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" + integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0= dependencies: big.js "^3.1.3" emojis-list "^2.0.0" @@ -4287,57 +5359,60 @@ loader-utils@^1.0.2, loader-utils@^1.1.0: locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= dependencies: p-locate "^2.0.0" path-exists "^3.0.0" -lodash-es@^4.2.0, lodash-es@^4.2.1: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7" +lodash-es@^4.2.1: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.11.tgz#145ab4a7ac5c5e52a3531fb4f310255a152b4be0" + integrity sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q== lodash._reinterpolate@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - -lodash.assign@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= lodash.cond@^4.3.0: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" + integrity sha1-9HGh2khr5g9quVXRcRVSPdHSVdU= + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= lodash.flowright@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/lodash.flowright/-/lodash.flowright-3.5.0.tgz#2b5fff399716d7e7dc5724fe9349f67065184d67" + integrity sha1-K1//OZcW1+fcVyT+k0n2cGUYTWc= -lodash.isobject@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - -lodash.pick@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= lodash.template@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0" + integrity sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A= dependencies: lodash._reinterpolate "~3.0.0" lodash.templatesettings "^4.0.0" @@ -4345,34 +5420,46 @@ lodash.template@^4.4.0: lodash.templatesettings@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316" + integrity sha1-K01OlbpEDZFf8IvImeRVNmZxMxY= dependencies: lodash._reinterpolate "~3.0.0" +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -"lodash@>=3.5 <5", lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.4: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" +"lodash@>=3.5 <5", lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.4: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== loglevel@^1.4.1: version "1.6.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" + integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.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" + js-tokens "^3.0.0 || ^4.0.0" loud-rejection@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= dependencies: currently-unhandled "^0.4.1" signal-exit "^3.0.0" @@ -4380,62 +5467,87 @@ loud-rejection@^1.0.0: lower-case@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= lowercase-keys@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== lru-cache@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + version "4.1.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" + integrity sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA== dependencies: pseudomap "^1.0.2" yallist "^2.1.2" -macaddress@^0.2.8: - version "0.2.8" - resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" - make-dir@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51" + version "1.3.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" + integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== dependencies: pify "^3.0.0" makeerror@1.0.x: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= dependencies: tmpl "1.0.x" +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" math-expression-evaluator@^1.2.14: version "1.2.17" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" + integrity sha1-3oGf282E3M2PrlnGrreWFbnSZqw= + +math-random@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" + integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w= md5.js@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== dependencies: hash-base "^3.0.0" inherits "^2.0.1" + safe-buffer "^5.1.2" media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= mem@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y= dependencies: mimic-fn "^1.0.0" memory-fs@^0.4.0, memory-fs@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= dependencies: errno "^0.1.3" readable-stream "^2.0.1" @@ -4443,6 +5555,7 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: meow@^3.3.0, meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= dependencies: camelcase-keys "^2.0.0" decamelize "^1.1.2" @@ -4458,18 +5571,22 @@ meow@^3.3.0, meow@^3.7.0: merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= -merge@^1.1.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" +merge@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" + integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= micromatch@^2.1.5, micromatch@^2.3.11: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU= dependencies: arr-diff "^2.0.0" array-unique "^0.2.1" @@ -4485,142 +5602,242 @@ micromatch@^2.1.5, micromatch@^2.3.11: parse-glob "^3.0.4" regex-cache "^0.4.2" +micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== dependencies: bn.js "^4.0.0" brorand "^1.0.1" -"mime-db@>= 1.30.0 < 2": - version "1.32.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.32.0.tgz#485b3848b01a3cda5f968b4882c0771e58e09414" - -mime-db@~1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" +"mime-db@>= 1.36.0 < 2", mime-db@~1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" + integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== -mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: - version "2.1.17" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" +mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.19, mime-types@~2.1.7: + version "2.1.21" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" + integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== dependencies: - mime-db "~1.30.0" + mime-db "~1.37.0" mime@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== mime@^1.4.1, mime@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== mimic-fn@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== -minimalistic-assert@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= minimatch@3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" + integrity sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q= dependencies: brace-expansion "^1.0.0" minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, 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@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= + +minipass@^2.2.1, minipass@^2.3.4: + version "2.3.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" + integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.1.tgz#6734acc045a46e61d596a43bb9d9cd326e19cc42" + integrity sha512-TrfjCjk4jLhcJyGMYymBH6oTXcWjYbUAXTHDbtnWHjZC25h0cdajHuPE1zxb4DVmu8crfh+HwH/WMuyLG0nHBg== + dependencies: + minipass "^2.2.1" + +mixin-deep@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" + integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: minimist "0.0.8" moment@^2.20.1: - version "2.20.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd" + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= 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.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= multicast-dns@^6.0.1: - version "6.2.2" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.2.tgz#300b6133361f8aaaf2b8d1248e85c363fe5b95a0" + version "6.2.3" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" + integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== dependencies: - dns-packet "^1.0.1" - thunky "^0.1.0" + dns-packet "^1.3.1" + thunky "^1.0.2" mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - -nan@^2.3.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" + integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= + +nan@^2.3.0, nan@^2.9.2: + version "2.11.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766" + integrity sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" 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= -ncname@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/ncname/-/ncname-1.0.0.tgz#5b57ad18b1ca092864ef62b0b1ed8194f383b71c" +needle@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" + integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== dependencies: - xml-char-classes "^1.0.0" + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= + +neo-async@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835" + integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA== + +next-tick@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= no-case@^2.2.0: version "2.3.2" resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== dependencies: lower-case "^1.1.1" node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== dependencies: encoding "^0.1.11" is-stream "^1.0.1" -node-forge@0.6.33: - version "0.6.33" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc" +node-forge@0.7.5: + version "0.7.5" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df" + integrity sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ== node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= node-libs-browser@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df" + integrity sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg== dependencies: assert "^1.1.1" browserify-zlib "^0.2.0" @@ -4647,17 +5864,35 @@ node-libs-browser@^2.0.0: vm-browserify "0.0.4" node-notifier@^5.0.2: - version "5.2.1" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.2.1.tgz#fa313dd08f5517db0e2502e5758d664ac69f9dea" + version "5.3.0" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.3.0.tgz#c77a4a7b84038733d5fb351aafd8a268bfe19a01" + integrity sha512-AhENzCSGZnZJgBARsUjnQ7DnZbzyP+HxlVXuD0xqAnvL8q+OqtSX7lGg9e8nHzwXkMMXNdVeqq4E2M3EUAqX6Q== dependencies: growly "^1.3.0" - semver "^5.4.1" + semver "^5.5.0" shellwords "^0.1.1" which "^1.3.0" -node-pre-gyp@^0.6.36, node-pre-gyp@^0.6.39: +node-pre-gyp@^0.10.0: + version "0.10.3" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" + integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +node-pre-gyp@^0.6.36: version "0.6.39" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" + integrity sha512-OsJV74qxnvz/AMGgcfZoDaeDXKD3oY3QVIbBmwszTFkRisTSXbMQyn4UWzUMOtA5SVhrBZOTp0wcoSBgfMfMmQ== dependencies: detect-libc "^1.0.2" hawk "3.1.3" @@ -4671,13 +5906,10 @@ node-pre-gyp@^0.6.36, node-pre-gyp@^0.6.39: tar "^2.2.1" tar-pack "^3.4.0" -node-status-codes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" - nopt@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= dependencies: abbrev "1" osenv "^0.1.4" @@ -4685,40 +5917,59 @@ nopt@^4.0.1: normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw== dependencies: hosted-git-info "^2.1.4" is-builtin-module "^1.0.0" semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.0, normalize-path@^2.0.1: +normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= dependencies: remove-trailing-separator "^1.0.1" normalize-range@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= normalize-url@^1.4.0: version "1.9.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= dependencies: object-assign "^4.0.1" prepend-http "^1.0.0" query-string "^4.1.0" sort-keys "^1.0.0" +npm-bundled@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" + integrity sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g== + +npm-packlist@^1.1.6: + version "1.1.12" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a" + integrity sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= dependencies: path-key "^2.0.0" npmlog@^4.0.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== dependencies: are-we-there-yet "~1.1.2" console-control-strings "~1.1.0" @@ -4726,85 +5977,139 @@ npmlog@^4.0.2: set-blocking "~2.0.0" nth-check@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== dependencies: boolbase "~1.0.0" num2fraction@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= "nwmatcher@>= 1.3.9 < 2.0.0": - version "1.4.3" - resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c" + version "1.4.4" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e" + integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ== -oauth-sign@~0.8.1, oauth-sign@~0.8.2: +oauth-sign@~0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= + +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-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" object-hash@^1.1.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.2.0.tgz#e96af0e96981996a1d47f88ead8f74f1ebc4422b" + version "1.3.1" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" + integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== -object-keys@^1.0.8: - version "1.0.11" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" +object-keys@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" + integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== -object-path@^0.11.4: - version "0.11.4" - resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949" +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo= dependencies: for-own "^0.1.4" is-extendable "^0.1.1" +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + obuf@^1.0.0, obuf@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.1.tgz#104124b6c602c6796881a042541d36db43a5264e" + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= dependencies: ee-first "1.1.1" on-headers@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" + integrity sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c= once@^1.3.0, once@^1.3.3, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= dependencies: mimic-fn "^1.0.0" -opn@5.2.0, opn@^5.1.0: +opn@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225" + integrity sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ== + dependencies: + is-wsl "^1.1.0" + +opn@^5.1.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.4.0.tgz#cb545e7aab78562beb11aa3bfabc7042e1761035" + integrity sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw== dependencies: is-wsl "^1.1.0" +optimism@^0.6.6: + version "0.6.8" + resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.6.8.tgz#0780b546da8cd0a72e5207e0c3706c990c8673a6" + integrity sha512-bN5n1KCxSqwBDnmgDnzMtQTHdL+uea2HYFx1smvtE+w2AMl0Uy31g0aXnP/Nt85OINnMJPRpJyfRQLTCqn5Weg== + dependencies: + immutable-tuple "^0.4.9" + optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= dependencies: minimist "~0.0.1" wordwrap "~0.0.2" @@ -4812,6 +6117,7 @@ optimist@^0.6.1: optionator@^0.8.1, optionator@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= dependencies: deep-is "~0.1.3" fast-levenshtein "~2.0.4" @@ -4821,28 +6127,33 @@ optionator@^0.8.1, optionator@^0.8.2: wordwrap "~1.0.0" original@>=0.0.5: - version "1.0.0" - resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b" + version "1.0.2" + resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" + integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== dependencies: - url-parse "1.0.x" + url-parse "^1.4.3" os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= os-locale@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= dependencies: lcid "^1.0.0" os-locale@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" + integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA== dependencies: execa "^0.7.0" lcid "^1.0.0" @@ -4851,10 +6162,12 @@ os-locale@^2.0.0: os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, 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= -osenv@^0.1.0, osenv@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== dependencies: os-homedir "^1.0.0" os-tmpdir "^1.0.0" @@ -4862,32 +6175,38 @@ osenv@^0.1.0, osenv@^0.1.4: p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= p-limit@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== dependencies: p-try "^1.0.0" p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= dependencies: p-limit "^1.1.0" p-map@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" + integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA== p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= -package-json@^2.0.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-2.4.0.tgz#0d15bd67d1cbbddbb2ca222ff2edb86bcb31a8bb" +package-json@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" + integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= dependencies: - got "^5.0.0" + got "^6.7.1" registry-auth-token "^3.0.1" registry-url "^3.0.3" semver "^5.1.0" @@ -4895,16 +6214,19 @@ package-json@^2.0.0: pako@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" + integrity sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg== param-case@2.1.x: version "2.1.1" resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" + integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc= dependencies: no-case "^2.2.0" parse-asn1@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712" + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" + integrity sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw== dependencies: asn1.js "^4.0.0" browserify-aes "^1.0.0" @@ -4915,77 +6237,103 @@ parse-asn1@^5.0.0: parse-glob@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw= dependencies: glob-base "^0.3.0" is-dotfile "^1.0.0" is-extglob "^1.0.0" is-glob "^2.0.0" -parse-json@^2.1.0, parse-json@^2.2.0: +parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= dependencies: error-ex "^1.2.0" 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= parse5@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" + integrity sha1-m387DeMr543CQBsXVzzK8Pb1nZQ= parser-toolkit@>=0.0.3: version "0.0.5" resolved "https://registry.yarnpkg.com/parser-toolkit/-/parser-toolkit-0.0.5.tgz#ec4b61729c86318b56ea971bfba6b3c672d62c01" + integrity sha1-7EthcpyGMYtW6pcb+6azxnLWLAE= parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M= + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= path-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" + integrity sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo= + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= dependencies: pinkie-promise "^2.0.0" 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, path-is-absolute@^1.0.1: 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-is-inside@^1.0.1, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= path-key@^2.0.0: 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.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= path-to-regexp@^1.0.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= dependencies: isarray "0.0.1" path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= dependencies: graceful-fs "^4.1.2" pify "^2.0.0" @@ -4994,12 +6342,14 @@ path-type@^1.0.0: path-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= dependencies: pify "^2.0.0" pbkdf2@^3.0.3: - version "3.0.14" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade" + version "3.0.17" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" + integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" @@ -5010,56 +6360,72 @@ pbkdf2@^3.0.3: performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + integrity sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU= 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= pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= dependencies: pinkie "^2.0.0" pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= pkg-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + integrity sha1-ektQio1bstYp1EcFb/TpyTFM89Q= dependencies: find-up "^1.0.0" pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= dependencies: find-up "^2.1.0" pluralize@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== portfinder@^1.0.9: - version "1.0.13" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" + version "1.0.19" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.19.tgz#07e87914a55242dcda5b833d42f018d6875b595f" + integrity sha512-23aeQKW9KgHe6citUrG3r9HjeX6vls0h713TAa+CwTKZwNIr/pD2ApaxYF4Um3ZZyq4ar+Siv3+fhoHaIwSOSw== dependencies: async "^1.5.2" debug "^2.2.0" mkdirp "0.5.x" +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + postcss-calc@^5.2.0: version "5.3.1" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" + integrity sha1-d7rnypKK2FcW4v2kLyYb98HWW14= dependencies: postcss "^5.0.2" postcss-message-helpers "^2.0.0" @@ -5068,6 +6434,7 @@ postcss-calc@^5.2.0: postcss-colormin@^2.1.8: version "2.2.2" resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b" + integrity sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks= dependencies: colormin "^1.0.5" postcss "^5.0.13" @@ -5076,6 +6443,7 @@ postcss-colormin@^2.1.8: postcss-convert-values@^2.3.4: version "2.6.1" resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d" + integrity sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0= dependencies: postcss "^5.0.11" postcss-value-parser "^3.1.2" @@ -5083,50 +6451,57 @@ postcss-convert-values@^2.3.4: postcss-discard-comments@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d" + integrity sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0= dependencies: postcss "^5.0.14" postcss-discard-duplicates@^2.0.1: version "2.1.0" resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932" + integrity sha1-uavye4isGIFYpesSq8riAmO5GTI= dependencies: postcss "^5.0.4" postcss-discard-empty@^2.0.1: version "2.1.0" resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5" + integrity sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU= dependencies: postcss "^5.0.14" postcss-discard-overridden@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58" + integrity sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg= dependencies: postcss "^5.0.16" postcss-discard-unused@^2.2.1: version "2.2.3" resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433" + integrity sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM= dependencies: postcss "^5.0.14" uniqs "^2.0.0" postcss-filter-plugins@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c" + version "2.0.3" + resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz#82245fdf82337041645e477114d8e593aa18b8ec" + integrity sha512-T53GVFsdinJhgwm7rg1BzbeBRomOg9y5MBVhGcsV0CxurUdVj1UlPdKtn7aqYA/c/QVkzKMjq2bSV5dKG5+AwQ== dependencies: postcss "^5.0.4" - uniqid "^4.0.0" postcss-flexbugs-fixes@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-3.2.0.tgz#9b8b932c53f9cf13ba0f61875303e447c33dcc51" + integrity sha512-0AuD9HG1Ey3/3nqPWu9yqf7rL0KCPu5VgjDsjf5mzEcuo9H/z8nco/fljKgjsOUrZypa95MI0kS4xBZeBzz2lw== dependencies: postcss "^6.0.1" postcss-load-config@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-1.2.0.tgz#539e9afc9ddc8620121ebf9d8c3673e0ce50d28a" + integrity sha1-U56a/J3chiASHr+djDZz4M5Q0oo= dependencies: cosmiconfig "^2.1.0" object-assign "^4.1.0" @@ -5136,6 +6511,7 @@ postcss-load-config@^1.2.0: postcss-load-options@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/postcss-load-options/-/postcss-load-options-1.2.0.tgz#b098b1559ddac2df04bc0bb375f99a5cfe2b6d8c" + integrity sha1-sJixVZ3awt8EvAuzdfmaXP4rbYw= dependencies: cosmiconfig "^2.1.0" object-assign "^4.1.0" @@ -5143,6 +6519,7 @@ postcss-load-options@^1.2.0: postcss-load-plugins@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz#745768116599aca2f009fad426b00175049d8d92" + integrity sha1-dFdoEWWZrKLwCfrUJrABdQSdjZI= dependencies: cosmiconfig "^2.1.1" object-assign "^4.1.0" @@ -5150,6 +6527,7 @@ postcss-load-plugins@^2.3.0: postcss-loader@2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.8.tgz#8c67ddb029407dfafe684a406cfc16bad2ce0814" + integrity sha512-KtXBiQ/r/WYW8LxTSJK7h8wLqvCMSub/BqmRnud/Mu8RzwflW9cmXxwsMwbn15TNv287Hcufdb3ZSs7xHKnG8Q== dependencies: loader-utils "^1.1.0" postcss "^6.0.0" @@ -5159,6 +6537,7 @@ postcss-loader@2.0.8: postcss-merge-idents@^2.1.5: version "2.1.7" resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270" + integrity sha1-TFUwMTwI4dWzu/PSu8dH4njuonA= dependencies: has "^1.0.1" postcss "^5.0.10" @@ -5167,12 +6546,14 @@ postcss-merge-idents@^2.1.5: postcss-merge-longhand@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658" + integrity sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg= dependencies: postcss "^5.0.4" postcss-merge-rules@^2.0.3: version "2.1.2" resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721" + integrity sha1-0d9d+qexrMO+VT8OnhDofGG19yE= dependencies: browserslist "^1.5.2" caniuse-api "^1.5.2" @@ -5183,10 +6564,12 @@ postcss-merge-rules@^2.0.3: postcss-message-helpers@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e" + integrity sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4= postcss-minify-font-values@^1.0.2: version "1.0.5" resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69" + integrity sha1-S1jttWZB66fIR0qzUmyv17vey2k= dependencies: object-assign "^4.0.1" postcss "^5.0.4" @@ -5195,6 +6578,7 @@ postcss-minify-font-values@^1.0.2: postcss-minify-gradients@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1" + integrity sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE= dependencies: postcss "^5.0.12" postcss-value-parser "^3.3.0" @@ -5202,6 +6586,7 @@ postcss-minify-gradients@^1.0.1: postcss-minify-params@^1.0.4: version "1.2.2" resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3" + integrity sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM= dependencies: alphanum-sort "^1.0.1" postcss "^5.0.2" @@ -5211,6 +6596,7 @@ postcss-minify-params@^1.0.4: postcss-minify-selectors@^2.0.4: version "2.1.1" resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf" + integrity sha1-ssapjAByz5G5MtGkllCBFDEXNb8= dependencies: alphanum-sort "^1.0.2" has "^1.0.1" @@ -5218,14 +6604,16 @@ postcss-minify-selectors@^2.0.4: postcss-selector-parser "^2.0.0" postcss-modules-extract-imports@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz#b614c9720be6816eaee35fb3a5faa1dba6a05ddb" + version "1.2.1" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz#dc87e34148ec7eab5f791f7cd5849833375b741a" + integrity sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw== dependencies: postcss "^6.0.1" postcss-modules-local-by-default@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" + integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= dependencies: css-selector-tokenizer "^0.7.0" postcss "^6.0.1" @@ -5233,6 +6621,7 @@ postcss-modules-local-by-default@^1.0.1: postcss-modules-scope@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" + integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= dependencies: css-selector-tokenizer "^0.7.0" postcss "^6.0.1" @@ -5240,6 +6629,7 @@ postcss-modules-scope@^1.0.0: postcss-modules-values@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" + integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= dependencies: icss-replace-symbols "^1.1.0" postcss "^6.0.1" @@ -5247,12 +6637,14 @@ postcss-modules-values@^1.1.0: postcss-normalize-charset@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1" + integrity sha1-757nEhLX/nWceO0WL2HtYrXLk/E= dependencies: postcss "^5.0.5" postcss-normalize-url@^3.0.7: version "3.0.8" resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222" + integrity sha1-EI90s/L82viRov+j6kWSJ5/HgiI= dependencies: is-absolute-url "^2.0.0" normalize-url "^1.4.0" @@ -5262,6 +6654,7 @@ postcss-normalize-url@^3.0.7: postcss-ordered-values@^2.1.0: version "2.2.3" resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d" + integrity sha1-7sbCpntsQSqNsgQud/6NpD+VwR0= dependencies: postcss "^5.0.4" postcss-value-parser "^3.0.1" @@ -5269,6 +6662,7 @@ postcss-ordered-values@^2.1.0: postcss-reduce-idents@^2.2.2: version "2.4.0" resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3" + integrity sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM= dependencies: postcss "^5.0.4" postcss-value-parser "^3.0.2" @@ -5276,12 +6670,14 @@ postcss-reduce-idents@^2.2.2: postcss-reduce-initial@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea" + integrity sha1-aPgGlfBF0IJjqHmtJA343WT2ROo= dependencies: postcss "^5.0.4" postcss-reduce-transforms@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1" + integrity sha1-/3b02CEkN7McKYpC0uFEQCV3GuE= dependencies: has "^1.0.1" postcss "^5.0.8" @@ -5290,6 +6686,7 @@ postcss-reduce-transforms@^1.0.3: postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: version "2.2.3" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" + integrity sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A= dependencies: flatten "^1.0.2" indexes-of "^1.0.1" @@ -5298,6 +6695,7 @@ postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: postcss-svgo@^2.1.1: version "2.1.6" resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" + integrity sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0= dependencies: is-svg "^2.0.0" postcss "^5.0.14" @@ -5307,18 +6705,21 @@ postcss-svgo@^2.1.1: postcss-unique-selectors@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d" + integrity sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0= dependencies: alphanum-sort "^1.0.1" postcss "^5.0.4" uniqs "^2.0.0" postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== postcss-zindex@^2.0.1: version "2.2.0" resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22" + integrity sha1-0hCd3AVbka9n/EyzsCWUZjnSryI= dependencies: has "^1.0.1" postcss "^5.0.4" @@ -5327,6 +6728,7 @@ postcss-zindex@^2.0.1: postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16: version "5.2.18" resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" + integrity sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg== dependencies: chalk "^1.1.3" js-base64 "^2.1.9" @@ -5334,32 +6736,38 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 supports-color "^3.2.3" postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.13: - version "6.0.16" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.16.tgz#112e2fe2a6d2109be0957687243170ea5589e146" + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== dependencies: - chalk "^2.3.0" + chalk "^2.4.1" source-map "^0.6.1" - supports-color "^5.1.0" + supports-color "^5.4.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= prepend-http@^1.0.0, prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= pretty-bytes@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9" + integrity sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk= pretty-error@^2.0.2: version "2.1.1" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" + integrity sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM= dependencies: renderkid "^2.0.1" utila "~0.4" @@ -5367,94 +6775,122 @@ pretty-error@^2.0.2: pretty-format@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-20.0.3.tgz#020e350a560a1fe1a98dc3beb6ccffb386de8b14" + integrity sha1-Ag41ClYKH+GpjcO+tsz/s4beixQ= dependencies: ansi-regex "^2.1.1" ansi-styles "^3.0.0" -private@^0.1.6, private@^0.1.7: +private@^0.1.6, private@^0.1.7, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= progress@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + version "2.0.1" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.1.tgz#c9242169342b1c29d275889c95734621b1952e31" + integrity sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg== promise@8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/promise/-/promise-8.0.1.tgz#e45d68b00a17647b6da711bf85ed6ed47208f450" + integrity sha1-5F1osAoXZHttpxG/he1u1HII9FA= dependencies: asap "~2.0.3" promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== dependencies: asap "~2.0.3" -prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0: - version "15.6.0" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" +prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@~15.6.0: + version "15.6.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" + integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ== dependencies: - fbjs "^0.8.16" loose-envify "^1.3.1" object-assign "^4.1.1" -proxy-addr@~2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" +proxy-addr@~2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" + integrity sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA== dependencies: forwarded "~0.1.2" - ipaddr.js "1.5.2" + ipaddr.js "1.8.0" prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + +psl@^1.1.24: + version "1.1.29" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" + integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ== public-encrypt@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6" + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== dependencies: bn.js "^4.1.0" browserify-rsa "^4.0.0" create-hash "^1.1.0" parse-asn1 "^5.0.0" randombytes "^2.0.1" + safe-buffer "^5.1.2" punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +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== q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qs@6.5.1, qs@~6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +qs@6.5.2, qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + integrity sha1-E+JtKK1rD/qpExLNO/cI7TUecjM= query-string@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= dependencies: object-assign "^4.1.0" strict-uri-encode "^1.0.0" @@ -5462,41 +6898,52 @@ query-string@^4.1.0: querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -querystringify@0.0.x: - version "0.0.4" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c" - -querystringify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" +querystringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef" + integrity sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg== -raf@3.4.0, raf@^3.2.0: +raf@3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" + integrity sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw== dependencies: performance-now "^2.1.0" -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" +raf@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" + performance-now "^2.1.0" + +randomatic@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" + integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== + dependencies: + is-number "^4.0.0" + kind-of "^6.0.0" + math-random "^1.0.1" randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" + integrity sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A== dependencies: safe-buffer "^5.1.0" randomfill@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.3.tgz#b96b7df587f01dd91726c418f30553b1418e3d62" + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== dependencies: randombytes "^2.0.5" safe-buffer "^5.1.0" @@ -5504,45 +6951,50 @@ randomfill@^1.0.3: range-parser@^1.0.3, range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= -raw-body@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" +raw-body@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" + integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== dependencies: bytes "3.0.0" - http-errors "1.6.2" - iconv-lite "0.4.19" + http-errors "1.6.3" + iconv-lite "0.4.23" unpipe "1.0.0" -rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: - version "1.2.4" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.4.tgz#a0f606caae2a3b862bbd0ef85482c0125b315fa3" +rc@^1.0.1, rc@^1.1.6, rc@^1.1.7, rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== dependencies: - deep-extend "~0.4.0" + deep-extend "^0.6.0" ini "~1.3.0" minimist "^1.2.0" strip-json-comments "~2.0.1" react-apollo@2.x: - version "2.0.4" - resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-2.0.4.tgz#01dd32a8e388672f5d7385b21cdd0b94009ee9ee" + version "2.3.1" + resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-2.3.1.tgz#f3687062f438b9b40e525d7e1ac2bcf0101b4495" + integrity sha512-xPteQmCzMJT8wE4v0zq38E6JWLhrNcJENl66fP117lwzU2lZA+qoUDcizqKl0z2OY5b+KklmqnPPD+wcWAdy7w== dependencies: - apollo-link "^1.0.0" - hoist-non-react-statics "^2.2.0" - invariant "^2.2.1" + fbjs "^1.0.0" + hoist-non-react-statics "^3.0.0" + invariant "^2.2.2" lodash.flowright "^3.5.0" - lodash.pick "^4.4.0" - prop-types "^15.5.8" + lodash.isequal "^4.5.0" + prop-types "^15.6.0" react-dev-utils@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-5.0.0.tgz#425ac7c9c40c2603bc4f7ab8836c1406e96bb473" + version "5.0.3" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-5.0.3.tgz#92f97668f03deb09d7fa11ea288832a8c756e35e" + integrity sha512-Mvs6ofsc2xTjeZIrMaIfbXfsPVrbdVy/cVqq6SAacnqfMlcBpDuivhWZ1ODGeJ8HgmyWTLH971PYjj/EPCDVAw== dependencies: address "1.0.3" babel-code-frame "6.26.0" chalk "1.1.3" cross-spawn "5.1.0" - detect-port-alt "1.1.5" + detect-port-alt "1.1.6" escape-string-regexp "1.0.5" filesize "3.5.11" global-modules "1.0.0" @@ -5550,78 +7002,100 @@ react-dev-utils@^5.0.0: inquirer "3.3.0" is-root "1.0.0" opn "5.2.0" - react-error-overlay "^4.0.0" + react-error-overlay "^4.0.1" recursive-readdir "2.2.1" shell-quote "1.6.1" - sockjs-client "1.1.4" + sockjs-client "1.1.5" strip-ansi "3.0.1" text-table "0.2.0" react-dom@16.x: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044" + version "16.6.3" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.6.3.tgz#8fa7ba6883c85211b8da2d0efeffc9d3825cccc0" + integrity sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ== dependencies: - fbjs "^0.8.16" loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.0" + prop-types "^15.6.2" + scheduler "^0.11.2" "react-dom@^15.0.0 || 15.x": version "15.6.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.2.tgz#41cfadf693b757faf2708443a1d1fd5a02bef730" + integrity sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA= dependencies: fbjs "^0.8.9" loose-envify "^1.1.0" object-assign "^4.1.0" prop-types "^15.5.10" -react-error-overlay@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.0.tgz#d198408a85b4070937a98667f500c832f86bd5d4" +react-error-overlay@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.1.tgz#417addb0814a90f3a7082eacba7cee588d00da89" + integrity sha512-xXUbDAZkU08aAkjtUvldqbvI04ogv+a1XdHxvYuHPYKIVk/42BIOD0zSKTHAWV4+gDy3yGm283z2072rA2gdtw== + +react-is@^16.3.1, react-is@^16.3.2, react-is@^16.6.0: + version "16.6.3" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.3.tgz#d2d7462fcfcbe6ec0da56ad69047e47e56e7eac0" + integrity sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA== + +react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== react-redux@^5.0.5: - version "5.0.6" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946" + version "5.1.1" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.1.tgz#88e368682c7fa80e34e055cd7ac56f5936b0f52f" + integrity sha512-LE7Ned+cv5qe7tMV5BPYkGQ5Lpg8gzgItK07c67yHvJ8t0iaD9kPFPAli/mYkiyJYrs2pJgExR2ZgsGqlrOApg== dependencies: - hoist-non-react-statics "^2.2.1" - invariant "^2.0.0" - lodash "^4.2.0" - lodash-es "^4.2.0" + "@babel/runtime" "^7.1.2" + hoist-non-react-statics "^3.1.0" + invariant "^2.2.4" loose-envify "^1.1.0" - prop-types "^15.5.10" + prop-types "^15.6.1" + react-is "^16.6.0" + react-lifecycles-compat "^3.0.0" -react-resize-detector@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-1.1.0.tgz#4a9831fa3caad32230478dd0185cbd2aa91a5ebf" +react-resize-detector@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-2.3.0.tgz#57bad1ae26a28a62a2ddb678ba6ffdf8fa2b599c" + integrity sha512-oCAddEWWeFWYH5FAcHdBYcZjAw9fMzRUK9sWSx6WvSSOPVRxcHd5zTIGy/mOus+AhN/u6T4TMiWxvq79PywnJQ== dependencies: - prop-types "^15.5.10" + lodash.debounce "^4.0.8" + lodash.throttle "^4.1.1" + prop-types "^15.6.0" + resize-observer-polyfill "^1.5.0" react-router-dom@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" + integrity sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA== dependencies: history "^4.7.2" - invariant "^2.2.2" + invariant "^2.2.4" loose-envify "^1.3.1" - prop-types "^15.5.4" - react-router "^4.2.0" - warning "^3.0.0" + prop-types "^15.6.1" + react-router "^4.3.1" + warning "^4.0.1" -react-router@^4.1.1, react-router@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986" +react-router@^4.1.1, react-router@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" + integrity sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg== dependencies: history "^4.7.2" - hoist-non-react-statics "^2.3.0" - invariant "^2.2.2" + hoist-non-react-statics "^2.5.0" + invariant "^2.2.4" loose-envify "^1.3.1" path-to-regexp "^1.7.0" - prop-types "^15.5.4" - warning "^3.0.0" + prop-types "^15.6.1" + warning "^4.0.1" react-scripts@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-1.1.0.tgz#0c94b2b2e14cff2dad8919397901b5edebeba511" + integrity sha512-6FxNkE9ljbu/I0w0oxTvUlOv9zfwAJNxASSoi7qqIhDkf3qmhl4xLuz5Pbn4ayiAz+8G9+P3AfaI/Iq6iCE73g== dependencies: autoprefixer "7.1.6" babel-core "6.26.0" @@ -5663,51 +7137,47 @@ react-scripts@1.1.0: optionalDependencies: fsevents "1.1.2" -react-smooth@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-1.0.0.tgz#b29dbebddddb06d21b5b08962167fb9eac1897d8" +react-smooth@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-1.0.2.tgz#f7a2d932ece8db898646078c3c97f3e9533e0486" + integrity sha512-pIGzL1g9VGAsRsdZQokIK0vrCkcdKtnOnS1gyB2rrowdLy69lNSWoIjCTWAfgbiYvria8tm5hEZqj+jwXMkV4A== dependencies: lodash "~4.17.4" prop-types "^15.6.0" - raf "^3.2.0" - react-transition-group "^2.2.1" + raf "^3.4.0" + react-transition-group "^2.5.0" -react-transition-group@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10" +react-transition-group@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.0.tgz#70bca0e3546102c4dc5cf3f5f57f73447cce6874" + integrity sha512-qYB3JBF+9Y4sE4/Mg/9O6WFpdoYjeeYqx0AFb64PTazVy8RPMiE3A47CG9QmM4WJ/mzDiZYslV+Uly6O1Erlgw== dependencies: - chain-function "^1.0.0" - classnames "^2.2.5" - dom-helpers "^3.2.0" - loose-envify "^1.3.1" - prop-types "^15.5.8" - warning "^3.0.0" + dom-helpers "^3.3.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-lifecycles-compat "^3.0.4" react-trend@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/react-trend/-/react-trend-1.2.4.tgz#498987286abd43ee3dff881115cd64d1c8e72d95" + integrity sha1-SYmHKGq9Q+49/4gRFc1k0cjnLZU= dependencies: prop-types "^15.5.8" react@16.x: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" + version "16.6.3" + resolved "https://registry.yarnpkg.com/react/-/react-16.6.3.tgz#25d77c91911d6bbdd23db41e70fb094cc1e0871c" + integrity sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw== dependencies: - fbjs "^0.8.16" loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.0" - -read-all-stream@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" - dependencies: - pinkie-promise "^2.0.0" - readable-stream "^2.0.0" + prop-types "^15.6.2" + scheduler "^0.11.2" read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= dependencies: find-up "^1.0.0" read-pkg "^1.0.0" @@ -5715,6 +7185,7 @@ read-pkg-up@^1.0.1: read-pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= dependencies: find-up "^2.0.0" read-pkg "^2.0.0" @@ -5722,6 +7193,7 @@ read-pkg-up@^2.0.0: read-pkg@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= dependencies: load-json-file "^1.0.0" normalize-package-data "^2.3.2" @@ -5730,6 +7202,7 @@ read-pkg@^1.0.0: read-pkg@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= dependencies: load-json-file "^2.0.0" normalize-package-data "^2.3.2" @@ -5738,56 +7211,63 @@ read-pkg@^2.0.0: readable-stream@1.0: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= dependencies: core-util-is "~1.0.0" inherits "~2.0.1" isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3, readable-stream@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" isarray "~1.0.0" - process-nextick-args "~1.0.6" + process-nextick-args "~2.0.0" safe-buffer "~5.1.1" - string_decoder "~1.0.3" + string_decoder "~1.1.1" util-deprecate "~1.0.1" readdirp@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== dependencies: - graceful-fs "^4.1.2" - minimatch "^3.0.2" + graceful-fs "^4.1.11" + micromatch "^3.1.10" readable-stream "^2.0.2" - set-immediate-shim "^1.0.1" -recharts-scale@0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.3.2.tgz#dac7621714a4765d152cb2adbc30c73b831208c9" +recharts-scale@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.2.tgz#b66315d985cd9b80d5f7d977a5aab9a305abc354" + integrity sha512-p/cKt7j17D1CImLgX2f5+6IXLbRHGUQkogIp06VUoci/XkhOQiGSzUrsD1uRmiI7jha4u8XNFOjkHkzzBPivMg== + dependencies: + decimal.js-light "^2.4.1" recharts@^1.0.0-alpha.6: - version "1.0.0-beta.9" - resolved "https://registry.yarnpkg.com/recharts/-/recharts-1.0.0-beta.9.tgz#f00ff7dc9b7b017c8b4f66a10b96f39b20cd1353" - dependencies: - classnames "2.2.5" - core-js "2.5.1" - d3-interpolate "^1.1.5" - d3-scale "1.0.6" - d3-shape "1.2.0" + version "1.4.1" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-1.4.1.tgz#92df29090b457c34c58beec263dc766c2b9ea300" + integrity sha512-HtI3B4xGPn591kNDU5MaCcJeUtlSqOH3cOBtHuhXGxDU2MDkS6abaQtgZD1J4mQiUCUZT3LCziTz5+wD7k+pvw== + dependencies: + classnames "~2.2.5" + core-js "~2.5.1" + d3-interpolate "~1.3.0" + d3-scale "~2.1.0" + d3-shape "~1.2.0" lodash "~4.17.4" - prop-types "^15.6.0" - react-resize-detector "1.1.0" - react-smooth "1.0.0" - recharts-scale "0.3.2" - reduce-css-calc "1.3.0" + prop-types "~15.6.0" + react-resize-detector "~2.3.0" + react-smooth "~1.0.0" + recharts-scale "^0.4.2" + reduce-css-calc "~1.3.0" recompose@^0.26.0: version "0.26.0" resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.26.0.tgz#9babff039cb72ba5bd17366d55d7232fbdfb2d30" + integrity sha512-KwOu6ztO0mN5vy3+zDcc45lgnaUoaQse/a5yLVqtzTK13czSWnFGmXbQVmnoMgDkI5POd1EwIKSbjU1V7xdZog== dependencies: change-emitter "^0.1.2" fbjs "^0.8.1" @@ -5797,19 +7277,22 @@ recompose@^0.26.0: recursive-readdir@2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.1.tgz#90ef231d0778c5ce093c9a48d74e5c5422d13a99" + integrity sha1-kO8jHQd4xc4JPJpI105cVCLROpk= dependencies: minimatch "3.0.3" redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= dependencies: indent-string "^2.1.0" strip-indent "^1.0.1" -reduce-css-calc@1.3.0, reduce-css-calc@^1.2.6: +reduce-css-calc@^1.2.6, reduce-css-calc@~1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" + integrity sha1-dHyRTgSWFKTJz7umKYca0dKSdxY= dependencies: balanced-match "^0.4.2" math-expression-evaluator "^1.2.14" @@ -5818,16 +7301,19 @@ reduce-css-calc@1.3.0, reduce-css-calc@^1.2.6: reduce-function-call@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99" + integrity sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk= dependencies: balanced-match "^0.4.2" redux-thunk@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5" + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== redux@^3.6.0: version "3.7.2" resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b" + integrity sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A== dependencies: lodash "^4.2.1" lodash-es "^4.2.1" @@ -5835,16 +7321,24 @@ redux@^3.6.0: symbol-observable "^1.0.3" regenerate@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-runtime@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" + integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== regenerator-transform@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" + integrity sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q== dependencies: babel-runtime "^6.18.0" babel-types "^6.19.0" @@ -5853,12 +7347,22 @@ regenerator-transform@^0.10.0: regex-cache@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" + integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ== dependencies: is-equal-shallow "^0.1.3" +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + regexpu-core@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" + integrity sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs= dependencies: regenerate "^1.2.1" regjsgen "^0.2.0" @@ -5867,14 +7371,16 @@ regexpu-core@^1.0.0: regexpu-core@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + integrity sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA= dependencies: regenerate "^1.2.1" regjsgen "^0.2.0" regjsparser "^0.1.4" registry-auth-token@^3.0.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.1.tgz#fb0d3289ee0d9ada2cbb52af5dfe66cb070d3006" + version "3.3.2" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" + integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ== dependencies: rc "^1.1.6" safe-buffer "^5.0.1" @@ -5882,54 +7388,64 @@ registry-auth-token@^3.0.1: registry-url@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" + integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= dependencies: rc "^1.0.1" regjsgen@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc= regjsparser@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw= dependencies: jsesc "~0.5.0" relateurl@0.2.x: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= renderkid@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.1.tgz#898cabfc8bede4b7b91135a3ffd323e58c0db319" + version "2.0.2" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.2.tgz#12d310f255360c07ad8fde253f6c9e9de372d2aa" + integrity sha512-FsygIxevi1jSiPY9h7vZmBFUbAOcbYm9UwyiLNdVsLRs/5We9Ob5NMPbGYUTWiLq5L+ezlVdE0A8bbME5CWTpg== dependencies: css-select "^1.1.0" - dom-converter "~0.1" + dom-converter "~0.2" htmlparser2 "~3.3.0" strip-ansi "^3.0.0" - utila "~0.3" + utila "^0.4.0" repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== -repeat-string@^1.5.2: +repeat-string@^1.5.2, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= repeating@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= dependencies: is-finite "^1.0.0" request@2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + integrity sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA= dependencies: aws-sign2 "~0.6.0" aws4 "^1.2.1" @@ -5955,64 +7471,75 @@ request@2.81.0: uuid "^3.0.0" request@^2.79.0, request@^2.81.0: - version "2.83.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== dependencies: aws-sign2 "~0.7.0" - aws4 "^1.6.0" + aws4 "^1.8.0" caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" + combined-stream "~1.0.6" + extend "~3.0.2" forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" + form-data "~2.3.2" + har-validator "~5.1.0" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" + mime-types "~2.1.19" + oauth-sign "~0.9.0" performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.3" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" tunnel-agent "^0.6.0" - uuid "^3.1.0" + uuid "^3.3.2" require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= require-from-string@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" + integrity sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg= require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= require-uncached@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= dependencies: caller-path "^0.1.0" resolve-from "^1.0.0" -requires-port@1.0.x, requires-port@1.x.x, requires-port@~1.0.0: +requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + +resize-observer-polyfill@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.0.tgz#660ff1d9712a2382baa2cad450a4716209f9ca69" + integrity sha512-M2AelyJDVR/oLnToJLtuDJRBBWUGUvvGigj1411hXhAdyFWqMaqHp7TixW3FpiLuVaikIcR1QL+zqoJoZlOgpg== resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= dependencies: resolve-from "^3.0.0" resolve-dir@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" + integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= dependencies: expand-tilde "^2.0.0" global-modules "^1.0.0" @@ -6020,74 +7547,110 @@ resolve-dir@^1.0.0: resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY= resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= resolve-pathname@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" + integrity sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= resolve@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= resolve@^1.3.2, resolve@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" + version "1.8.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" + integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== dependencies: path-parse "^1.0.5" restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= dependencies: onetime "^2.0.0" signal-exit "^3.0.2" +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + integrity sha1-YTObci/mo1FWiSENJOFMlhSGE+8= dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1, rimraf@~2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== dependencies: glob "^7.0.5" ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== dependencies: - hash-base "^2.0.0" + hash-base "^3.0.0" inherits "^2.0.1" run-async@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= dependencies: is-promise "^2.1.0" rx-lite-aggregates@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74= dependencies: rx-lite "*" rx-lite@*, rx-lite@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= -safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.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== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", 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== sane@~1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/sane/-/sane-1.6.0.tgz#9610c452307a135d29c1fdfe2547034180c46775" + integrity sha1-lhDEUjB6E10pwf3+JUcDQYDEZ3U= dependencies: anymatch "^1.3.0" exec-sh "^0.2.0" @@ -6097,44 +7660,59 @@ sane@~1.6.0: walker "~1.0.5" watch "~0.10.0" -sax@^1.2.1, sax@~1.2.1: +sax@^1.2.1, sax@^1.2.4, sax@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +scheduler@^0.11.2: + version "0.11.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.2.tgz#a8db5399d06eba5abac51b705b7151d2319d33d3" + integrity sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" schema-utils@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" + integrity sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8= dependencies: ajv "^5.0.0" select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= selfsigned@^1.9.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.1.tgz#bf8cb7b83256c4551e31347c6311778db99eec52" + version "1.10.4" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.4.tgz#cdd7eccfca4ed7635d47a08bf2d5d3074092e2cd" + integrity sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw== dependencies: - node-forge "0.6.33" + node-forge "0.7.5" semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" + integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= dependencies: semver "^5.0.3" -"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1: - version "5.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -send@0.16.1: - version "0.16.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" +send@0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" + integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== dependencies: debug "2.6.9" - depd "~1.1.1" + depd "~1.1.2" destroy "~1.0.4" - encodeurl "~1.0.1" + encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" fresh "0.5.2" @@ -6143,11 +7721,12 @@ send@0.16.1: ms "2.0.0" on-finished "~2.3.0" range-parser "~1.2.0" - statuses "~1.3.1" + statuses "~1.4.0" serve-index@^1.7.2: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= dependencies: accepts "~1.3.4" batch "0.6.1" @@ -6157,42 +7736,60 @@ serve-index@^1.7.2: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719" +serve-static@1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" + integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== dependencies: - encodeurl "~1.0.1" + encodeurl "~1.0.2" escape-html "~1.0.3" parseurl "~1.3.2" - send "0.16.1" + send "0.16.2" serviceworker-cache-polyfill@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/serviceworker-cache-polyfill/-/serviceworker-cache-polyfill-4.0.0.tgz#de19ee73bef21ab3c0740a37b33db62464babdeb" + integrity sha1-3hnuc77yGrPAdAo3sz22JGS6ves= set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -set-immediate-shim@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" +set-value@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" + integrity sha1-fbCPnT0i3H945Trzw79GZuzfzPE= + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.1" + to-object-path "^0.3.0" + +set-value@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" + integrity sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - -setprototypeof@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.10" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.10.tgz#b1fde5cd7d11a5626638a07c604ab909cfa31f9b" + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" @@ -6200,16 +7797,19 @@ sha.js@^2.4.0, sha.js@^2.4.8: 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= shell-quote@1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" + integrity sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c= dependencies: array-filter "~0.0.0" array-map "~0.0.0" @@ -6219,40 +7819,78 @@ shell-quote@1.6.1: shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= slice-ansi@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg== dependencies: is-fullwidth-code-point "^2.0.0" -slide@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" sntp@1.x.x: version "1.0.9" resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + integrity sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg= dependencies: hoek "2.x.x" -sntp@2.x.x: - version "2.1.0" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" - dependencies: - hoek "4.x.x" - sockjs-client@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12" + integrity sha1-W6vjhrd15M8U51IJEUUmVAFsixI= + dependencies: + debug "^2.6.6" + eventsource "0.1.6" + faye-websocket "~0.11.0" + inherits "^2.0.1" + json3 "^3.3.2" + url-parse "^1.1.8" + +sockjs-client@1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.5.tgz#1bb7c0f7222c40f42adf14f4442cbd1269771a83" + integrity sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM= dependencies: debug "^2.6.6" eventsource "0.1.6" @@ -6264,6 +7902,7 @@ sockjs-client@1.1.4: sockjs@0.3.18: version "0.3.18" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.18.tgz#d9b289316ca7df77595ef299e075f0f937eb4207" + integrity sha1-2bKJMWyn33dZXvKZ4HXw+TfrQgc= dependencies: faye-websocket "^0.10.0" uuid "^2.0.2" @@ -6271,50 +7910,78 @@ sockjs@0.3.18: sort-keys@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= dependencies: is-plain-obj "^1.0.0" source-list-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + +source-map-resolve@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== + dependencies: + atob "^2.1.1" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" source-map-support@^0.4.15: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== dependencies: source-map "^0.5.6" -source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.6: +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -source-map@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - dependencies: - amdefine ">=0.0.4" - -source-map@^0.6.1, source-map@~0.6.1: +source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -spdx-correct@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" +spdx-correct@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.2.tgz#19bb409e91b47b1ad54159243f7312a858db3c2e" + integrity sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ== dependencies: - spdx-license-ids "^1.0.2" + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" -spdx-expression-parse@~1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== -spdx-license-ids@^1.0.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz#a59efc09784c2a5bada13cfeaf5c75dd214044d2" + integrity sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg== spdy-transport@^2.0.18: - version "2.0.20" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.0.20.tgz#735e72054c486b2354fe89e702256004a39ace4d" + version "2.1.1" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.1.1.tgz#c54815d73858aadd06ce63001e7d25fa6441623b" + integrity sha512-q7D8c148escoB3Z7ySCASadkegMmUZW8Wb/Q1u0/XBgDKMO880rLQDj8Twiew/tYi7ghemKUi/whSYOwE17f5Q== dependencies: debug "^2.6.8" detect-node "^2.0.3" @@ -6327,6 +7994,7 @@ spdy-transport@^2.0.18: spdy@^3.4.1: version "3.4.7" resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc" + integrity sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw= dependencies: debug "^2.6.8" handle-thing "^1.2.5" @@ -6335,192 +8003,240 @@ spdy@^3.4.1: select-hose "^2.0.0" spdy-transport "^2.0.18" +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + 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= sshpk@^1.7.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + version "1.15.2" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.2.tgz#c946d6bd9b1a39d0e8635763f5242d6ed6dcb629" + integrity sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" - dashdash "^1.12.0" - getpass "^0.1.1" - optionalDependencies: 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" -"statuses@>= 1.3.1 < 2": +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +statuses@~1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" - -statuses@~1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" + integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== stream-browserify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" + integrity sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds= dependencies: inherits "~2.0.1" readable-stream "^2.0.2" stream-http@^2.7.2: - version "2.8.0" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.0.tgz#fd86546dac9b1c91aff8fc5d287b98fafb41bc10" + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== dependencies: builtin-status-codes "^3.0.0" inherits "^2.0.1" - readable-stream "^2.3.3" + readable-stream "^2.3.6" to-arraybuffer "^1.0.0" xtend "^4.0.0" stream-json@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/stream-json/-/stream-json-0.5.2.tgz#f4256c0ef1a905f2ef2d473706b4b3ff827653cf" + version "0.5.3" + resolved "https://registry.yarnpkg.com/stream-json/-/stream-json-0.5.3.tgz#119e466c6966ffc63989848184377d2a07c600c8" + integrity sha512-zk8rHH33cfpuvxmwvQjQgRon2srjZUVzbulvB2CYfehcSGGF1jrLQZwEGm3jx0LnIZdJmKtal/qE3p+uZXynIg== dependencies: parser-toolkit ">=0.0.3" strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= string-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/string-length/-/string-length-1.0.1.tgz#56970fb1c38558e9e70b728bf3de269ac45adfac" + integrity sha1-VpcPscOFWOnnC3KL894mmsRa36w= dependencies: strip-ansi "^3.0.0" string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= dependencies: code-point-at "^1.0.0" is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== dependencies: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string_decoder@^1.0.0, string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" +string_decoder@^1.0.0, string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= -stringstream@~0.0.4, stringstream@~0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" +stringstream@~0.0.4: + version "0.0.6" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" + integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== strip-ansi@3.0.1, strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= dependencies: ansi-regex "^2.0.0" strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= dependencies: ansi-regex "^3.0.0" strip-bom@3.0.0, strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= dependencies: is-utf8 "^0.2.0" strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= strip-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= dependencies: get-stdin "^4.0.1" strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= style-loader@0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.19.0.tgz#7258e788f0fee6a42d710eaf7d6c2412a4c50759" + integrity sha512-9mx9sC9nX1dgP96MZOODpGC6l1RzQBITI2D5WJhu+wnbrSYVKLGuy14XJSLVQih/0GFrPpjelt+s//VcZQ2Evw== dependencies: loader-utils "^1.0.2" schema-utils "^0.3.0" styled-components@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.0.2.tgz#dbcd66ee84d444ee4332a7f74027e8a675191593" + version "3.4.10" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.4.10.tgz#9a654c50ea2b516c36ade57ddcfa296bf85c96e1" + integrity sha512-TA8ip8LoILgmSAFd3r326pKtXytUUGu5YWuqZcOQVwVVwB6XqUMn4MHW2IuYJ/HAD81jLrdQed8YWfLSG1LX4Q== dependencies: buffer "^5.0.3" css-to-react-native "^2.0.3" - fbjs "^0.8.9" - hoist-non-react-statics "^1.2.0" - is-plain-object "^2.0.1" + fbjs "^0.8.16" + hoist-non-react-statics "^2.5.0" prop-types "^15.5.4" - stylis "^3.4.0" + react-is "^16.3.1" + stylis "^3.5.0" + stylis-rule-sheet "^0.0.10" supports-color "^3.2.3" -stylis@^3.4.0: - version "3.4.8" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.4.8.tgz#94380babbcd4c75726215794ca985b38ec96d1a3" +stylis-rule-sheet@^0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430" + integrity sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw== + +stylis@^3.5.0: + version "3.5.4" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" + integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q== subscriptions-transport-ws@^0.9.5: - version "0.9.5" - resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.5.tgz#faa9eb1230d5f2efe355368cd973b867e4483c53" + version "0.9.15" + resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.15.tgz#68a8b7ba0037d8c489fb2f5a102d1494db297d0d" + integrity sha512-f9eBfWdHsePQV67QIX+VRhf++dn1adyC/PZHP6XI5AfKnZ4n0FW+v5omxwdHVpd4xq2ZijaHEcmlQrhBY79ZWQ== dependencies: backo2 "^1.0.2" - eventemitter3 "^2.0.3" - iterall "^1.1.1" - lodash.assign "^4.2.0" - lodash.isobject "^3.0.2" - lodash.isstring "^4.0.1" + eventemitter3 "^3.1.0" + iterall "^1.2.1" symbol-observable "^1.0.4" - ws "^3.0.0" + ws "^5.2.0" supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= supports-color@^3.1.2, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= dependencies: has-flag "^1.0.0" -supports-color@^4.0.0, supports-color@^4.2.1: +supports-color@^4.2.1: version "4.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" + integrity sha1-vnoN5ITexcXN34s9WRJQRJEvY1s= dependencies: has-flag "^2.0.0" -supports-color@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.1.0.tgz#058a021d1b619f7ddf3980d712ea3590ce7de3d5" +supports-color@^5.3.0, supports-color@^5.4.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 "^2.0.0" + has-flag "^3.0.0" svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" + integrity sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U= dependencies: coa "~1.0.1" colors "~1.1.2" @@ -6533,14 +8249,16 @@ svgo@^0.7.0: sw-precache-webpack-plugin@0.11.4: version "0.11.4" resolved "https://registry.yarnpkg.com/sw-precache-webpack-plugin/-/sw-precache-webpack-plugin-0.11.4.tgz#a695017e54eed575551493a519dc1da8da2dc5e0" + integrity sha1-ppUBflTu1XVVFJOlGdwdqNotxeA= dependencies: del "^2.2.2" sw-precache "^5.1.1" uglify-js "^3.0.13" sw-precache@^5.1.1: - version "5.2.0" - resolved "https://registry.yarnpkg.com/sw-precache/-/sw-precache-5.2.0.tgz#eb6225ce580ceaae148194578a0ad01ab7ea199c" + version "5.2.1" + resolved "https://registry.yarnpkg.com/sw-precache/-/sw-precache-5.2.1.tgz#06134f319eec68f3b9583ce9a7036b1c119f7179" + integrity sha512-8FAy+BP/FXE+ILfiVTt+GQJ6UEf4CVHD9OfhzH0JX+3zoy2uFk7Vn9EfXASOtVmmIVbL3jE/W8Z66VgPSZcMhw== dependencies: dom-urls "^1.1.0" es6-promise "^4.0.5" @@ -6551,29 +8269,33 @@ sw-precache@^5.1.1: mkdirp "^0.5.1" pretty-bytes "^4.0.2" sw-toolbox "^3.4.0" - update-notifier "^1.0.3" + update-notifier "^2.3.0" sw-toolbox@^3.4.0: version "3.6.0" resolved "https://registry.yarnpkg.com/sw-toolbox/-/sw-toolbox-3.6.0.tgz#26df1d1c70348658e4dea2884319149b7b3183b5" + integrity sha1-Jt8dHHA0hljk3qKIQxkUm3sxg7U= dependencies: path-to-regexp "^1.0.1" serviceworker-cache-polyfill "^4.0.0" symbol-observable@^1.0.2, symbol-observable@^1.0.3, symbol-observable@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.1.0.tgz#5c68fd8d54115d9dfb72a84720549222e8db9b32" + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== symbol-tree@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" + integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY= table@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" + version "4.0.3" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc" + integrity sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg== dependencies: - ajv "^5.2.3" - ajv-keywords "^2.1.0" + ajv "^6.0.1" + ajv-keywords "^3.0.0" chalk "^2.1.0" lodash "^4.17.4" slice-ansi "1.0.0" @@ -6582,10 +8304,12 @@ table@^4.0.1: tapable@^0.2.7: version "0.2.8" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" + integrity sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI= tar-pack@^3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" + integrity sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg== dependencies: debug "^2.2.0" fstream "^1.0.10" @@ -6599,14 +8323,36 @@ tar-pack@^3.4.0: tar@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE= dependencies: block-stream "*" fstream "^1.0.2" inherits "2" -test-exclude@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26" +tar@^4: + version "4.4.8" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" + integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ== + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.3.4" + minizlib "^1.1.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.2" + +term-size@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" + integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= + dependencies: + execa "^0.7.0" + +test-exclude@^4.2.1: + version "4.2.3" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.3.tgz#a9a5e64474e4398339245a0a769ad7c2f4a97c20" + integrity sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA== dependencies: arrify "^1.0.1" micromatch "^2.3.11" @@ -6617,118 +8363,176 @@ test-exclude@^4.1.1: text-table@0.2.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= throat@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/throat/-/throat-3.2.0.tgz#50cb0670edbc40237b9e347d7e1f88e4620af836" + integrity sha512-/EY8VpvlqJ+sFtLPeOgc8Pl7kQVOWv0woD87KTXVHPIAE842FGT+rokxIhe8xIUP1cfgrkt0as0vDLjDiMtr8w== 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= -thunky@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" +thunky@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826" + integrity sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow== time-stamp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357" + version "2.2.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.2.0.tgz#917e0a66905688790ec7bbbde04046259af83f57" + integrity sha512-zxke8goJQpBeEgD82CXABeMh0LSJcj7CXEd0OHOg45HgcofF7pxNwZm9+RknpxpDhwN4gFpySkApKfFYfRQnUA== -timed-out@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" +timed-out@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= timers-browserify@^2.0.4: - version "2.0.5" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.5.tgz#04878fb12a155a159c9d1e59faa1f77bf4ecc44c" + version "2.0.10" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" + integrity sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg== dependencies: setimmediate "^1.0.4" 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" tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" toposort@^1.0.0: - version "1.0.6" - resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.6.tgz#c31748e55d210effc00fdcdc7d6e68d7d7bb9cec" + version "1.0.7" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" + integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk= -tough-cookie@^2.3.2, tough-cookie@~2.3.0, tough-cookie@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" +tough-cookie@^2.3.2, tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +tough-cookie@~2.3.0: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA== dependencies: punycode "^1.4.1" tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= 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= 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-is@~1.6.15: - version "1.6.15" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" +type-is@~1.6.16: + version "1.6.16" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" + integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== dependencies: media-typer "0.3.0" - mime-types "~2.1.15" + mime-types "~2.1.18" typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -ua-parser-js@^0.7.9: - version "0.7.17" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" +ua-parser-js@^0.7.18: + version "0.7.19" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b" + integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ== -uglify-js@3.3.x, uglify-js@^3.0.13: - version "3.3.8" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.3.8.tgz#51e9a5db73afb53ac98603d08224edcd0be45fd8" +uglify-js@3.4.x, uglify-js@^3.0.13, uglify-js@^3.1.4: + version "3.4.9" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" + integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q== dependencies: - commander "~2.13.0" + commander "~2.17.1" source-map "~0.6.1" -uglify-js@^2.6, uglify-js@^2.8.29: +uglify-js@^2.8.29: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + integrity sha1-KcVzMUgFe7Th913zW3qcty5qWd0= dependencies: source-map "~0.5.1" yargs "~3.10.0" @@ -6738,10 +8542,12 @@ uglify-js@^2.6, uglify-js@^2.8.29: uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + integrity sha1-bgkk1r2mta/jSeOabWMoUKD4grc= uglifyjs-webpack-plugin@^0.4.6: version "0.4.6" resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" + integrity sha1-uVH0q7a9YX5m9j64kUmOORdj4wk= dependencies: source-map "^0.5.6" uglify-js "^2.8.29" @@ -6750,61 +8556,105 @@ uglifyjs-webpack-plugin@^0.4.6: uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" + integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE= -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" +union-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" + integrity sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ= + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^0.4.3" uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - -uniqid@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-4.1.1.tgz#89220ddf6b751ae52b5f72484863528596bb84c1" - dependencies: - macaddress "^0.2.8" + integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= uniqs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= + +unique-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" + integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= + dependencies: + crypto-random-string "^1.0.0" universalify@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= -unzip-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" -update-notifier@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-1.0.3.tgz#8f92c515482bd6831b7c93013e70f87552c7cf5a" +unzip-response@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" + integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= + +upath@^1.0.5: + version "1.1.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" + integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw== + +update-notifier@^2.3.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" + integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== dependencies: - boxen "^0.6.0" - chalk "^1.0.0" - configstore "^2.0.0" + boxen "^1.2.1" + chalk "^2.0.1" + configstore "^3.0.0" + import-lazy "^2.1.0" + is-ci "^1.0.10" + is-installed-globally "^0.1.0" is-npm "^1.0.0" - latest-version "^2.0.0" - lazy-req "^1.1.0" + latest-version "^3.0.0" semver-diff "^2.0.0" - xdg-basedir "^2.0.0" + xdg-basedir "^3.0.0" upper-case@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" + integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg= + +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" urijs@^1.16.1: - version "1.19.0" - resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.0.tgz#d8aa284d0e7469703a6988ad045c4cbfdf08ada0" + version "1.19.1" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.1.tgz#5b0ff530c0cbde8386f6342235ba5ca6e995d25a" + integrity sha512-xVrGVi94ueCJNrBSTjWqjvtgvl3cyOTThp2zaMaFNGp3F542TR6sM3f2o8RqZl+AwteClSVmoCyt0ka4RjQOQg== + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= url-loader@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.6.2.tgz#a007a7109620e9d988d14bce677a1decb9a993f7" + integrity sha512-h3qf9TNn53BpuXTTcpC+UehiRrl0Cv45Yr/xWayApjw6G8Bg2dGke7rIwDQ39piciWCWrC+WiqLjOh3SUp9n0Q== dependencies: loader-utils "^1.0.2" mime "^1.4.1" @@ -6813,82 +8663,97 @@ url-loader@0.6.2: url-parse-lax@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= dependencies: prepend-http "^1.0.1" -url-parse@1.0.x: - version "1.0.5" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b" +url-parse@^1.1.8, url-parse@^1.4.3: + version "1.4.4" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8" + integrity sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg== dependencies: - querystringify "0.0.x" - requires-port "1.0.x" - -url-parse@^1.1.8: - version "1.2.0" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986" - dependencies: - querystringify "~1.0.0" - requires-port "~1.0.0" + querystringify "^2.0.0" + requires-port "^1.0.0" url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= dependencies: punycode "1.3.2" querystring "0.2.0" +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util@0.10.3, util@^0.10.3: +util@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= dependencies: inherits "2.0.1" -utila@~0.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/utila/-/utila-0.3.3.tgz#d7e8e7d7e309107092b05f8d9688824d633a4226" +util@^0.10.3: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== + dependencies: + inherits "2.0.3" -utila@~0.4: +utila@^0.4.0, utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^2.0.1, uuid@^2.0.2: +uuid@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" + integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho= -uuid@^3.0.0, uuid@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" +uuid@^3.0.0, uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== validate-npm-package-license@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== dependencies: - spdx-correct "~1.0.0" - spdx-expression-parse "~1.0.0" + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" value-equal@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" + integrity sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw== vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= vendors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" + version "1.0.2" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.2.tgz#7fcb5eef9f5623b156bcea89ec37d63676f21801" + integrity sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ== 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" @@ -6897,50 +8762,66 @@ verror@1.10.0: vm-browserify@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" + integrity sha1-XX6kW7755Kb/ZflUOOCofDV9WnM= dependencies: indexof "0.0.1" walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= dependencies: makeerror "1.0.x" warning@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" + integrity sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w= + dependencies: + loose-envify "^1.0.0" + +warning@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607" + integrity sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug== dependencies: loose-envify "^1.0.0" watch@~0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/watch/-/watch-0.10.0.tgz#77798b2da0f9910d595f1ace5b0c2258521f21dc" + integrity sha1-d3mLLaD5kQ1ZXxrOWwwiWFIfIdw= watchpack@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" + version "1.6.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" + integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== dependencies: - async "^2.1.2" - chokidar "^1.7.0" + chokidar "^2.0.2" graceful-fs "^4.1.2" + neo-async "^2.5.0" wbuf@^1.1.0, wbuf@^1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.2.tgz#d697b99f1f59512df2751be42769c1580b5801fe" + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== dependencies: minimalistic-assert "^1.0.0" webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= webidl-conversions@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== webpack-dev-middleware@^1.11.0: version "1.12.2" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e" + integrity sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A== dependencies: memory-fs "~0.4.1" mime "^1.5.0" @@ -6951,6 +8832,7 @@ webpack-dev-middleware@^1.11.0: webpack-dev-server@2.9.4: version "2.9.4" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.9.4.tgz#7883e61759c6a4b33e9b19ec4037bd4ab61428d1" + integrity sha512-thrqC0EQEoSjXeYgP6pUXcUCZ+LNrKsDPn+mItLnn5VyyNZOJKd06hUP5vqkYwL8nWWXsii0loSF9NHNccT6ow== dependencies: ansi-html "0.0.7" array-includes "^3.0.3" @@ -6983,13 +8865,15 @@ webpack-dev-server@2.9.4: webpack-manifest-plugin@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-1.3.2.tgz#5ea8ee5756359ddc1d98814324fe43496349a7d4" + integrity sha512-MX60Bv2G83Zks9pi3oLOmRgnPAnwrlMn+lftMrWBm199VQjk46/xgzBi9lPfpZldw2+EI2S+OevuLIaDuxCWRw== dependencies: fs-extra "^0.30.0" lodash ">=3.5 <5" webpack-sources@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54" + version "1.3.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" + integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA== dependencies: source-list-map "^2.0.0" source-map "~0.6.1" @@ -6997,6 +8881,7 @@ webpack-sources@^1.0.1: webpack@3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.8.1.tgz#b16968a81100abe61608b0153c9159ef8bb2bd83" + integrity sha512-5ZXLWWsMqHKFr5y0N3Eo5IIisxeEeRAajNq4mELb/WELOR7srdbQk2N5XiyNy2A/AgvlR3AmeBCZJW8lHrolbw== dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" @@ -7024,6 +8909,7 @@ webpack@3.8.1: websocket-driver@>=0.5.1: version "0.7.0" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" + integrity sha1-DK+dLXVdk67gSdS90NP+LMoqJOs= dependencies: http-parser-js ">=0.4.0" websocket-extensions ">=0.1.1" @@ -7031,20 +8917,29 @@ websocket-driver@>=0.5.1: websocket-extensions@>=0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" + integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg== whatwg-encoding@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz#57c235bc8657e914d24e1a397d3c82daee0a6ba3" + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== dependencies: - iconv-lite "0.4.19" + iconv-lite "0.4.24" -whatwg-fetch@2.0.3, whatwg-fetch@>=0.10.0: +whatwg-fetch@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" + integrity sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ= + +whatwg-fetch@>=0.10.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" + integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== whatwg-url@^4.3.0: version "4.8.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0" + integrity sha1-0pgaqRSMHgCkHFphMRZqtGg7vMA= dependencies: tr46 "~0.0.3" webidl-conversions "^3.0.0" @@ -7052,59 +8947,70 @@ whatwg-url@^4.3.0: whet.extend@~0.9.9: version "0.9.9" resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" + integrity sha1-+HfVv2SMl+WqVC+twW1qJZucEaE= which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + 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" wide-align@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== dependencies: - string-width "^1.0.2" + string-width "^1.0.2 || 2" -widest-line@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-1.0.0.tgz#0c09c85c2a94683d0d7eaf8ee097d564bf0e105c" +widest-line@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" + integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== dependencies: - string-width "^1.0.1" + string-width "^2.1.1" window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0= 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@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= 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.3.1: - version "1.5.2" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.2.tgz#32b312e5dc3d5d45d79ef44acc2587491cd729ae" + version "1.6.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0" + integrity sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ== dependencies: - errno "^0.1.4" - xtend "^4.0.1" + errno "~0.1.7" wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= dependencies: string-width "^1.0.1" strip-ansi "^3.0.1" @@ -7112,76 +9018,86 @@ wrap-ansi@^2.0.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write-file-atomic@^1.1.2: - version "1.3.4" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f" +write-file-atomic@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab" + integrity sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA== dependencies: graceful-fs "^4.1.11" imurmurhash "^0.1.4" - slide "^1.1.5" + signal-exit "^3.0.2" write@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= dependencies: mkdirp "^0.5.1" -ws@^3.0.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" +ws@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== dependencies: async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" -xdg-basedir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2" - dependencies: - os-homedir "^1.0.0" - -xml-char-classes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d" +xdg-basedir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" + integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= xml-name-validator@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" + integrity sha1-TYuPHszTQZqjYgYb7O9RXh5VljU= -xtend@^4.0.0, xtend@^4.0.1: +xtend@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + +yallist@^3.0.0, yallist@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" + integrity sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k= yargs-parser@^4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" + integrity sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw= dependencies: camelcase "^3.0.0" yargs-parser@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" + integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo= dependencies: camelcase "^3.0.0" yargs-parser@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" + integrity sha1-jQrELxbqVd69MyyvTEA4s+P139k= dependencies: camelcase "^4.1.0" yargs@^6.6.0: version "6.6.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" + integrity sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg= dependencies: camelcase "^3.0.0" cliui "^3.2.0" @@ -7200,6 +9116,7 @@ yargs@^6.6.0: yargs@^7.0.2: version "7.1.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" + integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= dependencies: camelcase "^3.0.0" cliui "^3.2.0" @@ -7218,6 +9135,7 @@ yargs@^7.0.2: yargs@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" + integrity sha1-YpmpBVsc78lp/355wdkY3Osiw2A= dependencies: camelcase "^4.1.0" cliui "^3.2.0" @@ -7236,6 +9154,7 @@ yargs@^8.0.2: yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + integrity sha1-9+572FfdfB0tOMDnTvvWgdFDH9E= dependencies: camelcase "^1.0.2" cliui "^2.1.0" @@ -7243,13 +9162,18 @@ yargs@~3.10.0: window-size "0.1.0" yarn@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.3.2.tgz#5939762581b5b4ddcd3418c0f6be42df3aee195f" + version "1.12.3" + resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.12.3.tgz#fb4599bf1f8da01552bcc7e1571dfd4e53788203" + integrity sha512-8f5rWNDvkhAmCxmn8C0LsNWMxTYVk4VGKiq0sIB6HGZjaZTHsGIH87SUmVDUEd2Wk54bqKoUlbVWgQFCQhRkVw== -zen-observable@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.6.1.tgz#01dbed3bc8d02cbe9ee1112c83e04c807f647244" +zen-observable-ts@^0.8.10: + version "0.8.10" + resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.10.tgz#18e2ce1c89fe026e9621fd83cc05168228fce829" + integrity sha512-5vqMtRggU/2GhePC9OU4sYEWOdvmayp2k3gjPf4F0mXwB3CSbbNznfDUvDJx9O2ZTa1EIXdJhPchQveFKwNXPQ== + dependencies: + zen-observable "^0.8.0" -zen-observable@^0.7.0: - version "0.7.1" - resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.7.1.tgz#f84075c0ee085594d3566e1d6454207f126411b3" +zen-observable@^0.8.0: + version "0.8.11" + resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.11.tgz#d3415885eeeb42ee5abb9821c95bb518fcd6d199" + integrity sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ== diff --git a/analytics/index.js b/analytics/index.js new file mode 100644 index 0000000000..7955d6da65 --- /dev/null +++ b/analytics/index.js @@ -0,0 +1,35 @@ +// @flow +const debug = require('debug')('analytics'); +import createWorker from '../shared/bull/create-worker'; + +import trackAnalytics from './queues/track-analytics'; +import identifyAnalytics from './queues/identify-analytics'; + +import { TRACK_ANALYTICS, IDENTIFY_ANALYTICS } from './queues/constants'; + +const PORT = process.env.PORT || 3009; + +debug('\n📈 Analytics worker is starting...'); +debug('Logging with debug enabled!'); +debug(''); + +const server = createWorker({ + [TRACK_ANALYTICS]: trackAnalytics, + [IDENTIFY_ANALYTICS]: identifyAnalytics, +}); + +debug( + `🗄 Queues open for business ${(process.env.NODE_ENV === 'production' && + // $FlowIssue + `at ${process.env.COMPOSE_REDIS_URL}:${process.env.COMPOSE_REDIS_PORT}`) || + 'locally'}` +); + +// $FlowIssue +server.listen(PORT, 'localhost', () => { + debug( + `💉 Healthcheck server running at ${server.address().address}:${ + server.address().port + }` + ); +}); diff --git a/analytics/models/channel.js b/analytics/models/channel.js new file mode 100644 index 0000000000..d01033b51d --- /dev/null +++ b/analytics/models/channel.js @@ -0,0 +1,10 @@ +// @flow +import type { DBChannel } from 'shared/types'; +import { db } from 'shared/db'; + +export const getChannelById = (channelId: string): Promise => { + return db + .table('channels') + .get(channelId) + .run(); +}; diff --git a/analytics/models/community.js b/analytics/models/community.js new file mode 100644 index 0000000000..8e635e411d --- /dev/null +++ b/analytics/models/community.js @@ -0,0 +1,10 @@ +// @flow +import type { DBCommunity } from 'shared/types'; +import { db } from 'shared/db'; + +export const getCommunityById = (communityId: string): Promise => { + return db + .table('communities') + .get(communityId) + .run(); +}; diff --git a/analytics/models/message.js b/analytics/models/message.js new file mode 100644 index 0000000000..ce1b77d6cc --- /dev/null +++ b/analytics/models/message.js @@ -0,0 +1,10 @@ +// @flow +import type { DBMessage } from 'shared/types'; +import { db } from 'shared/db'; + +export const getMessageById = (messageId: string): Promise => { + return db + .table('messages') + .get(messageId) + .run(); +}; diff --git a/analytics/models/notification.js b/analytics/models/notification.js new file mode 100644 index 0000000000..a3ded7904a --- /dev/null +++ b/analytics/models/notification.js @@ -0,0 +1,12 @@ +// @flow +import type { DBNotification } from 'shared/types'; +import { db } from 'shared/db'; + +export const getNotificationById = ( + notificationId: string +): Promise => { + return db + .table('notifications') + .get(notificationId) + .run(); +}; diff --git a/analytics/models/reaction.js b/analytics/models/reaction.js new file mode 100644 index 0000000000..2cb72bed53 --- /dev/null +++ b/analytics/models/reaction.js @@ -0,0 +1,18 @@ +// @flow +import type { DBReaction, DBThreadReaction } from 'shared/types'; +import { db } from 'shared/db'; + +export const getReactionById = (reactionId: string): Promise => { + return db + .table('reactions') + .get(reactionId) + .run(); +}; + +// prettier-ignore +export const getThreadReactionById = (reactionId: string): Promise => { + return db + .table('threadReactions') + .get(reactionId) + .run(); +}; diff --git a/analytics/models/thread.js b/analytics/models/thread.js new file mode 100644 index 0000000000..38cc1cb06b --- /dev/null +++ b/analytics/models/thread.js @@ -0,0 +1,10 @@ +// @flow +import type { DBThread } from 'shared/types'; +import { db } from 'shared/db'; + +export const getThreadById = (threadId: string): Promise => { + return db + .table('threads') + .get(threadId) + .run(); +}; diff --git a/analytics/models/usersChannels.js b/analytics/models/usersChannels.js new file mode 100644 index 0000000000..b232f51fec --- /dev/null +++ b/analytics/models/usersChannels.js @@ -0,0 +1,26 @@ +// @flow +import type { DBUsersChannels } from 'shared/types'; +import { db } from 'shared/db'; + +const defaultResult = { + isMember: false, + isOwner: false, + isModerator: false, + isBlocked: false, + isPending: false, + reputation: 0, +}; + +export const getUserPermissionsInChannel = ( + userId: string, + channelId: string +): Promise => { + return db + .table('usersChannels') + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) + .run() + .then(results => { + if (!results || results.length === 0) return defaultResult; + return results[0]; + }); +}; diff --git a/analytics/models/usersCommunities.js b/analytics/models/usersCommunities.js new file mode 100644 index 0000000000..466e6aaa81 --- /dev/null +++ b/analytics/models/usersCommunities.js @@ -0,0 +1,25 @@ +// @flow +import type { DBUsersCommunities } from 'shared/types'; +import { db } from 'shared/db'; + +const defaultResult = { + isMember: false, + isOwner: false, + isModerator: false, + isBlocked: false, + reputation: 0, +}; + +export const getUserPermissionsInCommunity = ( + userId: string, + communityId: string +): Promise => { + return db + .table('usersCommunities') + .getAll([userId, communityId], { index: 'userIdAndCommunityId' }) + .run() + .then(results => { + if (!results || results.length === 0) return defaultResult; + return results[0]; + }); +}; diff --git a/analytics/models/usersThreads.js b/analytics/models/usersThreads.js new file mode 100644 index 0000000000..73510a195d --- /dev/null +++ b/analytics/models/usersThreads.js @@ -0,0 +1,17 @@ +// @flow +import type { DBUsersThreads } from 'shared/types'; +import { db } from 'shared/db'; + +export const getThreadNotificationStatusForUser = ( + threadId: string, + userId: string +): Promise => { + return db + .table('usersThreads') + .getAll([userId, threadId], { index: 'userIdAndThreadId' }) + .run() + .then(results => { + if (!results || results.length === 0) return null; + return results[0]; + }); +}; diff --git a/analytics/package.json b/analytics/package.json new file mode 100644 index 0000000000..b1d5c4cf32 --- /dev/null +++ b/analytics/package.json @@ -0,0 +1,29 @@ +{ + "name": "analytics", + "version": "1.0.0", + "scripts": { + "start": "NODE_ENV=production node main.js" + }, + "dependencies": { + "amplitude": "^3.5.0", + "aws-sdk": "^2.395.0", + "bull": "3.3.10", + "datadog-metrics": "^0.8.1", + "debug": "^4.1.1", + "faker": "^4.1.0", + "ioredis": "3.2.2", + "lodash.intersection": "^4.4.0", + "node-env-file": "^0.1.8", + "now-env": "^3.1.0", + "performance-now": "^2.1.0", + "raven": "^2.6.4", + "redis-tag-cache": "^1.2.1", + "rethinkdb-inspector": "^0.3.3", + "rethinkdbdash": "^2.3.31", + "rethinkhaberdashery": "^2.3.32", + "sanitize-filename": "^1.6.1", + "sha1": "^1.1.1", + "source-map-support": "^0.5.10", + "toobusy-js": "^0.5.1" + } +} diff --git a/analytics/queues/constants.js b/analytics/queues/constants.js new file mode 100644 index 0000000000..179d7676c6 --- /dev/null +++ b/analytics/queues/constants.js @@ -0,0 +1,3 @@ +// @flow +export const TRACK_ANALYTICS = 'track analytics'; +export const IDENTIFY_ANALYTICS = 'identify analytics'; diff --git a/analytics/queues/identify-analytics.js b/analytics/queues/identify-analytics.js new file mode 100644 index 0000000000..a50689bb11 --- /dev/null +++ b/analytics/queues/identify-analytics.js @@ -0,0 +1,29 @@ +// @flow +const debug = require('debug')('analytics:queues:identify'); +import Raven from 'shared/raven'; +import type { Job, IdentifyAnalyticsData } from 'shared/bull/types'; +import { getUserById } from 'shared/db/queries/user'; +import { identify, transformations } from '../utils'; + +const processJob = async (job: Job) => { + const { userId } = job.data; + + if (!userId) return; + + const user = await getUserById(userId); + + if (!user) return; + + const analyticsUser = transformations.analyticsUser(user); + return await identify(userId, analyticsUser); +}; + +export default async (job: Job) => { + try { + await processJob(job); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + } +}; diff --git a/analytics/queues/track-analytics.js b/analytics/queues/track-analytics.js new file mode 100644 index 0000000000..94b92fbd2a --- /dev/null +++ b/analytics/queues/track-analytics.js @@ -0,0 +1,32 @@ +// @flow +const debug = require('debug')('analytics:queues:track'); +import Raven from 'shared/raven'; +import type { Job, TrackAnalyticsData } from 'shared/bull/types'; +import { getContext, track } from '../utils'; + +const processJob = async (job: Job) => { + const { userId, event, context, properties = {} } = job.data; + + debug(`Incoming job: ${event}`); + + if (!context) { + return track(userId, event, { ...properties }); + } + + const contextProperties = await getContext({ userId, ...context }); + + return await track(userId, event, { + ...contextProperties, + ...properties, + }); +}; + +export default async (job: Job) => { + try { + await processJob(job); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + } +}; diff --git a/analytics/utils/amplitude.js b/analytics/utils/amplitude.js new file mode 100644 index 0000000000..76e71cfc5d --- /dev/null +++ b/analytics/utils/amplitude.js @@ -0,0 +1,15 @@ +// @flow +require('now-env'); +const Amplitude = require('amplitude'); + +const IS_PROD = process.env.NODE_ENV === 'production'; + +const AMPLITUDE_API_KEY = IS_PROD + ? process.env.AMPLITUDE_API_KEY + : process.env.AMPLITUDE_API_KEY_DEVELOPMENT; + +if (!AMPLITUDE_API_KEY) { + console.warn('No amplitude api key provided'); +} + +export const amplitude = new Amplitude(AMPLITUDE_API_KEY); diff --git a/analytics/utils/getContext.js b/analytics/utils/getContext.js new file mode 100644 index 0000000000..db661487d5 --- /dev/null +++ b/analytics/utils/getContext.js @@ -0,0 +1,254 @@ +// @flow +import type { DBThreadReaction } from 'shared/types'; +import { transformations } from './index'; +import { getThreadNotificationStatusForUser } from '../models/usersThreads'; +import { getReactionById, getThreadReactionById } from '../models/reaction'; +import { getMessageById } from '../models/message'; +import { getThreadById } from '../models/thread'; +import { getChannelById } from '../models/channel'; +import { getCommunityById } from '../models/community'; +import { getUserPermissionsInChannel } from '../models/usersChannels'; +import { getUserPermissionsInCommunity } from '../models/usersCommunities'; +import { getNotificationById } from '../models/notification'; +/* + + Given an entityId, return structured data to track in internal analytics + E.g. given a reactionId, return the reaction, message, thread, channel, and community + where the reaction was left + + E.g. given a threadId, return the thread, channel and community + +*/ +type EntityObjType = { + reactionId?: string, + threadReactionId?: string, + messageId?: string, + threadId?: string, + channelId?: string, + communityId?: string, + notificationId?: string, + userId: string, +}; + +export const getContext = async (obj: EntityObjType) => { + if (obj.reactionId) { + const reaction = await getReactionById(obj.reactionId); + const message = await getMessageById(reaction.messageId); + const thread = await getThreadById(message.threadId); + + // if no thread was found, we are in a dm + if (!thread) { + return { + reaction: transformations.analyticsReaction(reaction), + message: transformations.analyticsMessage(message), + }; + } + + const [ + channel, + community, + channelPermissions, + communityPermissions, + threadPermissions, + ] = await Promise.all([ + getChannelById(thread.channelId), + getCommunityById(thread.communityId), + getUserPermissionsInChannel(obj.userId, thread.channelId), + getUserPermissionsInCommunity(obj.userId, thread.communityId), + getThreadNotificationStatusForUser(thread.id, obj.userId), + ]); + + return { + reaction: transformations.analyticsReaction(reaction), + message: transformations.analyticsMessage(message), + thread: { + ...transformations.analyticsThread(thread), + ...transformations.analyticsThreadPermissions(threadPermissions), + isPinned: community.pinnedThreadId + ? community.pinnedThreadId === thread.id + : false, + }, + channel: { + ...transformations.analyticsChannel(channel), + ...transformations.analyticsChannelPermissions(channelPermissions), + }, + community: { + ...transformations.analyticsCommunity(community), + ...transformations.analyticsCommunityPermissions(communityPermissions), + }, + }; + } + + if (obj.threadReactionId) { + const threadReaction: DBThreadReaction = await getThreadReactionById( + obj.threadReactionId + ); + const thread = await getThreadById(threadReaction.threadId); + + // if no thread was found, we are in a dm + if (!thread) { + return {}; + } + + const [ + channel, + community, + channelPermissions, + communityPermissions, + threadPermissions, + ] = await Promise.all([ + getChannelById(thread.channelId), + getCommunityById(thread.communityId), + getUserPermissionsInChannel(obj.userId, thread.channelId), + getUserPermissionsInCommunity(obj.userId, thread.communityId), + getThreadNotificationStatusForUser(thread.id, obj.userId), + ]); + + return { + threadReaction: transformations.analyticsThreadReaction(threadReaction), + thread: { + ...transformations.analyticsThread(thread), + ...transformations.analyticsThreadPermissions(threadPermissions), + isPinned: community.pinnedThreadId + ? community.pinnedThreadId === thread.id + : false, + }, + channel: { + ...transformations.analyticsChannel(channel), + ...transformations.analyticsChannelPermissions(channelPermissions), + }, + community: { + ...transformations.analyticsCommunity(community), + ...transformations.analyticsCommunityPermissions(communityPermissions), + }, + }; + } + + if (obj.messageId) { + const message = await getMessageById(obj.messageId); + const thread = await getThreadById(message.threadId); + + // if no thread was found, we are in a dm + if (!thread) { + return { + message: transformations.analyticsMessage(message), + }; + } + + const [ + channel, + community, + channelPermissions, + communityPermissions, + threadPermissions, + ] = await Promise.all([ + getChannelById(thread.channelId), + getCommunityById(thread.communityId), + getUserPermissionsInChannel(obj.userId, thread.channelId), + getUserPermissionsInCommunity(obj.userId, thread.communityId), + getThreadNotificationStatusForUser(thread.id, obj.userId), + ]); + + return { + message: transformations.analyticsMessage(message), + thread: { + ...transformations.analyticsThread(thread), + ...transformations.analyticsThreadPermissions(threadPermissions), + isPinned: community.pinnedThreadId + ? community.pinnedThreadId === thread.id + : false, + }, + channel: { + ...transformations.analyticsChannel(channel), + ...transformations.analyticsChannelPermissions(channelPermissions), + }, + community: { + ...transformations.analyticsCommunity(community), + ...transformations.analyticsCommunityPermissions(communityPermissions), + }, + }; + } + + if (obj.threadId) { + const thread = await getThreadById(obj.threadId); + + const [ + channel, + community, + channelPermissions, + communityPermissions, + threadPermissions, + ] = await Promise.all([ + getChannelById(thread.channelId), + getCommunityById(thread.communityId), + getUserPermissionsInChannel(obj.userId, thread.channelId), + getUserPermissionsInCommunity(obj.userId, thread.communityId), + getThreadNotificationStatusForUser(thread.id, obj.userId), + ]); + + return { + thread: { + ...transformations.analyticsThread(thread), + ...transformations.analyticsThreadPermissions(threadPermissions), + isPinned: community.pinnedThreadId + ? community.pinnedThreadId === thread.id + : false, + }, + channel: { + ...transformations.analyticsChannel(channel), + ...transformations.analyticsChannelPermissions(channelPermissions), + }, + community: { + ...transformations.analyticsCommunity(community), + ...transformations.analyticsCommunityPermissions(communityPermissions), + }, + }; + } + + if (obj.channelId) { + const channel = await getChannelById(obj.channelId); + const [ + community, + channelPermissions, + communityPermissions, + ] = await Promise.all([ + getCommunityById(channel.communityId), + getUserPermissionsInChannel(obj.userId, channel.id), + getUserPermissionsInCommunity(obj.userId, channel.communityId), + ]); + + return { + channel: { + ...transformations.analyticsChannel(channel), + ...transformations.analyticsChannelPermissions(channelPermissions), + }, + community: { + ...transformations.analyticsCommunity(community), + ...transformations.analyticsCommunityPermissions(communityPermissions), + }, + }; + } + + if (obj.communityId) { + const communityId = obj.communityId; + + const [community, communityPermissions] = await Promise.all([ + getCommunityById(obj.communityId), + getUserPermissionsInCommunity(obj.userId, communityId), + ]); + return { + community: { + ...transformations.analyticsCommunity(community), + ...transformations.analyticsCommunityPermissions(communityPermissions), + }, + }; + } + + if (obj.notificationId) { + const notificationId = obj.notificationId; + const notification = await getNotificationById(notificationId); + return { + event: notification.event, + }; + } +}; diff --git a/analytics/utils/hash.js b/analytics/utils/hash.js new file mode 100644 index 0000000000..edb8837a2e --- /dev/null +++ b/analytics/utils/hash.js @@ -0,0 +1,4 @@ +// @flow +import sha1 from 'sha1'; + +export default (text: string): string => sha1(text); diff --git a/analytics/utils/identify.js b/analytics/utils/identify.js new file mode 100644 index 0000000000..9e32f1910b --- /dev/null +++ b/analytics/utils/identify.js @@ -0,0 +1,21 @@ +// @flow +const debug = require('debug')('analytics:identify'); +import Raven from 'shared/raven'; +import { amplitude } from './amplitude'; +import { hash } from './'; + +export const identify = (userId: string, userProperties: Object) => { + const amplitudePromise = () => { + debug(`[Amplitude] Identify ${userId}`); + return amplitude.identify({ + userId: hash(userId), + userProperties, + }); + }; + + return Promise.all([amplitudePromise()]).catch(err => { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + }); +}; diff --git a/analytics/utils/index.js b/analytics/utils/index.js new file mode 100644 index 0000000000..6253672b3b --- /dev/null +++ b/analytics/utils/index.js @@ -0,0 +1,9 @@ +// @flow +import { events } from 'shared/analytics'; +import * as transformations from './transformations'; +import { track } from './track'; +import { identify } from './identify'; +import hash from './hash'; +import { getContext } from './getContext'; + +export { hash, events, transformations, track, identify, getContext }; diff --git a/analytics/utils/track.js b/analytics/utils/track.js new file mode 100644 index 0000000000..74c75b9856 --- /dev/null +++ b/analytics/utils/track.js @@ -0,0 +1,39 @@ +// @flow +const debug = require('debug')('analytics:track'); +import Raven from 'shared/raven'; +import { amplitude } from './amplitude'; +import { hash } from './'; + +export const track = ( + userId: string, + eventType: string, + eventProperties: Object = {} +) => { + if (!userId) { + console.error('Undefined received as userId in tracking queue: ', { + userId, + eventType, + eventProperties, + }); + return; + } + + const amplitudePromise = () => { + debug(`[Amplitude] Tracking ${eventType}`); + + return amplitude.track({ + userId: hash(userId), + eventType, + eventProperties: { + ...eventProperties, + client: 'api', + }, + }); + }; + + return Promise.all([amplitudePromise()]).catch(err => { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + }); +}; diff --git a/analytics/utils/transformations.js b/analytics/utils/transformations.js new file mode 100644 index 0000000000..93753fbdf2 --- /dev/null +++ b/analytics/utils/transformations.js @@ -0,0 +1,244 @@ +// @flow +import type { + DBChannel, + DBUsersChannels, + DBCommunity, + DBUsersCommunities, + DBThread, + DBUser, + DBReaction, + DBThreadReaction, + DBMessage, + DBUsersThreads, +} from 'shared/types'; +import { getTruthyValuesFromObject } from 'shared/truthy-values'; + +type AnalyticsChannel = { + id: ?string, + name: ?string, + slug: ?string, + isPrivate: ?boolean, + isArchived: ?boolean, +}; + +type AnalyticsCommunity = { + id: ?string, + name: ?string, + slug: ?string, + isPrivate: boolean, +}; + +type AnalyticsChannelPermissions = { + roles: Array, +}; + +type AnalyticsCommunityPermissions = { + roles: Array, +}; + +type AnalyticsThread = { + id: ?string, + isLocked: ?boolean, + isWatercooler: ?boolean, +}; + +type AnalyticsThreadPermissions = { + isParticipant: ?boolean, + receiveNotifications: ?boolean, +}; + +type AnalyticsUser = { + createdAt: ?string, + twitterAuthed: boolean, + fbAuthed: boolean, + githubAuthed: boolean, + googleAuthed: boolean, + hasUsername: boolean, + lastSeen: ?string, +}; + +type AnalyticsReaction = { + id: ?string, + type: ?string, +}; + +type AnalyticsThreadReaction = { + id: ?string, + type: ?string, +}; + +type AnalyticsMessage = { + id: ?string, + threadType: ?string, + parentId: ?string, +}; + +export const analyticsReaction = (reaction: ?DBReaction): AnalyticsReaction => { + if (!reaction) { + return { + id: null, + type: null, + }; + } + + return { + id: reaction.id, + type: reaction.type, + }; +}; + +export const analyticsThreadReaction = ( + reaction: ?DBThreadReaction +): AnalyticsThreadReaction => { + if (!reaction) { + return { + id: null, + type: null, + }; + } + + return { + id: reaction.id, + type: reaction.type, + }; +}; + +export const analyticsMessage = (message: ?DBMessage): AnalyticsMessage => { + if (!message) { + return { + id: null, + threadType: null, + parentId: null, + }; + } + + return { + id: message.id, + threadType: message.threadType, + parentId: message.parentId ? message.parentId : null, + }; +}; + +export const analyticsChannel = (channel: ?DBChannel): AnalyticsChannel => { + if (!channel) { + return { + id: null, + name: null, + slug: null, + isPrivate: null, + isArchived: null, + }; + } + + return { + id: channel.id, + name: channel.name, + slug: channel.slug, + isPrivate: channel.isPrivate, + isArchived: channel.archivedAt ? true : false, + }; +}; + +export const analyticsChannelPermissions = ( + channelPermissions: ?DBUsersChannels +): AnalyticsChannelPermissions => { + if (!channelPermissions) + return { + roles: [], + }; + + return { + roles: getTruthyValuesFromObject(channelPermissions), + }; +}; + +export const analyticsCommunity = ( + community: DBCommunity +): AnalyticsCommunity => { + if (!community) { + return { + id: null, + name: null, + slug: null, + isPrivate: false, + }; + } + + return { + id: community.id, + name: community.name, + slug: community.slug, + isPrivate: community.isPrivate, + }; +}; + +export const analyticsCommunityPermissions = ( + communityPermissions: DBUsersCommunities +): AnalyticsCommunityPermissions => { + if (!communityPermissions) { + return { + roles: [], + reputation: 0, + }; + } + + return { + roles: getTruthyValuesFromObject(communityPermissions), + reputation: communityPermissions.reputation, + }; +}; + +export const analyticsThread = (thread: ?DBThread): AnalyticsThread => { + if (!thread) { + return { + id: null, + isLocked: null, + isWatercooler: null, + }; + } + + return { + id: thread.id, + isLocked: thread.isLocked, + isWatercooler: thread.watercooler ? true : false, + }; +}; + +export const analyticsThreadPermissions = ( + usersThread: ?DBUsersThreads +): AnalyticsThreadPermissions => { + if (!usersThread) { + return { + isParticipant: false, + receiveNotifications: false, + }; + } + + return { + isParticipant: usersThread.isParticipant, + receiveNotifications: usersThread.receiveNotifications, + }; +}; + +export const analyticsUser = (user: DBUser): AnalyticsUser => { + if (!user) { + return { + createdAt: null, + twitterAuthed: false, + fbAuthed: false, + githubAuthed: false, + googleAuthed: false, + hasUsername: false, + lastSeen: null, + }; + } + return { + createdAt: user.createdAt, + twitterAuthed: user.providerId ? true : false, + fbAuthed: user.fbProviderId ? true : false, + githubAuthed: user.githubProviderId ? true : false, + googleAuthed: user.googleProviderId ? true : false, + hasUsername: user.username ? true : false, + lastSeen: user.lastSeen ? user.lastSeen : null, + }; +}; diff --git a/analytics/yarn.lock b/analytics/yarn.lock new file mode 100644 index 0000000000..5bb33d1726 --- /dev/null +++ b/analytics/yarn.lock @@ -0,0 +1,732 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +amplitude@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/amplitude/-/amplitude-3.5.0.tgz#63edadbd9a0aef49467f66c11340fe9b371cd7c3" + integrity sha1-Y+2tvZoK70lGf2bBE0D+mzcc18M= + dependencies: + superagent "^3.3.1" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +aws-sdk@^2.395.0: + version "2.395.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.395.0.tgz#637e5fa06d69bfb923b17bde24a8bd2a74dedab3" + integrity sha512-ldTTjctniZT4E2lq2z3D8Y2u+vpkp+laoEnDkXgjKXTKbiJ0QEtfWsUdx/IQ7awCt8stoxyqZK47DJOxIbRNoA== + dependencies: + buffer "4.9.1" + events "1.1.1" + ieee754 "1.1.8" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + +base64-js@^1.0.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" + integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== + +bignumber.js@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-1.1.1.tgz#1a415d9ac014c13256af1feed9d1a3e5717a8cf7" + integrity sha1-GkFdmsAUwTJWrx/u2dGj5XF6jPc= + +"bluebird@>= 3.0.1", bluebird@^3.3.4, bluebird@^3.5.0: + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + +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== + +buffer@4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +bull@3.3.10: + version "3.3.10" + resolved "https://registry.yarnpkg.com/bull/-/bull-3.3.10.tgz#32e76281902070b4720bc37b3793f09db635f646" + integrity sha512-bO1w83BONVTE3Rb10e0wPf11lXH1fGFNGmZH4Ys9jR7jGN4qmTNo7odxm7ELhjKXofjiFLWZFuTdONCs8kV8ug== + dependencies: + bluebird "^3.5.0" + cron-parser "^2.4.1" + debuglog "^1.0.0" + ioredis "^3.1.4" + lodash "^4.17.4" + semver "^5.4.1" + uuid "^3.1.0" + +"charenc@>= 0.0.1", charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + +cluster-key-slot@^1.0.6: + version "1.0.12" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.12.tgz#d5deff2a520717bc98313979b687309b2d368e29" + integrity sha512-21O0kGmvED5OJ7ZTdqQ5lQQ+sjuez33R+d35jZKLwqUb5mqcPHUsxOSzj61+LHVtxGZd1kShbQM3MjB/gBJkVg== + +combined-stream@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" + integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== + dependencies: + delayed-stream "~1.0.0" + +component-emitter@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= + +cookiejar@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" + integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA== + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cron-parser@^2.4.1: + version "2.7.3" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.7.3.tgz#12603f89f5375af353a9357be2543d3172eac651" + integrity sha512-t9Kc7HWBWPndBzvbdQ1YG9rpPRB37Tb/tTviziUOh1qs3TARGh3b1p+tnkOHNe1K5iI3oheBPgLqwotMM7+lpg== + dependencies: + is-nan "^1.2.1" + moment-timezone "^0.5.23" + +"crypt@>= 0.0.1", crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + +datadog-metrics@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/datadog-metrics/-/datadog-metrics-0.8.1.tgz#be87237109a7084193c668d80112533ef00e3f21" + integrity sha512-qTSKnddO6GxTJW9FYpmWjvvift3qfyMurDjwNjJnJhBk76pBdDhC0B5V9V+XwPdn4r42qu48kwXNuHJslXlDOA== + dependencies: + debug "3.1.0" + dogapi "1.1.0" + +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@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.1: + 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" + +debuglog@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +define-properties@^1.1.1: + 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" + +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= + +denque@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.0.tgz#79e2f0490195502107f24d9553f374837dabc916" + integrity sha512-gh513ac7aiKrAgjiIBWZG0EASyDF9p4JMWwKA8YU5s9figrL5SRNEMT6FDynsegakuhWd1wVqTvqvqAoDxw7wQ== + +dogapi@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/dogapi/-/dogapi-1.1.0.tgz#71a43865ad4bb4cb18bc3e13cf769971f501030a" + integrity sha1-caQ4Za1LtMsYvD4Tz3aZcfUBAwo= + dependencies: + extend "^3.0.0" + json-bigint "^0.1.4" + minimist "^1.1.1" + rc "^1.0.0" + +events@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +faker@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" + integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8= + +flexbuffer@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/flexbuffer/-/flexbuffer-0.0.6.tgz#039fdf23f8823e440c38f3277e6fef1174215b30" + integrity sha1-A5/fI/iCPkQMOPMnfm/vEXQhWzA= + +form-data@^2.3.1: + 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" + +formidable@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" + integrity sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg== + +ieee754@1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + integrity sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q= + +ieee754@^1.1.4: + version "1.1.12" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" + integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== + +inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +ioredis@3.2.2, ioredis@^3.1.4: + version "3.2.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-3.2.2.tgz#b7d5ff3afd77bb9718bb2821329b894b9a44c00b" + integrity sha512-g+ShTQYLsCcOUkNOK6CCEZbj3aRDVPw3WOwXk+LxlUKvuS9ujEqP2MppBHyRVYrNNFW/vcPaTBUZ2ctGNSiOCA== + dependencies: + bluebird "^3.3.4" + cluster-key-slot "^1.0.6" + debug "^2.6.9" + denque "^1.1.0" + flexbuffer "0.0.6" + lodash.assign "^4.2.0" + lodash.bind "^4.2.1" + lodash.clone "^4.5.0" + lodash.clonedeep "^4.5.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.foreach "^4.5.0" + lodash.isempty "^4.4.0" + lodash.keys "^4.2.0" + lodash.noop "^3.0.1" + lodash.partial "^4.2.1" + lodash.pick "^4.4.0" + lodash.sample "^4.2.1" + lodash.shuffle "^4.2.0" + lodash.values "^4.3.0" + redis-commands "^1.2.0" + redis-parser "^2.4.0" + +ioredis@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.2.0.tgz#f0f76fa5067a51c365ef1411f6572478a825971d" + integrity sha512-PdxZGNJBfPiR2RI6DkqmiacL1+ML3gaqEiaC5QXWQt9eSTlGj+BwDCct0s8irn1ed8GyzAHTzcjvU9fmnl6D7A== + dependencies: + cluster-key-slot "^1.0.6" + debug "^3.1.0" + denque "^1.1.0" + flexbuffer "0.0.6" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + redis-commands "1.4.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^1.0.0" + +is-buffer@~1.1.1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-nan@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.2.1.tgz#9faf65b6fb6db24b7f5c0628475ea71f988401e2" + integrity sha1-n69ltvttskt/XAYoR16nH5iEAeI= + dependencies: + define-properties "^1.1.1" + +isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +jmespath@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= + +json-bigint@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-0.1.4.tgz#b5d40b8a9009e92f157f7c079db097001830e01e" + integrity sha1-tdQLipAJ6S8Vf3wHnbCXABgw4B4= + dependencies: + bignumber.js "~1.1.1" + +lodash.assign@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= + +lodash.bind@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" + integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU= + +lodash.clone@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" + integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= + +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + +lodash.foreach@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= + +lodash.intersection@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.intersection/-/lodash.intersection-4.4.0.tgz#0a11ba631d0e95c23c7f2f4cbb9a692ed178e705" + integrity sha1-ChG6Yx0OlcI8fy9Mu5ppLtF45wU= + +lodash.isempty@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= + +lodash.keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" + integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU= + +lodash.noop@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" + integrity sha1-OBiPTWUKOkdCWEObluxFsyYXEzw= + +lodash.partial@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.partial/-/lodash.partial-4.2.1.tgz#49f3d8cfdaa3bff8b3a91d127e923245418961d4" + integrity sha1-SfPYz9qjv/izqR0SfpIyRUGJYdQ= + +lodash.pick@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= + +lodash.sample@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.sample/-/lodash.sample-4.2.1.tgz#5e4291b0c753fa1abeb0aab8fb29df1b66f07f6d" + integrity sha1-XkKRsMdT+hq+sKq4+ynfG2bwf20= + +lodash.shuffle@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz#145b5053cf875f6f5c2a33f48b6e9948c6ec7b4b" + integrity sha1-FFtQU8+HX29cKjP0i26ZSMbse0s= + +lodash.values@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" + integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= + +lodash@^4.17.4: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + +md5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + +methods@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +mime-db@~1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" + integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== + +mime-types@^2.1.12: + version "2.1.21" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" + integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== + dependencies: + mime-db "~1.37.0" + +mime@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +minimist@^1.1.1, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +moment-timezone@^0.5.23: + version "0.5.23" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" + integrity sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w== + dependencies: + moment ">= 2.9.0" + +"moment@>= 2.9.0": + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= + +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.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +node-env-file@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/node-env-file/-/node-env-file-0.1.8.tgz#fccb7b050f735b5a33da9eb937cf6f1ab457fb69" + integrity sha1-/Mt7BQ9zW1oz2p65N89vGrRX+2k= + +now-env@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/now-env/-/now-env-3.1.0.tgz#e0198b67279d387229cfd4b25de4c2fc7156943f" + integrity sha512-f+jXC+UkoxD/g9Nlig99Bxswoh7UUuQxw0EsPfuueHnVpVE0LfgQ4el5dxY4TSXwrL9mEF9GGm0gb7r3K8r/ug== + +object-keys@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" + integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== + +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= + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +qs@^6.5.1: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +raven@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/raven/-/raven-2.6.4.tgz#458d4a380c8fbb59e0150c655625aaf60c167ea3" + integrity sha512-6PQdfC4+DQSFncowthLf+B6Hr0JpPsFBgTVYTAOq7tCmx/kR4SXbeawtPch20+3QfUcQDoJBLjWW1ybvZ4kXTw== + dependencies: + cookie "0.3.1" + md5 "^2.2.1" + stack-trace "0.0.10" + timed-out "4.0.1" + uuid "3.3.2" + +rc@^1.0.0: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +redis-commands@1.4.0, redis-commands@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.4.0.tgz#52f9cf99153efcce56a8f86af986bd04e988602f" + integrity sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= + +redis-parser@^2.4.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" + integrity sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs= + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" + +redis-tag-cache@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/redis-tag-cache/-/redis-tag-cache-1.2.1.tgz#07d6e1f945d9d5c8186602129cb10573167d7534" + integrity sha512-0F+rLtoIkz4NOWGt9k3qaPcbpvr6+wcEC9aWV78newMKTRN/nIYHgvfyO1Q0ySEepiEYbqj9DhjvQ4CUAtT9/g== + dependencies: + ioredis "^4.0.0" + +rethinkdb-inspector@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/rethinkdb-inspector/-/rethinkdb-inspector-0.3.3.tgz#f0d88c66d17e0234b5518ca51cd8c272cb787003" + integrity sha512-1R0S5maattWOptfkHsU5ulXnt6FIKqjgyEdd8WgW9QbYMiHaXNqkHTAsmtXXSAf0j5iFH3F2qq/7eg4xwb8Euw== + +rethinkdbdash@^2.3.31: + version "2.3.31" + resolved "https://registry.yarnpkg.com/rethinkdbdash/-/rethinkdbdash-2.3.31.tgz#fe2f73d1fa6e6f5d96d8e881292013cf6dca914d" + integrity sha512-6nXrKFjdg2Ug0YpdmPWSvyD/2EisHnFNt4FWZ74dcXGK48ievSv+cNFTmVv+KjLi6I9CCf6w4CKZ6yCYTfMfdQ== + dependencies: + bluebird ">= 3.0.1" + +rethinkhaberdashery@^2.3.32: + version "2.3.32" + resolved "https://registry.yarnpkg.com/rethinkhaberdashery/-/rethinkhaberdashery-2.3.32.tgz#ddcc4ba1342e653a9d3bb6982b526087e93d537e" + integrity sha512-scDswDEu7R47WqomjScq46LkCdhaFhpaGQDP3P44GDF32iCFgYbsL4fnJdaieE115qLeinRhEjna08XWCtV0iQ== + dependencies: + bluebird ">= 3.0.1" + +safe-buffer@~5.1.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== + +sanitize-filename@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.1.tgz#612da1c96473fa02dccda92dcd5b4ab164a6772a" + integrity sha1-YS2hyWRz+gLczaktzVtKsWSmdyo= + dependencies: + truncate-utf8-bytes "^1.0.0" + +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o= + +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +semver@^5.4.1: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + +sha1@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/sha1/-/sha1-1.1.1.tgz#addaa7a93168f393f19eb2b15091618e2700f848" + integrity sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg= + dependencies: + charenc ">= 0.0.1" + crypt ">= 0.0.1" + +source-map-support@^0.5.10: + version "0.5.10" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" + integrity sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +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== + +stack-trace@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= + +standard-as-callback@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-1.0.1.tgz#2e9e1e9d278d7d77580253faaec42269015e3c1d" + integrity sha512-izxEITSyc7S+5oOiF/URiYaNkemPUxIndCNv66jJ548Y1TVxhBvioNMSPrZIQdaZDlhnguOdUzHA/7hJ3xFhuQ== + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +superagent@^3.3.1: + version "3.8.3" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" + integrity sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA== + dependencies: + component-emitter "^1.2.0" + cookiejar "^2.1.0" + debug "^3.1.0" + extend "^3.0.0" + form-data "^2.3.1" + formidable "^1.2.0" + methods "^1.1.1" + mime "^1.4.1" + qs "^6.5.1" + readable-stream "^2.3.5" + +timed-out@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= + +toobusy-js@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/toobusy-js/-/toobusy-js-0.5.1.tgz#5511f78f6a87a6a512d44fdb0efa13672217f659" + integrity sha1-VRH3j2qHpqUS1E/bDvoTZyIX9lk= + +truncate-utf8-bytes@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" + integrity sha1-QFkjkJWS1W94pYGENLC3hInKXys= + dependencies: + utf8-byte-length "^1.0.1" + +url@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +utf8-byte-length@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" + integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E= + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +uuid@3.3.2, uuid@^3.1.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + +xml2js@0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= diff --git a/api/apollo-server.js b/api/apollo-server.js new file mode 100644 index 0000000000..85d24c4544 --- /dev/null +++ b/api/apollo-server.js @@ -0,0 +1,125 @@ +// @flow +import { ApolloServer } from 'apollo-server-express'; +import depthLimit from 'graphql-depth-limit'; +import costAnalysis from 'graphql-cost-analysis'; +import createLoaders from './loaders'; +import createErrorFormatter from './utils/create-graphql-error-formatter'; +import schema from './schema'; +import { setUserOnline } from 'shared/db/queries/user'; +import { getUserIdFromReq } from './utils/session-store'; +import UserError from './utils/UserError'; +import type { DBUser } from 'shared/types'; + +// NOTE(@mxstbr): Evil hack to make graphql-cost-analysis work with Apollo Server v2 +// @see pa-bru/graphql-cost-analysis#12 +// @author @arianon +class ProtectedApolloServer extends ApolloServer { + async createGraphQLServerOptions( + req: express$Request, + res: express$Response + ): Promise<*> { + const options = await super.createGraphQLServerOptions(req, res); + + return { + ...options, + validationRules: [ + ...options.validationRules, + costAnalysis({ + maximumCost: 750, + defaultCost: 1, + variables: req.body.variables, + createError: (max, actual) => { + const err = new UserError( + `GraphQL query exceeds maximum complexity, please remove some nesting or fields and try again. (max: ${max}, actual: ${actual})` + ); + return err; + }, + }), + ], + }; + } +} + +const server = new ProtectedApolloServer({ + schema, + formatError: createErrorFormatter(), + // For subscriptions, this gets passed "connection", for everything else "req" and "res" + context: ({ req, res, connection, ...rest }, ...other) => { + if (connection) { + return { + ...(connection.context || {}), + }; + } + + // Add GraphQL operation information to the statsd tags + req.statsdTags = { + graphqlOperationName: req.body.operationName || 'unknown_operation', + }; + const loaders = createLoaders(); + let currentUser = req.user && !req.user.bannedAt ? req.user : null; + + return { + loaders, + updateCookieUserData: (data: DBUser) => + new Promise((res, rej) => + req.login(data, err => (err ? rej(err) : res())) + ), + req, + user: currentUser, + }; + }, + subscriptions: { + path: '/websocket', + onOperation: (_: any, params: Object) => { + const errorFormatter = createErrorFormatter(); + params.formatError = errorFormatter; + return params; + }, + onDisconnect: rawSocket => { + return getUserIdFromReq(rawSocket.upgradeReq) + .then(id => id && setUserOnline(id, false)) + .catch(err => { + console.error(err); + }); + }, + onConnect: (connectionParams, rawSocket) => + getUserIdFromReq(rawSocket.upgradeReq) + .then(id => (id ? setUserOnline(id, true) : null)) + .then(user => { + return { + user: user || null, + loaders: createLoaders({ cache: false }), + }; + }) + .catch(err => { + console.error(err); + return { + loaders: createLoaders({ cache: false }), + }; + }), + }, + playground: process.env.NODE_ENV !== 'production' && { + settings: { + 'editor.theme': 'light', + }, + tabs: [ + { + endpoint: 'http://localhost:3001/api', + query: `{ + user(username: "mxstbr") { + id + username + } +}`, + }, + ], + }, + introspection: process.env.NODE_ENV !== 'production', + maxFileSize: 25 * 1024 * 1024, // 25MB + engine: false, + tracing: false, + cacheControl: false, + validationRules: [depthLimit(10)], +}); + +export default server; diff --git a/api/authentication.js b/api/authentication.js index ab4c71c72b..98e2eea828 100644 --- a/api/authentication.js +++ b/api/authentication.js @@ -6,11 +6,11 @@ const { Strategy: FacebookStrategy } = require('passport-facebook'); const { Strategy: GoogleStrategy } = require('passport-google-oauth2'); const { Strategy: GitHubStrategy } = require('passport-github2'); const { - getUser, + getUserById, createOrFindUser, saveUserProvider, getUserByIndex, -} = require('./models/user'); +} = require('shared/db/queries/user'); const IS_PROD = !process.env.FORCE_DEV && process.env.NODE_ENV === 'production'; @@ -46,21 +46,39 @@ const GITHUB_OAUTH_CLIENT_ID = IS_PROD ? '208a2e8684d88883eded' : 'ed3e924f4a599313c83b'; +const isSerializedJSON = (str: string) => + str[0] === '{' && str[str.length - 1] === '}'; + const init = () => { // Setup use serialization passport.serializeUser((user, done) => { - done(null, user.id); + done(null, typeof user === 'string' ? user : JSON.stringify(user)); }); - passport.deserializeUser((id, done) => { - getUser({ id }) + // NOTE(@mxstbr): `data` used to be just the userID, but is now the full user data + // to avoid having to go to the db on every single request. We have to handle both + // cases here, as more and more users use Spectrum again we go to the db less and less + passport.deserializeUser((data, done) => { + // Fast path: we got the full user data in the cookie + if (isSerializedJSON(data)) { + let user; + // Ignore errors if our isSerializedJSON heuristic is wrong and `data` isn't serialized JSON + try { + user = JSON.parse(data); + } catch (err) {} + + if (user && user.id && user.createdAt) { + return done(null, user); + } + } + + // Slow path: data is just the userID (legacy), so we have to go to the db to get the full data + return getUserById(data) .then(user => { done(null, user); - return null; }) .catch(err => { done(err); - return null; }); }); @@ -70,7 +88,9 @@ const init = () => { { consumerKey: TWITTER_OAUTH_CLIENT_ID, consumerSecret: TWITTER_OAUTH_CLIENT_SECRET, - callbackURL: '/auth/twitter/callback', + callbackURL: IS_PROD + ? 'https://spectrum.chat/auth/twitter/callback' + : 'http://localhost:3001/auth/twitter/callback', includeEmail: true, }, (token, tokenSecret, profile, done) => { @@ -111,8 +131,6 @@ const init = () => { profile._json.entities.url.urls.length > 0 ? profile._json.entities.url.urls[0].expanded_url : '', - createdAt: new Date(), - lastSeen: new Date(), }; return createOrFindUser(user, 'providerId') @@ -178,8 +196,6 @@ const init = () => { ? profile.photos[0].value : null, coverPhoto: profile._json.cover ? profile._json.cover.source : '', - createdAt: new Date(), - lastSeen: new Date(), }; return createOrFindUser(user, 'fbProviderId') @@ -244,8 +260,6 @@ const init = () => { profile._json.urls && profile._json.urls.length > 0 ? profile._json.urls[0].value : '', - createdAt: new Date(), - lastSeen: new Date(), }; return createOrFindUser(user, 'googleProviderId') @@ -290,7 +304,16 @@ const init = () => { // 1 // if the user already has a githubProviderId, don't override it if (req.user.githubProviderId) { - if (!req.user.githubUsername) { + /* + Update the cached content of the github profile that we store + in redis for the graphql resolver. This allows us to put a button + on the client for a user to re-connect a github profile from + the web app which will update the cache with any changed usernames + */ + if ( + !req.user.githubUsername || + req.user.githubUsername !== githubUsername + ) { return saveUserProvider( req.user.id, 'githubProviderId', @@ -357,8 +380,6 @@ const init = () => { null, profilePhoto: (profile._json.avatar_url && profile._json.avatar_url) || null, - createdAt: new Date(), - lastSeen: new Date(), }; return createOrFindUser(user, 'githubProviderId') diff --git a/api/index.js b/api/index.js index d97c4e9fb3..17dbf02e59 100644 --- a/api/index.js +++ b/api/index.js @@ -2,101 +2,84 @@ /** * The entry point for the server, this is where everything starts */ -console.log('Server starting...'); const compression = require('compression'); const debug = require('debug')('api'); +debug('Server starting...'); debug('logging with debug enabled!'); import { createServer } from 'http'; import express from 'express'; import Raven from 'shared/raven'; -import { ApolloEngine } from 'apollo-engine'; import toobusy from 'shared/middlewares/toobusy'; +import addSecurityMiddleware from 'shared/middlewares/security'; +import csrf from 'shared/middlewares/csrf'; +import statsd from 'shared/middlewares/statsd'; import { init as initPassport } from './authentication.js'; +import apolloServer from './apollo-server'; +import { corsOptions } from 'shared/middlewares/cors'; +import errorHandler from 'shared/middlewares/error-handler'; +import middlewares from './routes/middlewares'; +import authRoutes from './routes/auth'; +import apiRoutes from './routes/api'; import type { DBUser } from 'shared/types'; +import type { Loader } from './loaders/types'; + +export type GraphQLContext = { + user: DBUser, + updateCookieUserData: (data: DBUser) => Promise, + loaders: { + [key: string]: Loader, + }, +}; const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001; -// Initialize authentication initPassport(); -// API server const app = express(); +// Instantiate the statsd middleware as soon as possible to get accurate time tracking +app.use(statsd); + // Trust the now proxy app.set('trust proxy', true); - -// Return the request if the server is too busy app.use(toobusy); -// Send all responses as gzip -app.use(compression()); +// Security middleware. +addSecurityMiddleware(app, { enableNonce: false, enableCSP: false }); +if (process.env.NODE_ENV === 'production' && !process.env.FORCE_DEV) { + app.use(csrf); +} -import middlewares from './routes/middlewares'; +// All other middlewares +app.use(compression()); app.use(middlewares); -import authRoutes from './routes/auth'; +// Routes app.use('/auth', authRoutes); - -import apiRoutes from './routes/api'; app.use('/api', apiRoutes); +// GraphQL middleware +apolloServer.applyMiddleware({ app, path: '/api', cors: corsOptions }); + +// Redirect a request to the root path to the main app +app.use('/', (req: express$Request, res: express$Response) => { + res.redirect( + process.env.NODE_ENV === 'production' && !process.env.FORCE_DEV + ? 'https://spectrum.chat' + : 'http://localhost:3000' + ); +}); + // $FlowIssue -app.use( - ( - err: Error, - req: express$Request, - res: express$Response, - next: express$NextFunction - ) => { - if (err) { - console.error(err); - res - .status(500) - .send( - 'Oops, something went wrong! Our engineers have been alerted and will fix this asap.' - ); - Raven.captureException(err); - } else { - return next(); - } - } -); +app.use(errorHandler); -import type { Loader } from './loaders/types'; -export type GraphQLContext = { - user: DBUser, - loaders: { - [key: string]: Loader, - }, -}; +// We need to create a separate HTTP server to handle GraphQL subscriptions via websockets +const httpServer = createServer(app); +apolloServer.installSubscriptionHandlers(httpServer); -const server = createServer(app); - -// Create subscriptions server at /websocket -import createSubscriptionsServer from './routes/create-subscription-server'; -const subscriptionsServer = createSubscriptionsServer(server, '/websocket'); - -// Start API wrapped in Apollo Engine -// const engine = new ApolloEngine({ -// logging: { -// level: 'WARN', -// }, -// apiKey: process.env.APOLLO_ENGINE_API_KEY, -// // Only send perf data to the remote server in production -// reporting: { -// disabled: process.env.NODE_ENV !== 'production', -// hostname: process.env.NOW_URL || undefined, -// privateHeaders: ['authorization', 'Authorization', 'AUTHORIZATION'], -// }, -// }); - -// engine.listen({ -// port: PORT, -// httpServer: server, -// graphqlPaths: ['/api'], -// }); -server.listen(PORT); -console.log(`GraphQL server running at http://localhost:${PORT}/api`); +httpServer.listen(PORT); + +debug(`GraphQL API running at http://localhost:${PORT}/api`); process.on('unhandledRejection', async err => { console.error('Unhandled rejection', err); @@ -119,5 +102,3 @@ process.on('uncaughtException', async err => { process.exit(1); } }); - -// diff --git a/api/loaders/channel.js b/api/loaders/channel.js index a22c672c28..c0c72242ef 100644 --- a/api/loaders/channel.js +++ b/api/loaders/channel.js @@ -3,11 +3,11 @@ import { getChannels, getChannelsThreadCounts, getChannelsMemberCounts, + getChannelsOnlineMemberCounts, } from '../models/channel'; import { getChannelsSettings } from '../models/channelSettings'; import createLoader from './create-loader'; import { getPendingUsersInChannels } from '../models/usersChannels'; -import type { Loader } from './types'; export const __createChannelLoader = createLoader(channels => getChannels(channels) @@ -18,13 +18,18 @@ export const __createChannelThreadCountLoader = createLoader( 'group' ); +export const __createChannelPendingMembersLoader = createLoader( + channels => getPendingUsersInChannels(channels), + 'group' +); + export const __createChannelMemberCountLoader = createLoader( channels => getChannelsMemberCounts(channels), 'group' ); -export const __createChannelPendingMembersLoader = createLoader( - channels => getPendingUsersInChannels(channels), +export const __createChannelOnlineMemberCountLoader = createLoader( + channelIds => getChannelsOnlineMemberCounts(channelIds), 'group' ); diff --git a/api/loaders/community.js b/api/loaders/community.js index b942998300..1eaa6f0b00 100644 --- a/api/loaders/community.js +++ b/api/loaders/community.js @@ -2,18 +2,13 @@ import { getCommunities, getCommunitiesBySlug, - getCommunitiesMemberCounts, getCommunitiesChannelCounts, + getCommunitiesOnlineMemberCounts, + getCommunitiesMemberCounts, } from '../models/community'; import { getCommunitiesSettings } from '../models/communitySettings'; -import { getCommunitiesRecurringPayments } from '../models/recurringPayment'; import createLoader from './create-loader'; -export const __createCommunityRecurringPaymentsLoader = createLoader( - communities => getCommunitiesRecurringPayments(communities), - 'group' -); - export const __createCommunityLoader = createLoader(communities => getCommunities(communities) ); @@ -33,6 +28,11 @@ export const __createCommunityChannelCountLoader = createLoader( 'group' ); +export const __createCommunityOnlineMemberCountLoader = createLoader( + communityIds => getCommunitiesOnlineMemberCounts(communityIds), + 'group' +); + export const __createCommunitySettingsLoader = createLoader( communityIds => getCommunitiesSettings(communityIds), key => key.communityId diff --git a/api/loaders/create-loader.js b/api/loaders/create-loader.js index f0b022a0f8..3261d479ac 100644 --- a/api/loaders/create-loader.js +++ b/api/loaders/create-loader.js @@ -1,5 +1,4 @@ // @flow -//$FlowFixMe import DataLoader from 'dataloader'; import unique from 'shared/unique-elements'; import type { Loader, DataLoaderOptions } from './types'; @@ -26,7 +25,7 @@ const createLoader = ( // https://github.com/facebook/dataloader/blob/master/examples/RethinkDB.md function indexResults(results, indexField, cacheKeyFn) { var indexedResults = new Map(); - results.forEach(res => { + results.filter(Boolean).forEach(res => { const key = typeof indexField === 'function' ? indexField(res) : res[indexField]; indexedResults.set(cacheKeyFn(key), res); diff --git a/api/loaders/directMessageThread.js b/api/loaders/directMessageThread.js index a0b936d483..1657fca0e0 100644 --- a/api/loaders/directMessageThread.js +++ b/api/loaders/directMessageThread.js @@ -1,7 +1,7 @@ // @flow import { getDirectMessageThreads } from '../models/directMessageThread'; import { getMembersInDirectMessageThreads } from '../models/usersDirectMessageThreads'; -import { getLastMessages } from '../models/message'; +import { getLastMessageOfThreads } from '../models/message'; import createLoader from './create-loader'; import type { Loader } from './types'; @@ -15,8 +15,8 @@ export const __createDirectMessageParticipantsLoader = createLoader( ); export const __createDirectMessageSnippetLoader = createLoader( - threads => getLastMessages(threads), - 'group' + threads => getLastMessageOfThreads(threads), + 'threadId' ); export default () => { diff --git a/api/loaders/index.js b/api/loaders/index.js index eab83ff918..f47ee366de 100644 --- a/api/loaders/index.js +++ b/api/loaders/index.js @@ -3,7 +3,6 @@ import { __createUserLoader, __createUserByUsernameLoader, __createUserThreadCountLoader, - __createUserRecurringPaymentsLoader, __createUserPermissionsInCommunityLoader, __createUserTotalReputationLoader, __createUserPermissionsInChannelLoader, @@ -12,31 +11,35 @@ import { import { __createThreadLoader, __createThreadParticipantsLoader, - __createThreadMessageCountLoader, } from './thread'; import { __createNotificationLoader } from './notification'; import { __createChannelLoader, - __createChannelMemberCountLoader, __createChannelThreadCountLoader, + __createChannelMemberCountLoader, + __createChannelOnlineMemberCountLoader, __createChannelPendingMembersLoader, __createChannelSettingsLoader, } from './channel'; import { __createCommunityLoader, __createCommunityBySlugLoader, - __createCommunityRecurringPaymentsLoader, - __createCommunityMemberCountLoader, __createCommunityChannelCountLoader, __createCommunitySettingsLoader, + __createCommunityMemberCountLoader, + __createCommunityOnlineMemberCountLoader, } from './community'; import { __createDirectMessageThreadLoader, __createDirectMessageParticipantsLoader, __createDirectMessageSnippetLoader, } from './directMessageThread'; -import { __createReactionLoader } from './reaction'; -import { __createStripeCustomersLoader } from './stripe'; +import { + __createReactionLoader, + __createSingleReactionLoader, +} from './reaction'; +import { __createThreadReactionLoader } from './threadReaction'; +import { __createMessageLoader } from './message'; import type { DataLoaderOptions } from './types'; // Create all the necessary loaders to be attached to the GraphQL context for each request @@ -44,7 +47,6 @@ const createLoaders = (options?: DataLoaderOptions) => ({ user: __createUserLoader(options), userByUsername: __createUserByUsernameLoader(options), userThreadCount: __createUserThreadCountLoader(options), - userRecurringPayments: __createUserRecurringPaymentsLoader(options), userPermissionsInCommunity: __createUserPermissionsInCommunityLoader(options), userPermissionsInChannel: __createUserPermissionsInChannelLoader(options), userTotalReputation: __createUserTotalReputationLoader(options), @@ -53,24 +55,26 @@ const createLoaders = (options?: DataLoaderOptions) => ({ ), thread: __createThreadLoader(options), threadParticipants: __createThreadParticipantsLoader(options), - threadMessageCount: __createThreadMessageCountLoader(options), notification: __createNotificationLoader(options), channel: __createChannelLoader(options), channelMemberCount: __createChannelMemberCountLoader(options), + channelOnlineMemberCount: __createChannelOnlineMemberCountLoader(options), channelThreadCount: __createChannelThreadCountLoader(options), channelPendingUsers: __createChannelPendingMembersLoader(options), channelSettings: __createChannelSettingsLoader(options), community: __createCommunityLoader(options), communityBySlug: __createCommunityBySlugLoader(options), - communityRecurringPayments: __createCommunityRecurringPaymentsLoader(options), - stripeCustomers: __createStripeCustomersLoader(options), communityChannelCount: __createCommunityChannelCountLoader(options), communityMemberCount: __createCommunityMemberCountLoader(options), + communityOnlineMemberCount: __createCommunityOnlineMemberCountLoader(options), communitySettings: __createCommunitySettingsLoader(options), directMessageThread: __createDirectMessageThreadLoader(options), directMessageParticipants: __createDirectMessageParticipantsLoader(options), directMessageSnippet: __createDirectMessageSnippetLoader(options), + message: __createMessageLoader(options), messageReaction: __createReactionLoader(options), + threadReaction: __createThreadReactionLoader(options), + reaction: __createSingleReactionLoader(options), }); export default createLoaders; diff --git a/api/loaders/message.js b/api/loaders/message.js new file mode 100644 index 0000000000..6950a52a01 --- /dev/null +++ b/api/loaders/message.js @@ -0,0 +1,14 @@ +// @flow +import { getManyMessages } from '../models/message'; +import createLoader from './create-loader'; +import type { Loader } from './types'; + +export const __createMessageLoader = createLoader((messages: string[]) => + getManyMessages(messages) +); + +export default () => { + throw new Error( + '⚠️ Do not import loaders directly, get them from the GraphQL context instead! ⚠️' + ); +}; diff --git a/api/loaders/reaction.js b/api/loaders/reaction.js index ea683bd5ba..6008dc8ac6 100644 --- a/api/loaders/reaction.js +++ b/api/loaders/reaction.js @@ -1,5 +1,5 @@ // @flow -import { getReactions } from '../models/reaction'; +import { getReactions, getReactionsByIds } from '../models/reaction'; import createLoader from './create-loader'; export const __createReactionLoader = createLoader( @@ -7,6 +7,10 @@ export const __createReactionLoader = createLoader( 'group' ); +export const __createSingleReactionLoader = createLoader(reactionIds => + getReactionsByIds(reactionIds) +); + export default () => { throw new Error( '⚠️ Do not import loaders directly, get them from the GraphQL context instead! ⚠️' diff --git a/api/loaders/stripe.js b/api/loaders/stripe.js deleted file mode 100644 index 8cfb15096b..0000000000 --- a/api/loaders/stripe.js +++ /dev/null @@ -1,7 +0,0 @@ -import { getStripeCustomersByCustomerIds } from '../models/stripeCustomers'; -import createLoader from './create-loader'; - -export const __createStripeCustomersLoader = createLoader( - customers => getStripeCustomersByCustomerIds(customers), - 'group' -); diff --git a/api/loaders/thread.js b/api/loaders/thread.js index 45c626757f..bc3519d9d9 100644 --- a/api/loaders/thread.js +++ b/api/loaders/thread.js @@ -14,11 +14,6 @@ export const __createThreadParticipantsLoader = createLoader( 'group' ); -export const __createThreadMessageCountLoader = createLoader( - threadIds => getMessageCountInThreads(threadIds), - 'group' -); - export default () => { throw new Error( '⚠️ Do not import loaders directly, get them from the GraphQL context instead! ⚠️' diff --git a/api/loaders/threadReaction.js b/api/loaders/threadReaction.js new file mode 100644 index 0000000000..3e6d520b99 --- /dev/null +++ b/api/loaders/threadReaction.js @@ -0,0 +1,14 @@ +// @flow +import { getThreadReactions } from '../models/threadReaction'; +import createLoader from './create-loader'; + +export const __createThreadReactionLoader = createLoader( + threadIds => getThreadReactions(threadIds), + 'group' +); + +export default () => { + throw new Error( + '⚠️ Do not import loaders directly, get them from the GraphQL context instead! ⚠️' + ); +}; diff --git a/api/loaders/types.js b/api/loaders/types.js index 7ac77dbca1..bf55ad90d8 100644 --- a/api/loaders/types.js +++ b/api/loaders/types.js @@ -2,7 +2,7 @@ export type Loader = { load: (key: string | Array) => Promise, - loadMany: (keys: Array) => Promise, + loadMany: (keys: Array<*>) => Promise, clear: (key: string | Array) => void, }; diff --git a/api/loaders/user.js b/api/loaders/user.js index dd474ab552..96ea7cf462 100644 --- a/api/loaders/user.js +++ b/api/loaders/user.js @@ -3,8 +3,7 @@ import { getUsers, getUsersThreadCount, getUsersByUsername, -} from '../models/user'; -import { getUsersRecurringPayments } from '../models/recurringPayment'; +} from 'shared/db/queries/user'; import { getUsersPermissionsInCommunities, getUsersTotalReputation, @@ -12,7 +11,6 @@ import { import { getUsersPermissionsInChannels } from '../models/usersChannels'; import { getThreadsNotificationStatusForUsers } from '../models/usersThreads'; import createLoader from './create-loader'; -import type { Loader } from './types'; export const __createUserLoader = createLoader(users => getUsers(users), 'id'); @@ -26,11 +24,6 @@ export const __createUserThreadCountLoader = createLoader( 'id' ); -export const __createUserRecurringPaymentsLoader = createLoader( - users => getUsersRecurringPayments(users), - 'group' -); - export const __createUserPermissionsInCommunityLoader = createLoader( usersCommunities => getUsersPermissionsInCommunities(usersCommunities), input => `${input.userId}|${input.communityId}`, diff --git a/api/migrations/20180411183454-lowercase-all-the-slugs.js b/api/migrations/20180411183454-lowercase-all-the-slugs.js new file mode 100644 index 0000000000..d65a01f964 --- /dev/null +++ b/api/migrations/20180411183454-lowercase-all-the-slugs.js @@ -0,0 +1,27 @@ +exports.up = async (r, conn) => { + return Promise.all([ + r + .table('users') + .update({ + username: r.row('username').downcase(), + email: r.row('email').downcase(), + }) + .run(conn), + r + .table('communities') + .update({ + slug: r.row('slug').downcase(), + }) + .run(conn), + r + .table('channels') + .update({ + slug: r.row('slug').downcase(), + }) + .run(conn), + ]); +}; + +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/migrations/20180428001543-reset-slack-import-records.js b/api/migrations/20180428001543-reset-slack-import-records.js new file mode 100644 index 0000000000..8736afbd8c --- /dev/null +++ b/api/migrations/20180428001543-reset-slack-import-records.js @@ -0,0 +1,14 @@ +exports.up = function(r, conn) { + return Promise.all([ + r + .table('slackImports') + .update({ + members: r.literal(), + }) + .run(conn), + ]).catch(err => console.error(err)); +}; + +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/migrations/20180504003702-encrypt-existing-slack-data.js b/api/migrations/20180504003702-encrypt-existing-slack-data.js new file mode 100644 index 0000000000..3f904a4d59 --- /dev/null +++ b/api/migrations/20180504003702-encrypt-existing-slack-data.js @@ -0,0 +1,67 @@ +const { encryptString } = require('../../shared/encryption'); + +exports.up = async (r, conn) => { + const encryptOldSlackImportData = async () => { + const records = await r + .table('slackImports') + .run(conn) + .then(cursor => cursor.toArray()); + + const recordPromises = records.map(async record => { + const teamId = encryptString(record.teamId); + const teamName = encryptString(record.teamName); + const token = encryptString(record.token); + + return await r + .table('slackImports') + .get(record.id) + .update({ + teamId, + teamName, + token, + }) + .run(conn); + }); + + return await Promise.all([...recordPromises]); + }; + + const encryptNewSlackImportData = async () => { + const records = await r + .table('communitySettings') + .filter(row => row.hasFields('slackSettings')) + .run(conn) + .then(cursor => cursor.toArray()); + + const recordPromises = records.map(async record => { + const teamId = encryptString(record.slackSettings.teamId); + const teamName = encryptString(record.slackSettings.teamName); + const token = encryptString(record.slackSettings.token); + const scope = encryptString(record.slackSettings.scope); + + return await r + .table('communitySettings') + .get(record.id) + .update({ + slackSettings: { + teamId, + teamName, + token, + scope, + }, + }) + .run(conn); + }); + + return await Promise.all([...recordPromises]); + }; + + return await Promise.all([ + encryptOldSlackImportData(), + encryptNewSlackImportData(), + ]).catch(err => console.error(err)); +}; + +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/migrations/20180517180716-enable-private-communities.js b/api/migrations/20180517180716-enable-private-communities.js new file mode 100644 index 0000000000..d0ad106774 --- /dev/null +++ b/api/migrations/20180517180716-enable-private-communities.js @@ -0,0 +1,17 @@ +exports.up = async (r, conn) => { + return r + .table('communities') + .update({ + isPrivate: false, + }) + .run(conn); +}; + +exports.down = function(r, conn) { + return r + .table('communities') + .update({ + isPrivate: r.literal(), + }) + .run(conn); +}; diff --git a/api/migrations/20180517215503-add-ispending-to-userscommunities.js b/api/migrations/20180517215503-add-ispending-to-userscommunities.js new file mode 100644 index 0000000000..aed213d768 --- /dev/null +++ b/api/migrations/20180517215503-add-ispending-to-userscommunities.js @@ -0,0 +1,17 @@ +exports.up = async (r, conn) => { + return r + .table('usersCommunities') + .update({ + isPending: false, + }) + .run(conn); +}; + +exports.down = function(r, conn) { + return r + .table('usersCommunities') + .update({ + isPending: r.literal(), + }) + .run(conn); +}; diff --git a/api/migrations/20180518135040-add-join-settings-to-community-settings.js b/api/migrations/20180518135040-add-join-settings-to-community-settings.js new file mode 100644 index 0000000000..4cd7aedfb2 --- /dev/null +++ b/api/migrations/20180518135040-add-join-settings-to-community-settings.js @@ -0,0 +1,20 @@ +exports.up = async (r, conn) => { + return r + .table('communitySettings') + .update({ + joinSettings: { + tokenJoinEnabled: false, + token: null, + }, + }) + .run(conn); +}; + +exports.down = function(r, conn) { + return r + .table('communitySettings') + .update({ + joinSettings: r.literal(), + }) + .run(conn); +}; diff --git a/api/migrations/20180621001409-thread-likes-table.js b/api/migrations/20180621001409-thread-likes-table.js new file mode 100644 index 0000000000..19f702c997 --- /dev/null +++ b/api/migrations/20180621001409-thread-likes-table.js @@ -0,0 +1,22 @@ +exports.up = function(r, conn) { + return r + .tableCreate('threadReactions') + .run(conn) + .then(() => { + return Promise.all([ + r + .table('threadReactions') + .indexCreate('threadId') + .run(conn), + r + .table('threadReactions') + .indexCreate('userId') + .run(conn), + ]); + }) + .catch(err => console.error(err)); +}; + +exports.down = function(r, conn) { + return Promise.all([r.tableDrop('threadReactions').run(conn)]); +}; diff --git a/api/migrations/20180823115847-add-users-communities-indexes.js b/api/migrations/20180823115847-add-users-communities-indexes.js new file mode 100644 index 0000000000..f8bf7b8af3 --- /dev/null +++ b/api/migrations/20180823115847-add-users-communities-indexes.js @@ -0,0 +1,61 @@ +exports.up = function(r, conn) { + return Promise.all([ + r + .table('usersCommunities') + .indexCreate('communityIdAndIsMember', [ + r.row('communityId'), + r.row('isMember'), + ]) + .run(conn), + r + .table('usersCommunities') + .indexCreate('communityIdAndIsMemberAndReputation', [ + r.row('communityId'), + r.row('isMember'), + r.row('reputation'), + ]) + .run(conn), + r + .table('usersCommunities') + .indexCreate('communityIdAndIsModerator', [ + r.row('communityId'), + r.row('isModerator'), + ]) + .run(conn), + r + .table('usersCommunities') + .indexCreate('communityIdAndIsOwner', [ + r.row('communityId'), + r.row('isOwner'), + ]) + .run(conn), + r + .table('usersCommunities') + .indexCreate('communityIdAndIsTeamMember', [ + r.row('communityId'), + r.row('isOwner').or(r.row('isModerator')), + ]) + .run(conn), + ]); +}; + +exports.down = function(r, conn) { + return Promise.all([ + r + .table('usersCommunities') + .indexDrop('communityIdAndIsMember') + .run(conn), + r + .table('usersCommunities') + .indexDrop('communityIdAndIsModerator') + .run(conn), + r + .table('usersCommunities') + .indexDrop('communityIdAndIsOwner') + .run(conn), + r + .table('usersCommunities') + .indexDrop('communityIdAndIsTeamMember') + .run(conn), + ]); +}; diff --git a/api/migrations/20181001061156-thread-metadata-denormalization.js b/api/migrations/20181001061156-thread-metadata-denormalization.js new file mode 100644 index 0000000000..1c8757d6b3 --- /dev/null +++ b/api/migrations/20181001061156-thread-metadata-denormalization.js @@ -0,0 +1,57 @@ +exports.up = function(r, conn) { + return r + .table('threads') + .update( + { + messageCount: r + .table('messages') + .getAll(r.row('id'), { index: 'threadId' }) + .count() + .default(0), + reactionCount: r + .table('threadReactions') + .getAll(r.row('id'), { index: 'threadId' }) + .count() + .default(0), + }, + { + nonAtomic: true, + } + ) + .run(conn) + .then(() => { + return Promise.all([ + r + .table('threads') + .indexCreate('messageCount') + .run(conn), + r + .table('threads') + .indexCreate('reactionCount') + .run(conn), + ]); + }) + .catch(err => console.error(err)); +}; + +exports.down = function(r, conn) { + return r + .table('threads') + .update({ + messageCount: r.literal(), + reactionCount: r.literal(), + }) + .run(conn) + .then(() => { + return Promise.all([ + r + .table('threads') + .indexDrop('messageCount') + .run(conn), + r + .table('threads') + .indexDrop('reactionCount') + .run(conn), + ]); + }); +}; diff --git a/api/migrations/20181001064151-fix-thread-metadata-message-counts.js b/api/migrations/20181001064151-fix-thread-metadata-message-counts.js new file mode 100644 index 0000000000..65e120152f --- /dev/null +++ b/api/migrations/20181001064151-fix-thread-metadata-message-counts.js @@ -0,0 +1,29 @@ +exports.up = function(r, conn) { + return r + .table('threads') + .update( + { + messageCount: r + .table('messages') + .getAll(r.row('id'), { index: 'threadId' }) + .filter(row => r.not(row.hasFields('deletedAt'))) + .count() + .default(0), + reactionCount: r + .table('threadReactions') + .getAll(r.row('id'), { index: 'threadId' }) + .filter(row => r.not(row.hasFields('deletedAt'))) + .count() + .default(0), + }, + { + nonAtomic: true, + } + ) + .run(conn) + .catch(err => console.error(err)); +}; + +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/migrations/20181002060237-remove-payments.js b/api/migrations/20181002060237-remove-payments.js new file mode 100644 index 0000000000..9662aa8042 --- /dev/null +++ b/api/migrations/20181002060237-remove-payments.js @@ -0,0 +1,57 @@ +exports.up = async (r, conn) => { + const betaSupporterIds = await r + .db('spectrum') + .table('recurringPayments') + .filter({ planId: 'beta-pro' }) + .map(row => row('userId')) + .run(conn) + .then(cursor => cursor.toArray()); + + const addSupporterFieldToUsersPromises = async () => + await r + .db('spectrum') + .table('users') + .getAll(...betaSupporterIds) + .update({ + betaSupporter: true, + }) + .run(conn); + + const cleanCommunitiesModel = () => + r + .table('communities') + .update({ + stripeCustomerId: r.literal(), + analyticsEnabled: r.literal(), + prioritySupportEnabled: r.literal(), + ossVerified: r.literal(), + }) + .run(conn); + + return await Promise.all([ + cleanCommunitiesModel(), + addSupporterFieldToUsersPromises(), + ]); +}; + +exports.down = async (r, conn) => { + return await Promise.all([ + r + .db('spectrum') + .table('communities') + .update({ + stripeCustomerId: null, + analyticsEnabled: false, + prioritySupportEnabled: false, + ossVerified: false, + }) + .run(conn), + r + .db('spectrum') + .table('users') + .update({ + betaSupporter: r.literal(), + }) + .run(conn), + ]); +}; diff --git a/api/migrations/20181003233411-thread-reactions-useridandthreadid-index.js b/api/migrations/20181003233411-thread-reactions-useridandthreadid-index.js new file mode 100644 index 0000000000..896c0a0601 --- /dev/null +++ b/api/migrations/20181003233411-thread-reactions-useridandthreadid-index.js @@ -0,0 +1,13 @@ +exports.up = function(r, conn) { + return r + .table('threadReactions') + .indexCreate('userIdAndThreadId', [r.row('userId'), r.row('threadId')]) + .run(conn); +}; + +exports.down = function(r, conn) { + return r + .table('threadReactions') + .indexDrop('userIdAndThreadId') + .run(conn); +}; diff --git a/api/migrations/20181004222636-denormalize-channel-community-member-counts.js b/api/migrations/20181004222636-denormalize-channel-community-member-counts.js new file mode 100644 index 0000000000..98b639d238 --- /dev/null +++ b/api/migrations/20181004222636-denormalize-channel-community-member-counts.js @@ -0,0 +1,52 @@ +exports.up = function(r, conn) { + return Promise.all([ + r + .table('communities') + .update( + { + memberCount: r + .table('usersCommunities') + .getAll(r.row('id'), { index: 'communityId' }) + .filter(row => row('isMember').eq(true)) + .count() + .default(1), + }, + { + nonAtomic: true, + } + ) + .run(conn), + r + .table('channels') + .update( + { + memberCount: r + .table('usersChannels') + .getAll(r.row('id'), { index: 'channelId' }) + .filter(row => row('isMember').eq(true)) + .count() + .default(1), + }, + { + nonAtomic: true, + } + ) + .run(conn), + ]).catch(err => console.error(err)); +}; +exports.down = function(r, conn) { + return Promise.all([ + r + .table('communities') + .update({ + memberCount: r.literal(), + }) + .run(conn), + r + .table('channels') + .update({ + memberCount: r.literal(), + }) + .run(conn), + ]); +}; diff --git a/api/migrations/20181005143053-users-notifications-useridandnotificationid-index.js b/api/migrations/20181005143053-users-notifications-useridandnotificationid-index.js new file mode 100644 index 0000000000..5469580eeb --- /dev/null +++ b/api/migrations/20181005143053-users-notifications-useridandnotificationid-index.js @@ -0,0 +1,16 @@ +exports.up = function(r, conn) { + return r + .table('usersNotifications') + .indexCreate('userIdAndNotificationId', [ + r.row('userId'), + r.row('notificationId'), + ]) + .run(conn); +}; + +exports.down = function(r, conn) { + return r + .table('usersNotifications') + .indexDrop('userIdAndNotificationId') + .run(conn); +}; diff --git a/api/migrations/20181005144259-users-notifications-userIdAndIsSeen-index.js b/api/migrations/20181005144259-users-notifications-userIdAndIsSeen-index.js new file mode 100644 index 0000000000..661e6d17de --- /dev/null +++ b/api/migrations/20181005144259-users-notifications-userIdAndIsSeen-index.js @@ -0,0 +1,13 @@ +exports.up = function(r, conn) { + return r + .table('usersNotifications') + .indexCreate('userIdAndIsSeen', [r.row('userId'), r.row('isSeen')]) + .run(conn); +}; + +exports.down = function(r, conn) { + return r + .table('usersNotifications') + .indexDrop('userIdAndIsSeen') + .run(conn); +}; diff --git a/api/migrations/20181023160027-update-denormalized-member-counts.js b/api/migrations/20181023160027-update-denormalized-member-counts.js new file mode 100644 index 0000000000..accb8af8d6 --- /dev/null +++ b/api/migrations/20181023160027-update-denormalized-member-counts.js @@ -0,0 +1,39 @@ +exports.up = function(r, conn) { + return Promise.all([ + r + .table('communities') + .update( + { + memberCount: r + .table('usersCommunities') + .getAll(r.row('id'), { index: 'communityId' }) + .filter(row => row('isMember').eq(true)) + .count() + .default(1), + }, + { + nonAtomic: true, + } + ) + .run(conn), + r + .table('channels') + .update( + { + memberCount: r + .table('usersChannels') + .getAll(r.row('id'), { index: 'channelId' }) + .filter(row => row('isMember').eq(true)) + .count() + .default(1), + }, + { + nonAtomic: true, + } + ) + .run(conn), + ]).catch(err => console.error(err)); +}; +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/migrations/20181024173616-indexes-for-digests.js b/api/migrations/20181024173616-indexes-for-digests.js new file mode 100644 index 0000000000..bfe6ea3367 --- /dev/null +++ b/api/migrations/20181024173616-indexes-for-digests.js @@ -0,0 +1,31 @@ +exports.up = function(r, conn) { + return Promise.all([ + r + .table('usersSettings') + .indexCreate( + 'weeklyDigestEmail', + r.row('notifications')('types')('weeklyDigest')('email') + ) + .run(conn), + r + .table('usersSettings') + .indexCreate( + 'dailyDigestEmail', + r.row('notifications')('types')('dailyDigest')('email') + ) + .run(conn), + ]); +}; + +exports.down = function(r, conn) { + return Promise.all([ + r + .table('usersSettings') + .indexDrop('weeklyDigestEmail') + .run(conn), + r + .table('usersSettings') + .indexDrop('dailyDigestEmail') + .run(conn), + ]); +}; diff --git a/api/migrations/20181027050052-remove-attachments-from-thread-model.js b/api/migrations/20181027050052-remove-attachments-from-thread-model.js new file mode 100644 index 0000000000..c8e8447140 --- /dev/null +++ b/api/migrations/20181027050052-remove-attachments-from-thread-model.js @@ -0,0 +1,13 @@ +exports.up = function(r, conn) { + return Promise.all([ + r + .table('threads') + .update({ + attachments: r.literal(), + }) + .run(conn), + ]).catch(err => console.error(err)); +}; +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/migrations/20181102025454-fix-old-image-urls-in-messages.js b/api/migrations/20181102025454-fix-old-image-urls-in-messages.js new file mode 100644 index 0000000000..022c3be6ff --- /dev/null +++ b/api/migrations/20181102025454-fix-old-image-urls-in-messages.js @@ -0,0 +1,33 @@ +exports.up = async function(r, conn) { + const messages = await r + .db('spectrum') + .table('messages') + .filter({ messageType: 'media' }) + .filter(row => row('timestamp').lt(r.epochTime(1540929600))) + .filter(row => row('content')('body').match('spectrum.imgix.net')) + .filter(row => row('content')('body').match('%20')) + .filter(row => row.hasFields('deletedAt').not()) + .map(row => ({ id: row('id'), url: row('content')('body') })) + .run(conn) + .then(cursor => cursor.toArray()); + + const messagePromises = messages.map(async obj => { + return await r + .db('spectrum') + .table('messages') + .get(obj.id) + .update({ + content: { + body: decodeURIComponent(obj.url), + }, + imageReplaced: new Date(), + }) + .run(conn); + }); + + return await Promise.all(messagePromises); +}; + +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/migrations/20181102040518-fix-old-image-urls-in-threads.js b/api/migrations/20181102040518-fix-old-image-urls-in-threads.js new file mode 100644 index 0000000000..ce2f96bdf5 --- /dev/null +++ b/api/migrations/20181102040518-fix-old-image-urls-in-threads.js @@ -0,0 +1,71 @@ +exports.up = async function(r, conn) { + const threads = await r + .db('spectrum') + .table('threads') + .filter(row => row('modifiedAt').lt(r.epochTime(1540929600))) + .filter(row => row('content')('body').match('spectrum.imgix.net')) + .filter(row => row('content')('body').match('%20')) + .filter(row => row.hasFields('deletedAt').not()) + .map(row => ({ id: row('id'), body: row('content')('body') })) + .run(conn) + .then(cursor => cursor.toArray()); + + const threadPromises = threads.map(async obj => { + const newBody = JSON.parse(obj.body); + + const imageKeys = Object.keys(newBody.entityMap).filter( + key => newBody.entityMap[key].type.toLowerCase() === 'image' + ); + + const LEGACY_PREFIX = 'https://spectrum.imgix.net/'; + const hasLegacyPrefix = url => url.startsWith(LEGACY_PREFIX, 0); + const stripLegacyPrefix = url => url.replace(LEGACY_PREFIX, ''); + + const processImageUrl = str => { + if (str.indexOf(LEGACY_PREFIX) < 0) { + return str; + } + if (str.indexOf('%20') < 0) { + return str; + } + + const split = str.split('?'); + const imagePath = split[0]; + + const decoded = decodeURIComponent(imagePath); + + const processed = hasLegacyPrefix(decoded) + ? stripLegacyPrefix(decoded) + : decoded; + + return processed; + }; + + imageKeys.forEach((key, index) => { + if (!newBody.entityMap[key]) { + return; + } + + const { src } = newBody.entityMap[key].data; + newBody.entityMap[key].data.src = processImageUrl(src); + }); + + return await r + .db('spectrum') + .table('threads') + .get(obj.id) + .update({ + content: { + body: JSON.stringify(newBody), + }, + imageReplaced: new Date(), + }) + .run(conn); + }); + + return await Promise.all(threadPromises); +}; + +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/migrations/20181102044407-fix-old-image-urls-in-communities.js b/api/migrations/20181102044407-fix-old-image-urls-in-communities.js new file mode 100644 index 0000000000..face2a859c --- /dev/null +++ b/api/migrations/20181102044407-fix-old-image-urls-in-communities.js @@ -0,0 +1,73 @@ +exports.up = async function(r, conn) { + const communities = await r + .db('spectrum') + .table('communities') + .filter(row => row('modifiedAt').lt(r.epochTime(1540929600))) + .filter(row => + row('profilePhoto') + .match('spectrum.imgix.net') + .or(row('coverPhoto').match('spectrum.imgix.net')) + ) + .filter(row => + row('profilePhoto') + .match('%20') + .or(row('coverPhoto').match('%20')) + ) + .filter(row => row.hasFields('deletedAt').not()) + .map(row => ({ + id: row('id'), + profilePhoto: row('profilePhoto'), + coverPhoto: row('coverPhoto'), + })) + .run(conn) + .then(cursor => cursor.toArray()); + + const communityPromises = communities.map(async obj => { + const { profilePhoto, coverPhoto } = obj; + + const LEGACY_PREFIX = 'https://spectrum.imgix.net/'; + const hasLegacyPrefix = url => url.startsWith(LEGACY_PREFIX, 0); + const stripLegacyPrefix = url => url.replace(LEGACY_PREFIX, ''); + + const processImageUrl = str => { + if (str.indexOf(LEGACY_PREFIX) < 0) { + return str; + } + + if (str.indexOf('%20') < 0) { + return str; + } + + const split = str.split('?'); + const imagePath = split[0]; + + const decoded = decodeURIComponent(imagePath); + + const processed = hasLegacyPrefix(decoded) + ? stripLegacyPrefix(decoded) + : decoded; + + return processed; + }; + + const newProfilePhoto = processImageUrl(profilePhoto); + const newCoverPhoto = processImageUrl(coverPhoto); + + return await r + .db('spectrum') + .table('communities') + .get(obj.id) + .update({ + coverPhoto: newCoverPhoto, + profilePhoto: newProfilePhoto, + imageReplaced: new Date(), + }) + .run(conn); + }); + + return await Promise.all(communityPromises); +}; + +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/migrations/20181102045821-fix-old-image-urls-in-users.js b/api/migrations/20181102045821-fix-old-image-urls-in-users.js new file mode 100644 index 0000000000..f768f21a67 --- /dev/null +++ b/api/migrations/20181102045821-fix-old-image-urls-in-users.js @@ -0,0 +1,76 @@ +exports.up = async function(r, conn) { + const users = await r + .db('spectrum') + .table('users') + .filter(row => row('modifiedAt').lt(r.epochTime(1540929600))) + .filter(row => + row.hasFields('coverPhoto').and(row.hasFields('profilePhoto')) + ) + .filter(row => + row('profilePhoto') + .match('spectrum.imgix.net') + .or(row('coverPhoto').match('spectrum.imgix.net')) + ) + .filter(row => + row('profilePhoto') + .match('%20') + .or(row('coverPhoto').match('%20')) + ) + .filter(row => row.hasFields('deletedAt').not()) + .map(row => ({ + id: row('id'), + profilePhoto: row('profilePhoto'), + coverPhoto: row('coverPhoto'), + })) + .run(conn) + .then(cursor => cursor.toArray()); + + const userPromises = users.map(async obj => { + const { profilePhoto, coverPhoto } = obj; + + const LEGACY_PREFIX = 'https://spectrum.imgix.net/'; + const hasLegacyPrefix = url => url.startsWith(LEGACY_PREFIX, 0); + const stripLegacyPrefix = url => url.replace(LEGACY_PREFIX, ''); + + const processImageUrl = str => { + if (str.indexOf(LEGACY_PREFIX) < 0) { + return str; + } + + if (str.indexOf('%20') < 0) { + return str; + } + + const split = str.split('?'); + const imagePath = split[0]; + + const decoded = decodeURIComponent(imagePath); + + const processed = hasLegacyPrefix(decoded) + ? stripLegacyPrefix(decoded) + : decoded; + + return processed; + }; + + const newProfilePhoto = processImageUrl(profilePhoto); + const newCoverPhoto = processImageUrl(coverPhoto); + + return await r + .db('spectrum') + .table('users') + .get(obj.id) + .update({ + coverPhoto: newCoverPhoto, + profilePhoto: newProfilePhoto, + imageReplaced: new Date(), + }) + .run(conn); + }); + + return await Promise.all(userPromises); +}; + +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/migrations/20181102054523-fix-aws-static-url-community-photos.js b/api/migrations/20181102054523-fix-aws-static-url-community-photos.js new file mode 100644 index 0000000000..fc88d3e667 --- /dev/null +++ b/api/migrations/20181102054523-fix-aws-static-url-community-photos.js @@ -0,0 +1,54 @@ +exports.up = async function(r, conn) { + const LEGACY_PREFIX = 'https://s3.amazonaws.com/spectrum-chat/'; + + const communities = await r + .db('spectrum') + .table('communities') + .filter(row => + row('profilePhoto') + .match(LEGACY_PREFIX) + .or(row('coverPhoto').match(LEGACY_PREFIX)) + ) + .filter(row => row.hasFields('deletedAt').not()) + .map(row => ({ + id: row('id'), + profilePhoto: row('profilePhoto'), + coverPhoto: row('coverPhoto'), + })) + .run(conn) + .then(cursor => cursor.toArray()); + + const communityPromises = communities.map(async obj => { + const { profilePhoto, coverPhoto } = obj; + const hasLegacyPrefix = url => url.startsWith(LEGACY_PREFIX, 0); + const stripLegacyPrefix = url => url.replace(LEGACY_PREFIX, ''); + + const processImageUrl = str => { + if (str.indexOf(LEGACY_PREFIX) < 0) { + return str; + } + + return stripLegacyPrefix(str); + }; + + const newProfilePhoto = processImageUrl(profilePhoto); + const newCoverPhoto = processImageUrl(coverPhoto); + + return await r + .db('spectrum') + .table('communities') + .get(obj.id) + .update({ + coverPhoto: newCoverPhoto, + profilePhoto: newProfilePhoto, + awsStaticReplaced: new Date(), + }) + .run(conn); + }); + + return await Promise.all(communityPromises); +}; + +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/migrations/20181116173949-add-terms-last-accepted-field-to-users.js b/api/migrations/20181116173949-add-terms-last-accepted-field-to-users.js new file mode 100644 index 0000000000..6a55682b80 --- /dev/null +++ b/api/migrations/20181116173949-add-terms-last-accepted-field-to-users.js @@ -0,0 +1,17 @@ +exports.up = async (r, conn) => { + return r + .table('users') + .update({ + termsLastAcceptedAt: r.row('createdAt'), + }) + .run(conn); +}; + +exports.down = function(r, conn) { + return r + .table('users') + .update({ + termsLastAcceptedAt: r.literal(), + }) + .run(conn); +}; diff --git a/api/migrations/20181121054300-resync-community-member-counts.js b/api/migrations/20181121054300-resync-community-member-counts.js new file mode 100644 index 0000000000..accb8af8d6 --- /dev/null +++ b/api/migrations/20181121054300-resync-community-member-counts.js @@ -0,0 +1,39 @@ +exports.up = function(r, conn) { + return Promise.all([ + r + .table('communities') + .update( + { + memberCount: r + .table('usersCommunities') + .getAll(r.row('id'), { index: 'communityId' }) + .filter(row => row('isMember').eq(true)) + .count() + .default(1), + }, + { + nonAtomic: true, + } + ) + .run(conn), + r + .table('channels') + .update( + { + memberCount: r + .table('usersChannels') + .getAll(r.row('id'), { index: 'channelId' }) + .filter(row => row('isMember').eq(true)) + .count() + .default(1), + }, + { + nonAtomic: true, + } + ) + .run(conn), + ]).catch(err => console.error(err)); +}; +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/migrations/20181122162921-users-communities-useridandmember-index.js b/api/migrations/20181122162921-users-communities-useridandmember-index.js new file mode 100644 index 0000000000..753a861de3 --- /dev/null +++ b/api/migrations/20181122162921-users-communities-useridandmember-index.js @@ -0,0 +1,13 @@ +exports.up = function(r, conn) { + return r + .table('usersCommunities') + .indexCreate('userIdAndIsMember', [r.row('userId'), r.row('isMember')]) + .run(conn); +}; + +exports.down = function(r, conn) { + return r + .table('usersCommunities') + .indexDrop('userIdAndIsMember') + .run(conn); +}; diff --git a/api/migrations/20181126094455-users-channels-roles.js b/api/migrations/20181126094455-users-channels-roles.js new file mode 100644 index 0000000000..0d7275d1a7 --- /dev/null +++ b/api/migrations/20181126094455-users-channels-roles.js @@ -0,0 +1,59 @@ +const branch = (r, field, fallback) => { + return r.branch(r.row(`is${field}`).eq(true), field.toLowerCase(), fallback); +}; + +exports.up = function(r, conn) { + return Promise.all([ + r + .table('usersChannels') + .indexCreate('channelIdAndRole', [ + r.row('channelId'), + branch( + r, + 'Pending', + branch( + r, + 'Blocked', + branch( + r, + 'Owner', + branch(r, 'Moderator', branch(r, 'Member', r.literal())) + ) + ) + ), + ]) + .run(conn), + r + .table('usersChannels') + .indexCreate('userIdAndRole', [ + r.row('userId'), + branch( + r, + 'Pending', + branch( + r, + 'Blocked', + branch( + r, + 'Owner', + branch(r, 'Moderator', branch(r, 'Member', r.literal())) + ) + ) + ), + ]) + .run(conn), + ]); +}; + +exports.down = function(r, conn) { + return Promise.all([ + r + .table('usersChannels') + .indexDrop('channelIdAndRole') + .run(conn), + r + .table('usersChannels') + .indexDrop('userIdAndRole') + .run(conn), + ]); +}; diff --git a/api/migrations/20181127090014-communities-member-count-index.js b/api/migrations/20181127090014-communities-member-count-index.js new file mode 100644 index 0000000000..eecdfa1163 --- /dev/null +++ b/api/migrations/20181127090014-communities-member-count-index.js @@ -0,0 +1,13 @@ +exports.up = function(r, conn) { + return r + .table('communities') + .indexCreate('memberCount') + .run(conn); +}; + +exports.down = function(r, conn) { + return r + .table('communities') + .indexDrop('memberCount') + .run(conn); +}; diff --git a/api/migrations/20181205171559-remove-old-users-notifications.js b/api/migrations/20181205171559-remove-old-users-notifications.js new file mode 100644 index 0000000000..560032f618 --- /dev/null +++ b/api/migrations/20181205171559-remove-old-users-notifications.js @@ -0,0 +1,61 @@ +exports.up = async (r, conn) => { + let after = 0; + let limit = 10000; + let done = false; + + const getRecords = async (after, limit) => { + return await r + .table('usersNotifications') + .skip(after) + .limit(limit) + .run(conn) + .then(cursor => cursor.toArray()); + }; + + const deleteRecords = async arr => { + if (!arr || arr.length === 0) return; + + const filtered = arr + .filter(rec => rec.isSeen) + .filter(rec => { + const THIRTY_DAYS = 1000 * 60 * 60 * 24 * 30; //ms + const added = new Date(rec.entityAddedAt).getTime(); //ms + const now = new Date().getTime(); //ms + return now - added > THIRTY_DAYS; + }) + .map(rec => rec.id) + .filter(Boolean); + + return await r + .table('usersNotifications') + .getAll(...filtered) + .delete() + .run(conn); + }; + + const processUsersNotificiations = async arr => { + if (done) { + return await deleteRecords(arr); + } + + if (arr.length < limit) { + done = true; + return await deleteRecords(arr); + } + + return deleteRecords(arr).then(async () => { + after = after + limit; + const nextRecords = await getRecords(after, limit); + + return processUsersNotificiations(nextRecords); + }); + }; + + const initialRecordIds = await getRecords(after, limit); + + return processUsersNotificiations(initialRecordIds); +}; + +exports.down = function(r, conn) { + return Promise.resolve(); +}; diff --git a/api/migrations/20181211181146-add-usersthreads-user-id-and-participant-index.js b/api/migrations/20181211181146-add-usersthreads-user-id-and-participant-index.js new file mode 100644 index 0000000000..b876c1386b --- /dev/null +++ b/api/migrations/20181211181146-add-usersthreads-user-id-and-participant-index.js @@ -0,0 +1,19 @@ +exports.up = function(r, conn) { + return Promise.all([ + r + .table('usersThreads') + .indexCreate('userIdAndIsParticipant', [ + r.row('userId'), + r.row('isParticipant'), + ]) + .run(conn), + ]); +}; +exports.down = function(r, conn) { + return Promise.all([ + r + .table('usersThreads') + .indexDrop('userIdAndIsParticipant') + .run(conn), + ]); +}; diff --git a/api/migrations/config.js b/api/migrations/config.js index b4979a9e43..2764982df8 100644 --- a/api/migrations/config.js +++ b/api/migrations/config.js @@ -1,5 +1,6 @@ const path = require('path'); const fs = require('fs'); +const debug = require('debug')('migrations'); const DEFAULT_CONFIG = { driver: 'rethinkdbdash', @@ -9,14 +10,24 @@ const DEFAULT_CONFIG = { migrationsDirectory: 'api/migrations', }; +let ca; + +try { + ca = fs.readFileSync(path.join(process.cwd(), 'cacert')); +} catch (err) {} + const RUN_IN_PROD = !!process.env.AWS_RETHINKDB_PASSWORD; +if (!ca && RUN_IN_PROD) + throw new Error( + 'Please provide the SSL certificate to connect to the production database in a file called `cacert` in the root directory.' + ); + if (RUN_IN_PROD && process.argv[4] === 'down') { throw new Error('Do not drop the production database!!!!!'); - process.exit(1); } -if (RUN_IN_PROD) console.log('Running migration in production...'); +if (RUN_IN_PROD) debug('Running migration in production...'); module.exports = !RUN_IN_PROD ? DEFAULT_CONFIG @@ -24,4 +35,11 @@ module.exports = !RUN_IN_PROD password: process.env.AWS_RETHINKDB_PASSWORD, host: process.env.AWS_RETHINKDB_URL, port: process.env.AWS_RETHINKDB_PORT, + ...(ca + ? { + ssl: { + ca, + }, + } + : {}), }); diff --git a/api/migrations/seed/default/channels.js b/api/migrations/seed/default/channels.js index 3a31897411..7c159ff130 100644 --- a/api/migrations/seed/default/channels.js +++ b/api/migrations/seed/default/channels.js @@ -5,6 +5,7 @@ const { SPECTRUM_COMMUNITY_ID, PAYMENTS_COMMUNITY_ID, DELETED_COMMUNITY_ID, + PRIVATE_COMMUNITY_ID, SPECTRUM_GENERAL_CHANNEL_ID, SPECTRUM_PRIVATE_CHANNEL_ID, PAYMENTS_GENERAL_CHANNEL_ID, @@ -13,6 +14,7 @@ const { SPECTRUM_DELETED_CHANNEL_ID, DELETED_COMMUNITY_DELETED_CHANNEL_ID, MODERATOR_CREATED_CHANNEL_ID, + PRIVATE_GENERAL_CHANNEL_ID, } = constants; module.exports = [ @@ -25,6 +27,7 @@ module.exports = [ slug: 'general', isPrivate: false, isDefault: true, + memberCount: 5, }, { @@ -36,6 +39,7 @@ module.exports = [ slug: 'private', isPrivate: true, isDefault: false, + memberCount: 5, }, { @@ -47,6 +51,7 @@ module.exports = [ slug: 'general', isPrivate: false, isDefault: true, + memberCount: 5, }, { @@ -58,6 +63,7 @@ module.exports = [ slug: 'private', isPrivate: true, isDefault: false, + memberCount: 5, }, { @@ -70,6 +76,7 @@ module.exports = [ isPrivate: false, isDefault: true, archivedAt: new Date(DATE), + memberCount: 3, }, { @@ -82,6 +89,7 @@ module.exports = [ isPrivate: false, isDefault: false, deletedAt: new Date(DATE), + memberCount: 0, }, { @@ -94,6 +102,7 @@ module.exports = [ isPrivate: false, isDefault: false, deletedAt: new Date(DATE), + memberCount: 1, }, { @@ -105,5 +114,18 @@ module.exports = [ slug: 'moderator-created', isPrivate: false, isDefault: false, + memberCount: 1, + }, + + { + id: PRIVATE_GENERAL_CHANNEL_ID, + communityId: PRIVATE_COMMUNITY_ID, + createdAt: new Date(DATE), + name: 'General', + description: 'General', + slug: 'private-general', + isPrivate: false, + isDefault: false, + memberCount: 1, }, ]; diff --git a/api/migrations/seed/default/communities.js b/api/migrations/seed/default/communities.js index ec2e32fd70..b0e93a19f1 100644 --- a/api/migrations/seed/default/communities.js +++ b/api/migrations/seed/default/communities.js @@ -5,12 +5,14 @@ const { SPECTRUM_COMMUNITY_ID, PAYMENTS_COMMUNITY_ID, DELETED_COMMUNITY_ID, + PRIVATE_COMMUNITY_ID, } = constants; module.exports = [ { id: SPECTRUM_COMMUNITY_ID, createdAt: new Date(DATE), + isPrivate: false, name: 'Spectrum', description: 'The future of communities', website: 'https://spectrum.chat', @@ -19,10 +21,12 @@ module.exports = [ coverPhoto: 'https://spectrum.imgix.net/communities/-Kh6RfPYjmSaIWbkck8i/Twitter Header.png.0.3303118636071434', slug: 'spectrum', + memberCount: 4, }, { id: PAYMENTS_COMMUNITY_ID, createdAt: new Date(DATE), + isPrivate: false, name: 'Payments', description: 'Where payments are tested', website: 'https://spectrum.chat', @@ -31,11 +35,13 @@ module.exports = [ coverPhoto: 'https://spectrum.imgix.net/communities/-Kh6RfPYjmSaIWbkck8i/Twitter Header.png.0.3303118636071434', slug: 'payments', + memberCount: 5, }, { id: DELETED_COMMUNITY_ID, createdAt: new Date(DATE), deletedAt: new Date(DATE), + isPrivate: false, name: 'Deleted', description: 'Things didnt work out', website: 'https://spectrum.chat', @@ -44,5 +50,20 @@ module.exports = [ coverPhoto: 'https://spectrum.imgix.net/communities/-Kh6RfPYjmSaIWbkck8i/Twitter Header.png.0.3303118636071434', slug: 'deleted', + memberCount: 0, + }, + { + id: PRIVATE_COMMUNITY_ID, + createdAt: new Date(DATE), + isPrivate: true, + name: 'Private community', + description: 'Private community', + website: 'https://spectrum.chat', + profilePhoto: + 'https://spectrum.imgix.net/communities/-Kh6RfPYjmSaIWbkck8i/Twitter Profile.png.0.6225566835336693', + coverPhoto: + 'https://spectrum.imgix.net/communities/-Kh6RfPYjmSaIWbkck8i/Twitter Header.png.0.3303118636071434', + slug: 'private', + memberCount: 1, }, ]; diff --git a/api/migrations/seed/default/constants.js b/api/migrations/seed/default/constants.js index 9f0156c5de..eff3218ab3 100644 --- a/api/migrations/seed/default/constants.js +++ b/api/migrations/seed/default/constants.js @@ -22,6 +22,7 @@ const COMMUNITY_MODERATOR_USER_ID = '9'; const SPECTRUM_COMMUNITY_ID = '1'; const PAYMENTS_COMMUNITY_ID = '2'; const DELETED_COMMUNITY_ID = '3'; +const PRIVATE_COMMUNITY_ID = '4'; // channels const SPECTRUM_GENERAL_CHANNEL_ID = '1'; @@ -32,6 +33,7 @@ const SPECTRUM_ARCHIVED_CHANNEL_ID = '5'; const SPECTRUM_DELETED_CHANNEL_ID = '6'; const DELETED_COMMUNITY_DELETED_CHANNEL_ID = '7'; const MODERATOR_CREATED_CHANNEL_ID = '8'; +const PRIVATE_GENERAL_CHANNEL_ID = '9'; module.exports = { DATE, @@ -47,6 +49,7 @@ module.exports = { SPECTRUM_COMMUNITY_ID, PAYMENTS_COMMUNITY_ID, DELETED_COMMUNITY_ID, + PRIVATE_COMMUNITY_ID, SPECTRUM_GENERAL_CHANNEL_ID, SPECTRUM_PRIVATE_CHANNEL_ID, PAYMENTS_GENERAL_CHANNEL_ID, @@ -55,4 +58,5 @@ module.exports = { SPECTRUM_DELETED_CHANNEL_ID, DELETED_COMMUNITY_DELETED_CHANNEL_ID, MODERATOR_CREATED_CHANNEL_ID, + PRIVATE_GENERAL_CHANNEL_ID, }; diff --git a/api/migrations/seed/default/directMessageThreads.js b/api/migrations/seed/default/directMessageThreads.js index d4f024fcf1..f738be291f 100644 --- a/api/migrations/seed/default/directMessageThreads.js +++ b/api/migrations/seed/default/directMessageThreads.js @@ -9,4 +9,10 @@ module.exports = [ name: null, threadLastActive: new Date(DATE), }, + { + id: 'dm-2', + createdAt: new Date(DATE - 1), + name: null, + threadLastActive: new Date(DATE - 1), + }, ]; diff --git a/api/migrations/seed/default/index.js b/api/migrations/seed/default/index.js index 89b1d89950..d1f58072c8 100644 --- a/api/migrations/seed/default/index.js +++ b/api/migrations/seed/default/index.js @@ -9,7 +9,11 @@ const defaultDirectMessageThreads = require('./directMessageThreads'); const defaultUsersDirectMessageThreads = require('./usersDirectMessageThreads'); const defaultUsersCommunities = require('./usersCommunities'); const defaultUsersChannels = require('./usersChannels'); +const defaultUsersSettings = require('./usersSettings')(); const defaultMessages = require('./messages'); +const defaultReactions = require('./reactions'); +const defaultUsersNotifications = require('./usersNotifications'); +const defaultNotifications = require('./notifications'); module.exports = { constants, @@ -23,8 +27,10 @@ module.exports = { defaultUsersCommunities, defaultUsersChannels, defaultMessages, - defaultNotifications: [], + defaultUsersSettings, + defaultNotifications, + defaultUsersNotifications, defaultCommunitySettings: [], defaultChannelSettings: [], - defaultUsersSettings: [], + defaultReactions, }; diff --git a/api/migrations/seed/default/messages.js b/api/migrations/seed/default/messages.js index 4989c6d87e..a34c0c8898 100644 --- a/api/migrations/seed/default/messages.js +++ b/api/migrations/seed/default/messages.js @@ -7,7 +7,6 @@ module.exports = [ { id: '1', threadId: 'thread-1', - attachments: [], content: { body: JSON.stringify({ blocks: [ @@ -32,7 +31,6 @@ module.exports = [ { id: '2', threadId: 'thread-1', - attachments: [], content: { body: JSON.stringify( toJSON(fromPlainText('This is the second message!')) @@ -46,7 +44,6 @@ module.exports = [ { id: '3', threadId: 'thread-1', - attachments: [], content: { body: JSON.stringify( toJSON(fromPlainText('The next one is an emoji-only one :scream:')) @@ -60,7 +57,6 @@ module.exports = [ { id: '4', threadId: 'thread-1', - attachments: [], content: { body: JSON.stringify(toJSON(fromPlainText('🎉'))), }, @@ -73,7 +69,6 @@ module.exports = [ { id: '5', threadId: 'thread-2', - attachments: [], content: { body: JSON.stringify({ blocks: [ @@ -98,7 +93,6 @@ module.exports = [ { id: '6', threadId: 'thread-2', - attachments: [], content: { body: JSON.stringify( toJSON(fromPlainText('This is the second message!')) @@ -112,7 +106,6 @@ module.exports = [ { id: '7', threadId: 'thread-2', - attachments: [], content: { body: JSON.stringify( toJSON(fromPlainText('The next one is an emoji-only one :scream:')) @@ -126,7 +119,6 @@ module.exports = [ { id: '8', threadId: 'thread-2', - attachments: [], content: { body: JSON.stringify(toJSON(fromPlainText('🎉'))), }, @@ -141,7 +133,6 @@ module.exports = [ id: '9', threadId: 'dm-1', threadType: 'directMessageThread', - attachments: [], content: { body: JSON.stringify( toJSON(fromPlainText('Direct message thread message!')) @@ -155,7 +146,6 @@ module.exports = [ id: '10', threadId: 'dm-1', threadType: 'directMessageThread', - attachments: [], content: { body: JSON.stringify(toJSON(fromPlainText('A second one'))), }, @@ -167,7 +157,6 @@ module.exports = [ id: '11', threadId: 'dm-1', threadType: 'directMessageThread', - attachments: [], content: { body: JSON.stringify(toJSON(fromPlainText('A third one'))), }, @@ -179,7 +168,6 @@ module.exports = [ id: '12', threadId: 'dm-1', threadType: 'directMessageThread', - attachments: [], content: { body: JSON.stringify(toJSON(fromPlainText('A fourth one'))), }, @@ -191,7 +179,6 @@ module.exports = [ id: '13', threadId: 'dm-1', threadType: 'directMessageThread', - attachments: [], content: { body: JSON.stringify(toJSON(fromPlainText('A fifth one'))), }, diff --git a/api/migrations/seed/default/notifications.js b/api/migrations/seed/default/notifications.js new file mode 100644 index 0000000000..5eb2506e65 --- /dev/null +++ b/api/migrations/seed/default/notifications.js @@ -0,0 +1,35 @@ +// @flow +const constants = require('./constants'); +const users = require('./users'); + +const { DATE, BRIAN_ID, PREVIOUS_MEMBER_USER_ID } = constants; + +module.exports = [ + { + actors: [ + { + id: PREVIOUS_MEMBER_USER_ID, + payload: JSON.stringify( + users.find(u => u.id === PREVIOUS_MEMBER_USER_ID) + ), + type: 'USER', + }, + ], + context: { + id: 'dm-2', + payload: '', + type: 'DIRECT_MESSAGE_THREAD', + }, + createdAt: new Date(DATE + 1), + entities: [ + { + id: '1', + payload: '', + type: 'MESSAGE', + }, + ], + event: 'MESSAGE_CREATED', + id: '1', + modifiedAt: new Date(DATE + 1), + }, +]; diff --git a/api/migrations/seed/default/reactions.js b/api/migrations/seed/default/reactions.js new file mode 100644 index 0000000000..0b86fbacaa --- /dev/null +++ b/api/migrations/seed/default/reactions.js @@ -0,0 +1,13 @@ +// @flow +const constants = require('./constants'); +const { DATE, MAX_ID } = constants; + +module.exports = [ + { + id: '1', + messageId: '4', + type: 'like', + senderId: MAX_ID, + timestamp: new Date(DATE + 4), + }, +]; diff --git a/api/migrations/seed/default/threads.js b/api/migrations/seed/default/threads.js index e6bb926413..c7df1aef87 100644 --- a/api/migrations/seed/default/threads.js +++ b/api/migrations/seed/default/threads.js @@ -6,17 +6,14 @@ const { BRIAN_ID, MAX_ID, BRYN_ID, - CHANNEL_MODERATOR_USER_ID, SPECTRUM_GENERAL_CHANNEL_ID, + PRIVATE_GENERAL_CHANNEL_ID, SPECTRUM_PRIVATE_CHANNEL_ID, - PAYMENTS_GENERAL_CHANNEL_ID, - PAYMENTS_PRIVATE_CHANNEL_ID, - SPECTRUM_DELETED_CHANNEL_ID, DELETED_COMMUNITY_DELETED_CHANNEL_ID, MODERATOR_CREATED_CHANNEL_ID, DELETED_COMMUNITY_ID, SPECTRUM_COMMUNITY_ID, - PAYMENTS_COMMUNITY_ID, + PRIVATE_COMMUNITY_ID, SPECTRUM_ARCHIVED_CHANNEL_ID, } = constants; @@ -36,7 +33,6 @@ module.exports = [ toJSON(fromPlainText('This is it, we got a thread here')) ), }, - attachments: [], edits: [ { timestamp: new Date(DATE), @@ -50,6 +46,8 @@ module.exports = [ ], modifiedAt: new Date(DATE), lastActive: new Date(DATE), + messageCount: 4, + reactionCount: 0, }, { id: 'thread-2', @@ -66,7 +64,6 @@ module.exports = [ toJSON(fromPlainText('This is just another thread')) ), }, - attachments: [], edits: [ { timestamp: new Date(DATE + 1), @@ -80,6 +77,8 @@ module.exports = [ ], modifiedAt: new Date(DATE + 1), lastActive: new Date(DATE + 1), + messageCount: 4, + reactionCount: 0, }, { id: 'thread-3', @@ -96,7 +95,6 @@ module.exports = [ toJSON(fromPlainText('This is just another thread')) ), }, - attachments: [], edits: [ { timestamp: new Date(DATE + 2), @@ -110,6 +108,8 @@ module.exports = [ ], modifiedAt: new Date(DATE + 2), lastActive: new Date(DATE + 2), + messageCount: 0, + reactionCount: 0, }, { @@ -127,7 +127,6 @@ module.exports = [ toJSON(fromPlainText('This is it, we got a thread here')) ), }, - attachments: [], edits: [ { timestamp: new Date(DATE), @@ -141,6 +140,8 @@ module.exports = [ ], modifiedAt: new Date(DATE), lastActive: new Date(DATE), + messageCount: 0, + reactionCount: 0, }, { id: 'thread-5', @@ -157,7 +158,6 @@ module.exports = [ toJSON(fromPlainText('This is just another thread')) ), }, - attachments: [], edits: [ { timestamp: new Date(DATE + 1), @@ -171,6 +171,8 @@ module.exports = [ ], modifiedAt: new Date(DATE + 1), lastActive: new Date(DATE + 1), + messageCount: 0, + reactionCount: 0, }, { id: 'thread-6', @@ -187,7 +189,6 @@ module.exports = [ toJSON(fromPlainText('This is just another thread')) ), }, - attachments: [], edits: [ { timestamp: new Date(DATE + 2), @@ -201,6 +202,8 @@ module.exports = [ ], modifiedAt: new Date(DATE + 2), lastActive: new Date(DATE + 2), + messageCount: 0, + reactionCount: 0, }, { id: 'thread-7', @@ -217,7 +220,6 @@ module.exports = [ toJSON(fromPlainText('This is just another thread')) ), }, - attachments: [], edits: [ { timestamp: new Date(DATE + 2), @@ -232,6 +234,8 @@ module.exports = [ modifiedAt: new Date(DATE + 2), lastActive: new Date(DATE + 2), deletedAt: new Date(DATE + 3), + messageCount: 0, + reactionCount: 0, }, { id: 'thread-8', @@ -248,7 +252,6 @@ module.exports = [ toJSON(fromPlainText('This is just another thread')) ), }, - attachments: [], edits: [ { timestamp: new Date(DATE + 2), @@ -262,6 +265,8 @@ module.exports = [ ], modifiedAt: new Date(DATE + 2), lastActive: new Date(DATE + 2), + messageCount: 0, + reactionCount: 0, }, { id: 'thread-9', @@ -278,7 +283,6 @@ module.exports = [ toJSON(fromPlainText('This is just another thread')) ), }, - attachments: [], edits: [ { timestamp: new Date(DATE + 2), @@ -309,7 +313,6 @@ module.exports = [ toJSON(fromPlainText('This is just another thread')) ), }, - attachments: [], edits: [ { timestamp: new Date(DATE + 2), @@ -323,5 +326,102 @@ module.exports = [ ], modifiedAt: new Date(DATE + 2), lastActive: new Date(DATE + 2), + messageCount: 0, + reactionCount: 0, + }, + { + id: 'thread-11', + createdAt: new Date(DATE + 2), + creatorId: BRYN_ID, + channelId: SPECTRUM_GENERAL_CHANNEL_ID, + communityId: SPECTRUM_COMMUNITY_ID, + isPublished: true, + isLocked: true, + type: 'DRAFTJS', + content: { + title: 'Deleted thread', + body: JSON.stringify(toJSON(fromPlainText('This is a deleted thread'))), + }, + edits: [ + { + timestamp: new Date(DATE + 2), + content: { + title: 'Deleted thread', + body: JSON.stringify( + toJSON(fromPlainText('This is a deleted thread')) + ), + }, + }, + ], + modifiedAt: new Date(DATE + 2), + lastActive: new Date(DATE + 2), + deletedAt: new Date(DATE + 3), + messageCount: 0, + reactionCount: 0, + }, + + { + id: 'thread-12', + createdAt: new Date(DATE + 2), + creatorId: BRYN_ID, + channelId: DELETED_COMMUNITY_DELETED_CHANNEL_ID, + communityId: DELETED_COMMUNITY_ID, + isPublished: true, + isLocked: false, + type: 'DRAFTJS', + content: { + title: 'Yet another thread', + body: JSON.stringify( + toJSON(fromPlainText('This is just another thread')) + ), + }, + edits: [ + { + timestamp: new Date(DATE + 2), + content: { + title: 'Yet another thread', + body: JSON.stringify( + toJSON(fromPlainText('This is just another thread')) + ), + }, + }, + ], + modifiedAt: new Date(DATE + 2), + lastActive: new Date(DATE + 2), + deletedAt: new Date(DATE), + messageCount: 0, + reactionCount: 0, + }, + + { + id: 'thread-13', + createdAt: new Date(DATE + 2), + creatorId: MAX_ID, + channelId: PRIVATE_GENERAL_CHANNEL_ID, + communityId: PRIVATE_COMMUNITY_ID, + isPublished: true, + isLocked: false, + type: 'DRAFTJS', + content: { + title: 'Yet another thread', + body: JSON.stringify( + toJSON(fromPlainText('This is just another thread')) + ), + }, + edits: [ + { + timestamp: new Date(DATE + 2), + content: { + title: 'Yet another thread', + body: JSON.stringify( + toJSON(fromPlainText('This is just another thread')) + ), + }, + }, + ], + modifiedAt: new Date(DATE + 2), + lastActive: new Date(DATE + 2), + messageCount: 0, + reactionCount: 0, }, ]; diff --git a/api/migrations/seed/default/usersChannels.js b/api/migrations/seed/default/usersChannels.js index 2de1a4621c..14aaee961c 100644 --- a/api/migrations/seed/default/usersChannels.js +++ b/api/migrations/seed/default/usersChannels.js @@ -6,13 +6,14 @@ const { MAX_ID, BRYN_ID, BLOCKED_USER_ID, - PENDING_USER_ID, PREVIOUS_MEMBER_USER_ID, CHANNEL_MODERATOR_USER_ID, COMMUNITY_MODERATOR_USER_ID, SPECTRUM_GENERAL_CHANNEL_ID, + PRIVATE_GENERAL_CHANNEL_ID, SPECTRUM_ARCHIVED_CHANNEL_ID, SPECTRUM_PRIVATE_CHANNEL_ID, + DELETED_COMMUNITY_DELETED_CHANNEL_ID, PAYMENTS_GENERAL_CHANNEL_ID, PAYMENTS_PRIVATE_CHANNEL_ID, MODERATOR_CREATED_CHANNEL_ID, @@ -359,4 +360,40 @@ module.exports = [ isPending: false, receiveNotifications: true, }, + { + id: '29', + createdAt: new Date(DATE), + userId: BRIAN_ID, + channelId: DELETED_COMMUNITY_DELETED_CHANNEL_ID, + isOwner: false, + isModerator: false, + isMember: true, + isBlocked: false, + isPending: false, + receiveNotifications: true, + }, + { + id: '30', + createdAt: new Date(DATE), + userId: PREVIOUS_MEMBER_USER_ID, + channelId: SPECTRUM_GENERAL_CHANNEL_ID, + isOwner: false, + isModerator: false, + isMember: false, + isBlocked: false, + isPending: false, + receiveNotifications: false, + }, + { + id: '31', + createdAt: new Date(DATE), + userId: MAX_ID, + channelId: PRIVATE_GENERAL_CHANNEL_ID, + isOwner: true, + isModerator: false, + isMember: true, + isBlocked: false, + isPending: false, + receiveNotifications: false, + }, ]; diff --git a/api/migrations/seed/default/usersCommunities.js b/api/migrations/seed/default/usersCommunities.js index 2599911242..63f9993373 100644 --- a/api/migrations/seed/default/usersCommunities.js +++ b/api/migrations/seed/default/usersCommunities.js @@ -11,6 +11,7 @@ const { COMMUNITY_MODERATOR_USER_ID, SPECTRUM_COMMUNITY_ID, PAYMENTS_COMMUNITY_ID, + PRIVATE_COMMUNITY_ID, } = constants; module.exports = [ @@ -23,6 +24,7 @@ module.exports = [ isModerator: false, isMember: true, isBlocked: false, + isPending: false, receiveNotifications: true, reputation: 100, }, @@ -35,6 +37,7 @@ module.exports = [ isModerator: false, isMember: true, isBlocked: false, + isPending: false, receiveNotifications: true, reputation: 100, }, @@ -47,6 +50,7 @@ module.exports = [ isModerator: false, isMember: true, isBlocked: false, + isPending: false, receiveNotifications: true, reputation: 100, }, @@ -59,6 +63,7 @@ module.exports = [ isModerator: false, isMember: false, isBlocked: true, + isPending: false, receiveNotifications: true, reputation: 100, }, @@ -71,6 +76,7 @@ module.exports = [ isModerator: false, isMember: false, isBlocked: false, + isPending: false, receiveNotifications: false, reputation: 100, }, @@ -84,6 +90,7 @@ module.exports = [ isModerator: false, isMember: true, isBlocked: false, + isPending: false, receiveNotifications: true, reputation: 100, }, @@ -96,6 +103,7 @@ module.exports = [ isModerator: false, isMember: true, isBlocked: false, + isPending: false, receiveNotifications: true, reputation: 100, }, @@ -108,6 +116,7 @@ module.exports = [ isModerator: false, isMember: true, isBlocked: false, + isPending: false, receiveNotifications: true, reputation: 100, }, @@ -120,6 +129,7 @@ module.exports = [ isModerator: false, isMember: true, isBlocked: false, + isPending: false, receiveNotifications: true, reputation: 100, }, @@ -132,6 +142,7 @@ module.exports = [ isModerator: false, isMember: true, isBlocked: false, + isPending: false, receiveNotifications: true, reputation: 100, }, @@ -144,6 +155,7 @@ module.exports = [ isModerator: true, isMember: false, isBlocked: false, + isPending: false, receiveNotifications: true, reputation: 100, }, @@ -156,6 +168,34 @@ module.exports = [ isModerator: true, isMember: true, isBlocked: false, + isPending: false, + receiveNotifications: true, + reputation: 100, + }, + { + id: '14', + createdAt: new Date(DATE), + userId: MAX_ID, + communityId: PRIVATE_COMMUNITY_ID, + isOwner: true, + isModerator: false, + isMember: true, + isBlocked: false, + isPending: false, + receiveNotifications: true, + reputation: 100, + }, + + { + id: '15', + createdAt: new Date(DATE), + userId: BRIAN_ID, + communityId: PRIVATE_COMMUNITY_ID, + isOwner: false, + isModerator: false, + isMember: false, + isBlocked: false, + isPending: false, receiveNotifications: true, reputation: 100, }, diff --git a/api/migrations/seed/default/usersDirectMessageThreads.js b/api/migrations/seed/default/usersDirectMessageThreads.js index 58ecce7c0b..be3fa186f3 100644 --- a/api/migrations/seed/default/usersDirectMessageThreads.js +++ b/api/migrations/seed/default/usersDirectMessageThreads.js @@ -1,6 +1,6 @@ // @flow const constants = require('./constants'); -const { DATE, BRIAN_ID, MAX_ID, BRYN_ID } = constants; +const { DATE, BRIAN_ID, MAX_ID, BRYN_ID, PREVIOUS_MEMBER_USER_ID } = constants; module.exports = [ { @@ -30,4 +30,22 @@ module.exports = [ lastSeen: new Date(DATE), receiveNotifications: true, }, + { + id: '4', + createdAt: new Date(DATE), + userId: BRIAN_ID, + threadId: 'dm-2', + lastActive: new Date(DATE - 1), + lastSeen: null, + receiveNotifications: true, + }, + { + id: '5', + createdAt: new Date(DATE), + userId: PREVIOUS_MEMBER_USER_ID, + threadId: 'dm-2', + lastActive: new Date(DATE - 1), + lastSeen: new Date(DATE - 1), + receiveNotifications: true, + }, ]; diff --git a/api/migrations/seed/default/usersNotifications.js b/api/migrations/seed/default/usersNotifications.js new file mode 100644 index 0000000000..9833127f7e --- /dev/null +++ b/api/migrations/seed/default/usersNotifications.js @@ -0,0 +1,15 @@ +// @flow +const constants = require('./constants'); +const { DATE, BRIAN_ID } = constants; + +module.exports = [ + { + id: '1', + notificationId: '1', + createdAt: new Date(DATE), + userId: BRIAN_ID, + entityAddedAt: new Date(DATE + 1), + isRead: false, + isSeen: false, + }, +]; diff --git a/api/migrations/seed/default/usersSettings.js b/api/migrations/seed/default/usersSettings.js new file mode 100644 index 0000000000..c46e54c28b --- /dev/null +++ b/api/migrations/seed/default/usersSettings.js @@ -0,0 +1,33 @@ +// @flow + +module.exports = () => { + let settings = []; + for (let step = 0; step < 10; step++) { + settings.push({ + userId: step.toString(), + notifications: { + types: { + newMessageInThreads: { + email: true, + }, + newMention: { + email: true, + }, + newDirectMessage: { + email: true, + }, + newThreadCreated: { + email: true, + }, + dailyDigest: { + email: true, + }, + weeklyDigest: { + email: true, + }, + }, + }, + }); + } + return settings; +}; diff --git a/api/migrations/seed/generate.js b/api/migrations/seed/generate.js index 4d5bdf92b6..cfc4544152 100644 --- a/api/migrations/seed/generate.js +++ b/api/migrations/seed/generate.js @@ -181,7 +181,6 @@ const generateThread = (communityId, channelId, creatorId) => { communityId, isPublished: faker.random.boolean(), content, - attachments: [], type: 'DRAFTJS', lastActive: faker.date.between(createdAt, new Date()), edits: [ @@ -244,7 +243,6 @@ const generateMessage = (senderId, threadId, threadType) => { content: { body: casual.text(), }, - attachments: [], messageType: 'text', timestamp: faker.date.past(2), }; diff --git a/api/migrations/seed/index.js b/api/migrations/seed/index.js index 77f350f8b9..8bc67cf946 100644 --- a/api/migrations/seed/index.js +++ b/api/migrations/seed/index.js @@ -61,6 +61,7 @@ users.forEach(user => { debug('Generating channels...'); let channels = defaultChannels; communities.forEach(community => { + if (community.deletedAt) return; randomAmount({ max: 10 }, () => { channels.push(generateChannel(community.id)); }); @@ -78,6 +79,11 @@ generatedUsersChannels.map(elem => { debug('Generating threads...'); let threads = defaultThreads; channels.forEach(channel => { + const community = communities.find( + community => community.id === channel.communityId + ); + if (community.deletedAt) return; + randomAmount({ max: 10 }, () => { const creator = faker.random.arrayElement(users); const thread = generateThread(channel.communityId, channel.id, creator.id); @@ -149,7 +155,7 @@ messages.map(message => { debug('Connecting to db...'); // $FlowFixMe -const db = require('rethinkdbdash')({ +const db = require('rethinkhaberdashery')({ db: 'spectrum', }); @@ -229,5 +235,6 @@ Promise.all([ debug( 'Encountered error while inserting data (see below), please run yarn run db:drop and yarn run db:migrate to restore tables to original condition, then run this script again.' ); - debug(err); + console.error('❌ Error in job:\n'); + console.error(err); }); diff --git a/api/models/channel.js b/api/models/channel.js index 0889714cef..5b2cedd2c4 100644 --- a/api/models/channel.js +++ b/api/models/channel.js @@ -1,16 +1,40 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); import { sendChannelNotificationQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; import type { DBChannel } from 'shared/types'; -const getChannelsByCommunity = ( - communityId: string -): Promise> => { - return db +// reusable query parts -- begin +const channelsByCommunitiesQuery = (...communityIds: string[]) => + db .table('channels') - .getAll(communityId, { index: 'communityId' }) - .filter(channel => db.not(channel.hasFields('deletedAt'))) - .run(); + .getAll(...communityIds, { index: 'communityId' }) + .filter(channel => channel.hasFields('deletedAt').not()); + +const channelsByIdsQuery = (...channelIds: string[]) => + db + .table('channels') + .getAll(...channelIds) + .filter(channel => channel.hasFields('deletedAt').not()); + +const threadsByChannelsQuery = (...channelIds: string[]) => + channelsByIdsQuery(...channelIds) + .eqJoin('id', db.table('threads'), { index: 'channelId' }) + .map(row => row('right')) + .filter(thread => db.not(thread.hasFields('deletedAt'))); + +const membersByChannelsQuery = (...channelIds: string[]) => + channelsByIdsQuery(...channelIds) + .eqJoin('id', db.table('usersChannels'), { index: 'channelId' }) + .map(row => row('right')) + .filter({ isBlocked: false, isPending: false, isMember: true }); + +// reusable query parts -- end + +// prettier-ignore +const getChannelsByCommunity = (communityId: string): Promise> => { + return channelsByCommunitiesQuery(communityId).run(); }; /* @@ -18,13 +42,9 @@ const getChannelsByCommunity = ( from public channels. We use this function to return an array of channelIds that are public, and pass them into a getThreads function */ -const getPublicChannelsByCommunity = ( - communityId: string -): Promise> => { - return db - .table('channels') - .getAll(communityId, { index: 'communityId' }) - .filter(channel => db.not(channel.hasFields('deletedAt'))) +// prettier-ignore +const getPublicChannelsByCommunity = (communityId: string): Promise> => { + return channelsByCommunitiesQuery(communityId) .filter({ isPrivate: false }) .map(c => c('id')) .run(); @@ -37,14 +57,9 @@ const getPublicChannelsByCommunity = ( to a channelId. This array of IDs will be passed into a threads method which will only return threads in those channels */ -const getChannelsByUserAndCommunity = async ( - communityId: string, - userId: string -): Promise> => { - const channels = await db - .table('channels') - .getAll(communityId, { index: 'communityId' }) - .run(); +// prettier-ignore +const getChannelsByUserAndCommunity = async (communityId: string, userId: string): Promise> => { + const channels = await getChannelsByCommunity(communityId); const channelIds = channels.map(c => c.id); const publicChannels = channels.filter(c => !c.isPrivate).map(c => c.id); @@ -60,57 +75,51 @@ const getChannelsByUserAndCommunity = async ( const usersChannelsIds = usersChannels.map(c => c.channelId); const allPossibleChannels = [...publicChannels, ...usersChannelsIds]; - const distinct = allPossibleChannels.filter((x, i, a) => a.indexOf(x) == i); + const distinct = allPossibleChannels.filter((x, i, a) => a.indexOf(x) === i); return distinct; }; const getChannelsByUser = (userId: string): Promise> => { - return ( - db - .table('usersChannels') - // get all the user's channels - .getAll(userId, { index: 'userId' }) - // only return channels where the user is a member - .filter({ isMember: true }) - // get the channel objects for each channel - .eqJoin('channelId', db.table('channels')) - // get rid of unnecessary info from the usersChannels object on the left - .without({ left: ['id', 'channelId', 'userId', 'createdAt'] }) - // zip the tables - .zip() - // ensure we don't return any deleted channels - .filter(channel => db.not(channel.hasFields('deletedAt'))) - .run() - ); + return db + .table('usersChannels') + .getAll([userId, 'member'], [userId, 'moderator'], [userId, 'owner'], { + index: 'userIdAndRole', + }) + .eqJoin('channelId', db.table('channels')) + .without({ left: ['id', 'channelId', 'userId', 'createdAt'] }) + .zip() + .filter(channel => db.not(channel.hasFields('deletedAt'))) + .run(); }; -const getChannelBySlug = ( +const getChannelBySlug = async ( channelSlug: string, communitySlug: string -): Promise => { +): Promise => { + const [communityId] = await db + .table('communities') + .getAll(communitySlug, { index: 'slug' })('id') + .run(); + + if (!communityId) return null; + return db .table('channels') + .getAll(communityId, { index: 'communityId' }) .filter(channel => channel('slug') .eq(channelSlug) .and(db.not(channel.hasFields('deletedAt'))) ) - .eqJoin('communityId', db.table('communities')) - .filter({ right: { slug: communitySlug } }) .run() - .then(result => { - if (result && result[0]) { - return result[0].left; - } + .then(res => { + if (Array.isArray(res) && res.length > 0) return res[0]; return null; }); }; -const getChannelById = (id: string) => { - return db - .table('channels') - .get(id) - .run(); +const getChannelById = async (id: string) => { + return (await channelsByIdsQuery(id).run())[0] || null; }; type GetChannelByIdArgs = {| @@ -125,28 +134,7 @@ type GetChannelBySlugArgs = {| export type GetChannelArgs = GetChannelByIdArgs | GetChannelBySlugArgs; const getChannels = (channelIds: Array): Promise> => { - return db - .table('channels') - .getAll(...channelIds) - .filter(channel => db.not(channel.hasFields('deletedAt'))) - .run(); -}; - -const getChannelMetaData = (channelId: string): Promise> => { - const getThreadCount = db - .table('threads') - .getAll(channelId, { index: 'channelId' }) - .count() - .run(); - - const getMemberCount = db - .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ isBlocked: false, isPending: false }) - .count() - .run(); - - return Promise.all([getThreadCount, getMemberCount]); + return channelsByIdsQuery(...channelIds).run(); }; type GroupedCount = { @@ -154,24 +142,17 @@ type GroupedCount = { reduction: number, }; -const getChannelsThreadCounts = ( - channelIds: Array -): Promise> => { - return db - .table('threads') - .getAll(...channelIds, { index: 'channelId' }) +// prettier-ignore +const getChannelsThreadCounts = (channelIds: Array): Promise> => { + return threadsByChannelsQuery(...channelIds) .group('channelId') .count() .run(); }; -const getChannelsMemberCounts = ( - channelIds: Array -): Promise> => { - return db - .table('usersChannels') - .getAll(...channelIds, { index: 'channelId' }) - .filter({ isBlocked: false, isPending: false, isMember: true }) +// prettier-ignore +const getChannelsMemberCounts = (channelIds: Array): Promise> => { + return membersByChannelsQuery(...channelIds) .group('channelId') .count() .run(); @@ -198,12 +179,10 @@ export type EditChannelInput = { }, }; -const createChannel = ( - { - input: { communityId, name, slug, description, isPrivate, isDefault }, - }: CreateChannelInput, - userId: string -): Promise => { +// prettier-ignore +const createChannel = ({ input }: CreateChannelInput, userId: string): Promise => { + const { communityId, name, slug, description, isPrivate, isDefault } = input; + return db .table('channels') .insert( @@ -215,12 +194,19 @@ const createChannel = ( slug, isPrivate, isDefault: isDefault ? true : false, + memberCount: 0, }, { returnChanges: true } ) .run() .then(result => result.changes[0].new_val) .then(channel => { + trackQueue.add({ + userId: userId, + event: events.CHANNEL_CREATED, + context: { channelId: channel.id }, + }); + // only trigger a new channel notification is the channel is public if (!channel.isPrivate) { sendChannelNotificationQueue.add({ channel, userId }); @@ -230,10 +216,8 @@ const createChannel = ( }); }; -const createGeneralChannel = ( - communityId: string, - userId: string -): Promise => { +// prettier-ignore +const createGeneralChannel = (communityId: string, userId: string): Promise => { return createChannel( { input: { @@ -249,9 +233,10 @@ const createGeneralChannel = ( ); }; -const editChannel = async ({ - input: { name, slug, description, isPrivate, channelId }, -}: EditChannelInput): Promise => { +// prettier-ignore +const editChannel = async ({ input }: EditChannelInput, userId: string): Promise => { + const { name, slug, description, isPrivate, channelId } = input; + const channelRecord = await db .table('channels') .get(channelId) @@ -273,11 +258,26 @@ const editChannel = async ({ .then(result => { // if an update happened if (result.replaced === 1) { + trackQueue.add({ + userId, + event: events.CHANNEL_EDITED, + context: { channelId: channelId }, + }); + return result.changes[0].new_val; } // an update was triggered from the client, but no data was changed if (result.unchanged === 1) { + trackQueue.add({ + userId, + event: events.CHANNEL_EDITED_FAILED, + context: { channelId: channelId }, + properties: { + reason: 'no changes', + }, + }); + return result.changes[0].old_val; } @@ -285,16 +285,13 @@ const editChannel = async ({ }); }; -/* - We delete data non-destructively, meaning the record does not get cleared - from the db. -*/ -const deleteChannel = (channelId: string): Promise => { +const deleteChannel = (channelId: string, userId: string): Promise => { return db .table('channels') .get(channelId) .update( { + deletedBy: userId, deletedAt: new Date(), slug: db.uuid(), }, @@ -303,48 +300,164 @@ const deleteChannel = (channelId: string): Promise => { nonAtomic: true, } ) - .run(); + .run() + .then(() => { + trackQueue.add({ + userId, + event: events.CHANNEL_DELETED, + context: { channelId }, + }); + }); }; -const getChannelMemberCount = (channelId: string): number => { +// prettier-ignore +const archiveChannel = (channelId: string, userId: string): Promise => { return db .table('channels') - .get(channelId)('members') - .count() + .get(channelId) + .update({ archivedAt: new Date() }, { returnChanges: 'always' }) + .run() + .then(result => { + trackQueue.add({ + userId: userId, + event: events.CHANNEL_ARCHIVED, + context: { channelId }, + }); + + return result.changes[0].new_val || result.changes[0].old_val; + }); +}; + +// prettier-ignore +const restoreChannel = (channelId: string, userId: string): Promise => { + return db + .table('channels') + .get(channelId) + .update({ archivedAt: db.literal() }, { returnChanges: 'always' }) + .run() + .then(result => { + trackQueue.add({ + userId, + event: events.CHANNEL_RESTORED, + context: { channelId }, + }); + + return result.changes[0].new_val || result.changes[0].old_val; + }); +}; + +// prettier-ignore +const archiveAllPrivateChannels = async (communityId: string, userId: string) => { + const channels = await db + .table('channels') + .getAll(communityId, { index: 'communityId' }) + .filter({ isPrivate: true }) + .run(); + + if (!channels || channels.length === 0) return; + + const trackingPromises = channels.map(channel => { + return trackQueue.add({ + userId, + event: events.CHANNEL_ARCHIVED, + context: { channelId: channel.id }, + }); + }); + + const archivePromise = db + .table('channels') + .getAll(communityId, { index: 'communityId' }) + .filter({ isPrivate: true }) + .update({ archivedAt: new Date() }) .run(); + + return await Promise.all([...trackingPromises, archivePromise]); }; -const archiveChannel = (channelId: string) => { +const incrementMemberCount = (channelId: string): Promise => { return db .table('channels') .get(channelId) - .update({ archivedAt: new Date() }, { returnChanges: 'always' }) + .update( + { + memberCount: db + .row('memberCount') + .default(0) + .add(1), + }, + { returnChanges: true } + ) .run() .then(result => result.changes[0].new_val || result.changes[0].old_val); }; -const restoreChannel = (channelId: string) => { +const decrementMemberCount = (channelId: string): Promise => { return db .table('channels') .get(channelId) - .update({ archivedAt: db.literal() }, { returnChanges: 'always' }) + .update( + { + memberCount: db + .row('memberCount') + .default(1) + .sub(1), + }, + { returnChanges: true } + ) .run() .then(result => result.changes[0].new_val || result.changes[0].old_val); }; -const archiveAllPrivateChannels = (communityId: string) => { +const setMemberCount = ( + channelId: string, + value: number +): Promise => { return db .table('channels') - .getAll(communityId, { index: 'communityId' }) - .filter({ isPrivate: true }) - .update({ archivedAt: new Date() }) + .get(channelId) + .update( + { + memberCount: value, + }, + { returnChanges: true } + ) + .run() + .then(result => result.changes[0].new_val || result.changes[0].old_val); +}; + +const getChannelsOnlineMemberCounts = (channelIds: Array) => { + return db + .table('usersChannels') + .getAll(...channelIds, { + index: 'channelId', + }) + .filter({ isBlocked: false, isMember: true }) + .pluck(['channelId', 'userId']) + .eqJoin('userId', db.table('users')) + .pluck('left', { right: ['lastSeen', 'isOnline'] }) + .zip() + .filter(rec => + rec('isOnline') + .eq(true) + .or( + rec('lastSeen') + .toEpochTime() + .ge( + db + .now() + .toEpochTime() + .sub(86400) + ) + ) + ) + .group('channelId') + .count() .run(); }; module.exports = { getChannelBySlug, getChannelById, - getChannelMetaData, getChannelsByUser, getChannelsByCommunity, getPublicChannelsByCommunity, @@ -353,11 +466,20 @@ module.exports = { createGeneralChannel, editChannel, deleteChannel, - getChannelMemberCount, getChannelsMemberCounts, getChannelsThreadCounts, getChannels, archiveChannel, restoreChannel, archiveAllPrivateChannels, + incrementMemberCount, + decrementMemberCount, + setMemberCount, + getChannelsOnlineMemberCounts, + __forQueryTests: { + channelsByCommunitiesQuery, + channelsByIdsQuery, + threadsByChannelsQuery, + membersByChannelsQuery, + }, }; diff --git a/api/models/channelSettings.js b/api/models/channelSettings.js index eab1fea066..6716ada3ee 100644 --- a/api/models/channelSettings.js +++ b/api/models/channelSettings.js @@ -1,32 +1,49 @@ // @flow -const { db } = require('./db'); -import type { DBChannelSettings } from 'shared/types'; +const { db } = require('shared/db'); +import type { DBChannelSettings, DBChannel } from 'shared/types'; import { getChannelById } from './channel'; -import shortid from 'shortid'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import uuidv4 from 'uuid/v4'; const defaultSettings = { joinSettings: { tokenJoinEnabled: false, message: null, }, + slackSettings: { + botLinks: { + threadCreated: null, + }, + }, }; -export const getChannelSettings = (id: string) => { - return db +// prettier-ignore +export const getOrCreateChannelSettings = async (channelId: string): Promise => { + const settings = await db .table('channelSettings') - .getAll(id, { index: 'channelId' }) - .run() - .then(data => { - if (!data || data.length === 0) { - return defaultSettings; - } - return data[0]; - }); + .getAll(channelId, { index: 'channelId' }) + .run(); + + if (!settings || settings.length === 0) { + return await db + .table('channelSettings') + .insert( + { + ...defaultSettings, + channelId, + }, + { returnChanges: true } + ) + .run() + .then(results => results.changes[0].new_val); + } + + return settings[0]; }; -export const getChannelsSettings = ( - channelIds: Array -): Promise => { +// prettier-ignore +export const getChannelsSettings = (channelIds: Array): Promise => { return db .table('channelSettings') .getAll(...channelIds, { index: 'channelId' }) @@ -50,57 +67,118 @@ export const getChannelsSettings = ( }); }; -export const createChannelSettings = (id: string) => { +export const enableChannelTokenJoin = (channelId: string, userId: string) => { return db .table('channelSettings') - .insert({ - channelId: id, + .getAll(channelId, { index: 'channelId' }) + .update({ joinSettings: { - token: null, - tokenJoinEnabled: false, + tokenJoinEnabled: true, + token: uuidv4(), }, }) .run() - .then(async () => await getChannelById(id)); + .then(async () => { + trackQueue.add({ + userId, + event: events.CHANNEL_JOIN_TOKEN_ENABLED, + context: { channelId }, + }); + + return await getChannelById(channelId); + }); }; -export const enableChannelTokenJoin = (id: string) => { +export const disableChannelTokenJoin = (channelId: string, userId: string) => { return db .table('channelSettings') - .getAll(id, { index: 'channelId' }) + .getAll(channelId, { index: 'channelId' }) .update({ joinSettings: { - tokenJoinEnabled: true, - token: shortid.generate(), + tokenJoinEnabled: false, + token: null, }, }) .run() - .then(async () => await getChannelById(id)); + .then(async () => { + trackQueue.add({ + userId, + event: events.CHANNEL_JOIN_TOKEN_DISABLED, + context: { channelId }, + }); + + return await getChannelById(channelId); + }); }; -export const disableChannelTokenJoin = (id: string) => { +export const resetChannelJoinToken = (channelId: string, userId: string) => { return db .table('channelSettings') - .getAll(id, { index: 'channelId' }) + .getAll(channelId, { index: 'channelId' }) .update({ joinSettings: { - tokenJoinEnabled: false, - token: null, + token: uuidv4(), }, }) .run() - .then(async () => await getChannelById(id)); + .then(async () => { + trackQueue.add({ + userId, + event: events.CHANNEL_JOIN_TOKEN_RESET, + context: { channelId }, + }); + + return await getChannelById(channelId); + }); }; -export const resetChannelJoinToken = (id: string) => { +type UpdateInput = { + channelId: string, + slackChannelId: ?string, + eventType: 'threadCreated', +}; + +// prettier-ignore +export const updateChannelSlackBotLinks = async ({ channelId, slackChannelId, eventType }: UpdateInput, userId: string): Promise => { + const settings: DBChannelSettings = await getOrCreateChannelSettings( + channelId + ); + + let newSettings; + if (!settings.slackSettings) { + settings.slackSettings = { + botLinks: { + [eventType]: + slackChannelId && slackChannelId.length > 0 ? slackChannelId : null, + }, + }; + newSettings = Object.assign({}, settings); + } else { + newSettings = Object.assign({}, settings, { + slackSettings: { + botLinks: { + [eventType]: + slackChannelId && slackChannelId.length > 0 ? slackChannelId : null, + }, + }, + }); + } + return db .table('channelSettings') - .getAll(id, { index: 'channelId' }) + .getAll(channelId, { index: 'channelId' }) .update({ - joinSettings: { - token: shortid.generate(), - }, + ...newSettings, }) .run() - .then(async () => await getChannelById(id)); + .then(async () => { + + trackQueue.add({ + userId, + event: events.CHANNEL_SLACK_BOT_LINK_UPDATED, + context: { channelId }, + }); + + return await getChannelById(channelId) + }); }; diff --git a/api/models/community.js b/api/models/community.js index 6597de3557..db4920c35e 100644 --- a/api/models/community.js +++ b/api/models/community.js @@ -1,14 +1,17 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); +import intersection from 'lodash.intersection'; import { parseRange } from './utils'; -import { uploadImage } from '../utils/s3'; +import { uploadImage } from '../utils/file-storage'; import getRandomDefaultPhoto from '../utils/get-random-default-photo'; import { sendNewCommunityWelcomeEmailQueue, _adminSendCommunityCreatedEmailQueue, + searchQueue, + trackQueue, } from 'shared/bull/queues'; -import { removeMemberInChannel } from './usersChannels'; -import type { DBCommunity } from 'shared/types'; +import { events } from 'shared/analytics'; +import type { DBCommunity, DBUser } from 'shared/types'; import type { Timeframe } from './utils'; export const getCommunityById = (id: string): Promise => { @@ -22,9 +25,8 @@ export const getCommunityById = (id: string): Promise => { }); }; -export const getCommunities = ( - communityIds: Array -): Promise> => { +// prettier-ignore +export const getCommunities = (communityIds: Array): Promise> => { return db .table('communities') .getAll(...communityIds) @@ -32,9 +34,8 @@ export const getCommunities = ( .run(); }; -export const getCommunitiesBySlug = ( - slugs: Array -): Promise> => { +// prettier-ignore +export const getCommunitiesBySlug = (slugs: Array): Promise> => { return db .table('communities') .getAll(...slugs, { index: 'slug' }) @@ -42,16 +43,25 @@ export const getCommunitiesBySlug = ( .run(); }; -export const getCommunitiesByUser = ( - userId: string -): Promise> => { +export const getCommunityBySlug = (slug: string): Promise => { + return db + .table('communities') + .getAll(slug, { index: 'slug' }) + .filter(community => db.not(community.hasFields('deletedAt'))) + .run() + .then(results => { + if (!results || results.length === 0) return null; + return results[0]; + }); +}; + +// prettier-ignore +export const getCommunitiesByUser = (userId: string): Promise> => { return ( db .table('usersCommunities') // get all the user's communities - .getAll(userId, { index: 'userId' }) - // only return communities the user is a member of - .filter({ isMember: true }) + .getAll([userId, true], { index: 'userIdAndIsMember' }) // get the community objects for each community .eqJoin('communityId', db.table('communities')) // get rid of unnecessary info from the usersCommunities object on the left @@ -64,6 +74,70 @@ export const getCommunitiesByUser = ( ); }; +// prettier-ignore +export const getVisibleCommunitiesByUser = async (evaluatingUserId: string, currentUserId: string) => { + const evaluatingUserMemberships = await db + .table('usersCommunities') + // get all the user's communities + .getAll([evaluatingUserId, true], { index: 'userIdAndIsMember' }) + // get the community objects for each community + .eqJoin('communityId', db.table('communities')) + // get rid of unnecessary info from the usersCommunities object on the left + .without({ left: ['id', 'communityId', 'userId', 'createdAt'] }) + // zip the tables + .zip() + // ensure we don't return any deleted communities + .filter(community => db.not(community.hasFields('deletedAt'))) + .run() + + const currentUserMemberships = await db + .table('usersCommunities') + // get all the user's communities + .getAll([currentUserId, true], { index: 'userIdAndIsMember' }) + // get the community objects for each community + .eqJoin('communityId', db.table('communities')) + // get rid of unnecessary info from the usersCommunities object on the left + .without({ left: ['id', 'communityId', 'userId', 'createdAt'] }) + // zip the tables + .zip() + // ensure we don't return any deleted communities + .filter(community => db.not(community.hasFields('deletedAt'))) + .run() + + const evaluatingUserCommunityIds = evaluatingUserMemberships.map(community => community.id) + const currentUserCommunityIds = currentUserMemberships.map(community => community.id) + const publicCommunityIds = evaluatingUserMemberships + .filter(community => !community.isPrivate) + .map(community => community.id) + + const overlappingMemberships = intersection(evaluatingUserCommunityIds, currentUserCommunityIds) + const allVisibleCommunityIds = [...publicCommunityIds, ...overlappingMemberships] + const distinctCommunityIds = allVisibleCommunityIds.filter((x, i, a) => a.indexOf(x) === i) + + return await db + .table('communities') + .getAll(...distinctCommunityIds) + .run() +} + +export const getPublicCommunitiesByUser = async (userId: string) => { + return await db + .table('usersCommunities') + // get all the user's communities + .getAll([userId, true], { index: 'userIdAndIsMember' }) + // get the community objects for each community + .eqJoin('communityId', db.table('communities')) + // only return public community ids + .filter(row => row('right')('isPrivate').eq(false)) + // get rid of unnecessary info from the usersCommunities object on the left + .without({ left: ['id', 'communityId', 'userId', 'createdAt'] }) + // zip the tables + .zip() + // ensure we don't return any deleted communities + .filter(community => db.not(community.hasFields('deletedAt'))) + .run(); +}; + export const getCommunitiesChannelCounts = (communityIds: Array) => { return db .table('channels') @@ -77,38 +151,41 @@ export const getCommunitiesChannelCounts = (communityIds: Array) => { export const getCommunitiesMemberCounts = (communityIds: Array) => { return db .table('usersCommunities') - .getAll(...communityIds, { index: 'communityId' }) - .filter({ isBlocked: false, isMember: true }) + .getAll(...communityIds.map(id => [id, true]), { + index: 'communityIdAndIsMember', + }) .group('communityId') .count() .run(); }; -export const getCommunityMetaData = ( - communityId: string -): Promise> => { - const getChannelCount = db - .table('channels') - .getAll(communityId, { index: 'communityId' }) - .filter(channel => db.not(channel.hasFields('deletedAt'))) - .count() - .run(); - - const getMemberCount = db - .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter({ isBlocked: false, isMember: true }) - .count() - .run(); - - return Promise.all([getChannelCount, getMemberCount]); -}; - -export const getMemberCount = (communityId: string): Promise => { +export const getCommunitiesOnlineMemberCounts = ( + communityIds: Array +) => { return db .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter({ isBlocked: false, isMember: true }) + .getAll(...communityIds.map(id => [id, true]), { + index: 'communityIdAndIsMember', + }) + .pluck(['communityId', 'userId']) + .eqJoin('userId', db.table('users')) + .pluck('left', { right: ['lastSeen', 'isOnline'] }) + .zip() + .filter(rec => + rec('isOnline') + .eq(true) + .or( + rec('lastSeen') + .toEpochTime() + .ge( + db + .now() + .toEpochTime() + .sub(86400) + ) + ) + ) + .group('communityId') .count() .run(); }; @@ -121,6 +198,7 @@ export type CreateCommunityInput = { website: string, file: Object, coverFile: Object, + isPrivate: boolean, }, }; @@ -132,19 +210,15 @@ export type EditCommunityInput = { website: string, file: Object, coverFile: Object, + coverPhoto: string, communityId: string, }, }; -// TODO(@mxstbr): Use DBUser type -type CommunityCreator = Object; +// prettier-ignore +export const createCommunity = ({ input }: CreateCommunityInput, user: DBUser): Promise => { + const { name, slug, description, website, file, coverFile, isPrivate } = input -export const createCommunity = ( - { - input: { name, slug, description, website, file, coverFile }, - }: CreateCommunityInput, - user: CommunityCreator -): Promise => { return db .table('communities') .insert( @@ -159,13 +233,26 @@ export const createCommunity = ( modifiedAt: null, creatorId: user.id, administratorEmail: user.email, - stripeCustomerId: null, + isPrivate, + memberCount: 0, }, { returnChanges: true } ) .run() .then(result => result.changes[0].new_val) .then(community => { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_CREATED, + context: { communityId: community.id }, + }); + + searchQueue.add({ + id: community.id, + type: 'community', + event: 'created' + }) + // send a welcome email to the community creator sendNewCommunityWelcomeEmailQueue.add({ user, community }); // email brian with info about the community and owner @@ -199,8 +286,8 @@ export const createCommunity = ( if (file || coverFile) { if (file && !coverFile) { const { coverPhoto } = getRandomDefaultPhoto(); - return uploadImage(file, 'communities', community.id).then( - profilePhoto => { + return uploadImage(file, 'communities', community.id) + .then(profilePhoto => { return ( db .table('communities') @@ -227,12 +314,14 @@ export const createCommunity = ( } }) ); - } - ); + }) + .catch(err => { + console.error(err); + }); } else if (!file && coverFile) { const { profilePhoto } = getRandomDefaultPhoto(); - return uploadImage(coverFile, 'communities', community.id).then( - coverPhoto => { + return uploadImage(coverFile, 'communities', community.id) + .then(coverPhoto => { // update the community with the profilePhoto return ( db @@ -262,15 +351,23 @@ export const createCommunity = ( return null; }) ); - } - ); + }) + .catch(err => { + console.error(err); + }); } else if (file && coverFile) { const uploadFile = file => { - return uploadImage(file, 'communities', community.id); + return uploadImage(file, 'communities', community.id).catch(err => { + console.error(err); + }); }; const uploadCoverFile = coverFile => { - return uploadImage(coverFile, 'communities', community.id); + return uploadImage(coverFile, 'communities', community.id).catch( + err => { + console.error(err); + } + ); }; return Promise.all([ @@ -311,9 +408,11 @@ export const createCommunity = ( }); }; -export const editCommunity = ({ - input: { name, slug, description, website, file, coverFile, communityId }, -}: EditCommunityInput): Promise => { +// prettier-ignore +export const editCommunity = ({ input }: EditCommunityInput, userId: string): Promise => { + const { name, slug, description, website, file, coverFile, communityId } = input + let { coverPhoto } = input + return db .table('communities') .get(communityId) @@ -328,21 +427,44 @@ export const editCommunity = ({ }); }) .then(community => { + searchQueue.add({ + id: community.id, + type: 'community', + event: 'edited' + }) + // if no file was uploaded, update the community with new string values if (!file && !coverFile) { + // if the coverPhoto was deleted, reset to default + if (!coverPhoto) { + ({ coverPhoto } = getRandomDefaultPhoto()) + } return db .table('communities') .get(communityId) - .update({ ...community }, { returnChanges: 'always' }) - .run() + .update({ ...community, coverPhoto }, { returnChanges: 'always' }) + .run() .then(result => { // if an update happened if (result.replaced === 1) { + trackQueue.add({ + userId, + event: events.COMMUNITY_EDITED, + context: { communityId } + }) return result.changes[0].new_val; } // an update was triggered from the client, but no data was changed if (result.unchanged === 1) { + trackQueue.add({ + userId, + event: events.COMMUNITY_EDITED_FAILED, + context: { communityId }, + properties: { + reason: 'no changes' + } + }) return result.changes[0].old_val; } }); @@ -350,6 +472,10 @@ export const editCommunity = ({ if (file || coverFile) { if (file && !coverFile) { + // if the coverPhoto was deleted, reset to default + if (!coverPhoto) { + ({ coverPhoto } = getRandomDefaultPhoto()) + } return uploadImage(file, 'communities', community.id).then( profilePhoto => { // update the community with the profilePhoto @@ -361,6 +487,7 @@ export const editCommunity = ({ { ...community, profilePhoto, + coverPhoto }, { returnChanges: 'always' } ) @@ -458,23 +585,14 @@ export const editCommunity = ({ }); }; -/* - We delete data non-destructively, meaning the record does not get cleared - from the db. Instead, we set a 'deleted' field on the object with a value - of the current time on the db. - - We set the value as a timestamp so that in the future we have option value - to perform actions like: - - permanantely delete records that were deleted > X days ago - - run logs for deletions over time - - etc -*/ -export const deleteCommunity = (communityId: string): Promise => { +// prettier-ignore +export const deleteCommunity = (communityId: string, userId: string): Promise => { return db .table('communities') .get(communityId) .update( { + deletedBy: userId, deletedAt: new Date(), slug: db.uuid(), }, @@ -483,13 +601,24 @@ export const deleteCommunity = (communityId: string): Promise => { nonAtomic: true, } ) - .run(); + .run() + .then(() => { + trackQueue.add({ + userId, + event: events.COMMUNITY_DELETED, + context: { communityId }, + }); + + searchQueue.add({ + id: communityId, + type: 'community', + event: 'deleted' + }) + }); }; -export const setPinnedThreadInCommunity = ( - communityId: string, - value: string -): Promise => { +// prettier-ignore +export const setPinnedThreadInCommunity = (communityId: string, value: string, userId: string): Promise => { return db .table('communities') .get(communityId) @@ -500,39 +629,22 @@ export const setPinnedThreadInCommunity = ( { returnChanges: 'always' } ) .run() - .then(result => result.changes[0].new_val); -}; + .then(result => { + // prettier-ignore + const event = value ? events.THREAD_PINNED : events.THREAD_UNPINNED; -export const unsubscribeFromAllChannelsInCommunity = ( - communityId: string, - userId: string -): Promise> => { - return db - .table('channels') - .getAll(communityId, { index: 'communityId' }) - .run() - .then(channels => { - return channels.map(channel => removeMemberInChannel(channel.id, userId)); - }); -}; + trackQueue.add({ + userId, + event: event, + context: { threadId: value }, + }); -export const userIsMemberOfCommunity = ( - communityId: string, - userId: string -): Promise => { - return db - .table('communities') - .get(communityId) - .run() - .then(community => { - return community.members.indexOf(userId) > -1; + return result.changes[0].new_val }); }; -export const userIsMemberOfAnyChannelInCommunity = ( - communityId: string, - userId: string -): Promise => { +// prettier-ignore +export const userIsMemberOfAnyChannelInCommunity = (communityId: string, userId: string): Promise => { return db('spectrum') .table('channels') .getAll(communityId, { index: 'communityId' }) @@ -541,10 +653,7 @@ export const userIsMemberOfAnyChannelInCommunity = ( .filter({ userId }) .pluck('isMember') .run() - .then(channels => { - // if any of the channels return true for isMember, we return true - return channels.some(channel => channel.isMember); - }); + .then(channels => channels.some(channel => channel.isMember)); }; export const getRecentCommunities = (): Array => { @@ -556,32 +665,6 @@ export const getRecentCommunities = (): Array => { .run(); }; -export const getCommunitiesBySearchString = ( - string: string, - amount: number -): Promise> => { - return db - .table('communities') - .filter(community => community.coerceTo('string').match(`(?i)${string}`)) - .filter(community => db.not(community.hasFields('deletedAt'))) - .limit(amount) - .run(); -}; - -// TODO(@mxstbr): Replace Array with Array -export const searchThreadsInCommunity = ( - channels: Array, - searchString: string -): Promise> => { - return db - .table('threads') - .getAll(...channels, { index: 'channelId' }) - .filter(thread => thread.coerceTo('string').match(`(?i)${searchString}`)) - .filter(thread => db.not(thread.hasFields('deletedAt'))) - .orderBy(db.desc('lastActive')) - .run(); -}; - export const getThreadCount = (communityId: string) => { return db .table('threads') @@ -623,33 +706,44 @@ export const getCommunityGrowth = async ( }; }; -export const setCommunityPendingAdministratorEmail = ( - communityId: string, - pendingAdministratorEmail: string -): Promise => { +// prettier-ignore +export const setCommunityPendingAdministratorEmail = (communityId: string, email: string, userId: string): Promise => { return db .table('communities') .get(communityId) .update({ - pendingAdministratorEmail, + pendingAdministratorEmail: email, }) .run() - .then(() => getCommunityById(communityId)); + .then(async () => { + trackQueue.add({ + userId, + event: events.COMMUNITY_ADMINISTRATOR_EMAIL_ADDED, + context: { communityId }, + }); + + return await getCommunityById(communityId) + }); }; -export const updateCommunityAdministratorEmail = ( - communityId: string, - administratorEmail: string -): Promise => { +// prettier-ignore +export const updateCommunityAdministratorEmail = (communityId: string, email: string, userId: string): Promise => { return db .table('communities') .get(communityId) .update({ - administratorEmail, + administratorEmail: email, pendingAdministratorEmail: db.literal(), }) .run() - .then(() => getCommunityById(communityId)); + .then(async () => { + trackQueue.add({ + userId, + event: events.COMMUNITY_ADMINISTRATOR_EMAIL_VERIFIED, + context: { communityId }, + }); + return await getCommunityById(communityId) + }); }; export const resetCommunityAdministratorEmail = (communityId: string) => { @@ -663,51 +757,57 @@ export const resetCommunityAdministratorEmail = (communityId: string) => { .run(); }; -export const setStripeCustomerId = ( - communityId: string, - stripeCustomerId: string +export const incrementMemberCount = ( + communityId: string ): Promise => { return db .table('communities') .get(communityId) .update( { - stripeCustomerId, + memberCount: db + .row('memberCount') + .default(0) + .add(1), }, - { - returnChanges: 'always', - } + { returnChanges: true } ) .run() .then(result => result.changes[0].new_val || result.changes[0].old_val); }; -export const disablePaidFeatureFlags = (communityId: string) => { +export const decrementMemberCount = ( + communityId: string +): Promise => { return db .table('communities') .get(communityId) - .update({ - analyticsEnabled: false, - prioritySupportEnabled: false, - }) - .run(); + .update( + { + memberCount: db + .row('memberCount') + .default(1) + .sub(1), + }, + { returnChanges: true } + ) + .run() + .then(result => result.changes[0].new_val || result.changes[0].old_val); }; -export const updateCommunityPaidFeature = ( +export const setMemberCount = ( communityId: string, - feature: string, - value: boolean -) => { - const obj = { [feature]: value }; + value: number +): Promise => { return db .table('communities') .get(communityId) - .update(obj, { returnChanges: 'always' }) + .update( + { + memberCount: value, + }, + { returnChanges: true } + ) .run() - .then(result => { - if (result && result.changes.length > 0) { - return result.changes[0].new_val || result.changes[0].old_val; - } - return { id: communityId }; - }); + .then(result => result.changes[0].new_val || result.changes[0].old_val); }; diff --git a/api/models/communitySettings.js b/api/models/communitySettings.js index 201247c77c..f71ae32d41 100644 --- a/api/models/communitySettings.js +++ b/api/models/communitySettings.js @@ -1,16 +1,61 @@ // @flow -const { db } = require('./db'); -import type { DBCommunitySettings } from 'shared/types'; +const { db } = require('shared/db'); +import type { DBCommunitySettings, DBCommunity } from 'shared/types'; import { getCommunityById } from './community'; +import uuidv4 from 'uuid/v4'; +import axios from 'axios'; +import { decryptString } from 'shared/encryption'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; const defaultSettings = { brandedLogin: { isEnabled: false, message: null, }, + slackSettings: { + connectedAt: null, + connectedBy: null, + teamName: null, + teamId: null, + scope: null, + token: null, + invitesSentAt: null, + invitesMemberCount: null, + invitesCustomMessage: null, + }, + joinSettings: { + tokenJoinEnabled: false, + token: null, + }, +}; + +// prettier-ignore +export const getOrCreateCommunitySettings = async (communityId: string): Promise => { + const settings = await db + .table('communitySettings') + .getAll(communityId, { index: 'communityId' }) + .run(); + + if (!settings || settings.length === 0) { + return await db + .table('communitySettings') + .insert( + { + ...defaultSettings, + communityId, + }, + { returnChanges: true } + ) + .run() + .then(results => results.changes[0].new_val); + } + + return settings[0]; }; -export const getCommunitySettings = (id: string) => { +// prettier-ignore +export const getCommunitySettings = (id: string): Promise => { return db .table('communitySettings') .getAll(id, { index: 'communityId' }) @@ -23,19 +68,19 @@ export const getCommunitySettings = (id: string) => { }); }; -export const getCommunitiesSettings = ( - communityIds: Array -): Promise => { +// prettier-ignore +export const getCommunitiesSettings = (communityIds: Array): Promise => { return db .table('communitySettings') .getAll(...communityIds, { index: 'communityId' }) .run() .then(data => { - if (!data || data.length === 0) + if (!data || data.length === 0) { return Array.from({ length: communityIds.length }, (_, index) => ({ ...defaultSettings, communityId: communityIds[index], })); + } if (data.length === communityIds.length) { return data.map( @@ -50,70 +95,361 @@ export const getCommunitiesSettings = ( } if (data.length < communityIds.length) { - return communityIds.map(community => { - const record = data.find(o => o.communityId === community); + return communityIds.map(communityId => { + const record = data.find(o => o.communityId === communityId); + if (record) return record; + return { + ...defaultSettings, + communityId, + }; + }); + } + + if (data.length > communityIds.length) { + return communityIds.map(communityId => { + const record = data.find(o => o.communityId === communityId); if (record) return record; return { ...defaultSettings, - communityId: community, + communityId, }; }); } }); }; -export const createCommunitySettings = (id: string) => { +// prettier-ignore +export const createCommunitySettings = (communityId: string): Promise => { return db .table('communitySettings') .insert({ - communityId: id, - brandedLogin: { - isEnabled: false, - message: null, - }, + communityId, + ...defaultSettings }) .run() - .then(async () => await getCommunityById(id)); + .then(async () => await getCommunityById(communityId)); }; -export const enableCommunityBrandedLogin = (id: string) => { +// prettier-ignore +export const enableCommunityBrandedLogin = (communityId: string, userId: string): Promise => { return db .table('communitySettings') - .getAll(id, { index: 'communityId' }) + .getAll(communityId, { index: 'communityId' }) .update({ brandedLogin: { isEnabled: true, }, }) .run() - .then(async () => await getCommunityById(id)); + .then(async () => { + trackQueue.add({ + userId, + event: events.COMMUNITY_BRANDED_LOGIN_ENABLED, + context: { communityId } + }) + return await getCommunityById(communityId) + }); }; -export const disableCommunityBrandedLogin = (id: string) => { +// prettier-ignore +export const disableCommunityBrandedLogin = (communityId: string, userId: string): Promise => { return db .table('communitySettings') - .getAll(id, { index: 'communityId' }) + .getAll(communityId, { index: 'communityId' }) .update({ brandedLogin: { isEnabled: false, }, }) .run() - .then(async () => await getCommunityById(id)); + .then(async () => { + trackQueue.add({ + userId, + event: events.COMMUNITY_BRANDED_LOGIN_DISABLED, + context: { communityId } + }) + return await getCommunityById(communityId) + }); }; -export const updateCommunityBrandedLoginMessage = ( - id: string, - message: ?string -) => { +// prettier-ignore +export const updateCommunityBrandedLoginMessage = (communityId: string, message: ?string, userId: string): Promise => { return db .table('communitySettings') - .getAll(id, { index: 'communityId' }) + .getAll(communityId, { index: 'communityId' }) .update({ brandedLogin: { message: message, }, }) .run() - .then(async () => await getCommunityById(id)); + .then(async () => { + trackQueue.add({ + userId, + event: events.COMMUNITY_BRANDED_LOGIN_SETTINGS_SAVED, + context: { communityId } + }) + return await getCommunityById(communityId) + }); +}; + +type UpdateSlackSettingsInput = { + token: string, + teamName: string, + teamId: string, + connectedBy: string, + scope: string, +}; +export const updateSlackSettingsAfterConnection = async ( + communityId: string, + input: UpdateSlackSettingsInput, + userId: string +): Promise => { + const settings = await db + .table('communitySettings') + .getAll(communityId, { index: 'communityId' }) + .run(); + + if (!settings || settings.length === 0) { + return await createCommunitySettings(communityId) + .then(() => { + return db + .table('communitySettings') + .getAll(communityId, { index: 'communityId' }) + .update({ + slackSettings: { + ...defaultSettings.slackSettings, + ...input, + connectedAt: new Date(), + }, + }) + .run(); + }) + .then(async () => { + trackQueue.add({ + userId, + event: events.COMMUNITY_SLACK_TEAM_CONNECTED, + context: { communityId }, + }); + + return await getCommunityById(communityId); + }); + } + + return await db + .table('communitySettings') + .getAll(communityId, { index: 'communityId' }) + .update({ + slackSettings: { + ...defaultSettings.slackSettings, + ...input, + connectedAt: new Date(), + }, + }) + .run() + .then(async () => { + trackQueue.add({ + userId, + event: events.COMMUNITY_SLACK_TEAM_CONNECTED, + context: { communityId }, + }); + + return await getCommunityById(communityId); + }); +}; + +export const markInitialSlackInvitationsSent = async ( + communityId: string, + inviteCustomMessage: ?string, + userId: string +): Promise => { + return db + .table('communitySettings') + .getAll(communityId, { index: 'communityId' }) + .update({ + slackSettings: { + invitesSentAt: new Date(), + invitesCustomMessage: inviteCustomMessage, + }, + }) + .run() + .then(async () => { + trackQueue.add({ + userId, + event: events.COMMUNITY_SLACK_TEAM_INVITES_SENT, + context: { communityId }, + }); + + return await getCommunityById(communityId); + }); +}; + +const resetSlackSettings = async (communityId: string) => { + return db + .table('communitySettings') + .getAll(communityId, { index: 'communityId' }) + .update({ + slackSettings: { + ...defaultSettings.slackSettings, + }, + }) + .run() + .then(() => []); +}; + +// prettier-ignore +export const getSlackPublicChannelList = (communityId: string, token: string) => { + if (!token) return []; + const decryptedToken = decryptString(token); + + return axios + .get( + `https://slack.com/api/channels.list?token=${decryptedToken}&exclude_archived=true&exclude_members=true` + ) + .then(response => { + return handleSlackChannelResponse(response.data, communityId); + }) + .catch(error => { + console.error('\n\nerror', error); + return []; + }); +}; + +// prettier-ignore +export const getSlackPrivateChannelList = (communityId: string, token: ?string) => { + if (!token) return []; + const decryptedToken = decryptString(token); + + return axios + .get( + `https://slack.com/api/groups.list?token=${decryptedToken}&exclude_archived=true&exclude_members=true` + ) + .then(response => { + return handleSlackChannelResponse(response.data, communityId); + }) + .catch(error => { + console.error('\n\nerror', error); + return []; + }); +}; + +// prettier-ignore +const handleSlackChannelResponse = async (data: Object, communityId: string) => { + const mapData = (arr: Array) => + arr && + arr.length > 0 && + arr.map( + o => + o && { + id: o.id, + name: o.name, + } + ); + + if (data && data.ok) { + if (data.groups) { + return mapData(data.groups) || []; + } + + if (data.channels) { + return mapData(data.channels) || []; + } + } + + const errorsToTriggerRest = [ + 'token_revoked', + 'not_authed', + 'invalid_auth', + 'account_inactive', + 'no_permission', + ]; + + if (data.error && errorsToTriggerRest.indexOf(data.error) >= 0) { + trackQueue.add({ + userId: 'ADMIN', + event: events.COMMUNITY_SLACK_TEAM_RESET, + context: { communityId }, + properties: { + error: data.error + } + }) + + return resetSlackSettings(communityId); + } + + return []; +}; + +export const enableCommunityTokenJoin = ( + communityId: string, + userId: string +) => { + return db + .table('communitySettings') + .getAll(communityId, { index: 'communityId' }) + .update({ + joinSettings: { + tokenJoinEnabled: true, + token: uuidv4(), + }, + }) + .run() + .then(async () => { + trackQueue.add({ + userId, + event: events.COMMUNITY_JOIN_TOKEN_ENABLED, + context: { communityId }, + }); + + return await getCommunityById(communityId); + }); +}; + +export const disableCommunityTokenJoin = ( + communityId: string, + userId: string +) => { + return db + .table('communitySettings') + .getAll(communityId, { index: 'communityId' }) + .update({ + joinSettings: { + tokenJoinEnabled: false, + token: null, + }, + }) + .run() + .then(async () => { + trackQueue.add({ + userId, + event: events.COMMUNITY_JOIN_TOKEN_DISABLED, + context: { communityId }, + }); + + return await getCommunityById(communityId); + }); +}; + +export const resetCommunityJoinToken = ( + communityId: string, + userId: string +) => { + return db + .table('communitySettings') + .getAll(communityId, { index: 'communityId' }) + .update({ + joinSettings: { + token: uuidv4(), + }, + }) + .run() + .then(async () => { + trackQueue.add({ + userId, + event: events.COMMUNITY_JOIN_TOKEN_RESET, + context: { communityId }, + }); + + return await getCommunityById(communityId); + }); }; diff --git a/api/models/curatedContent.js b/api/models/curatedContent.js index dd15d9ce93..17ed52608f 100644 --- a/api/models/curatedContent.js +++ b/api/models/curatedContent.js @@ -1,11 +1,10 @@ //@flow -const { db } = require('./db'); +const { db } = require('shared/db'); import type { DBCommunity } from 'shared/types'; import { getCommunitiesBySlug } from './community'; -export const getCuratedCommunities = ( - type: string -): Promise> => { +// prettier-ignore +export const getCuratedCommunities = (type: string): Promise> => { return db .table('curatedContent') .filter({ type }) diff --git a/api/models/db.js b/api/models/db.js deleted file mode 100644 index 136302afd2..0000000000 --- a/api/models/db.js +++ /dev/null @@ -1,52 +0,0 @@ -// @flow -/** - * Database setup is done here - */ -const IS_PROD = !process.env.FORCE_DEV && process.env.NODE_ENV === 'production'; - -const DEFAULT_CONFIG = { - // Connect to the test database when, well, testing - db: !process.env.TEST_DB ? 'spectrum' : 'testing', - max: 500, // Maximum number of connections, default is 1000 - buffer: 5, // Minimum number of connections open at any given moment, default is 50 - timeoutGb: 60 * 1000, // How long should an unused connection stick around, default is an hour, this is a minute -}; - -const PRODUCTION_CONFIG = { - password: process.env.AWS_RETHINKDB_PASSWORD, - host: process.env.AWS_RETHINKDB_URL, - port: process.env.AWS_RETHINKDB_PORT, -}; - -const config = IS_PROD - ? { - ...DEFAULT_CONFIG, - ...PRODUCTION_CONFIG, - } - : { - ...DEFAULT_CONFIG, - }; - -var r = require('rethinkdbdash')(config); - -if (process.env.NODE_ENV === 'development') { - const fs = require('fs'); - const inspect = require('rethinkdb-inspector'); - const queries = []; - inspect(r, { - onQueryComplete: (query, { size, time }) => { - if (query.indexOf('.changes') > -1) return; - queries.push({ query, time, size }); - fs.writeFileSync( - 'queries-by-time.js', - JSON.stringify(queries.sort((a, b) => b.time - a.time), null, 2) - ); - fs.writeFileSync( - 'queries-by-response-size.js', - JSON.stringify(queries.sort((a, b) => b.size - a.size), null, 2) - ); - }, - }); -} - -module.exports = { db: r }; diff --git a/api/models/directMessageThread.js b/api/models/directMessageThread.js index fad5845577..ad6ac85b54 100644 --- a/api/models/directMessageThread.js +++ b/api/models/directMessageThread.js @@ -1,7 +1,9 @@ //@flow -const { db } = require('./db'); +const { db } = require('shared/db'); import { NEW_DOCUMENTS } from './utils'; import { createChangefeed } from 'shared/changefeed-utils'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; export type DBDirectMessageThread = { createdAt: Date, @@ -10,21 +12,21 @@ export type DBDirectMessageThread = { threadLastActive: Date, }; -const getDirectMessageThread = ( - directMessageThreadId: string -): Promise => { +// prettier-ignore +const getDirectMessageThread = (directMessageThreadId: string): Promise => { return db .table('directMessageThreads') .get(directMessageThreadId) - .run(); + .run() + .then(res => res && !res.deletedAt ? res : null); }; -const getDirectMessageThreads = ( - ids: Array -): Promise> => { +// prettier-ignore +const getDirectMessageThreads = (ids: Array): Promise> => { return db .table('directMessageThreads') .getAll(...ids) + .filter(row => row.hasFields('deletedAt').not()) .run(); }; @@ -36,6 +38,7 @@ const getDirectMessageThreadsByUser = ( return db .table('usersDirectMessageThreads') .getAll(userId, { index: 'userId' }) + .filter(row => row.hasFields('deletedAt').not()) .eqJoin('threadId', db.table('directMessageThreads')) .without({ left: ['id', 'createdAt', 'threadId', 'userId', 'lastActive', 'lastSeen'], @@ -47,7 +50,8 @@ const getDirectMessageThreadsByUser = ( .run(); }; -const createDirectMessageThread = (isGroup: boolean): DBDirectMessageThread => { +// prettier-ignore +const createDirectMessageThread = (isGroup: boolean, userId: string): DBDirectMessageThread => { return db .table('directMessageThreads') .insert( @@ -60,12 +64,20 @@ const createDirectMessageThread = (isGroup: boolean): DBDirectMessageThread => { { returnChanges: true } ) .run() - .then(result => result.changes[0].new_val); + .then(result => { + trackQueue.add({ + userId, + event: events.DIRECT_MESSAGE_THREAD_CREATED, + properties: { + isGroup + } + }) + return result.changes[0].new_val + }); }; -const setDirectMessageThreadLastActive = ( - id: string -): DBDirectMessageThread => { +// prettier-ignore +const setDirectMessageThreadLastActive = (id: string): DBDirectMessageThread => { return db .table('directMessageThreads') .get(id) diff --git a/api/models/expo-push-subscription.js b/api/models/expo-push-subscription.js deleted file mode 100644 index 23c2e0daa8..0000000000 --- a/api/models/expo-push-subscription.js +++ /dev/null @@ -1,34 +0,0 @@ -// @flow -const debug = require('debug')('api:models:expo-push-subscription'); -const { db } = require('./db'); -import type { DBExpoPushSubscription } from 'shared/types'; - -export const storeExpoSubscription = (token: string, userId: string) => { - debug(`store subscription for user#${userId}`); - return db - .table('expoPushSubscriptions') - .insert({ - token, - userId, - }) - .run(); -}; - -export const getExpoSubscriptions = ( - userId: string -): Promise> => { - debug(`get subscriptions for user#${userId}`); - return db - .table('expoPushSubscriptions') - .getAll(userId, { index: 'userId' }) - .run(); -}; - -export const removeExpoSubscription = (token: string) => { - debug(`remove subscription ${token}`); - return db - .table('expoPushSubscriptions') - .getAll(token, { index: 'token' }) - .delete() - .run(); -}; diff --git a/api/models/invoice.js b/api/models/invoice.js deleted file mode 100644 index 050fb5c45d..0000000000 --- a/api/models/invoice.js +++ /dev/null @@ -1,86 +0,0 @@ -// @flow -import { db } from './db'; -import { - sendCommunityInvoicePaidNotificationQueue, - sendProInvoicePaidNotificationQueue, -} from 'shared/bull/queues'; - -export const getInvoice = (id: string): Promise> => { - return db - .table('invoices') - .get(id) - .run(); -}; - -export const getInvoicesByCommunity = (id: string): Promise> => { - return db - .table('invoices') - .getAll(id, { index: 'communityId' }) - .run(); -}; - -export const getInvoicesByUser = (id: string): Promise> => { - return db - .table('invoices') - .getAll(id, { index: 'userId' }) - .filter({ planId: 'beta-pro' }) - .run(); -}; - -export const createInvoice = ( - event: Object, - subscription: Object, - customer: Object, - recurringPayment: Object -): Promise => { - return db - .table('invoices') - .insert( - { - status: event.paid ? 'paid' : 'unpaid', - customerId: event.customer, - subscriptionId: event.subscription, - amount: event.total, - planId: subscription.plan.id, - planName: subscription.plan.name, - quantity: subscription.quantity, - paidAt: event.date, - chargeId: event.charge, - sourceBrand: customer.sources.data[0].brand, - sourceLast4: customer.sources.data[0].last4, - communityId: recurringPayment.communityId, - userId: recurringPayment.userId, - }, - { returnChanges: true } - ) - .run() - .then(result => { - // in the future if we have more plans we can check for each plan name individually to return the correct queue name - const queueName = - subscription.plan.id === 'community-standard' - ? 'community invoice paid notification' - : 'pro invoice paid notification'; - - const queue = - subscription.plan.id === 'community-standard' - ? sendCommunityInvoicePaidNotificationQueue - : sendProInvoicePaidNotificationQueue; - - const invoice = result.changes[0].new_val; - - // trigger an email to the user for the invoice receipt - queue.add({ invoice }); - return invoice; - }); -}; - -export const getInvoiceByChargeId = (chargeId: string): Promise => { - return db - .table('invoices') - .filter({ chargeId }) - .run() - .then(result => { - if (!result || result.length === 0) return false; - return true; - }); -}; diff --git a/api/models/message.js b/api/models/message.js index 9619c9e5db..12af8881cd 100644 --- a/api/models/message.js +++ b/api/models/message.js @@ -1,20 +1,26 @@ //@flow -const { db } = require('./db'); +const { db } = require('shared/db'); import { sendMessageNotificationQueue, sendDirectMessageNotificationQueue, processReputationEventQueue, _adminProcessToxicMessageQueue, + trackQueue, + searchQueue, } from 'shared/bull/queues'; import { NEW_DOCUMENTS } from './utils'; import { createChangefeed } from 'shared/changefeed-utils'; -import { setThreadLastActive } from './thread'; +import { + setThreadLastActive, + incrementMessageCount, + decrementMessageCount, +} from './thread'; +import { events } from 'shared/analytics'; +import type { DBMessage } from 'shared/types'; export type MessageTypes = 'text' | 'media'; -// TODO: Fix this -export type Message = Object; -export const getMessage = (messageId: string): Promise => { +export const getMessage = (messageId: string): Promise => { return db .table('messages') .get(messageId) @@ -25,12 +31,20 @@ export const getMessage = (messageId: string): Promise => { }); }; +export const getManyMessages = (messageIds: string[]): Promise => { + return db + .table('messages') + .getAll(...messageIds) + .run() + .then(messages => { + return messages.filter(message => message && !message.deletedAt); + }); +}; + type BackwardsPaginationOptions = { last?: number, before?: number | Date }; -const getBackwardsMessages = ( - threadId: string, - { last, before }: BackwardsPaginationOptions -) => { +// prettier-ignore +const getBackwardsMessages = (threadId: string, { last, before }: BackwardsPaginationOptions) => { return db .table('messages') .between( @@ -46,10 +60,8 @@ const getBackwardsMessages = ( type ForwardsPaginationOptions = { first?: number, after?: number | Date }; -const getForwardMessages = ( - threadId: string, - { first, after }: ForwardsPaginationOptions -) => { +// prettier-ignore +const getForwardMessages = (threadId: string, { first, after }: ForwardsPaginationOptions) => { return db .table('messages') .between( @@ -71,46 +83,46 @@ export const getMessages = ( last, before, }: { ...BackwardsPaginationOptions, ...ForwardsPaginationOptions } -): Promise> => { +): Promise> => { // $FlowIssue if (last || before) return getBackwardsMessages(threadId, { last, before }); // $FlowIssue return getForwardMessages(threadId, { first, after }); }; -export const getLastMessage = (threadId: string): Promise => { +export const getLastMessage = (threadId: string): Promise => { return db .table('messages') - .getAll(threadId, { index: 'threadId' }) + .between([threadId, db.minval], [threadId, db.maxval], { + index: 'threadIdAndTimestamp', + leftBound: 'open', + rightBound: 'closed', + }) + .orderBy({ index: db.desc('threadIdAndTimestamp') }) .filter(db.row.hasFields('deletedAt').not()) - .max('timestamp') - .run(); + .limit(1) + .run() + .then(res => (Array.isArray(res) && res.length > 0 ? res[0] : null)); }; -export const getLastMessages = (threadIds: Array): Promise => { - return db - .table('messages') - .getAll(...threadIds, { index: 'threadId' }) - .filter(db.row.hasFields('deletedAt').not()) - .group('threadId') - .max(row => row('timestamp')) - .run(); +export const getLastMessageOfThreads = ( + threadIds: Array +): Promise> => { + return Promise.all(threadIds.map(id => getLastMessage(id))); }; -export const getMediaMessagesForThread = ( - threadId: string -): Promise> => { +// prettier-ignore +export const getMediaMessagesForThread = (threadId: string): Promise> => { return db .table('messages') .getAll(threadId, { index: 'threadId' }) .filter({ messageType: 'media' }) + .filter(db.row.hasFields('deletedAt').not()) .run(); }; -export const storeMessage = ( - message: Message, - userId: string -): Promise => { +// prettier-ignore +export const storeMessage = (message: Object, userId: string): Promise => { // Insert a message return db .table('messages') @@ -130,20 +142,41 @@ export const storeMessage = ( ) .run() .then(result => result.changes[0].new_val) - .then(message => { + .then(async message => { if (message.threadType === 'directMessageThread') { - sendDirectMessageNotificationQueue.add({ message, userId }); + await Promise.all([ + trackQueue.add({ + userId, + event: events.DIRECT_MESSAGE_SENT, + context: { messageId: message.id }, + }), + sendDirectMessageNotificationQueue.add({ message, userId }), + ]) } if (message.threadType === 'story') { - sendMessageNotificationQueue.add({ message }); - _adminProcessToxicMessageQueue.add({ message }); + await Promise.all([ + sendMessageNotificationQueue.add({ message }), + searchQueue.add({ + id: message.id, + type: 'message', + event: 'created' + }), processReputationEventQueue.add({ userId, type: 'message created', entityId: message.threadId, - }); - setThreadLastActive(message.threadId, message.timestamp); + }), + trackQueue.add({ + userId, + event: events.MESSAGE_SENT, + context: { messageId: message.id }, + }), + _adminProcessToxicMessageQueue.add({ message }), + + setThreadLastActive(message.threadId, message.timestamp), + incrementMessageCount(message.threadId) + ]) } return message; @@ -172,9 +205,8 @@ export const getMessageCount = (threadId: string): Promise => { .run(); }; -export const getMessageCountInThreads = ( - threadIds: Array -): Promise> => { +// prettier-ignore +export const getMessageCountInThreads = (threadIds: Array): Promise> => { return db .table('messages') .getAll(...threadIds, { index: 'threadId' }) @@ -184,32 +216,97 @@ export const getMessageCountInThreads = ( .run(); }; -export const deleteMessage = (userId: string, id: string) => { +export const deleteMessage = (userId: string, messageId: string) => { return db .table('messages') - .get(id) - .update({ - deletedAt: new Date(), - }) + .get(messageId) + .update( + { + deletedBy: userId, + deletedAt: new Date(), + }, + { returnChanges: 'always' } + ) .run() - .then(res => { - processReputationEventQueue.add({ - userId, - type: 'message deleted', - entityId: id, - }); - return res; + .then(result => result.changes[0].new_val || result.changes[0].old_val) + .then(async message => { + const event = + message.threadType === 'story' + ? events.MESSAGE_DELETED + : events.DIRECT_MESSAGE_DELETED; + + await Promise.all([ + trackQueue.add({ + userId, + event, + context: { messageId }, + }), + processReputationEventQueue.add({ + userId, + type: 'message deleted', + entityId: messageId, + }), + message.threadType === 'story' + ? decrementMessageCount(message.threadId) + : Promise.resolve(), + message.threadType === 'story' + ? searchQueue.add({ + id: message.id, + type: 'message', + event: 'deleted', + }) + : Promise.resolve(), + ]); + + return message; }); }; -export const deleteMessagesInThread = (threadId: string) => { - return db +// prettier-ignore +export const deleteMessagesInThread = async (threadId: string, userId: string) => { + const messages = await db + .table('messages') + .getAll(threadId, { index: 'threadId' }) + .run(); + + if (!messages || messages.length === 0) return; + + const trackingPromises = messages.map(message => { + const event = message.threadType === 'story' + ? events.MESSAGE_DELETED + : events.DIRECT_MESSAGE_DELETED + return trackQueue.add({ + userId, + event, + context: { messageId: message.id }, + }); + }); + + const searchPromises = messages.map(message => { + if (message.threadType !== 'story') return null + return searchQueue.add({ + id: message.id, + type: 'message', + event: 'deleted' + }) + }) + + const deletePromise = db .table('messages') .getAll(threadId, { index: 'threadId' }) .update({ + deletedBy: userId, deletedAt: new Date(), }) .run(); + + return await Promise.all([ + ...trackingPromises, + deletePromise, + ...searchPromises + ]).then(() => { + return Promise.all(Array.from({ length: messages.length }).map(() => decrementMessageCount(threadId))) + }); }; export const userHasMessagesInThread = (threadId: string, userId: string) => { @@ -220,3 +317,62 @@ export const userHasMessagesInThread = (threadId: string, userId: string) => { .contains(userId) .run(); }; + +type EditInput = { + id: string, + content: { + body: string, + }, +}; + +// prettier-ignore +export const editMessage = (message: EditInput, userId: string): Promise => { + // Insert a message + return db + .table('messages') + .get(message.id) + .update( + { + content: message.content, + modifiedAt: new Date(), + edits: db.branch( + db.row.hasFields('edits'), + db.row('edits').append({ + content: db.row('content'), + timestamp: db.row('modifiedAt'), + }), + [{ + content: db.row('content'), + timstamp: db.row('timestamp') + }] + ), + }, + { returnChanges: 'always' } + ) + .run() + .then(result => result.changes[0].new_val || result.changes[0].old_val) + .then(message => { + if (message.threadType === 'directMessageThread') { + trackQueue.add({ + userId, + event: events.DIRECT_MESSAGE_EDITED, + context: { messageId: message.id }, + }); + } + if (message.threadType === 'story') { + trackQueue.add({ + userId, + event: events.MESSAGE_EDITED, + context: { messageId: message.id }, + }); + + searchQueue.add({ + id: message.id, + type: 'message', + event: 'edited' + }) + } + + return message; + }); +}; diff --git a/api/models/meta.js b/api/models/meta.js index c450034c30..6f78ca7a9e 100644 --- a/api/models/meta.js +++ b/api/models/meta.js @@ -1,6 +1,6 @@ // @flow -const { db } = require('./db'); -import { getUserById } from '../models/user'; +const { db } = require('shared/db'); +import { getUserById } from 'shared/db/queries/user'; /* =========================================================== @@ -19,8 +19,7 @@ const saveUserCommunityPermissions = ( ): Promise => { return db .table('usersCommunities') - .getAll(userId, { index: 'userId' }) - .filter({ communityId }) + .getAll([userId, communityId], { index: 'userIdAndCommunityId' }) .update( { ...permissions, diff --git a/api/models/notification.js b/api/models/notification.js index c43414aec4..c452decbee 100644 --- a/api/models/notification.js +++ b/api/models/notification.js @@ -1,12 +1,20 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); import { NEW_DOCUMENTS } from './utils'; import { createChangefeed } from 'shared/changefeed-utils'; +import type { DBNotification } from 'shared/types'; -export const getNotificationsByUser = ( - userId: string, - { first, after }: { first: number, after: Date } -) => { +// prettier-ignore +export const getNotification = (notificationId: string): Promise => { + return db + .table('notifications') + .get(notificationId) + .run(); +}; + +type InputType = { first: number, after: Date }; +export const getNotificationsByUser = (userId: string, input: InputType) => { + const { first, after } = input; return db .table('usersNotifications') .between( @@ -29,10 +37,10 @@ export const getNotificationsByUser = ( .run(); }; -export const getUnreadDirectMessageNotifications = ( - userId: string, - { first, after }: { first: number, after: Date } -): Promise> => { +// prettier-ignore +export const getUnreadDirectMessageNotifications = (userId: string, input: InputType,): Promise> => { + const { first, after } = input + return db .table('usersNotifications') .between( @@ -61,7 +69,7 @@ const hasChanged = (field: string) => .row('old_val')(field) .ne(db.row('new_val')(field)); -const MODIFIED_AT_CHANGED = hasChanged('entityAddedAt'); +const ENTITY_ADDED = hasChanged('entityAddedAt'); const getNewNotificationsChangefeed = () => db @@ -69,7 +77,7 @@ const getNewNotificationsChangefeed = () => .changes({ includeInitial: false, }) - .filter(NEW_DOCUMENTS.or(MODIFIED_AT_CHANGED))('new_val') + .filter(NEW_DOCUMENTS.or(ENTITY_ADDED))('new_val') .eqJoin('notificationId', db.table('notifications')) .without({ left: ['notificationId', 'createdAt', 'id', 'entityAddedAt'], @@ -92,7 +100,7 @@ const getNewDirectMessageNotificationsChangefeed = () => .changes({ includeInitial: false, }) - .filter(NEW_DOCUMENTS.or(MODIFIED_AT_CHANGED))('new_val') + .filter(NEW_DOCUMENTS.or(ENTITY_ADDED))('new_val') .eqJoin('notificationId', db.table('notifications')) .without({ left: ['notificationId', 'createdAt', 'id', 'entityAddedAt'], diff --git a/api/models/reaction.js b/api/models/reaction.js index aab373b5ef..f4f8bb96df 100644 --- a/api/models/reaction.js +++ b/api/models/reaction.js @@ -1,32 +1,27 @@ // @flow -import { db } from './db'; +import { db } from 'shared/db'; import { sendReactionNotificationQueue, processReputationEventQueue, } from 'shared/bull/queues'; +import type { DBReaction } from 'shared/types'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; type ReactionType = 'like'; -type DBReaction = { - id: string, - messageId: string, - timestamp: Date, - type: ReactionType, - userId: string, -}; - export type ReactionInput = { messageId: string, type: ReactionType, }; -export const getReactions = ( - messageIds: Array -): Promise> => { +// prettier-ignore +export const getReactions = (messageIds: Array): Promise> => { const distinctMessageIds = messageIds.filter((x, i, a) => a.indexOf(x) == i); return db .table('reactions') .getAll(...distinctMessageIds, { index: 'messageId' }) + .filter(row => row.hasFields('deletedAt').not()) .group('messageId') .run(); }; @@ -38,56 +33,105 @@ export const getReaction = (reactionId: string): Promise => { .run(); }; -export const toggleReaction = ( - reaction: ReactionInput, - userId: string -): Promise => { +// prettier-ignore +export const getReactionsByIds = (reactionIds: Array): Promise> => { + return db + .table('reactions') + .getAll(...reactionIds) + .run(); +}; + +// prettier-ignore +export const toggleReaction = (reaction: ReactionInput, userId: string): Promise => { return db .table('reactions') .getAll(reaction.messageId, { index: 'messageId' }) .filter({ userId }) .run() - .then(result => { - // this user has already reacted to the message, remove the reaction - if (result.length > 0) { - const existing = result[0]; + .then(async result => { + // user has already reacted + if (result && result.length > 0) { + const thisReaction = result[0]; - processReputationEventQueue.add({ + // user is re-reacting + if (thisReaction.deletedAt) { + trackQueue.add({ + userId, + event: events.REACTION_CREATED, + context: { + reactionId: thisReaction.id, + }, + }); + + processReputationEventQueue.add({ + userId, + type: 'reaction created', + entityId: thisReaction.messageId, + }); + + return db + .table('reactions') + .get(thisReaction.id) + .update({ + deletedAt: db.literal(), + }) + .run(); + } + + // deleting reaction + trackQueue.add({ userId, - type: 'reaction deleted', - entityId: existing.messageId, + event: events.REACTION_DELETED, + context: { + reactionId: thisReaction.id, + }, }); - return db - .table('reactions') - .get(existing.id) - .delete() - .run(); - } else { processReputationEventQueue.add({ userId, - type: 'reaction created', - entityId: reaction.messageId, + type: 'reaction deleted', + entityId: thisReaction.messageId, }); return db .table('reactions') - .insert( - { - ...reaction, - userId, - timestamp: Date.now(), - }, - { returnChanges: true } - ) - .run() - .then(result => result.changes[0].new_val) - .then(reaction => { - sendReactionNotificationQueue.add({ reaction, userId }); - - return reaction; - }); + .get(thisReaction.id) + .update({ + deletedAt: new Date(), + }) + .run(); } + + // user has not reacted yet + return db + .table('reactions') + .insert( + { + ...reaction, + userId, + timestamp: Date.now(), + }, + { returnChanges: true } + ) + .run() + .then(result => result.changes[0].new_val) + .then(reaction => { + trackQueue.add({ + userId, + event: events.REACTION_CREATED, + context: { reactionId: reaction.id }, + }); + + sendReactionNotificationQueue.add({ reaction, userId }); + + processReputationEventQueue.add({ + userId, + type: 'reaction created', + entityId: reaction.messageId, + }); + + return reaction; + }); }) .then(() => { // return the message object itself in order to more easily update the UI with the apollo store diff --git a/api/models/recurringPayment.js b/api/models/recurringPayment.js deleted file mode 100644 index 1d0bf8c587..0000000000 --- a/api/models/recurringPayment.js +++ /dev/null @@ -1,126 +0,0 @@ -import { db } from './db'; - -const parseStripeDataToDb = (stripeData): Object => ({ - customerId: stripeData.customer, - subscriptionId: stripeData.id, - planId: stripeData.plan.id, - planName: stripeData.plan.name, - amount: stripeData.plan.amount, - quantity: stripeData.quantity, - status: stripeData.status, - currentPeriodStart: stripeData.current_period_start, - currentPeriodEnd: stripeData.current_period_end, - createdAt: stripeData.created, - canceledAt: stripeData.status === 'canceled' ? new Date() : null, - sourceBrand: stripeData.sourceBrand, - sourceLast4: stripeData.sourceLast4, -}); - -export const createRecurringPayment = (props): Promise => { - const { userId, communityId, stripeData } = props; - - return db - .table('recurringPayments') - .insert({ - userId: userId ? userId : null, - communityId: communityId ? communityId : null, - ...parseStripeDataToDb(stripeData), - }) - .run(); -}; - -/* - Stripe returns a full subscription object with a new 'status' field that equals - 'canceled' - this will cause the 'isPro' checks on the client side to return - false, without having to do anything destructive or complicated with the - subscription record itself in the db -*/ -export const updateRecurringPayment = (props): Promise => { - const { id, stripeData } = props; - return db - .table('recurringPayments') - .get(id) - .update({ - ...parseStripeDataToDb(stripeData), - }) - .run(); -}; - -// when a subscription is paid in the background, stripe sends a webhook event to our server which triggers this function. The only thing we want to do is find the right recurringPayment record and update the currentPeriodEnd and currentPeriodStart timestamps so that in the UI we can show when the user's next billing event will occur -export const updateRecurringPaymentPeriod = ( - event: Object -): Promise => { - return db - .table('recurringPayments') - .filter({ - customerId: event.customer, - subscriptionId: event.subscription, - }) - .run() - .then(results => { - if (!results || results.length === 0) return; - const subToUpdate = results[0]; - return db - .table('recurringPayments') - .get(subToUpdate.id) - .update( - { - currentPeriodEnd: event.period_end, - currentPeriodStart: event.period_start, - }, - { returnChanges: true } - ) - .run() - .then(result => result.changes[0].new_val); - }); -}; - -export const getUserRecurringPayments = (userId: string): Promise => { - return db - .table('recurringPayments') - .getAll(userId, { index: 'userId' }) - .run() - .then(result => (result && result.length > 0 ? result : null)); -}; - -export const getCommunityRecurringPayments = ( - communityId: string -): Promise => { - return db - .table('recurringPayments') - .getAll(communityId, { index: 'communityId' }) - .run(); -}; - -export const getCommunitiesRecurringPayments = ( - communityIds: Array -): Promise> => { - return db - .table('recurringPayments') - .getAll(...communityIds, { index: 'communityId' }) - .group('communityId') - .run(); -}; - -export const getUsersRecurringPayments = ( - userIds: Array -): Promise => { - return db - .table('recurringPayments') - .getAll(...userIds, { index: 'userId' }) - .group('userId') - .run(); -}; - -export const getRecurringPaymentFromSubscriptionId = ( - subscriptionId: string -): Promise => { - return db - .table('recurringPayments') - .filter({ subscriptionId }) - .run() - .then(results => { - if (!results || results.length === 0) return null; - return results[0]; - }); -}; diff --git a/api/models/reputationEvents.js b/api/models/reputationEvents.js index 278b50bfd7..d0c7054814 100644 --- a/api/models/reputationEvents.js +++ b/api/models/reputationEvents.js @@ -1,10 +1,9 @@ // @flow -import { db } from './db'; +import { db } from 'shared/db'; import { parseRange } from './utils'; -export const getTopMembersInCommunity = ( - communityId: string -): Promise> => { +// prettier-ignore +export const getTopMembersInCommunity = (communityId: string): Promise> => { const { current } = parseRange('weekly'); return db @@ -14,7 +13,7 @@ export const getTopMembersInCommunity = ( .group('userId') .run() .then(results => { - if (!results) return null; + if (!results) return []; const sorted = results .map(c => ({ userId: c.group, diff --git a/api/models/search.js b/api/models/search.js index b65a3e5eb0..c92871acf4 100644 --- a/api/models/search.js +++ b/api/models/search.js @@ -1,9 +1,8 @@ //@flow -const { db } = require('./db'); +const { db } = require('shared/db'); -export const getPublicChannelIdsInCommunity = ( - communityId: string -): Promise> => { +// prettier-ignore +export const getPublicChannelIdsInCommunity = (communityId: string): Promise> => { return db .table('channels') .getAll(communityId, { index: 'communityId' }) @@ -17,9 +16,8 @@ export const getPublicChannelIdsInCommunity = ( .run(); }; -export const getPrivateChannelIdsInCommunity = ( - communityId: string -): Promise> => { +// prettier-ignore +export const getPrivateChannelIdsInCommunity = (communityId: string): Promise> => { return db .table('channels') .getAll(communityId, { index: 'communityId' }) @@ -33,9 +31,8 @@ export const getPrivateChannelIdsInCommunity = ( .run(); }; -export const getPublicChannelIdsForUsersThreads = ( - userId: string -): Promise> => { +// prettier-ignore +export const getPublicChannelIdsForUsersThreads = (userId: string): Promise> => { return db .table('threads') .getAll(userId, { index: 'creatorId' }) @@ -47,9 +44,22 @@ export const getPublicChannelIdsForUsersThreads = ( .run(); }; -export const getPrivateChannelIdsForUsersThreads = ( +export const getPublicCommunityIdsForUsersThreads = ( userId: string ): Promise> => { + return db + .table('threads') + .getAll(userId, { index: 'creatorId' }) + .filter(row => row.hasFields('deletedAt').not()) + .eqJoin('communityId', db.table('communities')) + .filter(row => row('right')('isPrivate').eq(false)) + .zip() + .map(row => row('communityId')) + .run(); +}; + +// prettier-ignore +export const getPrivateChannelIdsForUsersThreads = (userId: string): Promise> => { return db .table('threads') .getAll(userId, { index: 'creatorId' }) @@ -61,26 +71,64 @@ export const getPrivateChannelIdsForUsersThreads = ( .run(); }; -export const getUsersJoinedChannels = ( +export const getPrivateCommunityIdsForUsersThreads = ( userId: string ): Promise> => { + return db + .table('threads') + .getAll(userId, { index: 'creatorId' }) + .filter(row => row.hasFields('deletedAt').not()) + .eqJoin('communityId', db.table('communities')) + .filter(row => row('right')('isPrivate').eq(true)) + .zip() + .map(row => row('communityId')) + .run(); +}; + +// prettier-ignore +export const getUsersJoinedChannels = (userId: string): Promise> => { return db .table('usersChannels') - .getAll(userId, { index: 'userId' }) - .filter({ isMember: true }) + .getAll([userId, "member"], [userId, "moderator"], [userId, "owner"], { index: 'userIdAndRole' }) + .eqJoin('channelId', db.table('channels')) + .filter(row => row('right').hasFields('deletedAt').not()) + .zip() .map(row => row('channelId')) .run(); }; -export const getUsersJoinedPrivateChannelIds = ( - userId: string -): Promise> => { +// prettier-ignore +export const getUsersJoinedCommunities = (userId: string): Promise> => { + return db + .table('usersCommunities') + .getAll([userId, true], { index: 'userIdAndIsMember' }) + .eqJoin('communityId', db.table('communities')) + .filter(row => row('right').hasFields('deletedAt').not()) + .zip() + .map(row => row('communityId')) + .run(); +}; + +// prettier-ignore +export const getUsersJoinedPrivateChannelIds = (userId: string): Promise> => { return db .table('usersChannels') - .getAll(userId, { index: 'userId' }) - .filter({ isMember: true }) + .getAll([userId, "member"], [userId, "moderator"], [userId, "owner"], { index: 'userIdAndRole' }) .eqJoin('channelId', db.table('channels')) - .filter(row => row('right')('isPrivate').eq(true)) + .filter(row => row('right')('isPrivate').eq(true).and(row('right').hasFields('deletedAt').not())) + .without({ left: ['id'] }) + .zip() + .map(row => row('id')) + .run(); +}; + +// prettier-ignore +export const getUsersJoinedPrivateCommunityIds = (userId: string): Promise> => { + return db + .table('usersCommunities') + .getAll([userId, true], { index: 'userIdAndIsMember' }) + .eqJoin('communityId', db.table('communities')) + .filter(row => row('right')('isPrivate').eq(true).and(row('right').hasFields('deletedAt').not())) .without({ left: ['id'] }) .zip() .map(row => row('id')) diff --git a/api/models/session.js b/api/models/session.js index 1f99dd16fd..404aa2d1f2 100644 --- a/api/models/session.js +++ b/api/models/session.js @@ -1,6 +1,10 @@ // @flow -import { db } from './db'; +import { db } from 'shared/db'; export const destroySession = (id: string) => { - return db.table('sessions').get(id).delete().run(); + return db + .table('sessions') + .get(id) + .delete() + .run(); }; diff --git a/api/models/slackImport.js b/api/models/slackImport.js index 18f48a8392..f1e8adc227 100644 --- a/api/models/slackImport.js +++ b/api/models/slackImport.js @@ -2,23 +2,30 @@ require('now-env'); import axios from 'axios'; const querystring = require('querystring'); -const { db } = require('./db'); -import { slackImportQueue } from 'shared/bull/queues'; -const IS_PROD = process.env.NODE_ENV === 'production'; +const { db } = require('shared/db'); let SLACK_SECRET = process.env.SLACK_SECRET; -if (!IS_PROD) { - SLACK_SECRET = SLACK_SECRET || 'asdf123'; +if (!SLACK_SECRET) { + SLACK_SECRET = process.env.SLACK_SECRET_DEVELOPMENT || 'asdf123'; } -export const generateOAuthToken = (code: string, redirect_uri: string) => { +type SlackData = { + access_token: string, + team_id: string, + team_name: string, + scope: string, +}; + +// prettier-ignore +export const generateOAuthToken = (code: string, redirect_uri: string): Promise => { return axios .post( 'https://slack.com/api/oauth.access', querystring.stringify({ code: code, - scope: 'users:read.email,users:read,admin,chat:write', - client_id: '201769987287.200380534417', + scope: + 'users:read.email,users:read,chat:write,bot,chat:write:bot,channels:read,groups:read', + client_id: '201769987287.271382863153', client_secret: SLACK_SECRET, redirect_uri, }) @@ -30,11 +37,12 @@ export const generateOAuthToken = (code: string, redirect_uri: string) => { access_token: response.data.access_token, team_id: response.data.team_id, team_name: response.data.team_name, + scope: response.data.scope, }; } }) .catch(error => { - console.log('\n\nerror', error); + console.error('\n\nerror', error); return null; }); }; @@ -70,11 +78,6 @@ export const createSlackImportRecord = (input: CreateSlackImportType) => { .then(result => { // kick off a queue worker to get the member data from slack const data = result.changes[0].new_val; - const token = data.token; - const importId = data.id; - - slackImportQueue.add({ token, importId }); - return data; }); }); @@ -90,19 +93,3 @@ export const getSlackImport = (communityId: string) => { return results[0]; }); }; - -export const markSlackImportAsSent = (communityId: string) => { - return db - .table('slackImports') - .getAll(communityId, { index: 'communityId' }) - .update( - { - sent: new Date(), - }, - { returnChanges: true } - ) - .run() - .then(results => { - return results.changes[0].new_val; - }); -}; diff --git a/api/models/stripeCustomers.js b/api/models/stripeCustomers.js deleted file mode 100644 index c01a504322..0000000000 --- a/api/models/stripeCustomers.js +++ /dev/null @@ -1,90 +0,0 @@ -// @flow -import { db } from './db'; -import type { RawCustomer } from 'shared/stripe/types/customer'; -const debug = require('debug')('api:models:stripe-customers'); - -export const getStripeCustomer = (customerId: string): Promise => { - return db - .table('stripeCustomers') - .get(customerId) - .run(); -}; - -export const getStripeCustomersByCustomerIds = ( - customerIds: Array -): Promise> => { - return db - .table('stripeCustomers') - .getAll(...customerIds) - .group('customerId') - .run(); -}; - -export const recordExists = async (customerId: string): Promise => { - debug(`Checking for duplicate records ${customerId}`); - return await db - .table('stripeCustomers') - .getAll(customerId) - .run() - .then( - result => - debug(`\nRecord exists for ${customerId}`) || - (result && result.length > 0) - ) - .catch(err => { - console.log('ERROR: ', err); - return new Error(err); - }); -}; - -export const insertStripeCustomer = async (record: Object): Promise => { - debug(`Inserting ${record.id}`); - const expanded = Object.assign({}, record, { customerId: record.id }); - return await db - .table('stripeCustomers') - .insert(expanded, { returnChanges: 'always' }) - .run() - .then( - result => - debug('\nInserted') || - result.changes[0].new_val || - result.changes[0].old_val - ) - .catch(err => { - console.log('ERROR: ', err); - return new Error(err); - }); -}; - -export const replaceStripeCustomer = async ( - customerId: string, - record: Object -): Promise => { - const expanded = Object.assign({}, record, { customerId: record.id }); - return await db - .table('stripeCustomers') - .getAll(customerId) - .replace(expanded, { returnChanges: 'always' }) - .run() - .then( - result => - debug('\nReplaced') || - result.changes[0].new_val || - result.changes[0].old_val - ) - .catch(err => { - console.log('ERROR: ', err); - return new Error(err); - }); -}; - -export const insertOrReplaceStripeCustomer = async (customer: RawCustomer) => { - const exists = await recordExists(customer.id); - if (exists) { - debug(`Customer record exists, replacing ${customer.id}`); - return await replaceStripeCustomer(customer.id, customer); - } else { - debug(`Customer does not exist, inserting ${customer.id}`); - return await insertStripeCustomer(customer); - } -}; diff --git a/api/models/stripeInvoices.js b/api/models/stripeInvoices.js deleted file mode 100644 index 452b5964c6..0000000000 --- a/api/models/stripeInvoices.js +++ /dev/null @@ -1,20 +0,0 @@ -// @flow -import { db } from './db'; - -export const getInvoices = ( - invoices: Array -): Promise> => { - return db - .table('stripeInvoices') - .getAll(...invoices) - .run(); -}; - -// prettier-ignore -export const getInvoicesByCustomerId = async (customerId: ?string): Promise> => { - if (!customerId) return Promise.resolve([]); - return db - .table('stripeInvoices') - .getAll(customerId, { index: 'customerId' }) - .run(); -}; diff --git a/api/models/stripeSources.js b/api/models/stripeSources.js deleted file mode 100644 index 96b2cff6b2..0000000000 --- a/api/models/stripeSources.js +++ /dev/null @@ -1,29 +0,0 @@ -// @flow -import { db } from './db'; - -export const getSources = (sources: Array): Promise> => { - return db - .table('stripeSources') - .getAll(...sources) - .run(); -}; - -export const getSourcesByCustomerId = async ( - customerId: ?string -): Promise> => { - if (!customerId) return await []; - return db - .table('stripeSources') - .getAll(customerId, { index: 'customerId' }) - .run(); -}; - -export const getStripeSourcesByCustomers = ( - customerIds: Array -): Promise> => { - return db - .table('stripeSources') - .getAll(...customerIds, { index: 'customerId' }) - .group('customerId') - .run(); -}; diff --git a/api/models/stripeSubscriptions.js b/api/models/stripeSubscriptions.js deleted file mode 100644 index 48db3b7e1f..0000000000 --- a/api/models/stripeSubscriptions.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow -import { db } from './db'; - -export const getSubscriptions = ( - subscriptions: Array -): Promise> => { - return db - .table('stripeSubscriptions') - .getAll(...subscriptions) - .run(); -}; - -export const getSubscriptionsByCustomerId = async ( - customerId: ?string -): Promise> => { - if (!customerId) return await []; - return db - .table('stripeSubscriptions') - .getAll(customerId, { index: 'customerId' }) - .run(); -}; diff --git a/api/models/test/__snapshots__/channel.test.js.snap b/api/models/test/__snapshots__/channel.test.js.snap new file mode 100644 index 0000000000..1ce9af67ec --- /dev/null +++ b/api/models/test/__snapshots__/channel.test.js.snap @@ -0,0 +1,201 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`models/channel getChannels excludes deleted channels 1`] = ` +Array [ + Object { + "communityId": "1", + "createdAt": 2016-12-31T23:00:00.000Z, + "description": "General chatter", + "id": "1", + "isDefault": true, + "isPrivate": false, + "memberCount": 5, + "name": "General", + "slug": "general", + }, +] +`; + +exports[`models/channel getChannelsByCommunity returns correct set of channels 1`] = ` +Array [ + Object { + "archivedAt": 2016-12-31T23:00:00.000Z, + "communityId": "1", + "createdAt": 2016-12-31T23:00:00.000Z, + "description": "Testing archiving", + "id": "5", + "isDefault": true, + "isPrivate": false, + "memberCount": 3, + "name": "Archived", + "slug": "archived", + }, + Object { + "communityId": "1", + "createdAt": 2016-12-31T23:00:00.000Z, + "description": "General chatter", + "id": "1", + "isDefault": true, + "isPrivate": false, + "memberCount": 5, + "name": "General", + "slug": "general", + }, + Object { + "communityId": "1", + "createdAt": 2016-12-31T23:00:00.000Z, + "description": "Private chatter", + "id": "2", + "isDefault": false, + "isPrivate": true, + "memberCount": 5, + "name": "Private", + "slug": "private", + }, + Object { + "communityId": "1", + "createdAt": 2016-12-31T23:00:00.000Z, + "description": "Moderator created channel", + "id": "8", + "isDefault": false, + "isPrivate": false, + "memberCount": 1, + "name": "Moderator created", + "slug": "moderator-created", + }, +] +`; + +exports[`models/channel getChannelsByUser returns correct set of channels 1`] = ` +Array [ + Object { + "communityId": "2", + "createdAt": 2016-12-31T23:00:00.000Z, + "description": "Private chatter", + "id": "4", + "isBlocked": false, + "isDefault": false, + "isMember": true, + "isModerator": false, + "isOwner": false, + "isPending": false, + "isPrivate": true, + "memberCount": 5, + "name": "Private", + "receiveNotifications": true, + "slug": "private", + }, + Object { + "communityId": "1", + "createdAt": 2016-12-31T23:00:00.000Z, + "description": "Private chatter", + "id": "2", + "isBlocked": false, + "isDefault": false, + "isMember": true, + "isModerator": false, + "isOwner": false, + "isPending": false, + "isPrivate": true, + "memberCount": 5, + "name": "Private", + "receiveNotifications": true, + "slug": "private", + }, + Object { + "communityId": "1", + "createdAt": 2016-12-31T23:00:00.000Z, + "description": "General chatter", + "id": "1", + "isBlocked": false, + "isDefault": true, + "isMember": true, + "isModerator": false, + "isOwner": false, + "isPending": false, + "isPrivate": false, + "memberCount": 5, + "name": "General", + "receiveNotifications": true, + "slug": "general", + }, + Object { + "communityId": "2", + "createdAt": 2016-12-31T23:00:00.000Z, + "description": "General chatter", + "id": "3", + "isBlocked": false, + "isDefault": true, + "isMember": true, + "isModerator": false, + "isOwner": false, + "isPending": false, + "isPrivate": false, + "memberCount": 5, + "name": "General", + "receiveNotifications": true, + "slug": "general", + }, +] +`; + +exports[`models/channel getChannelsByUserAndCommunity returns correct set of channels 1`] = ` +Array [ + "5", + "1", + "8", + "2", +] +`; + +exports[`models/channel getChannelsMemberCounts excludes deleted channels 1`] = ` +Array [ + Object { + "group": "1", + "reduction": 5, + }, +] +`; + +exports[`models/channel getChannelsMemberCounts excludes non-members 1`] = ` +Array [ + Object { + "group": "1", + "reduction": 5, + }, + Object { + "group": "2", + "reduction": 5, + }, +] +`; + +exports[`models/channel getChannelsThreadCounts excludes deleted channels 1`] = ` +Array [ + Object { + "group": "1", + "reduction": 4, + }, +] +`; + +exports[`models/channel getChannelsThreadCounts excludes deleted threads 1`] = ` +Array [ + Object { + "group": "1", + "reduction": 4, + }, + Object { + "group": "2", + "reduction": 3, + }, +] +`; + +exports[`models/channel getPublicChannelsByCommunity returns correct set of channels 1`] = ` +Array [ + "5", + "1", + "8", +] +`; diff --git a/api/models/test/channel.test.js b/api/models/test/channel.test.js new file mode 100644 index 0000000000..f706e8f5c9 --- /dev/null +++ b/api/models/test/channel.test.js @@ -0,0 +1,175 @@ +// @flow + +import * as channel from '../channel'; + +const queries = channel.__forQueryTests; + +import { + BRIAN_ID, + MAX_ID, + SPECTRUM_COMMUNITY_ID, + SPECTRUM_GENERAL_CHANNEL_ID, + SPECTRUM_PRIVATE_CHANNEL_ID, + DELETED_COMMUNITY_DELETED_CHANNEL_ID, +} from 'api/migrations/seed/default/constants'; + +describe('models/channel', () => { + describe('channelsByCommunitiesQuery', () => { + it('excludes deleted channels', async () => { + const channels = await queries + .channelsByCommunitiesQuery(SPECTRUM_COMMUNITY_ID) + .run(); + expect( + channels.filter(channel => channel.deletedAt !== undefined) + ).toEqual([]); + }); + }); + + describe('channelsByIdsQuery', () => { + it('excludes deleted channels', async () => { + const channels = await queries + .channelsByIdsQuery( + SPECTRUM_GENERAL_CHANNEL_ID, + DELETED_COMMUNITY_DELETED_CHANNEL_ID + ) + .run(); + expect( + channels.filter(channel => channel.deletedAt !== undefined) + ).toEqual([]); + }); + }); + + describe('threadsByChannelsQuery', () => { + it('excludes deleted channels', async () => { + const threads = await queries + .threadsByChannelsQuery(DELETED_COMMUNITY_DELETED_CHANNEL_ID) + .run(); + expect(threads).toEqual([]); + }); + + it('excludes deleted threads', async () => { + const threads = await queries + .threadsByChannelsQuery(SPECTRUM_GENERAL_CHANNEL_ID) + .run(); + expect(threads.filter(thread => thread.deletedAt !== undefined)).toEqual( + [] + ); + }); + }); + + describe('membersByChannelsQuery', () => { + it('excludes deleted channels', async () => { + const members = await queries + .membersByChannelsQuery(DELETED_COMMUNITY_DELETED_CHANNEL_ID) + .run(); + expect(members).toEqual([]); + }); + + it('excludes removed members', async () => { + const members = await queries + .membersByChannelsQuery(SPECTRUM_GENERAL_CHANNEL_ID) + .run(); + expect(members.filter(member => !member.isMember)).toEqual([]); + }); + }); + + describe('getChannelsByCommunity', () => { + it('returns correct set of channels', async () => { + expect( + await channel.getChannelsByCommunity(SPECTRUM_COMMUNITY_ID) + ).toMatchSnapshot(); + }); + }); + + describe('getPublicChannelsByCommunity', () => { + it('returns correct set of channels', async () => { + expect( + await channel.getPublicChannelsByCommunity(SPECTRUM_COMMUNITY_ID) + ).toMatchSnapshot(); + }); + }); + + describe('getChannelsByUserAndCommunity', () => { + it('returns correct set of channels', async () => { + expect( + await channel.getChannelsByUserAndCommunity( + SPECTRUM_COMMUNITY_ID, + MAX_ID + ) + ).toMatchSnapshot(); + }); + }); + + describe('getChannelsByUser', () => { + it('returns correct set of channels', async () => { + expect(await channel.getChannelsByUser(BRIAN_ID)).toMatchSnapshot(); + }); + }); + + describe('getChannelBySlug', () => { + it('excludes deleted channels', async () => { + expect(await channel.getChannelBySlug('deleted', 'spectrum')).toEqual( + null + ); + }); + }); + + describe('getChannelById', () => { + it('excludes deleted channels', async () => { + expect( + await channel.getChannelById(DELETED_COMMUNITY_DELETED_CHANNEL_ID) + ).toEqual(null); + }); + }); + + describe('getChannels', () => { + it('excludes deleted channels', async () => { + expect( + await channel.getChannels([ + SPECTRUM_GENERAL_CHANNEL_ID, + DELETED_COMMUNITY_DELETED_CHANNEL_ID, + ]) + ).toMatchSnapshot(); + }); + }); + + describe('getChannelsThreadCounts', () => { + it('excludes deleted channels', async () => { + expect( + await channel.getChannelsThreadCounts([ + SPECTRUM_GENERAL_CHANNEL_ID, + DELETED_COMMUNITY_DELETED_CHANNEL_ID, + ]) + ).toMatchSnapshot(); + }); + + it('excludes deleted threads', async () => { + expect( + await channel.getChannelsThreadCounts([ + SPECTRUM_GENERAL_CHANNEL_ID, + SPECTRUM_PRIVATE_CHANNEL_ID, + ]) + ).toMatchSnapshot(); + }); + }); + + describe('getChannelsMemberCounts', () => { + it('excludes deleted channels', async () => { + expect( + await channel.getChannelsMemberCounts([ + SPECTRUM_GENERAL_CHANNEL_ID, + DELETED_COMMUNITY_DELETED_CHANNEL_ID, + ]) + ).toMatchSnapshot(); + }); + + it('excludes non-members', async () => { + expect( + await channel.getChannelsMemberCounts([ + SPECTRUM_GENERAL_CHANNEL_ID, + SPECTRUM_PRIVATE_CHANNEL_ID, + ]) + ).toMatchSnapshot(); + }); + }); +}); diff --git a/api/models/thread.js b/api/models/thread.js index 9a8ee4d5d0..137e6d657a 100644 --- a/api/models/thread.js +++ b/api/models/thread.js @@ -1,10 +1,10 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); import intersection from 'lodash.intersection'; import { processReputationEventQueue, - sendThreadNotificationQueue, - _adminProcessToxicThreadQueue, + trackQueue, + searchQueue, } from 'shared/bull/queues'; const { NEW_DOCUMENTS, parseRange } = require('./utils'); import { createChangefeed } from 'shared/changefeed-utils'; @@ -13,6 +13,7 @@ import { turnOffAllThreadNotifications } from '../models/usersThreads'; import type { PaginationOptions } from '../utils/paginate-arrays'; import type { DBThread, FileUpload } from 'shared/types'; import type { Timeframe } from './utils'; +import { events } from 'shared/analytics'; export const getThread = (threadId: string): Promise => { return db @@ -21,9 +22,8 @@ export const getThread = (threadId: string): Promise => { .run(); }; -export const getThreads = ( - threadIds: Array -): Promise> => { +// prettier-ignore +export const getThreads = (threadIds: Array): Promise> => { return db .table('threads') .getAll(...threadIds) @@ -31,6 +31,18 @@ export const getThreads = ( .run(); }; +export const getThreadById = (threadId: string): Promise => { + return db + .table('threads') + .getAll(threadId) + .filter(thread => db.not(thread.hasFields('deletedAt'))) + .run() + .then(results => { + if (!results || results.length === 0) return null; + return results[0]; + }); +}; + // this is used to get all threads that need to be marked as deleted whenever a channel is deleted export const getThreadsByChannelToDelete = (channelId: string) => { return db @@ -40,10 +52,10 @@ export const getThreadsByChannelToDelete = (channelId: string) => { .run(); }; -export const getThreadsByChannel = ( - channelId: string, - { first, after }: PaginationOptions -): Promise> => { +// prettier-ignore +export const getThreadsByChannel = (channelId: string, options: PaginationOptions): Promise> => { + const { first, after } = options + return db .table('threads') .between( @@ -61,23 +73,35 @@ export const getThreadsByChannel = ( .run(); }; +// prettier-ignore +type GetThreadsByChannelPaginationOptions = { + first: number, + after: number, + sort: 'latest' | 'trending' +}; + export const getThreadsByChannels = ( channelIds: Array, - { first, after }: PaginationOptions + options: GetThreadsByChannelPaginationOptions ): Promise> => { + const { first, after, sort = 'latest' } = options; + + let order = [db.desc('lastActive'), db.desc('createdAt')]; + // If we want the top threads, first sort by the score and then lastActive + if (sort === 'trending') order.unshift(db.desc('score')); + return db .table('threads') .getAll(...channelIds, { index: 'channelId' }) .filter(thread => db.not(thread.hasFields('deletedAt'))) - .orderBy(db.desc('lastActive'), db.desc('createdAt')) + .orderBy(...order) .skip(after || 0) .limit(first || 999999) .run(); }; -export const getThreadsByCommunity = ( - communityId: string -): Promise> => { +// prettier-ignore +export const getThreadsByCommunity = (communityId: string): Promise> => { return db .table('threads') .between([communityId, db.minval], [communityId, db.maxval], { @@ -90,10 +114,8 @@ export const getThreadsByCommunity = ( .run(); }; -export const getThreadsByCommunityInTimeframe = ( - communityId: string, - range: Timeframe -): Promise> => { +// prettier-ignore +export const getThreadsByCommunityInTimeframe = (communityId: string, range: Timeframe): Promise> => { const { current } = parseRange(range); return db .table('threads') @@ -103,9 +125,8 @@ export const getThreadsByCommunityInTimeframe = ( .run(); }; -export const getThreadsInTimeframe = ( - range: Timeframe -): Promise> => { +// prettier-ignore +export const getThreadsInTimeframe = (range: Timeframe): Promise> => { const { current } = parseRange(range); return db .table('threads') @@ -114,6 +135,17 @@ export const getThreadsInTimeframe = ( .run(); }; +// We do not filter by deleted threads intentionally to prevent users from spam +// creating/deleting threads +// prettier-ignore +export const getThreadsByUserAsSpamCheck = (userId: string, timeframe: number = 60 * 10): Promise> => { + return db + .table('threads') + .getAll(userId, { index: 'creatorId' }) + .filter(db.row('createdAt').during(db.now().sub(timeframe), db.now())) + .run(); +}; + /* When viewing a user profile we have to take two arguments into account: 1. The user who is being viewed @@ -126,16 +158,29 @@ export const getThreadsInTimeframe = ( export const getViewableThreadsByUser = async ( evalUser: string, currentUser: string, - { first, after }: PaginationOptions + options: PaginationOptions ): Promise> => { + const { first, after } = options; // get a list of the channelIds the current user is allowed to see threads const getCurrentUsersChannelIds = db .table('usersChannels') - .getAll(currentUser, { index: 'userId' }) - .filter({ isBlocked: false, isMember: true }) + .getAll( + [currentUser, 'member'], + [currentUser, 'moderator'], + [currentUser, 'owner'], + { + index: 'userIdAndRole', + } + ) .map(userChannel => userChannel('channelId')) .run(); + const getCurrentUserCommunityIds = db + .table('usersCommunities') + .getAll([currentUser, true], { index: 'userIdAndIsMember' }) + .map(userCommunity => userCommunity('communityId')) + .run(); + // get a list of the channels where the user posted a thread const getPublishedChannelIds = db .table('threads') @@ -143,9 +188,22 @@ export const getViewableThreadsByUser = async ( .map(thread => thread('channelId')) .run(); - const [currentUsersChannelIds, publishedChannelIds] = await Promise.all([ + const getPublishedCommunityIds = db + .table('threads') + .getAll(evalUser, { index: 'creatorId' }) + .map(thread => thread('communityId')) + .run(); + + const [ + currentUsersChannelIds, + publishedChannelIds, + currentUsersCommunityIds, + publishedCommunityIds, + ] = await Promise.all([ getCurrentUsersChannelIds, getPublishedChannelIds, + getCurrentUserCommunityIds, + getPublishedCommunityIds, ]); // get a list of all the channels that are public @@ -156,16 +214,32 @@ export const getViewableThreadsByUser = async ( .map(channel => channel('id')) .run(); - const allIds = [...currentUsersChannelIds, ...publicChannelIds]; - const distinctIds = allIds.filter((x, i, a) => a.indexOf(x) == i); - const validIds = intersection(distinctIds, publishedChannelIds); + const publicCommunityIds = await db + .table('communities') + .getAll(...publishedCommunityIds) + .filter({ isPrivate: false }) + .map(community => community('id')) + .run(); + + const allIds = [ + ...currentUsersChannelIds, + ...currentUsersCommunityIds, + ...publicChannelIds, + ...publicCommunityIds, + ]; + const distinctIds = allIds.filter((x, i, a) => a.indexOf(x) === i); + let validChannelIds = intersection(distinctIds, publishedChannelIds); + let validCommunityIds = intersection(distinctIds, publishedCommunityIds); // takes ~70ms for a heavy load return await db .table('threads') .getAll(evalUser, { index: 'creatorId' }) .filter(thread => db.not(thread.hasFields('deletedAt'))) - .filter(thread => db.expr(validIds).contains(thread('channelId'))) + .filter(thread => db.expr(validChannelIds).contains(thread('channelId'))) + .filter(thread => + db.expr(validCommunityIds).contains(thread('communityId')) + ) .orderBy(db.desc('lastActive'), db.desc('createdAt')) .skip(after || 0) .limit(first) @@ -175,10 +249,9 @@ export const getViewableThreadsByUser = async ( }); }; -export const getPublicThreadsByUser = ( - evalUser: string, - { first, after }: PaginationOptions -): Promise> => { +// prettier-ignore +export const getPublicThreadsByUser = (evalUser: string, options: PaginationOptions): Promise> => { + const { first, after } = options return db .table('threads') .getAll(evalUser, { index: 'creatorId' }) @@ -187,6 +260,10 @@ export const getPublicThreadsByUser = ( .filter({ right: { isPrivate: false } }) .without('right') .zip() + .eqJoin('communityId', db.table('communities')) + .filter({ right: { isPrivate: false } }) + .without('right') + .zip() .orderBy(db.desc('lastActive'), db.desc('createdAt')) .skip(after || 0) .limit(first || 10) @@ -196,35 +273,66 @@ export const getPublicThreadsByUser = ( export const getViewableParticipantThreadsByUser = async ( evalUser: string, currentUser: string, - { first, after }: PaginationOptions + options: PaginationOptions ): Promise> => { + const { first, after } = options; // get a list of the channelIds the current user is allowed to see threads for const getCurrentUsersChannelIds = db .table('usersChannels') - .getAll(currentUser, { index: 'userId' }) - .filter({ isBlocked: false, isMember: true }) + .getAll( + [currentUser, 'member'], + [currentUser, 'moderator'], + [currentUser, 'owner'], + { + index: 'userIdAndRole', + } + ) .map(userChannel => userChannel('channelId')) .run(); + const getCurrentUserCommunityIds = db + .table('usersCommunities') + .getAll([currentUser, true], { index: 'userIdAndIsMember' }) + .map(userCommunity => userCommunity('communityId')) + .run(); + // get a list of the channels where the user participated in a thread const getParticipantChannelIds = db .table('usersThreads') - .getAll(evalUser, { index: 'userId' }) - .filter({ isParticipant: true }) + .getAll([evalUser, true], { index: 'userIdAndIsParticipant' }) .eqJoin('threadId', db.table('threads')) .zip() .pluck('channelId', 'threadId') .run(); - const [currentUsersChannelIds, participantChannelIds] = await Promise.all([ + const getParticipantCommunityIds = db + .table('usersThreads') + .getAll([evalUser, true], { index: 'userIdAndIsParticipant' }) + .eqJoin('threadId', db.table('threads')) + .zip() + .pluck('communityId', 'threadId') + .run(); + + const [ + currentUsersChannelIds, + participantChannelIds, + currentUsersCommunityIds, + participantCommunityIds, + ] = await Promise.all([ getCurrentUsersChannelIds, getParticipantChannelIds, + getCurrentUserCommunityIds, + getParticipantCommunityIds, ]); - const participantThreadIds = participantChannelIds.map(c => c.threadId); + const participantThreadIds = participantChannelIds.map(c => c && c.threadId); const distinctParticipantChannelIds = participantChannelIds .map(c => c.channelId) - .filter((x, i, a) => a.indexOf(x) == i); + .filter((x, i, a) => a.indexOf(x) === i); + + const distinctParticipantCommunityIds = participantCommunityIds + .map(c => c.communityId) + .filter((x, i, a) => a.indexOf(x) === i); // get a list of all the channels that are public const publicChannelIds = await db @@ -234,15 +342,37 @@ export const getViewableParticipantThreadsByUser = async ( .map(channel => channel('id')) .run(); - const allIds = [...currentUsersChannelIds, ...publicChannelIds]; - const distinctIds = allIds.filter((x, i, a) => a.indexOf(x) == i); - const validIds = intersection(distinctIds, distinctParticipantChannelIds); + const publicCommunityIds = await db + .table('communities') + .getAll(...distinctParticipantCommunityIds) + .filter({ isPrivate: false }) + .map(community => community('id')) + .run(); + + const allIds = [ + ...currentUsersChannelIds, + ...publicChannelIds, + ...currentUsersCommunityIds, + ...publicCommunityIds, + ]; + const distinctIds = allIds.filter((x, i, a) => a.indexOf(x) === i); + let validChannelIds = intersection( + distinctIds, + distinctParticipantChannelIds + ); + let validCommunityIds = intersection( + distinctIds, + distinctParticipantCommunityIds + ); return await db .table('threads') .getAll(...participantThreadIds) .filter(thread => db.not(thread.hasFields('deletedAt'))) - .filter(thread => db.expr(validIds).contains(thread('channelId'))) + .filter(thread => db.expr(validChannelIds).contains(thread('channelId'))) + .filter(thread => + db.expr(validCommunityIds).contains(thread('communityId')) + ) .orderBy(db.desc('lastActive'), db.desc('createdAt')) .skip(after || 0) .limit(first) @@ -252,14 +382,12 @@ export const getViewableParticipantThreadsByUser = async ( }); }; -export const getPublicParticipantThreadsByUser = ( - evalUser: string, - { first, after }: PaginationOptions -): Promise> => { +// prettier-ignore +export const getPublicParticipantThreadsByUser = (evalUser: string, options: PaginationOptions): Promise> => { + const { first, after } = options return db .table('usersThreads') - .getAll(evalUser, { index: 'userId' }) - .filter({ isParticipant: true }) + .getAll([evalUser, true], { index: 'userIdAndIsParticipant' }) .eqJoin('threadId', db.table('threads')) .without({ left: [ @@ -277,6 +405,10 @@ export const getPublicParticipantThreadsByUser = ( .filter({ right: { isPrivate: false } }) .without('right') .zip() + .eqJoin('communityId', db.table('communities')) + .filter({ right: { isPrivate: false } }) + .without('right') + .zip() .orderBy(db.desc('lastActive'), db.desc('createdAt')) .skip(after || 0) .limit(first || 10) @@ -286,11 +418,6 @@ export const getPublicParticipantThreadsByUser = ( }); }; -/* - A thread may receive a field 'filesToUpload' if it contains images. We destructure - the incoming argument in order to ignore that field and only return the rest - of the thread fields -*/ export const publishThread = ( // eslint-disable-next-line { filesToUpload, ...thread }: Object, @@ -307,29 +434,33 @@ export const publishThread = ( isPublished: true, isLocked: false, edits: [], + reactionCount: 0, + messageCount: 0, }), { returnChanges: true } ) .run() .then(result => { const thread = result.changes[0].new_val; - sendThreadNotificationQueue.add({ thread }); - processReputationEventQueue.add({ + + searchQueue.add({ + id: thread.id, + type: 'thread', + event: 'created', + }); + + trackQueue.add({ userId, - type: 'thread created', - entityId: thread.id, + event: events.THREAD_CREATED, + context: { threadId: thread.id }, }); - _adminProcessToxicThreadQueue.add({ thread }); return thread; }); }; -export const setThreadLock = ( - threadId: string, - value: boolean, - userId: string -): Promise => { +// prettier-ignore +export const setThreadLock = (threadId: string, value: boolean, userId: string, byModerator: boolean = false): Promise => { return ( db .table('threads') @@ -345,35 +476,44 @@ export const setThreadLock = ( { returnChanges: true } ) .run() - .then( - result => - result.changes.length > 0 - ? result.changes[0].new_val - : db - .table('threads') - .get(threadId) - .run() - ) + .then(async () => { + const thread = await getThreadById(threadId) + + const event = value + ? byModerator + ? events.THREAD_LOCKED_BY_MODERATOR + : events.THREAD_LOCKED + : byModerator + ? events.THREAD_UNLOCKED_BY_MODERATOR + : events.THREAD_UNLOCKED + + trackQueue.add({ + userId, + event, + context: { threadId } + }) + + return thread + }) ); }; -export const setThreadLastActive = (threadId: string, value: Date) => - db +export const setThreadLastActive = (threadId: string, value: Date) => { + return db .table('threads') .get(threadId) .update({ lastActive: value }) .run(); +}; -/* - Non-destructively delete a thread by setting the `deletedAt` field to a date. - After a thread is deleted, set `receiveNotifications` to false for all users who were participants or had subscribed to notifications. -*/ -export const deleteThread = (threadId: string): Promise => { +// prettier-ignore +export const deleteThread = (threadId: string, userId: string): Promise => { return db .table('threads') .get(threadId) .update( { + deletedBy: userId, deletedAt: new Date(), }, { @@ -386,12 +526,24 @@ export const deleteThread = (threadId: string): Promise => { Promise.all([ result, turnOffAllThreadNotifications(threadId), - deleteMessagesInThread(threadId), + deleteMessagesInThread(threadId, userId), ]) ) .then(([result]) => { const thread = result.changes[0].new_val; + searchQueue.add({ + id: thread.id, + type: 'thread', + event: 'deleted' + }) + + trackQueue.add({ + userId, + event: events.THREAD_DELETED, + context: { threadId }, + }); + processReputationEventQueue.add({ userId: thread.creatorId, type: 'thread deleted', @@ -404,36 +556,27 @@ export const deleteThread = (threadId: string): Promise => { type File = FileUpload; -type Attachment = { - attachmentType: string, - data: string, -}; - export type EditThreadInput = { threadId: string, content: { title: string, body: ?string, }, - attachments?: ?Array, filesToUpload?: ?Array, }; + // shouldUpdate arguemnt is used to prevent a thread from being marked as edited when the images are uploaded at publish time -export const editThread = ( - input: EditThreadInput, - shouldUpdate: boolean = true -): Promise => { +// prettier-ignore +export const editThread = (input: EditThreadInput, userId: string, shouldUpdate: boolean = true): Promise => { return db .table('threads') .get(input.threadId) .update( { content: input.content, - attachments: input.attachments, modifiedAt: shouldUpdate ? new Date() : null, edits: db.row('edits').append({ content: db.row('content'), - attachments: db.row('attachments'), timestamp: new Date(), }), }, @@ -444,9 +587,31 @@ export const editThread = ( // if an update happened if (result.replaced === 1) { const thread = result.changes[0].new_val; + + searchQueue.add({ + id: thread.id, + type: 'thread', + event: 'edited' + }) + + trackQueue.add({ + userId, + event: events.THREAD_EDITED, + context: { threadId: input.threadId } + }) + return thread; } + trackQueue.add({ + userId, + event: events.THREAD_EDITED_FAILED, + context: { threadId: input.threadId }, + properties: { + reason: 'no changes' + } + }) + // an update was triggered from the client, but no data was changed return result.changes[0].old_val; }); @@ -480,7 +645,7 @@ export const updateThreadWithImages = (id: string, body: string) => { }); }; -export const moveThread = (id: string, channelId: string) => { +export const moveThread = (id: string, channelId: string, userId: string) => { return db .table('threads') .get(id) @@ -492,11 +657,89 @@ export const moveThread = (id: string, channelId: string) => { ) .run() .then(result => { - if (result.replaced === 1) return result.changes[0].new_val; + if (result.replaced === 1) { + const thread = result.changes[0].new_val; + + searchQueue.add({ + id: thread.id, + type: 'thread', + event: 'moved', + }); + + trackQueue.add({ + userId, + event: events.THREAD_MOVED, + context: { threadId: id }, + }); + + return thread; + } + + trackQueue.add({ + userId, + event: events.THREAD_MOVED_FAILED, + context: { threadId: id }, + properties: { + reason: 'no changes', + }, + }); + return null; }); }; +export const incrementMessageCount = (threadId: string) => { + return db + .table('threads') + .get(threadId) + .update({ + messageCount: db + .row('messageCount') + .default(0) + .add(1), + }) + .run(); +}; + +export const decrementMessageCount = (threadId: string) => { + return db + .table('threads') + .get(threadId) + .update({ + messageCount: db + .row('messageCount') + .default(1) + .sub(1), + }) + .run(); +}; + +export const incrementReactionCount = (threadId: string) => { + return db + .table('threads') + .get(threadId) + .update({ + reactionCount: db + .row('reactionCount') + .default(0) + .add(1), + }) + .run(); +}; + +export const decrementReactionCount = (threadId: string) => { + return db + .table('threads') + .get(threadId) + .update({ + reactionCount: db + .row('reactionCount') + .default(1) + .sub(1), + }) + .run(); +}; + const hasChanged = (field: string) => db .row('old_val')(field) diff --git a/api/models/threadReaction.js b/api/models/threadReaction.js new file mode 100644 index 0000000000..782af0519a --- /dev/null +++ b/api/models/threadReaction.js @@ -0,0 +1,162 @@ +// @flow +import { db } from 'shared/db'; +import { + sendThreadReactionNotificationQueue, + processReputationEventQueue, +} from 'shared/bull/queues'; +import type { DBThreadReaction } from 'shared/types'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import { incrementReactionCount, decrementReactionCount } from './thread'; +import { getThreadById } from './thread'; + +type ThreadReactionType = 'like'; + +// prettier-ignore +export const getThreadReactions = (threadIds: Array): Promise> => { + const distinctMessageIds = threadIds.filter((x, i, a) => a.indexOf(x) == i); + return db + .table('threadReactions') + .getAll(...distinctMessageIds, { index: 'threadId' }) + .filter(row => row.hasFields('deletedAt').not()) + .group('threadId') + .run(); +}; + +export const hasReactedToThread = ( + userId: string, + threadId: string +): Promise => { + return db + .table('threadReactions') + .getAll([userId, threadId], { index: 'userIdAndThreadId' }) + .filter(row => row.hasFields('deletedAt').not()) + .count() + .eq(1) + .run(); +}; + +type ThreadReactionInput = { + threadId: string, + type: ThreadReactionType, +}; + +// prettier-ignore +export const addThreadReaction = (input: ThreadReactionInput, userId: string): Promise => { + return db + .table('threadReactions') + .getAll(input.threadId, { index: 'threadId' }) + .filter({ userId }) + .run() + .then(async results => { + const thread = await getThreadById(input.threadId) + // if the reaction already exists in the db, it was previously deleted + // just remove the deletedAt field + if (results && results.length > 0) { + const thisReaction = results[0]; + + const sendReactionNotification = thread && (thread.creatorId !== userId) + ? sendThreadReactionNotificationQueue.add({ threadReaction: thisReaction, userId }) + : null + + await Promise.all([ + trackQueue.add({ + userId, + event: events.THREAD_REACTION_CREATED, + context: { + threadReactionId: thisReaction.id, + }, + }), + sendReactionNotification, + processReputationEventQueue.add({ + userId, + type: 'thread reaction created', + entityId: thisReaction.threadId, + }), + incrementReactionCount(thisReaction.threadId) + ]) + + return db + .table('threadReactions') + .get(thisReaction.id) + .update({ + deletedAt: db.literal(), + }, { returnChanges: 'always' }) + .run() + .then(result => result.changes[0].new_val || result.changes[0].new_val) + } + + return db + .table('threadReactions') + .insert( + { + ...input, + userId, + createdAt: Date.now(), + }, + { returnChanges: 'always' } + ) + .run() + .then(result => result.changes[0].new_val) + .then(async threadReaction => { + const sendReactionNotification = thread && (thread.creatorId !== userId) + ? sendThreadReactionNotificationQueue.add({ threadReaction, userId }) + : null + + await Promise.all([ + trackQueue.add({ + userId, + event: events.THREAD_REACTION_CREATED, + context: { threadReactionId: threadReaction.id }, + }), + processReputationEventQueue.add({ + userId, + type: 'thread reaction created', + entityId: threadReaction.threadId, + }), + sendReactionNotification, + incrementReactionCount(threadReaction.threadId) + ]) + + return threadReaction; + }); + }); +}; + +// prettier-ignore +export const removeThreadReaction = (threadId: string, userId: string): Promise => { + return db + .table('threadReactions') + .getAll(threadId, { index: 'threadId' }) + .filter({ userId }) + .run() + .then(async results => { + // no reaction exists to be removed + if (!results || results.length === 0) return null; + + const threadReaction = results[0]; + + await Promise.all([ + trackQueue.add({ + userId, + event: events.THREAD_REACTION_DELETED, + context: { threadReactionId: threadReaction.id }, + }), + processReputationEventQueue.add({ + userId, + type: 'thread reaction deleted', + entityId: threadReaction.threadId, + }), + decrementReactionCount(threadId) + ]) + + return db + .table('threadReactions') + .get(threadReaction.id) + .update({ + deletedAt: new Date(), + }, { returnChanges: 'always' }) + .run() + .then(result => result.changes[0].new_val || result.changes[0].new_val) + }); +}; diff --git a/api/models/user.js b/api/models/user.js deleted file mode 100644 index af1254e273..0000000000 --- a/api/models/user.js +++ /dev/null @@ -1,469 +0,0 @@ -// @flow -const { db } = require('./db'); -import { uploadImage } from '../utils/s3'; -import { createNewUsersSettings } from './usersSettings'; -import { sendNewUserWelcomeEmailQueue } from 'shared/bull/queues'; -import type { PaginationOptions } from '../utils/paginate-arrays'; -import type { DBUser, FileUpload } from 'shared/types'; - -type GetUserInput = { - id?: string, - username?: string, -}; - -const getUser = async (input: GetUserInput): Promise => { - if (input.id) return await getUserById(input.id); - if (input.username) return await getUserByUsername(input.username); - return null; -}; - -const getUserById = (userId: string): Promise => { - return db - .table('users') - .get(userId) - .run(); -}; - -const getUserByEmail = (email: string): Promise => { - return db - .table('users') - .getAll(email, { index: 'email' }) - .run() - .then(results => (results.length > 0 ? results[0] : null)); -}; - -const getUserByUsername = (username: string): Promise => { - return db - .table('users') - .getAll(username, { index: 'username' }) - .run() - .then(result => (result ? result[0] : null)); -}; - -const getUsersByUsername = ( - usernames: Array -): Promise> => { - return db - .table('users') - .getAll(...usernames, { index: 'username' }) - .run(); -}; - -const getUsers = (userIds: Array): Promise> => { - return db - .table('users') - .getAll(...userIds) - .run(); -}; - -const getUsersBySearchString = (string: string): Promise> => { - return ( - db - .table('users') - // get users whose username or displayname matches a case insensitive string - .filter(user => user.coerceTo('string').match(`(?i)${string}`)) - // only return the 10 users who match to avoid overloading the dom and sending - // down too much data at once - .limit(10) - .run() - ); -}; - -const storeUser = (user: Object): Promise => { - return db - .table('users') - .insert( - { - ...user, - modifiedAt: null, - }, - { returnChanges: true } - ) - .run() - .then(result => { - const user = result.changes[0].new_val; - - // whenever a new user is created, create a usersSettings record - // and send a welcome email - sendNewUserWelcomeEmailQueue.add({ user }); - return Promise.all([user, createNewUsersSettings(user.id)]); - }) - .then(([user]) => user); -}; - -const saveUserProvider = ( - userId: string, - providerMethod: string, - providerId: number, - extraFields?: Object -) => { - return db - .table('users') - .get(userId) - .run() - .then(result => { - let obj = Object.assign({}, result); - obj[providerMethod] = providerId; - return obj; - }) - .then(user => { - return db - .table('users') - .get(userId) - .update( - { - ...user, - ...extraFields, - }, - { returnChanges: true } - ) - .run() - .then(result => result.changes[0].new_val); - }); -}; - -const getUserByIndex = (indexName: string, indexValue: string) => { - return db - .table('users') - .getAll(indexValue, { index: indexName }) - .run() - .then(results => results && results.length > 0 && results[0]); -}; - -const createOrFindUser = ( - user: Object, - providerMethod: string -): Promise => { - // if a user id gets passed in, we know that a user most likely exists and we just need to retrieve them from the db - // however, if a user id doesn't exist we need to do a lookup by the email address passed in - if an email address doesn't exist, we know that we're going to be creating a new user - let promise; - if (user.id) { - promise = getUser({ id: user.id }); - } else { - if (user[providerMethod]) { - promise = getUserByIndex(providerMethod, user[providerMethod]).then( - storedUser => { - if (storedUser) { - return storedUser; - } - - if (user.email) { - return getUserByEmail(user.email); - } else { - return Promise.resolve({}); - } - } - ); - } else { - if (user.email) { - promise = getUserByEmail(user.email); - } else { - promise = Promise.resolve({}); - } - } - } - - return promise - .then(storedUser => { - // if a user is found with an id or email, return the user in the db - if (storedUser && storedUser.id) { - // if a user is signing in with a second auth method from what their user was created with, store the new auth method - if (!storedUser[providerMethod]) { - return saveUserProvider( - storedUser.id, - providerMethod, - user[providerMethod] - ).then(() => Promise.resolve(storedUser)); - } else { - return Promise.resolve(storedUser); - } - } - - // if no user exists, create a new one with the oauth profile data - return storeUser(user); - }) - .catch(err => { - if (user.id) { - console.error(err); - return new Error(`No user found for id ${user.id}.`); - } - return storeUser(user); - }); -}; - -const getEverything = ( - userId: string, - { first, after }: PaginationOptions -): Promise> => { - return db - .table('usersChannels') - .getAll(userId, { index: 'userId' }) - .filter(userChannel => userChannel('isMember').eq(true)) - .map(userChannel => userChannel('channelId')) - .run() - .then( - userChannels => - userChannels && - userChannels.length > 0 && - db - .table('threads') - .orderBy({ index: db.desc('lastActive') }) - .filter(thread => - db - .expr(userChannels) - .contains(thread('channelId')) - .and(db.not(thread.hasFields('deletedAt'))) - ) - .skip(after || 0) - .limit(first) - .run() - ); -}; - -type UserThreadCount = { - id: string, - count: number, -}; - -const getUsersThreadCount = ( - threadIds: Array -): Promise> => { - const getThreadCounts = threadIds.map(creatorId => - db - .table('threads') - .getAll(creatorId, { index: 'creatorId' }) - .count() - .run() - ); - - return Promise.all(getThreadCounts).then(result => { - return result.map((threadCount, index) => ({ - id: threadIds[index], - count: threadCount, - })); - }); -}; - -export type EditUserInput = { - input: { - file?: FileUpload, - name?: string, - description?: string, - website?: string, - coverFile?: FileUpload, - username?: string, - timezone?: number, - }, -}; - -const editUser = (input: EditUserInput, userId: string): Promise => { - const { - input: { name, description, website, file, coverFile, username, timezone }, - } = input; - return db - .table('users') - .get(userId) - .run() - .then(result => { - return Object.assign({}, result, { - name, - description, - website, - username, - timezone, - modifiedAt: new Date(), - }); - }) - .then(user => { - // if no file was uploaded, update the community with new string values - - if (file || coverFile) { - if (file && !coverFile) { - return uploadImage(file, 'users', user.id).then(profilePhoto => { - // update the user with the profilePhoto - return ( - db - .table('users') - .get(user.id) - .update( - { - ...user, - profilePhoto, - }, - { returnChanges: 'always' } - ) - .run() - // return the resulting user with the profilePhoto set - .then(result => { - // if an update happened - if (result.replaced === 1) { - return result.changes[0].new_val; - } - - // an update was triggered from the client, but no data was changed - if (result.unchanged === 1) { - return result.changes[0].old_val; - } - }) - ); - }); - } else if (!file && coverFile) { - return uploadImage(coverFile, 'users', user.id).then(coverPhoto => { - // update the user with the profilePhoto - return ( - db - .table('users') - .get(user.id) - .update( - { - ...user, - coverPhoto, - }, - { returnChanges: 'always' } - ) - .run() - // return the resulting user with the profilePhoto set - .then(result => { - // if an update happened - if (result.replaced === 1) { - return result.changes[0].new_val; - } - - // an update was triggered from the client, but no data was changed - if (result.unchanged === 1) { - return result.changes[0].old_val; - } - }) - ); - }); - } else if (file && coverFile) { - const uploadFile = file => { - return uploadImage(file, 'users', user.id); - }; - - const uploadCoverFile = coverFile => { - return uploadImage(coverFile, 'users', user.id); - }; - - return Promise.all([ - uploadFile(file), - uploadCoverFile(coverFile), - ]).then(([profilePhoto, coverPhoto]) => { - return ( - db - .table('users') - .get(user.id) - .update( - { - ...user, - coverPhoto, - profilePhoto, - }, - { returnChanges: 'always' } - ) - .run() - // return the resulting community with the profilePhoto set - .then(result => { - // if an update happened - if (result.replaced === 1) { - return result.changes[0].new_val; - } - - // an update was triggered from the client, but no data was changed - if (result.unchanged === 1) { - return result.changes[0].old_val; - } - }) - ); - }); - } - } else { - return db - .table('users') - .get(user.id) - .update( - { - ...user, - }, - { returnChanges: 'always' } - ) - .run() - .then(result => { - // if an update happened - if (result.replaced === 1) { - return result.changes[0].new_val; - } - - // an update was triggered from the client, but no data was changed - if (result.unchanged === 1) { - return result.changes[0].old_val; - } - }); - } - }); -}; - -const setUserOnline = (id: string, isOnline: boolean): DBUser => { - let data = {}; - - data.isOnline = isOnline; - - // If a user is going offline, store their lastSeen - if (isOnline === false) { - data.lastSeen = new Date(); - } - return db - .table('users') - .get(id) - .update(data, { returnChanges: 'always' }) - .run() - .then(result => { - if (result.changes[0].new_val) return result.changes[0].new_val; - return result.changes[0].old_val; - }); -}; - -const setUserPendingEmail = ( - userId: string, - pendingEmail: string -): Promise => { - return db - .table('users') - .get(userId) - .update({ - pendingEmail, - }) - .run() - .then(() => getUserById(userId)); -}; -const updateUserEmail = (userId: string, email: string): Promise => { - return db - .table('users') - .get(userId) - .update({ - email, - pendingEmail: db.literal(), - }) - .run() - .then(() => getUserById(userId)); -}; - -module.exports = { - getUser, - getUserById, - getUserByEmail, - getUserByUsername, - getUsersByUsername, - getUsersThreadCount, - getUsers, - getUsersBySearchString, - getUserByIndex, - saveUserProvider, - createOrFindUser, - storeUser, - editUser, - getEverything, - setUserOnline, - setUserPendingEmail, - updateUserEmail, -}; diff --git a/api/models/usersChannels.js b/api/models/usersChannels.js index e148bde3be..e6bfea7921 100644 --- a/api/models/usersChannels.js +++ b/api/models/usersChannels.js @@ -1,5 +1,12 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import { + incrementMemberCount, + decrementMemberCount, + setMemberCount, +} from './channel'; import type { DBUsersChannels, DBChannel } from 'shared/types'; /* @@ -12,10 +19,13 @@ import type { DBUsersChannels, DBChannel } from 'shared/types'; // invoked only when a new channel is being created. the user who is doing // the creation is automatically an owner and a member -const createOwnerInChannel = ( - channelId: string, - userId: string -): Promise => { +// prettier-ignore +const createOwnerInChannel = (channelId: string, userId: string): Promise => { + trackQueue.add({ + userId, + event: events.USER_WAS_ADDED_AS_OWNER_IN_CHANNEL, + context: { channelId } + }) return db .table('usersChannels') .insert( @@ -33,29 +43,34 @@ const createOwnerInChannel = ( { returnChanges: true } ) .run() - .then(result => { + .then(async result => { const join = result.changes[0].new_val; - return db.table('channels').get(join.channelId); + await incrementMemberCount(channelId) + return db.table('channels').get(join.channelId).run(); }); }; -// creates a single member in a channel. invoked when a user joins a public -// channel -const createMemberInChannel = ( - channelId: string, - userId: string -): Promise => { +// creates a single member in a channel. invoked when a user joins a public channel +// prettier-ignore +const createMemberInChannel = (channelId: string, userId: string, token: boolean): Promise => { + const event = token ? events.USER_JOINED_CHANNEL_WITH_TOKEN : events.USER_JOINED_CHANNEL + + trackQueue.add({ + userId, + event, + context: { channelId } + }) + return db .table('usersChannels') - .getAll(userId, { index: 'userId' }) - .filter({ channelId }) + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) .run() .then(result => { if (result && result.length > 0) { return db .table('usersChannels') - .getAll(userId, { index: 'userId' }) - .filter({ channelId, isBlocked: false }) + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) + .filter({ isBlocked: false }) .update( { createdAt: new Date(), @@ -85,19 +100,18 @@ const createMemberInChannel = ( .run(); } }) - .then(() => db.table('channels').get(channelId)); + .then(async () => { + await incrementMemberCount(channelId) + return db.table('channels').get(channelId).run() + }); }; -// removes a single member from a channel. will be invoked if a user leaves -// a channel -const removeMemberInChannel = ( - channelId: string, - userId: string -): Promise => { +// removes a single member from a channel. will be invoked if a user leaves a channel +// prettier-ignore +const removeMemberInChannel = (channelId: string, userId: string): Promise => { return db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ userId }) + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) .update( { isModerator: false, @@ -108,24 +122,43 @@ const removeMemberInChannel = ( { returnChanges: 'always' } ) .run() - .then(result => { + .then(async result => { if (result && result.changes && result.changes.length > 0) { const join = result.changes[0].old_val; - return db.table('channels').get(join.channelId); + if (join.isPending) { + trackQueue.add({ + userId, + event: events.USER_CANCELED_REQUEST_TO_JOIN_CHANNEL, + context: { channelId } + }) + } else { + trackQueue.add({ + userId, + event: events.USER_LEFT_CHANNEL, + context: { channelId } + }) + } + + await decrementMemberCount(channelId) + return db.table('channels').get(join.channelId).run(); } else { - return; + return null; } }); }; -const unblockMemberInChannel = ( - channelId: string, - userId: string -): Promise => { +// prettier-ignore +const unblockMemberInChannel = (channelId: string, userId: string): Promise => { + + trackQueue.add({ + userId, + event: events.USER_WAS_UNBLOCKED_IN_CHANNEL, + context: { channelId } + }) + return db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ userId }) + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) .update( { isBlocked: false, @@ -135,9 +168,9 @@ const unblockMemberInChannel = ( .run() .then(result => { if (result && result.changes && result.changes.length > 0) { - return db.table('channels').get(channelId); + return db.table('channels').get(channelId).run(); } else { - return; + return null; } }); }; @@ -145,9 +178,10 @@ const unblockMemberInChannel = ( // removes all the user relationships to a channel. will be invoked when a // channel is deleted, at which point we don't want any records in the // database to show a user relationship to the deleted channel -const removeMembersInChannel = ( - channelId: string -): Promise> => { +// prettier-ignore +const removeMembersInChannel = async (channelId: string): Promise> => { + await setMemberCount(channelId, 0) + return db .table('usersChannels') .getAll(channelId, { index: 'channelId' }) @@ -160,21 +194,24 @@ const removeMembersInChannel = ( // creates a single pending user in channel. invoked only when a user is requesting // to join a private channel -const createOrUpdatePendingUserInChannel = ( - channelId: string, - userId: string -): Promise => { +// prettier-ignore +const createOrUpdatePendingUserInChannel = (channelId: string, userId: string): Promise => { + + trackQueue.add({ + userId, + event: events.USER_REQUESTED_TO_JOIN_CHANNEL, + context: { channelId } + }) + return db .table('usersChannels') - .getAll(userId, { index: 'userId' }) - .filter({ channelId }) + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) .run() .then(data => { if (data && data.length > 0) { return db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ userId }) + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) .update( { isPending: true, @@ -203,7 +240,7 @@ const createOrUpdatePendingUserInChannel = ( } }) .then(() => { - return db.table('channels').get(channelId); + return db.table('channels').get(channelId).run(); }); }; @@ -214,8 +251,7 @@ const createOrUpdatePendingUserInChannel = ( const removePendingUsersInChannel = (channelId: string): Promise => { return db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ isPending: true }) + .getAll([channelId, 'pending'], { index: 'channelIdAndRole' }) .update({ isPending: false, receiveNotifications: false, @@ -223,24 +259,35 @@ const removePendingUsersInChannel = (channelId: string): Promise => { .run() .then(result => { const join = result.changes[0].new_val; - return db.table('channels').get(join.channelId); + return db + .table('channels') + .get(join.channelId) + .run(); }); }; // toggles user to blocked in a channel. invoked by a channel or community // owner when managing a private channel. sets pending to false to handle // private channels modifying pending users to be blocked -const blockUserInChannel = ( - channelId: string, - userId: string -): Promise => { +// prettier-ignore +const blockUserInChannel = async (channelId: string, userId: string): Promise => { + + trackQueue.add({ + userId, + event: events.USER_WAS_BLOCKED_IN_CHANNEL, + context: { channelId } + }) + + await decrementMemberCount(channelId) + return db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ userId }) + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) .update( { isMember: false, + isModerator: false, + isOwner: false, isPending: false, isBlocked: true, receiveNotifications: false, @@ -252,14 +299,20 @@ const blockUserInChannel = ( // toggles a pending user to member in a channel. invoked by a channel or community // owner when managing a private channel -const approvePendingUserInChannel = ( - channelId: string, - userId: string -): Promise => { +// prettier-ignore +const approvePendingUserInChannel = async (channelId: string, userId: string): Promise => { + + trackQueue.add({ + userId, + event: events.USER_WAS_APPROVED_IN_CHANNEL, + context: { channelId } + }) + + await incrementMemberCount(channelId) + return db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ userId }) + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) .update( { isMember: true, @@ -270,20 +323,39 @@ const approvePendingUserInChannel = ( ) .run() .then(() => { - return db.table('channels').get(channelId); + return db.table('channels').get(channelId).run(); }); }; // toggles all pending users to make them a member in a channel. invoked by a // channel or community owner when turning a private channel into a public // channel -const approvePendingUsersInChannel = ( - channelId: string -): Promise => { +// prettier-ignore +const approvePendingUsersInChannel = async (channelId: string): Promise => { + const currentCount = await db.table('usersChannels') + .getAll( + [channelId, 'member'], + [channelId, 'moderator'], + [channelId, 'owner'], + { + index: 'channelIdAndRole', + } + ) + .count() + .default(1) + .run() + + const pendingCount = await db.table('usersChannels') + .getAll([channelId, "pending"], { index: 'channelIdAndRole' }) + .count() + .default(0) + .run() + + setMemberCount(channelId, currentCount + pendingCount) + return db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ isPending: true }) + .getAll([channelId, "pending"], { index: 'channelIdAndRole' }) .update( { isMember: true, @@ -293,78 +365,34 @@ const approvePendingUsersInChannel = ( { returnChanges: true } ) .run() - .then(result => result.changes[0].new_val); }; // unblocks a blocked user in a channel. invoked by a channel or community // owner when managing a private channel. this *does* add the user // as a member -const approveBlockedUserInChannel = ( - channelId: string, - userId: string -): Promise => { - return db - .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ userId, isBlocked: true }) - .update( - { - isMember: true, - isBlocked: false, - receiveNotifications: false, - }, - { returnChanges: true } - ) - .run(); -}; +// prettier-ignore +const approveBlockedUserInChannel = async (channelId: string, userId: string): Promise => { + trackQueue.add({ + userId, + event: events.USER_WAS_UNBLOCKED_IN_CHANNEL, + context: { channelId } + }) -// adds a *new* user to a channel as both a moderator and member. this will be invoked -// when a community owner invites teammates or moderators to the channel before those -// people have joined the channel themselves -const createModeratorInChannel = ( - channelId: string, - userId: string -): Promise => { - return db - .table('usersChannels') - .insert( - { - channelId, - userId, - createdAt: new Date(), - isMember: true, - isOwner: false, - isModerator: true, - isBlocked: false, - isPending: false, - receiveNotifications: true, - }, - { returnChanges: true } - ) - .run() - .then(result => result.changes[0].new_val); -}; + await incrementMemberCount(channelId) -// moves an *existing* user in a channel to be a moderator -const makeMemberModeratorInChannel = ( - channelId: string, - userId: string -): Promise => { return db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ userId }) + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) + .filter({ isBlocked: true }) .update( { isMember: true, isBlocked: false, - isModerator: true, - receiveNotifications: true, + receiveNotifications: false, }, { returnChanges: true } ) - .run() - .then(result => result.changes[0].new_val); + .run(); }; // moves a moderator to be only a member in a channel. does not remove them from the channel @@ -374,8 +402,7 @@ const removeModeratorInChannel = ( ): Promise => { return db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ userId }) + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) .update({ isModerator: false, }) @@ -384,10 +411,8 @@ const removeModeratorInChannel = ( // creates a new relationship between the user and all of a community's // default channels, skipping over any relationships that already exist -const createMemberInDefaultChannels = ( - communityId: string, - userId: string -): Promise> => { +// prettier-ignore +const createMemberInDefaultChannels = (communityId: string, userId: string): Promise> => { // get the default channels for the community being joined const defaultChannels = db .table('channels') @@ -419,24 +444,78 @@ const createMemberInDefaultChannels = ( // create all the necessary relationships return Promise.all( defaultChannelsTheUserHasNotJoined.map(channel => - createMemberInChannel(channel, userId) + createMemberInChannel(channel, userId, false) ) ); } ); }; -const toggleUserChannelNotifications = ( - userId: string, - channelId: string, - value: boolean -): Promise => { - return db +// prettier-ignore +const toggleUserChannelNotifications = async (userId: string, channelId: string, value: boolean): Promise => { + const event = value ? events.CHANNEL_NOTIFICATIONS_ENABLED : events.CHANNEL_NOTIFICATIONS_DISABLED + + trackQueue.add({ + userId, + event, + context: { channelId } + }) + + const permissions = await db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ userId }) - .update({ receiveNotifications: value }) + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) .run(); + + // permissions exist, this user is trying to toggle notifications for a channel where they + // are already a member + if (permissions && permissions.length > 0) { + return db + .table('usersChannels') + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) + .update({ receiveNotifications: value }) + .run(); + } + + // if permissions don't exist, create a usersChannel relationship with notifications on + return createMemberInChannel(channelId, userId, false) +}; + +const removeUsersChannelMemberships = async (userId: string) => { + const usersChannels = await db + .table('usersChannels') + .getAll(userId, { index: 'userId' }) + .run(); + + if (!usersChannels || usersChannels.length === 0) return; + + const trackingPromises = usersChannels.map(usersChannel => { + return trackQueue.add({ + userId, + event: events.USER_LEFT_CHANNEL, + context: { channelId: usersChannel.channelId }, + }); + }); + + const memberCountPromises = usersChannels.map(usersChannel => { + return decrementMemberCount(usersChannel.channelId); + }); + + const channelPromise = db + .table('usersChannels') + .getAll(userId, { index: 'userId' }) + .update({ + isOwner: false, + isModerator: false, + isMember: false, + receiveNotifications: false, + }) + .run(); + + return await Promise.all([ + ...trackingPromises, + memberCountPromises, + channelPromise, + ]); }; /* @@ -447,31 +526,29 @@ const toggleUserChannelNotifications = ( =========================================================== */ -const getMembersInChannel = ( - channelId: string, - { first, after }: { first: number, after: number } -): Promise> => { +type Options = { first: number, after: number }; +// prettier-ignore +const getMembersInChannel = (channelId: string, options: Options): Promise> => { + const { first, after } = options + return ( db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ isMember: true }) + .getAll([channelId, "member"], [channelId, "moderator"], [channelId, "owner"], { index: 'channelIdAndRole' }) .skip(after || 0) - .limit(first || 999999) + .limit(first || 25) // return an array of the userIds to be loaded by gql .map(userChannel => userChannel('userId')) .run() ); }; -const getPendingUsersInChannel = ( - channelId: string -): Promise> => { +// prettier-ignore +const getPendingUsersInChannel = (channelId: string): Promise> => { return ( db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ isPending: true }) + .getAll([channelId, "pending"], { index: 'channelIdAndRole' }) // return an array of the userIds to be loaded by gql .map(userChannel => userChannel('userId')) .run() @@ -481,20 +558,21 @@ const getPendingUsersInChannel = ( const getPendingUsersInChannels = (channelIds: Array) => { return db .table('usersChannels') - .getAll(...channelIds, { index: 'channelId' }) + .getAll(...channelIds.map(id => [id, 'pending']), { + index: 'channelIdAndRole', + }) .group('channelId') - .filter({ isPending: true }) .run(); }; -const getBlockedUsersInChannel = ( - channelId: string -): Promise> => { +// prettier-ignore +const getBlockedUsersInChannel = (channelId: string): Promise> => { return ( db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ isBlocked: true }) + .getAll([channelId, 'blocked'], { + index: 'channelIdAndRole', + }) // return an array of the userIds to be loaded by gql .map(userChannel => userChannel('userId')) .run() @@ -505,8 +583,9 @@ const getModeratorsInChannel = (channelId: string): Promise> => { return ( db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ isModerator: true }) + .getAll([channelId, 'moderator'], { + index: 'channelIdAndRole', + }) // return an array of the userIds to be loaded by gql .map(userChannel => userChannel('userId')) .run() @@ -517,8 +596,9 @@ const getOwnersInChannel = (channelId: string): Promise> => { return ( db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ isOwner: true }) + .getAll([channelId, 'owner'], { + index: 'channelIdAndRole', + }) // return an array of the userIds to be loaded by gql .map(userChannel => userChannel('userId')) .run() @@ -534,10 +614,8 @@ const DEFAULT_USER_CHANNEL_PERMISSIONS = { receiveNotifications: false, }; -const getUserPermissionsInChannel = ( - channelId: string, - userId: string -): Promise => { +// prettier-ignore +const getUserPermissionsInChannel = (channelId: string, userId: string): Promise => { return db .table('usersChannels') .getAll([userId, channelId], { index: 'userIdAndChannelId' }) @@ -554,11 +632,10 @@ const getUserPermissionsInChannel = ( }); }; -type UserIdAndChannelId = [string, string]; +type UserIdAndChannelId = [?string, string]; -const getUsersPermissionsInChannels = ( - input: Array -): Promise> => { +// prettier-ignore +const getUsersPermissionsInChannels = (input: Array): Promise> => { return db .table('usersChannels') .getAll(...input, { index: 'userIdAndChannelId' }) @@ -586,11 +663,36 @@ const getUsersPermissionsInChannels = ( const getUserUsersChannels = (userId: string) => { return db .table('usersChannels') - .getAll(userId, { index: 'userId' }) - .filter({ isMember: true }) + .getAll([userId, 'member'], [userId, 'owner'], [userId, 'moderator'], { + index: 'userIdAndRole', + }) .run(); }; +const getUserChannelIds = (userId: string) => { + return db + .table('usersChannels') + .getAll([userId, 'member'], [userId, 'owner'], [userId, 'moderator'], { + index: 'userIdAndRole', + }) + .map(rec => rec('channelId')) + .run(); +}; + +// const getUserChannelIds = createQuery({ +// read: (userId: string) => +// db +// .table('usersChannels') +// .getAll(userId, { index: 'userId' }) +// .filter({ isMember: true }) +// .map(rec => rec('channelId')), +// tags: (userId: string) => (usersChannels: ?Array) => [ +// userId, +// ...(usersChannels || []).map(({ channelId }) => channelId), +// ...(usersChannels || []).map(({ id }) => id), +// ], +// }); + module.exports = { // modify and create createOwnerInChannel, @@ -604,11 +706,10 @@ module.exports = { approvePendingUserInChannel, approvePendingUsersInChannel, approveBlockedUserInChannel, - createModeratorInChannel, - makeMemberModeratorInChannel, removeModeratorInChannel, createMemberInDefaultChannels, toggleUserChannelNotifications, + removeUsersChannelMemberships, // get getMembersInChannel, getPendingUsersInChannel, @@ -619,6 +720,7 @@ module.exports = { getUsersPermissionsInChannels, getPendingUsersInChannels, getUserUsersChannels, + getUserChannelIds, // constants DEFAULT_USER_CHANNEL_PERMISSIONS, }; diff --git a/api/models/usersCommunities.js b/api/models/usersCommunities.js index 5919cf5f65..dc69563cde 100644 --- a/api/models/usersCommunities.js +++ b/api/models/usersCommunities.js @@ -1,7 +1,14 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); import { sendCommunityNotificationQueue } from 'shared/bull/queues'; -import type { DBUsersCommunities } from 'shared/types'; +import type { DBUsersCommunities, DBCommunity } from 'shared/types'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import { + incrementMemberCount, + decrementMemberCount, + setMemberCount, +} from './community'; /* =========================================================== @@ -13,10 +20,8 @@ import type { DBUsersCommunities } from 'shared/types'; // invoked only when a new community is being created. the user who is doing // the creation is automatically an owner and a member -const createOwnerInCommunity = ( - communityId: string, - userId: string -): Promise => { +// prettier-ignore +export const createOwnerInCommunity = (communityId: string, userId: string): Promise => { return db .table('usersCommunities') .insert( @@ -28,25 +33,41 @@ const createOwnerInCommunity = ( isMember: true, isModerator: false, isBlocked: false, + isPending: false, receiveNotifications: true, reputation: 0, }, { returnChanges: true } ) .run() - .then(result => result.changes[0].new_val); + .then(async result => { + await Promise.all([ + trackQueue.add({ + userId, + event: events.USER_WAS_ADDED_AS_OWNER_IN_COMMUNITY, + context: { communityId } + }), + incrementMemberCount(communityId) + ]) + + return result.changes[0].new_val + }); }; // creates a single member in a community. invoked when a user joins a public // community -const createMemberInCommunity = ( - communityId: string, - userId: string -): Promise => { +// prettier-ignore +export const createMemberInCommunity = (communityId: string, userId: string): Promise => { + + trackQueue.add({ + userId, + event: events.USER_JOINED_COMMUNITY, + context: { communityId } + }) + return db .table('usersCommunities') - .getAll(userId, { index: 'userId' }) - .filter({ communityId }) + .getAll([userId, communityId], { index: 'userIdAndCommunityId' }) .run() .then(result => { if (result && result.length > 0) { @@ -57,8 +78,7 @@ const createMemberInCommunity = ( return db .table('usersCommunities') - .getAll(userId, { index: 'userId' }) - .filter({ communityId }) + .getAll([userId, communityId], { index: 'userIdAndCommunityId' }) .update( { createdAt: new Date(), @@ -81,6 +101,7 @@ const createMemberInCommunity = ( isOwner: false, isModerator: false, isBlocked: false, + isPending: false, receiveNotifications: true, reputation: 0, }, @@ -89,41 +110,68 @@ const createMemberInCommunity = ( .run(); } }) - .then(result => { - sendCommunityNotificationQueue.add({ communityId, userId }); + .then(async result => { + await Promise.all([ + sendCommunityNotificationQueue.add({ communityId, userId }), + incrementMemberCount(communityId) + ]) return result.changes[0].new_val; }); }; -// removes a single member from a community. will be invoked if a user leaves -// a community -const removeMemberInCommunity = ( - communityId: string, - userId: string -): Promise => { +// removes a single member from a community. will be invoked if a user leaves a community +// prettier-ignore +export const removeMemberInCommunity = (communityId: string, userId: string): Promise => { + + trackQueue.add({ + userId, + event: events.USER_LEFT_COMMUNITY, + context: { communityId } + }) + return db .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter({ userId }) + .getAll([userId, communityId], { index: 'userIdAndCommunityId'}) .update({ isModerator: false, isMember: false, receiveNotifications: false, }) .run() - .then(() => - db + .then(async () => { + const community = await db .table('communities') .get(communityId) .run() - ); + + await decrementMemberCount(communityId) + + return community + }) }; // removes all the user relationships to a community. will be invoked when a // community is deleted, at which point we don't want any records in the // database to show a user relationship to the deleted community -const removeMembersInCommunity = (communityId: string): Promise => { - return db +// prettier-ignore +export const removeMembersInCommunity = async (communityId: string): Promise => { + + const usersCommunities = await db + .table('usersCommunities') + .getAll(communityId, { index: 'communityId' }) + .run() + + if (!usersCommunities || usersCommunities.length === 0) return + + const trackingPromises = usersCommunities.map(usersCommunity => { + return trackQueue.add({ + userId: usersCommunity.userId, + event: events.USER_LEFT_COMMUNITY, + context: { communityId } + }) + }) + + const leavePromise = await db .table('usersCommunities') .getAll(communityId, { index: 'communityId' }) .update({ @@ -131,19 +179,22 @@ const removeMembersInCommunity = (communityId: string): Promise => { receiveNotifications: false, }) .run(); + + return Promise.all([ + ...trackingPromises, + setMemberCount(communityId, 0), + leavePromise + ]) }; // toggles user to blocked in a community. invoked by a community or community // owner when managing a private community. sets pending to false to handle // private communitys modifying pending users to be blocked -const blockUserInCommunity = ( - communityId: string, - userId: string -): Promise => { +// prettier-ignore +export const blockUserInCommunity = (communityId: string, userId: string): Promise => { return db .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter({ userId }) + .getAll([userId, communityId], { index: 'userIdAndCommunityId'}) .update( { isMember: false, @@ -155,108 +206,308 @@ const blockUserInCommunity = ( { returnChanges: true } ) .run() - .then(result => result.changes[0].new_val); + .then(async result => { + await Promise.all([ + trackQueue.add({ + userId, + event: events.USER_WAS_BLOCKED_IN_COMMUNITY, + context : { communityId } + }), + decrementMemberCount(communityId) + ]) + return result.changes[0].new_val + }); }; // unblocks a blocked user in a community. invoked by a community or community // owner when managing a private community. this *does* add the user // as a member -const unblockUserInCommunity = ( - communityId: string, - userId: string -): Promise => { +// prettier-ignore +export const unblockUserInCommunity = (communityId: string, userId: string): Promise => { return db .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter({ userId, isBlocked: true }) + .getAll([userId, communityId], { index: 'userIdAndCommunityId' }) + .filter({ isBlocked: true }) .update( { isModerator: false, isMember: true, isBlocked: false, + isPending: false, receiveNotifications: true, }, { returnChanges: true } ) .run() - .then(result => result.changes[0].new_val); + .then(async result => { + + await Promise.all([ + trackQueue.add({ + userId, + event: events.USER_WAS_UNBLOCKED_IN_COMMUNITY, + context: { communityId } + }), + incrementMemberCount(communityId) + ]) + + return result.changes[0].new_val + }); }; -// adds a *new* user to a community as both a moderator and member. this will be invoked -// when a community owner invites teammates or moderators to the community before those -// people have joined the community themselves -const createModeratorInCommunity = ( - communityId: string, - userId: string -): Promise => { +// moves an *existing* user in a community to be a moderator +// prettier-ignore +export const makeMemberModeratorInCommunity = (communityId: string, userId: string): Promise => { return db .table('usersCommunities') - .insert( + .getAll([userId, communityId], { index: 'userIdAndCommunityId'}) + .update( { - communityId, - userId, - createdAt: new Date(), + isBlocked: false, isMember: true, - isOwner: false, isModerator: true, - isBlocked: false, receiveNotifications: true, }, { returnChanges: true } ) .run() - .then(result => result.changes[0].new_val); + .then(result => { + + trackQueue.add({ + userId, + event: events.USER_WAS_ADDED_AS_MODERATOR_IN_COMMUNITY, + context: { communityId } + }) + + return result.changes[0].new_val + }); +}; + +// moves a moderator to be only a member in a community. does not remove them from the community +// prettier-ignore +export const removeModeratorInCommunity = (communityId: string, userId: string): Promise => { + return db + .table('usersCommunities') + .getAll([userId, communityId], { index: 'userIdAndCommunityId'}) + .update( + { + isModerator: false, + }, + { returnChanges: true } + ) + .run() + .then(result => { + + trackQueue.add({ + userId, + event: events.USER_WAS_REMOVED_AS_MODERATOR_IN_COMMUNITY, + context: { communityId } + }) + + return result.changes[0].new_val + }); }; -// moves an *existing* user in a community to be a moderator -const makeMemberModeratorInCommunity = ( +// changes all moderators in a community to members +// prettier-ignore +export const removeModeratorsInCommunity = async (communityId: string): Promise => { + const moderators = await db + .table('usersCommunities') + .getAll(communityId, { index: 'communityId' }) + .filter({ isModerator: true }) + .run() + + if (!moderators || moderators.length === 0) return + + const trackingPromises = moderators.map(moderator => { + return trackQueue.add({ + userId: moderator.userId, + event: events.USER_WAS_REMOVED_AS_MODERATOR_IN_COMMUNITY, + context: { communityId } + }) + }) + + const removePromise = db + .table('usersCommunities') + .getAll(communityId, { index: 'communityId' }) + .filter({ isModerator: true }) + .update({ isModerator: false }, { returnChanges: true }) + .run(); + + return Promise.all([ + ...trackingPromises, + removePromise + ]) +}; + +// invoked when a user is deleting their account or being banned +export const removeUsersCommunityMemberships = async (userId: string) => { + const memberships = await db + .table('usersCommunities') + .getAll(userId, { index: 'userId' }) + .run(); + + if (!memberships || memberships.length === 0) return; + + const trackingPromises = memberships.map(member => { + return trackQueue.add({ + userId, + event: events.USER_LEFT_COMMUNITY, + context: { communityId: member.communityId }, + }); + }); + + const memberCountPromises = memberships.map(member => { + return decrementMemberCount(member.communityId); + }); + + const removeMembershipsPromise = db + .table('usersCommunities') + .getAll(userId, { index: 'userId' }) + .update({ + isOwner: false, + isModerator: false, + isMember: false, + isPending: false, + receiveNotifications: false, + }) + .run(); + + return Promise.all([ + ...trackingPromises, + memberCountPromises, + removeMembershipsPromise, + ]); +}; + +// prettier-ignore +export const createPendingMemberInCommunity = async (communityId: string, userId: string): Promise => { + return db + .table('usersCommunities') + .getAll([userId, communityId], { index: 'userIdAndCommunityId' }) + .run() + .then(result => { + if (result && result.length > 0) { + // if the result exists, it means the user has a previous relationship + // with this community - we handle blocked logic upstream in the mutation, + // so in this case we can just update the record to be pending + + return db + .table('usersCommunities') + .getAll([userId, communityId], { index: 'userIdAndCommunityId' }) + .update( + { + createdAt: new Date(), + isPending: true + }, + { returnChanges: 'always' } + ) + .run(); + } else { + // if no relationship exists, we can create a new one from scratch + return db + .table('usersCommunities') + .insert( + { + communityId, + userId, + createdAt: new Date(), + isMember: false, + isOwner: false, + isModerator: false, + isBlocked: false, + isPending: true, + receiveNotifications: true, + reputation: 0, + }, + { returnChanges: true } + ) + .run(); + } + }) + .then(result => { + trackQueue.add({ + userId, + event: events.USER_REQUESTED_TO_JOIN_COMMUNITY, + context: { communityId } + }) + + // TODO@PRIVATE_COMMUNITIES + // add queue for sending notification to community owner + + return result.changes[0].new_val; + }); +} + +// prettier-ignore +export const removePendingMemberInCommunity = async (communityId: string, userId: string): Promise => { + trackQueue.add({ + userId, + event: events.USER_CANCELED_REQUEST_TO_JOIN_COMMUNITY, + context: { communityId } + }) + + return db + .table('usersCommunities') + .getAll([userId, communityId], { index: 'userIdAndCommunityId'}) + .update({ + isPending: false, + }) + .run() +} + +export const approvePendingMemberInCommunity = async ( communityId: string, userId: string ): Promise => { return db .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter({ userId }) + .getAll([userId, communityId], { index: 'userIdAndCommunityId' }) .update( { - isBlocked: false, isMember: true, - isModerator: true, + isPending: false, receiveNotifications: true, }, - { returnChanges: true } + { returnChanges: 'always' } ) .run() - .then(result => result.changes[0].new_val); + .then(result => { + trackQueue.add({ + userId, + event: events.USER_WAS_APPROVED_IN_COMMUNITY, + context: { communityId }, + }); + + incrementMemberCount(communityId); + + return result.changes[0].new_val; + }); }; -// moves a moderator to be only a member in a community. does not remove them from the community -const removeModeratorInCommunity = ( +export const blockPendingMemberInCommunity = async ( communityId: string, userId: string -): Promise => { +): Promise => { return db .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter({ userId }) + .getAll([userId, communityId], { index: 'userIdAndCommunityId' }) .update( { - isModerator: false, + isPending: false, + isBlocked: true, }, - { returnChanges: true } + { returnChanges: 'always' } ) .run() - .then(result => result.changes[0].new_val); -}; + .then(result => { + trackQueue.add({ + userId, + event: events.USER_WAS_BLOCKED_IN_COMMUNITY, + context: { communityId }, + }); -// changes all moderators in a community to members -const removeModeratorsInCommunity = (communityId: string): Promise => { - return db - .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter({ isModerator: true }) - .update({ isModerator: false }, { returnChanges: true }) - .run(); + return result.changes[0].new_val; + }); }; /* @@ -267,79 +518,105 @@ const removeModeratorsInCommunity = (communityId: string): Promise => { =========================================================== */ -const getMembersInCommunity = ( - communityId: string, - { first, after }: { first: number, after: number }, - filter: Object -): Promise> => { - return ( - db - .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter(filter ? filter : { isMember: true }) - .orderBy(db.desc('reputation')) - .skip(after || 0) - .limit(first || 999999) - // return an array of the userIds to be loaded by gql - .map(userCommunity => userCommunity('userId')) - .run() - ); +type Options = { first: number, after: number }; + +// prettier-ignore +export const getMembersInCommunity = (communityId: string, options: Options): Promise> => { + const { first, after } = options + return db + .table('usersCommunities') + .between([communityId, true, db.minval], [communityId, true, db.maxval], { + index: 'communityIdAndIsMemberAndReputation', + leftBound: 'open', + rightBound: 'open', + }) + .orderBy({ index: db.desc('communityIdAndIsMemberAndReputation') }) + .skip(after || 0) + .limit(first || 25) + .map(userCommunity => userCommunity('userId')) + .run() }; -const getBlockedUsersInCommunity = ( - communityId: string -): Promise> => { +// prettier-ignore +export const getBlockedUsersInCommunity = (communityId: string, options: Options): Promise> => { return ( db .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) + .getAll([communityId, false], { index: 'communityIdAndIsMember' }) .filter({ isBlocked: true }) - // return an array of the userIds to be loaded by gql + .skip(options.after || 0) + .limit(options.first || 25) .map(userCommunity => userCommunity('userId')) .run() ); }; -const getModeratorsInCommunity = ( - communityId: string -): Promise> => { +// prettier-ignore +export const getPendingUsersInCommunity = (communityId: string, options: Options): Promise> => { return ( db .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter({ isModerator: true }) - // return an array of the userIds to be loaded by gql + .getAll([communityId, false], { index: 'communityIdAndIsMember' }) + .filter({ isPending: true }) + .skip(options.after || 0) + .limit(options.first || 25) .map(userCommunity => userCommunity('userId')) .run() ); }; -const getOwnersInCommunity = (communityId: string): Promise> => { +// prettier-ignore +export const getModeratorsInCommunity = (communityId: string, options: Options): Promise> => { return ( db .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter({ isOwner: true }) - // return an array of the userIds to be loaded by gql + .getAll([communityId, true], { index: 'communityIdAndIsModerator' }) + .skip(options.after || 0) + .limit(options.first || 25) .map(userCommunity => userCommunity('userId')) .run() ); }; -const DEFAULT_USER_COMMUNITY_PERMISSIONS = { +export const getOwnersInCommunity = ( + communityId: string, + options: Options +): Promise> => { + return db + .table('usersCommunities') + .getAll([communityId, true], { index: 'communityIdAndIsOwner' }) + .skip(options.after || 0) + .limit(options.first || 25) + .map(userCommunity => userCommunity('userId')) + .run(); +}; + +export const getTeamMembersInCommunity = ( + communityId: string, + options: Options +): Promise> => { + return db + .table('usersCommunities') + .getAll([communityId, true], { index: 'communityIdAndIsTeamMember' }) + .skip(options.after || 0) + .limit(options.first || 25) + .map(userCommunity => userCommunity('userId')) + .run(); +}; + +export const DEFAULT_USER_COMMUNITY_PERMISSIONS = { isOwner: false, isMember: false, isModerator: false, isBlocked: false, + isPending: false, receiveNotifications: false, reputation: 0, }; // NOTE @BRIAN: DEPRECATED - DONT USE IN THE FUTURE -const getUserPermissionsInCommunity = ( - communityId: string, - userId: string -): Promise => { +// prettier-ignore +export const getUserPermissionsInCommunity = (communityId: string, userId: string): Promise => { return db .table('usersCommunities') .getAll([userId, communityId], { @@ -362,10 +639,8 @@ const getUserPermissionsInCommunity = ( }); }; -const checkUserPermissionsInCommunity = ( - communityId: string, - userId: string -): Promise => { +// prettier-ignore +export const checkUserPermissionsInCommunity = (communityId: string, userId: string): Promise => { return db .table('usersCommunities') .getAll([userId, communityId], { index: 'userIdAndCommunityId' }) @@ -374,9 +649,8 @@ const checkUserPermissionsInCommunity = ( type UserIdAndCommunityId = [string, string]; -const getUsersPermissionsInCommunities = ( - input: Array -) => { +// prettier-ignore +export const getUsersPermissionsInCommunities = (input: Array) => { return db .table('usersCommunities') .getAll(...input, { index: 'userIdAndCommunityId' }) @@ -402,24 +676,21 @@ const getUsersPermissionsInCommunities = ( }); }; -const getReputationByUser = (userId: string): Promise => { +export const getReputationByUser = (userId: string): Promise => { return db .table('usersCommunities') - .getAll(userId, { index: 'userId' }) - .filter({ isMember: true }) + .getAll([userId, true], { index: 'userIdAndIsMember' }) .map(rec => rec('reputation')) - .reduce((l, r) => l.add(r)) + .count() .default(0) .run(); }; -const getUsersTotalReputation = ( - userIds: Array -): Promise> => { +// prettier-ignore +export const getUsersTotalReputation = (userIds: Array): Promise> => { return db .table('usersCommunities') - .getAll(...userIds, { index: 'userId' }) - .filter({ isMember: true }) + .getAll(...userIds.map(userId => ([userId, true])), { index: 'userIdAndIsMember' }) .group('userId') .map(rec => rec('reputation')) .reduce((l, r) => l.add(r)) @@ -435,28 +706,3 @@ const getUsersTotalReputation = ( ) ); }; - -module.exports = { - // modify and create - createOwnerInCommunity, - createMemberInCommunity, - removeMemberInCommunity, - removeMembersInCommunity, - blockUserInCommunity, - unblockUserInCommunity, - createModeratorInCommunity, - makeMemberModeratorInCommunity, - removeModeratorInCommunity, - removeModeratorsInCommunity, - // get - DEFAULT_USER_COMMUNITY_PERMISSIONS, - getMembersInCommunity, - getBlockedUsersInCommunity, - getModeratorsInCommunity, - getOwnersInCommunity, - getUserPermissionsInCommunity, - checkUserPermissionsInCommunity, - getReputationByUser, - getUsersTotalReputation, - getUsersPermissionsInCommunities, -}; diff --git a/api/models/usersDirectMessageThreads.js b/api/models/usersDirectMessageThreads.js index 44c07e5f22..23d918a1e3 100644 --- a/api/models/usersDirectMessageThreads.js +++ b/api/models/usersDirectMessageThreads.js @@ -1,5 +1,5 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); /* =========================================================== @@ -11,11 +11,8 @@ const { db } = require('./db'); // creates a single member in a direct message thread. invoked when a user is added // to an existing direct message thread (group thread only) -const createMemberInDirectMessageThread = ( - threadId: string, - userId: string, - setActive: boolean -): Promise => { +// prettier-ignore +const createMemberInDirectMessageThread = (threadId: string, userId: string, setActive: boolean): Promise => { return db .table('usersDirectMessageThreads') .insert( @@ -35,10 +32,8 @@ const createMemberInDirectMessageThread = ( // removes a single member from a channel. will be invoked if a user leaves // a channel -const removeMemberInDirectMessageThread = ( - threadId: string, - userId: string -): Promise => { +// prettier-ignore +const removeMemberInDirectMessageThread = (threadId: string, userId: string): Promise => { return db .table('usersDirectMessageThreads') .getAll(threadId, { index: 'threadId' }) @@ -50,9 +45,8 @@ const removeMemberInDirectMessageThread = ( // removes all the user relationships to a dm thread. will be invoked when a // dm thread is permanently deleted, at which point we don't want any records in the // database to show a user relationship to the deleted thread -const removeMembersInDirectMessageThread = ( - threadId: string -): Promise => { +// prettier-ignore +const removeMembersInDirectMessageThread = (threadId: string): Promise => { return db .table('usersDirectMessageThreads') .getAll(threadId, { index: 'threadId' }) @@ -60,10 +54,8 @@ const removeMembersInDirectMessageThread = ( .run(); }; -const setUserLastSeenInDirectMessageThread = ( - threadId: string, - userId: string -): Promise => { +// prettier-ignore +const setUserLastSeenInDirectMessageThread = (threadId: string, userId: string): Promise => { return db .table('usersDirectMessageThreads') .getAll(userId, { index: 'userId' }) @@ -80,11 +72,8 @@ const setUserLastSeenInDirectMessageThread = ( ); }; -const updateDirectMessageThreadNotificationStatusForUser = ( - threadId: string, - userId: string, - val: boolean -): Promise => { +// prettier-ignore +const updateDirectMessageThreadNotificationStatusForUser = (threadId: string, userId: string, val: boolean): Promise => { return db .table('usersDirectMessageThreads') .getAll(userId, { index: 'userId' }) @@ -103,9 +92,8 @@ const updateDirectMessageThreadNotificationStatusForUser = ( =========================================================== */ -const getMembersInDirectMessageThread = ( - threadId: string -): Promise> => { +// prettier-ignore +const getMembersInDirectMessageThread = (threadId: string): Promise> => { return db .table('usersDirectMessageThreads') .getAll(threadId, { index: 'threadId' }) @@ -116,9 +104,8 @@ const getMembersInDirectMessageThread = ( }; // for loader -const getMembersInDirectMessageThreads = ( - threadIds: Array -): Promise> => { +// prettier-ignore +const getMembersInDirectMessageThreads = (threadIds: Array): Promise> => { return db .table('usersDirectMessageThreads') .getAll(...threadIds, { index: 'threadId' }) diff --git a/api/models/usersNotifications.js b/api/models/usersNotifications.js deleted file mode 100644 index 595e35772a..0000000000 --- a/api/models/usersNotifications.js +++ /dev/null @@ -1,174 +0,0 @@ -// @flow -const { db } = require('./db'); -/* -=========================================================== - - MODIFYING AND CREATING DATA IN USERSNOTIFICATIONS - -=========================================================== -*/ - -// creates a single notification in the usersNotifications join table -export const createUsersNotification = ( - notificationId: string, - userId: string -): Promise => { - return db - .table('usersNotifications') - .insert( - { - notificationId, - userId, - createdAt: new Date(), - isRead: false, - isSeen: false, - }, - { returnChanges: true } - ) - .run() - .then(result => result.changes[0].new_val); -}; - -// marks one notification as read -export const markNotificationRead = ( - notificationId: string, - userId: string -): Promise => { - return db - .table('usersNotifications') - .getAll(notificationId, { index: 'notificationId' }) - .filter({ - userId, - }) - .update( - { - isRead: true, - }, - { returnChanges: 'always' } - ) - .run() - .then( - result => - result.changes.length > 0 - ? result.changes[0].new_val - : result.changes[0].old_val - ); -}; - -// marks one notification as read -export const markSingleNotificationSeen = ( - notificationId: string, - userId: string -): Promise => { - return db - .table('usersNotifications') - .getAll(notificationId, { index: 'notificationId' }) - .filter({ - userId, - }) - .update( - { - isSeen: true, - }, - { returnChanges: true } - ) - .run() - .then(() => true) - .catch(err => false); -}; - -export const markNotificationsSeen = (notifications: Array) => { - return db - .table('usersNotifications') - .getAll(...notifications, { index: 'notificationId' }) - .update({ - isSeen: true, - }) - .run(); -}; - -// marks all notifications for a user as seen -export const markAllNotificationsSeen = (userId: string): Promise => { - return db - .table('usersNotifications') - .getAll(userId, { index: 'userId' }) - .filter({ isSeen: false }) - .eqJoin('notificationId', db.table('notifications')) - .without({ left: ['createdAt', 'id'] }) - .zip() - .filter(row => row('context')('type').ne('DIRECT_MESSAGE_THREAD')) - .run() - .then(notifications => - markNotificationsSeen( - notifications - .filter(notification => !!notification) - .map(notification => notification.id) - ) - ) - .then(() => true) - .catch(err => false); -}; - -// marks all notifications for a user as read -export const markAllNotificationsRead = (userId: string): Promise => { - return db - .table('usersNotifications') - .getAll(userId, { index: 'userId' }) - .eqJoin('notificationId', db.table('notifications')) - .without({ left: ['createdAt', 'id'] }) - .zip() - .filter(row => row('context')('type').ne('DIRECT_MESSAGE_THREAD')) - .run() - .then(notifications => { - return Promise.all( - notifications.map(notification => { - return markNotificationRead(notification.notificationId, userId); - }) - ); - }) - .then(() => true) - .catch(err => false); -}; - -export const markDirectMessageNotificationsSeen = ( - userId: string -): Promise => { - return db - .table('usersNotifications') - .getAll(userId, { index: 'userId' }) - .filter({ isSeen: false }) - .eqJoin('notificationId', db.table('notifications')) - .without({ left: ['createdAt', 'id'] }) - .zip() - .filter(row => row('context')('type').eq('DIRECT_MESSAGE_THREAD')) - .run() - .then(notifications => - markNotificationsSeen( - notifications - .filter(notification => !!notification) - .map(notification => notification.id) - ) - ) - .then(() => true) - .catch(err => false); -}; - -/* -=========================================================== - - GETTING DATA FROM USERS NOTIFICATIONS - -=========================================================== -*/ - -export const getUsersNotifications = ( - userId: string -): Promise> => { - return db - .table('usersNotifications') - .getAll(userId, { index: 'userId' }) - .eqJoin('notificationId', db.table('notifications')) - .without({ left: ['createdAt', 'id'] }) - .zip() - .run(); -}; diff --git a/api/models/usersSettings.js b/api/models/usersSettings.js index 4e3a65160f..21cb2cb9ee 100644 --- a/api/models/usersSettings.js +++ b/api/models/usersSettings.js @@ -1,31 +1,43 @@ -const { db } = require('./db'); +const { db } = require('shared/db'); +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import type { DBUserSettings } from 'shared/types'; -export const createNewUsersSettings = (userId: string): Promise => { - return db.table('usersSettings').insert({ - userId, - notifications: { - types: { - newMessageInThreads: { - email: true, - }, - newMention: { - email: true, - }, - newDirectMessage: { - email: true, - }, - newThreadCreated: { - email: true, - }, - dailyDigest: { - email: true, - }, - weeklyDigest: { - email: true, +export const createNewUsersSettings = ( + userId: string +): Promise => { + return db + .table('usersSettings') + .insert( + { + userId, + notifications: { + types: { + newMessageInThreads: { + email: true, + }, + newMention: { + email: true, + }, + newDirectMessage: { + email: true, + }, + newThreadCreated: { + email: true, + }, + dailyDigest: { + email: true, + }, + weeklyDigest: { + email: true, + }, + }, }, }, - }, - }); + { returnChanges: 'always' } + ) + .run() + .then(res => res.changes[0].new_val); }; export const getUsersSettings = (userId: string): Promise => { @@ -37,14 +49,25 @@ export const getUsersSettings = (userId: string): Promise => { if (results && results.length > 0) { // if the user already has a relationship with the thread we don't need to do anything, return return results[0]; + } else { + return null; } }); }; -export const updateUsersNotificationSettings = ( - userId: string, - settings: object -): Promise => { +// prettier-ignore +export const updateUsersNotificationSettings = (userId: string, settings: object, type: string, method: string, enabled: string): Promise => { + const event = enabled ? events.USER_NOTIFICATIONS_DISABLED : events.USER_NOTIFICATIONS_ENABLED + + trackQueue.add({ + userId, + event, + properties: { + type, + method, + } + }) + return db .table('usersSettings') .getAll(userId, { index: 'userId' }) @@ -54,16 +77,54 @@ export const updateUsersNotificationSettings = ( .run(); }; -export const unsubscribeUserFromEmailNotification = ( - userId: string, - type: object -): Promise => { +// prettier-ignore +export const unsubscribeUserFromEmailNotification = (userId: string, type: object): Promise => { const obj = { notifications: { types: {} } }; obj['notifications']['types'][type] = { email: false }; + trackQueue.add({ + userId, + event: events.USER_NOTIFICATIONS_DISABLED, + properties: { + type: type, + method: 'email' + } + }) + return db .table('usersSettings') .getAll(userId, { index: 'userId' }) .update({ ...obj }) .run(); }; + +export const disableAllUsersEmailSettings = (userId: string) => { + return db + .table('usersSettings') + .getAll(userId, { index: 'userId' }) + .update({ + notifications: { + types: { + dailyDigest: { + email: false, + }, + newDirectMessage: { + email: false, + }, + newMention: { + email: false, + }, + newMessageInThreads: { + email: false, + }, + newThreadCreated: { + email: false, + }, + weeklyDigest: { + email: false, + }, + }, + }, + }) + .run(); +}; diff --git a/api/models/usersThreads.js b/api/models/usersThreads.js index 5b028b86fd..ebc671971c 100644 --- a/api/models/usersThreads.js +++ b/api/models/usersThreads.js @@ -1,14 +1,15 @@ // @flow -const { db } = require('./db'); +import type { DBUsersThreads } from 'shared/types'; +const { db } = require('shared/db'); +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; // invoked only when a thread is created or a user leaves a message on a thread. // Because a user could leave multiple messages on a thread, we first check // to see if a record exists with a relationship. If it does, we return and // do nothing. If it doesn't, we create the relationship. -export const createParticipantInThread = ( - threadId: string, - userId: string -): Promise => { +// prettier-ignore +export const createParticipantInThread = (threadId: string, userId: string): Promise => { return db .table('usersThreads') .getAll([userId, threadId], { index: 'userIdAndThreadId' }) @@ -21,6 +22,14 @@ export const createParticipantInThread = ( const { id, isParticipant, receiveNotifications } = result[0]; if (isParticipant) return; + if (receiveNotifications === true) { + trackQueue.add({ + userId, + event: events.THREAD_NOTIFICATIONS_ENABLED, + context: { threadId } + }) + } + // otherwise, mark them as a participant return db .table('usersThreads') @@ -34,6 +43,12 @@ export const createParticipantInThread = ( .run(); } else { // if there is no relationship with the thread, create one + trackQueue.add({ + userId, + event: events.THREAD_NOTIFICATIONS_ENABLED, + context: { threadId } + }) + return db.table('usersThreads').insert({ createdAt: new Date(), userId, @@ -45,61 +60,39 @@ export const createParticipantInThread = ( }); }; -export const createParticipantWithoutNotificationsInThread = ( - threadId: string, - userId: string -): Promise => { +// prettier-ignore +export const deleteParticipantInThread = (threadId: string, userId: string): Promise => { return db .table('usersThreads') .getAll([userId, threadId], { index: 'userIdAndThreadId' }) + .delete() .run() - .then(result => { - if (result && result.length > 0) { - // if the user already has a relationship with the thread we don't need to do anything, return - return; - } else { - // if there is no relationship with the thread, create one - return db.table('usersThreads').insert({ - createdAt: new Date(), - userId, - threadId, - isParticipant: true, - receiveNotifications: false, - }); - } - }); }; -export const deleteParticipantInThread = ( - threadId: string, - userId: string -): Promise => { +// Users can opt in to notifications on a thread without having to leave a message or be the thread creator. This will only activate notifications and the user will not appear as a participant in the UI +// prettier-ignore +export const createNotifiedUserInThread = (threadId: string, userId: string): Promise => { return db .table('usersThreads') - .getAll([userId, threadId], { index: 'userIdAndThreadId' }) - .delete() - .run(); -}; - -/* - Users can opt in to notifications on a thread without having to leave a message or be the thread creator. This will only activate notifications and the user will not appear as a participant in the UI -*/ -export const createNotifiedUserInThread = ( - threadId: string, - userId: string -): Promise => { - return db.table('usersThreads').insert({ - createdAt: new Date(), - userId, - threadId, - isParticipant: false, - receiveNotifications: true, - }); + .insert({ + createdAt: new Date(), + userId, + threadId, + isParticipant: false, + receiveNotifications: true, + }) + .run() + .then(() => { + trackQueue.add({ + userId, + event: events.THREAD_NOTIFICATIONS_ENABLED, + context: { threadId } + }) + }) }; -export const getParticipantsInThread = ( - threadId: string -): Promise> => { +// prettier-ignore +export const getParticipantsInThread = (threadId: string): Promise> => { return db .table('usersThreads') .getAll(threadId, { index: 'threadId' }) @@ -126,21 +119,22 @@ export const getParticipantsInThreads = (threadIds: Array) => { .run(); }; -export const getThreadNotificationStatusForUser = ( - threadId: string, - userId: string -): Promise> => { +// prettier-ignore +export const getThreadNotificationStatusForUser = (threadId: string, userId: string): Promise => { return db .table('usersThreads') .getAll([userId, threadId], { index: 'userIdAndThreadId' }) - .run(); + .run() + .then(results => { + if (!results || results.length === 0) return null; + return results[0]; + }); }; type UserIdAndThreadId = [string, string]; -export const getThreadsNotificationStatusForUsers = ( - input: Array -) => { +// prettier-ignore +export const getThreadsNotificationStatusForUsers = (input: Array) => { return db .table('usersThreads') .getAll(...input, { index: 'userIdAndThreadId' }) @@ -152,11 +146,8 @@ export const getThreadsNotificationStatusForUsers = ( }); }; -export const updateThreadNotificationStatusForUser = ( - threadId: string, - userId: string, - value: boolean -): Promise => { +// prettier-ignore +export const updateThreadNotificationStatusForUser = (threadId: string, userId: string, value: boolean): Promise => { return db .table('usersThreads') .getAll([userId, threadId], { index: 'userIdAndThreadId' }) @@ -165,6 +156,15 @@ export const updateThreadNotificationStatusForUser = ( // if no record exists, the user is trying to mute a thread they // aren't a member of - e.g. someone mentioned them in a thread // so create a record + + const event = value ? events.THREAD_NOTIFICATIONS_ENABLED : events.THREAD_NOTIFICATIONS_DISABLED + + trackQueue.add({ + userId, + event, + context: { threadId } + }) + if (!results || results.length === 0) { return db.table('usersThreads').insert({ createdAt: new Date(), @@ -172,7 +172,8 @@ export const updateThreadNotificationStatusForUser = ( threadId, isParticipant: false, receiveNotifications: value, - }); + }) + .run() } const record = results[0]; @@ -187,9 +188,8 @@ export const updateThreadNotificationStatusForUser = ( }; // when a thread is deleted, we make sure all relationships to that thread have notifications turned off -export const turnOffAllThreadNotifications = ( - threadId: string -): Promise => { +// prettier-ignore +export const turnOffAllThreadNotifications = (threadId: string): Promise => { return db .table('usersThreads') .getAll(threadId, { index: 'threadId' }) @@ -198,3 +198,13 @@ export const turnOffAllThreadNotifications = ( }) .run(); }; + +export const disableAllThreadNotificationsForUser = (userId: string) => { + return db + .table('usersThreads') + .getAll(userId, { index: 'userId' }) + .update({ + receiveNotifications: false, + }) + .run(); +}; diff --git a/api/models/utils.js b/api/models/utils.js index 98bcc7ca85..7af288ea1c 100644 --- a/api/models/utils.js +++ b/api/models/utils.js @@ -1,6 +1,5 @@ // @flow -const debug = require('debug')('api:models:utils'); -import { db } from './db'; +import { db } from 'shared/db'; export const NEW_DOCUMENTS = db .row('old_val') @@ -54,18 +53,18 @@ export const getAu = (range: Timeframe) => { const { current } = parseRange(range); return db .table('users') - .filter(db.row('lastSeen').during(db.now().sub(current), db.now())) + .filter(row => + row + .hasFields('lastSeen') + .and(row('lastSeen').during(db.now().sub(current), db.now())) + ) .count() .default(0) .run(); }; -export const getGrowth = async ( - table: string, - range: Timeframe, - field: string, - filter: ?mixed -) => { +// prettier-ignore +export const getGrowth = async (table: string, range: Timeframe, field: string, filter: ?mixed) => { const { current, previous } = parseRange(range); const currentPeriodCount = await db .table(table) @@ -107,6 +106,8 @@ export const getCount = (table: string, filter: mixed) => { export const getCoreMetrics = () => { return db .table('coreMetrics') + .orderBy(db.desc('date')) + .limit(90) .orderBy('date') .run(); }; diff --git a/api/models/web-push-subscription.js b/api/models/web-push-subscription.js index f83d694b08..c5107a4275 100644 --- a/api/models/web-push-subscription.js +++ b/api/models/web-push-subscription.js @@ -1,12 +1,10 @@ // @flow const debug = require('debug')('api:models:webPushSubscription'); -const { db } = require('./db'); +const { db } = require('shared/db'); import type { WebPushSubscription } from '../mutations/user'; -export const storeSubscription = ( - subscription: WebPushSubscription, - userId: string -) => { +// prettier-ignore +export const storeSubscription = (subscription: WebPushSubscription, userId: string) => { debug( `store subscription for user#${userId}, endpoint ${subscription.endpoint}` ); diff --git a/api/mutations/channel/archiveChannel.js b/api/mutations/channel/archiveChannel.js index c0d26dae71..f6feb2b679 100644 --- a/api/mutations/channel/archiveChannel.js +++ b/api/mutations/channel/archiveChannel.js @@ -1,61 +1,63 @@ // @flow import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; -import { getUserPermissionsInChannel } from '../../models/usersChannels'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; -import { getChannelById, archiveChannel } from '../../models/channel'; - -export default async ( - _: any, - { input: { channelId } }: { input: { channelId: string } }, - { user }: GraphQLContext -) => { - const currentUser = user; - - // user must be authed to delete a channel - if (!currentUser) { - return new UserError( - 'You must be signed in to make changes to this channel.' - ); - } - - const [channelToEvaluate, currentUserChannelPermissions] = await Promise.all([ - // get the channel to evaluate - getChannelById(channelId), - // get the channel's permissions - getUserPermissionsInChannel(channelId, currentUser.id), - ]); +import { archiveChannel } from '../../models/channel'; +import { + isAuthedResolver as requireAuth, + canModerateChannel, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; + +type Input = { + input: { + channelId: string, + }, +}; - // if channel wasn't found or was previously deleted, something - // has gone wrong and we need to escape - if (!channelToEvaluate || channelToEvaluate.deletedAt) { - return new UserError("Channel doesn't exist"); +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { channelId } = args.input; + const { user, loaders } = ctx; + + const channelToEvaluate = await loaders.channel.load(channelId); + + if (!await canModerateChannel(user.id, channelId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_ARCHIVED_FAILED, + context: { channelId }, + properties: { + reason: 'no permission', + }, + }); + return new UserError('You don’t have permission to archive this channel'); } if (channelToEvaluate.archivedAt) { - return new UserError('Channel already archived'); + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_ARCHIVED_FAILED, + context: { channelId }, + properties: { + reason: 'channel already archived', + }, + }); + return new UserError('This channel is already archived'); } if (channelToEvaluate.slug === 'general') { - return new UserError("The general channel can't be archived"); - } - - // get the community parent of the channel being deleted - const currentUserCommunityPermissions = await getUserPermissionsInCommunity( - channelToEvaluate.communityId, - currentUser.id - ); - - if ( - currentUserCommunityPermissions.isOwner || - currentUserChannelPermissions.isOwner || - currentUserCommunityPermissions.isModerator || - currentUserChannelPermissions.isModerator - ) { - return await archiveChannel(channelId); + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_ARCHIVED_FAILED, + context: { channelId }, + properties: { + reason: 'general channel', + }, + }); + return new UserError( + 'The general channel in a community can’t be archived' + ); } - return new UserError( - "You don't have permission to make changes to this channel" - ); -}; + return await archiveChannel(channelId, user.id); +}); diff --git a/api/mutations/channel/createChannel.js b/api/mutations/channel/createChannel.js index 571ac9d8d8..c18656b13b 100644 --- a/api/mutations/channel/createChannel.js +++ b/api/mutations/channel/createChannel.js @@ -3,73 +3,73 @@ import type { GraphQLContext } from '../../'; import type { CreateChannelInput } from '../../models/channel'; import UserError from '../../utils/UserError'; import { channelSlugIsBlacklisted } from '../../utils/permissions'; -import { getCommunities } from '../../models/community'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; import { getChannelBySlug, createChannel } from '../../models/channel'; import { createOwnerInChannel } from '../../models/usersChannels'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -export default async ( - _: any, - args: CreateChannelInput, - { user }: GraphQLContext -) => { - const currentUser = user; +export default requireAuth( + async (_: any, args: CreateChannelInput, ctx: GraphQLContext) => { + const { user, loaders } = ctx; - // user must be authed to create a channel - if (!currentUser) { - return new UserError('You must be signed in to create a new community.'); - } - - if (channelSlugIsBlacklisted(args.input.slug)) { - return new UserError('This channel name is reserved.'); - } + // TODO: Figure out how to not have to do this - somehow combine forces with canModerateChannel function which is fetching most of the same data anyways + const community = await loaders.community.load(args.input.communityId); - const [communities, currentUserCommunityPermissions] = await Promise.all([ - // get the community parent where the channel is being created - getCommunities([args.input.communityId]), - // get the permission of the user in the parent community - getUserPermissionsInCommunity(args.input.communityId, currentUser.id), - ]); + if (!await canModerateCommunity(user.id, args.input.communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_CREATED_FAILED, + context: { communityId: community.id }, + properties: { + reason: 'no permission', + }, + }); + return new UserError( + 'You don’t have permission to create channels in this community' + ); + } - const communityToEvaluate = communities && communities[0]; + if (channelSlugIsBlacklisted(args.input.slug)) { + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_CREATED_FAILED, + context: { communityId: community.id }, + properties: { + reason: 'slug blacklisted', + }, + }); + return new UserError( + 'This channel url is reserved - please try another name' + ); + } - // if there is no community being evaluated, we can assume the - // community doesn't exist any more - if (!communityToEvaluate) { - return new UserError( - "You don't have permission to create a channel in this community." + const channelWithSlug = await getChannelBySlug( + args.input.slug, + community.slug ); - } - // if the current user is not the owner or moderator of the parent community - // they can not create channels - if ( - !currentUserCommunityPermissions.isOwner && - !currentUserCommunityPermissions.isModerator - ) { - return new UserError( - "You don't have permission to create a channel in this community." - ); - } + if (channelWithSlug) { + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_CREATED_FAILED, + context: { communityId: community.id }, + properties: { + reason: 'slug taken', + }, + }); + return new UserError( + 'A channel with this url already exists in this community - please try another name' + ); + } - const channelWithSlug = await getChannelBySlug( - args.input.slug, - communityToEvaluate.slug - ); + const newChannel = await createChannel(args, user.id); - // if a channel is returned, it means a duplicate was being created - // so we need to escape - if (channelWithSlug) { - return new UserError('A channel with this slug already exists.'); + return await createOwnerInChannel(newChannel.id, user.id).then( + () => newChannel + ); } - - // if no channel was returned, it means we are creating a unique - // new channel and can proceed - const channel = await createChannel(args, currentUser.id); - - // once the channel is created, create the user's relationship with - // the new channel - return await createOwnerInChannel(channel.id, currentUser.id).then( - () => channel - ); -}; +); diff --git a/api/mutations/channel/deleteChannel.js b/api/mutations/channel/deleteChannel.js index 5440e13db6..4a04830724 100644 --- a/api/mutations/channel/deleteChannel.js +++ b/api/mutations/channel/deleteChannel.js @@ -1,86 +1,60 @@ // @flow import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; -import { - getUserPermissionsInChannel, - removeMembersInChannel, -} from '../../models/usersChannels'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; -import { getChannels, deleteChannel } from '../../models/channel'; +import { removeMembersInChannel } from '../../models/usersChannels'; +import { getChannelById, deleteChannel } from '../../models/channel'; import { getThreadsByChannelToDelete, deleteThread } from '../../models/thread'; +import { + isAuthedResolver as requireAuth, + canModerateChannel, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; + +type Input = { + channelId: string, +}; -export default async ( - _: any, - { channelId }: { channelId: string }, - { user }: GraphQLContext -) => { - const currentUser = user; - - // user must be authed to delete a channel - if (!currentUser) { - return new UserError( - 'You must be signed in to make changes to this channel.' - ); +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { channelId } = args; + const { user, loaders } = ctx; + + if (!await canModerateChannel(user.id, channelId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_DELETED_FAILED, + context: { channelId }, + properties: { + reason: 'no permission', + }, + }); + return new UserError('You don’t have permission to manage this channel'); } - const [channels, currentUserChannelPermissions] = await Promise.all([ - // get the channel to evaluate - getChannels([channelId]), - // get the channel's permissions - getUserPermissionsInChannel(channelId, currentUser.id), - ]); - - // select the channel to evaluate - const channelToEvaluate = channels && channels[0]; + const channel = await getChannelById(channelId); - // if channel wasn't found or was previously deleted, something - // has gone wrong and we need to escape - if (!channelToEvaluate || channelToEvaluate.deletedAt) { - return new UserError("Channel doesn't exist"); - } + if (channel.slug === 'general') { + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_DELETED_FAILED, + context: { channelId }, + properties: { + reason: 'general channel', + }, + }); - if (channelToEvaluate.slug === 'general') { return new UserError("The general channel can't be deleted"); } - // get the community parent of the channel being deleted - const currentUserCommunityPermissions = await getUserPermissionsInCommunity( - channelToEvaluate.communityId, - currentUser.id - ); - - if ( - currentUserCommunityPermissions.isOwner || - currentUserChannelPermissions.isOwner || - currentUserCommunityPermissions.isModerator || - currentUserChannelPermissions.isModerator - ) { - // all checks passed - // delete the channel requested from the client side user - const deleteTheInputChannel = deleteChannel(channelId); - - // get all the threads in the channel to prepare for deletion - const getAllThreadsInChannel = getThreadsByChannelToDelete(channelId); - - // update all the UsersChannels objects in the db to be non-members - const removeRelationships = removeMembersInChannel(channelId); - - // eslint-disable-next-line - const [allThreadsInChannel, __, ___] = await Promise.all([ - getAllThreadsInChannel, - deleteTheInputChannel, - removeRelationships, - ]); - - // if there were no threads in that channel, we are done - if (allThreadsInChannel.length === 0) return true; + const [allThreadsInChannel] = await Promise.all([ + getThreadsByChannelToDelete(channelId), + deleteChannel(channelId, user.id), + removeMembersInChannel(channelId), + ]); - // otherwise we need to mark all the threads in that channel - // as deleted - return allThreadsInChannel.map(thread => deleteThread(thread.id)); - } + if (allThreadsInChannel.length === 0) return true; - return new UserError( - "You don't have permission to make changes to this channel" + return allThreadsInChannel.map( + async thread => await deleteThread(thread.id, user.id) ); -}; +}); diff --git a/api/mutations/channel/disableChannelTokenJoin.js b/api/mutations/channel/disableChannelTokenJoin.js index f5b15d4af4..0bc4d3dde1 100644 --- a/api/mutations/channel/disableChannelTokenJoin.js +++ b/api/mutations/channel/disableChannelTokenJoin.js @@ -2,41 +2,40 @@ import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; import { - createChannelSettings, + getOrCreateChannelSettings, disableChannelTokenJoin, } from '../../models/channelSettings'; +import { + isAuthedResolver as requireAuth, + canModerateChannel, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -type DisableChannelTokenJoinInput = { +type Input = { input: { id: string, }, }; -export default async ( - _: any, - { input: { id: channelId } }: DisableChannelTokenJoinInput, - { user, loaders }: GraphQLContext -) => { - const currentUser = user; - if (!currentUser) { - return new UserError('You must be signed in to manage this channel.'); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { id: channelId } = args.input; + const { user, loaders } = ctx; - const [permissions, settings] = await Promise.all([ - loaders.userPermissionsInChannel.load([currentUser.id, channelId]), - loaders.channelSettings.load(channelId), - ]); + if (!await canModerateChannel(user.id, channelId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_JOIN_TOKEN_DISABLED_FAILED, + context: { channelId }, + properties: { + reason: 'no permission', + }, + }); - if (!permissions.isOwner) { - return new UserError("You don't have permission to do this."); + return new UserError('You don’t have permission to manage this channel'); } - loaders.channelSettings.clear(channelId); - - // settings.id tells us that a channelSettings record exists in the db - if (settings.id) { - return await disableChannelTokenJoin(channelId); - } else { - return await createChannelSettings(channelId); - } -}; + return await getOrCreateChannelSettings(channelId).then( + async () => await disableChannelTokenJoin(channelId, user.id) + ); +}); diff --git a/api/mutations/channel/editChannel.js b/api/mutations/channel/editChannel.js index 649359b903..8114eb6705 100644 --- a/api/mutations/channel/editChannel.js +++ b/api/mutations/channel/editChannel.js @@ -2,63 +2,38 @@ import type { GraphQLContext } from '../../'; import type { EditChannelInput } from '../../models/channel'; import UserError from '../../utils/UserError'; +import { approvePendingUsersInChannel } from '../../models/usersChannels'; +import { editChannel } from '../../models/channel'; import { - getUserPermissionsInChannel, - approvePendingUsersInChannel, -} from '../../models/usersChannels'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; -import { editChannel, getChannels } from '../../models/channel'; - -export default async ( - _: any, - args: EditChannelInput, - { user }: GraphQLContext -) => { - const currentUser = user; - - // user must be authed to edit a channel - if (!currentUser) { - return new UserError( - 'You must be signed in to make changes to this channel.' - ); - } - - const [channels, currentUserChannelPermissions] = await Promise.all([ - getChannels([args.input.channelId]), - getUserPermissionsInChannel(args.input.channelId, currentUser.id), - ]); - - // select the channel to evaluate - const channelToEvaluate = channels && channels[0]; - - // if a channel wasn't found or was deleted - if (!channelToEvaluate || channelToEvaluate.deletedAt) { - return new UserError("This channel doesn't exist"); - } - - // get the community parent of the channel being deleted - const currentUserCommunityPermissions = await getUserPermissionsInCommunity( - channelToEvaluate.communityId, - currentUser.id - ); + isAuthedResolver as requireAuth, + canModerateChannel, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; + +export default requireAuth( + async (_: any, args: EditChannelInput, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + + const channel = await loaders.channel.load(args.input.channelId); + + if (!await canModerateChannel(user.id, args.input.channelId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_EDITED_FAILED, + context: { channelId: args.input.channelId }, + properties: { + reason: 'no permission', + }, + }); + + return new UserError('You don’t have permission to manage this channel'); + } - if ( - currentUserCommunityPermissions.isOwner || - currentUserChannelPermissions.isOwner || - currentUserCommunityPermissions.isModerator || - currentUserChannelPermissions.isModerator - ) { - // all checks passed - // if a channel is being converted from private to public, make - // all the pending users members in the channel - if (channelToEvaluate.isPrivate && !args.input.isPrivate) { + if (channel.isPrivate && !args.input.isPrivate) { approvePendingUsersInChannel(args.input.channelId); } - return editChannel(args); + return editChannel(args, user.id); } - - return new UserError( - "You don't have permission to make changes to this channel." - ); -}; +); diff --git a/api/mutations/channel/enableChannelTokenJoin.js b/api/mutations/channel/enableChannelTokenJoin.js index 877a4d1efd..bd3ab465a4 100644 --- a/api/mutations/channel/enableChannelTokenJoin.js +++ b/api/mutations/channel/enableChannelTokenJoin.js @@ -2,43 +2,40 @@ import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; import { - createChannelSettings, + getOrCreateChannelSettings, enableChannelTokenJoin, } from '../../models/channelSettings'; +import { + isAuthedResolver as requireAuth, + canModerateChannel, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -type EnableTokenJoinInput = { +type Input = { input: { id: string, }, }; -export default async ( - _: any, - { input: { id: channelId } }: EnableTokenJoinInput, - { user, loaders }: GraphQLContext -) => { - const currentUser = user; - if (!currentUser) { - return new UserError('You must be signed in to manage this channel.'); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { id: channelId } = args.input; + const { user, loaders } = ctx; - const [permissions, settings] = await Promise.all([ - loaders.userPermissionsInChannel.load([currentUser.id, channelId]), - loaders.channelSettings.load(channelId), - ]); + if (!await canModerateChannel(user.id, channelId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_JOIN_TOKEN_ENABLED_FAILED, + context: { channelId }, + properties: { + reason: 'no permission', + }, + }); - if (!permissions.isOwner) { - return new UserError("You don't have permission to do this."); + return new UserError('You don’t have permission to manage this channel'); } - loaders.channelSettings.clear(channelId); - - // settings.id tells us that a channelSettings record exists in the db - if (settings.id) { - return await enableChannelTokenJoin(channelId); - } else { - return await createChannelSettings(channelId).then(() => - enableChannelTokenJoin(channelId) - ); - } -}; + return await getOrCreateChannelSettings(channelId).then( + async () => await enableChannelTokenJoin(channelId, user.id) + ); +}); diff --git a/api/mutations/channel/index.js b/api/mutations/channel/index.js index 5d1323407a..ed3a6b0a07 100644 --- a/api/mutations/channel/index.js +++ b/api/mutations/channel/index.js @@ -12,6 +12,7 @@ import joinChannelWithToken from './joinChannelWithToken'; import enableChannelTokenJoin from './enableChannelTokenJoin'; import disableChannelTokenJoin from './disableChannelTokenJoin'; import resetChannelJoinToken from './resetChannelJoinToken'; +import updateChannelSlackBotLinks from './updateChannelSlackBotLinks'; module.exports = { Mutation: { @@ -28,5 +29,6 @@ module.exports = { enableChannelTokenJoin, disableChannelTokenJoin, resetChannelJoinToken, + updateChannelSlackBotLinks, }, }; diff --git a/api/mutations/channel/joinChannelWithToken.js b/api/mutations/channel/joinChannelWithToken.js index 4461a550ee..e6e9887628 100644 --- a/api/mutations/channel/joinChannelWithToken.js +++ b/api/mutations/channel/joinChannelWithToken.js @@ -12,9 +12,12 @@ import { getUserPermissionsInCommunity, createMemberInCommunity, } from '../../models/usersCommunities'; -import { getChannelSettings } from '../../models/channelSettings'; +import { getOrCreateChannelSettings } from '../../models/channelSettings'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; -type JoinChannelWithTokenInput = { +type Input = { input: { communitySlug: string, channelSlug: string, @@ -22,22 +25,23 @@ type JoinChannelWithTokenInput = { }, }; -export default async ( - _: any, - { input }: JoinChannelWithTokenInput, - { user }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser) { - return new UserError('You must be signed in to to join this channel.'); - } - - const { communitySlug, channelSlug, token } = input; +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user } = ctx; + const { communitySlug, channelSlug, token } = args.input; const channel = await getChannelBySlug(channelSlug, communitySlug); - if (!channel) return new UserError('No channel found in this community'); + if (!channel) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_CHANNEL_WITH_TOKEN_FAILED, + properties: { + reason: 'no channel', + }, + }); + + return new UserError('No channel found in this community'); + } if (!channel.isPrivate) { return channel; @@ -48,9 +52,9 @@ export default async ( channelPermissions, settings, ] = await Promise.all([ - getUserPermissionsInCommunity(channel.communityId, currentUser.id), - getUserPermissionsInChannel(channel.id, currentUser.id), - getChannelSettings(channel.id), + getUserPermissionsInCommunity(channel.communityId, user.id), + getUserPermissionsInChannel(channel.id, user.id), + getOrCreateChannelSettings(channel.id), ]); if ( @@ -62,10 +66,28 @@ export default async ( } if (channelPermissions.isBlocked || communityPermissions.isBlocked) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_CHANNEL_WITH_TOKEN_FAILED, + context: { channelId: channel.id }, + properties: { + reason: 'no permission', + }, + }); + return new UserError("You don't have permission to view this channel"); } - if (!settings.joinSettings.tokenJoinEnabled) { + if (!settings.joinSettings || !settings.joinSettings.tokenJoinEnabled) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_CHANNEL_WITH_TOKEN_FAILED, + context: { channelId: channel.id }, + properties: { + reason: 'no token or changed token', + }, + }); + return new UserError( "You can't join at this time, the token may have changed" ); @@ -74,6 +96,15 @@ export default async ( settings.joinSettings.tokenJoinEnabled && token !== settings.joinSettings.token ) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_CHANNEL_WITH_TOKEN_FAILED, + context: { channelId: channel.id }, + properties: { + reason: 'no token or changed token', + }, + }); + return new UserError( "You can't join at this time, the token may have changed" ); @@ -81,26 +112,35 @@ export default async ( if (!communityPermissions.isMember) { return await Promise.all([ - createMemberInCommunity(channel.communityId, currentUser.id), - createMemberInDefaultChannels(channel.communityId, currentUser.id), + createMemberInCommunity(channel.communityId, user.id), + createMemberInDefaultChannels(channel.communityId, user.id), ]) .then(async () => { if (channelPermissions.isPending) { - return await approvePendingUserInChannel(channel.id, currentUser.id); + return await approvePendingUserInChannel(channel.id, user.id); } else { - return await createMemberInChannel(channel.id, currentUser.id); + return await createMemberInChannel(channel.id, user.id, true); } }) .then(joinedChannel => joinedChannel); } if (channelPermissions.isPending) { - return await approvePendingUserInChannel(channel.id, currentUser.id); + return await approvePendingUserInChannel(channel.id, user.id); } if (!channelPermissions.isMember) { - return await createMemberInChannel(channel.id, currentUser.id); + return await createMemberInChannel(channel.id, user.id, true); } + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_CHANNEL_WITH_TOKEN_FAILED, + context: { channelId: channel.id }, + properties: { + reason: 'unknown error', + }, + }); + return new UserError("Couldn't authenticate this request to join a channel"); -}; +}); diff --git a/api/mutations/channel/resetChannelJoinToken.js b/api/mutations/channel/resetChannelJoinToken.js index 86aa45b63b..d2642c7731 100644 --- a/api/mutations/channel/resetChannelJoinToken.js +++ b/api/mutations/channel/resetChannelJoinToken.js @@ -2,44 +2,40 @@ import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; import { - createChannelSettings, - enableChannelTokenJoin, + getOrCreateChannelSettings, resetChannelJoinToken, } from '../../models/channelSettings'; +import { + isAuthedResolver as requireAuth, + canModerateChannel, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -type ResetJoinTokenInput = { +type Input = { input: { id: string, }, }; -export default async ( - _: any, - { input: { id: channelId } }: ResetJoinTokenInput, - { user, loaders }: GraphQLContext -) => { - const currentUser = user; - if (!currentUser) { - return new UserError('You must be signed in to manage this channel.'); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { id: channelId } = args.input; + const { user, loaders } = ctx; - const [permissions, settings] = await Promise.all([ - loaders.userPermissionsInChannel.load([currentUser.id, channelId]), - loaders.channelSettings.load(channelId), - ]); + if (!await canModerateChannel(user.id, channelId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_JOIN_TOKEN_RESET_FAILED, + context: { channelId }, + properties: { + reason: 'no permission', + }, + }); - if (!permissions.isOwner) { - return new UserError("You don't have permission to do this."); + return new UserError('You don’t have permission to manage this channel'); } - loaders.channelSettings.clear(channelId); - - // settings.id tells us that a channelSettings record exists in the db - if (settings.id) { - return await resetChannelJoinToken(channelId); - } else { - return await createChannelSettings(channelId).then( - async () => await enableChannelTokenJoin(channelId) - ); - } -}; + return await getOrCreateChannelSettings(channelId).then( + async () => await resetChannelJoinToken(channelId, user.id) + ); +}); diff --git a/api/mutations/channel/restoreChannel.js b/api/mutations/channel/restoreChannel.js index 1e6e2d3f96..2b3112baaf 100644 --- a/api/mutations/channel/restoreChannel.js +++ b/api/mutations/channel/restoreChannel.js @@ -1,57 +1,50 @@ // @flow import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; -import { getUserPermissionsInChannel } from '../../models/usersChannels'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; -import { getChannelById, restoreChannel } from '../../models/channel'; - -export default async ( - _: any, - { input: { channelId } }: { input: { channelId: string } }, - { user }: GraphQLContext -) => { - const currentUser = user; - - // user must be authed to delete a channel - if (!currentUser) { - return new UserError( - 'You must be signed in to make changes to this channel.' - ); - } - - const [channelToEvaluate, currentUserChannelPermissions] = await Promise.all([ - // get the channel to evaluate - getChannelById(channelId), - // get the channel's permissions - getUserPermissionsInChannel(channelId, currentUser.id), - ]); +import { restoreChannel } from '../../models/channel'; +import { + isAuthedResolver as requireAuth, + canModerateChannel, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; + +type Input = { + input: { + channelId: string, + }, +}; - // if channel wasn't found or was previously deleted, something - // has gone wrong and we need to escape - if (!channelToEvaluate || channelToEvaluate.deletedAt) { - return new UserError("Channel doesn't exist"); +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { channelId } = args.input; + const { user, loaders } = ctx; + + // TODO: Figure out how to not have to do this - somehow combine forces with canModerateChannel function which is fetching most of the same data anyways + const channel = await loaders.channel.load(channelId); + + if (!await canModerateChannel(user.id, channelId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_RESTORED_FAILED, + context: { channelId }, + properties: { + reason: 'no permission', + }, + }); + return new UserError('You don’t have permission to manage this channel'); } - if (!channelToEvaluate.archivedAt) { + if (!channel.archivedAt) { + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_RESTORED_FAILED, + context: { channelId }, + properties: { + reason: 'channel already restored', + }, + }); return new UserError('Channel already restored'); } - // get the community parent of the channel being deleted - const currentUserCommunityPermissions = await getUserPermissionsInCommunity( - channelToEvaluate.communityId, - currentUser.id - ); - - if ( - currentUserCommunityPermissions.isOwner || - currentUserChannelPermissions.isOwner || - currentUserCommunityPermissions.isModerator || - currentUserChannelPermissions.isModerator - ) { - return await restoreChannel(channelId); - } - - return new UserError( - "You don't have permission to make changes to this channel" - ); -}; + return await restoreChannel(channelId, user.id); +}); diff --git a/api/mutations/channel/sendChannelEmailInvites.js b/api/mutations/channel/sendChannelEmailInvites.js deleted file mode 100644 index e2a6e102fb..0000000000 --- a/api/mutations/channel/sendChannelEmailInvites.js +++ /dev/null @@ -1,61 +0,0 @@ -// @flow -import type { GraphQLContext } from '../../'; -import { getUserPermissionsInChannel } from '../../models/usersChannels'; -import { sendPrivateChannelInviteNotificationQueue } from 'shared/bull/queues'; -import UserError from '../../utils/UserError'; - -type Contact = { - email: string, - firstName: string, - lastName: string, -}; - -type EmailInvitesInput = { - customMessage: ?string, - contacts: Array, - id: string, -}; - -export default async ( - _: any, - { input }: { input: EmailInvitesInput }, - { user }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser) { - return new UserError( - 'You must be signed in to invite people to this channel.' - ); - } - - // make sure the user is the owner of the channel - const permissions = await getUserPermissionsInChannel( - input.id, - currentUser.id - ); - - if (!permissions.isOwner) { - return new UserError( - "You don't have permission to invite people to this channel." - ); - } - - return ( - input.contacts - // can't invite yourself - .filter(contact => contact.email !== currentUser.email) - .map(contact => { - return sendPrivateChannelInviteNotificationQueue.add({ - recipient: { - email: contact.email, - firstName: contact.firstName ? contact.firstName : null, - lastName: contact.lastName ? contact.lastName : null, - }, - channelId: input.id, - senderId: currentUser.id, - customMessage: input.customMessage ? input.customMessage : null, - }); - }) - ); -}; diff --git a/api/mutations/channel/toggleChannelNotifications.js b/api/mutations/channel/toggleChannelNotifications.js index c1dc096e01..b4b37d94ff 100644 --- a/api/mutations/channel/toggleChannelNotifications.js +++ b/api/mutations/channel/toggleChannelNotifications.js @@ -1,44 +1,47 @@ // @flow import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; -import { - getUserPermissionsInChannel, - toggleUserChannelNotifications, -} from '../../models/usersChannels'; -import { getChannels } from '../../models/channel'; +import { toggleUserChannelNotifications } from '../../models/usersChannels'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -export default async ( - _: any, - { channelId }: { channelId: string }, - { user }: GraphQLContext -) => { - const currentUser = user; +type Input = { + channelId: string, +}; - // user must be authed to join a channel - if (!currentUser) { - return new UserError( - 'You must be signed in to get notifications for this channel.' - ); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { channelId } = args; + const { user, loaders } = ctx; - // get the current user's permissions in the channel - const permissions = await getUserPermissionsInChannel( - channelId, - currentUser.id - ); + const [channel, permissions] = await Promise.all([ + loaders.channel.load(channelId), + loaders.userPermissionsInChannel.load([user.id, channelId]), + ]); - // user is blocked, they can't join the channel - if (permissions.isBlocked || !permissions.isMember) { + // only reject if the user is blocked in the channel + if (permissions && permissions.isBlocked) { + let event = !permissions + ? events.CHANNEL_NOTIFICATIONS_ENABLED_FAILED + : permissions.receiveNotifications + ? events.CHANNEL_NOTIFICATIONS_DISABLED_FAILED + : events.CHANNEL_NOTIFICATIONS_ENABLED_FAILED; + + trackQueue.add({ + userId: user.id, + event, + context: { channelId }, + properties: { + reason: 'no permission', + }, + }); return new UserError("You don't have permission to do that."); } - // pass in the oppositve value of the current user's subscriptions - const value = !permissions.receiveNotifications; - return toggleUserChannelNotifications(currentUser.id, channelId, value).then( - async () => { - // return the channel being evaluated - const channels = await getChannels([channelId]); - return channels[0]; - } + // if the user hasn't joined the channel, they are trying to join it now + const value = permissions ? !permissions.receiveNotifications : true; + + return toggleUserChannelNotifications(user.id, channelId, value).then( + () => channel ); -}; +}); diff --git a/api/mutations/channel/toggleChannelSubscription.js b/api/mutations/channel/toggleChannelSubscription.js index 1d3984d7e0..9ad62e1496 100644 --- a/api/mutations/channel/toggleChannelSubscription.js +++ b/api/mutations/channel/toggleChannelSubscription.js @@ -1,7 +1,7 @@ // @flow import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; -import { getChannels } from '../../models/channel'; +import { getChannelById } from '../../models/channel'; import { userIsMemberOfAnyChannelInCommunity } from '../../models/community'; import { removeMemberInCommunity } from '../../models/usersCommunities'; import { @@ -16,43 +16,63 @@ import { createMemberInCommunity, } from '../../models/usersCommunities'; import { sendPrivateChannelRequestQueue } from 'shared/bull/queues'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; -export default async ( - _: any, - { channelId }: { channelId: string }, - { user }: GraphQLContext -) => { - const currentUser = user; - - // user must be authed to join a channel - if (!currentUser) { - return new UserError('You must be signed in to follow this channel.'); - } - - // get the channel to evaluate - const channels = await getChannels([channelId]); +type Input = { + channelId: string, +}; - const currentUserChannelPermissions = await getUserPermissionsInChannel( - channelId, - currentUser.id - ); +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { channelId } = args; + const { user } = ctx; - // select the channel - const channelToEvaluate = channels[0]; + const [channelToEvaluate, currentUserChannelPermissions] = await Promise.all([ + getChannelById(channelId), + getUserPermissionsInChannel(channelId, user.id), + ]); // if channel wasn't found or was deleted if (!channelToEvaluate || channelToEvaluate.deletedAt) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_CHANNEL_FAILED, + context: { channelId }, + properties: { + reason: 'no channel', + }, + }); + return new UserError("This channel doesn't exist"); } // user is blocked, they can't join the channel if (currentUserChannelPermissions.isBlocked) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_CHANNEL_FAILED, + context: { channelId }, + properties: { + reason: 'no permission', + }, + }); + return new UserError("You don't have permission to do that."); } // if the person owns the channel, they have accidentally triggered // a join or leave action, which isn't allowed if (currentUserChannelPermissions.isOwner) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_CHANNEL_FAILED, + context: { channelId }, + properties: { + reason: 'owner in channel', + }, + }); + return new UserError( "Owners of a community can't join or leave their own channel." ); @@ -61,13 +81,22 @@ export default async ( // get the current user's permissions in the community const currentUserCommunityPermissions = await getUserPermissionsInCommunity( channelToEvaluate.communityId, - currentUser.id + user.id ); if ( currentUserCommunityPermissions && currentUserCommunityPermissions.isBlocked ) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_CHANNEL_FAILED, + context: { channelId }, + properties: { + reason: 'blocked', + }, + }); + return new UserError('You have been blocked in this community'); } @@ -75,7 +104,7 @@ export default async ( // to leave the channel if (currentUserChannelPermissions.isMember) { // remove the relationship of the user to the channel - const removeRelationship = removeMemberInChannel(channelId, currentUser.id); + const removeRelationship = removeMemberInChannel(channelId, user.id); return ( Promise.all([channelToEvaluate, removeRelationship]) @@ -87,7 +116,7 @@ export default async ( // should also be removed from the parent community itself const isMemberOfAnotherChannel = userIsMemberOfAnyChannelInCommunity( channelToEvaluate.communityId, - currentUser.id + user.id ); return Promise.all([channelToEvaluate, isMemberOfAnotherChannel]); @@ -102,10 +131,7 @@ export default async ( // the community return Promise.all([ channelToEvaluate, - removeMemberInCommunity( - channelToEvaluate.communityId, - currentUser.id - ), + removeMemberInCommunity(channelToEvaluate.communityId, user.id), ]); } }) @@ -123,7 +149,7 @@ export default async ( // 1. user has already requested to join, so remove them from pending if (currentUserChannelPermissions.isPending) { - return removeMemberInChannel(channelId, currentUser.id); + return removeMemberInChannel(channelId, user.id); } // 2. if the channel is private, request to join - since this action @@ -133,15 +159,15 @@ export default async ( // owner approves the user if (channelToEvaluate.isPrivate) { sendPrivateChannelRequestQueue.add({ - userId: currentUser.id, + userId: user.id, channel: channelToEvaluate, }); - return createOrUpdatePendingUserInChannel(channelId, currentUser.id); + return createOrUpdatePendingUserInChannel(channelId, user.id); } // otherwise the channel is not private so the user can just join. // we'll create new usersChannels relationship - const join = createMemberInChannel(channelId, currentUser.id); + const join = createMemberInChannel(channelId, user.id, false); // we also need to see if the user is a member of the parent community. // if they are, we can just continue @@ -161,14 +187,8 @@ export default async ( // join the community and the community's default channels return Promise.all([ joinedChannel, - createMemberInCommunity( - joinedChannel.communityId, - currentUser.id - ), - createMemberInDefaultChannels( - joinedChannel.communityId, - currentUser.id - ), + createMemberInCommunity(joinedChannel.communityId, user.id), + createMemberInDefaultChannels(joinedChannel.communityId, user.id), ]); } }) @@ -176,4 +196,4 @@ export default async ( .then(data => data[0]) ); } -}; +}); diff --git a/api/mutations/channel/togglePendingUser.js b/api/mutations/channel/togglePendingUser.js index 64314abe4b..b8d93dd9a7 100644 --- a/api/mutations/channel/togglePendingUser.js +++ b/api/mutations/channel/togglePendingUser.js @@ -7,13 +7,20 @@ import { approvePendingUserInChannel, createMemberInDefaultChannels, } from '../../models/usersChannels'; -import { getChannels } from '../../models/channel'; +import { getChannelById } from '../../models/channel'; import { getUserPermissionsInCommunity, createMemberInCommunity, } from '../../models/usersCommunities'; +import { sendPrivateChannelRequestApprovedQueue } from 'shared/bull/queues'; +import { + isAuthedResolver as requireAuth, + canModerateChannel, +} from '../../utils/permissions'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; -type TogglePendingUserInput = { +type Input = { input: { channelId: string, userId: string, @@ -21,98 +28,97 @@ type TogglePendingUserInput = { }, }; -export default async ( - _: any, - { input }: TogglePendingUserInput, - { user }: GraphQLContext -) => { - const currentUser = user; - - // user must be authed to edit a channel - if (!currentUser) - return new UserError( - 'You must be signed in to make changes to this channel.' - ); +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { userId, action, channelId } = args.input; + const { user, loaders } = ctx; - const [ - currentUserChannelPermissions, - evaluatedUserPermissions, - channels, - ] = await Promise.all([ - getUserPermissionsInChannel(input.channelId, currentUser.id), - getUserPermissionsInChannel(input.channelId, input.userId), - getChannels([input.channelId]), - ]); + const eventFailed = + action === 'block' + ? events.USER_UNBLOCKED_MEMBER_IN_CHANNEL_FAILED + : events.USER_BLOCKED_MEMBER_IN_CHANNEL_FAILED; - // select the channel to be evaluated - const channelToEvaluate = channels && channels[0]; + if (!await canModerateChannel(user.id, channelId, loaders)) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + context: { channelId }, + properties: { + reason: 'no permission', + }, + }); - // if channel wasn't found or was deleted - if (!channelToEvaluate || channelToEvaluate.deletedAt) { - return new UserError("This channel doesn't exist"); + return new UserError('You don’t have permission to manage this channel'); } - // get the community parent of channel - const [ - currentUserCommunityPermissions, - evaluatedUserCommunityPermissions, - ] = await Promise.all([ - getUserPermissionsInCommunity( - channelToEvaluate.communityId, - currentUser.id - ), - getUserPermissionsInCommunity(channelToEvaluate.communityId, input.userId), + const [evaluatedUserPermissions, channel] = await Promise.all([ + getUserPermissionsInChannel(channelId, userId), + getChannelById(channelId), ]); - // if the user isn't on the pending list if (!evaluatedUserPermissions.isPending) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + context: { channelId }, + properties: { + reason: 'not pending', + }, + }); + return new UserError( 'This user is not currently pending access to this channel.' ); } - // user is neither a community or channel owner, they don't have permission - if ( - currentUserChannelPermissions.isOwner || - currentUserCommunityPermissions.isOwner - ) { - // all checks passed - // determine whether to approve or block them - if (input.action === 'block') { - // remove the user from the pending list - return blockUserInChannel(input.channelId, input.userId).then( - () => channelToEvaluate - ); - } + if (action === 'block') { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_MEMBER_IN_CHANNEL, + context: { channelId }, + }); - if (input.action === 'approve') { - const approveUser = approvePendingUserInChannel( - input.channelId, - input.userId - ); + return blockUserInChannel(channelId, userId).then(() => channel); + } - // if the user is a member of the parent community, we can return - if (evaluatedUserCommunityPermissions.isMember) { - return Promise.all([channelToEvaluate, approveUser]).then( - () => channelToEvaluate - ); - } else { - // if the user is not a member of the parent community, - // join the community and the community's default channels - return await Promise.all([ - channelToEvaluate, - createMemberInCommunity(channelToEvaluate.communityId, input.userId), - createMemberInDefaultChannels( - channelToEvaluate.communityId, - input.userId - ), - approveUser, - ]).then(() => channelToEvaluate); - } + if (action === 'approve') { + const evaluatedUserCommunityPermissions = await getUserPermissionsInCommunity( + channel.communityId, + userId + ); + + sendPrivateChannelRequestApprovedQueue.add({ + userId: userId, + channelId: channelId, + communityId: channel.communityId, + moderatorId: user.id, + }); + + // if the user is a member of the parent community, we can return + if ( + evaluatedUserCommunityPermissions && + evaluatedUserCommunityPermissions.isMember + ) { + trackQueue.add({ + userId: user.id, + event: events.USER_APPROVED_MEMBER_IN_CHANNEL, + context: { channelId }, + }); + + return approvePendingUserInChannel(channelId, userId).then(() => channel); + } else { + // if the user is not a member of the parent community, + // join the community and the community's default channels + trackQueue.add({ + userId: user.id, + event: events.USER_APPROVED_MEMBER_IN_CHANNEL, + context: { channelId }, + }); + + return await Promise.all([ + approvePendingUserInChannel(channelId, userId), + createMemberInCommunity(channel.communityId, userId), + createMemberInDefaultChannels(channel.communityId, userId), + ]).then(() => channel); } } - - return new UserError( - "You don't have permission to make changes to this channel." - ); -}; +}); diff --git a/api/mutations/channel/unblockUser.js b/api/mutations/channel/unblockUser.js index 98dcbedf2b..3f22572b17 100644 --- a/api/mutations/channel/unblockUser.js +++ b/api/mutations/channel/unblockUser.js @@ -5,69 +5,61 @@ import { getUserPermissionsInChannel, unblockMemberInChannel, } from '../../models/usersChannels'; -import { getChannels } from '../../models/channel'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; +import { getChannelById } from '../../models/channel'; +import { + isAuthedResolver as requireAuth, + canModerateChannel, +} from '../../utils/permissions'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; -type UnblockUserInput = { +type Input = { input: { channelId: string, userId: string, }, }; -export default async ( - _: any, - { input }: UnblockUserInput, - { user }: GraphQLContext -) => { - const currentUser = user; - - // user must be authed to edit a channel - if (!currentUser) { - return new UserError( - 'You must be signed in to make changes to this channel.' - ); - } - - const [ - currentUserChannelPermissions, - evaluatedUserChannelPermissions, - channels, - ] = await Promise.all([ - getUserPermissionsInChannel(input.channelId, currentUser.id), - getUserPermissionsInChannel(input.channelId, input.userId), - getChannels([input.channelId]), - ]); +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { channelId, userId } = args.input; + const { user, loaders } = ctx; - // get the channel to evaluate - const channelToEvaluate = channels && channels[0]; + if (!await canModerateChannel(user.id, channelId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.USER_UNBLOCKED_MEMBER_IN_CHANNEL_FAILED, + context: { channelId }, + properties: { + reason: 'no permission', + }, + }); - // if channel wasn't found or was deleted - if (!channelToEvaluate || channelToEvaluate.deletedAt) { - return new UserError("This channel doesn't exist"); + return new UserError('You don’t have permission to manage this channel'); } - const currentUserCommunityPermissions = await getUserPermissionsInCommunity( - channelToEvaluate.communityId, - currentUser.id - ); + const [channel, evaluatedUserChannelPermissions] = await Promise.all([ + getChannelById(channelId), + getUserPermissionsInChannel(channelId, userId), + ]); if (!evaluatedUserChannelPermissions.isBlocked) { + trackQueue.add({ + userId: user.id, + event: events.USER_UNBLOCKED_MEMBER_IN_CHANNEL_FAILED, + context: { channelId }, + properties: { + reason: 'not blocked', + }, + }); + return new UserError('This user is not currently blocked in this channel.'); } - // if a user owns the community or owns the channel, they can make this change - if ( - currentUserChannelPermissions.isOwner || - currentUserCommunityPermissions.isOwner - ) { - return unblockMemberInChannel(input.channelId, input.userId).then( - () => channelToEvaluate - ); - } + trackQueue.add({ + userId: user.id, + event: events.USER_UNBLOCKED_MEMBER_IN_CHANNEL, + context: { channelId }, + }); - // user is neither a community or channel owner, they don't have permission - return new UserError( - "You don't have permission to make changes to this channel." - ); -}; + return unblockMemberInChannel(channelId, userId).then(() => channel); +}); diff --git a/api/mutations/channel/updateChannelSlackBotLinks.js b/api/mutations/channel/updateChannelSlackBotLinks.js new file mode 100644 index 0000000000..7e0a57129e --- /dev/null +++ b/api/mutations/channel/updateChannelSlackBotLinks.js @@ -0,0 +1,38 @@ +// @flow +import type { GraphQLContext } from '../../'; +import UserError from '../../utils/UserError'; +import { updateChannelSlackBotLinks } from '../../models/channelSettings'; +import { + isAuthedResolver as requireAuth, + canModerateChannel, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; + +export type Input = { + input: { + channelId: string, + slackChannelId: ?string, + eventType: 'threadCreated', + }, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { input, input: { channelId } } = args; + const { user, loaders } = ctx; + + if (!await canModerateChannel(user.id, channelId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.CHANNEL_SLACK_BOT_LINK_UPDATED_FAILED, + context: { channelId }, + properties: { + reason: 'no permission', + }, + }); + + return new UserError('You don’t have permission to manage this channel'); + } + + return await updateChannelSlackBotLinks(input, user.id); +}); diff --git a/api/mutations/community/addPaymentSource.js b/api/mutations/community/addPaymentSource.js deleted file mode 100644 index 4e3003746b..0000000000 --- a/api/mutations/community/addPaymentSource.js +++ /dev/null @@ -1,79 +0,0 @@ -// @flow -const debug = require('debug')('api:mutations:community:add-payment-source'); -import type { GraphQLContext } from '../../'; -import { insertOrReplaceStripeCustomer } from '../../models/stripeCustomers'; -import UserError from '../../utils/UserError'; -import { StripeUtil } from 'shared/stripe/utils'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; - -export default async ( - _: any, - { input }: { input: { sourceId: string, communityId: string } }, - { user }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser) { - return new UserError('You must be signed in to manage this community'); - } - - const { sourceId, communityId } = input; - - const { customer, community } = await StripeUtil.jobPreflight(communityId); - - if (!community) { - debug('Error getting community in preflight'); - return new UserError( - 'We had trouble processing this request - please try again later' - ); - } - - if (!customer) { - debug('Error creating customer in preflight'); - return new UserError( - 'We had trouble processing this request - please try again later' - ); - } - - const currentUserCommunityPermissions = await getUserPermissionsInCommunity( - communityId, - currentUser.id - ); - - if (!currentUserCommunityPermissions.isOwner) { - return new UserError( - 'You must own this community to manage payment sources' - ); - } - - let changedSource; - try { - changedSource = await StripeUtil.attachNewSource({ - customerId: customer.id, - sourceId: sourceId, - }); - } catch (err) { - return new UserError(err.message); - } - - try { - await StripeUtil.changeDefaultSource({ - customerId: customer.id, - sourceId: sourceId, - }); - } catch (err) { - console.error('Could not update default payment method'); - } - - const newCustomer = await StripeUtil.getCustomer(changedSource.customer); - - // we only want to return from this mutation as soon as our db record - // is in sync with stripe. Normally we defer this to webhooks, but since - // this event needs updated data to respond to something the user is doing - // *right now* we manually update the Stripe customer record in our db - return await insertOrReplaceStripeCustomer(newCustomer) - .then(() => community) - .catch(err => { - return new UserError('We had trouble saving your card', err.message); - }); -}; diff --git a/api/mutations/community/cancelSubscription.js b/api/mutations/community/cancelSubscription.js deleted file mode 100644 index 8afa87e905..0000000000 --- a/api/mutations/community/cancelSubscription.js +++ /dev/null @@ -1,69 +0,0 @@ -// @flow -const debug = require('debug')('api:mutations:community:add-payment-source'); -import type { GraphQLContext } from '../../'; -import { removeModeratorsInCommunity } from '../../models/usersCommunities'; -import { - disablePaidFeatureFlags, - getCommunityById, -} from '../../models/community'; -import { archiveAllPrivateChannels } from '../../models/channel'; -import UserError from '../../utils/UserError'; -import { StripeUtil } from 'shared/stripe/utils'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; - -export default async ( - _: any, - { input }: { input: { communityId: string } }, - { user }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser) { - return new UserError('You must be signed in to manage this community'); - } - - const { communityId } = input; - - const { customer, community } = await StripeUtil.jobPreflight(communityId); - - const currentUserCommunityPermissions = await getUserPermissionsInCommunity( - communityId, - currentUser.id - ); - - if (!currentUserCommunityPermissions.isOwner) { - return new UserError( - 'You must own this community to manage payment sources' - ); - } - - if (!community) { - debug('Error getting community in preflight'); - return new UserError( - 'We had trouble processing this request - please try again later' - ); - } - - if (!customer) { - debug('Error getting customer in preflight'); - return new UserError( - 'We had trouble processing this request - please try again later' - ); - } - - // in the mutation we only need to update the records relating to the community - // in the db. Pluto will receive queue jobs for each change and reconcile - // with stripe asynchronosouly. As a result, there is a small delay between - // when the usure cancels their subscription and when all of the subscriptions - // have been removed - return await Promise.all([ - removeModeratorsInCommunity(communityId), - disablePaidFeatureFlags(communityId), - archiveAllPrivateChannels(communityId), - ]) - .then(() => getCommunityById(communityId)) - .catch(err => { - console.log(err); - return new UserError(`Error canceling subscription: ${err.message}`); - }); -}; diff --git a/api/mutations/community/createCommunity.js b/api/mutations/community/createCommunity.js index 0c2d2df955..bb9fb53d65 100644 --- a/api/mutations/community/createCommunity.js +++ b/api/mutations/community/createCommunity.js @@ -7,84 +7,108 @@ import { getCommunitiesBySlug, createCommunity } from '../../models/community'; import { createOwnerInCommunity } from '../../models/usersCommunities'; import { createGeneralChannel } from '../../models/channel'; import { createOwnerInChannel } from '../../models/usersChannels'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; -export default async ( - _: any, - args: CreateCommunityInput, - { user }: GraphQLContext -) => { - const currentUser = user; - // user must be authed to create a community - if (!currentUser) { - return new UserError('You must be signed in to create a new community.'); - } +export default requireAuth( + async (_: any, args: CreateCommunityInput, { user }: GraphQLContext) => { + if (!user.email) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_CREATED_FAILED, + properties: { + reason: 'no email address', + }, + }); + return new UserError( + 'You must have a working email address to create communities. Add an email address in your settings.' + ); + } - if (!currentUser.email) { - return new UserError( - 'You must have a working email address to create communities. Add an email address in your settings.' - ); - } + if (!args.input.slug || args.input.slug.length === 0) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_CREATED_FAILED, + properties: { + reason: 'no slug', + }, + }); - if (!args.input.slug || args.input.slug.length === 0) { - return new UserError( - 'Communities must have a valid url so people can find it!' + return new UserError( + 'Communities must have a valid url so people can find it!' + ); + } + + // replace any non alpha-num characters to prevent bad community slugs + // (/[\W_]/g, "-") => replace non-alphanum with hyphens + // (/-{2,}/g, '-') => replace multiple hyphens in a row with one hyphen + const sanitizedSlug = args.input.slug + .replace(/[\W_]/g, '-') + .replace(/-{2,}/g, '-'); + const sanitizedArgs = Object.assign( + {}, + { + ...args, + input: { + ...args.input, + slug: sanitizedSlug, + }, + } ); - } - // replace any non alpha-num characters to prevent bad community slugs - // (/[\W_]/g, "-") => replace non-alphanum with hyphens - // (/-{2,}/g, '-') => replace multiple hyphens in a row with one hyphen - const sanitizedSlug = args.input.slug - .replace(/[\W_]/g, '-') - .replace(/-{2,}/g, '-'); - const sanitizedArgs = Object.assign( - {}, - { - ...args, - input: { - ...args.input, - slug: sanitizedSlug, - }, + if (communitySlugIsBlacklisted(sanitizedSlug)) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_CREATED_FAILED, + properties: { + reason: 'url taken', + }, + }); + + return new UserError( + `This url is already taken - feel free to change it if + you're set on the name ${args.input.name}!` + ); } - ); - if (communitySlugIsBlacklisted(sanitizedSlug)) { - return new UserError( - `This url is already taken - feel free to change it if - you're set on the name ${args.input.name}!` - ); - } + // get communities with the input slug to check for duplicates + const communities = await getCommunitiesBySlug([sanitizedSlug]); - // get communities with the input slug to check for duplicates - const communities = await getCommunitiesBySlug([sanitizedSlug]); + // if a community with this slug already exists + if (communities.length > 0) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_CREATED_FAILED, + properties: { + reason: 'community already exists', + }, + }); - // if a community with this slug already exists - if (communities.length > 0) { - return new UserError('A community with this slug already exists.'); - } + return new UserError('A community with this slug already exists.'); + } - // all checks passed - const community = await createCommunity(sanitizedArgs, currentUser); + // all checks passed + const community = await createCommunity(sanitizedArgs, user); - // create a new relationship with the community - const communityRelationship = await createOwnerInCommunity( - community.id, - currentUser.id - ); + // create a new relationship with the community + const communityRelationship = await createOwnerInCommunity( + community.id, + user.id + ); - // create a default 'general' channel - const generalChannel = await createGeneralChannel( - community.id, - currentUser.id - ); + // create a default 'general' channel + const generalChannel = await createGeneralChannel(community.id, user.id); - // create a new relationship with the general channel - const generalChannelRelationship = createOwnerInChannel( - generalChannel.id, - currentUser.id - ); + // create a new relationship with the general channel + const generalChannelRelationship = createOwnerInChannel( + generalChannel.id, + user.id + ); - return Promise.all([communityRelationship, generalChannelRelationship]).then( - () => community - ); -}; + return Promise.all([ + communityRelationship, + generalChannelRelationship, + ]).then(() => community); + } +); diff --git a/api/mutations/community/deleteCommunity.js b/api/mutations/community/deleteCommunity.js index 42c621e707..20c3eb96e5 100644 --- a/api/mutations/community/deleteCommunity.js +++ b/api/mutations/community/deleteCommunity.js @@ -1,44 +1,52 @@ // @flow import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; -import { - getUserPermissionsInCommunity, - removeMembersInCommunity, -} from '../../models/usersCommunities'; -import { getCommunities, deleteCommunity } from '../../models/community'; +import { removeMembersInCommunity } from '../../models/usersCommunities'; +import { deleteCommunity } from '../../models/community'; import { getChannelsByCommunity, deleteChannel } from '../../models/channel'; import { getThreadsByCommunity } from '../../models/thread'; import { removeMembersInChannel } from '../../models/usersChannels'; import { deleteThread } from '../../models/thread'; +import { + isAuthedResolver as requireAuth, + canAdministerCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -export default async ( - _: any, - { communityId }: { communityId: string }, - { user }: GraphQLContext -) => { - const currentUser = user; +type Input = { + communityId: string, +}; - // user must be authed to delete a community - if (!currentUser) { - return new UserError( - 'You must be signed in to make changes to this community.' - ); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { communityId } = args; + const { user, loaders } = ctx; - const [currentUserCommunityPermissions, communities] = await Promise.all([ - getUserPermissionsInCommunity(communityId, currentUser.id), - getCommunities([communityId]), - ]); + const communityToEvaluate = await loaders.community.load(communityId); - const communityToEvaluate = communities && communities[0]; - - // if no community was found or was deleted if (!communityToEvaluate || communityToEvaluate.deletedAt) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_DELETED_FAILED, + context: { communityId }, + properties: { + reason: 'no community found', + }, + }); + return new UserError("This community doesn't exist."); } - // user must own the community to delete the community - if (!currentUserCommunityPermissions.isOwner) { + if (!await canAdministerCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_DELETED_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); + return new UserError( "You don't have permission to make changes to this community." ); @@ -48,26 +56,28 @@ export default async ( getChannelsByCommunity(communityId), getThreadsByCommunity(communityId), removeMembersInCommunity(communityId), - deleteCommunity(communityId), + deleteCommunity(communityId, user.id), ]); // after a community has been deleted, we need to mark all the channels // as deleted const removeAllChannels = allChannelsInCommunity.map(channel => - deleteChannel(channel.id) + deleteChannel(channel.id, user.id) ); + // and remove all relationships to the deleted channels const removeAllRelationshipsToChannels = allChannelsInCommunity.map(channel => removeMembersInChannel(channel.id) ); + // and mark all the threads in that community as deleted const removeAllThreadsInCommunity = allThreadsInCommunity.map(thread => - deleteThread(thread.id) + deleteThread(thread.id, user.id) ); return Promise.all([ - removeAllChannels, - removeAllRelationshipsToChannels, - removeAllThreadsInCommunity, + ...removeAllChannels, + ...removeAllRelationshipsToChannels, + ...removeAllThreadsInCommunity, ]).then(() => communityToEvaluate); -}; +}); diff --git a/api/mutations/community/disableBrandedLogin.js b/api/mutations/community/disableBrandedLogin.js index 7efda1fd77..2f75dcbbf0 100644 --- a/api/mutations/community/disableBrandedLogin.js +++ b/api/mutations/community/disableBrandedLogin.js @@ -5,38 +5,44 @@ import { createCommunitySettings, disableCommunityBrandedLogin, } from '../../models/communitySettings'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -type DisableBrandedLoginInput = { +type Input = { input: { id: string, }, }; -export default async ( - _: any, - { input: { id: communityId } }: DisableBrandedLoginInput, - { user, loaders }: GraphQLContext -) => { - const currentUser = user; - if (!currentUser) { - return new UserError('You must be signed in to manage this community.'); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { id: communityId } = args.input; + const { user, loaders } = ctx; - const [permissions, settings] = await Promise.all([ - loaders.userPermissionsInCommunity.load([currentUser.id, communityId]), - loaders.communitySettings.load(communityId), - ]); + if (!await canModerateCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_BRANDED_LOGIN_DISABLED_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); - if (!permissions.isOwner) { return new UserError("You don't have permission to do this."); } + const settings = await loaders.communitySettings.load(communityId); + loaders.communitySettings.clear(communityId); // settings.id tells us that a channelSettings record exists in the db if (settings.id) { - return await disableCommunityBrandedLogin(communityId); + return await disableCommunityBrandedLogin(communityId, user.id); } else { return await createCommunitySettings(communityId); } -}; +}); diff --git a/api/mutations/community/disableCommunityAnalytics.js b/api/mutations/community/disableCommunityAnalytics.js deleted file mode 100644 index 8cf5279c1b..0000000000 --- a/api/mutations/community/disableCommunityAnalytics.js +++ /dev/null @@ -1,43 +0,0 @@ -// @flow -import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -import { updateCommunityPaidFeature } from '../../models/community'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; - -export default async ( - _: any, - { input: { communityId } }: { input: { communityId: string } }, - { user }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser) { - return new UserError('You must be signed in to manage this community'); - } - - if (!communityId) { - return new UserError('No communityId found'); - } - - const currentUserCommunityPermissions = await getUserPermissionsInCommunity( - communityId, - currentUser.id - ); - - if ( - !currentUserCommunityPermissions.isOwner && - !currentUserCommunityPermissions.isModerator - ) { - return new UserError( - 'You must own or moderate this community to disable analytics' - ); - } - - return await updateCommunityPaidFeature( - communityId, - 'analyticsEnabled', - false - ).catch(err => { - return new UserError('We had trouble saving your card', err.message); - }); -}; diff --git a/api/mutations/community/disableCommunityTokenJoin.js b/api/mutations/community/disableCommunityTokenJoin.js new file mode 100644 index 0000000000..e979c76e30 --- /dev/null +++ b/api/mutations/community/disableCommunityTokenJoin.js @@ -0,0 +1,41 @@ +// @flow +import type { GraphQLContext } from '../../'; +import UserError from '../../utils/UserError'; +import { + getOrCreateCommunitySettings, + disableCommunityTokenJoin, +} from '../../models/communitySettings'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; + +type Input = { + input: { + id: string, + }, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { id: communityId } = args.input; + const { user, loaders } = ctx; + + if (!await canModerateCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_JOIN_TOKEN_DISABLED_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); + + return new UserError('You don’t have permission to manage this community'); + } + + return await getOrCreateCommunitySettings(communityId).then( + async () => await disableCommunityTokenJoin(communityId, user.id) + ); +}); diff --git a/api/mutations/community/editCommunity.js b/api/mutations/community/editCommunity.js index 2c35629d69..10bd3afe36 100644 --- a/api/mutations/community/editCommunity.js +++ b/api/mutations/community/editCommunity.js @@ -2,45 +2,34 @@ import type { GraphQLContext } from '../../'; import type { EditCommunityInput } from '../../models/community'; import UserError from '../../utils/UserError'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; -import { getCommunities, editCommunity } from '../../models/community'; +import { editCommunity } from '../../models/community'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -export default async ( - _: any, - args: EditCommunityInput, - { user }: GraphQLContext -) => { - const currentUser = user; +export default requireAuth( + // prettier-ignore + async (_: any, args: EditCommunityInput, ctx: GraphQLContext) => { + const { user, loaders} = ctx + const { communityId } = args.input - // user must be authed to edit a community - if (!currentUser) { - return new UserError( - 'You must be signed in to make changes to this community.' - ); - } - - const [currentUserCommunityPermissions, communities] = await Promise.all([ - getUserPermissionsInCommunity(args.input.communityId, currentUser.id), - getCommunities([args.input.communityId]), - ]); - - const communityToEvaluate = communities && communities[0]; + // user must own the community to edit the community + if (!await canModerateCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_EDITED_FAILED, + context: { communityId }, + properties: { + reason: 'no permission' + } + }) + + return new UserError("You don't have permission to edit this community."); + } - // if no community was found or was deleted - if (!communityToEvaluate || communityToEvaluate.deletedAt) { - return new UserError("This community doesn't exist."); + return editCommunity(args, user.id); } - - // user must own the community to edit the community - if ( - !currentUserCommunityPermissions.isOwner && - !currentUserCommunityPermissions.isModerator - ) { - return new UserError( - "You don't have permission to make changes to this community." - ); - } - - // all checks passed - return editCommunity(args); -}; +); diff --git a/api/mutations/community/enableBrandedLogin.js b/api/mutations/community/enableBrandedLogin.js index cb8134ff9d..a8a982b058 100644 --- a/api/mutations/community/enableBrandedLogin.js +++ b/api/mutations/community/enableBrandedLogin.js @@ -5,40 +5,46 @@ import { createCommunitySettings, enableCommunityBrandedLogin, } from '../../models/communitySettings'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -type EnableBrandedLoginInput = { +type Input = { input: { id: string, }, }; -export default async ( - _: any, - { input: { id: communityId } }: EnableBrandedLoginInput, - { user, loaders }: GraphQLContext -) => { - const currentUser = user; - if (!currentUser) { - return new UserError('You must be signed in to manage this community.'); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { id: communityId } = args.input; + const { user, loaders } = ctx; - const [permissions, settings] = await Promise.all([ - loaders.userPermissionsInCommunity.load([currentUser.id, communityId]), - loaders.communitySettings.load(communityId), - ]); + if (!await canModerateCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_BRANDED_LOGIN_ENABLED_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); - if (!permissions.isOwner) { return new UserError("You don't have permission to do this."); } + const settings = await loaders.communitySettings.load(communityId); + loaders.communitySettings.clear(communityId); // settings.id tells us that a channelSettings record exists in the db if (settings.id) { - return await enableCommunityBrandedLogin(communityId); + return await enableCommunityBrandedLogin(communityId, user.id); } else { return await createCommunitySettings(communityId).then( - async () => await enableCommunityBrandedLogin(communityId) + async () => await enableCommunityBrandedLogin(communityId, user.id) ); } -}; +}); diff --git a/api/mutations/community/enableCommunityAnalytics.js b/api/mutations/community/enableCommunityAnalytics.js deleted file mode 100644 index 788c0fbf1b..0000000000 --- a/api/mutations/community/enableCommunityAnalytics.js +++ /dev/null @@ -1,43 +0,0 @@ -// @flow -import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -import { updateCommunityPaidFeature } from '../../models/community'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; - -export default async ( - _: any, - { input: { communityId } }: { input: { communityId: string } }, - { user }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser) { - return new UserError('You must be signed in to manage this community'); - } - - if (!communityId) { - return new UserError('No communityId found'); - } - - const currentUserCommunityPermissions = await getUserPermissionsInCommunity( - communityId, - currentUser.id - ); - - if ( - !currentUserCommunityPermissions.isOwner && - !currentUserCommunityPermissions.isModerator - ) { - return new UserError( - 'You must own or moderate this community to enable analytics' - ); - } - - return await updateCommunityPaidFeature( - communityId, - 'analyticsEnabled', - true - ).catch(err => { - return new UserError('We had trouble saving your card', err.message); - }); -}; diff --git a/api/mutations/community/enableCommunityTokenJoin.js b/api/mutations/community/enableCommunityTokenJoin.js new file mode 100644 index 0000000000..b3655da06a --- /dev/null +++ b/api/mutations/community/enableCommunityTokenJoin.js @@ -0,0 +1,41 @@ +// @flow +import type { GraphQLContext } from '../../'; +import UserError from '../../utils/UserError'; +import { + getOrCreateCommunitySettings, + enableCommunityTokenJoin, +} from '../../models/communitySettings'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; + +type Input = { + input: { + id: string, + }, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { id: communityId } = args.input; + const { user, loaders } = ctx; + + if (!await canModerateCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_JOIN_TOKEN_ENABLED_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); + + return new UserError('You don’t have permission to manage this Community'); + } + + return await getOrCreateCommunitySettings(communityId).then( + async () => await enableCommunityTokenJoin(communityId, user.id) + ); +}); diff --git a/api/mutations/community/importSlackMembers.js b/api/mutations/community/importSlackMembers.js new file mode 100644 index 0000000000..7cf3cb5f1b --- /dev/null +++ b/api/mutations/community/importSlackMembers.js @@ -0,0 +1,50 @@ +// @flow + +/* + +Deprecated. We now send slack invitations directly by dispatching a job to +athena 'sendSlackInvites' in mutation + +*/ + +import type { GraphQLContext } from '../../'; +import UserError from '../../utils/UserError'; +import { slackImportQueue } from 'shared/bull/queues'; +import { getSlackImport } from '../../models/slackImport'; + +type ImportSlackMemberInput = { + input: { + id: string, + }, +}; + +export default async ( + _: any, + { input: { id } }: ImportSlackMemberInput, + { user, loaders }: GraphQLContext +) => { + const currentUser = user; + if (!currentUser) { + return new UserError( + 'You must be signed in to pin a thread in this community.' + ); + } + + const permissions = await loaders.userPermissionsInCommunity.load([ + currentUser.id, + id, + ]); + + if (!permissions.isOwner && !permissions.isModerator) { + return new UserError("You don't have permission to do this."); + } + + const slackImport = await getSlackImport(id); + + if (!slackImport) + return new UserError('No Slack team connected, cannot import members.'); + + slackImportQueue.add({ token: slackImport.token, importId: slackImport.id }); + + return true; +}; diff --git a/api/mutations/community/index.js b/api/mutations/community/index.js index 9269502ce5..306611569a 100644 --- a/api/mutations/community/index.js +++ b/api/mutations/community/index.js @@ -7,15 +7,13 @@ import sendSlackInvites from './sendSlackInvites'; import sendEmailInvites from './sendEmailInvites'; import pinThread from './pinThread'; import updateAdministratorEmail from './updateAdministratorEmail'; -import addPaymentSource from './addPaymentSource'; -import removePaymentSource from './removePaymentSource'; -import makePaymentSourceDefault from './makePaymentSourceDefault'; -import cancelSubscription from './cancelSubscription'; -import enableCommunityAnalytics from './enableCommunityAnalytics'; -import disableCommunityAnalytics from './disableCommunityAnalytics'; import enableBrandedLogin from './enableBrandedLogin'; import disableBrandedLogin from './disableBrandedLogin'; import saveBrandedLoginSettings from './saveBrandedLoginSettings'; +import importSlackMembers from './importSlackMembers'; +import enableCommunityTokenJoin from './enableCommunityTokenJoin'; +import disableCommunityTokenJoin from './disableCommunityTokenJoin'; +import resetCommunityJoinToken from './resetCommunityJoinToken'; module.exports = { Mutation: { @@ -27,14 +25,12 @@ module.exports = { sendEmailInvites, pinThread, updateAdministratorEmail, - addPaymentSource, - removePaymentSource, - makePaymentSourceDefault, - cancelSubscription, - enableCommunityAnalytics, - disableCommunityAnalytics, enableBrandedLogin, disableBrandedLogin, saveBrandedLoginSettings, + importSlackMembers, + enableCommunityTokenJoin, + disableCommunityTokenJoin, + resetCommunityJoinToken, }, }; diff --git a/api/mutations/community/makePaymentSourceDefault.js b/api/mutations/community/makePaymentSourceDefault.js deleted file mode 100644 index a967db3856..0000000000 --- a/api/mutations/community/makePaymentSourceDefault.js +++ /dev/null @@ -1,67 +0,0 @@ -// @flow -const debug = require('debug')( - 'api:mutations:community:change-default-payment-source' -); -import { replaceStripeCustomer } from '../../models/stripeCustomers'; -import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -import { StripeUtil } from 'shared/stripe/utils'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; - -export default async ( - _: any, - { input }: { input: { sourceId: string, communityId: string } }, - { user }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser) { - return new UserError('You must be signed in to manage this community'); - } - - const { sourceId, communityId } = input; - - const { customer, community } = await StripeUtil.jobPreflight(communityId); - - if (!community) { - debug('Error getting community in preflight'); - return new UserError( - 'We had trouble processing this request - please try again later' - ); - } - - if (!customer) { - debug('Error creating customer in preflight'); - return new UserError( - 'We had trouble processing this request - please try again later' - ); - } - - const currentUserCommunityPermissions = await getUserPermissionsInCommunity( - communityId, - currentUser.id - ); - - if (!currentUserCommunityPermissions.isOwner) { - return new UserError( - 'You must own this community to manage payment sources' - ); - } - - const changeDefaultSource = async () => - await StripeUtil.changeDefaultSource({ - customerId: customer.id, - sourceId: sourceId, - }); - - return changeDefaultSource() - .then(async () => await StripeUtil.getCustomer(customer.id)) - .then( - async newCustomer => - await replaceStripeCustomer(newCustomer.id, newCustomer) - ) - .then(() => community) - .catch(err => { - return new UserError('Error changing default method: ', err.message); - }); -}; diff --git a/api/mutations/community/pinThread.js b/api/mutations/community/pinThread.js index c0347396fd..572c0f7604 100644 --- a/api/mutations/community/pinThread.js +++ b/api/mutations/community/pinThread.js @@ -1,45 +1,71 @@ // @flow import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; -import { getThreads } from '../../models/thread'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; +import { getThreadById } from '../../models/thread'; import { setPinnedThreadInCommunity } from '../../models/community'; -import { getChannels } from '../../models/channel'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; -type PinThreadInput = { +type Input = { threadId: string, communityId: string, value: string, }; -export default async ( - _: any, - { threadId, communityId, value }: PinThreadInput, - { user }: GraphQLContext -) => { - const currentUser = user; - if (!currentUser) { - return new UserError( - 'You must be signed in to pin a thread in this community.' - ); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { threadId, communityId, value } = args; + const { user, loaders } = ctx; + + const eventFailed = value + ? events.THREAD_PINNED_FAILED + : events.THREAD_UNPINNED_FAILED; - const [permissions, threads] = await Promise.all([ - getUserPermissionsInCommunity(communityId, currentUser.id), - getThreads([threadId]), - ]); + if (!await canModerateCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + context: { threadId }, + properties: { + reason: 'no permission', + }, + }); - if (!permissions.isOwner && !permissions.isModerator) { return new UserError("You don't have permission to do this."); } - const threadToEvaluate = threads[0]; + const thread = await getThreadById(threadId); + + if (!thread) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + context: { threadId }, + properties: { + reason: 'thread not found', + }, + }); + + return new UserError('This thread has been deleted'); + } + + const channel = await loaders.channel.load(thread.channelId); - // we have to ensure the thread isn't in a private channel - const channels = await getChannels([threadToEvaluate.channelId]); + if (channel.isPrivate) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + context: { threadId }, + properties: { + reason: 'private channel thread', + }, + }); - if (channels && channels[0].isPrivate) { return new UserError('Only threads in public channels can be pinned.'); } - return setPinnedThreadInCommunity(communityId, value); -}; + + return setPinnedThreadInCommunity(communityId, value, user.id); +}); diff --git a/api/mutations/community/removePaymentSource.js b/api/mutations/community/removePaymentSource.js deleted file mode 100644 index d6567e4641..0000000000 --- a/api/mutations/community/removePaymentSource.js +++ /dev/null @@ -1,71 +0,0 @@ -// @flow -const debug = require('debug')( - 'api:mutations:community:remove-payment-source' -); -import { replaceStripeCustomer } from '../../models/stripeCustomers'; -import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -import { StripeUtil } from 'shared/stripe/utils'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; - -export default async ( - _: any, - { input }: { input: { sourceId: string, communityId: string } }, - { user }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser) { - return new UserError('You must be signed in to manage this community'); - } - - const { sourceId, communityId } = input; - - const { customer, community } = await StripeUtil.jobPreflight(communityId); - - if (!community) { - debug('Error getting community in preflight'); - return new UserError( - 'We had trouble processing this request - please try again later' - ); - } - - if (!customer) { - debug('Error creating customer in preflight'); - return new UserError( - 'We had trouble processing this request - please try again later' - ); - } - - const currentUserCommunityPermissions = await getUserPermissionsInCommunity( - communityId, - currentUser.id - ); - - if (!currentUserCommunityPermissions.isOwner) { - return new UserError( - 'You must own this community to manage payment sources' - ); - } - - const detachSource = async () => - await StripeUtil.detachSource({ - customerId: customer.id, - sourceId: sourceId, - }); - - return detachSource() - .then(async () => await StripeUtil.getCustomer(customer.id)) - .then( - async newCustomer => - console.log('newCustomer', newCustomer) || - (await replaceStripeCustomer(newCustomer.id, newCustomer)) - ) - .then( - replacedCustomer => - console.log('replacedCustomer', replacedCustomer) || community - ) - .catch(err => { - return new UserError(`Error removing payment method: ${err.message}`); - }); -}; diff --git a/api/mutations/community/resetCommunityJoinToken.js b/api/mutations/community/resetCommunityJoinToken.js new file mode 100644 index 0000000000..2402ce33a1 --- /dev/null +++ b/api/mutations/community/resetCommunityJoinToken.js @@ -0,0 +1,41 @@ +// @flow +import type { GraphQLContext } from '../../'; +import UserError from '../../utils/UserError'; +import { + getOrCreateCommunitySettings, + resetCommunityJoinToken, +} from '../../models/communitySettings'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; + +type Input = { + input: { + id: string, + }, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { id: communityId } = args.input; + const { user, loaders } = ctx; + + if (!await canModerateCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_JOIN_TOKEN_RESET_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); + + return new UserError('You don’t have permission to manage this community'); + } + + return await getOrCreateCommunitySettings(communityId).then( + async () => await resetCommunityJoinToken(communityId, user.id) + ); +}); diff --git a/api/mutations/community/saveBrandedLoginSettings.js b/api/mutations/community/saveBrandedLoginSettings.js index 41f60611bd..6b098eb889 100644 --- a/api/mutations/community/saveBrandedLoginSettings.js +++ b/api/mutations/community/saveBrandedLoginSettings.js @@ -5,47 +5,67 @@ import { createCommunitySettings, updateCommunityBrandedLoginMessage, } from '../../models/communitySettings'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -type EnableBrandedLoginInput = { +type Input = { input: { id: string, message: string, }, }; -export default async ( - _: any, - { input: { id: communityId, message } }: EnableBrandedLoginInput, - { user, loaders }: GraphQLContext -) => { - const currentUser = user; - if (!currentUser) { - return new UserError('You must be signed in to manage this community.'); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { id: communityId, message } = args.input; + const { user, loaders } = ctx; if (message && message.length > 280) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_BRANDED_LOGIN_SETTINGS_SAVED_FAILED, + context: { communityId }, + properties: { + reason: 'message too long', + }, + }); + return new UserError( 'Custom login messages should be less than 280 characters' ); } - const [permissions, settings] = await Promise.all([ - loaders.userPermissionsInCommunity.load([currentUser.id, communityId]), - loaders.communitySettings.load(communityId), - ]); + if (!await canModerateCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_BRANDED_LOGIN_SETTINGS_SAVED_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); - if (!permissions.isOwner) { return new UserError("You don't have permission to do this."); } + const settings = await loaders.communitySettings.load(communityId); + loaders.communitySettings.clear(communityId); // settings.id tells us that a channelSettings record exists in the db if (settings.id) { - return await updateCommunityBrandedLoginMessage(communityId, message); + return await updateCommunityBrandedLoginMessage( + communityId, + message, + user.id + ); } else { return await createCommunitySettings(communityId).then( - async () => await updateCommunityBrandedLoginMessage(communityId, message) + async () => + await updateCommunityBrandedLoginMessage(communityId, message, user.id) ); } -}; +}); diff --git a/api/mutations/community/sendEmailInvites.js b/api/mutations/community/sendEmailInvites.js index 8d4a3dfdf4..91c3ab35fe 100644 --- a/api/mutations/community/sendEmailInvites.js +++ b/api/mutations/community/sendEmailInvites.js @@ -3,7 +3,12 @@ import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; import { isEmail } from 'validator'; import { sendCommunityInviteNotificationQueue } from 'shared/bull/queues'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; type Contact = { email: string, @@ -11,7 +16,7 @@ type Contact = { lastName?: ?string, }; -type SendEmailInvitesInput = { +type Input = { input: { id: string, contacts: Array, @@ -19,44 +24,44 @@ type SendEmailInvitesInput = { }, }; -export default async ( - _: any, - { input }: SendEmailInvitesInput, - { user }: GraphQLContext -) => { - const currentUser = user; +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { input, input: { id: communityId } } = args; + const { user: currentUser, loaders } = ctx; + + if (!await canModerateCommunity(currentUser.id, communityId, loaders)) { + trackQueue.add({ + userId: currentUser.id, + event: events.COMMUNITY_EMAIL_INVITE_SENT_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); - if (!currentUser) { return new UserError( - 'You must be signed in to invite people to this community.' + "You don't have permission to invite people to this community." ); } - // make sure the user is the owner of the community - const permissions = await getUserPermissionsInCommunity( - input.id, - currentUser.id - ); + return input.contacts + .filter(user => user.email !== currentUser.email) + .filter(user => user && user.email && isEmail(user.email)) + .map(user => { + trackQueue.add({ + userId: currentUser.id, + event: events.COMMUNITY_EMAIL_INVITE_SENT, + context: { communityId }, + }); - if (!permissions.isOwner && !permissions.isModerator) { - return new UserError( - "You don't have permission to invite people to this community." - ); - } else { - return input.contacts - .filter(user => user.email !== currentUser.email) - .filter(user => user && user.email && isEmail(user.email)) - .map(user => { - return sendCommunityInviteNotificationQueue.add({ - recipient: { - email: user.email, - firstName: user.firstName ? user.firstName : null, - lastName: user.lastName ? user.lastName : null, - }, - communityId: input.id, - senderId: currentUser.id, - customMessage: input.customMessage ? input.customMessage : null, - }); + return sendCommunityInviteNotificationQueue.add({ + recipient: { + email: user.email, + firstName: user.firstName ? user.firstName : null, + lastName: user.lastName ? user.lastName : null, + }, + communityId: input.id, + senderId: currentUser.id, + customMessage: input.customMessage ? input.customMessage : null, }); - } -}; + }); +}); diff --git a/api/mutations/community/sendSlackInvites.js b/api/mutations/community/sendSlackInvites.js index 3c0ebcc845..c86c8da01c 100644 --- a/api/mutations/community/sendSlackInvites.js +++ b/api/mutations/community/sendSlackInvites.js @@ -1,109 +1,84 @@ // @flow -import { isEmail } from 'validator'; import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; -import { - getSlackImport, - markSlackImportAsSent, -} from '../../models/slackImport'; +import { markInitialSlackInvitationsSent } from '../../models/communitySettings'; import { getCommunityById } from '../../models/community'; +import { sendSlackInvitationsQueue } from 'shared/bull/queues'; import { - _adminProcessSlackImportQueue, - sendCommunityInviteNotificationQueue, -} from 'shared/bull/queues'; -import { getUserById } from '../../models/user'; + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -type SendSlackInvitesInput = { +type Input = { input: { id: string, customMessage?: ?string, }, }; -export default async ( - _: any, - { input }: SendSlackInvitesInput, - { user }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser) { - return new UserError( - 'You must be signed in to invite people to this community.' - ); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { id: communityId, customMessage } = args.input; + const { user, loaders } = ctx; - // make sure the user is the owner of the community - const permissions = await getUserPermissionsInCommunity( - input.id, - currentUser.id - ); + if (!await canModerateCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_SLACK_TEAM_INVITES_SENT_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); - if (!permissions.isOwner && !permissions.isModerator) { return new UserError( - "You don't have permission to invite people to this community." + "You don't have permission to invite a Slack team to this community." ); } - // get the slack import to make sure it hasn't already been sent before - const result = await getSlackImport(input.id); + const settings = await loaders.communitySettings.load(communityId); + + if (!settings || !settings.slackSettings || !settings.slackSettings.scope) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_SLACK_TEAM_INVITES_SENT_FAILED, + context: { communityId }, + properties: { + reason: 'no slack team connected', + }, + }); - // if no slack import exists - if (!result) { return new UserError( 'No Slack team is connected to this community. Try reconnecting.' ); } - // if the slack import was already sent - if (result.sent && result.sent !== null) { + + if (settings && settings.slackSettings.invitesSentAt) { + trackQueue.add({ + userId: user.id, + event: events.COMMUNITY_SLACK_TEAM_INVITES_SENT_FAILED, + context: { communityId }, + properties: { + reason: 'slack team already invited', + }, + }); + return new UserError( 'This Slack team has already been invited to join your community!' ); } - // mark the slack import for this community as sent - const inviteRecord = await markSlackImportAsSent(input.id); - - if (inviteRecord.members.length === 0) { - return new UserError('This Slack team has no members to invite!'); - } - - // for each member on the invite record, send a community invitation - - inviteRecord.members - .filter(user => user && user.email && isEmail(user.email)) - .filter(user => user.email !== currentUser.email) - .map(user => { - return sendCommunityInviteNotificationQueue.add({ - recipient: { - email: user.email, - firstName: user.firstName ? user.firstName : null, - lastName: user.lastName ? user.lastName : null, - }, - communityId: inviteRecord.communityId, - senderId: inviteRecord.senderId, - customMessage: input.customMessage, - }); + return await markInitialSlackInvitationsSent( + communityId, + customMessage, + user.id + ).then(async () => { + loaders.communitySettings.clear(communityId); + sendSlackInvitationsQueue.add({ + communityId, + userId: user.id, }); - - // send the community record back to the client - const [{ members, teamName }, community, thisUser] = await Promise.all([ - getSlackImport(input.id), - getCommunityById(input.id), - getUserById(currentUser.id), - ]); - - const invitedCount = members - .filter(user => !!user.email) - .filter(user => user.email !== thisUser.email).length; - - _adminProcessSlackImportQueue.add({ - thisUser, - community, - invitedCount, - teamName, + return await getCommunityById(communityId); }); - - return community; -}; +}); diff --git a/api/mutations/community/updateAdministratorEmail.js b/api/mutations/community/updateAdministratorEmail.js index d965480a32..6a3808a3b4 100644 --- a/api/mutations/community/updateAdministratorEmail.js +++ b/api/mutations/community/updateAdministratorEmail.js @@ -4,32 +4,36 @@ import UserError from '../../utils/UserError'; import { setCommunityPendingAdministratorEmail } from '../../models/community'; import isEmail from 'validator/lib/isEmail'; import { sendAdministratorEmailValidationEmailQueue } from 'shared/bull/queues'; +import { + isAuthedResolver as requireAuth, + canAdministerCommunity, +} from '../../utils/permissions'; -export default ( - _: any, - { input }: { input: { id: string, email: string } }, - { user }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser) { - return new UserError( - "You must be signed in to update this community's administrator email" - ); - } +type Input = { + input: { + id: string, + email: string, + }, +}; - const { id, email } = input; +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { id: communityId, email } = args.input; + const { loaders, user } = ctx; - if (!isEmail(email)) { + if (!email || !isEmail(email)) { return new UserError('Please enter a working email address'); } - return setCommunityPendingAdministratorEmail(id, email) + if (!await canAdministerCommunity(user.id, communityId, loaders)) { + return new UserError('You don’t have permission to manage this community'); + } + + return setCommunityPendingAdministratorEmail(communityId, email, user.id) .then(community => { sendAdministratorEmailValidationEmailQueue.add({ email, userId: user.id, - communityId: id, + communityId, community, }); return community; @@ -40,4 +44,4 @@ export default ( "We weren't able to send a confirmation email. Please try again." ) ); -}; +}); diff --git a/api/mutations/communityMember/addCommunityMember.js b/api/mutations/communityMember/addCommunityMember.js index 865082bc75..b32b21bd05 100644 --- a/api/mutations/communityMember/addCommunityMember.js +++ b/api/mutations/communityMember/addCommunityMember.js @@ -7,6 +7,9 @@ import { checkUserPermissionsInCommunity, } from '../../models/usersCommunities'; import { createMemberInDefaultChannels } from '../../models/usersChannels'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; type Input = { input: { @@ -14,28 +17,33 @@ type Input = { }, }; -export default async (_: any, { input }: Input, { user }: GraphQLContext) => { - const currentUser = user; - const { communityId } = input; - - if (!currentUser) { - return new UserError('You must be signed in to join this community.'); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user } = ctx; + const { communityId } = args.input; const [permissions, community] = await Promise.all([ - checkUserPermissionsInCommunity(communityId, currentUser.id), + checkUserPermissionsInCommunity(communityId, user.id), getCommunityById(communityId), ]); if (!community) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no community', + }, + }); + return new UserError("We couldn't find that community."); } // if no permissions exist, join them to the community! if (!permissions || permissions.length === 0) { return await Promise.all([ - createMemberInCommunity(communityId, currentUser.id), - createMemberInDefaultChannels(communityId, currentUser.id), + createMemberInCommunity(communityId, user.id), + createMemberInDefaultChannels(communityId, user.id), ]) // return the community to fulfill the resolver .then(() => community); @@ -44,18 +52,54 @@ export default async (_: any, { input }: Input, { user }: GraphQLContext) => { const permission = permissions[0]; if (permission.isBlocked) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'user blocked', + }, + }); + return new UserError("You aren't able to join this community."); } if (permission.isOwner) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'already owner', + }, + }); + return new UserError("You're already the owner of this community."); } if (permission.isModerator) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'already moderator', + }, + }); + return new UserError("You're already a moderator in this community."); } if (permission.isMember) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'already member', + }, + }); + return new UserError('You are already a member of this community.'); } @@ -63,14 +107,23 @@ export default async (_: any, { input }: Input, { user }: GraphQLContext) => { // they are trying to re-join the community. if (!permission.isMember) { return await Promise.all([ - createMemberInCommunity(communityId, currentUser.id), - createMemberInDefaultChannels(communityId, currentUser.id), + createMemberInCommunity(communityId, user.id), + createMemberInDefaultChannels(communityId, user.id), ]) // return the community to fulfill the resolver - .then(() => community); + .then(() => getCommunityById(communityId)); } + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'unknown error', + }, + }); + return new UserError( "We weren't able to process your request to join this community." ); -}; +}); diff --git a/api/mutations/communityMember/addCommunityMemberWithToken.js b/api/mutations/communityMember/addCommunityMemberWithToken.js new file mode 100644 index 0000000000..4896407a92 --- /dev/null +++ b/api/mutations/communityMember/addCommunityMemberWithToken.js @@ -0,0 +1,115 @@ +// @flow +import type { GraphQLContext } from '../../'; +import UserError from '../../utils/UserError'; +import { createMemberInDefaultChannels } from '../../models/usersChannels'; +import { + getUserPermissionsInCommunity, + createMemberInCommunity, + approvePendingMemberInCommunity, +} from '../../models/usersCommunities'; +import { getCommunityBySlug } from '../../models/community'; +import { getOrCreateCommunitySettings } from '../../models/communitySettings'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; + +type Input = { + input: { + communitySlug: string, + communitySlug: string, + token: string, + }, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user } = ctx; + const { communitySlug, token } = args.input; + + const community = await getCommunityBySlug(communitySlug); + + if (!community) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_COMMUNITY_WITH_TOKEN_FAILED, + properties: { + reason: 'no community', + }, + }); + + return new UserError('No community found in this community'); + } + + if (!community.isPrivate) { + return community; + } + + const [communityPermissions, settings] = await Promise.all([ + getUserPermissionsInCommunity(community.id, user.id), + getOrCreateCommunitySettings(community.id), + ]); + + if ( + communityPermissions.isOwner || + communityPermissions.isModerator || + communityPermissions.isMember + ) { + return community; + } + + if (communityPermissions.isBlocked) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_COMMUNITY_WITH_TOKEN_FAILED, + context: { communityId: community.id }, + properties: { + reason: 'no permission', + }, + }); + + return new UserError("You don't have permission to view this community"); + } + + if (!settings.joinSettings || !settings.joinSettings.tokenJoinEnabled) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_COMMUNITY_WITH_TOKEN_FAILED, + context: { communityId: community.id }, + properties: { + reason: 'no token or changed token', + }, + }); + + return new UserError( + "You can't join at this time, the token may have changed" + ); + } + if ( + settings.joinSettings.tokenJoinEnabled && + token !== settings.joinSettings.token + ) { + trackQueue.add({ + userId: user.id, + event: events.USER_JOINED_COMMUNITY_WITH_TOKEN_FAILED, + context: { communityId: community.id }, + properties: { + reason: 'no token or changed token', + }, + }); + + return new UserError( + "You can't join at this time, the token may have changed" + ); + } + + if (communityPermissions.isPending) { + return await Promise.all([ + approvePendingMemberInCommunity(community.id, user.id), + createMemberInDefaultChannels(community.id, user.id), + ]).then(() => community); + } + + return await Promise.all([ + createMemberInCommunity(community.id, user.id), + createMemberInDefaultChannels(community.id, user.id), + ]).then(() => community); +}); diff --git a/api/mutations/communityMember/addCommunityMembers.js b/api/mutations/communityMember/addCommunityMembers.js new file mode 100644 index 0000000000..d587534644 --- /dev/null +++ b/api/mutations/communityMember/addCommunityMembers.js @@ -0,0 +1,65 @@ +// @flow +import type { GraphQLContext } from '../../'; +import type { DBCommunity } from 'shared/types'; +import { getCommunityById } from '../../models/community'; +import { + createMemberInCommunity, + checkUserPermissionsInCommunity, +} from '../../models/usersCommunities'; +import { createMemberInDefaultChannels } from '../../models/usersChannels'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import UserError from '../../utils/UserError'; + +type Input = { + input: { + communityIds: Array, + }, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user } = ctx; + const { communityIds } = args.input; + + if (communityIds.length > 20) { + return new UserError('Try joining just a few communities at a time'); + } + + const canJoinCommunity = async ( + communityId: string + ): Promise => { + const [permissions, community] = await Promise.all([ + checkUserPermissionsInCommunity(communityId, user.id), + getCommunityById(communityId), + ]); + + if (!community) { + return null; + } + + // if no permissions exist, join them to the community! + if (!permissions || permissions.length === 0) { + return community; + } + + const permission = permissions[0]; + + if (!permission.isMember) { + return community; + } + + return null; + }; + + const handleJoin = async (communityId: string) => { + return await Promise.all([ + createMemberInCommunity(communityId, user.id), + createMemberInDefaultChannels(communityId, user.id), + ]); + }; + + return communityIds.map(async id => { + const community = await canJoinCommunity(id); + if (!community) return null; + return await handleJoin(id).then(() => community); + }); +}); diff --git a/api/mutations/communityMember/addCommunityModerator.js b/api/mutations/communityMember/addCommunityModerator.js index 25571926eb..409f116a57 100644 --- a/api/mutations/communityMember/addCommunityModerator.js +++ b/api/mutations/communityMember/addCommunityModerator.js @@ -6,6 +6,12 @@ import { makeMemberModeratorInCommunity, checkUserPermissionsInCommunity, } from '../../models/usersCommunities'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; type Input = { input: { @@ -14,49 +20,58 @@ type Input = { }, }; -export default async (_: any, { input }: Input, { user }: GraphQLContext) => { - const currentUser = user; - const { communityId, userId: userToEvaluateId } = input; +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + const { communityId, userId: userToEvaluateId } = args.input; + + if (!(await canModerateCommunity(user.id, communityId, loaders))) { + trackQueue.add({ + userId: user.id, + event: events.USER_ADDED_MODERATOR_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); - if (!currentUser) { return new UserError( - 'You must be signed in to manage moderators in this community.' + 'You must own or moderate this community to manage moderators.' ); } - const [ - currentUserPermissions, - userToEvaluatePermissions, - community, - ] = await Promise.all([ - checkUserPermissionsInCommunity(communityId, currentUser.id), + const [userToEvaluatePermissions, community] = await Promise.all([ checkUserPermissionsInCommunity(communityId, userToEvaluateId), getCommunityById(communityId), ]); if (!community) { + trackQueue.add({ + userId: user.id, + event: events.USER_ADDED_MODERATOR_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no community', + }, + }); + return new UserError("We couldn't find that community."); } - const { stripeCustomerId } = community; - if (!stripeCustomerId) - return new UserError( - 'You must have a valid payment method for this community to add new moderators' - ); - - // if no permissions exist, the user performing this mutation isn't even - // a member of this community - if (!currentUserPermissions || currentUserPermissions.length === 0) { - return new UserError('You must own this community to manage moderators.'); - } + if (!userToEvaluatePermissions || userToEvaluatePermissions.length === 0) { + trackQueue.add({ + userId: user.id, + event: events.USER_ADDED_MODERATOR_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'user not member', + }, + }); - if (!userToEvaluatePermissions || userToEvaluatePermissions === 0) { return new UserError( 'This person must be a member of the community before becoming a moderator.' ); } - const currentUserPermission = currentUserPermissions[0]; const userToEvaluatePermission = userToEvaluatePermissions[0]; // it's possible for a member to be moving from blocked -> moderator @@ -68,32 +83,57 @@ export default async (_: any, { input }: Input, { user }: GraphQLContext) => { !userToEvaluatePermission.isMember && !userToEvaluatePermission.isBlocked ) { + trackQueue.add({ + userId: user.id, + event: events.USER_ADDED_MODERATOR_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'user not member or is blocked', + }, + }); + return new UserError( 'This person must be a member of the community before becoming a moderator.' ); } if (userToEvaluatePermission.isModerator) { - return new UserError( - 'This person is already a moderator in your community.' - ); - } + trackQueue.add({ + userId: user.id, + event: events.USER_ADDED_MODERATOR_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'already moderator', + }, + }); - if (!currentUserPermission.isOwner && !currentUserPermission.isModerator) { return new UserError( - 'You must own or moderate this community to manage moderators.' + 'This person is already a moderator in your community.' ); } // all checks pass - if (currentUserPermission.isOwner || currentUserPermission.isModerator) { - return await makeMemberModeratorInCommunity( - communityId, - userToEvaluateId - ).catch(err => new UserError(err)); - } + return await makeMemberModeratorInCommunity(communityId, userToEvaluateId) + .then(result => { + trackQueue.add({ + userId: user.id, + event: events.USER_ADDED_MODERATOR_IN_COMMUNITY, + context: { communityId }, + }); - return new UserError( - "We weren't able to process your request to add a moderator to this community." - ); -}; + return result; + }) + .catch(err => { + trackQueue.add({ + userId: user.id, + event: events.USER_ADDED_MODERATOR_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'unknown error', + error: err.message, + }, + }); + + return new UserError(err); + }); +}); diff --git a/api/mutations/communityMember/addPendingCommunityMember.js b/api/mutations/communityMember/addPendingCommunityMember.js new file mode 100644 index 0000000000..8115c0c397 --- /dev/null +++ b/api/mutations/communityMember/addPendingCommunityMember.js @@ -0,0 +1,145 @@ +// @flow +import type { GraphQLContext } from '../../'; +import UserError from '../../utils/UserError'; +import { getCommunityById } from '../../models/community'; +import { + createMemberInCommunity, + createPendingMemberInCommunity, + checkUserPermissionsInCommunity, +} from '../../models/usersCommunities'; +import { createMemberInDefaultChannels } from '../../models/usersChannels'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { + trackQueue, + sendPrivateCommunityRequestQueue, +} from 'shared/bull/queues'; + +type Input = { + input: { + communityId: string, + }, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user } = ctx; + const { communityId } = args.input; + + const [permissions, community] = await Promise.all([ + checkUserPermissionsInCommunity(communityId, user.id), + getCommunityById(communityId), + ]); + + if (!community) { + trackQueue.add({ + userId: user.id, + event: events.USER_REQUESTED_TO_JOIN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no community', + }, + }); + + return new UserError("We couldn't find that community."); + } + + // shouldn't happen, but handle this case anyways + if (!community.isPrivate) { + if (!permissions || permissions.length === 0) { + return await Promise.all([ + createMemberInCommunity(communityId, user.id), + createMemberInDefaultChannels(communityId, user.id), + ]) + // return the community to fulfill the resolver + .then(() => community); + } + + const permission = permissions[0]; + + if (permission.isBlocked) { + trackQueue.add({ + userId: user.id, + event: events.USER_REQUESTED_TO_JOIN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'user blocked', + }, + }); + + return new UserError("You aren't able to join this community."); + } + + if (permission.isOwner || permission.isModerator || permission.isMember) { + trackQueue.add({ + userId: user.id, + event: events.USER_REQUESTED_TO_JOIN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'already member', + }, + }); + + return new UserError("You're already a member of this community."); + } + + return await Promise.all([ + createMemberInCommunity(communityId, user.id), + createMemberInDefaultChannels(communityId, user.id), + ]) + // return the community to fulfill the resolver + .then(() => community); + } + + const permission = permissions[0]; + + if (permission && permission.isBlocked) { + trackQueue.add({ + userId: user.id, + event: events.USER_REQUESTED_TO_JOIN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'user blocked', + }, + }); + + return new UserError("You aren't able to join this community."); + } + + if ( + permission && + (permission.isOwner || permission.isModerator || permissions.isMember) + ) { + trackQueue.add({ + userId: user.id, + event: events.USER_REQUESTED_TO_JOIN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'already member', + }, + }); + + return new UserError("You're already a member of this community."); + } + + if (permission && permission.isPending) { + trackQueue.add({ + userId: user.id, + event: events.USER_REQUESTED_TO_JOIN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'already pending', + }, + }); + + return new UserError('You have already requested to join this community.'); + } + + sendPrivateCommunityRequestQueue.add({ + userId: user.id, + communityId, + }); + + return await createPendingMemberInCommunity(communityId, user.id).then( + () => community + ); +}); diff --git a/api/mutations/communityMember/approvePendingCommunityMember.js b/api/mutations/communityMember/approvePendingCommunityMember.js new file mode 100644 index 0000000000..d3ef72f18d --- /dev/null +++ b/api/mutations/communityMember/approvePendingCommunityMember.js @@ -0,0 +1,141 @@ +// @flow +import type { GraphQLContext } from '../../'; +import UserError from '../../utils/UserError'; +import { getCommunityById } from '../../models/community'; +import { createMemberInDefaultChannels } from '../../models/usersChannels'; +import { + approvePendingMemberInCommunity, + checkUserPermissionsInCommunity, +} from '../../models/usersCommunities'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { + trackQueue, + sendPrivateCommunityRequestApprovedQueue, +} from 'shared/bull/queues'; + +type Input = { + input: { + userId: string, + communityId: string, + }, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + const { communityId, userId: userToEvaluateId } = args.input; + + if (!await canModerateCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.USER_APPROVED_PENDING_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); + + return new UserError( + 'You must own or moderate this community to manage members.' + ); + } + + const [userToEvaluatePermissions, community] = await Promise.all([ + checkUserPermissionsInCommunity(communityId, userToEvaluateId), + getCommunityById(communityId), + ]); + + if (!community) { + trackQueue.add({ + userId: user.id, + event: events.USER_APPROVED_PENDING_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no community', + }, + }); + + return new UserError("We couldn't find that community."); + } + + if (!userToEvaluatePermissions || userToEvaluatePermissions === 0) { + trackQueue.add({ + userId: user.id, + event: events.USER_APPROVED_PENDING_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'not pending', + }, + }); + + return new UserError( + 'This person is not requesting to join your community.' + ); + } + + const userToEvaluatePermission = userToEvaluatePermissions[0]; + + if (!userToEvaluatePermission.isPending) { + trackQueue.add({ + userId: user.id, + event: events.USER_APPROVED_PENDING_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'not pending', + }, + }); + + return new UserError( + 'This person is not requesting to join your community.' + ); + } + + if (userToEvaluatePermission.isBlocked) { + trackQueue.add({ + userId: user.id, + event: events.USER_APPROVED_PENDING_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'blocked', + }, + }); + + return new UserError('This person is already blocked in your community.'); + } + + return await Promise.all([ + approvePendingMemberInCommunity(communityId, userToEvaluateId), + createMemberInDefaultChannels(communityId, userToEvaluateId), + ]) + .then(([newPermissions]) => { + sendPrivateCommunityRequestApprovedQueue.add({ + userId: userToEvaluateId, + communityId, + moderatorId: user.id, + }); + + trackQueue.add({ + userId: user.id, + event: events.USER_APPROVED_PENDING_MEMBER_IN_COMMUNITY, + context: { communityId }, + }); + + return newPermissions; + }) + .catch(err => { + trackQueue.add({ + userId: user.id, + event: events.USER_APPROVED_PENDING_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'unknown error', + error: err.message, + }, + }); + + return new UserError(err); + }); +}); diff --git a/api/mutations/communityMember/blockCommunityMember.js b/api/mutations/communityMember/blockCommunityMember.js index 2a1d86b639..04a1d01673 100644 --- a/api/mutations/communityMember/blockCommunityMember.js +++ b/api/mutations/communityMember/blockCommunityMember.js @@ -8,6 +8,12 @@ import { blockUserInCommunity, checkUserPermissionsInCommunity, } from '../../models/usersCommunities'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; type Input = { input: { @@ -16,74 +22,133 @@ type Input = { }, }; -export default async (_: any, { input }: Input, { user }: GraphQLContext) => { - const currentUser = user; - const { communityId, userId: userToEvaluateId } = input; +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + const { communityId, userId: userToEvaluateId } = args.input; + + if (!(await canModerateCommunity(user.id, communityId, loaders))) { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); - if (!currentUser) { return new UserError( - 'You must be signed in to manage moderators in this community.' + 'You must own or moderate this community to manage members.' ); } - const [ - currentUserPermissions, - userToEvaluatePermissions, - community, - ] = await Promise.all([ - checkUserPermissionsInCommunity(communityId, currentUser.id), + const [userToEvaluatePermissions, community] = await Promise.all([ checkUserPermissionsInCommunity(communityId, userToEvaluateId), getCommunityById(communityId), ]); if (!community) { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no community', + }, + }); + return new UserError("We couldn't find that community."); } - // if no permissions exist, the user performing this mutation isn't even - // a member of this community - if (!currentUserPermissions || currentUserPermissions.length === 0) { - return new UserError('You must own this community to manage members.'); - } + if (!userToEvaluatePermissions || userToEvaluatePermissions.length === 0) { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'not member', + }, + }); - if (!userToEvaluatePermissions || userToEvaluatePermissions === 0) { return new UserError('This person is not a member of your community.'); } - const currentUserPermission = currentUserPermissions[0]; const userToEvaluatePermission = userToEvaluatePermissions[0]; if (!userToEvaluatePermission.isMember) { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'not member', + }, + }); + return new UserError('This person is not a member of your community.'); } if (userToEvaluatePermission.isBlocked) { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'blocked', + }, + }); + return new UserError('This person is already blocked in your community.'); } - if (!currentUserPermission.isOwner && !currentUserPermission.isModerator) { - return new UserError( - 'You must own or moderate this community to manage members.' - ); + if (userToEvaluatePermission.isOwner) { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'user is owner', + }, + }); + + return new UserError('The owner of the community cannot be blocked.'); } - // all checks pass - if (currentUserPermission.isOwner || currentUserPermission.isModerator) { - const channels = await getChannelsByCommunity(community.id); - const channelIds = channels.map(c => c.id); - const blockInChannelPromises = channelIds.map( - async channelId => await blockUserInChannel(channelId, userToEvaluateId) - ); + const channels = await getChannelsByCommunity(community.id); + const channelIds = channels.map(c => c.id); + const blockInChannelPromises = channelIds.map(async channelId => { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_MEMBER_IN_CHANNEL, + context: { channelId }, + }); - return await Promise.all([ - blockUserInCommunity(communityId, userToEvaluateId), - ...blockInChannelPromises, - ]) - .then(([newPermissions]) => newPermissions) - .catch(err => new UserError(err)); - } + return await blockUserInChannel(channelId, userToEvaluateId); + }); - return new UserError( - "We weren't able to process your request to block a member in this community." - ); -}; + return await Promise.all([ + blockUserInCommunity(communityId, userToEvaluateId), + ...blockInChannelPromises, + ]) + .then(([newPermissions]) => { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_MEMBER_IN_COMMUNITY, + context: { communityId }, + }); + + return newPermissions; + }) + .catch(err => { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'unknown error', + error: err.message, + }, + }); + + return new UserError(err); + }); +}); diff --git a/api/mutations/communityMember/blockPendingCommunityMember.js b/api/mutations/communityMember/blockPendingCommunityMember.js new file mode 100644 index 0000000000..885865919f --- /dev/null +++ b/api/mutations/communityMember/blockPendingCommunityMember.js @@ -0,0 +1,144 @@ +// @flow +import type { GraphQLContext } from '../../'; +import UserError from '../../utils/UserError'; +import { getCommunityById } from '../../models/community'; +import { blockUserInChannel } from '../../models/usersChannels'; +import { getChannelsByCommunity } from '../../models/channel'; +import { + blockPendingMemberInCommunity, + checkUserPermissionsInCommunity, +} from '../../models/usersCommunities'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; + +type Input = { + input: { + userId: string, + communityId: string, + }, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + const { communityId, userId: userToEvaluateId } = args.input; + + if (!await canModerateCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_PENDING_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); + + return new UserError( + 'You must own or moderate this community to manage members.' + ); + } + + const [userToEvaluatePermissions, community] = await Promise.all([ + checkUserPermissionsInCommunity(communityId, userToEvaluateId), + getCommunityById(communityId), + ]); + + if (!community) { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_PENDING_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no community', + }, + }); + + return new UserError("We couldn't find that community."); + } + + if (!userToEvaluatePermissions || userToEvaluatePermissions === 0) { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_PENDING_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'not member', + }, + }); + + return new UserError('This person is not a member of your community.'); + } + + const userToEvaluatePermission = userToEvaluatePermissions[0]; + + if ( + !userToEvaluatePermission.isMember && + !userToEvaluatePermission.isPending + ) { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_PENDING_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'not member', + }, + }); + + return new UserError('This person is not a member of your community.'); + } + + if (userToEvaluatePermission.isBlocked) { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_PENDING_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'blocked', + }, + }); + + return new UserError('This person is already blocked in your community.'); + } + + const channels = await getChannelsByCommunity(community.id); + const channelIds = channels.map(c => c.id); + const blockInChannelPromises = channelIds.map(async channelId => { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_MEMBER_IN_CHANNEL, + context: { channelId }, + }); + + return await blockUserInChannel(channelId, userToEvaluateId); + }); + + return await Promise.all([ + blockPendingMemberInCommunity(communityId, userToEvaluateId), + ...blockInChannelPromises, + ]) + .then(([newPermissions]) => { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_PENDING_MEMBER_IN_COMMUNITY, + context: { communityId }, + }); + + return newPermissions; + }) + .catch(err => { + trackQueue.add({ + userId: user.id, + event: events.USER_BLOCKED_PENDING_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'unknown error', + error: err.message, + }, + }); + + return new UserError(err); + }); +}); diff --git a/api/mutations/communityMember/index.js b/api/mutations/communityMember/index.js index 7b31ee081b..c597990eee 100644 --- a/api/mutations/communityMember/index.js +++ b/api/mutations/communityMember/index.js @@ -1,18 +1,30 @@ // @flow import addCommunityMember from './addCommunityMember'; +import addCommunityMembers from './addCommunityMembers'; +import addCommunityMemberWithToken from './addCommunityMemberWithToken'; +import addPendingCommunityMember from './addPendingCommunityMember'; import removeCommunityMember from './removeCommunityMember'; +import removePendingCommunityMember from './removePendingCommunityMember'; import addCommunityModerator from './addCommunityModerator'; +import approvePendingCommunityMember from './approvePendingCommunityMember'; import removeCommunityModerator from './removeCommunityModerator'; import blockCommunityMember from './blockCommunityMember'; +import blockPendingCommunityMember from './blockPendingCommunityMember'; import unblockCommunityMember from './unblockCommunityMember'; module.exports = { Mutation: { addCommunityMember, + addCommunityMembers, + addCommunityMemberWithToken, + addPendingCommunityMember, removeCommunityMember, + removePendingCommunityMember, addCommunityModerator, + approvePendingCommunityMember, removeCommunityModerator, blockCommunityMember, + blockPendingCommunityMember, unblockCommunityMember, }, }; diff --git a/api/mutations/communityMember/removeCommunityMember.js b/api/mutations/communityMember/removeCommunityMember.js index 205d3a1cdf..b6d3fea864 100644 --- a/api/mutations/communityMember/removeCommunityMember.js +++ b/api/mutations/communityMember/removeCommunityMember.js @@ -8,6 +8,13 @@ import { } from '../../models/usersCommunities'; import { removeMemberInChannel } from '../../models/usersChannels'; import { getChannelsByUserAndCommunity } from '../../models/channel'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import { + getThreadNotificationStatusForUser, + updateThreadNotificationStatusForUser, +} from '../../models/usersThreads'; type Input = { input: { @@ -15,24 +22,38 @@ type Input = { }, }; -export default async (_: any, { input }: Input, { user }: GraphQLContext) => { - const currentUser = user; - const { communityId } = input; - - if (!currentUser) { - return new UserError('You must be signed in to leave this community.'); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { communityId } = args.input; + const { user } = ctx; const [permissions, community] = await Promise.all([ - checkUserPermissionsInCommunity(communityId, currentUser.id), + checkUserPermissionsInCommunity(communityId, user.id), getCommunityById(communityId), ]); if (!community) { + trackQueue.add({ + userId: user.id, + event: events.USER_LEFT_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no community', + }, + }); + return new UserError("We couldn't find that community."); } if (!permissions || permissions.length === 0) { + trackQueue.add({ + userId: user.id, + event: events.USER_LEFT_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'not member', + }, + }); + return new UserError("You're not a member of this community."); } @@ -40,6 +61,15 @@ export default async (_: any, { input }: Input, { user }: GraphQLContext) => { // they've already left the community if (!permission.isMember) { + trackQueue.add({ + userId: user.id, + event: events.USER_LEFT_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'not member', + }, + }); + return new UserError("You're not a member of this community."); } @@ -48,10 +78,28 @@ export default async (_: any, { input }: Input, { user }: GraphQLContext) => { // anyways, but we protect this regardless because we want to retain the // usersCommunities record forever that indicates this user is blocked if (permission.isBlocked) { + trackQueue.add({ + userId: user.id, + event: events.USER_LEFT_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'blocked', + }, + }); + return new UserError("You aren't able to leave this community."); } if (permission.isOwner) { + trackQueue.add({ + userId: user.id, + event: events.USER_LEFT_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'is owner', + }, + }); + return new UserError('Community owners cannot leave their own community.'); } @@ -59,19 +107,55 @@ export default async (_: any, { input }: Input, { user }: GraphQLContext) => { if (permission.isMember || permission.isModerator) { const allChannelsInCommunity = await getChannelsByUserAndCommunity( communityId, - currentUser.id + user.id ); + const leaveChannelsPromises = allChannelsInCommunity.map(channel => - removeMemberInChannel(channel, currentUser.id) + removeMemberInChannel(channel, user.id) ); return await Promise.all([ - removeMemberInCommunity(communityId, currentUser.id), + removeMemberInCommunity(communityId, user.id), ...leaveChannelsPromises, - ]).then(() => community); + ]) + .then(async () => { + // if the community has a watercooler and the current user has a subscription + // to it, remove the subscription + if (community.watercoolerId) { + const threadId = community.watercoolerId; + const threadNotificationStatus = await getThreadNotificationStatusForUser( + threadId, + user.id + ); + if ( + !threadNotificationStatus || + !threadNotificationStatus.receiveNotifications + ) { + return; + } + + return await updateThreadNotificationStatusForUser( + threadId, + user.id, + false + ); + } + + return; + }) + .then(() => getCommunityById(communityId)); } + trackQueue.add({ + userId: user.id, + event: events.USER_LEFT_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'unknown error', + }, + }); + return new UserError( "We weren't able to process your request to leave this community." ); -}; +}); diff --git a/api/mutations/communityMember/removeCommunityModerator.js b/api/mutations/communityMember/removeCommunityModerator.js index 01219295db..decf0fa757 100644 --- a/api/mutations/communityMember/removeCommunityModerator.js +++ b/api/mutations/communityMember/removeCommunityModerator.js @@ -8,6 +8,12 @@ import { } from '../../models/usersCommunities'; import { removeModeratorInChannel } from '../../models/usersChannels'; import { getChannelsByUserAndCommunity } from '../../models/channel'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; type Input = { input: { @@ -16,41 +22,54 @@ type Input = { }, }; -export default async (_: any, { input }: Input, { user }: GraphQLContext) => { - const currentUser = user; - const { communityId, userId: userToEvaluateId } = input; +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { communityId, userId: userToEvaluateId } = args.input; + const { user, loaders } = ctx; + + if (!await canModerateCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.USER_REMOVED_MODERATOR_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); - if (!currentUser) { - return new UserError( - 'You must be signed in to manage moderators in this community.' - ); + return new UserError('You must own this community to manage moderators.'); } - const [ - currentUserPermissions, - userToEvaluatePermissions, - community, - ] = await Promise.all([ - checkUserPermissionsInCommunity(communityId, currentUser.id), + const [userToEvaluatePermissions, community] = await Promise.all([ checkUserPermissionsInCommunity(communityId, userToEvaluateId), getCommunityById(communityId), ]); if (!community) { - return new UserError("We couldn't find that community."); - } + trackQueue.add({ + userId: user.id, + event: events.USER_REMOVED_MODERATOR_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no community', + }, + }); - // if no permissions exist, the user performing this mutation isn't even - // a member of this community - if (!currentUserPermissions || currentUserPermissions.length === 0) { - return new UserError('You must own this community to manage moderators.'); + return new UserError("We couldn't find that community."); } if (!userToEvaluatePermissions || userToEvaluatePermissions === 0) { + trackQueue.add({ + userId: user.id, + event: events.USER_REMOVED_MODERATOR_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'user not member of community', + }, + }); + return new UserError('This person is not a member of the community.'); } - const currentUserPermission = currentUserPermissions[0]; const userToEvaluatePermission = userToEvaluatePermissions[0]; // it's possible for a member to be moving from blocked -> moderator @@ -62,43 +81,72 @@ export default async (_: any, { input }: Input, { user }: GraphQLContext) => { !userToEvaluatePermission.isMember && !userToEvaluatePermission.isBlocked ) { + trackQueue.add({ + userId: user.id, + event: events.USER_REMOVED_MODERATOR_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'user not member of community', + }, + }); + return new UserError('This person is not a member of your community.'); } if (!userToEvaluatePermission.isModerator) { + trackQueue.add({ + userId: user.id, + event: events.USER_REMOVED_MODERATOR_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'user not moderator of community', + }, + }); + return new UserError('This person is not a moderator in your community.'); } - if (!currentUserPermission.isOwner && !currentUserPermission.isModerator) { - return new UserError( - 'You must own or moderate this community to manage moderators.' - ); - } + // remove as moderator in community and all channels, this should be expected UX + const allChannelsInCommunity = await getChannelsByUserAndCommunity( + communityId, + userToEvaluateId + ); - // all checks pass - if ( - (currentUserPermission.isOwner || currentUserPermission.isModerator) && - userToEvaluatePermission.isModerator - ) { - // remove as moderator in community and all channels, this should be expected UX - const allChannelsInCommunity = await getChannelsByUserAndCommunity( - communityId, - userToEvaluateId - ); - - const removeChannelModeratorPromises = allChannelsInCommunity.map(channel => - removeModeratorInChannel(channel, userToEvaluateId) - ); - - return await Promise.all([ - removeModeratorInCommunity(communityId, userToEvaluateId), - ...removeChannelModeratorPromises, - ]) - .then(([newPermissions]) => newPermissions) - .catch(err => new UserError(err)); - } + const removeChannelModeratorPromises = allChannelsInCommunity.map( + channelId => { + trackQueue.add({ + userId: user.id, + event: events.USER_REMOVED_MODERATOR_IN_CHANNEL, + context: { channelId }, + }); - return new UserError( - "We weren't able to process your request to remove a moderator in this community." + return removeModeratorInChannel(channelId, userToEvaluateId); + } ); -}; + + return await Promise.all([ + removeModeratorInCommunity(communityId, userToEvaluateId), + ...removeChannelModeratorPromises, + ]) + .then(([newPermissions]) => { + trackQueue.add({ + userId: user.id, + event: events.USER_REMOVED_MODERATOR_IN_COMMUNITY, + context: { communityId }, + }); + return newPermissions; + }) + .catch(err => { + trackQueue.add({ + userId: user.id, + event: events.USER_REMOVED_MODERATOR_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'unknown error', + error: err.message, + }, + }); + + return new UserError(err); + }); +}); diff --git a/api/mutations/communityMember/removePendingCommunityMember.js b/api/mutations/communityMember/removePendingCommunityMember.js new file mode 100644 index 0000000000..bc26406438 --- /dev/null +++ b/api/mutations/communityMember/removePendingCommunityMember.js @@ -0,0 +1,58 @@ +// @flow +import type { GraphQLContext } from '../../'; +import UserError from '../../utils/UserError'; +import { getCommunityById } from '../../models/community'; +import { + removePendingMemberInCommunity, + checkUserPermissionsInCommunity, +} from '../../models/usersCommunities'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; + +type Input = { + input: { + communityId: string, + }, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user } = ctx; + const { communityId } = args.input; + + const [permissions, community] = await Promise.all([ + checkUserPermissionsInCommunity(communityId, user.id), + getCommunityById(communityId), + ]); + + // if no permissions exist, the user wasn't pending to begin with + if (!permissions || permissions.length === 0) { + trackQueue.add({ + userId: user.id, + event: events.USER_CANCELED_REQUEST_TO_JOIN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'not pending', + }, + }); + + return community; + } + + if (!community) { + trackQueue.add({ + userId: user.id, + event: events.USER_CANCELED_REQUEST_TO_JOIN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no community', + }, + }); + + return new UserError("We couldn't find that community."); + } + + return await removePendingMemberInCommunity(communityId, user.id).then( + () => community + ); +}); diff --git a/api/mutations/communityMember/unblockCommunityMember.js b/api/mutations/communityMember/unblockCommunityMember.js index 90bcd531f0..77380701a6 100644 --- a/api/mutations/communityMember/unblockCommunityMember.js +++ b/api/mutations/communityMember/unblockCommunityMember.js @@ -8,6 +8,12 @@ import { unblockUserInCommunity, checkUserPermissionsInCommunity, } from '../../models/usersCommunities'; +import { + isAuthedResolver as requireAuth, + canModerateCommunity, +} from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; type Input = { input: { @@ -16,71 +22,109 @@ type Input = { }, }; -export default async (_: any, { input }: Input, { user }: GraphQLContext) => { - const currentUser = user; - const { communityId, userId: userToEvaluateId } = input; +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + const { communityId, userId: userToEvaluateId } = args.input; + + if (!await canModerateCommunity(user.id, communityId, loaders)) { + trackQueue.add({ + userId: user.id, + event: events.USER_UNBLOCKED_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no permission', + }, + }); - if (!currentUser) { return new UserError( - 'You must be signed in to manage members in this community.' + 'You must own or moderate this community to manage members.' ); } - const [ - currentUserPermissions, - userToEvaluatePermissions, - community, - ] = await Promise.all([ - checkUserPermissionsInCommunity(communityId, currentUser.id), + const [userToEvaluatePermissions, community] = await Promise.all([ checkUserPermissionsInCommunity(communityId, userToEvaluateId), getCommunityById(communityId), ]); if (!community) { - return new UserError("We couldn't find that community."); - } + trackQueue.add({ + userId: user.id, + event: events.USER_UNBLOCKED_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'no community', + }, + }); - // if no permissions exist, the user performing this mutation isn't even - // a member of this community - if (!currentUserPermissions || currentUserPermissions.length === 0) { - return new UserError('You must own this community to manage members.'); + return new UserError("We couldn't find that community."); } if (!userToEvaluatePermissions || userToEvaluatePermissions === 0) { + trackQueue.add({ + userId: user.id, + event: events.USER_UNBLOCKED_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'user not member of community', + }, + }); + return new UserError('This person is not a member of your community.'); } - const currentUserPermission = currentUserPermissions[0]; const userToEvaluatePermission = userToEvaluatePermissions[0]; if (!userToEvaluatePermission.isBlocked) { + trackQueue.add({ + userId: user.id, + event: events.USER_UNBLOCKED_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'user not blocked', + }, + }); + return new UserError('This person is not blocked in your community.'); } - if (!currentUserPermission.isOwner && !currentUserPermission.isModerator) { - return new UserError( - 'You must own or moderate this community to manage members.' - ); - } + const channels = await getChannelsByCommunity(community.id); + const defaultChannelIds = channels.filter(c => c.isDefault).map(c => c.id); + const unblockInDefaultChannelPromises = defaultChannelIds.map( + async channelId => { + trackQueue.add({ + userId: user.id, + event: events.USER_UNBLOCKED_MEMBER_IN_CHANNEL, + context: { channelId }, + }); - // all checks passed - if (userToEvaluatePermission.isBlocked) { - const channels = await getChannelsByCommunity(community.id); - const defaultChannelIds = channels.filter(c => c.isDefault).map(c => c.id); - const unblockInDefaultChannelPromises = defaultChannelIds.map( - async channelId => - await approveBlockedUserInChannel(channelId, userToEvaluateId) - ); + return await approveBlockedUserInChannel(channelId, userToEvaluateId); + } + ); - return await Promise.all([ - unblockUserInCommunity(communityId, userToEvaluateId), - ...unblockInDefaultChannelPromises, - ]) - .then(([newPermissions]) => newPermissions) - .catch(err => new UserError(err)); - } + return await Promise.all([ + unblockUserInCommunity(communityId, userToEvaluateId), + ...unblockInDefaultChannelPromises, + ]) + .then(([newPermissions]) => { + trackQueue.add({ + userId: user.id, + event: events.USER_UNBLOCKED_MEMBER_IN_COMMUNITY, + context: { communityId }, + }); - return new UserError( - "We weren't able to process your request to unblock a member in this community." - ); -}; + return newPermissions; + }) + .catch(err => { + trackQueue.add({ + userId: user.id, + event: events.USER_UNBLOCKED_MEMBER_IN_COMMUNITY_FAILED, + context: { communityId }, + properties: { + reason: 'unknown error', + error: err.message, + }, + }); + + return new UserError(err); + }); +}); diff --git a/api/mutations/directMessageThread/createDirectMessageThread.js b/api/mutations/directMessageThread/createDirectMessageThread.js index 0a62b79407..b8fe6c2a74 100644 --- a/api/mutations/directMessageThread/createDirectMessageThread.js +++ b/api/mutations/directMessageThread/createDirectMessageThread.js @@ -5,16 +5,20 @@ import { checkForExistingDMThread, getDirectMessageThread, createDirectMessageThread, + setDirectMessageThreadLastActive, } from '../../models/directMessageThread'; -import { uploadImage } from '../../utils/s3'; +import { uploadImage } from '../../utils/file-storage'; import { storeMessage } from '../../models/message'; import { setUserLastSeenInDirectMessageThread, createMemberInDirectMessageThread, } from '../../models/usersDirectMessageThreads'; import type { FileUpload } from 'shared/types'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; -type DMThreadInput = { +export type CreateDirectMessageThreadInput = { input: { participants: Array, message: { @@ -28,93 +32,127 @@ type DMThreadInput = { }, }; -export default async ( - _: any, - { input }: DMThreadInput, - { user }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser) - return new UserError('You must be signed in to send a direct message.'); - - if (!input.participants) - return new UserError('Nobody was selected to create a thread.'); - - // if users and messages exist, continue - const { participants, message } = input; - - // if the group being created has more than one participant, a group - // thread is being created - this means that people can be added - // and removed from the thread in the future. we *don't* want this - // behavior for 1:1 threads to preserve privacy, so we store an `isGroup` - // boolean on the dmThread object itself which will be used in other - // mutations to add or remove members - const isGroup = participants.length > 1; - - // collect all participant ids and the current user id into an array - we - // use this to determine if an existing DM thread with this exact - // set of participants already exists or not - const allMemberIds = [...participants, currentUser.id]; - - // placeholder - let threadId, threadToReturn; - - // check to see if a dm thread with this exact set of participants exists - const existingThread = await checkForExistingDMThread(allMemberIds); - - if (existingThread) { - threadId = existingThread; - threadToReturn = await getDirectMessageThread(threadId); - } else { - threadToReturn = await createDirectMessageThread(isGroup); - threadId = threadToReturn.id; - } - - const handleStoreMessage = async message => { - if (message.messageType === 'text' || message.messageType === 'draftjs') { - // once we have an id we can generate a proper message object - const messageWithThread = { - ...message, - threadId, - }; - - return await storeMessage(messageWithThread, currentUser.id); - } else if (message.messageType === 'media' && message.file) { - const url = await uploadImage(message.file, 'threads', threadId); - - // build a new message object with a new file field with metadata - const newMessage = Object.assign({}, message, { - ...message, - threadId: threadId, - content: { - body: url, - }, - file: { - name: message.file && message.file.filename, - size: null, - type: message.file && message.file.mimetype, +export default requireAuth( + async (_: any, args: CreateDirectMessageThreadInput, ctx: GraphQLContext) => { + const { user } = ctx; + const { input } = args; + + if (!input.participants) { + trackQueue.add({ + userId: user.id, + event: events.DIRECT_MESSAGE_THREAD_CREATED_FAILED, + properties: { + reason: 'no users selected', }, }); - return await storeMessage(newMessage, currentUser.id); + return new UserError('Nobody was selected to create a thread.'); + } + + // if users and messages exist, continue + const { participants, message } = input; + + // if the group being created has more than one participant, a group + // thread is being created - this means that people can be added + // and removed from the thread in the future. we *don't* want this + // behavior for 1:1 threads to preserve privacy, so we store an `isGroup` + // boolean on the dmThread object itself which will be used in other + // mutations to add or remove members + const isGroup = participants.length > 1; + + // collect all participant ids and the current user id into an array - we + // use this to determine if an existing DM thread with this exact + // set of participants already exists or not + const allMemberIds = [...participants, user.id]; + + // placeholder + let threadId, threadToReturn; + + // check to see if a dm thread with this exact set of participants exists + const existingThread = await checkForExistingDMThread(allMemberIds); + + if (existingThread) { + threadId = existingThread; + threadToReturn = await getDirectMessageThread(threadId); } else { - return new UserError('Unknown message type on this bad boy.'); + threadToReturn = await createDirectMessageThread(isGroup, user.id); + threadId = threadToReturn.id; + } + + const handleStoreMessage = async message => { + if (message.messageType === 'text' || message.messageType === 'draftjs') { + // once we have an id we can generate a proper message object + const messageWithThread = { + ...message, + threadId, + }; + + return await storeMessage(messageWithThread, user.id); + } else if (message.messageType === 'media' && message.file) { + let url; + try { + url = await uploadImage(message.file, 'threads', threadId); + } catch (err) { + trackQueue.add({ + userId: user.id, + event: events.DIRECT_MESSAGE_THREAD_CREATED_FAILED, + properties: { + reason: 'image upload failed', + }, + }); + return new UserError(err.message); + } + + // build a new message object with a new file field with metadata + const newMessage = Object.assign({}, message, { + ...message, + threadId: threadId, + content: { + body: url, + }, + file: { + name: message.file && message.file.filename, + size: null, + type: message.file && message.file.mimetype, + }, + }); + + return await storeMessage(newMessage, user.id); + } else { + trackQueue.add({ + userId: user.id, + event: events.DIRECT_MESSAGE_THREAD_CREATED_FAILED, + properties: { + reason: 'unknown message type', + }, + }); + return new UserError('Unknown message type on this bad boy.'); + } + }; + + if (existingThread) { + return await Promise.all([ + setUserLastSeenInDirectMessageThread(threadId, user.id), + setDirectMessageThreadLastActive(threadId), + handleStoreMessage(message), + ]).then(() => threadToReturn); } - }; - if (existingThread) { + trackQueue.add({ + userId: user.id, + event: events.DIRECT_MESSAGE_THREAD_CREATED, + }); + return await Promise.all([ - setUserLastSeenInDirectMessageThread(threadId, currentUser.id), + createMemberInDirectMessageThread(threadId, user.id, true), handleStoreMessage(message), + participants.map(participant => { + trackQueue.add({ + userId: participant, + event: events.DIRECT_MESSAGE_THREAD_RECEIVED, + }); + return createMemberInDirectMessageThread(threadId, participant, false); + }), ]).then(() => threadToReturn); } - - return await Promise.all([ - createMemberInDirectMessageThread(threadId, currentUser.id, true), - handleStoreMessage(message), - participants.map(participant => - createMemberInDirectMessageThread(threadId, participant, false) - ), - ]).then(() => threadToReturn); -}; +); diff --git a/api/mutations/directMessageThread/setLastSeen.js b/api/mutations/directMessageThread/setLastSeen.js index 5d86f8a160..11e18fea12 100644 --- a/api/mutations/directMessageThread/setLastSeen.js +++ b/api/mutations/directMessageThread/setLastSeen.js @@ -1,6 +1,15 @@ // @flow import type { GraphQLContext } from '../../'; import { setUserLastSeenInDirectMessageThread } from '../../models/usersDirectMessageThreads'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; -export default (_: any, { id }: { id: string }, { user }: GraphQLContext) => - setUserLastSeenInDirectMessageThread(id, user.id); +type Input = { + id: string, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { id } = args; + const { user } = ctx; + + return setUserLastSeenInDirectMessageThread(id, user.id); +}); diff --git a/api/mutations/message/addMessage.js b/api/mutations/message/addMessage.js index 6942348e0b..4c19eb503b 100644 --- a/api/mutations/message/addMessage.js +++ b/api/mutations/message/addMessage.js @@ -1,20 +1,26 @@ // @flow +import { stateFromMarkdown } from 'draft-js-import-markdown'; +import { convertToRaw } from 'draft-js'; import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; -import { uploadImage } from '../../utils/s3'; -import { storeMessage } from '../../models/message'; +import { uploadImage } from '../../utils/file-storage'; +import { storeMessage, getMessage } from '../../models/message'; import { setDirectMessageThreadLastActive } from '../../models/directMessageThread'; import { setUserLastSeenInDirectMessageThread } from '../../models/usersDirectMessageThreads'; import { createMemberInChannel } from '../../models/usersChannels'; -import { - createParticipantInThread, - createParticipantWithoutNotificationsInThread, -} from '../../models/usersThreads'; +import { createParticipantInThread } from '../../models/usersThreads'; import addCommunityMember from '../communityMember/addCommunityMember'; import { trackUserThreadLastSeenQueue } from 'shared/bull/queues'; import type { FileUpload } from 'shared/types'; +import { events } from 'shared/analytics'; +import { + isAuthedResolver as requireAuth, + canViewDMThread, +} from '../../utils/permissions'; +import { trackQueue, calculateThreadScoreQueue } from 'shared/bull/queues'; +import { validateRawContentState } from '../../utils/validate-draft-js-input'; -type AddMessageInput = { +type Input = { message: { threadId: string, threadType: 'story' | 'directMessageThread', @@ -22,28 +28,58 @@ type AddMessageInput = { content: { body: string, }, + parentId?: string, file?: FileUpload, }, }; -export default async ( - _: any, - { message }: AddMessageInput, - { user, loaders }: GraphQLContext -) => { - const currentUser = user; +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { message } = args; + const { user, loaders } = ctx; + + const eventFailed = + message.threadType === 'story' + ? events.MESSAGE_SENT_FAILED + : events.DIRECT_MESSAGE_SENT_FAILED; + + if (message.threadType === 'directMessageThread') { + if (!(await canViewDMThread(user.id, message.threadId, loaders))) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'no permission', + }, + }); - if (!currentUser) { - return new UserError('You must be signed in to send a message.'); + return new UserError( + 'You don’t have permission to send a message in this conversation' + ); + } } if (message.messageType === 'media' && !message.file) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'media message without file', + }, + }); + return new UserError( "Can't send media message without an image, please try again." ); } if (message.messageType !== 'media' && message.file) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'non media message with file', + }, + }); return new UserError( `To send an image, please use messageType: "media" instead of "${ message.messageType @@ -51,6 +87,69 @@ export default async ( ); } + if (message.messageType === 'text') { + message.content.body = JSON.stringify( + convertToRaw( + stateFromMarkdown(message.content.body, { + parserOptions: { + breaks: true, + }, + }) + ) + ); + message.messageType = 'draftjs'; + } + + if (message.messageType === 'draftjs') { + let body; + try { + body = JSON.parse(message.content.body); + } catch (err) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'invalid draftjs data', + message, + }, + }); + + return new UserError( + 'Please provide serialized raw DraftJS content state as content.body' + ); + } + if (!validateRawContentState(body)) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'invalid draftjs data', + message, + }, + }); + + throw new UserError( + 'Please provide serialized raw DraftJS content state as content.body' + ); + } + } + + if (message.parentId) { + const parent = await getMessage(message.parentId); + if (parent.threadId !== message.threadId) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'quoted message in different thread', + message, + }, + }); + + return new UserError('You can only quote messages from the same thread.'); + } + } + // construct the shape of the object to be stored in the db let messageForDb = Object.assign({}, message); if (message.file && message.messageType === 'media') { @@ -62,12 +161,36 @@ export default async ( type: file.mimetype, }; - const url = await uploadImage(file, 'threads', message.threadId); + let url; + try { + url = await uploadImage(file, 'threads', message.threadId); + } catch (err) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'media upload failed', + message, + }, + }); + + return new UserError(err.message); + } + + if (!url) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'media upload failed', + message, + }, + }); - if (!url) return new UserError( "We weren't able to upload this image, please try again" ); + } messageForDb = Object.assign({}, messageForDb, { content: { @@ -77,43 +200,68 @@ export default async ( }); } - const messagePromise = async () => - await storeMessage(messageForDb, currentUser.id); + const messagePromise = async () => await storeMessage(messageForDb, user.id); // handle DM thread messages up front if (message.threadType === 'directMessageThread') { setDirectMessageThreadLastActive(message.threadId); - setUserLastSeenInDirectMessageThread(message.threadId, currentUser.id); + setUserLastSeenInDirectMessageThread(message.threadId, user.id); return await messagePromise(); } // at this point we are only dealing with thread messages const thread = await loaders.thread.load(message.threadId); - if (thread.isDeleted) { + if (!thread || thread.deletedAt) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'thread deleted', + }, + }); return new UserError("Can't reply in a deleted thread."); } if (thread.isLocked) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'thread locked', + }, + }); return new UserError("Can't reply in a locked thread."); } const [communityPermissions, channelPermissions, channel] = await Promise.all( [ - loaders.userPermissionsInCommunity.load([ - currentUser.id, - thread.communityId, - ]), - loaders.userPermissionsInChannel.load([currentUser.id, thread.channelId]), + loaders.userPermissionsInCommunity.load([user.id, thread.communityId]), + loaders.userPermissionsInChannel.load([user.id, thread.channelId]), loaders.channel.load(thread.channelId), ] ); if (!channel || channel.deletedAt) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'channel deleted', + }, + }); return new UserError('This channel doesn’t exist'); } if (channel.archivedAt) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'channel archived', + }, + }); + return new UserError('This channel has been archived'); } @@ -123,6 +271,14 @@ export default async ( // user can't post if blocked at any level if (isBlockedInCommunity || isBlockedInChannel) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'no permission', + }, + }); + return new UserError( "You don't have permission to post in this conversation" ); @@ -132,22 +288,19 @@ export default async ( channel.isPrivate && (!channelPermissions || !channelPermissions.isMember) ) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'no permission', + }, + }); + return new UserError( 'You dont’t have permission to post in this conversation' ); } - const participantPromise = async () => { - if (thread.watercooler) { - return await createParticipantWithoutNotificationsInThread( - message.threadId, - currentUser.id - ); - } else { - return await createParticipantInThread(message.threadId, currentUser.id); - } - }; - // dummy async function that will run if the user is already a member of the // channel where the message is being sent let membershipPromise = async () => await {}; @@ -160,7 +313,7 @@ export default async ( (!channelPermissions || !channelPermissions.isMember) ) { membershipPromise = async () => - await createMemberInChannel(thread.channelId, currentUser.id); + await createMemberInChannel(thread.channelId, user.id, false); } // if the user is not a member of the community, or has previously joined @@ -173,14 +326,14 @@ export default async ( await addCommunityMember( {}, { input: { communityId: thread.communityId } }, - { user: currentUser, loaders: loaders } + ctx ); } return membershipPromise() - .then(() => participantPromise()) + .then(() => createParticipantInThread(message.threadId, user.id)) .then(() => messagePromise()) - .then(dbMessage => { + .then(async dbMessage => { const contextPermissions = { communityId: thread.communityId, reputation: communityPermissions ? communityPermissions.reputation : 0, @@ -191,18 +344,35 @@ export default async ( }; trackUserThreadLastSeenQueue.add({ - userId: currentUser.id, + userId: user.id, threadId: message.threadId, timestamp: Date.now(), }); + calculateThreadScoreQueue.add( + { + threadId: message.threadId, + }, + { + jobId: message.threadId, + } + ); return { ...dbMessage, contextPermissions, }; }) .catch(err => { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + message, + reason: 'unknown error', + error: err.message, + }, + }); console.error('Error sending message', err); return new UserError('Error sending message, please try again'); }); -}; +}); diff --git a/api/mutations/message/deleteMessage.js b/api/mutations/message/deleteMessage.js index cdcbb3e390..d4b60e57a8 100644 --- a/api/mutations/message/deleteMessage.js +++ b/api/mutations/message/deleteMessage.js @@ -5,54 +5,105 @@ import { getMessage, deleteMessage, userHasMessagesInThread, + getMessages, } from '../../models/message'; -import { getThread } from '../../models/thread'; +import { setThreadLastActive } from '../../models/thread'; import { deleteParticipantInThread } from '../../models/usersThreads'; import { getUserPermissionsInChannel } from '../../models/usersChannels'; import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; +import { events } from 'shared/analytics'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { trackQueue, calculateThreadScoreQueue } from 'shared/bull/queues'; -export default async ( - _: any, - { id }: { id: string }, - { user }: GraphQLContext -) => { - const currentUser = user; - if (!currentUser) - throw new UserError('You must be signed in to delete a message.'); +type Input = { + id: string, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { id } = args; + const { user, loaders } = ctx; const message = await getMessage(id); - if (!message) throw new UserError('This message does not exist.'); - if (message.senderId !== currentUser.id) { + if (!message) { + trackQueue.add({ + userId: user.id, + event: events.MESSAGE_DELETED_FAILED, + properties: { + reason: 'message not found', + }, + }); + + return new UserError('This message does not exist.'); + } + + const eventFailed = + message.threadType === 'story' + ? events.MESSAGE_DELETED_FAILED + : events.DIRECT_MESSAGE_DELETED_FAILED; + + const thread = await loaders.thread.load(message.threadId); + + if (message.senderId !== user.id) { // Only the sender can delete a directMessageThread message if (message.threadType === 'directMessageThread') { - throw new UserError('You can only delete your own messages.'); + trackQueue.add({ + userId: user.id, + event: eventFailed, + context: { messageId: id }, + properties: { + reason: 'message not sent by user', + }, + }); + + return new UserError('You can only delete your own messages.'); } - const thread = await getThread(message.threadId); - const communityPermissions = await getUserPermissionsInCommunity( - thread.communityId, - currentUser.id - ); - const channelPermissions = await getUserPermissionsInChannel( - thread.channelId, - currentUser.id - ); + const [communityPermissions, channelPermissions] = await Promise.all([ + getUserPermissionsInCommunity(thread.communityId, user.id), + getUserPermissionsInChannel(thread.channelId, user.id), + ]); + const canModerate = channelPermissions.isOwner || communityPermissions.isOwner || channelPermissions.isModerator || communityPermissions.isModerator; - if (!canModerate) - throw new UserError("You don't have permission to delete this message."); + if (!canModerate) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + context: { messageId: id }, + properties: { + reason: 'no permission', + }, + }); + + return new UserError("You don't have permission to delete this message."); + } } // Delete message and remove participant from thread if it's the only message from that person - return deleteMessage(currentUser.id, id) + return deleteMessage(user.id, id) .then(async () => { // We don't need to delete participants of direct message threads if (message.threadType === 'directMessageThread') return true; + // If it was the last message in the thread, reset thread.lastActive to + // the previous messages timestamp + if ( + new Date(thread.lastActive).getTime() === + new Date(message.timestamp).getTime() + ) { + const messages = await getMessages(message.threadId, { last: 1 }); + await setThreadLastActive( + message.threadId, + messages && messages.length > 0 + ? messages[0].timestamp + : thread.createdAt + ); + } + const hasMoreMessages = await userHasMessagesInThread( message.threadId, message.senderId @@ -65,5 +116,15 @@ export default async ( message.senderId ); }) + .then(() => { + return calculateThreadScoreQueue.add( + { + threadId: message.threadId, + }, + { + jobId: message.threadId, + } + ); + }) .then(() => true); -}; +}); diff --git a/api/mutations/message/editMessage.js b/api/mutations/message/editMessage.js new file mode 100644 index 0000000000..51a0b439b9 --- /dev/null +++ b/api/mutations/message/editMessage.js @@ -0,0 +1,130 @@ +// @flow +import type { GraphQLContext } from '../../'; +import { convertToRaw } from 'draft-js'; +import { stateFromMarkdown } from 'draft-js-import-markdown'; +import UserError from '../../utils/UserError'; +import { + getMessage, + editMessage, + userHasMessagesInThread, + getMessages, +} from '../../models/message'; +import { setThreadLastActive } from '../../models/thread'; +import { deleteParticipantInThread } from '../../models/usersThreads'; +import { getUserPermissionsInChannel } from '../../models/usersChannels'; +import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; +import { events } from 'shared/analytics'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { trackQueue } from 'shared/bull/queues'; +import { validateRawContentState } from '../../utils/validate-draft-js-input'; + +type Args = { + input: { + id: string, + messageType?: 'draftjs' | 'text' | 'media', + content: { + body: string, + }, + }, +}; + +export default requireAuth(async (_: any, args: Args, ctx: GraphQLContext) => { + const { + input: { id, content, messageType }, + } = args; + const { user, loaders } = ctx; + + const message = await getMessage(id); + + if (!message) { + trackQueue.add({ + userId: user.id, + event: events.MESSAGE_EDITED_FAILED, + properties: { + reason: 'message not found', + }, + }); + return new UserError('This message does not exist.'); + } + + let body = content.body; + if (messageType === 'text') { + body = JSON.stringify( + convertToRaw( + stateFromMarkdown(body, { + parserOptions: { + breaks: true, + }, + }) + ) + ); + messageType === 'draftjs'; + } + + const eventFailed = + message.threadType === 'story' + ? events.MESSAGE_EDITED_FAILED + : events.DIRECT_MESSAGE_EDITED_FAILED; + + if (messageType === 'draftjs') { + let parsed; + try { + parsed = JSON.parse(body); + } catch (err) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'invalid draftjs data', + message, + }, + }); + + return new UserError( + 'Please provide serialized raw DraftJS content state as content.body' + ); + } + if (!validateRawContentState(body)) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + properties: { + reason: 'invalid draftjs data', + message, + }, + }); + + throw new UserError( + 'Please provide serialized raw DraftJS content state as content.body' + ); + } + } + + if (body === message.content.body) { + return message; + } + + if (message.senderId !== user.id) { + trackQueue.add({ + userId: user.id, + event: eventFailed, + context: { messageId: id }, + properties: { + reason: 'message not sent by user', + }, + }); + + return new UserError('You can only edit your own messages.'); + } + + return editMessage( + { + ...args.input, + content: { + body, + }, + messageType, + }, + user.id + ); +}); diff --git a/api/mutations/message/index.js b/api/mutations/message/index.js index 8886a7a4b9..ee4f4961ef 100644 --- a/api/mutations/message/index.js +++ b/api/mutations/message/index.js @@ -1,10 +1,12 @@ // @flow import addMessage from './addMessage'; import deleteMessage from './deleteMessage'; +import editMessage from './editMessage'; module.exports = { Mutation: { addMessage, deleteMessage, + editMessage, }, }; diff --git a/api/mutations/notification/index.js b/api/mutations/notification/index.js index a61d3acd3c..f1749ec20e 100644 --- a/api/mutations/notification/index.js +++ b/api/mutations/notification/index.js @@ -1,13 +1,11 @@ // @flow import markAllNotificationsSeen from './markAllNotificationsSeen'; -import markAllNotificationsRead from './markAllNotificationsRead'; import markSingleNotificationSeen from './markSingleNotificationSeen'; import markDirectMessageNotificationsSeen from './markDirectMessageNotificationsSeen'; module.exports = { Mutation: { markAllNotificationsSeen, - markAllNotificationsRead, markDirectMessageNotificationsSeen, markSingleNotificationSeen, }, diff --git a/api/mutations/notification/markAllNotificationsRead.js b/api/mutations/notification/markAllNotificationsRead.js deleted file mode 100644 index ea78e3b105..0000000000 --- a/api/mutations/notification/markAllNotificationsRead.js +++ /dev/null @@ -1,10 +0,0 @@ -// @flow -import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -import { markAllNotificationsRead } from '../../models/usersNotifications'; - -export default (_: any, __: any, { user }: GraphQLContext) => { - if (!user) - return new UserError('You must be logged in to view notifications'); - return markAllNotificationsRead(user.id); -}; diff --git a/api/mutations/notification/markAllNotificationsSeen.js b/api/mutations/notification/markAllNotificationsSeen.js index 1f34943a4a..1b58f87606 100644 --- a/api/mutations/notification/markAllNotificationsSeen.js +++ b/api/mutations/notification/markAllNotificationsSeen.js @@ -1,10 +1,10 @@ // @flow import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -import { markAllNotificationsSeen } from '../../models/usersNotifications'; +import { markAllNotificationsSeen } from 'shared/db/queries/usersNotifications'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; -export default (_: any, __: any, { user }: GraphQLContext) => { - if (!user) - return new UserError('You must be logged in to view notifications'); - return markAllNotificationsSeen(user.id); -}; +export default requireAuth( + async (_: any, __: any, { user }: GraphQLContext) => { + return await markAllNotificationsSeen(user.id); + } +); diff --git a/api/mutations/notification/markDirectMessageNotificationsSeen.js b/api/mutations/notification/markDirectMessageNotificationsSeen.js index f29a1e48ed..74c124751d 100644 --- a/api/mutations/notification/markDirectMessageNotificationsSeen.js +++ b/api/mutations/notification/markDirectMessageNotificationsSeen.js @@ -1,10 +1,10 @@ // @flow import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -import { markDirectMessageNotificationsSeen } from '../../models/usersNotifications'; +import { markDirectMessageNotificationsSeen } from 'shared/db/queries/usersNotifications'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; -export default (_: any, __: any, { user }: GraphQLContext) => { - if (!user) - return new UserError('You must be logged in to view notifications'); - return markDirectMessageNotificationsSeen(user.id); -}; +export default requireAuth( + async (_: any, __: any, { user }: GraphQLContext) => { + return markDirectMessageNotificationsSeen(user.id); + } +); diff --git a/api/mutations/notification/markSingleNotificationSeen.js b/api/mutations/notification/markSingleNotificationSeen.js index eb1e76fded..ced0a1d011 100644 --- a/api/mutations/notification/markSingleNotificationSeen.js +++ b/api/mutations/notification/markSingleNotificationSeen.js @@ -1,10 +1,10 @@ // @flow import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -import { markSingleNotificationSeen } from '../../models/usersNotifications'; +import { markSingleNotificationSeen } from 'shared/db/queries/usersNotifications'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; -export default (_: any, { id }: { id: string }, { user }: GraphQLContext) => { - if (!user) - return new UserError('You must be logged in to view notifications'); - return markSingleNotificationSeen(id, user.id); -}; +export default requireAuth( + async (_: any, { id }: { id: string }, { user }: GraphQLContext) => { + return await markSingleNotificationSeen(id, user.id); + } +); diff --git a/api/mutations/reaction/toggleReaction.js b/api/mutations/reaction/toggleReaction.js index 5eb4dfa7df..55bb182a35 100644 --- a/api/mutations/reaction/toggleReaction.js +++ b/api/mutations/reaction/toggleReaction.js @@ -1,23 +1,15 @@ // @flow import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; import { toggleReaction } from '../../models/reaction'; import type { ReactionInput } from '../../models/reaction'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; -type ToggleReactionType = { +type Input = { reaction: ReactionInput, }; -export default ( - _: any, - { reaction }: ToggleReactionType, - { user }: GraphQLContext -) => { - const currentUser = user; - // user must be authed to send a message - if (!currentUser) - return new UserError('You must be signed in to add a reaction.'); - - // all checks passed - return toggleReaction(reaction, currentUser.id); -}; +export default requireAuth( + async (_: any, { reaction }: Input, { user, loaders }: GraphQLContext) => { + return await toggleReaction(reaction, user.id); + } +); diff --git a/api/mutations/recurringPayment/downgradeCommunity.js b/api/mutations/recurringPayment/downgradeCommunity.js deleted file mode 100644 index 25bf5c1050..0000000000 --- a/api/mutations/recurringPayment/downgradeCommunity.js +++ /dev/null @@ -1,112 +0,0 @@ -// @flow -require('now-env'); -import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -const STRIPE_TOKEN = process.env.STRIPE_TOKEN; -const stripe = require('stripe')(STRIPE_TOKEN); - -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; -import { - getUserRecurringPayments, - updateRecurringPayment, -} from '../../models/recurringPayment'; -import { deleteStripeSubscription } from './utils'; -import { getCommunities } from '../../models/community'; - -type DowngradeCommunityInput = { - input: { - id: string, - }, -}; - -export default ( - _: any, - { input }: DowngradeCommunityInput, - { user }: GraphQLContext -) => { - const currentUser = user; - - // user must be authed to create a community - if (!currentUser) { - return new UserError('You must be signed in to continue.'); - } - - // one async function to handle all downgrade logic - const handleCommunityDowngrade = async () => { - // get the current user's permissions in the community being downgraded - const permissions = await getUserPermissionsInCommunity( - input.id, - currentUser.id - ); - - // get any recurringPayments records from the database matching this community - const rPayments = await getUserRecurringPayments(currentUser.id); - - // if the current user doesn't own the community, break out - if (!permissions.isOwner) { - return new UserError( - "You don't have permission to downgrade this community." - ); - } - - // only evaluate community subscriptions, and not pro subscriptions - const proSubscriptions = - // if payments were found, make sure to select the first community-standard plan to update, otherwise return null and we will be creating a new payment - rPayments && - rPayments - .filter(pmt => pmt.communityId === input.id) - .filter(pmt => pmt.planId === 'community-standard'); - - const recurringPaymentToEvaluate = - proSubscriptions && proSubscriptions.length > 0 - ? proSubscriptions[0] - : null; - - // if no recurringPayments exist on the 'community-standard' plan, there is nothing to downgrade - if (!recurringPaymentToEvaluate) { - return new UserError( - "We couldn't find a record of this community being upgraded." - ); - } - - const customerId = recurringPaymentToEvaluate.customerId; - const customer = await stripe.customers.retrieve(customerId); - - // if no customer could be found on stripe, we have no way to delete the right subscription - if (!customer || !customer.id) { - return new UserError("We couln't find a record of this subscription."); - } - - // a customer record from stripe returns all of their subscriptions - we need to ensure we are only deleting the subscription for their community upgrade - const subscriptionId = customer.subscriptions.data.filter( - pmt => pmt.plan.id === 'community-standard' - )[0].id; - - // delete the subscription - const stripeData = await deleteStripeSubscription(subscriptionId); - - // update the recurringPayment record in the database to reflect the new canceled status - return await updateRecurringPayment({ - id: recurringPaymentToEvaluate.id, - stripeData: { - ...stripeData, - sourceBrand: customer.sources.data[0].brand, - sourceLast4: customer.sources.data[0].last4, - }, - }); - }; - - // handle the entire downgrade flow - return ( - handleCommunityDowngrade() - // return the community to update the client side cache for isPro - .then(() => getCommunities([input.id])) - .then(communities => communities[0]) - .catch(err => { - console.log('Error downgrading a community: ', err.message); - return new UserError( - "We weren't able to downgrade your community: " + err.message - ); - }) - ); -}; diff --git a/api/mutations/recurringPayment/downgradeFromPro.js b/api/mutations/recurringPayment/downgradeFromPro.js deleted file mode 100644 index 0fe90ba776..0000000000 --- a/api/mutations/recurringPayment/downgradeFromPro.js +++ /dev/null @@ -1,76 +0,0 @@ -// @flow -import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -import { - getUserRecurringPayments, - updateRecurringPayment, -} from '../../models/recurringPayment'; -import { getStripeCustomer, deleteStripeSubscription } from './utils'; -import { getUserById } from '../../models/user'; - -export default (_: any, __: any, { user }: GraphQLContext) => { - const currentUser = user; - - // user must be authed to create a community - if (!currentUser) { - return new UserError( - 'You must be signed in to cancel your Pro subscription.' - ); - } - - const handleProDowngrade = async () => { - // get any recurring payments for the current user - const rPayments = await getUserRecurringPayments(currentUser.id); - - // only evaluate pro subscriptions, and not community subscriptions - const proSubscriptions = - // if payments were found, make sure to select the first community-standard plan to update, otherwise return null and we will be creating a new payment - rPayments && rPayments.filter(pmt => pmt.planId === 'beta-pro'); - - const recurringPaymentToEvaluate = - proSubscriptions && proSubscriptions.length > 0 - ? proSubscriptions[0] - : null; - - // if the result is null, we don't have a record of the recurringPayment - if (!recurringPaymentToEvaluate) { - return new UserError("We couldn't find a record of a Pro subscription."); - } - - const customerId = recurringPaymentToEvaluate.customerId; - const customer = await getStripeCustomer(customerId); - - // if we can't find a customer record on stripe, we will have nobody to downgrade - if (!customer || !customer.id) { - return new UserError("We couldn't find a record of this subscription."); - } - - // a customer record from stripe returns all of their subscriptions - we need to ensure we are only deleting the subscription for Pro - const subscriptionId = customer.subscriptions.data.filter( - pmt => pmt.plan.id === 'beta-pro' - )[0].id; - - // delete the subscription on stripe - const stripeData = await deleteStripeSubscription(subscriptionId); - - // update the recurringPayment record in the database - return await updateRecurringPayment({ - id: recurringPaymentToEvaluate.id, - stripeData: { - ...stripeData, - sourceBrand: customer.sources.data[0].brand, - sourceLast4: customer.sources.data[0].last4, - }, - }); - }; - - // handle all downgrade logic and then return the user record to the client to bust the isPro cache - return handleProDowngrade() - .then(() => getUserById(currentUser.id)) - .catch(err => { - console.log('Error downgrading from Pro: ', err.message); - return new UserError( - "We weren't able to cancel your subsciption: " + err.message - ); - }); -}; diff --git a/api/mutations/recurringPayment/index.js b/api/mutations/recurringPayment/index.js deleted file mode 100644 index 2e16e544d7..0000000000 --- a/api/mutations/recurringPayment/index.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import upgradeToPro from './upgradeToPro'; -import downgradeFromPro from './downgradeFromPro'; -import upgradeCommunity from './upgradeCommunity'; -import downgradeCommunity from './downgradeCommunity'; - -module.exports = { - Mutation: { - upgradeToPro, - downgradeFromPro, - upgradeCommunity, - downgradeCommunity, - }, -}; diff --git a/api/mutations/recurringPayment/upgradeCommunity.js b/api/mutations/recurringPayment/upgradeCommunity.js deleted file mode 100644 index 43ecde2475..0000000000 --- a/api/mutations/recurringPayment/upgradeCommunity.js +++ /dev/null @@ -1,141 +0,0 @@ -// @flow -import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -import { - getUserRecurringPayments, - updateRecurringPayment, - createRecurringPayment, -} from '../../models/recurringPayment'; -import { - getStripeCustomer, - createStripeCustomer, - updateStripeCustomer, - createStripeSubscription, -} from './utils'; -import { getCommunities } from '../../models/community'; -import { getMemberCount } from '../../models/community'; - -type UpgradeCommunityInput = { - input: { - plan: string, - token: string, - communityId: string, - }, -}; - -export default ( - _: any, - args: UpgradeCommunityInput, - { user }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser.email) { - return new UserError( - 'Please add an email address in your settings before upgrading to Pro' - ); - } - - // user must be authed to create a community - if (!currentUser) { - return new UserError('You must be signed in to upgrade this community.'); - } - - // gql should have caught this, but just in case not token or plan - // was specified, return an error - if (!args.input.plan || !args.input.token) { - return new UserError( - 'Something went wrong upgrading your community. Please try again.' - ); - } - - const handleCommunityUpgrade = async () => { - // parse the token string into an object - let token = JSON.parse(args.input.token); - const { input: { plan, communityId } } = args; - - // get the number of members in a community to determine the quantity of subscriptions to create, as well as retreive any existing recurringPayments records for this community to determine if the user is re-upgrading - const memberCount = await getMemberCount(communityId); - const rPayments = await getUserRecurringPayments(currentUser.id); - - // only evaluate community subscriptions, and not pro subscriptions - const proSubscriptions = - // if payments were found, make sure to select the first community-standard plan to update, otherwise return null and we will be creating a new payment - rPayments && - rPayments - .filter(pmt => pmt.communityId === communityId) - .filter(pmt => pmt.planId === 'community-standard'); - - const recurringPaymentToEvaluate = - proSubscriptions && proSubscriptions.length > 0 - ? proSubscriptions[0] - : null; - - // we still want to know globally if a user has a customerId already so that we avoid create duplicate customers in Stripe - const hasCustomerId = (await rPayments) && rPayments.length > 0; - - // if the result is null, the user has never upgraded this community which means we need to create a stripe customer and then create the recurringPayment record in the database - if (!recurringPaymentToEvaluate && currentUser.email) { - const customer = hasCustomerId - ? await getStripeCustomer(rPayments[0].customerId) - : await createStripeCustomer(currentUser.email, token.id); - - // create the subscription in stripe - const subscription = await createStripeSubscription( - customer.id, - plan, - Math.ceil(memberCount / 1000) // round up from the nearest 1,000 members - ); - - // create a recurringPayment record in the database - return await createRecurringPayment({ - communityId, - userId: currentUser.id, - stripeData: { - ...subscription, - sourceBrand: customer.sources.data[0].brand, - sourceLast4: customer.sources.data[0].last4, - }, - }); - } - - if (recurringPaymentToEvaluate && currentUser.email) { - // if a result is returned, lets make sure that they don't - // already have an active recurringPayment - if (recurringPaymentToEvaluate.status === 'active') { - return new UserError('This community is already upgraded - thank you!'); - } - - const customer = await updateStripeCustomer( - recurringPaymentToEvaluate.customerId, - currentUser.email, - token.id - ); - - const subscription = await createStripeSubscription( - customer.id, - plan, - Math.ceil(memberCount / 1000) // round up from the nearest 1,000 members - ); - - return await updateRecurringPayment({ - id: recurringPaymentToEvaluate.id, - stripeData: { - ...subscription, - sourceBrand: customer.sources.data[0].brand, - sourceLast4: customer.sources.data[0].last4, - }, - }); - } - }; - - return handleCommunityUpgrade() - .then(() => getCommunities([args.input.communityId])) - .then(communities => communities[0]) - .catch(err => { - console.log('Error upgrading a community: ', err.message); - return new UserError( - "We weren't able to upgrade your community: " + err.message - ); - }); -}; diff --git a/api/mutations/recurringPayment/upgradeToPro.js b/api/mutations/recurringPayment/upgradeToPro.js deleted file mode 100644 index 23ba92edc7..0000000000 --- a/api/mutations/recurringPayment/upgradeToPro.js +++ /dev/null @@ -1,124 +0,0 @@ -// @flow -import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -import { - getUserRecurringPayments, - updateRecurringPayment, - createRecurringPayment, -} from '../../models/recurringPayment'; -import { - getStripeCustomer, - createStripeCustomer, - updateStripeCustomer, - createStripeSubscription, -} from './utils'; -import { getUserById } from '../../models/user'; - -type UpgradeToProInput = { - input: { - plan: string, - token: string, - }, -}; - -export default (_: any, args: UpgradeToProInput, { user }: GraphQLContext) => { - const currentUser = user; - - // user must be authed to create a community - if (!currentUser) { - return new UserError('You must be signed in to upgrade to Pro.'); - } - - if (!currentUser.email) { - return new UserError( - 'Please add an email address in your settings before upgrading to Pro' - ); - } - - // gql should have caught this, but just in case not token or plan - // was specified, return an error - if (!args.input.plan || !args.input.token) { - return new UserError( - 'Something went wrong upgrading you to Pro. Please try again.' - ); - } - - const handleProUpgrade = async () => { - // parse the token string into an object - let token = JSON.parse(args.input.token); - const { input: { plan } } = args; - - // get any recurring payments for the current user - const rPayments = await getUserRecurringPayments(currentUser.id); - - // only evaluate pro subscriptions, and not community subscriptions - const proSubscriptions = - // if payments were found, make sure to select the first community-standard plan to update, otherwise return null and we will be creating a new payment - rPayments && rPayments.filter(pmt => pmt.planId === 'beta-pro'); - - const recurringPaymentToEvaluate = - proSubscriptions && proSubscriptions.length > 0 - ? proSubscriptions[0] - : null; - - // we still want to know globally if a user has a customerId already so that we avoid create duplicate customers in Stripe - const hasCustomerId = rPayments && rPayments.length > 0; - - // if no recurringPaymentToEvaluate is found, it means the user has never been pro and we can go ahead and create a new subscription - if (!recurringPaymentToEvaluate && currentUser.email) { - const customer = hasCustomerId - ? await getStripeCustomer(rPayments[0].customerId) - : await createStripeCustomer(currentUser.email, token.id); - - const stripeData = await createStripeSubscription(customer.id, plan, 1); - - return await createRecurringPayment({ - userId: currentUser.id, - stripeData: { - ...stripeData, - sourceBrand: customer.sources.data[0].brand, - sourceLast4: customer.sources.data[0].last4, - }, - }); - } - - if ( - recurringPaymentToEvaluate && - recurringPaymentToEvaluate.status === 'active' - ) { - return new UserError("You're already a Pro member - thank you!"); - } - - if (recurringPaymentToEvaluate && currentUser.email) { - const customer = await updateStripeCustomer( - recurringPaymentToEvaluate.customerId, - currentUser.email, - token.id - ); - - const subscription = await createStripeSubscription(customer.id, plan, 1); - - return await updateRecurringPayment({ - id: recurringPaymentToEvaluate.id, - stripeData: { - ...subscription, - sourceBrand: customer.sources.data[0].brand, - sourceLast4: customer.sources.data[0].last4, - }, - }); - } - }; - - // handle pro upgrade logic - return ( - handleProUpgrade() - // return the user record to update the cilent side cache for isPro - .then(() => getUserById(currentUser.id)) - .catch(err => { - console.log('Error upgrading to Pro: ', err.message); - return new UserError( - "We weren't able to upgrade you to Pro: " + err.message - ); - }) - ); -}; diff --git a/api/mutations/recurringPayment/utils.js b/api/mutations/recurringPayment/utils.js deleted file mode 100644 index 082164d3a6..0000000000 --- a/api/mutations/recurringPayment/utils.js +++ /dev/null @@ -1,107 +0,0 @@ -// @flow -require('now-env'); -import UserError from '../../utils/UserError'; -const STRIPE_TOKEN = process.env.STRIPE_TOKEN; -const stripe = require('stripe')(STRIPE_TOKEN); - -type Err = { - type: string, -}; - -export const parseStripeErrors = (err: Err) => { - switch (err.type) { - case 'StripeCardError': - // A declined card error - return new UserError(err); // => e.g. "Your card's expiration year is invalid." - case 'RateLimitError': - // Too many requests made to the API too quickly - return new UserError( - 'Could not upgrade to Pro at this time, try again later: 1' - ); - case 'StripeInvalidRequestError': - // Invalid parameters were supplied to Stripe's API - return new UserError( - 'Could not upgrade to Pro at this time, try again later: 2' - ); - case 'StripeAPIError': - // An error occurred internally with Stripe's API - return new UserError( - 'Something went wrong at Stripe, try again later: 3' - ); - case 'StripeConnectionError': - // Some kind of error occurred during the HTTPS communication - return new UserError( - 'Something went wrong at Stripe, try again later: 4' - ); - case 'StripeAuthenticationError': - // You probably used an incorrect API key - return new UserError( - 'Something went wrong at Stripe, try again later: 5' - ); - default: - // Handle any other types of unexpected errors - return new UserError('Something went wrong, try again later: 6'); - } -}; - -export const getStripeCustomer = (customerId: string) => { - try { - return stripe.customers.retrieve(customerId); - } catch (err) { - return console.log(err) || err; - } -}; - -export const createStripeCustomer = (email: string, source: string) => { - try { - return stripe.customers.create({ - email, - source, - }); - } catch (err) { - return console.log(err) || err; - } -}; - -export const updateStripeCustomer = ( - customer: string, - email: string, - source: string -) => { - try { - return stripe.customers.update(customer, { - email, - source, - }); - } catch (err) { - return console.log(err) || err; - } -}; - -export const createStripeSubscription = ( - customer: string, - plan: string, - quantity: number -) => { - try { - return stripe.subscriptions.create({ - customer, - items: [ - { - plan, - quantity: quantity ? quantity : 1, // if no quantity is provided, default to one - }, - ], - }); - } catch (err) { - return console.log(err) || err; - } -}; - -export const deleteStripeSubscription = (subscription: string) => { - try { - return stripe.subscriptions.del(subscription); - } catch (err) { - return console.log(err) || err; - } -}; diff --git a/api/mutations/thread/addThreadReaction.js b/api/mutations/thread/addThreadReaction.js new file mode 100644 index 0000000000..d0249768f0 --- /dev/null +++ b/api/mutations/thread/addThreadReaction.js @@ -0,0 +1,36 @@ +// @flow +import type { GraphQLContext } from '../../'; +import { addThreadReaction } from '../../models/threadReaction'; +import { getThreadById } from '../../models/thread'; +import { + isAuthedResolver as requireAuth, + canViewThread, +} from '../../utils/permissions'; +import { calculateThreadScoreQueue } from 'shared/bull/queues'; +import UserError from '../../utils/UserError'; + +type Input = { + input: { + threadId: string, + type: 'like', + }, +}; + +export default requireAuth( + async (_: any, args: Input, { user, loaders }: GraphQLContext) => { + if (await canViewThread(user.id, args.input.threadId, loaders)) { + return await addThreadReaction(args.input, user.id).then(() => { + calculateThreadScoreQueue.add( + { threadId: args.input.threadId }, + { jobId: args.input.threadId } + ); + + return getThreadById(args.input.threadId); + }); + } + + return new UserError( + 'You don’t have permission to view this conversation.' + ); + } +); diff --git a/api/mutations/thread/deleteThread.js b/api/mutations/thread/deleteThread.js index 03777654ba..baf98f36d6 100644 --- a/api/mutations/thread/deleteThread.js +++ b/api/mutations/thread/deleteThread.js @@ -5,20 +5,17 @@ import { processReputationEventQueue } from 'shared/bull/queues'; import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; import { getUserPermissionsInChannel } from '../../models/usersChannels'; import { deleteThread, getThreads } from '../../models/thread'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -export default async ( - _: any, - { threadId }: { threadId: string }, - { user }: GraphQLContext -) => { - const currentUser = user; +type Input = { + threadId: string, +}; - // user must be authed to edit a thread - if (!currentUser) { - return new UserError( - 'You must be signed in to make changes to this thread.' - ); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user } = ctx; + const { threadId } = args; // get the thread being locked const threads = await getThreads([threadId]); @@ -27,6 +24,15 @@ export default async ( // if the thread doesn't exist if (!threadToEvaluate || threadToEvaluate.deletedAt) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_DELETED_FAILED, + context: { threadId }, + properties: { + reason: 'thread does not exist', + }, + }); + return new UserError("This thread doesn't exist"); } @@ -35,8 +41,8 @@ export default async ( currentUserChannelPermissions, currentUserCommunityPermissions, ] = await Promise.all([ - getUserPermissionsInChannel(threadToEvaluate.channelId, currentUser.id), - getUserPermissionsInCommunity(threadToEvaluate.communityId, currentUser.id), + getUserPermissionsInChannel(threadToEvaluate.channelId, user.id), + getUserPermissionsInCommunity(threadToEvaluate.communityId, user.id), ]); // if the user owns the community or the channel, or they are the original creator, they can delete the thread @@ -45,22 +51,31 @@ export default async ( currentUserChannelPermissions.isModerator || currentUserCommunityPermissions.isOwner || currentUserCommunityPermissions.isModerator || - threadToEvaluate.creatorId === currentUser.id + threadToEvaluate.creatorId === user.id ) { // if the current user doing the deleting does not match the thread creator, we can assume that this deletion is happening as a moderation event. In this case we grant reputation to the moderator - if (currentUser.id !== threadToEvaluate.creatorId) { + if (user.id !== threadToEvaluate.creatorId) { processReputationEventQueue.add({ - userId: currentUser.id, + userId: user.id, type: 'thread deleted by moderation', entityId: threadToEvaluate.communityId, }); } - return deleteThread(threadId); + return await deleteThread(threadId, user.id); } // if the user is not a channel or community owner, the thread can't be locked + trackQueue.add({ + userId: user.id, + event: events.THREAD_DELETED_FAILED, + context: { threadId }, + properties: { + reason: 'no permission', + }, + }); + return new UserError( "You don't have permission to make changes to this thread." ); -}; +}); diff --git a/api/mutations/thread/editThread.js b/api/mutations/thread/editThread.js index 773b7a4027..089a34e18d 100644 --- a/api/mutations/thread/editThread.js +++ b/api/mutations/thread/editThread.js @@ -2,27 +2,38 @@ import type { GraphQLContext } from '../../'; import type { EditThreadInput } from '../../models/thread'; import UserError from '../../utils/UserError'; -import { uploadImage } from '../../utils/s3'; +import { uploadImage } from '../../utils/file-storage'; import { getThreads, editThread } from '../../models/thread'; import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; import { getUserPermissionsInChannel } from '../../models/usersChannels'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import { + LEGACY_PREFIX, + hasLegacyPrefix, + stripLegacyPrefix, +} from 'shared/imgix'; + +type Input = { + input: EditThreadInput, +}; -export default async ( - _: any, - { input }: { input: EditThreadInput }, - { user }: GraphQLContext -) => { - const currentUser = user; - - // user must be authed to edit a thread - if (!currentUser) { - return new UserError( - 'You must be signed in to make changes to this thread.' - ); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { input } = args; + const { user } = ctx; if (input.type === 'SLATE') { - throw new UserError( + trackQueue.add({ + userId: user.id, + event: events.THREAD_EDITED_FAILED, + context: { threadId: input.threadId }, + properties: { + reason: 'slate type', + }, + }); + + return new UserError( "You're on an old version of Spectrum, please refresh your browser." ); } @@ -34,77 +45,150 @@ export default async ( // if the thread doesn't exist if (!threads || !threadToEvaluate) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_EDITED_FAILED, + context: { threadId: input.threadId }, + properties: { + reason: 'thread does not exist', + }, + }); + return new UserError("This thread doesn't exist"); } const [communityPermissions, channelPermissions] = await Promise.all([ - getUserPermissionsInCommunity(threadToEvaluate.communityId, currentUser.id), - getUserPermissionsInChannel(threadToEvaluate.channelId, currentUser.id), + getUserPermissionsInCommunity(threadToEvaluate.communityId, user.id), + getUserPermissionsInChannel(threadToEvaluate.channelId, user.id), ]); // only the thread creator can edit the thread // also prevent deletion if the user was blocked if ( - threadToEvaluate.creatorId !== currentUser.id || + threadToEvaluate.creatorId !== user.id || channelPermissions.isBlocked || communityPermissions.isBlocked ) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_EDITED_FAILED, + context: { threadId: input.threadId }, + properties: { + reason: 'no permission', + }, + }); + return new UserError( "You don't have permission to make changes to this thread." ); } - let attachments = []; - // if the thread came in with attachments - if (input.attachments) { - // iterate through them and construct a new attachment object - input.attachments.map(attachment => { - attachments.push({ - attachmentType: attachment.attachmentType, - data: JSON.parse(attachment.data), - }); + /* + When threads are sent to the client, all image urls are signed and proxied + via imgix. If a user edits the thread, we have to restore all image upload + urls back to their previous state so that we don't accidentally store + an encoded, signed, and expired image url back into the db + */ + const initialBody = input.content.body && JSON.parse(input.content.body); + + if (initialBody) { + const imageKeys = Object.keys(initialBody.entityMap).filter( + key => initialBody.entityMap[key].type.toLowerCase() === 'image' + ); + + const stripQueryParams = (str: string): string => { + if ( + str.indexOf('https://spectrum.imgix.net') < 0 && + str.indexOf('https://spectrum-proxy.imgix.net') < 0 + ) { + return str; + } + + const split = str.split('?'); + // if no query params existed, we can just return the original image + if (split.length < 2) return str; + + // otherwise the image path is everything before the first ? in the url + const imagePath = split[0]; + // images are encoded during the signing process (shared/imgix/index.js) + // so they must be decoded here for accurate storage in the db + const decoded = decodeURIComponent(imagePath); + // we remove https://spectrum.imgix.net from the path as well so that the + // path represents the generic location of the file in s3 and decouples + // usage with imgix + const processed = hasLegacyPrefix(decoded) + ? stripLegacyPrefix(decoded) + : decoded; + return processed; + }; + + imageKeys.forEach((key, index) => { + if (!initialBody.entityMap[key[index]]) return; + + const { src } = initialBody.entityMap[imageKeys[index]].data; + initialBody.entityMap[imageKeys[index]].data.src = stripQueryParams(src); }); } const newInput = Object.assign({}, input, { ...input, content: { - ...input.content, + body: JSON.stringify(initialBody), title: input.content.title.trim(), }, - attachments, }); // $FlowIssue - const editedThread = await editThread(newInput); + const editedThread = await editThread(newInput, user.id); if (!input.filesToUpload) return editedThread; - const urls = await Promise.all( - input.filesToUpload.map(file => - uploadImage(file, 'threads', editedThread.id) - ) - ); + let urls; + try { + urls = await Promise.all( + input.filesToUpload.map(file => + uploadImage(file, 'threads', editedThread.id) + ) + ); + } catch (err) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_EDITED_FAILED, + context: { threadId: input.threadId }, + properties: { + reason: 'images failed to upload', + }, + }); + + return new UserError(err.message); + } if (!urls || urls.length === 0) return editedThread; // Replace the local image srcs with the remote image src const body = editedThread.content.body && JSON.parse(editedThread.content.body); + const imageKeys = Object.keys(body.entityMap).filter( - key => body.entityMap[key].type === 'image' + key => + body.entityMap[key].type.toLowerCase() === 'image' && + body.entityMap[key].data.src.startsWith('blob:') ); + urls.forEach((url, index) => { if (!body.entityMap[imageKeys[index]]) return; body.entityMap[imageKeys[index]].data.src = url; }); // Update the thread with the new links - return editThread({ - threadId: editedThread.id, - content: { - ...editedThread.content, - body: JSON.stringify(body), + return editThread( + { + threadId: editedThread.id, + content: { + ...editedThread.content, + body: JSON.stringify(body), + }, }, - }); -}; + user.id + ); +}); diff --git a/api/mutations/thread/index.js b/api/mutations/thread/index.js index df89c21a9d..2e77ea9398 100644 --- a/api/mutations/thread/index.js +++ b/api/mutations/thread/index.js @@ -5,6 +5,8 @@ import deleteThread from './deleteThread'; import setThreadLock from './setThreadLock'; import toggleThreadNotifications from './toggleThreadNotifications'; import moveThread from './moveThread'; +import addThreadReaction from './addThreadReaction'; +import removeThreadReaction from './removeThreadReaction'; module.exports = { Mutation: { @@ -14,5 +16,7 @@ module.exports = { setThreadLock, toggleThreadNotifications, moveThread, + addThreadReaction, + removeThreadReaction, }, }; diff --git a/api/mutations/thread/moveThread.js b/api/mutations/thread/moveThread.js index 5c92f1323a..d28bb9aa9f 100644 --- a/api/mutations/thread/moveThread.js +++ b/api/mutations/thread/moveThread.js @@ -4,43 +4,97 @@ import UserError from '../../utils/UserError'; import { getThread, moveThread } from '../../models/thread'; import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; import { getChannels } from '../../models/channel'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -export default async ( - _: any, - { threadId, channelId }: { threadId: string, channelId: string }, - { user }: GraphQLContext -) => { - const currentUser = user; - if (!currentUser) - throw new UserError('You must be signed in to move a thread.'); +type Input = { + threadId: string, + channelId: string, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user } = ctx; + const { threadId, channelId } = args; const thread = await getThread(threadId); - if (!thread) throw new UserError('Cannot move a non-existant thread.'); + if (!thread) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_MOVED_FAILED, + context: { threadId }, + properties: { + reason: 'thread not found', + }, + }); + + return new UserError('Cannot move a non-existant thread.'); + } const { isOwner, isModerator, isBlocked, - } = await getUserPermissionsInCommunity(thread.communityId, currentUser.id); + } = await getUserPermissionsInCommunity(thread.communityId, user.id); if (isBlocked) { - throw new UserError("You don't have permission to post in that channel."); + trackQueue.add({ + userId: user.id, + event: events.THREAD_MOVED_FAILED, + context: { threadId }, + properties: { + reason: 'no permission blocked', + }, + }); + + return new UserError("You don't have permission to post in that channel."); } - if (thread.creatorId !== currentUser.id && (!isOwner && !isModerator)) - throw new UserError( + if (thread.creatorId !== user.id && (!isOwner && !isModerator)) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_MOVED_FAILED, + context: { threadId }, + properties: { + reason: 'no permission', + }, + }); + + return new UserError( 'You have to be a moderator or owner of the community to move a thread.' ); + } const [newChannel] = await getChannels([channelId]); - if (newChannel.communityId !== thread.communityId) - throw new UserError('You can only move threads within the same community.'); + if (newChannel.communityId !== thread.communityId) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_MOVED_FAILED, + context: { threadId }, + properties: { + reason: 'moving between communities', + }, + }); + + return new UserError( + 'You can only move threads within the same community.' + ); + } - return moveThread(threadId, channelId).then(res => { + return moveThread(threadId, channelId, user.id).then(res => { if (res) return res; - throw new UserError( + trackQueue.add({ + userId: user.id, + event: events.THREAD_MOVED_FAILED, + context: { threadId }, + properties: { + reason: 'unknown error', + }, + }); + + return new UserError( 'Oops, something went wrong with moving the thread. Please try again!' ); }); -}; +}); diff --git a/api/mutations/thread/publishThread.js b/api/mutations/thread/publishThread.js index 63d5cc06d7..afd5a907ab 100644 --- a/api/mutations/thread/publishThread.js +++ b/api/mutations/thread/publishThread.js @@ -1,203 +1,392 @@ // @flow +const debug = require('debug')('api:mutations:thread:publish-thread'); +import stringSimilarity from 'string-similarity'; +import { convertToRaw } from 'draft-js'; +import { stateFromMarkdown } from 'draft-js-import-markdown'; import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; -import { uploadImage } from '../../utils/s3'; -import { getUserPermissionsInChannel } from '../../models/usersChannels'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; -import { getCommunityRecurringPayments } from '../../models/recurringPayment'; -import { getChannelById } from '../../models/channel'; -import { getCommunityById } from '../../models/community'; -import { publishThread, editThread } from '../../models/thread'; +import { uploadImage } from '../../utils/file-storage'; +import { + publishThread, + editThread, + getThreadsByUserAsSpamCheck, +} from '../../models/thread'; import { createParticipantInThread } from '../../models/usersThreads'; -import { StripeUtil } from 'shared/stripe/utils'; -import type { FileUpload } from 'shared/types'; -import { PRIVATE_CHANNEL, FREE_PRIVATE_CHANNEL } from 'pluto/queues/constants'; +import type { FileUpload, DBThread } from 'shared/types'; +import { toPlainText, toState } from 'shared/draft-utils'; +import { + processReputationEventQueue, + sendThreadNotificationQueue, + _adminProcessToxicThreadQueue, + _adminProcessUserSpammingThreadsQueue, +} from 'shared/bull/queues'; +import getPerspectiveScore from 'athena/queues/moderationEvents/perspective'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; -type Attachment = { - attachmentType: string, - data: string, -}; +const threadBodyToPlainText = (body: any): string => + toPlainText(toState(JSON.parse(body))); + +const MEMBER_SPAM_LMIT = 3; +const SPAM_TIMEFRAME = 60 * 10; type File = FileUpload; -type PublishThreadInput = { +export type PublishThreadInput = { thread: { channelId: string, communityId: string, - type: 'SLATE' | 'DRAFTJS', + type: 'SLATE' | 'DRAFTJS' | 'TEXT', content: { title: string, body?: string, }, - attachments?: ?Array, filesToUpload?: ?Array, }, }; -export default async ( - _: any, - { thread }: PublishThreadInput, - { user, loaders }: GraphQLContext -) => { - const currentUser = user; +export default requireAuth( + async (_: any, args: PublishThreadInput, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + const { thread } = args; - // user must be authed to publish a thread - if (!currentUser) { - return new UserError('You must be signed in to publish a new thread.'); - } + let { type } = thread; - if (thread.type === 'SLATE') { - throw new UserError( - "You're on an old version of Spectrum, please refresh your browser." - ); - } + if (type === 'SLATE') { + trackQueue.add({ + userId: user.id, + event: events.THREAD_CREATED_FAILED, + context: { channelId: thread.channelId }, + properties: { + reason: 'slate type', + }, + }); - // get the current user's permissions in the channel where the thread is being posted - const getCurrentUserChannelPermissions = getUserPermissionsInChannel( - thread.channelId, - currentUser.id - ); - - const getCurrentUserCommunityPermissions = getUserPermissionsInCommunity( - thread.communityId, - currentUser.id - ); - - const getChannel = getChannelById(thread.channelId); - const getCommunity = getCommunityById(thread.communityId); - - const [ - currentUserChannelPermissions, - currentUserCommunityPermissions, - channel, - community, - ] = await Promise.all([ - getCurrentUserChannelPermissions, - getCurrentUserCommunityPermissions, - getChannel, - getCommunity, - ]); - - if (!community) { - return new UserError('This community doesn’t exist'); - } + return new UserError( + "You're on an old version of Spectrum, please refresh your browser." + ); + } - // if channel wasn't found or is deleted - if (!channel || channel.deletedAt) { - return new UserError("This channel doesn't exist"); - } + if (type === 'TEXT') { + type = 'DRAFTJS'; + if (thread.content.body) { + thread.content.body = JSON.stringify( + convertToRaw( + stateFromMarkdown(thread.content.body, { + parserOptions: { + breaks: true, + }, + }) + ) + ); + } + } - if (channel.isArchived) { - return new UserError('This channel has been archived'); - } + thread.type = type; - // if user isn't a channel member - if ( - !currentUserChannelPermissions.isMember || - currentUserChannelPermissions.isBlocked || - currentUserCommunityPermissions.isBlocked - ) { - return new UserError( - "You don't have permission to create threads in this channel." - ); - } + const [ + currentUserChannelPermissions, + currentUserCommunityPermissions, + channel, + community, + usersPreviousPublishedThreads, + ] = await Promise.all([ + loaders.userPermissionsInChannel.load([user.id, thread.channelId]), + loaders.userPermissionsInCommunity.load([user.id, thread.communityId]), + loaders.channel.load(thread.channelId), + loaders.community.load(thread.communityId), + getThreadsByUserAsSpamCheck(user.id, SPAM_TIMEFRAME), + ]); - const { customer } = await StripeUtil.jobPreflight(community.id); + if (!community || community.deletedAt) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_CREATED_FAILED, + context: { channelId: thread.channelId }, + properties: { + reason: 'community doesn’t exist', + }, + }); - if (!customer) { - return new UserError( - 'We could not verify the billing status for this channel, please try again' - ); - } + return new UserError('This community doesn’t exist'); + } + + // if channel wasn't found or is deleted + if (!channel || channel.deletedAt) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_CREATED_FAILED, + context: { channelId: thread.channelId }, + properties: { + reason: 'channel doesn’t exist', + }, + }); + + return new UserError("This channel doesn't exist"); + } + + if (channel.isArchived) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_CREATED_FAILED, + context: { channelId: thread.channelId }, + properties: { + reason: 'channel archived', + }, + }); + + return new UserError('This channel has been archived'); + } + + // if user isn't a channel member + if ( + !currentUserChannelPermissions.isMember || + currentUserChannelPermissions.isBlocked || + currentUserCommunityPermissions.isBlocked + ) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_CREATED_FAILED, + context: { channelId: thread.channelId }, + properties: { + reason: 'no permission', + }, + }); + + return new UserError( + "You don't have permission to create threads in this channel." + ); + } + + const isOwnerOrModerator = + currentUserChannelPermissions.isOwner || + currentUserChannelPermissions.isModerator || + currentUserCommunityPermissions.isOwner || + currentUserCommunityPermissions.isModerator; + + // if user has published other threads in the last hour, check for spam + if ( + !isOwnerOrModerator && + usersPreviousPublishedThreads && + usersPreviousPublishedThreads.length > 0 + ) { + debug( + 'User has posted at least once in the previous 10m - running spam checks' + ); + + if (usersPreviousPublishedThreads.length >= MEMBER_SPAM_LMIT) { + debug('User has posted at least 3 times in the previous 10m'); + _adminProcessUserSpammingThreadsQueue.add({ + user: user, + threads: usersPreviousPublishedThreads, + // $FlowIssue + publishing: thread, + community: community, + channel: channel, + }); + + trackQueue.add({ + userId: user.id, + event: events.THREAD_CREATED_FAILED, + context: { channelId: thread.channelId }, + properties: { + reason: 'user spamming threads', + }, + }); + + return new UserError( + 'You’ve been posting a lot! Please wait a few minutes before posting more.' + ); + } + + const checkForSpam = usersPreviousPublishedThreads.map(t => { + if (!t) return false; + + const incomingTitle = thread.content.title; + const thisTitle = t.content.title; + + const titleSimilarity = stringSimilarity.compareTwoStrings( + incomingTitle, + thisTitle + ); + debug(`Title similarity score for spam check: ${titleSimilarity}`); + if (titleSimilarity > 0.8) return true; + + if (thread.content.body) { + const incomingBody = threadBodyToPlainText(thread.content.body); + const thisBody = threadBodyToPlainText(t.content.body); - const hasPaidPrivateChannel = await StripeUtil.hasSubscriptionItemOfType( - customer, - PRIVATE_CHANNEL - ); - const hasFreePrivateChannel = await StripeUtil.hasSubscriptionItemOfType( - customer, - FREE_PRIVATE_CHANNEL - ); - - if (channel.isPrivate && (!hasPaidPrivateChannel && !hasFreePrivateChannel)) { - return new UserError( - 'This private channel does not have an active subscription' + if (incomingBody.length === 0 || thisBody.length === 0) return false; + + const bodySimilarity = stringSimilarity.compareTwoStrings( + incomingBody, + thisBody + ); + debug(`Body similarity score for spam check: ${bodySimilarity}`); + if (bodySimilarity > 0.8) return true; + } + + return false; + }); + + const isSpamming = checkForSpam.filter(Boolean).length > 0; + + if (isSpamming) { + debug('User is spamming similar content'); + + trackQueue.add({ + userId: user.id, + event: events.THREAD_FLAGGED_AS_SPAM, + context: { channelId: thread.channelId }, + }); + + _adminProcessUserSpammingThreadsQueue.add({ + user: user, + threads: usersPreviousPublishedThreads, + // $FlowIssue + publishing: thread, + community: community, + channel: channel, + }); + + return new UserError( + 'It looks like you’ve been posting about a similar topic recently - please wait a while before posting more.' + ); + } + } + + let threadObject = Object.assign( + {}, + { + ...thread, + content: { + ...thread.content, + title: thread.content.title.trim(), + }, + } ); - } - /* - If the thread has attachments, we have to iterate through each attachment and JSON.parse() the data payload. This is because we want a generic data shape in the graphQL layer like this: + // $FlowFixMe + const dbThread: DBThread = await publishThread(threadObject, user.id); - { - attachmentType: enum String - data: String - } + // we check for toxicity here only to determine whether or not to send + // email notifications - the thread will be published regardless, but we can + // prevent some abuse and spam if we ensure people dont get email notifications + // with titles like "fuck you" + const checkToxicity = async () => { + const body = thread.content.body + ? threadBodyToPlainText(thread.content.body) + : ''; + const title = thread.content.title; + const text = `${title} ${body}`; - But when we get the data onto the client we JSON.parse the `data` field so that we can have any generic shape for attachments in the future. -*/ - let attachments = []; - let threadObject = Object.assign( - {}, - { - ...thread, - content: { - ...thread.content, - title: thread.content.title.trim(), - }, + const scores = await getPerspectiveScore(text).catch(err => + console.error( + 'Error getting thread moderation scores from providers', + err.message + ) + ); + + const perspectiveScore = scores && scores[1]; + + // if neither models returned results + if (!perspectiveScore) { + debug('Toxicity checks from providers say not toxic'); + return false; + } + + // if both services agree that the thread is >= 98% toxic + if (perspectiveScore >= 0.9) { + debug('Thread is toxic according to both providers'); + return true; + } + + return false; + }; + + const threadIsToxic = await checkToxicity(); + if (!isOwnerOrModerator && threadIsToxic) { + debug( + 'Thread determined to be toxic, not sending notifications or adding rep' + ); + + trackQueue.add({ + userId: user.id, + event: events.THREAD_FLAGGED_AS_TOXIC, + context: { threadId: dbThread.id }, + }); + + // generate an alert for admins + _adminProcessToxicThreadQueue.add({ thread: dbThread }); + processReputationEventQueue.add({ + userId: user.id, + type: 'thread created', + entityId: dbThread.id, + }); + } else { + debug('Thread is not toxic, send notifications and add rep'); + // thread is clean, send notifications and process reputation + sendThreadNotificationQueue.add({ thread: dbThread }); + processReputationEventQueue.add({ + userId: user.id, + type: 'thread created', + entityId: dbThread.id, + }); } - ); - // if the thread has attachments - if (thread.attachments) { - // iterate through them and construct a new attachment object - thread.attachments.map(attachment => { - attachments.push({ - attachmentType: attachment.attachmentType, - data: JSON.parse(attachment.data), + + // create a relationship between the thread and the author + await createParticipantInThread(dbThread.id, user.id); + + if (!thread.filesToUpload || thread.filesToUpload.length === 0) { + return dbThread; + } + + let urls; + try { + // if the original mutation input contained files to upload + urls = await Promise.all( + // upload each of the files to s3 + thread.filesToUpload.map( + file => file && uploadImage(file, 'threads', dbThread.id) + ) + ); + } catch (err) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_CREATED_FAILED, + context: { channelId: thread.channelId }, + properties: { + reason: 'images failed to upload', + }, }); - }); - // create a new thread object, overriding the attachments field with our new array - threadObject = Object.assign({}, threadObject, { - attachments, + return new UserError(err.message); + } + + // Replace the local image srcs with the remote image src + const body = dbThread.content.body && JSON.parse(dbThread.content.body); + + if (!body) return dbThread; + const imageKeys = Object.keys(body.entityMap).filter( + key => body.entityMap[key].type.toLowerCase() === 'image' + ); + urls.forEach((url, index) => { + if (!body.entityMap[imageKeys[index]]) return; + body.entityMap[imageKeys[index]].data.src = url; }); - } - // $FlowIssue - const dbThread = await publishThread(threadObject, currentUser.id); - - // create a relationship between the thread and the author. this can happen in the background so we can also immediately pass the thread down the promise chain - await createParticipantInThread(dbThread.id, currentUser.id); - - if (!thread.filesToUpload || thread.filesToUpload.length === 0) - return dbThread; - - // if the original mutation input contained files to upload - const urls = await Promise.all( - // upload each of the files to s3 - thread.filesToUpload.map( - file => file && uploadImage(file, 'threads', dbThread.id) - ) - ); - - // Replace the local image srcs with the remote image src - const body = dbThread.content.body && JSON.parse(dbThread.content.body); - const imageKeys = Object.keys(body.entityMap).filter( - key => body.entityMap[key].type === 'image' - ); - urls.forEach((url, index) => { - if (!body.entityMap[imageKeys[index]]) return; - body.entityMap[imageKeys[index]].data.src = url; - }); - - // Update the thread with the new links - return editThread( - { - threadId: dbThread.id, - content: { - ...dbThread.content, - body: JSON.stringify(body), + // Update the thread with the new links + return editThread( + { + threadId: dbThread.id, + content: { + ...dbThread.content, + body: JSON.stringify(body), + }, }, - }, - false - ); -}; + user.id, + false + ); + } +); diff --git a/api/mutations/thread/removeThreadReaction.js b/api/mutations/thread/removeThreadReaction.js new file mode 100644 index 0000000000..d778d06847 --- /dev/null +++ b/api/mutations/thread/removeThreadReaction.js @@ -0,0 +1,40 @@ +// @flow +import type { GraphQLContext } from '../../'; +import { removeThreadReaction } from '../../models/threadReaction'; +import { getThreadById } from '../../models/thread'; +import { + isAuthedResolver as requireAuth, + canViewThread, +} from '../../utils/permissions'; +import { calculateThreadScoreQueue } from 'shared/bull/queues'; +import UserError from '../../utils/UserError'; + +type Input = { + input: { + threadId: string, + }, +}; + +export default requireAuth( + async (_: any, args: Input, { user, loaders }: GraphQLContext) => { + if (await canViewThread(user.id, args.input.threadId, loaders)) { + return await removeThreadReaction(args.input.threadId, user.id).then( + () => { + calculateThreadScoreQueue.add( + { + threadId: args.input.threadId, + }, + { + jobId: args.input.threadId, + } + ); + return getThreadById(args.input.threadId); + } + ); + } + + return new UserError( + 'You don’t have permission to view this conversation.' + ); + } +); diff --git a/api/mutations/thread/setThreadLock.js b/api/mutations/thread/setThreadLock.js index 235c283ff7..e3ed27c57f 100644 --- a/api/mutations/thread/setThreadLock.js +++ b/api/mutations/thread/setThreadLock.js @@ -1,38 +1,44 @@ // @flow import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; -import { getThreads, setThreadLock } from '../../models/thread'; -import { getUserPermissionsInChannel } from '../../models/usersChannels'; -import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; +import { setThreadLock } from '../../models/thread'; import type { DBThread } from 'shared/types'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; -export default async ( - _: any, - { threadId, value }: { threadId: string, value: boolean }, - { user, loaders }: GraphQLContext -) => { - const currentUser = user; +type Input = { + threadId: string, + value: boolean, +}; - // user must be authed to edit a thread - if (!currentUser) { - return new UserError('You must be signed in to make changes.'); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + const { threadId, value } = args; const thread: DBThread = await loaders.thread.load(threadId); // if the thread doesn't exist if (!thread || thread.deletedAt) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_LOCKED_FAILED, + properties: { + reason: 'thread not found', + }, + }); + return new UserError(`Could not find thread with ID '${threadId}'.`); } // A threads author can always lock their thread, but only unlock it if // it was locked by themselves. (if a mod locks a thread an author cannot // unlock it anymore) - const isAuthor = thread.creatorId === currentUser.id; + const isAuthor = thread.creatorId === user.id; const authorCanLock = !thread.isLocked || thread.lockedBy === thread.creatorId; if (isAuthor && authorCanLock) { - return setThreadLock(threadId, value, currentUser.id); + return setThreadLock(threadId, value, user.id, false); } // get the channel permissions @@ -40,11 +46,8 @@ export default async ( currentUserChannelPermissions, currentUserCommunityPermissions, ] = await Promise.all([ - loaders.userPermissionsInChannel.load([currentUser.id, thread.channelId]), - loaders.userPermissionsInCommunity.load([ - currentUser.id, - thread.communityId, - ]), + loaders.userPermissionsInChannel.load([user.id, thread.channelId]), + loaders.userPermissionsInCommunity.load([user.id, thread.communityId]), ]); if (!currentUserChannelPermissions) currentUserChannelPermissions = {}; @@ -57,16 +60,35 @@ export default async ( currentUserCommunityPermissions.isOwner || currentUserCommunityPermissions.isModerator ) { - return setThreadLock(threadId, value, currentUser.id); + return setThreadLock(threadId, value, user.id, true); } // if the user is not a channel or community owner, the thread can't be locked if (isAuthor) { + trackQueue.add({ + userId: user.id, + event: events.THREAD_LOCKED_FAILED, + context: { threadId }, + properties: { + reason: 'previously locked by moderator', + }, + }); + return new UserError( "You don't have permission to unlock this thread as it was locked by a moderator." ); } + + trackQueue.add({ + userId: user.id, + event: events.THREAD_LOCKED_FAILED, + context: { threadId }, + properties: { + reason: 'no permission', + }, + }); + return new UserError( "You don't have permission to make changes to this thread." ); -}; +}); diff --git a/api/mutations/thread/toggleThreadNotifications.js b/api/mutations/thread/toggleThreadNotifications.js index 31f5d3be19..dbd982a6ec 100644 --- a/api/mutations/thread/toggleThreadNotifications.js +++ b/api/mutations/thread/toggleThreadNotifications.js @@ -1,32 +1,25 @@ // @flow import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; import { getThreadNotificationStatusForUser, updateThreadNotificationStatusForUser, createNotifiedUserInThread, } from '../../models/usersThreads'; import { getThread } from '../../models/thread'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; -export default ( - _: any, - { threadId }: { threadId: string }, - { user }: GraphQLContext -) => { - const currentUser = user; +type Input = { + threadId: string, +}; - // user must be authed to edit a thread - if (!currentUser) { - return new UserError( - 'You must be signed in to get notifications for this thread.' - ); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user } = ctx; + const { threadId } = args; // check to see if a relationship between this user and this thread exists - return getThreadNotificationStatusForUser(threadId, currentUser.id) - .then(threads => { - if (threads && threads.length > 0) { - const threadToEvaluate = threads[0]; + return getThreadNotificationStatusForUser(threadId, user.id) + .then(threadToEvaluate => { + if (threadToEvaluate) { // a relationship with this thread exists, we are going to update it let value; if (threadToEvaluate.receiveNotifications) { @@ -34,7 +27,7 @@ export default ( value = false; return updateThreadNotificationStatusForUser( threadId, - currentUser.id, + user.id, value ); } else { @@ -42,14 +35,14 @@ export default ( value = true; return updateThreadNotificationStatusForUser( threadId, - currentUser.id, + user.id, value ); } } else { // if a relationship doesn't exist, create a new one - return createNotifiedUserInThread(threadId, currentUser.id); + return createNotifiedUserInThread(threadId, user.id); } }) .then(() => getThread(threadId)); -}; +}); diff --git a/api/mutations/user/banUser.js b/api/mutations/user/banUser.js new file mode 100644 index 0000000000..ceacb4ed18 --- /dev/null +++ b/api/mutations/user/banUser.js @@ -0,0 +1,69 @@ +// @flow +import { isAuthedResolver, isAdmin } from '../../utils/permissions'; +import UserError from '../../utils/UserError'; +import type { GraphQLContext } from '../../'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import { banUser } from 'shared/db/queries/user'; + +type BanUserInput = { + input: { + userId: string, + reason: string, + }, +}; + +export default isAuthedResolver( + async (_: any, args: BanUserInput, ctx: GraphQLContext) => { + const { + input: { userId, reason }, + } = args; + const { loaders, user: currentUser } = ctx; + + if (!isAdmin(currentUser.id)) { + return new UserError('You don’t have permission to do that.'); + } + + if (currentUser.id === userId) { + return new UserError('You cannot ban yourself.'); + } + + const reportedUser = await loaders.user.load(userId); + + if (!reportedUser) { + return new UserError(`User with ID ${userId} does not exist.`); + } + + if (reportedUser.bannedAt) { + return new UserError('This user has already been banned'); + } + + return banUser({ + userId, + reason, + currentUserId: currentUser.id, + }) + .then(() => { + trackQueue.add({ + userId, + event: events.USER_WAS_BANNED, + properties: { + reason, + reportedBy: currentUser.id, + }, + }); + + trackQueue.add({ + userId: currentUser.id, + event: events.USER_BANNED_USER, + properties: { + reason, + reportedUser: userId, + }, + }); + + return true; + }) + .catch(err => new UserError(err.message)); + } +); diff --git a/api/mutations/user/deleteCurrentUser.js b/api/mutations/user/deleteCurrentUser.js new file mode 100644 index 0000000000..eb1bfe8b6e --- /dev/null +++ b/api/mutations/user/deleteCurrentUser.js @@ -0,0 +1,25 @@ +// @flow +import type { GraphQLContext } from '../../'; +import UserError from '../../utils/UserError'; +import { deleteUser } from 'shared/db/queries/user'; +import { removeUsersCommunityMemberships } from '../../models/usersCommunities'; +import { removeUsersChannelMemberships } from '../../models/usersChannels'; +import { disableAllThreadNotificationsForUser } from '../../models/usersThreads'; +import { disableAllUsersEmailSettings } from '../../models/usersSettings'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; + +export default requireAuth(async (_: any, __: any, ctx: GraphQLContext) => { + const { user } = ctx; + + return Promise.all([ + deleteUser(user.id), + disableAllUsersEmailSettings(user.id), + removeUsersCommunityMemberships(user.id), + removeUsersChannelMemberships(user.id), + disableAllThreadNotificationsForUser(user.id), + ]) + .then(() => true) + .catch(err => new UserError(err.message)); +}); diff --git a/api/mutations/user/editUser.js b/api/mutations/user/editUser.js index cd2e8fdc33..f4aecaa748 100644 --- a/api/mutations/user/editUser.js +++ b/api/mutations/user/editUser.js @@ -1,34 +1,92 @@ // @flow +import Raven from 'shared/raven'; import type { GraphQLContext } from '../../'; -import type { EditUserInput } from '../../models/user'; +import type { EditUserInput } from 'shared/db/queries/user'; import UserError from '../../utils/UserError'; -import { getUser, editUser } from '../../models/user'; +import { + getUserByUsername, + getUserById, + editUser, + getUsersByEmail, + setUserPendingEmail, +} from 'shared/db/queries/user'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import isEmail from 'validator/lib/isEmail'; +import { sendEmailValidationEmailQueue } from 'shared/bull/queues'; -export default (_: any, args: EditUserInput, { user }: GraphQLContext) => { - const currentUser = user; +export default requireAuth( + async (_: any, args: EditUserInput, ctx: GraphQLContext) => { + const { user: currentUser, updateCookieUserData } = ctx; + const { input } = args; - // user must be authed to edit a channel - if (!currentUser) { - return new UserError( - 'You must be signed in to make changes to this profile.' - ); - } + // If the user is trying to change their username check whether there's a person with that username already + if (input.username) { + if (input.username === 'null' || input.username === 'undefined') { + trackQueue.add({ + userId: currentUser.id, + event: events.USER_EDITED_FAILED, + properties: { + reason: 'bad username input', + }, + }); + + return new UserError('Nice try! 😉'); + } + + const dbUser = await getUserByUsername(input.username); + if (dbUser && dbUser.id !== currentUser.id) { + trackQueue.add({ + userId: currentUser.id, + event: events.USER_EDITED_FAILED, + properties: { + reason: 'username taken', + }, + }); - // if the user is changing their username, check for uniqueness on the server - if (args.input.username) { - if (args.input.username === 'null' || args.input.username === 'undefined') { - throw new UserError('Nice try! 😉'); + return new UserError( + 'Looks like that username got swooped! Try another?' + ); + } } - return getUser({ username: args.input.username }).then(user => { - // no user exists - if (!user) return editUser(args, currentUser.id); - // if the user is saving themselves, it's safe to edit - if (user.id === currentUser.id) return editUser(args, currentUser.id); - return new UserError( - 'Looks like that username got swooped! Try another?' + + if (input.email && typeof input.email === 'string') { + const pendingEmail = input.email; + + // if user is changing their email, make sure it's not taken by someone else + if (pendingEmail !== currentUser.email || !currentUser.email) { + if (!isEmail(input.email)) { + return new UserError('Please enter a valid email address.'); + } + + const dbUsers = await getUsersByEmail(pendingEmail); + if (dbUsers && dbUsers.length > 0) { + return new UserError('Please enter a valid email address.'); + } + + // the user will have to confirm their email for it to be saved in + // order to prevent spoofing your email as someone elses + await setUserPendingEmail(currentUser.id, pendingEmail).then(() => { + sendEmailValidationEmailQueue.add({ + email: pendingEmail, + userId: currentUser.id, + }); + }); + } + } + + const editedUser = await editUser(args, currentUser.id); + + await updateCookieUserData({ + ...editedUser, + }).catch(err => { + Raven.captureException( + new Error(`Error updating cookie user data: ${err.message}`) ); + return editedUser; }); - } else { - return editUser(args, currentUser.id); + + return editedUser; } -}; +); diff --git a/api/mutations/user/index.js b/api/mutations/user/index.js index 9d9ae8d6c0..0db88cbc80 100644 --- a/api/mutations/user/index.js +++ b/api/mutations/user/index.js @@ -12,6 +12,9 @@ import toggleNotificationSettings from './toggleNotificationSettings'; import subscribeWebPush from './subscribeWebPush'; import unsubscribeWebPush from './unsubscribeWebPush'; import updateUserEmail from './updateUserEmail'; +import deleteCurrentUser from './deleteCurrentUser'; +import reportUser from './reportUser'; +import banUser from './banUser'; module.exports = { Mutation: { @@ -20,5 +23,8 @@ module.exports = { subscribeWebPush, unsubscribeWebPush, updateUserEmail, + deleteCurrentUser, + reportUser, + banUser, }, }; diff --git a/api/mutations/user/reportUser.js b/api/mutations/user/reportUser.js new file mode 100644 index 0000000000..2500a0eea3 --- /dev/null +++ b/api/mutations/user/reportUser.js @@ -0,0 +1,64 @@ +// @flow +import { isAuthedResolver } from '../../utils/permissions'; +import UserError from '../../utils/UserError'; +import { _adminProcessUserReportedQueue } from 'shared/bull/queues'; +import type { GraphQLContext } from '../../'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; + +type ReportUserInput = { + input: { + userId: string, + reason: string, + }, +}; + +export default isAuthedResolver( + async (_: any, args: ReportUserInput, ctx: GraphQLContext) => { + const { + input: { userId, reason }, + } = args; + const { loaders, user: currentUser } = ctx; + + if (currentUser.id === userId) { + return new UserError('You cannot report yourself.'); + } + + const reportedUser = await loaders.user.load(userId); + + if (!reportedUser) { + return new UserError(`User with ID ${userId} does not exist.`); + } + + try { + trackQueue.add({ + userId, + event: events.USER_WAS_REPORTED, + properties: { + reason, + reportedBy: currentUser.id, + }, + }); + + trackQueue.add({ + userId: currentUser.id, + event: events.USER_REPORTED_USER, + properties: { + reason, + reportedUser: userId, + }, + }); + + await _adminProcessUserReportedQueue.add({ + userId, + reason, + reportedBy: currentUser.id, + reportedAt: new Date(), + }); + } catch (err) { + console.error(err); + return false; + } + return true; + } +); diff --git a/api/mutations/user/subscribeExpoPush.js b/api/mutations/user/subscribeExpoPush.js deleted file mode 100644 index 097724adfc..0000000000 --- a/api/mutations/user/subscribeExpoPush.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow -import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -import { storeExpoSubscription } from '../../models/expo-push-subscription'; - -export default ( - _: any, - { token }: { token: string }, - { user }: GraphQLContext -) => { - if (!user || !user.id) - throw new UserError('Can only enable push notifications when logged in.'); - - return storeExpoSubscription(token, user.id) - .then(() => true) - .catch(err => { - throw new UserError( - "Couldn't enable push notifications, please try again." - ); - }); -}; diff --git a/api/mutations/user/subscribeWebPush.js b/api/mutations/user/subscribeWebPush.js index 85834ffcd5..983cdc4f87 100644 --- a/api/mutations/user/subscribeWebPush.js +++ b/api/mutations/user/subscribeWebPush.js @@ -4,16 +4,22 @@ import type { WebPushSubscription } from './'; import UserError from '../../utils/UserError'; import { storeSubscription } from '../../models/web-push-subscription'; import sendWebPushNotification from 'shared/send-web-push-notification'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; -export default ( - _: any, - { subscription }: { subscription: WebPushSubscription }, - { user }: GraphQLContext -) => { - if (!user || !user.id) - throw new UserError( - 'Can only subscribe to web push notifications when logged in.' - ); +type Input = { + subscription: WebPushSubscription, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user } = ctx; + const { subscription } = args; + + trackQueue.add({ + userId: user.id, + event: events.WEB_PUSH_NOTIFICATIONS_SUBSCRIBED, + }); return storeSubscription(subscription, user.id) .then(() => { @@ -27,13 +33,13 @@ export default ( TTL: 300, // If the user doesn't go online for five minutes don't send him this notification anymore } ).catch(err => { - throw new UserError( + return new UserError( "It seems like we can't send you web push notifications. Please ping @mxstbr with your browser and OS versions and he'll take a look!" ); }); }) .then(() => true) .catch(err => { - throw new UserError("Couldn't store web push subscription."); + return new UserError("Couldn't store web push subscription."); }); -}; +}); diff --git a/api/mutations/user/toggleNotificationSettings.js b/api/mutations/user/toggleNotificationSettings.js index 31834fc93c..ccae552a75 100644 --- a/api/mutations/user/toggleNotificationSettings.js +++ b/api/mutations/user/toggleNotificationSettings.js @@ -1,32 +1,25 @@ // @flow import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; import { getUsersSettings, updateUsersNotificationSettings, } from '../../models/usersSettings'; -import { getUserById } from '../../models/user'; +import { getUserById } from 'shared/db/queries/user'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; -type ToggleNotificationsArguments = { - deliveryMethod: string, - notificationType: string, +type Input = { + input: { + deliveryMethod: string, + notificationType: string, + }, }; -export default async ( - _: any, - { input }: { input: ToggleNotificationsArguments }, - { user }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser) { - return new UserError( - 'You must be signed in to make changes to this profile.' - ); - } +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { user } = ctx; + const { deliveryMethod, notificationType } = args.input; // eslint-disable-next-line - const { id, ...settings } = await getUsersSettings(currentUser.id); + const { id, ...settings } = await getUsersSettings(user.id); // destructure the notifications so we don't pass the id into the model downstream // trying to update a primary key 'id' will throw a reql error @@ -34,13 +27,16 @@ export default async ( ...settings, }); - let oldVal = - settings.notifications.types[input.notificationType][input.deliveryMethod]; - newSettings['notifications']['types'][input.notificationType][ - input.deliveryMethod + let oldVal = settings.notifications.types[notificationType][deliveryMethod]; + newSettings['notifications']['types'][notificationType][ + deliveryMethod ] = !oldVal; - return updateUsersNotificationSettings(currentUser.id, newSettings).then(() => - getUserById(currentUser.id) - ); -}; + return await updateUsersNotificationSettings( + user.id, + newSettings, + notificationType, + deliveryMethod, + oldVal + ).then(() => getUserById(user.id)); +}); diff --git a/api/mutations/user/unsubscribeWebPush.js b/api/mutations/user/unsubscribeWebPush.js index 3ebbfe1d76..451e22a7c5 100644 --- a/api/mutations/user/unsubscribeWebPush.js +++ b/api/mutations/user/unsubscribeWebPush.js @@ -2,15 +2,23 @@ import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; import { removeSubscription } from '../../models/web-push-subscription'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; -export default (_: any, endpoint: string, { user }: GraphQLContext) => { - if (!user || !user.id) - throw new UserError( - 'Can only unsubscribe from web push notifications when logged in.' - ); - return removeSubscription(endpoint) - .then(() => true) - .catch(err => { - throw new UserError("Couldn't remove web push subscription."); +export default requireAuth( + async (_: any, endpoint: string, ctx: GraphQLContext) => { + const { user } = ctx; + + trackQueue.add({ + userId: user.id, + event: events.WEB_PUSH_NOTIFICATIONS_UNSUBSCRIBED, }); -}; + + return await removeSubscription(endpoint) + .then(() => true) + .catch(err => { + return new UserError("Couldn't remove web push subscription."); + }); + } +); diff --git a/api/mutations/user/updateUserEmail.js b/api/mutations/user/updateUserEmail.js index 5700564985..5df65ef764 100644 --- a/api/mutations/user/updateUserEmail.js +++ b/api/mutations/user/updateUserEmail.js @@ -1,19 +1,18 @@ // @flow import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; -import { getUserByEmail, setUserPendingEmail } from '../../models/user'; +import { getUserByEmail, setUserPendingEmail } from 'shared/db/queries/user'; import isEmail from 'validator/lib/isEmail'; import { sendEmailValidationEmailQueue } from 'shared/bull/queues'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; -export default async ( - _: any, - { email }: { email: string }, - { user }: GraphQLContext -) => { - const currentUser = user; - if (!currentUser) { - return new UserError('You must be signed in to update your email address'); - } +type Input = { + email: string, +}; + +export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { + const { email } = args; + const { user } = ctx; if (!isEmail(email)) { return new UserError('Please enter a working email address'); @@ -27,7 +26,7 @@ export default async ( ); } - return setUserPendingEmail(user.id, email) + return await setUserPendingEmail(user.id, email) .then(user => { sendEmailValidationEmailQueue.add({ email, userId: user.id }); return user; @@ -38,4 +37,4 @@ export default async ( "We weren't able to send a confirmation email. Please try again." ) ); -}; +}); diff --git a/api/package.json b/api/package.json index 6275c9ab73..ec198026cf 100644 --- a/api/package.json +++ b/api/package.json @@ -1,127 +1,140 @@ { + "engines": { + "node": "^10.0.0" + }, "dependencies": { - "algoliasearch": "^3.24.7", - "apollo-engine": "1.x", - "apollo-local-query": "^0.3.0", - "apollo-upload-client": "^8.0.0", - "apollo-upload-server": "^5.0.0", + "algoliasearch": "^3.32.0", + "apollo-local-query": "^0.3.1", + "apollo-server-express": "2.4.0-alpha.0", + "apollo-upload-client": "^9.1.0", "aws-sdk": "2.200.0", "axios": "^0.16.2", + "b2a": "^1.0.10", "babel-plugin-replace-dynamic-import-runtime": "^1.0.2", - "babel-plugin-styled-components": "^1.1.7", + "babel-plugin-styled-components": "^1.10.0", "babel-plugin-transform-flow-strip-types": "^6.22.0", "babel-plugin-transform-object-rest-spread": "^6.23.0", - "babel-preset-env": "^1.5.2", - "backpack-core": "^0.4.1", - "body-parser": "^1.17.1", - "bull": "3.3.10", + "babel-preset-env": "^1.7.0", + "backpack-core": "^0.8.3", + "body-parser": "^1.18.3", + "bull": "^3.6.0", "casual": "^1.5.12", - "compression": "^1.7.1", + "compression": "^1.7.3", "cookie-parser": "^1.4.3", "cookie-session": "^2.0.0-beta.3", - "cors": "^2.8.3", - "dataloader": "^1.3.0", - "debounce": "^1.1.0", - "debug": "^2.6.8", - "draft-js": "^0.10.0", + "cors": "^2.8.5", + "cryptr": "^3.0.0", + "datadog-metrics": "^0.8.1", + "dataloader": "^1.4.0", + "debounce": "^1.2.0", + "debug": "^4.1.1", + "decode-uri-component": "^0.2.0", + "draft-js": "^0.10.5", "draft-js-code-editor-plugin": "0.2.1", "draft-js-drag-n-drop-plugin": "2.0.0-rc9", "draft-js-embed-plugin": "^1.2.0", "draft-js-focus-plugin": "2.0.0-rc2", "draft-js-image-plugin": "2.0.0-rc8", - "draft-js-import-markdown": "^1.2.0", + "draft-js-import-markdown": "^1.3.0", "draft-js-linkify-plugin": "^2.0.0-beta1", - "draft-js-markdown-plugin": "^1.0.0", - "draft-js-plugins-editor": "^2.0.3", + "draft-js-markdown-plugin": "^1.4.4", + "draft-js-plugins-editor": "^2.1.1", "draft-js-prism-plugin": "0.1.1", "draftjs-to-markdown": "^0.4.2", "emoji-regex": "^6.1.1", - "express": "^4.15.2", + "express": "^4.16.4", + "express-enforces-ssl": "^1.1.0", + "express-hot-shots": "^1.0.2", "express-session": "^1.15.2", "faker": "^4.1.0", - "find-with-regex": "^1.0.2", - "flow-typed": "^2.1.5", - "graphql": "0.11.x", - "graphql-cost-analysis": "^0.1.1", + "find-with-regex": "^1.1.3", + "flow-typed": "^2.5.1", + "graphql": "0.13.x", + "graphql-cost-analysis": "^1.0.3", "graphql-date": "^1.0.3", "graphql-depth-limit": "^1.1.0", - "graphql-log": "0.1.2", - "graphql-server-express": "1.3.0", - "graphql-subscriptions": "0.4.x", - "graphql-tools": "1.2.3", - "highlight.js": "^9.10.0", + "graphql-log": "^0.1.3", + "graphql-rate-limit": "^1.2.3", + "graphql-tools": "^4.0.4", + "helmet": "^3.15.0", + "highlight.js": "^9.14.1", "history": "^4.6.1", - "hoist-non-react-statics": "^2.3.1", - "imgix-core-js": "^1.0.6", - "immutability-helper": "^2.2.0", + "hoist-non-react-statics": "^2.5.5", + "host-validation": "^1.2.0", + "hot-shots": "^5.9.2", + "hpp": "^0.2.2", + "hsts": "^2.1.0", + "imgix-core-js": "^1.2.0", + "immutability-helper": "^2.9.1", + "ioredis": "3.2.2", "isomorphic-fetch": "^2.2.1", "iterall": "^1.2.2", "jest": "^21.2.1", - "json-stringify-pretty-compact": "^1.0.4", - "jsonwebtoken": "^8.0.1", - "keygrip": "^1.0.2", - "linkify-it": "^2.0.3", - "localstorage-memory": "^1.0.2", - "lodash": "^4.17.4", + "json-stringify-pretty-compact": "^1.2.0", + "jsonwebtoken": "^8.4.0", + "keygrip": "^1.0.3", + "linkify-it": "^2.1.0", + "localstorage-memory": "^1.0.3", + "lodash": "^4.17.11", "lodash.intersection": "^4.4.0", "longjohn": "^0.2.12", - "moment": "^2.18.1", + "moment": "^2.24.0", + "ms": "^2.1.1", "node-env-file": "^0.1.8", - "node-localstorage": "^1.3.0", - "now-env": "^3.0.1", - "offline-plugin": "^4.8.4", - "optics-agent": "^1.1.2", + "node-localstorage": "^1.3.1", + "now-env": "^3.1.0", + "offline-plugin": "^4.9.1", "passport": "^0.3.2", "passport-facebook": "^2.1.1", "passport-github2": "^0.1.10", "passport-google-oauth2": "^0.1.6", "passport-twitter": "^1.0.4", - "postmark": "^1.4.1", "pre-commit": "^1.2.2", - "prismjs": "^1.8.1", - "query-string": "^5.0.0", - "raven": "^2.0.2", - "raven-js": "^3.18.1", + "prismjs": "^1.15.0", + "query-string": "5.1.1", + "raven": "^2.6.4", "react": "^15.4.1", - "react-app-rewire-styled-components": "^3.0.0", - "react-app-rewired": "^1.0.5", + "react-app-rewire-styled-components": "^3.0.2", + "react-app-rewired": "^1.6.2", "react-dom": "^15.4.1", "react-helmet": "5.x", "react-infinite-scroller-with-scroll-element": "^1.0.4", "react-loadable": "5.2.2", - "react-modal": "^1.6.5", - "react-redux": "^5.0.2", + "react-modal": "^3.8.1", + "react-redux": "^5.1.1", "react-remarkable": "^1.1.1", "react-router": "^4.0.0-beta.7", "react-router-dom": "^4.0.0-beta.7", - "react-stripe-checkout": "^2.2.5", "react-textarea-autosize": "^4.0.5", - "react-transition-group": "^2.2.0", + "react-transition-group": "^2.5.3", "react-trend": "^1.2.4", "recompose": "^0.23.1", + "redis-tag-cache": "^1.2.1", "redraft": "0.8.0", "redux": "^3.6.0", - "redux-thunk": "^2.2.0", + "redux-thunk": "^2.3.0", + "request-ip": "^2.1.3", "rethinkdb-changefeed-reconnect": "^0.3.2", "rethinkdb-inspector": "^0.3.3", - "rethinkdb-migrate": "^1.1.0", - "rethinkdbdash": "^2.3.29", - "serialize-javascript": "^1.4.0", + "rethinkdb-migrate": "^1.4.0", + "rethinkhaberdashery": "^2.3.32", + "sanitize-filename": "^1.6.1", + "serialize-javascript": "^1.6.1", "session-rethinkdb": "^2.0.0", - "shortid": "^2.2.8", - "slate": "^0.20.1", + "slate": "^0.44.10", "slate-markdown": "0.1.0", "slugg": "^1.1.0", "string-replace-to-array": "^1.0.3", - "stripe": "^4.15.0", + "string-similarity": "^1.2.2", "striptags": "2.x", "styled-components": "3.1.x", - "subscriptions-transport-ws": "^0.9.5", - "sw-precache-webpack-plugin": "^0.11.4", + "subscriptions-transport-ws": "^0.9.15", + "sw-precache-webpack-plugin": "^0.11.5", "then-queue": "^1.3.0", "toobusy-js": "^0.5.1", - "validator": "^9.0.0", - "web-push": "^3.2.5" + "uuid": "^3.3.2", + "validator": "^9.4.1", + "web-push": "^3.3.3" }, "scripts": { "start": "NODE_ENV=production node main.js" diff --git a/api/queries/channel/blockedUsers.js b/api/queries/channel/blockedUsers.js index 2fff5b6fb2..3645984512 100644 --- a/api/queries/channel/blockedUsers.js +++ b/api/queries/channel/blockedUsers.js @@ -1,10 +1,21 @@ // @flow import type { GraphQLContext } from '../../'; import type { DBChannel } from 'shared/types'; +import UserError from '../../utils/UserError'; import { getBlockedUsersInChannel } from '../../models/usersChannels'; +import { + isAuthedResolver as requireAuth, + canModerateChannel, +} from '../../utils/permissions'; -export default ({ id }: DBChannel, _: any, { loaders }: GraphQLContext) => { - return getBlockedUsersInChannel(id).then(users => - loaders.user.loadMany(users) - ); -}; +export default requireAuth( + async ({ id }: DBChannel, _: any, { user, loaders }: GraphQLContext) => { + if (!await canModerateChannel(user.id, id, loaders)) { + return new UserError('You don’t have permission to manage this channel'); + } + + return getBlockedUsersInChannel(id).then(users => + loaders.user.loadMany(users) + ); + } +); diff --git a/api/queries/channel/channelPermissions.js b/api/queries/channel/channelPermissions.js index ad555b1e7a..39a4e6fe5a 100644 --- a/api/queries/channel/channelPermissions.js +++ b/api/queries/channel/channelPermissions.js @@ -2,31 +2,29 @@ import type { GraphQLContext } from '../../'; import type { DBChannel } from 'shared/types'; -export default (root: DBChannel, _: any, { user, loaders }: GraphQLContext) => { +export default async ( + root: DBChannel, + _: any, + { user, loaders }: GraphQLContext +) => { const channelId = root.id; + const defaultPermissions = { + isOwner: false, + isMember: false, + isModerator: false, + isBlocked: false, + isPending: false, + receiveNotifications: false, + }; + if (!channelId || !user) { - return { - isOwner: false, - isMember: false, - isModerator: false, - isBlocked: false, - isPending: false, - receiveNotifications: false, - }; + return defaultPermissions; } - return loaders.userPermissionsInChannel - .load([user.id, channelId]) - .then(res => { - if (!res) { - return { - isOwner: false, - isMember: false, - isModerator: false, - isBlocked: false, - isPending: false, - receiveNotifications: false, - }; - } - return res; - }); + + const permissions = await loaders.userPermissionsInChannel.load([ + user.id, + channelId, + ]); + + return permissions || defaultPermissions; }; diff --git a/api/queries/channel/community.js b/api/queries/channel/community.js index b55ba43ae6..c268b4862f 100644 --- a/api/queries/channel/community.js +++ b/api/queries/channel/community.js @@ -1,8 +1,20 @@ // @flow import type { GraphQLContext } from '../../'; +import type { DBChannel } from 'shared/types'; +import { canViewCommunity } from '../../utils/permissions'; -export default ( - { communityId }: { communityId: string }, - _: any, - { loaders }: GraphQLContext -) => loaders.community.load(communityId); +export default async (channel: DBChannel, _: any, ctx: GraphQLContext) => { + const { communityId } = channel; + const { loaders, user: currentUser } = ctx; + + const community = await loaders.community.load(communityId); + if (community.isPrivate) { + if (await canViewCommunity(currentUser, community.id, loaders)) { + return community; + } else { + return null; + } + } + + return community; +}; diff --git a/api/queries/channel/communityPermissions.js b/api/queries/channel/communityPermissions.js index 19f4fc759c..d302472f83 100644 --- a/api/queries/channel/communityPermissions.js +++ b/api/queries/channel/communityPermissions.js @@ -2,17 +2,28 @@ import type { GraphQLContext } from '../../'; import type { DBChannel } from 'shared/types'; -export default (root: DBChannel, _: any, { user, loaders }: GraphQLContext) => { +export default async ( + root: DBChannel, + _: any, + { user, loaders }: GraphQLContext +) => { const communityId = root.id || root.communityId; + const defaultPermissions = { + isOwner: false, + isMember: false, + isModerator: false, + isBlocked: false, + isPending: false, + receiveNotifications: false, + }; + if (!communityId || !user) { - return { - isOwner: false, - isMember: false, - isModerator: false, - isBlocked: false, - isPending: false, - receiveNotifications: false, - }; + return defaultPermissions; } - return loaders.userPermissionsInCommunity.load([user.id, communityId]); + + const permissions = await loaders.userPermissionsInCommunity.load([ + user.id, + communityId, + ]); + return permissions || defaultPermissions; }; diff --git a/api/queries/channel/index.js b/api/queries/channel/index.js index 0b2483567f..7241c50eab 100644 --- a/api/queries/channel/index.js +++ b/api/queries/channel/index.js @@ -14,6 +14,7 @@ import moderators from './moderators'; import owners from './owners'; import isArchived from './isArchived'; import joinSettings from './joinSettings'; +import slackSettings from './slackSettings'; module.exports = { Query: { @@ -33,5 +34,6 @@ module.exports = { owners, isArchived, joinSettings, + slackSettings, }, }; diff --git a/api/queries/channel/isArchived.js b/api/queries/channel/isArchived.js index ecf90144b9..9deb98c5f0 100644 --- a/api/queries/channel/isArchived.js +++ b/api/queries/channel/isArchived.js @@ -1,7 +1,7 @@ // @flow import type { DBChannel } from 'shared/types'; -export default ({ archivedAt }: DBChannel) => { +export default ({ archivedAt, ...rest }: DBChannel) => { if (archivedAt) return true; return false; }; diff --git a/api/queries/channel/joinSettings.js b/api/queries/channel/joinSettings.js index 28966984c9..de826d38ff 100644 --- a/api/queries/channel/joinSettings.js +++ b/api/queries/channel/joinSettings.js @@ -1,13 +1,17 @@ // @flow -import { getChannelSettings } from '../../models/channelSettings'; import type { DBChannel } from 'shared/types'; import type { GraphQLContext } from '../../'; +import { canModerateChannel } from '../../utils/permissions'; + +export default async ({ id }: DBChannel, _: any, ctx: GraphQLContext) => { + const { user: currentUser, loaders } = ctx; + + if (!currentUser) return null; + + if (!(await canModerateChannel(currentUser.id, id, loaders))) { + return null; + } -export default async ( - { id }: DBChannel, - _: any, - { loaders }: GraphQLContext -) => { return loaders.channelSettings.load(id).then(settings => { return settings.joinSettings; }); diff --git a/api/queries/channel/memberConnection.js b/api/queries/channel/memberConnection.js index 7d2bd85b2e..97b3779b1c 100644 --- a/api/queries/channel/memberConnection.js +++ b/api/queries/channel/memberConnection.js @@ -4,12 +4,20 @@ import type { GraphQLContext } from '../../'; import type { DBChannel } from 'shared/types'; import { getMembersInChannel } from '../../models/usersChannels'; import { encode, decode } from '../../utils/base64'; +import { canViewChannel } from '../../utils/permissions'; -export default ( - { id }: DBChannel, +export default async ( + channel: DBChannel, { first, after }: PaginationOptions, - { loaders }: GraphQLContext + ctx: GraphQLContext ) => { + const { loaders, user: currentUser } = ctx; + const { id, isPrivate } = channel; + + if (isPrivate) { + if (!(await canViewChannel(currentUser, id, loaders))) return null; + } + const cursor = decode(after); // Get the index from the encoded cursor, asdf234gsdf-2 => ["-2", "2"] const lastDigits = cursor.match(/-(\d+)$/); diff --git a/api/queries/channel/memberCount.js b/api/queries/channel/memberCount.js index d73e28cff3..a521defa67 100644 --- a/api/queries/channel/memberCount.js +++ b/api/queries/channel/memberCount.js @@ -1,5 +1,15 @@ // @flow +import type { DBChannel } from 'shared/types'; +import type { GraphQLContext } from '../../'; +import { canViewChannel } from '../../utils/permissions'; -import { getChannelMemberCount } from '../../models/channel'; +export default async (channel: DBChannel, _: any, ctx: GraphQLContext) => { + const { id, memberCount, isPrivate } = channel; + const { loaders, user: currentUser } = ctx; -export default ({ id }: { id: string }) => getChannelMemberCount(id); + if (isPrivate) { + if (!(await canViewChannel(currentUser, id, loaders))) return 0; + } + + return memberCount || 1; +}; diff --git a/api/queries/channel/metaData.js b/api/queries/channel/metaData.js index a9631de0c9..cf62b0a740 100644 --- a/api/queries/channel/metaData.js +++ b/api/queries/channel/metaData.js @@ -1,19 +1,49 @@ -// @flow -import type { GraphQLContext } from '../../'; +// TODO: Flow type again +import Raven from 'shared/raven'; import type { DBChannel } from 'shared/types'; +import type { GraphQLContext } from '../../'; +import { canViewChannel } from '../../utils/permissions'; +import cache from 'shared/cache/redis'; +import { channelOnlineMemberCount } from 'shared/graphql-cache-keys'; + +export default async (root: DBChannel, _: any, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + const { id, memberCount: rootMemberCount } = root; + + if (!(await canViewChannel(user, id, loaders))) { + return { + members: 0, + onlineMembers: 0, + }; + } + + const cachedOnlineMemberCount = await cache.get(channelOnlineMemberCount(id)); + + const onlineMemberCount = + (await typeof cachedOnlineMemberCount) === 'number' || + loaders.channelOnlineMemberCount + .load(id) + .then(res => (res && res.reduction) || 0); + + // Cache the fields for an hour + (await typeof cachedOnlineMemberCount) === 'number' || + cache.set(channelOnlineMemberCount(id), onlineMemberCount, 'EX', 3600); -export default async ( - { id }: DBChannel, - _: any, - { loaders }: GraphQLContext -) => { - const [threads, members] = await Promise.all([ - loaders.channelThreadCount.load(id), - loaders.channelMemberCount.load(id), - ]); + if (typeof rootMemberCount === 'number') { + return { + members: rootMemberCount, + onlineMembers: onlineMemberCount, + }; + } + // Fallback if there's no denormalized memberCount, also report to Sentry + Raven.captureException( + new Error(`Channel with ID "${id}" does not have denormalized memberCount.`) + ); return { - threads: threads ? threads.reduction : 0, - members: members ? members.reduction : 0, + members: await loaders.channelMemberCount + .load(id) + .then(res => (res && res.reduction) || 0), + onlineMembers: onlineMemberCount, }; }; diff --git a/api/queries/channel/moderators.js b/api/queries/channel/moderators.js index 3782b1eae5..b97694b7bb 100644 --- a/api/queries/channel/moderators.js +++ b/api/queries/channel/moderators.js @@ -2,7 +2,15 @@ import type { GraphQLContext } from '../../'; import type { DBChannel } from 'shared/types'; import { getModeratorsInChannel } from '../../models/usersChannels'; +import { canViewChannel } from '../../utils/permissions'; + +export default async (channel: DBChannel, _: any, ctx: GraphQLContext) => { + const { loaders, user: currentUser } = ctx; + const { id, isPrivate } = channel; + + if (isPrivate) { + if (!(await canViewChannel(currentUser, id, loaders))) return null; + } -export default ({ id }: DBChannel, _: any, { loaders }: GraphQLContext) => { return getModeratorsInChannel(id).then(users => loaders.user.loadMany(users)); }; diff --git a/api/queries/channel/owners.js b/api/queries/channel/owners.js index 8098118d9a..f42a95250c 100644 --- a/api/queries/channel/owners.js +++ b/api/queries/channel/owners.js @@ -2,7 +2,15 @@ import type { GraphQLContext } from '../../'; import type { DBChannel } from 'shared/types'; import { getOwnersInChannel } from '../../models/usersChannels'; +import { canViewChannel } from '../../utils/permissions'; + +export default async (channel: DBChannel, _: any, ctx: GraphQLContext) => { + const { id, isPrivate } = channel; + const { loaders, user: currentUser } = ctx; + + if (isPrivate) { + if (!(await canViewChannel(currentUser, id, loaders))) return null; + } -export default ({ id }: DBChannel, _: any, { loaders }: GraphQLContext) => { return getOwnersInChannel(id).then(users => loaders.user.loadMany(users)); }; diff --git a/api/queries/channel/pendingUsers.js b/api/queries/channel/pendingUsers.js index 6030bf3584..8c4ea0a844 100644 --- a/api/queries/channel/pendingUsers.js +++ b/api/queries/channel/pendingUsers.js @@ -1,16 +1,27 @@ // @flow import type { GraphQLContext } from '../../'; import type { DBChannel } from 'shared/types'; +import UserError from '../../utils/UserError'; +import { + isAuthedResolver as requireAuth, + canModerateChannel, +} from '../../utils/permissions'; -export default ({ id }: DBChannel, _: any, { loaders }: GraphQLContext) => { - return loaders.channelPendingUsers - .load(id) - .then(res => { - if (!res || res.length === 0) return []; - return res.reduction.map(rec => rec.userId); - }) - .then(users => { - if (!users || users.length === 0) return []; - return loaders.user.loadMany(users); - }); -}; +export default requireAuth( + async ({ id }: DBChannel, _: any, { user, loaders }: GraphQLContext) => { + if (!await canModerateChannel(user.id, id, loaders)) { + return new UserError('You don’t have permission to manage this channel'); + } + + return loaders.channelPendingUsers + .load(id) + .then(res => { + if (!res || res.length === 0) return []; + return res.reduction.map(rec => rec.userId); + }) + .then(users => { + if (!users || users.length === 0) return []; + return loaders.user.loadMany(users); + }); + } +); diff --git a/api/queries/channel/rootChannel.js b/api/queries/channel/rootChannel.js index a3cf5db4a4..854c96e9e7 100644 --- a/api/queries/channel/rootChannel.js +++ b/api/queries/channel/rootChannel.js @@ -1,11 +1,26 @@ // @flow import type { GraphQLContext } from '../../'; import type { GetChannelArgs } from '../../models/channel'; +import UserError from '../../utils/UserError'; import { getChannelBySlug } from '../../models/channel'; +import { canViewChannel } from '../../utils/permissions'; -export default (_: any, args: GetChannelArgs, { loaders }: GraphQLContext) => { - if (args.id) return loaders.channel.load(args.id); - if (args.channelSlug && args.communitySlug) - return getChannelBySlug(args.channelSlug, args.communitySlug); - return null; +export default async (_: any, args: GetChannelArgs, ctx: GraphQLContext) => { + const { loaders, user: currentUser } = ctx; + + if (args.id) { + if (!(await canViewChannel(currentUser, args.id, loaders))) return null; + return await loaders.channel.load(args.id); + } + + if (args.channelSlug && args.communitySlug) { + const channel = await getChannelBySlug( + args.channelSlug, + args.communitySlug + ); + if (!channel) return null; + if (!(await canViewChannel(currentUser, channel.id, loaders))) return null; + return channel; + } + return new UserError('We couldn’t find this channel'); }; diff --git a/api/queries/channel/slackSettings.js b/api/queries/channel/slackSettings.js new file mode 100644 index 0000000000..388d8dfdfb --- /dev/null +++ b/api/queries/channel/slackSettings.js @@ -0,0 +1,18 @@ +// @flow +import type { DBChannel } from 'shared/types'; +import type { GraphQLContext } from '../../'; +import UserError from '../../utils/UserError'; +import { + canModerateChannel, + isAuthedResolver as requireAuth, +} from '../../utils/permissions'; + +export default requireAuth( + async ({ id }: DBChannel, _: any, { loaders, user }: GraphQLContext) => { + if (!await canModerateChannel(user.id, id, loaders)) { + return new UserError('You don’t have permission to manage this channel'); + } + + return await loaders.channelSettings.load(id); + } +); diff --git a/api/queries/channel/threadConnection.js b/api/queries/channel/threadConnection.js index a8fe81988f..d77c8de056 100644 --- a/api/queries/channel/threadConnection.js +++ b/api/queries/channel/threadConnection.js @@ -1,12 +1,23 @@ // @flow +import type { GraphQLContext } from '../../'; import type { PaginationOptions } from '../../utils/paginate-arrays'; import { getThreadsByChannel } from '../../models/thread'; import { encode, decode } from '../../utils/base64'; +import { canViewChannel } from '../../utils/permissions'; +import type { DBChannel } from 'shared/types'; -export default ( - { id }: { id: string }, - { first, after }: PaginationOptions +export default async ( + channel: DBChannel, + { first, after }: PaginationOptions, + ctx: GraphQLContext ) => { + const { id, isPrivate } = channel; + const { loaders, user: currentUser } = ctx; + + if (isPrivate) { + if (!(await canViewChannel(currentUser, id, loaders))) return null; + } + // $FlowFixMe return getThreadsByChannel(id, { first, diff --git a/api/queries/channelSlackSettings/botLinks.js b/api/queries/channelSlackSettings/botLinks.js new file mode 100644 index 0000000000..f336879e98 --- /dev/null +++ b/api/queries/channelSlackSettings/botLinks.js @@ -0,0 +1,11 @@ +// @flow +import type { DBChannelSettings } from 'shared/types'; +import type { GraphQLContext } from '../../'; + +export default async ( + { slackSettings }: DBChannelSettings, + _: any, + { loaders }: GraphQLContext +) => { + return slackSettings ? slackSettings.botLinks : null; +}; diff --git a/api/queries/channelSlackSettings/index.js b/api/queries/channelSlackSettings/index.js new file mode 100644 index 0000000000..8d90b5ad75 --- /dev/null +++ b/api/queries/channelSlackSettings/index.js @@ -0,0 +1,8 @@ +// @flow +import botLinks from './botLinks'; + +module.exports = { + ChannelSlackSettings: { + botLinks, + }, +}; diff --git a/api/queries/community/billingSettings.js b/api/queries/community/billingSettings.js deleted file mode 100644 index 03bc93b5f4..0000000000 --- a/api/queries/community/billingSettings.js +++ /dev/null @@ -1,58 +0,0 @@ -// @flow -import { getInvoicesByCustomerId } from '../../models/stripeInvoices'; -import { StripeUtil } from 'shared/stripe/utils'; -import type { GraphQLContext } from '../..'; -import type { DBCommunity } from 'shared/types'; - -export default async ( - { - id, - pendingAdministratorEmail, - administratorEmail, - stripeCustomerId, - }: DBCommunity, - _: any, - { user, loaders }: GraphQLContext -) => { - const defaultResult = { - pendingAdministratorEmail, - administratorEmail, - sources: [], - invoices: [], - subscriptions: [], - }; - - if (!stripeCustomerId) return defaultResult; - - const [permissions, stripeCustomer] = await Promise.all([ - loaders.userPermissionsInCommunity.load([user.id, id]), - loaders.stripeCustomers.load(stripeCustomerId), - ]); - - const { isOwner, isModerator } = permissions; - const customer = - stripeCustomer && stripeCustomer.reduction.length > 0 - ? stripeCustomer.reduction[0] - : null; - const sources = - (isOwner || isModerator) && customer - ? await StripeUtil.getSources(customer) - : []; - const invoices = - isOwner && customer ? await getInvoicesByCustomerId(stripeCustomerId) : []; - const cleanInvoices = StripeUtil.cleanInvoices(invoices); - let subscriptions = - isOwner && customer ? await StripeUtil.cleanSubscriptions(customer) : []; - subscriptions = - subscriptions.length > 0 - ? subscriptions.filter(sub => sub && sub.items.length >= 1) - : subscriptions; - - return { - pendingAdministratorEmail: isOwner ? pendingAdministratorEmail : null, - administratorEmail: isOwner ? administratorEmail : null, - sources: sources, - invoices: cleanInvoices, - subscriptions: subscriptions, - }; -}; diff --git a/api/queries/community/brandedLogin.js b/api/queries/community/brandedLogin.js index 35c11f2066..1ffd3e429d 100644 --- a/api/queries/community/brandedLogin.js +++ b/api/queries/community/brandedLogin.js @@ -1,6 +1,5 @@ // @flow import type { DBCommunity } from 'shared/types'; -import { getCommunitySettings } from '../../models/communitySettings'; import type { GraphQLContext } from '../../'; export default async ( @@ -9,6 +8,7 @@ export default async ( { loaders }: GraphQLContext ) => { return await loaders.communitySettings.load(id).then(settings => { + if (!settings) return { isEnabled: null, message: null }; return settings.brandedLogin; }); }; diff --git a/api/queries/community/channelConnection.js b/api/queries/community/channelConnection.js index b2401579d0..43903355c6 100644 --- a/api/queries/community/channelConnection.js +++ b/api/queries/community/channelConnection.js @@ -1,14 +1,29 @@ // @flow +import type { GraphQLContext } from '../../'; import type { DBCommunity } from 'shared/types'; import { getChannelsByCommunity } from '../../models/channel'; +import { canViewCommunity } from '../../utils/permissions'; -export default ({ id }: DBCommunity) => ({ - pageInfo: { - hasNextPage: false, - }, - edges: getChannelsByCommunity(id).then(channels => - channels.map(channel => ({ - node: channel, - })) - ), -}); +export default async ({ id }: DBCommunity, _: any, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + + if (!await canViewCommunity(user, id, loaders)) { + return { + pageInfo: { + hasNextPage: false, + }, + edges: [], + }; + } + + return { + pageInfo: { + hasNextPage: false, + }, + edges: getChannelsByCommunity(id).then(channels => + channels.map(channel => ({ + node: channel, + })) + ), + }; +}; diff --git a/api/queries/community/communityPermissions.js b/api/queries/community/communityPermissions.js index 4bc3705834..7a481f4e60 100644 --- a/api/queries/community/communityPermissions.js +++ b/api/queries/community/communityPermissions.js @@ -1,14 +1,31 @@ // @flow import type { DBCommunity } from 'shared/types'; import type { GraphQLContext } from '../../'; +import { DEFAULT_USER_COMMUNITY_PERMISSIONS } from '../../models/usersCommunities'; -export default ( +export default async ( { id }: DBCommunity, _: any, { user, loaders }: GraphQLContext ) => { - if (!id || !user) return {}; - return loaders.userPermissionsInCommunity - .load([user.id, id]) - .then(result => (result ? result : {})); + const defaultPermissions = { + ...DEFAULT_USER_COMMUNITY_PERMISSIONS, + userId: null, + communityId: null, + }; + + if (!id || !user) return defaultPermissions; + + const permissions = await loaders.userPermissionsInCommunity.load([ + user.id, + id, + ]); + + const fallbackPermissions = { + ...defaultPermissions, + userId: user.id, + communityId: id, + }; + + return permissions || fallbackPermissions; }; diff --git a/api/queries/community/conversationGrowth.js b/api/queries/community/conversationGrowth.js index 5a6371d20d..fef6b4b5cd 100644 --- a/api/queries/community/conversationGrowth.js +++ b/api/queries/community/conversationGrowth.js @@ -2,6 +2,7 @@ import type { DBCommunity } from 'shared/types'; import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; +import { canModerateCommunity } from '../../utils/permissions'; const { getThreadCount, getCommunityGrowth, @@ -18,36 +19,28 @@ export default async ( return new UserError('You must be signed in to continue.'); } - const { isOwner } = await loaders.userPermissionsInCommunity.load([ - currentUser.id, - id, - ]); - - if (!isOwner) { + if (!(await canModerateCommunity(currentUser.id, id, loaders))) { return new UserError( - 'You must be the owner of this community to view analytics.' + 'You must be a team member to view community analytics.' ); } + const [ + count, + weeklyGrowth, + monthlyGrowth, + quarterlyGrowth, + ] = await Promise.all([ + getThreadCount(id), + getCommunityGrowth('threads', 'weekly', 'createdAt', id), + getCommunityGrowth('threads', 'monthly', 'createdAt', id), + getCommunityGrowth('threads', 'quarterly', 'createdAt', id), + ]); + return { - count: await getThreadCount(id), - weeklyGrowth: await getCommunityGrowth( - 'threads', - 'weekly', - 'createdAt', - id - ), - monthlyGrowth: await getCommunityGrowth( - 'threads', - 'monthly', - 'createdAt', - id - ), - quarterlyGrowth: await getCommunityGrowth( - 'threads', - 'quarterly', - 'createdAt', - id - ), + count, + weeklyGrowth, + monthlyGrowth, + quarterlyGrowth, }; }; diff --git a/api/queries/community/coverPhoto.js b/api/queries/community/coverPhoto.js new file mode 100644 index 0000000000..10297b7b7a --- /dev/null +++ b/api/queries/community/coverPhoto.js @@ -0,0 +1,9 @@ +// @flow +import type { GraphQLContext } from '../../'; +import type { DBCommunity } from 'shared/types'; +import { signCommunity } from 'shared/imgix'; + +export default (community: DBCommunity, _: any, ctx: GraphQLContext) => { + const { coverPhoto } = signCommunity(community); + return coverPhoto; +}; diff --git a/api/queries/community/hasChargeableSource.js b/api/queries/community/hasChargeableSource.js deleted file mode 100644 index 398595eace..0000000000 --- a/api/queries/community/hasChargeableSource.js +++ /dev/null @@ -1,25 +0,0 @@ -// @flow -import type { GraphQLContext } from '../..'; -import type { DBCommunity } from 'shared/types'; - -export default async ( - { id, stripeCustomerId }: DBCommunity, - _: any, - { user, loaders }: GraphQLContext -) => { - if (!stripeCustomerId) return false; - - const { - isOwner, - isModerator, - } = await loaders.userPermissionsInCommunity.load([user.id, id]); - - if (!isOwner && !isModerator) return null; - return loaders.stripeCustomers.load(stripeCustomerId).then(results => { - const customers = results && results.reduction; - if (!customers || customers.length === 0) return false; - const customerToEvaluate = customers[0]; - const sources = customerToEvaluate.sources.data; - return sources.some(source => source.status === 'chargeable'); - }); -}; diff --git a/api/queries/community/hasFeatures.js b/api/queries/community/hasFeatures.js deleted file mode 100644 index 8ca9e4cbe4..0000000000 --- a/api/queries/community/hasFeatures.js +++ /dev/null @@ -1,50 +0,0 @@ -// @flow -const debug = require('debug')('api:queries:community:has-features'); -import type { GraphQLContext } from '../..'; -import type { DBCommunity } from 'shared/types'; -import UserError from '../../utils/UserError'; -import { StripeUtil } from 'shared/stripe/utils'; -import { COMMUNITY_ANALYTICS } from 'pluto/queues/constants'; - -export default async ( - { id }: DBCommunity, - _: any, - { user }: GraphQLContext -) => { - const defaultFeatures = { - analytics: false, - prioritySupport: false, - }; - - const currentUser = user; - - if (!currentUser) { - return defaultFeatures; - } - - const { customer, community } = await StripeUtil.jobPreflight(id); - - if (!community) { - debug('Error getting community in preflight'); - return defaultFeatures; - } - - if (!customer) { - debug('Error creating customer in preflight'); - return defaultFeatures; - } - - const hasAnalytics = await StripeUtil.hasSubscriptionItemOfType( - customer, - COMMUNITY_ANALYTICS - ); - const hasPrioritySupport = await StripeUtil.hasSubscriptionItemOfType( - customer, - 'priority-support' - ); - - return { - analytics: hasAnalytics, - prioritySupport: hasPrioritySupport, - }; -}; diff --git a/api/queries/community/index.js b/api/queries/community/index.js index 55884c0f98..f20d1d63d9 100644 --- a/api/queries/community/index.js +++ b/api/queries/community/index.js @@ -15,19 +15,32 @@ import pinnedThread from './pinnedThread'; import threadConnection from './threadConnection'; import metaData from './metaData'; import slackImport from './slackImport'; -import invoices from './invoices'; -import recurringPayments from './recurringPayments'; import memberGrowth from './memberGrowth'; import conversationGrowth from './conversationGrowth'; import topMembers from './topMembers'; import topAndNewThreads from './topAndNewThreads'; -import isPro from './isPro'; import contextPermissions from './contextPermissions'; import watercooler from './watercooler'; -import billingSettings from './billingSettings'; -import hasChargeableSource from './hasChargeableSource'; -import hasFeatures from './hasFeatures'; import brandedLogin from './brandedLogin'; +import slackSettings from './slackSettings'; +import joinSettings from './joinSettings'; +import coverPhoto from './coverPhoto'; +import profilePhoto from './profilePhoto'; + +// no-op resolvers to transition while removing payments +import type { DBCommunity } from 'shared/types'; +const isPro = () => false; +const recurringPayments = () => []; +const invoices = () => []; +const billingSettings = (community: DBCommunity) => ({ + pendingAdministratorEmail: null, + administratorEmail: community.administratorEmail, + sources: [], + invoices: [], + subscriptions: [], +}); +const hasChargeableSource = () => false; +const hasFeatures = () => ({ analytics: true, prioritySupport: true }); module.exports = { Query: { @@ -47,18 +60,23 @@ module.exports = { threadConnection, metaData, slackImport, - invoices, - recurringPayments, memberGrowth, conversationGrowth, topMembers, topAndNewThreads, - isPro, contextPermissions, watercooler, + brandedLogin, + slackSettings, + joinSettings, + coverPhoto, + profilePhoto, + + invoices, + recurringPayments, + isPro, billingSettings, hasChargeableSource, hasFeatures, - brandedLogin, }, }; diff --git a/api/queries/community/invoices.js b/api/queries/community/invoices.js deleted file mode 100644 index 704415cb8a..0000000000 --- a/api/queries/community/invoices.js +++ /dev/null @@ -1,13 +0,0 @@ -// @flow -import type { DBCommunity } from 'shared/types'; -import type { GraphQLContext } from '../../'; -import { getInvoicesByCommunity } from '../../models/invoice'; -import UserError from '../../utils/UserError'; - -export default ({ id }: DBCommunity, _: any, { user }: GraphQLContext) => { - const currentUser = user; - if (!currentUser) - return new UserError('You must be logged in to view community settings.'); - - return getInvoicesByCommunity(id); -}; diff --git a/api/queries/community/isPro.js b/api/queries/community/isPro.js deleted file mode 100644 index 3f849a3b9d..0000000000 --- a/api/queries/community/isPro.js +++ /dev/null @@ -1,13 +0,0 @@ -// @flow -import type { DBCommunity } from 'shared/types'; -import type { GraphQLContext } from '../../'; - -export default ({ id }: DBCommunity, _: any, { loaders }: GraphQLContext) => { - return loaders.communityRecurringPayments.load(id).then(res => { - const subs = res && res.reduction; - if (!subs || subs.length === 0) return false; - if (!Array.isArray(subs)) return subs.status === 'active'; - - return subs.some(sub => sub.status === 'active'); - }); -}; diff --git a/api/queries/community/joinSettings.js b/api/queries/community/joinSettings.js new file mode 100644 index 0000000000..ca8b57e976 --- /dev/null +++ b/api/queries/community/joinSettings.js @@ -0,0 +1,18 @@ +// @flow +import type { DBCommunity } from 'shared/types'; +import type { GraphQLContext } from '../../'; +import { canModerateCommunity } from '../../utils/permissions'; + +export default async ({ id }: DBCommunity, _: any, ctx: GraphQLContext) => { + const { user: currentUser, loaders } = ctx; + + if (!currentUser) return null; + + if (!(await canModerateCommunity(currentUser.id, id, loaders))) { + return null; + } + + return loaders.communitySettings.load(id).then(settings => { + return settings.joinSettings; + }); +}; diff --git a/api/queries/community/memberConnection.js b/api/queries/community/memberConnection.js index dd3b40e000..01ca75f99e 100644 --- a/api/queries/community/memberConnection.js +++ b/api/queries/community/memberConnection.js @@ -9,6 +9,7 @@ import type { GraphQLContext } from '../../'; import type { PaginationOptions } from '../../utils/paginate-arrays'; import { encode, decode } from '../../utils/base64'; const { getMembersInCommunity } = require('../../models/usersCommunities'); +import { canViewCommunity } from '../../utils/permissions'; type MemberConnectionFilterType = { isMember?: boolean, @@ -18,15 +19,26 @@ type MemberConnectionFilterType = { isBlocked?: boolean, }; -export default ( - { id }: DBCommunity, - { - first = 10, - after, - filter, - }: { ...$Exact, filter: MemberConnectionFilterType }, - { loaders }: GraphQLContext -) => { +type Args = { + ...$Exact, + filter: MemberConnectionFilterType, +}; + +export default async (root: DBCommunity, args: Args, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + const { id } = root; + + if (!await canViewCommunity(user, id, loaders)) { + return { + pageInfo: { + hasNextPage: false, + }, + edges: [], + }; + } + + const { first = 10, after, filter } = args; + const cursor = decode(after); // Get the index from the encoded cursor, asdf234gsdf-2 => ["-2", "2"] const lastDigits = cursor.match(/-(\d+)$/); diff --git a/api/queries/community/memberGrowth.js b/api/queries/community/memberGrowth.js index 528dd559eb..7c54a9142a 100644 --- a/api/queries/community/memberGrowth.js +++ b/api/queries/community/memberGrowth.js @@ -2,13 +2,11 @@ import type { DBCommunity } from 'shared/types'; import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; -const { - getMemberCount, - getCommunityGrowth, -} = require('../../models/community'); +const { getCommunityGrowth } = require('../../models/community'); +import { canModerateCommunity } from '../../utils/permissions'; export default async ( - { id }: DBCommunity, + { id, memberCount }: DBCommunity, _: any, { user, loaders }: GraphQLContext ) => { @@ -18,19 +16,14 @@ export default async ( return new UserError('You must be signed in to continue.'); } - const { isOwner } = await loaders.userPermissionsInCommunity.load([ - currentUser.id, - id, - ]); - - if (!isOwner) { + if (!(await canModerateCommunity(currentUser.id, id, loaders))) { return new UserError( - 'You must be the owner of this community to view analytics.' + 'You must be a team member to view community analytics.' ); } return { - count: await getMemberCount(id), + count: memberCount || 1, weeklyGrowth: await getCommunityGrowth( 'usersCommunities', 'weekly', diff --git a/api/queries/community/members.js b/api/queries/community/members.js index faa1861e57..ba7f916fa5 100644 --- a/api/queries/community/members.js +++ b/api/queries/community/members.js @@ -3,7 +3,15 @@ import type { DBCommunity } from 'shared/types'; import type { GraphQLContext } from '../../'; import type { PaginationOptions } from '../../utils/paginate-arrays'; import { encode, decode } from '../../utils/base64'; -const { getMembersInCommunity } = require('../../models/usersCommunities'); +import { canViewCommunity } from '../../utils/permissions'; +import { + getMembersInCommunity, + getOwnersInCommunity, + getModeratorsInCommunity, + getTeamMembersInCommunity, + getPendingUsersInCommunity, + getBlockedUsersInCommunity, +} from '../../models/usersCommunities'; type MembersFilterType = { isMember?: boolean, @@ -13,31 +21,47 @@ type MembersFilterType = { isBlocked?: boolean, }; -export default ( - { id }: DBCommunity, - { - first = 10, - after, - filter, - }: { ...$Exact, filter: MembersFilterType }, - { loaders }: GraphQLContext -) => { +type Args = { + ...$Exact, + filter?: MembersFilterType, +}; + +export default async (root: DBCommunity, args: Args, ctx: GraphQLContext) => { + const { id } = root; + const { user, loaders } = ctx; + + if (!(await canViewCommunity(user, id, loaders))) { + return { + pageInfo: { + hasNextPage: false, + }, + edges: [], + }; + } + + const { first = 10, after, filter = {} } = args; const cursor = decode(after); // Get the index from the encoded cursor, asdf234gsdf-2 => ["-2", "2"] const lastDigits = cursor.match(/-(\d+)$/); const lastUserIndex = - lastDigits && lastDigits.length > 0 && parseInt(lastDigits[1], 10); + (lastDigits && lastDigits.length > 0 && parseInt(lastDigits[1], 10)) || 0; - // Note @brian: this is a shitty hack, but if we want to show both - // moderators and admins in a single list, I need to tweak the inbound - // filter here - let dbfilter = filter; - if (filter && (filter.isOwner && filter.isModerator)) { - dbfilter = row => row('isModerator').or(row('isOwner')); + let query; + if (filter.isBlocked) { + query = getBlockedUsersInCommunity; + } else if (filter.isPending) { + query = getPendingUsersInCommunity; + } else if (filter.isModerator && filter.isOwner) { + query = getTeamMembersInCommunity; + } else if (filter.isModerator) { + query = getModeratorsInCommunity; + } else if (filter.isOwner) { + query = getOwnersInCommunity; + } else { + query = getMembersInCommunity; } - // $FlowFixMe - return getMembersInCommunity(id, { first, after: lastUserIndex }, dbfilter) + return query(id, { first, after: lastUserIndex }) .then(users => { const permissionsArray = users.map(userId => [userId, id]); // $FlowIssue diff --git a/api/queries/community/metaData.js b/api/queries/community/metaData.js index 4116433f4c..cf163c92c0 100644 --- a/api/queries/community/metaData.js +++ b/api/queries/community/metaData.js @@ -1,23 +1,83 @@ -// @flow +// TODO: Flow type again +import Raven from 'shared/raven'; import type { DBCommunity } from 'shared/types'; import type { GraphQLContext } from '../../'; +import { canViewCommunity } from '../../utils/permissions'; +import cache from 'shared/cache/redis'; +import { + communityOnlineMemberCount, + communityChannelCount, +} from 'shared/graphql-cache-keys'; -type MemberOrChannelCount = { - reduction?: number, -}; +export default async (root: DBCommunity, _: any, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + const { id, memberCount: rootMemberCount } = root; + + if (!(await canViewCommunity(user, id, loaders))) { + return { + channels: 0, + members: 0, + onlineMembers: 0, + }; + } + + const [cachedChannelCount, cachedOnlineMemberCount] = await Promise.all([ + cache.get(communityChannelCount(id)), + cache.get(communityOnlineMemberCount(id)), + ]); + + const getDbCommunityChannelCount = async () => { + const count = await loaders.communityChannelCount + .load(id) + .then(res => (res && res.reduction) || 0); + + await cache.set(communityChannelCount(id), count, 'ex', 3600); + + return count; + }; + + const getDbCommunityOnlineMemberCount = async () => { + const count = await loaders.communityOnlineMemberCount + .load(id) + .then(res => (res && res.reduction) || 0); + + await cache.set(communityOnlineMemberCount(id), count, 'ex', 3600); + + return count; + }; + + const getDbRootMemberCount = async () => { + const count = await loaders.communityMemberCount + .load(id) + .then(res => (res && res.reduction) || 0); + + await Raven.captureException( + new Error( + `Community with ID "${id}" does not have denormalized memberCount.` + ) + ); + + return count; + }; + + const returnedChannelCount = + typeof cachedChannelCount === 'string' + ? parseInt(cachedChannelCount, 10) + : await getDbCommunityChannelCount(); + + const returnedOnlineMemberCount = + typeof cachedOnlineMemberCount === 'string' + ? parseInt(cachedOnlineMemberCount, 10) + : await getDbCommunityOnlineMemberCount(); + + const returnedMemberCount = + typeof rootMemberCount === 'number' + ? rootMemberCount + : await getDbRootMemberCount(); -export default ({ id }: DBCommunity, _: any, { loaders }: GraphQLContext) => { - // $FlowIssue - return Promise.all([ - loaders.communityChannelCount.load(id), - loaders.communityMemberCount.load(id), - ]).then( - ([channelCount, memberCount]: [ - MemberOrChannelCount, - MemberOrChannelCount, - ]) => ({ - channels: channelCount ? channelCount.reduction : 0, - members: memberCount ? memberCount.reduction : 0, - }) - ); + return { + channels: returnedChannelCount, + members: returnedMemberCount, + onlineMembers: returnedOnlineMemberCount, + }; }; diff --git a/api/queries/community/pinnedThread.js b/api/queries/community/pinnedThread.js index 537b0eac9b..3cdf3c2a9f 100644 --- a/api/queries/community/pinnedThread.js +++ b/api/queries/community/pinnedThread.js @@ -1,14 +1,18 @@ // @flow +import type { GraphQLContext } from '../../'; import type { DBCommunity } from 'shared/types'; -import { getThreads } from '../../models/thread'; +import { getThreadById } from '../../models/thread'; +import { canViewCommunity } from '../../utils/permissions'; -export default async ({ pinnedThreadId }: DBCommunity) => { - let pinnedThread; - if (pinnedThreadId) { - pinnedThread = await getThreads([pinnedThreadId]); +export default async (root: DBCommunity, _: any, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + const { pinnedThreadId, id } = root; + + if (!pinnedThreadId) return null; + + if (!await canViewCommunity(user, id, loaders)) { + return null; } - if (pinnedThread && Array.isArray(pinnedThread) && pinnedThread.length > 0) - return pinnedThread[0]; - return null; + return await getThreadById(pinnedThreadId); }; diff --git a/api/queries/community/profilePhoto.js b/api/queries/community/profilePhoto.js new file mode 100644 index 0000000000..dac9b86c5b --- /dev/null +++ b/api/queries/community/profilePhoto.js @@ -0,0 +1,9 @@ +// @flow +import type { GraphQLContext } from '../../'; +import type { DBCommunity } from 'shared/types'; +import { signCommunity } from 'shared/imgix'; + +export default (community: DBCommunity, _: any, ctx: GraphQLContext) => { + const { profilePhoto } = signCommunity(community); + return profilePhoto; +}; diff --git a/api/queries/community/recurringPayments.js b/api/queries/community/recurringPayments.js deleted file mode 100644 index 052c5dfdd6..0000000000 --- a/api/queries/community/recurringPayments.js +++ /dev/null @@ -1,42 +0,0 @@ -// @flow -import type { DBCommunity } from 'shared/types'; -import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; - -export default ( - { id }: DBCommunity, - _: any, - { user, loaders }: GraphQLContext -) => { - const currentUser = user; - - if (!currentUser) { - return new UserError('You must be signed in to continue.'); - } - - const queryRecurringPayments = async () => { - const userPermissions = await loaders.userPermissionsInCommunity.load([ - currentUser.id, - id, - ]); - if (!userPermissions.isOwner) return; - - const results = await loaders.communityRecurringPayments.load(id); - const rPayments = results && results.reduction; - - const communitySubscriptions = - rPayments && - rPayments.length > 0 && - rPayments.filter(obj => obj.planId === 'community-standard'); - - if (!communitySubscriptions || communitySubscriptions.length === 0) return; - return communitySubscriptions.map(subscription => ({ - amount: subscription.amount * subscription.quantity, - createdAt: subscription.createdAt, - plan: subscription.planName, - status: subscription.status, - })); - }; - - return queryRecurringPayments(); -}; diff --git a/api/queries/community/rootSearchCommunities.js b/api/queries/community/rootSearchCommunities.js index 33d00f7b93..599a146518 100644 --- a/api/queries/community/rootSearchCommunities.js +++ b/api/queries/community/rootSearchCommunities.js @@ -22,6 +22,6 @@ export default ( return loaders.community.loadMany(communityIds); }) .catch(err => { - console.log('err', err); + console.error('err', err); }); }; diff --git a/api/queries/community/rootSearchCommunityThreads.js b/api/queries/community/rootSearchCommunityThreads.js index e452a52496..cb79099ffb 100644 --- a/api/queries/community/rootSearchCommunityThreads.js +++ b/api/queries/community/rootSearchCommunityThreads.js @@ -33,7 +33,7 @@ export default async ( })); }) .catch(err => { - console.log('err', err); + console.error('err', err); }); const IS_AUTHED_USER = user && user.id; diff --git a/api/queries/community/slackSettings.js b/api/queries/community/slackSettings.js new file mode 100644 index 0000000000..fa809dc296 --- /dev/null +++ b/api/queries/community/slackSettings.js @@ -0,0 +1,18 @@ +// @flow +import type { DBCommunity } from 'shared/types'; +import type { GraphQLContext } from '../../'; +import UserError from '../../utils/UserError'; +import { + canModerateCommunity, + isAuthedResolver as requireAuth, +} from '../../utils/permissions'; + +export default requireAuth( + async ({ id }: DBCommunity, _: any, { user, loaders }: GraphQLContext) => { + if (!await canModerateCommunity(user.id, id, loaders)) { + return new UserError('You don’t have permission to manage this channel'); + } + + return await loaders.communitySettings.load(id); + } +); diff --git a/api/queries/community/threadConnection.js b/api/queries/community/threadConnection.js index 3577458618..d3aee98d81 100644 --- a/api/queries/community/threadConnection.js +++ b/api/queries/community/threadConnection.js @@ -8,17 +8,34 @@ import { getPublicChannelsByCommunity, } from '../../models/channel'; import { getThreadsByChannels } from '../../models/thread'; +import { canViewCommunity } from '../../utils/permissions'; + +export type CommunityThreadConnectionPaginationOptions = { + after: string, + first: number, + sort: 'latest' | 'trending', +}; + +// prettier-ignore +export default async (root: DBCommunity, args: CommunityThreadConnectionPaginationOptions, ctx: GraphQLContext) => { + const { first = 10, after, sort = 'latest' } = args + const { user, loaders } = ctx + const { id } = root + + if (!await canViewCommunity(user, id, loaders)) { + return { + pageInfo: { + hasNextPage: false, + }, + edges: [] + } + } -export default async ( - { id }: DBCommunity, - { first = 10, after }: PaginationOptions, - { user }: GraphQLContext -) => { const cursor = decode(after); // Get the index from the encoded cursor, asdf234gsdf-2 => ["-2", "2"] const lastDigits = cursor.match(/-(\d+)$/); const lastThreadIndex = - lastDigits && lastDigits.length > 0 && parseInt(lastDigits[1], 10); + lastDigits && lastDigits.length > 0 && parseInt(lastDigits[1], 10) || 0; const currentUser = user; // if the user is signed in, only return stories for the channels @@ -33,10 +50,10 @@ export default async ( channels = await getPublicChannelsByCommunity(id); } - // $FlowFixMe const threads = await getThreadsByChannels(channels, { first, after: lastThreadIndex, + sort, }); return { diff --git a/api/queries/community/topAndNewThreads.js b/api/queries/community/topAndNewThreads.js index aa16bacec2..f452349a1c 100644 --- a/api/queries/community/topAndNewThreads.js +++ b/api/queries/community/topAndNewThreads.js @@ -2,47 +2,60 @@ import type { DBCommunity } from 'shared/types'; import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; +import { canModerateCommunity } from '../../utils/permissions'; import { getMessageCount } from '../../models/message'; import { getThreads, getThreadsByCommunityInTimeframe, } from '../../models/thread'; -export default ({ id }: DBCommunity, __: any, { user }: GraphQLContext) => { +export default async ( + { id }: DBCommunity, + __: any, + { user, loaders }: GraphQLContext +) => { const currentUser = user; if (!currentUser) { return new UserError('You must be signed in to continue.'); } - return getThreadsByCommunityInTimeframe(id, 'weekly').then(async threads => { - if (!threads) return { topThreads: [], newThreads: [] }; - - const messageCountPromises = threads.map(async ({ id }) => ({ - id, - messageCount: await getMessageCount(id), - })); - - // promise all the active threads and message counts - const threadsWithMessageCounts = await Promise.all(messageCountPromises); - - const topThreads = threadsWithMessageCounts - .filter(t => t.messageCount > 0) - .sort((a, b) => { - const bc = parseInt(b.messageCount, 10); - const ac = parseInt(a.messageCount, 10); - return bc <= ac ? -1 : 1; - }) - .slice(0, 10) - .map(t => t.id); - - const newThreads = threadsWithMessageCounts - .filter(t => t.messageCount === 0) - .map(t => t.id); - - return { - topThreads: await getThreads([...topThreads]), - newThreads: await getThreads([...newThreads]), - }; - }); + if (!(await canModerateCommunity(currentUser.id, id, loaders))) { + return new UserError( + 'You must be a team member to view community analytics.' + ); + } + + const threads = await getThreadsByCommunityInTimeframe(id, 'weekly'); + + if (!threads || threads.length === 0) + return { topThreads: [], newThreads: [] }; + + const threadsWithMessageCounts = await Promise.all( + threads.map(({ id }) => + getMessageCount(id).then(messageCount => ({ + id, + messageCount, + })) + ) + ); + + const topThreads = threadsWithMessageCounts + .filter(t => t.messageCount > 0) + .sort((a, b) => { + const bc = parseInt(b.messageCount, 10); + const ac = parseInt(a.messageCount, 10); + return bc <= ac ? -1 : 1; + }) + .slice(0, 10) + .map(t => t.id); + + const newThreads = threadsWithMessageCounts + .filter(t => t.messageCount === 0) + .map(t => t.id); + + return { + topThreads: await getThreads([...topThreads]), + newThreads: await getThreads([...newThreads]), + }; }; diff --git a/api/queries/community/topMembers.js b/api/queries/community/topMembers.js index c2bedf3ec0..79b0b21067 100644 --- a/api/queries/community/topMembers.js +++ b/api/queries/community/topMembers.js @@ -2,11 +2,12 @@ import type { DBCommunity } from 'shared/types'; import type { GraphQLContext } from '../../'; import UserError from '../../utils/UserError'; +import { canModerateCommunity } from '../../utils/permissions'; const { getTopMembersInCommunity } = require('../../models/reputationEvents'); export default async ( { id }: DBCommunity, - __: any, + _: any, { user, loaders }: GraphQLContext ) => { const currentUser = user; @@ -15,19 +16,19 @@ export default async ( return new UserError('You must be signed in to continue.'); } - const { isOwner } = await loaders.userPermissionsInCommunity.load([ - currentUser.id, - id, - ]); - - if (!isOwner) { + if (!(await canModerateCommunity(currentUser.id, id, loaders))) { return new UserError( - 'You must be the owner of this community to view analytics.' + 'You must be a team member to view community analytics.' ); } - return getTopMembersInCommunity(id).then(users => { - if (!users) return []; - return loaders.user.loadMany(users); - }); + // $FlowFixMe + const userIds = await getTopMembersInCommunity(id); + + if (!userIds || userIds.length === 0) { + return []; + } + + const permissionsArray = userIds.map(userId => [userId, id]); + return loaders.userPermissionsInCommunity.loadMany(permissionsArray); }; diff --git a/api/queries/community/watercooler.js b/api/queries/community/watercooler.js index 0ef929d218..50a4ef73c4 100644 --- a/api/queries/community/watercooler.js +++ b/api/queries/community/watercooler.js @@ -1,9 +1,18 @@ // @flow +import type { GraphQLContext } from '../../'; import type { DBCommunity } from 'shared/types'; import { getThreads } from '../../models/thread'; +import { canViewCommunity } from '../../utils/permissions'; -export default async ({ watercoolerId }: DBCommunity) => { +export default async (root: DBCommunity, _: any, ctx: GraphQLContext) => { + const { watercoolerId, id } = root; + const { user, loaders } = ctx; if (!watercoolerId) return null; + + if (!await canViewCommunity(user, id, loaders)) { + return null; + } + return await getThreads([watercoolerId]).then( res => (res && res.length > 0 ? res[0] : null) ); diff --git a/api/queries/communityMember/roles.js b/api/queries/communityMember/roles.js index 23124d9636..909c74e6d9 100644 --- a/api/queries/communityMember/roles.js +++ b/api/queries/communityMember/roles.js @@ -1,10 +1,16 @@ // @flow import type { DBUsersCommunities } from 'shared/types'; -export default ({ isModerator, isOwner, isBlocked }: DBUsersCommunities) => { +export default ({ + isModerator, + isOwner, + isBlocked, + isPending, +}: DBUsersCommunities) => { const roles = []; if (isModerator) roles.push('moderator'); if (isOwner) roles.push('admin'); if (isBlocked) roles.push('blocked'); + if (isPending) roles.push('pending'); return roles; }; diff --git a/api/queries/communitySlackSettings/hasSentInvites.js b/api/queries/communitySlackSettings/hasSentInvites.js new file mode 100644 index 0000000000..f99028daec --- /dev/null +++ b/api/queries/communitySlackSettings/hasSentInvites.js @@ -0,0 +1,11 @@ +// @flow +import type { DBCommunitySettings } from 'shared/types'; +import type { GraphQLContext } from '../../'; + +export default async ( + { slackSettings }: DBCommunitySettings, + _: any, + { loaders }: GraphQLContext +) => { + return slackSettings ? (slackSettings.invitesSentAt ? true : false) : false; +}; diff --git a/api/queries/communitySlackSettings/index.js b/api/queries/communitySlackSettings/index.js new file mode 100644 index 0000000000..6081b8e05b --- /dev/null +++ b/api/queries/communitySlackSettings/index.js @@ -0,0 +1,18 @@ +// @flow +import isConnected from './isConnected'; +import teamName from './teamName'; +import hasSentInvites from './hasSentInvites'; +import slackChannelList from './slackChannelList'; +import memberCount from './memberCount'; +import invitesSentAt from './invitesSentAt'; + +module.exports = { + CommunitySlackSettings: { + teamName, + isConnected, + hasSentInvites, + slackChannelList, + memberCount, + invitesSentAt, + }, +}; diff --git a/api/queries/communitySlackSettings/invitesSentAt.js b/api/queries/communitySlackSettings/invitesSentAt.js new file mode 100644 index 0000000000..a487a45c0f --- /dev/null +++ b/api/queries/communitySlackSettings/invitesSentAt.js @@ -0,0 +1,11 @@ +// @flow +import type { DBCommunitySettings } from 'shared/types'; +import type { GraphQLContext } from '../../'; + +export default async ( + { slackSettings }: DBCommunitySettings, + _: any, + { loaders }: GraphQLContext +) => { + return slackSettings ? slackSettings.invitesSentAt : null; +}; diff --git a/api/queries/communitySlackSettings/isConnected.js b/api/queries/communitySlackSettings/isConnected.js new file mode 100644 index 0000000000..a5f2e00129 --- /dev/null +++ b/api/queries/communitySlackSettings/isConnected.js @@ -0,0 +1,11 @@ +// @flow +import type { DBCommunitySettings } from 'shared/types'; +import type { GraphQLContext } from '../../'; + +export default async ( + { slackSettings }: DBCommunitySettings, + _: any, + { loaders }: GraphQLContext +) => { + return slackSettings ? (slackSettings.connectedAt ? true : false) : false; +}; diff --git a/api/queries/communitySlackSettings/memberCount.js b/api/queries/communitySlackSettings/memberCount.js new file mode 100644 index 0000000000..8de4e7ae54 --- /dev/null +++ b/api/queries/communitySlackSettings/memberCount.js @@ -0,0 +1,11 @@ +// @flow +import type { DBCommunitySettings } from 'shared/types'; +import type { GraphQLContext } from '../../'; + +export default async ( + { slackSettings }: DBCommunitySettings, + _: any, + { loaders }: GraphQLContext +) => { + return slackSettings ? slackSettings.invitesMemberCount : null; +}; diff --git a/api/queries/communitySlackSettings/slackChannelList.js b/api/queries/communitySlackSettings/slackChannelList.js new file mode 100644 index 0000000000..4e45ec220a --- /dev/null +++ b/api/queries/communitySlackSettings/slackChannelList.js @@ -0,0 +1,22 @@ +// @flow +import type { DBCommunitySettings } from 'shared/types'; +import type { GraphQLContext } from '../../'; +import { + getSlackPublicChannelList, + getSlackPrivateChannelList, +} from '../../models/communitySettings'; + +export default async ( + { slackSettings, communityId }: DBCommunitySettings, + _: any, + { loaders }: GraphQLContext +) => { + if (!slackSettings || !slackSettings.token) return []; + + const [publicChannelList, privateChannelList] = await Promise.all([ + getSlackPublicChannelList(communityId, slackSettings.token), + getSlackPrivateChannelList(communityId, slackSettings.token), + ]); + + return [...publicChannelList, ...privateChannelList]; +}; diff --git a/api/queries/communitySlackSettings/teamName.js b/api/queries/communitySlackSettings/teamName.js new file mode 100644 index 0000000000..ab0bc356c2 --- /dev/null +++ b/api/queries/communitySlackSettings/teamName.js @@ -0,0 +1,14 @@ +// @flow +import type { DBCommunitySettings } from 'shared/types'; +import type { GraphQLContext } from '../../'; +import { decryptString } from 'shared/encryption'; + +export default async ( + { slackSettings }: DBCommunitySettings, + _: any, + { loaders }: GraphQLContext +) => { + return slackSettings && slackSettings.teamName + ? decryptString(slackSettings.teamName) + : null; +}; diff --git a/api/queries/directMessageThread/index.js b/api/queries/directMessageThread/index.js index 725bef87b8..0fd447678b 100644 --- a/api/queries/directMessageThread/index.js +++ b/api/queries/directMessageThread/index.js @@ -1,5 +1,6 @@ // @flow import directMessageThread from './rootDirectMessageThread'; +import directMessageThreadByUserId from './rootDirectMessageThreadByUserId'; import messageConnection from './messageConnection'; import participants from './participants'; import snippet from './snippet'; @@ -7,6 +8,7 @@ import snippet from './snippet'; module.exports = { Query: { directMessageThread, + directMessageThreadByUserId, }, DirectMessageThread: { messageConnection, diff --git a/api/queries/directMessageThread/messageConnection.js b/api/queries/directMessageThread/messageConnection.js index c3af11c419..a59bb12e16 100644 --- a/api/queries/directMessageThread/messageConnection.js +++ b/api/queries/directMessageThread/messageConnection.js @@ -1,7 +1,7 @@ // @flow import type { PaginationOptions } from '../../utils/paginate-arrays'; import type { GraphQLContext } from '../../'; -import { canViewDMThread } from './utils'; +import { canViewDMThread } from '../../utils/permissions'; import { encode, decode } from '../../utils/base64'; import { getMessages } from '../../models/message'; @@ -12,7 +12,7 @@ export default async ( ) => { if (!user || !user.id) return null; - const canViewThread = await canViewDMThread(id, user.id, { loaders }); + const canViewThread = await canViewDMThread(user.id, id, loaders); if (!canViewThread) return null; const cursor = parseInt(decode(after), 10); diff --git a/api/queries/directMessageThread/participants.js b/api/queries/directMessageThread/participants.js index ab87570d92..ca4cd4d913 100644 --- a/api/queries/directMessageThread/participants.js +++ b/api/queries/directMessageThread/participants.js @@ -1,20 +1,20 @@ // @flow import type { GraphQLContext } from '../../'; -import { canViewDMThread } from './utils'; +import { canViewDMThread } from '../../utils/permissions'; +import { signUser } from 'shared/imgix'; -export default async ( - { id }: { id: string }, - _: any, - { loaders, user }: GraphQLContext -) => { - if (!user || !user.id) return null; +export default async ({ id }: { id: string }, _: any, ctx: GraphQLContext) => { + const { loaders, user } = ctx; + if (!user || !user.id) return []; - const canViewThread = await canViewDMThread(id, user.id, { loaders }); + const canViewThread = await canViewDMThread(user.id, id, loaders); - if (!canViewThread) return null; + if (!canViewThread) return []; return loaders.directMessageParticipants.load(id).then(results => { - if (!results || results.length === 0) return null; - return results.reduction; + if (!results || results.length === 0) return []; + return results.reduction.map(user => { + return signUser(user); + }); }); }; diff --git a/api/queries/directMessageThread/rootDirectMessageThread.js b/api/queries/directMessageThread/rootDirectMessageThread.js index b4254a3249..a48c3fd040 100644 --- a/api/queries/directMessageThread/rootDirectMessageThread.js +++ b/api/queries/directMessageThread/rootDirectMessageThread.js @@ -1,6 +1,6 @@ // @flow import type { GraphQLContext } from '../../'; -import { canViewDMThread } from './utils'; +import { canViewDMThread } from '../../utils/permissions'; export default async ( _: any, @@ -10,7 +10,7 @@ export default async ( // signed out users should never be able to request a dm thread if (!user || !user.id) return null; - const canViewThread = await canViewDMThread(id, user.id, { loaders }); + const canViewThread = await canViewDMThread(user.id, id, loaders); if (!canViewThread) return null; diff --git a/api/queries/directMessageThread/rootDirectMessageThreadByUserId.js b/api/queries/directMessageThread/rootDirectMessageThreadByUserId.js new file mode 100644 index 0000000000..ff375050e6 --- /dev/null +++ b/api/queries/directMessageThread/rootDirectMessageThreadByUserId.js @@ -0,0 +1,21 @@ +// @flow +import type { GraphQLContext } from '../../'; +import { checkForExistingDMThread } from '../../models/directMessageThread'; +import { isAuthedResolver as requireAuth } from '../../utils/permissions'; + +type Args = { + userId: string, +}; + +export default requireAuth(async (_: any, args: Args, ctx: GraphQLContext) => { + // signed out users will never be able to view a dm thread + const { user: currentUser, loaders } = ctx; + const { userId } = args; + + const allMemberIds = [userId, currentUser.id]; + const existingThread = await checkForExistingDMThread(allMemberIds); + + if (!existingThread) return null; + + return loaders.directMessageThread.load(existingThread); +}); diff --git a/api/queries/directMessageThread/snippet.js b/api/queries/directMessageThread/snippet.js index bfe5e95b72..d0c907539d 100644 --- a/api/queries/directMessageThread/snippet.js +++ b/api/queries/directMessageThread/snippet.js @@ -1,7 +1,7 @@ // @flow import type { GraphQLContext } from '../../'; -import { canViewDMThread } from './utils'; +import { canViewDMThread } from '../../utils/permissions'; import { toPlainText, toState } from 'shared/draft-utils'; export default async ( @@ -11,12 +11,12 @@ export default async ( ) => { if (!user || !user.id) return null; - const canViewThread = await canViewDMThread(id, user.id, { loaders }); + const canViewThread = await canViewDMThread(user.id, id, loaders); if (!canViewThread) return null; - return loaders.directMessageSnippet.load(id).then(results => { - if (!results) return 'No messages yet...'; - const message = results.reduction; + return loaders.directMessageSnippet.load(id).then(message => { + if (!message) return 'No messages yet...'; + if (message.messageType === 'media') return '📷 Photo'; return message.messageType === 'draftjs' ? toPlainText(toState(JSON.parse(message.content.body))) : message.content.body; diff --git a/api/queries/directMessageThread/utils.js b/api/queries/directMessageThread/utils.js deleted file mode 100644 index e477f9b026..0000000000 --- a/api/queries/directMessageThread/utils.js +++ /dev/null @@ -1,19 +0,0 @@ -// @flow - -export const canViewDMThread = async ( - threadId: string, - userId: string, - { loaders }: { loaders: Object } -) => { - if (!userId) return false; - - const result = await loaders.directMessageParticipants.load(threadId); - if (!result || !result.reduction || result.reduction.length === 0) - return false; - - const members = result.reduction; - const ids = members.map(({ userId }) => userId); - if (ids.indexOf(userId) === -1) return false; - - return true; -}; diff --git a/api/queries/message/content.js b/api/queries/message/content.js new file mode 100644 index 0000000000..ed005f64a4 --- /dev/null +++ b/api/queries/message/content.js @@ -0,0 +1,9 @@ +// @flow +import type { GraphQLContext } from '../../'; +import type { DBMessage } from 'shared/types'; +import { signMessage } from 'shared/imgix'; + +export default (message: DBMessage, _: any, ctx: GraphQLContext) => { + const signedMessage = signMessage(message); + return signedMessage.content; +}; diff --git a/api/queries/message/index.js b/api/queries/message/index.js index c384830190..0e83b9feff 100644 --- a/api/queries/message/index.js +++ b/api/queries/message/index.js @@ -5,6 +5,8 @@ import sender from './sender'; import author from './author'; import thread from './thread'; import reactions from './reactions'; +import parent from './parent'; +import content from './content'; module.exports = { Query: { @@ -16,5 +18,7 @@ module.exports = { sender, // deprecated thread, reactions, + parent, + content, }, }; diff --git a/api/queries/message/parent.js b/api/queries/message/parent.js new file mode 100644 index 0000000000..ff8263d5b2 --- /dev/null +++ b/api/queries/message/parent.js @@ -0,0 +1,12 @@ +// @flow +import type { DBMessage } from 'shared/types'; +import type { GraphQLContext } from '../../'; + +export default ( + { parentId }: DBMessage, + _: void, + { loaders }: GraphQLContext +) => { + if (!parentId) return null; + return loaders.message.load(parentId); +}; diff --git a/api/queries/message/rootMessage.js b/api/queries/message/rootMessage.js index 97bde58b2b..45de31a9c0 100644 --- a/api/queries/message/rootMessage.js +++ b/api/queries/message/rootMessage.js @@ -1,4 +1,6 @@ // @flow import { getMessage } from '../../models/message'; +import type { GraphQLContext } from '../../'; -export default (_: any, { id }: { id: string }) => getMessage(id); +export default (_: any, { id }: { id: string }, { loaders }: GraphQLContext) => + loaders.message.load(id); diff --git a/api/queries/meta/isAdmin.js b/api/queries/meta/isAdmin.js index 190626e844..35eca0f1b9 100644 --- a/api/queries/meta/isAdmin.js +++ b/api/queries/meta/isAdmin.js @@ -3,6 +3,6 @@ import type { GraphQLContext } from '../../'; import { isAdmin } from '../../utils/permissions'; export default (_: any, __: any, { user }: GraphQLContext) => { - if (!isAdmin(user.id)) return false; + if (!user || !isAdmin(user.id)) return false; return true; }; diff --git a/api/queries/search/search.js b/api/queries/search/search.js index 2aef7923ae..0e63396c35 100644 --- a/api/queries/search/search.js +++ b/api/queries/search/search.js @@ -15,7 +15,7 @@ type Input = { export default (_: any, input: Input, ctx: GraphQLContext) => { const { type, first, after, last, before, queryString, filter } = input; - if (!queryString) throw new UserError('Please provide a search term.'); + if (!queryString) return new UserError('Please provide a search term.'); const args = { first, after, diff --git a/api/queries/search/searchCommunities.js b/api/queries/search/searchCommunities.js index 3551a79412..04537dd2c2 100644 --- a/api/queries/search/searchCommunities.js +++ b/api/queries/search/searchCommunities.js @@ -3,19 +3,33 @@ import type { GraphQLContext } from '../../'; import initIndex from 'shared/algolia'; const communitySearchIndex = initIndex('communities'); import type { Args } from './types'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; -export default (args: Args, { loaders }: GraphQLContext) => { +export default (args: Args, { loaders, user }: GraphQLContext) => { const { queryString } = args; return communitySearchIndex .search({ query: queryString }) .then(content => { + if (user && user.id) { + trackQueue.add({ + userId: user.id, + event: events.SEARCHED_COMMUNITIES, + properties: { + queryString, + hitsCount: content.hits ? content.hits.length : 0, + }, + }); + } + if (!content.hits || content.hits.length === 0) return []; const communityIds = content.hits.map(o => o.objectID); return loaders.community.loadMany(communityIds); }) .then(data => data.filter(Boolean)) + .then(data => data.filter(community => !community.isPrivate)) .catch(err => { - console.log('err', err); + console.error('err', err); }); }; diff --git a/api/queries/search/searchCommunityMembers.js b/api/queries/search/searchCommunityMembers.js index c8c68c8294..86d69f0e30 100644 --- a/api/queries/search/searchCommunityMembers.js +++ b/api/queries/search/searchCommunityMembers.js @@ -3,8 +3,10 @@ import type { GraphQLContext } from '../../'; import initIndex from 'shared/algolia'; const usersSearchIndex = initIndex('users'); import type { Args } from './types'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; -export default (args: Args, { loaders }: GraphQLContext) => { +export default (args: Args, { loaders, user }: GraphQLContext) => { const { queryString, filter } = args; const searchFilter = filter; @@ -14,12 +16,24 @@ export default (args: Args, { loaders }: GraphQLContext) => { return usersSearchIndex .search({ query: queryString, hitsPerPage }) .then(content => { + if (user && user.id) { + trackQueue.add({ + userId: user.id, + event: events.SEARCHED_COMMUNITY_MEMBERS, + properties: { + queryString, + hitsCount: content.hits ? content.hits.length : 0, + }, + }); + } + if (!content.hits || content.hits.length === 0) return []; // if no search filter was passed, there's no way to be searching for // community members if (searchFilter && !searchFilter.communityId) return []; const userIds = content.hits.map(o => o.objectID); const input = userIds.map(userId => { + /*eslint array-callback-return: "off"*/ if (!searchFilter || !searchFilter.communityId) return; return [userId, searchFilter.communityId]; }); @@ -32,6 +46,6 @@ export default (args: Args, { loaders }: GraphQLContext) => { ) .then(data => data.filter(Boolean)) .catch(err => { - console.log('err', err); + console.error('err', err); }); }; diff --git a/api/queries/search/searchThreads.js b/api/queries/search/searchThreads.js index d7afa95616..07e5a03f29 100644 --- a/api/queries/search/searchThreads.js +++ b/api/queries/search/searchThreads.js @@ -12,15 +12,21 @@ import { DEFAULT_USER_CHANNEL_PERMISSIONS, } from '../../models/usersChannels'; import { getChannelById, getChannels } from '../../models/channel'; -import { getCommunityById } from '../../models/community'; +import { getCommunityById, getCommunities } from '../../models/community'; import { getPublicChannelIdsInCommunity, + getPublicCommunityIdsForUsersThreads, getPrivateChannelIdsInCommunity, + getPrivateCommunityIdsForUsersThreads, getUsersJoinedPrivateChannelIds, + getUsersJoinedPrivateCommunityIds, getPublicChannelIdsForUsersThreads, getPrivateChannelIdsForUsersThreads, getUsersJoinedChannels, + getUsersJoinedCommunities, } from '../../models/search'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; const threadsSearchIndex = initIndex('threads_and_messages'); @@ -32,6 +38,18 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { threadsSearchIndex .search({ query: queryString, filters }) .then(content => { + if (user && user.id) { + trackQueue.add({ + userId: user.id, + event: events.SEARCHED_CONVERSATIONS, + properties: { + queryString, + filters, + hitsCount: content.hits ? content.hits.length : 0, + }, + }); + } + if (!content.hits || content.hits.length === 0) return null; return content.hits.map(o => ({ threadId: o.threadId, @@ -41,7 +59,7 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { })); }) .catch(err => { - console.log('err', err); + console.error('err', err); }); const IS_AUTHED_USER = user && user.id; @@ -55,31 +73,43 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { // if no threads exist, send an empty array to the client if (!searchResultThreads || searchResultThreads.length === 0) return []; - const getChannel = getChannelById(channelId); + const channel = await getChannelById(channelId); + + // channel doesn't exist + if (!channel) return []; + + const usersPermissionsInCommunity = IS_AUTHED_USER + ? getUserPermissionsInCommunity(channel.communityId, user.id) + : DEFAULT_USER_COMMUNITY_PERMISSIONS; + const usersPermissionsInChannel = IS_AUTHED_USER ? getUserPermissionsInChannel(channelId, user.id) : DEFAULT_USER_CHANNEL_PERMISSIONS; - const [channel, permissions] = await Promise.all([ - getChannel, + const [ + community, + communityPermissions, + channelPermissions, + ] = await Promise.all([ + getCommunityById(channel.communityId), + usersPermissionsInCommunity, usersPermissionsInChannel, ]); - // channel doesn't exist - if (!channel) return []; - if (permissions.isBlocked) return []; + if (!community) return []; - // if the channel is private and the user isn't a member - if (channel.isPrivate && !permissions.isMember) { - return []; - } + if (community.isPrivate && !communityPermissions.isMember) return []; + + if (channelPermissions.isBlocked) return []; + + if (channel.isPrivate && !channelPermissions.isMember) return []; searchResultThreads = searchResultThreads.filter( t => t.channelId === channel.id ); return loaders.thread - .loadMany(searchResultThreads.map(t => t.threadId)) + .loadMany(searchResultThreads.map(t => t && t.threadId)) .then(data => data.filter(thread => thread && !thread.deletedAt)); } @@ -118,14 +148,16 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { getCurrentUsersPermissionInCommunity, ]); - // community is deleted or not found if (!community) return []; + if (community.isPrivate && !currentUserPermissionInCommunity.isMember) + return []; if (currentUserPermissionInCommunity.isBlocked) return []; const privateChannelsWhereUserIsMember = intersection( privateChannels, currentUsersPrivateChannels ); + const availableChannelsForSearch = [ ...publicChannels, ...privateChannelsWhereUserIsMember, @@ -136,7 +168,7 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { .filter(t => t.communityId === searchFilter.communityId); return loaders.thread - .loadMany(searchResultThreads.map(t => t.threadId)) + .loadMany(searchResultThreads.map(t => t && t.threadId)) .then(data => data.filter(thread => thread && !thread.deletedAt)); } @@ -149,6 +181,15 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { if (!searchResultThreads || searchResultThreads.length === 0) return []; const getPublicChannelIds = getPublicChannelIdsForUsersThreads(creatorId); + const getPublicCommunityIds = getPublicCommunityIdsForUsersThreads( + creatorId + ); + const getPrivateCommunityIds = IS_AUTHED_USER + ? getPrivateCommunityIdsForUsersThreads(creatorId) + : []; + const getCurrentUsersCommunityIds = IS_AUTHED_USER + ? getUsersJoinedPrivateCommunityIds(user.id) + : []; const getPrivateChannelIds = IS_AUTHED_USER ? getPrivateChannelIdsForUsersThreads(creatorId) : []; @@ -157,19 +198,36 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { : []; const [ + publicCommunities, + privateCommunities, publicChannels, privateChannels, currentUsersPrivateChannels, + currentUsersPrivateCommunities, ] = await Promise.all([ + getPublicCommunityIds, + getPrivateCommunityIds, getPublicChannelIds, getPrivateChannelIds, getCurrentUsersChannelIds, + getCurrentUsersCommunityIds, ]); + const privateCommunitiesWhereUserIsMember = intersection( + privateCommunities, + currentUsersPrivateCommunities + ); + + const availableCommunitiesForSearch = [ + ...publicCommunities, + ...privateCommunitiesWhereUserIsMember, + ]; + const privateChannelsWhereUserIsMember = intersection( privateChannels, currentUsersPrivateChannels ); + const availableChannelsForSearch = [ ...publicChannels, ...privateChannelsWhereUserIsMember, @@ -177,10 +235,11 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { searchResultThreads = searchResultThreads .filter(t => availableChannelsForSearch.indexOf(t.channelId) >= 0) + .filter(t => availableCommunitiesForSearch.indexOf(t.communityId) >= 0) .filter(t => t.creatorId === searchFilter.creatorId); return loaders.thread - .loadMany(searchResultThreads.map(t => t.threadId)) + .loadMany(searchResultThreads.map(t => t && t.threadId)) .then(data => data.filter(thread => thread && !thread.deletedAt)); } @@ -192,20 +251,25 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { // if no threads exist, send an empty array to the client if (!searchResultThreads || searchResultThreads.length === 0) return []; + const getAvailableCommunityids = IS_AUTHED_USER + ? getUsersJoinedCommunities(user.id) + : []; + const getAvailableChannelIds = IS_AUTHED_USER ? getUsersJoinedChannels(user.id) : []; - const [availableChannelsForSearch] = await Promise.all([ - getAvailableChannelIds, - ]); + const [ + availableCommunitiesForSearch, + availableChannelsForSearch, + ] = await Promise.all([getAvailableCommunityids, getAvailableChannelIds]); - searchResultThreads = searchResultThreads.filter( - t => availableChannelsForSearch.indexOf(t.channelId) >= 0 - ); + searchResultThreads = searchResultThreads + .filter(t => availableChannelsForSearch.indexOf(t.channelId) >= 0) + .filter(t => availableCommunitiesForSearch.indexOf(t.communityId) >= 0); return loaders.thread - .loadMany(searchResultThreads.map(t => t.threadId)) + .loadMany(searchResultThreads.map(t => t && t.threadId)) .then(data => data.filter(thread => thread && !thread.deletedAt)); } @@ -218,7 +282,11 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { // first, lets get the channels where the thread results were posted const channelsOfThreads = await getChannels( - searchResultThreads.map(t => t.channelId) + searchResultThreads.map(t => t && t.channelId) + ); + + const communitiesOfThreads = await getCommunities( + searchResultThreads.map(t => t && t.communityId) ); // see if any channels where thread results were found are private @@ -226,11 +294,18 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { .filter(c => c.isPrivate) .map(c => c.id); + const privateCommunityIds = communitiesOfThreads + .filter(c => c.isPrivate) + .map(c => c.id); + // if the search results contain threads that aren't in any private channels, // send down the results - if (!privateChannelIds || privateChannelIds.length === 0) { + if ( + (!privateChannelIds || privateChannelIds.length === 0) && + (!privateCommunityIds || privateCommunityIds.length === 0) + ) { return loaders.thread - .loadMany(searchResultThreads.map(t => t.threadId)) + .loadMany(searchResultThreads.map(t => t && t.threadId)) .then(data => data.filter(thread => thread && !thread.deletedAt)); } else { // otherwise here we know that the user found threads where some of them are @@ -239,23 +314,39 @@ export default async (args: Args, { loaders, user }: GraphQLContext) => { ? await getUsersJoinedPrivateChannelIds(user.id) : []; + const currentUsersPrivateCommunityIds = IS_AUTHED_USER + ? await getUsersJoinedPrivateCommunityIds(user.id) + : []; + // find which private channels the user is a member of const availablePrivateChannels = currentUsersPrivateChannelIds.length > 0 ? intersection(privateChannelIds, currentUsersPrivateChannelIds) : []; + const availablePrivateCommunities = + currentUsersPrivateCommunityIds.length > 0 + ? intersection(privateCommunityIds, currentUsersPrivateCommunityIds) + : []; + // for each thread in the search results, determine if it was posted in // a private channel. if yes, is the current user a member? searchResultThreads = searchResultThreads.filter(thread => { + if (!thread) return null; + if (privateChannelIds.indexOf(thread.channelId) >= 0) { return availablePrivateChannels.indexOf(thread.channelId) >= 0; } + + if (privateCommunityIds.indexOf(thread.communityId) >= 0) { + return availablePrivateCommunities.indexOf(thread.communityId) >= 0; + } + return thread; }); return loaders.thread - .loadMany(searchResultThreads.map(t => t.threadId)) + .loadMany(searchResultThreads.map(t => t && t.threadId)) .then(data => data.filter(thread => thread && !thread.deletedAt)); } }; diff --git a/api/queries/search/searchUsers.js b/api/queries/search/searchUsers.js index f639fcbbe1..7a4b89fe30 100644 --- a/api/queries/search/searchUsers.js +++ b/api/queries/search/searchUsers.js @@ -1,11 +1,12 @@ // @flow import type { GraphQLContext } from '../../'; -import { getUsersPermissionsInCommunities } from '../../models/usersCommunities'; import initIndex from 'shared/algolia'; const usersSearchIndex = initIndex('users'); import type { Args } from './types'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; -export default (args: Args, { loaders }: GraphQLContext) => { +export default (args: Args, { loaders, user }: GraphQLContext) => { const { queryString, filter } = args; const searchFilter = filter; @@ -15,12 +16,29 @@ export default (args: Args, { loaders }: GraphQLContext) => { return usersSearchIndex .search({ query: queryString, hitsPerPage }) .then(content => { + const event = + searchFilter && searchFilter.communityId + ? events.SEARCHED_COMMUNITY_MEMBERS + : events.SEARCHED_USERS; + + if (user && user.id) { + trackQueue.add({ + userId: user.id, + event, + properties: { + queryString, + hitsCount: content.hits ? content.hits.length : 0, + }, + }); + } + if (!content.hits || content.hits.length === 0) return []; + const userIds = content.hits.map(o => o.objectID); return loaders.user.loadMany(userIds); }) .then(data => data.filter(Boolean)) .catch(err => { - console.log('err', err); + console.error('err', err); }); }; diff --git a/api/queries/thread/attachments.js b/api/queries/thread/attachments.js index 149bb1d73a..b58b881b8a 100644 --- a/api/queries/thread/attachments.js +++ b/api/queries/thread/attachments.js @@ -1,11 +1,9 @@ // @flow +/* + +Deprecated Oct 26 2018 by @brian + +*/ import type { DBThread } from 'shared/types'; -export default ({ attachments }: DBThread) => - attachments && - attachments.map(attachment => { - return { - attachmentType: attachment.attachmentType, - data: JSON.stringify(attachment.data), - }; - }); +export default () => []; diff --git a/api/queries/thread/content.js b/api/queries/thread/content.js new file mode 100644 index 0000000000..67804d3b78 --- /dev/null +++ b/api/queries/thread/content.js @@ -0,0 +1,12 @@ +// @flow +import type { GraphQLContext } from '../../'; +import type { DBThread } from 'shared/types'; +import { signThread } from 'shared/imgix'; + +export default (thread: DBThread, _: any, ctx: GraphQLContext) => { + const signedThread = signThread(thread); + return { + ...signedThread.content, + body: signedThread.content.body, + }; +}; diff --git a/api/queries/thread/index.js b/api/queries/thread/index.js index 030625d663..0a5e8426b3 100644 --- a/api/queries/thread/index.js +++ b/api/queries/thread/index.js @@ -12,8 +12,12 @@ import receiveNotifications from './receiveNotifications'; import messageConnection from './messageConnection'; import author from './author'; import creator from './creator'; -import messageCount from './messageCount'; import currentUserLastSeen from './currentUserLastSeen'; +import content from './content'; +import reactions from './reactions'; +import metaImage from './metaImage'; + +import type { DBThread } from 'shared/types'; module.exports = { Query: { @@ -31,7 +35,10 @@ module.exports = { messageConnection, author, creator, // deprecated - messageCount, currentUserLastSeen, + content, + reactions, + metaImage, + messageCount: ({ messageCount }: DBThread) => messageCount || 0, }, }; diff --git a/api/queries/thread/messageConnection.js b/api/queries/thread/messageConnection.js index c7e26643a5..cc978824ac 100644 --- a/api/queries/thread/messageConnection.js +++ b/api/queries/thread/messageConnection.js @@ -36,7 +36,7 @@ export default ( ' before:', before ); - throw new UserError( + return new UserError( 'Cannot paginate back- and forwards at the same time. Please only ask for the first messages after a certain point or the last messages before a certain point.' ); } @@ -47,28 +47,29 @@ export default ( cursor = decode(cursor); if (cursor) cursor = parseInt(cursor, 10); } catch (err) { - debug(err); - throw new UserError('Invalid cursor passed to thread.messageConnection.'); + console.error('❌ Error in job:\n'); + console.error(err); + return new UserError('Invalid cursor passed to thread.messageConnection.'); } if (cursor) debug(`cursor: ${cursor}`); let options = { - // Default first/last to 50 if their counterparts after/before are provided + // Default first/last to 25 if their counterparts after/before are provided // so users can query messageConnection(after: "cursor") or (before: "cursor") // without any more options - first: first ? first : after ? 50 : null, - last: last ? last : before ? 50 : null, + first: first ? first : after ? 25 : null, + last: last ? last : before ? 25 : null, // Set after/before to the cursor depending on which one was requested by the user after: after ? cursor : null, before: before ? cursor : null, }; // If we didn't get any arguments at all (i.e messageConnection {}) - // then just fetch the first 50 messages + // then just fetch the first 25 messages // $FlowIssue if (Object.keys(options).every(key => !options[key])) { options = { - first: 50, + first: 25, }; } diff --git a/api/queries/thread/messageCount.js b/api/queries/thread/messageCount.js deleted file mode 100644 index a055fde3e7..0000000000 --- a/api/queries/thread/messageCount.js +++ /dev/null @@ -1,9 +0,0 @@ -// @flow -import type { GraphQLContext } from '../../'; -import type { DBThread } from 'shared/types'; - -export default ({ id }: DBThread, __: any, { loaders }: GraphQLContext) => { - return loaders.threadMessageCount - .load(id) - .then(messageCount => (messageCount ? messageCount.reduction : 0)); -}; diff --git a/api/queries/thread/metaImage.js b/api/queries/thread/metaImage.js new file mode 100644 index 0000000000..9c337ab9c2 --- /dev/null +++ b/api/queries/thread/metaImage.js @@ -0,0 +1,24 @@ +// @flow +import type { GraphQLContext } from '../../'; +import type { DBThread } from 'shared/types'; +import generateImageFromText from '../../utils/generate-thread-meta-image-from-text'; +import { signImageUrl } from 'shared/imgix'; + +export default async (thread: DBThread, _: any, ctx: GraphQLContext) => { + const { loaders } = ctx; + const { watercooler, communityId, content } = thread; + + const community = await loaders.community.load(communityId); + if (!community) return null; + + const imageUrl = generateImageFromText({ + title: watercooler + ? `Chat with the ${community.name} community` + : content.title, + footer: `spectrum.chat/${community.slug}`, + }); + + if (!imageUrl) return null; + + return signImageUrl(imageUrl); +}; diff --git a/api/queries/thread/reactions.js b/api/queries/thread/reactions.js new file mode 100644 index 0000000000..d94514e45e --- /dev/null +++ b/api/queries/thread/reactions.js @@ -0,0 +1,36 @@ +// @flow +import { hasReactedToThread } from 'api/models/threadReaction'; +import Raven from 'shared/raven'; +import type { DBThread } from 'shared/types'; +import type { GraphQLContext } from '../../'; + +export default async (root: DBThread, _: any, ctx: GraphQLContext) => { + const { user, loaders } = ctx; + const { id, reactionCount } = root; + + const getReactionCount = async () => { + if (typeof reactionCount === 'number') return reactionCount; + + Raven.captureException( + new Error( + `Thread with ID "${id}" does not have denormalized reactionCount.` + ) + ); + + return await loaders.threadReaction.load(id).then(res => { + if (res && Array.isArray(res.reduction)) { + return res.reduction.length; + } + + return 0; + }); + }; + + const hasReacted = user ? await hasReactedToThread(user.id, id) : false; + const count = await getReactionCount(); + + return { + hasReacted, + count, + }; +}; diff --git a/api/queries/thread/rootSearchThreads.js b/api/queries/thread/rootSearchThreads.js index 902e356f6e..fbb0506a8a 100644 --- a/api/queries/thread/rootSearchThreads.js +++ b/api/queries/thread/rootSearchThreads.js @@ -51,7 +51,7 @@ export default async ( })); }) .catch(err => { - console.log('err', err); + console.error('err', err); }); const IS_AUTHED_USER = user && user.id; diff --git a/api/queries/thread/rootThread.js b/api/queries/thread/rootThread.js index 383913366f..9ecea27877 100644 --- a/api/queries/thread/rootThread.js +++ b/api/queries/thread/rootThread.js @@ -1,34 +1,30 @@ // @flow +import { calculateThreadScoreQueue } from 'shared/bull/queues'; import type { GraphQLContext } from '../../'; +import { canViewThread } from '../../utils/permissions'; export default async ( _: any, { id }: { id: string }, { loaders, user }: GraphQLContext ) => { - const thread = await loaders.thread.load(id); - - // if a thread wasn't found - if (!thread) return null; - - /* - If no user exists, we need to make sure the thread being fetched is not in a private channel - */ - if (!user) { - const channel = await loaders.channel.load(thread.channelId); - - // if the channel is private, don't return any thread data - if (channel.isPrivate) return null; - return thread; - } else { - // if the user is signed in, we need to check if the channel is private as well as the user's permission in that channel - const [permissions, channel] = await Promise.all([ - loaders.userPermissionsInChannel.load([user.id, thread.channelId]), - loaders.channel.load(thread.channelId), - ]); + if (!(await canViewThread(user ? user.id : 'undefined', id, loaders))) { + return null; + } - // if the thread is in a private channel where the user is not a member, don't return any thread data - if (channel.isPrivate && !permissions.isMember) return null; - return thread; + const thread = await loaders.thread.load(id); + // If the threads score hasn't been updated in the past + // 24 hours add a new job to the queue to update it + if ( + (!thread.score && !thread.scoreUpdatedAt) || + (thread.scoreUpdatedAt && + Date.now() > new Date(thread.scoreUpdatedAt).getTime() + 86400000) + ) { + calculateThreadScoreQueue.add( + { threadId: thread.id }, + { jobId: thread.id } + ); } + + return thread; }; diff --git a/api/queries/user/communityConnection.js b/api/queries/user/communityConnection.js index 38c5895ee4..1c88a9e7b3 100644 --- a/api/queries/user/communityConnection.js +++ b/api/queries/user/communityConnection.js @@ -1,15 +1,33 @@ // @flow import type { DBUser } from 'shared/types'; -import { getCommunitiesByUser } from '../../models/community'; +import { + getVisibleCommunitiesByUser, + getPublicCommunitiesByUser, + getCommunitiesByUser, +} from '../../models/community'; import type { GraphQLContext } from '../../'; -export default (user: DBUser, _: any, { loaders }: GraphQLContext) => ({ - // Don't paginate communities and channels of a user - pageInfo: { - hasNextPage: false, - }, - edges: getCommunitiesByUser(user.id).then(communities => - communities.map(async community => { +export default async (user: DBUser, _: any, ctx: GraphQLContext) => { + const evaluatingUserId = user.id; + const { loaders, user: currentUser } = ctx; + + let communities; + if (!currentUser || !currentUser.id) { + communities = await getPublicCommunitiesByUser(evaluatingUserId); + } else if (evaluatingUserId === currentUser.id) { + communities = await getCommunitiesByUser(currentUser.id); + } else { + communities = await getVisibleCommunitiesByUser( + evaluatingUserId, + currentUser.id + ); + } + + return { + pageInfo: { + hasNextPage: false, + }, + edges: communities.map(async community => { const permissions = await loaders.userPermissionsInCommunity.load([ user.id, community.id, @@ -26,6 +44,6 @@ export default (user: DBUser, _: any, { loaders }: GraphQLContext) => ({ }, }, }; - }) - ), -}); + }), + }; +}; diff --git a/api/queries/user/coverPhoto.js b/api/queries/user/coverPhoto.js index 4f2a6b0963..e383f22bfd 100644 --- a/api/queries/user/coverPhoto.js +++ b/api/queries/user/coverPhoto.js @@ -1,16 +1,9 @@ // @flow +import type { GraphQLContext } from '../../'; import type { DBUser } from 'shared/types'; -import ImgixClient from 'imgix-core-js'; -let imgix = new ImgixClient({ - host: 'spectrum-imgp.imgix.net', - secureURLToken: 'asGmuMn5yq73B3cH', -}); +import { signUser } from 'shared/imgix'; -export default ({ coverPhoto }: DBUser) => { - // if the image is not being served from our S3 imgix source, serve it from our web proxy - if (coverPhoto && coverPhoto.indexOf('spectrum.imgix.net') < 0) { - return imgix.buildURL(coverPhoto, { w: 640, h: 192 }); - } - // if the image is being served from the S3 imgix source, return that url +export default (user: DBUser, _: any, ctx: GraphQLContext) => { + const { coverPhoto } = signUser(user); return coverPhoto; }; diff --git a/api/queries/user/email.js b/api/queries/user/email.js index 32a68a73fe..1a0ff99bf5 100644 --- a/api/queries/user/email.js +++ b/api/queries/user/email.js @@ -3,8 +3,13 @@ import type { GraphQLContext } from '../../'; import type { DBUser } from 'shared/types'; import { isAdmin } from '../../utils/permissions'; -export default ({ id, email }: DBUser, _: any, { user }: GraphQLContext) => { +export default async ( + { id }: DBUser, + _: any, + { user, loaders }: GraphQLContext +) => { // Only admins and the user themselves can view the email if (!user || (id !== user.id && !isAdmin(user.id))) return null; + const { email } = await loaders.user.load(id); return email; }; diff --git a/api/queries/user/everything.js b/api/queries/user/everything.js index 90f02bfa6a..c67c00358d 100644 --- a/api/queries/user/everything.js +++ b/api/queries/user/everything.js @@ -2,7 +2,7 @@ import type { GraphQLContext } from '../../'; import type { PaginationOptions } from '../../utils/paginate-arrays'; import { encode, decode } from '../../utils/base64'; -import { getEverything } from '../../models/user'; +import { getEverything } from 'shared/db/queries/user'; export default ( _: any, diff --git a/api/queries/user/index.js b/api/queries/user/index.js index c0570ad1e5..a7b08e8d56 100644 --- a/api/queries/user/index.js +++ b/api/queries/user/index.js @@ -6,21 +6,24 @@ import searchUsers from './rootSearchUsers'; import email from './email'; import coverPhoto from './coverPhoto'; import profilePhoto from './profilePhoto'; -import isPro from './isPro'; import everything from './everything'; import communityConnection from './communityConnection'; import channelConnection from './channelConnection'; import directMessageThreadsConnection from './directMessageThreadsConnection'; import threadConnection from './threadConnection'; import threadCount from './threadCount'; -import recurringPayments from './recurringPayments'; import settings from './settings'; -import invoices from './invoices'; import totalReputation from './totalReputation'; import isAdmin from './isAdmin'; import contextPermissions from './contextPermissions'; import githubProfile from './githubProfile'; +// no-op resolvers to transition while removing payments +import type { DBUser } from 'shared/types'; +const isPro = (dbUser: DBUser) => dbUser.betaSupporter; +const recurringPayments = () => []; +const invoices = () => []; + module.exports = { Query: { user, @@ -31,19 +34,19 @@ module.exports = { email, coverPhoto, profilePhoto, - isPro, everything, communityConnection, channelConnection, directMessageThreadsConnection, threadConnection, threadCount, - recurringPayments, settings, - invoices, totalReputation, isAdmin, githubProfile, contextPermissions, + isPro, + recurringPayments, + invoices, }, }; diff --git a/api/queries/user/invoices.js b/api/queries/user/invoices.js deleted file mode 100644 index 8b8372f967..0000000000 --- a/api/queries/user/invoices.js +++ /dev/null @@ -1,12 +0,0 @@ -// @flow -import type { GraphQLContext } from '../../'; -import UserError from '../../utils/UserError'; -import { getInvoicesByUser } from '../../models/invoice'; - -export default (_: any, __: any, { user }: GraphQLContext) => { - const currentUser = user; - if (!currentUser) - return new UserError('You must be logged in to view these settings.'); - - return getInvoicesByUser(currentUser.id); -}; diff --git a/api/queries/user/isPro.js b/api/queries/user/isPro.js deleted file mode 100644 index d17dbb8b12..0000000000 --- a/api/queries/user/isPro.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import type { GraphQLContext } from '../../'; -import type { DBUser } from 'shared/types'; - -export default ({ id }: DBUser, _: any, { loaders }: GraphQLContext) => { - return loaders.userRecurringPayments.load(id).then(result => { - if (!result || result.length === 0) return false; - const subs = result.reduction; - - return subs.some( - sub => sub.status === 'active' && sub.planId === 'beta-pro' - ); - }); -}; diff --git a/api/queries/user/profilePhoto.js b/api/queries/user/profilePhoto.js index b1bc9f35a8..c978deabd6 100644 --- a/api/queries/user/profilePhoto.js +++ b/api/queries/user/profilePhoto.js @@ -1,16 +1,9 @@ // @flow +import type { GraphQLContext } from '../../'; import type { DBUser } from 'shared/types'; -import ImgixClient from 'imgix-core-js'; -let imgix = new ImgixClient({ - host: 'spectrum-imgp.imgix.net', - secureURLToken: 'asGmuMn5yq73B3cH', -}); +import { signUser } from 'shared/imgix'; -export default ({ profilePhoto }: DBUser) => { - // if the image is not being served from our S3 imgix source, serve it from our web proxy - if (profilePhoto && profilePhoto.indexOf('spectrum.imgix.net') < 0) { - return imgix.buildURL(profilePhoto, { w: 128, h: 128 }); - } - // if the image is being served from the S3 imgix source, return that url +export default (user: DBUser, _: any, ctx: GraphQLContext) => { + const { profilePhoto } = signUser(user); return profilePhoto; }; diff --git a/api/queries/user/recurringPayments.js b/api/queries/user/recurringPayments.js deleted file mode 100644 index d9615f24fc..0000000000 --- a/api/queries/user/recurringPayments.js +++ /dev/null @@ -1,31 +0,0 @@ -// @flow -import type { GraphQLContext } from '../../'; -import type { DBUser } from 'shared/types'; -import UserError from '../../utils/UserError'; - -export default ({ id }: DBUser, __: any, { user, loaders }: GraphQLContext) => { - if (!user) { - return new UserError('You must be signed in to continue.'); - } - if (id !== user.id) { - throw new UserError('You can only see your own recurring payments.'); - } - - return loaders.userRecurringPayments.load(user.id).then(result => { - const subs = result && result.reduction; - const userProSubs = - subs && subs.length > 0 && subs.filter(obj => obj.planId === 'beta-pro'); - if (!userProSubs || userProSubs.length === 0) { - return []; - } else { - return userProSubs.map(subscription => { - return { - amount: subscription.amount, - createdAt: subscription.createdAt, - plan: subscription.planName, - status: subscription.status, - }; - }); - } - }); -}; diff --git a/api/queries/user/rootCurrentUser.js b/api/queries/user/rootCurrentUser.js index d8e0ff62ca..467a4f78aa 100644 --- a/api/queries/user/rootCurrentUser.js +++ b/api/queries/user/rootCurrentUser.js @@ -1,4 +1,10 @@ // @flow import type { GraphQLContext } from '../../'; +import { getUserById } from 'shared/db/queries/user'; -export default (_: any, __: any, { user }: GraphQLContext) => user; +export default async (_: any, __: any, { user }: GraphQLContext) => { + if (!user || !user.id) return null; + const dbUser = await getUserById(user.id); + if (!dbUser || dbUser.bannedAt) return null; + return dbUser; +}; diff --git a/api/queries/user/rootSearchUsers.js b/api/queries/user/rootSearchUsers.js index e05e26b590..4c6d51cac1 100644 --- a/api/queries/user/rootSearchUsers.js +++ b/api/queries/user/rootSearchUsers.js @@ -21,6 +21,6 @@ export default ( return loaders.user.loadMany(userIds); }) .catch(err => { - console.log('err', err); + console.error('err', err); }); }; diff --git a/api/queries/user/settings.js b/api/queries/user/settings.js index 3a37398aaa..74534d9117 100644 --- a/api/queries/user/settings.js +++ b/api/queries/user/settings.js @@ -1,10 +1,15 @@ // @flow import type { GraphQLContext } from '../../'; import type { DBUser } from 'shared/types'; -import { getUsersSettings } from '../../models/usersSettings'; +import { + getUsersSettings, + createNewUsersSettings, +} from '../../models/usersSettings'; import UserError from '../../utils/UserError'; -export default (_: DBUser, __: any, { user }: GraphQLContext) => { +export default async (_: DBUser, __: any, { user }: GraphQLContext) => { if (!user) return new UserError('You must be signed in to continue.'); - return getUsersSettings(user.id); + const settings = await getUsersSettings(user.id); + if (settings) return settings; + return await createNewUsersSettings(user.id); }; diff --git a/api/routes/api/email.js b/api/routes/api/email.js index 553ccae2f8..07e556880e 100644 --- a/api/routes/api/email.js +++ b/api/routes/api/email.js @@ -2,11 +2,11 @@ require('now-env'); const IS_PROD = process.env.NODE_ENV === 'production'; const IS_TESTING = process.env.TEST_DB; -import { PAYMENTS_COMMUNITY_ID } from '../../migrations/seed/default/constants'; +import { BRIAN_ID } from '../../migrations/seed/default/constants'; import { Router } from 'express'; const jwt = require('jsonwebtoken'); const emailRouter = Router(); -import { updateUserEmail } from '../../models/user'; +import { updateUserEmail } from 'shared/db/queries/user'; import { unsubscribeUserFromEmailNotification } from '../../models/usersSettings'; import { updateThreadNotificationStatusForUser } from '../../models/usersThreads'; import { updateDirectMessageThreadNotificationStatusForUser } from '../../models/usersDirectMessageThreads'; @@ -71,7 +71,7 @@ emailRouter.get('/unsubscribe', (req, res) => { res .status(200) .send( - 'You will no longer recieve new thread emails from this channel.' + 'You will no longer receive new thread emails from this channel.' ) ); case 'muteCommunity': @@ -84,7 +84,7 @@ emailRouter.get('/unsubscribe', (req, res) => { res .status(200) .send( - 'You will no longer recieve new thread emails from this community.' + 'You will no longer receive new thread emails from this community.' ) ); case 'muteThread': @@ -96,7 +96,7 @@ emailRouter.get('/unsubscribe', (req, res) => { res .status(200) .send( - 'You will no longer recieve emails about new messages in this thread.' + 'You will no longer receive emails about new messages in this thread.' ) ); case 'muteDirectMessageThread': @@ -108,7 +108,7 @@ emailRouter.get('/unsubscribe', (req, res) => { res .status(200) .send( - 'You will no longer recieve emails about new messages in this direct message conversation.' + 'You will no longer receive emails about new messages in this direct message conversation.' ) ); default: { @@ -168,15 +168,11 @@ emailRouter.get('/validate', (req, res) => { // validate a new administrator email address if (communityId) { try { - return updateCommunityAdministratorEmail(communityId, email).then( + return updateCommunityAdministratorEmail(communityId, email, userId).then( community => IS_PROD - ? res.redirect( - `https://spectrum.chat/${community.slug}/settings/billing` - ) - : res.redirect( - `http://localhost:3000/${community.slug}/settings/billing` - ) + ? res.redirect(`https://spectrum.chat/${community.slug}/settings`) + : res.redirect(`http://localhost:3000/${community.slug}/settings`) ); } catch (err) { console.error(err); @@ -190,16 +186,24 @@ emailRouter.get('/validate', (req, res) => { // and send a database request to update the user record with this email try { - return updateUserEmail(userId, email).then( - user => - IS_PROD - ? res.redirect( - `https://spectrum.chat/users/${user.username}/settings` - ) - : res.redirect( - `http://localhost:3000/users/${user.username}/settings` - ) - ); + return updateUserEmail(userId, email).then(user => { + const rootRedirect = IS_PROD + ? `https://spectrum.chat` + : `http://localhost:3000`; + + req.login(user, err => { + if (err) { + return res + .status(400) + .send( + 'We ran into an issue validating this email address. You can re-enter your email address in your community settings to resend a confirmation email, or get in touch with us at hi@spectrum.chat.' + ); + } + + if (!user.username) return res.redirect(rootRedirect); + return res.redirect(`${rootRedirect}/users/${user.username}/settings`); + }); + }); } catch (err) { console.error(err); return res @@ -210,23 +214,4 @@ emailRouter.get('/validate', (req, res) => { } }); -if (IS_TESTING) { - // $FlowIssue - emailRouter.get('/validate/test-payments/verify', (req, res) => { - return updateCommunityAdministratorEmail( - PAYMENTS_COMMUNITY_ID, - 'briandlovin@gmail.com' - ).then(() => - res.redirect('http://localhost:3000/payments/settings/billing') - ); - }); - - // $FlowIssue - emailRouter.get('/validate/test-payments/reset', (req, res) => { - return resetCommunityAdministratorEmail(PAYMENTS_COMMUNITY_ID).then(() => - res.redirect('http://localhost:3000/payments/settings/billing') - ); - }); -} - export default emailRouter; diff --git a/api/routes/api/export-user-data.js b/api/routes/api/export-user-data.js new file mode 100644 index 0000000000..43829f2027 --- /dev/null +++ b/api/routes/api/export-user-data.js @@ -0,0 +1,25 @@ +// @flow +import { Router } from 'express'; +const userDataRouter = Router(); +import { getUserById } from 'shared/db/queries/user'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; + +userDataRouter.get('/', async (req: express$Request, res: express$Response) => { + if (!req.user) return res.send('No logged-in user'); + + // $FlowIssue + const user = await getUserById(req.user.id); + if (!user) return res.send('User not found.'); + + trackQueue.add({ + userId: user.id, + event: events.USER_DOWNLOADED_PERSONAL_DATA, + }); + + // This forces the browser to download a .json file instead of rendering it + res.setHeader('Content-Type', 'application/octet-stream'); + return res.send(user); +}); + +export default userDataRouter; diff --git a/api/routes/api/graphiql.js b/api/routes/api/graphiql.js deleted file mode 100644 index aa201d601e..0000000000 --- a/api/routes/api/graphiql.js +++ /dev/null @@ -1,9 +0,0 @@ -// @flow -import { graphiqlExpress } from 'graphql-server-express'; - -export default graphiqlExpress({ - endpointURL: '/api', - subscriptionsEndpoint: 'ws://localhost:3001/websocket', - query: - '{\n user(id: "58a023a4-912d-48fe-a61c-eec7274f7699") {\n name\n username\n communities {\n name\n frequencies {\n name\n stories {\n content {\n title\n }\n messages {\n message {\n content\n }\n }\n }\n }\n }\n }\n}', -}); diff --git a/api/routes/api/graphql.js b/api/routes/api/graphql.js deleted file mode 100644 index 6334ac5121..0000000000 --- a/api/routes/api/graphql.js +++ /dev/null @@ -1,34 +0,0 @@ -// @flow -import { graphqlExpress } from 'graphql-server-express'; -import depthLimit from 'graphql-depth-limit'; -import costAnalysis from 'graphql-cost-analysis'; -import Raven from 'shared/raven'; -import UserError from '../../utils/UserError'; -import createLoaders from '../../loaders/'; - -import createErrorFormatter from '../../utils/create-graphql-error-formatter'; -import schema from '../../schema'; - -export default graphqlExpress(req => ({ - schema, - formatError: createErrorFormatter(req), - tracing: true, - context: { - user: req.user, - loaders: createLoaders(), - }, - validationRules: [ - depthLimit(10), - costAnalysis({ - variables: req.body.variables, - maximumCost: 750, - defaultCost: 1, - createError: (max, actual) => { - const err = new UserError( - `GraphQL query exceeds maximum complexity, please remove some nesting or fields and try again. (max: ${max}, actual: ${actual})` - ); - return err; - }, - }), - ], -})); diff --git a/api/routes/api/index.js b/api/routes/api/index.js index 8eb1ce4b5b..d70855c72a 100644 --- a/api/routes/api/index.js +++ b/api/routes/api/index.js @@ -3,22 +3,22 @@ import { Router } from 'express'; const apiRouter = Router(); -import graphiql from './graphiql'; -// Only allow GraphiQL in development -if (process.env.NODE_ENV === 'development') { - apiRouter.use('/graphiql', graphiql); -} +// import graphiql from './graphiql'; +// // Only allow GraphiQL in development +// if (process.env.NODE_ENV === 'development') { +// apiRouter.use('/graphiql', graphiql); +// } import slackImporter from './slackImporter'; apiRouter.use('/slack', slackImporter); -import stripe from './stripe'; -apiRouter.use('/stripe', stripe); - import email from './email'; apiRouter.use('/email', email); -import graphql from './graphql'; -apiRouter.use('/', graphql); +import userExportRouter from './export-user-data'; +apiRouter.use('/user.json', userExportRouter); + +// import graphql from './graphql'; +// apiRouter.use('/', graphql); export default apiRouter; diff --git a/api/routes/api/slackImporter.js b/api/routes/api/slackImporter.js index f1bcf24ac2..652d40de11 100644 --- a/api/routes/api/slackImporter.js +++ b/api/routes/api/slackImporter.js @@ -1,19 +1,34 @@ +// @flow import { Router } from 'express'; import UserError from '../../utils/UserError'; -import { getCommunities } from '../../models/community'; -import { - createSlackImportRecord, - generateOAuthToken, -} from '../../models/slackImport'; +import { generateOAuthToken } from '../../models/slackImport'; +import { updateSlackSettingsAfterConnection } from '../../models/communitySettings'; +import { encryptString } from 'shared/encryption'; const IS_PROD = process.env.NODE_ENV === 'production'; const slackRouter = Router(); -slackRouter.get('/', (req, res) => { +const constructInput = (data: any, connectedBy: string) => { + const token = encryptString(data.access_token); + const teamName = encryptString(data.team_name); + const teamId = encryptString(data.team_id); + const scope = encryptString(data.scope); + + return { + token, + teamName, + teamId, + connectedBy, + scope, + }; +}; + +// TODO: Figure out how to type this properly +slackRouter.get('/', (req: any, res: any) => { const code = req.query.code; const communityId = req.query.state; - const senderId = req.user.id; + const connectedBy = req.user.id; const returnURI = IS_PROD ? 'https://spectrum.chat/api/slack' : 'http://localhost:3001/api/slack'; @@ -22,33 +37,26 @@ slackRouter.get('/', (req, res) => { return generateOAuthToken(code, returnURI) .then(data => { if (!data) return new UserError('No token generated for this Slack team'); - const token = data.access_token; - const teamName = data.team_name; - const teamId = data.team_id; - const input = { - token, - teamName, - teamId, - senderId, + const input = constructInput(data, connectedBy); + return updateSlackSettingsAfterConnection( communityId, - }; - return createSlackImportRecord(input); + input, + connectedBy + ); }) - .then(() => { - // once the record has been created we can redirect the user back to Spectrum to finish the importing flow. To do this we'll get the community: - return getCommunities([communityId]).then(data => data[0].slug); - }) - .then(community => { + .then(community => community.slug) + .then(slug => { return IS_PROD - ? res.redirect(`https://spectrum.chat/${community}/settings/members`) - : res.redirect(`http://localhost:3000/${community}/settings/members`); + ? res.redirect(`https://spectrum.chat/${slug}/settings`) + : res.redirect(`http://localhost:3000/${slug}/settings`); }); }); -slackRouter.get('/onboarding', (req, res) => { +// TODO: Figure out how to type this properly +slackRouter.get('/onboarding', (req: any, res: any) => { const code = req.query.code; const communityId = req.query.state; - const senderId = req.user.id; + const connectedBy = req.user.id; const returnURI = IS_PROD ? 'https://spectrum.chat/api/slack/onboarding' : 'http://localhost:3001/api/slack/onboarding'; @@ -57,30 +65,18 @@ slackRouter.get('/onboarding', (req, res) => { return generateOAuthToken(code, returnURI) .then(data => { if (!data) return new UserError('No token generated for this Slack team'); - const token = data.access_token; - const teamName = data.team_name; - const teamId = data.team_id; - const input = { - token, - teamName, - teamId, - senderId, + const input = constructInput(data, connectedBy); + return updateSlackSettingsAfterConnection( communityId, - }; - return createSlackImportRecord(input); - }) - .then(() => { - // once the record has been created we can redirect the user back to Spectrum to finish the importing flow. To do this we'll get the community: - return getCommunities([communityId]).then(data => data[0]); + input, + connectedBy + ); }) - .then(community => { + .then(community => community.id) + .then(id => { return IS_PROD - ? res.redirect( - `https://spectrum.chat/new/community?s=2&id=${community.id}` - ) - : res.redirect( - `http://localhost:3000/new/community?s=2&id=${community.id}` - ); + ? res.redirect(`https://spectrum.chat/new/community?s=2&id=${id}`) + : res.redirect(`http://localhost:3000/new/community?s=2&id=${id}`); }); }); diff --git a/api/routes/api/stripe.js b/api/routes/api/stripe.js deleted file mode 100644 index 716b444730..0000000000 --- a/api/routes/api/stripe.js +++ /dev/null @@ -1,76 +0,0 @@ -// $FlowFixMe -import { Router } from 'express'; -const stripeRouter = Router(); -import { stripe } from 'shared/stripe'; -import { getCommunityById } from '../../models/community'; -import { processInvoicePaid } from '../webhooks'; -import { db } from '../../models/db'; - -/* - * - NOTE @brian: - Please keep this reset route as it makes local testing much faster - to clear databases and reset stripe data in the test mode - * - */ - -// stripeRouter.get('/reset', async (req, res) => { -// const customers = await stripe.customers.list({ limit: 100 }); -// const deleteCustomers = customers.data.map( -// async customer => -// console.log('deleting customer') || -// (await stripe.customers.del(customer.id)) -// ); -// const stripeTables = ['stripeCustomers', 'stripeInvoices']; -// const deleteDB = stripeTables.map( -// async table => -// await db -// .table(table) -// .delete() -// .run() -// ); - -// return Promise.all([...deleteCustomers, ...deleteDB]) -// .then(async () => { -// return await db -// .table('communities') -// .get('e5b75426-4c73-43b2-975a-c0be8a6b6d1d') -// .update({ -// stripeCustomerId: null, -// administratorEmail: null, -// analyticsEnabled: false, -// }) -// .run(); -// }) -// .then(async () => { -// return await db -// .table('communities') -// .get('e5b75426-4c73-43b2-975a-c0be8a6b6d1d') -// .update({ -// administratorEmail: 'briandlovin@gmail.com', -// }) -// .run(); -// }) -// .then(() => { -// return res.status(200).send({ success: true }); -// }); -// }); - -stripeRouter.post('/', (req, res) => { - const event = req.body; - - switch (event.type) { - case 'invoice.payment_succeeded': { - return processInvoicePaid(event) - .then(() => res.status(200).send('Webhook received: ' + event.id)) - .catch(err => { - console.error(`Error in webhook for ${event.type}: ` + err); - }); - } - default: { - res.status(200).send('Webhook received: ' + event.id); - } - } -}); - -export default stripeRouter; diff --git a/api/routes/auth/create-signin-routes.js b/api/routes/auth/create-signin-routes.js index 26c07335f2..0c4b78d1e2 100644 --- a/api/routes/auth/create-signin-routes.js +++ b/api/routes/auth/create-signin-routes.js @@ -10,7 +10,6 @@ import passport from 'passport'; import { URL } from 'url'; import isSpectrumUrl from '../../utils/is-spectrum-url'; -import { signCookie } from 'shared/cookie-utils'; const IS_PROD = process.env.NODE_ENV === 'production'; const FALLBACK_URL = IS_PROD @@ -35,10 +34,6 @@ export const createSigninRoutes = ( // Attach the redirectURL and authType to the session so we have it in the /auth/twitter/callback route // $FlowIssue req.session.redirectUrl = url; - if (req.query.authType === 'token') { - // $FlowIssue - req.session.authType = 'token'; - } return passport.authenticate(strategy, strategyOptions)(req, ...rest); }, @@ -55,30 +50,15 @@ export const createSigninRoutes = ( : new URL(FALLBACK_URL); redirectUrl.searchParams.append('authed', 'true'); - // Add the session cookies to the query params if token authentication - if ( - // $FlowIssue - req.session.authType === 'token' && - req.cookies && - req.cookies.session && - req.cookies['session.sig'] - ) { - redirectUrl.searchParams.append( - 'accessToken', - signCookie( - `session=${req.cookies.session}; session.sig=${ - req.cookies['session.sig'] - }` - ) - ); - // $FlowIssue - req.session.authType = undefined; - } - // Delete the redirectURL from the session again so we don't redirect // to the old URL the next time around // $FlowIssue req.session.redirectUrl = undefined; + res.cookie('_now_no_cache', '1', { + maxAge: 315569260000, // 10 years + sameSite: 'lax', + secure: false, + }); return res.redirect(redirectUrl.href); }, ], diff --git a/api/routes/auth/github.js b/api/routes/auth/github.js index eaa8ded06f..3dc1b8bad9 100644 --- a/api/routes/auth/github.js +++ b/api/routes/auth/github.js @@ -5,6 +5,7 @@ import { createSigninRoutes } from './create-signin-routes'; const githubAuthRouter = Router(); const { main, callbacks } = createSigninRoutes('github', { scope: ['read:user,user:email'], + state: true, }); githubAuthRouter.get('/', main); diff --git a/api/routes/auth/google.js b/api/routes/auth/google.js index 5b98dfdfd3..4289281490 100644 --- a/api/routes/auth/google.js +++ b/api/routes/auth/google.js @@ -4,10 +4,7 @@ import { createSigninRoutes } from './create-signin-routes'; const googleAuthRouter = Router(); const { main, callbacks } = createSigninRoutes('google', { - scope: [ - 'https://www.googleapis.com/auth/plus.login', - 'https://www.googleapis.com/auth/plus.profile.emails.read', - ], + scope: 'profile email', }); googleAuthRouter.get('/', main); diff --git a/api/routes/auth/logout.js b/api/routes/auth/logout.js index d2402a9a20..dc47aeb88f 100644 --- a/api/routes/auth/logout.js +++ b/api/routes/auth/logout.js @@ -7,7 +7,7 @@ const HOME = IS_PROD ? '/' : 'http://localhost:3000/'; const logoutRouter = Router(); logoutRouter.get('/', (req, res) => { - req.session = null; + req.logout(); return res.redirect(HOME); }); diff --git a/api/routes/create-subscription-server.js b/api/routes/create-subscription-server.js index c36fd79b04..0bbb62118e 100644 --- a/api/routes/create-subscription-server.js +++ b/api/routes/create-subscription-server.js @@ -4,7 +4,7 @@ import { execute, subscribe } from 'graphql'; import schema from '../schema'; import createLoaders from '../loaders'; -import { getUser, setUserOnline } from '../models/user'; +import { setUserOnline } from 'shared/db/queries/user'; import { getUserIdFromReq } from '../utils/session-store'; import createErrorFormatter from '../utils/create-graphql-error-formatter'; @@ -18,34 +18,6 @@ const createSubscriptionsServer = (server: any, path: string) => { execute, subscribe, schema, - onOperation: (_: any, params: Object) => { - const errorFormatter = createErrorFormatter(); - params.formatError = errorFormatter; - return params; - }, - onDisconnect: rawSocket => { - getUserIdFromReq(rawSocket.upgradeReq) - .then(id => { - setUserOnline(id, false); - }) - .catch(err => { - // Ignore errors - }); - }, - onConnect: (connectionParams, rawSocket) => - getUserIdFromReq(rawSocket.upgradeReq) - .then(id => setUserOnline(id, true)) - .then(user => { - return { - user, - loaders: createLoaders({ cache: false }), - }; - }) - .catch(err => { - return { - loaders: createLoaders({ cache: false }), - }; - }), }, { server, diff --git a/api/routes/middlewares/index.js b/api/routes/middlewares/index.js index a488c83c3d..3d52548201 100644 --- a/api/routes/middlewares/index.js +++ b/api/routes/middlewares/index.js @@ -1,8 +1,10 @@ import { Router } from 'express'; -import jwt from 'jsonwebtoken'; const middlewares = Router(); +import bodyParser from 'body-parser'; +middlewares.use(bodyParser.json()); + if (process.env.NODE_ENV === 'development') { const logging = require('shared/middlewares/logging'); middlewares.use(logging); @@ -14,17 +16,6 @@ if (process.env.NODE_ENV === 'production' && !process.env.FORCE_DEV) { middlewares.use(raven); } -middlewares.use((req, res, next) => { - if (req.headers && !req.headers.cookie && req.headers.authorization) { - const token = req.headers.authorization.replace(/^\s*Bearer\s*/, ''); - try { - const decoded = jwt.verify(token, process.env.API_TOKEN_SECRET); - if (decoded.cookie) req.headers.cookie = decoded.cookie; - } catch (err) {} - } - next(); -}); - // Cross origin request support import cors from 'shared/middlewares/cors'; middlewares.use(cors); @@ -33,16 +24,6 @@ middlewares.options('*', cors); import cookieParser from 'cookie-parser'; middlewares.use(cookieParser()); -import bodyParser from 'body-parser'; -middlewares.use(bodyParser.json()); - -import { apolloUploadExpress } from 'apollo-upload-server'; -middlewares.use( - apolloUploadExpress({ - maxFileSize: 52428800, // 50mb - }) -); - import session from 'shared/middlewares/session'; middlewares.use(session); @@ -50,6 +31,38 @@ import passport from 'passport'; middlewares.use(passport.initialize()); middlewares.use(passport.session()); +// Refresh authenticated users expiry time +middlewares.use((req, res, next) => { + if (req.session && req.user) { + req.session.lastRequest = Date.now(); + } + next(); +}); + +const isSerializedJSON = str => str[0] === '{'; + +// NOTE(@mxstbr): If a logged-in user with a legacy cookie (just the user ID) sends a request +// we add all the user data to the cookie (by calling req.login) to move them to the new cookie +// format. +// @see #3944 +// @see https://stackoverflow.com/a/24498660 +middlewares.use((req, res, next) => { + if ( + req.session && + req.session.passport && + typeof req.session.passport.user === 'string' && + !isSerializedJSON(req.session.passport.user[0]) && + req.user + ) { + req.login(req.user, () => { + next(); + }); + return; + } + + next(); +}); + // This needs to come after passport otherwise we'll always redirect logged-in users import threadParamRedirect from 'shared/middlewares/thread-param'; middlewares.use(threadParamRedirect); diff --git a/api/routes/webhooks/index.js b/api/routes/webhooks/index.js deleted file mode 100644 index 2e92ee7c3e..0000000000 --- a/api/routes/webhooks/index.js +++ /dev/null @@ -1,41 +0,0 @@ -// @flow -import { stripe } from 'shared/stripe'; - -import { createInvoice, getInvoiceByChargeId } from '../../models/invoice'; -import { - updateRecurringPaymentPeriod, - getRecurringPaymentFromSubscriptionId, -} from '../../models/recurringPayment'; - -export const processInvoicePaid = async (event: Object) => { - // the object field contains all the data related to the event - const { data: { object } } = event; - - // ensure we aren't duplicating an invoice in the db - // chargeIds should be unique on stripe, so if we have a record of an invoice with the same charge id we can assume it is a duplicate - const existingInvoice = await getInvoiceByChargeId(object.charge); - if (existingInvoice) return Promise.all([]); - - // fetch the subscription record from stripe as it contains a few new fields we need below - const getSubscription = await stripe.subscriptions.retrieve( - object.subscription - ); - // we need to retrieve a community id for the payment, if one exists - const rPayment = await getRecurringPaymentFromSubscriptionId( - object.subscription - ); - - // get the customer object from stripe - const customer = await stripe.customers.retrieve(object.customer); - // create an invoice record in the database (receipt) - const invoice = await createInvoice( - object, - getSubscription, - customer, - rPayment - ); - // update the recurringPayment record in the database that triggered this invoice payment and update the period_end and period_start dates to show in the ui - const updateRecurringPayment = await updateRecurringPaymentPeriod(object); - - return Promise.all([getSubscription, invoice, updateRecurringPayment]); -}; diff --git a/api/schema.js b/api/schema.js index f93efec1ce..9ecc93730d 100644 --- a/api/schema.js +++ b/api/schema.js @@ -1,8 +1,7 @@ -//@flow +// @flow /** * The combined schema out of types and resolvers (queries, mutations and subscriptions) */ -//$FlowFixMe const { makeExecutableSchema, addSchemaLevelResolveFunction, @@ -11,7 +10,6 @@ const debug = require('debug')('api:resolvers'); const logExecutions = require('graphql-log')({ logger: debug, }); -//$FlowFixMe const { merge } = require('lodash'); import UserError from './utils/UserError'; @@ -20,15 +18,17 @@ const generalTypes = require('./types/general'); const Thread = require('./types/Thread'); const Channel = require('./types/Channel'); +const ChannelSlackSettings = require('./types/ChannelSlackSettings'); const Community = require('./types/Community'); +const CommunitySlackSettings = require('./types/CommunitySlackSettings'); const Message = require('./types/Message'); const Reaction = require('./types/Reaction'); const User = require('./types/User'); const DirectMessageThread = require('./types/DirectMessageThread'); const Notification = require('./types/Notification'); const Meta = require('./types/Meta'); -const Invoice = require('./types/Invoice'); const Search = require('./types/Search'); +const Invoice = require('./types/Invoice'); const CommunityMember = require('./types/CommunityMember'); const ThreadParticipant = require('./types/ThreadParticipant'); @@ -43,11 +43,12 @@ const notificationQueries = require('./queries/notification'); const metaQueries = require('./queries/meta'); const searchQueries = require('./queries/search'); const communityMemberQueries = require('./queries/communityMember'); +const communitySlackSettingsQueries = require('./queries/communitySlackSettings'); +const channelSlackSettingsQueries = require('./queries/channelSlackSettings'); const messageMutations = require('./mutations/message'); const threadMutations = require('./mutations/thread'); const reactionMutations = require('./mutations/reaction'); -const recurringPaymentMutations = require('./mutations/recurringPayment'); const communityMutations = require('./mutations/community'); const channelMutations = require('./mutations/channel'); const directMessageThreadMutations = require('./mutations/directMessageThread'); @@ -61,28 +62,39 @@ const notificationSubscriptions = require('./subscriptions/notification'); const directMessageThreadSubscriptions = require('./subscriptions/directMessageThread'); const threadSubscriptions = require('./subscriptions/thread'); +const rateLimit = require('./utils/rate-limit-directive').default; + +const IS_PROD = process.env.NODE_ENV === 'production' && !process.env.FORCE_DEV; + const Root = /* GraphQL */ ` - # The dummy queries and mutations are necessary because - # graphql-js cannot have empty root types and we only extend - # these types later on - # Ref: apollographql/graphql-tools#293 - type Query { - dummy: String - } - - type Mutation { - dummy: String - } - - type Subscription { - dummy: String - } - - schema { - query: Query - mutation: Mutation - subscription: Subscription - } + directive @rateLimit( + max: Int + window: Int + message: String + identityArgs: [String] + ) on FIELD_DEFINITION + + # The dummy queries and mutations are necessary because + # graphql-js cannot have empty root types and we only extend + # these types later on + # Ref: apollographql/graphql-tools#293 + type Query { + dummy: String + } + + type Mutation { + dummy: String + } + + type Subscription { + dummy: String + } + + schema { + query: Query + mutation: Mutation + subscription: Subscription + } `; const resolvers = merge( @@ -100,12 +112,13 @@ const resolvers = merge( metaQueries, searchQueries, communityMemberQueries, + communitySlackSettingsQueries, + channelSlackSettingsQueries, // mutations messageMutations, threadMutations, directMessageThreadMutations, reactionMutations, - recurringPaymentMutations, communityMutations, channelMutations, notificationMutations, @@ -131,8 +144,10 @@ const schema = makeExecutableSchema({ generalTypes, Root, Community, + CommunitySlackSettings, CommunityMember, Channel, + ChannelSlackSettings, Thread, ThreadParticipant, Message, @@ -145,13 +160,18 @@ const schema = makeExecutableSchema({ Search, ], resolvers, + schemaDirectives: IS_PROD + ? { + rateLimit, + } + : {}, }); if (process.env.REACT_APP_MAINTENANCE_MODE === 'enabled') { - console.log('\n\n⚠️ ----MAINTENANCE MODE ENABLED----⚠️\n\n'); + console.error('\n\n⚠️ ----MAINTENANCE MODE ENABLED----⚠️\n\n'); addSchemaLevelResolveFunction(schema, () => { throw new UserError( - "We're currently undergoing planned maintenance. We'll be back by 3pm UTC, please check https://twitter.com/withspectrum for ongoing updates!" + "We're currently undergoing planned maintenance. We'll be back soon, please check https://twitter.com/withspectrum for ongoing updates!" ); }); } diff --git a/api/subscriptions/directMessageThread.js b/api/subscriptions/directMessageThread.js index 1d4247b330..edf95e7324 100644 --- a/api/subscriptions/directMessageThread.js +++ b/api/subscriptions/directMessageThread.js @@ -3,8 +3,6 @@ * Define the notification subscription resolvers */ const debug = require('debug')('api:subscriptions:direct-message-thread'); -import { withFilter } from 'graphql-subscriptions'; -import { userCanViewDirectMessageThread } from './utils'; const { listenToUpdatedDirectMessageThreads, } = require('../models/directMessageThread'); @@ -31,7 +29,7 @@ module.exports = { info: GraphQLResolveInfo ) => { if (!user || !user.id) - throw new UserError( + return new UserError( 'Can only listen to direct message thread updates when signed in.' ); diff --git a/api/subscriptions/message.js b/api/subscriptions/message.js index 3cc8f48e10..c29ddd8a15 100644 --- a/api/subscriptions/message.js +++ b/api/subscriptions/message.js @@ -11,7 +11,6 @@ import Raven from 'shared/raven'; const addMessageListener = asyncify(listenToNewMessages); -import type { Message } from '../models/message'; import type { GraphQLContext } from '../'; import type { GraphQLResolveInfo } from 'graphql'; @@ -51,7 +50,7 @@ module.exports = { debug( `denying listener ${moniker}, tried listening to new messages in ${thread}` ); - throw new UserError('Thread not found.'); + return new UserError('Thread not found.'); } debug(`${moniker} listening to new messages in ${thread}`); diff --git a/api/subscriptions/notification.js b/api/subscriptions/notification.js index 4b2cfa0305..0f87695551 100644 --- a/api/subscriptions/notification.js +++ b/api/subscriptions/notification.js @@ -1,6 +1,5 @@ // @flow const debug = require('debug')('api:subscriptions:notification'); -import { withFilter } from 'graphql-subscriptions'; const { listenToNewNotifications, listenToNewDirectMessageNotifications, @@ -27,7 +26,7 @@ module.exports = { info: GraphQLResolveInfo ) => { if (!user || !user.id) - throw new UserError( + return new UserError( 'Can only listen to notifications when signed in.' ); @@ -52,7 +51,7 @@ module.exports = { info: GraphQLResolveInfo ) => { if (!user || !user.id) - throw new UserError( + return new UserError( 'Can only listen to notifications when signed in.' ); diff --git a/api/subscriptions/thread.js b/api/subscriptions/thread.js index 631e5dab5c..6f5e164ee0 100644 --- a/api/subscriptions/thread.js +++ b/api/subscriptions/thread.js @@ -1,7 +1,5 @@ // @flow const debug = require('debug')('api:subscriptions:notification'); -import { withFilter } from 'graphql-subscriptions'; -import { userIsMemberOfChannel } from './utils'; import { listenToUpdatedThreads } from '../models/thread'; import { getUserUsersChannels, @@ -26,7 +24,7 @@ module.exports = { info: GraphQLResolveInfo ) => { if (!channelIds && (!user || !user.id)) - throw new UserError( + return new UserError( 'Please provide a list of channels to listen to when not signed in.' ); @@ -38,7 +36,7 @@ module.exports = { } else { // If specific channels were passed make sure the user has permission to listen in those channels const permissions = await getUsersPermissionsInChannels( - ids.map(id => [user.id, id]) + ids.map(id => [user ? user.id : null, id]) ); ids = permissions .filter( diff --git a/api/test/__snapshots__/community.test.js.snap b/api/test/__snapshots__/community.test.js.snap index 615a8424a2..2edeba6b8f 100644 --- a/api/test/__snapshots__/community.test.js.snap +++ b/api/test/__snapshots__/community.test.js.snap @@ -59,12 +59,10 @@ exports[`should fetch a community 1`] = ` Object { "data": Object { "community": Object { - "coverPhoto": "https://spectrum.imgix.net/communities/-Kh6RfPYjmSaIWbkck8i/Twitter Header.png.0.3303118636071434", "createdAt": "2016-12-31T23:00:00.000Z", "description": "The future of communities", "id": "1", "name": "Spectrum", - "profilePhoto": "https://spectrum.imgix.net/communities/-Kh6RfPYjmSaIWbkck8i/Twitter Profile.png.0.6225566835336693", "slug": "spectrum", "website": "https://spectrum.chat", }, @@ -77,11 +75,9 @@ Object { "data": Object { "communities": Array [ Object { - "coverPhoto": "https://spectrum.imgix.net/communities/-Kh6RfPYjmSaIWbkck8i/Twitter Header.png.0.3303118636071434", "createdAt": "2016-12-31T23:00:00.000Z", "description": "The future of communities", "id": "1", - "profilePhoto": "https://spectrum.imgix.net/communities/-Kh6RfPYjmSaIWbkck8i/Twitter Profile.png.0.6225566835336693", "slug": "spectrum", "website": "https://spectrum.chat", }, @@ -111,7 +107,7 @@ Object { }, }, Object { - "cursor": "OC0y", + "cursor": "Mi0y", "node": Object { "isBlocked": false, "isMember": true, @@ -119,33 +115,33 @@ Object { "isOwner": false, "reputation": 100, "user": Object { - "id": "8", + "id": "2", }, }, }, Object { - "cursor": "MS0z", + "cursor": "OC0z", "node": Object { "isBlocked": false, "isMember": true, "isModerator": false, - "isOwner": true, + "isOwner": false, "reputation": 100, "user": Object { - "id": "1", + "id": "8", }, }, }, Object { - "cursor": "Mi00", + "cursor": "MS00", "node": Object { "isBlocked": false, "isMember": true, "isModerator": false, - "isOwner": false, + "isOwner": true, "reputation": 100, "user": Object { - "id": "2", + "id": "1", }, }, }, diff --git a/api/test/channel/mutations/deleteChannel.test.js b/api/test/channel/mutations/deleteChannel.test.js index 733cc0196b..f1490c774c 100644 --- a/api/test/channel/mutations/deleteChannel.test.js +++ b/api/test/channel/mutations/deleteChannel.test.js @@ -56,7 +56,6 @@ const DEFAULT_THREADS = [ title: 'The first thread! 🎉', body: '', }, - attachments: [], edits: [], modifiedAt: new Date(DATE), lastActive: new Date(DATE), @@ -73,7 +72,6 @@ const DEFAULT_THREADS = [ title: 'Another thread', body: '', }, - attachments: [], edits: [], modifiedAt: new Date(DATE + 1), lastActive: new Date(DATE + 1), @@ -90,7 +88,6 @@ const DEFAULT_THREADS = [ title: 'Yet another thread', body: '', }, - attachments: [], edits: [], modifiedAt: new Date(DATE + 2), lastActive: new Date(DATE + 2), @@ -140,8 +137,8 @@ const variables = { it('should delete a channel if user is owner', async () => { const query = /* GraphQL */ ` mutation deleteChannel($channelId: ID!) { - deleteChannel (channelId: $channelId) - }, + deleteChannel(channelId: $channelId) + } `; const context = { @@ -157,8 +154,8 @@ it('should delete a channel if user is owner', async () => { it('should not delete a channel if user is not owner', async () => { const query = /* GraphQL */ ` mutation deleteChannel($channelId: ID!) { - deleteChannel (channelId: $channelId) - }, + deleteChannel(channelId: $channelId) + } `; const context = { @@ -174,8 +171,8 @@ it('should not delete a channel if user is not owner', async () => { it('should not delete a channel if user is not signed in', async () => { const query = /* GraphQL */ ` mutation deleteChannel($channelId: ID!) { - deleteChannel (channelId: $channelId) - }, + deleteChannel(channelId: $channelId) + } `; expect.assertions(1); @@ -187,8 +184,8 @@ it('should not delete a channel if user is not signed in', async () => { it('should not delete the general channel', async () => { const query = /* GraphQL */ ` mutation deleteChannel($channelId: ID!) { - deleteChannel (channelId: $channelId) - }, + deleteChannel(channelId: $channelId) + } `; const context = { @@ -215,8 +212,8 @@ it('should delete all threads in the deleted channel', async () => { const query = /* GraphQL */ ` mutation deleteChannel($channelId: ID!) { - deleteChannel (channelId: $channelId) - }, + deleteChannel(channelId: $channelId) + } `; const context = { diff --git a/api/test/channel/queries/__snapshots__/blockedUsers.test.js.snap b/api/test/channel/queries/__snapshots__/blockedUsers.test.js.snap index d012b9db42..201baadfdb 100644 --- a/api/test/channel/queries/__snapshots__/blockedUsers.test.js.snap +++ b/api/test/channel/queries/__snapshots__/blockedUsers.test.js.snap @@ -1,18 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should fetch a channels blocked users 1`] = ` +exports[`should fetch blocked users if moderates channel 1`] = ` Object { "data": Object { "channel": Object { "blockedUsers": Array [ Object { - "coverPhoto": "https://spectrum-imgp.imgix.net/https%3A%2F%2Fpbs.twimg.com%2Fprofile_banners%2F17106008%2F1491444958%2F1500x500?w=640&h=192&ixlib=js-1.1.1&s=f544283ec86a42694f40b2d6c462d695", "description": "I am blocked in the Spectrum community", "firstName": null, "id": "4", "isOnline": null, "name": "Blocked user", - "profilePhoto": "https://spectrum-imgp.imgix.net/https%3A%2F%2Fpbs.twimg.com%2Fprofile_images%2F848823167699230721%2F-9CbPtto_bigger.jpg?w=128&h=128&ixlib=js-1.1.1&s=fc14b0aafec83958dc1896b0c899b68c", "timezone": null, "username": "blocked-user", "website": "", @@ -23,3 +21,75 @@ Object { }, } `; + +exports[`should fetch blocked users if moderates community 1`] = ` +Object { + "data": Object { + "channel": Object { + "blockedUsers": Array [ + Object { + "description": "I am blocked in the Spectrum community", + "firstName": null, + "id": "4", + "isOnline": null, + "name": "Blocked user", + "timezone": null, + "username": "blocked-user", + "website": "", + }, + ], + "id": "1", + }, + }, +} +`; + +exports[`should fetch blocked users if owns community 1`] = ` +Object { + "data": Object { + "channel": Object { + "blockedUsers": Array [ + Object { + "description": "I am blocked in the Spectrum community", + "firstName": null, + "id": "4", + "isOnline": null, + "name": "Blocked user", + "timezone": null, + "username": "blocked-user", + "website": "", + }, + ], + "id": "1", + }, + }, +} +`; + +exports[`should not fetch blocked users if no permissions 1`] = ` +Object { + "data": Object { + "channel": Object { + "blockedUsers": null, + "id": "1", + }, + }, + "errors": Array [ + [GraphQLError: You don’t have permission to manage this channel], + ], +} +`; + +exports[`should not fetch blocked users if not authed 1`] = ` +Object { + "data": Object { + "channel": Object { + "blockedUsers": null, + "id": "1", + }, + }, + "errors": Array [ + [GraphQLError: You must be signed in to do this], + ], +} +`; diff --git a/api/test/channel/queries/__snapshots__/memberConnection.test.js.snap b/api/test/channel/queries/__snapshots__/memberConnection.test.js.snap index caf42504d1..5064fa79c5 100644 --- a/api/test/channel/queries/__snapshots__/memberConnection.test.js.snap +++ b/api/test/channel/queries/__snapshots__/memberConnection.test.js.snap @@ -8,11 +8,11 @@ Object { "memberConnection": Object { "edges": Array [ Object { - "cursor": "OC0x", + "cursor": "My0x", "node": Object { "contextPermissions": null, - "id": "8", - "name": "Channel moderator", + "id": "3", + "name": "Bryn Jackson", }, }, Object { @@ -40,11 +40,11 @@ Object { }, }, Object { - "cursor": "My01", + "cursor": "OC01", "node": Object { "contextPermissions": null, - "id": "3", - "name": "Bryn Jackson", + "id": "8", + "name": "Channel moderator", }, }, ], diff --git a/api/test/channel/queries/__snapshots__/pendingUsers.test.js.snap b/api/test/channel/queries/__snapshots__/pendingUsers.test.js.snap index 56e5adb16d..b795694c71 100644 --- a/api/test/channel/queries/__snapshots__/pendingUsers.test.js.snap +++ b/api/test/channel/queries/__snapshots__/pendingUsers.test.js.snap @@ -1,12 +1,83 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should fetch a channels pending users 1`] = ` +exports[`should fetch pending users if moderates channel 1`] = ` Object { "data": Object { "channel": Object { + "blockedUsers": Array [ + Object { + "description": "I am blocked in the Spectrum community", + "firstName": null, + "id": "4", + "isOnline": null, + "name": "Blocked user", + "timezone": null, + "username": "blocked-user", + "website": "", + }, + ], "id": "2", - "pendingUsers": Array [], }, }, } `; + +exports[`should fetch pending users if moderates community 1`] = ` +Object { + "data": Object { + "channel": Object { + "blockedUsers": Array [ + Object { + "description": "I am blocked in the Spectrum community", + "firstName": null, + "id": "4", + "isOnline": null, + "name": "Blocked user", + "timezone": null, + "username": "blocked-user", + "website": "", + }, + ], + "id": "2", + }, + }, +} +`; + +exports[`should fetch pending users if owns community 1`] = ` +Object { + "data": Object { + "channel": Object { + "blockedUsers": Array [ + Object { + "description": "I am blocked in the Spectrum community", + "firstName": null, + "id": "4", + "isOnline": null, + "name": "Blocked user", + "timezone": null, + "username": "blocked-user", + "website": "", + }, + ], + "id": "2", + }, + }, +} +`; + +exports[`should not fetch pending users if no permissions 1`] = ` +Object { + "data": Object { + "channel": null, + }, +} +`; + +exports[`should not fetch pending users if not authed 1`] = ` +Object { + "data": Object { + "channel": null, + }, +} +`; diff --git a/api/test/channel/queries/__snapshots__/slackSettings.test.js.snap b/api/test/channel/queries/__snapshots__/slackSettings.test.js.snap new file mode 100644 index 0000000000..67a5d71d14 --- /dev/null +++ b/api/test/channel/queries/__snapshots__/slackSettings.test.js.snap @@ -0,0 +1,74 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should fetch slack settings if moderates channel 1`] = ` +Object { + "data": Object { + "channel": Object { + "id": "1", + "slackSettings": Object { + "botLinks": Object { + "threadCreated": null, + }, + }, + }, + }, +} +`; + +exports[`should fetch slack settings if moderates community 1`] = ` +Object { + "data": Object { + "channel": Object { + "id": "1", + "slackSettings": Object { + "botLinks": Object { + "threadCreated": null, + }, + }, + }, + }, +} +`; + +exports[`should fetch slack settings if owns community 1`] = ` +Object { + "data": Object { + "channel": Object { + "id": "1", + "slackSettings": Object { + "botLinks": Object { + "threadCreated": null, + }, + }, + }, + }, +} +`; + +exports[`should not fetch slack settings if no permissions 1`] = ` +Object { + "data": Object { + "channel": Object { + "id": "1", + "slackSettings": null, + }, + }, + "errors": Array [ + [GraphQLError: You don’t have permission to manage this channel], + ], +} +`; + +exports[`should not fetch slack settings if not authed 1`] = ` +Object { + "data": Object { + "channel": Object { + "id": "1", + "slackSettings": null, + }, + }, + "errors": Array [ + [GraphQLError: You must be signed in to do this], + ], +} +`; diff --git a/api/test/channel/queries/blockedUsers.test.js b/api/test/channel/queries/blockedUsers.test.js index 9713c200ab..f140c07e2a 100644 --- a/api/test/channel/queries/blockedUsers.test.js +++ b/api/test/channel/queries/blockedUsers.test.js @@ -1,16 +1,31 @@ //@flow import { request } from '../../utils'; -import { SPECTRUM_GENERAL_CHANNEL_ID } from '../../../migrations/seed/default/constants'; +import data from 'shared/testing/data'; +import { + SPECTRUM_GENERAL_CHANNEL_ID, + CHANNEL_MODERATOR_USER_ID, + COMMUNITY_MODERATOR_USER_ID, + MAX_ID, + PREVIOUS_MEMBER_USER_ID, +} from '../../../migrations/seed/default/constants'; +const channelModerator = data.users.find( + ({ id }) => id === CHANNEL_MODERATOR_USER_ID +); +const communityModerator = data.users.find( + ({ id }) => id === COMMUNITY_MODERATOR_USER_ID +); +const communityOwner = data.users.find(({ id }) => id === MAX_ID); +const noPermissionUser = data.users.find( + ({ id }) => id === PREVIOUS_MEMBER_USER_ID +); -it('should fetch a channels blocked users', async () => { +it('should not fetch blocked users if not authed', async () => { const query = /* GraphQL */ ` { channel(id: "${SPECTRUM_GENERAL_CHANNEL_ID}") { id blockedUsers { id - profilePhoto - coverPhoto name firstName description @@ -28,3 +43,111 @@ it('should fetch a channels blocked users', async () => { expect(result).toMatchSnapshot(); }); + +it('should not fetch blocked users if no permissions', async () => { + const query = /* GraphQL */ ` + { + channel(id: "${SPECTRUM_GENERAL_CHANNEL_ID}") { + id + blockedUsers { + id + name + firstName + description + website + username + isOnline + timezone + } + } + } + `; + + const context = { user: noPermissionUser }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); + +it('should fetch blocked users if moderates channel', async () => { + const query = /* GraphQL */ ` + { + channel(id: "${SPECTRUM_GENERAL_CHANNEL_ID}") { + id + blockedUsers { + id + name + firstName + description + website + username + isOnline + timezone + } + } + } + `; + + const context = { user: channelModerator }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); + +it('should fetch blocked users if moderates community', async () => { + const query = /* GraphQL */ ` + { + channel(id: "${SPECTRUM_GENERAL_CHANNEL_ID}") { + id + blockedUsers { + id + name + firstName + description + website + username + isOnline + timezone + } + } + } + `; + + const context = { user: communityModerator }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); + +it('should fetch blocked users if owns community', async () => { + const query = /* GraphQL */ ` + { + channel(id: "${SPECTRUM_GENERAL_CHANNEL_ID}") { + id + blockedUsers { + id + name + firstName + description + website + username + isOnline + timezone + } + } + } + `; + + const context = { user: communityOwner }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); diff --git a/api/test/channel/queries/channelSettings.test.js b/api/test/channel/queries/channelSettings.test.js index 5c55a74994..e30b82f778 100644 --- a/api/test/channel/queries/channelSettings.test.js +++ b/api/test/channel/queries/channelSettings.test.js @@ -1,6 +1,14 @@ //@flow import { request } from '../../utils'; -import { SPECTRUM_GENERAL_CHANNEL_ID } from '../../../migrations/seed/default/constants'; +import data from 'shared/testing/data'; +import { + SPECTRUM_GENERAL_CHANNEL_ID, + CHANNEL_MODERATOR_USER_ID, +} from '../../../migrations/seed/default/constants'; + +const channelModerator = data.users.find( + ({ id }) => id === CHANNEL_MODERATOR_USER_ID +); it('should fetch a private channels token join settings', async () => { const query = /* GraphQL */ ` @@ -16,8 +24,10 @@ it('should fetch a private channels token join settings', async () => { `; expect.assertions(3); - const result = await request(query); - const { data: { channel } } = result; + const result = await request(query, { context: { user: channelModerator } }); + const { + data: { channel }, + } = result; expect(channel.joinSettings.tokenJoinEnabled).toEqual(false); expect(channel.joinSettings.token).toEqual(null); diff --git a/api/test/channel/queries/pendingUsers.test.js b/api/test/channel/queries/pendingUsers.test.js index f020876cc4..83237b6580 100644 --- a/api/test/channel/queries/pendingUsers.test.js +++ b/api/test/channel/queries/pendingUsers.test.js @@ -1,16 +1,31 @@ //@flow import { request } from '../../utils'; -import { SPECTRUM_PRIVATE_CHANNEL_ID } from '../../../migrations/seed/default/constants'; +import data from 'shared/testing/data'; +import { + SPECTRUM_PRIVATE_CHANNEL_ID, + CHANNEL_MODERATOR_USER_ID, + COMMUNITY_MODERATOR_USER_ID, + MAX_ID, + PREVIOUS_MEMBER_USER_ID, +} from '../../../migrations/seed/default/constants'; +const channelModerator = data.users.find( + ({ id }) => id === CHANNEL_MODERATOR_USER_ID +); +const communityModerator = data.users.find( + ({ id }) => id === COMMUNITY_MODERATOR_USER_ID +); +const communityOwner = data.users.find(({ id }) => id === MAX_ID); +const noPermissionUser = data.users.find( + ({ id }) => id === PREVIOUS_MEMBER_USER_ID +); -it('should fetch a channels pending users', async () => { +it('should not fetch pending users if not authed', async () => { const query = /* GraphQL */ ` { channel(id: "${SPECTRUM_PRIVATE_CHANNEL_ID}") { id - pendingUsers { + blockedUsers { id - profilePhoto - coverPhoto name firstName description @@ -28,3 +43,111 @@ it('should fetch a channels pending users', async () => { expect(result).toMatchSnapshot(); }); + +it('should not fetch pending users if no permissions', async () => { + const query = /* GraphQL */ ` + { + channel(id: "${SPECTRUM_PRIVATE_CHANNEL_ID}") { + id + blockedUsers { + id + name + firstName + description + website + username + isOnline + timezone + } + } + } + `; + + const context = { user: noPermissionUser }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); + +it('should fetch pending users if moderates channel', async () => { + const query = /* GraphQL */ ` + { + channel(id: "${SPECTRUM_PRIVATE_CHANNEL_ID}") { + id + blockedUsers { + id + name + firstName + description + website + username + isOnline + timezone + } + } + } + `; + + const context = { user: channelModerator }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); + +it('should fetch pending users if moderates community', async () => { + const query = /* GraphQL */ ` + { + channel(id: "${SPECTRUM_PRIVATE_CHANNEL_ID}") { + id + blockedUsers { + id + name + firstName + description + website + username + isOnline + timezone + } + } + } + `; + + const context = { user: communityModerator }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); + +it('should fetch pending users if owns community', async () => { + const query = /* GraphQL */ ` + { + channel(id: "${SPECTRUM_PRIVATE_CHANNEL_ID}") { + id + blockedUsers { + id + name + firstName + description + website + username + isOnline + timezone + } + } + } + `; + + const context = { user: communityOwner }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); diff --git a/api/test/channel/queries/slackSettings.test.js b/api/test/channel/queries/slackSettings.test.js new file mode 100644 index 0000000000..809b799a6c --- /dev/null +++ b/api/test/channel/queries/slackSettings.test.js @@ -0,0 +1,128 @@ +//@flow +import { request } from '../../utils'; +import data from 'shared/testing/data'; +import { + SPECTRUM_GENERAL_CHANNEL_ID, + CHANNEL_MODERATOR_USER_ID, + COMMUNITY_MODERATOR_USER_ID, + MAX_ID, + PREVIOUS_MEMBER_USER_ID, +} from '../../../migrations/seed/default/constants'; +const channelModerator = data.users.find( + ({ id }) => id === CHANNEL_MODERATOR_USER_ID +); +const communityModerator = data.users.find( + ({ id }) => id === COMMUNITY_MODERATOR_USER_ID +); +const communityOwner = data.users.find(({ id }) => id === MAX_ID); +const noPermissionUser = data.users.find( + ({ id }) => id === PREVIOUS_MEMBER_USER_ID +); + +it('should not fetch slack settings if not authed', async () => { + const query = /* GraphQL */ ` + { + channel(id: "${SPECTRUM_GENERAL_CHANNEL_ID}") { + id + slackSettings { + botLinks { + threadCreated + } + } + } + } + `; + + expect.assertions(1); + const result = await request(query); + + expect(result).toMatchSnapshot(); +}); + +it('should not fetch slack settings if no permissions', async () => { + const query = /* GraphQL */ ` + { + channel(id: "${SPECTRUM_GENERAL_CHANNEL_ID}") { + id + slackSettings { + botLinks { + threadCreated + } + } + } + } + `; + + const context = { user: noPermissionUser }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); + +it('should fetch slack settings if moderates channel', async () => { + const query = /* GraphQL */ ` + { + channel(id: "${SPECTRUM_GENERAL_CHANNEL_ID}") { + id + slackSettings { + botLinks { + threadCreated + } + } + } + } + `; + + const context = { user: channelModerator }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); + +it('should fetch slack settings if moderates community', async () => { + const query = /* GraphQL */ ` + { + channel(id: "${SPECTRUM_GENERAL_CHANNEL_ID}") { + id + slackSettings { + botLinks { + threadCreated + } + } + } + } + `; + + const context = { user: communityModerator }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); + +it('should fetch slack settings if owns community', async () => { + const query = /* GraphQL */ ` + { + channel(id: "${SPECTRUM_GENERAL_CHANNEL_ID}") { + id + slackSettings { + botLinks { + threadCreated + } + } + } + } + `; + + const context = { user: communityOwner }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); diff --git a/api/test/community.test.js b/api/test/community.test.js index 64a31e223f..b5b7c57f04 100644 --- a/api/test/community.test.js +++ b/api/test/community.test.js @@ -12,8 +12,6 @@ it('should fetch a community', async () => { slug description website - profilePhoto - coverPhoto } } `; @@ -56,8 +54,6 @@ it('should fetch a list of communities', async () => { slug description website - profilePhoto - coverPhoto } } `; diff --git a/api/test/community/queries/__snapshots__/slackSettings.test.js.snap b/api/test/community/queries/__snapshots__/slackSettings.test.js.snap new file mode 100644 index 0000000000..bcba16620c --- /dev/null +++ b/api/test/community/queries/__snapshots__/slackSettings.test.js.snap @@ -0,0 +1,77 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should fetch slack settings if moderates community 1`] = ` +Object { + "data": Object { + "community": Object { + "id": "1", + "slackSettings": Object { + "hasSentInvites": false, + "invitesSentAt": null, + "isConnected": false, + "memberCount": null, + "teamName": null, + }, + }, + }, +} +`; + +exports[`should fetch slack settings if owns community 1`] = ` +Object { + "data": Object { + "community": Object { + "id": "1", + "slackSettings": Object { + "hasSentInvites": false, + "invitesSentAt": null, + "isConnected": false, + "memberCount": null, + "teamName": null, + }, + }, + }, +} +`; + +exports[`should not fetch slack settings if moderates channel 1`] = ` +Object { + "data": Object { + "community": Object { + "id": "1", + "slackSettings": null, + }, + }, + "errors": Array [ + [GraphQLError: You don’t have permission to manage this channel], + ], +} +`; + +exports[`should not fetch slack settings if no permissions 1`] = ` +Object { + "data": Object { + "community": Object { + "id": "1", + "slackSettings": null, + }, + }, + "errors": Array [ + [GraphQLError: You don’t have permission to manage this channel], + ], +} +`; + +exports[`should not fetch slack settings if not authed 1`] = ` +Object { + "data": Object { + "community": Object { + "id": "1", + "slackSettings": null, + }, + }, + "errors": Array [ + [GraphQLError: You must be signed in to do this], + ], +} +`; diff --git a/api/test/community/queries/slackSettings.test.js b/api/test/community/queries/slackSettings.test.js new file mode 100644 index 0000000000..67582e539e --- /dev/null +++ b/api/test/community/queries/slackSettings.test.js @@ -0,0 +1,138 @@ +//@flow +import { request } from '../../utils'; +import data from 'shared/testing/data'; +import { + SPECTRUM_COMMUNITY_ID, + CHANNEL_MODERATOR_USER_ID, + COMMUNITY_MODERATOR_USER_ID, + MAX_ID, + PREVIOUS_MEMBER_USER_ID, +} from '../../../migrations/seed/default/constants'; +const channelModerator = data.users.find( + ({ id }) => id === CHANNEL_MODERATOR_USER_ID +); +const communityModerator = data.users.find( + ({ id }) => id === COMMUNITY_MODERATOR_USER_ID +); +const communityOwner = data.users.find(({ id }) => id === MAX_ID); +const noPermissionUser = data.users.find( + ({ id }) => id === PREVIOUS_MEMBER_USER_ID +); + +it('should not fetch slack settings if not authed', async () => { + const query = /* GraphQL */ ` + { + community(id: "${SPECTRUM_COMMUNITY_ID}") { + id + slackSettings { + teamName + isConnected + hasSentInvites + memberCount + invitesSentAt + } + } + } + `; + + expect.assertions(1); + const result = await request(query); + + expect(result).toMatchSnapshot(); +}); + +it('should not fetch slack settings if no permissions', async () => { + const query = /* GraphQL */ ` + { + community(id: "${SPECTRUM_COMMUNITY_ID}") { + id + slackSettings { + teamName + isConnected + hasSentInvites + memberCount + invitesSentAt + } + } + } + `; + + const context = { user: noPermissionUser }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); + +it('should not fetch slack settings if moderates channel', async () => { + const query = /* GraphQL */ ` + { + community(id: "${SPECTRUM_COMMUNITY_ID}") { + id + slackSettings { + teamName + isConnected + hasSentInvites + memberCount + invitesSentAt + } + } + } + `; + + const context = { user: channelModerator }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); + +it('should fetch slack settings if moderates community', async () => { + const query = /* GraphQL */ ` + { + community(id: "${SPECTRUM_COMMUNITY_ID}") { + id + slackSettings { + teamName + isConnected + hasSentInvites + memberCount + invitesSentAt + } + } + } + `; + + const context = { user: communityModerator }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); + +it('should fetch slack settings if owns community', async () => { + const query = /* GraphQL */ ` + { + community(id: "${SPECTRUM_COMMUNITY_ID}") { + id + slackSettings { + teamName + isConnected + hasSentInvites + memberCount + invitesSentAt + } + } + } + `; + + const context = { user: communityOwner }; + + expect.assertions(1); + const result = await request(query, { context }); + + expect(result).toMatchSnapshot(); +}); diff --git a/api/test/utils.js b/api/test/utils.js index 321443cf74..3da122f2b6 100644 --- a/api/test/utils.js +++ b/api/test/utils.js @@ -1,7 +1,6 @@ // @flow import { graphql } from 'graphql'; import createLoaders from '../loaders'; - import schema from '../schema'; type Options = { @@ -12,11 +11,15 @@ type Options = { }; // Nice little helper function for tests -export const request = (query: mixed, { context, variables }: Options = {}) => - graphql( +export const request = (query: mixed, { context, variables }: Options = {}) => { + return graphql( schema, query, undefined, - { loaders: createLoaders(), ...context }, + { + loaders: createLoaders(), + ...context, + }, variables ); +}; diff --git a/api/test/utils/__mocks__/debug.js b/api/test/utils/__mocks__/debug.js new file mode 100644 index 0000000000..a9293c78f4 --- /dev/null +++ b/api/test/utils/__mocks__/debug.js @@ -0,0 +1,15 @@ +// @flow + +const loggers = {}; + +function makeLogger() { + function logger(...args: any[]) { + logger.log.push(args.join(' ')); + } + logger.log = []; + return logger; +} + +module.exports = (namespace: string) => { + return (loggers[namespace] = loggers[namespace] || makeLogger()); +}; diff --git a/api/test/utils/__snapshots__/create-graphql-error-formatter.test.js.snap b/api/test/utils/__snapshots__/create-graphql-error-formatter.test.js.snap new file mode 100644 index 0000000000..db89c79001 --- /dev/null +++ b/api/test/utils/__snapshots__/create-graphql-error-formatter.test.js.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`createGraphQLErrorFormatter logs error path 1`] = ` +Array [ + "---GraphQL Error---", + "Cannot return null for non-nullable field Thread.channel. + +GraphQL request (9:15) + 8: id + 9: channel { + ^ +10: id +", + "path community.threadConnection.edges[0].node.channel", + "------------------- +", +] +`; + +exports[`createGraphQLErrorFormatter logs query and variables 1`] = ` +Array [ + "---GraphQL Error---", + "Cannot return null for non-nullable field Thread.channel. + +GraphQL request (9:15) + 8: id + 9: channel { + ^ +10: id +", + "query getCommunityThreadConnection( $id: ID )", + "variables {\\"id\\":1}", + "path community.threadConnection.edges[0].node.channel", + "------------------- +", +] +`; diff --git a/api/test/utils/create-graphql-error-formatter.test.js b/api/test/utils/create-graphql-error-formatter.test.js new file mode 100644 index 0000000000..26552e86a1 --- /dev/null +++ b/api/test/utils/create-graphql-error-formatter.test.js @@ -0,0 +1,106 @@ +// @flow + +const debug = require('debug')('api:utils:error-formatter'); + +import { graphql, print } from 'graphql'; +import { makeExecutableSchema } from 'graphql-tools'; + +import createErrorFormatter from '../../utils/create-graphql-error-formatter'; + +const typeDefs = ` + type Community { + id: ID! + threadConnection(first: Int = 10, after: String): CommunityThreadsConnection! + } + + type CommunityThreadsConnection { + edges: [CommunityThreadEdge!] + } + + type CommunityThreadEdge { + node: Thread! + } + + type Thread { + id: ID! + channel: Channel! + } + + type Channel { + id: ID! + } + + type Query { + community(id: ID): Community + } +`; + +const resolvers = { + Query: { + community: () => ({ id: 1 }), + }, + Community: { + threadConnection: () => ({}), + }, + CommunityThreadsConnection: { + edges: () => [6], + }, + CommunityThreadEdge: { + node: id => ({ id }), + }, + Thread: { + channel: () => null, + }, +}; + +const schema = makeExecutableSchema({ typeDefs, resolvers }); + +const query = ` + query getCommunityThreadConnection( $id: ID ){ + community( id: $id ) { + id + threadConnection { + edges { + node { + id + channel { + id + } + } + } + } + } + } + `; + +const variables = { id: 1 }; + +describe('createGraphQLErrorFormatter', () => { + const stderrWrite = process.stderr.write; + + afterEach(() => { + process.stderr.write = stderrWrite; + }); + + it('returns function', () => { + expect(createErrorFormatter()).toBeInstanceOf(Function); + }); + + it('logs error path', async () => { + debug.log = []; + + const result = await graphql(schema, query, null, null, variables); + const formatter = createErrorFormatter(); + (result.errors || []).forEach(formatter); + expect(debug.log).toMatchSnapshot(); + }); + + it('logs query and variables', async () => { + debug.log = []; + + const result = await graphql(schema, query, null, null, variables); + const formatter = createErrorFormatter({ body: { query, variables } }); + (result.errors || []).forEach(formatter); + expect(debug.log).toMatchSnapshot(); + }); +}); diff --git a/api/types/Channel.js b/api/types/Channel.js index 3e6f110ac8..fe3a193801 100644 --- a/api/types/Channel.js +++ b/api/types/Channel.js @@ -1,139 +1,142 @@ // @flow const Channel = /* GraphQL */ ` - type ChannelMembersConnection { - pageInfo: PageInfo! - edges: [ChannelMemberEdge!] - } - - type ChannelMemberEdge { - cursor: String! - node: User! - } - - type ChannelThreadsConnection { - pageInfo: PageInfo! - edges: [ChannelThreadEdge!] - } - - type ChannelThreadEdge { - cursor: String! - node: Thread! - } - - type ChannelMetaData { - threads: Int - members: Int - } - - input CreateChannelInput { - name: String! - slug: String! - description: String - communityId: ID! - isPrivate: Boolean - isDefault: Boolean - } - - input EditChannelInput { - name: String - slug: String - description: String - isPrivate: Boolean - channelId: ID! - } - - enum PendingActionType { - block - approve - } - - input TogglePendingUserInput { - channelId: ID! - userId: ID! - action: PendingActionType! - } - - input UnblockUserInput { - channelId: ID! - userId: ID! - } - - type JoinSettings { - tokenJoinEnabled: Boolean - token: String - } - - type Channel { - id: ID! - createdAt: Date! - modifiedAt: Date - name: String! - description: String! - slug: String! - isPrivate: Boolean - isDefault: Boolean - isArchived: Boolean + type ChannelMembersConnection { + pageInfo: PageInfo! + edges: [ChannelMemberEdge!] + } + + type ChannelMemberEdge { + cursor: String! + node: User! + } + + type ChannelThreadsConnection { + pageInfo: PageInfo! + edges: [ChannelThreadEdge!] + } + + type ChannelThreadEdge { + cursor: String! + node: Thread! + } + + type ChannelMetaData { + threads: Int + @deprecated(reason: "metaData.threads is deprecated and always returns 0") + members: Int + onlineMembers: Int + } + + input CreateChannelInput { + name: String! + slug: LowercaseString! + description: String + communityId: ID! + isPrivate: Boolean + isDefault: Boolean + } + + input EditChannelInput { + name: String + slug: LowercaseString + description: String + isPrivate: Boolean + channelId: ID! + } + + enum PendingActionType { + block + approve + } + + input TogglePendingUserInput { + channelId: ID! + userId: ID! + action: PendingActionType! + } + + input UnblockUserInput { + channelId: ID! + userId: ID! + } + + type Channel { + id: ID! + createdAt: Date! + modifiedAt: Date + name: String! + description: String! + slug: LowercaseString! + isPrivate: Boolean + isDefault: Boolean + isArchived: Boolean channelPermissions: ChannelPermissions! @cost(complexity: 1) - communityPermissions: CommunityPermissions! + communityPermissions: CommunityPermissions! community: Community! @cost(complexity: 1) - threadConnection(first: Int = 10, after: String): ChannelThreadsConnection! @cost(complexity: 1, multiplier: "first") - memberConnection(first: Int = 10, after: String): ChannelMembersConnection! @cost(complexity: 1, multiplier: "first") - memberCount: Int! + threadConnection(first: Int = 10, after: String): ChannelThreadsConnection! + @cost(complexity: 1, multipliers: ["first"]) + memberConnection(first: Int = 10, after: String): ChannelMembersConnection! + @cost(complexity: 1, multipliers: ["first"]) + memberCount: Int! metaData: ChannelMetaData @cost(complexity: 1) pendingUsers: [User] @cost(complexity: 3) - blockedUsers: [User] @cost(complexity: 3) - moderators: [User] @cost(complexity: 3) - owners: [User] @cost(complexity: 3) - joinSettings: JoinSettings - - } - - extend type Query { - channel(id: ID, channelSlug: String, communitySlug: String): Channel @cost(complexity: 1) - } - - input ArchiveChannelInput { - channelId: ID! - } - - input RestoreChannelInput { - channelId: ID! - } - - input JoinChannelWithTokenInput { - communitySlug: String! - channelSlug: String! - token: String! - } - - input EnableChannelTokenJoinInput { - id: ID! - } - - input DisableChannelTokenJoinInput { - id: ID! - } - - input ResetChannelJoinTokenInput { - id: ID! - } - - extend type Mutation { - createChannel(input: CreateChannelInput!): Channel - editChannel(input: EditChannelInput!): Channel - deleteChannel(channelId: ID!): Boolean - toggleChannelSubscription(channelId: ID!): Channel - joinChannelWithToken(input: JoinChannelWithTokenInput!): Channel - toggleChannelNotifications(channelId: ID!): Channel - togglePendingUser(input: TogglePendingUserInput!): Channel - unblockUser(input: UnblockUserInput!): Channel - sendChannelEmailInvites(input: EmailInvitesInput!): Boolean - archiveChannel(input: ArchiveChannelInput!): Channel - restoreChannel(input: RestoreChannelInput!): Channel - enableChannelTokenJoin(input: EnableChannelTokenJoinInput!): Channel - disableChannelTokenJoin(input: DisableChannelTokenJoinInput!): Channel - resetChannelJoinToken(input: ResetChannelJoinTokenInput!): Channel - } + blockedUsers: [User] @cost(complexity: 3) + moderators: [User] @cost(complexity: 3) + owners: [User] @cost(complexity: 3) + joinSettings: JoinSettings + slackSettings: ChannelSlackSettings + } + + extend type Query { + channel( + id: ID + channelSlug: LowercaseString + communitySlug: LowercaseString + ): Channel @cost(complexity: 1) + } + + input ArchiveChannelInput { + channelId: ID! + } + + input RestoreChannelInput { + channelId: ID! + } + + input JoinChannelWithTokenInput { + communitySlug: LowercaseString! + channelSlug: LowercaseString! + token: String! + } + + input EnableChannelTokenJoinInput { + id: ID! + } + + input DisableChannelTokenJoinInput { + id: ID! + } + + input ResetChannelJoinTokenInput { + id: ID! + } + + extend type Mutation { + createChannel(input: CreateChannelInput!): Channel + @rateLimit(max: 10, window: "10m") + editChannel(input: EditChannelInput!): Channel + deleteChannel(channelId: ID!): Boolean + toggleChannelSubscription(channelId: ID!): Channel + joinChannelWithToken(input: JoinChannelWithTokenInput!): Channel + toggleChannelNotifications(channelId: ID!): Channel + togglePendingUser(input: TogglePendingUserInput!): Channel + unblockUser(input: UnblockUserInput!): Channel + archiveChannel(input: ArchiveChannelInput!): Channel + restoreChannel(input: RestoreChannelInput!): Channel + enableChannelTokenJoin(input: EnableChannelTokenJoinInput!): Channel + disableChannelTokenJoin(input: DisableChannelTokenJoinInput!): Channel + resetChannelJoinToken(input: ResetChannelJoinTokenInput!): Channel + } `; module.exports = Channel; diff --git a/api/types/ChannelSlackSettings.js b/api/types/ChannelSlackSettings.js new file mode 100644 index 0000000000..5bfa251594 --- /dev/null +++ b/api/types/ChannelSlackSettings.js @@ -0,0 +1,26 @@ +// @flow +const ChannelSlackSettings = /* GraphQL */ ` + enum BotLinksEventType { + threadCreated + } + + type BotLinks { + threadCreated: String + } + + type ChannelSlackSettings { + botLinks: BotLinks + } + + input UpdateChannelSlackBotLinksInput { + channelId: String + slackChannelId: String + eventType: BotLinksEventType + } + + extend type Mutation { + updateChannelSlackBotLinks(input: UpdateChannelSlackBotLinksInput): Channel + } +`; + +module.exports = ChannelSlackSettings; diff --git a/api/types/Community.js b/api/types/Community.js index 95bf589db4..524c80a065 100644 --- a/api/types/Community.js +++ b/api/types/Community.js @@ -1,294 +1,333 @@ // @flow const Community = /* GraphQL */ ` - type CommunityChannelsConnection { - pageInfo: PageInfo! - edges: [CommunityChannelEdge!] - } - - type CommunityChannelEdge { - node: Channel! - } - - type CommunityMembersConnection @deprecated(reason:"Use the new Community.members type") { - pageInfo: PageInfo! - edges: [CommunityMemberEdge!] - } - - type CommunityMemberEdge @deprecated(reason:"Use the new Community.members type") { - cursor: String! - node: User! - } - - type CommunityMembers { - pageInfo: PageInfo! - edges: [CommunityMembersEdge!] - } - - type CommunityMembersEdge { - cursor: String! - node: CommunityMember! - } - - type CommunityThreadsConnection { - pageInfo: PageInfo! - edges: [CommunityThreadEdge!] - } - - type CommunityThreadEdge { - cursor: String! - node: Thread! - } - - type CommunityMetaData { - members: Int - channels: Int - } - - type SlackImport { - members: String - teamName: String - sent: Date - } - - type TopAndNewThreads { - topThreads: [Thread] - newThreads: [Thread] - } - - type StripeCard { - brand: String - exp_month: Int - exp_year: Int - last4: String - } - - type StripeSource { - id: ID - card: StripeCard - isDefault: Boolean - } - - type StripeItem { - id: ID - amount: Int - quantity: Int - planId: String - planName: String - } - - type StripeSubscriptionItem { - created: Int - planId: String - planName: String - amount: Int - quantity: Int - id: String - } - - type StripeDiscount { - amount_off: Int - percent_off: Int - id: String - } - - type StripeSubscription { - id: ID - created: Int - discount: StripeDiscount - billing_cycle_anchor: Int - current_period_end: Int - canceled_at: Int - items: [StripeSubscriptionItem] - status: String - } - - type StripeInvoice { - id: ID - date: Int - items: [StripeItem] - total: Int - } - - type CommunityBillingSettings { - pendingAdministratorEmail: String - administratorEmail: String - sources: [StripeSource] - invoices: [StripeInvoice] - subscriptions: [StripeSubscription] - } - - type Features { - analytics: Boolean - prioritySupport: Boolean - } - - type BrandedLogin { - isEnabled: Boolean - message: String - } - - type Community { - id: ID! - createdAt: Date! - name: String! - slug: String! - description: String! - website: String - profilePhoto: String - coverPhoto: String - reputation: Int - pinnedThreadId: String - pinnedThread: Thread + type CommunityChannelsConnection { + pageInfo: PageInfo! + edges: [CommunityChannelEdge!] + } + + type CommunityChannelEdge { + node: Channel! + } + + type CommunityMembersConnection + @deprecated(reason: "Use the new Community.members type") { + pageInfo: PageInfo! + edges: [CommunityMemberEdge!] + } + + type CommunityMemberEdge + @deprecated(reason: "Use the new Community.members type") { + cursor: String! + node: User! + } + + type CommunityMembers { + pageInfo: PageInfo! + edges: [CommunityMembersEdge!] + } + + type CommunityMembersEdge { + cursor: String! + node: CommunityMember! + } + + type CommunityThreadsConnection { + pageInfo: PageInfo! + edges: [CommunityThreadEdge!] + } + + type CommunityThreadEdge { + cursor: String! + node: Thread! + } + + type CommunityMetaData @cacheControl(maxAge: 1800) { + members: Int + channels: Int + onlineMembers: Int + } + + type SlackImport @deprecated(reason: "Use the slack settings field instead") { + members: String + teamName: String + sent: Date + } + + type TopAndNewThreads { + topThreads: [Thread] + newThreads: [Thread] + } + + type BrandedLogin { + isEnabled: Boolean + message: String + } + + enum CommunityThreadConnectionSort { + latest + trending + } + + type Features { + analytics: Boolean + prioritySupport: Boolean + } + + type StripeCard { + brand: String + exp_month: Int + exp_year: Int + last4: String + } + + type StripeSource { + id: ID + card: StripeCard + isDefault: Boolean + } + + type StripeItem { + id: ID + amount: Int + quantity: Int + planId: String + planName: String + } + + type StripeSubscriptionItem { + created: Int + planId: String + planName: String + amount: Int + quantity: Int + id: String + } + + type StripeDiscount { + amount_off: Int + percent_off: Int + id: String + } + + type StripeSubscription { + id: ID + created: Int + discount: StripeDiscount + billing_cycle_anchor: Int + current_period_end: Int + canceledAt: Int + items: [StripeSubscriptionItem] + status: String + } + + type StripeInvoice { + id: ID + date: Int + items: [StripeItem] + total: Int + } + + type CommunityBillingSettings { + pendingAdministratorEmail: LowercaseString + administratorEmail: LowercaseString + sources: [StripeSource] + invoices: [StripeInvoice] + subscriptions: [StripeSubscription] + } + + type Community { + id: ID! + createdAt: Date + name: String! + slug: LowercaseString! + description: String + website: String + profilePhoto: String + coverPhoto: String + reputation: Int + pinnedThreadId: String + pinnedThread: Thread + isPrivate: Boolean communityPermissions: CommunityPermissions @cost(complexity: 1) - channelConnection: CommunityChannelsConnection! @cost(complexity: 1) - members(first: Int = 10, after: String, filter: MembersFilter): CommunityMembers! @cost(complexity: 5, multiplier: "first") - threadConnection(first: Int = 10, after: String): CommunityThreadsConnection! @cost(complexity: 2, multiplier: "first") + channelConnection: CommunityChannelsConnection @cost(complexity: 1) + members( + first: Int = 10 + after: String + filter: MembersFilter + ): CommunityMembers @cost(complexity: 5, multipliers: ["first"]) + threadConnection( + first: Int = 10 + after: String + sort: CommunityThreadConnectionSort = latest + ): CommunityThreadsConnection @cost(complexity: 2, multipliers: ["first"]) metaData: CommunityMetaData @cost(complexity: 10) - slackImport: SlackImport @cost(complexity: 2) - invoices: [Invoice] @cost(complexity: 1) - recurringPayments: [RecurringPayment] - isPro: Boolean @cost(complexity: 1) memberGrowth: GrowthData @cost(complexity: 10) conversationGrowth: GrowthData @cost(complexity: 3) - topMembers: [User] @cost(complexity: 10) + topMembers: [CommunityMember] @cost(complexity: 10) topAndNewThreads: TopAndNewThreads @cost(complexity: 4) - watercooler: Thread - brandedLogin: BrandedLogin - - hasFeatures: Features - hasChargeableSource: Boolean - billingSettings: CommunityBillingSettings - - memberConnection(first: Int = 10, after: String, filter: MemberConnectionFilter): CommunityMembersConnection! @deprecated(reason:"Use the new Community.members type") - contextPermissions: ContextPermissions @deprecated(reason:"Use the new CommunityMember type to get permissions") - } - - extend type Query { - community(id: ID, slug: String): Community - communities(slugs: [String], ids: [ID], curatedContentType: String): [Community] - communityMember(userId: String, communityId: String): CommunityMember - topCommunities(amount: Int = 20): [Community!] @cost(complexity: 4, multiplier: "amount") - recentCommunities: [Community!] - - searchCommunities(string: String, amount: Int = 20): [Community] @deprecated(reason:"Use the new Search query endpoint") - searchCommunityThreads(communityId: ID!, searchString: String): [Thread] @deprecated(reason:"Use the new Search query endpoint") - } - - input MembersFilter { - isOwner: Boolean - isMember: Boolean - isBlocked: Boolean - isPending: Boolean - isModerator: Boolean - } - - input MemberConnectionFilter @deprecated(reason: "Use the new MembersFilter input type") { - isOwner: Boolean - isMember: Boolean - isBlocked: Boolean - isPending: Boolean - isModerator: Boolean - } - - input CreateCommunityInput { - name: String! - slug: String! - description: String! - website: String - file: Upload - coverFile: Upload - } - - input EditCommunityInput { - name: String - description: String - website: String - file: Upload - coverFile: Upload - communityId: ID! - } - - input SendSlackInvitesInput { - id: ID! - customMessage: String - } - - input UpgradeCommunityInput { - plan: String! - token: String! - communityId: String! - } - - input DowngradeCommunityInput { - id: String! - } - - input UpdateAdministratorEmailInput { - id: ID! - email: String! - } - - input AddPaymentSourceInput { - sourceId: String! - communityId: ID! - } - - input RemovePaymentSourceInput { - sourceId: String! - communityId: ID! - } - - input MakePaymentSourceDefaultInput { - sourceId: String! - communityId: ID! - } - - input CancelSubscriptionInput { - communityId: ID! - } - - input EnableCommunityAnalyticsInput { - communityId: ID! - } - - input DisableCommunityAnalyticsInput { - communityId: ID! - } - - input EnableBrandedLoginInput { - id: String! - } - - input DisableBrandedLoginInput { - id: String! - } - - input SaveBrandedLoginSettingsInput { - id: String! - message: String - } - - extend type Mutation { - createCommunity(input: CreateCommunityInput!): Community - editCommunity(input: EditCommunityInput!): Community - deleteCommunity(communityId: ID!): Boolean - toggleCommunityMembership(communityId: ID!): Community @deprecated(reason:"Use the new addCommunityMember or removeCommunityMember mutations") - sendSlackInvites(input: SendSlackInvitesInput!): Community - sendEmailInvites(input: EmailInvitesInput!): Boolean - pinThread(threadId: ID!, communityId: ID!, value: String): Community - upgradeCommunity(input: UpgradeCommunityInput!): Community @deprecated(reason:"Use feature level upgrade mutations like enableCommunityAnalytics") - downgradeCommunity(input: DowngradeCommunityInput!): Community @deprecated(reason:"Use feature level downgrade mutations like disableCommunityAnalytics") - updateAdministratorEmail(input: UpdateAdministratorEmailInput!): Community - addPaymentSource(input: AddPaymentSourceInput!): Community - removePaymentSource(input: RemovePaymentSourceInput!): Community - makePaymentSourceDefault(input: MakePaymentSourceDefaultInput!): Community - cancelSubscription(input: CancelSubscriptionInput!): Community - enableCommunityAnalytics(input: EnableCommunityAnalyticsInput!): Community - disableCommunityAnalytics(input: DisableCommunityAnalyticsInput!): Community - enableBrandedLogin(input: EnableBrandedLoginInput!): Community - disableBrandedLogin(input: DisableBrandedLoginInput!): Community - saveBrandedLoginSettings(input: SaveBrandedLoginSettingsInput!): Community - } + watercooler: Thread + brandedLogin: BrandedLogin + joinSettings: JoinSettings + slackSettings: CommunitySlackSettings @cost(complexity: 2) + slackImport: SlackImport + @cost(complexity: 2) + @deprecated(reason: "Use slack settings field instead") + memberConnection( + first: Int = 10 + after: String + filter: MemberConnectionFilter + ): CommunityMembersConnection! + @deprecated(reason: "Use the new Community.members type") + contextPermissions: ContextPermissions + @deprecated(reason: "Use the new CommunityMember type to get permissions") + + hasFeatures: Features @deprecated(reason: "Payments are no longer used") + hasChargeableSource: Boolean + @deprecated(reason: "Payments are no longer used") + billingSettings: CommunityBillingSettings + @deprecated(reason: "Payments are no longer used") + invoices: [Invoice] @deprecated(reason: "Payments are no longer used") + recurringPayments: [RecurringPayment] + @deprecated(reason: "Payments are no longer used") + isPro: Boolean @deprecated(reason: "Payments are no longer used") + } + + extend type Query { + community(id: ID, slug: LowercaseString): Community + communities( + slugs: [LowercaseString] + ids: [ID] + curatedContentType: String + ): [Community] + topCommunities(amount: Int = 20): [Community!] + @cost(complexity: 4, multipliers: ["amount"]) + recentCommunities: [Community!] + + searchCommunities(string: String, amount: Int = 20): [Community] + @deprecated(reason: "Use the new Search query endpoint") + searchCommunityThreads(communityId: ID!, searchString: String): [Thread] + @deprecated(reason: "Use the new Search query endpoint") + } + + input MembersFilter { + isOwner: Boolean + isMember: Boolean + isBlocked: Boolean + isPending: Boolean + isModerator: Boolean + } + + input MemberConnectionFilter + @deprecated(reason: "Use the new MembersFilter input type") { + isOwner: Boolean + isMember: Boolean + isBlocked: Boolean + isPending: Boolean + isModerator: Boolean + } + + input CreateCommunityInput { + name: String! + slug: LowercaseString! + description: String! + website: String + file: Upload + coverFile: Upload + isPrivate: Boolean + } + + input EditCommunityInput { + name: String + description: String + website: String + file: Upload + coverFile: Upload + coverPhoto: String + communityId: ID! + } + + input UpgradeCommunityInput { + plan: String! + token: String! + communityId: String! + } + + input DowngradeCommunityInput { + id: String! + } + + input UpdateAdministratorEmailInput { + id: ID! + email: LowercaseString! + } + + input EnableBrandedLoginInput { + id: String! + } + + input DisableBrandedLoginInput { + id: String! + } + + input SaveBrandedLoginSettingsInput { + id: String! + message: String + } + + input ImportSlackMembersInput + @deprecated( + reason: "Slack imports are no longer used, invites sent directly with sendSlackInvites" + ) { + id: String! + } + + input SendSlackInvitesInput { + id: ID! + customMessage: String + } + + input EnableCommunityTokenJoinInput { + id: ID! + } + + input DisableCommunityTokenJoinInput { + id: ID! + } + + input ResetCommunityJoinTokenInput { + id: ID! + } + + extend type Mutation { + createCommunity(input: CreateCommunityInput!): Community + @rateLimit(max: 3, window: "15m") + editCommunity(input: EditCommunityInput!): Community + deleteCommunity(communityId: ID!): Boolean + toggleCommunityMembership(communityId: ID!): Community + @deprecated( + reason: "Use the new addCommunityMember or removeCommunityMember mutations" + ) + sendSlackInvites(input: SendSlackInvitesInput!): Community + importSlackMembers(input: ImportSlackMembersInput!): Boolean + @deprecated(reason: "Importing slack members is deprecated") + sendEmailInvites(input: EmailInvitesInput!): Boolean + pinThread(threadId: ID!, communityId: ID!, value: String): Community + upgradeCommunity(input: UpgradeCommunityInput!): Community + @deprecated( + reason: "Use feature level upgrade mutations like enableCommunityAnalytics" + ) + downgradeCommunity(input: DowngradeCommunityInput!): Community + @deprecated( + reason: "Use feature level downgrade mutations like disableCommunityAnalytics" + ) + updateAdministratorEmail(input: UpdateAdministratorEmailInput!): Community + enableBrandedLogin(input: EnableBrandedLoginInput!): Community + disableBrandedLogin(input: DisableBrandedLoginInput!): Community + saveBrandedLoginSettings(input: SaveBrandedLoginSettingsInput!): Community + enableCommunityTokenJoin(input: EnableCommunityTokenJoinInput!): Community + disableCommunityTokenJoin(input: DisableCommunityTokenJoinInput!): Community + resetCommunityJoinToken(input: ResetCommunityJoinTokenInput!): Community + } `; module.exports = Community; diff --git a/api/types/CommunityMember.js b/api/types/CommunityMember.js index 4b305a77ea..249d69c698 100644 --- a/api/types/CommunityMember.js +++ b/api/types/CommunityMember.js @@ -8,17 +8,22 @@ const CommunityMember = /* GraphQL */ ` isModerator: Boolean isOwner: Boolean isBlocked: Boolean + isPending: Boolean reputation: Int } extend type Query { - communityMember(userId: ID!, communityId: ID!): CommunityMember + communityMember(userId: ID!, communityId: ID!): CommunityMember } input AddCommunityMemberInput { communityId: ID! } + input AddCommunityMembersInput { + communityIds: [ID!] + } + input RemoveCommunityMemberInput { communityId: ID! } @@ -43,11 +48,50 @@ const CommunityMember = /* GraphQL */ ` communityId: ID! } + input AddPendingCommunityMemberInput { + communityId: ID! + } + + input RemovePendingCommunityMemberInput { + communityId: ID! + } + + input ApprovePendingCommunityMemberInput { + userId: ID! + communityId: ID! + } + + input BlockPendingCommunityMemberInput { + userId: ID! + communityId: ID! + } + + input AddCommunityMemberWithTokenInput { + communitySlug: LowercaseString! + token: String! + } + extend type Mutation { addCommunityMember(input: AddCommunityMemberInput!): Community + addCommunityMembers(input: AddCommunityMembersInput!): [Community] + addCommunityMemberWithToken( + input: AddCommunityMemberWithTokenInput! + ): Community + addPendingCommunityMember(input: AddPendingCommunityMemberInput!): Community + removePendingCommunityMember( + input: RemovePendingCommunityMemberInput! + ): Community + approvePendingCommunityMember( + input: ApprovePendingCommunityMemberInput! + ): CommunityMember + blockPendingCommunityMember( + input: BlockPendingCommunityMemberInput! + ): CommunityMember removeCommunityMember(input: RemoveCommunityMemberInput!): Community addCommunityModerator(input: AddCommunityModeratorInput!): CommunityMember - removeCommunityModerator(input: RemoveCommunityModeratorInput!): CommunityMember + removeCommunityModerator( + input: RemoveCommunityModeratorInput! + ): CommunityMember blockCommunityMember(input: BlockCommunityMemberInput!): CommunityMember unblockCommunityMember(input: UnblockCommunityMemberInput!): CommunityMember } diff --git a/api/types/CommunitySlackSettings.js b/api/types/CommunitySlackSettings.js new file mode 100644 index 0000000000..00f77f7851 --- /dev/null +++ b/api/types/CommunitySlackSettings.js @@ -0,0 +1,18 @@ +// @flow +const CommunitySlackSettings = /* GraphQL */ ` + type SlackChannel { + id: String + name: String + } + + type CommunitySlackSettings { + isConnected: Boolean + hasSentInvites: Boolean + teamName: String + memberCount: Int + invitesSentAt: Date + slackChannelList: [ SlackChannel ] + } +`; + +module.exports = CommunitySlackSettings; diff --git a/api/types/DirectMessageThread.js b/api/types/DirectMessageThread.js index bc830ff806..616e951dbc 100644 --- a/api/types/DirectMessageThread.js +++ b/api/types/DirectMessageThread.js @@ -1,68 +1,74 @@ // @flow const DirectMessageThread = /* GraphQL */ ` - type DirectMessagesConnection { - pageInfo: PageInfo! - edges: [DirectMessageEdge!] - } + type DirectMessagesConnection { + pageInfo: PageInfo! + edges: [DirectMessageEdge!] + } - type DirectMessageEdge { - cursor: String! - node: Message! - } + type DirectMessageEdge { + cursor: String! + node: Message! + } - type ParticipantInfo { - id: ID! - name: String - username: String - profilePhoto: String - lastActive: Date - lastSeen: Date - userId: ID! - isOnline: Boolean - } + type ParticipantInfo { + id: ID! + name: String + username: String + profilePhoto: String + lastActive: Date + lastSeen: Date + userId: ID! + isOnline: Boolean + } - type DirectMessageThread { - id: ID! - messageConnection(first: Int = 20, after: String): DirectMessagesConnection! @cost(complexity: 1, multiplier: "first") + type DirectMessageThread { + id: ID! + messageConnection( + first: Int = 20 + after: String + ): DirectMessagesConnection! @cost(complexity: 1, multipliers: ["first"]) participants: [ParticipantInfo]! @cost(complexity: 1) - snippet: String! @cost(complexity: 2) - threadLastActive: Date! - } + snippet: String @cost(complexity: 2) + threadLastActive: Date! + } - extend type Query { - directMessageThread(id: ID!): DirectMessageThread - } + extend type Query { + directMessageThread(id: ID!): DirectMessageThread + directMessageThreadByUserId(userId: ID!): DirectMessageThread + } - enum MessageType { - text - media - draftjs - } + enum MessageType { + text + media + draftjs + } - input ContentInput { - body: String! - } + input ContentInput { + body: String! + } - input DirectMessageContentInput { - messageType: MessageType! - threadType: String! - content: ContentInput! - file: Upload - } + input DirectMessageContentInput { + messageType: MessageType! + threadType: String! + content: ContentInput! + file: Upload + } - input DirectMessageThreadInput { - participants: [ID!] - message: DirectMessageContentInput! - } + input DirectMessageThreadInput { + participants: [ID!] + message: DirectMessageContentInput! + } - extend type Mutation { - createDirectMessageThread(input: DirectMessageThreadInput!): DirectMessageThread - setLastSeen(id: ID!): DirectMessageThread - } + extend type Mutation { + createDirectMessageThread( + input: DirectMessageThreadInput! + ): DirectMessageThread @rateLimit(max: 10, window: "20m") + setLastSeen(id: ID!): DirectMessageThread + } - extend type Subscription { - directMessageThreadUpdated: DirectMessageThread - } + extend type Subscription { + directMessageThreadUpdated: DirectMessageThread + } `; module.exports = DirectMessageThread; diff --git a/api/types/Invoice.js b/api/types/Invoice.js index bb05a3e002..d869066f26 100644 --- a/api/types/Invoice.js +++ b/api/types/Invoice.js @@ -1,15 +1,16 @@ // @flow +// deprecated 9/27/2018 while removing payments const Invoice = /* GraphQL */ ` - type Invoice { - id: ID! + type Invoice { + id: ID! paidAt: Int amount: Int - sourceBrand: String - sourceLast4: String - planName: String - } + sourceBrand: String + sourceLast4: String + planName: String + } - extend type Query { + extend type Query { invoice(id: ID): Invoice } `; diff --git a/api/types/Message.js b/api/types/Message.js index ff4c0e04a3..db768f4fbf 100644 --- a/api/types/Message.js +++ b/api/types/Message.js @@ -1,63 +1,72 @@ // @flow const Message = /* GraphQL */ ` - enum MessageTypes { - text - media - draftjs - } - - enum ThreadTypes { - story - directMessageThread - } - - type MessageContent { - body: String! - } - - type ReactionData { - count: Int! - hasReacted: Boolean - } - - type Message { - id: ID! - timestamp: Date! - thread: Thread - content: MessageContent! + enum MessageTypes { + text + media + draftjs + } + + enum ThreadTypes { + story + directMessageThread + } + + type MessageContent { + body: String! + } + + type ReactionData { + count: Int! + hasReacted: Boolean + } + + type Message { + id: ID! + timestamp: Date! + thread: Thread + content: MessageContent! author: ThreadParticipant! @cost(complexity: 2) reactions: ReactionData @cost(complexity: 1) - messageType: MessageTypes! - - sender: User! @deprecated(reason:"Use Message.author field instead") - } + messageType: MessageTypes! + parent: Message + modifiedAt: Date + sender: User! @deprecated(reason: "Use Message.author field instead") + } - input MessageContentInput { - body: String - } + input MessageContentInput { + body: String + } - input MessageInput { - threadId: ID! - threadType: ThreadTypes! - messageType: MessageTypes! - content: MessageContentInput! - file: Upload - } + input MessageInput { + threadId: ID! + threadType: ThreadTypes! + messageType: MessageTypes! + content: MessageContentInput! + parentId: String + file: Upload + } - extend type Query { - message(id: ID!): Message - getMediaMessagesForThread(threadId: ID!): [Message] - } + extend type Query { + message(id: ID!): Message + getMediaMessagesForThread(threadId: ID!): [Message] + } + input EditMessageInput { + id: ID! + messageType: MessageTypes! + content: MessageContentInput + } - extend type Mutation { - addMessage(message: MessageInput!): Message - deleteMessage(id: ID!): Boolean - } + extend type Mutation { + addMessage(message: MessageInput!): Message + @rateLimit(max: 30, window: "1m") + deleteMessage(id: ID!): Boolean + editMessage(input: EditMessageInput!): Message + } - extend type Subscription { - messageAdded(thread: ID!): Message - } + extend type Subscription { + messageAdded(thread: ID!): Message + } `; module.exports = Message; diff --git a/api/types/Notification.js b/api/types/Notification.js index 20c0c07cb5..7dc81a9034 100644 --- a/api/types/Notification.js +++ b/api/types/Notification.js @@ -1,78 +1,85 @@ // @flow const Notification = /* GraphQL */ ` - enum NotificationEventType { - REACTION_CREATED - MESSAGE_CREATED - THREAD_CREATED - CHANNEL_CREATED - DIRECT_MESSAGE_THREAD_CREATED - USER_JOINED_COMMUNITY - USER_REQUESTED_TO_JOIN_PRIVATE_CHANNEL - USER_APPROVED_TO_JOIN_PRIVATE_CHANNEL - THREAD_LOCKED_BY_OWNER - THREAD_DELETED_BY_OWNER - COMMUNITY_INVITE - MENTION_THREAD - MENTION_MESSAGE - PRIVATE_CHANNEL_REQUEST_SENT - PRIVATE_CHANNEL_REQUEST_APPROVED - } + enum NotificationEventType { + REACTION_CREATED + THREAD_REACTION_CREATED + MESSAGE_CREATED + THREAD_CREATED + CHANNEL_CREATED + DIRECT_MESSAGE_THREAD_CREATED + USER_JOINED_COMMUNITY + USER_REQUESTED_TO_JOIN_PRIVATE_CHANNEL + USER_APPROVED_TO_JOIN_PRIVATE_CHANNEL + THREAD_LOCKED_BY_OWNER + THREAD_DELETED_BY_OWNER + COMMUNITY_INVITE + MENTION_THREAD + MENTION_MESSAGE + PRIVATE_CHANNEL_REQUEST_SENT + PRIVATE_CHANNEL_REQUEST_APPROVED + PRIVATE_COMMUNITY_REQUEST_SENT + PRIVATE_COMMUNITY_REQUEST_APPROVED + } - enum EntityType { - REACTION - MESSAGE - THREAD - CHANNEL - COMMUNITY - USER - DIRECT_MESSAGE_THREAD - } + enum EntityType { + REACTION + THREAD_REACTION + MESSAGE + THREAD + CHANNEL + COMMUNITY + USER + DIRECT_MESSAGE_THREAD + } - type NotificationEntityType { - id: ID! - payload: String! - type: EntityType - } + type NotificationEntityType { + id: ID! + payload: String! + type: EntityType + } - type NotificationsConnection { - pageInfo: PageInfo - edges: [NotificationEdge] - } + type NotificationsConnection { + pageInfo: PageInfo + edges: [NotificationEdge] + } - type NotificationEdge { - cursor: String - node: Notification - } + type NotificationEdge { + cursor: String + node: Notification + } - type Notification { - id: ID! - createdAt: Date! - modifiedAt: Date! - actors: [ NotificationEntityType ]! - context: NotificationEntityType! - entities: [ NotificationEntityType ]! - event: NotificationEventType! - isRead: Boolean! - isSeen: Boolean! - } + type Notification { + id: ID! + createdAt: Date! + modifiedAt: Date! + actors: [NotificationEntityType]! + context: NotificationEntityType! + entities: [NotificationEntityType]! + event: NotificationEventType! + isRead: Boolean! + isSeen: Boolean! + } - extend type Query { - notification(id: ID!): Notification - notifications(first: Int = 10, after: String): NotificationsConnection @cost(complexity: 1, multiplier: "first") - directMessageNotifications(first: Int = 10, after: String): NotificationsConnection @cost(complexity: 1, multiplier: "first") - } + extend type Query { + notification(id: ID!): Notification + notifications(first: Int = 10, after: String): NotificationsConnection + @cost(complexity: 1, multipliers: ["first"]) + directMessageNotifications( + first: Int = 10 + after: String + ): NotificationsConnection @cost(complexity: 1, multipliers: ["first"]) + } - extend type Mutation { - markAllNotificationsSeen: Boolean - markAllNotificationsRead: Boolean - markDirectMessageNotificationsSeen: Boolean - markSingleNotificationSeen(id: ID!): Boolean - } + extend type Mutation { + markAllNotificationsSeen: Boolean + markDirectMessageNotificationsSeen: Boolean + markSingleNotificationSeen(id: ID!): Boolean + } - extend type Subscription { - notificationAdded: Notification - dmNotificationAdded: Notification - } + extend type Subscription { + notificationAdded: Notification + dmNotificationAdded: Notification + } `; module.exports = Notification; diff --git a/api/types/Search.js b/api/types/Search.js index 3280ccc175..d6e9fb7289 100644 --- a/api/types/Search.js +++ b/api/types/Search.js @@ -30,24 +30,24 @@ const Search = /* GraphQL */ ` searchResultsConnection: SearchResultsConnection } - extend type Query { - search ( + extend type Query { + search( # Returns the first *n* results from the list - first: Int, + first: Int # Returns the elements in the list that come after the specified ID - after: String, + after: String # Returns the last *n* results from the list - last: Int, + last: Int # Returns the elements in the list that come before the specified ID - before: String, + before: String # The string typed by the user to search for - queryString: String!, + queryString: String! # The types of items that can be searched type: SearchType! # Optional ID to be used to filter search results by community, channel, user, etc. filter: SearchFilter - ): SearchResults @cost(complexity: 2, multiplier: "first") - } + ): SearchResults @cost(complexity: 2, multipliers: ["first"]) + } `; module.exports = Search; diff --git a/api/types/Thread.js b/api/types/Thread.js index 555b0830f1..620bd60922 100644 --- a/api/types/Thread.js +++ b/api/types/Thread.js @@ -1,113 +1,146 @@ // @flow const Thread = /* GraphQL */ ` - type ThreadMessagesConnection { - pageInfo: PageInfo! - edges: [ThreadMessageEdge!] - } - - type ThreadMessageEdge { - cursor: String! - node: Message! - } - - # The contents of a thread - type ThreadContent { - title: String - body: String - media: String - } - - type Edit { - timestamp: Date! - content: ThreadContent! - } - - enum ThreadType { - SLATE - DRAFTJS - } - - type Attachment { - attachmentType: String - data: String - } - - type Thread { - id: ID! - createdAt: Date! - modifiedAt: Date - channel: Channel! + enum ThreadReactionTypes { + like + } + + type ThreadReactions { + count: Int! + hasReacted: Boolean + } + + type ThreadMessagesConnection { + pageInfo: PageInfo! + edges: [ThreadMessageEdge!] + } + + type ThreadMessageEdge { + cursor: String! + node: Message! + } + + # The contents of a thread + type ThreadContent { + title: String + body: String + media: String + } + + type Edit { + timestamp: Date! + content: ThreadContent! + } + + enum ThreadType { + SLATE + DRAFTJS + TEXT + } + + type Attachment { + attachmentType: String + data: String + } + + type Thread { + id: ID! + createdAt: Date! + modifiedAt: Date + channel: Channel! community: Community! @cost(complexity: 1) - isPublished: Boolean! - content: ThreadContent! - isLocked: Boolean - isAuthor: Boolean + isPublished: Boolean! + content: ThreadContent! + isLocked: Boolean + isAuthor: Boolean receiveNotifications: Boolean @cost(complexity: 1) - lastActive: Date - type: ThreadType - edits: [Edit!] - participants: [User] @cost(complexity: 1) - messageConnection(first: Int, after: String, last: Int, before: String): ThreadMessagesConnection! @cost(complexity: 1, multiplier: "first") + lastActive: Date + type: ThreadType + edits: [Edit!] + messageConnection( + first: Int + after: String + last: Int + before: String + ): ThreadMessagesConnection! @cost(complexity: 1, multipliers: ["first"]) messageCount: Int @cost(complexity: 1) author: ThreadParticipant! @cost(complexity: 2) - attachments: [Attachment] - watercooler: Boolean + watercooler: Boolean currentUserLastSeen: Date @cost(complexity: 1) - - isCreator: Boolean @deprecated(reason: "Use Thread.isAuthor instead") - creator: User! @deprecated(reason:"Use Thread.author instead") - } - - input SearchThreadsFilter { - communityId: ID - creatorId: ID - channelId: ID - everythingFeed: Boolean - } - - extend type Query { - thread(id: ID!): Thread - searchThreads(queryString: String!, filter: SearchThreadsFilter): [Thread] @deprecated(reason:"Use the new Search query endpoint") - } - - input AttachmentInput { - attachmentType: String - data: String - } - - input ThreadContentInput { - title: String - body: String - } - - input EditThreadInput { - threadId: ID! - content: ThreadContentInput! - attachments: [AttachmentInput] - filesToUpload: [Upload] - } - - input ThreadInput { - channelId: ID! - communityId: ID! - type: ThreadType - content: ThreadContentInput! - attachments: [AttachmentInput] - filesToUpload: [Upload] - } - - extend type Mutation { - publishThread(thread: ThreadInput!): Thread - editThread(input: EditThreadInput!): Thread - setThreadLock(threadId: ID!, value: Boolean!): Thread - toggleThreadNotifications(threadId: ID!): Thread - deleteThread(threadId: ID!): Boolean - moveThread(threadId: ID!, channelId: ID!): Thread - } - - extend type Subscription { + reactions: ThreadReactions @cost(complexity: 1) + metaImage: String + + attachments: [Attachment] + @deprecated(reason: "Attachments no longer used for link previews") + isCreator: Boolean @deprecated(reason: "Use Thread.isAuthor instead") + creator: User! @deprecated(reason: "Use Thread.author instead") + participants: [User] + @cost(complexity: 1) + @deprecated(reason: "No longer used") + } + + input SearchThreadsFilter { + communityId: ID + creatorId: ID + channelId: ID + everythingFeed: Boolean + } + + extend type Query { + thread(id: ID!): Thread + searchThreads(queryString: String!, filter: SearchThreadsFilter): [Thread] + @deprecated(reason: "Use the new Search query endpoint") + } + + input AttachmentInput { + attachmentType: String + data: String + } + + input ThreadContentInput { + title: String + body: String + } + + input EditThreadInput { + threadId: ID! + content: ThreadContentInput! + attachments: [AttachmentInput] + filesToUpload: [Upload] + } + + input ThreadInput { + channelId: ID! + communityId: ID! + type: ThreadType + content: ThreadContentInput! + attachments: [AttachmentInput] + filesToUpload: [Upload] + } + + input AddThreadReactionInput { + threadId: ID! + type: ThreadReactionTypes + } + + input RemoveThreadReactionInput { + threadId: ID! + } + + extend type Mutation { + publishThread(thread: ThreadInput!): Thread + @rateLimit(max: 7, window: "10m") + editThread(input: EditThreadInput!): Thread + setThreadLock(threadId: ID!, value: Boolean!): Thread + toggleThreadNotifications(threadId: ID!): Thread + deleteThread(threadId: ID!): Boolean + moveThread(threadId: ID!, channelId: ID!): Thread + addThreadReaction(input: AddThreadReactionInput!): Thread + removeThreadReaction(input: RemoveThreadReactionInput!): Thread + } + + extend type Subscription { threadUpdated(channelIds: [String!]): Thread - } + } `; module.exports = Thread; diff --git a/api/types/User.js b/api/types/User.js index fe511029ef..6ac9c8b676 100644 --- a/api/types/User.js +++ b/api/types/User.js @@ -1,165 +1,192 @@ // @flow const User = /* GraphQL */ ` - type UserCommunitiesConnection { - pageInfo: PageInfo! - edges: [UserCommunityEdge!] - } - - type UserCommunityEdge { - node: Community! - } - - type UserChannelsConnection { - pageInfo: PageInfo! - edges: [UserChannelEdge!] - } - - type UserChannelEdge { - node: Channel! - } - - type UserDirectMessageThreadsConnection { - pageInfo: PageInfo! - edges: [DirectMessageThreadEdge] - } - - type DirectMessageThreadEdge { - cursor: String! - node: DirectMessageThread! - } - - type UserThreadsConnection { - pageInfo: PageInfo! - edges: [UserThreadEdge!] - } - - type UserThreadEdge { - cursor: String! - node: Thread! - } - - type EverythingThreadsConnection { - pageInfo: PageInfo! - edges: [EverythingThreadEdge!] - } - - type EverythingThreadEdge { - cursor: String! - node: Thread! - } - - type NotificationKindSettings { - email: Boolean - } - - type NotificationSettingsType { - newMessageInThreads: NotificationKindSettings - newDirectMessage: NotificationKindSettings - newThreadCreated: NotificationKindSettings - weeklyDigest: NotificationKindSettings - dailyDigest: NotificationKindSettings - newMention: NotificationKindSettings - } - - type UserNotificationsSettings { - types: NotificationSettingsType - } - - type UserSettings { - notifications: UserNotificationsSettings - } - - enum ThreadConnectionType { - participant - creator - } - - type GithubProfile { - id: Int - username: String - } - - type User { - id: ID! - name: String - firstName: String - description: String - website: String - username: String - profilePhoto: String - coverPhoto: String - email: String - providerId: String - createdAt: Date! - lastSeen: Date! - isOnline: Boolean - timezone: Int - totalReputation: Int - pendingEmail: String - - # non-schema fields + type UserCommunitiesConnection { + pageInfo: PageInfo! + edges: [UserCommunityEdge!] + } + + type UserCommunityEdge { + node: Community! + } + + type UserChannelsConnection { + pageInfo: PageInfo! + edges: [UserChannelEdge!] + } + + type UserChannelEdge { + node: Channel! + } + + type UserDirectMessageThreadsConnection { + pageInfo: PageInfo! + edges: [DirectMessageThreadEdge] + } + + type DirectMessageThreadEdge { + cursor: String! + node: DirectMessageThread! + } + + type UserThreadsConnection { + pageInfo: PageInfo! + edges: [UserThreadEdge!] + } + + type UserThreadEdge { + cursor: String! + node: Thread! + } + + type EverythingThreadsConnection { + pageInfo: PageInfo! + edges: [EverythingThreadEdge!] + } + + type EverythingThreadEdge { + cursor: String! + node: Thread! + } + + type NotificationKindSettings { + email: Boolean + } + + type NotificationSettingsType { + newMessageInThreads: NotificationKindSettings + newDirectMessage: NotificationKindSettings + newThreadCreated: NotificationKindSettings + weeklyDigest: NotificationKindSettings + dailyDigest: NotificationKindSettings + newMention: NotificationKindSettings + } + + type UserNotificationsSettings { + types: NotificationSettingsType + } + + type UserSettings { + notifications: UserNotificationsSettings + } + + enum ThreadConnectionType { + participant + creator + } + + type GithubProfile { + id: Int + username: String + } + + type User { + id: ID! + name: String + firstName: String + description: String + website: String + username: LowercaseString + profilePhoto: String + coverPhoto: String + email: LowercaseString + providerId: String + createdAt: Date! + lastSeen: Date! + isOnline: Boolean + timezone: Int + totalReputation: Int + pendingEmail: LowercaseString + betaSupporter: Boolean + + isPro: Boolean @deprecated(reason: "Use the betaSupporter field instead") + recurringPayments: [RecurringPayment] + @deprecated(reason: "Payments are no longer used") + invoices: [Invoice] @deprecated(reason: "Payments are no longer used") + + # non-schema fields threadCount: Int @cost(complexity: 1) - isAdmin: Boolean - isPro: Boolean! @cost(complexity: 1) - communityConnection: UserCommunitiesConnection! - channelConnection: UserChannelsConnection! - directMessageThreadsConnection(first: Int = 15, after: String): UserDirectMessageThreadsConnection! @cost(complexity: 1, multiplier: "first") - threadConnection(first: Int = 20, after: String, kind: ThreadConnectionType): UserThreadsConnection! @cost(complexity: 1, multiplier: "first") - everything(first: Int = 20, after: String): EverythingThreadsConnection! @cost(complexity: 1, multiplier: "first") - recurringPayments: [RecurringPayment] - invoices: [Invoice] + isAdmin: Boolean + communityConnection: UserCommunitiesConnection! + channelConnection: UserChannelsConnection! + directMessageThreadsConnection( + first: Int = 15 + after: String + ): UserDirectMessageThreadsConnection! + @cost(complexity: 1, multipliers: ["first"]) + threadConnection( + first: Int = 10 + after: String + kind: ThreadConnectionType + ): UserThreadsConnection! @cost(complexity: 1, multipliers: ["first"]) + everything(first: Int = 10, after: String): EverythingThreadsConnection! + @cost(complexity: 1, multipliers: ["first"]) settings: UserSettings @cost(complexity: 1) - githubProfile: GithubProfile - - contextPermissions: ContextPermissions @deprecated(reason:"Use the CommunityMember type to get permissions") - } - - extend type Query { - user(id: ID, username: String): User - currentUser: User - searchUsers(string: String): [User] @deprecated(reason:"Use the new Search query endpoint") - } - - input EditUserInput { - file: Upload - coverFile: Upload - name: String - description: String - website: String - username: String - timezone: Int - } - - input UpgradeToProInput { - plan: String! - token: String! - } - - input ToggleNotificationSettingsInput { - deliveryMethod: String! - notificationType: String! - } - - input WebPushSubscriptionKeys { - p256dh: String! - auth: String! - } - - input WebPushSubscription { - endpoint: String! - keys: WebPushSubscriptionKeys! - } - - extend type Mutation { - editUser(input: EditUserInput!): User - upgradeToPro(input: UpgradeToProInput!): User - downgradeFromPro: User - toggleNotificationSettings(input: ToggleNotificationSettingsInput): User - subscribeWebPush(subscription: WebPushSubscription!): Boolean - unsubscribeWebPush(endpoint: String!): Boolean - subscribeExpoPush(token: String!): Boolean - updateUserEmail(email: String!): User - } + githubProfile: GithubProfile + + contextPermissions: ContextPermissions + @deprecated(reason: "Use the CommunityMember type to get permissions") + } + + extend type Query { + user(id: ID, username: LowercaseString): User + currentUser: User + searchUsers(string: String): [User] + @deprecated(reason: "Use the new Search query endpoint") + } + + input EditUserInput { + file: Upload + coverFile: Upload + name: String + description: String + website: String + username: LowercaseString + timezone: Int + email: String + } + + input UpgradeToProInput { + plan: String! + token: String! + } + + input ToggleNotificationSettingsInput { + deliveryMethod: String! + notificationType: String! + } + + input WebPushSubscriptionKeys { + p256dh: String! + auth: String! + } + + input WebPushSubscription { + endpoint: String! + keys: WebPushSubscriptionKeys! + } + + input ReportUserInput { + userId: String! + reason: String! + } + + input BanUserInput { + userId: String! + reason: String! + } + + extend type Mutation { + editUser(input: EditUserInput!): User + upgradeToPro(input: UpgradeToProInput!): User + downgradeFromPro: User + toggleNotificationSettings(input: ToggleNotificationSettingsInput): User + subscribeWebPush(subscription: WebPushSubscription!): Boolean + unsubscribeWebPush(endpoint: String!): Boolean + deleteCurrentUser: Boolean + updateUserEmail(email: LowercaseString!): User + reportUser(input: ReportUserInput!): Boolean + banUser(input: BanUserInput!): Boolean + } `; module.exports = User; diff --git a/api/types/custom-scalars/LowercaseString.js b/api/types/custom-scalars/LowercaseString.js new file mode 100644 index 0000000000..33ba2a28b1 --- /dev/null +++ b/api/types/custom-scalars/LowercaseString.js @@ -0,0 +1,23 @@ +// @flow +import { GraphQLScalarType } from 'graphql'; +// $FlowIssue +import { Kind } from 'graphql/language'; + +const LowercaseString = new GraphQLScalarType({ + name: 'LowercaseString', + description: 'Returns all strings in lower case', + parseValue(value) { + return value.toLowerCase(); + }, + serialize(value) { + return value.toLowerCase(); + }, + parseLiteral(ast) { + if (ast.kind === Kind.STRING) { + return ast.value.toLowerCase(); + } + return null; + }, +}); + +export default LowercaseString; diff --git a/api/types/general.js b/api/types/general.js index d7d6130f75..24a37ee63c 100644 --- a/api/types/general.js +++ b/api/types/general.js @@ -4,53 +4,43 @@ */ const general = /* GraphQL */ ` - type PageInfo { - hasNextPage: Boolean - hasPreviousPage: Boolean - } - - type ChannelPermissions { - isMember: Boolean - isBlocked: Boolean - isPending: Boolean - isOwner: Boolean - isModerator: Boolean - receiveNotifications: Boolean - } - - type CommunityPermissions { - isMember: Boolean - isBlocked: Boolean - isOwner: Boolean - isModerator: Boolean - receiveNotifications: Boolean - reputation: Int - } + type PageInfo { + hasNextPage: Boolean + hasPreviousPage: Boolean + } - input File { - name: String! - type: String! - size: Int! - path: String! + type ChannelPermissions { + isMember: Boolean + isBlocked: Boolean + isPending: Boolean + isOwner: Boolean + isModerator: Boolean + receiveNotifications: Boolean } - type RecurringPayment { - plan: String - amount: String - createdAt: String - status: String - } + type CommunityPermissions { + isMember: Boolean + isBlocked: Boolean + isOwner: Boolean + isModerator: Boolean + isPending: Boolean + receiveNotifications: Boolean + reputation: Int + } - type ContextPermissions @deprecated(reason:"Use the CommunityMember or ThreadParticipant type to get permissions") { - communityId: String - reputation: Int - isModerator: Boolean - isOwner: Boolean - isMember: Boolean - isBlocked: Boolean - } + type ContextPermissions + @deprecated( + reason: "Use the CommunityMember or ThreadParticipant type to get permissions" + ) { + communityId: String + reputation: Int + isModerator: Boolean + isOwner: Boolean + isMember: Boolean + isBlocked: Boolean + } - type GrowthDataCounts { + type GrowthDataCounts { growth: Float currentPeriodCount: Int prevPeriodCount: Int @@ -61,19 +51,31 @@ const general = /* GraphQL */ ` weeklyGrowth: GrowthDataCounts monthlyGrowth: GrowthDataCounts quarterlyGrowth: GrowthDataCounts - } - - input EmailInviteContactInput { - email: String! - firstName: String - lastName: String - } + } - input EmailInvitesInput { - id: ID! - contacts: [ EmailInviteContactInput ] - customMessage: String - } + input EmailInviteContactInput { + email: LowercaseString! + firstName: String + lastName: String + } + + input EmailInvitesInput { + id: ID! + contacts: [EmailInviteContactInput] + customMessage: String + } + + type JoinSettings { + tokenJoinEnabled: Boolean + token: String + } + + type RecurringPayment { + plan: String + amount: String + createdAt: String + status: String + } `; module.exports = general; diff --git a/api/types/scalars.js b/api/types/scalars.js index 2781ade2a3..a54bee8a12 100644 --- a/api/types/scalars.js +++ b/api/types/scalars.js @@ -4,16 +4,20 @@ * both their type definitions and their resolvers */ const GraphQLDate = require('graphql-date'); -import { GraphQLUpload } from 'apollo-upload-server'; +// NOTE(@mxstbr): We can remove this once we stop using makeExecutableSchema +import { GraphQLUpload } from 'apollo-server-express'; +import LowercaseString from './custom-scalars/LowercaseString'; const typeDefs = /* GraphQL */ ` - scalar Date + scalar Date scalar Upload + scalar LowercaseString `; const resolvers = { Date: GraphQLDate, Upload: GraphQLUpload, + LowercaseString: LowercaseString, }; module.exports = { diff --git a/api/utils/create-graphql-error-formatter.js b/api/utils/create-graphql-error-formatter.js index 03c5c8018c..fd44d47315 100644 --- a/api/utils/create-graphql-error-formatter.js +++ b/api/utils/create-graphql-error-formatter.js @@ -1,20 +1,56 @@ // @flow const debug = require('debug')('api:utils:error-formatter'); +import { RateLimitError } from 'graphql-rate-limit'; import Raven from 'shared/raven'; import { IsUserError } from './UserError'; import type { GraphQLError } from 'graphql'; -const createGraphQLErrorFormatter = (req?: express$Request) => ( - error: GraphQLError -) => { +const queryRe = /\s*(query|mutation)[^{]*/; + +const collectQueries = query => { + if (!query) return 'No query'; + return query + .split('\n') + .map(line => { + const m = line.match(queryRe); + return m ? m[0].trim() : ''; + }) + .filter(line => !!line) + .join('\n'); +}; + +const errorPath = error => { + if (!error.path) return ''; + return error.path + .map((value, index) => { + if (!index) return value; + return typeof value === 'number' ? `[${value}]` : `.${value}`; + }) + .join(''); +}; + +const logGraphQLError = (req, error) => { debug('---GraphQL Error---'); debug(error); + if (req) { + debug(collectQueries(req.body.query)); + debug('variables', JSON.stringify(req.body.variables || {})); + } + const path = errorPath(error); + path && debug('path', path); debug('-------------------\n'); - const isUserError = error.originalError - ? error.originalError[IsUserError] - : error[IsUserError]; +}; + +const createGraphQLErrorFormatter = (req?: express$Request) => ( + error: GraphQLError +) => { + logGraphQLError(req, error); + + const err = error.originalError || error; + const isUserError = err[IsUserError] || err instanceof RateLimitError; + let sentryId = 'ID only generated in production'; - if (!isUserError) { + if (!isUserError || err instanceof RateLimitError) { if (process.env.NODE_ENV === 'production') { sentryId = Raven.captureException( error, @@ -22,6 +58,7 @@ const createGraphQLErrorFormatter = (req?: express$Request) => ( ); } } + return { message: isUserError ? error.message : `Internal server error: ${sentryId}`, // Hide the stack trace in production mode diff --git a/api/utils/file-storage.js b/api/utils/file-storage.js new file mode 100644 index 0000000000..5c27f0f6db --- /dev/null +++ b/api/utils/file-storage.js @@ -0,0 +1,27 @@ +// @flow +require('now-env'); + +import type { FileUpload, EntityTypes } from 'shared/types'; + +const { FILE_STORAGE } = process.env; + +const getUploadImageFn = () => { + switch (FILE_STORAGE) { + case 'local': + return require('./file-system').uploadImage; + case 's3': + default: + return require('./s3').uploadImage; + } +}; + +const uploadImageFn = getUploadImageFn(); + +export const uploadImage = ( + file: FileUpload, + entity: EntityTypes, + id: string +): Promise => + uploadImageFn(file, entity, id).catch(err => { + throw new Error(err); + }); diff --git a/api/utils/file-system.js b/api/utils/file-system.js new file mode 100644 index 0000000000..b5f1bf359a --- /dev/null +++ b/api/utils/file-system.js @@ -0,0 +1,40 @@ +// @flow +import uuidv4 from 'uuid/v4'; +import fs from 'fs'; + +import type { FileUpload, EntityTypes } from 'shared/types'; + +const STORAGE_DIR = 'public/uploads'; +const READ_WRITE_MODE = 0o777; + +const dirExists = (path: string): Promise => + new Promise(res => fs.access(path, fs.constants.F_OK, err => res(!!!err))); + +const createUploadsDir = (path: string): Promise => + new Promise(res => + fs.mkdir(path, READ_WRITE_MODE, err => { + if (err) throw new Error(err); + res(); + }) + ); + +export const uploadImage = async ( + file: FileUpload, + entity: EntityTypes, + id: string +): Promise => { + const result = await file; + + if (!(await dirExists(STORAGE_DIR))) { + await createUploadsDir(STORAGE_DIR); + } + + return new Promise(res => { + const filePath = `${uuidv4()}-${entity}-${id}`; + const { stream } = result; + stream.pipe(fs.createWriteStream(`${STORAGE_DIR}/${filePath}`)); + stream.on('end', () => { + res(encodeURI(`/uploads/${filePath}`)); + }); + }); +}; diff --git a/api/utils/generate-thread-meta-image-from-text.js b/api/utils/generate-thread-meta-image-from-text.js new file mode 100644 index 0000000000..9f89f93bf1 --- /dev/null +++ b/api/utils/generate-thread-meta-image-from-text.js @@ -0,0 +1,78 @@ +// @flow +// Generates a meta image which shows a title and a footer text on a nice Spectrum background. +import theme from 'shared/theme'; +import { btoa } from 'b2a'; +import { stringify } from 'query-string'; + +// NOTES(@mxstbr): +// Imgix has a useful ~text endpoint that allows us to add text to any image, but unfortunately that endpoint only allows us to add one piece of text to an image—but we need two! +// We work around this by generating two images that only contain the text of the title and the footer respectively, nothing else, and then using the blend option to blend the background image with the title image (which only has the title text) and used the mark option to add our footer text as a "watermark" to the footer. + +const WIDTH = 1500; +const IMGIX_TEXT_ENDPOINT = 'https://assets.imgix.net/~text'; + +const TITLE_PARAMS = { + w: WIDTH * 0.8, // 10% padding on each side + h: 270, // Magic number, clips text after three lines + txtsize: 56, + txtlead: 16, + txtfont: 'Helvetica,Bold', + txtalign: 'left,middle', + txtcolor: 'ffffff', + // NOTE(@mxstbr): txtclip (i.e. ellipsis on overflowing text) only works with single-line text, which titles aren't, so this doesn't do anything rn + txtclip: 'end,ellipsis', +}; + +const FOOTER_PARAMS = { + h: 64, + txtsize: 36, + txtcolor: theme.brand.default.replace('#', ''), + txtalign: 'right,middle', + txtfont: 'Helvetica,Bold', +}; + +const BACKGROUND_URL = `https://spectrum.imgix.net/default_images/twitter-share-card.png`; + +type GetMetaImageInput = { + title: string, + footer: string, +}; + +const generateImageFromText = ({ + title, + footer, +}: GetMetaImageInput): ?string => { + const base64title = btoa(title); + const base64footer = btoa(footer); + if (!base64title || !base64footer) return null; + + const titleUrl = `${IMGIX_TEXT_ENDPOINT}?${stringify( + { ...TITLE_PARAMS, txt64: base64title.replace(/=/g, '') }, + { encode: false } + )}`; + const footerUrl = `${IMGIX_TEXT_ENDPOINT}?${stringify( + { ...FOOTER_PARAMS, txt64: base64footer.replace(/=/g, '') }, + { encode: false } + )}`; + + const base64titleurl = btoa(titleUrl); + const base64footerurl = btoa(footerUrl); + + if (!base64titleurl || !base64footerurl) return null; + + const BACKGROUND_PARAMS = { + w: WIDTH, + bm: 'normal', // Blend the title normally, don't change opacity or color or anything, just overlay it + by: 170, // Magic numbers that get the position right + bx: 170, + markalign: 'left,bottom', // Show the footer on the left side + markpad: 24, // We overwrite the X pos, so the padding only applies on the y-axis + markx: 140, + blend64: btoa(titleUrl).replace(/=/g, ''), + mark64: btoa(footerUrl).replace(/=/g, ''), + }; + + return `${BACKGROUND_URL}?${stringify(BACKGROUND_PARAMS, { encode: false })}`; +}; + +export default generateImageFromText; diff --git a/api/utils/get-page-meta.js b/api/utils/get-page-meta.js index 83cd918ee6..e6b6289495 100644 --- a/api/utils/get-page-meta.js +++ b/api/utils/get-page-meta.js @@ -152,8 +152,8 @@ export default ( if (!promise) return Promise.resolve(generateMetaInfo()); return promise.catch(err => { - console.log(`⚠️ Failed to load metadata for ${url}! ⚠️`); - console.log(err); + console.error(`⚠️ Failed to load metadata for ${url}! ⚠️`); + console.error(err); return generateMetaInfo(); }); }; diff --git a/api/utils/get-random-default-photo.js b/api/utils/get-random-default-photo.js index e4c9869a90..e508240505 100644 --- a/api/utils/get-random-default-photo.js +++ b/api/utils/get-random-default-photo.js @@ -17,7 +17,7 @@ const PALETTE = [ export default () => { const color = faker.random.arrayElement(PALETTE); return { - profilePhoto: `https://s3.amazonaws.com/spectrum-chat/default_images/profile-${color}.png`, - coverPhoto: `https://s3.amazonaws.com/spectrum-chat/default_images/cover-${color}.svg`, + profilePhoto: `/default_images/profile-${color}.png`, + coverPhoto: `/default_images/cover-${color}.svg`, }; }; diff --git a/api/utils/is-spectrum-url.js b/api/utils/is-spectrum-url.js index e674f29cec..b1c2dcb194 100644 --- a/api/utils/is-spectrum-url.js +++ b/api/utils/is-spectrum-url.js @@ -3,7 +3,7 @@ import { URL } from 'url'; import { RELATIVE_URL } from 'shared/regexps'; const IS_PROD = process.env.NODE_ENV === 'production'; -const EXPO_URL = /^https:\/\/auth\.expo\.io\/@(mxstbr|uberbryn|brianlovin)\//; +const EXPO_URL = /^https:\/\/auth\.expo\.io\//; /** * Make a URL string is a spectrum.chat URL @@ -16,7 +16,8 @@ export default (url: string): boolean => { const { hostname, protocol } = new URL(url); // hostname might be spectrum.chat or subdomain.spectrum.chat, so we use .endsWith // We don't just check .contains because otherwise folks could make spectrum.chat.mydomain.com - const IS_SPECTRUM_URL = hostname.endsWith('spectrum.chat'); + const IS_SPECTRUM_URL = + hostname === 'spectrum.chat' || hostname === 'alpha.spectrum.chat'; const IS_LOCALHOST = hostname === 'localhost'; const IS_HTTP = protocol === 'https:' || protocol === 'http:'; // Make sure the passed redirect URL is a spectrum.chat one or (in development) localhost @@ -25,8 +26,8 @@ export default (url: string): boolean => { } } catch (err) { // Swallow URL parsing errors (when an invalid URL is passed) and redirect to the standard one - console.log(`Invalid URL ("${url}") passed. Full error:`); - console.log(err); + console.error(`Invalid URL ("${url}") passed. Full error:`); + console.error(err); } return false; }; diff --git a/api/utils/permissions.js b/api/utils/permissions.js index 9b3442062c..de0a306977 100644 --- a/api/utils/permissions.js +++ b/api/utils/permissions.js @@ -1,8 +1,12 @@ // @flow +import UserError from './UserError'; +import type { GraphQLContext } from '../'; +import type { DBChannel, DBCommunity, DBUser } from 'shared/types'; import { COMMUNITY_SLUG_BLACKLIST, CHANNEL_SLUG_BLACKLIST, } from 'shared/slug-blacklists'; +import { getThreadById } from '../models/thread'; export const isAdmin = (id: string): boolean => { const admins = [ @@ -20,3 +24,236 @@ export const communitySlugIsBlacklisted = (slug: string): boolean => { export const channelSlugIsBlacklisted = (slug: string): boolean => { return CHANNEL_SLUG_BLACKLIST.indexOf(slug) > -1; }; + +// prettier-ignore +export const isAuthedResolver = (resolver: Function) => async (obj: any, args: any, context: GraphQLContext, info: any) => { + if (!context.user || !context.user.id) { + return new UserError('You must be signed in to do this') + } + + const user = await context.loaders.user.load(context.user.id) + + if (!user || user.bannedAt || user.deletedAt) { + return new UserError('You must be signed in to do this') + } + + return resolver(obj, args, context, info) +} + +// prettier-ignore +const channelExists = async (channelId: string, loaders: any): Promise => { + const channel = await loaders.channel.load(channelId); + if (!channel || channel.deletedAt) return null; + return channel; +}; + +// prettier-ignore +const communityExists = async (communityId: string, loaders: any): Promise => { + const community = await loaders.community.load(communityId); + if (!community || community.deletedAt) return null; + return community; +}; + +// prettier-ignore +export const canAdministerChannel = async (userId: string, channelId: string, loaders: any) => { + if (!userId || !channelId) return false; + + const channel = await channelExists(channelId, loaders); + if (!channel) return false; + + const [communityPermissions, channelPermissions] = await Promise.all([ + loaders.userPermissionsInCommunity.load([userId, channel.communityId]), + loaders.userPermissionsInChannel.load([userId, channelId]), + ]); + + if (!communityPermissions) return false; + if (communityPermissions.isOwner || communityPermissions.isModerator) + return true; + if (!channelPermissions) return false; + if (channelPermissions.isOwner) return true; + + return false; +}; + +// prettier-ignore +export const canModerateChannel = async (userId: string, channelId: string, loaders: any) => { + if (!userId || !channelId) return false; + + const channel = await channelExists(channelId, loaders); + if (!channel) return false; + + const [communityPermissions, channelPermissions] = await Promise.all([ + loaders.userPermissionsInCommunity.load([userId, channel.communityId]), + loaders.userPermissionsInChannel.load([userId, channelId]), + ]); + + if (!communityPermissions) return false; + if (communityPermissions.isBlocked) return false + if (communityPermissions.isOwner || communityPermissions.isModerator) return true + + if (!channelPermissions) return false; + if (channelPermissions.isBlocked) return false + return channelPermissions.isOwner || channelPermissions.isModerator +}; + +// prettier-ignore +export const canAdministerCommunity = async (userId: string, communityId: string, loaders: any) => { + if (!userId || !communityId) return false; + + const community = await communityExists(communityId, loaders); + if (!community) return false; + + const communityPermissions = await loaders.userPermissionsInCommunity.load([ + userId, + communityId, + ]); + + if (!communityPermissions) return false + if (communityPermissions.isBlocked) return false + return communityPermissions.isOwner +}; + +// prettier-igore +export const canModerateCommunity = async ( + userId: string, + communityId: string, + loaders: any +) => { + if (!userId || !communityId) return false; + + const community = await communityExists(communityId, loaders); + if (!community) return false; + + const communityPermissions = await loaders.userPermissionsInCommunity.load([ + userId, + communityId, + ]); + + if (!communityPermissions) return false; + if (communityPermissions.isBlocked) return false; + return communityPermissions.isOwner || communityPermissions.isModerator; +}; + +// prettier-ignore +export const canViewCommunity = async (user: DBUser, communityId: string, loaders: any) => { + if (!communityId) return false; + + const community = await communityExists(communityId, loaders); + if (!community) return false; + + if (!community.isPrivate) return true + + if (!user) return false + + const communityPermissions = await loaders.userPermissionsInCommunity.load([ + user.id, + communityId, + ]); + + if (!communityPermissions) return false; + if (communityPermissions.isBlocked) return false + if (!communityPermissions.isMember) return false + + return true; +} + +export const canViewThread = async ( + userId: string, + threadId: string, + loaders: any +) => { + const thread = await getThreadById(threadId); + + if (!thread || thread.deletedAt) return false; + + const [ + channel, + community, + channelPermissions, + communityPermissions, + ] = await Promise.all([ + loaders.channel.load(thread.channelId), + loaders.community.load(thread.communityId), + loaders.userPermissionsInChannel.load([userId, thread.channelId]), + loaders.userPermissionsInCommunity.load([userId, thread.communityId]), + ]); + + if (!channel || !community) return false; + if (channel.deletedAt || community.deletedAt) return false; + + if (!channel.isPrivate && !community.isPrivate) return true; + + if (channel.isPrivate) + return ( + channelPermissions && + channelPermissions.isMember && + !channelPermissions.isBlocked + ); + if (community.isPrivate) + return ( + communityPermissions && + communityPermissions.isMember && + !communityPermissions.isBlocked + ); + + return false; +}; + +export const canViewDMThread = async ( + userId: string, + threadId: string, + loaders: any +) => { + if (!userId) return false; + + const thread = await loaders.directMessageParticipants.load(threadId); + + if (!thread || !thread.reduction || thread.reduction.length === 0) + return false; + + const participants = thread.reduction; + + const ids = participants.map(({ userId }) => userId); + + if (ids.indexOf(userId) === -1) return false; + + return true; +}; + +// prettier-ignore +export const canViewChannel = async (user: DBUser, channelId: string, loaders: any) => { + if (!channelId) return false; + + const channel = await channelExists(channelId, loaders); + if (!channel) return false; + + const community = await communityExists(channel.communityId, loaders); + if (!community) return false + + if (!channel.isPrivate && !community.isPrivate) return true + + if (!user) return false + + const [ + communityPermissions, + channelPermissions + ] = await Promise.all([ + loaders.userPermissionsInCommunity.load([ + user.id, + community.id, + ]), + loaders.userPermissionsInChannel.load([ + user.id, + channel.id, + ]) + ]) + + if (channel.isPrivate && !channelPermissions) return false + if (community.isPrivate && !communityPermissions) return false + if (channel.isPrivate && !channelPermissions.isMember) return false + if (community.isPrivate && !communityPermissions.isMember) return false + if (channelPermissions && channelPermissions.isBlocked) return false + if (communityPermissions && communityPermissions.isBlocked) return false + + return true +} diff --git a/api/utils/rate-limit-directive.js b/api/utils/rate-limit-directive.js new file mode 100644 index 0000000000..18bacae08c --- /dev/null +++ b/api/utils/rate-limit-directive.js @@ -0,0 +1,15 @@ +// @flow +import { createRateLimitDirective, RedisStore } from 'graphql-rate-limit'; +import { getClientIp } from 'request-ip'; +import createRedis from 'shared/bull/create-redis'; +import ms from 'ms'; + +export default createRateLimitDirective({ + identifyContext: ctx => (ctx.user && ctx.user.id) || getClientIp(ctx.request), + store: new RedisStore(createRedis()), + formatError: ({ fieldName, fieldIdentity, max, window }) => + `Slow down there partner! You've called '${fieldName}' ${max} times in the past ${ms( + window, + { long: true } + )}. Relax for a bit and try again later.`, +}); diff --git a/api/utils/s3.js b/api/utils/s3.js index 64cd13932f..c40fa0aaf3 100644 --- a/api/utils/s3.js +++ b/api/utils/s3.js @@ -1,11 +1,15 @@ // @flow require('now-env'); import AWS from 'aws-sdk'; -import shortid from 'shortid'; +import uuidv4 from 'uuid/v4'; +import _ from 'lodash'; +import Raven from 'shared/raven'; +import sanitize from 'sanitize-filename'; +import UserError from './UserError'; + const IS_PROD = process.env.NODE_ENV === 'production'; -import type { FileUpload } from 'shared/types'; -type EntityTypes = 'communities' | 'channels' | 'users' | 'threads'; +import type { FileUpload, EntityTypes } from 'shared/types'; let S3_TOKEN = process.env.S3_TOKEN; let S3_SECRET = process.env.S3_SECRET; @@ -24,51 +28,46 @@ AWS.config.update({ }); const s3 = new AWS.S3(); -const generateImageUrl = path => { - // remove the bucket name from the path - const newPath = path.replace('spectrum-chat/', ''); - - // this is the default source for our imgix account, which starts - // at the bucket root, thus we remove the bucket from the path - const imgixBase = 'https://spectrum.imgix.net'; - - // return a new url to update the user object - return imgixBase + '/' + newPath; -}; +// remove the bucket name from the url +// the bucket name is not required since it is automatically bound +// to our imgix source +const generateImageUrl = path => path.replace('spectrum-chat/', ''); -const upload = async ( +export const uploadImage = async ( file: FileUpload, entity: EntityTypes, id: string ): Promise => { const result = await file; - const { filename, stream } = result; + const { filename, stream, mimetype } = result; + const sanitized = sanitize(filename); + const encoded = encodeURIComponent(sanitized); + const validMediaTypes = ['image/gif', 'image/jpeg', 'image/png', 'video/mp4']; return new Promise(res => { + // mimetype not in the validMediaType collection + if (_.indexOf(validMediaTypes, _.toLower(mimetype)) < 0) { + const unsupportedMediaTypeError = new UserError( + `We aren’t able to support uploads with the type ${mimetype}. Try uploading another image.` + ); + Raven.captureException(unsupportedMediaTypeError); + throw unsupportedMediaTypeError; + } + const path = `spectrum-chat/${entity}/${id}`; - const fileKey = `${shortid.generate()}-${filename}`; + const fileKey = `${uuidv4()}-${encoded}`; return s3.upload( { Bucket: path, Key: fileKey, Body: stream, - ACL: 'public-read', }, (err, data) => { if (err) throw new Error(err); - if (!data || !data.Key) throw new Error('Image upload failed.'); + if (!data || !data.Key) + throw new UserError('Image upload failed. Please try again.'); const url = generateImageUrl(data.Key); - res(encodeURI(url)); + res(url); } ); }); }; - -export const uploadImage = async ( - file: FileUpload, - entity: EntityTypes, - id: string -): Promise => { - return await upload(file, entity, id).catch(err => { - throw new Error(err); - }); -}; diff --git a/api/utils/session-store.js b/api/utils/session-store.js index bfde7ad334..df27f0727a 100644 --- a/api/utils/session-store.js +++ b/api/utils/session-store.js @@ -4,16 +4,32 @@ import session from 'shared/middlewares/session'; const ONE_YEAR = 31556952000; const ONE_DAY = 86400000; +const isSerializedJSON = (str: string) => + str[0] === '{' && str[str.length - 1] === '}'; + /** * Get the sessions' users' ID of a req manually, needed for websocket authentication */ -export const getUserIdFromReq = (req: any): Promise => +export const getUserIdFromReq = (req: any): Promise => new Promise((res, rej) => { session(req, {}, err => { - if (err) return rej(err); - if (!req.session || !req.session.passport || !req.session.passport.user) - return rej(); + if (err) { + return rej(err); + } + if (!req.session || !req.session.passport || !req.session.passport.user) { + return res(null); + } + + // NOTE(@mxstbr): `req.session.passport.user` used to be just the userID, but is now the full user data + // JSON.stringified to avoid having to go to the db on every single request. We have to handle both + // cases here to get the ID. + if (!isSerializedJSON(req.session.passport.user)) + return res(req.session.passport.user); - return res(req.session.passport.user); + try { + return res(JSON.parse(req.session.passport.user).id); + } catch (err) { + return res(null); + } }); }); diff --git a/api/utils/validate-draft-js-input.js b/api/utils/validate-draft-js-input.js new file mode 100644 index 0000000000..9b1961b171 --- /dev/null +++ b/api/utils/validate-draft-js-input.js @@ -0,0 +1,13 @@ +// @flow +export const validateRawContentState = (input: any) => { + if ( + !input || + !input.blocks || + !Array.isArray(input.blocks) || + !input.entityMap + ) { + return false; + } + + return true; +}; diff --git a/api/yarn.lock b/api/yarn.lock index 7a14cd526b..ba500b9799 100644 --- a/api/yarn.lock +++ b/api/yarn.lock @@ -2,87 +2,1036 @@ # yarn lockfile v1 -"@babel/runtime@^7.0.0-beta.38", "@babel/runtime@^7.0.0-beta.40": - version "7.0.0-beta.41" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0-beta.41.tgz#776ce13391b8154ccfdea71018a47b63e4d97e74" +"@apollographql/apollo-tools@^0.2.6": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@apollographql/apollo-tools/-/apollo-tools-0.2.9.tgz#1e20999d11728ef47f8f812f2be0426b5dde1a51" + integrity sha512-AEIQwPkS0QLbkpb6WyRhV4aOMxuErasp47ABv5niDKOasQH8mrD8JSGKJAHuQxVe4kB8DE9sLRoc5qeQ0KFCHA== dependencies: - core-js "^2.5.3" - regenerator-runtime "^0.11.1" + apollo-env "0.2.5" -"@types/graphql@^0.9.0", "@types/graphql@^0.9.1": - version "0.9.4" - resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.4.tgz#cdeb6bcbef9b6c584374b81aa7f48ecf3da404fa" +"@apollographql/graphql-playground-html@^1.6.6": + version "1.6.6" + resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.6.tgz#022209e28a2b547dcde15b219f0c50f47aa5beb3" + integrity sha512-lqK94b+caNtmKFs5oUVXlSpN3sm5IXZ+KfhMxOtr0LR2SqErzkoJilitjDvJ1WbjHlxLI7WtCjRmOLdOGJqtMQ== -"@types/node@^9.4.6": - version "9.4.7" - resolved "http://registry.npmjs.org/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275" +"@babel/code-frame@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" + integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/core@^7.0.0", "@babel/core@^7.1.2": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.2.0.tgz#a4dd3814901998e93340f0086e9867fefa163ada" + integrity sha512-7pvAdC4B+iKjFFp9Ztj0QgBndJ++qaMeonT185wAqUnhipw8idm9Rv1UMyBuKtYjfl6ORNkgEgcsYLfHX/GpLw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.2.0" + "@babel/helpers" "^7.2.0" + "@babel/parser" "^7.2.0" + "@babel/template" "^7.1.2" + "@babel/traverse" "^7.1.6" + "@babel/types" "^7.2.0" + convert-source-map "^1.1.0" + debug "^4.1.0" + json5 "^2.1.0" + lodash "^4.17.10" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.1.6", "@babel/generator@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.2.0.tgz#eaf3821fa0301d9d4aef88e63d4bcc19b73ba16c" + integrity sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg== + dependencies: + "@babel/types" "^7.2.0" + jsesc "^2.5.1" + lodash "^4.17.10" + source-map "^0.5.0" + trim-right "^1.0.1" + +"@babel/helper-annotate-as-pure@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" + integrity sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz#6b69628dfe4087798e0c4ed98e3d4a6b2fbd2f5f" + integrity sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-call-delegate@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz#6a957f105f37755e8645343d3038a22e1449cc4a" + integrity sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ== + dependencies: + "@babel/helper-hoist-variables" "^7.0.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-create-class-features-plugin@^7.2.1": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.2.1.tgz#f6e8027291669ef64433220dc8327531233f1161" + integrity sha512-EsEP7XLFmcJHjcuFYBxYD1FkP0irC8C9fsrt2tX/jrAi/eTnFI6DOPgVFb+WREeg1GboF+Ib+nCHbGBodyAXSg== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-member-expression-to-functions" "^7.0.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + +"@babel/helper-define-map@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c" + integrity sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/types" "^7.0.0" + lodash "^4.17.10" + +"@babel/helper-explode-assignable-expression@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz#537fa13f6f1674df745b0c00ec8fe4e99681c8f6" + integrity sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA== + dependencies: + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-function-name@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" + integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== + dependencies: + "@babel/helper-get-function-arity" "^7.0.0" + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-get-function-arity@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" + integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-hoist-variables@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz#46adc4c5e758645ae7a45deb92bab0918c23bb88" + integrity sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-member-expression-to-functions@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz#8cd14b0a0df7ff00f009e7d7a436945f47c7a16f" + integrity sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-module-imports@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d" + integrity sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-module-transforms@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.1.0.tgz#470d4f9676d9fad50b324cdcce5fbabbc3da5787" + integrity sha512-0JZRd2yhawo79Rcm4w0LwSMILFmFXjugG3yqf+P/UsKsRS1mJCmMwwlHDlMg7Avr9LrvSpp4ZSULO9r8jpCzcw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-simple-access" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + lodash "^4.17.10" + +"@babel/helper-optimise-call-expression@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz#a2920c5702b073c15de51106200aa8cad20497d5" + integrity sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-plugin-utils@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" + integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== + +"@babel/helper-regex@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.0.0.tgz#2c1718923b57f9bbe64705ffe5640ac64d9bdb27" + integrity sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg== + dependencies: + lodash "^4.17.10" + +"@babel/helper-remap-async-to-generator@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz#361d80821b6f38da75bd3f0785ece20a88c5fe7f" + integrity sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-wrap-function" "^7.1.0" + "@babel/template" "^7.1.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-replace-supers@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.1.0.tgz#5fc31de522ec0ef0899dc9b3e7cf6a5dd655f362" + integrity sha512-BvcDWYZRWVuDeXTYZWxekQNO5D4kO55aArwZOTFXw6rlLQA8ZaDicJR1sO47h+HrnCiDFiww0fSPV0d713KBGQ== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.0.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-simple-access@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c" + integrity sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w== + dependencies: + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-split-export-declaration@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813" + integrity sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-wrap-function@^7.1.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz#c4e0012445769e2815b55296ead43a958549f6fa" + integrity sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/template" "^7.1.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.2.0" + +"@babel/helpers@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.2.0.tgz#8335f3140f3144270dc63c4732a4f8b0a50b7a21" + integrity sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A== + dependencies: + "@babel/template" "^7.1.2" + "@babel/traverse" "^7.1.5" + "@babel/types" "^7.2.0" + +"@babel/highlight@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" + integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.2", "@babel/parser@^7.1.6", "@babel/parser@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.2.0.tgz#02d01dbc330b6cbf36b76ac93c50752c69027065" + integrity sha512-M74+GvK4hn1eejD9lZ7967qAwvqTZayQa3g10ag4s9uewgR7TKjeaT0YMyoq+gVfKYABiWZ4MQD701/t5e1Jhg== + +"@babel/plugin-proposal-async-generator-functions@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e" + integrity sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.1.0" + "@babel/plugin-syntax-async-generators" "^7.2.0" + +"@babel/plugin-proposal-class-properties@^7.0.0": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.2.1.tgz#c734a53e0a1ec40fe5c22ee5069d26da3b187d05" + integrity sha512-/4FKFChkQ2Jgb8lBDsvFX496YTi7UWTetVgS8oJUpX1e/DlaoeEK57At27ug8Hu2zI2g8bzkJ+8k9qrHZRPGPA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.2.1" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-proposal-json-strings@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317" + integrity sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-json-strings" "^7.2.0" + +"@babel/plugin-proposal-object-rest-spread@^7.0.0", "@babel/plugin-proposal-object-rest-spread@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.2.0.tgz#88f5fec3e7ad019014c97f7ee3c992f0adbf7fb8" + integrity sha512-1L5mWLSvR76XYUQJXkd/EEQgjq8HHRP6lQuZTTg0VA4tTGPpGemmCdAfQIz1rzEuWAm+ecP8PyyEm30jC1eQCg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + +"@babel/plugin-proposal-optional-catch-binding@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz#135d81edb68a081e55e56ec48541ece8065c38f5" + integrity sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + +"@babel/plugin-proposal-unicode-property-regex@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.2.0.tgz#abe7281fe46c95ddc143a65e5358647792039520" + integrity sha512-LvRVYb7kikuOtIoUeWTkOxQEV1kYvL5B6U3iWEGCzPNRus1MzJweFqORTj+0jkxozkTSYNJozPOddxmqdqsRpw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + regexpu-core "^4.2.0" + +"@babel/plugin-syntax-async-generators@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f" + integrity sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-json-strings@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470" + integrity sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-object-rest-spread@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e" + integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz#a94013d6eda8908dfe6a477e7f9eda85656ecf5c" + integrity sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-arrow-functions@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550" + integrity sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-async-to-generator@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.2.0.tgz#68b8a438663e88519e65b776f8938f3445b1a2ff" + integrity sha512-CEHzg4g5UraReozI9D4fblBYABs7IM6UerAVG7EJVrTLC5keh00aEuLUT+O40+mJCEzaXkYfTCUKIyeDfMOFFQ== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.1.0" + +"@babel/plugin-transform-block-scoped-functions@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz#5d3cc11e8d5ddd752aa64c9148d0db6cb79fd190" + integrity sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-block-scoping@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz#f17c49d91eedbcdf5dd50597d16f5f2f770132d4" + integrity sha512-vDTgf19ZEV6mx35yiPJe4fS02mPQUUcBNwWQSZFXSzTSbsJFQvHt7DqyS3LK8oOWALFOsJ+8bbqBgkirZteD5Q== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + lodash "^4.17.10" + +"@babel/plugin-transform-classes@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.2.0.tgz#374f8876075d7d21fea55aeb5c53561259163f96" + integrity sha512-aPCEkrhJYebDXcGTAP+cdUENkH7zqOlgbKwLbghjjHpJRJBWM/FSlCjMoPGA8oUdiMfOrk3+8EFPLLb5r7zj2w== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-define-map" "^7.1.0" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz#83a7df6a658865b1c8f641d510c6f3af220216da" + integrity sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-destructuring@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.2.0.tgz#e75269b4b7889ec3a332cd0d0c8cff8fed0dc6f3" + integrity sha512-coVO2Ayv7g0qdDbrNiadE4bU7lvCd9H539m2gMknyVjjMdwF/iCOM7R+E8PkntoqLkltO0rk+3axhpp/0v68VQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-dotall-regex@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz#f0aabb93d120a8ac61e925ea0ba440812dbe0e49" + integrity sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + regexpu-core "^4.1.3" + +"@babel/plugin-transform-duplicate-keys@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz#d952c4930f312a4dbfff18f0b2914e60c35530b3" + integrity sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-exponentiation-operator@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz#a63868289e5b4007f7054d46491af51435766008" + integrity sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-for-of@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.2.0.tgz#ab7468befa80f764bb03d3cb5eef8cc998e1cad9" + integrity sha512-Kz7Mt0SsV2tQk6jG5bBv5phVbkd0gd27SgYD4hH1aLMJRchM0dzHaXvrWhVZ+WxAlDoAKZ7Uy3jVTW2mKXQ1WQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-function-name@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz#f7930362829ff99a3174c39f0afcc024ef59731a" + integrity sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-literals@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz#690353e81f9267dad4fd8cfd77eafa86aba53ea1" + integrity sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-amd@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz#82a9bce45b95441f617a24011dc89d12da7f4ee6" + integrity sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw== + dependencies: + "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.2.0.tgz#c4f1933f5991d5145e9cfad1dfd848ea1727f404" + integrity sha512-V6y0uaUQrQPXUrmj+hgnks8va2L0zcZymeU7TtWEgdRLNkceafKXEduv7QzgQAE4lT+suwooG9dC7LFhdRAbVQ== + dependencies: + "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-simple-access" "^7.1.0" + +"@babel/plugin-transform-modules-systemjs@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.2.0.tgz#912bfe9e5ff982924c81d0937c92d24994bb9068" + integrity sha512-aYJwpAhoK9a+1+O625WIjvMY11wkB/ok0WClVwmeo3mCjcNRjt+/8gHWrB5i+00mUju0gWsBkQnPpdvQ7PImmQ== + dependencies: + "@babel/helper-hoist-variables" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-umd@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz#7678ce75169f0877b8eb2235538c074268dd01ae" + integrity sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw== + dependencies: + "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-new-target@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz#ae8fbd89517fa7892d20e6564e641e8770c3aa4a" + integrity sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-object-super@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz#b35d4c10f56bab5d650047dad0f1d8e8814b6598" + integrity sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.2.0.tgz#0d5ad15dc805e2ea866df4dd6682bfe76d1408c2" + integrity sha512-kB9+hhUidIgUoBQ0MsxMewhzr8i60nMa2KgeJKQWYrqQpqcBYtnpR+JgkadZVZoaEZ/eKu9mclFaVwhRpLNSzA== + dependencies: + "@babel/helper-call-delegate" "^7.1.0" + "@babel/helper-get-function-arity" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-regenerator@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz#5b41686b4ed40bef874d7ed6a84bdd849c13e0c1" + integrity sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw== + dependencies: + regenerator-transform "^0.13.3" + +"@babel/plugin-transform-runtime@^7.0.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.2.0.tgz#566bc43f7d0aedc880eaddbd29168d0f248966ea" + integrity sha512-jIgkljDdq4RYDnJyQsiWbdvGeei/0MOTtSHKO/rfbd/mXBxNpdlulMx49L0HQ4pug1fXannxoqCI+fYSle9eSw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + resolve "^1.8.1" + semver "^5.5.1" + +"@babel/plugin-transform-shorthand-properties@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz#6333aee2f8d6ee7e28615457298934a3b46198f0" + integrity sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-spread@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.0.tgz#0c76c12a3b5826130078ee8ec84a7a8e4afd79c4" + integrity sha512-7TtPIdwjS/i5ZBlNiQePQCovDh9pAhVbp/nGVRBZuUdBiVRThyyLend3OHobc0G+RLCPPAN70+z/MAMhsgJd/A== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-sticky-regex@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz#a1e454b5995560a9c1e0d537dfc15061fd2687e1" + integrity sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + +"@babel/plugin-transform-template-literals@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz#d87ed01b8eaac7a92473f608c97c089de2ba1e5b" + integrity sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-typeof-symbol@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz#117d2bcec2fbf64b4b59d1f9819894682d29f2b2" + integrity sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-unicode-regex@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz#4eb8db16f972f8abb5062c161b8b115546ade08b" + integrity sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + regexpu-core "^4.1.3" + +"@babel/preset-env@^7.0.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.2.0.tgz#a5030e7e4306af5a295dd5d7c78dc5464af3fee2" + integrity sha512-haGR38j5vOGVeBatrQPr3l0xHbs14505DcM57cbJy48kgMFvvHHoYEhHuRV+7vi559yyAUAVbTWzbK/B/pzJng== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-async-generator-functions" "^7.2.0" + "@babel/plugin-proposal-json-strings" "^7.2.0" + "@babel/plugin-proposal-object-rest-spread" "^7.2.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.2.0" + "@babel/plugin-syntax-async-generators" "^7.2.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + "@babel/plugin-transform-arrow-functions" "^7.2.0" + "@babel/plugin-transform-async-to-generator" "^7.2.0" + "@babel/plugin-transform-block-scoped-functions" "^7.2.0" + "@babel/plugin-transform-block-scoping" "^7.2.0" + "@babel/plugin-transform-classes" "^7.2.0" + "@babel/plugin-transform-computed-properties" "^7.2.0" + "@babel/plugin-transform-destructuring" "^7.2.0" + "@babel/plugin-transform-dotall-regex" "^7.2.0" + "@babel/plugin-transform-duplicate-keys" "^7.2.0" + "@babel/plugin-transform-exponentiation-operator" "^7.2.0" + "@babel/plugin-transform-for-of" "^7.2.0" + "@babel/plugin-transform-function-name" "^7.2.0" + "@babel/plugin-transform-literals" "^7.2.0" + "@babel/plugin-transform-modules-amd" "^7.2.0" + "@babel/plugin-transform-modules-commonjs" "^7.2.0" + "@babel/plugin-transform-modules-systemjs" "^7.2.0" + "@babel/plugin-transform-modules-umd" "^7.2.0" + "@babel/plugin-transform-new-target" "^7.0.0" + "@babel/plugin-transform-object-super" "^7.2.0" + "@babel/plugin-transform-parameters" "^7.2.0" + "@babel/plugin-transform-regenerator" "^7.0.0" + "@babel/plugin-transform-shorthand-properties" "^7.2.0" + "@babel/plugin-transform-spread" "^7.2.0" + "@babel/plugin-transform-sticky-regex" "^7.2.0" + "@babel/plugin-transform-template-literals" "^7.2.0" + "@babel/plugin-transform-typeof-symbol" "^7.2.0" + "@babel/plugin-transform-unicode-regex" "^7.2.0" + browserslist "^4.3.4" + invariant "^2.2.2" + js-levenshtein "^1.1.3" + semver "^5.3.0" + +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.2.0.tgz#b03e42eeddf5898e00646e4c840fa07ba8dcad7f" + integrity sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg== + dependencies: + regenerator-runtime "^0.12.0" + +"@babel/template@^7.1.0", "@babel/template@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644" + integrity sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.1.2" + "@babel/types" "^7.1.2" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.1.6": + version "7.1.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.6.tgz#c8db9963ab4ce5b894222435482bd8ea854b7b5c" + integrity sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.1.6" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/parser" "^7.1.6" + "@babel/types" "^7.1.6" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.10" + +"@babel/types@^7.0.0", "@babel/types@^7.1.2", "@babel/types@^7.1.6", "@babel/types@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.0.tgz#7941c5b2d8060e06f9601d6be7c223eef906d5d8" + integrity sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A== + dependencies: + esutils "^2.0.2" + lodash "^4.17.10" + to-fast-properties "^2.0.0" + +"@octokit/rest@^15.2.6": + version "15.18.0" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-15.18.0.tgz#e6de702b57dec94c71e806f1cff0ecb9725b3054" + integrity sha512-D1dDJMbvT4dok9++vc8uwCr92ndadwfz6vHK+IklzBHKSsuLlhpv2/dzx97Y4aRlm0t74LeXKDp4j0b4M2vmQw== + dependencies: + before-after-hook "^1.1.0" + btoa-lite "^1.0.0" + debug "^3.1.0" + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.0" + lodash "^4.17.4" + node-fetch "^2.1.1" + universal-user-agent "^2.0.0" + url-template "^2.0.8" + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= -"@types/zen-observable@0.5.3": - version "0.5.3" - resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.5.3.tgz#91b728599544efbb7386d8b6633693a3c2e7ade5" +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + +"@types/accepts@^1.3.5": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575" + integrity sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ== + dependencies: + "@types/node" "*" + +"@types/body-parser@*", "@types/body-parser@1.17.0": + version "1.17.0" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.0.tgz#9f5c9d9bd04bb54be32d5eb9fc0d8c974e6cf58c" + integrity sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.32" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28" + integrity sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg== + dependencies: + "@types/node" "*" + +"@types/cors@^2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.4.tgz#50991a759a29c0b89492751008c6af7a7c8267b0" + integrity sha512-ipZjBVsm2tF/n8qFGOuGBkUij9X9ZswVi9G3bx/6dz7POpVa6gVHcj1wsX/LVEn9MMF41fxK/PnZPPoTD1UFPw== + dependencies: + "@types/express" "*" + +"@types/events@*": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" + integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA== + +"@types/express-serve-static-core@*": + version "4.16.0" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.0.tgz#fdfe777594ddc1fe8eb8eccce52e261b496e43e7" + integrity sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w== + dependencies: + "@types/events" "*" + "@types/node" "*" + "@types/range-parser" "*" + +"@types/express@*", "@types/express@4.16.0": + version "4.16.0" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.16.0.tgz#6d8bc42ccaa6f35cf29a2b7c3333cb47b5a32a19" + integrity sha512-TtPEYumsmSTtTetAPXlJVf3kEqb6wZK0bZojpJQrnD/djV4q1oB6QQ8aKvKqwNPACoe02GNiy5zDzcYivR5Z2w== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "*" + "@types/serve-static" "*" + +"@types/long@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef" + integrity sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q== + +"@types/mime@*": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b" + integrity sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA== + +"@types/node@*", "@types/node@^10.1.0": + version "10.12.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.12.tgz#e15a9d034d9210f00320ef718a50c4a799417c47" + integrity sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A== + +"@types/range-parser@*": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" + integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== + +"@types/redis-mock@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@types/redis-mock/-/redis-mock-0.17.0.tgz#13c56c8cf3f748ae1656efc2da9bd6a97cc38e4d" + integrity sha512-UDKHu9otOSE1fPjgn0H7UoggqVyuRYfo3WJpdXdVmzgGmr1XIM/dTk/gRYf/bLjIK5mxpV8inA5uNBS2sVOilA== + dependencies: + "@types/redis" "*" + +"@types/redis@*": + version "2.8.10" + resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.10.tgz#27130c3bcc803265a51cb978538ca330dff2e748" + integrity sha512-X0NeV3ivoif2SPsmuPhwTkKfcr1fDJlaJIOyxZ9/TCIEbvzMzmZlstqCZ5r7AOolbOJtAfvuGArNXMexYYH3ng== + dependencies: + "@types/node" "*" + +"@types/serve-static@*": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.2.tgz#f5ac4d7a6420a99a6a45af4719f4dcd8cd907a48" + integrity sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q== + dependencies: + "@types/express-serve-static-core" "*" + "@types/mime" "*" + +"@types/ws@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.1.tgz#ca7a3f3756aa12f62a0a62145ed14c6db25d5a28" + integrity sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q== + dependencies: + "@types/events" "*" + "@types/node" "*" + +"@webassemblyjs/ast@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace" + integrity sha512-ZEzy4vjvTzScC+SH8RBssQUawpaInUdMTYwYYLh54/s8TuT0gBLuyUnppKsVyZEi876VmmStKsUs28UxPgdvrA== + dependencies: + "@webassemblyjs/helper-module-context" "1.7.11" + "@webassemblyjs/helper-wasm-bytecode" "1.7.11" + "@webassemblyjs/wast-parser" "1.7.11" + +"@webassemblyjs/floating-point-hex-parser@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.11.tgz#a69f0af6502eb9a3c045555b1a6129d3d3f2e313" + integrity sha512-zY8dSNyYcgzNRNT666/zOoAyImshm3ycKdoLsyDw/Bwo6+/uktb7p4xyApuef1dwEBo/U/SYQzbGBvV+nru2Xg== + +"@webassemblyjs/helper-api-error@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.11.tgz#c7b6bb8105f84039511a2b39ce494f193818a32a" + integrity sha512-7r1qXLmiglC+wPNkGuXCvkmalyEstKVwcueZRP2GNC2PAvxbLYwLLPr14rcdJaE4UtHxQKfFkuDFuv91ipqvXg== + +"@webassemblyjs/helper-buffer@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz#3122d48dcc6c9456ed982debe16c8f37101df39b" + integrity sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w== + +"@webassemblyjs/helper-code-frame@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.11.tgz#cf8f106e746662a0da29bdef635fcd3d1248364b" + integrity sha512-T8ESC9KMXFTXA5urJcyor5cn6qWeZ4/zLPyWeEXZ03hj/x9weSokGNkVCdnhSabKGYWxElSdgJ+sFa9G/RdHNw== + dependencies: + "@webassemblyjs/wast-printer" "1.7.11" + +"@webassemblyjs/helper-fsm@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.11.tgz#df38882a624080d03f7503f93e3f17ac5ac01181" + integrity sha512-nsAQWNP1+8Z6tkzdYlXT0kxfa2Z1tRTARd8wYnc/e3Zv3VydVVnaeePgqUzFrpkGUyhUUxOl5ML7f1NuT+gC0A== + +"@webassemblyjs/helper-module-context@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.11.tgz#d874d722e51e62ac202476935d649c802fa0e209" + integrity sha512-JxfD5DX8Ygq4PvXDucq0M+sbUFA7BJAv/GGl9ITovqE+idGX+J3QSzJYz+LwQmL7fC3Rs+utvWoJxDb6pmC0qg== + +"@webassemblyjs/helper-wasm-bytecode@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.11.tgz#dd9a1e817f1c2eb105b4cf1013093cb9f3c9cb06" + integrity sha512-cMXeVS9rhoXsI9LLL4tJxBgVD/KMOKXuFqYb5oCJ/opScWpkCMEz9EJtkonaNcnLv2R3K5jIeS4TRj/drde1JQ== + +"@webassemblyjs/helper-wasm-section@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz#9c9ac41ecf9fbcfffc96f6d2675e2de33811e68a" + integrity sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q== + dependencies: + "@webassemblyjs/ast" "1.7.11" + "@webassemblyjs/helper-buffer" "1.7.11" + "@webassemblyjs/helper-wasm-bytecode" "1.7.11" + "@webassemblyjs/wasm-gen" "1.7.11" + +"@webassemblyjs/ieee754@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz#c95839eb63757a31880aaec7b6512d4191ac640b" + integrity sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.7.11.tgz#d7267a1ee9c4594fd3f7e37298818ec65687db63" + integrity sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw== + dependencies: + "@xtuc/long" "4.2.1" + +"@webassemblyjs/utf8@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.7.11.tgz#06d7218ea9fdc94a6793aa92208160db3d26ee82" + integrity sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA== + +"@webassemblyjs/wasm-edit@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz#8c74ca474d4f951d01dbae9bd70814ee22a82005" + integrity sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg== + dependencies: + "@webassemblyjs/ast" "1.7.11" + "@webassemblyjs/helper-buffer" "1.7.11" + "@webassemblyjs/helper-wasm-bytecode" "1.7.11" + "@webassemblyjs/helper-wasm-section" "1.7.11" + "@webassemblyjs/wasm-gen" "1.7.11" + "@webassemblyjs/wasm-opt" "1.7.11" + "@webassemblyjs/wasm-parser" "1.7.11" + "@webassemblyjs/wast-printer" "1.7.11" + +"@webassemblyjs/wasm-gen@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz#9bbba942f22375686a6fb759afcd7ac9c45da1a8" + integrity sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA== + dependencies: + "@webassemblyjs/ast" "1.7.11" + "@webassemblyjs/helper-wasm-bytecode" "1.7.11" + "@webassemblyjs/ieee754" "1.7.11" + "@webassemblyjs/leb128" "1.7.11" + "@webassemblyjs/utf8" "1.7.11" + +"@webassemblyjs/wasm-opt@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz#b331e8e7cef8f8e2f007d42c3a36a0580a7d6ca7" + integrity sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg== + dependencies: + "@webassemblyjs/ast" "1.7.11" + "@webassemblyjs/helper-buffer" "1.7.11" + "@webassemblyjs/wasm-gen" "1.7.11" + "@webassemblyjs/wasm-parser" "1.7.11" + +"@webassemblyjs/wasm-parser@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz#6e3d20fa6a3519f6b084ef9391ad58211efb0a1a" + integrity sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg== + dependencies: + "@webassemblyjs/ast" "1.7.11" + "@webassemblyjs/helper-api-error" "1.7.11" + "@webassemblyjs/helper-wasm-bytecode" "1.7.11" + "@webassemblyjs/ieee754" "1.7.11" + "@webassemblyjs/leb128" "1.7.11" + "@webassemblyjs/utf8" "1.7.11" + +"@webassemblyjs/wast-parser@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.7.11.tgz#25bd117562ca8c002720ff8116ef9072d9ca869c" + integrity sha512-lEyVCg2np15tS+dm7+JJTNhNWq9yTZvi3qEhAIIOaofcYlUp0UR5/tVqOwa/gXYr3gjwSZqw+/lS9dscyLelbQ== + dependencies: + "@webassemblyjs/ast" "1.7.11" + "@webassemblyjs/floating-point-hex-parser" "1.7.11" + "@webassemblyjs/helper-api-error" "1.7.11" + "@webassemblyjs/helper-code-frame" "1.7.11" + "@webassemblyjs/helper-fsm" "1.7.11" + "@xtuc/long" "4.2.1" + +"@webassemblyjs/wast-printer@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.7.11.tgz#c4245b6de242cb50a2cc950174fdbf65c78d7813" + integrity sha512-m5vkAsuJ32QpkdkDOUPGSltrg8Cuk3KBx4YrmAGQwCZPRdUHXxG4phIOuuycLemHFr74sWL9Wthqss4fzdzSwg== + dependencies: + "@webassemblyjs/ast" "1.7.11" + "@webassemblyjs/wast-parser" "1.7.11" + "@xtuc/long" "4.2.1" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.1.tgz#5c85d662f76fa1d34575766c5dcd6615abcd30d8" + integrity sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g== abab@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" + integrity sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4= abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" +accepts@^1.3.5, accepts@~1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" + integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I= dependencies: - mime-types "~2.1.16" + mime-types "~2.1.18" negotiator "0.6.1" -acorn-dynamic-import@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" +acorn-dynamic-import@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz#901ceee4c7faaef7e07ad2a47e890675da50a278" + integrity sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg== dependencies: - acorn "^4.0.3" + acorn "^5.0.0" acorn-globals@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf" + integrity sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8= dependencies: acorn "^4.0.4" -acorn@^4.0.3, acorn@^4.0.4: +acorn@^4.0.4: version "4.0.13" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" + integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c= -acorn@^5.0.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822" +acorn@^5.0.0, acorn@^5.6.2: + version "5.7.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" + integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== + +agent-base@4, agent-base@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" + integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== + dependencies: + es6-promisify "^5.0.0" agentkeepalive@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-2.2.0.tgz#c5d1bd4b129008f1163f236f86e5faea2026e2ef" + integrity sha1-xdG9SxKQCPEWPyNvhuX66iAm4u8= -ajv-keywords@^2.0.0, ajv-keywords@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" +ajv-errors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" + integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== -ajv@^4.9.1: - version "4.11.8" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" - dependencies: - co "^4.6.0" - json-stable-stringify "^1.0.1" +ajv-keywords@^3.0.0, ajv-keywords@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" + integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= -ajv@^5.1.0, ajv@^5.1.5, ajv@^5.2.3: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" +ajv@^6.0.1, ajv@^6.1.0, ajv@^6.5.5: + version "6.6.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61" + integrity sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww== dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" + fast-deep-equal "^2.0.1" fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" -algoliasearch@^3.24.7: - version "3.24.9" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-3.24.9.tgz#19063470efe5b6779ec081394b1f7aa400438273" +algoliasearch@^3.32.0: + version "3.32.0" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-3.32.0.tgz#5818168c26ff921bd0346a919071bac928b747ce" + integrity sha512-C8oQnPTf0wPuyD2jSZwtBAPvz+lHOE7zRIPpgXGBuNt6ZNcC4omsbytG26318rT77a8h4759vmIp6n9p8iw4NA== dependencies: agentkeepalive "^2.2.0" debug "^2.6.8" @@ -100,191 +1049,236 @@ algoliasearch@^3.24.7: semver "^5.1.0" tunnel-agent "^0.6.0" -align-text@^0.1.1, align-text@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" - dependencies: - kind-of "^3.0.2" - longest "^1.0.1" - repeat-string "^1.5.2" - -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - -ansi-align@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-1.1.0.tgz#2f0c1658829739add5ebb15e6b0c6e3423f016ba" - dependencies: - string-width "^1.0.1" - ansi-align@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" + integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= dependencies: string-width "^2.0.0" ansi-escapes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" + version "3.1.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" + integrity sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw== ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= -ansi-styles@^3.1.0, ansi-styles@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" +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" -anymatch@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" - dependencies: - micromatch "^2.1.5" - normalize-path "^2.0.0" - anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== dependencies: micromatch "^3.1.4" normalize-path "^2.1.1" -apollo-cache-control@^0.0.x: - version "0.0.7" - resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.0.7.tgz#ffef56413a429a1ce204be5b78d248c4fe3b67ac" +apollo-cache-control@0.5.0-alpha.0: + version "0.5.0-alpha.0" + resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.5.0-alpha.0.tgz#aed22e09121b90f6f5ab24a38429c2551d1d870d" + integrity sha512-vb1tU8+oE6WAPvUwz18SBvxKUfG6OMJlaIiBLFQ+1bFkqZneTil7A3L4QgRSTPZKus7ckCZ9WR7DNx/zj7hn8A== dependencies: - graphql-extensions "^0.0.x" - -apollo-engine-binary-darwin@0.2018.2-111-gb13195fb0: - version "0.2018.2-111-gb13195fb0" - resolved "https://registry.yarnpkg.com/apollo-engine-binary-darwin/-/apollo-engine-binary-darwin-0.2018.2-111-gb13195fb0.tgz#5e5fcbde78a5c6ad818b07ea7c850c5024b70f97" + apollo-server-env "2.2.0" + graphql-extensions "0.5.0-alpha.0" -apollo-engine-binary-linux@0.2018.2-111-gb13195fb0: - version "0.2018.2-111-gb13195fb0" - resolved "https://registry.yarnpkg.com/apollo-engine-binary-linux/-/apollo-engine-binary-linux-0.2018.2-111-gb13195fb0.tgz#fede5c64dacd117012f81734f317ff60554d78ab" +apollo-datasource@0.3.0-alpha.0: + version "0.3.0-alpha.0" + resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-0.3.0-alpha.0.tgz#beeaab9b7d5cb370b92c7bff17d3ad0d1cd56e4a" + integrity sha512-xY3H4/lbwOdh4t2vGLkSAN/FG1OOvmUgRp6vzOW12yzLK9+Hn6j6k0X29WkJFpjLf9JlWqWHEHumi2hqKJtOEg== + dependencies: + apollo-server-caching "0.3.0-alpha.0" + apollo-server-env "2.2.0" -apollo-engine-binary-windows@0.2018.2-111-gb13195fb0: - version "0.2018.2-111-gb13195fb0" - resolved "https://registry.yarnpkg.com/apollo-engine-binary-windows/-/apollo-engine-binary-windows-0.2018.2-111-gb13195fb0.tgz#cd0a01af2842946fcb0733c125ff7bd6b3e11548" +apollo-engine-reporting-protobuf@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.2.0.tgz#2aaf4d2eddefe7924d469cf1135267bc0deadf73" + integrity sha512-qI+GJKN78UMJ9Aq/ORdiM2qymZ5yswem+/VDdVFocq+/e1QqxjnpKjQWISkswci5+WtpJl9SpHBNxG98uHDKkA== + dependencies: + protobufjs "^6.8.6" -apollo-engine@1.x: - version "1.0.1" - resolved "https://registry.yarnpkg.com/apollo-engine/-/apollo-engine-1.0.1.tgz#a3c4ed0318754bc2ce23bc088f108ca87c26988e" - optionalDependencies: - apollo-engine-binary-darwin "0.2018.2-111-gb13195fb0" - apollo-engine-binary-linux "0.2018.2-111-gb13195fb0" - apollo-engine-binary-windows "0.2018.2-111-gb13195fb0" +apollo-engine-reporting@0.3.0-alpha.0: + version "0.3.0-alpha.0" + resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-0.3.0-alpha.0.tgz#3a55da736404bded6d7ab560f0544e0cf109ad35" + integrity sha512-0hRFvT8Gc/VATKVa+qtehaQYTgi+2sOOpHJ7TA8gzkVXmSXiIy6tbJZBnLbh9D1xKfOoF3qtspJrcxBI5A8Ceg== + dependencies: + apollo-engine-reporting-protobuf "0.2.0" + apollo-server-env "2.2.0" + async-retry "^1.2.1" + graphql-extensions "0.5.0-alpha.0" + lodash "^4.17.10" -apollo-link-http-common@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.3.tgz#82ae0d4ff0cdd7c5c8826411d9dd7f7d8049ca46" +apollo-env@0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/apollo-env/-/apollo-env-0.2.5.tgz#162c785bccd2aea69350a7600fab4b7147fc9da5" + integrity sha512-Gc7TEbwCl7jJVutnn8TWfzNSkrrqyoo0DP92BQJFU9pZbJhpidoXf2Sw1YwOJl82rRKH3ujM3C8vdZLOgpFcFA== dependencies: - apollo-link "^1.2.1" + core-js "^3.0.0-beta.3" + node-fetch "^2.2.0" -apollo-link@^1.0.0: - version "1.0.7" - resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.0.7.tgz#42cd38a7378332fc3e41a214ff6a6e5e703a556f" +apollo-link-http-common@^0.2.5: + version "0.2.6" + resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.6.tgz#47b9012500599fe343e2e87378026384b4fc8c1f" + integrity sha512-LUOMWvrZuBP1hyWLBXyaW0KyFeKo79j+k3N+Q4HSkXKbLibnllXQ+JxxoSKGhm0bhREygiLtJAG9JnGlhxGO/Q== dependencies: - "@types/zen-observable" "0.5.3" - apollo-utilities "^1.0.0" - zen-observable "^0.6.0" + apollo-link "^1.2.4" -apollo-link@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.1.tgz#c120b16059f9bd93401b9f72b94d2f80f3f305d2" +apollo-link@^1.2.3, apollo-link@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.4.tgz#ab4d21d2e428db848e88b5e8f4adc717b19c954b" + integrity sha512-B1z+9H2nTyWEhMXRFSnoZ1vSuAYP+V/EdUJvRx9uZ8yuIBZMm6reyVtr1n0BWlKeSFyPieKJy2RLzmITAAQAMQ== dependencies: - "@types/node" "^9.4.6" apollo-utilities "^1.0.0" - zen-observable-ts "^0.8.6" + zen-observable-ts "^0.8.11" -apollo-local-query@^0.3.0: +apollo-local-query@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/apollo-local-query/-/apollo-local-query-0.3.1.tgz#e290375253879badd09ebe7ca410744aad7e5ec9" + integrity sha512-S8xom02//NsEHmOBGqTJJAGaP8K3Vd16Dtm+rCEMx0PJ+wdgBHDqK6CxYyt/OxIzY9maS2MXm+mnffW2IedT3w== dependencies: debug "^2.6.9" -apollo-server-core@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-1.3.2.tgz#f36855a3ebdc2d77b8b9c454380bf1d706105ffc" - dependencies: - apollo-cache-control "^0.0.x" - apollo-tracing "^0.1.0" - graphql-extensions "^0.0.x" - -apollo-server-express@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-1.3.2.tgz#0ff8201c0bf362804a151e1399767dae6ab7e309" - dependencies: - apollo-server-core "^1.3.2" - apollo-server-module-graphiql "^1.3.0" - -apollo-server-module-graphiql@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/apollo-server-module-graphiql/-/apollo-server-module-graphiql-1.3.2.tgz#0a9e4c48dece3af904fee333f95f7b9817335ca7" - -apollo-tracing@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.1.1.tgz#7a5707543fc102f81cda7ba45b98331a82a6750b" - dependencies: - graphql-extensions "^0.0.x" +apollo-server-caching@0.3.0-alpha.0: + version "0.3.0-alpha.0" + resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-0.3.0-alpha.0.tgz#1a4e3762da926018be975d3dfd17da4d60e66923" + integrity sha512-GY2OKVnVGvClY9sXQLnuCuMn9foE4dTExA9UHNf5stZqx3E/t/r9pLnu7w1wC1B8ppV9iW7oh83/MLKgJ/dnsQ== + dependencies: + lru-cache "^5.0.0" + +apollo-server-core@2.4.0-alpha.0: + version "2.4.0-alpha.0" + resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.4.0-alpha.0.tgz#420193c2aa0f05dac92feaa64f50e7c9a700f1cb" + integrity sha512-/n0y1zCtCpmEmn45eT7jorFqVTMR5JoP67yjSTup+FfEOSjskAeP3uuuCHLx3qhdoDgXTtzdeDmWFXyYS/1nVA== + dependencies: + "@apollographql/apollo-tools" "^0.2.6" + "@apollographql/graphql-playground-html" "^1.6.6" + "@types/ws" "^6.0.0" + apollo-cache-control "0.5.0-alpha.0" + apollo-datasource "0.3.0-alpha.0" + apollo-engine-reporting "0.3.0-alpha.0" + apollo-server-caching "0.3.0-alpha.0" + apollo-server-env "2.2.0" + apollo-server-errors "2.2.0" + apollo-server-plugin-base "0.3.0-alpha.0" + apollo-tracing "0.5.0-alpha.0" + graphql-extensions "0.5.0-alpha.0" + graphql-subscriptions "^1.0.0" + graphql-tag "^2.9.2" + graphql-tools "^4.0.0" + graphql-upload "^8.0.2" + json-stable-stringify "^1.0.1" + lodash "^4.17.10" + subscriptions-transport-ws "^0.9.11" + ws "^6.0.0" -apollo-upload-client@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/apollo-upload-client/-/apollo-upload-client-8.0.0.tgz#0067f3b426b3828f971964799bc31f8073bd0607" +apollo-server-env@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-2.2.0.tgz#5eec5dbf46581f663fd6692b2e05c7e8ae6d6034" + integrity sha512-wjJiI5nQWPBpNmpiLP389Ezpstp71szS6DHAeTgYLb/ulCw3CTuuA+0/E1bsThVWiQaDeHZE0sE3yI8q2zrYiA== dependencies: - "@babel/runtime" "^7.0.0-beta.40" - apollo-link-http-common "^0.2.3" - extract-files "^3.1.0" + node-fetch "^2.1.2" + util.promisify "^1.0.0" -apollo-upload-server@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/apollo-upload-server/-/apollo-upload-server-5.0.0.tgz#c953b523608313966e0c8444637f4ae8ef77d5bc" - dependencies: - "@babel/runtime" "^7.0.0-beta.40" - busboy "^0.2.14" - object-path "^0.11.4" +apollo-server-errors@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.2.0.tgz#5b452a1d6ff76440eb0f127511dc58031a8f3cb5" + integrity sha512-gV9EZG2tovFtT1cLuCTavnJu2DaKxnXPRNGSTo+SDI6IAk6cdzyW0Gje5N2+3LybI0Wq5KAbW6VLei31S4MWmg== + +apollo-server-express@2.4.0-alpha.0: + version "2.4.0-alpha.0" + resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.4.0-alpha.0.tgz#a919f6e75f7181e73185b50e387fa7e159153277" + integrity sha512-ODrCMxxpgSYZ8tV45l2UuCKuQ7D4h88C+IfBXAU5ItGrfny0tkJCSuRnOmyY8o/MOF8aenqB7m0IWdf8dRxG/Q== + dependencies: + "@apollographql/graphql-playground-html" "^1.6.6" + "@types/accepts" "^1.3.5" + "@types/body-parser" "1.17.0" + "@types/cors" "^2.8.4" + "@types/express" "4.16.0" + accepts "^1.3.5" + apollo-server-core "2.4.0-alpha.0" + body-parser "^1.18.3" + cors "^2.8.4" + graphql-subscriptions "^1.0.0" + graphql-tools "^4.0.0" + type-is "^1.6.16" + +apollo-server-plugin-base@0.3.0-alpha.0: + version "0.3.0-alpha.0" + resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.3.0-alpha.0.tgz#e5b0f860a530a9b7dca01a39f493051562fda32f" + integrity sha512-ygDc6CGgyEU2PaVPR7xOM1ZzMnR/evGRMHAane1VhMgc8m60jaoDRprM+7B7MJ9NJw4slA08Gr6Hq65kezAMRA== + +apollo-tracing@0.5.0-alpha.0: + version "0.5.0-alpha.0" + resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.5.0-alpha.0.tgz#2d4aa71b76bbaf7ec3e25053c25320606549366e" + integrity sha512-uYetlXSKHv29jNKoDqC0n7AgoWrQ/s9OmSJAuAHLE9Q0+9PvMrbMXtoL7WH4vcy1ObYMwaL+Zh727191VzrSVw== + dependencies: + apollo-server-env "2.2.0" + graphql-extensions "0.5.0-alpha.0" + +apollo-upload-client@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/apollo-upload-client/-/apollo-upload-client-9.1.0.tgz#13191714ae07388088f2c773ebbfd53ba2f64c53" + integrity sha512-ZN5gsbBjImEZTWWTUHpCEGDasnoBGbaODpznQ5EawyNHceuFYSNJbbft+ZZ841vZAcj9XZdKUKoaLBlMZ/r7nw== + dependencies: + apollo-link "^1.2.3" + apollo-link-http-common "^0.2.5" + extract-files "^4.0.0" apollo-utilities@^1.0.0, apollo-utilities@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.4.tgz#560009ea5541b9fdc4ee07ebb1714ee319a76c15" + version "1.0.26" + resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.26.tgz#589c66bf4d16223531351cf667a230c787def1da" + integrity sha512-URw7o3phymliqYCYatcird2YRPUU2eWCNvip64U9gQrX56mEfK4m99yBIDCMTpmcvOFsKLii1sIEZsHIs/bvnw== + dependencies: + fast-json-stable-stringify "^2.0.0" append-transform@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" + integrity sha1-126/jKlNJ24keja61EpLdKthGZE= dependencies: default-require-extensions "^1.0.0" -aproba@^1.0.3: +aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== are-we-there-yet@~1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== dependencies: delegates "^1.0.0" readable-stream "^2.0.6" argparse@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + 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" argparse@~0.1.15: version "0.1.16" resolved "https://registry.yarnpkg.com/argparse/-/argparse-0.1.16.tgz#cfd01e0fbba3d6caed049fbd758d40f65196f57c" + integrity sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw= dependencies: underscore "~1.7.0" underscore.string "~2.4.0" @@ -292,86 +1286,106 @@ argparse@~0.1.15: arr-diff@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8= dependencies: arr-flatten "^1.0.1" arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= arr-flatten@^1.0.1, arr-flatten@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== arr-union@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= dependencies: array-uniq "^1.0.1" array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= array-unique@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -arrify@^1.0.0, arrify@^1.0.1: +arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= asap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/asap/-/asap-1.0.0.tgz#b2a45da5fdfa20b0496fc3768cc27c12fa916a7d" + integrity sha1-sqRdpf36ILBJb8N2jMJ8EvqRan0= asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= asn1.js@^4.0.0: - version "4.9.2" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.2.tgz#8117ef4f7ed87cd8f89044b5bff97ac243a16c9a" + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== dependencies: bn.js "^4.0.0" inherits "^2.0.1" minimalistic-assert "^1.0.0" -asn1.js@^4.8.1: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" +asn1.js@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.0.1.tgz#7668b56416953f0ce3421adbb3893ace59c96f59" + integrity sha512-aO8EaEgbgqq77IEw+1jfx5c9zTbzvkfuRBuZsSsPnTHMkmd5AI4J6OtITLZFa381jReeaQL67J0GBTUu0+ZTVw== dependencies: bn.js "^4.0.0" inherits "^2.0.1" minimalistic-assert "^1.0.0" asn1@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" assert-err@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/assert-err/-/assert-err-1.1.0.tgz#c05062799a1d97d3f5eaa258e3242aab499fc8ef" + integrity sha1-wFBieZodl9P16qJY4yQqq0mfyO8= dependencies: escape-string-regexp "^1.0.5" object-assign "^4.1.0" @@ -379,58 +1393,68 @@ assert-err@^1.0.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" - -assert-plus@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= assert@^1.1.1: version "1.4.1" resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" + integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE= dependencies: util "0.10.3" assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= 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== async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + integrity sha1-GdOGodntxufByF04iu28xW0zYC0= async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== -async@^1.4.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" +async-retry@^1.2.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.2.3.tgz#a6521f338358d322b1a0012b79030c6f411d1ce0" + integrity sha512-tfDb02Th6CE6pJUF2gjW5ZVjsgwlucVXOEQMvEX9JgSJMs9gAX+Nz3xRuJBKuUYjTSYORqvDBORdAQ3LU59g7Q== + dependencies: + retry "0.12.0" -async@^2.1.2, async@^2.1.4: - version "2.6.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" +async@^2.1.4, async@^2.5.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" + integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ== dependencies: - lodash "^4.14.0" + lodash "^4.17.10" asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -atob@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" +atob@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== autolinker@~0.15.0: version "0.15.3" resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.15.3.tgz#342417d8f2f3461b14cf09088d5edf8791dc9832" + integrity sha1-NCQX2PLzRhsUzwkIjV7fh5HcmDI= aws-sdk@2.200.0: version "2.200.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.200.0.tgz#f460c96408725b0eb8c658fddea6e0bfe0ef5a44" + integrity sha1-9GDJZAhyWw64xlj93qbgv+DvWkQ= dependencies: buffer "4.9.1" events "^1.1.1" @@ -442,36 +1466,42 @@ aws-sdk@2.200.0: xml2js "0.4.17" xmlbuilder "4.2.1" -aws-sign2@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" - 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.2.1, aws4@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" +aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== axios@^0.16.2: version "0.16.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d" + integrity sha1-uk+S8XFn37q0CYN4VFS5rBScPG0= dependencies: follow-redirects "^1.2.3" is-buffer "^1.1.5" +b2a@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/b2a/-/b2a-1.0.10.tgz#979271967ae2dd6d0bafea827ab5d02bb0362c01" + integrity sha512-qRdfj/Abk64Wp1QKE5t4dG+ioF87/er959LgzyBg0DGBNNQMcOzljC5lpFeYeFFUcUdtUhwofDRRo/0YM8Il1Q== + babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= dependencies: chalk "^1.1.3" esutils "^2.0.2" js-tokens "^3.0.2" -babel-core@^6.0.0, babel-core@^6.25.0, babel-core@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" +babel-core@^6.0.0, babel-core@^6.26.0: + version "6.26.3" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" + integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== dependencies: babel-code-frame "^6.26.0" babel-generator "^6.26.0" @@ -483,19 +1513,20 @@ babel-core@^6.0.0, babel-core@^6.25.0, babel-core@^6.26.0: babel-traverse "^6.26.0" babel-types "^6.26.0" babylon "^6.18.0" - convert-source-map "^1.5.0" - debug "^2.6.8" + convert-source-map "^1.5.1" + debug "^2.6.9" json5 "^0.5.1" lodash "^4.17.4" minimatch "^3.0.4" path-is-absolute "^1.0.1" - private "^0.1.7" + private "^0.1.8" slash "^1.0.0" - source-map "^0.5.6" + source-map "^0.5.7" babel-generator@^6.18.0, babel-generator@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" + integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== dependencies: babel-messages "^6.23.0" babel-runtime "^6.26.0" @@ -503,12 +1534,13 @@ babel-generator@^6.18.0, babel-generator@^6.26.0: detect-indent "^4.0.0" jsesc "^1.3.0" lodash "^4.17.4" - source-map "^0.5.6" + source-map "^0.5.7" trim-right "^1.0.1" babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" + integrity sha1-zORReto1b0IgvK6KAsKzRvmlZmQ= dependencies: babel-helper-explode-assignable-expression "^6.24.1" babel-runtime "^6.22.0" @@ -517,6 +1549,7 @@ babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: babel-helper-builder-react-jsx@^6.24.1: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0" + integrity sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA= dependencies: babel-runtime "^6.26.0" babel-types "^6.26.0" @@ -525,6 +1558,7 @@ babel-helper-builder-react-jsx@^6.24.1: babel-helper-call-delegate@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" + integrity sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340= dependencies: babel-helper-hoist-variables "^6.24.1" babel-runtime "^6.22.0" @@ -534,6 +1568,7 @@ babel-helper-call-delegate@^6.24.1: babel-helper-define-map@^6.24.1: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" + integrity sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8= dependencies: babel-helper-function-name "^6.24.1" babel-runtime "^6.26.0" @@ -543,6 +1578,7 @@ babel-helper-define-map@^6.24.1: babel-helper-explode-assignable-expression@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" + integrity sha1-8luCz33BBDPFX3BZLVdGQArCLKo= dependencies: babel-runtime "^6.22.0" babel-traverse "^6.24.1" @@ -551,6 +1587,7 @@ babel-helper-explode-assignable-expression@^6.24.1: babel-helper-function-name@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + integrity sha1-00dbjAPtmCQqJbSDUasYOZ01gKk= dependencies: babel-helper-get-function-arity "^6.24.1" babel-runtime "^6.22.0" @@ -561,6 +1598,7 @@ babel-helper-function-name@^6.24.1: babel-helper-get-function-arity@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + integrity sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0= dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" @@ -568,6 +1606,7 @@ babel-helper-get-function-arity@^6.24.1: babel-helper-hoist-variables@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" + integrity sha1-HssnaJydJVE+rbyZFKc/VAi+enY= dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" @@ -575,6 +1614,7 @@ babel-helper-hoist-variables@^6.24.1: babel-helper-optimise-call-expression@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" + integrity sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc= dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" @@ -582,6 +1622,7 @@ babel-helper-optimise-call-expression@^6.24.1: babel-helper-regex@^6.24.1: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" + integrity sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI= dependencies: babel-runtime "^6.26.0" babel-types "^6.26.0" @@ -590,6 +1631,7 @@ babel-helper-regex@^6.24.1: babel-helper-remap-async-to-generator@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" + integrity sha1-XsWBgnrXI/7N04HxySg5BnbkVRs= dependencies: babel-helper-function-name "^6.24.1" babel-runtime "^6.22.0" @@ -600,6 +1642,7 @@ babel-helper-remap-async-to-generator@^6.24.1: babel-helper-replace-supers@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" + integrity sha1-v22/5Dk40XNpohPKiov3S2qQqxo= dependencies: babel-helper-optimise-call-expression "^6.24.1" babel-messages "^6.23.0" @@ -611,6 +1654,7 @@ babel-helper-replace-supers@^6.24.1: babel-helpers@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI= dependencies: babel-runtime "^6.22.0" babel-template "^6.24.1" @@ -618,121 +1662,140 @@ babel-helpers@^6.24.1: babel-jest@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-21.2.0.tgz#2ce059519a9374a2c46f2455b6fbef5ad75d863e" + integrity sha512-O0W2qLoWu1QOoOGgxiR2JID4O6WSpxPiQanrkyi9SSlM0PJ60Ptzlck47lhtnr9YZO3zYOsxHwnyeWJ6AffoBQ== dependencies: babel-plugin-istanbul "^4.0.0" babel-preset-jest "^21.2.0" -babel-loader@^7.1.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.2.tgz#f6cbe122710f1aa2af4d881c6d5b54358ca24126" +babel-loader@^8.0.2, babel-loader@v8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.4.tgz#7bbf20cbe4560629e2e41534147692d3fecbdce6" + integrity sha512-fhBhNkUToJcW9nV46v8w87AJOwAJDz84c1CL57n3Stj73FANM/b9TbCUK4YhdOwEyZ+OxhYpdeZDNzSI29Firw== dependencies: find-cache-dir "^1.0.0" loader-utils "^1.0.2" mkdirp "^0.5.1" + util.promisify "^1.0.0" babel-messages@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= dependencies: babel-runtime "^6.22.0" babel-plugin-check-es2015-constants@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + integrity sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o= dependencies: babel-runtime "^6.22.0" babel-plugin-istanbul@^4.0.0: - version "4.1.5" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz#6760cdd977f411d3e175bb064f2bc327d99b2b6e" + version "4.1.6" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" + integrity sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ== dependencies: + babel-plugin-syntax-object-rest-spread "^6.13.0" find-up "^2.1.0" - istanbul-lib-instrument "^1.7.5" - test-exclude "^4.1.1" + istanbul-lib-instrument "^1.10.1" + test-exclude "^4.2.1" babel-plugin-jest-hoist@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-21.2.0.tgz#2cef637259bd4b628a6cace039de5fcd14dbb006" + integrity sha512-yi5QuiVyyvhBUDLP4ButAnhYzkdrUwWDtvUJv71hjH3fclhnZg4HkDeqaitcR2dZZx/E67kGkRcPVjtVu+SJfQ== babel-plugin-replace-dynamic-import-runtime@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/babel-plugin-replace-dynamic-import-runtime/-/babel-plugin-replace-dynamic-import-runtime-1.0.2.tgz#20ab1da8bac209524bf998d1cf48f3b9349492b3" + integrity sha512-nuBKiQFcdoT3GjjcSzEQesr1AWW1MNC5nk5HBY1mvLPLl9ZBMRC6yvrkTA5QgvTj+SxxZHmD3QEMyJe1AMvHEg== dependencies: babel-plugin-syntax-dynamic-import "^6.18.0" babel-template "^6.24.1" babel-types "^6.24.1" -babel-plugin-styled-components@^1.1.4, babel-plugin-styled-components@^1.1.7: - version "1.4.0" - resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.4.0.tgz#3a2207a1852770a383f0106ca85aded4f3e3488f" +babel-plugin-styled-components@^1.1.4: + version "1.9.3" + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.9.3.tgz#facc61da441d15b291128b36cba07f309ed324f0" + integrity sha512-MkRRvMyWRSZti9HlNCROdbrxkaE2TCsRjKqT8OJ1GH4+ksNIIPNEG3HuqWzcwicKbpW6O1KZizOGFEmnpOeD4Q== dependencies: - babel-types "^6.26.0" - stylis "^3.0.0" + "@babel/helper-annotate-as-pure" "^7.0.0" + babel-plugin-syntax-jsx "^6.18.0" + lodash "^4.17.10" + +babel-plugin-styled-components@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.0.tgz#ff1f42ad2cc78c21f26b62266b8f564dbc862939" + integrity sha512-sQVKG8irFXx14ZfaK1bBePirfkacl3j8nZwSZK+ZjsbnadRHKQTbhXbe/RB1vT6Vgkz45E+V95LBq4KqdhZUNw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-module-imports" "^7.0.0" + babel-plugin-syntax-jsx "^6.18.0" + lodash "^4.17.10" babel-plugin-syntax-async-functions@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" - -babel-plugin-syntax-class-properties@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" + integrity sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU= babel-plugin-syntax-dynamic-import@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" + integrity sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo= babel-plugin-syntax-exponentiation-operator@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + integrity sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4= babel-plugin-syntax-flow@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" + integrity sha1-TDqyCiryaqIM0lmVw5jE63AxDI0= -babel-plugin-syntax-jsx@^6.8.0: +babel-plugin-syntax-jsx@^6.18.0, babel-plugin-syntax-jsx@^6.8.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= babel-plugin-syntax-object-rest-spread@^6.13.0, babel-plugin-syntax-object-rest-spread@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= babel-plugin-syntax-trailing-function-commas@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + integrity sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM= babel-plugin-transform-async-to-generator@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" + integrity sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E= dependencies: babel-helper-remap-async-to-generator "^6.24.1" babel-plugin-syntax-async-functions "^6.8.0" babel-runtime "^6.22.0" -babel-plugin-transform-class-properties@^6.19.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac" - dependencies: - babel-helper-function-name "^6.24.1" - babel-plugin-syntax-class-properties "^6.8.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-plugin-transform-es2015-arrow-functions@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" + integrity sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" + integrity sha1-u8UbSflk1wy42OC5ToICRs46YUE= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-block-scoping@^6.23.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" + integrity sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8= dependencies: babel-runtime "^6.26.0" babel-template "^6.26.0" @@ -743,6 +1806,7 @@ babel-plugin-transform-es2015-block-scoping@^6.23.0: babel-plugin-transform-es2015-classes@^6.23.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" + integrity sha1-WkxYpQyclGHlZLSyo7+ryXolhNs= dependencies: babel-helper-define-map "^6.24.1" babel-helper-function-name "^6.24.1" @@ -757,6 +1821,7 @@ babel-plugin-transform-es2015-classes@^6.23.0: babel-plugin-transform-es2015-computed-properties@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" + integrity sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM= dependencies: babel-runtime "^6.22.0" babel-template "^6.24.1" @@ -764,12 +1829,14 @@ babel-plugin-transform-es2015-computed-properties@^6.22.0: babel-plugin-transform-es2015-destructuring@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" + integrity sha1-mXux8auWf2gtKwh2/jWNYOdlxW0= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-duplicate-keys@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" + integrity sha1-c+s9MQypaePvnskcU3QabxV2Qj4= dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" @@ -777,12 +1844,14 @@ babel-plugin-transform-es2015-duplicate-keys@^6.22.0: babel-plugin-transform-es2015-for-of@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" + integrity sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-function-name@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" + integrity sha1-g0yJhTvDaxrw86TF26qU/Y6sqos= dependencies: babel-helper-function-name "^6.24.1" babel-runtime "^6.22.0" @@ -791,20 +1860,23 @@ babel-plugin-transform-es2015-function-name@^6.22.0: babel-plugin-transform-es2015-literals@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" + integrity sha1-T1SgLWzWbPkVKAAZox0xklN3yi4= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" + integrity sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ= dependencies: babel-plugin-transform-es2015-modules-commonjs "^6.24.1" babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-modules-commonjs@^6.18.0, babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" +babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: + version "6.26.2" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" + integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q== dependencies: babel-plugin-transform-strict-mode "^6.24.1" babel-runtime "^6.26.0" @@ -814,6 +1886,7 @@ babel-plugin-transform-es2015-modules-commonjs@^6.18.0, babel-plugin-transform-e babel-plugin-transform-es2015-modules-systemjs@^6.23.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" + integrity sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM= dependencies: babel-helper-hoist-variables "^6.24.1" babel-runtime "^6.22.0" @@ -822,6 +1895,7 @@ babel-plugin-transform-es2015-modules-systemjs@^6.23.0: babel-plugin-transform-es2015-modules-umd@^6.23.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" + integrity sha1-rJl+YoXNGO1hdq22B9YCNErThGg= dependencies: babel-plugin-transform-es2015-modules-amd "^6.24.1" babel-runtime "^6.22.0" @@ -830,13 +1904,15 @@ babel-plugin-transform-es2015-modules-umd@^6.23.0: babel-plugin-transform-es2015-object-super@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" + integrity sha1-JM72muIcuDp/hgPa0CH1cusnj40= dependencies: babel-helper-replace-supers "^6.24.1" babel-runtime "^6.22.0" -babel-plugin-transform-es2015-parameters@^6.21.0, babel-plugin-transform-es2015-parameters@^6.23.0: +babel-plugin-transform-es2015-parameters@^6.23.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" + integrity sha1-V6w1GrScrxSpfNE7CfZv3wpiXys= dependencies: babel-helper-call-delegate "^6.24.1" babel-helper-get-function-arity "^6.24.1" @@ -848,6 +1924,7 @@ babel-plugin-transform-es2015-parameters@^6.21.0, babel-plugin-transform-es2015- babel-plugin-transform-es2015-shorthand-properties@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" + integrity sha1-JPh11nIch2YbvZmkYi5R8U3jiqA= dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" @@ -855,12 +1932,14 @@ babel-plugin-transform-es2015-shorthand-properties@^6.22.0: babel-plugin-transform-es2015-spread@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + integrity sha1-1taKmfia7cRTbIGlQujdnxdG+NE= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-sticky-regex@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" + integrity sha1-AMHNsaynERLN8M9hJsLta0V8zbw= dependencies: babel-helper-regex "^6.24.1" babel-runtime "^6.22.0" @@ -869,18 +1948,21 @@ babel-plugin-transform-es2015-sticky-regex@^6.22.0: babel-plugin-transform-es2015-template-literals@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" + integrity sha1-qEs0UPfp+PH2g51taH2oS7EjbY0= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-typeof-symbol@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" + integrity sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I= dependencies: babel-runtime "^6.22.0" babel-plugin-transform-es2015-unicode-regex@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" + integrity sha1-04sS9C6nMj9yk4fxinxa4frrNek= dependencies: babel-helper-regex "^6.24.1" babel-runtime "^6.22.0" @@ -889,6 +1971,7 @@ babel-plugin-transform-es2015-unicode-regex@^6.22.0: babel-plugin-transform-exponentiation-operator@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" + integrity sha1-KrDJx/MJj6SJB3cruBP+QejeOg4= dependencies: babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" babel-plugin-syntax-exponentiation-operator "^6.8.0" @@ -897,13 +1980,15 @@ babel-plugin-transform-exponentiation-operator@^6.22.0: babel-plugin-transform-flow-strip-types@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" + integrity sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988= dependencies: babel-plugin-syntax-flow "^6.18.0" babel-runtime "^6.22.0" -babel-plugin-transform-object-rest-spread@^6.20.2, babel-plugin-transform-object-rest-spread@^6.23.0: +babel-plugin-transform-object-rest-spread@^6.23.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" + integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY= dependencies: babel-plugin-syntax-object-rest-spread "^6.8.0" babel-runtime "^6.26.0" @@ -911,6 +1996,7 @@ babel-plugin-transform-object-rest-spread@^6.20.2, babel-plugin-transform-object babel-plugin-transform-react-jsx@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3" + integrity sha1-hAoCjn30YN/DotKfDA2R9jduZqM= dependencies: babel-helper-builder-react-jsx "^6.24.1" babel-plugin-syntax-jsx "^6.8.0" @@ -919,46 +2005,47 @@ babel-plugin-transform-react-jsx@^6.24.1: babel-plugin-transform-regenerator@^6.22.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" + integrity sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8= dependencies: regenerator-transform "^0.10.0" -babel-plugin-transform-runtime@^6.15.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz#88490d446502ea9b8e7efb0fe09ec4d99479b1ee" - dependencies: - babel-runtime "^6.22.0" - babel-plugin-transform-strict-mode@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + integrity sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g= dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-polyfill@^6.23.0: +babel-polyfill@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" + integrity sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM= dependencies: babel-runtime "^6.26.0" core-js "^2.5.0" regenerator-runtime "^0.10.5" -babel-preset-backpack@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/babel-preset-backpack/-/babel-preset-backpack-0.4.3.tgz#781869c0242e3ea12bd2fe1eab4f45a934aa23e9" - dependencies: - babel-plugin-transform-class-properties "^6.19.0" - babel-plugin-transform-es2015-modules-commonjs "^6.18.0" - babel-plugin-transform-es2015-parameters "^6.21.0" - babel-plugin-transform-object-rest-spread "^6.20.2" - babel-plugin-transform-regenerator "^6.22.0" - babel-plugin-transform-runtime "^6.15.0" - babel-preset-env "^1.1.4" - babel-runtime "^6.22.0" - -babel-preset-env@^1.1.4, babel-preset-env@^1.5.2: - version "1.6.1" - resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.1.tgz#a18b564cc9b9afdf4aae57ae3c1b0d99188e6f48" +babel-preset-backpack@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/babel-preset-backpack/-/babel-preset-backpack-0.8.2.tgz#c27197249a78e28c9ce7513e15508bd54f593bcf" + integrity sha512-wJEOPz8eW7qnmanOEZsOM8fIl6AEnApdh4XO3flp1Gu4c8/M5i7Ay7Oz2W4tlKCHPtKbyvN9R0Jvs5ByMCrZCA== + dependencies: + "@babel/core" "^7.0.0" + "@babel/plugin-proposal-class-properties" "^7.0.0" + "@babel/plugin-proposal-object-rest-spread" "^7.0.0" + "@babel/plugin-transform-modules-commonjs" "^7.0.0" + "@babel/plugin-transform-parameters" "^7.0.0" + "@babel/plugin-transform-regenerator" "^7.0.0" + "@babel/plugin-transform-runtime" "^7.0.0" + "@babel/preset-env" "^7.0.0" + "@babel/runtime" "^7.0.0" + babel-loader v8.0.4 + +babel-preset-env@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a" + integrity sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg== dependencies: babel-plugin-check-es2015-constants "^6.22.0" babel-plugin-syntax-trailing-function-commas "^6.22.0" @@ -987,13 +2074,14 @@ babel-preset-env@^1.1.4, babel-preset-env@^1.5.2: babel-plugin-transform-es2015-unicode-regex "^6.22.0" babel-plugin-transform-exponentiation-operator "^6.22.0" babel-plugin-transform-regenerator "^6.22.0" - browserslist "^2.1.2" + browserslist "^3.2.6" invariant "^2.2.2" semver "^5.3.0" babel-preset-jest@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-21.2.0.tgz#ff9d2bce08abd98e8a36d9a8a5189b9173b85638" + integrity sha512-hm9cBnr2h3J7yXoTtAVV0zg+3vg0Q/gT2GYuzlreTU0EPkJRtlNgKJJ3tBKEn0+VjAi3JykV6xCJkuUYttEEfA== dependencies: babel-plugin-jest-hoist "^21.2.0" babel-plugin-syntax-object-rest-spread "^6.13.0" @@ -1001,6 +2089,7 @@ babel-preset-jest@^21.2.0: babel-register@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" + integrity sha1-btAhFz4vy0htestFxgCahW9kcHE= dependencies: babel-core "^6.26.0" babel-runtime "^6.26.0" @@ -1013,6 +2102,7 @@ babel-register@^6.26.0: babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= dependencies: core-js "^2.4.0" regenerator-runtime "^0.11.0" @@ -1020,6 +2110,7 @@ babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= dependencies: babel-runtime "^6.26.0" babel-traverse "^6.26.0" @@ -1030,6 +2121,7 @@ babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= dependencies: babel-code-frame "^6.26.0" babel-messages "^6.23.0" @@ -1044,6 +2136,7 @@ babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= dependencies: babel-runtime "^6.26.0" esutils "^2.0.2" @@ -1053,41 +2146,44 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26 babylon@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== backo2@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= -backpack-core@^0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/backpack-core/-/backpack-core-0.4.3.tgz#587234309db49706d362aa0f2801c7234e3a5611" +backpack-core@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/backpack-core/-/backpack-core-0.8.3.tgz#7c92458d29c77987fcb278ee83b23b55624ec2af" + integrity sha512-d94a47CT1+/Ozb9L7wICoenQF4tyaSOxqPrKxFmk33TW67oKHrA6Ye/1ZJjBUVYwjfNCae6iO2xqyrjRzzQAcA== dependencies: - babel-core "^6.25.0" - babel-loader "^7.1.0" - babel-preset-backpack "^0.4.3" + "@babel/core" "^7.1.2" + babel-loader "^8.0.2" + babel-preset-backpack "^0.8.2" cross-spawn "^5.0.1" - friendly-errors-webpack-plugin "^1.6.1" - nodemon "^1.11.0" + friendly-errors-webpack-plugin "^1.7.0" + json-loader "^0.5.7" + nodemon "^1.18.7" ramda "^0.23.0" source-map-support "^0.4.15" - webpack "^3.0.0" - webpack-node-externals "^1.6.0" + webpack "^4.23.1" + webpack-node-externals "^1.7.2" 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= base64-js@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" - -base64url@2.0.0, base64url@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" + version "1.3.0" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" + integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== dependencies: cache-base "^1.0.1" class-utils "^0.3.5" @@ -1098,98 +2194,85 @@ base@^0.11.1: pascalcase "^0.1.1" bcrypt-pbkdf@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + 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" +before-after-hook@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.2.0.tgz#1079c10312cd4d4ad0d1676d37951ef8bfc3a563" + integrity sha512-wI3QtdLppHNkmM1VgRVLCrlWCKk/YexlPicYbXPs4eYdd1InrUCTFsx5bX1iUQzzMsoRXXPpM1r+p7JEJJydag== + +big-integer@^1.6.17: + version "1.6.40" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.40.tgz#02e4cd4d6e266c4d9ece2469c05cb6439149fc78" + integrity sha512-CjhtJp0BViLzP1ZkEnoywjgtFQXS2pomKjAJtIISTCnuHILkLcAXLdFLG/nxsHc4s9kJfc+82Xpg8WNyhfACzQ== + big.js@^3.1.3: version "3.2.0" resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" + integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== + +bignumber.js@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-1.1.1.tgz#1a415d9ac014c13256af1feed9d1a3e5717a8cf7" + integrity sha1-GkFdmsAUwTJWrx/u2dGj5XF6jPc= binary-extensions@^1.0.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" + version "1.12.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" + integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg== -"binary@>= 0.3.0 < 1": +binary@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= dependencies: buffers "~0.1.1" chainsaw "~0.1.0" -block-stream@*: - version "0.0.9" - resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" - dependencies: - inherits "~2.0.0" - -"bluebird@>= 2.3.2 < 3", bluebird@^2.10.2: +"bluebird@>= 2.3.2 < 3": version "2.11.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" + integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE= -"bluebird@>= 3.0.1", bluebird@^3.3.4, bluebird@^3.5.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" +"bluebird@>= 3.0.1", bluebird@^3.3.4, bluebird@^3.5.1: + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -body-parser@1.18.2, body-parser@^1.17.1: - version "1.18.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" +body-parser@1.18.3, body-parser@^1.18.3: + version "1.18.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" + integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= dependencies: bytes "3.0.0" content-type "~1.0.4" debug "2.6.9" - depd "~1.1.1" - http-errors "~1.6.2" - iconv-lite "0.4.19" + depd "~1.1.2" + http-errors "~1.6.3" + iconv-lite "0.4.23" on-finished "~2.3.0" - qs "6.5.1" - raw-body "2.3.2" - type-is "~1.6.15" - -boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - -boom@2.x.x: - version "2.10.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" - dependencies: - hoek "2.x.x" - -boom@4.x.x: - version "4.3.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" - dependencies: - hoek "4.x.x" - -boom@5.x.x: - version "5.2.0" - resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" - dependencies: - hoek "4.x.x" - -boxen@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-0.6.0.tgz#8364d4248ac34ff0ef1b2f2bf49a6c60ce0d81b6" - dependencies: - ansi-align "^1.1.0" - camelcase "^2.1.0" - chalk "^1.1.1" - cli-boxes "^1.0.0" - filled-array "^1.0.0" - object-assign "^4.0.1" - repeating "^2.0.0" - string-width "^1.0.1" - widest-line "^1.0.0" + qs "6.5.2" + raw-body "2.3.3" + type-is "~1.6.16" boxen@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" + integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== dependencies: ansi-align "^2.0.0" camelcase "^4.0.0" @@ -1200,8 +2283,9 @@ boxen@^1.2.1: widest-line "^2.0.0" brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + 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" @@ -1209,18 +2293,19 @@ brace-expansion@^1.1.7: braces@^1.8.2: version "1.8.5" resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= dependencies: expand-range "^1.8.1" preserve "^0.2.0" repeat-element "^1.1.2" -braces@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.0.tgz#a46941cb5fb492156b3d6a656e06c35364e3e66e" +braces@^2.3.0, braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== dependencies: arr-flatten "^1.1.0" array-unique "^0.3.2" - define-property "^1.0.0" extend-shallow "^2.0.1" fill-range "^4.0.0" isobject "^3.0.1" @@ -1233,20 +2318,24 @@ braces@^2.3.0: brackets2dots@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brackets2dots/-/brackets2dots-1.1.0.tgz#3f3d40375fc660ce0fd004fa27d67b34f9469ac3" + integrity sha1-Pz1AN1/GYM4P0AT6J9Z7NPlGmsM= brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= browser-resolve@^1.11.2: - version "1.11.2" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" + integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== dependencies: resolve "1.1.7" browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f" + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== dependencies: buffer-xor "^1.0.3" cipher-base "^1.0.0" @@ -1256,24 +2345,28 @@ browserify-aes@^1.0.0, browserify-aes@^1.0.4: safe-buffer "^5.0.1" browserify-cipher@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a" + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== dependencies: browserify-aes "^1.0.4" browserify-des "^1.0.0" evp_bytestokey "^1.0.0" browserify-des@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd" + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== dependencies: cipher-base "^1.0.1" des.js "^1.0.0" inherits "^2.0.1" + safe-buffer "^5.1.2" browserify-rsa@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= dependencies: bn.js "^4.1.0" randombytes "^2.0.1" @@ -1281,6 +2374,7 @@ browserify-rsa@^4.0.0: browserify-sign@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= dependencies: bn.js "^4.1.1" browserify-rsa "^4.0.0" @@ -1293,41 +2387,77 @@ browserify-sign@^4.0.0: browserify-zlib@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== dependencies: pako "~1.0.5" -browserslist@^2.1.2: - version "2.11.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.11.3.tgz#fe36167aed1bbcde4827ebfe71347a2cc70b99b2" +browserslist@^3.2.6: + version "3.2.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" + integrity sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ== dependencies: - caniuse-lite "^1.0.30000792" - electron-to-chromium "^1.3.30" + caniuse-lite "^1.0.30000844" + electron-to-chromium "^1.3.47" + +browserslist@^4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.3.5.tgz#1a917678acc07b55606748ea1adf9846ea8920f7" + integrity sha512-z9ZhGc3d9e/sJ9dIx5NFXkKoaiQTnrvrMsN3R1fGb1tkWWNSz12UewJn9TNxGo1l7J23h0MRaPmk7jfeTZYs1w== + dependencies: + caniuse-lite "^1.0.30000912" + electron-to-chromium "^1.3.86" + node-releases "^1.0.5" bser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" + integrity sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk= dependencies: node-int64 "^0.4.0" +btoa-lite@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" + integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= + buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + +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== + +buffer-indexof-polyfill@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.1.tgz#a9fb806ce8145d5428510ce72f278bb363a638bf" + integrity sha1-qfuAbOgUXVQoUQznLyeLs2OmOL8= + +buffer-shims@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" + integrity sha1-mXjOMXOIxkmth5MCjDR37wRKi1E= buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= buffer@4.9.1, buffer@^4.3.0: version "4.9.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.0.3: - version "5.0.8" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.0.8.tgz#84daa52e7cf2fa8ce4195bc5cf0f7809e0930b24" +buffer@^5.0.3, buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" + integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg== dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" @@ -1335,47 +2465,70 @@ buffer@^5.0.3: buffers@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -bull@3.3.10: - version "3.3.10" - resolved "https://registry.yarnpkg.com/bull/-/bull-3.3.10.tgz#32e76281902070b4720bc37b3793f09db635f646" +bull@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/bull/-/bull-3.6.0.tgz#9d137a4470d9f5a0df54801ca4390656e5054a42" + integrity sha512-705Vf3weiRr8D49/+lsPSxV/1NejhjfmVviv9qG2srIYPr7IS2euLwHa+2GNfaVDA2tmx8xyJFW9bPw3fPfHPg== dependencies: - bluebird "^3.5.0" - cron-parser "^2.4.1" + cron-parser "^2.7.3" debuglog "^1.0.0" - ioredis "^3.1.4" - lodash "^4.17.4" - semver "^5.4.1" - uuid "^3.1.0" + ioredis "^4.5.1" + lodash "^4.17.11" + p-timeout "^2.0.1" + promise.prototype.finally "^3.1.0" + semver "^5.6.0" + util.promisify "^1.0.0" + uuid "^3.2.1" busboy@^0.2.14: version "0.2.14" resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" + integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM= dependencies: dicer "0.2.5" readable-stream "1.1.x" -bytebuffer@~5: - version "5.0.1" - resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd" - dependencies: - long "~3" - bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +cacache@^11.0.2: + version "11.3.1" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.1.tgz#d09d25f6c4aca7a6d305d141ae332613aa1d515f" + integrity sha512-2PEw4cRRDu+iQvBTTuttQifacYjLPhET+SYO/gEFMy8uhi+jlJREDAjSF5FWSdV/Aw5h18caHA7vMTw2c+wDzA== + dependencies: + bluebird "^3.5.1" + chownr "^1.0.1" + figgy-pudding "^3.1.0" + glob "^7.1.2" + graceful-fs "^4.1.11" + lru-cache "^4.1.3" + mississippi "^3.0.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.2" + ssri "^6.0.0" + unique-filename "^1.1.0" + y18n "^4.0.0" cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== dependencies: collection-visit "^1.0.0" component-emitter "^1.2.1" @@ -1390,69 +2543,77 @@ cache-base@^1.0.1: callsites@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= camelcase-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= dependencies: camelcase "^2.0.0" map-obj "^1.0.0" -camelcase@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" - -camelcase@^2.0.0, camelcase@^2.1.0: +camelcase@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= + +camelize@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" + integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= + +caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000912: + version "1.0.30000918" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000918.tgz#6288f79da3c5c8b45e502f47ad8f3eb91f1379a9" + integrity sha512-CAZ9QXGViBvhHnmIHhsTPSWFBujDaelKnUj7wwImbyQRxmXynYqKGi3UaZTSz9MoVh+1EVxOS/DFIkrJYgR3aw== -caniuse-lite@^1.0.30000792: - version "1.0.30000792" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000792.tgz#d0cea981f8118f3961471afbb43c9a1e5bbf0332" +capture-exit@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-1.2.0.tgz#1c5fcc489fd0ab00d4f1ac7ae1072e3173fbab6f" + integrity sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28= + dependencies: + rsvp "^3.3.3" capture-stack-trace@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" + version "1.0.1" + resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" + integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= casual@^1.5.12: version "1.5.19" resolved "https://registry.yarnpkg.com/casual/-/casual-1.5.19.tgz#66fac46f7ae463f468f5913eb139f9c41c58bbf2" + integrity sha512-zMWzIs7y4grU0mkb6ZAWyHVJFHRZ2V/zi9GTrVv9v9PcS4Ur3AHaKocl4DLjhLR0J5LnMhMsMiAjGooqYo2t1Q== dependencies: mersenne-twister "^1.0.1" moment "^2.15.2" -center-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" - dependencies: - align-text "^0.1.3" - lazy-cache "^1.0.3" - -chain-function@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc" - chainsaw@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= dependencies: traverse ">=0.3.0 <0.4" -chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: +chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= dependencies: ansi-styles "^2.2.1" escape-string-regexp "^1.0.2" @@ -1460,61 +2621,29 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.1, chalk@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" + integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== dependencies: - ansi-styles "^3.1.0" + ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" - supports-color "^4.0.0" + supports-color "^5.3.0" change-emitter@^0.1.2: version "0.1.6" resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" + integrity sha1-6LL+PX8at9aaMhma/5HqaTFAlRU= charenc@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= -cheerio@^0.22.0: - version "0.22.0" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" - dependencies: - css-select "~1.2.0" - dom-serializer "~0.1.0" - entities "~1.1.1" - htmlparser2 "^3.9.1" - lodash.assignin "^4.0.9" - lodash.bind "^4.1.4" - lodash.defaults "^4.0.1" - lodash.filter "^4.4.0" - lodash.flatten "^4.2.0" - lodash.foreach "^4.3.0" - lodash.map "^4.4.0" - lodash.merge "^4.4.0" - lodash.pick "^4.2.1" - lodash.reduce "^4.4.0" - lodash.reject "^4.4.0" - lodash.some "^4.4.0" - -chokidar@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" - dependencies: - anymatch "^1.3.0" - async-each "^1.0.0" - glob-parent "^2.0.0" - inherits "^2.0.1" - is-binary-path "^1.0.0" - is-glob "^2.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.0.0" - optionalDependencies: - fsevents "^1.0.0" - -chokidar@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.0.tgz#6686313c541d3274b2a5c01233342037948c911b" +chokidar@^2.0.2, chokidar@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" + integrity sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ== dependencies: anymatch "^2.0.0" async-each "^1.0.0" @@ -1523,19 +2652,35 @@ chokidar@^2.0.0: inherits "^2.0.1" is-binary-path "^1.0.0" is-glob "^4.0.0" + lodash.debounce "^4.0.8" normalize-path "^2.1.1" path-is-absolute "^1.0.0" readdirp "^2.0.0" + upath "^1.0.5" optionalDependencies: - fsevents "^1.0.0" + fsevents "^1.2.2" -ci-info@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4" +chownr@^1.0.1, chownr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" + integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== + +chrome-trace-event@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz#45a91bd2c20c9411f0963b5aaeb9a1b95e09cc48" + integrity sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A== + dependencies: + tslib "^1.9.0" + +ci-info@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" + integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" @@ -1543,142 +2688,146 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== dependencies: arr-union "^3.1.0" define-property "^0.2.5" isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" - cli-boxes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" + integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= -clipboard@^1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-1.7.1.tgz#360d6d6946e99a7a1fef395e42ba92b5e9b5a16b" +clipboard@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d" + integrity sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ== dependencies: good-listener "^1.2.2" select "^1.1.2" tiny-emitter "^2.0.0" -cliui@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" - dependencies: - center-align "^0.1.1" - right-align "^0.1.1" - wordwrap "0.0.2" - cliui@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= dependencies: string-width "^1.0.1" strip-ansi "^3.0.1" wrap-ansi "^2.0.0" -cluster-key-slot@^1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.8.tgz#7654556085a65330932a2e8b5976f8e2d0b3e414" +cliui@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" + integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== + dependencies: + string-width "^2.1.1" + strip-ansi "^4.0.0" + wrap-ansi "^2.0.0" -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" +cluster-key-slot@^1.0.6: + version "1.0.12" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.12.tgz#d5deff2a520717bc98313979b687309b2d368e29" + integrity sha512-21O0kGmvED5OJ7ZTdqQ5lQQ+sjuez33R+d35jZKLwqUb5mqcPHUsxOSzj61+LHVtxGZd1kShbQM3MjB/gBJkVg== code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= dependencies: map-visit "^1.0.0" object-visit "^1.0.0" color-convert@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" + 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.1" + color-name "1.1.3" -color-name@^1.1.1: +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= colors@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + version "1.3.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" + integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== -combined-stream@^1.0.5, combined-stream@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" + integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== dependencies: delayed-stream "~1.0.0" commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" + integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA== + +commander@~2.17.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" + integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= -compressible@~2.0.11: - version "2.0.12" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66" +compressible@~2.0.14: + version "2.0.15" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.15.tgz#857a9ab0a7e5a07d8d837ed43fe2defff64fe212" + integrity sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw== dependencies: - mime-db ">= 1.30.0 < 2" + mime-db ">= 1.36.0 < 2" -compression@^1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.1.tgz#eff2603efc2e22cf86f35d2eb93589f9875373db" +compression@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.3.tgz#27e0e176aaf260f7f2c2813c3e440adb9f1993db" + integrity sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg== dependencies: - accepts "~1.3.4" + accepts "~1.3.5" bytes "3.0.0" - compressible "~2.0.11" + compressible "~2.0.14" debug "2.6.9" on-headers "~1.0.1" - safe-buffer "5.1.1" + safe-buffer "5.1.2" vary "~1.1.2" 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= -concat-stream@^1.4.7: - version "1.6.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" +concat-stream@^1.4.7, concat-stream@^1.5.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== dependencies: + buffer-from "^1.0.0" inherits "^2.0.3" readable-stream "^2.2.2" typedarray "^0.0.6" -configstore@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1" - dependencies: - dot-prop "^3.0.0" - graceful-fs "^4.1.2" - mkdirp "^0.5.0" - object-assign "^4.0.1" - os-tmpdir "^1.0.0" - osenv "^0.1.0" - uuid "^2.0.1" - write-file-atomic "^1.1.2" - xdg-basedir "^2.0.0" - configstore@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.1.tgz#094ee662ab83fad9917678de114faaea8fcdca90" + version "3.1.2" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" + integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw== dependencies: dot-prop "^4.1.0" graceful-fs "^4.1.2" @@ -1690,36 +2839,51 @@ configstore@^3.0.0: console-browserify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" + integrity sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA= dependencies: date-now "^0.1.4" console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= content-disposition@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= + +content-security-policy-builder@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/content-security-policy-builder/-/content-security-policy-builder-2.0.0.tgz#8749a1d542fcbe82237281ea9f716ce68b394dd2" + integrity sha512-j+Nhmj1yfZAikJLImCvPJFE29x/UuBi+/MWqggGGc515JKaZrjuei2RhULJmy0MsstW3E3htl002bwmBNMKr7w== content-type-parser@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7" + integrity sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ== 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== -convert-source-map@^1.4.0, convert-source-map@^1.5.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" +convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" + integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== + dependencies: + safe-buffer "~5.1.1" cookie-parser@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.3.tgz#0fe31fa19d000b95f4aadf1f53fdc2b8a203baa5" + integrity sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU= dependencies: cookie "0.3.1" cookie-signature "1.0.6" @@ -1727,6 +2891,7 @@ cookie-parser@^1.4.3: cookie-session@^2.0.0-beta.3: version "2.0.0-beta.3" resolved "https://registry.yarnpkg.com/cookie-session/-/cookie-session-2.0.0-beta.3.tgz#4e446bd9f85bd7e27d3e226f4e99af12011a4386" + integrity sha512-zyqm5tA0z9yMEB/xyP7lnRnqp8eLR2e0dap+9+rBwVigla9yPKn8XTL1jJymog8xjfrowqW2o5LUjixQChkqrw== dependencies: cookies "0.7.1" debug "3.1.0" @@ -1736,37 +2901,62 @@ cookie-session@^2.0.0-beta.3: cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= cookies@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.7.1.tgz#7c8a615f5481c61ab9f16c833731bcb8f663b99b" + integrity sha1-fIphX1SBxhq58WyDNzG8uPZjuZs= dependencies: depd "~1.1.1" keygrip "~1.0.2" +copy-concurrently@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" + integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== + dependencies: + aproba "^1.1.1" + fs-write-stream-atomic "^1.0.8" + iferr "^0.1.5" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.0" + copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-js@^1.0.0: version "1.2.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.1, core-js@^2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" +core-js@^2.4.0, core-js@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.0.tgz#1e30793e9ee5782b307e37ffa22da0eacddd84d4" + integrity sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw== + +core-js@^3.0.0-beta.3: + version "3.0.0-beta.4" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.0-beta.4.tgz#7443c32990d21198d23de18acb061a5e5bc9f549" + integrity sha512-yz4iJCkkSQLQSLHPGUln6r5ZBkLPzZSvHG0g1nfvcdnmpIe+KE9WOb1ZEEf6EEaEmjp9Ol0Kw5J5vnoIWc5eWw== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cors@^2.8.3: - version "2.8.4" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.4.tgz#2bd381f2eb201020105cd50ea59da63090694686" +cors@^2.8.4, cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== dependencies: object-assign "^4" vary "^1" @@ -1774,32 +2964,45 @@ cors@^2.8.3: crc@3.4.4: version "3.4.4" resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b" + integrity sha1-naHpgOO9RPxck79as9ozeNheRms= + +crc@^3.5.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" + integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== + dependencies: + buffer "^5.1.0" create-ecdh@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d" + version "4.0.3" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" + integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== dependencies: bn.js "^4.1.0" elliptic "^6.0.0" -create-error-class@^3.0.0, create-error-class@^3.0.1: +create-error-class@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" + integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= dependencies: capture-stack-trace "^1.0.0" create-hash@^1.1.0, create-hash@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== dependencies: cipher-base "^1.0.1" inherits "^2.0.1" - ripemd160 "^2.0.0" + md5.js "^1.3.4" + ripemd160 "^2.0.1" sha.js "^2.4.0" create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.6" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06" + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== dependencies: cipher-base "^1.0.3" create-hash "^1.1.0" @@ -1808,48 +3011,52 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -create-react-class@^15.5.2, create-react-class@^15.6.0: - version "15.6.2" - resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.2.tgz#cf1ed15f12aad7f14ef5f2dfe05e6c42f91ef02a" +create-react-class@^15.6.0: + version "15.6.3" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" + integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg== dependencies: fbjs "^0.8.9" loose-envify "^1.3.1" object-assign "^4.1.1" -cron-parser@^2.4.1: - version "2.4.3" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.4.3.tgz#cae844c20117fc72c678f63ac83c7884be199e78" +cron-parser@^2.7.3: + version "2.7.3" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.7.3.tgz#12603f89f5375af353a9357be2543d3172eac651" + integrity sha512-t9Kc7HWBWPndBzvbdQ1YG9rpPRB37Tb/tTviziUOh1qs3TARGh3b1p+tnkOHNe1K5iI3oheBPgLqwotMM7+lpg== dependencies: is-nan "^1.2.1" - moment-timezone "^0.5.0" + moment-timezone "^0.5.23" cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= dependencies: lru-cache "^4.0.1" shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^6.0.0: + 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" + crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - -cryptiles@2.x.x: - version "2.0.5" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" - dependencies: - boom "2.x.x" - -cryptiles@3.x.x: - version "3.1.2" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" - dependencies: - boom "5.x.x" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== dependencies: browserify-cipher "^1.0.0" browserify-sign "^4.0.0" @@ -1866,198 +3073,264 @@ crypto-browserify@^3.11.0: crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" + integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= + +cryptr@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cryptr/-/cryptr-3.0.0.tgz#4cdb1ac8b0b292c6ac1dcdf503bb27a05e4eba10" + integrity sha512-ojvQNR6fiYVPVeRJbihzuHdJ4lU4zWa/A2heJuW54R8U51DnRBrMD7KA7gET9yPrTXkcKjntflx/wW5nohKRPQ== css-color-keywords@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" - -css-select@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" + integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU= css-to-react-native@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.0.4.tgz#cf4cc407558b3474d4ba8be1a2cd3b6ce713101b" + version "2.2.2" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.2.2.tgz#c077d0f7bf3e6c915a539e7325821c9dd01f9965" + integrity sha512-w99Fzop1FO8XKm0VpbQp3y5mnTnaS+rtCvS+ylSEOK76YXO5zoHQx/QMB1N54Cp+Ya9jB9922EHrh14ld4xmmw== dependencies: css-color-keywords "^1.0.0" fbjs "^0.8.5" postcss-value-parser "^3.3.0" -css-what@2.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" - cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": - version "0.3.2" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" + version "0.3.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797" + integrity sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog== "cssstyle@>= 0.2.37 < 0.3.0": version "0.2.37" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" + integrity sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ= dependencies: cssom "0.3.x" currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= dependencies: array-find-index "^1.0.1" curry2@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/curry2/-/curry2-1.0.3.tgz#38191d55f1060bfea47ca08009385bb878f6612f" + integrity sha1-OBkdVfEGC/6kfKCACThbuHj2YS8= dependencies: fast-bind "^1.0.0" -d@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" - dependencies: - es5-ext "^0.10.9" +cyclist@~0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" + integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= 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" -dataloader@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.3.0.tgz#6fec5be4b30a712e4afd30b86b4334566b97673b" +dasherize@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308" + integrity sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg= + +datadog-metrics@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/datadog-metrics/-/datadog-metrics-0.8.1.tgz#be87237109a7084193c668d80112533ef00e3f21" + integrity sha512-qTSKnddO6GxTJW9FYpmWjvvift3qfyMurDjwNjJnJhBk76pBdDhC0B5V9V+XwPdn4r42qu48kwXNuHJslXlDOA== + dependencies: + debug "3.1.0" + dogapi "1.1.0" + +dataloader@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8" + integrity sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw== date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" + integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= -debounce@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.1.0.tgz#6a1a4ee2a9dc4b7c24bb012558dbcdb05b37f408" +debounce@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" + integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg== -debug@2.6.9, debug@^2.2.0, debug@^2.3.2, debug@^2.3.3, debug@^2.5.2, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.5.2, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@3.1.0, debug@^3.1.0: +debug@3.1.0, 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@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" + integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg== + dependencies: + ms "^2.1.1" + +debug@^4.1.1: + 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" + debuglog@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= -decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: +decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= decompress-response@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= dependencies: mimic-response "^1.0.0" decorate-component-with-props@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/decorate-component-with-props/-/decorate-component-with-props-1.1.0.tgz#b496c814c6a2aba0cf2ad26e44cbedb8ead42f15" + integrity sha512-tTYQojixN64yK3/WBODMfvss/zbmyUx9HQXhzSxZiSiofeekVeRyyuToy9BCiTMrVEIKWxTcla2t3y5qdaUF7Q== deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= -deep-extend@^0.4.0, deep-extend@~0.4.0: +deep-extend@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" + integrity sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8= + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== deep-for-each@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/deep-for-each/-/deep-for-each-1.0.6.tgz#afa0ce249c58492a9720539478a18d37e1b10bae" + integrity sha1-r6DOJJxYSSqXIFOUeKGNN+GxC64= dependencies: is-plain-object "^2.0.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-require-extensions@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" + integrity sha1-836hXT4T/9m0N9M+GnW1+5eHTLg= dependencies: strip-bom "^2.0.0" -define-properties@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" +define-properties@^1.1.1, define-properties@^1.1.2: + 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: - foreach "^2.0.5" - object-keys "^1.0.8" + object-keys "^1.0.12" define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= dependencies: is-descriptor "^0.1.0" define-property@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= dependencies: is-descriptor "^1.0.0" -del@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +del@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" + integrity sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU= dependencies: - globby "^5.0.0" + globby "^6.1.0" is-path-cwd "^1.0.0" is-path-in-cwd "^1.0.0" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" + p-map "^1.1.1" + pify "^3.0.0" rimraf "^2.2.8" 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= delegate@^3.1.2: version "3.2.0" resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= denque@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/denque/-/denque-1.2.2.tgz#e06cf7cf0da8badc88cbdaabf8fc0a70d659f1d4" - -depd@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" + version "1.4.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.0.tgz#79e2f0490195502107f24d9553f374837dabc916" + integrity sha512-gh513ac7aiKrAgjiIBWZG0EASyDF9p4JMWwKA8YU5s9figrL5SRNEMT6FDynsegakuhWd1wVqTvqvqAoDxw7wQ== -depd@~1.1.1: +depd@~1.1.1, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= deprecated-decorator@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37" + integrity sha1-AJZjF7ehL+kvPMgx91g68ym4bDc= des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" + integrity sha1-wHTS4qpqipoH29YfmhXCzYPsjsw= dependencies: inherits "^2.0.1" minimalistic-assert "^1.0.0" @@ -2065,35 +3338,42 @@ des.js@^1.0.0: destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= detect-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= dependencies: repeating "^2.0.0" detect-libc@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= detect-newline@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= dicer@0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" + integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8= dependencies: readable-stream "1.1.x" streamsearch "0.1.2" diff@^3.2.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== diffie-hellman@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e" + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== dependencies: bn.js "^4.1.0" miller-rabin "^4.0.0" @@ -2102,89 +3382,80 @@ diffie-hellman@^5.0.0: direction@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/direction/-/direction-0.1.5.tgz#ce5d797f97e26f8be7beff53f7dc40e1c1a9ec4c" + integrity sha1-zl15f5fib4vnvv9T99xA4cGp7Ew= -dom-helpers@^3.2.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6" - -dom-serializer@0, dom-serializer@~0.1.0: +dns-prefetch-control@0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" + resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2" + integrity sha1-YN20V3dOF48flBXwyrsOhbCzALI= + +dogapi@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/dogapi/-/dogapi-1.1.0.tgz#71a43865ad4bb4cb18bc3e13cf769971f501030a" + integrity sha1-caQ4Za1LtMsYvD4Tz3aZcfUBAwo= + dependencies: + extend "^3.0.0" + json-bigint "^0.1.4" + minimist "^1.1.1" + rc "^1.0.0" + +dom-helpers@^3.3.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" + integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== dependencies: - domelementtype "~1.1.1" - entities "~1.1.1" + "@babel/runtime" "^7.1.2" dom-urls@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/dom-urls/-/dom-urls-1.1.0.tgz#001ddf81628cd1e706125c7176f53ccec55d918e" + integrity sha1-AB3fgWKM0ecGElxxdvU8zsVdkY4= dependencies: urijs "^1.16.1" dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" + integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg= domain-browser@^1.1.1: - version "1.1.7" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" - -domelementtype@1, domelementtype@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" - -domelementtype@~1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" - -domhandler@^2.3.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" - dependencies: - domelementtype "1" - -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - dependencies: - dom-serializer "0" - domelementtype "1" - -domutils@^1.5.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff" - dependencies: - dom-serializer "0" - domelementtype "1" + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== -dot-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177" - dependencies: - is-obj "^1.0.0" +dont-sniff-mimetype@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz#5932890dc9f4e2f19e5eb02a20026e5e5efc8f58" + integrity sha1-WTKJDcn04vGeXrAqIAJuXl78j1g= dot-prop@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" + integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== dependencies: is-obj "^1.0.0" dotenv@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" + integrity sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0= dotsplit.js@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/dotsplit.js/-/dotsplit.js-1.1.0.tgz#25a239eabe922a91ffa5d2a172d6c9fb82451e02" + integrity sha1-JaI56r6SKpH/pdKhctbJ+4JFHgI= draft-js-checkable-list-item@^2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/draft-js-checkable-list-item/-/draft-js-checkable-list-item-2.0.6.tgz#19dfb99421e07ac1a93736f4a5d04e222de2a647" + integrity sha512-YHnGr3rKSFfqXGcHqp8SGees5Y/KAsHoyknoDRM1Gyal3B+duiFjmYvxIZlSYnWIcHgH4pQpPDQJJ9aaPmsvNw== dependencies: draft-js-modifiers "^0.1.5" draft-js-code-editor-plugin@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/draft-js-code-editor-plugin/-/draft-js-code-editor-plugin-0.2.1.tgz#86dda298fcc950427ee980f26b3c75fa25a0b86e" + integrity sha512-sKF5nYUoXfUA2Viuj1wwvharJKRO49G+oRYghJYJ4OlOCGhKaixSQWOZD/vmCdr2+iWJGDDB5+p5USsef++43w== dependencies: babel-plugin-transform-react-jsx "^6.24.1" draft-js "0.x" @@ -2193,6 +3464,7 @@ draft-js-code-editor-plugin@0.2.1: draft-js-code@0.3.x: version "0.3.0" resolved "https://registry.yarnpkg.com/draft-js-code/-/draft-js-code-0.3.0.tgz#d955d134367baeee5844b57f897c8d848bc979c5" + integrity sha512-e/Rblaj1FvYzvQK45QHt8qAl5lBLFSHWPzNrF8Avv/8Bx0ZtPHD8ykCdazM2NjTvinZmPJ5dLoRHphgFQycasg== dependencies: detect-indent "^4.0.0" detect-newline "^2.1.0" @@ -2202,6 +3474,7 @@ draft-js-code@0.3.x: draft-js-drag-n-drop-plugin@2.0.0-rc9: version "2.0.0-rc9" resolved "https://registry.yarnpkg.com/draft-js-drag-n-drop-plugin/-/draft-js-drag-n-drop-plugin-2.0.0-rc9.tgz#679975d05ce9444bea6a585c560b68c235f0c85f" + integrity sha512-WelV0l6btwrJyUVpkyMGYvaSvRWxAhgkcejbJRYyDWDBh0/bge+AZ0nL/hLTAunlvQ64TSyRrJrcNdufscSHqg== dependencies: decorate-component-with-props "^1.0.2" find-with-regex "^1.0.2" @@ -2212,12 +3485,14 @@ draft-js-drag-n-drop-plugin@2.0.0-rc9: draft-js-embed-plugin@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/draft-js-embed-plugin/-/draft-js-embed-plugin-1.2.0.tgz#cf41c6de58ce58c3662d8cd3d45325733587d3d1" + integrity sha1-z0HG3ljOWMNmLYzT1FMlczWH09E= dependencies: decorate-component-with-props "^1.0.2" draft-js-focus-plugin@2.0.0-rc2: version "2.0.0-rc2" resolved "https://registry.yarnpkg.com/draft-js-focus-plugin/-/draft-js-focus-plugin-2.0.0-rc2.tgz#6b65e0c489a0cedc9cfad414b3313d44168375d9" + integrity sha1-a2XgxImgztyc+tQUszE9RBaDddk= dependencies: decorate-component-with-props "^1.0.2" find-with-regex "^1.0.2" @@ -2228,6 +3503,7 @@ draft-js-focus-plugin@2.0.0-rc2: draft-js-image-plugin@2.0.0-rc8: version "2.0.0-rc8" resolved "https://registry.yarnpkg.com/draft-js-image-plugin/-/draft-js-image-plugin-2.0.0-rc8.tgz#9ccd0d83c84f53dceefcd3c72f944e26ef9e2db5" + integrity sha512-bzoKv7PAkzBe1YTsMVP3bDSP92yAjia3s4Ltx5hB4Y7SmrP8YmV4deEA/ae5VuuxPuKKf+qhEFP4nqLqppRG6A== dependencies: decorate-component-with-props "^1.0.2" find-with-regex "^1.0.2" @@ -2235,23 +3511,26 @@ draft-js-image-plugin@2.0.0-rc8: prop-types "^15.5.8" union-class-names "^1.0.0" -draft-js-import-element@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/draft-js-import-element/-/draft-js-import-element-1.2.1.tgz#9a6a56d74690d48d35d8d089564e6d710b4926eb" +draft-js-import-element@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/draft-js-import-element/-/draft-js-import-element-1.3.0.tgz#64ca3b32e770cc0227f563cfb362b19bd5c60e4e" + integrity sha512-asRZSsMbqzpQ3xlUX9+HMuOn/DR8OyWYVV9wdcnPw7QwVUqb3L4XGj0XbDCbdQte0aX8W1DU4BV0nhC2KcSCEQ== dependencies: - draft-js-utils "^1.2.0" + draft-js-utils "^1.3.0" synthetic-dom "^1.2.0" -draft-js-import-markdown@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/draft-js-import-markdown/-/draft-js-import-markdown-1.2.1.tgz#ec18eb15008bab13d9878d65db50e181dd64a5ce" +draft-js-import-markdown@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/draft-js-import-markdown/-/draft-js-import-markdown-1.3.0.tgz#0f939ecb72c342345e685b37cb43c701c167dc36" + integrity sha512-V8JQflHOGYMmHOlrodY/l7zce00juHJ3uhSeL63x7x26sqKgTa75hdAAo7M1Z6ayuL7/AJkTQHP/NKsR4/vujA== dependencies: - draft-js-import-element "^1.2.1" + draft-js-import-element "^1.3.0" synthetic-dom "^1.2.0" draft-js-linkify-plugin@^2.0.0-beta1: version "2.0.1" resolved "https://registry.yarnpkg.com/draft-js-linkify-plugin/-/draft-js-linkify-plugin-2.0.1.tgz#28978b53640ce64c639cd2821a54c24de9f79c3f" + integrity sha512-dCqlAUCMy+qkqx1dvHgCb8NBDZprcMkPO4Hnpj4Jo0AU4q2J/Uat6zo2Z62NdAZRkftqTRv0arMWqikTGQb1xg== dependencies: decorate-component-with-props "^1.0.2" immutable "~3.7.4" @@ -2260,41 +3539,61 @@ draft-js-linkify-plugin@^2.0.0-beta1: tlds "^1.189.0" union-class-names "^1.0.0" -draft-js-markdown-plugin@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/draft-js-markdown-plugin/-/draft-js-markdown-plugin-1.1.0.tgz#65f9219e0e0954f509f81c9ee2adc01b7a9defa2" +draft-js-markdown-plugin@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/draft-js-markdown-plugin/-/draft-js-markdown-plugin-1.4.4.tgz#d3ca991edbec40aebf70c51c4209aca80eb2fd4b" + integrity sha512-gTHUX7dpS6rje105chLBkJDL0BsxSY5QfceQj4wHReYieuD0IhFBk9Nf1ajeyYwj5iVTlKstjDDuN3ElvbNscQ== dependencies: decorate-component-with-props "^1.0.2" - draft-js "~0.10.1" + draft-js "^0.10.4" draft-js-checkable-list-item "^2.0.5" draft-js-prism-plugin "^0.1.1" immutable "~3.7.4" - jest "^21.1.0" + react-click-outside "^3.0.1" draft-js-modifiers@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/draft-js-modifiers/-/draft-js-modifiers-0.1.5.tgz#5134a6dbf3fa5b76b74dc269e1877b93f29336b0" + integrity sha512-UVbTvlbFSOlJ4LHNK68yflR1k6UyBge0o89DM0/YA8w5PXI+bExMTkByRcwhV5XIWYDgboHWLQSXjao02dW4mQ== dependencies: draft-js "~0.10.0" immutable "~3.7.4" -draft-js-plugins-editor@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/draft-js-plugins-editor/-/draft-js-plugins-editor-2.0.4.tgz#ab7178de886d6d11f9f43a448c844da6266053e0" +draft-js-plugins-editor@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/draft-js-plugins-editor/-/draft-js-plugins-editor-2.1.1.tgz#7c9aedd696737c6ddf45d47c978bd764ef33f688" + integrity sha512-fKGe71irNvFHJ5L/lUrh+3vPkBNq0de6x+cgiZUJ9zQERc5KPBtGXIFiarLFVHyrRTCPq+K6xmgfFSAERaFHPw== dependencies: decorate-component-with-props "^1.0.2" - find-with-regex "^1.0.2" + find-with-regex "^1.1.3" immutable "~3.7.4" prop-types "^15.5.8" union-class-names "^1.0.0" -draft-js-prism-plugin@0.1.1, draft-js-prism-plugin@^0.1.1: +draft-js-prism-plugin@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/draft-js-prism-plugin/-/draft-js-prism-plugin-0.1.1.tgz#c8cdf3841e6e3fdfc766f2131e47a49fb972367e" + integrity sha512-0lvgDB+AGI5M/B0Eh6PLNIJ1PdCEz0EhDG8pZ7rfrnqmtlCVfpXF3Yk7gtTApHL6qQUyxGOQtrerSKTsWTZaIw== dependencies: draft-js-prism ngs/draft-js-prism#6edb31c3805dd1de3fb897cc27fced6bac1bafbb react "*" +draft-js-prism-plugin@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/draft-js-prism-plugin/-/draft-js-prism-plugin-0.1.3.tgz#b33669bae009749d876986c6b19b0d6936f311fd" + integrity sha512-w/agtisitO7SCHBBqa5vfkwy7DlhfKMCak6ivE4GpSV97MYLUIuRtafP1ei5tCMwGkJxJDKMv3Bgrd5miG55ZA== + dependencies: + draft-js-prism "^1.0.6" + react "*" + +draft-js-prism@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/draft-js-prism/-/draft-js-prism-1.0.6.tgz#880c5e5ae1a161646cdacc7296a01bf48b0030b4" + integrity sha512-9iNPPr6/vaC9K60DtVes1JGDZ9uD0vZ7/8i6de5cZEzbftOj/ijIGplEV0dTFT/q8U+bY1uR1ikQevjRh2pEpQ== + dependencies: + extend "^3.0.0" + immutable "*" + draft-js-prism@ngs/draft-js-prism#6edb31c3805dd1de3fb897cc27fced6bac1bafbb: version "1.0.3" resolved "https://codeload.github.com/ngs/draft-js-prism/tar.gz/6edb31c3805dd1de3fb897cc27fced6bac1bafbb" @@ -2303,13 +3602,15 @@ draft-js-prism@ngs/draft-js-prism#6edb31c3805dd1de3fb897cc27fced6bac1bafbb: immutable "*" prismjs "^1.5.0" -draft-js-utils@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/draft-js-utils/-/draft-js-utils-1.2.0.tgz#f5cb23eb167325ffed3d79882fdc317721d2fd12" +draft-js-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/draft-js-utils/-/draft-js-utils-1.3.0.tgz#9102ca34450da3de2097a074c6cc9c92f72a312e" + integrity sha512-wZaY6HQ/jZTh0wshkPzXNrPu0qcE6PuZwIYJp/q8nFAc3elWma7EBtGLXDPkSwvAXEl596mD6GPfk3jnPDuA+w== -draft-js@0.x, draft-js@^0.10.0, draft-js@~0.10.0, draft-js@~0.10.1: - version "0.10.4" - resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.4.tgz#147741642097c8120d8edc232e9503e8b7fb8d35" +draft-js@0.x, draft-js@^0.10.4, draft-js@^0.10.5, draft-js@~0.10.0: + version "0.10.5" + resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.5.tgz#bfa9beb018fe0533dbb08d6675c371a6b08fa742" + integrity sha512-LE6jSCV9nkPhfVX2ggcRLA4FKs6zWq9ceuO/88BpXdNCS7mjRTgs0NsV6piUCJX9YxMsB9An33wnkMmU2sD2Zg== dependencies: fbjs "^0.8.15" immutable "~3.7.4" @@ -2318,59 +3619,64 @@ draft-js@0.x, draft-js@^0.10.0, draft-js@~0.10.0, draft-js@~0.10.1: draftjs-to-markdown@^0.4.2: version "0.4.5" resolved "https://registry.yarnpkg.com/draftjs-to-markdown/-/draftjs-to-markdown-0.4.5.tgz#1cb2da90bf7790ff23e24891441882a40fca6d53" + integrity sha1-HLLakL93kP8j4kiRRBiCpA/KbVM= -duplexer2@^0.1.4: +duplexer2@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= dependencies: readable-stream "^2.0.2" duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= -duplexer@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" +duplexify@^3.4.2, duplexify@^3.6.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.1.tgz#b1a7a29c4abfd639585efaecce80d666b1e34125" + integrity sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" ecc-jsbn@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + 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" -ecdsa-sig-formatter@1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + integrity sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM= dependencies: - base64url "^2.0.0" safe-buffer "^5.0.1" ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= ejs@^2.3.4: - version "2.5.7" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" - -electron-releases@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/electron-releases/-/electron-releases-2.1.0.tgz#c5614bf811f176ce3c836e368a0625782341fd4e" + version "2.6.1" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" + integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ== -electron-to-chromium@^1.3.30: - version "1.3.30" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.30.tgz#9666f532a64586651fc56a72513692e820d06a80" - dependencies: - electron-releases "^2.1.0" - -element-class@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/element-class/-/element-class-0.2.2.tgz#9d3bbd0767f9013ef8e1c8ebe722c1402a60050e" +electron-to-chromium@^1.3.47, electron-to-chromium@^1.3.86: + version "1.3.88" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.88.tgz#f36ab32634f49ef2b0fdc1e82e2d1cc17feb29e7" + integrity sha512-UPV4NuQMKeUh1S0OWRvwg0PI8ASHN9kBC8yDTk1ROXLC85W5GnhTRu/MZu3Teqx3JjlQYuckuHYXSUSgtb3J+A== elliptic@^6.0.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" + version "6.4.1" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a" + integrity sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ== dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -2383,221 +3689,228 @@ elliptic@^6.0.0: emoji-regex@^6.1.1: version "6.5.1" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2" + integrity sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ== emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" + integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= -encodeurl@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= encoding@^0.1.11: version "0.1.12" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= dependencies: iconv-lite "~0.4.13" +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== + dependencies: + once "^1.4.0" + ends-with@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/ends-with/-/ends-with-0.2.0.tgz#2f9da98d57a50cfda4571ce4339000500f4e6b8a" + integrity sha1-L52pjVelDP2kVxzkM5AAUA9Oa4o= -enhanced-resolve@^3.4.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" +enhanced-resolve@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" + integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== dependencies: graceful-fs "^4.1.2" memory-fs "^0.4.0" - object-assign "^4.0.1" - tapable "^0.2.7" - -entities@^1.1.1, entities@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + tapable "^1.0.0" envify@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/envify/-/envify-4.1.0.tgz#f39ad3db9d6801b4e6b478b61028d3f0b6819f7e" + integrity sha512-IKRVVoAYr4pIx4yIWNsz9mOsboxlNXiu7TNBnem/K/uTHdkyzXWDzHCK7UTolqBbgaBz0tQHsD3YNls0uIIjiw== dependencies: esprima "^4.0.0" through "~2.3.4" -errno@^0.1.3, errno@^0.1.4: - version "0.1.6" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.6.tgz#c386ce8a6283f14fc09563b71560908c9bf53026" +errno@^0.1.3, errno@~0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" + integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== dependencies: prr "~1.0.1" error-ex@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" error-stack-parser@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.1.tgz#a3202b8fb03114aa9b40a0e3669e48b2b65a010a" - dependencies: - stackframe "^1.0.3" - -es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: - version "0.10.37" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.37.tgz#0ee741d148b80069ba27d020393756af257defc3" - dependencies: - es6-iterator "~2.0.1" - es6-symbol "~3.1.1" - -es6-iterator@^2.0.1, es6-iterator@~2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-map@^0.1.3, es6-map@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-set "~0.1.5" - es6-symbol "~3.1.1" - event-emitter "~0.3.5" - -es6-promise@^4.0.5, es6-promise@^4.1.0, es6-promise@^4.1.1: - version "4.2.2" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.2.tgz#f722d7769af88bd33bc13ec6605e1f92966b82d9" - -es6-set@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-symbol "3.1.1" - event-emitter "~0.3.5" - -es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + version "2.0.2" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.2.tgz#4ae8dbaa2bf90a8b450707b9149dcabca135520d" + integrity sha512-E1fPutRDdIj/hohG0UpT5mayXNCxXP9d+snxFsPU9X0XgccOumKraa3juDMwTUyi7+Bu5+mCGagjg4IYeNbOdw== + dependencies: + stackframe "^1.0.4" + +es-abstract@^1.5.1: + version "1.12.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" + integrity sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA== + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.1" + has "^1.0.1" + is-callable "^1.1.3" + is-regex "^1.0.4" + +es-abstract@^1.9.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" + integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== + dependencies: + es-to-primitive "^1.2.0" + function-bind "^1.1.1" + has "^1.0.3" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-keys "^1.0.12" + +es-to-primitive@^1.1.1, 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== dependencies: - d "1" - es5-ext "~0.10.14" + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" -es6-weak-map@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" +es6-promise@^4.0.3, es6-promise@^4.0.5, es6-promise@^4.1.0: + version "4.2.5" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054" + integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= dependencies: - d "1" - es5-ext "^0.10.14" - es6-iterator "^2.0.1" - es6-symbol "^3.1.1" + es6-promise "^4.0.3" escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= escape-string-regexp@^1.0.2, 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= escodegen@^1.6.1: - version "1.9.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852" + version "1.11.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589" + integrity sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw== dependencies: esprima "^3.1.3" estraverse "^4.2.0" esutils "^2.0.2" optionator "^0.8.1" optionalDependencies: - source-map "~0.5.6" + source-map "~0.6.1" -escope@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" +eslint-scope@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" + integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA== dependencies: - es6-map "^0.1.3" - es6-weak-map "^2.0.1" esrecurse "^4.1.0" estraverse "^4.1.1" esprima@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= esprima@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esrecurse@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" + 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" - object-assign "^4.0.1" esrever@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/esrever/-/esrever-0.2.0.tgz#96e9d28f4f1b1a76784cd5d490eaae010e7407b8" + integrity sha1-lunSj08bGnZ4TNXUkOquAQ50B7g= estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -event-emitter@~0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - dependencies: - d "1" - es5-ext "~0.10.14" - -event-stream@~3.3.0: - version "3.3.4" - resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" - dependencies: - duplexer "~0.1.1" - from "~0" - map-stream "~0.1.0" - pause-stream "0.0.11" - split "0.3" - stream-combiner "~0.0.4" - through "~2.3.1" - -eventemitter3@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" +eventemitter3@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" + integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== events@^1.0.0, events@^1.1.0, events@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== dependencies: md5.js "^1.3.4" safe-buffer "^5.1.1" exec-sh@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38" + version "0.2.2" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.2.tgz#2a5e7ffcbd7d0ba2755bdecb16e5a427dfbdec36" + integrity sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw== dependencies: - merge "^1.1.3" + merge "^1.2.0" + +execa@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" + integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw== + dependencies: + cross-spawn "^6.0.0" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" execa@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= dependencies: cross-spawn "^5.0.1" get-stream "^3.0.0" @@ -2607,23 +3920,22 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -exenv@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.0.tgz#3835f127abf075bfe082d0aed4484057c78e3c89" - -exenv@^1.2.1: +exenv@^1.2.0, exenv@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s= dependencies: is-posix-bracket "^0.1.0" expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= dependencies: debug "^2.3.3" define-property "^0.2.5" @@ -2636,12 +3948,19 @@ expand-brackets@^2.1.4: expand-range@^1.8.1: version "1.8.2" resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= dependencies: fill-range "^2.1.0" +expect-ct@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/expect-ct/-/expect-ct-0.1.1.tgz#de84476a2dbcb85000d5903737e9bc8a5ba7b897" + integrity sha512-ngXzTfoRGG7fYens3/RMb6yYoVLvLMfmsSllP/mZPxNHgFq41TmPSLF/nLY7fwoclI2vElvAmILFWGUYqdjfCg== + expect@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/expect/-/expect-21.2.1.tgz#003ac2ac7005c3c29e73b38a272d4afadd6d1d7b" + integrity sha512-orfQQqFRTX0jH7znRIGi8ZMR8kTNpXklTTz8+HGTpmTKZo3Occ6JNB5FXMb8cRuiiC/GyDqsr30zUa66ACYlYw== dependencies: ansi-styles "^3.2.0" jest-diff "^21.2.1" @@ -2650,9 +3969,23 @@ expect@^21.2.1: jest-message-util "^21.2.1" jest-regex-util "^21.2.0" +express-enforces-ssl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/express-enforces-ssl/-/express-enforces-ssl-1.1.0.tgz#cf29c6a61c5bdd802e2c7ed265a4a98e7487d1ac" + integrity sha1-zynGphxb3YAuLH7SZaSpjnSH0aw= + +express-hot-shots@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/express-hot-shots/-/express-hot-shots-1.0.2.tgz#570c2d5f7c18b6e2c6d3fb48a0736e92af5ddc1d" + integrity sha512-EW/HoBhpTSaO0QCwcIHSQ2M39ugKr/l2jtH4+qRSCbFAc31qLsOlFt5TGXIGUmMyyb1ffv0kiFHDbaWCd21kzA== + dependencies: + hot-shots "^5.9.2" + obj-extend "~0.1.0" + express-session@^1.15.2: version "1.15.6" resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.15.6.tgz#47b4160c88f42ab70fe8a508e31cbff76757ab0a" + integrity sha512-r0nrHTCYtAMrFwZ0kBzZEXa1vtPVrw0dKvGSrKP4dahwBQ1BJpF2/y1Pp4sCD/0kvxV4zZeclyvfmw0B4RMJQA== dependencies: cookie "0.3.1" cookie-signature "1.0.6" @@ -2664,67 +3997,73 @@ express-session@^1.15.2: uid-safe "~2.1.5" utils-merge "1.0.1" -express@^4.15.2: - version "4.16.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" +express@^4.16.4: + version "4.16.4" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e" + integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg== dependencies: - accepts "~1.3.4" + accepts "~1.3.5" array-flatten "1.1.1" - body-parser "1.18.2" + body-parser "1.18.3" content-disposition "0.5.2" content-type "~1.0.4" cookie "0.3.1" cookie-signature "1.0.6" debug "2.6.9" - depd "~1.1.1" - encodeurl "~1.0.1" + depd "~1.1.2" + encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "1.1.0" + finalhandler "1.1.1" fresh "0.5.2" merge-descriptors "1.0.1" methods "~1.1.2" on-finished "~2.3.0" parseurl "~1.3.2" path-to-regexp "0.1.7" - proxy-addr "~2.0.2" - qs "6.5.1" + proxy-addr "~2.0.4" + qs "6.5.2" range-parser "~1.2.0" - safe-buffer "5.1.1" - send "0.16.1" - serve-static "1.13.1" + safe-buffer "5.1.2" + send "0.16.2" + serve-static "1.13.2" setprototypeof "1.1.0" - statuses "~1.3.1" - type-is "~1.6.15" + statuses "~1.4.0" + type-is "~1.6.16" utils-merge "1.0.1" vary "~1.1.2" extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= dependencies: is-extendable "^0.1.0" -extend-shallow@^3.0.0: +extend-shallow@^3.0.0, extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= dependencies: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" +extend@^3.0.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== extglob@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE= dependencies: is-extglob "^1.0.0" -extglob@^2.0.2: +extglob@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== dependencies: array-unique "^0.3.2" define-property "^1.0.0" @@ -2735,49 +4074,57 @@ extglob@^2.0.2: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-files@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-3.1.0.tgz#b70424c9d4a1a4208efe22069388f428e4ae00f1" - dependencies: - "@babel/runtime" "^7.0.0-beta.38" +extract-files@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-4.1.0.tgz#2d5b64af688dfd030274ca542c43fabba325019a" + integrity sha512-2gjdb3dVzr1ie9+K8pupPTnsNkK4qmzbTFOIxghiWoh6nCTajGCGC72ZNYX0nBWy5IOq1FXfRVgvkkLqqE4sdw== 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.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= faker@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" + integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8= fast-bind@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-bind/-/fast-bind-1.0.0.tgz#7fa9652cb3325f5cd1e252d6cb4f160de1a76e75" + integrity sha1-f6llLLMyX1zR4lLWy08WDeGnbnU= -fast-deep-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= fast-json-stable-stringify@^2.0.0: version "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: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= fb-watchman@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" + integrity sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg= dependencies: bser "^2.0.0" -fbjs@^0.8.1, fbjs@^0.8.15, fbjs@^0.8.16, fbjs@^0.8.5, fbjs@^0.8.9: - version "0.8.16" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" +fbjs@^0.8.1, fbjs@^0.8.15, fbjs@^0.8.5, fbjs@^0.8.9: + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= dependencies: core-js "^1.0.0" isomorphic-fetch "^2.1.1" @@ -2785,65 +4132,87 @@ fbjs@^0.8.1, fbjs@^0.8.15, fbjs@^0.8.16, fbjs@^0.8.5, fbjs@^0.8.9: object-assign "^4.1.0" promise "^7.1.1" setimmediate "^1.0.5" - ua-parser-js "^0.7.9" + ua-parser-js "^0.7.18" + +feature-policy@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.2.0.tgz#22096de49ab240176878ffe2bde2f6ff04d48c43" + integrity sha512-2hGrlv6efG4hscYVZeaYjpzpT6I2OZgYqE2yDUzeAcKj2D1SH0AsEzqJNXzdoglEddcIXQQYop3lD97XpG75Jw== + +figgy-pudding@^3.1.0, figgy-pudding@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" + integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= fileset@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" + integrity sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA= dependencies: glob "^7.0.3" minimatch "^3.0.3" fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + version "2.2.4" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" + integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== dependencies: is-number "^2.1.0" isobject "^2.0.0" - randomatic "^1.1.3" + randomatic "^3.0.0" repeat-element "^1.1.2" repeat-string "^1.5.2" fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= dependencies: extend-shallow "^2.0.1" is-number "^3.0.0" repeat-string "^1.6.1" to-regex-range "^2.1.0" -filled-array@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filled-array/-/filled-array-1.1.0.tgz#c3c4f6c663b923459a9aa29912d2d031f1507f84" - -finalhandler@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" +finalhandler@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" + integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg== dependencies: debug "2.6.9" - encodeurl "~1.0.1" + encodeurl "~1.0.2" escape-html "~1.0.3" on-finished "~2.3.0" parseurl "~1.3.2" - statuses "~1.3.1" + statuses "~1.4.0" unpipe "~1.0.0" find-cache-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" + integrity sha1-kojj6ePMN0hxfTnq3hfPcfww7m8= dependencies: commondir "^1.0.1" make-dir "^1.0.0" pkg-dir "^2.0.0" +find-cache-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d" + integrity sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA== + dependencies: + commondir "^1.0.1" + make-dir "^1.0.0" + pkg-dir "^3.0.0" + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= dependencies: path-exists "^2.0.0" pinkie-promise "^2.0.0" @@ -2851,152 +4220,196 @@ find-up@^1.0.0: find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= dependencies: locate-path "^2.0.0" -find-with-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/find-with-regex/-/find-with-regex-1.0.2.tgz#d3b36286539f14c527e31f194159c6d251651a45" +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" + +find-with-regex@^1.0.2, find-with-regex@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/find-with-regex/-/find-with-regex-1.1.3.tgz#d6c6f2debee898d36b6a77e05709b13dd5dc8a26" + integrity sha512-zkEVQ1H3PIQL/19ADKt1lCQU4QGM3OneiderUcFgn5EgTm/TnoUh7HxPAwP8w/vXxWSLC6KtpbDQpypJ5+majw== flexbuffer@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/flexbuffer/-/flexbuffer-0.0.6.tgz#039fdf23f8823e440c38f3277e6fef1174215b30" + integrity sha1-A5/fI/iCPkQMOPMnfm/vEXQhWzA= -flow-typed@^2.1.5: - version "2.2.3" - resolved "https://registry.yarnpkg.com/flow-typed/-/flow-typed-2.2.3.tgz#e7a35915a0f4cfcf8068c1ce291b5c99e6b89efa" +flow-typed@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/flow-typed/-/flow-typed-2.5.1.tgz#0ff565cc94d2af8c557744ba364b6f14726a6b9f" + integrity sha1-D/VlzJTSr4xVd0S6NktvFHJqa58= dependencies: - babel-polyfill "^6.23.0" + "@octokit/rest" "^15.2.6" + babel-polyfill "^6.26.0" colors "^1.1.2" - fs-extra "^4.0.0" - github "0.2.4" + fs-extra "^5.0.0" glob "^7.1.2" got "^7.1.0" md5 "^2.1.0" mkdirp "^0.5.1" - request "^2.81.0" - rimraf "^2.6.1" - semver "^5.1.0" - table "^4.0.1" + rimraf "^2.6.2" + semver "^5.5.0" + table "^4.0.2" through "^2.3.8" - unzip "^0.1.11" - which "^1.2.14" + unzipper "^0.8.11" + which "^1.3.0" yargs "^4.2.0" +flush-write-stream@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd" + integrity sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw== + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.4" + follow-redirects@^1.2.3: - version "1.3.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.3.0.tgz#f684871fc116d2e329fda55ef67687f4fabc905c" + 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" + debug "=3.1.0" for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= for-own@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= dependencies: for-in "^1.0.1" foreach@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= 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.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" - -form-data@~2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" +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.5" + combined-stream "^1.0.6" mime-types "^2.1.12" forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= dependencies: map-cache "^0.2.2" +frameguard@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/frameguard/-/frameguard-3.0.0.tgz#7bcad469ee7b96e91d12ceb3959c78235a9272e9" + integrity sha1-e8rUae57lukdEs6zlZx4I1qScuk= + fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -friendly-errors-webpack-plugin@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.6.1.tgz#e32781c4722f546a06a9b5d7a7cfa28520375d70" +friendly-errors-webpack-plugin@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.7.0.tgz#efc86cbb816224565861a1be7a9d84d0aafea136" + integrity sha512-K27M3VK30wVoOarP651zDmb93R9zF28usW4ocaK3mfQeIEI5BPht/EzZs5E8QLLwbLRJQMwscAjDxYPb1FuNiw== dependencies: chalk "^1.1.3" error-stack-parser "^2.0.0" - string-length "^1.0.1" + string-width "^2.0.0" -from@~0: - version "0.1.7" - resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" +from2@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" + integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" -fs-extra@^4.0.0, fs-extra@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" +fs-capacitor@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-1.0.1.tgz#ff9dbfa14dfaf4472537720f19c3088ed9278df0" + integrity sha512-XdZK0Q78WP29Vm3FGgJRhRhrBm51PagovzWtW2kJ3Q6cYJbGtZqWSGTSPwvtEkyjIirFd7b8Yes/dpOYjt4RRQ== + +fs-extra@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" + integrity sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ== dependencies: graceful-fs "^4.1.2" jsonfile "^4.0.0" universalify "^0.1.0" -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - -fsevents@^1.0.0, fsevents@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== dependencies: - nan "^2.3.0" - node-pre-gyp "^0.6.39" + minipass "^2.2.1" -fstream-ignore@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" +fs-write-stream-atomic@^1.0.8: + version "1.0.10" + resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" + integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= dependencies: - fstream "^1.0.0" - inherits "2" - minimatch "^3.0.0" + graceful-fs "^4.1.2" + iferr "^0.1.5" + imurmurhash "^0.1.4" + readable-stream "1 || 2" -"fstream@>= 0.1.30 < 1": - version "0.1.31" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-0.1.31.tgz#7337f058fbbbbefa8c9f561a28cab0849202c988" +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= + +fsevents@^1.2.2, fsevents@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" + integrity sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg== dependencies: - graceful-fs "~3.0.2" - inherits "~2.0.0" - mkdirp "0.5" - rimraf "2" + nan "^2.9.2" + node-pre-gyp "^0.10.0" -fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: +fstream@~1.0.10: version "1.0.11" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE= dependencies: graceful-fs "^4.1.2" inherits "~2.0.0" mkdirp ">=0.5 0" rimraf "2" +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== + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= dependencies: aproba "^1.0.3" console-control-strings "^1.0.0" @@ -3008,46 +4421,36 @@ gauge@~2.7.3: wide-align "^1.1.0" get-caller-file@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" - -get-document@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/get-document/-/get-document-1.0.0.tgz#4821bce66f1c24cb0331602be6cb6b12c4f01c4b" + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - -get-window@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-window/-/get-window-1.1.1.tgz#0750f8970c88a54ac1294deb97add9568b3db594" - dependencies: - get-document "1" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= 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" -github@0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/github/-/github-0.2.4.tgz#24fa7f0e13fa11b946af91134c51982a91ce538b" - dependencies: - mime "^1.2.11" - glob-base@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q= dependencies: glob-parent "^2.0.0" is-glob "^2.0.0" @@ -3055,19 +4458,22 @@ glob-base@^0.3.0: glob-parent@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= dependencies: is-glob "^2.0.0" glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= dependencies: is-glob "^3.1.0" path-dirname "^1.0.0" glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -3079,26 +4485,34 @@ glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: global-dirs@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" + integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= dependencies: ini "^1.3.4" global@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" + integrity sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8= dependencies: min-document "^2.19.0" process "~0.5.1" +globals@^11.1.0: + version "11.9.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.9.0.tgz#bde236808e987f290768a93d065060d78e6ab249" + integrity sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg== + globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== -globby@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= dependencies: array-union "^1.0.1" - arrify "^1.0.0" glob "^7.0.3" object-assign "^4.0.1" pify "^2.0.0" @@ -3107,32 +4521,14 @@ globby@^5.0.0: good-listener@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= dependencies: delegate "^3.1.2" -got@^5.0.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" - dependencies: - create-error-class "^3.0.1" - duplexer2 "^0.1.4" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - node-status-codes "^1.0.0" - object-assign "^4.0.1" - parse-json "^2.1.0" - pinkie-promise "^2.0.0" - read-all-stream "^3.0.0" - readable-stream "^2.0.5" - timed-out "^3.0.0" - unzip-response "^1.0.2" - url-parse-lax "^1.0.0" - got@^6.7.1: version "6.7.1" resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" + integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= dependencies: create-error-class "^3.0.0" duplexer3 "^0.1.4" @@ -3149,6 +4545,7 @@ got@^6.7.1: got@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" + integrity sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw== dependencies: decompress-response "^3.2.0" duplexer3 "^0.1.4" @@ -3166,160 +4563,179 @@ got@^7.1.0: url-to-options "^1.0.1" graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== -graceful-fs@~3.0.2: - version "3.0.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818" - dependencies: - natives "^1.1.0" - -graphql-cost-analysis@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/graphql-cost-analysis/-/graphql-cost-analysis-0.1.1.tgz#e500cb2e896efe333f6912bb264ccb7948a6c06a" +graphql-cost-analysis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/graphql-cost-analysis/-/graphql-cost-analysis-1.0.3.tgz#25b97c8e638c7e538af5ba9bcf6012cda74420ce" + integrity sha512-2kogZrc3iPVW5Lf2cSadVfufNx440XMoqKbMjNRi96HV80jCk9is1AI7CwizT5CSGzKlsnGQmaSqjeR1dJB0Gw== dependencies: selectn "^1.1.2" graphql-date@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/graphql-date/-/graphql-date-1.0.3.tgz#31ce05ae40ed8c8ceb040364060109771e712e91" + integrity sha1-Mc4FrkDtjIzrBANkBgEJdx5xLpE= dependencies: assert-err "^1.0.0" graphql-depth-limit@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/graphql-depth-limit/-/graphql-depth-limit-1.1.0.tgz#59fe6b2acea0ab30ee7344f4c75df39cc18244e8" + integrity sha512-+3B2BaG8qQ8E18kzk9yiSdAa75i/hnnOwgSeAxVJctGQPvmeiLtqKOYF6HETCyRjiF7Xfsyal0HbLlxCQkgkrw== dependencies: arrify "^1.0.1" -graphql-extensions@^0.0.x: - version "0.0.5" - resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.0.5.tgz#63bc4a3fd31aab12bfadf783cbc038a9a6937cf0" +graphql-extensions@0.5.0-alpha.0: + version "0.5.0-alpha.0" + resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.5.0-alpha.0.tgz#978cb03f6af4637e355f1bf9cf8dfd11b9c25218" + integrity sha512-+U2qE7w8/VMinCZrg+wVQ78UAwnEXQtU2RlcxMW+MBg007YDLl3cu2IyLqMNP8/ADBZ5w84RqBrYT02/r2IfxA== dependencies: - core-js "^2.5.1" - source-map-support "^0.5.0" + "@apollographql/apollo-tools" "^0.2.6" -graphql-log@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/graphql-log/-/graphql-log-0.1.2.tgz#d733fc58b14cbb336effa2bb355274d13683c6b5" +graphql-log@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/graphql-log/-/graphql-log-0.1.3.tgz#a1cc002fbaa9392f6df0483ed57937dd291826b0" + integrity sha512-DiwQasuTjN6sdibuFT4dmaFEdkCAY4wI83a3qXBY8UX4VOUaegyrLvIjPvnGZGJF8sfYOLHzPWG676AYdFTizQ== dependencies: deep-for-each "^1.0.6" is-function "^1.0.1" -graphql-server-express@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/graphql-server-express/-/graphql-server-express-1.3.0.tgz#f9276a59c2aedff893fe78869a5bc5dea519f6f7" +graphql-rate-limit@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/graphql-rate-limit/-/graphql-rate-limit-1.2.3.tgz#33a98062bc251ba78eb5e74a8b7b08b1aac596c7" + integrity sha512-nhVYmEC/r1uhmCaSdjgOTNuBBbNcgYvdFRqTwWZ/+55tY/gTpEYTwH9ypayoERoqXleXwYsEROGQ9EEumA/uKA== dependencies: - apollo-server-express "^1.3.0" + "@types/redis-mock" "^0.17.0" + graphql-tools "^4.0.3" + lodash.get "^4.4.2" + ms "^2.1.1" -graphql-subscriptions@0.4.x: - version "0.4.4" - resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-0.4.4.tgz#39cff32d08dd3c990113864bab77154403727e9b" +graphql-subscriptions@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-1.0.0.tgz#475267694b3bd465af6477dbab4263a3f62702b8" + integrity sha512-+ytmryoHF1LVf58NKEaNPRUzYyXplm120ntxfPcgOBC7TnK7Tv/4VRHeh4FAR9iL+O1bqhZs4nkibxQ+OA5cDQ== dependencies: - "@types/graphql" "^0.9.1" - es6-promise "^4.0.5" - iterall "^1.1.1" + iterall "^1.2.1" -graphql-subscriptions@^0.5.6: - version "0.5.6" - resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-0.5.6.tgz#0d8e960fbaaf9ecbe7900366e86da2fc143fc5b2" - dependencies: - es6-promise "^4.1.1" - iterall "^1.1.3" +graphql-tag@^2.9.2: + version "2.10.0" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.0.tgz#87da024be863e357551b2b8700e496ee2d4353ae" + integrity sha512-9FD6cw976TLLf9WYIUPCaaTpniawIjHWZSwIRZSjrfufJamcXbVVYfN2TWvJYbw0Xf2JjYbl1/f2+wDnBVw3/w== -graphql-tools@1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-1.2.3.tgz#079bf4d157e46c0a0bae9fec117e0eea6e03ba2c" +graphql-tools@^4.0.0, graphql-tools@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-4.0.3.tgz#23b5cb52c519212b1b2e4630a361464396ad264b" + integrity sha512-NNZM0WSnVLX1zIMUxu7SjzLZ4prCp15N5L2T2ro02OVyydZ0fuCnZYRnx/yK9xjGWbZA0Q58yEO//Bv/psJWrg== dependencies: + apollo-link "^1.2.3" + apollo-utilities "^1.0.1" deprecated-decorator "^0.1.6" - uuid "^3.0.1" - optionalDependencies: - "@types/graphql" "^0.9.0" + iterall "^1.1.3" + uuid "^3.1.0" -"graphql-tools@^1 || ^2": - version "2.18.0" - resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-2.18.0.tgz#8e2d6436f9adba1d579c1a1710ae95e7f5e7248b" +graphql-tools@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-4.0.4.tgz#ca08a63454221fdde825fe45fbd315eb2a6d566b" + integrity sha512-chF12etTIGVVGy3fCTJ1ivJX2KB7OSG4c6UOJQuqOHCmBQwTyNgCDuejZKvpYxNZiEx7bwIjrodDgDe9RIkjlw== dependencies: - apollo-link "^1.0.0" + apollo-link "^1.2.3" apollo-utilities "^1.0.1" deprecated-decorator "^0.1.6" - graphql-subscriptions "^0.5.6" + iterall "^1.1.3" uuid "^3.1.0" -graphql@0.11.x: - version "0.11.7" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.11.7.tgz#e5abaa9cb7b7cccb84e9f0836bf4370d268750c6" +graphql-upload@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/graphql-upload/-/graphql-upload-8.0.2.tgz#1c1f116f15b7f8485cf40ff593a21368f0f58856" + integrity sha512-u8a5tKPfJ0rU4MY+B3skabL8pEjMkm3tUzq25KBx6nT0yEWmqUO7Z5tdwvwYLFpkLwew94Gue0ARbZtar3gLTw== + dependencies: + busboy "^0.2.14" + fs-capacitor "^1.0.0" + http-errors "^1.7.1" + object-path "^0.11.4" + +graphql@0.13.x: + version "0.13.2" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.13.2.tgz#4c740ae3c222823e7004096f832e7b93b2108270" + integrity sha512-QZ5BL8ZO/B20VA8APauGBg3GyEgZ19eduvpLWoq5x7gMmWnHoy8rlQWPLmWgFvo1yNgjSEFMesmS4R6pPr7xog== dependencies: - iterall "1.1.3" + iterall "^1.2.1" growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= handlebars@^4.0.3: - version "4.0.11" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" + version "4.0.12" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5" + integrity sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA== dependencies: - async "^1.4.0" + async "^2.5.0" optimist "^0.6.1" - source-map "^0.4.4" + source-map "^0.6.1" optionalDependencies: - uglify-js "^2.6" - -har-schema@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + uglify-js "^3.1.4" 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@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" - dependencies: - ajv "^4.9.1" - har-schema "^1.0.5" - -har-validator@~5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" +har-validator@~5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== dependencies: - ajv "^5.1.0" + ajv "^6.5.5" har-schema "^2.0.0" has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= dependencies: ansi-regex "^2.0.0" has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= -has-flag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" +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-symbol-support-x@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.1.tgz#66ec2e377e0c7d7ccedb07a3a84d77510ff1bc4c" + version "1.4.2" + resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" + integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= has-to-string-tag-x@^1.2.0: version "1.4.1" resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" + integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== dependencies: has-symbol-support-x "^1.4.1" has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= dependencies: get-value "^2.0.3" has-values "^0.1.4" @@ -3328,6 +4744,7 @@ has-value@^0.3.1: has-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= dependencies: get-value "^2.0.6" has-values "^1.0.0" @@ -3336,59 +4753,88 @@ has-value@^1.0.0: has-values@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= has-values@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= dependencies: is-number "^3.0.0" kind-of "^4.0.0" -hash-base@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" +has@^1.0.1, 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: - inherits "^2.0.1" + function-bind "^1.1.1" hash-base@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== dependencies: inherits "^2.0.3" - minimalistic-assert "^1.0.0" - -hawk@3.1.3, hawk@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" - dependencies: - boom "2.x.x" - cryptiles "2.x.x" - hoek "2.x.x" - sntp "1.x.x" + minimalistic-assert "^1.0.1" -hawk@~6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" - dependencies: - boom "4.x.x" - cryptiles "3.x.x" - hoek "4.x.x" - sntp "2.x.x" +helmet-crossdomain@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/helmet-crossdomain/-/helmet-crossdomain-0.3.0.tgz#707e2df930f13ad61f76ed08e1bb51ab2b2e85fa" + integrity sha512-YiXhj0E35nC4Na5EPE4mTfoXMf9JTGpN4OtB4aLqShKuH9d2HNaJX5MQoglO6STVka0uMsHyG5lCut5Kzsy7Lg== + +helmet-csp@2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.7.1.tgz#e8e0b5186ffd4db625cfcce523758adbfadb9dca" + integrity sha512-sCHwywg4daQ2mY0YYwXSZRsgcCeerUwxMwNixGA7aMLkVmPTYBl7gJoZDHOZyXkqPrtuDT3s2B1A+RLI7WxSdQ== + dependencies: + camelize "1.0.0" + content-security-policy-builder "2.0.0" + dasherize "2.0.0" + platform "1.3.5" + +helmet@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.15.0.tgz#fe0bb80e05d9eec589e3cbecaf5384409a3a64c9" + integrity sha512-j9JjtAnWJj09lqe/PEICrhuDaX30TeokXJ9tW6ZPhVH0+LMoihDeJ58CdWeTGzM66p6EiIODmgAaWfdeIWI4Gg== + dependencies: + dns-prefetch-control "0.1.0" + dont-sniff-mimetype "1.0.0" + expect-ct "0.1.1" + feature-policy "0.2.0" + frameguard "3.0.0" + helmet-crossdomain "0.3.0" + helmet-csp "2.7.1" + hide-powered-by "1.0.0" + hpkp "2.0.0" + hsts "2.1.0" + ienoopen "1.0.0" + nocache "2.0.0" + referrer-policy "1.1.0" + x-xss-protection "1.1.0" + +hide-powered-by@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hide-powered-by/-/hide-powered-by-1.0.0.tgz#4a85ad65881f62857fc70af7174a1184dccce32b" + integrity sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys= -highlight.js@^9.10.0: - version "9.12.0" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" +highlight.js@^9.14.1: + version "9.14.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.14.1.tgz#62bc7945692a0feb2e2f0cf7c97b6a00411d7ba0" + integrity sha512-UpSrdhp5jHPbrf9+/bE1p8kxZlh9QHWD24zp2jMxCP1Po9be7XH7GiK5Q00OvCBlji1FVa+nTYOkZqrBE1pcHw== history@^4.6.1, history@^4.7.2: version "4.7.2" resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" + integrity sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA== dependencies: invariant "^2.2.1" loose-envify "^1.2.0" @@ -3399,144 +4845,238 @@ history@^4.6.1, history@^4.7.2: hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= dependencies: hash.js "^1.0.3" minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoek@2.x.x: - version "2.16.3" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" +hoek@5.x.x: + version "5.0.4" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.4.tgz#0f7fa270a1cafeb364a4b2ddfaa33f864e4157da" + integrity sha512-Alr4ZQgoMlnere5FZJsIyfIjORBqZll5POhDsF4q64dPuJR6rNxXdDxtHSQq8OXRurhmx+PWYEE8bXRROY8h0w== -hoek@4.x.x: - version "4.2.0" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" +hoek@6.x.x: + version "6.1.2" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.2.tgz#99e6d070561839de74ee427b61aa476bd6bddfd6" + integrity sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q== hoist-non-react-statics@^1.0.0, hoist-non-react-statics@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" + integrity sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs= -hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" +hoist-non-react-statics@^2.1.1, hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.5: + version "2.5.5" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" + integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== + +hoist-non-react-statics@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.2.1.tgz#c09c0555c84b38a7ede6912b61efddafd6e75e1e" + integrity sha512-TFsu3TV3YLY+zFTZDrN8L2DTFanObwmBLpWvJs1qfUuEQ5bTAdFcwfx2T/bsCXfM9QHSLvjfP+nihEl0yvozxw== + dependencies: + react-is "^16.3.2" home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg= dependencies: os-homedir "^1.0.0" os-tmpdir "^1.0.1" +host-validation@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/host-validation/-/host-validation-1.2.0.tgz#1f7946c8361b200e83005ca2229494383d6e8cb7" + integrity sha512-WaTXLEOc2oZcRc+ZrZPCZK9gC+i0i6Hw7/mXTunzDCq69Ta9TxnO9XSW20ntK+5uj2uC5tH+sCCmbIFjoRROFA== + hosted-git-info@^2.1.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + version "2.7.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" + integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== + +hot-shots@^5.9.2: + version "5.9.2" + resolved "https://registry.yarnpkg.com/hot-shots/-/hot-shots-5.9.2.tgz#1cff097706f40ba2c1350d4806deebbd16a5e8c8" + integrity sha512-ruHZvHaxZRVUCoCleiwwCcjdr9A2/Y97C7oc9LpyMg9ae39blHvsJJQQ0QtMtxKX+i4lig/7/BqZBjUUZPUp3A== + +hpkp@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hpkp/-/hpkp-2.0.0.tgz#10e142264e76215a5d30c44ec43de64dee6d1672" + integrity sha1-EOFCJk52IVpdMMROxD3mTe5tFnI= + +hpp@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/hpp/-/hpp-0.2.2.tgz#0ec5f77472049a74361d85ba2b88e2470a4356f8" + integrity sha1-DsX3dHIEmnQ2HYW6K4jiRwpDVvg= + dependencies: + lodash "^4.7.0" + type-is "^1.6.12" + +hsts@2.1.0, hsts@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hsts/-/hsts-2.1.0.tgz#cbd6c918a2385fee1dd5680bfb2b3a194c0121cc" + integrity sha512-zXhh/DqgrTXJ7erTN6Fh5k/xjMhDGXCqdYN3wvxUvGUQvnxcFfUd8E+6vLg/nk3ss1TYMb+DhRl25fYABioTvA== html-encoding-sniffer@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== dependencies: whatwg-encoding "^1.0.1" -htmlparser2@^3.9.1: - version "3.9.2" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" +http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= dependencies: - domelementtype "^1.3.0" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^2.0.2" + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" -http-errors@1.6.2, http-errors@~1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" +http-errors@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.1.tgz#6a4ffe5d35188e1c39f872534690585852e1f027" + integrity sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw== dependencies: - depd "1.1.1" + depd "~1.1.2" inherits "2.0.3" - setprototypeof "1.0.3" - statuses ">= 1.3.1 < 2" + setprototypeof "1.1.0" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" -http-signature@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" +http-proxy-agent@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" + integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== dependencies: - assert-plus "^0.2.0" - jsprim "^1.2.2" - sshpk "^1.7.0" + agent-base "4" + debug "3.1.0" 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" -http_ece@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/http_ece/-/http_ece-0.5.2.tgz#5654d7ec9d996b749ce00a276e18d54b6d8f905f" +http_ece@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/http_ece/-/http_ece-1.0.5.tgz#b60660faaf14215102d1493ea720dcd92b53372f" + integrity sha1-tgZg+q8UIVEC0Uk+pyDc2StTNy8= dependencies: urlsafe-base64 "~1.0.0" https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + +https-proxy-agent@^2.2.0, https-proxy-agent@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" + integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== + dependencies: + agent-base "^4.1.0" + debug "^3.1.0" + +iconv-lite@0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA== + dependencies: + safer-buffer ">= 2.1.2 < 3" -iconv-lite@0.4.19, iconv-lite@~0.4.13: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +iconv-lite@0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: + 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" ieee754@^1.1.4: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + version "1.1.12" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" + integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== + +ienoopen@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ienoopen/-/ienoopen-1.0.0.tgz#346a428f474aac8f50cf3784ea2d0f16f62bda6b" + integrity sha1-NGpCj0dKrI9QzzeE6i0PFvYr2ms= + +iferr@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" + integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= ignore-by-default@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= -imgix-core-js@^1.0.6: - version "1.1.1" - resolved "https://registry.yarnpkg.com/imgix-core-js/-/imgix-core-js-1.1.1.tgz#892bc1659738878cacd4b8feb5dc9e9d91e9f837" +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== + dependencies: + minimatch "^3.0.4" + +imgix-core-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/imgix-core-js/-/imgix-core-js-1.2.0.tgz#72163ebd312b25cdae077340d13cf94dc09327f2" + integrity sha512-Eq8IabyhZwwP1m+E26L4AJLCznqckFuL2nvOFmtYESmyFEv4IXa/UU58DHegnmMYoPqHunmG9ttL6NDdB0ddbg== dependencies: + crc "^3.5.0" js-base64 "^2.1.9" md5 "^2.2.1" -immutability-helper@^2.2.0: - version "2.6.4" - resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-2.6.4.tgz#a931aef97257fcb6d2b5456de652ab6e3bba8408" +immutability-helper@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-2.9.1.tgz#71c423ba387e67b6c6ceba0650572f2a2a6727df" + integrity sha512-r/RmRG8xO06s/k+PIaif2r5rGc3j4Yhc01jSBfwPCXDLYZwp/yxralI37Df1mwmuzcCsen/E/ITKcTEvc1PQmQ== dependencies: invariant "^2.2.0" -immutable@*, immutable@3.x, immutable@^3.8.1: +immutable@*, immutable@3.x: version "3.8.2" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" + integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM= immutable@~3.7.4: version "3.7.6" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" + integrity sha1-E7TTyxK++hVIKib+Gy665kAHHks= import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= 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@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= dependencies: repeating "^2.0.0" indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= 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" @@ -3544,32 +5084,34 @@ inflight@^1.0.4: inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -interpret@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" - -invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" +invariant@^2.2.0, invariant@^2.2.1, 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" invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= -ioredis@^3.1.4: +ioredis@3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-3.2.2.tgz#b7d5ff3afd77bb9718bb2821329b894b9a44c00b" + integrity sha512-g+ShTQYLsCcOUkNOK6CCEZbj3aRDVPw3WOwXk+LxlUKvuS9ujEqP2MppBHyRVYrNNFW/vcPaTBUZ2ctGNSiOCA== dependencies: bluebird "^3.3.4" cluster-key-slot "^1.0.6" @@ -3595,71 +5137,125 @@ ioredis@^3.1.4: redis-commands "^1.2.0" redis-parser "^2.4.0" -ipaddr.js@1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" +ioredis@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.3.0.tgz#a92850dd8794eaee4f38a265c830ca823a09d345" + integrity sha512-TwTp93UDKlKVQeg9ThuavNh4Vs31JTlqn+cI/J6z21OtfghyJm5I349ZlsKobOeEyS4INITMLQ1fhR7xwf9Fxg== + dependencies: + cluster-key-slot "^1.0.6" + debug "^3.1.0" + denque "^1.1.0" + flexbuffer "0.0.6" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + redis-commands "1.4.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^1.0.0" + +ioredis@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.5.1.tgz#b1c1c1657697caa3a617acb9370e3c0694edb775" + integrity sha512-p1BblrFZdb5Oc5EBsEb4EoycDqn7xi/NTNT4bDvo/w6B08eMNO1E7RAOOEA1GAb65+8Hbs2LgUyz3cZOTiP3xg== + dependencies: + cluster-key-slot "^1.0.6" + debug "^3.1.0" + denque "^1.1.0" + flexbuffer "0.0.6" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + redis-commands "1.4.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^1.0.0" + +ipaddr.js@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" + integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4= is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= dependencies: kind-of "^3.0.2" is-accessor-descriptor@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== dependencies: kind-of "^6.0.0" is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= dependencies: binary-extensions "^1.0.0" is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-builtin-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74= dependencies: builtin-modules "^1.0.0" +is-callable@^1.1.3, is-callable@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== + is-ci@^1.0.10: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" + integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== dependencies: - ci-info "^1.0.0" + ci-info "^1.5.0" is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= dependencies: kind-of "^3.0.2" is-data-descriptor@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== dependencies: kind-of "^6.0.0" +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= + is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== dependencies: is-accessor-descriptor "^0.1.6" is-data-descriptor "^0.1.4" kind-of "^5.0.0" -is-descriptor@^1.0.0: +is-descriptor@^1.0.0, is-descriptor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== dependencies: is-accessor-descriptor "^1.0.0" is-data-descriptor "^1.0.0" @@ -3668,80 +5264,86 @@ is-descriptor@^1.0.0: is-dotfile@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" - -is-empty@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-empty/-/is-empty-1.2.0.tgz#de9bb5b278738a05a0b09a57e1fb4d4a341a9f6b" + integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE= is-equal-shallow@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ= dependencies: is-primitive "^2.0.0" is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= is-extendable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== dependencies: is-plain-object "^2.0.4" is-extglob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= is-extglob@^2.1.0, 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-finite@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= dependencies: number-is-nan "^1.0.0" is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= dependencies: number-is-nan "^1.0.0" 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-function@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5" + integrity sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU= is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= dependencies: is-extglob "^1.0.0" is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= dependencies: is-extglob "^2.1.0" is-glob@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= dependencies: is-extglob "^2.1.1" -is-in-browser@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" - is-installed-globally@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" + integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= dependencies: global-dirs "^0.1.0" is-path-inside "^1.0.0" @@ -3749,130 +5351,177 @@ is-installed-globally@^0.1.0: is-nan@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.2.1.tgz#9faf65b6fb6db24b7f5c0628475ea71f988401e2" + integrity sha1-n69ltvttskt/XAYoR16nH5iEAeI= dependencies: define-properties "^1.1.1" is-npm@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" + integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= dependencies: kind-of "^3.0.2" is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= dependencies: kind-of "^3.0.2" +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== + is-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= is-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" - -is-odd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-1.0.0.tgz#3b8a932eb028b3775c39bb09e91767accdb69088" - dependencies: - is-number "^3.0.0" + integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0= is-path-in-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" + integrity sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ== dependencies: is-path-inside "^1.0.0" is-path-inside@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" + integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= dependencies: path-is-inside "^1.0.1" is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: isobject "^3.0.1" is-posix-bracket@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q= is-primitive@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= is-redirect@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" + integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= + +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= + dependencies: + has "^1.0.1" is-retry-allowed@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" + integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ= is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-symbol@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" + integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + dependencies: + has-symbols "^1.0.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= is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= -is-window@^1.0.2: +is-windows@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/is-window/-/is-window-1.0.2.tgz#2c896ca53db97de45d3c33133a65d8c9f563480d" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is_js@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/is_js/-/is_js-0.9.0.tgz#0ab94540502ba7afa24c856aa985561669e9c52d" + integrity sha1-CrlFQFArp6+iTIVqqYVWFmnpxS0= isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= isarray@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.2.tgz#5aa99638daf2248b10b9598b763a045688ece3ee" + version "2.0.4" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.4.tgz#38e7bcbb0f3ba1b7933c86ba1894ddfc3781bbb7" + integrity sha512-GMxXOiUirWg1xTKRipM0Ek07rX+ubx4nNVElTJdNLYmNO/2YrDkgJGw9CljXn+r4EWiDQg/8lsRdHyg2PJuUaA== -isemail@2.x.x: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isemail/-/isemail-2.2.1.tgz#0353d3d9a62951080c262c2aa0a42b8ea8e9e2a6" +isemail@3.x.x: + version "3.2.0" + resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.2.0.tgz#59310a021931a9fb06bbb51e155ce0b3f236832c" + integrity sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg== + dependencies: + punycode "2.x.x" 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= isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= dependencies: isarray "1.0.0" isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= isomorphic-fetch@^2.1.1, isomorphic-fetch@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= dependencies: node-fetch "^1.0.1" whatwg-fetch ">=0.10.0" @@ -3880,98 +5529,102 @@ isomorphic-fetch@^2.1.1, isomorphic-fetch@^2.2.1: 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= istanbul-api@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.1.tgz#0c60a0515eb11c7d65c6b50bba2c6e999acd8620" + version "1.3.7" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.7.tgz#a86c770d2b03e11e3f778cd7aedd82d2722092aa" + integrity sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA== dependencies: async "^2.1.4" fileset "^2.0.2" - istanbul-lib-coverage "^1.1.1" - istanbul-lib-hook "^1.1.0" - istanbul-lib-instrument "^1.9.1" - istanbul-lib-report "^1.1.2" - istanbul-lib-source-maps "^1.2.2" - istanbul-reports "^1.1.3" + istanbul-lib-coverage "^1.2.1" + istanbul-lib-hook "^1.2.2" + istanbul-lib-instrument "^1.10.2" + istanbul-lib-report "^1.1.5" + istanbul-lib-source-maps "^1.2.6" + istanbul-reports "^1.5.1" js-yaml "^3.7.0" mkdirp "^0.5.1" once "^1.4.0" -istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" +istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0" + integrity sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ== -istanbul-lib-hook@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz#8538d970372cb3716d53e55523dd54b557a8d89b" +istanbul-lib-hook@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz#bc6bf07f12a641fbf1c85391d0daa8f0aea6bf86" + integrity sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw== dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e" +istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2, istanbul-lib-instrument@^1.4.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca" + integrity sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A== dependencies: babel-generator "^6.18.0" babel-template "^6.16.0" babel-traverse "^6.18.0" babel-types "^6.18.0" babylon "^6.18.0" - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.2.1" semver "^5.3.0" -istanbul-lib-report@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425" +istanbul-lib-report@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz#f2a657fc6282f96170aaf281eb30a458f7f4170c" + integrity sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw== dependencies: - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.2.1" mkdirp "^0.5.1" path-parse "^1.0.5" supports-color "^3.1.2" -istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz#750578602435f28a0c04ee6d7d9e0f2960e62c1c" +istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz#37b9ff661580f8fca11232752ee42e08c6675d8f" + integrity sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg== dependencies: debug "^3.1.0" - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.2.1" mkdirp "^0.5.1" rimraf "^2.6.1" source-map "^0.5.3" -istanbul-reports@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.3.tgz#3b9e1e8defb6d18b1d425da8e8b32c5a163f2d10" +istanbul-reports@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.1.tgz#97e4dbf3b515e8c484caea15d6524eebd3ff4e1a" + integrity sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw== dependencies: handlebars "^4.0.3" isurl@^1.0.0-alpha5: version "1.0.0" resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" + integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== dependencies: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" -items@2.x.x: - version "2.1.1" - resolved "https://registry.yarnpkg.com/items/-/items-2.1.1.tgz#8bd16d9c83b19529de5aea321acaada78364a198" - -iterall@1.1.3, iterall@^1.1.1, iterall@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.1.3.tgz#1cbbff96204056dde6656e2ed2e2226d0e6d72c9" - -iterall@^1.2.2: +iterall@^1.1.3, iterall@^1.2.1, iterall@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" + integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA== jest-changed-files@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-21.2.0.tgz#5dbeecad42f5d88b482334902ce1cba6d9798d29" + integrity sha512-+lCNP1IZLwN1NOIvBcV5zEL6GENK6TXrDj4UxWIeLvIsIDa+gf6J7hkqsW2qVVt/wvH65rVvcPwqXdps5eclTQ== dependencies: throat "^4.0.0" jest-cli@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-21.2.1.tgz#9c528b6629d651911138d228bdb033c157ec8c00" + integrity sha512-T1BzrbFxDIW/LLYQqVfo94y/hhaj1NzVQkZgBumAC+sxbjMROI7VkihOdxNR758iYbQykL2ZOWUBurFgkQrzdg== dependencies: ansi-escapes "^3.0.0" chalk "^2.0.1" @@ -4006,6 +5659,7 @@ jest-cli@^21.2.1: jest-config@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-21.2.1.tgz#c7586c79ead0bcc1f38c401e55f964f13bf2a480" + integrity sha512-fJru5HtlD/5l2o25eY9xT0doK3t2dlglrqoGpbktduyoI0T5CwuB++2YfoNZCrgZipTwPuAGonYv0q7+8yDc/A== dependencies: chalk "^2.0.1" glob "^7.1.1" @@ -4022,6 +5676,7 @@ jest-config@^21.2.1: jest-diff@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-21.2.1.tgz#46cccb6cab2d02ce98bc314011764bb95b065b4f" + integrity sha512-E5fu6r7PvvPr5qAWE1RaUwIh/k6Zx/3OOkZ4rk5dBJkEWRrUuSgbMt2EO8IUTPTd6DOqU3LW6uTIwX5FRvXoFA== dependencies: chalk "^2.0.1" diff "^3.2.0" @@ -4031,10 +5686,12 @@ jest-diff@^21.2.1: jest-docblock@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414" + integrity sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw== jest-environment-jsdom@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-21.2.1.tgz#38d9980c8259b2a608ec232deee6289a60d9d5b4" + integrity sha512-mecaeNh0eWmzNrUNMWARysc0E9R96UPBamNiOCYL28k7mksb1d0q6DD38WKP7ABffjnXyUWJPVaWRgUOivwXwg== dependencies: jest-mock "^21.2.0" jest-util "^21.2.1" @@ -4043,6 +5700,7 @@ jest-environment-jsdom@^21.2.1: jest-environment-node@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-21.2.1.tgz#98c67df5663c7fbe20f6e792ac2272c740d3b8c8" + integrity sha512-R211867wx9mVBVHzrjGRGTy5cd05K7eqzQl/WyZixR/VkJ4FayS8qkKXZyYnwZi6Rxo6WEV81cDbiUx/GfuLNw== dependencies: jest-mock "^21.2.0" jest-util "^21.2.1" @@ -4050,10 +5708,12 @@ jest-environment-node@^21.2.1: jest-get-type@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-21.2.0.tgz#f6376ab9db4b60d81e39f30749c6c466f40d4a23" + integrity sha512-y2fFw3C+D0yjNSDp7ab1kcd6NUYfy3waPTlD8yWkAtiocJdBRQqNoRqVfMNxgj+IjT0V5cBIHJO0z9vuSSZ43Q== jest-haste-map@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-21.2.0.tgz#1363f0a8bb4338f24f001806571eff7a4b2ff3d8" + integrity sha512-5LhsY/loPH7wwOFRMs+PT4aIAORJ2qwgbpMFlbWbxfN0bk3ZCwxJ530vrbSiTstMkYLao6JwBkLhCJ5XbY7ZHw== dependencies: fb-watchman "^2.0.0" graceful-fs "^4.1.11" @@ -4065,6 +5725,7 @@ jest-haste-map@^21.2.0: jest-jasmine2@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-21.2.1.tgz#9cc6fc108accfa97efebce10c4308548a4ea7592" + integrity sha512-lw8FXXIEekD+jYNlStfgNsUHpfMWhWWCgHV7n0B7mA/vendH7vBFs8xybjQsDzJSduptBZJHqQX9SMssya9+3A== dependencies: chalk "^2.0.1" expect "^21.2.1" @@ -4078,6 +5739,7 @@ jest-jasmine2@^21.2.1: jest-matcher-utils@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-21.2.1.tgz#72c826eaba41a093ac2b4565f865eb8475de0f64" + integrity sha512-kn56My+sekD43dwQPrXBl9Zn9tAqwoy25xxe7/iY4u+mG8P3ALj5IK7MLHZ4Mi3xW7uWVCjGY8cm4PqgbsqMCg== dependencies: chalk "^2.0.1" jest-get-type "^21.2.0" @@ -4086,6 +5748,7 @@ jest-matcher-utils@^21.2.1: jest-message-util@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-21.2.1.tgz#bfe5d4692c84c827d1dcf41823795558f0a1acbe" + integrity sha512-EbC1X2n0t9IdeMECJn2BOg7buOGivCvVNjqKMXTzQOu7uIfLml+keUfCALDh8o4rbtndIeyGU8/BKfoTr/LVDQ== dependencies: chalk "^2.0.1" micromatch "^2.3.11" @@ -4094,20 +5757,24 @@ jest-message-util@^21.2.1: jest-mock@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-21.2.0.tgz#7eb0770e7317968165f61ea2a7281131534b3c0f" + integrity sha512-aZDfyVf0LEoABWiY6N0d+O963dUQSyUa4qgzurHR3TBDPen0YxKCJ6l2i7lQGh1tVdsuvdrCZ4qPj+A7PievCw== jest-regex-util@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-21.2.0.tgz#1b1e33e63143babc3e0f2e6c9b5ba1eb34b2d530" + integrity sha512-BKQ1F83EQy0d9Jen/mcVX7D+lUt2tthhK/2gDWRgLDJRNOdRgSp1iVqFxP8EN1ARuypvDflRfPzYT8fQnoBQFQ== jest-resolve-dependencies@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-21.2.0.tgz#9e231e371e1a736a1ad4e4b9a843bc72bfe03d09" + integrity sha512-ok8ybRFU5ScaAcfufIQrCbdNJSRZ85mkxJ1EhUp8Bhav1W1/jv/rl1Q6QoVQHObNxmKnbHVKrfLZbCbOsXQ+bQ== dependencies: jest-regex-util "^21.2.0" jest-resolve@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-21.2.0.tgz#068913ad2ba6a20218e5fd32471f3874005de3a6" + integrity sha512-vefQ/Lr+VdNvHUZFQXWtOqHX3HEdOc2MtSahBO89qXywEbUxGPB9ZLP9+BHinkxb60UT2Q/tTDOS6rYc6Mwigw== dependencies: browser-resolve "^1.11.2" chalk "^2.0.1" @@ -4116,6 +5783,7 @@ jest-resolve@^21.2.0: jest-runner@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-21.2.1.tgz#194732e3e518bfb3d7cbfc0fd5871246c7e1a467" + integrity sha512-Anb72BOQlHqF/zETqZ2K20dbYsnqW/nZO7jV8BYENl+3c44JhMrA8zd1lt52+N7ErnsQMd2HHKiVwN9GYSXmrg== dependencies: jest-config "^21.2.1" jest-docblock "^21.2.0" @@ -4131,6 +5799,7 @@ jest-runner@^21.2.1: jest-runtime@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-21.2.1.tgz#99dce15309c670442eee2ebe1ff53a3cbdbbb73e" + integrity sha512-6omlpA3+NSE+rHwD0PQjNEjZeb2z+oRmuehMfM1tWQVum+E0WV3pFt26Am0DUfQkkPyTABvxITRjCUclYgSOsA== dependencies: babel-core "^6.0.0" babel-jest "^21.2.0" @@ -4153,6 +5822,7 @@ jest-runtime@^21.2.1: jest-snapshot@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-21.2.1.tgz#29e49f16202416e47343e757e5eff948c07fd7b0" + integrity sha512-bpaeBnDpdqaRTzN8tWg0DqOTo2DvD3StOemxn67CUd1p1Po+BUpvePAp44jdJ7Pxcjfg+42o4NHw1SxdCA2rvg== dependencies: chalk "^2.0.1" jest-diff "^21.2.1" @@ -4164,6 +5834,7 @@ jest-snapshot@^21.2.1: jest-util@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-21.2.1.tgz#a274b2f726b0897494d694a6c3d6a61ab819bb78" + integrity sha512-r20W91rmHY3fnCoO7aOAlyfC51x2yeV3xF+prGsJAUsYhKeV670ZB8NO88Lwm7ASu8SdH0S+U+eFf498kjhA4g== dependencies: callsites "^2.0.0" chalk "^2.0.1" @@ -4176,42 +5847,58 @@ jest-util@^21.2.1: jest-validate@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-21.2.1.tgz#cc0cbca653cd54937ba4f2a111796774530dd3c7" + integrity sha512-k4HLI1rZQjlU+EC682RlQ6oZvLrE5SCh3brseQc24vbZTxzT/k/3urar5QMCVgjadmSO7lECeGdc6YxnM3yEGg== dependencies: chalk "^2.0.1" jest-get-type "^21.2.0" leven "^2.1.0" pretty-format "^21.2.1" -jest@^21.1.0, jest@^21.2.1: +jest@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest/-/jest-21.2.1.tgz#c964e0b47383768a1438e3ccf3c3d470327604e1" + integrity sha512-mXN0ppPvWYoIcC+R+ctKxAJ28xkt/Z5Js875padm4GbgUn6baeR5N4Ng6LjatIRpUQDZVJABT7Y4gucFjPryfw== dependencies: jest-cli "^21.2.1" jmespath@0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= -joi@^10.6.0: - version "10.6.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-10.6.0.tgz#52587f02d52b8b75cdb0c74f0b164a191a0e1fc2" +joi@^13.1.2: + version "13.7.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-13.7.0.tgz#cfd85ebfe67e8a1900432400b4d03bbd93fb879f" + integrity sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q== dependencies: - hoek "4.x.x" - isemail "2.x.x" - items "2.x.x" - topo "2.x.x" + hoek "5.x.x" + isemail "3.x.x" + topo "3.x.x" js-base64@^2.1.9: - version "2.4.0" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.0.tgz#9e566fee624751a1d720c966cd6226d29d4025aa" + version "2.4.9" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.9.tgz#748911fb04f48a60c4771b375cac45a80df11c03" + integrity sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ== + +js-levenshtein@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.4.tgz#3a56e3cbf589ca0081eb22cd9ba0b1290a16d26e" + integrity sha512-PxfGzSs0ztShKrUYPIn5r0MtyAhYcCwmndozzpz8YObbPnD1jFxzlBGbRnX2mIu6Z13xN6+PTu05TQFnZFlzow== + +"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-tokens@^3.0.0, js-tokens@^3.0.2: +js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= js-yaml@^3.7.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" + version "3.12.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" + integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -4219,10 +5906,12 @@ js-yaml@^3.7.0: jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= jsdom@^9.12.0: version "9.12.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" + integrity sha1-6MVG//ywbADUgzyoRBD+1/igl9Q= dependencies: abab "^1.0.3" acorn "^4.0.4" @@ -4247,60 +5936,97 @@ jsdom@^9.12.0: jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= + +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-bigint@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-0.1.4.tgz#b5d40b8a9009e92f157f7c079db097001830e01e" + integrity sha1-tdQLipAJ6S8Vf3wHnbCXABgw4B4= + dependencies: + bignumber.js "~1.1.1" -json-loader@^0.5.4: +json-loader@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" + integrity sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w== json-mask@^0.3.8: version "0.3.8" resolved "https://registry.yarnpkg.com/json-mask/-/json-mask-0.3.8.tgz#2d66415de14b0e8bc6c1514554a90bfca8356941" + integrity sha1-LWZBXeFLDovGwVFFVKkL/Kg1aUE= -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" +json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +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.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= json-stable-stringify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= dependencies: jsonify "~0.0.0" -json-stringify-pretty-compact@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.1.0.tgz#4fa5b898f61a287d64828691baa822a41f3ad5ab" +json-stringify-pretty-compact@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.2.0.tgz#0bc316b5e6831c07041fc35612487fb4e9ab98b8" + integrity sha512-/11Pj1OyX814QMKO7K8l85SHPTr/KsFxHp8GE2zVa0BtJgGimDjXHfM3FhC7keQdWDea7+nXf+f1de7ATZcZkQ== 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= json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= + +json5@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850" + integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ== + dependencies: + minimist "^1.2.0" jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= optionalDependencies: graceful-fs "^4.1.6" jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= -jsonwebtoken@^8.0.1: - version "8.1.0" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.1.0.tgz#c6397cd2e5fd583d65c007a83dc7bb78e6982b83" +jsonwebtoken@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz#8757f7b4cb7440d86d5e2f3becefa70536c8e46a" + integrity sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg== dependencies: - jws "^3.1.4" + jws "^3.1.5" lodash.includes "^4.3.0" lodash.isboolean "^3.0.3" lodash.isinteger "^4.0.4" @@ -4308,115 +6034,107 @@ jsonwebtoken@^8.0.1: lodash.isplainobject "^4.0.6" lodash.isstring "^4.0.1" lodash.once "^4.0.0" - ms "^2.0.0" - xtend "^4.0.1" + ms "^2.1.1" jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= dependencies: assert-plus "1.0.0" extsprintf "1.3.0" json-schema "0.2.3" verror "1.10.0" -jwa@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5" +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + integrity sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw== dependencies: - base64url "2.0.0" buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.9" + ecdsa-sig-formatter "1.0.10" safe-buffer "^5.0.1" -jws@^3.1.3, jws@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2" +jws@^3.1.3, jws@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + integrity sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ== dependencies: - base64url "^2.0.0" - jwa "^1.1.4" + jwa "^1.1.5" safe-buffer "^5.0.1" -keycode@^2.1.2: - version "2.1.9" - resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa" - -keygrip@^1.0.2, keygrip@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.2.tgz#ad3297c557069dea8bcfe7a4fa491b75c5ddeb91" +keygrip@^1.0.3, keygrip@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.3.tgz#399d709f0aed2bab0a059e0cdd3a5023a053e1dc" + integrity sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g== kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= dependencies: is-buffer "^1.1.5" kind-of@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= dependencies: is-buffer "^1.1.5" -kind-of@^5.0.0, kind-of@^5.0.2: +kind-of@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== kind-of@^6.0.0, kind-of@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - -latest-version@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b" - dependencies: - package-json "^2.0.0" + integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== latest-version@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" + integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= dependencies: package-json "^4.0.0" -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" - -lazy-cache@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" - dependencies: - set-getter "^0.1.0" - -lazy-req@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac" - lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= dependencies: invert-kv "^1.0.0" leven@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" + integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA= 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" -linkify-it@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f" +linkify-it@^2.0.3, linkify-it@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.1.0.tgz#c4caf38a6cd7ac2212ef3c7d2bde30a91561f9db" + integrity sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg== dependencies: uc.micro "^1.0.1" +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= dependencies: graceful-fs "^4.1.2" parse-json "^2.2.0" @@ -4427,6 +6145,7 @@ load-json-file@^1.0.0: load-json-file@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= dependencies: graceful-fs "^4.1.2" parse-json "^2.2.0" @@ -4436,14 +6155,17 @@ load-json-file@^2.0.0: load-script@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/load-script/-/load-script-1.0.0.tgz#0491939e0bee5643ee494a7e3da3d2bac70c6ca4" + integrity sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ= loader-runner@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" + version "2.3.1" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.1.tgz#026f12fe7c3115992896ac02ba022ba92971b979" + integrity sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw== loader-utils@0.2.x: version "0.2.17" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" + integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g= dependencies: big.js "^3.1.3" emojis-list "^2.0.0" @@ -4453,157 +6175,192 @@ loader-utils@0.2.x: loader-utils@^1.0.2, loader-utils@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" + integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0= dependencies: big.js "^3.1.3" emojis-list "^2.0.0" json5 "^0.5.0" -localstorage-memory@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/localstorage-memory/-/localstorage-memory-1.0.2.tgz#cd4a8f210e55dd519c929f4b4cc82829b58f9a51" +localstorage-memory@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/localstorage-memory/-/localstorage-memory-1.0.3.tgz#566b37968fe0c4d76ba36a6da564fa613945ca72" + integrity sha512-t9P8WB6DcVttbw/W4PIE8HOqum8Qlvx5SjR6oInwR9Uia0EEmyUeBh7S+weKByW+l/f45Bj4L/dgZikGFDM6ng== locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= dependencies: p-locate "^2.0.0" path-exists "^3.0.0" -lodash-es@^4.2.0, lodash-es@^4.2.1: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7" +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-es@^4.2.1: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.11.tgz#145ab4a7ac5c5e52a3531fb4f310255a152b4be0" + integrity sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q== lodash._reinterpolate@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= lodash.assign@^4.0.3, lodash.assign@^4.0.6, lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= -lodash.assignin@^4.0.9: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" - -lodash.bind@^4.1.4, lodash.bind@^4.2.1: +lodash.bind@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" + integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU= lodash.clone@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" + integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= -lodash.defaults@^4.0.1, lodash.defaults@^4.2.0: +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + +lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= lodash.difference@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= -lodash.filter@^4.4.0: +lodash.every@^4.6.0: version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" + resolved "https://registry.yarnpkg.com/lodash.every/-/lodash.every-4.6.0.tgz#eb89984bebc4364279bb3aefbbd1ca19bfa6c6a7" + integrity sha1-64mYS+vENkJ5uzrvu9HKGb+mxqc= lodash.flatten@^4.2.0, lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= -lodash.foreach@^4.3.0, lodash.foreach@^4.5.0: +lodash.foreach@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= lodash.intersection@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.intersection/-/lodash.intersection-4.4.0.tgz#0a11ba631d0e95c23c7f2f4cbb9a692ed178e705" + integrity sha1-ChG6Yx0OlcI8fy9Mu5ppLtF45wU= lodash.isboolean@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= lodash.isnumber@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - -lodash.isobject@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= lodash.keys@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" + integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU= -lodash.map@^4.4.0: +lodash.map@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" + integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= -lodash.merge@^4.4.0: +lodash.maxby@^4.6.0: version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" + resolved "https://registry.yarnpkg.com/lodash.maxby/-/lodash.maxby-4.6.0.tgz#082240068f3c7a227aa00a8380e4f38cf0786e3d" + integrity sha1-CCJABo88eiJ6oAqDgOTzjPB4bj0= lodash.noop@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" + integrity sha1-OBiPTWUKOkdCWEObluxFsyYXEzw= lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= lodash.partial@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.partial/-/lodash.partial-4.2.1.tgz#49f3d8cfdaa3bff8b3a91d127e923245418961d4" + integrity sha1-SfPYz9qjv/izqR0SfpIyRUGJYdQ= -lodash.pick@^4.2.1, lodash.pick@^4.4.0: +lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" - -lodash.reduce@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" - -lodash.reject@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" + integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= lodash.sample@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.sample/-/lodash.sample-4.2.1.tgz#5e4291b0c753fa1abeb0aab8fb29df1b66f07f6d" + integrity sha1-XkKRsMdT+hq+sKq4+ynfG2bwf20= lodash.shuffle@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz#145b5053cf875f6f5c2a33f48b6e9948c6ec7b4b" - -lodash.some@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" + integrity sha1-FFtQU8+HX29cKjP0i26ZSMbse0s= lodash.template@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0" + integrity sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A= dependencies: lodash._reinterpolate "~3.0.0" lodash.templatesettings "^4.0.0" @@ -4611,110 +6368,121 @@ lodash.template@^4.4.0: lodash.templatesettings@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316" + integrity sha1-K01OlbpEDZFf8IvImeRVNmZxMxY= dependencies: lodash._reinterpolate "~3.0.0" lodash.values@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" + integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= -lodash@^4.0.0: - version "4.17.5" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" - -lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" - -long@~3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" +lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.7.0: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== -longest@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== longjohn@^0.2.12: version "0.2.12" resolved "https://registry.yarnpkg.com/longjohn/-/longjohn-0.2.12.tgz#7ca7446b083655c377e7512213dc754d52a64a7e" + integrity sha1-fKdEawg2VcN351EiE9x1TVKmSn4= dependencies: source-map-support "0.3.2 - 1.0.0" -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.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" + js-tokens "^3.0.0 || ^4.0.0" loud-rejection@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= dependencies: currently-unhandled "^0.4.1" signal-exit "^3.0.0" lowercase-keys@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== -lru-cache@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" +lru-cache@^4.0.1, lru-cache@^4.1.3: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== dependencies: pseudomap "^1.0.2" yallist "^2.1.2" -lsmod@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b" +lru-cache@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +macos-release@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.0.0.tgz#7dddf4caf79001a851eb4fba7fb6034f251276ab" + integrity sha512-iCM3ZGeqIzlrH7KxYK+fphlJpCCczyHXc+HhRVbEu9uNTCrzYJjvvtefzeKTCVHd5AP/aD/fzC80JZ4ZP+dQ/A== make-dir@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51" + version "1.3.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" + integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== dependencies: pify "^3.0.0" makeerror@1.0.x: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= dependencies: tmpl "1.0.x" map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - -map-stream@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= dependencies: object-visit "^1.0.0" -"match-stream@>= 0.0.2 < 1": - version "0.0.2" - resolved "https://registry.yarnpkg.com/match-stream/-/match-stream-0.0.2.tgz#99eb050093b34dffade421b9ac0b410a9cfa17cf" - dependencies: - buffers "~0.1.1" - readable-stream "~1.0.0" +math-random@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" + integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w= md5.js@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== dependencies: hash-base "^3.0.0" inherits "^2.0.1" + safe-buffer "^5.1.2" md5@^2.1.0, md5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= dependencies: charenc "~0.0.1" crypt "~0.0.1" @@ -4723,16 +6491,19 @@ md5@^2.1.0, md5@^2.2.1: media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= mem@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y= dependencies: mimic-fn "^1.0.0" memory-fs@^0.4.0, memory-fs@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= dependencies: errno "^0.1.3" readable-stream "^2.0.1" @@ -4740,6 +6511,7 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= dependencies: camelcase-keys "^2.0.0" decamelize "^1.1.2" @@ -4755,22 +6527,27 @@ meow@^3.7.0: merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= -merge@1.2.0, merge@^1.1.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" +merge@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" + integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== mersenne-twister@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz#f916618ee43d7179efcf641bec4531eb9670978a" + integrity sha1-+RZhjuQ9cXnvz2Qb7EUx65Zwl4o= methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromatch@^2.1.5, micromatch@^2.3.11: +micromatch@^2.3.11: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU= dependencies: arr-diff "^2.0.0" array-unique "^0.2.1" @@ -4786,174 +6563,267 @@ micromatch@^2.1.5, micromatch@^2.3.11: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.1.4: - version "3.1.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.5.tgz#d05e168c206472dfbca985bfef4f57797b4cd4ba" +micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== dependencies: arr-diff "^4.0.0" array-unique "^0.3.2" - braces "^2.3.0" - define-property "^1.0.0" - extend-shallow "^2.0.1" - extglob "^2.0.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" fragment-cache "^0.2.1" - kind-of "^6.0.0" - nanomatch "^1.2.5" + kind-of "^6.0.2" + nanomatch "^1.2.9" object.pick "^1.3.0" regex-not "^1.0.0" snapdragon "^0.8.1" - to-regex "^3.0.1" + to-regex "^3.0.2" miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== dependencies: bn.js "^4.0.0" brorand "^1.0.1" -"mime-db@>= 1.30.0 < 2": - version "1.32.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.32.0.tgz#485b3848b01a3cda5f968b4882c0771e58e09414" - -mime-db@~1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" +"mime-db@>= 1.36.0 < 2", mime-db@~1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" + integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== -mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: - version "2.1.17" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" +mime-types@^2.1.12, mime-types@~2.1.18, mime-types@~2.1.19: + version "2.1.21" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" + integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== dependencies: - mime-db "~1.30.0" + mime-db "~1.37.0" mime@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" - -mime@^1.2.11: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== mimic-fn@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== mimic-response@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== min-document@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= dependencies: dom-walk "^0.1.0" -minimalistic-assert@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: +minimatch@^3.0.3, 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@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= + +minipass@^2.2.1, minipass@^2.3.4: + version "2.3.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" + integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" + integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== + dependencies: + minipass "^2.2.1" + +mississippi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" + integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== + dependencies: + concat-stream "^1.5.0" + duplexify "^3.4.2" + end-of-stream "^1.1.0" + flush-write-stream "^1.0.0" + from2 "^2.1.0" + parallel-transform "^1.1.0" + pump "^3.0.0" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" mixin-deep@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.0.tgz#47a8732ba97799457c8c1eca28f95132d7e8150a" + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" + integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ== dependencies: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@0.5, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: +"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: minimist "0.0.8" -moment-timezone@^0.5.0: - version "0.5.14" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.14.tgz#4eb38ff9538b80108ba467a458f3ed4268ccfcb1" +moment-timezone@^0.5.23: + version "0.5.23" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" + integrity sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w== dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@^2.15.2, moment@^2.18.1: - version "2.20.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd" +"moment@>= 2.9.0", moment@^2.15.2, moment@^2.20.1: + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= + +moment@^2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" + integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== + +move-concurrently@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" + integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= + dependencies: + aproba "^1.1.1" + copy-concurrently "^1.0.0" + fs-write-stream-atomic "^1.0.8" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.3" 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.0.0: +ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -nan@^2.3.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" +nan@^2.9.2: + version "2.11.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766" + integrity sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA== -nanomatch@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.7.tgz#53cd4aa109ff68b7f869591fdc9d10daeeea3e79" +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== dependencies: arr-diff "^4.0.0" array-unique "^0.3.2" - define-property "^1.0.0" - extend-shallow "^2.0.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" fragment-cache "^0.2.1" - is-odd "^1.0.0" - kind-of "^5.0.2" + is-windows "^1.0.2" + kind-of "^6.0.2" object.pick "^1.3.0" regex-not "^1.0.0" snapdragon "^0.8.1" to-regex "^3.0.1" -natives@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.1.tgz#011acce1f7cbd87f7ba6b3093d6cd9392be1c574" - 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= + +needle@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" + integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= + +neo-async@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835" + integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA== + +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== + +nocache@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.0.0.tgz#202b48021a0c4cbde2df80de15a17443c8b43980" + integrity sha1-ICtIAhoMTL3i34DeFaF0Q8i0OYA= node-env-file@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/node-env-file/-/node-env-file-0.1.8.tgz#fccb7b050f735b5a33da9eb937cf6f1ab457fb69" + integrity sha1-/Mt7BQ9zW1oz2p65N89vGrRX+2k= node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== dependencies: encoding "^0.1.11" is-stream "^1.0.1" +node-fetch@^2.1.1, node-fetch@^2.1.2, node-fetch@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5" + integrity sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= node-libs-browser@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df" + integrity sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg== dependencies: assert "^1.1.1" browserify-zlib "^0.2.0" @@ -4979,58 +6849,66 @@ node-libs-browser@^2.0.0: util "^0.10.3" vm-browserify "0.0.4" -node-localstorage@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/node-localstorage/-/node-localstorage-1.3.0.tgz#2e436aae8dcc9ace97b43c65c16c0d577be0a55c" +node-localstorage@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-localstorage/-/node-localstorage-1.3.1.tgz#3177ef42837f398aee5dd75e319b281e40704243" + integrity sha512-NMWCSWWc6JbHT5PyWlNT2i8r7PgGYXVntmKawY83k/M0UJScZ5jirb61TLnqKwd815DfBQu+lR3sRw08SPzIaQ== dependencies: write-file-atomic "^1.1.4" node-notifier@^5.0.2: - version "5.2.1" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.2.1.tgz#fa313dd08f5517db0e2502e5758d664ac69f9dea" + version "5.3.0" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.3.0.tgz#c77a4a7b84038733d5fb351aafd8a268bfe19a01" + integrity sha512-AhENzCSGZnZJgBARsUjnQ7DnZbzyP+HxlVXuD0xqAnvL8q+OqtSX7lGg9e8nHzwXkMMXNdVeqq4E2M3EUAqX6Q== dependencies: growly "^1.3.0" - semver "^5.4.1" + semver "^5.5.0" shellwords "^0.1.1" which "^1.3.0" -node-pre-gyp@^0.6.39: - version "0.6.39" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" +node-pre-gyp@^0.10.0: + version "0.10.3" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" + integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A== dependencies: detect-libc "^1.0.2" - hawk "3.1.3" mkdirp "^0.5.1" + needle "^2.2.1" nopt "^4.0.1" + npm-packlist "^1.1.6" npmlog "^4.0.2" - rc "^1.1.7" - request "2.81.0" + rc "^1.2.7" rimraf "^2.6.1" semver "^5.3.0" - tar "^2.2.1" - tar-pack "^3.4.0" + tar "^4" -node-status-codes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" +node-releases@^1.0.5: + version "1.1.1" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.1.tgz#8fff8aea1cfcad1fb4205f805149054fbf73cafd" + integrity sha512-2UXrBr6gvaebo5TNF84C66qyJJ6r0kxBObgZIDX3D3/mt1ADKiHux3NJPWisq0wxvJJdkjECH+9IIKYViKj71Q== + dependencies: + semver "^5.3.0" -nodemon@^1.11.0: - version "1.14.11" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.14.11.tgz#cc0009dd8d82f126f3aba50ace7e753827a8cebc" +nodemon@^1.18.7: + version "1.18.8" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.8.tgz#eb4c0052dc81395bdc503f3c8ae3cba86ca7146a" + integrity sha512-CgC/JdCf+CT7Z+K6wWaV30t8GU1DPtXpr/6PuXC1/LboXCmUQNKOaz0AEMjoWDTt2AdHOBFxgv41dyC0i79SbA== dependencies: - chokidar "^2.0.0" + chokidar "^2.0.4" debug "^3.1.0" ignore-by-default "^1.0.1" minimatch "^3.0.4" - pstree.remy "^1.1.0" - semver "^5.4.1" + pstree.remy "^1.1.3" + semver "^5.5.0" + supports-color "^5.2.0" touch "^3.1.0" - undefsafe "^2.0.1" - update-notifier "^2.3.0" + undefsafe "^2.0.2" + update-notifier "^2.5.0" nopt@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= dependencies: abbrev "1" osenv "^0.1.4" @@ -5038,94 +6916,130 @@ nopt@^4.0.1: nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= dependencies: abbrev "1" normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw== dependencies: hosted-git-info "^2.1.4" is-builtin-module "^1.0.0" semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: +normalize-path@^2.0.1, normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= dependencies: remove-trailing-separator "^1.0.1" -now-env@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/now-env/-/now-env-3.0.2.tgz#b0725023a799240c5c7e4a43515eb0a65c2c3663" +now-env@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/now-env/-/now-env-3.1.0.tgz#e0198b67279d387229cfd4b25de4c2fc7156943f" + integrity sha512-f+jXC+UkoxD/g9Nlig99Bxswoh7UUuQxw0EsPfuueHnVpVE0LfgQ4el5dxY4TSXwrL9mEF9GGm0gb7r3K8r/ug== + +npm-bundled@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" + integrity sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g== + +npm-packlist@^1.1.6: + version "1.1.12" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a" + integrity sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= dependencies: path-key "^2.0.0" npmlog@^4.0.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== dependencies: are-we-there-yet "~1.1.2" console-control-strings "~1.1.0" gauge "~2.7.3" set-blocking "~2.0.0" -nth-check@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" - dependencies: - boolbase "~1.0.0" - number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= "nwmatcher@>= 1.3.9 < 2.0.0": - version "1.4.3" - resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c" + version "1.4.4" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e" + integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ== -oauth-sign@~0.8.1, oauth-sign@~0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +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== oauth@0.9.x: version "0.9.15" resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" + integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= + +obj-extend@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/obj-extend/-/obj-extend-0.1.0.tgz#bb448a4775fb95eb34a781f908bbac2df23dbb5b" + integrity sha1-u0SKR3X7les0p4H5CLusLfI9u1s= object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= dependencies: copy-descriptor "^0.1.0" define-property "^0.2.5" kind-of "^3.0.3" -object-keys@^1.0.11, object-keys@^1.0.8, object-keys@~1.0.0: - version "1.0.11" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@~1.0.0: + version "1.0.12" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" + integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== object-path@^0.11.4: version "0.11.4" resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949" + integrity sha1-NwrnUvvzfePqcKhhwju6iRVpGUk= object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= dependencies: isobject "^3.0.0" +object.getownpropertydescriptors@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" + integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.1" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo= dependencies: for-own "^0.1.4" is-extendable "^0.1.1" @@ -5133,12 +7047,14 @@ object.omit@^2.0.0: object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= dependencies: isobject "^3.0.1" -offline-plugin@^4.8.4: - version "4.9.0" - resolved "https://registry.yarnpkg.com/offline-plugin/-/offline-plugin-4.9.0.tgz#0874960c0cb0c249f96b7cfc674217934660ed5c" +offline-plugin@^4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/offline-plugin/-/offline-plugin-4.9.1.tgz#e97a6be3118b4dc360e06ed4bc473f10f2de73b6" + integrity sha1-6Xpr4xGLTcNg4G7UvEc/EPLec7Y= dependencies: deep-extend "^0.4.0" ejs "^2.3.4" @@ -5146,34 +7062,29 @@ offline-plugin@^4.8.4: minimatch "^3.0.3" slash "^1.0.0" -on-finished@^2.3.0, on-finished@~2.3.0: +on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= dependencies: ee-first "1.1.1" on-headers@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" + integrity sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c= -once@^1.3.0, once@^1.3.3, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" -optics-agent@^1.1.2: - version "1.1.9" - resolved "https://registry.yarnpkg.com/optics-agent/-/optics-agent-1.1.9.tgz#5cdf3f88ac2fb664e4f0b7a11d38c5e780c4deb8" - dependencies: - graphql-tools "^1 || ^2" - on-finished "^2.3.0" - protobufjs-no-cli "^5.0.1" - request "^2.74.0" - optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= dependencies: minimist "~0.0.1" wordwrap "~0.0.2" @@ -5181,6 +7092,7 @@ optimist@^0.6.1: optionator@^0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= dependencies: deep-is "~0.1.3" fast-levenshtein "~2.0.4" @@ -5192,86 +7104,126 @@ optionator@^0.8.1: os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= os-locale@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= dependencies: lcid "^1.0.0" os-locale@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" + integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA== dependencies: execa "^0.7.0" lcid "^1.0.0" mem "^1.1.0" +os-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.0.0.tgz#e1434dbfddb8e74b44c98b56797d951b7648a5d9" + integrity sha512-7c74tib2FsdFbQ3W+qj8Tyd1R3Z6tuVRNNxXjJcZ4NgjIEQU9N/prVMqcW29XZPXGACqaXN3jq58/6hoaoXH6g== + dependencies: + macos-release "^2.0.0" + windows-release "^3.1.0" + os-shim@^0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" + integrity sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc= os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@^0.1.0, osenv@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== dependencies: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -"over@>= 0.0.5 < 1": - version "0.0.5" - resolved "https://registry.yarnpkg.com/over/-/over-0.0.5.tgz#f29852e70fd7e25f360e013a8ec44c82aedb5708" - p-cancelable@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" + integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw== p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= p-limit@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== dependencies: p-try "^1.0.0" +p-limit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.0.0.tgz#e624ed54ee8c460a778b3c9f3670496ff8a57aec" + integrity sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A== + dependencies: + p-try "^2.0.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= dependencies: p-limit "^1.1.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-map@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" + integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA== + p-timeout@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" + integrity sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y= + dependencies: + p-finally "^1.0.0" + +p-timeout@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" + integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== dependencies: p-finally "^1.0.0" p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= -package-json@^2.0.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-2.4.0.tgz#0d15bd67d1cbbddbb2ca222ff2edb86bcb31a8bb" - dependencies: - got "^5.0.0" - registry-auth-token "^3.0.1" - registry-url "^3.0.3" - semver "^5.1.0" +p-try@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" + integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ== package-json@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" + integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= dependencies: got "^6.7.1" registry-auth-token "^3.0.1" @@ -5279,12 +7231,23 @@ package-json@^4.0.0: semver "^5.1.0" pako@~1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" + version "1.0.7" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.7.tgz#2473439021b57f1516c82f58be7275ad8ef1bb27" + integrity sha512-3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ== + +parallel-transform@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06" + integrity sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY= + dependencies: + cyclist "~0.2.2" + inherits "^2.0.3" + readable-stream "^2.1.5" parse-asn1@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712" + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" + integrity sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw== dependencies: asn1.js "^4.0.0" browserify-aes "^1.0.0" @@ -5295,51 +7258,60 @@ parse-asn1@^5.0.0: parse-glob@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw= dependencies: glob-base "^0.3.0" is-dotfile "^1.0.0" is-extglob "^1.0.0" is-glob "^2.0.0" -parse-json@^2.1.0, parse-json@^2.2.0: +parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= dependencies: error-ex "^1.2.0" parse5@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" + integrity sha1-m387DeMr543CQBsXVzzK8Pb1nZQ= parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M= pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= passport-facebook@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/passport-facebook/-/passport-facebook-2.1.1.tgz#c39d0b52ae4d59163245a4e21a7b9b6321303311" + integrity sha1-w50LUq5NWRYyRaTiGnubYyEwMxE= dependencies: passport-oauth2 "1.x.x" passport-github2@^0.1.10: version "0.1.11" resolved "https://registry.yarnpkg.com/passport-github2/-/passport-github2-0.1.11.tgz#c92b56f3c38a44e766aac7e9e7c1384c5e93c999" + integrity sha1-yStW88OKROdmqsfp58E4TF6TyZk= dependencies: passport-oauth2 "1.x.x" passport-google-oauth2@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/passport-google-oauth2/-/passport-google-oauth2-0.1.6.tgz#dfd7016ac7449fe27cfeb252ae974afc23257a0d" + integrity sha1-39cBasdEn+J8/rJSrpdK/CMleg0= dependencies: passport-oauth2 "^1.1.2" passport-oauth1@1.x.x: version "1.1.0" resolved "https://registry.yarnpkg.com/passport-oauth1/-/passport-oauth1-1.1.0.tgz#a7de988a211f9cf4687377130ea74df32730c918" + integrity sha1-p96YiiEfnPRoc3cTDqdN8ycwyRg= dependencies: oauth "0.9.x" passport-strategy "1.x.x" @@ -5348,6 +7320,7 @@ passport-oauth1@1.x.x: passport-oauth2@1.x.x, passport-oauth2@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.4.0.tgz#f62f81583cbe12609be7ce6f160b9395a27b86ad" + integrity sha1-9i+BWDy+EmCb585vFguTlaJ7hq0= dependencies: oauth "0.9.x" passport-strategy "1.x.x" @@ -5357,10 +7330,12 @@ passport-oauth2@1.x.x, passport-oauth2@^1.1.2: passport-strategy@1.x.x: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= passport-twitter@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/passport-twitter/-/passport-twitter-1.0.4.tgz#01a799e1f760bf2de49f2ba5fba32282f18932d7" + integrity sha1-AaeZ4fdgvy3knyul+6MigvGJMtc= dependencies: passport-oauth1 "1.x.x" xtraverse "0.1.x" @@ -5368,6 +7343,7 @@ passport-twitter@^1.0.4: passport@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/passport/-/passport-0.3.2.tgz#9dd009f915e8fe095b0124a01b8f82da07510102" + integrity sha1-ndAJ+RXo/glbASSgG4+C2gdRAQI= dependencies: passport-strategy "1.x.x" pause "0.0.1" @@ -5375,50 +7351,61 @@ passport@^0.3.2: path-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" + integrity sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo= path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= dependencies: pinkie-promise "^2.0.0" 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, path-is-absolute@^1.0.1: 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-is-inside@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= -path-key@^2.0.0: +path-key@^2.0.0, 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.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= path-to-regexp@^1.0.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= dependencies: isarray "0.0.1" path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= dependencies: graceful-fs "^4.1.2" pify "^2.0.0" @@ -5427,22 +7414,19 @@ path-type@^1.0.0: path-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= dependencies: pify "^2.0.0" -pause-stream@0.0.11: - version "0.0.11" - resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" - dependencies: - through "~2.3" - pause@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10= pbkdf2@^3.0.3: - version "3.0.14" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade" + version "3.0.17" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" + integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" @@ -5450,55 +7434,66 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -performance-now@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" - 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= pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= dependencies: pinkie "^2.0.0" pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= dependencies: find-up "^2.1.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" + +platform@1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444" + integrity sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q== + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= postcss-value-parser@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" - -postmark@^1.4.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/postmark/-/postmark-1.5.0.tgz#6812a35195d14e59922181aba5920f3ceec156b6" - dependencies: - merge "1.2.0" + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== pre-commit@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/pre-commit/-/pre-commit-1.2.2.tgz#dbcee0ee9de7235e57f79c56d7ce94641a69eec6" + integrity sha1-287g7p3nI15X95xW186UZBpp7sY= dependencies: cross-spawn "^5.0.1" spawn-sync "^1.0.15" @@ -5507,143 +7502,207 @@ pre-commit@^1.2.2: 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= prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= pretty-bytes@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9" + integrity sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk= pretty-format@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-21.2.1.tgz#ae5407f3cf21066cd011aa1ba5fce7b6a2eddb36" + integrity sha512-ZdWPGYAnYfcVP8yKA3zFjCn8s4/17TeYH28MXuC8vTp0o21eXjbFGcOAXZEaDaOFJjc3h2qa7HQNHNshhvoh2A== dependencies: ansi-regex "^3.0.0" ansi-styles "^3.2.0" -prismjs@^1.5.0, prismjs@^1.6.0, prismjs@^1.8.1: - version "1.9.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.9.0.tgz#fa3e2d9edc3c3887c1f1f3095d41f1f9b4200f0f" +prismjs@^1.15.0, prismjs@^1.5.0, prismjs@^1.6.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.15.0.tgz#8801d332e472091ba8def94976c8877ad60398d9" + integrity sha512-Lf2JrFYx8FanHrjoV5oL8YHCclLQgbJcVZR+gikGGMqz6ub5QVWDTM6YIwm3BuPxM/LOV+rKns3LssXNLIf+DA== optionalDependencies: - clipboard "^1.7.1" + clipboard "^2.0.0" -private@^0.1.6, private@^0.1.7: +private@^0.1.6, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= process@~0.5.1: version "0.5.2" resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" + integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8= + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= + +promise.prototype.finally@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.0.tgz#66f161b1643636e50e7cf201dc1b84a857f3864e" + integrity sha512-7p/K2f6dI+dM8yjRQEGrTQs5hTQixUAdOGpMEA3+pVxpX5oHKRSKAXyLw9Q9HUWDTdwtoo39dSHGQtN90HcEwQ== + dependencies: + define-properties "^1.1.2" + es-abstract "^1.9.0" + function-bind "^1.1.1" promise@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/promise/-/promise-6.1.0.tgz#2ce729f6b94b45c26891ad0602c5c90e04c6eef6" + integrity sha1-LOcp9rlLRcJoka0GAsXJDgTG7vY= dependencies: asap "~1.0.0" promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== dependencies: asap "~2.0.3" -prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0: - version "15.6.0" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" +prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2: + version "15.6.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" + integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ== dependencies: - fbjs "^0.8.16" loose-envify "^1.3.1" object-assign "^4.1.1" -protobufjs-no-cli@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/protobufjs-no-cli/-/protobufjs-no-cli-5.0.1.tgz#17a527da0bc49f1f974f512d852950be26acbb82" - dependencies: - bytebuffer "~5" - -proxy-addr@~2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" +protobufjs@^6.8.6: + version "6.8.8" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c" + integrity sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.0" + "@types/node" "^10.1.0" + long "^4.0.0" + +proxy-addr@~2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" + integrity sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA== dependencies: forwarded "~0.1.2" - ipaddr.js "1.5.2" + ipaddr.js "1.8.0" prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - -ps-tree@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014" - dependencies: - event-stream "~3.3.0" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -pstree.remy@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.0.tgz#f2af27265bd3e5b32bbfcc10e80bac55ba78688b" - dependencies: - ps-tree "^1.1.0" +psl@^1.1.24, psl@^1.1.28: + version "1.1.29" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" + integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ== + +pstree.remy@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.4.tgz#a03d5dbc06ba639fb6dd4874644c4bad9882ec21" + integrity sha512-3kSyTN/iTJMxtL87idnFgTyOp2vQ6B/49QcHUO26kh2M2qahlUivFI1zWJ9FRFPoB+KgcP820JMOuIhkBJAP3Q== public-encrypt@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6" + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== dependencies: bn.js "^4.1.0" browserify-rsa "^4.0.0" create-hash "^1.1.0" parse-asn1 "^5.0.0" randombytes "^2.0.1" + safe-buffer "^5.1.2" -"pullstream@>= 0.4.1 < 1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/pullstream/-/pullstream-0.4.1.tgz#d6fb3bf5aed697e831150eb1002c25a3f8ae1314" +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.3: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== dependencies: - over ">= 0.0.5 < 1" - readable-stream "~1.0.31" - setimmediate ">= 1.0.2 < 2" - slice-stream ">= 1.0.0 < 2" + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +punycode@2.x.x, 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== punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -qs@6.5.1, qs@~6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" - -qs@~6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.0.4.tgz#51019d84720c939b82737e84556a782338ecea7b" - -qs@~6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" +qs@6.5.2, qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.0.1.tgz#6e2b86fe0e08aef682ecbe86e85834765402bd88" +query-string@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" + integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== dependencies: decode-uri-component "^0.2.0" object-assign "^4.1.0" @@ -5652,35 +7711,43 @@ query-string@^5.0.0: querystring-es3@^0.2.0, querystring-es3@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= ramda@^0.23.0: version "0.23.0" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.23.0.tgz#ccd13fff73497a93974e3e86327bfd87bd6e8e2b" + integrity sha1-zNE//3NJepOXTj6GMnv9h71ujis= random-bytes@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" + integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs= -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" +randomatic@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" + integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" + is-number "^4.0.0" + kind-of "^6.0.0" + math-random "^1.0.1" randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" + integrity sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A== dependencies: safe-buffer "^5.1.0" randomfill@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.3.tgz#b96b7df587f01dd91726c418f30553b1418e3d62" + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== dependencies: randombytes "^2.0.5" safe-buffer "^5.1.0" @@ -5688,60 +7755,66 @@ randomfill@^1.0.3: range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= -raven-js@^3.18.1: - version "3.22.1" - resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.22.1.tgz#1117f00dfefaa427ef6e1a7d50bbb1fb998a24da" - -raven@^2.0.2: - version "2.3.0" - resolved "https://registry.yarnpkg.com/raven/-/raven-2.3.0.tgz#96f15346bdaa433b3b6d47130804506155833d69" +raven@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/raven/-/raven-2.6.4.tgz#458d4a380c8fbb59e0150c655625aaf60c167ea3" + integrity sha512-6PQdfC4+DQSFncowthLf+B6Hr0JpPsFBgTVYTAOq7tCmx/kR4SXbeawtPch20+3QfUcQDoJBLjWW1ybvZ4kXTw== dependencies: cookie "0.3.1" - lsmod "1.0.0" - stack-trace "0.0.9" + md5 "^2.2.1" + stack-trace "0.0.10" timed-out "4.0.1" - uuid "3.0.0" + uuid "3.3.2" -raw-body@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" +raw-body@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" + integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== dependencies: bytes "3.0.0" - http-errors "1.6.2" - iconv-lite "0.4.19" + http-errors "1.6.3" + iconv-lite "0.4.23" unpipe "1.0.0" -rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: - version "1.2.3" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.3.tgz#51575a900f8dd68381c710b4712c2154c3e2035b" +rc@^1.0.0, rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== dependencies: - deep-extend "~0.4.0" + deep-extend "^0.6.0" ini "~1.3.0" minimist "^1.2.0" strip-json-comments "~2.0.1" -react-app-rewire-styled-components@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/react-app-rewire-styled-components/-/react-app-rewire-styled-components-3.0.0.tgz#0dd37b9ee1bd5245ccc5fb8d6fc93971019d8036" +react-app-rewire-styled-components@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/react-app-rewire-styled-components/-/react-app-rewire-styled-components-3.0.2.tgz#e1acfaff2738af7ff4c4ad557ebd4599d6cda862" + integrity sha512-G53Z2Tiwj0WHC46wf1mwPfEb09Dj6s3PGdtzXhIw0pKkXlevYN7jyUBxTxKgQmyYTm3ZxKbCNoitP+3/N+qOAw== dependencies: babel-plugin-styled-components "^1.1.4" react-app-rewired "^1.2.0" -react-app-rewired@^1.0.5, react-app-rewired@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/react-app-rewired/-/react-app-rewired-1.4.0.tgz#58186dde172b06d5933fcb39268feb8f3f8bcc58" +react-app-rewired@^1.2.0, react-app-rewired@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/react-app-rewired/-/react-app-rewired-1.6.2.tgz#32ce90abc2c4e2b86c71e4e270bd36663b3b1c9a" + integrity sha512-TxbP1W9jPPTmj0YJeFNgcJc6IMg7sv845aHUIUvVQtXdA8IWzOZairk3Bth6GHbxoF2QitbvUlM7/NAH4VmmJg== dependencies: cross-spawn "^5.1.0" dotenv "^4.0.0" -react-dom-factories@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/react-dom-factories/-/react-dom-factories-1.0.2.tgz#eb7705c4db36fb501b3aa38ff759616aa0ff96e0" +react-click-outside@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/react-click-outside/-/react-click-outside-3.0.1.tgz#6e77e84d2f17afaaac26dbad743cbbf909f5e24c" + integrity sha512-d0KWFvBt+esoZUF15rL2UBB7jkeAqLU8L/Ny35oLK6fW6mIbOv/ChD+ExF4sR9PD26kVx+9hNfD0FTIqRZEyRQ== + dependencies: + hoist-non-react-statics "^2.1.1" react-dom@^15.4.1: version "15.6.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.2.tgz#41cfadf693b757faf2708443a1d1fd5a02bef730" + integrity sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA= dependencies: fbjs "^0.8.9" loose-envify "^1.1.0" @@ -5751,6 +7824,7 @@ react-dom@^15.4.1: react-helmet@5.x: version "5.2.0" resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.0.tgz#a81811df21313a6d55c5f058c4aeba5d6f3d97a7" + integrity sha1-qBgR3yExOm1VxfBYxK66XW89l6c= dependencies: deep-equal "^1.0.1" object-assign "^4.1.1" @@ -5760,114 +7834,124 @@ react-helmet@5.x: react-infinite-scroller-with-scroll-element@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/react-infinite-scroller-with-scroll-element/-/react-infinite-scroller-with-scroll-element-1.0.4.tgz#b2b724b06fe27e46af62d23759a21237a7d156d9" + integrity sha512-84YGKoxgb+hvwJgqXmUV+LKPab6/BZbxw6qjCr6xLFDfXeUHUqJXFIfviCT43SMe1kIzf6bpecsmwH7KXljPxA== + +react-is@^16.3.2, react-is@^16.6.0: + version "16.6.3" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.3.tgz#d2d7462fcfcbe6ec0da56ad69047e47e56e7eac0" + integrity sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA== + +react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== react-loadable@5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/react-loadable/-/react-loadable-5.2.2.tgz#1a5ad75841cacb64be12b18a86ac0bc55ccb4dc7" + integrity sha1-GlrXWEHKy2S+ErGKhqwLxVzLTcc= -react-modal@^1.6.5: - version "1.9.7" - resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-1.9.7.tgz#07ef56790b953e3b98ef1e2989e347983c72871d" - dependencies: - create-react-class "^15.5.2" - element-class "^0.2.0" - exenv "1.2.0" - lodash.assign "^4.2.0" - prop-types "^15.5.7" - react-dom-factories "^1.0.0" - -react-portal@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-3.2.0.tgz#4224e19b2b05d5cbe730a7ba0e34ec7585de0043" +react-modal@^3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.8.1.tgz#7300f94a6f92a2e17994de0be6ccb61734464c9e" + integrity sha512-aLKeZM9pgXpIKVwopRHMuvqKWiBajkqisDA8UzocdCF6S4fyKVfLWmZR5G1Q0ODBxxxxf2XIwiCP8G/11GJAuw== dependencies: - prop-types "^15.5.8" + exenv "^1.2.0" + prop-types "^15.5.10" + react-lifecycles-compat "^3.0.0" + warning "^3.0.0" -react-redux@^5.0.2: - version "5.0.6" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946" +react-redux@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.1.tgz#88e368682c7fa80e34e055cd7ac56f5936b0f52f" + integrity sha512-LE7Ned+cv5qe7tMV5BPYkGQ5Lpg8gzgItK07c67yHvJ8t0iaD9kPFPAli/mYkiyJYrs2pJgExR2ZgsGqlrOApg== dependencies: - hoist-non-react-statics "^2.2.1" - invariant "^2.0.0" - lodash "^4.2.0" - lodash-es "^4.2.0" + "@babel/runtime" "^7.1.2" + hoist-non-react-statics "^3.1.0" + invariant "^2.2.4" loose-envify "^1.1.0" - prop-types "^15.5.10" + prop-types "^15.6.1" + react-is "^16.6.0" + react-lifecycles-compat "^3.0.0" react-remarkable@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/react-remarkable/-/react-remarkable-1.1.3.tgz#6ef3861812d806fbf747cc1d1e151ee3172130a6" + integrity sha512-H4kfiT0Q84OzNAcNfWKDMe1Xhm/7jJAIHV5n4mAff2N0nGpPtRN2M7zhLXaTalD5DNGCGKEq1b4XApZ/1QpKbg== dependencies: remarkable "^1.x" react-router-dom@^4.0.0-beta.7: - version "4.2.2" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" + integrity sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA== dependencies: history "^4.7.2" - invariant "^2.2.2" + invariant "^2.2.4" loose-envify "^1.3.1" - prop-types "^15.5.4" - react-router "^4.2.0" - warning "^3.0.0" + prop-types "^15.6.1" + react-router "^4.3.1" + warning "^4.0.1" -react-router@^4.0.0-beta.7, react-router@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986" +react-router@^4.0.0-beta.7, react-router@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" + integrity sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg== dependencies: history "^4.7.2" - hoist-non-react-statics "^2.3.0" - invariant "^2.2.2" + hoist-non-react-statics "^2.5.0" + invariant "^2.2.4" loose-envify "^1.3.1" path-to-regexp "^1.7.0" - prop-types "^15.5.4" - warning "^3.0.0" + prop-types "^15.6.1" + warning "^4.0.1" react-side-effect@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.1.3.tgz#512c25abe0dec172834c4001ec5c51e04d41bc5c" + version "1.1.5" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.1.5.tgz#f26059e50ed9c626d91d661b9f3c8bb38cd0ff2d" + integrity sha512-Z2ZJE4p/jIfvUpiUMRydEVpQRf2f8GMHczT6qLcARmX7QRb28JDBTpnM2g/i5y/p7ZDEXYGHWg0RbhikE+hJRw== dependencies: exenv "^1.2.1" shallowequal "^1.0.1" -react-stripe-checkout@^2.2.5: - version "2.6.3" - resolved "https://registry.yarnpkg.com/react-stripe-checkout/-/react-stripe-checkout-2.6.3.tgz#3173a870b04e5a3c321a06d24cd53c6030111c45" - react-textarea-autosize@^4.0.5: version "4.3.2" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-4.3.2.tgz#962a52c68caceae408c18acecec29049b81e42fa" + integrity sha1-lipSxoys6uQIwYrOzsKQSbgeQvo= dependencies: prop-types "^15.5.8" -react-transition-group@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10" +react-transition-group@^2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.3.tgz#26de363cab19e5c88ae5dbae105c706cf953bb92" + integrity sha512-2DGFck6h99kLNr8pOFk+z4Soq3iISydwOFeeEVPjTN6+Y01CmvbWmnN02VuTWyFdnRtIDPe+wy2q6Ui8snBPZg== dependencies: - chain-function "^1.0.0" - classnames "^2.2.5" - dom-helpers "^3.2.0" - loose-envify "^1.3.1" - prop-types "^15.5.8" - warning "^3.0.0" + dom-helpers "^3.3.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-lifecycles-compat "^3.0.4" react-trend@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/react-trend/-/react-trend-1.2.4.tgz#498987286abd43ee3dff881115cd64d1c8e72d95" + integrity sha1-SYmHKGq9Q+49/4gRFc1k0cjnLZU= dependencies: prop-types "^15.5.8" react@*: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" + version "16.6.3" + resolved "https://registry.yarnpkg.com/react/-/react-16.6.3.tgz#25d77c91911d6bbdd23db41e70fb094cc1e0871c" + integrity sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw== dependencies: - fbjs "^0.8.16" loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.0" + prop-types "^15.6.2" + scheduler "^0.11.2" "react@^0.14.0 || ^15.0.0", react@^15.4.1: version "15.6.2" resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72" + integrity sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI= dependencies: create-react-class "^15.6.0" fbjs "^0.8.9" @@ -5875,16 +7959,10 @@ react@*: object-assign "^4.1.0" prop-types "^15.5.10" -read-all-stream@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" - dependencies: - pinkie-promise "^2.0.0" - readable-stream "^2.0.0" - read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= dependencies: find-up "^1.0.0" read-pkg "^1.0.0" @@ -5892,6 +7970,7 @@ read-pkg-up@^1.0.1: read-pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= dependencies: find-up "^2.0.0" read-pkg "^2.0.0" @@ -5899,6 +7978,7 @@ read-pkg-up@^2.0.0: read-pkg@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= dependencies: load-json-file "^1.0.0" normalize-package-data "^2.3.2" @@ -5907,53 +7987,61 @@ read-pkg@^1.0.0: read-pkg@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= dependencies: load-json-file "^2.0.0" normalize-package-data "^2.3.2" path-type "^2.0.0" +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readable-stream@1.1.x: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= dependencies: core-util-is "~1.0.0" inherits "~2.0.1" isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" +readable-stream@~2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" + integrity sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA= dependencies: + buffer-shims "^1.0.0" core-util-is "~1.0.0" - inherits "~2.0.3" + inherits "~2.0.1" isarray "~1.0.0" process-nextick-args "~1.0.6" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" - -readable-stream@~1.0.0, readable-stream@~1.0.31: - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" string_decoder "~0.10.x" + util-deprecate "~1.0.1" readdirp@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== dependencies: - graceful-fs "^4.1.2" - minimatch "^3.0.2" + graceful-fs "^4.1.11" + micromatch "^3.1.10" readable-stream "^2.0.2" - set-immediate-shim "^1.0.1" recompose@^0.23.1: version "0.23.5" resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.23.5.tgz#72ac8261246bec378235d187467d02a721e8b1de" + integrity sha1-cqyCYSRr7DeCNdGHRn0CpyHosd4= dependencies: change-emitter "^0.1.2" fbjs "^0.8.1" @@ -5963,84 +8051,155 @@ recompose@^0.23.1: redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= dependencies: indent-string "^2.1.0" strip-indent "^1.0.1" -redis-commands@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b" +redis-commands@1.4.0, redis-commands@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.4.0.tgz#52f9cf99153efcce56a8f86af986bd04e988602f" + integrity sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= redis-parser@^2.4.0: version "2.6.0" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" + integrity sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs= + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" + +redis-tag-cache@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/redis-tag-cache/-/redis-tag-cache-1.2.1.tgz#07d6e1f945d9d5c8186602129cb10573167d7534" + integrity sha512-0F+rLtoIkz4NOWGt9k3qaPcbpvr6+wcEC9aWV78newMKTRN/nIYHgvfyO1Q0ySEepiEYbqj9DhjvQ4CUAtT9/g== + dependencies: + ioredis "^4.0.0" redraft@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/redraft/-/redraft-0.8.0.tgz#db2a5c01a8eb6b553f46cc8382c1ed085325e99f" + integrity sha1-2ypcAajra1U/RsyDgsHtCFMl6Z8= reduce@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/reduce/-/reduce-1.0.1.tgz#14fa2e5ff1fc560703a020cbb5fbaab691565804" + integrity sha1-FPouX/H8VgcDoCDLtfuqtpFWWAQ= dependencies: object-keys "~1.0.0" -redux-thunk@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5" +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== redux@^3.6.0: version "3.7.2" resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b" + integrity sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A== dependencies: lodash "^4.2.1" lodash-es "^4.2.1" loose-envify "^1.1.0" symbol-observable "^1.0.3" -regenerate@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" +referrer-policy@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.1.0.tgz#35774eb735bf50fb6c078e83334b472350207d79" + integrity sha1-NXdOtzW/UPtsB46DM0tHI1AgfXk= + +regenerate-unicode-properties@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c" + integrity sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.2.1, regenerate@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== regenerator-runtime@^0.10.5: version "0.10.5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + integrity sha1-M2w+/BIgrc7dosn6tntaeVWjNlg= -regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1: +regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-runtime@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" + integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== regenerator-transform@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" + integrity sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q== dependencies: babel-runtime "^6.18.0" babel-types "^6.19.0" private "^0.1.6" +regenerator-transform@^0.13.3: + version "0.13.3" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb" + integrity sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA== + dependencies: + private "^0.1.6" + regex-cache@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" + integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ== dependencies: is-equal-shallow "^0.1.3" -regex-not@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.0.tgz#42f83e39771622df826b02af176525d6a5f157f9" +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== dependencies: - extend-shallow "^2.0.1" + extend-shallow "^3.0.2" + safe-regex "^1.1.0" regexpu-core@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + integrity sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA= dependencies: regenerate "^1.2.1" regjsgen "^0.2.0" regjsparser "^0.1.4" +regexpu-core@^4.1.3, regexpu-core@^4.2.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.4.0.tgz#8d43e0d1266883969720345e70c275ee0aec0d32" + integrity sha512-eDDWElbwwI3K0Lo6CqbQbA6FwgtCz4kYTarrri1okfkRLZAqstU+B3voZBCjg8Fl6iq0gXrJG6MvRgLthfvgOA== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^7.0.0" + regjsgen "^0.5.0" + regjsparser "^0.6.0" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.0.2" + registry-auth-token@^3.0.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.1.tgz#fb0d3289ee0d9ada2cbb52af5dfe66cb070d3006" + version "3.3.2" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" + integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ== dependencies: rc "^1.1.6" safe-buffer "^5.0.1" @@ -6048,22 +8207,38 @@ registry-auth-token@^3.0.1: registry-url@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" + integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= dependencies: rc "^1.0.1" regjsgen@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc= + +regjsgen@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.0.tgz#a7634dc08f89209c2049adda3525711fb97265dd" + integrity sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA== regjsparser@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw= + dependencies: + jsesc "~0.5.0" + +regjsparser@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.0.tgz#f1e6ae8b7da2bae96c99399b868cd6c933a2ba9c" + integrity sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ== dependencies: jsesc "~0.5.0" remarkable@^1.x: version "1.7.1" resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.1.tgz#aaca4972100b66a642a63a1021ca4bac1be3bff6" + integrity sha1-qspJchALZqZCpjoQIcpLrBvjv/Y= dependencies: argparse "~0.1.15" autolinker "~0.15.0" @@ -6071,185 +8246,262 @@ remarkable@^1.x: remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== repeat-string@^1.5.2, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= repeating@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= dependencies: is-finite "^1.0.0" -request@2.81.0: - version "2.81.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" +request-ip@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/request-ip/-/request-ip-2.1.3.tgz#99ab2bafdeaf2002626e28083cb10597511d9e14" + integrity sha512-J3qdE/IhVM3BXkwMIVO4yFrvhJlU3H7JH16+6yHucadT4fePnR8dyh+vEs6FIx0S2x5TCt2ptiPfHcn0sqhbYQ== dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~4.2.1" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - performance-now "^0.2.0" - qs "~6.4.0" - safe-buffer "^5.0.1" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "^0.6.0" - uuid "^3.0.0" + is_js "^0.9.0" -request@^2.74.0, request@^2.79.0, request@^2.81.0: - version "2.83.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" +request@^2.79.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== dependencies: aws-sign2 "~0.7.0" - aws4 "^1.6.0" + aws4 "^1.8.0" caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" + combined-stream "~1.0.6" + extend "~3.0.2" forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" + form-data "~2.3.2" + har-validator "~5.1.0" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" + mime-types "~2.1.19" + oauth-sign "~0.9.0" performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.3" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" tunnel-agent "^0.6.0" - uuid "^3.1.0" + uuid "^3.3.2" require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= resolve-pathname@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" + integrity sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg== resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= resolve@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= + +resolve@^1.3.2, resolve@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" + integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== + dependencies: + path-parse "^1.0.5" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== rethinkdb-changefeed-reconnect@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/rethinkdb-changefeed-reconnect/-/rethinkdb-changefeed-reconnect-0.3.2.tgz#2999f5313205ab35d9ac2d1b0533765ee3376923" + integrity sha1-KZn1MTIFqzXZrC0bBTN2XuM3aSM= dependencies: babel-runtime "^6.18.0" rethinkdb-inspector@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/rethinkdb-inspector/-/rethinkdb-inspector-0.3.3.tgz#f0d88c66d17e0234b5518ca51cd8c272cb787003" + integrity sha512-1R0S5maattWOptfkHsU5ulXnt6FIKqjgyEdd8WgW9QbYMiHaXNqkHTAsmtXXSAf0j5iFH3F2qq/7eg4xwb8Euw== -rethinkdb-migrate@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/rethinkdb-migrate/-/rethinkdb-migrate-1.2.2.tgz#729046dde135a7231ed14980150481d57d9465ea" +rethinkdb-migrate@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/rethinkdb-migrate/-/rethinkdb-migrate-1.4.0.tgz#7a019148d73e6f7a8f4366550557e43e3dafed4c" + integrity sha512-MNJAB6L4XUBSztCnAeId4k2j/irlDz6ENHNHVe8j/S5ZhUg3OUuSXoA95shvSufn0sZxlfKFzMccmI1oOZeetA== dependencies: - fs-extra "^4.0.1" - joi "^10.6.0" + fs-extra "^5.0.0" + joi "^13.1.2" json-mask "^0.3.8" - moment "^2.18.1" + moment "^2.20.1" rethinkdb "^2.3.3" rethinkdbdash "^2.3.31" - yargs "^8.0.2" + yargs "^11.0.0" rethinkdb@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/rethinkdb/-/rethinkdb-2.3.3.tgz#3dc6586e22fa1dabee0d254e64bd0e379fad2f72" + integrity sha1-PcZYbiL6HavuDSVOZL0ON5+tL3I= dependencies: bluebird ">= 2.3.2 < 3" -rethinkdbdash@^2.3.29, rethinkdbdash@^2.3.31: +rethinkdbdash@^2.3.31: version "2.3.31" resolved "https://registry.yarnpkg.com/rethinkdbdash/-/rethinkdbdash-2.3.31.tgz#fe2f73d1fa6e6f5d96d8e881292013cf6dca914d" + integrity sha512-6nXrKFjdg2Ug0YpdmPWSvyD/2EisHnFNt4FWZ74dcXGK48ievSv+cNFTmVv+KjLi6I9CCf6w4CKZ6yCYTfMfdQ== dependencies: bluebird ">= 3.0.1" -right-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" +rethinkhaberdashery@^2.3.32: + version "2.3.32" + resolved "https://registry.yarnpkg.com/rethinkhaberdashery/-/rethinkhaberdashery-2.3.32.tgz#ddcc4ba1342e653a9d3bb6982b526087e93d537e" + integrity sha512-scDswDEu7R47WqomjScq46LkCdhaFhpaGQDP3P44GDF32iCFgYbsL4fnJdaieE115qLeinRhEjna08XWCtV0iQ== dependencies: - align-text "^0.1.1" + bluebird ">= 3.0.1" + +retry@0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== dependencies: glob "^7.0.5" ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== dependencies: - hash-base "^2.0.0" + hash-base "^3.0.0" inherits "^2.0.1" -safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +rsvp@^3.3.3: + version "3.6.2" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" + integrity sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw== + +run-queue@^1.0.0, run-queue@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" + integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= + dependencies: + aproba "^1.1.1" + +safe-buffer@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== + +safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.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== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", 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== sane@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/sane/-/sane-2.2.0.tgz#d6d2e2fcab00e3d283c93b912b7c3a20846f1d56" + version "2.5.2" + resolved "https://registry.yarnpkg.com/sane/-/sane-2.5.2.tgz#b4dc1861c21b427e929507a3e751e2a2cb8ab3fa" + integrity sha1-tNwYYcIbQn6SlQej51HiosuKs/o= dependencies: - anymatch "^1.3.0" + anymatch "^2.0.0" + capture-exit "^1.2.0" exec-sh "^0.2.0" fb-watchman "^2.0.0" - minimatch "^3.0.2" + micromatch "^3.1.4" minimist "^1.1.1" walker "~1.0.5" watch "~0.18.0" optionalDependencies: - fsevents "^1.1.1" + fsevents "^1.2.3" + +sanitize-filename@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.1.tgz#612da1c96473fa02dccda92dcd5b4ab164a6772a" + integrity sha1-YS2hyWRz+gLczaktzVtKsWSmdyo= + dependencies: + truncate-utf8-bytes "^1.0.0" sax@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o= -sax@>=0.6.0, sax@^1.2.1: +sax@>=0.6.0, sax@^1.2.1, sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +scheduler@^0.11.2: + version "0.11.3" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.3.tgz#b5769b90cf8b1464f3f3cfcafe8e3cd7555a2d6b" + integrity sha512-i9X9VRRVZDd3xZw10NY5Z2cVMbdYg6gqFecfj79USv1CFN+YrJ3gIPRKf1qlY+Sxly4djoKdfx1T+m9dnRB8kQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +schema-utils@^0.4.4: + version "0.4.7" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" + integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ== + dependencies: + ajv "^6.1.0" + ajv-keywords "^3.1.0" + +schema-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" + integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== + dependencies: + ajv "^6.1.0" + ajv-errors "^1.0.0" + ajv-keywords "^3.1.0" select@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" - -selection-is-backward@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/selection-is-backward/-/selection-is-backward-1.0.0.tgz#97a54633188a511aba6419fc5c1fa91b467e6be1" + integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= selectn@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/selectn/-/selectn-1.1.2.tgz#fc8acd91df3f45acb01891c6773ae529851d6b17" + integrity sha1-/IrNkd8/RaywGJHGdzrlKYUdaxc= dependencies: brackets2dots "^1.1.0" curry2 "^1.0.0" @@ -6259,21 +8511,24 @@ selectn@^1.1.2: semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" + integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= dependencies: semver "^5.0.3" -"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -send@0.16.1: - version "0.16.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" +send@0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" + integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== dependencies: debug "2.6.9" - depd "~1.1.1" + depd "~1.1.2" destroy "~1.0.4" - encodeurl "~1.0.1" + encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" fresh "0.5.2" @@ -6282,48 +8537,49 @@ send@0.16.1: ms "2.0.0" on-finished "~2.3.0" range-parser "~1.2.0" - statuses "~1.3.1" + statuses "~1.4.0" serialize-javascript@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005" + version "1.5.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.5.0.tgz#1aa336162c88a890ddad5384baebc93a655161fe" + integrity sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ== + +serialize-javascript@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879" + integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw== -serve-static@1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719" +serve-static@1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" + integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== dependencies: - encodeurl "~1.0.1" + encodeurl "~1.0.2" escape-html "~1.0.3" parseurl "~1.3.2" - send "0.16.1" + send "0.16.2" serviceworker-cache-polyfill@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/serviceworker-cache-polyfill/-/serviceworker-cache-polyfill-4.0.0.tgz#de19ee73bef21ab3c0740a37b33db62464babdeb" + integrity sha1-3hnuc77yGrPAdAo3sz22JGS6ves= session-rethinkdb@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/session-rethinkdb/-/session-rethinkdb-2.0.1.tgz#d1c8aecc94323b61bd05fca240548fc437fb4bbc" + integrity sha512-sCSnz1SjxI2tFljSFDR6QTXPZzOdgEg5bSVyi2JWJgsWlJHoQBQP1vABvB/exwsMPabGeXgFGPZjEq6Pwq4Pvg== dependencies: debug "^3.1.0" set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - -set-getter@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" - dependencies: - to-object-path "^0.3.0" - -set-immediate-shim@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= set-value@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" + integrity sha1-fbCPnT0i3H945Trzw79GZuzfzPE= dependencies: extend-shallow "^2.0.1" is-extendable "^0.1.1" @@ -6333,112 +8589,107 @@ set-value@^0.4.3: set-value@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" + integrity sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg== dependencies: extend-shallow "^2.0.1" is-extendable "^0.1.1" is-plain-object "^2.0.3" split-string "^3.0.1" -"setimmediate@>= 1.0.1 < 2", "setimmediate@>= 1.0.2 < 2", setimmediate@^1.0.4, setimmediate@^1.0.5: +setimmediate@^1.0.4, setimmediate@^1.0.5, setimmediate@~1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - -setprototypeof@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.9" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.9.tgz#98f64880474b74f4a38b8da9d3c0f2d104633e7d" + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" shallowequal@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.0.2.tgz#1561dbdefb8c01408100319085764da3fcf83f8f" + 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= shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" - -shortid@^2.2.8: - version "2.2.8" - resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= slate-markdown@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/slate-markdown/-/slate-markdown-0.1.0.tgz#8e7c203d7937c88527fc4cc4a5e129ac30d8e9ce" + integrity sha512-cQpbujvff/kmhlMW2v/jDOBdd2I8GcP1Ivhdjj/EVdJ19is6Z0g7OsLCJROHaQ9AS702HVBrUDfxsI5dtPMqSg== dependencies: prismjs "^1.6.0" react "^0.14.0 || ^15.0.0" styled-components "^2.0.0" -slate@^0.20.1: - version "0.20.7" - resolved "https://registry.yarnpkg.com/slate/-/slate-0.20.7.tgz#083ca9074dc7fd3ad8863985e6d92ed76bdc9eff" +slate@^0.44.10: + version "0.44.10" + resolved "https://registry.yarnpkg.com/slate/-/slate-0.44.10.tgz#ac7c3e3cf85570a8723a64a8a7807c881ddbfa8a" + integrity sha512-2jMPgOjExjeWfHrYUsYTMLo/ykbXjCwLUFwG3G34Q0vwRsmf4yOf3b0Zx5LoUaNlyvQDdBsOGsF1qi6yTx53DA== dependencies: - cheerio "^0.22.0" - debug "^2.3.2" + debug "^3.1.0" direction "^0.1.5" - es6-map "^0.1.4" esrever "^0.2.0" - get-window "^1.1.1" - immutable "^3.8.1" - is-empty "^1.0.0" - is-in-browser "^1.1.3" - is-window "^1.0.2" - keycode "^2.1.2" - prop-types "^15.5.8" - react-portal "^3.1.0" - selection-is-backward "^1.0.0" + is-plain-object "^2.0.4" + lodash "^4.17.4" + tiny-invariant "^1.0.1" + tiny-warning "^0.0.3" type-of "^2.0.1" slice-ansi@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg== dependencies: is-fullwidth-code-point "^2.0.0" -"slice-stream@>= 1.0.0 < 2": - version "1.0.0" - resolved "https://registry.yarnpkg.com/slice-stream/-/slice-stream-1.0.0.tgz#5b33bd66f013b1a7f86460b03d463dec39ad3ea0" - dependencies: - readable-stream "~1.0.31" - slide@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" + integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= slugg@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/slugg/-/slugg-1.2.1.tgz#e752af2241af3f2714463c5de225cea47608740a" + integrity sha1-51KvIkGvPycURjxd4iXOpHYIdAo= snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== dependencies: define-property "^1.0.0" isobject "^3.0.0" @@ -6447,12 +8698,14 @@ snapdragon-node@^2.0.1: snapdragon-util@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== dependencies: kind-of "^3.2.0" snapdragon@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.1.tgz#e12b5487faded3e3dea0ac91e9400bf75b401370" + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== dependencies: base "^0.11.1" debug "^2.2.0" @@ -6461,178 +8714,201 @@ snapdragon@^0.8.1: map-cache "^0.2.2" source-map "^0.5.6" source-map-resolve "^0.5.0" - use "^2.0.0" - -sntp@1.x.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" - dependencies: - hoek "2.x.x" - -sntp@2.x.x: - version "2.1.0" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" - dependencies: - hoek "4.x.x" + use "^3.1.0" source-list-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== source-map-resolve@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a" + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== dependencies: - atob "^2.0.0" + atob "^2.1.1" decode-uri-component "^0.2.0" resolve-url "^0.2.1" source-map-url "^0.4.0" urix "^0.1.0" -"source-map-support@0.3.2 - 1.0.0", source-map-support@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.0.tgz#2018a7ad2bdf8faf2691e5fddab26bed5a2bacab" +"source-map-support@0.3.2 - 1.0.0", source-map-support@~0.5.6: + version "0.5.9" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" + integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA== dependencies: + buffer-from "^1.0.0" source-map "^0.6.0" source-map-support@^0.4.15: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== dependencies: source-map "^0.5.6" source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= -source-map@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - dependencies: - amdefine ">=0.0.4" - -source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.6: +source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7: 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, source-map@~0.6.1: +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== spawn-sync@^1.0.15: version "1.0.15" resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476" + integrity sha1-sAeZVX63+wyDdsKdROih6mfldHY= dependencies: concat-stream "^1.4.7" os-shim "^0.1.2" -spdx-correct@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" +spdx-correct@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" + integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== dependencies: - spdx-license-ids "^1.0.2" - -spdx-expression-parse@~1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" -spdx-license-ids@^1.0.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" +spdx-license-ids@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz#a59efc09784c2a5bada13cfeaf5c75dd214044d2" + integrity sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg== split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== dependencies: extend-shallow "^3.0.0" -split@0.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" - dependencies: - through "2" - 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= sshpk@^1.7.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + version "1.15.2" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.2.tgz#c946d6bd9b1a39d0e8635763f5242d6ed6dcb629" + integrity sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" - dashdash "^1.12.0" - getpass "^0.1.1" - optionalDependencies: 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" -stack-trace@0.0.9: - version "0.0.9" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695" +ssri@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" + integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + dependencies: + figgy-pudding "^3.5.1" + +stack-trace@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= -stackframe@^1.0.3: +stackframe@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.0.4.tgz#357b24a992f9427cba6b545d96a14ed2cbca187b" + integrity sha512-to7oADIniaYwS3MhtCa/sQhrxidCCQiF/qp4/m5iN3ipf0Y7Xlri0f6eG29r08aL7JYl8n32AF3Q5GYBZ7K8vw== + +standard-as-callback@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-1.0.1.tgz#2e9e1e9d278d7d77580253faaec42269015e3c1d" + integrity sha512-izxEITSyc7S+5oOiF/URiYaNkemPUxIndCNv66jJ548Y1TVxhBvioNMSPrZIQdaZDlhnguOdUzHA/7hJ3xFhuQ== static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= dependencies: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.3.1 < 2": +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +statuses@~1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" - -statuses@~1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" + integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== stream-browserify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" + integrity sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds= dependencies: inherits "~2.0.1" readable-stream "^2.0.2" -stream-combiner@~0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" +stream-each@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" + integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== dependencies: - duplexer "~0.1.1" + end-of-stream "^1.1.0" + stream-shift "^1.0.0" stream-http@^2.7.2: - version "2.8.0" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.0.tgz#fd86546dac9b1c91aff8fc5d287b98fafb41bc10" + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== dependencies: builtin-status-codes "^3.0.0" inherits "^2.0.1" - readable-stream "^2.3.3" + readable-stream "^2.3.6" to-arraybuffer "^1.0.0" xtend "^4.0.0" +stream-shift@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" + integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= + streamsearch@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" + integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - -string-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-1.0.1.tgz#56970fb1c38558e9e70b728bf3de269ac45adfac" - dependencies: - strip-ansi "^3.0.0" + integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" + integrity sha1-1A27aGo6zpYMHP/KVivyxF+DY+0= dependencies: astral-regex "^1.0.0" strip-ansi "^4.0.0" @@ -6640,92 +8916,111 @@ string-length@^2.0.0: string-replace-to-array@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/string-replace-to-array/-/string-replace-to-array-1.0.3.tgz#c93eba999a5ee24d731aebbaf5aba36b5f18f7bf" + integrity sha1-yT66mZpe4k1zGuu69auja18Y978= dependencies: invariant "^2.2.1" lodash.flatten "^4.2.0" lodash.isstring "^4.0.1" -string-width@^1.0.1, string-width@^1.0.2: +string-similarity@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-1.2.2.tgz#99b2c20a3c9bbb3903964eae1d89856db3d8db9b" + integrity sha512-IoHUjcw3Srl8nsPlW04U3qwWPk3oG2ffLM0tN853d/E/JlIvcmZmDY2Kz5HzKp4lEi2T7QD7Zuvjq/1rDw+XcQ== + dependencies: + lodash.every "^4.6.0" + lodash.flattendeep "^4.4.0" + lodash.foreach "^4.5.0" + lodash.map "^4.6.0" + lodash.maxby "^4.6.0" + +string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= dependencies: code-point-at "^1.0.0" is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.0.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== dependencies: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string_decoder@^1.0.0, string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" +string_decoder@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" + integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== dependencies: safe-buffer "~5.1.0" string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= -stringstream@~0.0.4, stringstream@~0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= dependencies: ansi-regex "^2.0.0" strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= dependencies: ansi-regex "^3.0.0" strip-bom@3.0.0, strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= dependencies: is-utf8 "^0.2.0" strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= strip-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= dependencies: get-stdin "^4.0.1" strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - -stripe@^4.15.0: - version "4.25.0" - resolved "https://registry.yarnpkg.com/stripe/-/stripe-4.25.0.tgz#16af99c255e4fe22adbaf629f392af0715370760" - dependencies: - bluebird "^2.10.2" - lodash.isplainobject "^4.0.6" - object-assign "^4.1.0" - qs "~6.0.4" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= striptags@2.x: version "2.2.1" resolved "https://registry.yarnpkg.com/striptags/-/striptags-2.2.1.tgz#4c450b708d41b8bf39cf24c49ff234fc6aabfd32" + integrity sha1-TEULcI1BuL85zyTEn/I0/Gqr/TI= styled-components@3.1.x: version "3.1.6" resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.1.6.tgz#9c443146fa82c6659a9f64dd493bf2202480342e" + integrity sha1-nEQxRvqCxmWan2TdSTvyICSANC4= dependencies: buffer "^5.0.3" css-to-react-native "^2.0.3" @@ -6738,8 +9033,9 @@ styled-components@3.1.x: supports-color "^3.2.3" styled-components@^2.0.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-2.4.0.tgz#086d0fd483d54638837fca3ea546a030b94adf75" + version "2.4.1" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-2.4.1.tgz#663bd0485d4b6ab46f946210dc03d2398d1ade74" + integrity sha1-ZjvQSF1LarRvlGIQ3APSOY0a3nQ= dependencies: buffer "^5.0.3" css-to-react-native "^2.0.3" @@ -6753,51 +9049,56 @@ styled-components@^2.0.0: stylis-rule-sheet@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.7.tgz#5c51dc879141a61821c2094ba91d2cbcf2469c6c" + integrity sha512-qxzlUBO40tgcGMhYxk2gXAPcaZYpfCqHMoVHj92lFMyiFotcqaEl7Jb5eW1ccCanGwf1N9dVBKF9+i/gmDfzyQ== -stylis@^3.0.0, stylis@^3.4.0: - version "3.4.8" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.4.8.tgz#94380babbcd4c75726215794ca985b38ec96d1a3" +stylis@^3.4.0: + version "3.5.4" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" + integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q== -subscriptions-transport-ws@^0.9.5: - version "0.9.5" - resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.5.tgz#faa9eb1230d5f2efe355368cd973b867e4483c53" +subscriptions-transport-ws@^0.9.11, subscriptions-transport-ws@^0.9.15: + version "0.9.15" + resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.15.tgz#68a8b7ba0037d8c489fb2f5a102d1494db297d0d" + integrity sha512-f9eBfWdHsePQV67QIX+VRhf++dn1adyC/PZHP6XI5AfKnZ4n0FW+v5omxwdHVpd4xq2ZijaHEcmlQrhBY79ZWQ== dependencies: backo2 "^1.0.2" - eventemitter3 "^2.0.3" - iterall "^1.1.1" - lodash.assign "^4.2.0" - lodash.isobject "^3.0.2" - lodash.isstring "^4.0.1" + eventemitter3 "^3.1.0" + iterall "^1.2.1" symbol-observable "^1.0.4" - ws "^3.0.0" + ws "^5.2.0" supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= supports-color@^3.1.2, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= dependencies: has-flag "^1.0.0" -supports-color@^4.0.0, supports-color@^4.2.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" +supports-color@^5.2.0, 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 "^2.0.0" + has-flag "^3.0.0" -sw-precache-webpack-plugin@^0.11.4: - version "0.11.4" - resolved "https://registry.yarnpkg.com/sw-precache-webpack-plugin/-/sw-precache-webpack-plugin-0.11.4.tgz#a695017e54eed575551493a519dc1da8da2dc5e0" +sw-precache-webpack-plugin@^0.11.5: + version "0.11.5" + resolved "https://registry.yarnpkg.com/sw-precache-webpack-plugin/-/sw-precache-webpack-plugin-0.11.5.tgz#9b53f65a4966e3adc298e256b3cef7a55c73fdfd" + integrity sha512-K6E52DbYyzGNXGyv2LhI2Duomr3t/2FFMmnGdHZ1Ruk3ulFHDMASJtg3WpA3CXlWODZx189tTaOIO5mWkSKyVg== dependencies: - del "^2.2.2" - sw-precache "^5.1.1" - uglify-js "^3.0.13" + del "^3.0.0" + sw-precache "^5.2.1" + uglify-es "^3.3.9" -sw-precache@^5.1.1: - version "5.2.0" - resolved "https://registry.yarnpkg.com/sw-precache/-/sw-precache-5.2.0.tgz#eb6225ce580ceaae148194578a0ad01ab7ea199c" +sw-precache@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/sw-precache/-/sw-precache-5.2.1.tgz#06134f319eec68f3b9583ce9a7036b1c119f7179" + integrity sha512-8FAy+BP/FXE+ILfiVTt+GQJ6UEf4CVHD9OfhzH0JX+3zoy2uFk7Vn9EfXASOtVmmIVbL3jE/W8Z66VgPSZcMhw== dependencies: dom-urls "^1.1.0" es6-promise "^4.0.5" @@ -6808,72 +9109,95 @@ sw-precache@^5.1.1: mkdirp "^0.5.1" pretty-bytes "^4.0.2" sw-toolbox "^3.4.0" - update-notifier "^1.0.3" + update-notifier "^2.3.0" sw-toolbox@^3.4.0: version "3.6.0" resolved "https://registry.yarnpkg.com/sw-toolbox/-/sw-toolbox-3.6.0.tgz#26df1d1c70348658e4dea2884319149b7b3183b5" + integrity sha1-Jt8dHHA0hljk3qKIQxkUm3sxg7U= dependencies: path-to-regexp "^1.0.1" serviceworker-cache-polyfill "^4.0.0" symbol-observable@^1.0.3, symbol-observable@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.1.0.tgz#5c68fd8d54115d9dfb72a84720549222e8db9b32" + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== symbol-tree@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" + integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY= synthetic-dom@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/synthetic-dom/-/synthetic-dom-1.2.0.tgz#f3589aafe2b5e299f337bb32973a9be42dd5625e" + integrity sha1-81iar+K14pnzN7sylzqb5C3VYl4= -table@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" +table@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc" + integrity sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg== dependencies: - ajv "^5.2.3" - ajv-keywords "^2.1.0" + ajv "^6.0.1" + ajv-keywords "^3.0.0" chalk "^2.1.0" lodash "^4.17.4" slice-ansi "1.0.0" string-width "^2.1.1" -tapable@^0.2.7: - version "0.2.8" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" - -tar-pack@^3.4.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" - dependencies: - debug "^2.2.0" - fstream "^1.0.10" - fstream-ignore "^1.0.5" - once "^1.3.3" - readable-stream "^2.1.4" - rimraf "^2.5.1" - tar "^2.2.1" - uid-number "^0.0.6" - -tar@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" - dependencies: - block-stream "*" - fstream "^1.0.2" - inherits "2" +tapable@^1.0.0, tapable@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.1.tgz#4d297923c5a72a42360de2ab52dadfaaec00018e" + integrity sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA== + +tar@^4: + version "4.4.8" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" + integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ== + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.3.4" + minizlib "^1.1.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.2" term-size@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" + integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= dependencies: execa "^0.7.0" -test-exclude@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26" +terser-webpack-plugin@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.1.0.tgz#cf7c25a1eee25bf121f4a587bb9e004e3f80e528" + integrity sha512-61lV0DSxMAZ8AyZG7/A4a3UPlrbOBo8NIQ4tJzLPAdGOQ+yoNC7l5ijEow27lBAL2humer01KLS6bGIMYQxKoA== + dependencies: + cacache "^11.0.2" + find-cache-dir "^2.0.0" + schema-utils "^1.0.0" + serialize-javascript "^1.4.0" + source-map "^0.6.1" + terser "^3.8.1" + webpack-sources "^1.1.0" + worker-farm "^1.5.2" + +terser@^3.8.1: + version "3.11.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-3.11.0.tgz#60782893e1f4d6788acc696351f40636d0e37af0" + integrity sha512-5iLMdhEPIq3zFWskpmbzmKwMQixKmTYwY3Ox9pjtSklBLnHiuQ0GKJLhL1HSYtyffHM3/lDIFBnb82m9D7ewwQ== + dependencies: + commander "~2.17.1" + source-map "~0.6.1" + source-map-support "~0.5.6" + +test-exclude@^4.2.1: + version "4.2.3" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.3.tgz#a9a5e64474e4398339245a0a769ad7c2f4a97c20" + integrity sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA== dependencies: arrify "^1.0.1" micromatch "^2.3.11" @@ -6884,314 +9208,437 @@ test-exclude@^4.1.1: then-queue@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/then-queue/-/then-queue-1.3.0.tgz#19f63c2a93335ba54ea942f7eca3d6b8899015fb" + integrity sha1-GfY8KpMzW6VOqUL37KPWuImQFfs= dependencies: promise "^6.0.0" throat@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" + integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= + +through2@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" -through@2, through@^2.3.8, through@~2.3, through@~2.3.1, through@~2.3.4: +through@^2.3.8, through@~2.3.4: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= timed-out@4.0.1, timed-out@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - -timed-out@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" + integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= timers-browserify@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.4.tgz#96ca53f4b794a5e7c0e1bd7cc88a372298fa01e6" + version "2.0.10" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" + integrity sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg== dependencies: setimmediate "^1.0.4" tiny-emitter@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c" + integrity sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow== + +tiny-invariant@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.3.tgz#91efaaa0269ccb6271f0296aeedb05fc3e067b7a" + integrity sha512-ytQx8T4DL8PjlX53yYzcIC0WhIZbpR0p1qcYjw2pHu3w6UtgWwFJQ/02cnhOnBBhlFx/edUIfcagCaQSe3KMWg== + +tiny-warning@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-0.0.3.tgz#1807eb4c5f81784a6354d58ea1d5024f18c6c81f" + integrity sha512-r0SSA5Y5IWERF9Xh++tFPx0jITBgGggOsRLDWWew6YRw/C2dr4uNO1fw1vanrBmHsICmPyMLNBZboTlxUmUuaA== tlds@^1.189.0: - version "1.199.0" - resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.199.0.tgz#a4fc8c3058216488a80aaaebb427925007e55217" + version "1.203.1" + resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.203.1.tgz#4dc9b02f53de3315bc98b80665e13de3edfc1dfc" + integrity sha512-7MUlYyGJ6rSitEZ3r1Q1QNV8uSIzapS8SmmhSusBuIc7uIxPPwsKllEP0GRp1NS6Ik6F+fRZvnjDWm3ecv2hDw== tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= + +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= to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= dependencies: kind-of "^3.0.2" to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= dependencies: is-number "^3.0.0" repeat-string "^1.6.1" -to-regex@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.1.tgz#15358bee4a2c83bd76377ba1dc049d0f18837aae" +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== dependencies: - define-property "^0.2.5" - extend-shallow "^2.0.1" - regex-not "^1.0.0" + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== toobusy-js@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/toobusy-js/-/toobusy-js-0.5.1.tgz#5511f78f6a87a6a512d44fdb0efa13672217f659" + integrity sha1-VRH3j2qHpqUS1E/bDvoTZyIX9lk= -topo@2.x.x: - version "2.0.2" - resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182" +topo@3.x.x: + version "3.0.3" + resolved "https://registry.yarnpkg.com/topo/-/topo-3.0.3.tgz#d5a67fb2e69307ebeeb08402ec2a2a6f5f7ad95c" + integrity sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ== dependencies: - hoek "4.x.x" + hoek "6.x.x" touch@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== dependencies: nopt "~1.0.10" -tough-cookie@^2.3.2, tough-cookie@~2.3.0, tough-cookie@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" +tough-cookie@^2.3.2: + 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" + +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== dependencies: + psl "^1.1.24" punycode "^1.4.1" tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= "traverse@>=0.3.0 <0.4": version "0.3.9" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= + +truncate-utf8-bytes@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" + integrity sha1-QFkjkJWS1W94pYGENLC3hInKXys= + dependencies: + utf8-byte-length "^1.0.1" + +tslib@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" + integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= 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= 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-is@~1.6.15: - version "1.6.15" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" +type-is@^1.6.12, type-is@^1.6.16, type-is@~1.6.16: + version "1.6.16" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" + integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== dependencies: media-typer "0.3.0" - mime-types "~2.1.15" + mime-types "~2.1.18" type-of@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/type-of/-/type-of-2.0.1.tgz#e72a1741896568e9f628378d816d6912f7f23972" + integrity sha1-5yoXQYllaOn2KDeNgW1pEvfyOXI= typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -ua-parser-js@^0.7.9: - version "0.7.17" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" +ua-parser-js@^0.7.18: + version "0.7.19" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b" + integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ== uc.micro@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" - -uglify-js@^2.6, uglify-js@^2.8.29: - version "2.8.29" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" - dependencies: - source-map "~0.5.1" - yargs "~3.10.0" - optionalDependencies: - uglify-to-browserify "~1.0.0" + version "1.0.5" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376" + integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg== -uglify-js@^3.0.13: - version "3.3.7" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.3.7.tgz#28463e7c7451f89061d2b235e30925bf5625e14d" +uglify-es@^3.3.9: + version "3.3.9" + resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" + integrity sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ== dependencies: commander "~2.13.0" source-map "~0.6.1" -uglify-to-browserify@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" - -uglifyjs-webpack-plugin@^0.4.6: - version "0.4.6" - resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" +uglify-js@^3.1.4: + version "3.4.9" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" + integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q== dependencies: - source-map "^0.5.6" - uglify-js "^2.8.29" - webpack-sources "^1.0.1" - -uid-number@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" + commander "~2.17.1" + source-map "~0.6.1" uid-safe@~2.1.5: version "2.1.5" resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" + integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== dependencies: random-bytes "~1.0.0" uid2@0.0.x: version "0.0.3" resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" + integrity sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I= -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - -undefsafe@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.1.tgz#03b2f2a16c94556e14b2edef326cd66aaf82707a" +undefsafe@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" + integrity sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY= dependencies: debug "^2.2.0" underscore.string@~2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-2.4.0.tgz#8cdd8fbac4e2d2ea1e7e2e8097c42f442280f85b" + integrity sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs= underscore@~1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" + integrity sha1-a7rwh3UA02vjTsqlhODbn+8DUgk= + +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.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz#9f1dc76926d6ccf452310564fd834ace059663d4" + integrity sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0" + integrity sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg== union-class-names@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/union-class-names/-/union-class-names-1.0.0.tgz#9259608adacc39094a2b0cfe16c78e6200617847" + integrity sha1-kllgitrMOQlKKwz+FseOYgBheEc= union-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" + integrity sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ= dependencies: arr-union "^3.1.0" get-value "^2.0.6" is-extendable "^0.1.1" set-value "^0.4.3" +unique-filename@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.1.tgz#5e9edc6d1ce8fb264db18a507ef9bd8544451ca6" + integrity sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg== + dependencies: + imurmurhash "^0.1.4" + unique-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" + integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= dependencies: crypto-random-string "^1.0.0" +universal-user-agent@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-2.0.2.tgz#b0322da546100c658adcf4965110a56ed238aee6" + integrity sha512-nOwvHWLH3dBazyuzbECPA5uVFNd7AlgviXRHgR4yf48QqitIvpdncRrxMbZNMpPPEfgz30I9ubd1XmiJiqsTrg== + dependencies: + os-name "^3.0.0" + universalify@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= unset-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= dependencies: has-value "^0.3.1" isobject "^3.0.0" -unzip-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" - unzip-response@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" + integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= + +unzipper@^0.8.11: + version "0.8.14" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.8.14.tgz#ade0524cd2fc14d11b8de258be22f9d247d3f79b" + integrity sha512-8rFtE7EP5ssOwGpN2dt1Q4njl0N1hUXJ7sSPz0leU2hRdq6+pra57z4YPBlVqm40vcgv6ooKZEAx48fMTv9x4w== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "~1.0.10" + listenercount "~1.0.1" + readable-stream "~2.1.5" + setimmediate "~1.0.4" + +upath@^1.0.5: + version "1.1.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" + integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw== -unzip@^0.1.11: - version "0.1.11" - resolved "https://registry.yarnpkg.com/unzip/-/unzip-0.1.11.tgz#89749c63b058d7d90d619f86b98aa1535d3b97f0" - dependencies: - binary ">= 0.3.0 < 1" - fstream ">= 0.1.30 < 1" - match-stream ">= 0.0.2 < 1" - pullstream ">= 0.4.1 < 1" - readable-stream "~1.0.31" - setimmediate ">= 1.0.1 < 2" - -update-notifier@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-1.0.3.tgz#8f92c515482bd6831b7c93013e70f87552c7cf5a" - dependencies: - boxen "^0.6.0" - chalk "^1.0.0" - configstore "^2.0.0" - is-npm "^1.0.0" - latest-version "^2.0.0" - lazy-req "^1.1.0" - semver-diff "^2.0.0" - xdg-basedir "^2.0.0" - -update-notifier@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.3.0.tgz#4e8827a6bb915140ab093559d7014e3ebb837451" +update-notifier@^2.3.0, update-notifier@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" + integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== dependencies: boxen "^1.2.1" chalk "^2.0.1" configstore "^3.0.0" import-lazy "^2.1.0" + is-ci "^1.0.10" is-installed-globally "^0.1.0" is-npm "^1.0.0" latest-version "^3.0.0" semver-diff "^2.0.0" xdg-basedir "^3.0.0" +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" + urijs@^1.16.1: - version "1.19.0" - resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.0.tgz#d8aa284d0e7469703a6988ad045c4cbfdf08ada0" + version "1.19.1" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.1.tgz#5b0ff530c0cbde8386f6342235ba5ca6e995d25a" + integrity sha512-xVrGVi94ueCJNrBSTjWqjvtgvl3cyOTThp2zaMaFNGp3F542TR6sM3f2o8RqZl+AwteClSVmoCyt0ka4RjQOQg== urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= url-parse-lax@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= dependencies: prepend-http "^1.0.1" +url-template@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" + integrity sha1-/FZaPMy/93MMd19WQflVV5FDnyE= + url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" + integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k= url@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ= dependencies: punycode "1.3.2" querystring "0.2.0" @@ -7199,6 +9646,7 @@ url@0.10.3: url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= dependencies: punycode "1.3.2" querystring "0.2.0" @@ -7206,63 +9654,87 @@ url@^0.11.0: urlsafe-base64@^1.0.0, urlsafe-base64@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz#23f89069a6c62f46cf3a1d3b00169cefb90be0c6" + integrity sha1-I/iQaabGL0bPOh07ABac77kL4MY= -use@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8" - dependencies: - define-property "^0.2.5" - isobject "^3.0.0" - lazy-cache "^2.0.2" +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +utf8-byte-length@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" + integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E= util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" -util@0.10.3, util@^0.10.3: +util@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= dependencies: inherits "2.0.1" +util@^0.10.3: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== + dependencies: + inherits "2.0.3" + utils-merge@1.0.1, utils-merge@1.x.x: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728" - -uuid@3.1.0, uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0: +uuid@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + integrity sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g== -uuid@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" +uuid@3.3.2, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== validate-npm-package-license@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== dependencies: - spdx-correct "~1.0.0" - spdx-expression-parse "~1.0.0" + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" -validator@^9.0.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-9.2.0.tgz#ad216eed5f37cac31a6fe00ceab1f6b88bded03e" +validator@^9.4.1: + version "9.4.1" + resolved "https://registry.yarnpkg.com/validator/-/validator-9.4.1.tgz#abf466d398b561cd243050112c6ff1de6cc12663" + integrity sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA== value-equal@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" + integrity sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw== vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 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" @@ -7271,42 +9743,56 @@ verror@1.10.0: vm-browserify@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" + integrity sha1-XX6kW7755Kb/ZflUOOCofDV9WnM= dependencies: indexof "0.0.1" walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= dependencies: makeerror "1.0.x" warning@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" + integrity sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w= + dependencies: + loose-envify "^1.0.0" + +warning@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607" + integrity sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug== dependencies: loose-envify "^1.0.0" watch@~0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986" + integrity sha1-KAlUdsbffJDJYxOJkMClQj60uYY= dependencies: exec-sh "^0.2.0" minimist "^1.2.0" -watchpack@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" +watchpack@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" + integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== dependencies: - async "^2.1.2" - chokidar "^1.7.0" + chokidar "^2.0.2" graceful-fs "^4.1.2" + neo-async "^2.5.0" -web-push@^3.2.5: - version "3.2.5" - resolved "https://registry.yarnpkg.com/web-push/-/web-push-3.2.5.tgz#7eccf17514f25587105c937be37bf4153b39a271" +web-push@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-push/-/web-push-3.3.3.tgz#8dc7c578dd1243ceb5a8377389424e87ea9b15cc" + integrity sha512-Om4CNZpyzHP3AtGZpbBavCO7I9oCS9CFY2VDfTj/cFx2gm+mAtyK2OlKd6qu9pwCdZTyYanUiyhT0JSrs0ypHQ== dependencies: - asn1.js "^4.8.1" - http_ece "^0.5.2" + asn1.js "^5.0.0" + http_ece "1.0.5" + https-proxy-agent "^2.2.1" jws "^3.1.3" minimist "^1.2.0" urlsafe-base64 "^1.0.0" @@ -7314,62 +9800,72 @@ web-push@^3.2.5: webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= webidl-conversions@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -webpack-node-externals@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-1.6.0.tgz#232c62ec6092b100635a3d29d83c1747128df9bd" +webpack-node-externals@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz#6e1ee79ac67c070402ba700ef033a9b8d52ac4e3" + integrity sha512-ajerHZ+BJKeCLviLUUmnyd5B4RavLF76uv3cs6KNuO8W+HuQaEs0y0L7o40NQxdPy5w0pcv8Ew7yPUAQG0UdCg== -webpack-sources@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54" +webpack-sources@^1.1.0, webpack-sources@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" + integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA== dependencies: source-list-map "^2.0.0" source-map "~0.6.1" -webpack@^3.0.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.10.0.tgz#5291b875078cf2abf42bdd23afe3f8f96c17d725" - dependencies: - acorn "^5.0.0" - acorn-dynamic-import "^2.0.0" - ajv "^5.1.5" - ajv-keywords "^2.0.0" - async "^2.1.2" - enhanced-resolve "^3.4.0" - escope "^3.6.0" - interpret "^1.0.0" - json-loader "^0.5.4" - json5 "^0.5.1" +webpack@^4.23.1: + version "4.27.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.27.1.tgz#5f2e2db446d2266376fa15d7d2277a1a9c2e12bb" + integrity sha512-WArHiLvHrlfyRM8i7f+2SFbr/XbQ0bXqTkPF8JpHOzub5482Y3wx7rEO8stuLGOKOgZJcqcisLhD7LrM/+fVMw== + dependencies: + "@webassemblyjs/ast" "1.7.11" + "@webassemblyjs/helper-module-context" "1.7.11" + "@webassemblyjs/wasm-edit" "1.7.11" + "@webassemblyjs/wasm-parser" "1.7.11" + acorn "^5.6.2" + acorn-dynamic-import "^3.0.0" + ajv "^6.1.0" + ajv-keywords "^3.1.0" + chrome-trace-event "^1.0.0" + enhanced-resolve "^4.1.0" + eslint-scope "^4.0.0" + json-parse-better-errors "^1.0.2" loader-runner "^2.3.0" loader-utils "^1.1.0" memory-fs "~0.4.1" + micromatch "^3.1.8" mkdirp "~0.5.0" + neo-async "^2.5.0" node-libs-browser "^2.0.0" - source-map "^0.5.3" - supports-color "^4.2.1" - tapable "^0.2.7" - uglifyjs-webpack-plugin "^0.4.6" - watchpack "^1.4.0" - webpack-sources "^1.0.1" - yargs "^8.0.2" + schema-utils "^0.4.4" + tapable "^1.1.0" + terser-webpack-plugin "^1.1.0" + watchpack "^1.5.0" + webpack-sources "^1.3.0" whatwg-encoding@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz#57c235bc8657e914d24e1a397d3c82daee0a6ba3" + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== dependencies: - iconv-lite "0.4.19" + iconv-lite "0.4.24" whatwg-fetch@>=0.10.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" + integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== whatwg-url@^4.3.0: version "4.8.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0" + integrity sha1-0pgaqRSMHgCkHFphMRZqtGg7vMA= dependencies: tr46 "~0.0.3" webidl-conversions "^3.0.0" @@ -7377,71 +9873,74 @@ whatwg-url@^4.3.0: which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= which@1.2.x: version "1.2.14" resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" + integrity sha1-mofEN48D6CfOyvGs31bHNsAcFOU= dependencies: isexe "^2.0.0" -which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" +which@^1.2.12, which@^1.2.9, which@^1.3.0: + 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" wide-align@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" - dependencies: - string-width "^1.0.2" - -widest-line@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-1.0.0.tgz#0c09c85c2a94683d0d7eaf8ee097d564bf0e105c" + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== dependencies: - string-width "^1.0.1" + string-width "^1.0.2 || 2" widest-line@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.0.tgz#0142a4e8a243f8882c0233aa0e0281aa76152273" + version "2.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" + integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== dependencies: string-width "^2.1.1" -window-size@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" - window-size@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" + integrity sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU= -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" +windows-release@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.1.0.tgz#8d4a7e266cbf5a233f6c717dac19ce00af36e12e" + integrity sha512-hBb7m7acFgQPQc222uEQTmdcGLeBmQLNLFIh0rDk3CwFOBrfjefLzEfEfmpMq8Af/n/GnFf3eYf203FY1PmudA== + dependencies: + execa "^0.10.0" wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= 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.3.1: - version "1.5.2" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.2.tgz#32b312e5dc3d5d45d79ef44acc2587491cd729ae" +worker-farm@^1.3.1, worker-farm@^1.5.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0" + integrity sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ== dependencies: - errno "^0.1.4" - xtend "^4.0.1" + errno "~0.1.7" wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= dependencies: string-width "^1.0.1" strip-ansi "^3.0.1" @@ -7449,10 +9948,12 @@ wrap-ansi@^2.0.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write-file-atomic@^1.1.2, write-file-atomic@^1.1.4: +write-file-atomic@^1.1.4: version "1.3.4" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f" + integrity sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8= dependencies: graceful-fs "^4.1.11" imurmurhash "^0.1.4" @@ -7461,36 +9962,45 @@ write-file-atomic@^1.1.2, write-file-atomic@^1.1.4: write-file-atomic@^2.0.0, write-file-atomic@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab" + integrity sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA== dependencies: graceful-fs "^4.1.11" imurmurhash "^0.1.4" signal-exit "^3.0.2" -ws@^3.0.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" +ws@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== dependencies: async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" -xdg-basedir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2" +ws@^6.0.0: + version "6.1.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.2.tgz#3cc7462e98792f0ac679424148903ded3b9c3ad8" + integrity sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw== dependencies: - os-homedir "^1.0.0" + async-limiter "~1.0.0" + +x-xss-protection@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/x-xss-protection/-/x-xss-protection-1.1.0.tgz#4f1898c332deb1e7f2be1280efb3e2c53d69c1a7" + integrity sha512-rx3GzJlgEeZ08MIcDsU2vY2B1QEriUKJTSiNHHUIem6eg9pzVOr2TL3Y4Pd6TMAM5D5azGjcxqI62piITBDHVg== xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" + integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= xml-name-validator@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" + integrity sha1-TYuPHszTQZqjYgYb7O9RXh5VljU= xml2js@0.4.17: version "0.4.17" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868" + integrity sha1-F76T6q4/O3eTWceVtBlwWogX6Gg= dependencies: sax ">=0.6.0" xmlbuilder "^4.1.0" @@ -7498,34 +10008,51 @@ xml2js@0.4.17: xmlbuilder@4.2.1, xmlbuilder@^4.1.0: version "4.2.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5" + integrity sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU= dependencies: lodash "^4.0.0" xmldom@0.1.x: version "0.1.27" resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" + integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk= -xtend@^4.0.0, xtend@^4.0.1: +xtend@^4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= xtraverse@0.1.x: version "0.1.0" resolved "https://registry.yarnpkg.com/xtraverse/-/xtraverse-0.1.0.tgz#b741bad018ef78d8a9d2e83ade007b3f7959c732" + integrity sha1-t0G60BjveNip0ug63gB7P3lZxzI= dependencies: xmldom "0.1.x" y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + +yallist@^3.0.0, yallist@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" + integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== yargs-parser@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4" + integrity sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ= dependencies: camelcase "^3.0.0" lodash.assign "^4.0.6" @@ -7533,12 +10060,39 @@ yargs-parser@^2.4.1: yargs-parser@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" + integrity sha1-jQrELxbqVd69MyyvTEA4s+P139k= dependencies: camelcase "^4.1.0" +yargs-parser@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" + integrity sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc= + dependencies: + camelcase "^4.1.0" + +yargs@^11.0.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" + integrity sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A== + dependencies: + cliui "^4.0.0" + decamelize "^1.1.1" + find-up "^2.1.0" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^9.0.2" + yargs@^4.2.0: version "4.8.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" + integrity sha1-wMQpJMpKqmsObaFznfshZDn53cA= dependencies: cliui "^3.2.0" decamelize "^1.1.1" @@ -7555,27 +10109,10 @@ yargs@^4.2.0: y18n "^3.2.1" yargs-parser "^2.4.1" -yargs@^8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" - dependencies: - camelcase "^4.1.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^2.0.0" - read-pkg-up "^2.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1" - yargs-parser "^7.0.0" - yargs@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-9.0.1.tgz#52acc23feecac34042078ee78c0c007f5085db4c" + integrity sha1-UqzCP+7Kw0BCB47njAwAf1CF20w= dependencies: camelcase "^4.1.0" cliui "^3.2.0" @@ -7591,25 +10128,14 @@ yargs@^9.0.0: y18n "^3.2.1" yargs-parser "^7.0.0" -yargs@~3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" - dependencies: - camelcase "^1.0.2" - cliui "^2.1.0" - decamelize "^1.0.0" - window-size "0.1.0" - -zen-observable-ts@^0.8.6: - version "0.8.8" - resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.8.tgz#1a586dc204fa5632a88057f879500e0d2ba06869" +zen-observable-ts@^0.8.11: + version "0.8.11" + resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.11.tgz#d54a27cd17dc4b4bb6bd008e5c096af7fcb068a9" + integrity sha512-8bs7rgGV4kz5iTb9isudkuQjtWwPnQ8lXq6/T76vrepYZVMsDEv6BXaEA+DHdJSK3KVLduagi9jSpSAJ5NgKHw== dependencies: - zen-observable "^0.7.0" - -zen-observable@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.6.1.tgz#01dbed3bc8d02cbe9ee1112c83e04c807f647244" + zen-observable "^0.8.0" -zen-observable@^0.7.0: - version "0.7.1" - resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.7.1.tgz#f84075c0ee085594d3566e1d6454207f126411b3" +zen-observable@^0.8.0: + version "0.8.11" + resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.11.tgz#d3415885eeeb42ee5abb9821c95bb518fcd6d199" + integrity sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ== diff --git a/athena/index.js b/athena/index.js index 395146798c..0de03942c3 100644 --- a/athena/index.js +++ b/athena/index.js @@ -6,68 +6,72 @@ import processMessageNotification from './queues/new-message-in-thread'; import processMentionNotification from './queues/mention-notification'; import processDirectMessageNotification from './queues/direct-message-notification'; import processReactionNotification from './queues/reaction-notification'; +import processThreadReactionNotification from './queues/thread-reaction-notification'; import processChannelNotification from './queues/channel-notification'; import processCommunityNotification from './queues/community-notification'; import processThreadNotification from './queues/thread-notification'; -import processSlackImport from './queues/slack-import'; import processCommunityInvite from './queues/community-invite'; -import processCommunityInvoicePaid from './queues/community-invoice-paid'; -import processProInvoicePaid from './queues/pro-invoice-paid'; import trackUserThreadLastSeen from './queues/track-user-thread-last-seen'; import processAdminMessageModeration from './queues/moderationEvents/message'; import processAdminThreadModeration from './queues/moderationEvents/thread'; import processUserRequestedJoinPrivateChannel from './queues/private-channel-request-sent'; import processUserRequestPrivateChannelApproved from './queues/private-channel-request-approved'; +import processUserRequestedJoinPrivateCommunity from './queues/private-community-request-sent'; +import processUserRequestPrivateCommunityApproved from './queues/private-community-request-approved'; import processPushNotifications from './queues/send-push-notifications'; import startNotificationsListener from './listeners/notifications'; +import processSendSlackInvitations from './queues/send-slack-invitations'; import { MESSAGE_NOTIFICATION, MENTION_NOTIFICATION, DIRECT_MESSAGE_NOTIFICATION, REACTION_NOTIFICATION, + THREAD_REACTION_NOTIFICATION, CHANNEL_NOTIFICATION, COMMUNITY_NOTIFICATION, THREAD_NOTIFICATION, - SLACK_IMPORT, COMMUNITY_INVITE_NOTIFICATION, - COMMUNITY_INVOICE_PAID_NOTIFICATION, - PRO_INVOICE_PAID_NOTIFICATION, PROCESS_ADMIN_TOXIC_MESSAGE, PROCESS_ADMIN_TOXIC_THREAD, PRIVATE_CHANNEL_REQUEST_SENT, PRIVATE_CHANNEL_REQUEST_APPROVED, + PRIVATE_COMMUNITY_REQUEST_SENT, + PRIVATE_COMMUNITY_REQUEST_APPROVED, SEND_PUSH_NOTIFICATIONS, + TRACK_USER_LAST_SEEN, + SEND_SLACK_INVITIATIONS, } from './queues/constants'; const PORT = process.env.PORT || 3003; -console.log('\n🛠 Athena, the processing worker, is starting...'); +debug('\n🛠 Athena, the processing worker, is starting...'); debug('Logging with debug enabled!'); -console.log(''); +debug(''); const server = createWorker({ [MESSAGE_NOTIFICATION]: processMessageNotification, [MENTION_NOTIFICATION]: processMentionNotification, [DIRECT_MESSAGE_NOTIFICATION]: processDirectMessageNotification, [REACTION_NOTIFICATION]: processReactionNotification, + [THREAD_REACTION_NOTIFICATION]: processThreadReactionNotification, [CHANNEL_NOTIFICATION]: processChannelNotification, [COMMUNITY_NOTIFICATION]: processCommunityNotification, [THREAD_NOTIFICATION]: processThreadNotification, - [SLACK_IMPORT]: processSlackImport, + [SEND_SLACK_INVITIATIONS]: processSendSlackInvitations, [COMMUNITY_INVITE_NOTIFICATION]: processCommunityInvite, - [COMMUNITY_INVOICE_PAID_NOTIFICATION]: processCommunityInvoicePaid, - [PRO_INVOICE_PAID_NOTIFICATION]: processProInvoicePaid, - 'track user thread last seen': trackUserThreadLastSeen, + [TRACK_USER_LAST_SEEN]: trackUserThreadLastSeen, [PROCESS_ADMIN_TOXIC_MESSAGE]: processAdminMessageModeration, [PROCESS_ADMIN_TOXIC_THREAD]: processAdminThreadModeration, [PRIVATE_CHANNEL_REQUEST_SENT]: processUserRequestedJoinPrivateChannel, [PRIVATE_CHANNEL_REQUEST_APPROVED]: processUserRequestPrivateChannelApproved, + [PRIVATE_COMMUNITY_REQUEST_SENT]: processUserRequestedJoinPrivateCommunity, + [PRIVATE_COMMUNITY_REQUEST_APPROVED]: processUserRequestPrivateCommunityApproved, [SEND_PUSH_NOTIFICATIONS]: processPushNotifications, }); startNotificationsListener(); -console.log( +debug( `🗄 Queues open for business ${(process.env.NODE_ENV === 'production' && // $FlowIssue `at ${process.env.COMPOSE_REDIS_URL}:${process.env.COMPOSE_REDIS_PORT}`) || @@ -76,7 +80,7 @@ console.log( // $FlowIssue server.listen(PORT, 'localhost', () => { - console.log( + debug( `💉 Healthcheck server running at ${server.address().address}:${ server.address().port }` diff --git a/athena/models/channel.js b/athena/models/channel.js index 218e984262..b13f164ef5 100644 --- a/athena/models/channel.js +++ b/athena/models/channel.js @@ -1,5 +1,5 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); import type { DBChannel } from 'shared/types'; export const getChannelById = (id: string): Promise => { diff --git a/athena/models/channelSettings.js b/athena/models/channelSettings.js new file mode 100644 index 0000000000..66e2e7c089 --- /dev/null +++ b/athena/models/channelSettings.js @@ -0,0 +1,13 @@ +// @flow +const { db } = require('shared/db'); + +export const getChannelSettings = (id: string) => { + return db + .table('channelSettings') + .getAll(id, { index: 'channelId' }) + .run() + .then(data => { + if (!data || data.length === 0) return null; + return data[0]; + }); +}; diff --git a/athena/models/community.js b/athena/models/community.js index 10cfbeec27..ecaf98d934 100644 --- a/athena/models/community.js +++ b/athena/models/community.js @@ -1,5 +1,5 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); import type { DBCommunity } from 'shared/types'; export const getCommunityById = (id: string): Promise => { diff --git a/athena/models/communitySettings.js b/athena/models/communitySettings.js new file mode 100644 index 0000000000..05d368c1ab --- /dev/null +++ b/athena/models/communitySettings.js @@ -0,0 +1,73 @@ +// @flow +const { db } = require('shared/db'); +import axios from 'axios'; +const querystring = require('querystring'); +import { decryptString } from 'shared/encryption'; + +const defaultSlackSettings = { + connectedAt: null, + connectedBy: null, + teamName: null, + teamId: null, + scope: null, + token: null, + invitesSentAt: null, + invitesMemberCount: null, + invitesCustomMessage: null, +}; + +export const getCommunitySettings = (id: string) => { + return db + .table('communitySettings') + .getAll(id, { index: 'communityId' }) + .run() + .then(data => { + if (!data || data.length === 0) return null; + return data[0]; + }); +}; + +export const resetCommunitySlackSettings = (id: string) => { + return db + .table('communitySettings') + .getAll(id, { index: 'communityId' }) + .update({ + slackSettings: { + ...defaultSlackSettings, + }, + }) + .run(); +}; + +export const updateSlackInvitesMemberCount = (id: string, count: number) => { + return db + .table('communitySettings') + .getAll(id, { index: 'communityId' }) + .update({ + slackSettings: { + invitesMemberCount: count, + }, + }) + .run(); +}; + +export const getSlackUserListData = (token: string, scope: string) => { + const decryptedToken = decryptString(token); + + return axios + .post( + 'https://slack.com/api/users.list', + querystring.stringify({ + token: decryptedToken, + scope: scope, + }) + ) + .then(response => { + // if the response is valid + if (response.data && response.data.ok) { + return response.data.members; + } + + return null; + }); +}; diff --git a/athena/models/db.js b/athena/models/db.js deleted file mode 100644 index 950125d657..0000000000 --- a/athena/models/db.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Database setup is done here - */ -const IS_PROD = !process.env.FORCE_DEV && process.env.NODE_ENV === 'production'; - -const DEFAULT_CONFIG = { - db: 'spectrum', - max: 100, // Maximum number of connections, default is 1000 - buffer: 10, // Minimum number of connections open at any given moment, default is 50 - timeoutGb: 60 * 1000, // How long should an unused connection stick around, default is an hour, this is a minute -}; - -const PRODUCTION_CONFIG = { - password: process.env.AWS_RETHINKDB_PASSWORD, - host: process.env.AWS_RETHINKDB_URL, - port: process.env.AWS_RETHINKDB_PORT, -}; - -const config = IS_PROD - ? { - ...DEFAULT_CONFIG, - ...PRODUCTION_CONFIG, - } - : { - ...DEFAULT_CONFIG, - }; - -var r = require('rethinkdbdash')(config); - -module.exports = { db: r }; diff --git a/athena/models/directMessageThread.js b/athena/models/directMessageThread.js index ac3209b98b..5f646c527b 100644 --- a/athena/models/directMessageThread.js +++ b/athena/models/directMessageThread.js @@ -1,5 +1,5 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); import type { DBDirectMessageThread } from 'shared/types'; export const getDirectMessageThreadById = ( diff --git a/athena/models/message.js b/athena/models/message.js index cdfa3e8316..ab04f84c2d 100644 --- a/athena/models/message.js +++ b/athena/models/message.js @@ -1,5 +1,5 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); import type { DBMessage } from 'shared/types'; export const getMessageById = (id: string): Promise => { diff --git a/athena/models/notification.js b/athena/models/notification.js index ae52876dc6..548714f5f6 100644 --- a/athena/models/notification.js +++ b/athena/models/notification.js @@ -1,5 +1,5 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); import type { NotificationEventType, DBNotification } from 'shared/types'; import { TIME_BUFFER } from '../queues/constants'; import { NEW_DOCUMENTS } from 'api/models/utils'; @@ -32,7 +32,7 @@ export const checkForExistingNotification = ( return notifications[0]; }) .catch(err => { - console.log(err); + console.error(err); return null; }); }; @@ -81,6 +81,15 @@ export const getNotifications = (notificationIds: Array) => { .run(); }; +export const getNotification = ( + notificationId: string +): Promise => { + return db + .table('notifications') + .get(notificationId) + .run(); +}; + const hasChanged = (field: string) => db .row('old_val')(field) diff --git a/athena/models/recurringPayment.js b/athena/models/recurringPayment.js deleted file mode 100644 index c8b63564b8..0000000000 --- a/athena/models/recurringPayment.js +++ /dev/null @@ -1,16 +0,0 @@ -// @flow -const { db } = require('./db'); -import type { DBInvoice, DBRecurringPayment } from 'shared/types'; - -export const getRecurringPaymentFromInvoice = ( - invoice: DBInvoice -): Promise => { - return db - .table('recurringPayments') - .filter({ - planId: invoice.planId, - customerId: invoice.customerId, - }) - .run() - .then(result => (result.length > 0 ? result[0] : null)); -}; diff --git a/athena/models/slackImports.js b/athena/models/slackImports.js deleted file mode 100644 index 14b22833db..0000000000 --- a/athena/models/slackImports.js +++ /dev/null @@ -1,39 +0,0 @@ -// @flow -const { db } = require('./db'); -import axios from 'axios'; -const querystring = require('querystring'); -import type { DBSlackUser, DBSlackImport } from 'shared/types'; - -export const getSlackUserListData = (token: string) => { - return axios - .post( - 'https://slack.com/api/users.list', - querystring.stringify({ - token: token, - scope: 'users:read.email,users:read, admin', - }) - ) - .then(response => { - // if the response is valid - if (response.data && response.data.ok) { - return response.data.members; - } - - return; - }) - .catch(error => { - console.log('\n\nerror', error); - return null; - }); -}; - -export const saveSlackImportData = ( - importId: string, - members: Array -): Promise => { - return db - .table('slackImports') - .get(importId) - .update({ members }) - .run(); -}; diff --git a/athena/models/thread.js b/athena/models/thread.js index ed831bac15..baacdfcfb5 100644 --- a/athena/models/thread.js +++ b/athena/models/thread.js @@ -1,5 +1,5 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); import type { DBThread } from 'shared/types'; export const getThreadById = (id: string): Promise => { diff --git a/athena/models/user.js b/athena/models/user.js deleted file mode 100644 index 608f45da55..0000000000 --- a/athena/models/user.js +++ /dev/null @@ -1,37 +0,0 @@ -// @flow -const { db } = require('./db'); -import type { DBUser } from 'shared/types'; - -export const getUsers = (ids: Array): Promise> => { - return db - .table('users') - .getAll(...ids) - .run(); -}; - -export const getUserById = (id: string): Promise => { - return db - .table('users') - .get(id) - .run(); -}; - -export const getUserByEmail = (email: string): Promise => { - return db - .table('users') - .getAll(email, { index: 'email' }) - .then(data => { - if (!data || data.length === 0) return null; - return data[0]; - }); -}; - -export const getUserByUsername = (username: string): Promise => { - return db - .table('users') - .getAll(username, { index: 'username' }) - .then(data => { - if (!data || data.length === 0) return null; - return data[0]; - }); -}; diff --git a/athena/models/usersChannels.js b/athena/models/usersChannels.js index 9aaebafcb6..52a16c9778 100644 --- a/athena/models/usersChannels.js +++ b/athena/models/usersChannels.js @@ -1,13 +1,18 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); export const getMembersInChannelWithNotifications = ( channelId: string ): Promise> => { return db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ isMember: true, receiveNotifications: true }) + .getAll( + [channelId, 'member'], + [channelId, 'moderator'], + [channelId, 'owner'], + { index: 'channelIdAndRole' } + ) + .filter({ receiveNotifications: true }) .group('userId') .run() .then(users => users.map(u => u.group)); @@ -19,8 +24,7 @@ export const getUserPermissionsInChannel = ( ): Promise => { return db .table('usersChannels') - .getAll(userId, { index: 'userId' }) - .filter({ channelId }) + .getAll([userId, channelId], { index: 'userIdAndChannelId' }) .group('userId') .run() .then(groups => { @@ -43,8 +47,17 @@ export const getOwnersInChannel = ( ): Promise> => { return db .table('usersChannels') - .getAll(channelId, { index: 'channelId' }) - .filter({ isOwner: true }) + .getAll([channelId, 'owner'], { index: 'channelIdAndRole' }) + .map(user => user('userId')) + .run(); +}; + +export const getModeratorsInChannel = ( + channelId: string +): Promise> => { + return db + .table('usersChannels') + .getAll([channelId, 'moderator'], { index: 'channelIdAndRole' }) .map(user => user('userId')) .run(); }; diff --git a/athena/models/usersCommunities.js b/athena/models/usersCommunities.js index c5a0939703..f0b7acc306 100644 --- a/athena/models/usersCommunities.js +++ b/athena/models/usersCommunities.js @@ -1,15 +1,14 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); export const getMembersInCommunity = ( communityId: string ): Promise> => { return db .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter({ isMember: true, receiveNotifications: true }) - .run() - .then(users => users.map(user => user.userId)); + .getAll([communityId, true], { index: 'communityIdAndIsMember' }) + .filter({ receiveNotifications: true })('userId') + .run(); }; export const getOwnersInCommunity = ( @@ -17,10 +16,19 @@ export const getOwnersInCommunity = ( ): Promise> => { return db .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter({ isOwner: true }) - .run() - .then(users => users.map(user => user.userId)); + .getAll([communityId, true], { index: 'communityIdAndIsOwner' })('userId') + .run(); +}; + +export const getModeratorsInCommunity = ( + communityId: string +): Promise> => { + return db + .table('usersCommunities') + .getAll([communityId, true], { index: 'communityIdAndIsModerator' })( + 'userId' + ) + .run(); }; export const getUserPermissionsInCommunity = ( @@ -29,8 +37,7 @@ export const getUserPermissionsInCommunity = ( ): Promise => { return db .table('usersCommunities') - .getAll(communityId, { index: 'communityId' }) - .filter({ userId }) + .getAll([userId, communityId], { index: 'userIdAndCommunityId' }) .run() .then(data => { // if a record exists diff --git a/athena/models/usersDirectMessageThreads.js b/athena/models/usersDirectMessageThreads.js index b1314210b3..30ff77574e 100644 --- a/athena/models/usersDirectMessageThreads.js +++ b/athena/models/usersDirectMessageThreads.js @@ -1,5 +1,5 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); export const getDirectMessageThreadMembers = ( id: string diff --git a/athena/models/usersNotifications.js b/athena/models/usersNotifications.js deleted file mode 100644 index 19a531e4c4..0000000000 --- a/athena/models/usersNotifications.js +++ /dev/null @@ -1,74 +0,0 @@ -// @flow -const { db } = require('./db'); -import type { DBUsersNotifications } from 'shared/types'; - -export const storeUsersNotifications = ( - notificationId: string, - userId: string -): Promise => { - return db - .table('usersNotifications') - .insert({ - createdAt: new Date(), - entityAddedAt: new Date(), - notificationId, - userId, - isSeen: false, - isRead: false, - }) - .run(); -}; - -export const markUsersNotificationsAsNew = ( - notificationId: string, - userId: string -): Promise => { - return db - .table('usersNotifications') - .getAll(notificationId, { index: 'notificationId' }) - .filter({ userId }) - .run() - .then(result => { - /* - If a user becomes a new participant on the notification before the time buffer runs out, we need to ensure that we include them in setting a notification. - - So in this section we check to see if an existing usersNotifications row exists, otherwise we create a new one. All users passed into this function should return an updated or new usersNotifications record. - */ - if (result && result.length > 0) { - return db - .table('usersNotifications') - .getAll(notificationId, { index: 'notificationId' }) - .filter({ userId }) - .update({ - isRead: false, - isSeen: false, - entityAddedAt: new Date(), - }) - .run(); - } else { - return storeUsersNotifications(notificationId, userId); - } - }); -}; - -// marks one notification as read -export const markSingleNotificationSeen = ( - notificationId: string, - userId: string -): Promise => { - return db - .table('usersNotifications') - .getAll(notificationId, { index: 'notificationId' }) - .filter({ - userId, - }) - .update( - { - isSeen: true, - }, - { returnChanges: true } - ) - .run() - .then(() => true) - .catch(err => false); -}; diff --git a/athena/models/usersSettings.js b/athena/models/usersSettings.js index f864381a4d..168aec5087 100644 --- a/athena/models/usersSettings.js +++ b/athena/models/usersSettings.js @@ -1,5 +1,5 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); import type { DBUsersSettings } from 'shared/types'; export const getUsersSettings = (userId: string): Promise => { diff --git a/athena/models/usersThreads.js b/athena/models/usersThreads.js index 52e1f49bc1..c9c50f62d4 100644 --- a/athena/models/usersThreads.js +++ b/athena/models/usersThreads.js @@ -1,13 +1,13 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); import type { DBUsersThreads } from 'shared/types'; export const getThreadNotificationUsers = ( - id: string + threadId: string ): Promise> => { return db .table('usersThreads') - .getAll(id, { index: 'threadId' }) + .getAll(threadId, { index: 'threadId' }) .filter({ receiveNotifications: true }) .eqJoin('userId', db.table('users')) .without({ right: ['id', 'createdAt'] }) @@ -21,8 +21,7 @@ export const getUsersThread = ( ): Promise => { return db .table('usersThreads') - .getAll(userId, { index: 'userId' }) - .filter({ threadId }) + .getAll([userId, threadId], { index: 'userIdAndThreadId' }) .run() .then(data => { // if no record exists @@ -38,8 +37,7 @@ export const getUserNotificationPermissionsInThread = ( ): Promise => { return db .table('usersThreads') - .getAll(userId, { index: 'userId' }) - .filter({ threadId }) + .getAll([userId, threadId], { index: 'userIdAndThreadId' }) .run() .then(data => data[0].receiveNotifications); }; @@ -51,8 +49,7 @@ export const setUserThreadLastSeen = ( ): Promise => { return db .table('usersThreads') - .getAll(userId, { index: 'userId' }) - .filter({ threadId }) + .getAll([userId, threadId], { index: 'userIdAndThreadId' }) .update({ lastSeen, }) diff --git a/athena/models/web-push-subscription.js b/athena/models/web-push-subscription.js index 6263248800..c717b7fe47 100644 --- a/athena/models/web-push-subscription.js +++ b/athena/models/web-push-subscription.js @@ -1,6 +1,6 @@ // @flow const debug = require('debug')('api:models:webPushSubscription'); -const { db } = require('./db'); +const { db } = require('shared/db'); import type { WebPushSubscription } from 'api/mutations/user'; export const storeSubscription = ( diff --git a/athena/package.json b/athena/package.json index 779e50e007..a19681aac8 100644 --- a/athena/package.json +++ b/athena/package.json @@ -5,17 +5,32 @@ "start": "NODE_ENV=production node main.js" }, "dependencies": { + "aws-sdk": "^2.383.0", "axios": "^0.16.2", "bull": "3.3.10", - "draft-js": "^0.10.3", + "cryptr": "^3.0.0", + "datadog-metrics": "^0.8.1", + "debug": "^4.1.1", + "decode-uri-component": "^0.2.0", + "draft-js": "^0.10.5", "emoji-regex": "^6.5.1", - "expo-server-sdk": "^2.3.3", + "escape-html": "^1.0.3", + "faker": "^4.1.0", + "imgix-core-js": "^1.2.0", + "ioredis": "3.2.2", + "lodash.intersection": "^4.4.0", "node-env-file": "^0.1.8", - "now-env": "^3.0.1", - "raven": "^2.1.1", - "rethinkdbdash": "^2.3.29", - "source-map-support": "^0.4.15", - "validator": "^9.2.0", - "web-push": "^3.2.2" + "now-env": "^3.1.0", + "performance-now": "^2.1.0", + "raven": "^2.6.4", + "redis-tag-cache": "^1.2.1", + "rethinkdb-inspector": "^0.3.3", + "rethinkdbdash": "^2.3.31", + "rethinkhaberdashery": "^2.3.32", + "sanitize-filename": "^1.6.1", + "source-map-support": "^0.4.18", + "toobusy-js": "^0.5.1", + "validator": "^9.4.1", + "web-push": "^3.3.3" } } diff --git a/athena/queues/channel-notification.js b/athena/queues/channel-notification.js index 0427893849..55396edea5 100644 --- a/athena/queues/channel-notification.js +++ b/athena/queues/channel-notification.js @@ -12,7 +12,7 @@ import { import { storeUsersNotifications, markUsersNotificationsAsNew, -} from '../models/usersNotifications'; +} from 'shared/db/queries/usersNotifications'; import type { Job, ChannelNotificationJobData } from 'shared/bull/types'; export default async (job: Job) => { @@ -88,9 +88,8 @@ export default async (job: Job) => { // for each person who should receie an updated notification, mark their notification as unseen and unread return Promise.all([notificationPromises]).catch(err => { - debug('❌ Error in job:\n'); - debug(err); + console.error('❌ Error in job:\n'); + console.error(err); Raven.captureException(err); - console.log(err); }); }; diff --git a/athena/queues/community-invite.js b/athena/queues/community-invite.js index a3f5800615..839b3bf37c 100644 --- a/athena/queues/community-invite.js +++ b/athena/queues/community-invite.js @@ -4,19 +4,22 @@ import Raven from '../../shared/raven'; import { fetchPayload } from '../utils/payloads'; import { getUserPermissionsInCommunity } from '../models/usersCommunities'; import { storeNotification } from '../models/notification'; -import { getUserByEmail } from '../models/user'; +import { getUserByEmail } from 'shared/db/queries/user'; import createQueue from '../../shared/bull/create-queue'; -import { storeUsersNotifications } from '../models/usersNotifications'; +import { storeUsersNotifications } from 'shared/db/queries/usersNotifications'; +import { getCommunitySettings } from '../models/communitySettings'; import { SEND_COMMUNITY_INVITE_EMAIL } from './constants'; const sendCommunityInviteEmailQueue = createQueue(SEND_COMMUNITY_INVITE_EMAIL); import type { CommunityInviteNotificationJobData, Job, } from 'shared/bull/types'; +import { signCommunity, signUser } from 'shared/imgix'; const addToSendCommunityInviteEmailQueue = ( recipient, community, + communitySettings, sender, customMessage ) => { @@ -29,8 +32,9 @@ const addToSendCommunityInviteEmailQueue = ( { to: recipient.email, recipient, - sender, - community, + sender: signUser(sender), + community: signCommunity(community), + communitySettings, customMessage, }, { @@ -80,6 +84,7 @@ export default async (job: Job) => { const context = await fetchPayload('COMMUNITY', communityId); const communityToInvite = JSON.parse(context.payload); + const communitySettings = await getCommunitySettings(communityId); const sender = JSON.parse(actor.payload); // if the recipient of the email is not a member of spectrum, pass their information along to the email queue @@ -88,11 +93,12 @@ export default async (job: Job) => { return addToSendCommunityInviteEmailQueue( inboundRecipient, communityToInvite, + communitySettings, sender, customMessage ).catch(err => { + console.error(err); Raven.captureException(err); - console.log(err); }); } else { // the user exists on spectrum @@ -124,8 +130,12 @@ export default async (job: Job) => { const updatedNotification = await storeNotification(newNotification); const sendInvite = await addToSendCommunityInviteEmailQueue( - inboundRecipient, + { + ...inboundRecipient, + userId: existingUser.id, + }, communityToInvite, + communitySettings, sender, customMessage ); @@ -139,10 +149,9 @@ export default async (job: Job) => { sendInvite, usersNotification, ]).catch(err => { - debug('❌ Error in job:\n'); - debug(err); + console.error('❌ Error in job:\n'); + console.error(err); Raven.captureException(err); - console.log(err); }); } }; diff --git a/athena/queues/community-invoice-paid.js b/athena/queues/community-invoice-paid.js deleted file mode 100644 index b857c1b446..0000000000 --- a/athena/queues/community-invoice-paid.js +++ /dev/null @@ -1,81 +0,0 @@ -// @flow -const debug = require('debug')( - 'athena:queue:community-invoice-paid-notification' -); -import Raven from '../../shared/raven'; -import createQueue from '../../shared/bull/create-queue'; -import { SEND_COMMUNITY_INVOICE_RECEIPT_EMAIL } from './constants'; -import { convertTimestampToDate } from '../utils/timestamp-to-date'; -import { getCommunityById } from '../models/community'; -import { getUsers } from '../models/user'; -import { getOwnersInCommunity } from '../models/usersCommunities'; -import type { Job, InvoiceJobData } from 'shared/bull/types'; - -const sendCommunityInvoiceReceiptQueue = createQueue( - SEND_COMMUNITY_INVOICE_RECEIPT_EMAIL -); - -export default async (job: Job) => { - const { invoice } = job.data; - - debug('processing community invoice'); - // Note(@mxstbr): I don't know why we have to do this twice, otherwise Flow complains :roll_eyes: - if (typeof invoice.communityId !== 'string') return; - const community = await getCommunityById(invoice.communityId); - if (typeof invoice.communityId !== 'string') return; - const ownersIds = await getOwnersInCommunity(invoice.communityId); - const owners = await getUsers(ownersIds); - - if (!owners || !community) - return new Error( - 'No owners or community found for an invoice being generated' - ); - debug('owners and community found'); - - const sendOwnerEmails = owners.map(owner => { - // convert 5000 => $50.00 - debug('sending an owner email'); - const amount = `$${(invoice.amount / 100) - .toFixed(2) - .replace(/(\d)(?=(\d{3})+\.)/g, '$1,')}`; - const paidAt = convertTimestampToDate(invoice.paidAt * 1000); - const brand = invoice.sourceBrand; - const last4 = invoice.sourceLast4; - const { id } = invoice; - - const memberCountString = quantity => { - return `${quantity <= 1 ? '1' : (quantity - 1) * 1000} - ${ - quantity <= 1 ? '' : `${quantity - 1},` - }999 members`; - }; - - return sendCommunityInvoiceReceiptQueue.add( - { - to: owner.email, - community: { - name: community.name, - }, - invoice: { - amount, - paidAt, - brand, - last4, - planName: invoice.planName, - memberCount: memberCountString(invoice.quantity), - id, - }, - }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - }); - - return Promise.all(sendOwnerEmails).catch(err => { - debug('❌ Error in job:\n'); - debug(err); - Raven.captureException(err); - console.log(err); - }); -}; diff --git a/athena/queues/community-notification.js b/athena/queues/community-notification.js index 809bd0aa6f..bf24a68edc 100644 --- a/athena/queues/community-notification.js +++ b/athena/queues/community-notification.js @@ -12,7 +12,7 @@ import { import { storeUsersNotifications, markUsersNotificationsAsNew, -} from '../models/usersNotifications'; +} from 'shared/db/queries/usersNotifications'; import type { Job, CommunityNotificationJobData } from 'shared/bull/types'; export default async (job: Job) => { @@ -76,9 +76,8 @@ export default async (job: Job) => { try { return Promise.all([notificationPromises]); } catch (err) { - debug('❌ Error in job:\n'); - debug(err); + console.error('❌ Error in job:\n'); + console.error(err); Raven.captureException(err); - console.log(err); } }; diff --git a/athena/queues/constants.js b/athena/queues/constants.js index ead35df80f..2a751b78c2 100644 --- a/athena/queues/constants.js +++ b/athena/queues/constants.js @@ -8,18 +8,13 @@ export const MESSAGE_NOTIFICATION = 'message notification'; export const MENTION_NOTIFICATION = 'mention notification'; export const DIRECT_MESSAGE_NOTIFICATION = 'direct message notification'; export const REACTION_NOTIFICATION = 'reaction notification'; +export const THREAD_REACTION_NOTIFICATION = 'thread reaction notification'; export const CHANNEL_NOTIFICATION = 'channel notification'; export const COMMUNITY_NOTIFICATION = 'community notification'; export const THREAD_NOTIFICATION = 'thread notification'; export const COMMUNITY_INVITE_NOTIFICATION = 'community invite notification'; export const SEND_COMMUNITY_INVITE_EMAIL = 'send community invite email'; export const SLACK_IMPORT = 'slack import'; -export const SEND_NEW_MESSAGE_EMAIL = 'send new message email'; -export const SEND_MENTION_THREAD_NOTIFICATION_EMAIL = - 'send thread mention email'; -export const SEND_MENTION_MESSAGE_NOTIFICATION_EMAIL = - 'send message mention email'; -export const SEND_NEW_DIRECT_MESSAGE_EMAIL = 'send new direct message email'; export const SEND_THREAD_CREATED_NOTIFICATION_EMAIL = 'send thread created notification email'; export const PROCESS_ADMIN_TOXIC_MESSAGE = 'process admin toxic message'; @@ -27,12 +22,10 @@ export const PROCESS_ADMIN_TOXIC_THREAD = 'process admin toxic thread'; export const PRIVATE_CHANNEL_REQUEST_SENT = 'private channel request sent'; export const PRIVATE_CHANNEL_REQUEST_APPROVED = 'private channel request approved'; +export const PRIVATE_COMMUNITY_REQUEST_SENT = 'private community request sent'; +export const PRIVATE_COMMUNITY_REQUEST_APPROVED = + 'private community request approved'; export const SEND_PUSH_NOTIFICATIONS = 'push notifications'; +export const SEND_SLACK_INVITIATIONS = 'send slack invitations'; -// invoices and recurring payments -export const PRO_INVOICE_PAID_NOTIFICATION = 'pro invoice paid notification'; -export const COMMUNITY_INVOICE_PAID_NOTIFICATION = - 'community invoice paid notification'; -export const SEND_PRO_INVOICE_RECEIPT_EMAIL = 'send pro invoice receipt email'; -export const SEND_COMMUNITY_INVOICE_RECEIPT_EMAIL = - 'send community invoice receipt email'; +export const TRACK_USER_LAST_SEEN = 'track user thread last seen'; diff --git a/athena/queues/create-thread-notification-email.js b/athena/queues/create-thread-notification-email.js index 09e22c4bf5..64f55dadf5 100644 --- a/athena/queues/create-thread-notification-email.js +++ b/athena/queues/create-thread-notification-email.js @@ -3,13 +3,13 @@ const debug = require('debug')('athena:queue:create-thread-email'); import Raven from '../../shared/raven'; import truncate from 'shared/truncate'; import getEmailStatus from '../utils/get-email-status'; -import { getUserById } from '../models/user'; +import { getUserById } from 'shared/db/queries/user'; import { getCommunityById } from '../models/community'; import { getChannelById } from '../models/channel'; -import { SEND_THREAD_CREATED_NOTIFICATION_EMAIL } from './constants'; import { toPlainText, toState } from 'shared/draft-utils'; -import addQueue from '../utils/addQueue'; +import { sendThreadCreatedNotificationEmailQueue } from 'shared/bull/queues'; import type { DBThread, DBUser } from 'shared/types'; +import { signCommunity, signUser, signThread } from 'shared/imgix'; const createThreadNotificationEmail = async ( thread: DBThread, @@ -34,26 +34,33 @@ const createThreadNotificationEmail = async ( // user is either online or has this notif type turned off if (!shouldSendEmail) return; + const signedThread = signThread(thread); + // at this point the email is safe to send, construct data for Hermes const rawBody = thread.type === 'DRAFTJS' - ? thread.content.body - ? toPlainText(toState(JSON.parse(thread.content.body))) + ? signedThread.content.body + ? toPlainText(toState(JSON.parse(signedThread.content.body))) : '' - : thread.content.body || ''; + : signedThread.content.body || ''; // if the body is long, truncate it at 280 characters for the email preview const body = rawBody && truncate(rawBody.trim(), 280); const primaryActionLabel = 'View conversation'; - return addQueue(SEND_THREAD_CREATED_NOTIFICATION_EMAIL, { + const signedCommunity = signCommunity(community); + const signedCreator = signUser(creator); + + return sendThreadCreatedNotificationEmailQueue.add({ + // $FlowIssue recipient, primaryActionLabel, thread: { ...thread, - creator, - community, + // $FlowIssue + creator: signedCreator, + community: signedCommunity, channel, content: { title: thread.content.title, @@ -65,10 +72,9 @@ const createThreadNotificationEmail = async ( // send all the emails return Promise.all([emailPromises]).catch(err => { - debug('❌ Error in job:\n'); - debug(err); + console.error('❌ Error in job:\n'); + console.error(err); Raven.captureException(err); - console.log(err); }); }; diff --git a/athena/queues/direct-message-notification.js b/athena/queues/direct-message-notification.js index 8ef932d033..b22cf5862e 100644 --- a/athena/queues/direct-message-notification.js +++ b/athena/queues/direct-message-notification.js @@ -3,7 +3,7 @@ const debug = require('debug')('athena:queue:direct-message-notification'); import Raven from '../../shared/raven'; import { fetchPayload, createPayload } from '../utils/payloads'; import { getDistinctActors } from '../utils/actors'; -import { getUserById } from '../models/user'; +import { getUserById } from 'shared/db/queries/user'; import getEmailStatus from '../utils/get-email-status'; import { storeNotification, @@ -13,13 +13,13 @@ import { import { storeUsersNotifications, markUsersNotificationsAsNew, -} from '../models/usersNotifications'; +} from 'shared/db/queries/usersNotifications'; import { getDirectMessageThreadMembers } from '../models/usersDirectMessageThreads'; import sentencify from '../utils/sentencify'; -import addQueue from '../utils/addQueue'; -import { SEND_NEW_DIRECT_MESSAGE_EMAIL } from './constants'; import { toPlainText, toState } from 'shared/draft-utils'; +import { sendNewDirectMessageEmailQueue } from 'shared/bull/queues'; import type { Job, DirectMessageNotificationJobData } from 'shared/bull/types'; +import { signUser, signMessage } from 'shared/imgix'; export default async (job: Job) => { const { message: incomingMessage, userId: currentUserId } = job.data; @@ -92,8 +92,11 @@ export default async (job: Job) => { ? markUsersNotificationsAsNew : storeUsersNotifications; + const signedMessage = signMessage(message); + const signedUser = signUser(user); + const addToQueue = recipient => { - return addQueue(SEND_NEW_DIRECT_MESSAGE_EMAIL, { + return sendNewDirectMessageEmailQueue.add({ recipient, thread: { content: { @@ -107,14 +110,14 @@ export default async (job: Job) => { path: `messages/${thread.id}`, id: thread.id, }, - user, + user: signedUser, message: { - ...message, + ...signedMessage, content: { body: - message.messageType === 'draftjs' - ? toPlainText(toState(JSON.parse(message.content.body))) - : message.content.body, + signedMessage.messageType === 'draftjs' + ? toPlainText(toState(JSON.parse(signedMessage.content.body))) + : signedMessage.content.body, }, }, }); @@ -163,9 +166,8 @@ export default async (job: Job) => { }); return Promise.all(formatAndBufferPromises).catch(err => { - debug('❌ Error in job:\n'); - debug(err); + console.error('❌ Error in job:\n'); + console.error(err); Raven.captureException(err); - console.log(err); }); }; diff --git a/athena/queues/mention-notification.js b/athena/queues/mention-notification.js index a1c37cb328..6a5e8e8044 100644 --- a/athena/queues/mention-notification.js +++ b/athena/queues/mention-notification.js @@ -1,6 +1,6 @@ // @flow const debug = require('debug')('athena:queue:mention-notification'); -import addQueue from '../utils/addQueue'; +import Raven from '../../shared/raven'; import { toPlainText, toState } from 'shared/draft-utils'; import truncate from 'shared/truncate'; import { fetchPayload } from '../utils/payloads'; @@ -11,18 +11,18 @@ import { getMessageById } from '../models/message'; import { getUserPermissionsInCommunity } from '../models/usersCommunities'; import { getCommunityById } from '../models/community'; import { getUsersThread } from '../models/usersThreads'; -import { storeUsersNotifications } from '../models/usersNotifications'; +import { storeUsersNotifications } from 'shared/db/queries/usersNotifications'; import { getUserPermissionsInChannel } from '../models/usersChannels'; import { getThreadById } from '../models/thread'; -import { getUserByUsername, getUserById } from '../models/user'; +import { getUserByUsername, getUserById } from 'shared/db/queries/user'; import { - SEND_MENTION_THREAD_NOTIFICATION_EMAIL, - SEND_MENTION_MESSAGE_NOTIFICATION_EMAIL, -} from './constants'; -import type { Mention } from 'shared/types'; + sendNewMentionThreadEmailQueue, + sendNewMentionMessageEmailQueue, +} from 'shared/bull/queues'; +import type { Job, MentionNotificationJobData } from 'shared/bull/types'; +import { signUser, signThread, signMessage, signCommunity } from 'shared/imgix'; -type JobData = Mention; -export default async ({ data }: { data: JobData }) => { +export default async ({ data }: Job) => { debug('mention job created'); const { threadId, messageId, senderId, username, type: mentionType } = data; // if we have incomplete data @@ -51,21 +51,29 @@ export default async ({ data }: { data: JobData }) => { // dont send any notification about the mention if (!thread || thread.deletedAt) return; - const { isPrivate } = await getChannelById(thread.channelId); + const { isPrivate: channelIsPrivate } = await getChannelById( + thread.channelId + ); + const { isPrivate: communityIsPrivate } = await getCommunityById( + thread.communityId + ); const { isBlocked: isBlockedInCommunity, + isMember: isMemberInCommunity, } = await getUserPermissionsInCommunity(thread.communityId, recipient.id); const { isMember: isMemberInChannel, isBlocked: isBlockedInChannel, } = await getUserPermissionsInChannel(recipient.id, thread.channelId); - // don't notify people where they are blocked, or where the channel is private and they aren't a member + if ( isBlockedInCommunity || isBlockedInChannel || - (isPrivate && !isMemberInChannel) - ) + (channelIsPrivate && !isMemberInChannel) || + (communityIsPrivate && !isMemberInCommunity) + ) { return; + } // see if a usersThreads record exists. If it does, and notifications are muted, we // should not send an email. If the record doesn't exist, it means the person being @@ -128,13 +136,15 @@ export default async ({ data }: { data: JobData }) => { getChannelById(thread.channelId), ]); + const signedThread = signThread(thread); + // compose preview text for the email const rawThreadBody = - thread.type === 'DRAFTJS' - ? thread.content.body - ? toPlainText(toState(JSON.parse(thread.content.body))) + signedThread.type === 'DRAFTJS' + ? signedThread.content.body + ? toPlainText(toState(JSON.parse(signedThread.content.body))) : '' - : thread.content.body || ''; + : signedThread.content.body || ''; const threadBody = rawThreadBody && rawThreadBody.length > 10 @@ -142,30 +152,33 @@ export default async ({ data }: { data: JobData }) => { : rawThreadBody.trim(); const primaryActionLabel = 'View conversation'; - const rawMessageBody = message - ? message.content.body - ? toPlainText(toState(JSON.parse(message.content.body))) - : '' - : null; + const signedMessage = message ? signMessage(message) : null; + + const rawMessageBody = signedMessage + ? toPlainText(toState(JSON.parse(signedMessage.content.body))) + : ''; // if the message was super long, truncate it const messageBody = rawMessageBody && truncate(rawMessageBody.trim(), 280); // otherwise send an email and add the in-app notification - const QUEUE_NAME = + const queue = mentionType === 'thread' - ? SEND_MENTION_THREAD_NOTIFICATION_EMAIL - : SEND_MENTION_MESSAGE_NOTIFICATION_EMAIL; + ? sendNewMentionThreadEmailQueue + : sendNewMentionMessageEmailQueue; + + const signedSender = signUser(sender); + const signedCommunity = signCommunity(community); return Promise.all([ - addQueue(QUEUE_NAME, { + queue.add({ recipient, - sender, + sender: signedSender, primaryActionLabel, thread: { ...thread, - creator: sender, - community, + creator: signedSender, + community: signedCommunity, channel, content: { title: thread.content.title, @@ -174,12 +187,16 @@ export default async ({ data }: { data: JobData }) => { }, message: { ...message, - sender, + sender: signedSender, content: { body: messageBody, }, }, }), storeUsersNotifications(storedNotification.id, recipient.id), - ]); + ]).catch(err => { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + }); }; diff --git a/athena/queues/moderationEvents/message.js b/athena/queues/moderationEvents/message.js index ffe224d141..6713ad308e 100644 --- a/athena/queues/moderationEvents/message.js +++ b/athena/queues/moderationEvents/message.js @@ -1,37 +1,36 @@ // @flow -const debug = require('debug')('athena:queue:channel-notification'); -import { getUserById } from '../../models/user'; +const debug = require('debug')('athena:queue:moderation-events:message'); +import { getUserById } from 'shared/db/queries/user'; import { getThreadById } from '../../models/thread'; import { getCommunityById } from '../../models/community'; import { getChannelById } from '../../models/channel'; -import { addQueue } from '../../utils/addQueue'; -import type { DBMessage } from 'shared/types'; import { toState, toPlainText } from 'shared/draft-utils'; -import getSpectrumScore from './spectrum'; import getPerspectiveScore from './perspective'; +import { _adminSendToxicContentEmailQueue } from 'shared/bull/queues'; import type { Job, AdminToxicMessageJobData } from 'shared/bull/types'; export default async (job: Job) => { debug('new job for admin message moderation'); - const { data: { message } } = job; + const { + data: { message }, + } = job; const text = message.messageType === 'draftjs' ? toPlainText(toState(JSON.parse(message.content.body))) : message.content.body; - const scores = await Promise.all([ - getSpectrumScore(text, message.id, message.senderId), - getPerspectiveScore(text), - ]).catch(err => - console.log('Error getting message moderation scores from providers', err) + const perspectiveScore = await getPerspectiveScore(text).catch(err => + console.error('Error getting message moderation score from providers', { + error: err.message, + data: { + text, + threadId: message.id, + }, + }) ); - const spectrumScore = scores && scores[0]; - const perspectiveScore = scores && scores[1]; - - // if neither models returned results - if (!spectrumScore && !perspectiveScore) return; + if (!perspectiveScore) return; const [user, thread] = await Promise.all([ getUserById(message.senderId), @@ -43,21 +42,15 @@ export default async (job: Job) => { getChannelById(thread.channelId), ]); - try { - return addQueue('admin toxic content email', { - type: 'message', - text, - user, - thread, - community, - channel, - toxicityConfidence: { - spectrumScore, - perspectiveScore, - }, - }); - } catch (err) { - console.log('\n\nerror getting message toxicity', err); - return; - } + return _adminSendToxicContentEmailQueue.add({ + type: 'message', + text, + user, + thread, + community, + channel, + toxicityConfidence: { + perspectiveScore, + }, + }); }; diff --git a/athena/queues/moderationEvents/perspective.js b/athena/queues/moderationEvents/perspective.js index c7b285f27d..ed59299092 100644 --- a/athena/queues/moderationEvents/perspective.js +++ b/athena/queues/moderationEvents/perspective.js @@ -1,12 +1,11 @@ // @flow +const debug = require('debug')('athena:queue:moderation-events:perspective'); require('now-env'); import axios from 'axios'; const PERSPECTIVE_API_KEY = process.env.PERSPECTIVE_API_KEY; if (!PERSPECTIVE_API_KEY) { - console.log( - 'No API key for Perspective provided, not sending moderation events.' - ); + debug('No API key for Perspective provided, not sending moderation events.'); } export default async (text: string) => { @@ -30,7 +29,7 @@ export default async (text: string) => { }); // if something failed? - if (!request || !request.data) return; + if (!request || !request.data) return null; // get the scores from the request const { attributeScores } = request.data; @@ -46,7 +45,7 @@ export default async (text: string) => { const { value } = TOXICITY.summaryScore; // if the toxicity probability is above 50%, alert us - if (value > 0.5) return value; + if (value > 0.9) return value; return null; }; diff --git a/athena/queues/moderationEvents/spectrum.js b/athena/queues/moderationEvents/spectrum.js deleted file mode 100644 index 80268abb99..0000000000 --- a/athena/queues/moderationEvents/spectrum.js +++ /dev/null @@ -1,34 +0,0 @@ -// @flow -import axios from 'axios'; - -export default async (text: string, contextId: string, userId: string) => { - const request = await axios({ - method: 'post', - url: 'https://api.prod.getspectrum.io/api/v1/classification', - headers: { - Authorization: 'Apikey 205276812f89fcec2856d48c9192b2588', - 'Content-Type': 'application/json', - }, - data: { - jsonrpc: '2.0', - method: 'classifyText', - params: { - text: text, - meta: { - authorId: userId, - }, - }, - id: contextId, - }, - }); - - const { data } = request; - - if (!data || !data.result) return; - - const { toxic, toxicityConfidence } = data.result; - - if (toxic) return toxicityConfidence; - - return null; -}; diff --git a/athena/queues/moderationEvents/thread.js b/athena/queues/moderationEvents/thread.js index a373d10e42..7815637fc2 100644 --- a/athena/queues/moderationEvents/thread.js +++ b/athena/queues/moderationEvents/thread.js @@ -1,19 +1,19 @@ // @flow -const debug = require('debug')('athena:queue:channel-notification'); -import { getUserById } from '../../models/user'; +const debug = require('debug')('athena:queue:moderation-events:thread'); +import { getUserById } from 'shared/db/queries/user'; import { getCommunityById } from '../../models/community'; import { getChannelById } from '../../models/channel'; -import { addQueue } from '../../utils/addQueue'; -import type { DBThread } from 'shared/types'; import { toState, toPlainText } from 'shared/draft-utils'; -import getSpectrumScore from './spectrum'; import getPerspectiveScore from './perspective'; +import { _adminSendToxicContentEmailQueue } from 'shared/bull/queues'; import type { Job, AdminToxicThreadJobData } from 'shared/bull/types'; export default async (job: Job) => { debug('new job for admin thread moderation'); - const { data: { thread } } = job; + const { + data: { thread }, + } = job; const body = thread.type === 'DRAFTJS' @@ -25,18 +25,18 @@ export default async (job: Job) => { const title = thread.content.title; const text = `${title} ${body}`; - const scores = await Promise.all([ - getSpectrumScore(text, thread.id, thread.creatorId), - getPerspectiveScore(text), - ]).catch(err => - console.log('Error getting thread moderation scores from providers', err) + const perspectiveScore = await getPerspectiveScore(text).catch(err => + console.error('Error getting thread moderation scores from providers', { + error: err.message, + data: { + text, + threadId: thread.id, + }, + }) ); - const spectrumScore = scores && scores[0]; - const perspectiveScore = scores && scores[1]; - // if neither models returned results - if (!spectrumScore && !perspectiveScore) return; + if (!perspectiveScore) return; const [user, community, channel] = await Promise.all([ getUserById(thread.creatorId), @@ -44,21 +44,15 @@ export default async (job: Job) => { getChannelById(thread.channelId), ]); - try { - return addQueue('admin toxic content email', { - type: 'message', - text, - user, - thread, - community, - channel, - toxicityConfidence: { - spectrumScore, - perspectiveScore, - }, - }); - } catch (err) { - console.log('\n\nerror getting message toxicity', err); - return; - } + return _adminSendToxicContentEmailQueue.add({ + type: 'message', + text, + user, + thread, + community, + channel, + toxicityConfidence: { + perspectiveScore, + }, + }); }; diff --git a/athena/queues/new-message-in-thread/buffer-email.js b/athena/queues/new-message-in-thread/buffer-email.js index 1d944480b7..1cb45b60e3 100644 --- a/athena/queues/new-message-in-thread/buffer-email.js +++ b/athena/queues/new-message-in-thread/buffer-email.js @@ -1,179 +1,237 @@ -// @flow -const debug = require('debug')('athena::send-message-notification-email'); -import addQueue from '../../utils/addQueue'; -import { SEND_NEW_MESSAGE_EMAIL } from '../constants'; -import { getNotifications } from '../../models/notification'; -import groupReplies from './group-replies'; -import getEmailStatus from '../../utils/get-email-status'; - -const IS_PROD = process.env.NODE_ENV === 'production'; -// Change buffer in dev to 10 seconds vs 3 minutes in prod -const BUFFER = IS_PROD ? 180000 : 10000; -// wait at most 10 minutes before sending an email notification -const MAX_WAIT = 600000; - -// Called when the buffer time is over to actually send an email -const timedOut = async recipient => { - const threadsInScope = timeouts[recipient.email].threads; - - // array of DBNotifications - const notificationsInScope = timeouts[recipient.email].notifications; - - // Clear timeout buffer for this recipient - delete timeouts[recipient.email]; - debug( - `send notification email for ${threadsInScope.length} threads to @${recipient.username} (${recipient.email})` - ); - - const shouldGetEmail = await getEmailStatus( - recipient.userId, - 'newMessageInThreads' - ); - if (!shouldGetEmail) { - debug(`@${recipient.username} should not get email, aborting`); - return; - } - - debug(`@${recipient.username} should get email, getting notifications`); - const notifications = await getNotifications( - notificationsInScope.map(notification => notification.id) - ); - if (!notifications || notifications.length === 0) { - debug('No notifications in scope'); - return; - } - - debug('notifications loaded, finding unseen threads'); - const unseenThreadIds = notifications - .filter(notification => !notification.isSeen && !notification.isRead) - .map(notification => notification.context.id); - - if (unseenThreadIds.length === 0) { - debug('aborting, no unseen threads'); - return; - } - debug('filter unseen threads, merge replies'); - - // Convert threads to object, merge replies to same thread - const threads = threadsInScope - .filter(thread => unseenThreadIds.includes(thread.id)) - .reduce((map, thread) => { - if (!map[thread.id]) { - map[thread.id] = thread; - return map; - } - map[thread.id] = { - ...map[thread.id], - replies: map[thread.id].replies.concat(thread.replies), - }; - return map; - }, {}); - - debug('group replies'); - // Group replies by sender, turn it back into an array - const threadKeys = Object.keys(threads); - - const threadsWithGroupedRepliesPromises = threadKeys.map(async threadId => ({ - ...threads[threadId], - replies: await groupReplies(threads[threadId].replies), - repliesCount: threads[threadId].replies.length, - })); - - const threadsWithGroupedReplies = await Promise.all([ - ...threadsWithGroupedRepliesPromises, - ]).catch(err => console.log('error grouping threads and replies', err)); - - const filteredThreadsWithGroupedReplies = - threadsWithGroupedReplies && - threadsWithGroupedReplies.length > 0 && - threadsWithGroupedReplies.filter(thread => thread.replies.length > 0); - - // this would happen if someone sends a message in a thread then deletes that message - if ( - filteredThreadsWithGroupedReplies && - filteredThreadsWithGroupedReplies.length === 0 - ) { - debug('no threads with at least one reply'); - return; - } - - debug(`adding email for @${recipient.username} to queue`); - return addQueue(SEND_NEW_MESSAGE_EMAIL, { - recipient, - threads: filteredThreadsWithGroupedReplies, - }); -}; - -type Timeouts = { - [email: string]: { - timeout: any, - firstTimeout: number, // timestamp - threads: Array, - notifications: Array, - }, -}; - -const timeouts: Timeouts = {}; - -/** - * We do some crazy timeout stuff here to avoid sending too many emails. The flow goes like this: - * - * - A message notification comes in, this function is called and sets a timeout for one minute to send the email - * - No further message notification for the same recipient comes in so we send the email after one minute - * - If another message notification comes in before one minute has passed we reset the timer to one minute and add the new notification to the list of threads to be batched into one email - * - We repeat this process for each further message notification until we have a one minute break - * - Because we do want people to get emails in a timely manner we force push them out after 10 minutes. Basically, if we get a message notification and no email has been sent but it's been more than 10 minutes since the very first notification we send the email with all current notifications batched into one email. - */ -type Recipient = { - email: string, - username: string, - userId: string, - name: string, -}; -const bufferMessageNotificationEmail = ( - recipient: Recipient, - thread: any, - notification: any -) => { - debug( - `send message notification email to ${recipient.email} for thread#${thread.id}` - ); - if (!timeouts[recipient.email]) { - debug( - `creating new timeout for ${recipient.email}, sending email after ${BUFFER}ms` - ); - timeouts[recipient.email] = { - timeout: setTimeout(() => timedOut(recipient), BUFFER), - firstTimeout: Date.now(), - threads: [thread], - notifications: [notification], - }; - } else { - // If we already have a timeout going - debug(`timeout exists for ${recipient.email}, clearing`); - clearTimeout(timeouts[recipient.email].timeout); - - debug(`adding new thread to ${recipient.email}'s threads`); - timeouts[recipient.email].threads.push(thread); - timeouts[recipient.email].notifications.push(notification); - - // If it's been a few minutes and we still haven't sent an email because messages - // keep coming send an email now to avoid not sending a notification for hours - if (timeouts[recipient.email].firstTimeout < Date.now() - MAX_WAIT) { - debug( - `force send email to ${recipient.email} because it's been over ${MAX_WAIT}ms without an email` - ); - timedOut(recipient); - } else { - debug( - `refresh ${BUFFER}ms timeout for ${recipient.email} with new thread#${thread.id}` - ); - timeouts[recipient.email].timeout = setTimeout( - () => timedOut(recipient), - BUFFER - ); - } - } -}; - -export default bufferMessageNotificationEmail; +// @flow +const debug = require('debug')('athena::send-message-notification-email'); +import { getNotifications } from '../../models/notification'; +import groupReplies from './group-replies'; +import getEmailStatus from '../../utils/get-email-status'; +import { + sendNewMessageEmailQueue, + bufferNewMessageEmailQueue, +} from 'shared/bull/queues'; +import type { + DBThread, + DBNotification, + DBChannel, + DBCommunity, +} from 'shared/types'; + +const IS_PROD = process.env.NODE_ENV === 'production'; +// Change buffer in dev to 10 seconds vs 3 minutes in prod +const BUFFER = IS_PROD ? 180000 : 10000; +// wait at most 10 minutes before sending an email notification +const MAX_WAIT = 600000; + +bufferNewMessageEmailQueue.process(async job => { + const threadsInScope = job.data.threads; + const notificationsInScope = job.data.notifications; + const recipient = job.data.recipient; + if (!recipient) return; + debug( + `send notification email for ${threadsInScope.length} threads to @${ + recipient.username + } (${recipient.email})` + ); + + const shouldGetEmail = await getEmailStatus( + recipient.userId, + 'newMessageInThreads' + ); + if (!shouldGetEmail) { + debug(`@${recipient.username} should not get email, aborting`); + return; + } + + debug(`@${recipient.username} should get email, getting notifications`); + const notifications = await getNotifications( + notificationsInScope.map(notification => notification.id) + ); + if (!notifications || notifications.length === 0) { + debug('No notifications in scope'); + return; + } + + debug('notifications loaded, finding unseen threads'); + const unseenThreadIds = notifications + .filter(notification => !notification.isSeen && !notification.isRead) + .map(notification => notification.context.id); + + if (unseenThreadIds.length === 0) { + debug('aborting, no unseen threads'); + return; + } + debug('filter unseen threads, merge replies'); + + // Convert threads to object, merge replies to same thread + const threads = threadsInScope + .filter(thread => unseenThreadIds.includes(thread.id)) + .reduce((map, thread) => { + if (!map[thread.id]) { + map[thread.id] = thread; + return map; + } + map[thread.id] = { + ...map[thread.id], + replies: map[thread.id].replies.concat(thread.replies), + }; + return map; + }, {}); + + debug('group replies'); + // Group replies by sender, turn it back into an array + const threadKeys = Object.keys(threads); + + const threadsWithGroupedRepliesPromises = threadKeys.map(async threadId => ({ + ...threads[threadId], + replies: await groupReplies(threads[threadId].replies), + repliesCount: threads[threadId].replies.length, + })); + + const threadsWithGroupedReplies = await Promise.all( + threadsWithGroupedRepliesPromises + ).catch(err => console.error('error grouping threads and replies', err)); + + const filteredThreadsWithGroupedReplies = + threadsWithGroupedReplies && + threadsWithGroupedReplies.length > 0 && + threadsWithGroupedReplies.filter(thread => thread.replies.length > 0); + + // this would happen if someone sends a message in a thread then deletes that message + if ( + !filteredThreadsWithGroupedReplies || + filteredThreadsWithGroupedReplies.length === 0 + ) { + debug('no threads with at least one reply'); + return; + } + + debug(`adding email for @${recipient.username} to queue`); + return sendNewMessageEmailQueue.add({ + recipient, + threads: filteredThreadsWithGroupedReplies, + }); +}); + +type Timeouts = { + [email: string]: { + timeout: any, + firstTimeout: number, // timestamp + threads: Array, + notifications: Array, + }, +}; + +const timeouts: Timeouts = {}; + +/** + * We do some crazy timeout stuff here to avoid sending too many emails. The flow goes like this: + * + * - A message notification comes in, this function is called and sets a timeout for one minute to send the email + * - No further message notification for the same recipient comes in so we send the email after one minute + * - If another message notification comes in before one minute has passed we reset the timer to one minute and add the new notification to the list of threads to be batched into one email + * - We repeat this process for each further message notification until we have a one minute break + * - Because we do want people to get emails in a timely manner we force push them out after 10 minutes. Basically, if we get a message notification and no email has been sent but it's been more than 10 minutes since the very first notification we send the email with all current notifications batched into one email. + */ +export type Recipient = { + email: string, + username: string, + userId: string, + name: string, +}; +type Reply = { + id: string, + sender: { + id: string, + profilePhoto: string, + name: string, + username: ?string, + }, + content: { + body: string, + }, +}; +export type NewMessageNotificationEmailThread = { + ...$Exact, + community: DBCommunity, + channel: DBChannel, + replies: Array, +}; +const bufferMessageNotificationEmail = async ( + recipient: Recipient, + thread: NewMessageNotificationEmailThread, + notification: DBNotification +) => { + debug( + `send message notification email to ${recipient.email} for thread#${ + thread.id + }` + ); + let job = await bufferNewMessageEmailQueue.getJob(recipient.email); + // Try to remove the job if it exists. This fails sometimes due to a + // race condition where the getJob returns a failed or active job. + // We circumvent that issue by simply pretending like no job exists + // and adding a new one + // Ref: withspectrum/spectrum#3189 + if (job) { + debug(`timeout exists for ${recipient.email}, clearing`); + try { + await job.remove(); + } catch (err) { + try { + await job.finished(); + // Note(@mxstbr): This throws if the job fails to complete + // but we don't care if that happens, so we ignore it + } catch (err) {} + + job = null; + } + } + if (!job) { + debug( + `creating new timeout for ${ + recipient.email + }, sending email after ${BUFFER}ms` + ); + return bufferNewMessageEmailQueue.add( + { + threads: [thread], + notifications: [notification], + firstTimeout: Date.now(), + recipient, + }, + { + delay: BUFFER, + jobId: recipient.email, + } + ); + } else { + const timeout = job.data; + debug(`adding new thread to ${recipient.email}'s threads`); + timeout.threads.push(thread); + timeout.notifications.push(notification); + + // If it's been a few minutes and we still haven't sent an email because messages + // keep coming send an email now to avoid not sending a notification for hours + if (timeout.firstTimeout < Date.now() - MAX_WAIT) { + debug( + `force send email to ${ + recipient.email + } because it's been over ${MAX_WAIT}ms without an email` + ); + return bufferNewMessageEmailQueue.add(timeout, { + delay: 0, + jobId: recipient.email, + }); + } else { + debug( + `refresh ${BUFFER}ms timeout for ${recipient.email} with new thread#${ + thread.id + }` + ); + return bufferNewMessageEmailQueue.add(timeout, { + delay: BUFFER, + jobId: recipient.email, + }); + } + } +}; + +export default bufferMessageNotificationEmail; diff --git a/athena/queues/new-message-in-thread/format-data.js b/athena/queues/new-message-in-thread/format-data.js index c0b3b6cbc9..5166e232de 100644 --- a/athena/queues/new-message-in-thread/format-data.js +++ b/athena/queues/new-message-in-thread/format-data.js @@ -6,77 +6,63 @@ import { getCommunityById } from '../../models/community'; import { getChannelById } from '../../models/channel'; import { toPlainText, toState } from 'shared/draft-utils'; import bufferNotificationEmail from './buffer-email'; +import type { DBUser, DBMessage, DBThread, DBNotification } from 'shared/types'; +import type { NewMessageNotificationEmailThread } from './buffer-email'; +import { signUser, signCommunity, signThread } from 'shared/imgix'; -type UserType = { - id: string, - profilePhoto: string, - name: string, - username: string, -}; -type MessageType = { - id: string, - content: { - body: string, - }, -}; +type UserType = DBUser; +type MessageType = DBMessage; type RecipientType = { email: string, username: string, userId: string, name: string, }; -type NotificationType = {}; -type ThreadType = { - id: string, - channelId: string, - communityId: string, -}; +type NotificationType = DBNotification; +type ThreadType = DBThread; export default async ( - recipient: RecipientType, thread: ThreadType, user: UserType, - message: MessageType, - notification: NotificationType -) => { - if (!recipient || !recipient.email || !thread || !user || !message) { - debug( - '⚠ aborting adding to email queue due to invalid data\nrecipient\n%O\nthread\n%O\nuser\n%O\nmessage\n%O', - recipient, - thread, - user, - message - ); - // $FlowIssue - return Promise.resolve(); + message: MessageType +): Promise => { + let body; + switch (message.messageType) { + case 'draftjs': { + body = toPlainText(toState(JSON.parse(message.content.body))); + break; + } + case 'media': { + body = message.content.body; + break; + } + default: { + body = message.content.body; + } } - const { communityId, channelId, ...restOfThread } = thread; + const signedUser = signUser(user); + const community = await getCommunityById(thread.communityId); + const signedCommunity = signCommunity(community); + const signedThread = signThread(thread); - return bufferNotificationEmail( - recipient, - { - ...restOfThread, - community: await getCommunityById(communityId), - channel: await getChannelById(channelId), - replies: [ - { - id: message.id, - sender: { - id: user.id, - profilePhoto: user.profilePhoto, - name: user.name, - username: user.username, - }, - content: { - body: - message.messageType === 'draftjs' - ? toPlainText(toState(JSON.parse(message.content.body))) - : message.content.body, - }, + return { + ...signedThread, + community: signedCommunity, + channel: await getChannelById(thread.channelId), + replies: [ + { + id: message.id, + sender: { + id: user.id, + profilePhoto: signedUser.profilePhoto, + name: user.name, + username: user.username, + }, + content: { + body, }, - ], - }, - notification - ); + }, + ], + }; }; diff --git a/athena/queues/new-message-in-thread/group-replies.js b/athena/queues/new-message-in-thread/group-replies.js index be2071079f..f62afe3072 100644 --- a/athena/queues/new-message-in-thread/group-replies.js +++ b/athena/queues/new-message-in-thread/group-replies.js @@ -1,51 +1,54 @@ -import { getMessageById } from '../../models/message'; - -export default async replies => { - let newReplies = []; - - const replyPromises = replies.map( - async reply => await getMessageById(reply.id) - ); - - // get all the messages for this thread - const messageRecords = await Promise.all(replyPromises).catch(err => - console.log('error getting reply promises', err) - ); - - // filter deleted ones and sort them by recency - const filteredMessageRecords = messageRecords - .filter(message => !message.deletedAt) - .sort((a, b) => a.timestamp > b.timestamp); - - const filteredPromises = filteredMessageRecords.map((message, index) => { - const reply = replies.filter(r => r.id === message.id)[0]; - const body = - message.messageType === 'media' - ? `

` - : `

${reply.content.body}

`; - - const newGroup = { - ...reply, - content: { - body, - }, - }; - - if (index === 0) return newReplies.push(newGroup); - if ( - newReplies[newReplies.length - 1] && - newReplies[newReplies.length - 1].sender.id === message.senderId - ) { - newReplies[newReplies.length - 1].content.body += body; - return; - } else { - newReplies.push(newGroup); - return; - } - }); - - return await Promise.all([filteredPromises]) - .then(() => newReplies) - .catch(err => console.log('error getting filteredPromises', err)); -}; +import { getMessageById } from '../../models/message'; +import { signImageUrl } from 'shared/imgix'; +import escapehtml from 'escape-html'; + +export default async replies => { + let newReplies = []; + + const replyPromises = replies.map( + async reply => await getMessageById(reply.id) + ); + + // get all the messages for this thread + const messageRecords = await Promise.all(replyPromises).catch(err => + console.error('error getting reply promises', err) + ); + + // filter deleted ones and sort them by recency + const filteredMessageRecords = messageRecords + .filter(message => !message.deletedAt) + .sort((a, b) => a.timestamp > b.timestamp); + + const filteredPromises = filteredMessageRecords.map((message, index) => { + const reply = replies.filter(r => r.id === message.id)[0]; + const body = + message.messageType === 'media' + ? // prettier-ignore + `

` + : // prettier-ignore + `

${escapehtml(reply.content.body)}

`; + + const newGroup = { + ...reply, + content: { + body, + }, + }; + + if (index === 0) return newReplies.push(newGroup); + if ( + newReplies[newReplies.length - 1] && + newReplies[newReplies.length - 1].sender.id === message.senderId + ) { + newReplies[newReplies.length - 1].content.body += body; + return; + } else { + newReplies.push(newGroup); + return; + } + }); + + return await Promise.all([filteredPromises]) + .then(() => newReplies) + .catch(err => console.error('error getting filteredPromises', err)); +}; diff --git a/athena/queues/new-message-in-thread/index.js b/athena/queues/new-message-in-thread/index.js index 8830a868d6..a067265ada 100644 --- a/athena/queues/new-message-in-thread/index.js +++ b/athena/queues/new-message-in-thread/index.js @@ -2,11 +2,11 @@ const debug = require('debug')('athena:queue:message-notification'); import { toState, toPlainText } from 'shared/draft-utils'; import getMentions from 'shared/get-mentions'; -import addQueue from '../../utils/addQueue'; import Raven from 'shared/raven'; import { fetchPayload, createPayload } from '../../utils/payloads'; import { getDistinctActors } from '../../utils/actors'; import formatData from './format-data'; +import bufferNotificationEmail from './buffer-email'; import { storeNotification, updateNotification, @@ -15,11 +15,15 @@ import { import { storeUsersNotifications, markUsersNotificationsAsNew, -} from '../../models/usersNotifications'; +} from 'shared/db/queries/usersNotifications'; import { getThreadNotificationUsers } from '../../models/usersThreads'; import { getUserPermissionsInChannel } from '../../models/usersChannels'; import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; +import { getUserById } from 'shared/db/queries/user'; +import { getMessageById } from '../../models/message'; +import { sendMentionNotificationQueue } from 'shared/bull/queues'; import type { MessageNotificationJobData, Job } from 'shared/bull/types'; +import type { DBMessage } from 'shared/types'; export default async (job: Job) => { const { message: incomingMessage } = job.data; @@ -96,10 +100,28 @@ export default async (job: Job) => { : incomingMessage.content.body; // get mentions in the message - const mentions = getMentions(body); + let mentions = getMentions(body); + // If the message quoted another message, send a mention notification to the author + // of the quoted message + if (typeof incomingMessage.parentId === 'string') { + // $FlowIssue + const parent = await getMessageById(incomingMessage.parentId); + // eslint-disable-next-line + (parent: DBMessage); + if (parent) { + const parentAuthor = await getUserById(parent.senderId); + if ( + parentAuthor && + parentAuthor.username && + mentions.indexOf(parentAuthor.username) < 0 + ) { + mentions.push(parentAuthor.username); + } + } + } if (mentions && mentions.length > 0) { mentions.forEach(username => { - addQueue('mention notification', { + sendMentionNotificationQueue.add({ messageId: incomingMessage.id, threadId: incomingMessage.threadId, senderId: incomingMessage.senderId, @@ -130,20 +152,32 @@ export default async (job: Job) => { : storeUsersNotifications; // send each recipient a notification - const formatAndBufferPromises = recipientsWithoutMentions.map(recipient => { - if (!recipient) return; - - formatData(recipient, thread, sender, message, notification); - - // store or update the notification in the db to trigger a ui update in app - debug('Updating the notification record in the db'); - return dbMethod(notification.id, recipient.userId); - }); + const formatAndBufferPromises = recipientsWithoutMentions.map( + async recipient => { + if (!recipient || !recipient.email || !thread || !message) { + debug( + '⚠ aborting adding to email queue due to invalid data\nrecipient\n%O\nthread\n%O\nuser\n%O\nmessage\n%O', + recipient, + thread, + message + ); + return Promise.resolve(); + } + + debug( + 'format data, buffer notification email and store/update notification in db' + ); + const data = await formatData(thread, sender, message); + return Promise.all([ + bufferNotificationEmail(recipient, data, notification), + dbMethod(notification.id, recipient.userId), + ]); + } + ); return Promise.all(formatAndBufferPromises).catch(err => { - debug('❌ Error in job:\n'); - debug(err); + console.error('❌ Error in job:\n'); + console.error(err); Raven.captureException(err); - console.log(err); }); }; diff --git a/athena/queues/private-channel-request-approved.js b/athena/queues/private-channel-request-approved.js index 18aaecdef2..cd17a771d6 100644 --- a/athena/queues/private-channel-request-approved.js +++ b/athena/queues/private-channel-request-approved.js @@ -3,23 +3,19 @@ const debug = require('debug')( 'athena:queue:user-request-private-channel-approved' ); import Raven from 'shared/raven'; -import addQueue from '../utils/addQueue'; import { getCommunityById } from '../models/community'; import { storeNotification } from '../models/notification'; -import { storeUsersNotifications } from '../models/usersNotifications'; -import { getUsers } from '../models/user'; +import { storeUsersNotifications } from 'shared/db/queries/usersNotifications'; +import { getUsers } from 'shared/db/queries/user'; import { fetchPayload } from '../utils/payloads'; import isEmail from 'validator/lib/isEmail'; +import { sendPrivateChannelRequestApprovedEmailQueue } from 'shared/bull/queues'; +import type { + Job, + PrivateChannelRequestApprovedJobData, +} from 'shared/bull/types'; -type JobData = { - data: { - userId: string, - channelId: string, - communityId: string, - moderatorId: string, - }, -}; -export default async (job: JobData) => { +export default async (job: Job) => { const { userId, channelId, communityId, moderatorId } = job.data; debug(`user request to join channel ${channelId} approved`); @@ -49,33 +45,33 @@ export default async (job: JobData) => { const recipients = await getUsers([userId]); // only get owners with emails - const filteredRecipients = recipients.filter(user => isEmail(user.email)); + const filteredRecipients = recipients.filter( + user => user && user.email && isEmail(user.email) + ); // for each owner, create a notification for the app - const usersNotificationPromises = filteredRecipients.map( - async recipient => - await storeUsersNotifications(updatedNotification.id, recipient.id) + const usersNotificationPromises = filteredRecipients.map(recipient => + storeUsersNotifications(updatedNotification.id, recipient.id) ); // for each owner,send an email const channelPayload = JSON.parse(entity.payload); const community = await getCommunityById(communityId); - const usersEmailPromises = filteredRecipients.map( - async recipient => - await addQueue('send private channel request approved email', { - recipient, - channel: channelPayload, - community, - }) + const usersEmailPromises = filteredRecipients.map(recipient => + sendPrivateChannelRequestApprovedEmailQueue.add({ + // $FlowIssue + recipient, + channel: channelPayload, + community, + }) ); return await Promise.all([ - usersEmailPromises, // handle emails separately - usersNotificationPromises, // update or store usersNotifications in-app + ...usersEmailPromises, // handle emails separately + ...usersNotificationPromises, // update or store usersNotifications in-app ]).catch(err => { - debug('❌ Error in job:\n'); - debug(err); + console.error('❌ Error in job:\n'); + console.error(err); Raven.captureException(err); - console.log(err); }); }; diff --git a/athena/queues/private-channel-request-sent.js b/athena/queues/private-channel-request-sent.js index 20ea2abffd..6482de2a4e 100644 --- a/athena/queues/private-channel-request-sent.js +++ b/athena/queues/private-channel-request-sent.js @@ -3,15 +3,21 @@ const debug = require('debug')( 'athena:queue:user-requested-join-private-channel' ); import Raven from 'shared/raven'; -import addQueue from '../utils/addQueue'; import { getCommunityById } from '../models/community'; import { storeNotification } from '../models/notification'; -import { storeUsersNotifications } from '../models/usersNotifications'; -import { getOwnersInChannel } from '../models/usersChannels'; -import { getUsers } from '../models/user'; +import { storeUsersNotifications } from 'shared/db/queries/usersNotifications'; +import { + getOwnersInChannel, + getModeratorsInChannel, +} from '../models/usersChannels'; +import { + getOwnersInCommunity, + getModeratorsInCommunity, +} from '../models/usersCommunities'; +import { getUsers } from 'shared/db/queries/user'; import { fetchPayload, createPayload } from '../utils/payloads'; import isEmail from 'validator/lib/isEmail'; -import type { DBChannel } from 'shared/types'; +import { sendPrivateChannelRequestEmailQueue } from 'shared/bull/queues'; import type { Job, PrivateChannelRequestJobData } from 'shared/bull/types'; export default async (job: Job) => { @@ -45,42 +51,57 @@ export default async (job: Job) => { const updatedNotification = await storeNotification(nextNotificationRecord); // get the owners of the channel - const recipients = await getOwnersInChannel(channel.id); + const [ + ownersInCommunity, + moderatorsInCommunity, + ownersInChannel, + moderatorsInChannel, + ] = await Promise.all([ + getOwnersInCommunity(channel.communityId), + getModeratorsInCommunity(channel.communityId), + getOwnersInChannel(channel.id), + getModeratorsInChannel(channel.id), + ]); + + const uniqueRecipientIds = [ + ...ownersInCommunity, + ...moderatorsInCommunity, + ...ownersInChannel, + ...moderatorsInChannel, + ].filter((item, i, ar) => ar.indexOf(item) === i); // get all the user data for the owners - const recipientsWithUserData = await getUsers([...recipients]); + const recipientsWithUserData = await getUsers([...uniqueRecipientIds]); - // only get owners with emails - const filteredRecipients = recipientsWithUserData.filter(owner => - isEmail(owner.email) + // only get owners + moderators with emails + const filteredRecipients = recipientsWithUserData.filter( + owner => owner && owner.email && isEmail(owner.email) ); // for each owner, create a notification for the app - const usersNotificationPromises = filteredRecipients.map( - async recipient => - await storeUsersNotifications(updatedNotification.id, recipient.id) + const usersNotificationPromises = filteredRecipients.map(recipient => + storeUsersNotifications(updatedNotification.id, recipient.id) ); // for each owner,send an email const userPayload = JSON.parse(actor.payload); const community = await getCommunityById(channel.communityId); - const usersEmailPromises = filteredRecipients.map( - async recipient => - await addQueue('send request join private channel email', { - user: userPayload, - recipient, - channel, - community, - }) + const usersEmailPromises = filteredRecipients.map(recipient => + sendPrivateChannelRequestEmailQueue.add({ + user: userPayload, + // $FlowIssue + recipient, + channel, + community, + }) ); return Promise.all([ usersEmailPromises, // handle emails separately usersNotificationPromises, // update or store usersNotifications in-app ]).catch(err => { - debug('❌ Error in job:\n'); - debug(err); + console.error('❌ Error in job:\n'); + console.error(err); Raven.captureException(err); - console.log(err); }); }; diff --git a/athena/queues/private-community-request-approved.js b/athena/queues/private-community-request-approved.js new file mode 100644 index 0000000000..0f45635c51 --- /dev/null +++ b/athena/queues/private-community-request-approved.js @@ -0,0 +1,68 @@ +// @flow +const debug = require('debug')( + 'athena:queue:user-request-private-community-approved' +); +import Raven from 'shared/raven'; +import { getCommunityById } from '../models/community'; +import { storeNotification } from '../models/notification'; +import { storeUsersNotifications } from 'shared/db/queries/usersNotifications'; +import { getUsers } from 'shared/db/queries/user'; +import { fetchPayload } from '../utils/payloads'; +import isEmail from 'validator/lib/isEmail'; +import { sendPrivateCommunityRequestApprovedEmailQueue } from 'shared/bull/queues'; +import type { + Job, + PrivateCommunityRequestApprovedJobData, +} from 'shared/bull/types'; + +export default async (job: Job) => { + const { userId, communityId, moderatorId } = job.data; + debug(`user request to join community ${communityId} approved`); + + const [actor, context] = await Promise.all([ + fetchPayload('USER', moderatorId), + fetchPayload('COMMUNITY', communityId), + ]); + + const eventType = 'PRIVATE_COMMUNITY_REQUEST_APPROVED'; + + // construct a new notification record to either be updated or stored in the db + const nextNotificationRecord = Object.assign( + {}, + { + event: eventType, + actors: [actor], + context, + entities: [context], + } + ); + + // update or store a record in the notifications table, returns a notification + const updatedNotification = await storeNotification(nextNotificationRecord); + + const community = await getCommunityById(communityId); + const recipients = await getUsers([userId]); + const filteredRecipients = recipients.filter( + user => user && isEmail(user.email) + ); + const usersNotificationPromises = filteredRecipients.map(recipient => + storeUsersNotifications(updatedNotification.id, recipient.id) + ); + + const usersEmailPromises = filteredRecipients.map(recipient => + sendPrivateCommunityRequestApprovedEmailQueue.add({ + // $FlowIssue + recipient, + community, + }) + ); + + return await Promise.all([ + ...usersEmailPromises, // handle emails separately + ...usersNotificationPromises, // update or store usersNotifications in-app + ]).catch(err => { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + }); +}; diff --git a/athena/queues/private-community-request-sent.js b/athena/queues/private-community-request-sent.js new file mode 100644 index 0000000000..27769e862f --- /dev/null +++ b/athena/queues/private-community-request-sent.js @@ -0,0 +1,92 @@ +// @flow +const debug = require('debug')( + 'athena:queue:user-requested-join-private-community' +); +import Raven from 'shared/raven'; +import { getCommunityById } from '../models/community'; +import { storeNotification } from '../models/notification'; +import { storeUsersNotifications } from 'shared/db/queries/usersNotifications'; +import { + getOwnersInCommunity, + getModeratorsInCommunity, +} from '../models/usersCommunities'; +import { getUsers } from 'shared/db/queries/user'; +import { fetchPayload } from '../utils/payloads'; +import isEmail from 'validator/lib/isEmail'; +import { sendPrivateCommunityRequestEmailQueue } from 'shared/bull/queues'; +import type { Job, PrivateCommunityRequestJobData } from 'shared/bull/types'; +import { signCommunity, signUser } from 'shared/imgix'; + +export default async (job: Job) => { + const { userId, communityId } = job.data; + debug( + `new request to join a private community from user ${userId} in community ${communityId}` + ); + + const [actor, context] = await Promise.all([ + fetchPayload('USER', userId), + fetchPayload('COMMUNITY', communityId), + ]); + + const eventType = 'PRIVATE_COMMUNITY_REQUEST_SENT'; + + // construct a new notification record to either be updated or stored in the db + const nextNotificationRecord = Object.assign( + {}, + { + event: eventType, + actors: [actor], + context, + entities: [context], + } + ); + + // update or store a record in the notifications table, returns a notification + const updatedNotification = await storeNotification(nextNotificationRecord); + + // get the owners of the community + const [ownersInCommunity, moderatorsInCommunity] = await Promise.all([ + getOwnersInCommunity(communityId), + getModeratorsInCommunity(communityId), + ]); + + const uniqueRecipientIds = [ + ...ownersInCommunity, + ...moderatorsInCommunity, + ].filter((item, i, ar) => ar.indexOf(item) === i); + + // get all the user data for the owners + const recipientsWithUserData = await getUsers([...uniqueRecipientIds]); + + // only get owners + moderators with emails + const filteredRecipients = recipientsWithUserData.filter( + owner => owner && owner.email && isEmail(owner.email) + ); + + // for each owner, create a notification for the app + const usersNotificationPromises = filteredRecipients.map(recipient => + storeUsersNotifications(updatedNotification.id, recipient.id) + ); + + // for each owner,send an email + const userPayload = JSON.parse(actor.payload); + const community = await getCommunityById(communityId); + const usersEmailPromises = filteredRecipients.map(recipient => + sendPrivateCommunityRequestEmailQueue.add({ + // $FlowFixMe + user: signUser(userPayload), + // $FlowFixMe + recipient: signUser(recipient), + community: signCommunity(community), + }) + ); + + return Promise.all([ + usersEmailPromises, // handle emails separately + usersNotificationPromises, // update or store usersNotifications in-app + ]).catch(err => { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + }); +}; diff --git a/athena/queues/pro-invoice-paid.js b/athena/queues/pro-invoice-paid.js deleted file mode 100644 index 92957e2e79..0000000000 --- a/athena/queues/pro-invoice-paid.js +++ /dev/null @@ -1,66 +0,0 @@ -// @flow -const debug = require('debug')('athena:queue:pro-invoice-paid-notification'); -import Raven from '../../shared/raven'; -import createQueue from '../../shared/bull/create-queue'; -import { SEND_PRO_INVOICE_RECEIPT_EMAIL } from './constants'; -import { convertTimestampToDate } from '../utils/timestamp-to-date'; -import { getUsers } from '../models/user'; -import { getRecurringPaymentFromInvoice } from '../models/recurringPayment'; -import type { Job, InvoiceJobData } from 'shared/bull/types'; - -const sendProInvoiceReceiptQueue = createQueue(SEND_PRO_INVOICE_RECEIPT_EMAIL); - -export default async (job: Job) => { - const { invoice } = job.data; - - debug(`new job for pro invoice id ${invoice.id}`); - - debug('processing pro invoice'); - // $FlowFixMe - const recurringPayment = await getRecurringPaymentFromInvoice(invoice); - const userToEvaluate = await getUsers([recurringPayment.userId]); - - if (!recurringPayment || !userToEvaluate) return; - debug('found a recurring payment and user to send the receipt to'); - - const email = userToEvaluate[0].email; - // if the user doesn't have an email address on file, escape - if (!email) return; - debug('user has a valid email address to receive the receipt'); - - // convert amount into readable currency amount - const amount = `$${(invoice.amount / 100) - .toFixed(2) - .replace(/(\d)(?=(\d{3})+\.)/g, '$1,')}`; - const paidAt = convertTimestampToDate(invoice.paidAt * 1000); - const brand = invoice.sourceBrand; - const last4 = invoice.sourceLast4; - - debug('sending pro invoice receipt email'); - - return sendProInvoiceReceiptQueue - .add( - { - to: email, - invoice: { - plan: invoice.planName, - amount, - paidAt, - brand, - last4, - planName: invoice.planName, - id: invoice.id, - }, - }, - { - removeOnComplete: true, - removeOnFail: true, - } - ) - .catch(err => { - debug('❌ Error in job:\n'); - debug(err); - Raven.captureException(err); - console.log(err); - }); -}; diff --git a/athena/queues/reaction-notification.js b/athena/queues/reaction-notification.js index f9d5f156c0..aa6a7b9d4c 100644 --- a/athena/queues/reaction-notification.js +++ b/athena/queues/reaction-notification.js @@ -13,7 +13,7 @@ import { import { storeUsersNotifications, markUsersNotificationsAsNew, -} from '../models/usersNotifications'; +} from 'shared/db/queries/usersNotifications'; import type { Job, ReactionNotificationJobData } from 'shared/bull/types'; export default async (job: Job) => { @@ -77,14 +77,12 @@ export default async (job: Job) => { debug('mark notification as new for sender'); - // if the user is allowed to recieve notifications, update their notification + // if the user is allowed to receive notifications, update their notification return Promise.all([ markUsersNotificationsAsNew(updatedNotification.id, message.senderId), ]).catch(err => { - debug('❌ Error in job:\n'); - debug(err); + console.error(err); Raven.captureException(err); - console.log(err); }); } else { // if no notification was found that matches our bundling criteria, create a new notification @@ -121,10 +119,9 @@ export default async (job: Job) => { return Promise.all([ storeUsersNotifications(updatedNotification.id, message.senderId), ]).catch(err => { - debug('❌ Error in job:\n'); - debug(err); + console.error('❌ Error in job:\n'); + console.error(err); Raven.captureException(err); - console.log(err); }); } }; diff --git a/athena/queues/send-push-notifications.js b/athena/queues/send-push-notifications.js index c6117addd0..311240733b 100644 --- a/athena/queues/send-push-notifications.js +++ b/athena/queues/send-push-notifications.js @@ -1,9 +1,19 @@ // @flow const debug = require('debug')('athena:queue:send-push-notifications'); import sendPushNotifications from '../utils/push-notifications'; +import Raven from '../../shared/raven'; import type { Job, PushNotificationsJobData } from 'shared/bull/types'; export default async (job: Job) => { - const { data: { notification } } = job; - return sendPushNotifications(notification); + const { + data: { notification }, + } = job; + + try { + return sendPushNotifications(notification); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + } }; diff --git a/athena/queues/send-slack-invitations.js b/athena/queues/send-slack-invitations.js new file mode 100644 index 0000000000..2985c8dcb9 --- /dev/null +++ b/athena/queues/send-slack-invitations.js @@ -0,0 +1,119 @@ +// @flow +const debug = require('debug')('athena:queue:slack-import'); +import Raven from '../../shared/raven'; +import { isEmail } from 'validator'; +import { + getCommunitySettings, + getSlackUserListData, + resetCommunitySlackSettings, + updateSlackInvitesMemberCount, +} from '../models/communitySettings'; +import { getUserById } from 'shared/db/queries/user'; +import { getCommunityById } from '../models/community'; +import { + sendCommunityInviteNotificationQueue, + _adminProcessSlackImportQueue, +} from 'shared/bull/queues'; +import type { Job, SendSlackInvitationsJobData } from 'shared/bull/types'; + +const processJob = async (job: Job) => { + const { communityId, userId } = job.data; + + debug(`sending slack invitations for ${communityId}`); + + const [settings, owner, community] = await Promise.all([ + getCommunitySettings(communityId), + getUserById(userId), + getCommunityById(communityId), + ]); + + if (!settings || !settings.slackSettings) { + debug('No settings or no slack settings found for community'); + return resetCommunitySlackSettings(communityId); + } + + const { + token, + scope, + connectedBy, + invitesCustomMessage, + teamName, + } = settings.slackSettings; + + if (!token) { + debug('No token saved for Slack import'); + return resetCommunitySlackSettings(communityId); + } + + // Send an API request to Slack using the generated token to return an array of members + const members = await getSlackUserListData(token, scope); + + if (!members) { + debug('found no members in slack team'); + return resetCommunitySlackSettings(communityId); + } + + debug('got members in slack team'); + const filteredMembers = members + // filter out any restricted members + .filter(member => !member.is_restricted || !member.is_ultra_restricted) + // filter out bots + .filter(member => !member.is_bot) + // filter out deleted members + .filter(member => !member.deleted) + // only save members with valid email + .filter( + member => + member && + member.profile && + member.profile.email && + isEmail(member.profile.email) + ) + // format output data + .map(member => ({ + firstName: member.profile.first_name, + lastName: member.profile.last_name, + email: member.profile.email, + })); + + if (!filteredMembers || filteredMembers.length === 0) { + debug(`no available members to handle notifications`); + return resetCommunitySlackSettings(communityId); + } + + const membersCount = filteredMembers.length; + + const invitePromises = filteredMembers.map(member => + sendCommunityInviteNotificationQueue.add({ + recipient: { + email: member.email, + firstName: member.firstName ? member.firstName : null, + lastName: member.lastName ? member.lastName : null, + }, + communityId: communityId, + senderId: connectedBy, + customMessage: invitesCustomMessage, + }) + ); + + return await Promise.all([ + ...invitePromises, + updateSlackInvitesMemberCount(communityId, membersCount), + _adminProcessSlackImportQueue.add({ + user: owner, + community, + invitedCount: membersCount, + teamName, + }), + ]); +}; + +export default async (job: Job) => { + try { + await processJob(job); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + } +}; diff --git a/athena/queues/slack-import.js b/athena/queues/slack-import.js deleted file mode 100644 index 46a5a2aa6a..0000000000 --- a/athena/queues/slack-import.js +++ /dev/null @@ -1,55 +0,0 @@ -// @flow -const debug = require('debug')('athena:queue:slack-import'); -import Raven from '../../shared/raven'; -import { - getSlackUserListData, - saveSlackImportData, -} from '../models/slackImports'; - -/* - Receives a token from API which will be used to fetch the user list from a slack team. - That list will then get filtered to remove bots, banned users, etc. and result in writing - a members array back to the slackInvite record in the db -*/ -type JobData = { - data: { - token: string, - importId: string, - }, -}; -export default (job: JobData) => { - const { token, importId } = job.data; - - debug('new job for a slack import'); - - // Send an API request to Slack using the generated token to return an array of members - return getSlackUserListData(token) - .then(results => { - debug('got data from Slack'); - if (!results) return; - - const members = results - // filter out any restricted members - .filter(member => !member.is_restricted || !member.is_ultra_restricted) - // filter out bots - .filter(member => !member.is_bot) - // filter out deleted members - .filter(member => !member.deleted) - .map(member => { - return { - firstName: member.profile.first_name, - lastName: member.profile.last_name, - email: member.profile.email, - }; - }); - - // save the members back to the slackImport record in the db - return saveSlackImportData(importId, members); - }) - .catch(err => { - debug('❌ Error in job:\n'); - debug(err); - Raven.captureException(err); - console.log(err); - }); -}; diff --git a/athena/queues/thread-notification.js b/athena/queues/thread-notification.js index 6f3f63c341..71c35c5290 100644 --- a/athena/queues/thread-notification.js +++ b/athena/queues/thread-notification.js @@ -1,79 +1,33 @@ // @flow const debug = require('debug')('athena:queue:new-thread-notification'); import Raven from 'shared/raven'; -import addQueue from '../utils/addQueue'; +import axios from 'axios'; import getMentions from 'shared/get-mentions'; import { toPlainText, toState } from 'shared/draft-utils'; -import { fetchPayload, createPayload } from '../utils/payloads'; -import { getDistinctActors } from '../utils/actors'; -import { - storeNotification, - updateNotification, - checkForExistingNotification, -} from '../models/notification'; -import { - storeUsersNotifications, - markUsersNotificationsAsNew, -} from '../models/usersNotifications'; -import { getUsers } from '../models/user'; +import { getUserById, getUsers } from 'shared/db/queries/user'; +import { getCommunityById } from '../models/community'; import { getMembersInChannelWithNotifications } from '../models/usersChannels'; import createThreadNotificationEmail from './create-thread-notification-email'; -import type { DBThread } from 'shared/types'; +import { sendMentionNotificationQueue } from 'shared/bull/queues'; import type { Job, ThreadNotificationJobData } from 'shared/bull/types'; +import { getChannelSettings } from '../models/channelSettings'; +import { getChannelById } from '../models/channel'; +import { getCommunitySettings } from '../models/communitySettings'; +import { truncateString } from '../utils/truncateString'; +import { handleSlackChannelResponse } from '../utils/slack'; +import { decryptString } from 'shared/encryption'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; +import { signThread, signUser } from 'shared/imgix'; export default async (job: Job) => { const { thread: incomingThread } = job.data; debug(`new job for a thread by ${incomingThread.creatorId}`); - const [actor, context, entity] = await Promise.all([ - fetchPayload('USER', incomingThread.creatorId), - fetchPayload('CHANNEL', incomingThread.channelId), - createPayload('THREAD', incomingThread), + const [channelSlackSettings, communitySlackSettings] = await Promise.all([ + getChannelSettings(incomingThread.channelId), + getCommunitySettings(incomingThread.communityId), ]); - const eventType = 'THREAD_CREATED'; - - // determine if a notification already exists - const existing = await checkForExistingNotification( - eventType, - incomingThread.channelId - ); - - // handle the notification record in the db - // if it exists, we'll be updating it with new actors and entities - const handleNotificationRecord = existing - ? updateNotification - : storeNotification; - - // handle the usersNotification record in the db - // if it exists, we'll mark it as new to trigger a badge in the app - const handleUsersNotificationRecord = existing - ? markUsersNotificationsAsNew - : storeUsersNotifications; - - // actors should always be distinct to make client side rendering easier - const distinctActors = existing - ? getDistinctActors([...existing.actors, actor]) - : [actor]; - - // append the new thread to the list of entities - const entities = existing ? [...existing.entities, entity] : [entity]; - - // construct a new notification record to either be updated or stored in the db - const nextNotificationRecord = Object.assign( - {}, - { - ...existing, - event: eventType, - actors: distinctActors, - context, - entities, - } - ); - - // update or store a record in the notifications table, returns a notification - const updatedNotification = await handleNotificationRecord( - nextNotificationRecord - ); // get the members in the channel who should receive notifications const recipients = await getMembersInChannelWithNotifications( @@ -88,16 +42,15 @@ export default async (job: Job) => { r => r.id !== incomingThread.creatorId ); + const plainTextBody = incomingThread.content.body + ? toPlainText(toState(JSON.parse(incomingThread.content.body))) + : ''; // see if anyone was mentioned in the thread - const mentions = getMentions( - incomingThread.content.body - ? toPlainText(toState(JSON.parse(incomingThread.content.body))) - : '' - ); + const mentions = getMentions(plainTextBody); // if people were mentioned in the thread, let em know if (mentions && mentions.length > 0) { mentions.forEach(username => { - addQueue('mention notification', { + sendMentionNotificationQueue.add({ threadId: incomingThread.id, // thread where the mention happened senderId: incomingThread.creatorId, // user who created the mention username: username, @@ -112,22 +65,105 @@ export default async (job: Job) => { return r.username && mentions.indexOf(r.username) < 0; }); - if (!recipientsWithoutMentions || recipientsWithoutMentions.length === 0) - return; + const signedRecipientsWithoutMentions = recipientsWithoutMentions.map(r => { + return signUser(r); + }); - // for each recipient that *wasn't* mentioned, create a notification in the db - const usersNotificationPromises = recipientsWithoutMentions.map( - async recipient => - await handleUsersNotificationRecord(updatedNotification.id, recipient.id) - ); + let slackNotificationPromise; + if ( + // process.env.NODE_ENV === 'production' && + communitySlackSettings && + communitySlackSettings.slackSettings && + communitySlackSettings.slackSettings.token && + channelSlackSettings && + channelSlackSettings.slackSettings && + channelSlackSettings.slackSettings.botLinks && + channelSlackSettings.slackSettings.botLinks.threadCreated + ) { + const slackChannel = + channelSlackSettings.slackSettings.botLinks.threadCreated; + + const [author, community, channel] = await Promise.all([ + // $FlowIssue + getUserById(incomingThread.creatorId), + getCommunityById(incomingThread.communityId), + getChannelById(incomingThread.channelId), + ]); + + const signedAuthor = signUser(author); + + const decryptedToken = decryptString( + communitySlackSettings.slackSettings.token + ); + + slackNotificationPromise = axios({ + method: 'post', + url: 'https://slack.com/api/chat.postMessage', + headers: { + Authorization: `Bearer ${decryptedToken}`, + }, + data: { + channel: slackChannel, + attachments: [ + { + fallback: `New conversation published in ${community.name} #${ + channel.name + }:`, + author_name: `${author.name} (@${author.username})`, + author_link: `https://spectrum.chat/users/${author.username}`, + author_icon: signedAuthor.profilePhoto, + pretext: `New conversation published in ${community.name} #${ + channel.name + }:`, + title: truncateString(incomingThread.content.title, 80), + title_link: `https://spectrum.chat/thread/${incomingThread.id}`, + text: truncateString(plainTextBody, 140), + footer: 'Spectrum', + footer_icon: + 'https://spectrum.chat/img/apple-icon-57x57-precomposed.png', + ts: new Date(incomingThread.createdAt).getTime() / 1000, + color: '#4400CC', + actions: [ + { + type: 'button', + text: 'View conversation', + url: `https://spectrum.chat/thread/${incomingThread.id}`, + }, + { + type: 'button', + text: `Message ${author.name}`, + url: `https://spectrum.chat/users/${author.username}`, + }, + ], + }, + ], + }, + }).then(response => { + trackQueue.add({ + userId: author.id, + event: events.THREAD_SENT_TO_SLACK, + context: { threadId: incomingThread.id }, + }); + + return handleSlackChannelResponse( + response.data, + incomingThread.communityId + ); + }); + } + + const signedThread = signThread(incomingThread); return Promise.all([ - createThreadNotificationEmail(incomingThread, recipientsWithoutMentions), // handle emails separately - usersNotificationPromises, // update or store usersNotifications in-app + createThreadNotificationEmail( + signedThread, + signedRecipientsWithoutMentions + ), // handle emails separately + slackNotificationPromise, ]).catch(err => { - debug('❌ Error in job:\n'); - debug(err); + console.error('❌ Error in job:\n'); + console.error(err); Raven.captureException(err); - console.log(err); + console.error(err); }); }; diff --git a/athena/queues/thread-reaction-notification.js b/athena/queues/thread-reaction-notification.js new file mode 100644 index 0000000000..44acab70b5 --- /dev/null +++ b/athena/queues/thread-reaction-notification.js @@ -0,0 +1,116 @@ +// @flow +const debug = require('debug')('athena:queue:reaction-notification'); +import Raven from '../../shared/raven'; +import { fetchPayload, createPayload } from '../utils/payloads'; +import { getDistinctActors } from '../utils/actors'; +import { getThreadById } from '../models/thread'; +import { getUserNotificationPermissionsInThread } from '../models/usersThreads'; +import { + storeNotification, + updateNotification, + checkForExistingNotification, +} from '../models/notification'; +import { + storeUsersNotifications, + markUsersNotificationsAsNew, +} from 'shared/db/queries/usersNotifications'; +import type { Job, ThreadReactionNotificationJobData } from 'shared/bull/types'; + +export default async (job: Job) => { + const incomingReaction = job.data.threadReaction; + const currentUserId = job.data.userId; + + debug(`new job for ${incomingReaction.id} by ${currentUserId}`); + + /* + These promises are used to create or modify a notification. The order is: + - actor + - context + - entity + */ + const actor = await fetchPayload('USER', incomingReaction.userId); + const context = await fetchPayload('THREAD', incomingReaction.threadId); + const entity = await createPayload('THREAD_REACTION', incomingReaction); + + // Check to see if an existing notif exists by matching the 'event' type, with the context of the notification, within a certain time period. + const notification = await checkForExistingNotification( + 'THREAD_REACTION_CREATED', + incomingReaction.threadId + ); + + if (notification) { + // if an existing notification exists, update it with the newest actor + entities + debug('found existing notification'); + + // actors should always be distinct to make client side rendering easier + const distinctActors = getDistinctActors([...notification.actors, actor]); + + // create a new notification + const newNotification = Object.assign({}, notification, { + actors: [...distinctActors], + context, + entities: [...notification.entities, entity], + }); + + debug('update existing notification in database with new data'); + + // update the notification in the db + const updatedNotification = await updateNotification(newNotification); + + // get the original thread where the reaction was left + const thread = await getThreadById(updatedNotification.context.id); + + const hasPermission = await getUserNotificationPermissionsInThread( + thread.creatorId, + thread.id + ); + + debug('check permission of sender'); + + if (!hasPermission) return; + + debug('mark notification as new for sender'); + + // if the user is allowed to receive notifications, update their notification + return Promise.all([ + markUsersNotificationsAsNew(updatedNotification.id, thread.creatorId), + ]).catch(err => { + console.error(err); + Raven.captureException(err); + }); + } else { + // if no notification was found that matches our bundling criteria, create a new notification + // create the notification record + const notification = { + actors: [actor], + event: 'THREAD_REACTION_CREATED', + context, + entities: [entity], + }; + debug('create notification in db'); + + const updatedNotification = await storeNotification(notification); + + // get the original thread where the reaction was left + const thread = await getThreadById(updatedNotification.context.id); + debug('get thread'); + + const hasPermission = await getUserNotificationPermissionsInThread( + thread.creatorId, + thread.id + ); + + debug('check permission of sender'); + if (!hasPermission) return; + + debug('mark notification as new for sender'); + + return Promise.all([ + storeUsersNotifications(updatedNotification.id, thread.creatorId), + ]).catch(err => { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + }); + } +}; diff --git a/athena/queues/track-user-thread-last-seen.js b/athena/queues/track-user-thread-last-seen.js index ebef7951b0..c81a10566c 100644 --- a/athena/queues/track-user-thread-last-seen.js +++ b/athena/queues/track-user-thread-last-seen.js @@ -7,6 +7,7 @@ import { createUserThread, } from '../models/usersThreads'; import type { Job, UserThreadLastSeenJobData } from 'shared/bull/types'; +import type { DBUsersThreads } from 'shared/types'; export default async (job: Job) => { const { userId, threadId, timestamp } = job.data; @@ -19,16 +20,34 @@ export default async (job: Job) => { ); return; } - const date = new Date(parseInt(timestamp, 10)); + // Timestamp will be serialized to Redis, so it's either a string date "Thu 20 Nov 2017" or a + // string timestamp. "1835463856" We gotta make sure to handle both those cases, so we try and + // parse to int first, and if that fails (i.e. returns NaN) we assume it's a string date. + let parsedTimestamp = + typeof timestamp === 'string' ? parseInt(timestamp, 10) : timestamp; + if (isNaN(parsedTimestamp)) parsedTimestamp = timestamp; + const date = new Date(parsedTimestamp); debug( `new job\nthreadId: ${threadId}\nuserId: ${userId}\ntimestamp: ${new Date( timestamp ).toString()}` ); - const record = await getUsersThread(userId, threadId); + const record: ?DBUsersThreads = await getUsersThread(userId, threadId); if (record) { + if ( + record.lastSeen && + new Date(record.lastSeen).getTime() > new Date(date).getTime() + ) { + debug( + `old lastSeen ${record.lastSeen.toString()} is later than new lastSeen ${date.toString()}, not running job:\nuserId: ${userId}\nthreadId: ${threadId}\ntimestamp: ${new Date( + timestamp + ).toString()}` + ); + return; + } + debug( `existing usersThread, updating usersThreads#${record.id} with lastSeen` ); @@ -45,11 +64,8 @@ export default async (job: Job) => { debug(`lastSeen successfully stored`); }) .catch(err => { - debug( - '❌ Error in job for\nuserId: ${userId}\nthreadId: ${threadId}\ntimestamp: ${timestamp}' - ); - debug(err); + console.error('❌ Error in job'); + console.error(err); Raven.captureException(err); - console.log(err); }); }; diff --git a/athena/utils/addQueue.js b/athena/utils/addQueue.js deleted file mode 100644 index da96147920..0000000000 --- a/athena/utils/addQueue.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -const createQueue = require('shared/bull/create-queue'); -export const addQueue = (name: string, data: any) => { - const worker = createQueue(name); - - return worker.add( - { ...data }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); -}; -export default addQueue; diff --git a/athena/utils/get-email-status.js b/athena/utils/get-email-status.js index 1accf4a877..dc1a80b828 100644 --- a/athena/utils/get-email-status.js +++ b/athena/utils/get-email-status.js @@ -1,7 +1,7 @@ // @flow const debug = require('debug')('athena:should-get-email'); import { getUsersSettings } from '../models/usersSettings'; -import { getUserById } from '../models/user'; +import { getUserById } from 'shared/db/queries/user'; const getEmailStatus = ( userId: string, @@ -28,7 +28,8 @@ const getEmailStatus = ( return true; }) .catch(err => { - debug(err); + console.error('❌ Error in job:\n'); + console.error(err); return false; }); }; diff --git a/athena/utils/payloads.js b/athena/utils/payloads.js index 3726c68212..f0ec647915 100644 --- a/athena/utils/payloads.js +++ b/athena/utils/payloads.js @@ -4,7 +4,7 @@ import { getMessageById } from '../models/message'; import { getThreadById } from '../models/thread'; import { getChannelById } from '../models/channel'; import { getCommunityById } from '../models/community'; -import { getUserById } from '../models/user'; +import { getUserById } from 'shared/db/queries/user'; import { getDirectMessageThreadById } from '../models/directMessageThread'; const debug = require('debug')('athena:payloads'); @@ -40,7 +40,7 @@ export const fetchPayload = ( ); } default: { - console.log(`Couldn't match the type '${type}' with EntityTypes`); + console.error(`Couldn't match the type '${type}' with EntityTypes`); return Promise.resolve({}); } } diff --git a/athena/utils/push-notifications/index.js b/athena/utils/push-notifications/index.js index 36fcf06c50..58368cbf52 100644 --- a/athena/utils/push-notifications/index.js +++ b/athena/utils/push-notifications/index.js @@ -1,31 +1,20 @@ // @flow const debug = require('debug')('athena:utils:send-push-notifications'); import { getSubscriptions } from '../../models/web-push-subscription'; -import { getExpoSubscriptions } from 'api/models/expo-push-subscription'; -import { markSingleNotificationSeen } from '../../models/usersNotifications'; -import formatNotification from './notification-formatting'; +import formatNotification from 'shared/notification-to-text'; import { sendWebPushNotification } from './send-web-push-notification'; -import { sendExpoPushNotifications } from './send-expo-push-notifications'; import type { DBNotificationsJoin } from 'shared/types'; const sendPushNotifications = async (notification: DBNotificationsJoin) => { debug('send notification as web push notification'); - const [expoSubscriptions = [], webPushSubscriptions = []] = await Promise.all( - [ - getExpoSubscriptions(notification.userId), - getSubscriptions(notification.userId), - ] - ); + const [webPushSubscriptions = []] = await Promise.all([ + getSubscriptions(notification.userId), + ]); - debug( - `expo subscriptions: ${expoSubscriptions.length}\nweb push subscriptions: ${ - webPushSubscriptions.length - }` - ); + debug(`web push subscriptions: ${webPushSubscriptions.length}`); - if (expoSubscriptions.length === 0 && webPushSubscriptions.length === 0) - return; + if (webPushSubscriptions.length === 0) return; const payload = formatNotification(notification, notification.userId); @@ -36,12 +25,8 @@ const sendPushNotifications = async (notification: DBNotificationsJoin) => { ...payload, }); }); - const expoPushNotifications = sendExpoPushNotifications( - expoSubscriptions, - payload - ); - return Promise.all([...webPushNotifications, ...expoPushNotifications]); + return Promise.all([...webPushNotifications]); }; export default sendPushNotifications; diff --git a/athena/utils/push-notifications/send-expo-push-notifications.js b/athena/utils/push-notifications/send-expo-push-notifications.js deleted file mode 100644 index 9b84ccdc68..0000000000 --- a/athena/utils/push-notifications/send-expo-push-notifications.js +++ /dev/null @@ -1,81 +0,0 @@ -// @flow -const debug = require('debug')('athena:utils:send-expo-push-notifications'); -import Expo from 'expo-server-sdk'; -import { removeExpoSubscription } from 'api/models/expo-push-subscription'; -import Raven from 'shared/raven'; -import type { DBExpoPushSubscription } from 'shared/types'; - -const expo = new Expo(); - -type PushNotificationPayload = { - title: string, - body: string, - data?: Object, -}; - -type ExpoPushMessage = { - to: string, - data?: Object, - title?: string, - body?: string, - sound?: 'default' | null, - ttl?: number, - expiration?: number, - priority?: 'default' | 'normal' | 'high', - badge?: number, -}; - -export const sendExpoPushNotifications = ( - subscriptions: Array, - payload: PushNotificationPayload -): Array> => { - let messages: Array = []; - subscriptions.forEach(({ token }) => { - // TODO(@mxstbr): Remove invalid subscriptions from db - if (!Expo.isExpoPushToken(token)) { - debug(`invalid expo token ${token}, not sending push notification.`); - removeExpoSubscription(token); - return; - } - - debug(`sending push notification to token ${token}`); - messages.push({ - to: token, - sound: 'default', - priority: 'high', - ttl: 86400, - title: payload.title, - body: payload.body, - data: payload.data, - }); - }); - - const chunks = expo.chunkPushNotifications(messages); - - return chunks.map(chunk => - expo - .sendPushNotificationAsync(chunk) - .then(receipts => { - if (!receipts.data || receipts.data.length === 0) return; - return receipts.data - .filter(({ status }) => status === 'error') - .map(receipt => { - if (receipt.details && receipt.details.error) { - switch (receipt.details.error) { - case 'DeviceNotRegistered': { - // TODO(@mxstbr): Remove the subscription here - // This is blocked upstream because the expo push service doesn't return the token - } - default: { - return Promise.resolve(); - } - } - } - }); - }) - .catch(err => { - // This means Expo is down or something - Raven.captureException(err); - }) - ); -}; diff --git a/athena/utils/slack/index.js b/athena/utils/slack/index.js new file mode 100644 index 0000000000..80eaee8909 --- /dev/null +++ b/athena/utils/slack/index.js @@ -0,0 +1,30 @@ +// @flow +import { resetCommunitySlackSettings } from '../../models/communitySettings'; +import { trackQueue } from 'shared/bull/queues'; +import { events } from 'shared/analytics'; + +// prettier-ignore +export const handleSlackChannelResponse = async (data: Object, communityId: string) => { + const errorsToTriggerRest = [ + 'token_revoked', + 'not_authed', + 'invalid_auth', + 'account_inactive', + 'no_permission', + ]; + + if (data.error && errorsToTriggerRest.indexOf(data.error) >= 0) { + trackQueue.add({ + userId: 'ADMIN', + event: events.COMMUNITY_SLACK_TEAM_RESET, + context: { communityId }, + properties: { + error: data.error + } + }) + + return await resetCommunitySlackSettings(communityId); + } + + return null; +}; diff --git a/mobile/components/ThreadItem/utils.js b/athena/utils/truncateString.js similarity index 72% rename from mobile/components/ThreadItem/utils.js rename to athena/utils/truncateString.js index 9845a65f29..88b5438d92 100644 --- a/mobile/components/ThreadItem/utils.js +++ b/athena/utils/truncateString.js @@ -1,5 +1,6 @@ // @flow -export const truncate = (str: string, length: number) => { + +export const truncateString = (str: string, length: number) => { if (str.length <= length) { return str; } diff --git a/athena/utils/types.js b/athena/utils/types.js index f996728eb2..e6ec36adb1 100644 --- a/athena/utils/types.js +++ b/athena/utils/types.js @@ -1,6 +1,7 @@ // @flow export type EntityTypes = | 'REACTION' + | 'THREAD_REACTION' | 'MESSAGE' | 'THREAD' | 'CHANNEL' @@ -10,6 +11,7 @@ export type EntityTypes = export type EventTypes = | 'REACTION_CREATED' + | 'THREAD_REACTION_CREATED' | 'MESSAGE_CREATED' | 'THREAD_CREATED' | 'THREAD_EDITED' diff --git a/athena/yarn.lock b/athena/yarn.lock index 23c95c23f6..9b63351e50 100644 --- a/athena/yarn.lock +++ b/athena/yarn.lock @@ -2,55 +2,96 @@ # yarn lockfile v1 +agent-base@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" + integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== + dependencies: + es6-promisify "^5.0.0" + asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= -asn1.js@^4.8.1: - version "4.9.2" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.2.tgz#8117ef4f7ed87cd8f89044b5bff97ac243a16c9a" +asn1.js@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.0.1.tgz#7668b56416953f0ce3421adbb3893ace59c96f59" + integrity sha512-aO8EaEgbgqq77IEw+1jfx5c9zTbzvkfuRBuZsSsPnTHMkmd5AI4J6OtITLZFa381jReeaQL67J0GBTUu0+ZTVw== dependencies: bn.js "^4.0.0" inherits "^2.0.1" minimalistic-assert "^1.0.0" +aws-sdk@^2.383.0: + version "2.383.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.383.0.tgz#86045c0a4a4898dca84a4877cbe765b7dc0f8bba" + integrity sha512-PN+s+NTABtBloS46c7C2dvoEzrdY2NZ5nsfljL3xDX2rvjJEQxdchS2jcCpyc5ZNudFwta66wY4EGBZqf4Attw== + dependencies: + buffer "4.9.1" + events "1.1.1" + ieee754 "1.1.8" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.1.0" + xml2js "0.4.19" + axios@^0.16.2: version "0.16.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d" + integrity sha1-uk+S8XFn37q0CYN4VFS5rBScPG0= dependencies: follow-redirects "^1.2.3" is-buffer "^1.1.5" -babel-runtime@^6.11.6: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" +base64-js@^1.0.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" + integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== -base64url@2.0.0, base64url@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" +bignumber.js@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-1.1.1.tgz#1a415d9ac014c13256af1feed9d1a3e5717a8cf7" + integrity sha1-GkFdmsAUwTJWrx/u2dGj5XF6jPc= "bluebird@>= 3.0.1", bluebird@^3.3.4, bluebird@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== bn.js@^4.0.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" - -boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + +buffer@4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" + integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" bull@3.3.10: version "3.3.10" resolved "https://registry.yarnpkg.com/bull/-/bull-3.3.10.tgz#32e76281902070b4720bc37b3793f09db635f646" + integrity sha512-bO1w83BONVTE3Rb10e0wPf11lXH1fGFNGmZH4Ys9jR7jGN4qmTNo7odxm7ELhjKXofjiFLWZFuTdONCs8kV8ug== dependencies: bluebird "^3.5.0" cron-parser "^2.4.1" @@ -60,197 +101,188 @@ bull@3.3.10: semver "^5.4.1" uuid "^3.1.0" -cheerio@^0.22.0: - version "0.22.0" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" - dependencies: - css-select "~1.2.0" - dom-serializer "~0.1.0" - entities "~1.1.1" - htmlparser2 "^3.9.1" - lodash.assignin "^4.0.9" - lodash.bind "^4.1.4" - lodash.defaults "^4.0.1" - lodash.filter "^4.4.0" - lodash.flatten "^4.2.0" - lodash.foreach "^4.3.0" - lodash.map "^4.4.0" - lodash.merge "^4.4.0" - lodash.pick "^4.2.1" - lodash.reduce "^4.4.0" - lodash.reject "^4.4.0" - lodash.some "^4.4.0" +charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= cluster-key-slot@^1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.8.tgz#7654556085a65330932a2e8b5976f8e2d0b3e414" + version "1.0.12" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.12.tgz#d5deff2a520717bc98313979b687309b2d368e29" + integrity sha512-21O0kGmvED5OJ7ZTdqQ5lQQ+sjuez33R+d35jZKLwqUb5mqcPHUsxOSzj61+LHVtxGZd1kShbQM3MjB/gBJkVg== cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= core-js@^1.0.0: version "1.2.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-js@^2.4.0: - version "2.5.3" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" +crc@^3.5.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" + integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== + dependencies: + buffer "^5.1.0" cron-parser@^2.4.1: - version "2.4.3" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.4.3.tgz#cae844c20117fc72c678f63ac83c7884be199e78" + version "2.7.1" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.7.1.tgz#d08c00b1e220db564fd1cecb5019c8dd450f84d1" + integrity sha512-gupE4KsGEVtp5X4YbUlQx6NiFt3e+VOhREPI4ZXS9FT5JcOjfw2ey1EUv3J6XWrxHR1aKYrk4uJDmdRjG39bgA== dependencies: is-nan "^1.2.1" - moment-timezone "^0.5.0" + moment-timezone "^0.5.23" -css-select@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" +crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + +cryptr@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cryptr/-/cryptr-3.0.0.tgz#4cdb1ac8b0b292c6ac1dcdf503bb27a05e4eba10" + integrity sha512-ojvQNR6fiYVPVeRJbihzuHdJ4lU4zWa/A2heJuW54R8U51DnRBrMD7KA7gET9yPrTXkcKjntflx/wW5nohKRPQ== + +datadog-metrics@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/datadog-metrics/-/datadog-metrics-0.8.1.tgz#be87237109a7084193c668d80112533ef00e3f21" + integrity sha512-qTSKnddO6GxTJW9FYpmWjvvift3qfyMurDjwNjJnJhBk76pBdDhC0B5V9V+XwPdn4r42qu48kwXNuHJslXlDOA== dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" + debug "3.1.0" + dogapi "1.1.0" -css-what@2.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" +debug@3.1.0, 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@^2.2.0, debug@^2.4.5: - version "2.6.8" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" +debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debuglog@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" -define-properties@^1.1.1, define-properties@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" +debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== dependencies: - foreach "^2.0.5" - object-keys "^1.0.8" + ms "^2.1.1" -denque@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/denque/-/denque-1.1.1.tgz#10229c2b88eec1bd15ff82c5fde356e7beb6db9e" +debuglog@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= -dom-serializer@0, dom-serializer@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" - dependencies: - domelementtype "~1.1.1" - entities "~1.1.1" +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -domelementtype@1, domelementtype@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -domelementtype@~1.1.1: +define-properties@^1.1.1: version "1.1.3" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" - -domhandler@^2.3.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== dependencies: - domelementtype "1" + object-keys "^1.0.12" -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - dependencies: - dom-serializer "0" - domelementtype "1" +denque@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.0.tgz#79e2f0490195502107f24d9553f374837dabc916" + integrity sha512-gh513ac7aiKrAgjiIBWZG0EASyDF9p4JMWwKA8YU5s9figrL5SRNEMT6FDynsegakuhWd1wVqTvqvqAoDxw7wQ== -domutils@^1.5.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff" +dogapi@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/dogapi/-/dogapi-1.1.0.tgz#71a43865ad4bb4cb18bc3e13cf769971f501030a" + integrity sha1-caQ4Za1LtMsYvD4Tz3aZcfUBAwo= dependencies: - dom-serializer "0" - domelementtype "1" + extend "^3.0.0" + json-bigint "^0.1.4" + minimist "^1.1.1" + rc "^1.0.0" -draft-js@^0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.3.tgz#5f3c3025af9f494c62f00a9066006ccdc1b3c943" +draft-js@^0.10.5: + version "0.10.5" + resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.5.tgz#bfa9beb018fe0533dbb08d6675c371a6b08fa742" + integrity sha512-LE6jSCV9nkPhfVX2ggcRLA4FKs6zWq9ceuO/88BpXdNCS7mjRTgs0NsV6piUCJX9YxMsB9An33wnkMmU2sD2Zg== dependencies: - enzyme "^2.9.1" fbjs "^0.8.15" immutable "~3.7.4" object-assign "^4.1.0" -ecdsa-sig-formatter@1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + integrity sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM= dependencies: - base64url "^2.0.0" safe-buffer "^5.0.1" emoji-regex@^6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2" + integrity sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ== encoding@^0.1.11: version "0.1.12" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= dependencies: iconv-lite "~0.4.13" -entities@^1.1.1, entities@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" +es6-promise@^4.0.3: + version "4.2.5" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054" + integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg== -enzyme@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-2.9.1.tgz#07d5ce691241240fb817bf2c4b18d6e530240df6" +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= dependencies: - cheerio "^0.22.0" - function.prototype.name "^1.0.0" - is-subset "^0.1.1" - lodash "^4.17.4" - object-is "^1.0.1" - object.assign "^4.0.4" - object.entries "^1.0.4" - object.values "^1.0.4" - prop-types "^15.5.10" - uuid "^3.0.1" - -es-abstract@^1.6.1: - version "1.9.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.9.0.tgz#690829a07cae36b222e7fd9b75c0d0573eb25227" - dependencies: - es-to-primitive "^1.1.1" - function-bind "^1.1.1" - has "^1.0.1" - is-callable "^1.1.3" - is-regex "^1.0.4" - -es-to-primitive@^1.1.1: + es6-promise "^4.0.3" + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +events@1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" - dependencies: - is-callable "^1.1.1" - is-date-object "^1.0.1" - is-symbol "^1.0.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= -expo-server-sdk@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/expo-server-sdk/-/expo-server-sdk-2.3.3.tgz#6e52312274ba8e7145fb8c755d85a207c78152e8" - dependencies: - babel-runtime "^6.11.6" - invariant "^2.2.2" - node-fetch "^1.6.1" +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +faker@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" + integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8= -fbjs@^0.8.15, fbjs@^0.8.16: - version "0.8.16" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" +fbjs@^0.8.15: + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= dependencies: core-js "^1.0.0" isomorphic-fetch "^2.1.1" @@ -258,82 +290,84 @@ fbjs@^0.8.15, fbjs@^0.8.16: object-assign "^4.1.0" promise "^7.1.1" setimmediate "^1.0.5" - ua-parser-js "^0.7.9" + ua-parser-js "^0.7.18" flexbuffer@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/flexbuffer/-/flexbuffer-0.0.6.tgz#039fdf23f8823e440c38f3277e6fef1174215b30" + integrity sha1-A5/fI/iCPkQMOPMnfm/vEXQhWzA= follow-redirects@^1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.4.tgz#355e8f4d16876b43f577b0d5ce2668b9723214ea" + version "1.5.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.9.tgz#c9ed9d748b814a39535716e531b9196a845d89c6" + integrity sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w== dependencies: - debug "^2.4.5" - -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - -function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + debug "=3.1.0" -function.prototype.name@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.0.3.tgz#0099ae5572e9dd6f03c97d023fd92bcc5e639eac" +http_ece@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/http_ece/-/http_ece-1.0.5.tgz#b60660faaf14215102d1493ea720dcd92b53372f" + integrity sha1-tgZg+q8UIVEC0Uk+pyDc2StTNy8= dependencies: - define-properties "^1.1.2" - function-bind "^1.1.0" - is-callable "^1.1.3" + urlsafe-base64 "~1.0.0" -has@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" +https-proxy-agent@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" + integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== dependencies: - function-bind "^1.0.2" + agent-base "^4.1.0" + debug "^3.1.0" -htmlparser2@^3.9.1: - version "3.9.2" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" +iconv-lite@~0.4.13: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: - domelementtype "^1.3.0" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^2.0.2" + safer-buffer ">= 2.1.2 < 3" -http_ece@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/http_ece/-/http_ece-0.5.2.tgz#5654d7ec9d996b749ce00a276e18d54b6d8f905f" - dependencies: - urlsafe-base64 "~1.0.0" +ieee754@1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + integrity sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q= -iconv-lite@~0.4.13: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +ieee754@^1.1.4: + version "1.1.12" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" + integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== + +imgix-core-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/imgix-core-js/-/imgix-core-js-1.2.0.tgz#72163ebd312b25cdae077340d13cf94dc09327f2" + integrity sha512-Eq8IabyhZwwP1m+E26L4AJLCznqckFuL2nvOFmtYESmyFEv4IXa/UU58DHegnmMYoPqHunmG9ttL6NDdB0ddbg== + dependencies: + crc "^3.5.0" + js-base64 "^2.1.9" + md5 "^2.2.1" immutable@~3.7.4: version "3.7.6" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" + integrity sha1-E7TTyxK++hVIKib+Gy665kAHHks= -inherits@^2.0.1, inherits@~2.0.3: +inherits@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -invariant@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" - dependencies: - loose-envify "^1.0.0" +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -ioredis@^3.1.4: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-3.2.1.tgz#4c10bcce9659fdb0af923b0e7915208fe023d3f0" +ioredis@3.2.2, ioredis@^3.1.4: + version "3.2.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-3.2.2.tgz#b7d5ff3afd77bb9718bb2821329b894b9a44c00b" + integrity sha512-g+ShTQYLsCcOUkNOK6CCEZbj3aRDVPw3WOwXk+LxlUKvuS9ujEqP2MppBHyRVYrNNFW/vcPaTBUZ2ctGNSiOCA== dependencies: bluebird "^3.3.4" cluster-key-slot "^1.0.6" - debug "^2.2.0" + debug "^2.6.9" denque "^1.1.0" flexbuffer "0.0.6" lodash.assign "^4.2.0" @@ -355,398 +389,493 @@ ioredis@^3.1.4: redis-commands "^1.2.0" redis-parser "^2.4.0" -is-buffer@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" - -is-callable@^1.1.1, is-callable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" +ioredis@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.2.0.tgz#f0f76fa5067a51c365ef1411f6572478a825971d" + integrity sha512-PdxZGNJBfPiR2RI6DkqmiacL1+ML3gaqEiaC5QXWQt9eSTlGj+BwDCct0s8irn1ed8GyzAHTzcjvU9fmnl6D7A== + dependencies: + cluster-key-slot "^1.0.6" + debug "^3.1.0" + denque "^1.1.0" + flexbuffer "0.0.6" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + redis-commands "1.4.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^1.0.0" -is-date-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" +is-buffer@^1.1.5, is-buffer@~1.1.1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-nan@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.2.1.tgz#9faf65b6fb6db24b7f5c0628475ea71f988401e2" + integrity sha1-n69ltvttskt/XAYoR16nH5iEAeI= dependencies: define-properties "^1.1.1" -is-regex@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" - dependencies: - has "^1.0.1" - is-stream@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-subset@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" - -is-symbol@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" - -isarray@~1.0.0: +isarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= isomorphic-fetch@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= dependencies: node-fetch "^1.0.1" whatwg-fetch ">=0.10.0" -js-tokens@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" +jmespath@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= -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" +js-base64@^2.1.9: + version "2.4.9" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.9.tgz#748911fb04f48a60c4771b375cac45a80df11c03" + integrity sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ== + +"js-tokens@^3.0.0 || ^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== + +json-bigint@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-0.1.4.tgz#b5d40b8a9009e92f157f7c079db097001830e01e" + integrity sha1-tdQLipAJ6S8Vf3wHnbCXABgw4B4= + dependencies: + bignumber.js "~1.1.1" -jwa@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5" +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + integrity sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw== dependencies: - base64url "2.0.0" buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.9" + ecdsa-sig-formatter "1.0.10" safe-buffer "^5.0.1" jws@^3.1.3: - version "3.1.4" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2" + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + integrity sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ== dependencies: - base64url "^2.0.0" - jwa "^1.1.4" + jwa "^1.1.5" safe-buffer "^5.0.1" lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= -lodash.assignin@^4.0.9: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" - -lodash.bind@^4.1.4, lodash.bind@^4.2.1: +lodash.bind@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" + integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU= lodash.clone@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" + integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= -lodash.defaults@^4.0.1, lodash.defaults@^4.2.0: +lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= lodash.difference@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= -lodash.filter@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" - -lodash.flatten@^4.2.0, lodash.flatten@^4.4.0: +lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= -lodash.foreach@^4.3.0, lodash.foreach@^4.5.0: +lodash.foreach@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= + +lodash.intersection@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.intersection/-/lodash.intersection-4.4.0.tgz#0a11ba631d0e95c23c7f2f4cbb9a692ed178e705" + integrity sha1-ChG6Yx0OlcI8fy9Mu5ppLtF45wU= lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= lodash.keys@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" - -lodash.map@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" - -lodash.merge@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" + integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU= lodash.noop@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" + integrity sha1-OBiPTWUKOkdCWEObluxFsyYXEzw= lodash.partial@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.partial/-/lodash.partial-4.2.1.tgz#49f3d8cfdaa3bff8b3a91d127e923245418961d4" + integrity sha1-SfPYz9qjv/izqR0SfpIyRUGJYdQ= -lodash.pick@^4.2.1, lodash.pick@^4.4.0: +lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" - -lodash.reduce@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" - -lodash.reject@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" + integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= lodash.sample@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.sample/-/lodash.sample-4.2.1.tgz#5e4291b0c753fa1abeb0aab8fb29df1b66f07f6d" + integrity sha1-XkKRsMdT+hq+sKq4+ynfG2bwf20= lodash.shuffle@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz#145b5053cf875f6f5c2a33f48b6e9948c6ec7b4b" - -lodash.some@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" + integrity sha1-FFtQU8+HX29cKjP0i26ZSMbse0s= lodash.values@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" + integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= lodash@^4.17.4: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== -loose-envify@^1.0.0, loose-envify@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" +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" + js-tokens "^3.0.0 || ^4.0.0" -lsmod@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b" +md5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" minimalistic-assert@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimist@^1.2.0: +minimist@^1.1.1, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -moment-timezone@^0.5.0: - version "0.5.13" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.13.tgz#99ce5c7d827262eb0f1f702044177f60745d7b90" +moment-timezone@^0.5.23: + version "0.5.23" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" + integrity sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w== dependencies: moment ">= 2.9.0" "moment@>= 2.9.0": - version "2.18.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= 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.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== node-env-file@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/node-env-file/-/node-env-file-0.1.8.tgz#fccb7b050f735b5a33da9eb937cf6f1ab457fb69" + integrity sha1-/Mt7BQ9zW1oz2p65N89vGrRX+2k= -node-fetch@^1.0.1, node-fetch@^1.6.1: +node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== dependencies: encoding "^0.1.11" is-stream "^1.0.1" -now-env@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/now-env/-/now-env-3.0.2.tgz#b0725023a799240c5c7e4a43515eb0a65c2c3663" - -nth-check@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" - dependencies: - boolbase "~1.0.0" +now-env@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/now-env/-/now-env-3.1.0.tgz#e0198b67279d387229cfd4b25de4c2fc7156943f" + integrity sha512-f+jXC+UkoxD/g9Nlig99Bxswoh7UUuQxw0EsPfuueHnVpVE0LfgQ4el5dxY4TSXwrL9mEF9GGm0gb7r3K8r/ug== -object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-is@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" - -object-keys@^1.0.10, object-keys@^1.0.8: - version "1.0.11" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" - -object.assign@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc" - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.0" - object-keys "^1.0.10" - -object.entries@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f" - dependencies: - define-properties "^1.1.2" - es-abstract "^1.6.1" - function-bind "^1.1.0" - has "^1.0.1" - -object.values@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" - dependencies: - define-properties "^1.1.2" - es-abstract "^1.6.1" - function-bind "^1.1.0" - has "^1.0.1" +object-keys@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" + integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" +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= promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== dependencies: asap "~2.0.3" -prop-types@^15.5.10: - version "15.6.0" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.3.1" - object-assign "^4.1.1" +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -raven@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/raven/-/raven-2.1.1.tgz#b3a974c6c29315c677c079e168435ead196525cd" +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +raven@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/raven/-/raven-2.6.4.tgz#458d4a380c8fbb59e0150c655625aaf60c167ea3" + integrity sha512-6PQdfC4+DQSFncowthLf+B6Hr0JpPsFBgTVYTAOq7tCmx/kR4SXbeawtPch20+3QfUcQDoJBLjWW1ybvZ4kXTw== dependencies: cookie "0.3.1" - json-stringify-safe "5.0.1" - lsmod "1.0.0" - stack-trace "0.0.9" + md5 "^2.2.1" + stack-trace "0.0.10" timed-out "4.0.1" - uuid "3.0.0" + uuid "3.3.2" -readable-stream@^2.0.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" +rc@^1.0.0: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" -redis-commands@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b" +redis-commands@1.4.0, redis-commands@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.4.0.tgz#52f9cf99153efcce56a8f86af986bd04e988602f" + integrity sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= redis-parser@^2.4.0: version "2.6.0" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" + integrity sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs= -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" -rethinkdbdash@^2.3.29: - version "2.3.29" - resolved "https://registry.yarnpkg.com/rethinkdbdash/-/rethinkdbdash-2.3.29.tgz#252e454c89a86783301eb4171959386bdb268c0c" +redis-tag-cache@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/redis-tag-cache/-/redis-tag-cache-1.2.1.tgz#07d6e1f945d9d5c8186602129cb10573167d7534" + integrity sha512-0F+rLtoIkz4NOWGt9k3qaPcbpvr6+wcEC9aWV78newMKTRN/nIYHgvfyO1Q0ySEepiEYbqj9DhjvQ4CUAtT9/g== + dependencies: + ioredis "^4.0.0" + +rethinkdb-inspector@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/rethinkdb-inspector/-/rethinkdb-inspector-0.3.3.tgz#f0d88c66d17e0234b5518ca51cd8c272cb787003" + integrity sha512-1R0S5maattWOptfkHsU5ulXnt6FIKqjgyEdd8WgW9QbYMiHaXNqkHTAsmtXXSAf0j5iFH3F2qq/7eg4xwb8Euw== + +rethinkdbdash@^2.3.31: + version "2.3.31" + resolved "https://registry.yarnpkg.com/rethinkdbdash/-/rethinkdbdash-2.3.31.tgz#fe2f73d1fa6e6f5d96d8e881292013cf6dca914d" + integrity sha512-6nXrKFjdg2Ug0YpdmPWSvyD/2EisHnFNt4FWZ74dcXGK48ievSv+cNFTmVv+KjLi6I9CCf6w4CKZ6yCYTfMfdQ== dependencies: bluebird ">= 3.0.1" -safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +rethinkhaberdashery@^2.3.32: + version "2.3.32" + resolved "https://registry.yarnpkg.com/rethinkhaberdashery/-/rethinkhaberdashery-2.3.32.tgz#ddcc4ba1342e653a9d3bb6982b526087e93d537e" + integrity sha512-scDswDEu7R47WqomjScq46LkCdhaFhpaGQDP3P44GDF32iCFgYbsL4fnJdaieE115qLeinRhEjna08XWCtV0iQ== + dependencies: + bluebird ">= 3.0.1" + +safe-buffer@^5.0.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== + +sanitize-filename@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.1.tgz#612da1c96473fa02dccda92dcd5b4ab164a6772a" + integrity sha1-YS2hyWRz+gLczaktzVtKsWSmdyo= + dependencies: + truncate-utf8-bytes "^1.0.0" + +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o= + +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== semver@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= -source-map-support@^0.4.15: - version "0.4.15" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" +source-map-support@^0.4.18: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== dependencies: source-map "^0.5.6" source-map@^0.5.6: - version "0.5.6" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -stack-trace@0.0.9: - version "0.0.9" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695" +stack-trace@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= -string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" - dependencies: - safe-buffer "~5.1.0" +standard-as-callback@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-1.0.1.tgz#2e9e1e9d278d7d77580253faaec42269015e3c1d" + integrity sha512-izxEITSyc7S+5oOiF/URiYaNkemPUxIndCNv66jJ548Y1TVxhBvioNMSPrZIQdaZDlhnguOdUzHA/7hJ3xFhuQ== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= timed-out@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= -ua-parser-js@^0.7.9: - version "0.7.14" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.14.tgz#110d53fa4c3f326c121292bbeac904d2e03387ca" +toobusy-js@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/toobusy-js/-/toobusy-js-0.5.1.tgz#5511f78f6a87a6a512d44fdb0efa13672217f659" + integrity sha1-VRH3j2qHpqUS1E/bDvoTZyIX9lk= + +truncate-utf8-bytes@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" + integrity sha1-QFkjkJWS1W94pYGENLC3hInKXys= + dependencies: + utf8-byte-length "^1.0.1" + +ua-parser-js@^0.7.18: + version "0.7.19" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b" + integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ== + +url@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ= + dependencies: + punycode "1.3.2" + querystring "0.2.0" urlsafe-base64@^1.0.0, urlsafe-base64@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz#23f89069a6c62f46cf3a1d3b00169cefb90be0c6" + integrity sha1-I/iQaabGL0bPOh07ABac77kL4MY= -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - -uuid@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728" - -uuid@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" +utf8-byte-length@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" + integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E= -uuid@^3.1.0: +uuid@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" - -validator@^9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-9.2.0.tgz#ad216eed5f37cac31a6fe00ceab1f6b88bded03e" - -web-push@^3.2.2: - version "3.2.5" - resolved "https://registry.yarnpkg.com/web-push/-/web-push-3.2.5.tgz#7eccf17514f25587105c937be37bf4153b39a271" - dependencies: - asn1.js "^4.8.1" - http_ece "^0.5.2" + integrity sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g== + +uuid@3.3.2, uuid@^3.1.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + +validator@^9.4.1: + version "9.4.1" + resolved "https://registry.yarnpkg.com/validator/-/validator-9.4.1.tgz#abf466d398b561cd243050112c6ff1de6cc12663" + integrity sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA== + +web-push@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-push/-/web-push-3.3.3.tgz#8dc7c578dd1243ceb5a8377389424e87ea9b15cc" + integrity sha512-Om4CNZpyzHP3AtGZpbBavCO7I9oCS9CFY2VDfTj/cFx2gm+mAtyK2OlKd6qu9pwCdZTyYanUiyhT0JSrs0ypHQ== + dependencies: + asn1.js "^5.0.0" + http_ece "1.0.5" + https-proxy-agent "^2.2.1" jws "^3.1.3" minimist "^1.2.0" urlsafe-base64 "^1.0.0" whatwg-fetch@>=0.10.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" + integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== + +xml2js@0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= diff --git a/built-email-templates/adminActiveCommunityReport.html b/built-email-templates/adminActiveCommunityReport.html new file mode 100644 index 0000000000..791e552ba9 --- /dev/null +++ b/built-email-templates/adminActiveCommunityReport.html @@ -0,0 +1,69 @@ + + + + + + + + {{{subject}}} + + + +
+ {{preheader}} +
+ + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ +

Daily active communities:

+
    +
  • Total ({{data.dacCount}})
  • +
  • New:

    {{data.newDac}}

    +
  • +
  • Lost:

    {{data.lostDac}}

    +
  • +
+ +

Weekly active communities:

+
    +
  • Total ({{data.wacCount}})
  • +
  • New:

    {{data.newWac}}

    +
  • +
  • Lost:

    {{data.lostWac}}

    +
  • +
+ +

Monthly active communities:

+
    +
  • Total ({{data.macCount}})
  • +
  • New:

    {{data.newMac}}

    +
  • +
  • Lost:

    {{data.lostMac}}

    +
  • +
+ + + diff --git a/built-email-templates/adminCommunityCreated.html b/built-email-templates/adminCommunityCreated.html new file mode 100644 index 0000000000..9805f12dc7 --- /dev/null +++ b/built-email-templates/adminCommunityCreated.html @@ -0,0 +1,59 @@ + + + + + + + + {{{subject}}} + + + +
+ New community {{community.name}} created by {{user.name}} +
+ + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ +

New community:

+ + +

Creator:

+ + + + diff --git a/built-email-templates/adminSlackImportProcessed.html b/built-email-templates/adminSlackImportProcessed.html new file mode 100644 index 0000000000..fd74546ccb --- /dev/null +++ b/built-email-templates/adminSlackImportProcessed.html @@ -0,0 +1,59 @@ + + + + + + + + {{{subject}}} + + + +
+ {{preheader}} +
+ + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ +

New slack import:

+
    +
  • Community: {{data.community.name}} +
  • +
  • {{data.invitedCount}} members invited
  • +
  • Slack team name: {{data.teamName}}
  • +
+ +

Community owner:

+ + + + diff --git a/built-email-templates/adminToxicContent.html b/built-email-templates/adminToxicContent.html new file mode 100644 index 0000000000..b39128268c --- /dev/null +++ b/built-email-templates/adminToxicContent.html @@ -0,0 +1,64 @@ + + + + + + + + {{{subject}}} + + + +
+ {{preheader}} +
+ + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ +

{{data.type}} text:

+

{{data.text}}

+ +

Info:

+
    + {{#data.toxicityConfidence.perspectivePercent}} +
  • +Perspective: {{.}}% confident
  • + {{/data.toxicityConfidence.perspectivePercent}} +
+ + + + + diff --git a/built-email-templates/adminUserReported.html b/built-email-templates/adminUserReported.html new file mode 100644 index 0000000000..687fd9b409 --- /dev/null +++ b/built-email-templates/adminUserReported.html @@ -0,0 +1,53 @@ + + + + + + + + {{{subject}}} + + + +
+ {{preheader}} +
+ + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ +

{{reportedUser.username}} was reported by {{reportingUser.username}} for reason: "{{reason}}"

+ + + + + diff --git a/built-email-templates/adminUserSpammingThreadsNotification.html b/built-email-templates/adminUserSpammingThreadsNotification.html new file mode 100644 index 0000000000..21e2b967fa --- /dev/null +++ b/built-email-templates/adminUserSpammingThreadsNotification.html @@ -0,0 +1,67 @@ + + + + + + + + {{{subject}}} + + + +
+ {{preheader}} +
+ + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + +

User:

+

{{data.user.name}}

+ +
+
+ +

Attempting to publish:

+

{{data.publishing.content.title}}

+

{{data.publishing.content.body}}

+ +
+
+ +

Previous threads published:

+ {{#each data.threads}} +

{{content.title}}

+ {{/each}} + +
+
+ +

Publishing in

+ {{data.community.name}} / {{data.channel.name}} + + + diff --git a/built-email-templates/communityInvite.html b/built-email-templates/communityInvite.html new file mode 100644 index 0000000000..2a168b2f8c --- /dev/null +++ b/built-email-templates/communityInvite.html @@ -0,0 +1,142 @@ + + + + + + + + {{{subject}}} + + + + + +
+ {{preheader}} +
+ + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/built-email-templates/mentionInMessage.html b/built-email-templates/mentionInMessage.html new file mode 100644 index 0000000000..8d14f5edf7 --- /dev/null +++ b/built-email-templates/mentionInMessage.html @@ -0,0 +1,195 @@ + + + + + + + + {{{subject}}} + + + + + + + + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/built-email-templates/mentionInThread.html b/built-email-templates/mentionInThread.html new file mode 100644 index 0000000000..2105fa5b56 --- /dev/null +++ b/built-email-templates/mentionInThread.html @@ -0,0 +1,190 @@ + + + + + + + + {{{subject}}} + + + + + + + + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/built-email-templates/newCommunityWelcome.html b/built-email-templates/newCommunityWelcome.html new file mode 100644 index 0000000000..af87c970c6 --- /dev/null +++ b/built-email-templates/newCommunityWelcome.html @@ -0,0 +1,168 @@ + + + + + + + + {{{subject}}} + + + + + +
+ Your {{community.name}} is live on Spectrum! +
+ + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/built-email-templates/newDirectMessage.html b/built-email-templates/newDirectMessage.html new file mode 100644 index 0000000000..6e3b1f68b3 --- /dev/null +++ b/built-email-templates/newDirectMessage.html @@ -0,0 +1,149 @@ + + + + + + + + {{{ subject }}} + + + + + + + + + + + + + diff --git a/built-email-templates/newRepliesInThreads.html b/built-email-templates/newRepliesInThreads.html new file mode 100644 index 0000000000..5cc4e099bb --- /dev/null +++ b/built-email-templates/newRepliesInThreads.html @@ -0,0 +1,232 @@ + + + + + + + + {{{subject}}} + + + + + + + + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/built-email-templates/newThreadNotification.html b/built-email-templates/newThreadNotification.html new file mode 100644 index 0000000000..29ecb607f9 --- /dev/null +++ b/built-email-templates/newThreadNotification.html @@ -0,0 +1,197 @@ + + + + + + + + {{{subject}}} + + + + + + + + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/built-email-templates/newUserWelcome.html b/built-email-templates/newUserWelcome.html new file mode 100644 index 0000000000..655ab047a4 --- /dev/null +++ b/built-email-templates/newUserWelcome.html @@ -0,0 +1,129 @@ + + + + + + + + {{{subject}}} + + + + + +
+ Here's what you need to know to get started on Spectrum... +
+ + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/built-email-templates/privateChannelRequestApproved.html b/built-email-templates/privateChannelRequestApproved.html new file mode 100644 index 0000000000..22cb0da59e --- /dev/null +++ b/built-email-templates/privateChannelRequestApproved.html @@ -0,0 +1,142 @@ + + + + + + + + {{{subject}}} + + + + + + + + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/built-email-templates/privateChannelRequestSent.html b/built-email-templates/privateChannelRequestSent.html new file mode 100644 index 0000000000..a1decd6e45 --- /dev/null +++ b/built-email-templates/privateChannelRequestSent.html @@ -0,0 +1,154 @@ + + + + + + + + {{{subject}}} + + + + + + + + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/built-email-templates/privateCommunityRequestApproved.html b/built-email-templates/privateCommunityRequestApproved.html new file mode 100644 index 0000000000..e0605e94a7 --- /dev/null +++ b/built-email-templates/privateCommunityRequestApproved.html @@ -0,0 +1,141 @@ + + + + + + + + {{ subject }} + + + + + + + + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/built-email-templates/privateCommunityRequestSent.html b/built-email-templates/privateCommunityRequestSent.html new file mode 100644 index 0000000000..d51e85287d --- /dev/null +++ b/built-email-templates/privateCommunityRequestSent.html @@ -0,0 +1,148 @@ + + + + + + + + {{ subject }} + + + + + + + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/built-email-templates/validate-community-administrator-email.html b/built-email-templates/validate-community-administrator-email.html new file mode 100644 index 0000000000..61634cd929 --- /dev/null +++ b/built-email-templates/validate-community-administrator-email.html @@ -0,0 +1,121 @@ + + + + + + + + {{ subject }} + + + + +
+ {{preheader}} +
+ + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/built-email-templates/validate-email.html b/built-email-templates/validate-email.html new file mode 100644 index 0000000000..96879a5bca --- /dev/null +++ b/built-email-templates/validate-email.html @@ -0,0 +1,123 @@ + + + + + + + + {{ subject }} + + + + + +
+ Confirm your email address in one click to start getting updates +
+ + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/built-email-templates/weeklyDigest.html b/built-email-templates/weeklyDigest.html new file mode 100644 index 0000000000..6a6d2a7b2e --- /dev/null +++ b/built-email-templates/weeklyDigest.html @@ -0,0 +1,225 @@ + + + + + + + + {{ subject }} + + + + + + + + +
+  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ + + + + + + + + diff --git a/cacert b/cacert deleted file mode 100644 index 32966813ce..0000000000 --- a/cacert +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDbzCCAlegAwIBAgIEWRMkLjANBgkqhkiG9w0BAQ0FADA5MTcwNQYDVQQDDC5T -cGFjZSBQcm9ncmFtLTc5Njk3YmRkYTQxNTg3YjFmMzk4MzYxNDhlNmJjMTZhMB4X -DTE3MDUxMDE0MzExMFoXDTM3MDUxMDE0MDAwMFowOTE3MDUGA1UEAwwuU3BhY2Ug -UHJvZ3JhbS03OTY5N2JkZGE0MTU4N2IxZjM5ODM2MTQ4ZTZiYzE2YTCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBALwgqk6SZZah3eVlCvZ8sFHDaHPWekVt -1k3XAUkV+SrxijNGWNPnzkumXEd+qWYS9gYL9ak1otEjbxPR9B7+zBiPOFbwX1fE -5o97W0gxjwS8iJGL3brSmSuJAfqx3be3l2Da4tpdgmQgKVID3c7E4AVFdgh0snh5 -NAChbx/BZXtCyJNk8gRR0G9tX01EtAumoRe3PkHs6CN0ObUNX7W9l1G6J5N00ECU -ZBEcXIyQ/lNzpJrIzcBrZ75mocyCVkp5HINjs0mG+CrSgVzY5KMtWOPFlr1KuH9P -DXwYBDAKI3sKxj3Bgmwq1WtFbhTfuZkynxSZ0rgnr+aVFcszL2ZRVDMCAwEAAaN/ -MH0wHQYDVR0OBBYEFIXsudbQwxml7S2NjYaFCcTs0meUMA4GA1UdDwEB/wQEAwIC -BDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TBAUwAwEB/zAf -BgNVHSMEGDAWgBSF7LnW0MMZpe0tjY2GhQnE7NJnlDANBgkqhkiG9w0BAQ0FAAOC -AQEAfjae2H+mdzABC9Kkh/tLUPKtGu1c3/3QSq4RTPyOAsCgmtWO2NSUcEI928eo -8EJvljx8Xo2vl3DbD9OmbWzPeUqQMm2Wsq98RB80KRvQAFSwOKMDqyv0+C/UGnnw -ry3UMfTuY5Y2rRwsY4Z6FDPWnLJJKIa6aKutYo1pzkkvphtwq8lPQO2NW4uTrpjG -uhhH2cmtBUZRvRGIey29Z0TXufUNN6EAcbo0JxEuux6HotXbwI0wRwPmqHLNbSWw -gCd/pne0Pjryvw6XHzd4CQUsElWafmOf/+N760O1RCC/XCzOnsjUSLiAt9R7C/Ao -iS3oQCP1KMbyfpulMFctZJR0kQ== ------END CERTIFICATE----- diff --git a/chronos/index.js b/chronos/index.js index 66877f6a4d..70dfe8a26b 100644 --- a/chronos/index.js +++ b/chronos/index.js @@ -1,66 +1,76 @@ // @flow const debug = require('debug')('chronos'); +import Raven from 'shared/raven'; import createWorker from 'shared/bull/create-worker'; -import processDataForDigest from './queues/digests'; -import processSingleDigestEmail from './queues/digests/processDigestEmail'; -import processDailyCoreMetrics from './queues/coreMetrics'; -import processActiveCommunityAdminReport from './queues/coreMetrics/activeCommunityAdminReport'; +import processDailyDigest from 'chronos/queues/digests/dailyDigest'; +import processWeeklyDigest from 'chronos/queues/digests/weeklyDigest'; +import processSingleDigestEmail from 'chronos/queues/digests/processIndividualDigest'; +import processDailyCoreMetrics from 'chronos/queues/coreMetrics'; +import processActiveCommunityAdminReport from 'chronos/queues/coreMetrics/activeCommunityAdminReport'; +import processRemoveSeenUsersNotifications from 'chronos/queues/remove-seen-usersNotifications'; import { PROCESS_WEEKLY_DIGEST_EMAIL, PROCESS_DAILY_DIGEST_EMAIL, PROCESS_INDIVIDUAL_DIGEST, PROCESS_DAILY_CORE_METRICS, PROCESS_ACTIVE_COMMUNITY_ADMIN_REPORT, -} from './queues/constants'; -import { - weeklyDigest, - dailyDigest, - dailyCoreMetrics, - activeCommunityReport, -} from './jobs'; + PROCESS_REMOVE_SEEN_USERS_NOTIFICATIONS, +} from 'chronos/queues/constants'; +import { startJobs } from 'chronos/jobs'; const PORT = parseInt(process.env.PORT, 10) || 3004; -console.log('\n✉️ Chronos, the cron job worker, is starting...'); -debug('Logging with debug enabled!'); -console.log(''); +debug('\n⏱ Chronos, the cron job worker, is starting...'); +debug('Logging with debug enabled!\n'); const server = createWorker( { - [PROCESS_WEEKLY_DIGEST_EMAIL]: processDataForDigest, - [PROCESS_DAILY_DIGEST_EMAIL]: processDataForDigest, + [PROCESS_WEEKLY_DIGEST_EMAIL]: processWeeklyDigest, + [PROCESS_DAILY_DIGEST_EMAIL]: processDailyDigest, [PROCESS_INDIVIDUAL_DIGEST]: processSingleDigestEmail, [PROCESS_DAILY_CORE_METRICS]: processDailyCoreMetrics, [PROCESS_ACTIVE_COMMUNITY_ADMIN_REPORT]: processActiveCommunityAdminReport, + [PROCESS_REMOVE_SEEN_USERS_NOTIFICATIONS]: processRemoveSeenUsersNotifications, }, { settings: { lockDuration: 600000, // Key expiration time for job locks. - stalledInterval: 0, // How often check for stalled jobs (use 0 for never checking). maxStalledCount: 0, // Max amount of times a stalled job will be re-processed. }, } ); // start the jobs -weeklyDigest(); -dailyDigest(); -dailyCoreMetrics(); -activeCommunityReport(); +startJobs(); // $FlowIssue -console.log( - `🗄 Crons open for business ${ - process.env.NODE_ENV === 'production' ? 'in production' : 'locally' - }` -); +debug('🗄 Crons open for business'); // NOTE(@mxstbr): 511 is the default value, have to add that so flow // doesn't complain. Ref: https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback server.listen(PORT, 'localhost', 511, () => { - console.log( - `💉 Healthcheck server running at ${server.address().address}:${ - server.address().port - }` - ); + // prettier-ignore + debug(`💉 Healthcheck server running at ${server.address().address}:${server.address().port}`); +}); + +process.on('unhandledRejection', async err => { + console.error('Unhandled rejection', err); + try { + await new Promise(resolve => Raven.captureException(err, resolve)); + } catch (err) { + console.error('Raven error', err); + } finally { + process.exit(1); + } +}); + +process.on('uncaughtException', async err => { + console.error('Uncaught exception', err); + try { + await new Promise(resolve => Raven.captureException(err, resolve)); + } catch (err) { + console.error('Raven error', err); + } finally { + process.exit(1); + } }); diff --git a/chronos/jobs/index.js b/chronos/jobs/index.js index 4193cd6178..d6ddfe52c4 100644 --- a/chronos/jobs/index.js +++ b/chronos/jobs/index.js @@ -1,51 +1,54 @@ // @flow -import { createJob } from './utils'; +import { defaultJobOptions } from './utils'; + import { - PROCESS_WEEKLY_DIGEST_EMAIL, - PROCESS_DAILY_DIGEST_EMAIL, - PROCESS_DAILY_CORE_METRICS, - PROCESS_ACTIVE_COMMUNITY_ADMIN_REPORT, -} from '../queues/constants'; - -// weekly digest -const weeklyDigest = () => - createJob( - PROCESS_WEEKLY_DIGEST_EMAIL, - '0 6 * * 1', // run at 6am on Monday - // '* * * * *', - 'weekly' - ); + weeklyDigestQueue, + dailyDigestQueue, + dailyCoreMetricsQueue, + activeCommunityReportQueue, + removeSeenUsersNotificationsQueue, +} from 'shared/bull/queues'; -// daily digest -const dailyDigest = () => - createJob( - PROCESS_DAILY_DIGEST_EMAIL, - '0 18 * * *', // run at 6pm every day - // '* * * * *', - 'daily' - ); +/* + None of the cron-job initaliziers need data in the Job, so we pass undefined + as the data argument to the queue +*/ -// daily coreMetrics collection -const dailyCoreMetrics = () => - createJob( - PROCESS_DAILY_CORE_METRICS, - '0 0 * * *', // run at midnight every day - // '* * * * *', - 'daily' +export const weeklyDigest = () => { + // monday morning + return weeklyDigestQueue.add(undefined, defaultJobOptions('0 6 * * 1')); +}; + +export const dailyDigest = () => { + // end of day, every day + return dailyDigestQueue.add(undefined, defaultJobOptions('0 19 * * *')); +}; + +export const dailyCoreMetrics = () => { + // midnight daily + return dailyCoreMetricsQueue.add(undefined, defaultJobOptions('0 0 * * *')); +}; + +export const activeCommunityReport = () => { + // 1am daily + return activeCommunityReportQueue.add( + undefined, + defaultJobOptions('0 1 * * *') ); +}; -// active community report -const activeCommunityReport = () => - createJob( - PROCESS_ACTIVE_COMMUNITY_ADMIN_REPORT, - '0 1 * * *', // run at 1am every day - // '* * * * *', - 'daily' +export const removeSeenUsersNotifications = () => { + // 2am daily + return removeSeenUsersNotificationsQueue.add( + undefined, + defaultJobOptions('0 2 * * *') ); +}; -module.exports = { - weeklyDigest, - dailyDigest, - dailyCoreMetrics, - activeCommunityReport, +export const startJobs = () => { + weeklyDigest(); + dailyDigest(); + dailyCoreMetrics(); + activeCommunityReport(); + removeSeenUsersNotifications(); }; diff --git a/chronos/jobs/utils.js b/chronos/jobs/utils.js index 50b3269164..4b5efedc58 100644 --- a/chronos/jobs/utils.js +++ b/chronos/jobs/utils.js @@ -1,30 +1,12 @@ // @flow -const createQueue = require('shared/bull/create-queue'); +import type { JobOptions } from 'shared/bull/types'; -export const addQueue = (name: string, data: any, opts: any) => { - const worker = createQueue(name); - - return worker.add({ ...data }, { ...opts }); -}; - -export const createJob = ( - name: string, // name of the queue the cron job should trigger - pattern: string, // cron pattern - timeframe: string // an optional parameter to get passed into the addQueue function for adding variance to the timeframe of a cronjob -) => { - try { - console.log('🕑 New cron job initiated: ' + name + ' - ' + timeframe); - return addQueue( - name, - { timeframe }, - { - removeOnComplete: true, - removeOnFail: true, - attempts: 1, - repeat: { cron: pattern, tz: 'America/Los_Angeles' }, - } - ); - } catch (err) { - console.log('❌ Error processing cron job:\n' + err); - } -}; +export const defaultJobOptions = (pattern: string): JobOptions => ({ + removeOnComplete: true, + removeOnFail: true, + attempts: 1, + repeat: { + cron: pattern, + tz: 'America/Los_Angeles', + }, +}); diff --git a/chronos/models/channel.js b/chronos/models/channel.js deleted file mode 100644 index 668f39a216..0000000000 --- a/chronos/models/channel.js +++ /dev/null @@ -1,9 +0,0 @@ -// @flow -const { db } = require('./db'); - -export const getChannelById = (id: string): Promise => { - return db - .table('channels') - .get(id) - .run(); -}; diff --git a/chronos/models/community.js b/chronos/models/community.js index 76e0d8a177..a6320b345f 100644 --- a/chronos/models/community.js +++ b/chronos/models/community.js @@ -1,77 +1,33 @@ // @flow -const { db } = require('./db'); -import type { DBCommunity } from 'shared/types'; +const { db } = require('shared/db'); +import type { Timeframe } from 'chronos/types'; +import { getRangeFromTimeframe } from 'chronos/models/utils'; -export const getCommunityById = (id: string): Promise => { +// prettier-ignore +export const getCommunitiesWithMinimumMembers = (min: number = 2): Promise> => { return db .table('communities') - .get(id) + .between(min, db.maxval, { index: 'memberCount' }) + .filter(community => community.hasFields('deletedAt').not()) + .map(row => row('id')) .run(); }; -export const getCommunities = ( - ids: Array -): Promise> => { - return db - .table('communities') - .getAll(...ids) - .run(); -}; +export const getCommunitiesWithActiveThreadsInTimeframe = async ( + timeframe: Timeframe +): Promise> => { + const range = getRangeFromTimeframe(timeframe); -export const getTopCommunities = (amount: number): Array => { return db - .table('communities') - .pluck('id') - .run() - .then(communities => communities.map(community => community.id)) - .then(communityIds => { - return Promise.all( - communityIds.map(community => { - return db - .table('usersCommunities') - .getAll(community, { index: 'communityId' }) - .filter({ isMember: true }) - .count() - .run() - .then(count => { - return { - id: community, - count, - }; - }); - }) - ); + .table('threads') + .between(db.now().sub(range), db.now(), { + index: 'lastActive', + leftBound: 'open', + rightBound: 'open', }) - .then(data => { - let sortedCommunities = data - .sort((x, y) => { - return y.count - x.count; - }) - .map(community => community.id) - .slice(0, amount); - - return db - .table('communities') - .getAll(...sortedCommunities) - .filter(community => db.not(community.hasFields('deletedAt'))) - .run(); - }); -}; - -export const getCommunitiesWithMinimumMembers = ( - min: number = 2, - communityIds: Array -) => { - return db - .table('usersCommunities') - .getAll(...communityIds, { index: 'communityId' }) + .filter(thread => db.not(thread.hasFields('deletedAt'))) .group('communityId') .ungroup() - .filter(row => - row('reduction') - .count() - .gt(min) - ) .map(row => row('group')) .run(); }; diff --git a/chronos/models/coreMetrics.js b/chronos/models/coreMetrics.js index 48ef70f9e3..210007008a 100644 --- a/chronos/models/coreMetrics.js +++ b/chronos/models/coreMetrics.js @@ -1,79 +1,77 @@ // @flow -const { db } = require('./db'); -import { getCoreMetricsActiveThreads } from './thread'; -import { getCommunitiesWithMinimumMembers, getCommunities } from './community'; +const { db } = require('shared/db'); +import { intersection } from 'lodash'; +import { + getCommunitiesWithMinimumMembers, + getCommunitiesWithActiveThreadsInTimeframe, +} from './community'; +import { getCommunitiesById } from 'shared/db/queries/community'; +import type { DBCommunity, DBCoreMetric } from 'shared/types'; +import { getRangeFromTimeframe } from './utils'; +import type { Timeframe } from 'chronos/types'; -export const saveCoreMetrics = (data: Object): Promise => { +export const saveCoreMetrics = (data: DBCoreMetric): Promise => { return db .table('coreMetrics') - .insert({ - date: new Date(), - ...data, - }) - .run(); -}; - -export const parseRange = (timeframe: string) => { - switch (timeframe) { - case 'daily': { - return 60 * 60 * 24; - } - case 'weekly': { - return 60 * 60 * 24 * 7; - } - case 'monthly': { - return 60 * 60 * 24 * 30; - } - case 'quarterly': { - return 60 * 60 * 24 * 90; - } - default: { - return 60 * 60 * 24 * 30; - } - } + .insert( + { + date: new Date(), + ...data, + }, + { + returnChanges: true, + } + ) + .run() + .then(result => result.changes[0].new_val); }; -// dau, wau, mau -export const getAu = (range: string) => { - const RANGE = parseRange(range); +export const getActiveUsersInTimeframe = ( + timeframe: Timeframe +): Promise => { + const range = getRangeFromTimeframe(timeframe); return db .table('users') - .filter(db.row('lastSeen').during(db.now().sub(RANGE), db.now())) + .filter(row => + row + .hasFields('lastSeen') + .and(row('lastSeen').during(db.now().sub(range), db.now())) + ) .count() .default(0) .run(); }; -// dac, wac, mac -export const getAc = async (range: string) => { - // constants - const RANGE = parseRange(range); - const MIN_THREAD_COUNT = 1; +type ACData = { + count: number, + communities: Array, +}; - // get threads posted in the range - const threadsPostedInRange = await getCoreMetricsActiveThreads(RANGE); - // returns an array of community ids - const activeCommunitiesByThreads = threadsPostedInRange - .filter(t => t.reduction.length > MIN_THREAD_COUNT) - .map(t => t.group); +// prettier-ignore +export const getActiveCommunitiesInTimeframe = async (timeframe: Timeframe): Promise => { + const [ + activeCommunitiesByMemberCount, + activeCommunitiesByActiveThreads + ] = await Promise.all([ + getCommunitiesWithMinimumMembers(2), + getCommunitiesWithActiveThreadsInTimeframe(timeframe) + ]) - // for each active community by thread count, only return communities with at least 2 members - const activeCommunitiesByMember = await getCommunitiesWithMinimumMembers( - 2, - activeCommunitiesByThreads - ); + const intersectingIds = intersection(activeCommunitiesByActiveThreads, activeCommunitiesByMemberCount) return { - count: activeCommunitiesByMember.length, - communities: await getCommunities(activeCommunitiesByMember), + count: intersectingIds.length, + communities: await getCommunitiesById(intersectingIds), }; }; -export const getCount = (table: string, filter: mixed) => { +export const getTableRecordCount = ( + table: string, + filter: mixed +): Promise => { if (filter) { return db .table(table) - .filter(filter) .filter(row => db.not(row.hasFields('deletedAt'))) .count() .run(); @@ -86,19 +84,7 @@ export const getCount = (table: string, filter: mixed) => { .run(); }; -// cpu, tpu, mpu -export const getPu = async (table: string) => { - const userCount = await getCount('users'); - const tableCount = await db - .table(table) - .filter(row => db.not(row.hasFields('deletedAt'))) - .count() - .run(); - - return parseFloat((tableCount / userCount).toFixed(3)); -}; - -export const getLastTwoCoreMetrics = () => { +export const getLastTwoCoreMetrics = (): Promise> => { return ( db .table('coreMetrics') diff --git a/chronos/models/db.js b/chronos/models/db.js deleted file mode 100644 index 950125d657..0000000000 --- a/chronos/models/db.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Database setup is done here - */ -const IS_PROD = !process.env.FORCE_DEV && process.env.NODE_ENV === 'production'; - -const DEFAULT_CONFIG = { - db: 'spectrum', - max: 100, // Maximum number of connections, default is 1000 - buffer: 10, // Minimum number of connections open at any given moment, default is 50 - timeoutGb: 60 * 1000, // How long should an unused connection stick around, default is an hour, this is a minute -}; - -const PRODUCTION_CONFIG = { - password: process.env.AWS_RETHINKDB_PASSWORD, - host: process.env.AWS_RETHINKDB_URL, - port: process.env.AWS_RETHINKDB_PORT, -}; - -const config = IS_PROD - ? { - ...DEFAULT_CONFIG, - ...PRODUCTION_CONFIG, - } - : { - ...DEFAULT_CONFIG, - }; - -var r = require('rethinkdbdash')(config); - -module.exports = { db: r }; diff --git a/chronos/models/message.js b/chronos/models/message.js index e435e577e0..104cf0cf61 100644 --- a/chronos/models/message.js +++ b/chronos/models/message.js @@ -1,5 +1,7 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); +import { getRangeFromTimeframe } from './utils'; +import type { Timeframe } from 'chronos/types'; export const getTotalMessageCount = (threadId: string): Promise => { return db @@ -10,36 +12,16 @@ export const getTotalMessageCount = (threadId: string): Promise => { .run(); }; -export const getNewMessageCount = ( - threadId: string, - timeframe: string -): Promise => { - let range; - switch (timeframe) { - case 'daily': { - range = 60 * 60 * 24; - break; - } - case 'weekly': { - range = 60 * 60 * 24 * 7; - break; - } - default: { - range = 60 * 60 * 24 * 7; - } // default to weekly - } +// prettier-ignore +export const getNewMessageCount = (threadId: string, timeframe: Timeframe): Promise => { + const range = getRangeFromTimeframe(timeframe) return db .table('messages') - .getAll(threadId, { index: 'threadId' }) + .between([threadId, db.now().sub(range)], [threadId, db.now()], { + index: 'threadIdAndTimestamp', + }) .filter(db.row.hasFields('deletedAt').not()) - .filter( - db.row('timestamp').during( - // only count messages sent in the past week - db.now().sub(range), - db.now() - ) - ) .count() .run(); }; diff --git a/chronos/models/reputationEvent.js b/chronos/models/reputationEvent.js index 41db89970a..8a479bbb8a 100644 --- a/chronos/models/reputationEvent.js +++ b/chronos/models/reputationEvent.js @@ -1,10 +1,8 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); -export const getReputationChangeInTimeframe = ( - userId: string, - timeframe: string -): Promise => { +// prettier-ignore +export const getReputationChangeInTimeframe = (userId: string, timeframe: string): Promise => { let range; switch (timeframe) { case 'daily': { diff --git a/chronos/models/thread.js b/chronos/models/thread.js index 1c8b48b827..2e78685a7e 100644 --- a/chronos/models/thread.js +++ b/chronos/models/thread.js @@ -1,35 +1,32 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); +import type { DBThread } from 'shared/types'; +import { getRangeFromTimeframe } from 'chronos/models/utils'; +import type { Timeframe } from 'chronos/types'; -export const getActiveThreadsInTimeframe = (timeframe: string) => { - let range; - switch (timeframe) { - case 'daily': { - range = 60 * 60 * 24; - break; - } - case 'weekly': { - range = 60 * 60 * 24 * 7; - break; - } - default: { - range = 60 * 60 * 24 * 7; - } // default to weekly - } +export const getThreadsInChannelsInTimeframe = async ( + timeframe: Timeframe, + channelIds: Array +): Promise> => { + const range = getRangeFromTimeframe(timeframe); + let threads = []; - return db - .table('threads') - .filter(db.row('lastActive').during(db.now().sub(range), db.now())) - .filter(thread => db.not(thread.hasFields('deletedAt'))) - .run(); -}; + const channelPromises = channelIds.map(async channelId => { + const threadsInChannelAndTimeframe = await db + .table('threads') + .between([channelId, db.now().sub(range)], [channelId, db.now()], { + index: 'channelIdAndLastActive', + leftBound: 'open', + rightBound: 'open', + }) + .orderBy({ index: db.desc('channelIdAndLastActive') }) + .filter(thread => db.not(thread.hasFields('deletedAt'))) + .run(); + + threads = [...threads, ...threadsInChannelAndTimeframe]; + }); -export const getCoreMetricsActiveThreads = (range: number) => { - return db - .table('threads') - .filter(db.row('lastActive').during(db.now().sub(range), db.now())) - .filter(thread => db.not(thread.hasFields('deletedAt'))) - .group('communityId') - .ungroup() - .run(); + return await Promise.all([...channelPromises]).then(() => { + return threads; + }); }; diff --git a/chronos/models/usersChannels.js b/chronos/models/usersChannels.js index 2f829ba7f3..7f7f13776a 100644 --- a/chronos/models/usersChannels.js +++ b/chronos/models/usersChannels.js @@ -1,13 +1,13 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); -export const getUsersChannelsEligibleForWeeklyDigest = ( - userId: string -): Promise> => { +// prettier-ignore +export const getUsersChannelsEligibleForWeeklyDigest = (userId: string): Promise> => { return db .table('usersChannels') - .getAll(userId, { index: 'userId' }) - .filter({ isMember: true }) + .getAll([userId, 'member'], [userId, 'moderator'], [userId, 'owner'], { + index: 'userIdAndRole', + }) .map(row => row('channelId')) .run(); }; diff --git a/chronos/models/usersCommunities.js b/chronos/models/usersCommunities.js index 67db7fb227..91cd219389 100644 --- a/chronos/models/usersCommunities.js +++ b/chronos/models/usersCommunities.js @@ -1,22 +1,10 @@ // @flow -const { db } = require('./db'); -const debug = require('debug')('hermes:queue:send-weekly-digest-email'); +const { db } = require('shared/db'); -export const getUsersCommunityIds = ( - userId: string -): Promise> => { - debug(userId); - return ( - db - .table('usersCommunities') - .getAll(userId, { index: 'userId' }) - .filter({ isMember: true }) - .run() - // return an array of the userIds to be loaded by gql - .then( - communities => - debug(communities) || - communities.map(community => community.communityId) - ) - ); +// prettier-ignore +export const getUsersCommunityIds = (userId: string): Promise> => { + return db + .table('usersCommunities') + .getAll([userId, true], { index: 'userIdAndIsMember' })('communityId') + .run(); }; diff --git a/chronos/models/usersNotifications.js b/chronos/models/usersNotifications.js new file mode 100644 index 0000000000..1b66e2b2fa --- /dev/null +++ b/chronos/models/usersNotifications.js @@ -0,0 +1,22 @@ +// @flow +const { db } = require('shared/db'); +import type { DBUsersNotifications } from 'shared/types'; + +// prettier-ignore +export const getSeenUsersNotifications = (after: number, limit: number): Promise> => { + return db + .table('usersNotifications') + .skip(after) + .limit(limit) + .run() +}; + +// prettier-ignore +export const deleteUsersNotifications = (arr: Array): Promise => { + return db + .table('usersNotifications') + .getAll(...arr) + .delete() + .run() + .then(() => true); +}; diff --git a/chronos/models/usersSettings.js b/chronos/models/usersSettings.js index 3a5d362c5f..3ad533b729 100644 --- a/chronos/models/usersSettings.js +++ b/chronos/models/usersSettings.js @@ -1,38 +1,16 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); +import type { Timeframe } from 'chronos/types'; -export const getUsersForDigest = ( - timeframe: string -): Promise> => { - let range; - switch (timeframe) { - case 'daily': { - range = 'dailyDigest'; - break; - } - case 'weekly': { - range = 'weeklyDigest'; - break; - } - } +// prettier-ignore +export const getUserIdsForDigest = (timeframe: Timeframe, after: number, limit: number): Promise> => { + let range = timeframe === 'daily' ? 'dailyDigest' : 'weeklyDigest'; - return ( - db - .table('usersSettings') - .filter(row => row('notifications')('types')(range)('email').eq(true)) - .eqJoin('userId', db.table('users')) - .zip() - .pluck(['userId', 'email', 'firstName', 'name', 'username', 'lastSeen']) - // save some processing time by making sure the user has an email address - .filter(row => row('email').ne(null)) - // save some processing time by making sure the user has a username - .filter(row => row.hasFields('username').and(row('username').ne(null))) - // save some processing time by making sure the user was active in the last month - .filter( - db.row('lastSeen').during(db.now().sub(60 * 60 * 24 * 30), db.now()) - ) - .pluck(['userId', 'email', 'firstName', 'name', 'username']) - .distinct() - .run() - ); + return db + .table('usersSettings') + .getAll(true, { index: `${range}Email` }) + .skip(after) + .limit(limit) + .map(row => row('userId')) + .run() }; diff --git a/chronos/models/utils.js b/chronos/models/utils.js new file mode 100644 index 0000000000..8c29225cae --- /dev/null +++ b/chronos/models/utils.js @@ -0,0 +1,11 @@ +// @flow +import type { Timeframe } from 'chronos/types'; + +export const getRangeFromTimeframe = (timeframe: Timeframe): number => { + if (timeframe === 'daily') return 60 * 60 * 24; + if (timeframe === 'weekly') return 60 * 60 * 24 * 7; + if (timeframe === 'monthly') return 60 * 60 * 24 * 30; + if (timeframe === 'quarterly') return 60 * 60 * 24 * 90; + // default 30 + return 60 * 60 * 24 * 7; +}; diff --git a/chronos/package.json b/chronos/package.json index 069b619803..746b575125 100644 --- a/chronos/package.json +++ b/chronos/package.json @@ -3,17 +3,29 @@ "start": "NODE_ENV=production node main.js" }, "dependencies": { - "bull": "3.3.10", - "debug": "^2.6.8", - "draft-js": "^0.10.3", - "lodash": "^4.17.4", + "aws-sdk": "^2.395.0", + "bull": "^3.6.0", + "datadog-metrics": "^0.8.1", + "debug": "^4.1.1", + "decode-uri-component": "^0.2.0", + "draft-js": "^0.10.5", + "faker": "^4.1.0", + "imgix-core-js": "^1.2.0", + "ioredis": "3.2.2", + "lodash": "^4.17.11", "lodash.intersection": "^4.4.0", - "postmark": "^1.3.1", - "raven": "^2.1.1", - "rethinkdbdash": "^2.3.29", - "source-map-support": "^0.4.15" + "now-env": "^3.1.0", + "performance-now": "^2.1.0", + "raven": "^2.6.4", + "redis-tag-cache": "^1.2.1", + "rethinkdb-inspector": "^0.3.3", + "rethinkdbdash": "^2.3.31", + "rethinkhaberdashery": "^2.3.32", + "sanitize-filename": "^1.6.1", + "source-map-support": "^0.4.18", + "toobusy-js": "^0.5.1" }, "devDependencies": { - "json-stringify-pretty-compact": "^1.0.4" + "json-stringify-pretty-compact": "^1.2.0" } } diff --git a/chronos/queues/constants.js b/chronos/queues/constants.js index 231b754e2e..6e7f66249d 100644 --- a/chronos/queues/constants.js +++ b/chronos/queues/constants.js @@ -1,16 +1,5 @@ // @flow -// counts for processing -// the thread must have at least # total messages -export const MIN_TOTAL_MESSAGE_COUNT = 5; -// # of the total messages must have been sent in the past week -export const MIN_NEW_MESSAGE_COUNT = 5; -// # only show the top # threads per channel -export const MAX_THREAD_COUNT_PER_CHANNEL = 10; -// don't send the digest if the email will have less than # total threads to show -export const MIN_THREADS_REQUIRED_FOR_DIGEST = 3; -// cap the digest at # threads -export const MAX_THREAD_COUNT_PER_DIGEST = 10; // upsell communities to join if the user has joined less than # communities export const COMMUNITY_UPSELL_THRESHOLD = 5; @@ -19,6 +8,7 @@ export const COMMUNITY_UPSELL_THRESHOLD = 5; // the end weekly digest will have threads sorted by the weight of (TOTAL * WEIGHT) + (NEW * WEIGHT) export const TOTAL_MESSAGE_COUNT_WEIGHT = 0.1; export const NEW_MESSAGE_COUNT_WEIGHT = 1.5; +export const WATERCOOLER_WEIGHT_REDUCTION = 0.5; /* Example weighting: @@ -28,12 +18,11 @@ export const NEW_MESSAGE_COUNT_WEIGHT = 1.5; */ // queues -export const SEND_DIGEST_EMAIL = 'send digest email'; export const PROCESS_INDIVIDUAL_DIGEST = 'send individual digest email'; export const PROCESS_WEEKLY_DIGEST_EMAIL = 'process weekly digest email'; export const PROCESS_DAILY_DIGEST_EMAIL = 'process daily digest email'; export const PROCESS_DAILY_CORE_METRICS = 'process daily core metrics'; export const PROCESS_ACTIVE_COMMUNITY_ADMIN_REPORT = 'process active community admin report'; -export const SEND_ACTIVE_COMMUNITY_ADMIN_REPORT_EMAIL = - 'send active community admin report email'; +export const PROCESS_REMOVE_SEEN_USERS_NOTIFICATIONS = + 'process remove seen usersNotifications'; diff --git a/chronos/queues/coreMetrics/activeCommunityAdminReport.js b/chronos/queues/coreMetrics/activeCommunityAdminReport.js index ed42106dd9..0a4d9570bd 100644 --- a/chronos/queues/coreMetrics/activeCommunityAdminReport.js +++ b/chronos/queues/coreMetrics/activeCommunityAdminReport.js @@ -1,22 +1,19 @@ // @flow -const debug = require('debug')('chronos:queue:process-core-metrics'); -import { intersection, difference } from 'lodash'; -import { getLastTwoCoreMetrics } from '../../models/coreMetrics'; -import { addQueue } from '../../jobs/utils'; -import { SEND_ACTIVE_COMMUNITY_ADMIN_REPORT_EMAIL } from '../constants'; +const debug = require('debug')('chronos:queues:active-community-admin-report'); +import Raven from 'shared/raven'; +import { difference } from 'lodash'; +import { getLastTwoCoreMetrics } from 'chronos/models/coreMetrics'; +import { _adminSendActiveCommunityReportEmailQueue } from 'shared/bull/queues'; + +const processJob = async () => { + const [thisCoreMetrics, prevCoreMetrics] = await getLastTwoCoreMetrics(); -export default async () => { - debug('\nprocessing active community admin report'); - const lastTwoCoreMetrics = await getLastTwoCoreMetrics(); - const thisCoreMetrics = lastTwoCoreMetrics[0]; - const prevCoreMetrics = lastTwoCoreMetrics[1]; - // don't do this for the first job if (!prevCoreMetrics || !prevCoreMetrics.dacSlugs) return; - // we want to figure out what daily, weekly, and monthly active communities - // were added or were lost during the last 24 hours. To do this, we will compare - // two arrays for each time range const { + dac: thisDacCount, + wac: thisWacCount, + mac: thisMacCount, dacSlugs: thisDacSlugs, wacSlugs: thisWacSlugs, macSlugs: thisMacSlugs, @@ -28,44 +25,25 @@ export default async () => { macSlugs: prevMacSlugs, } = prevCoreMetrics; - // values that both arrays contain - const overlappingDac = intersection(thisDacSlugs, prevDacSlugs); - const overlappingWac = intersection(thisWacSlugs, prevWacSlugs); - const overlappingMac = intersection(thisMacSlugs, prevMacSlugs); - - // values that exist in the 1st record but not in the 2nd - const newDac = difference(thisDacSlugs, prevDacSlugs); - const newWac = difference(thisWacSlugs, prevWacSlugs); - const newMac = difference(thisMacSlugs, prevMacSlugs); - - // values that exist in the 2nd record but not in the 1st record - const lostDac = difference(prevDacSlugs, thisDacSlugs); - const lostWac = difference(prevWacSlugs, thisWacSlugs); - const lostMac = difference(prevMacSlugs, thisMacSlugs); + _adminSendActiveCommunityReportEmailQueue.add({ + dacCount: thisDacCount, + wacCount: thisWacCount, + macCount: thisMacCount, + newDac: difference(prevDacSlugs, thisDacSlugs), + newWac: difference(prevWacSlugs, thisWacSlugs), + newMac: difference(prevMacSlugs, thisMacSlugs), + lostDac: difference(thisDacSlugs, prevDacSlugs), + lostWac: difference(thisWacSlugs, prevWacSlugs), + lostMac: difference(thisMacSlugs, prevMacSlugs), + }); +}; +export default async () => { try { - addQueue( - SEND_ACTIVE_COMMUNITY_ADMIN_REPORT_EMAIL, - { - allDac: thisDacSlugs, - allWac: thisWacSlugs, - allMac: thisMacSlugs, - overlappingDac, - overlappingWac, - overlappingMac, - newDac, - newWac, - newMac, - lostDac, - lostWac, - lostMac, - }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); + await processJob(); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); } }; diff --git a/chronos/queues/coreMetrics/index.js b/chronos/queues/coreMetrics/index.js index 8659622c82..abd5e5d261 100644 --- a/chronos/queues/coreMetrics/index.js +++ b/chronos/queues/coreMetrics/index.js @@ -1,84 +1,54 @@ // @flow -const debug = require('debug')('chronos:queue:process-core-metrics'); +const debug = require('debug')('chronos:queue:save-core-metrics'); +import Raven from 'shared/raven'; import { saveCoreMetrics, - getAu, - getAc, - getPu, - getCount, -} from '../../models/coreMetrics'; + getActiveUsersInTimeframe, + getActiveCommunitiesInTimeframe, + getTableRecordCount, +} from 'chronos/models/coreMetrics'; -/* - 1. Daily active users - 2. Weekly active users - 3. Monthly active users - 4. Daily active communities - 5. Weekly active communities - 6. Monthly active communities - 7. Communities per user - 8. Messages per user - 9. Threads per user - 10. Total users - 11. Total communities - 12. Total threads - 13. Total DM threads - 14. Total thread messages - 15. Total DM messages -*/ +const processJob = async () => { + debug('Processing daily core metrics'); -export default async () => { - debug('\nprocessing daily core metrics'); - - // 1; - const dau = await getAu('daily'); - - // 2 - const wau = await getAu('weekly'); - - // 3 - const mau = await getAu('monthly'); + const [ + dau, + wau, + mau, + { count: dac, communities: dacData }, + { count: wac, communities: wacData }, + { count: mac, communities: macData }, + usersCommunitiesCount, + messagesCount, + threadsCount, + users, + communities, + threads, + dmThreads, + ] = await Promise.all([ + getActiveUsersInTimeframe('daily'), + getActiveUsersInTimeframe('weekly'), + getActiveUsersInTimeframe('monthly'), + getActiveCommunitiesInTimeframe('daily'), + getActiveCommunitiesInTimeframe('weekly'), + getActiveCommunitiesInTimeframe('monthly'), + getTableRecordCount('usersCommunities'), + getTableRecordCount('messages'), + getTableRecordCount('threads'), + getTableRecordCount('users'), + getTableRecordCount('communities'), + getTableRecordCount('threads'), + getTableRecordCount('directMessageThreads'), + ]); + + const cpu = parseFloat((usersCommunitiesCount / users).toFixed(3)); + const mpu = parseFloat((messagesCount / users).toFixed(3)); + const tpu = parseFloat((threadsCount / users).toFixed(3)); - // 4 - const { count: dac, communities: dacData } = await getAc('daily'); const dacSlugs = dacData.map(c => c.slug); - - // 5 - const { count: wac, communities: wacData } = await getAc('weekly'); const wacSlugs = wacData.map(c => c.slug); - - // 6 - const { count: mac, communities: macData } = await getAc('monthly'); const macSlugs = macData.map(c => c.slug); - // 7 - const cpu = await getPu('usersCommunities'); - - // 8 - const mpu = await getPu('messages'); - - // 9 - const tpu = await getPu('threads'); - - // 10 - const users = await getCount('users'); - - // 11 - const communities = await getCount('communities'); - - // 12 - const threads = await getCount('threads'); - - // 13 - const dmThreads = await getCount('directMessageThreads'); - - // 14 - const threadMessages = await getCount('messages', { threadType: 'story' }); - - // 15 - const dmMessages = await getCount('messages', { - threadType: 'directMessageThread', - }); - const coreMetrics = { dau, wau, @@ -96,13 +66,17 @@ export default async () => { communities, threads, dmThreads, - threadMessages, - dmMessages, }; + return saveCoreMetrics(coreMetrics); +}; + +export default async () => { try { - return saveCoreMetrics(coreMetrics); + await processJob(); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); } }; diff --git a/chronos/queues/digests/dailyDigest.js b/chronos/queues/digests/dailyDigest.js new file mode 100644 index 0000000000..45f0e7560b --- /dev/null +++ b/chronos/queues/digests/dailyDigest.js @@ -0,0 +1,15 @@ +// @flow +const debug = require('debug')('chronos:queue:daily-digest'); +import Raven from 'shared/raven'; +import processDigest from './processDigest'; + +export default () => { + try { + debug('Init daily digest'); + return processDigest('daily'); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + } +}; diff --git a/chronos/queues/digests/getReputationString.js b/chronos/queues/digests/getReputationString.js new file mode 100644 index 0000000000..3321a52ef3 --- /dev/null +++ b/chronos/queues/digests/getReputationString.js @@ -0,0 +1,50 @@ +// @flow +import { + getReputationChangeInTimeframe, + getTotalReputation, +} from '../../models/reputationEvent'; +import type { Timeframe } from 'chronos/types'; + +export const getReputationString = ({ + totalReputation, + reputationGained, + timeframe, +}: { + timeframe: Timeframe, + totalReputation: number, + reputationGained: number, +}) => { + const hasGainedReputation = reputationGained > 0; + const isFirstReputation = totalReputation === reputationGained; + const during = timeframe === 'weekly' ? 'last week' : 'yesterday'; + + let reputationString; + if (hasGainedReputation) { + reputationString = `You gained ${reputationGained} reputation ${during}.`; + } else { + reputationString = `You didn’t gain any reputation ${during}.`; + } + + if (isFirstReputation) { + reputationString += ` Reputation is an indicator of how active and constructive you are across all your communities. The more great conversations you start or join, the more reputation you will have.`; + } else { + reputationString += ` You have a total of ${totalReputation} reputation across all of your communities${ + hasGainedReputation ? ' - well done!' : '.' + }`; + } + + return reputationString; +}; + +export default async (userId: string, timeframe: Timeframe) => { + const [reputationGained, totalReputation] = await Promise.all([ + getReputationChangeInTimeframe(userId, timeframe), + getTotalReputation(userId), + ]); + + return getReputationString({ + totalReputation: totalReputation.toLocaleString().toString(), + reputationGained: reputationGained.toLocaleString().toString(), + timeframe, + }); +}; diff --git a/chronos/queues/digests/getUpsellCommunities.js b/chronos/queues/digests/getUpsellCommunities.js new file mode 100644 index 0000000000..f58f3bf980 --- /dev/null +++ b/chronos/queues/digests/getUpsellCommunities.js @@ -0,0 +1,23 @@ +// @flow +import { COMMUNITY_UPSELL_THRESHOLD } from '../constants'; +import { getUsersCommunityIds } from '../../models/usersCommunities'; +import type { DBCommunity } from 'shared/types'; +import { signCommunity } from 'shared/imgix'; + +export const getUpsellCommunities = async ( + userId: string, + topCommunities: Array +): Promise> => { + // see what communities the user is in. if they are a member of less than 3 communities, we will upsell communities to join in the digest + const usersCommunityIds = await getUsersCommunityIds(userId); + + // if the user has joined less than the threshold number of communities, take the top communities on Spectrum, remove any that the user has already joined, and slice the first 3 to send into the email template + if (usersCommunityIds.length <= COMMUNITY_UPSELL_THRESHOLD) { + return topCommunities + .filter(community => usersCommunityIds.indexOf(community.id) <= -1) + .slice(0, 3) + .map(community => signCommunity(community)); + } + + return null; +}; diff --git a/chronos/queues/digests/index.js b/chronos/queues/digests/index.js deleted file mode 100644 index fa6b94e7de..0000000000 --- a/chronos/queues/digests/index.js +++ /dev/null @@ -1,82 +0,0 @@ -// @flow -const debug = require('debug')('chronos:queue:send-digest-email'); -import { addQueue } from '../../jobs/utils'; -import { PROCESS_INDIVIDUAL_DIGEST } from '../constants'; -import { getThreadsForDigest, attachDataToThreads } from './processThreads'; -import { getUsersForDigest } from '../../models/usersSettings'; -import { getTopCommunities } from '../../models/community'; - -/* - 1. Process threads - Get all the threads in a given timeframe, get their message counts, and discard any threads that dont meet the minimum thresholds for new/total messages - - 2. Process users - Get all users who are eligible for a daily digest - - 3. Query once for top communities - in each downstream worker we'll decide whether or not to show upsells to join more communities to the user - - 4. For each user who wants to receive a digest, start a new job containing the user data and full threads data - NOTE: This will create a job *per* user in order to spread out the load on the database over time -*/ - -type DigestJob = { - data: { - timeframe: 'daily' | 'weekly', - }, - id: string, -}; - -export default async (job: DigestJob) => { - const { timeframe } = job.data; - debug(`\nnew job: ${job.id}`); - debug(`\nprocessing ${timeframe} digest`); - - // 1 - const threads = await getThreadsForDigest(timeframe); - if (!threads || threads.length === 0) { - debug('\n ❌ No threads found for this digest'); - return; - } - debug('\n ⚙️ Fetched threads for digest'); - - const threadsWithData = await attachDataToThreads(threads); - if (!threadsWithData || threadsWithData.length === 0) { - debug('\n ❌ No threads with data eligible for this digest'); - return; - } - debug('\n ⚙️ Processed threads with data'); - - // 2 - const users = await getUsersForDigest(timeframe); - if (!users || users.length === 0) { - debug('\n ❌ No users who want this digest'); - return; - } - debug('\n ⚙️ Fetched users who want this digest'); - - //3 - const topCommunities = await getTopCommunities(20); - debug('\n ⚙️ Fetched top communities'); - - // 4 - const usersPromises = users.map(user => { - try { - return addQueue( - PROCESS_INDIVIDUAL_DIGEST, - { user, threadsWithData, topCommunities, timeframe }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - } catch (err) { - console.log('Error adding to queue: ', err); - } - }); - - debug('\n ⚙️ Created individual jobs for each users digest'); - try { - return Promise.all(usersPromises); - } catch (err) { - console.log('Error processing digests:', err); - } -}; diff --git a/chronos/queues/digests/processDigest.js b/chronos/queues/digests/processDigest.js new file mode 100644 index 0000000000..a9cfc6ca00 --- /dev/null +++ b/chronos/queues/digests/processDigest.js @@ -0,0 +1,62 @@ +// @flow +const debug = require('debug')('chronos:queue:process-digest'); +import Raven from 'shared/raven'; +import { getUserIdsForDigest } from 'chronos/models/usersSettings'; +import type { Timeframe } from 'chronos/types'; +import { processIndividualDigestQueue } from 'shared/bull/queues'; +import { getTopCommunitiesByMemberCount } from 'shared/db/queries/community'; + +const processJob = async (timeframe: Timeframe) => { + debug(`Processing ${timeframe} digest`); + + const topCommunities = await getTopCommunitiesByMemberCount(20); + const topCommunityIds = topCommunities.map(community => community.id); + + let after = 0; + let limit = 1000; + let done = false; + + const createJobs = async (arr: Array) => { + if (!arr || arr.length === 0) return; + + return Promise.all( + arr.map(userId => { + return processIndividualDigestQueue.add({ + userId, + timeframe, + topCommunityIds, + }); + }) + ); + }; + + const processUserIds = async (arr: Array) => { + if (done) { + return createJobs(arr); + } + + if (arr.length < limit) { + done = true; + return await createJobs(arr); + } + + return createJobs(arr).then(async () => { + after = after + limit; + const nextUserIds = await getUserIdsForDigest(timeframe, after, limit); + return processUserIds(nextUserIds); + }); + }; + + const initialUserIds = await getUserIdsForDigest(timeframe, after, limit); + return processUserIds(initialUserIds); +}; + +export default async (timeframe: Timeframe) => { + try { + await processJob(timeframe); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + } +}; diff --git a/chronos/queues/digests/processDigestEmail.js b/chronos/queues/digests/processDigestEmail.js deleted file mode 100644 index 78831fcc06..0000000000 --- a/chronos/queues/digests/processDigestEmail.js +++ /dev/null @@ -1,85 +0,0 @@ -// @flow -const debug = require('debug')('chronos:queue:process-individual-digest'); -import { addQueue } from '../../jobs/utils'; -import getReputationString from './processReputation'; -import { - getUpsellCommunities, - getIntersectingChannels, - getEligibleThreadsForUser, -} from './processUsers'; -import { getUsersChannelsEligibleForWeeklyDigest } from '../../models/usersChannels'; -import type { Timeframe, User, ThreadsWithData, Communities } from './types'; -import { SEND_DIGEST_EMAIL } from '../constants'; - -type Job = { - data: { - user: User, - threadsWithData: ThreadsWithData, - topCommunities: Communities, - timeframe: Timeframe, - }, -}; -export default async (job: Job) => { - const { user, threadsWithData, topCommunities, timeframe } = job.data; - debug('\n ⚙️ Starting a new job to process an individual digest'); - - const reputationString = await getReputationString(user, timeframe); - debug('\n ⚙️ Processed reputation string'); - - // we upsell communities if the user has joined less than a certain amount - const recommendedCommunities = await getUpsellCommunities( - user, - topCommunities - ); - debug('\n ⚙️ Processed recommended communities'); - - // get all the channels where the user is a member - const usersChannels = await getUsersChannelsEligibleForWeeklyDigest( - user.userId - ); - if (!usersChannels || usersChannels.length === 0) { - debug('\n ❌ No channels where user is a member'); - return; - } - debug('\n ⚙️ Fetched users channels'); - - // overlapping channel Ids where the user is a member *and* a thread was posted to for this digest - const intersectingChannels = await getIntersectingChannels( - threadsWithData, - usersChannels - ); - if (!intersectingChannels || intersectingChannels.length === 0) { - debug('\n ❌ No intersecting channels found'); - return; - } - debug('\n ⚙️ Processed intersecting channels'); - - // get the eligible threads for this users digest based on their intersecting channels - const eligibleThreadsForUser = await getEligibleThreadsForUser( - threadsWithData, - intersectingChannels - ); - if (!eligibleThreadsForUser || eligibleThreadsForUser.length === 0) { - debug('\n ❌ No eligible threads for users digest'); - return; - } - debug('\n ⚙️ Processed eligible threads for user'); - - return addQueue( - SEND_DIGEST_EMAIL, - { - ...user, - name: user.firstName || null, - communities: recommendedCommunities, - reputationString, - timeframe, - threads: eligibleThreadsForUser, - }, - { - removeOnComplete: true, - removeOnFail: true, - } - ) - .then(() => debug('\n ✅ Sent a daily digest')) - .catch(err => console.log('Error sending an individual digest:', err)); -}; diff --git a/chronos/queues/digests/processIndividualDigest.js b/chronos/queues/digests/processIndividualDigest.js new file mode 100644 index 0000000000..ba59f2b9f1 --- /dev/null +++ b/chronos/queues/digests/processIndividualDigest.js @@ -0,0 +1,90 @@ +// @flow +const debug = require('debug')('chronos:queue:process-individual-digest'); +import Raven from 'shared/raven'; +import getReputationString from './getReputationString'; +import { getUpsellCommunities } from './getUpsellCommunities'; +import { getThreadsInChannelsInTimeframe } from 'chronos/models/thread'; +import { getUsersChannelsEligibleForWeeklyDigest } from 'chronos/models/usersChannels'; +import { sendDigestEmailQueue } from 'shared/bull/queues'; +import { getCommunitiesById } from 'shared/db/queries/community'; +import { getUserById } from 'shared/db/queries/user'; +import type { Job, ProcessIndividualDigestJobData } from 'shared/bull/types'; + +import { + attachMetadataToThreads, + attachMessageCountStringToThreads, + attachScoreToThreads, + cleanThreadData, +} from './processThreads'; + +const processJob = async (job: Job) => { + const { userId, topCommunityIds, timeframe } = job.data; + + debug(`Processing individual digest for ${userId}`); + + const topCommunities = await getCommunitiesById(topCommunityIds); + + debug('Got top communities for digest'); + + const [reputationString, communities, usersChannelsIds] = await Promise.all([ + getReputationString(userId, timeframe), + getUpsellCommunities(userId, topCommunities), + getUsersChannelsEligibleForWeeklyDigest(userId), + ]); + + if (!usersChannelsIds || usersChannelsIds.length === 0) { + debug('User is not a member of any channels'); + return; + } + + debug('Got reputation string, upsell communities, and eligible channels'); + + const threadsInTimeframe = await getThreadsInChannelsInTimeframe( + timeframe, + usersChannelsIds + ); + + if (!threadsInTimeframe || threadsInTimeframe.length === 0) { + debug('No threads available for digest'); + return; + } + + debug('Got threads in timeframe'); + + let hasOverflowThreads = false; + const threads = await attachMetadataToThreads(threadsInTimeframe, timeframe) + .then(threads => attachMessageCountStringToThreads(threads)) + .then(threads => attachScoreToThreads(threads)) + .then(threads => cleanThreadData(threads)) + .then(threads => { + hasOverflowThreads = true; + return threads.slice(0, 20); + }); + + const user = await getUserById(userId); + + debug('Attached digest data to threads'); + debug(`Sending digest email to ${user.email ? user.email : user.id}`); + + return sendDigestEmailQueue.add({ + email: user.email, + userId: user.id, + username: user.username, + user, + communities, + reputationString, + timeframe, + threads, + hasOverflowThreads, + }); +}; + +export default async (job: Job) => { + try { + await processJob(job); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + } +}; diff --git a/chronos/queues/digests/processReputation.js b/chronos/queues/digests/processReputation.js deleted file mode 100644 index 9d59cc6935..0000000000 --- a/chronos/queues/digests/processReputation.js +++ /dev/null @@ -1,33 +0,0 @@ -// @flow -const debug = require('debug')('chronos:queue:digest-process-reputation'); -import { - getReputationChangeInTimeframe, - getTotalReputation, -} from '../../models/reputationEvent'; -import type { User, Timeframe } from './types'; - -export default async (user: User, timeframe: Timeframe) => { - const reputationGained = await getReputationChangeInTimeframe( - user.userId, - timeframe - ); - const totalReputation = await getTotalReputation(user.userId); - const hasGainedReputation = reputationGained > 0; - const isFirstReputation = totalReputation === reputationGained; - const reputationString = hasGainedReputation - ? // user gained some reputation during the last timeframe range - isFirstReputation - ? // if the total reputation and reputation gained are the same amount, this means the user has gained their first bit of rep! - timeframe === 'weekly' - ? // if this is a weekly digest - `Since last week you've gained ${reputationGained} rep! Your rep will keep growing as you start and join more conversations.` - : `Since yesterday you've gained ${reputationGained} rep!` - : // if the total reputation is greater than the reputation gained, it means this is an incremental amount of rep for the user - timeframe === 'weekly' - ? `Since last week you've gained ${reputationGained} rep. You're now sitting strong at ${totalReputation.toLocaleString()} - keep it up!` - : `Since yesterday you've gained ${reputationGained} rep. You now have ${totalReputation.toLocaleString()} total rep across all of your communities - well done!` - : // has not gained any reputation - `You've been a little quiet this week – this week try joining some conversations, your community wants to hear from you!`; - - return reputationString; -}; diff --git a/chronos/queues/digests/processThreads.js b/chronos/queues/digests/processThreads.js index f4ba317e07..250dc760e9 100644 --- a/chronos/queues/digests/processThreads.js +++ b/chronos/queues/digests/processThreads.js @@ -1,104 +1,158 @@ // @flow -const debug = require('debug')('chronos:queue:digest-process-threads'); import { getTotalMessageCount, getNewMessageCount } from '../../models/message'; -import { getActiveThreadsInTimeframe } from '../../models/thread'; -import { MIN_TOTAL_MESSAGE_COUNT, MIN_NEW_MESSAGE_COUNT } from '../constants'; -import { getCommunityById } from '../../models/community'; -import { getChannelById } from '../../models/channel'; -import type { Timeframe, Thread, Threads, ThreadsWithData } from './types'; - -export const getThreadsForDigest = async (timeframe: Timeframe) => { - // returns array of thread ids - const threadIds = await getActiveThreadsInTimeframe(timeframe); - - // if no threadIds, escape - if (!threadIds || threadIds.length === 0) { - return null; - } +import { + NEW_MESSAGE_COUNT_WEIGHT, + TOTAL_MESSAGE_COUNT_WEIGHT, + WATERCOOLER_WEIGHT_REDUCTION, +} from '../constants'; +import { getCommunitiesById } from 'shared/db/queries/community'; +import { getChannelsById } from 'shared/db/queries/channel'; +import type { + Timeframe, + ThreadWithDigestData, + CleanDigestThread, +} from 'chronos/types'; +import { signCommunity } from 'shared/imgix'; +import truncate from 'shared/truncate'; - // for each thread that was active in the last week, return a new array containing a record for each thread with the thread data and the message count - const messageCountPromises = threadIds.map( - async ({ communityId, channelId, id, content, ...thread }) => ({ - communityId, - channelId, - id, - title: content.title, - newMessageCount: await getNewMessageCount(id, timeframe), - totalMessageCount: await getTotalMessageCount(id), - }) +import type { DBThread, DBCommunity, DBChannel } from 'shared/types'; + +type DBThreadWithMetadata = { + ...$Exact, + community: DBCommunity, + channel: DBChannel, + messageCountString: string, + newMessageCount: number, + totalMessageCount: number, +}; + +type DBThreadWithMessageString = { + ...$Exact, + messageCountString: string, +}; + +export const attachMetadataToThreads = async ( + threads: Array, + timeframe: Timeframe +): Promise> => { + const [communities, channels] = await Promise.all([ + getCommunitiesById(threads.map(({ communityId }) => communityId)), + getChannelsById(threads.map(({ channelId }) => channelId)), + ]); + + const signedCommunities = communities.map(community => + signCommunity(community) ); - const messageCounts = await Promise.all(messageCountPromises); + const promises = threads.map( + async (thread): Promise => { + const community = signedCommunities.find( + community => community.id === thread.communityId + ); + const channel = channels.find(channel => channel.id === thread.channelId); + const newMessageCount = await getNewMessageCount(thread.id, timeframe); + const totalMessageCount = thread.messageCount + ? thread.messageCount + : await getTotalMessageCount(thread.id); - // remove any threads where the total message count is less than 10 - const filteredTopThreads = messageCounts - .filter(thread => thread.totalMessageCount >= MIN_TOTAL_MESSAGE_COUNT) - .filter(thread => thread.newMessageCount >= MIN_NEW_MESSAGE_COUNT); + // $FlowFixMe + return { + ...thread, + community, + channel, + newMessageCount, + totalMessageCount, + }; + } + ); + + return await Promise.all([...promises]); +}; - if (!filteredTopThreads || filteredTopThreads.length === 0) { - return null; +export const getMessageCountString = ( + newMessageCount: number, + totalMessageCount: number +): string => { + if (totalMessageCount === 0) { + return `New thread`; + } else if (newMessageCount === totalMessageCount) { + if (newMessageCount === 1) { + return `1 new message`; + } else { + return `${newMessageCount} new messages`; + } + } else { + return `${totalMessageCount} messages (${newMessageCount} new)`; } +}; - // returns an array of threads that are active in the last week and have the minimum required message count to be considered valuable - return filteredTopThreads; +export const attachMessageCountStringToThreads = async ( + threads: Array +): Promise> => { + const promises = threads.map(thread => { + let messageCountString = getMessageCountString( + thread.newMessageCount, + thread.totalMessageCount + ); + + return { + ...thread, + messageCountString, + }; + }); + + return Promise.all([...promises]); +}; + +export const attachScoreToThreads = async ( + threads: Array +): Promise> => { + const promises = threads + .map(thread => { + let score = + thread.newMessageCount * NEW_MESSAGE_COUNT_WEIGHT + + thread.totalMessageCount * TOTAL_MESSAGE_COUNT_WEIGHT; + + // watercooler threads tend to have more messages than the average thread + // often in the hundreds. So even with a small weight placed on total + // message count, watercoolers are often outranking new threads. + // to solve for this, we reduce the overall score of watercooler threads + // by a constant percentage to give higher priority to new conversations. + if (thread.watercooler) { + score = score * WATERCOOLER_WEIGHT_REDUCTION; + } + + return { + ...thread, + score, + }; + }) + .sort((a, b) => b.score - a.score); + + return Promise.all([...promises]); }; -export const attachDataToThreads = async (threads: Threads) => { - // create an empty object for the final output - let obj = {}; - - const getCommunity = id => getCommunityById(id); - const getChannel = id => getChannelById(id); - - // for each thread, get the community data that we'll need when rendering an email - const topThreadsWithDataPromises = threads.map(async thread => { - const community = await getCommunity(thread.communityId); - const channel = await getChannel(thread.channelId); - - // if the thread was created in the timeframe being evaluated, it's dumb to say: 10 messages (10 new!) - so here we're composing a string that will be passed to the email that determines what we should show for the message count. If all 10 messages are new, it will simply say '10 new!' - const messageCountString = - thread.newMessageCount === thread.totalMessageCount - ? `${thread.newMessageCount} new messages` - : ` - - ${thread.totalMessageCount} messages - - (${thread.newMessageCount} new) - `; - - // this is the final data we'll send to the email for each thread - const threadWithData = { +export const cleanThreadData = async ( + threads: Array +): Promise> => { + const promises = threads.map(thread => { + return { + id: thread.id, + content: { + title: truncate(thread.content.title, 80), + }, community: { - name: community.name, - slug: community.slug, - profilePhoto: community.profilePhoto, + slug: thread.community.slug, + name: thread.community.name, + profilePhoto: thread.community.profilePhoto, }, channel: { - name: channel.name, - slug: channel.slug, + slug: thread.channel.slug, + name: thread.channel.name, }, - channelId: thread.channelId, - title: thread.title, - threadId: thread.id, - messageCountString, - newMessageCount: thread.newMessageCount, - totalMessageCount: thread.totalMessageCount, + messageCountString: thread.messageCountString, }; - return threadWithData; }); - const threadsWithCommunityData = await Promise.all( - topThreadsWithDataPromises - ); - - // for each of the active threads this week, determine if that that thread has been categorized yet into the new object. If so, push that thread into the array, otherwise create a new key/value pair in the object for the channel + thread - threadsWithCommunityData.map( - thread => - obj[thread.channelId] - ? (obj[thread.channelId] = [...obj[thread.channelId], { ...thread }]) - : (obj[thread.channelId] = [{ ...thread }]) - ); - - // return the final object containing keys for channelIds, and arrays of threads for values - return obj; + return Promise.all([...promises]); }; diff --git a/chronos/queues/digests/processUsers.js b/chronos/queues/digests/processUsers.js deleted file mode 100644 index e50293c7bd..0000000000 --- a/chronos/queues/digests/processUsers.js +++ /dev/null @@ -1,60 +0,0 @@ -// @flow -const debug = require('debug')('chronos:queue:digest-process-user'); -import intersection from 'lodash.intersection'; -import { - COMMUNITY_UPSELL_THRESHOLD, - NEW_MESSAGE_COUNT_WEIGHT, - TOTAL_MESSAGE_COUNT_WEIGHT, - MIN_THREADS_REQUIRED_FOR_DIGEST, - MAX_THREAD_COUNT_PER_DIGEST, -} from '../constants'; -import type { User, Community, ThreadsWithData } from './types'; -import { getUsersCommunityIds } from '../../models/usersCommunities'; - -export const getIntersectingChannels = ( - threadsWithData: ThreadsWithData, - channelIds: Array -) => { - // get an array of all channels where there are active threads this week - const threadChannelKeys = Object.keys(threadsWithData); - - // for each user, determine the overlapping channels where they are a member and where active threads occurred this week - return intersection(channelIds, threadChannelKeys); -}; - -export const getEligibleThreadsForUser = ( - threadsWithData: ThreadsWithData, - channelIds: Array -) => { - let eligibleThreads = []; - channelIds.map(c => eligibleThreads.push(...threadsWithData[c])); - - if (eligibleThreads.length < MIN_THREADS_REQUIRED_FOR_DIGEST) { - return null; - } - - return eligibleThreads - .map(t => ({ - ...t, - score: - t.newMessageCount * NEW_MESSAGE_COUNT_WEIGHT + - t.totalMessageCount * TOTAL_MESSAGE_COUNT_WEIGHT, - })) - .sort((a, b) => b.score - a.score) - .slice(0, MAX_THREAD_COUNT_PER_DIGEST); -}; - -export const getUpsellCommunities = async ( - user: User, - topCommunities: Array -) => { - // see what communities the user is in. if they are a member of less than 3 communities, we will upsell communities to join in the digest - const usersCommunityIds = await getUsersCommunityIds(user.userId); - - // if the user has joined less than the threshold number of communities, take the top communities on Spectrum, remove any that the user has already joined, and slice the first 3 to send into the email template - return usersCommunityIds.length <= COMMUNITY_UPSELL_THRESHOLD - ? topCommunities - .filter(community => usersCommunityIds.indexOf(community.id) <= -1) - .slice(0, 3) - : null; -}; diff --git a/chronos/queues/digests/test/__snapshots__/message-count-string.test.js.snap b/chronos/queues/digests/test/__snapshots__/message-count-string.test.js.snap new file mode 100644 index 0000000000..403e77373b --- /dev/null +++ b/chronos/queues/digests/test/__snapshots__/message-count-string.test.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`new thread should get new thread string 1`] = `"New thread"`; + +exports[`new thread should get thread with more than one new message string 1`] = `"2 new messages"`; + +exports[`new thread should get thread with new and total string 1`] = `"10 messages (2 new)"`; + +exports[`new thread should get thread with one new message string 1`] = `"1 new message"`; diff --git a/chronos/queues/digests/test/__snapshots__/reputation-string.test.js.snap b/chronos/queues/digests/test/__snapshots__/reputation-string.test.js.snap new file mode 100644 index 0000000000..26e45c5f6f --- /dev/null +++ b/chronos/queues/digests/test/__snapshots__/reputation-string.test.js.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`existing users should handle no reputation gain 1`] = `"You didn’t gain any reputation last week. You have a total of 10 reputation across all of your communities."`; + +exports[`existing users should handle reputation gain 1`] = `"You gained 10 reputation last week. You have a total of 20 reputation across all of your communities - well done!"`; + +exports[`first-time users should be told when they haven't gained any reputation 1`] = `"You didn’t gain any reputation last week. Reputation is an indicator of how active and constructive you are across all your communities. The more great conversations you start or join, the more reputation you will have."`; + +exports[`first-time users should get an explanation what reputation is 1`] = `"You gained 10 reputation last week. Reputation is an indicator of how active and constructive you are across all your communities. The more great conversations you start or join, the more reputation you will have."`; + +exports[`should handle a daily timeframe 1`] = `"You gained 10 reputation yesterday. You have a total of 20 reputation across all of your communities - well done!"`; + +exports[`should handle a daily timeframe 2`] = `"You didn’t gain any reputation yesterday. Reputation is an indicator of how active and constructive you are across all your communities. The more great conversations you start or join, the more reputation you will have."`; diff --git a/chronos/queues/digests/test/message-count-string.test.js b/chronos/queues/digests/test/message-count-string.test.js new file mode 100644 index 0000000000..ed11fb10f1 --- /dev/null +++ b/chronos/queues/digests/test/message-count-string.test.js @@ -0,0 +1,20 @@ +// @flow +import { getMessageCountString } from '../processThreads'; + +describe('new thread', () => { + it('should get new thread string', () => { + expect(getMessageCountString(0, 0)).toMatchSnapshot(); + }); + + it('should get thread with one new message string', () => { + expect(getMessageCountString(1, 1)).toMatchSnapshot(); + }); + + it('should get thread with more than one new message string', () => { + expect(getMessageCountString(2, 2)).toMatchSnapshot(); + }); + + it('should get thread with new and total string', () => { + expect(getMessageCountString(2, 10)).toMatchSnapshot(); + }); +}); diff --git a/chronos/queues/digests/test/reputation-string.test.js b/chronos/queues/digests/test/reputation-string.test.js new file mode 100644 index 0000000000..df53941319 --- /dev/null +++ b/chronos/queues/digests/test/reputation-string.test.js @@ -0,0 +1,64 @@ +// @flow +import { getReputationString } from '../getReputationString'; + +describe('first-time users', () => { + it('should get an explanation what reputation is', () => { + expect( + getReputationString({ + timeframe: 'weekly', + reputationGained: 10, + totalReputation: 10, + }) + ).toMatchSnapshot(); + }); + + it("should be told when they haven't gained any reputation", () => { + expect( + getReputationString({ + timeframe: 'weekly', + reputationGained: 0, + totalReputation: 0, + }) + ).toMatchSnapshot(); + }); +}); + +describe('existing users', () => { + it('should handle no reputation gain', () => { + expect( + getReputationString({ + timeframe: 'weekly', + reputationGained: 0, + totalReputation: 10, + }) + ).toMatchSnapshot(); + }); + + it('should handle reputation gain', () => { + expect( + getReputationString({ + timeframe: 'weekly', + reputationGained: 10, + totalReputation: 20, + }) + ).toMatchSnapshot(); + }); +}); + +it('should handle a daily timeframe', () => { + expect( + getReputationString({ + timeframe: 'daily', + reputationGained: 10, + totalReputation: 20, + }) + ).toMatchSnapshot(); + + expect( + getReputationString({ + timeframe: 'daily', + reputationGained: 0, + totalReputation: 0, + }) + ).toMatchSnapshot(); +}); diff --git a/chronos/queues/digests/types.js b/chronos/queues/digests/types.js deleted file mode 100644 index 1f55d2f466..0000000000 --- a/chronos/queues/digests/types.js +++ /dev/null @@ -1,60 +0,0 @@ -// @flow - -export type Timeframe = 'daily' | 'weekly'; - -export type User = { - userId: string, - email: string, - firstName: ?string, - username: string, -}; -export type Users = Array; - -export type UserWithData = { - email: string, - username: string, - name: ?string, - userId: string, - channels: Array, -}; -export type UsersWithData = Array; - -export type Thread = { - channelId: string, - communityId: string, - totalMessageCount: number, - newMessageCount: number, - title: string, - id: string, -}; -export type Threads = Array; - -export type ThreadWithData = { - channelId: string, - communityId: string, - totalMessageCount: number, - newMessageCount: number, - title: string, - id: string, - threadId: string, - messageCountString: string, - score: number, - community: Community, - channel: Channel, -}; -export type ThreadsWithData = { - [key: string]: Array, -}; - -export type Community = { - name: string, - slug: string, - profilePhoto: string, - id: string, -}; -export type Communities = Array; - -export type Channel = { - name: string, - slug: string, -}; diff --git a/chronos/queues/digests/weeklyDigest.js b/chronos/queues/digests/weeklyDigest.js new file mode 100644 index 0000000000..de856a2c7f --- /dev/null +++ b/chronos/queues/digests/weeklyDigest.js @@ -0,0 +1,15 @@ +// @flow +const debug = require('debug')('chronos:queue:weekly-digest'); +import Raven from 'shared/raven'; +import processDigest from './processDigest'; + +export default () => { + try { + debug('Init weekly digest'); + return processDigest('weekly'); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + } +}; diff --git a/chronos/queues/remove-seen-usersNotifications.js b/chronos/queues/remove-seen-usersNotifications.js new file mode 100644 index 0000000000..73705031cc --- /dev/null +++ b/chronos/queues/remove-seen-usersNotifications.js @@ -0,0 +1,70 @@ +// @flow +const debug = require('debug')('chronos:queue:remove-seen-usersNotifications'); +import Raven from 'shared/raven'; +import type { DBUsersNotifications } from 'shared/types'; +import { + getSeenUsersNotifications, + deleteUsersNotifications, +} from 'chronos/models/usersNotifications'; + +const processJob = async () => { + let after = 0; + let limit = 10000; + let done = false; + + const processDeleteRecords = async (arr: Array) => { + if (!arr || arr.length === 0) return; + + debug('Process delete records'); + + const filteredIds = arr + .filter(rec => rec.isSeen) + .filter(rec => { + const THIRTY_DAYS = 1000 * 60 * 60 * 24 * 30; //ms + const added = new Date(rec.entityAddedAt).getTime(); //ms + const now = new Date().getTime(); //ms + return now - added > THIRTY_DAYS; + }) + .map(rec => rec.id) + .filter(Boolean); + + debug(`Removing ${filteredIds.length} usersNotifications`); + + return await deleteUsersNotifications(filteredIds); + }; + + const processUsersNotificiations = async ( + arr: Array + ) => { + if (done) { + return await processDeleteRecords(arr); + } + + if (arr.length < limit) { + done = true; + return await processDeleteRecords(arr); + } + + return processDeleteRecords(arr).then(async () => { + after = after + limit; + const nextRecords = await getSeenUsersNotifications(after, limit); + debug('Next loop'); + return processUsersNotificiations(nextRecords); + }); + }; + + debug('Initing job'); + + const initialRecords = await getSeenUsersNotifications(after, limit); + return processUsersNotificiations(initialRecords); +}; + +export default async () => { + try { + await processJob(); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); + } +}; diff --git a/chronos/types.js b/chronos/types.js new file mode 100644 index 0000000000..60aeef3388 --- /dev/null +++ b/chronos/types.js @@ -0,0 +1,31 @@ +// @flow +import type { DBCommunity, DBChannel, DBThread } from 'shared/types'; + +export type Timeframe = 'daily' | 'weekly' | 'monthly' | 'quarterly'; + +export type ThreadWithDigestData = { + ...$Exact, + community: DBCommunity, + channel: DBChannel, + messageCountString: string, + newMessageCount: number, + totalMessageCount: number, + score: number, +}; + +export type CleanDigestThread = { + id: string, + content: { + title: string, + }, + community: { + slug: string, + name: string, + profilePhoto: string, + }, + channel: { + slug: string, + name: string, + }, + messageCountString: string, +}; diff --git a/chronos/yarn.lock b/chronos/yarn.lock index ee3931fe9e..30e48492c9 100644 --- a/chronos/yarn.lock +++ b/chronos/yarn.lock @@ -5,145 +5,188 @@ asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +aws-sdk@^2.395.0: + version "2.395.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.395.0.tgz#637e5fa06d69bfb923b17bde24a8bd2a74dedab3" + integrity sha512-ldTTjctniZT4E2lq2z3D8Y2u+vpkp+laoEnDkXgjKXTKbiJ0QEtfWsUdx/IQ7awCt8stoxyqZK47DJOxIbRNoA== + dependencies: + buffer "4.9.1" + events "1.1.1" + ieee754 "1.1.8" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + +base64-js@^1.0.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" + integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== -"bluebird@>= 3.0.1", bluebird@^3.3.4, bluebird@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" +bignumber.js@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-1.1.1.tgz#1a415d9ac014c13256af1feed9d1a3e5717a8cf7" + integrity sha1-GkFdmsAUwTJWrx/u2dGj5XF6jPc= -boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" +"bluebird@>= 3.0.1", bluebird@^3.3.4: + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + +buffer@4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" -bull@3.3.10: - version "3.3.10" - resolved "https://registry.yarnpkg.com/bull/-/bull-3.3.10.tgz#32e76281902070b4720bc37b3793f09db635f646" +buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" + integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg== dependencies: - bluebird "^3.5.0" - cron-parser "^2.4.1" + base64-js "^1.0.2" + ieee754 "^1.1.4" + +bull@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/bull/-/bull-3.6.0.tgz#9d137a4470d9f5a0df54801ca4390656e5054a42" + integrity sha512-705Vf3weiRr8D49/+lsPSxV/1NejhjfmVviv9qG2srIYPr7IS2euLwHa+2GNfaVDA2tmx8xyJFW9bPw3fPfHPg== + dependencies: + cron-parser "^2.7.3" debuglog "^1.0.0" - ioredis "^3.1.4" - lodash "^4.17.4" - semver "^5.4.1" - uuid "^3.1.0" - -cheerio@^0.22.0: - version "0.22.0" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" - dependencies: - css-select "~1.2.0" - dom-serializer "~0.1.0" - entities "~1.1.1" - htmlparser2 "^3.9.1" - lodash.assignin "^4.0.9" - lodash.bind "^4.1.4" - lodash.defaults "^4.0.1" - lodash.filter "^4.4.0" - lodash.flatten "^4.2.0" - lodash.foreach "^4.3.0" - lodash.map "^4.4.0" - lodash.merge "^4.4.0" - lodash.pick "^4.2.1" - lodash.reduce "^4.4.0" - lodash.reject "^4.4.0" - lodash.some "^4.4.0" + ioredis "^4.5.1" + lodash "^4.17.11" + p-timeout "^2.0.1" + promise.prototype.finally "^3.1.0" + semver "^5.6.0" + util.promisify "^1.0.0" + uuid "^3.2.1" + +charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= cluster-key-slot@^1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.8.tgz#7654556085a65330932a2e8b5976f8e2d0b3e414" + version "1.0.12" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.12.tgz#d5deff2a520717bc98313979b687309b2d368e29" + integrity sha512-21O0kGmvED5OJ7ZTdqQ5lQQ+sjuez33R+d35jZKLwqUb5mqcPHUsxOSzj61+LHVtxGZd1kShbQM3MjB/gBJkVg== cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= core-js@^1.0.0: version "1.2.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" +crc@^3.5.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" + integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== + dependencies: + buffer "^5.1.0" -cron-parser@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.4.1.tgz#022befce1af293e4d3144ff04c2cbd2edb491271" +cron-parser@^2.7.3: + version "2.7.3" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.7.3.tgz#12603f89f5375af353a9357be2543d3172eac651" + integrity sha512-t9Kc7HWBWPndBzvbdQ1YG9rpPRB37Tb/tTviziUOh1qs3TARGh3b1p+tnkOHNe1K5iI3oheBPgLqwotMM7+lpg== dependencies: is-nan "^1.2.1" - moment-timezone "^0.5.0" + moment-timezone "^0.5.23" -css-select@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" +crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + +datadog-metrics@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/datadog-metrics/-/datadog-metrics-0.8.1.tgz#be87237109a7084193c668d80112533ef00e3f21" + integrity sha512-qTSKnddO6GxTJW9FYpmWjvvift3qfyMurDjwNjJnJhBk76pBdDhC0B5V9V+XwPdn4r42qu48kwXNuHJslXlDOA== dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" + debug "3.1.0" + dogapi "1.1.0" -css-what@2.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" +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@^2.2.0, debug@^2.6.8: - version "2.6.8" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" +debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debuglog@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" -define-properties@^1.1.1, define-properties@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" +debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== dependencies: - foreach "^2.0.5" - object-keys "^1.0.8" + ms "^2.1.1" -denque@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/denque/-/denque-1.2.2.tgz#e06cf7cf0da8badc88cbdaabf8fc0a70d659f1d4" +debuglog@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= -dom-serializer@0, dom-serializer@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" - dependencies: - domelementtype "~1.1.1" - entities "~1.1.1" +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -domelementtype@1, domelementtype@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -domelementtype@~1.1.1: +define-properties@^1.1.1, define-properties@^1.1.2: version "1.1.3" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" - -domhandler@^2.3.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== dependencies: - domelementtype "1" + object-keys "^1.0.12" -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - dependencies: - dom-serializer "0" - domelementtype "1" +denque@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.0.tgz#79e2f0490195502107f24d9553f374837dabc916" + integrity sha512-gh513ac7aiKrAgjiIBWZG0EASyDF9p4JMWwKA8YU5s9figrL5SRNEMT6FDynsegakuhWd1wVqTvqvqAoDxw7wQ== -domutils@^1.5.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff" +dogapi@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/dogapi/-/dogapi-1.1.0.tgz#71a43865ad4bb4cb18bc3e13cf769971f501030a" + integrity sha1-caQ4Za1LtMsYvD4Tz3aZcfUBAwo= dependencies: - dom-serializer "0" - domelementtype "1" + extend "^3.0.0" + json-bigint "^0.1.4" + minimist "^1.1.1" + rc "^1.0.0" -draft-js@^0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.3.tgz#5f3c3025af9f494c62f00a9066006ccdc1b3c943" +draft-js@^0.10.5: + version "0.10.5" + resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.5.tgz#bfa9beb018fe0533dbb08d6675c371a6b08fa742" + integrity sha512-LE6jSCV9nkPhfVX2ggcRLA4FKs6zWq9ceuO/88BpXdNCS7mjRTgs0NsV6piUCJX9YxMsB9An33wnkMmU2sD2Zg== dependencies: - enzyme "^2.9.1" fbjs "^0.8.15" immutable "~3.7.4" object-assign "^4.1.0" @@ -151,49 +194,50 @@ draft-js@^0.10.3: encoding@^0.1.11: version "0.1.12" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= dependencies: iconv-lite "~0.4.13" -entities@^1.1.1, entities@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" - -enzyme@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-2.9.1.tgz#07d5ce691241240fb817bf2c4b18d6e530240df6" - dependencies: - cheerio "^0.22.0" - function.prototype.name "^1.0.0" - is-subset "^0.1.1" - lodash "^4.17.4" - object-is "^1.0.1" - object.assign "^4.0.4" - object.entries "^1.0.4" - object.values "^1.0.4" - prop-types "^15.5.10" - uuid "^3.0.1" - -es-abstract@^1.6.1: - version "1.9.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.9.0.tgz#690829a07cae36b222e7fd9b75c0d0573eb25227" - dependencies: - es-to-primitive "^1.1.1" +es-abstract@^1.5.1, es-abstract@^1.9.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" + integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== + dependencies: + es-to-primitive "^1.2.0" function-bind "^1.1.1" - has "^1.0.1" - is-callable "^1.1.3" + has "^1.0.3" + is-callable "^1.1.4" is-regex "^1.0.4" + object-keys "^1.0.12" -es-to-primitive@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" +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== dependencies: - is-callable "^1.1.1" + is-callable "^1.1.4" is-date-object "^1.0.1" - is-symbol "^1.0.1" + is-symbol "^1.0.2" + +events@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= -fbjs@^0.8.15, fbjs@^0.8.16: - version "0.8.16" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +faker@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" + integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8= + +fbjs@^0.8.15: + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= dependencies: core-js "^1.0.0" isomorphic-fetch "^2.1.1" @@ -201,64 +245,74 @@ fbjs@^0.8.15, fbjs@^0.8.16: object-assign "^4.1.0" promise "^7.1.1" setimmediate "^1.0.5" - ua-parser-js "^0.7.9" + ua-parser-js "^0.7.18" flexbuffer@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/flexbuffer/-/flexbuffer-0.0.6.tgz#039fdf23f8823e440c38f3277e6fef1174215b30" + integrity sha1-A5/fI/iCPkQMOPMnfm/vEXQhWzA= -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - -function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: +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== + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= -function.prototype.name@^1.0.0: +has@^1.0.1, has@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.0.3.tgz#0099ae5572e9dd6f03c97d023fd92bcc5e639eac" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: - define-properties "^1.1.2" - function-bind "^1.1.0" - is-callable "^1.1.3" + function-bind "^1.1.1" -has@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" +iconv-lite@~0.4.13: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: - function-bind "^1.0.2" + safer-buffer ">= 2.1.2 < 3" -htmlparser2@^3.9.1: - version "3.9.2" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" - dependencies: - domelementtype "^1.3.0" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^2.0.2" +ieee754@1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + integrity sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q= -iconv-lite@~0.4.13: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +ieee754@^1.1.4: + version "1.1.12" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" + integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== + +imgix-core-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/imgix-core-js/-/imgix-core-js-1.2.0.tgz#72163ebd312b25cdae077340d13cf94dc09327f2" + integrity sha512-Eq8IabyhZwwP1m+E26L4AJLCznqckFuL2nvOFmtYESmyFEv4IXa/UU58DHegnmMYoPqHunmG9ttL6NDdB0ddbg== + dependencies: + crc "^3.5.0" + js-base64 "^2.1.9" + md5 "^2.2.1" immutable@~3.7.4: version "3.7.6" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" + integrity sha1-E7TTyxK++hVIKib+Gy665kAHHks= -inherits@^2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -ioredis@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-3.1.4.tgz#8688293f5f2f1757e1c812ad17cce49f46d811bc" +ioredis@3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-3.2.2.tgz#b7d5ff3afd77bb9718bb2821329b894b9a44c00b" + integrity sha512-g+ShTQYLsCcOUkNOK6CCEZbj3aRDVPw3WOwXk+LxlUKvuS9ujEqP2MppBHyRVYrNNFW/vcPaTBUZ2ctGNSiOCA== dependencies: bluebird "^3.3.4" cluster-key-slot "^1.0.6" - debug "^2.2.0" + debug "^2.6.9" denque "^1.1.0" flexbuffer "0.0.6" lodash.assign "^4.2.0" @@ -280,353 +334,516 @@ ioredis@^3.1.4: redis-commands "^1.2.0" redis-parser "^2.4.0" -is-callable@^1.1.1, is-callable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" +ioredis@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.3.0.tgz#a92850dd8794eaee4f38a265c830ca823a09d345" + integrity sha512-TwTp93UDKlKVQeg9ThuavNh4Vs31JTlqn+cI/J6z21OtfghyJm5I349ZlsKobOeEyS4INITMLQ1fhR7xwf9Fxg== + dependencies: + cluster-key-slot "^1.0.6" + debug "^3.1.0" + denque "^1.1.0" + flexbuffer "0.0.6" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + redis-commands "1.4.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^1.0.0" + +ioredis@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.5.1.tgz#b1c1c1657697caa3a617acb9370e3c0694edb775" + integrity sha512-p1BblrFZdb5Oc5EBsEb4EoycDqn7xi/NTNT4bDvo/w6B08eMNO1E7RAOOEA1GAb65+8Hbs2LgUyz3cZOTiP3xg== + dependencies: + cluster-key-slot "^1.0.6" + debug "^3.1.0" + denque "^1.1.0" + flexbuffer "0.0.6" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + redis-commands "1.4.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^1.0.0" + +is-buffer@~1.1.1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-callable@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== is-date-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= is-nan@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.2.1.tgz#9faf65b6fb6db24b7f5c0628475ea71f988401e2" + integrity sha1-n69ltvttskt/XAYoR16nH5iEAeI= dependencies: define-properties "^1.1.1" is-regex@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= dependencies: has "^1.0.1" is-stream@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-subset@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" - -is-symbol@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" +is-symbol@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" + integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + dependencies: + has-symbols "^1.0.0" -isarray@~1.0.0: +isarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= isomorphic-fetch@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= dependencies: node-fetch "^1.0.1" whatwg-fetch ">=0.10.0" -js-tokens@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" +jmespath@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= -json-stringify-pretty-compact@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.0.4.tgz#d5161131be27fd9748391360597fcca250c6c5ce" +js-base64@^2.1.9: + version "2.4.9" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.9.tgz#748911fb04f48a60c4771b375cac45a80df11c03" + integrity sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ== -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" +"js-tokens@^3.0.0 || ^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== + +json-bigint@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-0.1.4.tgz#b5d40b8a9009e92f157f7c079db097001830e01e" + integrity sha1-tdQLipAJ6S8Vf3wHnbCXABgw4B4= + dependencies: + bignumber.js "~1.1.1" + +json-stringify-pretty-compact@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.2.0.tgz#0bc316b5e6831c07041fc35612487fb4e9ab98b8" + integrity sha512-/11Pj1OyX814QMKO7K8l85SHPTr/KsFxHp8GE2zVa0BtJgGimDjXHfM3FhC7keQdWDea7+nXf+f1de7ATZcZkQ== lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= -lodash.assignin@^4.0.9: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" - -lodash.bind@^4.1.4, lodash.bind@^4.2.1: +lodash.bind@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" + integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU= lodash.clone@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" + integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= -lodash.defaults@^4.0.1, lodash.defaults@^4.2.0: +lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= lodash.difference@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= -lodash.filter@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" - -lodash.flatten@^4.2.0, lodash.flatten@^4.4.0: +lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= -lodash.foreach@^4.3.0, lodash.foreach@^4.5.0: +lodash.foreach@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= lodash.intersection@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.intersection/-/lodash.intersection-4.4.0.tgz#0a11ba631d0e95c23c7f2f4cbb9a692ed178e705" + integrity sha1-ChG6Yx0OlcI8fy9Mu5ppLtF45wU= lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= lodash.keys@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" - -lodash.map@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" - -lodash.merge@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" + integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU= lodash.noop@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" + integrity sha1-OBiPTWUKOkdCWEObluxFsyYXEzw= lodash.partial@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.partial/-/lodash.partial-4.2.1.tgz#49f3d8cfdaa3bff8b3a91d127e923245418961d4" + integrity sha1-SfPYz9qjv/izqR0SfpIyRUGJYdQ= -lodash.pick@^4.2.1, lodash.pick@^4.4.0: +lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" - -lodash.reduce@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" - -lodash.reject@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" + integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= lodash.sample@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.sample/-/lodash.sample-4.2.1.tgz#5e4291b0c753fa1abeb0aab8fb29df1b66f07f6d" + integrity sha1-XkKRsMdT+hq+sKq4+ynfG2bwf20= lodash.shuffle@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz#145b5053cf875f6f5c2a33f48b6e9948c6ec7b4b" - -lodash.some@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" + integrity sha1-FFtQU8+HX29cKjP0i26ZSMbse0s= lodash.values@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" + integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= -lodash@^4.17.4: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" +lodash@^4.17.11: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== -loose-envify@^1.0.0, loose-envify@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" +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" + js-tokens "^3.0.0 || ^4.0.0" -lsmod@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b" +md5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" -merge@1.2.0: +minimist@^1.1.1, minimist@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -moment-timezone@^0.5.0: - version "0.5.13" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.13.tgz#99ce5c7d827262eb0f1f702044177f60745d7b90" +moment-timezone@^0.5.23: + version "0.5.23" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" + integrity sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w== dependencies: moment ">= 2.9.0" "moment@>= 2.9.0": - version "2.18.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" + version "2.23.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.23.0.tgz#759ea491ac97d54bac5ad776996e2a58cc1bc225" + integrity sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA== 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.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== dependencies: encoding "^0.1.11" is-stream "^1.0.1" -nth-check@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" - dependencies: - boolbase "~1.0.0" +now-env@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/now-env/-/now-env-3.1.0.tgz#e0198b67279d387229cfd4b25de4c2fc7156943f" + integrity sha512-f+jXC+UkoxD/g9Nlig99Bxswoh7UUuQxw0EsPfuueHnVpVE0LfgQ4el5dxY4TSXwrL9mEF9GGm0gb7r3K8r/ug== -object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-is@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" - -object-keys@^1.0.10, object-keys@^1.0.8: - version "1.0.11" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" +object-keys@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" + integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== -object.assign@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc" +object.getownpropertydescriptors@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" + integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= dependencies: define-properties "^1.1.2" - function-bind "^1.1.0" - object-keys "^1.0.10" + es-abstract "^1.5.1" -object.entries@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f" - dependencies: - define-properties "^1.1.2" - es-abstract "^1.6.1" - function-bind "^1.1.0" - has "^1.0.1" +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -object.values@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" +p-timeout@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" + integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== dependencies: - define-properties "^1.1.2" - es-abstract "^1.6.1" - function-bind "^1.1.0" - has "^1.0.1" + p-finally "^1.0.0" -postmark@^1.3.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/postmark/-/postmark-1.4.1.tgz#68240baddc4bce37c68b23478bffbf114bd89f04" - dependencies: - merge "1.2.0" +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= -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" +promise.prototype.finally@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.0.tgz#66f161b1643636e50e7cf201dc1b84a857f3864e" + integrity sha512-7p/K2f6dI+dM8yjRQEGrTQs5hTQixUAdOGpMEA3+pVxpX5oHKRSKAXyLw9Q9HUWDTdwtoo39dSHGQtN90HcEwQ== + dependencies: + define-properties "^1.1.2" + es-abstract "^1.9.0" + function-bind "^1.1.1" promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== dependencies: asap "~2.0.3" -prop-types@^15.5.10: - version "15.6.0" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.3.1" - object-assign "^4.1.1" +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -raven@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/raven/-/raven-2.1.2.tgz#4aa7a72c4b3061d7fde06bfc62d669a74a651e27" +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +raven@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/raven/-/raven-2.6.4.tgz#458d4a380c8fbb59e0150c655625aaf60c167ea3" + integrity sha512-6PQdfC4+DQSFncowthLf+B6Hr0JpPsFBgTVYTAOq7tCmx/kR4SXbeawtPch20+3QfUcQDoJBLjWW1ybvZ4kXTw== dependencies: cookie "0.3.1" - json-stringify-safe "5.0.1" - lsmod "1.0.0" - stack-trace "0.0.9" + md5 "^2.2.1" + stack-trace "0.0.10" timed-out "4.0.1" - uuid "3.0.0" + uuid "3.3.2" -readable-stream@^2.0.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" +rc@^1.0.0: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" -redis-commands@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b" +redis-commands@1.4.0, redis-commands@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.4.0.tgz#52f9cf99153efcce56a8f86af986bd04e988602f" + integrity sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= redis-parser@^2.4.0: version "2.6.0" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" + integrity sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs= + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" + +redis-tag-cache@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/redis-tag-cache/-/redis-tag-cache-1.2.1.tgz#07d6e1f945d9d5c8186602129cb10573167d7534" + integrity sha512-0F+rLtoIkz4NOWGt9k3qaPcbpvr6+wcEC9aWV78newMKTRN/nIYHgvfyO1Q0ySEepiEYbqj9DhjvQ4CUAtT9/g== + dependencies: + ioredis "^4.0.0" + +rethinkdb-inspector@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/rethinkdb-inspector/-/rethinkdb-inspector-0.3.3.tgz#f0d88c66d17e0234b5518ca51cd8c272cb787003" + integrity sha512-1R0S5maattWOptfkHsU5ulXnt6FIKqjgyEdd8WgW9QbYMiHaXNqkHTAsmtXXSAf0j5iFH3F2qq/7eg4xwb8Euw== -rethinkdbdash@^2.3.29: +rethinkdbdash@^2.3.31: version "2.3.31" resolved "https://registry.yarnpkg.com/rethinkdbdash/-/rethinkdbdash-2.3.31.tgz#fe2f73d1fa6e6f5d96d8e881292013cf6dca914d" + integrity sha512-6nXrKFjdg2Ug0YpdmPWSvyD/2EisHnFNt4FWZ74dcXGK48ievSv+cNFTmVv+KjLi6I9CCf6w4CKZ6yCYTfMfdQ== + dependencies: + bluebird ">= 3.0.1" + +rethinkhaberdashery@^2.3.32: + version "2.3.32" + resolved "https://registry.yarnpkg.com/rethinkhaberdashery/-/rethinkhaberdashery-2.3.32.tgz#ddcc4ba1342e653a9d3bb6982b526087e93d537e" + integrity sha512-scDswDEu7R47WqomjScq46LkCdhaFhpaGQDP3P44GDF32iCFgYbsL4fnJdaieE115qLeinRhEjna08XWCtV0iQ== dependencies: bluebird ">= 3.0.1" -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +"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== + +sanitize-filename@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.1.tgz#612da1c96473fa02dccda92dcd5b4ab164a6772a" + integrity sha1-YS2hyWRz+gLczaktzVtKsWSmdyo= + dependencies: + truncate-utf8-bytes "^1.0.0" + +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o= + +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -semver@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +semver@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= -source-map-support@^0.4.15: - version "0.4.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.16.tgz#16fecf98212467d017d586a2af68d628b9421cd8" +source-map-support@^0.4.18: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== dependencies: source-map "^0.5.6" source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -stack-trace@0.0.9: - version "0.0.9" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695" +stack-trace@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= -string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" - dependencies: - safe-buffer "~5.1.0" +standard-as-callback@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-1.0.1.tgz#2e9e1e9d278d7d77580253faaec42269015e3c1d" + integrity sha512-izxEITSyc7S+5oOiF/URiYaNkemPUxIndCNv66jJ548Y1TVxhBvioNMSPrZIQdaZDlhnguOdUzHA/7hJ3xFhuQ== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= timed-out@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= -ua-parser-js@^0.7.9: - version "0.7.14" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.14.tgz#110d53fa4c3f326c121292bbeac904d2e03387ca" +toobusy-js@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/toobusy-js/-/toobusy-js-0.5.1.tgz#5511f78f6a87a6a512d44fdb0efa13672217f659" + integrity sha1-VRH3j2qHpqUS1E/bDvoTZyIX9lk= -util-deprecate@~1.0.1: +truncate-utf8-bytes@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" + integrity sha1-QFkjkJWS1W94pYGENLC3hInKXys= + dependencies: + utf8-byte-length "^1.0.1" -uuid@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728" +ua-parser-js@^0.7.18: + version "0.7.19" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b" + integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ== -uuid@^3.0.1, uuid@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" +url@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +utf8-byte-length@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" + integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E= + +util.promisify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + +uuid@3.3.2, uuid@^3.2.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== whatwg-fetch@>=0.10.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" + integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== + +xml2js@0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= diff --git a/config-overrides.js b/config-overrides.js index e7461ed997..284da68520 100644 --- a/config-overrides.js +++ b/config-overrides.js @@ -8,6 +8,7 @@ const debug = require('debug')('build:config-overrides'); const webpack = require('webpack'); const { injectBabelPlugin } = require('react-app-rewired'); const rewireStyledComponents = require('react-app-rewire-styled-components'); +const rewireReactHotLoader = require('react-app-rewire-hot-loader'); const swPrecachePlugin = require('sw-precache-webpack-plugin'); const fs = require('fs'); const path = require('path'); @@ -16,6 +17,7 @@ const WriteFilePlugin = require('write-file-webpack-plugin'); const { ReactLoadablePlugin } = require('react-loadable/webpack'); const OfflinePlugin = require('offline-plugin'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); +const BundleBuddyWebpackPlugin = require('bundle-buddy-webpack-plugin'); // Recursively walk a folder and get all file paths function walkFolder(currentDirPath, callback) { @@ -71,68 +73,64 @@ const transpileShared = config => { }; module.exports = function override(config, env) { + if (process.env.REACT_APP_MAINTENANCE_MODE === 'enabled') { + console.error('\n\n⚠️ ----MAINTENANCE MODE ENABLED----⚠️\n\n'); + } if (process.env.NODE_ENV === 'development') { config.output.path = path.join(__dirname, './build'); + config = rewireReactHotLoader(config, env); + config.plugins.push( + WriteFilePlugin({ + log: true, + useHashIndex: false, + }) + ); } config.plugins.push( new ReactLoadablePlugin({ filename: './build/react-loadable.json', }) ); - if (process.env.NODE_ENV === 'production') { - removeEslint(config); - } config = injectBabelPlugin('react-loadable/babel', config); config = transpileShared(config); - config.plugins.push( - new webpack.optimize.CommonsChunkPlugin({ - names: ['bootstrap'], - filename: './static/js/[name].js', - minChunks: Infinity, - }) - ); // Filter the default serviceworker plugin, add offline plugin instead config.plugins = config.plugins.filter( plugin => !isServiceWorkerPlugin(plugin) ); // Get all public files so they're cached by the SW - let externals = ['./public/install-raven.js']; - walkFolder('./public/img/', file => { + let externals = []; + walkFolder('./public/', file => { + if (file.indexOf('index.html') > -1) return; externals.push(file.replace(/public/, '')); }); config.plugins.push( new OfflinePlugin({ - // Don't cache anything in dev - caches: process.env.NODE_ENV === 'development' ? {} : 'all', - updateStrategy: 'all', // Update all files on update, seems safer than trying to only update changed files since we didn't write the webpack config - externals, // These files should be cached, but they're not emitted by webpack, so we gotta tell OfflinePlugin about 'em. - excludes: ['**/*.map'], // Don't cache any source maps, they're huge and unnecessary for clients - autoUpdate: true, // Automatically check for updates every hour - rewrites: arg => arg, + // We don't want to cache anything + caches: {}, + externals, + autoUpdate: true, + // NOTE(@mxstbr): Normally this is handled by setting + // appShell: './index.html' + // but we don't want to serve the app shell for the `/api` and `/auth` routes + // which means we have to manually do this and filter any of those routes out cacheMaps: [ { - match: url => { - // Don't return the cached index.html for API requests or /auth pages - if (url.pathname.indexOf('/api') === 0) return; - if (url.pathname.indexOf('/auth') === 0) return; - try { - return new URL('/index.html', url); - // TODO: Fix this properly instead of ignoring errors - } catch (err) { - return; - } + match: function() { + return false; }, - requestType: ['navigate'], + requestTypes: ['navigate'], }, ], + rewrites: arg => arg, ServiceWorker: { - entry: './public/push-sw.js', // Add the push notification ServiceWorker - events: true, // Emit events from the ServiceWorker + entry: './public/push-sw.js', + events: true, prefetchRequest: { - credentials: 'include', // Include credentials when fetching files, just to make sure we don't get into any issues + mode: 'cors', + credentials: 'include', }, }, - AppCache: false, // Don't cache using AppCache, too buggy that thing + AppCache: false, }) ); if (process.env.ANALYZE_BUNDLE === 'true') { @@ -144,11 +142,25 @@ module.exports = function override(config, env) { }) ); } - if (process.env.NODE_ENV === 'development') { + if (process.env.BUNDLE_BUDDY === 'true') { + config.plugins.push(new BundleBuddyWebpackPlugin()); + } + config.plugins.unshift( + new webpack.optimize.CommonsChunkPlugin({ + names: ['bootstrap'], + filename: 'static/js/[name].js', + minChunks: Infinity, + }) + ); + if (process.env.NODE_ENV === 'production') { + removeEslint(config); + config.plugins.push(new webpack.optimize.ModuleConcatenationPlugin()); config.plugins.push( - WriteFilePlugin({ - // Don't match hot-update files - test: /^((?!(hot-update)).)*$/g, + new webpack.DefinePlugin({ + 'process.env': { + SENTRY_DSN_CLIENT: `"${process.env.SENTRY_DSN_CLIENT}"`, + AMPLITUDE_API_KEY: `"${process.env.AMPLITUDE_API_KEY}"`, + }, }) ); } diff --git a/cypress.json b/cypress.json index 376bda3540..77ee091c6c 100644 --- a/cypress.json +++ b/cypress.json @@ -1,9 +1,12 @@ { "baseUrl": "http://localhost:3000", "viewportWidth": 1300, - "defaultCommandTimeout": 10000, - "blacklistHosts": ["*.google-analytics.com"], + "defaultCommandTimeout": 20000, + "blacklistHosts": [ + "*.google-analytics.com" + ], "env": { "DEBUG": "src*,testing*,build*" - } + }, + "projectId": "6a92uk" } diff --git a/cypress/integration/apps_page_spec.js b/cypress/integration/apps_page_spec.js new file mode 100644 index 0000000000..fd5147521c --- /dev/null +++ b/cypress/integration/apps_page_spec.js @@ -0,0 +1,11 @@ +describe('Apps View', () => { + describe('Loads page', () => { + beforeEach(() => { + cy.visit('/apps'); + }); + + it('should render the apps page', () => { + cy.get('[data-cy="apps-page"]').should('be.visible'); + }); + }); +}); diff --git a/cypress/integration/channel/settings/create_spec.js b/cypress/integration/channel/settings/create_spec.js index 5580567e4c..41ab960533 100644 --- a/cypress/integration/channel/settings/create_spec.js +++ b/cypress/integration/channel/settings/create_spec.js @@ -10,24 +10,15 @@ const { userId: ownerInChannelId } = data.usersChannels.find( ({ channelId, isOwner }) => channelId === channel.id && isOwner ); -// NOTE @brian: I will finish this after payments-api-v2 merges - describe('create a channel', () => { - before(() => { - cy.auth(ownerInChannelId); - // NOTE @brian: I can not get this to auth directly into /settings, having to work around for now - cy.visit(`/${community.slug}`); + beforeEach(() => { + cy.auth(ownerInChannelId).then(() => + cy.visit(`/${community.slug}/settings`) + ); }); it('should go through create a channel flow', () => { - cy - .get('[data-cy="community-settings-button"]') - .scrollIntoView() - .should('be.visible') - .click(); - - cy - .get('[data-cy="create-channel-button"]') + cy.get('[data-cy="create-channel-button"]') .scrollIntoView() .should('be.visible') .click(); diff --git a/cypress/integration/channel/settings/delete_spec.js b/cypress/integration/channel/settings/delete_spec.js index 18b9feb32e..ad2a768fce 100644 --- a/cypress/integration/channel/settings/delete_spec.js +++ b/cypress/integration/channel/settings/delete_spec.js @@ -20,17 +20,13 @@ const { userId: ownerInPrivateChannelId } = data.usersChannels.find( ); describe('deleting general channel', () => { - before(() => { - cy.auth(ownerInChannelId); - cy.visit(`/${community.slug}/${channel.slug}`); + beforeEach(() => { + cy.auth(ownerInChannelId).then(() => + cy.visit(`/${community.slug}/${channel.slug}/settings`) + ); }); it('should not allow general channel to be deleted', () => { - cy - .get('[data-cy="channel-settings-button"]') - .should('be.visible') - .click(); - cy.get('[data-cy="channel-overview"]').should('be.visible'); cy.get('[data-cy="delete-channel-button"]').should('not.be.visible'); @@ -38,21 +34,16 @@ describe('deleting general channel', () => { }); describe('deleting a channel', () => { - before(() => { - cy.auth(ownerInPrivateChannelId); - cy.visit(`/${privateCommunity.slug}/${privateChannel.slug}`); + beforeEach(() => { + cy.auth(ownerInPrivateChannelId).then(() => + cy.visit(`/${privateCommunity.slug}/${privateChannel.slug}/settings`) + ); }); it('should delete a channel', () => { - cy - .get('[data-cy="channel-settings-button"]') - .should('be.visible') - .click(); - cy.get('[data-cy="channel-overview"]').should('be.visible'); - cy - .get('[data-cy="delete-channel-button"]') + cy.get('[data-cy="delete-channel-button"]') .should('be.visible') .click(); diff --git a/cypress/integration/channel/settings/edit_spec.js b/cypress/integration/channel/settings/edit_spec.js index 9fa0c44545..8e81c9f5db 100644 --- a/cypress/integration/channel/settings/edit_spec.js +++ b/cypress/integration/channel/settings/edit_spec.js @@ -12,88 +12,36 @@ const { userId: ownerInChannelId } = data.usersChannels.find( const NEW_NAME = 'General Update'; const NEW_DESCRIPTION = 'New description'; -const ORIGINAL_NAME = ' General'; -const ORIGINAL_DESCRIPTION = 'General chatter'; describe('edit a channel', () => { - before(() => { - cy.auth(ownerInChannelId); - cy.visit(`/${community.slug}/${channel.slug}`); + beforeEach(() => { + cy.auth(ownerInChannelId).then(() => + cy.visit(`/${community.slug}/${channel.slug}/settings`) + ); }); it('should edit a channel', () => { - cy - .get('[data-cy="channel-settings-button"]') - .should('be.visible') - .click(); - cy.get('[data-cy="channel-overview"]').should('be.visible'); - cy - .get('[data-cy="channel-name-input"]') + cy.get('[data-cy="channel-name-input"]') .should('be.visible') .click() .clear() .type(NEW_NAME); - cy - .get('[data-cy="channel-description-input"]') + cy.get('[data-cy="channel-description-input"]') .should('be.visible') .click() .clear() .type(NEW_DESCRIPTION); - cy - .get('[data-cy="save-button"]') + cy.get('[data-cy="save-button"]') .should('be.visible') .click(); - cy.get('[data-cy="save-button"]').should('be.disabled'); - - cy.get('[data-cy="save-button"]').should('not.be.disabled'); - cy.visit(`/${community.slug}/${channel.slug}`); cy.get('[data-cy="channel-profile-full"]').should('be.visible'); cy.get('[data-cy="channel-profile-full"]').contains(NEW_NAME); cy.get('[data-cy="channel-profile-full"]').contains(NEW_DESCRIPTION); }); }); - -describe('undo editing a channel', () => { - before(() => { - cy.auth(ownerInChannelId); - cy.visit(`/${community.slug}/${channel.slug}`); - }); - - it('should revert the edit', () => { - cy - .get('[data-cy="channel-settings-button"]') - .should('be.visible') - .click(); - - cy.get('[data-cy="channel-overview"]').should('be.visible'); - - cy - .get('[data-cy="channel-name-input"]') - .should('be.visible') - .click() - .clear() - .type(ORIGINAL_NAME); - - cy - .get('[data-cy="channel-description-input"]') - .should('be.visible') - .click() - .clear() - .type(ORIGINAL_DESCRIPTION); - - cy - .get('[data-cy="save-button"]') - .should('be.visible') - .click(); - - cy.get('[data-cy="save-button"]').should('be.disabled'); - - cy.get('[data-cy="save-button"]').should('not.be.disabled'); - }); -}); diff --git a/cypress/integration/channel/settings/members_spec.js b/cypress/integration/channel/settings/members_spec.js new file mode 100644 index 0000000000..c52ddfdab6 --- /dev/null +++ b/cypress/integration/channel/settings/members_spec.js @@ -0,0 +1,48 @@ +import data from '../../../../shared/testing/data'; + +const channel = data.channels[0]; +const community = data.communities.find( + community => community.id === channel.communityId +); + +const { userId: ownerInChannelId } = data.usersChannels.find( + ({ channelId, isOwner }) => channelId === channel.id && isOwner +); + +const membersChannels = data.usersChannels.filter( + uc => uc.channelId === channel.id +); +const members = membersChannels + .filter(mc => mc.isMember) + .map(mc => data.users.find(user => mc.userId === user.id)); +const member = members[0]; + +describe('sending a message to channel member', () => { + beforeEach(() => { + cy.auth(ownerInChannelId).then(() => + cy.visit(`/${community.slug}/${channel.slug}/settings`) + ); + }); + + it('should render all members', () => { + cy.get('[data-cy="channel-overview"]').should('be.visible'); + + members.map(member => { + cy.contains(member.name) + .scrollIntoView() + .should('be.visible'); + }); + }); + + it('should init a new dm', () => { + cy.get('[data-cy="channel-overview"]').should('be.visible'); + + cy.get('[data-cy="message-user-button"]') + .first() + .click(); + + cy.url().should('include', '/messages/new'); + + cy.get('[data-cy="selected-user-pill"]').should('be.visible'); + }); +}); diff --git a/cypress/integration/channel/settings/private_invite_link_spec.js b/cypress/integration/channel/settings/private_invite_link_spec.js index 6b5353e3ea..85a9e61bc1 100644 --- a/cypress/integration/channel/settings/private_invite_link_spec.js +++ b/cypress/integration/channel/settings/private_invite_link_spec.js @@ -8,32 +8,32 @@ const { userId: ownerInChannelId } = data.usersChannels.find( ({ channelId, isOwner }) => channelId === channel.id && isOwner ); -describe('private channel invite link settings', () => { - beforeEach(() => { - cy.auth(ownerInChannelId); - cy.visit(`/${community.slug}/${channel.slug}/settings`); - }); +const enable = () => { + cy.get('[data-cy="channel-overview"]').should('be.visible'); - it('should enable private invite link', () => { - cy.get('[data-cy="channel-overview"]').should('be.visible'); + cy.get('[data-cy="login-with-token-settings"]').scrollIntoView(); - cy.get('[data-cy="login-with-token-settings"]').scrollIntoView(); + cy.get('[data-cy="toggle-token-link-invites-unchecked"]') + .should('be.visible') + .click(); - cy - .get('[data-cy="toggle-token-link-invites-unchecked"]') - .should('be.visible') - .click(); + cy.get('[data-cy="join-link-input"]').should('be.visible'); +}; - cy.get('[data-cy="join-link-input"]').should('be.visible'); +describe('private channel invite link settings', () => { + beforeEach(() => { + cy.auth(ownerInChannelId).then(() => + cy.visit(`/${community.slug}/${channel.slug}/settings`) + ); }); - it('should refresh invite link token', () => { - cy.get('[data-cy="channel-overview"]').should('be.visible'); + it('should handle enable, reset, and disable', () => { + // enable + enable(); + // reset token cy.get('[data-cy="login-with-token-settings"]').scrollIntoView(); - - cy - .get('[data-cy="join-link-input"]') + cy.get('[data-cy="join-link-input"]') .invoke('val') .then(val1 => { // do more work here @@ -43,27 +43,15 @@ describe('private channel invite link settings', () => { cy.get('[data-cy="refresh-join-link-token"]').click(); - cy.get('[data-cy="refresh-join-link-token"]').should('be.disabled'); - cy.get('[data-cy="refresh-join-link-token"]').should('not.be.disabled'); - // grab the input again and compare its previous value // to the current value - cy - .get('[data-cy="join-link-input"]') + cy.get('[data-cy="join-link-input"]') .invoke('val') - .should(val2 => { - expect(val1).not.to.eq(val2); - }); + .should('not.eq', val1); }); - }); - - it('should disable private invite link', () => { - cy.get('[data-cy="channel-overview"]').should('be.visible'); - - cy.get('[data-cy="login-with-token-settings"]').scrollIntoView(); - cy - .get('[data-cy="toggle-token-link-invites-checked"]') + // disable + cy.get('[data-cy="toggle-token-link-invites-checked"]') .should('be.visible') .click(); diff --git a/cypress/integration/channel/view/composer_spec.js b/cypress/integration/channel/view/composer_spec.js index 2ed1029f20..f04a79633c 100644 --- a/cypress/integration/channel/view/composer_spec.js +++ b/cypress/integration/channel/view/composer_spec.js @@ -20,9 +20,10 @@ const { userId: memberInArchivedChannelId } = data.usersChannels.find( const QUIET_USER_ID = constants.QUIET_USER_ID; describe('renders composer for logged in members', () => { - before(() => { - cy.auth(memberInChannelId); - cy.visit(`/${community.slug}/${channel.slug}`); + beforeEach(() => { + cy.auth(memberInChannelId).then(() => + cy.visit(`/${community.slug}/${channel.slug}`) + ); }); it('should render composer', () => { @@ -37,9 +38,10 @@ describe('renders composer for logged in members', () => { }); describe('does not render composer for non members', () => { - before(() => { - cy.auth(QUIET_USER_ID); - cy.visit(`/${community.slug}/${channel.slug}`); + beforeEach(() => { + cy.auth(QUIET_USER_ID).then(() => + cy.visit(`/${community.slug}/${channel.slug}`) + ); }); it('should not render composer', () => { @@ -50,7 +52,7 @@ describe('does not render composer for non members', () => { }); describe('does not render composer for logged out users', () => { - before(() => { + beforeEach(() => { cy.visit(`/${community.slug}/${channel.slug}`); }); @@ -62,9 +64,10 @@ describe('does not render composer for logged out users', () => { }); describe('does not render composer for archived channel', () => { - before(() => { - cy.auth(memberInArchivedChannelId); - cy.visit(`/${community.slug}/${archivedChannel.slug}`); + beforeEach(() => { + cy.auth(memberInArchivedChannelId).then(() => + cy.visit(`/${community.slug}/${archivedChannel.slug}`) + ); }); it('should not render composer', () => { diff --git a/cypress/integration/channel/view/members_spec.js b/cypress/integration/channel/view/members_spec.js index 4816bc7f19..5052261bb0 100644 --- a/cypress/integration/channel/view/members_spec.js +++ b/cypress/integration/channel/view/members_spec.js @@ -6,20 +6,23 @@ const community = data.communities.find( const usersChannels = data.usersChannels .filter(({ channelId, isMember }) => channelId === channel.id && isMember) .map(o => o.userId); + const members = data.users.filter(user => usersChannels.indexOf(user.id) >= 0); describe('renders members list on channel view', () => { - before(() => { + beforeEach(() => { cy.visit(`/${community.slug}/${channel.slug}`); }); it('should render members component', () => { - cy.get('[data-cy="channel-members-list"]').should('be.visible'); + cy.get('[data-cy="channel-members-list"]') + .scrollIntoView() + .should('be.visible'); members.map(member => { - cy - .get('[data-cy="channel-view"]') + cy.get('[data-cy="channel-view"]') .contains(`${member.name}`) + .scrollIntoView() .should('be.visible'); }); }); diff --git a/cypress/integration/channel/view/membership_spec.js b/cypress/integration/channel/view/membership_spec.js index f942b21260..bd48c2664a 100644 --- a/cypress/integration/channel/view/membership_spec.js +++ b/cypress/integration/channel/view/membership_spec.js @@ -28,37 +28,27 @@ const { userId: memberInPrivateChannelId } = data.usersChannels.find( const QUIET_USER_ID = constants.QUIET_USER_ID; const leave = () => { - cy - .get('[data-cy="channel-join-button"]') + cy.get('[data-cy="channel-leave-button"]') .should('be.visible') - .contains('Joined'); + .contains('Leave channel'); - cy.get('[data-cy="channel-join-button"]').click(); - - cy.get('[data-cy="channel-join-button"]').should('be.disabled'); - - cy.get('[data-cy="channel-join-button"]').should('not.be.disabled'); + cy.get('[data-cy="channel-leave-button"]').click(); cy.get('[data-cy="channel-join-button"]').contains(`Join `); }; const join = () => { - cy - .get('[data-cy="channel-join-button"]') + cy.get('[data-cy="channel-join-button"]') .should('be.visible') .contains('Join '); cy.get('[data-cy="channel-join-button"]').click(); - cy.get('[data-cy="channel-join-button"]').should('be.disabled'); - - cy.get('[data-cy="channel-join-button"]').should('not.be.disabled'); - - cy.get('[data-cy="channel-join-button"]').contains(`Joined`); + cy.get('[data-cy="channel-leave-button"]').contains(`Leave channel`); }; describe('logged out channel membership', () => { - before(() => { + beforeEach(() => { cy.visit(`/${community.slug}/${publicChannel.slug}`); }); @@ -68,9 +58,10 @@ describe('logged out channel membership', () => { }); describe('channel profile as member', () => { - before(() => { - cy.auth(memberInChannelId); - cy.visit(`/${community.slug}/${publicChannel.slug}`); + beforeEach(() => { + cy.auth(memberInChannelId).then(() => + cy.visit(`/${community.slug}/${publicChannel.slug}`) + ); }); it('should render leave channel button', () => { @@ -80,9 +71,10 @@ describe('channel profile as member', () => { }); describe('channel profile as non-member', () => { - before(() => { - cy.auth(QUIET_USER_ID); - cy.visit(`/${community.slug}/${publicChannel.slug}`); + beforeEach(() => { + cy.auth(QUIET_USER_ID).then(() => + cy.visit(`/${community.slug}/${publicChannel.slug}`) + ); }); it('should render join channel button', () => { @@ -92,14 +84,14 @@ describe('channel profile as non-member', () => { }); describe('channel profile as owner', () => { - before(() => { - cy.auth(ownerInChannelId); - cy.visit(`/${community.slug}/${publicChannel.slug}`); + beforeEach(() => { + cy.auth(ownerInChannelId).then(() => + cy.visit(`/${community.slug}/${publicChannel.slug}`) + ); }); it('should render settings button', () => { - cy - .get('[data-cy="channel-settings-button"]') + cy.get('[data-cy="channel-settings-button"]') .should('be.visible') .contains('Settings'); }); @@ -107,9 +99,10 @@ describe('channel profile as owner', () => { describe('private channel profile', () => { describe('private channel as member', () => { - before(() => { - cy.auth(memberInPrivateChannelId); - cy.visit(`/${community.slug}/${privateChannel.slug}`); + beforeEach(() => { + cy.auth(memberInPrivateChannelId).then(() => + cy.visit(`/${community.slug}/${privateChannel.slug}`) + ); }); it('should render profile', () => { @@ -118,23 +111,14 @@ describe('private channel profile', () => { }); describe('private channel as non-member', () => { - before(() => { - cy.auth(QUIET_USER_ID); - cy.visit(`/${community.slug}/${privateChannel.slug}`); + beforeEach(() => { + cy.auth(QUIET_USER_ID).then(() => + cy.visit(`/${community.slug}/${privateChannel.slug}`) + ); }); - it('should render request to join view', () => { - cy.get('[data-cy="channel-view-is-restricted"]').should('be.visible'); - - cy - .get('[data-cy="request-to-join-private-channel-button"]') - .contains(`Request to join ${privateChannel.name}`) - .click(); - - cy - .get('[data-cy="cancel-request-to-join-private-channel-button"]') - .contains('Cancel request') - .click(); + it('should render channel not found view', () => { + cy.get('[data-cy="channel-not-found"]').should('be.visible'); }); }); }); diff --git a/cypress/integration/channel/view/notifications_spec.js b/cypress/integration/channel/view/notifications_spec.js index 92df56ed80..4a79fb5d89 100644 --- a/cypress/integration/channel/view/notifications_spec.js +++ b/cypress/integration/channel/view/notifications_spec.js @@ -12,7 +12,7 @@ const { userId: memberInChannelId } = data.usersChannels.find( const QUIET_USER_ID = constants.QUIET_USER_ID; describe('channel notification preferences logged out', () => { - before(() => { + beforeEach(() => { cy.visit(`/${community.slug}/${channel.slug}`); }); @@ -24,37 +24,37 @@ describe('channel notification preferences logged out', () => { }); describe('channel notification preferences as member', () => { - before(() => { - cy.auth(memberInChannelId); - cy.visit(`/${community.slug}/${channel.slug}`); + beforeEach(() => { + cy.auth(memberInChannelId).then(() => + cy.visit(`/${community.slug}/${channel.slug}`) + ); }); it('should render notification settings', () => { cy.get('[data-cy="channel-view"]').should('be.visible'); - cy - .get('[data-cy="notifications-checkbox-checked"]') + cy.get('[data-cy="notifications-checkbox-checked"]') .should('be.visible') .click(); - cy - .get('[data-cy="notifications-checkbox-unchecked"]') + cy.get('[data-cy="notifications-checkbox-unchecked"]') .should('be.visible') .click(); }); }); describe('channel profile as non-member', () => { - before(() => { - cy.auth(QUIET_USER_ID); - cy.visit(`/${community.slug}/${channel.slug}`); + beforeEach(() => { + cy.auth(QUIET_USER_ID).then(() => + cy.visit(`/${community.slug}/${channel.slug}`) + ); }); it('should not render notifications settings', () => { cy.get('[data-cy="channel-view"]').should('be.visible'); - cy - .get('[data-cy="notifications-checkbox-checked"]') - .should('not.be.visible'); + cy.get('[data-cy="notifications-checkbox-checked"]').should( + 'not.be.visible' + ); }); }); diff --git a/cypress/integration/channel/view/profile_spec.js b/cypress/integration/channel/view/profile_spec.js index db8f261232..0a29c2467b 100644 --- a/cypress/integration/channel/view/profile_spec.js +++ b/cypress/integration/channel/view/profile_spec.js @@ -4,11 +4,20 @@ const publicChannel = data.channels[0]; const privateChannel = data.channels[1]; const archivedChannel = data.channels.find(c => c.slug === 'archived'); const deletedChannel = data.channels.find(c => c.slug === 'deleted'); +const publicChannelInPrivateCommunity = data.channels.find( + c => c.slug === 'private-general' +); + +const privateCommunity = data.communities.find( + community => community.id === publicChannelInPrivateCommunity.communityId +); const community = data.communities.find( community => community.id === publicChannel.communityId ); +const bryn = data.users.find(user => user.username === 'bryn'); + const { userId: blockedInChannelId } = data.usersChannels.find( ({ channelId, isBlocked }) => channelId === publicChannel.id && isBlocked ); @@ -17,8 +26,13 @@ const { userId: memberInPrivateChannelId } = data.usersChannels.find( ({ channelId, isMember }) => channelId === privateChannel.id && isMember ); +const { userId: memberInPrivateCommunityId } = data.usersChannels.find( + ({ channelId, isMember }) => + channelId === publicChannelInPrivateCommunity.id && isMember +); + describe('public channel', () => { - before(() => { + beforeEach(() => { cy.visit(`/${community.slug}/${publicChannel.slug}`); }); @@ -34,8 +48,48 @@ describe('public channel', () => { }); }); +describe('public channel in private community signed out', () => { + beforeEach(() => { + cy.visit( + `/${privateCommunity.slug}/${publicChannelInPrivateCommunity.slug}` + ); + }); + + it('should render channel not found view', () => { + cy.get('[data-cy="channel-not-found"]').should('be.visible'); + }); +}); + +describe('public channel in private community with permission', () => { + beforeEach(() => { + cy.auth(memberInPrivateCommunityId).then(() => + cy.visit( + `/${privateCommunity.slug}/${publicChannelInPrivateCommunity.slug}` + ) + ); + }); + + it('should render if user is member of community', () => { + cy.get('[data-cy="channel-view"]').should('be.visible'); + }); +}); + +describe('public channel in private community without permission', () => { + beforeEach(() => { + cy.auth(bryn.id).then(() => + cy.visit( + `/${privateCommunity.slug}/${publicChannelInPrivateCommunity.slug}` + ) + ); + }); + + it('should render channel not found view', () => { + cy.get('[data-cy="channel-not-found"]').should('be.visible'); + }); +}); + describe('archived channel', () => { - before(() => { + beforeEach(() => { cy.visit(`/${community.slug}/${archivedChannel.slug}`); }); @@ -50,7 +104,7 @@ describe('archived channel', () => { }); describe('deleted channel', () => { - before(() => { + beforeEach(() => { cy.visit(`/${community.slug}/${deletedChannel.slug}`); }); @@ -61,9 +115,10 @@ describe('deleted channel', () => { }); describe('blocked in public channel', () => { - before(() => { - cy.auth(blockedInChannelId); - cy.visit(`/${community.slug}/${publicChannel.slug}`); + beforeEach(() => { + cy.auth(blockedInChannelId).then(() => + cy.visit(`/${community.slug}/${publicChannel.slug}`) + ); }); it('should render error view', () => { @@ -73,9 +128,10 @@ describe('blocked in public channel', () => { }); describe('member in private channel', () => { - before(() => { - cy.auth(memberInPrivateChannelId); - cy.visit(`/${community.slug}/${privateChannel.slug}`); + beforeEach(() => { + cy.auth(memberInPrivateChannelId).then(() => + cy.visit(`/${community.slug}/${privateChannel.slug}`) + ); }); it('should render profile', () => { @@ -84,22 +140,23 @@ describe('member in private channel', () => { }); describe('blocked in private channel', () => { - before(() => { - cy.auth(blockedInChannelId); - cy.visit(`/${community.slug}/${privateChannel.slug}`); + beforeEach(() => { + cy.auth(blockedInChannelId).then(() => + cy.visit(`/${community.slug}/${privateChannel.slug}`) + ); }); - it('should render error view', () => { - cy.get('[data-cy="channel-view-blocked"]').should('be.visible'); + it('should render channel not found view', () => { + cy.get('[data-cy="channel-not-found"]').should('be.visible'); }); }); describe('is not logged in', () => { - before(() => { + beforeEach(() => { cy.visit(`/${community.slug}/${privateChannel.slug}`); }); - it('should render login view', () => { - cy.contains(`Sign in`); + it('should render channel not found view', () => { + cy.get('[data-cy="channel-not-found"]').should('be.visible'); }); }); diff --git a/cypress/integration/channel/view/search_spec.js b/cypress/integration/channel/view/search_spec.js index 943cf2bdfd..ba3daac0a9 100644 --- a/cypress/integration/channel/view/search_spec.js +++ b/cypress/integration/channel/view/search_spec.js @@ -5,7 +5,7 @@ const community = data.communities.find( ); describe('renders search on channel view', () => { - before(() => { + beforeEach(() => { cy.visit(`/${community.slug}/${channel.slug}`); }); diff --git a/cypress/integration/channel/view/threads_spec.js b/cypress/integration/channel/view/threads_spec.js index a675667751..474dc12ec4 100644 --- a/cypress/integration/channel/view/threads_spec.js +++ b/cypress/integration/channel/view/threads_spec.js @@ -12,13 +12,13 @@ const { userId: memberInChannelId } = data.usersChannels.find( ); describe('channel threads logged out', () => { - before(() => { + beforeEach(() => { cy.visit(`/${community.slug}/${channel.slug}`); }); it('should render list of threads', () => { data.threads - .filter(thread => thread.channelId === channel.id) + .filter(thread => !thread.deletedAt && thread.channelId === channel.id) .forEach(thread => { cy.contains(thread.content.title); }); @@ -26,14 +26,15 @@ describe('channel threads logged out', () => { }); describe('channel threads logged in', () => { - before(() => { - cy.auth(memberInChannelId); - cy.visit(`/${community.slug}/${channel.slug}`); + beforeEach(() => { + cy.auth(memberInChannelId).then(() => + cy.visit(`/${community.slug}/${channel.slug}`) + ); }); it('should render list of threads', () => { data.threads - .filter(thread => thread.channelId === channel.id) + .filter(thread => !thread.deletedAt && thread.channelId === channel.id) .forEach(thread => { cy.contains(thread.content.title); }); diff --git a/cypress/integration/community/settings/create_spec.js b/cypress/integration/community/settings/create_spec.js new file mode 100644 index 0000000000..ab2ea41bd7 --- /dev/null +++ b/cypress/integration/community/settings/create_spec.js @@ -0,0 +1,126 @@ +import data from '../../../../shared/testing/data'; + +const user = data.users[0]; + +describe('creating a public community', () => { + beforeEach(() => { + cy.auth(user.id).then(() => cy.visit(`/new/community`)); + }); + + it('should create a public community', () => { + cy.get('[data-cy="create-community-form"]') + .scrollIntoView() + .should('be.visible'); + + cy.get('[data-cy="community-name-input"]') + .scrollIntoView() + .should('be.visible') + .click() + .type('my public community'); + + cy.get('[data-cy="community-description-input"]') + .scrollIntoView() + .should('be.visible') + .click() + .type('my public description'); + + cy.get('[data-cy="community-website-input"]') + .scrollIntoView() + .should('be.visible') + .click() + .type('spectrum.chat'); + + cy.get('[data-cy="community-website-input"]') + .scrollIntoView() + .should('be.visible') + .click() + .type('spectrum.chat'); + + cy.get('[data-cy="community-public-selector-input"]') + .scrollIntoView() + .should('be.visible') + .should('be.checked'); + + cy.get('[data-cy="community-coc-input-unchecked"]') + .scrollIntoView() + .should('be.visible') + .click(); + + cy.get('[data-cy="community-create-button"]') + .scrollIntoView() + .should('be.visible') + .should('not.be.disabled') + .click(); + + cy.get('[data-cy="community-creation-invitation-step"]').should( + 'be.visible' + ); + }); +}); + +describe('creating a private community', () => { + beforeEach(() => { + cy.auth(user.id).then(() => cy.visit(`/new/community`)); + }); + + it('should create a private community', () => { + cy.get('[data-cy="create-community-form"]') + .scrollIntoView() + .should('be.visible'); + + cy.get('[data-cy="community-name-input"]') + .scrollIntoView() + .should('be.visible') + .click() + .type('private community'); + + cy.get('[data-cy="community-description-input"]') + .scrollIntoView() + .should('be.visible') + .click() + .type('my private description'); + + cy.get('[data-cy="community-website-input"]') + .scrollIntoView() + .should('be.visible') + .click() + .type('spectrum.chat'); + + cy.get('[data-cy="community-website-input"]') + .scrollIntoView() + .should('be.visible') + .click() + .type('spectrum.chat'); + + cy.get('[data-cy="community-public-selector-input"]') + .scrollIntoView() + .should('be.visible') + .should('be.checked'); + + cy.get('[data-cy="community-private-selector-input"]') + .should('be.visible') + .should('not.be.checked') + .click(); + + cy.get('[data-cy="community-private-selector-input"]').should('be.checked'); + + cy.get('[data-cy="community-public-selector-input"]').should( + 'not.be.checked' + ); + + cy.get('[data-cy="community-coc-input-unchecked"]') + .scrollIntoView() + .should('be.visible') + .click(); + + cy.get('[data-cy="community-create-button"]') + .scrollIntoView() + .should('be.visible') + .should('not.be.disabled') + .click(); + + cy.get('[data-cy="community-creation-invitation-step"]').should( + 'be.visible' + ); + }); +}); diff --git a/cypress/integration/community/settings/private_invite_link_spec.js b/cypress/integration/community/settings/private_invite_link_spec.js new file mode 100644 index 0000000000..48a157128e --- /dev/null +++ b/cypress/integration/community/settings/private_invite_link_spec.js @@ -0,0 +1,59 @@ +import data from '../../../../shared/testing/data'; + +const community = data.communities.find(c => c.slug === 'private'); +const { userId: ownerInCommunityId } = data.usersCommunities.find( + ({ communityId, isOwner }) => communityId === community.id && isOwner +); + +const enable = () => { + cy.get('[data-cy="community-settings"]').should('be.visible'); + + cy.get('[data-cy="login-with-token-settings"]').scrollIntoView(); + + cy.get('[data-cy="toggle-token-link-invites-unchecked"]') + .should('be.visible') + .click(); + + cy.get('[data-cy="join-link-input"]').should('be.visible'); +}; + +describe('private community invite link settings', () => { + beforeEach(() => { + cy.auth(ownerInCommunityId).then(() => + cy.visit(`/${community.slug}/settings/members`) + ); + }); + + it('should handle enable, reset, and disable', () => { + // enable + enable(); + + // reset token + cy.get('[data-cy="login-with-token-settings"]').scrollIntoView(); + cy.get('[data-cy="join-link-input"]') + .invoke('val') + .then(val1 => { + // do more work here + + // click the button which changes the input's value + cy.get('[data-cy="refresh-join-link-token"]').should('be.visible'); + + cy.get('[data-cy="refresh-join-link-token"]').click(); + + // grab the input again and compare its previous value + // to the current value + cy.get('[data-cy="join-link-input"]') + .invoke('val') + .should(val2 => { + expect(val1).not.to.eq(val2); + }); + }); + + // disable + cy.get('[data-cy="toggle-token-link-invites-checked"]') + .should('be.visible') + .click(); + + cy.get('[data-cy="join-link-input"]').should('not.be.visible'); + }); +}); diff --git a/cypress/integration/community/view/profile_spec.js b/cypress/integration/community/view/profile_spec.js new file mode 100644 index 0000000000..46a474e61f --- /dev/null +++ b/cypress/integration/community/view/profile_spec.js @@ -0,0 +1,284 @@ +import data from '../../../../shared/testing/data'; + +const publicCommunity = data.communities.find(c => c.slug === 'spectrum'); +const privateCommunity = data.communities.find(c => c.slug === 'private'); + +const publicUsersCommunities = data.usersCommunities + .filter( + uc => + uc.communityId === publicCommunity.id && + uc.isMember && + (uc.isOwner || uc.isModerator) + ) + .map(uc => uc.userId); +const privateUsersCommunities = data.usersCommunities + .filter( + uc => + uc.communityId === privateCommunity.id && + uc.isMember && + (uc.isOwner || uc.isModerator) + ) + .map(uc => uc.userId); +const publicTeamMembers = data.users.filter( + u => publicUsersCommunities.indexOf(u.id) >= 0 +); +const privateTeamMembers = data.users.filter( + u => privateUsersCommunities.indexOf(u.id) >= 0 +); + +const { userId: memberInPublicCommunityId } = data.usersCommunities.find( + ({ communityId, isMember }) => communityId === publicCommunity.id && isMember +); + +const { userId: nonMemberInPublicCommunityId } = data.usersCommunities.find( + ({ communityId, isMember, isBlocked }) => + communityId === publicCommunity.id && !isMember && !isBlocked +); + +const { userId: memberInPrivateCommunityId } = data.usersCommunities.find( + ({ communityId, isMember }) => communityId === privateCommunity.id && isMember +); + +const { userId: nonMemberInPrivateCommunityId } = data.usersCommunities.find( + ({ communityId, isMember, isBlocked }) => + communityId === privateCommunity.id && !isMember && !isBlocked +); + +describe('public community signed out', () => { + beforeEach(() => { + cy.visit(`/${publicCommunity.slug}`); + }); + + it('should render profile', () => { + cy.get('[data-cy="community-view"]').should('be.visible'); + cy.contains(publicCommunity.description); + cy.contains(publicCommunity.name); + cy.contains(publicCommunity.website); + }); + + it('should render threads', () => { + cy.get('[data-cy="community-view-content"]') + .scrollIntoView() + .should('be.visible'); + + data.threads + .filter( + thread => !thread.deletedAt && thread.communityId === publicCommunity.id + ) + .forEach(thread => + cy + .contains(thread.content.title) + .scrollIntoView() + .should('be.visible') + ); + }); + + it('should render channels', () => { + data.channels + .filter(channel => channel.communityId === publicCommunity.id) + .filter(channel => !channel.isPrivate) + .filter(channel => !channel.deletedAt) + .forEach(channel => { + cy.contains(channel.name) + .scrollIntoView() + .should('be.visible'); + }); + }); + + it('should render team', () => { + publicTeamMembers.forEach(user => { + cy.contains(user.name) + .scrollIntoView() + .should('be.visible'); + }); + }); + + it('should prompt user to login when joining', () => { + cy.get('[data-cy="join-community-button-login"]') + .scrollIntoView() + .should('be.visible') + .click(); + + cy.get('[data-cy="login-page"]').should('be.visible'); + }); +}); + +describe('public community signed in without permission', () => { + beforeEach(() => { + cy.auth(nonMemberInPublicCommunityId).then(() => + cy.visit(`/${publicCommunity.slug}`) + ); + }); + + it('should render profile', () => { + cy.get('[data-cy="community-view"]').should('be.visible'); + cy.contains(publicCommunity.description); + cy.contains(publicCommunity.name); + cy.contains(publicCommunity.website); + }); + + it('should render threads', () => { + cy.get('[data-cy="community-view-content"]') + .scrollIntoView() + .should('be.visible'); + + data.threads + .filter( + thread => !thread.deletedAt && thread.communityId === publicCommunity.id + ) + .forEach(thread => + cy + .contains(thread.content.title) + .scrollIntoView() + .should('be.visible') + ); + }); + + it('should render channels', () => { + data.channels + .filter(channel => channel.communityId === publicCommunity.id) + .filter(channel => !channel.isPrivate) + .filter(channel => !channel.deletedAt) + .forEach(channel => { + cy.contains(channel.name) + .scrollIntoView() + .should('be.visible'); + }); + }); + + it('should render team', () => { + publicTeamMembers.forEach(user => { + cy.contains(user.name) + .scrollIntoView() + .should('be.visible'); + }); + }); + + it('should join the community', () => { + cy.get('[data-cy="join-community-button"]') + .scrollIntoView() + .should('be.visible'); + + cy.get('[data-cy="join-community-button"]') + .contains(`Join ${publicCommunity.name}`) + .click(); + + cy.get('[data-cy="leave-community-button"]') + .contains(`Leave community`) + .click(); + + // triggered the leave modal + cy.get('[data-cy="delete-button"]') + .contains(`Leave Community`) + .should('be.visible') + .click(); + + cy.get('[data-cy="join-community-button"]') + .scrollIntoView() + .should('be.visible'); + }); +}); + +describe('public community signed in with permission', () => { + beforeEach(() => { + cy.auth(memberInPublicCommunityId).then(() => + cy.visit(`/${publicCommunity.slug}`) + ); + }); + + it('should render profile', () => { + cy.get('[data-cy="community-view"]').should('be.visible'); + cy.contains(publicCommunity.description); + cy.contains(publicCommunity.name); + cy.contains(publicCommunity.website); + }); +}); + +describe('private community signed out', () => { + beforeEach(() => { + cy.visit(`/${privateCommunity.slug}`); + }); + + it('should prompt a login', () => { + cy.get('[data-cy="login-page"]').should('be.visible'); + }); +}); + +describe('private community signed in without permission', () => { + beforeEach(() => { + cy.auth(nonMemberInPrivateCommunityId).then(() => + cy.visit(`/${privateCommunity.slug}`) + ); + }); + + it('should render the blocked page', () => { + cy.get('[data-cy="community-view-blocked"]').should('be.visible'); + cy.contains('This community is private'); + }); + + it('should request to join the private community', () => { + cy.get('[data-cy="request-to-join-private-community-button"]') + .should('be.visible') + .contains(`Request to join ${privateCommunity.name}`) + .click(); + + cy.get('[data-cy="cancel-request-to-join-private-community-button"]') + .should('be.visible') + .contains('Cancel request') + .click(); + + cy.get('[data-cy="request-to-join-private-community-button"]') + .should('be.visible') + .contains(`Request to join ${privateCommunity.name}`); + }); +}); + +describe('private community signed in with permissions', () => { + beforeEach(() => { + cy.auth(memberInPrivateCommunityId).then(() => + cy.visit(`/${privateCommunity.slug}`) + ); + }); + + it('should render profile', () => { + cy.get('[data-cy="community-view"]').should('be.visible'); + cy.contains(privateCommunity.description); + cy.contains(privateCommunity.name); + cy.contains(privateCommunity.website); + }); + + it('should render threads', () => { + cy.get('[data-cy="community-view-content"]') + .scrollIntoView() + .should('be.visible'); + + data.threads + .filter( + thread => + !thread.deletedAt && thread.communityId === privateCommunity.id + ) + .forEach(thread => + cy.contains(thread.content.title).should('be.visible') + ); + }); + + it('should render channels', () => { + data.channels + .filter(channel => channel.communityId === privateCommunity.id) + .filter(channel => !channel.isPrivate) + .filter(channel => !channel.deletedAt) + .forEach(channel => { + cy.contains(channel.name) + .scrollIntoView() + .should('be.visible'); + }); + }); + + it('should render team', () => { + privateTeamMembers.forEach(user => { + cy.contains(user.name) + .scrollIntoView() + .should('be.visible'); + }); + }); +}); diff --git a/cypress/integration/community_settings_billing_spec.js b/cypress/integration/community_settings_billing_spec.js deleted file mode 100644 index 11d92999e8..0000000000 --- a/cypress/integration/community_settings_billing_spec.js +++ /dev/null @@ -1,277 +0,0 @@ -import data from '../../shared/testing/data'; - -const community = data.communities.find(c => c.slug === 'payments'); -const members = data.usersCommunities - .filter( - ({ communityId, isMember }) => community.id === communityId && isMember - ) - .map(({ userId }) => data.users.find(({ id }) => id === userId)); -const { userId: ownerId } = data.usersCommunities.find( - ({ communityId, isOwner }) => communityId === community.id && isOwner -); -const channels = data.channels.filter( - ({ communityId }) => community.id === communityId -); - -describe('Community settings billing tab', () => { - beforeEach(() => { - cy.auth(ownerId); - }); - - describe('should prompt for admin email verification', () => { - it('should show administrator email form', () => { - cy.visit(`/${community.slug}/settings`); - cy - .get(`[href="/${community.slug}/settings/billing"]`) - .should('be.visible') - .click(); - cy - .get('[data-cy="community-settings-billing-admin-email-form"]') - .should('be.visible'); - - // Editing - const badEmail = 'foo'; - const goodEmail = 'briandlovin@gmail.com'; - - // Enter bad email - cy - .get('input[type="email"]') - .clear() - .type(`${badEmail}`); - - // Submit changes - cy.get('button[type="submit"]').click(); - - // should show error - cy.get('[data-cy="administrator-email-form-error"]').should('be.visible'); - - // Enter good email - cy - .get('input[type="email"]') - .click() - .clear() - .type(`${goodEmail}`); - - // Submit changes - cy.get('button[type="submit"]').click(); - - // should show pending notice - cy - .get('[data-cy="administrator-email-form-pending-sent"]') - .should('be.visible'); - - // go to analytics tab - cy - .get(`[href="/${community.slug}/settings/analytics"]`) - .should('be.visible') - .click(); - - // click unlock analytics - cy - .get('[data-cy="analytics-unlock-upsell-button"]') - .should('be.visible') - .click(); - - // admin email verification modal should appear - cy - .get('[data-cy="admin-email-address-verification-modal"]') - .should('be.visible'); - - // click the overlay to close the modal - cy - .get('div.ReactModal__Overlay') - .should('be.visible') - .click('topLeft'); - - // go to overview tab - cy - .get(`[href="/${community.slug}/settings"]`) - .should('be.visible') - .click(); - - // create channel modal - cy - .get('[data-cy="create-channel-button"]') - .should('be.visible') - .click(); - - // toggle private channel checkbox - cy - .get( - '[data-cy="create-channel-modal-toggle-private-checkbox-unchecked"]' - ) - .should('be.visible') - .first() - .click(); - - // should prompt user to enter admin email - cy - .get('[data-cy="community-settings-create-channel-admin-email-prompt"]') - .should('be.visible'); - - // click the overlay to close the modal - cy - .get('div.ReactModal__Overlay') - .should('be.visible') - .click('topLeft'); - - // go to members tab - cy - .get(`[href="/${community.slug}/settings/members"]`) - .should('be.visible') - .click(); - - // should show list of members - cy - .get('[data-cy="community-settings-members-list"]') - .should('be.visible'); - - // click on the settings icon next to first user - cy - .get('[data-cy="community-settings-member-edit-dropdown-trigger"]') - .first() - .click(); - - // click the moderator item in dropdown - cy - .get('[data-cy="community-settings-members-list"]') - .contains('Can edit and delete conversations') - .click(); - - // admin email verification modal should appear - cy - .get('[data-cy="admin-email-address-verification-modal"]') - .should('be.visible'); - - // click the overlay to close the modal - cy - .get('div.ReactModal__Overlay') - .should('be.visible') - .click('topLeft'); - }); - }); - - describe('should force verification of administration email', () => { - it('should verify email address', () => { - cy.visit(`http://localhost:3001/api/email/validate/test-payments/verify`); - }); - }); - - describe('should be able to view billing settings with save administrator email', () => { - it('should load community billing settings', () => { - cy.visit(`/${community.slug}/settings`); - cy - .get(`[href="/${community.slug}/settings/billing"]`) - .should('be.visible') - .click(); - cy.get('[data-cy="community-settings-billing"]').should('be.visible'); - - // go to analytics tab - cy - .get(`[href="/${community.slug}/settings/analytics"]`) - .should('be.visible') - .click(); - - // click unlock analytics - cy - .get('[data-cy="analytics-unlock-upsell-button"]') - .should('be.visible') - .click(); - - // admin email verification modal should appear - cy.get('[data-cy="upgrade-analytics-modal"]').should('be.visible'); - - // click the overlay to close the modal - cy - .get('div.ReactModal__Overlay') - .should('be.visible') - .click('topLeft'); - - // go to overview tab - cy - .get(`[href="/${community.slug}/settings"]`) - .should('be.visible') - .click(); - - // create channel modal - cy - .get('[data-cy="create-channel-button"]') - .should('be.visible') - .click(); - - // toggle private channel checkbox - cy - .get( - '[data-cy="create-channel-modal-toggle-private-checkbox-unchecked"]' - ) - .should('be.visible') - .first() - .click(); - - // should prompt user to enter admin email - cy - .get( - '[data-cy="community-settings-create-channel-admin-add-source-prompt"]' - ) - .should('be.visible'); - - // click the overlay to close the modal - cy - .get('div.ReactModal__Overlay') - .should('be.visible') - .click('topLeft'); - - // go to members tab - cy - .get(`[href="/${community.slug}/settings/members"]`) - .should('be.visible') - .click(); - - // should show list of members - cy - .get('[data-cy="community-settings-members-list"]') - .should('be.visible'); - - // click on the settings icon next to first user - cy - .get('[data-cy="community-settings-member-edit-dropdown-trigger"]') - .first() - .click(); - - // click the moderator item in dropdown - cy - .get('[data-cy="community-settings-members-list"]') - .contains('Can edit and delete conversations') - .click(); - - // admin email verification modal should appear - cy.get('[data-cy="upgrade-moderator-seat-modal"]').should('be.visible'); - - // click the overlay to close the modal - cy - .get('div.ReactModal__Overlay') - .should('be.visible') - .click('topLeft'); - }); - }); - - // as the last test, reset the administrator email - describe('should reset the administration email', () => { - it('should reset email addres', () => { - cy.visit(`http://localhost:3001/api/email/validate/test-payments/reset`); - }); - }); - - describe('should have successfully reset the administration email', () => { - it('should show administrator email form', () => { - cy.visit(`/${community.slug}/settings`); - cy - .get(`[href="/${community.slug}/settings/billing"]`) - .should('be.visible') - .click(); - cy - .get('[data-cy="community-settings-billing-admin-email-form"]') - .should('be.visible'); - }); - }); -}); diff --git a/cypress/integration/community_settings_overview_spec.js b/cypress/integration/community_settings_overview_spec.js index b4b73b0c80..998e51b2f3 100644 --- a/cypress/integration/community_settings_overview_spec.js +++ b/cypress/integration/community_settings_overview_spec.js @@ -10,8 +10,7 @@ const channels = data.channels describe('Community settings overview tab', () => { beforeEach(() => { - cy.auth(ownerId); - cy.visit(`/${community.slug}/settings`); + cy.auth(ownerId).then(() => cy.visit(`/${community.slug}/settings`)); }); it('should render the settings overview and allow editing the community metadata', () => { @@ -24,30 +23,27 @@ describe('Community settings overview tab', () => { }); // Make sure the subnav is rendered correctly cy.get(`[href*="settings/analytics"]`).should('be.visible'); - cy.get(`[href*="settings/billing"]`).should('be.visible'); cy.get(`[href*="settings/members"]`).should('be.visible'); // Editing const name = 'text'; const description = 'text'; // Change name - cy - .get('[data-cy="community-settings-name-input"]') + cy.get('[data-cy="community-settings-name-input"]') .clear() .type(`${name}`); // Change description - cy - .get('[data-cy="community-settings-description-input"]') + cy.get('[data-cy="community-settings-description-input"]') .clear() .type(description); const website = 'https://mxstbr.com/bla'; // Change website - cy - .get('[data-cy="community-settings-website-input"]') + cy.get('[data-cy="community-settings-website-input"]') .clear() .type(website); // Submit changes cy.get('button[type="submit"]').click(); + cy.visit(`/${community.slug}`); cy.location('pathname').should('eq', `/${community.slug}`); // Make sure changes were applied cy.contains(description); @@ -55,61 +51,53 @@ describe('Community settings overview tab', () => { cy.contains(website); // Revert changes cy.visit(`/${community.slug}/settings`); - cy - .get('[data-cy="community-settings-name-input"]') + cy.get('[data-cy="community-settings-name-input"]') .clear() .type(community.name); - cy - .get('[data-cy="community-settings-description-input"]') + cy.get('[data-cy="community-settings-description-input"]') .clear() .type(community.description); - cy - .get('[data-cy="community-settings-website-input"]') + cy.get('[data-cy="community-settings-website-input"]') .clear() .type(community.website); cy.get('button[type="submit"]').click(); + cy.visit(`/${community.slug}`); cy.location('pathname').should('eq', `/${community.slug}`); cy.contains(community.name); cy.contains(community.description); cy.contains(community.website); }); - // TODO: FIXME it.skip('should allow managing branded login settings', () => { const brandedLoginString = 'Testing branded login custom message'; // click the enable custom branded login toggle - cy - .get('[data-cy="community-settings-branded-login"]') + cy.get('[data-cy="community-settings-branded-login"]') .contains('Enable custom branded login') .click(); // should be enabled and input should appear - cy - .get('[data-cy="community-settings-branded-login-input"]') - .should('be.visible'); + cy.get('[data-cy="community-settings-branded-login-input"]').should( + 'be.visible' + ); // type in a new branded login string - cy - .get('[data-cy="community-settings-branded-login-input"]') + cy.get('[data-cy="community-settings-branded-login-input"]') .click() .type(brandedLoginString); // save the string - cy - .get('[data-cy="community-settings-branded-login-save"]') + cy.get('[data-cy="community-settings-branded-login-save"]') .should('be.visible') .click(); // go to the preview page - cy - .get('[data-cy="community-settings-branded-login-preview"]') + cy.get('[data-cy="community-settings-branded-login-preview"]') .should('be.visible') .click(); // custom string should be visible - cy - .get('[data-cy="community-login-page"]') + cy.get('[data-cy="community-login-page"]') .contains(brandedLoginString) .should('be.visible'); @@ -117,8 +105,7 @@ describe('Community settings overview tab', () => { cy.visit(`/${community.slug}/settings`); // disable branded login - cy - .get('[data-cy="community-settings-branded-login"]') + cy.get('[data-cy="community-settings-branded-login"]') .contains('Enable custom branded login') .click(); }); diff --git a/cypress/integration/community_spec.js b/cypress/integration/community_spec.js deleted file mode 100644 index 9bc74fe717..0000000000 --- a/cypress/integration/community_spec.js +++ /dev/null @@ -1,40 +0,0 @@ -import data from '../../shared/testing/data'; - -const community = data.communities[0]; - -describe('Community View', () => { - beforeEach(() => { - cy.visit(`/${community.slug}`); - }); - - it('should render all the communities data, and show a list of channels and threads', () => { - cy.get('[data-cy="community-view"]').should('be.visible'); - cy.contains(community.description); - cy.contains(community.name); - cy.contains(community.website); - cy.get(`[src*="${community.profilePhoto}"]`).should('be.visible'); - // TODO: Actually use a Cypress API for this instead of this hacky shit - cy.document().then(document => { - expect(document.body.toString().indexOf(community.coverPhoto) > -1); - }); - - cy - .get('[data-cy="community-view-content"]') - .scrollIntoView() - .should('be.visible'); - - data.threads - .filter(thread => thread.communityId === community.id) - .forEach(thread => { - cy.contains(thread.content.title).should('be.visible'); - }); - - data.channels - .filter(channel => channel.communityId === community.id) - .filter(channel => !channel.isPrivate) - .filter(channel => !channel.deletedAt) - .forEach(channel => { - cy.contains(channel.name).should('be.visible'); - }); - }); -}); diff --git a/cypress/integration/faq_page_spec.js b/cypress/integration/faq_page_spec.js new file mode 100644 index 0000000000..c28aa37bff --- /dev/null +++ b/cypress/integration/faq_page_spec.js @@ -0,0 +1,12 @@ +describe('FAQ View', () => { + describe('Loads page', () => { + beforeEach(() => { + cy.visit('/faq'); + }); + + it('should render the faq page', () => { + cy.get('[data-cy="faq-page"]').should('be.visible'); + cy.contains('Frequently Asked Questions').should('be.visible'); + }); + }); +}); diff --git a/cypress/integration/home_spec.js b/cypress/integration/home_spec.js index df1be67480..07c54f5003 100644 --- a/cypress/integration/home_spec.js +++ b/cypress/integration/home_spec.js @@ -1,5 +1,5 @@ describe('Home View', () => { - before(() => { + beforeEach(() => { cy.visit('/'); }); diff --git a/cypress/integration/inbox_spec.js b/cypress/integration/inbox_spec.js index f5a6fb65e1..8696043052 100644 --- a/cypress/integration/inbox_spec.js +++ b/cypress/integration/inbox_spec.js @@ -4,14 +4,13 @@ const user = data.users[0]; const channelIds = data.usersChannels .filter(({ userId }) => userId === user.id) .map(({ channelId }) => channelId); -const dashboardThreads = data.threads.filter(({ channelId }) => - channelIds.includes(channelId) +const dashboardThreads = data.threads.filter( + ({ deletedAt, channelId }) => !deletedAt && channelIds.includes(channelId) ); describe('Inbox View', () => { - before(() => { - cy.auth(user.id); - cy.visit('/'); + beforeEach(() => { + cy.auth(user.id).then(() => cy.visit('/')); }); it('should render the inbox view', () => { diff --git a/cypress/integration/login_spec.js b/cypress/integration/login_spec.js index aaff85a76c..cc2adcd444 100644 --- a/cypress/integration/login_spec.js +++ b/cypress/integration/login_spec.js @@ -9,8 +9,33 @@ describe('Login View', () => { cy.get('[href*="/auth/facebook"]').should('be.visible'); cy.get('[href*="/auth/google"]').should('be.visible'); cy.get('[href*="/auth/github"]').should('be.visible'); - cy - .get('[href*="github.com/withspectrum/code-of-conduct"]') - .should('be.visible'); + cy.get('[href*="github.com/withspectrum/code-of-conduct"]').should( + 'be.visible' + ); + }); +}); + +describe('Community Login View', () => { + beforeEach(() => { + cy.visit('/spectrum/login'); + }); + + it('should render', () => { + cy.get('[data-cy="community-login-page"]').should('be.visible'); + cy.get('[href*="/auth/twitter?r=http://localhost:3000/spectrum"]').should( + 'be.visible' + ); + cy.get('[href*="/auth/facebook?r=http://localhost:3000/spectrum"]').should( + 'be.visible' + ); + cy.get('[href*="/auth/google?r=http://localhost:3000/spectrum"]').should( + 'be.visible' + ); + cy.get('[href*="/auth/github?r=http://localhost:3000/spectrum"]').should( + 'be.visible' + ); + cy.get('[href*="github.com/withspectrum/code-of-conduct"]').should( + 'be.visible' + ); }); }); diff --git a/cypress/integration/messages_spec.js b/cypress/integration/messages_spec.js new file mode 100644 index 0000000000..721389e5be --- /dev/null +++ b/cypress/integration/messages_spec.js @@ -0,0 +1,160 @@ +import data from '../../shared/testing/data'; + +const user = data.users.find(user => user.username === 'brian'); +const directMessages = data.usersDirectMessageThreads.filter( + udm => udm.userId === user.id +); + +describe('/messages/new', () => { + beforeEach(() => { + cy.auth(user.id).then(() => cy.visit('/messages/new')); + }); + + it('should allow to continue composing message incase of crash or reload', () => { + const newMessage = 'Persist New Message'; + cy.get('[data-cy="chat-input"]').type(newMessage); + cy.get('[data-cy="chat-input"]').contains(newMessage); + + cy.wait(2000); + // Reload page(incase page closed or crashed ,reload should have same effect) + cy.reload(); + cy.get('[data-cy="chat-input"]').contains(newMessage); + }); +}); + +describe('/messages', () => { + beforeEach(() => { + cy.auth(user.id).then(() => cy.visit('/messages')); + }); + + it('should load list of direct messages', () => { + cy.contains('Max Stoiber and Bryn Jackson').should('be.visible'); + cy.contains('A fifth one').should('be.visible'); + + cy.contains('Previous member').should('be.visible'); + cy.contains('No messages yet...').should('be.visible'); + + cy.get('[data-cy="unread-dm-list-item"]').should($p => { + expect($p).to.have.length(1); + }); + cy.get('[data-cy="dm-list-item"]').should($p => { + expect($p).to.have.length(1); + }); + }); + + it('should open conversation composer', () => { + cy.get('[data-cy="compose-dm"]') + .should('be.visible') + .click(); + cy.url().should('eq', 'http://localhost:3000/messages/new'); + }); + + it('should select an individual conversation', () => { + cy.contains('Max Stoiber and Bryn Jackson') + .should('be.visible') + .click(); + cy.get('[data-cy="dm-header"]').should('be.visible'); + cy.get('[data-cy="dm-header"]').contains('Max Stoiber, Bryn Jackson'); + cy.get('[data-cy="message"]').should($p => { + expect($p).to.have.length(5); + }); + }); + + it('should switch conversations', () => { + cy.contains('Max Stoiber and Bryn Jackson') + .should('be.visible') + .click(); + cy.get('[data-cy="dm-header"]').should('be.visible'); + cy.get('[data-cy="dm-header"]').contains('Max Stoiber, Bryn Jackson'); + cy.get('[data-cy="message"]').should($p => { + expect($p).to.have.length(5); + }); + + cy.contains('Previous member') + .should('be.visible') + .click(); + cy.get('[data-cy="dm-header"]').should('be.visible'); + cy.get('[data-cy="dm-header"]').contains('Previous member'); + cy.get('[data-cy="message"]').should($p => { + expect($p).to.have.length(0); + }); + }); + + it('should send a message in a conversation', () => { + cy.contains('Previous member') + .should('be.visible') + .click(); + + const newMessage = 'A new message!'; + cy.get('[data-cy="chat-input"]').type(newMessage); + cy.get('[data-cy="chat-input-send-button"]').click(); + cy.get('[data-cy="chat-input"]').clear(); + cy.contains(newMessage); + + cy.get('[data-cy="unread-dm-list-item"]').should($p => { + expect($p).to.have.length(0); + }); + + cy.get('[data-cy="dm-list-item"]').should($p => { + expect($p).to.have.length(2); + }); + + cy.wait(2000); + + cy.get('[data-cy="dm-list-item"]') + .first() + .contains('Previous member'); + }); +}); + +describe('messages tab badge count', () => { + beforeEach(() => { + cy.auth(user.id).then(() => cy.visit('/')); + }); + + it('should show a badge for unread direct messages', () => { + cy.get('[data-cy="unread-badge-1"]').should('be.visible'); + }); + + it('should clear the badge when messages tab clicked', () => { + cy.get('[data-cy="unread-badge-1"]').should('be.visible'); + cy.get('[data-cy="navbar-messages"]').click(); + cy.get('[data-cy="dm-list-item"]').should($p => { + expect($p).to.have.length(1); + }); + cy.get('[data-cy="unread-dm-list-item"]').should($p => { + expect($p).to.have.length(1); + }); + cy.get('[data-cy="unread-badge-0"]').should('be.visible'); + }); + + it('should not show an unread badge after leaving messages tab', () => { + cy.get('[data-cy="unread-badge-1"]').should('be.visible'); + cy.get('[data-cy="navbar-messages"]').click(); + cy.get('[data-cy="dm-list-item"]').should($p => { + expect($p).to.have.length(1); + }); + cy.get('[data-cy="unread-dm-list-item"]').should($p => { + expect($p).to.have.length(1); + }); + cy.get('[data-cy="unread-badge-0"]').should('be.visible'); + cy.get('[data-cy="navbar-home"]').click(); + cy.get('[data-cy="unread-badge-0"]').should('be.visible'); + }); +}); + +describe('clearing messages tab', () => { + beforeEach(() => { + cy.auth(user.id).then(() => cy.visit('/messages')); + }); + + it('should clear the badge when landing directly on /messages', () => { + cy.get('[data-cy="dm-list-item"]').should($p => { + expect($p).to.have.length(1); + }); + cy.get('[data-cy="unread-dm-list-item"]').should($p => { + expect($p).to.have.length(1); + }); + cy.get('[data-cy="unread-badge-0"]').should('be.visible'); + }); +}); diff --git a/cypress/integration/navbar_spec.js b/cypress/integration/navbar_spec.js new file mode 100644 index 0000000000..2549e31178 --- /dev/null +++ b/cypress/integration/navbar_spec.js @@ -0,0 +1,126 @@ +import data from '../../shared/testing/data'; + +const user = data.users[0]; + +const coreSplashPageNavbarLinksVisible = () => { + cy.get('[data-cy="navbar-splash"]').should('be.visible'); + + cy.get('[data-cy="navbar-splash-features"]').should('be.visible'); + + cy.get('[data-cy="navbar-splash-apps"]').should('be.visible'); + + cy.get('[data-cy="navbar-splash-support"]').should('be.visible'); +}; + +const checkSignedOutSplashNavbarLinksRender = () => { + coreSplashPageNavbarLinksVisible(); + + cy.get('[data-cy="navbar-splash-signin"]').should('be.visible'); +}; + +const checkSignedInSplashNavbarLinksRender = () => { + coreSplashPageNavbarLinksVisible(); + + cy.get('[data-cy="navbar-splash-profile"]').should('be.visible'); +}; + +const checkProductNavbarLinksRender = () => { + cy.get('[data-cy="navbar"]').should('be.visible'); + + cy.get('[data-cy="navbar-logo"]').should('be.visible'); + + cy.get('[data-cy="navbar-home"]').should('be.visible'); + + cy.get('[data-cy="navbar-messages"]').should('be.visible'); + + cy.get('[data-cy="navbar-explore"]').should('be.visible'); + + cy.get('[data-cy="navbar-notifications"]').should('be.visible'); + + cy.get('[data-cy="navbar-profile"]').should('be.visible'); +}; + +const checkSignedOutNavbarRenders = () => { + cy.get('[data-cy="navbar"]').should('be.visible'); + + cy.get('[data-cy="navbar-logo"]').should('be.visible'); + + cy.get('[data-cy="navbar-explore"]').should('be.visible'); + + cy.get('[data-cy="navbar-support"]').should('be.visible'); +}; + +const checkSignedOutSplashNavbarRenders = () => { + cy.visit('/terms'); + checkSignedOutSplashNavbarLinksRender(); + + cy.visit('/privacy'); + checkSignedOutSplashNavbarLinksRender(); +}; + +const checkSignedInSplashNavbarRenders = () => { + cy.visit('/about'); + checkSignedInSplashNavbarLinksRender(); + + cy.visit('/features'); + checkSignedInSplashNavbarLinksRender(); + + cy.visit('/support'); + checkSignedInSplashNavbarLinksRender(); + + cy.visit('/faq'); + checkSignedInSplashNavbarLinksRender(); + + cy.visit('/terms'); + checkSignedInSplashNavbarLinksRender(); + + cy.visit('/privacy'); + checkSignedInSplashNavbarLinksRender(); +}; + +describe('Navbar logged in', () => { + beforeEach(() => { + cy.auth(user.id).then(() => cy.visit(`/`)); + }); + + it('should render product navbar', () => { + checkProductNavbarLinksRender(); + }); + + it('should make sure all product navbar links work on each view', () => { + cy.visit('/messages'); + checkProductNavbarLinksRender(); + + cy.visit('/explore'); + checkProductNavbarLinksRender(); + + cy.visit('/notifications'); + checkProductNavbarLinksRender(); + + cy.visit(`/users/${user.username}`); + checkProductNavbarLinksRender(); + }); + + it('should render splash navbar when viewing splash pages', () => { + checkSignedInSplashNavbarRenders(); + }); +}); + +describe('Navbar logged out', () => { + beforeEach(() => { + cy.visit(`/`); + }); + + it('should render splash page navbar', () => { + checkSignedOutSplashNavbarLinksRender(); + }); + + it('should make sure all splash page navbar links work', () => { + checkSignedOutSplashNavbarRenders(); + }); + + it('should render product navbar', () => { + cy.visit('/spectrum'); + checkSignedOutNavbarRenders(); + }); +}); diff --git a/cypress/integration/pricing_spec.js b/cypress/integration/pricing_spec.js deleted file mode 100644 index 31aa073cbf..0000000000 --- a/cypress/integration/pricing_spec.js +++ /dev/null @@ -1,38 +0,0 @@ -import data from '../../shared/testing/data'; - -const community = data.communities[0]; -const { userId: ownerId } = data.usersCommunities.find( - ({ communityId, isOwner }) => communityId === community.id && isOwner -); - -describe('Renders pricing page features lists', () => { - before(() => { - cy.visit(`/pricing`); - }); - - it('should render key pricing page components', () => { - cy.get('[data-cy="pricing-page"]').should('be.visible'); - cy - .get('[data-cy="paid-features-list"]') - .scrollIntoView() - .should('be.visible'); - cy - .get('[data-cy="free-features-list"]') - .scrollIntoView() - .should('be.visible'); - }); -}); - -describe('Renders pricing page owned communities', () => { - before(() => { - cy.auth(ownerId); - cy.visit(`/pricing`); - }); - - it('should render owned communities', () => { - cy - .get('[data-cy="owned-communities-list"]') - .scrollIntoView() - .should('be.visible'); - }); -}); diff --git a/cypress/integration/privacy_page_spec.js b/cypress/integration/privacy_page_spec.js index 8f90296829..6e034e2e9b 100644 --- a/cypress/integration/privacy_page_spec.js +++ b/cypress/integration/privacy_page_spec.js @@ -1,6 +1,6 @@ describe('Privacy View', () => { describe('Loads page', () => { - before(() => { + beforeEach(() => { cy.visit('/privacy'); }); @@ -10,7 +10,7 @@ describe('Privacy View', () => { }); describe('Loads page', () => { - before(() => { + beforeEach(() => { cy.visit('/privacy.html'); }); diff --git a/cypress/integration/terms_page_spec.js b/cypress/integration/terms_page_spec.js index 98da750284..c20e032c0d 100644 --- a/cypress/integration/terms_page_spec.js +++ b/cypress/integration/terms_page_spec.js @@ -1,6 +1,6 @@ describe('Terms View', () => { describe('Loads page', () => { - before(() => { + beforeEach(() => { cy.visit('/terms'); }); @@ -10,7 +10,7 @@ describe('Terms View', () => { }); describe('Loads page', () => { - before(() => { + beforeEach(() => { cy.visit('/terms.html'); }); diff --git a/cypress/integration/thread/action_bar_spec.js b/cypress/integration/thread/action_bar_spec.js index 1833f938a1..45a1481b2c 100644 --- a/cypress/integration/thread/action_bar_spec.js +++ b/cypress/integration/thread/action_bar_spec.js @@ -30,36 +30,27 @@ const lockThread = () => { // lock the thread cy.get('[data-cy="thread-dropdown-lock"]').contains('Lock chat'); cy.get('[data-cy="thread-dropdown-lock"]').click(); - cy.get('[data-cy="thread-dropdown-lock"]').should('be.disabled'); - cy.get('[data-cy="thread-dropdown-lock"]').should('not.be.disabled'); cy.get('[data-cy="thread-dropdown-lock"]').contains('Unlock chat'); // unlock the thread cy.get('[data-cy="thread-dropdown-lock"]').click(); - cy.get('[data-cy="thread-dropdown-lock"]').should('be.disabled'); - cy.get('[data-cy="thread-dropdown-lock"]').should('not.be.disabled'); cy.get('[data-cy="thread-dropdown-lock"]').contains('Lock chat'); }; const pinThread = () => { // pin the thread cy.get('[data-cy="thread-dropdown-pin"]').click(); - cy.get('[data-cy="thread-dropdown-pin"]').should('be.disabled'); - cy.get('[data-cy="thread-dropdown-pin"]').should('not.be.disabled'); cy.get('[data-cy="thread-dropdown-pin"]').contains('Unpin'); // unpin the thread cy.get('[data-cy="thread-dropdown-pin"]').click(); - cy.get('[data-cy="thread-dropdown-pin"]').should('be.disabled'); - cy.get('[data-cy="thread-dropdown-pin"]').should('not.be.disabled'); cy.get('[data-cy="thread-dropdown-pin"]').contains('Pin'); }; const triggerThreadDelete = () => { cy.get('[data-cy="thread-dropdown-delete"]').click(); cy.get('[data-cy="delete-button"]').should('be.visible'); - cy - .get('div.ReactModal__Overlay') + cy.get('div.ReactModal__Overlay') .should('be.visible') .click('topLeft'); }; @@ -67,36 +58,39 @@ const triggerThreadDelete = () => { const triggerMovingThread = () => { cy.get('[data-cy="thread-dropdown-move"]').click(); cy.get('[data-cy="move-thread-modal"]').should('be.visible'); - cy - .get('div.ReactModal__Overlay') + cy.get('div.ReactModal__Overlay') .should('be.visible') .click('topLeft'); }; +const openSettingsDropdown = () => { + cy.get('[data-cy="thread-actions-dropdown-trigger"]') + .should('be.visible') + .click(); +}; + describe('action bar renders', () => { describe('non authed', () => { - before(() => { + beforeEach(() => { cy.visit(`/thread/${publicThread.id}`); }); it('should render', () => { cy.get('[data-cy="thread-view"]').should('be.visible'); - cy - .get('[data-cy="thread-notifications-login-capture"]') - .should('be.visible'); cy.get('[data-cy="thread-facebook-button"]').should('be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('be.visible'); cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') - .should('not.be.visible'); + cy.get('[data-cy="thread-actions-dropdown-trigger"]').should( + 'not.be.visible' + ); }); }); describe('authed non member', () => { - before(() => { - cy.auth(nonMemberUser.id); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(nonMemberUser.id).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -105,16 +99,17 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-facebook-button"]').should('be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('be.visible'); cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') - .should('not.be.visible'); + cy.get('[data-cy="thread-actions-dropdown-trigger"]').should( + 'not.be.visible' + ); }); }); describe('authed member', () => { - before(() => { - cy.auth(memberInChannelUser.id); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(memberInChannelUser.id).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -123,16 +118,17 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-facebook-button"]').should('be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('be.visible'); cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') - .should('not.be.visible'); + cy.get('[data-cy="thread-actions-dropdown-trigger"]').should( + 'not.be.visible' + ); }); }); describe('authed private channel member', () => { - before(() => { - cy.auth(memberInChannelUser.id); - cy.visit(`/thread/${privateThread.id}`); + beforeEach(() => { + cy.auth(memberInChannelUser.id).then(() => + cy.visit(`/thread/${privateThread.id}`) + ); }); it('should render', () => { @@ -140,17 +136,18 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-notifications-toggle"]').should('be.visible'); cy.get('[data-cy="thread-facebook-button"]').should('not.be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('not.be.visible'); - cy.get('[data-cy="thread-copy-link-button"]').should('not.be.visible'); - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') - .should('not.be.visible'); + cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); + cy.get('[data-cy="thread-actions-dropdown-trigger"]').should( + 'not.be.visible' + ); }); }); describe('thread author', () => { - before(() => { - cy.auth(publicThreadAuthor.id); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(publicThreadAuthor.id).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -159,10 +156,9 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-facebook-button"]').should('be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('be.visible'); cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') - .should('be.visible') - .click(); + + openSettingsDropdown(); + cy.get('[data-cy="thread-actions-dropdown"]').should('be.visible'); // dropdown controls @@ -175,30 +171,29 @@ describe('action bar renders', () => { it('should lock the thread', () => { cy.auth(publicThreadAuthor.id); - + openSettingsDropdown(); lockThread(); }); it('should trigger delete thread', () => { cy.auth(publicThreadAuthor.id); - + openSettingsDropdown(); triggerThreadDelete(); }); it('should edit the thread', () => { cy.auth(publicThreadAuthor.id); + openSettingsDropdown(); + cy.get('[data-cy="thread-dropdown-edit"]').click(); cy.get('[data-cy="save-thread-edit-button"]').should('be.visible'); const title = 'Some new thread'; cy.get('[data-cy="rich-text-editor"]').should('be.visible'); - cy - .get('[data-cy="thread-editor-title-input"]') + cy.get('[data-cy="thread-editor-title-input"]') .clear() .type(title); cy.get('[data-cy="save-thread-edit-button"]').click(); - cy.get('[data-cy="save-thread-edit-button"]').should('be.disabled'); - cy.get('[data-cy="save-thread-edit-button"]').should('not.be.disabled'); cy.get('[data-cy="thread-view"]'); cy.contains(title); @@ -207,22 +202,20 @@ describe('action bar renders', () => { cy.get('[data-cy="save-thread-edit-button"]').should('be.visible'); const originalTitle = 'The first thread! 🎉'; cy.get('[data-cy="rich-text-editor"]').should('be.visible'); - cy - .get('[data-cy="thread-editor-title-input"]') + cy.get('[data-cy="thread-editor-title-input"]') .clear() .type(originalTitle); cy.get('[data-cy="save-thread-edit-button"]').click(); - cy.get('[data-cy="save-thread-edit-button"]').should('be.disabled'); - cy.get('[data-cy="save-thread-edit-button"]').should('not.be.disabled'); cy.get('[data-cy="thread-view"]'); cy.contains('The first thread! 🎉'); }); }); describe('channel moderator', () => { - before(() => { - cy.auth(constants.CHANNEL_MODERATOR_USER_ID); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(constants.CHANNEL_MODERATOR_USER_ID).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -231,10 +224,9 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-facebook-button"]').should('be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('be.visible'); cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') - .should('be.visible') - .click(); + + openSettingsDropdown(); + cy.get('[data-cy="thread-actions-dropdown"]').should('be.visible'); // dropdown controls @@ -248,21 +240,23 @@ describe('action bar renders', () => { it('should lock the thread', () => { cy.auth(constants.CHANNEL_MODERATOR_USER_ID); - // lock the thread + openSettingsDropdown(); lockThread(); }); it('should trigger delete thread', () => { cy.auth(constants.CHANNEL_MODERATOR_USER_ID); + openSettingsDropdown(); triggerThreadDelete(); }); }); describe('channel owner', () => { - before(() => { - cy.auth(constants.CHANNEL_MODERATOR_USER_ID); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(constants.CHANNEL_MODERATOR_USER_ID).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -271,10 +265,9 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-facebook-button"]').should('be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('be.visible'); cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') - .should('be.visible') - .click(); + + openSettingsDropdown(); + cy.get('[data-cy="thread-actions-dropdown"]').should('be.visible'); // dropdown controls @@ -288,20 +281,23 @@ describe('action bar renders', () => { it('should lock the thread', () => { cy.auth(constants.CHANNEL_MODERATOR_USER_ID); + openSettingsDropdown(); lockThread(); }); it('should trigger delete thread', () => { cy.auth(constants.CHANNEL_MODERATOR_USER_ID); + openSettingsDropdown(); triggerThreadDelete(); }); }); describe('community moderator', () => { - before(() => { - cy.auth(constants.COMMUNITY_MODERATOR_USER_ID); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(constants.COMMUNITY_MODERATOR_USER_ID).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -310,10 +306,9 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-facebook-button"]').should('be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('be.visible'); cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') - .should('be.visible') - .click(); + + openSettingsDropdown(); + cy.get('[data-cy="thread-actions-dropdown"]').should('be.visible'); // dropdown controls @@ -327,32 +322,37 @@ describe('action bar renders', () => { it('should lock the thread', () => { cy.auth(constants.COMMUNITY_MODERATOR_USER_ID); + openSettingsDropdown(); lockThread(); }); it('should pin the thread', () => { cy.auth(constants.COMMUNITY_MODERATOR_USER_ID); + openSettingsDropdown(); pinThread(); }); it('should trigger moving the thread', () => { cy.auth(constants.COMMUNITY_MODERATOR_USER_ID); + openSettingsDropdown(); triggerMovingThread(); }); it('should trigger delete thread', () => { cy.auth(constants.COMMUNITY_MODERATOR_USER_ID); + openSettingsDropdown(); triggerThreadDelete(); }); }); describe('community owner', () => { - before(() => { - cy.auth(constants.MAX_ID); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(constants.MAX_ID).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -361,10 +361,9 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-facebook-button"]').should('be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('be.visible'); cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') - .should('be.visible') - .click(); + + openSettingsDropdown(); + cy.get('[data-cy="thread-actions-dropdown"]').should('be.visible'); // dropdown controls @@ -378,24 +377,28 @@ describe('action bar renders', () => { it('should lock the thread', () => { cy.auth(constants.MAX_ID); + openSettingsDropdown(); lockThread(); }); it('should pin the thread', () => { cy.auth(constants.MAX_ID); + openSettingsDropdown(); pinThread(); }); it('should trigger moving the thread', () => { cy.auth(constants.MAX_ID); + openSettingsDropdown(); triggerMovingThread(); }); it('should trigger delete thread', () => { cy.auth(constants.MAX_ID); + openSettingsDropdown(); triggerThreadDelete(); }); }); diff --git a/cypress/integration/thread/chat_input_spec.js b/cypress/integration/thread/chat_input_spec.js index eed59e8f82..41843c118b 100644 --- a/cypress/integration/thread/chat_input_spec.js +++ b/cypress/integration/thread/chat_input_spec.js @@ -19,50 +19,50 @@ const archivedThread = data.threads.find( t => t.communityId === publicCommunity.id && t.channelId === archivedChannel.id ); -const lockedThread = data.threads.find(t => t.isLocked); const nonMemberUser = data.users.find(u => u.id === constants.QUIET_USER_ID); const memberInChannelUser = data.users.find(u => u.id === constants.BRIAN_ID); describe('chat input', () => { describe('non authed', () => { - before(() => { + beforeEach(() => { cy.visit(`/thread/${publicThread.id}`); }); it('should render', () => { cy.get('[data-cy="thread-view"]').should('be.visible'); - cy.get('[data-cy="chat-input-send-button"]').should('be.visible'); + cy.get('[data-cy="chat-input-send-button"]').should('not.be.visible'); cy.get('[data-cy="chat-input-media-uploader"]').should('not.be.visible'); - - const newMessage = 'A new message!'; - cy.get('[contenteditable="true"]').type(newMessage); - // Wait for the messages to be loaded before sending new message - cy.get('[data-cy="message-group"]').should('be.visible'); - cy.get('[data-cy="chat-input-send-button"]').click(); - cy.contains('Sign in'); + cy.get('[data-cy="join-channel-login-upsell"]').should('be.visible'); + cy.get('[data-cy="thread-join-channel-upsell-button"]').should( + 'be.visible' + ); + cy.get('[data-cy="thread-join-channel-upsell-button"]').click(); + cy.get('[data-cy="login-modal"]').should('be.visible'); }); }); describe('authed non member', () => { - before(() => { - cy.auth(nonMemberUser.id); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(nonMemberUser.id).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { cy.get('[data-cy="thread-view"]').should('be.visible'); cy.get('[data-cy="chat-input-send-button"]').should('not.be.visible'); - cy - .get('[data-cy="thread-join-channel-upsell-button"]') - .should('be.visible'); + cy.get('[data-cy="thread-join-channel-upsell-button"]').should( + 'be.visible' + ); }); }); describe('authed member', () => { beforeEach(() => { - cy.auth(memberInChannelUser.id); - cy.visit(`/thread/${publicThread.id}`); + cy.auth(memberInChannelUser.id).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -73,32 +73,77 @@ describe('chat input', () => { it('should allow authed members to send messages', () => { const newMessage = 'A new message!'; cy.get('[data-cy="thread-view"]').should('be.visible'); - cy.get('[contenteditable="true"]').type(newMessage); + cy.get('[data-cy="chat-input"]').type(newMessage); // Wait for the messages to be loaded before sending new message cy.get('[data-cy="message-group"]').should('be.visible'); cy.get('[data-cy="chat-input-send-button"]').click(); // Clear the chat input and make sure the message was sent by matching the text - cy.get('[contenteditable="true"]').type(''); + cy.get('[data-cy="chat-input"]').clear(); cy.contains(newMessage); }); + + it('should allow chat input to be maintained', () => { + const newMessage = 'Persist New Message'; + cy.get('[data-cy="thread-view"]').should('be.visible'); + cy.get('[data-cy="chat-input"]').type(newMessage); + cy.get('[data-cy="chat-input"]').contains(newMessage); + cy.get('[data-cy="message-group"]').should('be.visible'); + cy.wait(1000); + // Reload page(incase page closed or crashed ,reload should have same effect) + cy.reload(); + cy.get('[data-cy="chat-input"]').contains(newMessage); + }); }); - describe('locked thread', () => { - before(() => { - cy.auth(memberInChannelUser.id); - cy.visit(`/thread/${lockedThread.id}`); + describe('message attachments', () => { + beforeEach(() => { + cy.auth(memberInChannelUser.id).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); - it('should render', () => { - cy.get('[data-cy="chat-input-send-button"]').should('not.be.visible'); - cy.contains('This conversation has been locked'); + it('should allow quoting a message', () => { + // Quote a message + cy.get('[data-cy="staged-quoted-message"]').should('not.be.visible'); + cy.get('[data-cy="message"]') + .first() + .should('be.visible') + .click(); + cy.get('[data-cy="reply-to-message"]') + .first() + .should('be.visible') + .click({ force: true }); + + cy.get('[data-cy="staged-quoted-message"]').should('be.visible'); + + // Remove quoted message again + cy.get('[data-cy="remove-staged-quoted-message"]') + .should('be.visible') + .click(); + cy.get('[data-cy="staged-quoted-message"]').should('not.be.visible'); }); }); + // NOTE(@mxstbr): This fails in CI, but not locally for some reason + // we should fix This + // FIXME + // describe('locked thread', () => { + // beforeEach(() => { + // cy.auth(memberInChannelUser.id); + // cy.visit(`/thread/${lockedThread.id}`); + // }); + + // it('should render', () => { + // cy.get('[data-cy="chat-input-send-button"]').should('not.be.visible'); + // cy.contains('This conversation has been locked'); + // }); + // }); + describe('thread in archived channel', () => { - before(() => { - cy.auth(memberInChannelUser.id); - cy.visit(`/thread/${archivedThread.id}`); + beforeEach(() => { + cy.auth(memberInChannelUser.id).then(() => + cy.visit(`/thread/${archivedThread.id}`) + ); }); it('should render', () => { diff --git a/cypress/integration/thread/create_spec.js b/cypress/integration/thread/create_spec.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/cypress/integration/thread/delete_spec.js b/cypress/integration/thread/delete_spec.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/cypress/integration/thread/edit_spec.js b/cypress/integration/thread/edit_spec.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/cypress/integration/thread/view_spec.js b/cypress/integration/thread/view_spec.js index 15373872ab..2e66e23a57 100644 --- a/cypress/integration/thread/view_spec.js +++ b/cypress/integration/thread/view_spec.js @@ -36,31 +36,38 @@ const blockedCommunityUser = data.usersCommunities.find( describe('sidebar components on thread view', () => { describe('non authed', () => { - before(() => { + beforeEach(() => { cy.visit(`/thread/${publicThread.id}`); }); it('should render', () => { // loaded login upsell in sidebar - cy.get('[data-cy="thread-sidebar-login"]').should('be.visible'); + cy.get('[data-cy="thread-sidebar-login"]') + .scrollIntoView() + .should('be.visible'); // loaded community info - cy.get('[data-cy="thread-sidebar-community-info"]').should('be.visible'); + cy.get('[data-cy="thread-sidebar-community-info"]') + .scrollIntoView() + .should('be.visible'); // loaded join button which directs to login - cy - .get('[data-cy="thread-sidebar-join-login-button"]') - .should('be.visible'); + cy.get('[data-cy="thread-sidebar-join-login-button"]').should( + 'be.visible' + ); // loaded more conversations component - cy.get('[data-cy="thread-sidebar-more-threads"]').should('be.visible'); + cy.get('[data-cy="thread-sidebar-more-threads"]') + .scrollIntoView() + .should('be.visible'); }); }); describe('authed non member', () => { - before(() => { - cy.auth(nonMemberUser.id); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(nonMemberUser.id).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -68,22 +75,27 @@ describe('sidebar components on thread view', () => { cy.get('[data-cy="thread-sidebar-login"]').should('not.be.visible'); // loaded community info - cy.get('[data-cy="thread-sidebar-community-info"]').should('be.visible'); + cy.get('[data-cy="thread-sidebar-community-info"]') + .scrollIntoView() + .should('be.visible'); // loaded join button which directs to login - cy - .get('[data-cy="thread-sidebar-join-community-button"]') + cy.get('[data-cy="thread-sidebar-join-community-button"]') + .scrollIntoView() .should('be.visible'); // loaded more conversations component - cy.get('[data-cy="thread-sidebar-more-threads"]').should('be.visible'); + cy.get('[data-cy="thread-sidebar-more-threads"]') + .scrollIntoView() + .should('be.visible'); }); }); describe('authed member', () => { - before(() => { - cy.auth(memberInChannelUser.id); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(memberInChannelUser.id).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -91,22 +103,26 @@ describe('sidebar components on thread view', () => { cy.get('[data-cy="thread-sidebar-login"]').should('not.be.visible'); // loaded community info - cy.get('[data-cy="thread-sidebar-community-info"]').should('be.visible'); + cy.get('[data-cy="thread-sidebar-community-info"]') + .scrollIntoView() + .should('be.visible'); // loaded join button which directs to login - cy - .get('[data-cy="thread-sidebar-view-community-button"]') + cy.get('[data-cy="thread-sidebar-view-community-button"]') + .scrollIntoView() .should('be.visible'); // loaded more conversations component - cy.get('[data-cy="thread-sidebar-more-threads"]').should('be.visible'); + cy.get('[data-cy="thread-sidebar-more-threads"]') + .scrollIntoView() + .should('be.visible'); }); }); }); describe('public thread', () => { describe('not authed', () => { - before(() => { + beforeEach(() => { cy.visit(`/thread/${publicThread.id}`); }); @@ -125,16 +141,17 @@ describe('public thread', () => { // thread author info loaded cy.contains(publicThreadAuthor.name); cy.contains(publicThreadAuthor.username); - cy - .get(`[href*="/users/${publicThreadAuthor.username}"]`) - .should('be.visible'); + cy.get(`[href*="/users/${publicThreadAuthor.username}"]`).should( + 'be.visible' + ); }); }); describe('authed as non member', () => { - before(() => { - cy.auth(nonMemberUser.id); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(nonMemberUser.id).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -144,9 +161,10 @@ describe('public thread', () => { }); describe('authed as member', () => { - before(() => { - cy.auth(memberInChannelUser.id); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(memberInChannelUser.id).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -155,9 +173,10 @@ describe('public thread', () => { }); describe('authed as blocked channel user', () => { - before(() => { - cy.auth(blockedChannelUser.id); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(blockedChannelUser.id).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -167,9 +186,10 @@ describe('public thread', () => { }); describe('authed as blocked community user', () => { - before(() => { - cy.auth(blockedCommunityUser.id); - cy.visit(`/thread/${publicThread.id}`); + beforeEach(() => { + cy.auth(blockedCommunityUser.id).then(() => + cy.visit(`/thread/${publicThread.id}`) + ); }); it('should render', () => { @@ -181,7 +201,7 @@ describe('public thread', () => { describe('private thread', () => { describe('not authed', () => { - before(() => { + beforeEach(() => { cy.visit(`/thread/${privateThread.id}`); }); @@ -193,9 +213,10 @@ describe('private thread', () => { }); describe('authed as non member', () => { - before(() => { - cy.auth(nonMemberUser.id); - cy.visit(`/thread/${privateThread.id}`); + beforeEach(() => { + cy.auth(nonMemberUser.id).then(() => + cy.visit(`/thread/${privateThread.id}`) + ); }); it('should render', () => { @@ -205,9 +226,10 @@ describe('private thread', () => { }); describe('authed as member', () => { - before(() => { - cy.auth(memberInChannelUser.id); - cy.visit(`/thread/${privateThread.id}`); + beforeEach(() => { + cy.auth(memberInChannelUser.id).then(() => + cy.visit(`/thread/${privateThread.id}`) + ); }); it('should render', () => { @@ -216,9 +238,10 @@ describe('private thread', () => { }); describe('authed as blocked channel user', () => { - before(() => { - cy.auth(blockedChannelUser.id); - cy.visit(`/thread/${privateThread.id}`); + beforeEach(() => { + cy.auth(blockedChannelUser.id).then(() => + cy.visit(`/thread/${privateThread.id}`) + ); }); it('should render', () => { @@ -228,9 +251,10 @@ describe('private thread', () => { }); describe('authed as blocked community user', () => { - before(() => { - cy.auth(blockedCommunityUser.id); - cy.visit(`/thread/${privateThread.id}`); + beforeEach(() => { + cy.auth(blockedCommunityUser.id).then(() => + cy.visit(`/thread/${privateThread.id}`) + ); }); it('should render', () => { @@ -242,7 +266,7 @@ describe('private thread', () => { describe('deleted thread', () => { describe('not authed', () => { - before(() => { + beforeEach(() => { cy.visit(`/thread/${deletedThread.id}`); }); @@ -253,9 +277,10 @@ describe('deleted thread', () => { }); describe('authed', () => { - before(() => { - cy.auth(nonMemberUser.id); - cy.visit(`/thread/${deletedThread.id}`); + beforeEach(() => { + cy.auth(nonMemberUser.id).then(() => + cy.visit(`/thread/${deletedThread.id}`) + ); }); it('should render', () => { diff --git a/cypress/integration/thread_spec.js b/cypress/integration/thread_spec.js index 38e6d68694..27456ffcd5 100644 --- a/cypress/integration/thread_spec.js +++ b/cypress/integration/thread_spec.js @@ -1,72 +1,451 @@ import { toPlainText, toState } from '../../shared/draft-utils'; import data from '../../shared/testing/data'; +import { + SPECTRUM_PRIVATE_CHANNEL_ID, + QUIET_USER_ID, +} from '../../api/migrations/seed/default/constants'; +// Public const thread = data.threads[0]; const community = data.communities.find( community => community.id === thread.communityId ); +const moderator = data.usersCommunities + .filter(usersCommunity => usersCommunity.communityId === community.id) + .find(usersCommunity => usersCommunity.isOwner); const author = data.users.find(user => user.id === thread.creatorId); const messages = data.messages.filter( message => message.threadId === thread.id ); +// Private +const privateThread = data.threads.find( + thread => thread.channelId === SPECTRUM_PRIVATE_CHANNEL_ID +); +const privateAuthor = data.users.find( + user => user.id === privateThread.creatorId +); + describe('Thread View', () => { - // Before every test suite set up a new browser and page - beforeEach(() => { - cy.visit(`/thread/${thread.id}`); - }); + describe('Public', () => { + beforeEach(() => { + cy.visit(`/thread/${thread.id}`); + }); - it('should render', () => { - cy.get('[data-cy="thread-view"]').should('be.visible'); - cy.contains(thread.content.title); - cy.contains( - toPlainText(toState(JSON.parse(thread.content.body))).split(' ')[0] - ); - cy.contains(author.name); - cy.contains(author.username); - cy.get(`[href*="/users/${author.username}"]`).should('be.visible'); - cy.get(`[href*="/${community.slug}"]`).should('be.visible'); + it('should render', () => { + cy.get('[data-cy="thread-view"]').should('be.visible'); + cy.contains(thread.content.title); + cy.contains( + toPlainText(toState(JSON.parse(thread.content.body))).split(' ')[0] + ); + cy.contains(author.name); + cy.contains(author.username); + cy.get(`[href*="/users/${author.username}"]`).should('be.visible'); + cy.get(`[href*="/${community.slug}"]`).should('be.visible'); - cy.get('[data-cy="message-group"]').should('be.visible'); - messages.forEach(message => { - cy.contains(toPlainText(toState(JSON.parse(message.content.body)))); + cy.get('[data-cy="message-group"]').should('be.visible'); + messages.forEach(message => { + cy.contains(toPlainText(toState(JSON.parse(message.content.body)))); + }); }); - }); - it('should prompt logged-out users to log in', () => { - const newMessage = 'A new message!'; - cy.get('[data-cy="thread-view"]').should('be.visible'); - cy.get('[contenteditable="true"]').type(newMessage); - // Wait for the messages to be loaded before sending new message - cy.get('[data-cy="message-group"]').should('be.visible'); - cy.get('[data-cy="chat-input-send-button"]').click(); - cy.contains('Sign in'); + it('should prompt logged-out users to log in', () => { + const newMessage = 'A new message!'; + cy.get('[data-cy="thread-view"]').should('be.visible'); + cy.get('[data-cy="join-channel-login-upsell"]').should('be.visible'); + cy.get('[data-cy="thread-join-channel-upsell-button"]').should( + 'be.visible' + ); + cy.get('[data-cy="thread-join-channel-upsell-button"]').click(); + cy.get('[data-cy="login-modal"]').should('be.visible'); + }); }); - describe('authenticated', () => { + describe('Public (authenticated)', () => { beforeEach(() => { - cy.auth(author.id); + cy.auth(author.id).then(() => cy.visit(`/thread/${thread.id}`)); }); - it('should allow logged-in users to send messages', () => { - cy.auth(author.id); + it('should allow logged-in users to send public messages', () => { const newMessage = 'A new message!'; cy.get('[data-cy="thread-view"]').should('be.visible'); - cy.get('[contenteditable="true"]').type(newMessage); + cy.get('[data-cy="chat-input"]').type(newMessage); // Wait for the messages to be loaded before sending new message cy.get('[data-cy="message-group"]').should('be.visible'); cy.get('[data-cy="chat-input-send-button"]').click(); // Clear the chat input and make sure the message was sent by matching the text - cy.get('[contenteditable="true"]').type(''); + cy.get('[data-cy="chat-input"]').clear(); cy.contains(newMessage); }); }); + + describe('Private', () => { + beforeEach(() => { + cy.auth(QUIET_USER_ID).then(() => + cy.visit(`/thread/${privateThread.id}`) + ); + }); + + it("should not allow logged-in users to send private messages if they don't have permission", () => { + cy.get('[data-cy="null-thread-view"]').should('be.visible'); + }); + }); + + describe('Private (with permissions)', () => { + beforeEach(() => { + cy.auth(privateAuthor.id).then(() => + cy.visit(`/thread/${privateThread.id}`) + ); + }); + + it('should allow logged-in users to send private messages if they have permission', () => { + const newMessage = 'A new private message!'; + cy.get('[data-cy="thread-view"]').should('be.visible'); + cy.get('[data-cy="chat-input"]').type(newMessage); + cy.get('[data-cy="chat-input-send-button"]').click(); + // Clear the chat input and make sure the message was sent by matching the text + cy.get('[data-cy="chat-input"]').clear(); + cy.contains(newMessage); + }); + }); + + describe('Loading a thread with a message query parameter', () => { + beforeEach(() => { + cy.auth(author.id).then(() => + cy.visit(`/thread/thread-1?m=MTQ4MzIyNTIwMDAwMQ==`) + ); + }); + + it('should load only messages after the selected message', () => { + cy.get('[data-cy="thread-view"]').should('be.visible'); + // ensure messages have loaded + cy.contains('This is the first message!').should('not.be.visible'); + + // the first message should be selected + cy.get('[data-cy="message-selected"]').should('be.visible'); + + // only one message should be selected + cy.get('[data-cy="message-selected"]').should($p => { + expect($p).to.have.length(1); + }); + + // the other message should be unselected + cy.get('[data-cy="message"]').should($p => { + expect($p).to.have.length(1); + }); + + // load previous messages should be visible + cy.get('[data-cy="load-previous-messages"]') + .should('be.visible') + .click(); + + // all the messages should be loaded + cy.get('[data-cy="message"]').should($p => { + expect($p).to.have.length(4); + }); + }); + }); + + describe('copy link to message', () => { + beforeEach(() => { + cy.auth(author.id).then(() => cy.visit(`/thread/${thread.id}`)); + }); + + it('should copy link to message from message actions', () => { + cy.get('[data-cy="thread-view"]').should('be.visible'); + // ensure messages have loaded + cy.contains('This is the first message!').should('be.visible'); + + // click the copy link icon on the first message + cy.get('[data-cy="link-to-message"]') + .first() + .should('be.visible') + .click({ force: true }); + // message should be selected + cy.get('[data-cy="message-selected"]').should('be.visible'); + // only one message should be selected + cy.get('[data-cy="message-selected"]').should($p => { + expect($p).to.have.length(1); + }); + // the other three messages should be unselected + cy.get('[data-cy="message"]').should($p => { + expect($p).to.have.length(3); + }); + // the url should contain the message query param + cy.url().should('contain', `${thread.id}?m=MTQ4MzIyNTE5OTk5OQ==`); + }); + }); + + describe('message timestamp', () => { + beforeEach(() => { + cy.auth(author.id).then(() => cy.visit(`/thread/${thread.id}`)); + }); + + it('should link the thread with message query param', () => { + cy.get('[data-cy="thread-view"]').should('be.visible'); + // ensure messages have loaded + cy.contains('This is the first message!').should('be.visible'); + + // click the copy link icon on the first message + cy.get('[data-cy="message-timestamp"]') + .first() + .should('be.visible') + .click({ force: true }); + // message should be selected + cy.get('[data-cy="message-selected"]').should('be.visible'); + // only one message should be selected + cy.get('[data-cy="message-selected"]').should($p => { + expect($p).to.have.length(1); + }); + // the other three messages should be unselected + cy.get('[data-cy="message"]').should($p => { + expect($p).to.have.length(3); + }); + // the url should contain the message query param + cy.url().should('contain', `?m=MTQ4MzIyNTE5OTk5OQ==`); + }); + }); + + describe('liking a message signed in', () => { + beforeEach(() => { + cy.auth(author.id).then(() => cy.visit(`/thread/${thread.id}`)); + }); + + it('should like a message from the message action bar', () => { + cy.get('[data-cy="thread-view"]').should('be.visible'); + // ensure messages have loaded + cy.contains('This is the first message!').should('be.visible'); + + // click the first like action in the message action bar + cy.get('[data-cy="like-action"]') + .first() + .should('be.visible') + .click({ force: true }); + // message should be liked + cy.get('[data-cy="unlike-action"]').should('be.visible'); + // only one message should be liked + cy.get('[data-cy="like-action"]').should($p => { + expect($p).to.have.length(2); + }); + // the other three messages should not be liked + cy.get('[data-cy="unlike-action"]').should($p => { + expect($p).to.have.length(1); + }); + // the message should not be selected + cy.get('[data-cy="message-selected"]').should('not.be.visible'); + // the url should not have changed + cy.url().should('not.contain', `?m`); + + // unlike the message from the message action bar + cy.get('[data-cy="unlike-action"]') + .first() + .should('be.visible') + .click({ force: true }); + // message should not be liked + cy.get('[data-cy="unlike-action"]').should('not.be.visible'); + // the rest of the messages should not be liked + cy.get('[data-cy="like-action"]').should($p => { + expect($p).to.have.length(3); + }); + // the url should not have changed + cy.url().should('not.contain', `?m`); + }); + + it('should unlike a message from the inline reaction', () => { + cy.get('[data-cy="thread-view"]').should('be.visible'); + // ensure messages have loaded + cy.contains('This is the first message!').should('be.visible'); + + // click the first like action in the message action bar + cy.get('[data-cy="like-action"]') + .first() + .should('be.visible') + .click({ force: true }); + // message should be liked + cy.get('[data-cy="inline-unlike-action"]').should('be.visible'); + // should click the inline unlike button + cy.get('[data-cy="inline-unlike-action"]').click(); + // no inline like buttons should be visible + cy.get('[data-cy="inline-unlike-action"]').should('not.be.visible'); + // the url should not have changed + cy.url().should('not.contain', `?m`); + }); + + it('should not allow user to like their own message from inline reaction', () => { + cy.get('[data-cy="thread-view"]').should('be.visible'); + // ensure messages have loaded + cy.contains('This is the first message!').should('be.visible'); + + // the last message should have an inline unlike action + cy.get('[data-cy="inline-like-action"]') + .should('be.visible') + .click(); + + // the message should still have the unlike action + cy.get('[data-cy="inline-like-action"]').should('be.visible'); + + // the message should not have an inline like action + cy.get('[data-cy="inline-unlike-action"]').should('not.be.visible'); + }); + + it('should not allow a user to like their own message from action bar', () => { + cy.get('[data-cy="thread-view"]').should('be.visible'); + // ensure messages have loaded + cy.contains('This is the first message!').should('be.visible'); + + // the last message should not contain a like action in the message + // action bar + cy.get('[data-cy="message"]') + .last() + .should('not.contain', '[data-cy="like-action"]'); + }); + }); + + describe('like a message signed out', () => { + beforeEach(() => { + cy.visit(`/thread/${thread.id}`); + }); + + it('should prompt login for non users on inline reaction', () => { + cy.get('[data-cy="thread-view"]').should('be.visible'); + // ensure messages have loaded + cy.contains('This is the first message!').should('be.visible'); + + // click the first like action in the message action bar + cy.get('[data-cy="inline-like-action"]') + .scrollIntoView() + .should('be.visible') + .click({ force: true }); + // login modal should appear + cy.get('[data-cy="login-modal"]').should('be.visible'); + }); + + it('should prompt login for non users on like action bar', () => { + cy.get('[data-cy="thread-view"]').should('be.visible'); + // ensure messages have loaded + cy.contains('This is the first message!').should('be.visible'); + + // click the first like action in the message action bar + cy.get('[data-cy="like-action"]') + .first() + .should('be.visible') + .click({ force: true }); + // login modal should appear + cy.get('[data-cy="login-modal"]').should('be.visible'); + }); + }); + + describe('delete message as message author', () => { + beforeEach(() => { + cy.auth(author.id).then(() => cy.visit(`/thread/${thread.id}`)); + }); + + it('should allow a user to delete their own message', () => { + // the last message should have delete action in the message action bar + cy.get('[data-cy="delete-message"]').should('be.visible'); + // there should only be one deletable message + cy.get('[data-cy="delete-message"]').should($p => { + expect($p).to.have.length(1); + }); + // clicking delete message should open double check modal + cy.get('[data-cy="delete-message"]') + .last() + .click({ force: true }); + // modal should open + cy.get('[data-cy="delete-button"]').should('be.visible'); + }); + }); + + describe('delete message as community moderator', () => { + beforeEach(() => { + cy.auth(moderator.userId).then(() => cy.visit(`/thread/${thread.id}`)); + }); + + it('should allow a user to delete all messages', () => { + // the last message should have delete action in the message action bar + cy.get('[data-cy="delete-message"]').should('be.visible'); + // there should only be one deletable message + cy.get('[data-cy="delete-message"]').should($p => { + expect($p).to.have.length(4); + }); + // clicking delete message should open double check modal + cy.get('[data-cy="delete-message"]') + .last() + .click({ force: true }); + // modal should open + cy.get('[data-cy="delete-button"]').should('be.visible'); + }); + }); +}); + +describe('edit message signed out', () => { + beforeEach(() => { + cy.visit(`/thread/${thread.id}`); + }); + + it('should not render edit message buttons', () => { + cy.get('[data-cy="edit-message"]').should('not.be.visible'); + }); +}); + +describe('edit message signed in', () => { + beforeEach(() => { + cy.auth(moderator.userId).then(() => cy.visit(`/thread/${thread.id}`)); + }); + + it('should render edit buttons on current users messages', () => { + cy.get('[data-cy="edit-message"]').should('be.visible'); + cy.get('[data-cy="edit-message"]').should($p => { + expect($p).to.have.length(2); + }); + cy.contains('The next one is an emoji-only one :scream:') + .scrollIntoView() + .should('be.visible'); + + cy.get('[data-cy="edit-message"]') + .last() + .click({ force: true }); + + cy.get('[data-cy="edit-message-input"]') + .scrollIntoView() + .should('be.visible'); + + cy.get('[data-cy="edit-message-cancel"]') + .scrollIntoView() + .should('be.visible'); + + cy.get('[data-cy="edit-message-save"]') + .scrollIntoView() + .should('be.visible'); + + cy.get('[data-cy="edit-message-cancel"]').click(); + + cy.get('[data-cy="edit-message-input"]').should('not.be.visible'); + + cy.get('[data-cy="edit-message-cancel"]').should('not.be.visible'); + cy.get('[data-cy="edit-message-save"]').should('not.be.visible'); + + cy.get('[data-cy="edit-message"]') + .last() + .click({ force: true }); + + cy.get('[data-cy="edit-message-input"]'); + cy.get('[data-cy="editing-chat-input"]').type(' with edits'); + + cy.get('[data-cy="edit-message-save"]').click(); + + cy.get('[data-cy="edit-message-input"]').should('not.be.visible'); + + cy.get('[data-cy="edited-message-indicator"]').should('be.visible'); + cy.contains('The next one is an emoji-only one :scream: with edits') + .scrollIntoView() + .should('be.visible'); + }); }); describe('/new/thread', () => { beforeEach(() => { - cy.auth(author.id); - cy.visit('/new/thread'); + cy.auth(author.id).then(() => cy.visit('/new/thread')); }); it('should allow composing new threads', () => { @@ -84,4 +463,21 @@ describe('/new/thread', () => { cy.contains(title); cy.contains(body); }); + + it('should allow to continue composing thread incase of crash or reload', () => { + const title = 'Persist Title'; + const body = 'with some persisting content'; + cy.get('[data-cy="rich-text-editor"]').should('be.visible'); + cy.get('[data-cy="composer-community-selector"]').should('be.visible'); + cy.get('[data-cy="composer-channel-selector"]').should('be.visible'); + // Type title and body + cy.get('[data-cy="composer-title-input"]').type(title); + cy.get('[contenteditable="true"]').type(body); + /////need time as our localstorage is not set + cy.wait(1000); + cy.reload(); + + cy.get('[data-cy="composer-title-input"]').contains(title); + cy.get('[contenteditable="true"]').contains(body); + }); }); diff --git a/cypress/integration/user/delete_user_spec.js b/cypress/integration/user/delete_user_spec.js new file mode 100644 index 0000000000..306c0f906c --- /dev/null +++ b/cypress/integration/user/delete_user_spec.js @@ -0,0 +1,49 @@ +import data from '../../../shared/testing/data'; +const user = data.users[0]; + +describe.skip('can view delete controls in settings', () => { + beforeEach(() => { + cy.auth(user.id).then(() => cy.visit(`/users/${user.username}/settings`)); + }); + + it('should render delete account section', () => { + // scroll to delete account segment + cy.get('[data-cy="user-settings"]').should('be.visible'); + + cy.get('[data-cy="delete-account-container"]').scrollIntoView(); + + cy.get('[data-cy="delete-account-container"]').should('be.visible'); + + // should warn about owning communities since users[0] is max + cy.get('[data-cy="owns-communities-notice"]').should('be.visible'); + + // init delete + cy.get('[data-cy="delete-account-init-button"]') + .should('be.visible') + .click(); + + // should see option to confirm or cancel + cy.get('[data-cy="delete-account-confirm-button"]').should('be.visible'); + // click cancel + cy.get('[data-cy="delete-account-cancel-button"]') + .should('be.visible') + .click(); + + // after canceling it should reset + cy.get('[data-cy="delete-account-init-button"]') + .should('be.visible') + .click(); + + // actually delete the account + cy.get('[data-cy="delete-account-confirm-button"]') + .should('be.visible') + .click(); + + // should sign out and go to home page + cy.get('[data-cy="home-page"]').should('be.visible'); + + // user should be deleted + cy.visit(`/users/${user.username}`); + cy.get('[data-cy="user-not-found"]').should('be.visible'); + }); +}); diff --git a/cypress/integration/user/edit_user_spec.js b/cypress/integration/user/edit_user_spec.js new file mode 100644 index 0000000000..ad7941d2d4 --- /dev/null +++ b/cypress/integration/user/edit_user_spec.js @@ -0,0 +1,55 @@ +import data from '../../../shared/testing/data'; + +const user = data.users[0]; + +const NEW_NAME = 'Brian Edited'; +const NEW_DESCRIPTION = 'Description Edited'; +const NEW_WEBSITE = 'Website Edited'; +const NEW_USERNAME = 'brian-edited'; + +describe('edit a user', () => { + beforeEach(() => { + cy.auth(user.id).then(() => cy.visit(`/me/settings`)); + }); + + it('should edit a user', () => { + cy.get('[data-cy="user-edit-form"]').should('be.visible'); + + cy.get('[data-cy="user-name-input"]') + .should('be.visible') + .click() + .clear() + .type(NEW_NAME); + + cy.get('[data-cy="user-username-input"]') + .should('be.visible') + .click() + .clear() + .type(NEW_USERNAME); + + cy.get('[data-cy="user-description-input"]') + .should('be.visible') + .click() + .clear() + .type(NEW_DESCRIPTION); + + cy.get('[data-cy="user-email-input"]').should('be.visible'); + + cy.get('[data-cy="user-website-input"]') + .should('be.visible') + .click() + .clear() + .type(NEW_WEBSITE); + + cy.get('[data-cy="save-button"]') + .should('be.visible') + .click(); + + cy.visit(`/users/${NEW_USERNAME}`); + cy.get('[data-cy="user-view"]').should('be.visible'); + cy.get('[data-cy="user-view"]').contains(NEW_NAME); + cy.get('[data-cy="user-view"]').contains(NEW_DESCRIPTION); + cy.get('[data-cy="user-view"]').contains(NEW_WEBSITE); + cy.get('[data-cy="user-view"]').contains(NEW_USERNAME); + }); +}); diff --git a/cypress/integration/user/me_redirect_spec.js b/cypress/integration/user/me_redirect_spec.js new file mode 100644 index 0000000000..2f4792c173 --- /dev/null +++ b/cypress/integration/user/me_redirect_spec.js @@ -0,0 +1,39 @@ +import data from '../../../shared/testing/data'; +const user = data.users[0]; + +describe('can view current user settings at /me', () => { + beforeEach(() => { + cy.auth(user.id); + }); + + it('should render user profile', () => { + cy.visit('/me'); + cy.location('pathname').should('eq', `/users/${user.username}`); + cy.get('[data-cy="user-view"]').should('be.visible'); + cy.contains(user.username); + cy.contains(user.name); + cy.contains(user.description); + }); +}); + +describe('can view current user settings at /me/settings', () => { + beforeEach(() => { + cy.auth(user.id); + }); + + it('should render settings', () => { + cy.visit('/me/settings'); + cy.location('pathname').should('eq', `/users/${user.username}/settings`); + cy.get('[data-cy="user-settings"]').should('be.visible'); + }); +}); + +describe('loads login view at /me if no current user', () => { + beforeEach(() => { + cy.visit(`/me`); + }); + + it('should render login', () => { + cy.get('[data-cy="login-page"]').should('be.visible'); + }); +}); diff --git a/cypress/integration/user_spec.js b/cypress/integration/user_spec.js index 0e89f9605d..47c5b44983 100644 --- a/cypress/integration/user_spec.js +++ b/cypress/integration/user_spec.js @@ -3,7 +3,7 @@ import data from '../../shared/testing/data'; const user = data.users[0]; describe('User View', () => { - before(() => { + beforeEach(() => { cy.visit(`/users/${user.username}`); }); @@ -21,13 +21,13 @@ describe('User View', () => { }); }); - it('should list the communities a user is a member of, including their rep in that community', () => { + it('should list the public communities a user is a member of, including their rep in that community', () => { const usersCommunities = data.usersCommunities.filter( ({ userId }) => userId === user.id ); const communityIds = usersCommunities.map(({ communityId }) => communityId); - const communities = data.communities.filter(({ id }) => - communityIds.includes(id) + const communities = data.communities.filter( + ({ id, isPrivate }) => communityIds.includes(id) && isPrivate !== true ); communities.forEach(community => { cy.contains(community.name); diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index dffed2532f..411f1e1434 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -1,17 +1,7 @@ -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) +const browserify = require('@cypress/browserify-preprocessor'); module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config + const options = browserify.defaultOptions; + options.browserifyOptions.transform[1][1].presets.push('@babel/preset-flow'); + on('file:preprocessor', browserify(options)); }; diff --git a/cypress/support/commands.js b/cypress/support/commands.js index a53d20718b..e6cbf89db9 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -8,15 +8,15 @@ // https://on.cypress.io/custom-commands // *********************************************** import { encode } from '../../api/utils/base64'; +import data from '../../shared/testing/data'; +const getUser = userId => data.users.find(user => user.id === userId); Cypress.Commands.add('auth', userId => { - localStorage.setItem( - 'spectrum', - JSON.stringify({ currentUser: { id: userId } }) - ); + const user = getUser(userId); + return cy.setCookie( 'session', - encode(JSON.stringify({ passport: { user: userId } })), + encode(JSON.stringify({ passport: { user: user.id } })), { httpOnly: true, secure: false, diff --git a/cypress/support/index.js b/cypress/support/index.js index 070776b2b5..3749d0ec89 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -17,10 +17,13 @@ import './commands'; before(() => { - cy.exec( - `node -e "const teardown = require('./shared/testing/teardown.js')().then(() => process.exit())"` - ); - cy.exec( - `node -e "const setup = require('./shared/testing/setup.js')().then(() => process.exit())"` - ); + cy.resetdb(); + cy.clearLocalStorage(); + cy.clearCookies(); +}); + +beforeEach(() => { + cy.resetdb(); + cy.clearLocalStorage(); + cy.clearCookies(); }); diff --git a/dangerfile.js b/dangerfile.js deleted file mode 100644 index 0db4d93f62..0000000000 --- a/dangerfile.js +++ /dev/null @@ -1,57 +0,0 @@ -// @flow -import path from 'path'; -import { warn, fail, message, markdown, schedule, danger } from 'danger'; -import yarn from 'danger-plugin-yarn'; -import jest from 'danger-plugin-jest'; -import flow from 'danger-plugin-flow'; -import noTestShortcuts from 'danger-plugin-no-test-shortcuts'; -import noConsole from 'danger-plugin-no-console'; - -const APP_FOLDERS = [ - 'admin', - 'athena', - 'chronos', - 'hermes', - 'hyperion', - 'api', - 'mercury', - 'shared', - 'src', - 'vulcan', -]; - -// Make sure people describe what their PR is about -if (danger.github.pr.body.length < 10) { - fail('Please add a description to your PR.'); -} - -// Make sure the yarn.lock file is updated when dependencies get added and log any added dependencies -APP_FOLDERS.forEach(folder => { - schedule(yarn(path.join(__dirname, folder, 'package.json'))); -}); - -// Log test failures if there are any -jest(); - -// Make sure nobody does a it.only and blocks our entire test-suite from running -noTestShortcuts({ - testFilePredicate: filePath => - filePath.endsWith('.test.js') || filePath.endsWith('_spec.js'), -}); - -schedule(noConsole({ whitelist: ['error', 'warn'] })); - -schedule( - flow({ - // Fail on newly created untyped files - created: 'fail', - // Warn on modified untyped files - modified: 'warn', - blacklist: [ - 'flow-typed/**/*.js', - 'public/**/*.js', - 'api/migrations/**/*.js', - 'cypress/**/*.js', - ], - }) -); diff --git a/desktop/README.md b/desktop/README.md new file mode 100644 index 0000000000..b56bf0f97b --- /dev/null +++ b/desktop/README.md @@ -0,0 +1,19 @@ +# Desktop + +This folder contains the [Electron](https://electronjs.org/)-based desktop app for Spectrum. Electron renders a web view, which renders the main `Spectrum.chat` webpage. + +## Directory Structure + +* `release/` - the release build for each platform (mac, win, linux) +* `resources/` - the resources folder (icons, images ...) +* `src/` the source code of electron application + +## Scripts + +The project uses [Electron-builder](https://www.electron.build/) to package & build a ready for distribution Electron app for macOS, Windows and Linux with “auto update” support out of the box. + +* `dev` - run electron app in development mode. +* `package` - Build unpacked dir. Useful to test. +* `package:` build electron app for target platform (mac, linux, win). +* `package:all` - build electron app for all platform. +* `ship` - build and publish artifacts (to GitHub Releases). See https://goo.gl/tSFycD diff --git a/desktop/deployment.md b/desktop/deployment.md new file mode 100644 index 0000000000..ddad938250 --- /dev/null +++ b/desktop/deployment.md @@ -0,0 +1,25 @@ +# Desktop App Deployment + +1. Packages the desktop app for all operating systems and upload them to GitHub as a draft release with the following command: + + ``` + GH_TOKEN=xyz123 yarn run release:desktop + ``` + +> Note: GH_TOKEN has to be a valid GitHub personal token. You can get one from your user settings. +> Note for employees: You will find the company GitHub token in 1Password under the entry for "Spectrum GitHub Bot" + +2. You'll then have a draft release on GitHub that looks something like this: + + ![screen shot 2018-05-30 at 15 42 22](https://user-images.githubusercontent.com/7525670/40724411-4b5fe9ec-6421-11e8-8e4b-d0df96b46f72.png) + +3. Go to that draft release, download the build for your OS and test the new version locally. + +4. If you're happy with how it works, click the "Edit" button on the draft release and you get to this screen: + + ![screenshot-2018-5-30 withspectrum spectrum](https://user-images.githubusercontent.com/7525670/40724488-77256642-6421-11e8-9911-999ed0fa5957.png) + +4. Fill out the title and release notes of the release (like it's already done above), then click "Publish release". + +As soon as it's published, the app will prompt existing users to upgrade to the newest version and download and install it for them. + diff --git a/desktop/package.json b/desktop/package.json new file mode 100644 index 0000000000..9b90303af7 --- /dev/null +++ b/desktop/package.json @@ -0,0 +1,99 @@ +{ + "name": "Spectrum", + "description": "The community platform for the future.", + "homepage": "https://spectrum.chat", + "author": { + "name": "Space Program Inc.", + "email": "hi@spectrum.chat" + }, + "repository": "https://github.com/withspectrum/spectrum", + "version": "1.0.5", + "main": "src/main.js", + "private": true, + "dependencies": { + "electron-context-menu": "^0.10.1", + "electron-is-dev": "^1.0.1", + "electron-log": "^2.2.17", + "electron-updater": "^4.0.6", + "electron-window-state": "^5.0.3" + }, + "devDependencies": { + "electron": "^3.0.13", + "electron-builder": "^20.38.5", + "nodemon": "^1.18.9", + "rimraf": "^2.6.3" + }, + "scripts": { + "dev": "nodemon --exec \"electron ./src/main.js\" -w ./src", + "prepackage": "rimraf release", + "package": "build", + "package:mac": "yarn run package --mac", + "package:linux": "yarn run package --linux", + "package:win": "yarn run package --win --x64", + "package:all": "yarn run package -mwl", + "package:test": "yarn run package --linux", + "release": "yarn run package:all --publish always" + }, + "build": { + "productName": "Spectrum", + "appId": "chat.spectrum", + "copyright": "Copyright © 2018 Space Program Inc.", + "publish": [ + { + "provider": "github", + "repo": "spectrum", + "owner": "withspectrum" + } + ], + "releaseInfo": { + "releaseName": "${name} Desktop App v${version}" + }, + "files": [ + "src/**/*", + "node_modules/**/*", + "resources/**/*", + "package.json" + ], + "directories": { + "buildResources": "resources", + "output": "release" + }, + "mac": { + "category": "public.app-category.social-networking", + "target": "default", + "icon": "resources/icons/mac/icon.icns" + }, + "dmg": { + "background": "resources/background.tiff", + "iconTextSize": 14, + "contents": [ + { + "x": 158, + "y": 213, + "type": "file" + }, + { + "x": 385, + "y": 213, + "type": "link", + "path": "/Applications" + } + ] + }, + "linux": { + "icon": "resources/icons/png/icon-512x512.png", + "category": "Network", + "target": [ + "AppImage", + "deb" + ] + }, + "win": { + "target": "nsis", + "icon": "resources/icons/win/icon.ico" + } + }, + "resolutions": { + "event-stream": "3.3.4" + } +} \ No newline at end of file diff --git a/desktop/resources/background.tiff b/desktop/resources/background.tiff new file mode 100644 index 0000000000000000000000000000000000000000..a0c1b44dbc0ed6f977079e82b9e762a6ca2b0842 GIT binary patch literal 53084 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¬Bf&Bn30NTIffod%{x!(wxK&pn$?jT*z) zL%sjBY)H23a&*c{d-y_9d!S6w!ayl(v#DrpF}fw}Y<4SMG5Gbd8AVO36)nk)h@r0} zDwbsIlSPB3$!_Ip3u^8Vu1Sv9SZY1kNlq|;LAnw9h>#lmNm~vyt+Lo-UnI53-ElHo zY=W3b`<*PQ;!+^9dAgl-r?_gD#Y~HTvQWb0qS`iy*mD%TkaqneBKs^@L;cS}oQRBx zj}e(rwSh|?0KoH~tfZK_b$A6N_-_^h!u*AhgaZP{sTf!Gm6xH*FWZwcZ-YS{KMP$Z z^(n)|#k(hU9>66QA41s$6hoAD$%f274cKv+rVvx(qRvxw$2+!(3S5hY3s8~J`=w5q z;kYvfhTl5(qtt(8zD&k8?Z)oOM{6_d@DisuI@?;wG6yyDL?f^aemXn$+KKwmjnKV4 z;buNQbWu5mOYH7yq<^3G|n{?{uI7T$+aOivFI z!8I^eWecnoG|kg<*_By(xjIFWLBuU2oMbmMdGE%HEJ@6-FZ;p9!$n46&49sq0Gbmh zbRtndjwhH^&QQTYuL|ZHHf7usfSc>HeG?PMMkhld&T|p9aF>iux2pn+soL7CK4UdD z#Cwwjj!>l>OwVFG6tJ&yEQe9W1)HAe{+CevqiYew&+%V0+MF>kZi|y++wV-<`EEkz zNvk+-#6m?4H(?j0xOmX~Ti7?S*`A(9yCFMm)(JA!AXPDd2vInr-EnW&U;2#mrHu2Q z<49-`Llb(fwuU@H=hq)R0XXntjxLc5RQxL7CTNl4eNu!TKBrs0Cu>$J_Kc66kO-Qj zp{iI7wsg0R;be+lHV`6=WMe;4!N=RwG8G8tFG)7L#uew-skd$=lt`B*YkMj`Pmed= z=u~#(Z;=1`nifD|WUF4up(wg0o4Y-;TlMO{bg;KO!nrKPYV=e|rn0N~UsbaaOB|!K zvKm;#*S_lN5fjjb4sgJ%DqKl+DJKC$&p32+K3uN0b?pXl9REY<3z4$~sGM_e5P#%Y?Mvn&rSfs;AbH`vR4=Ll!6RzTklOdizc_qG-_^59{^4HXn6;1cF%@fEX`8cZJB)tO%drd$khlZS$`l!dv?4&>6qq z_eP&%$yr1_V?T?|s^;PZFLqo!Q+P+RB3`gh8JyDF_l+@2$#g^+N;oPsm9<@B6Q4h% zNNoTXSUkhfup4Co7a5W>2er#;1$LY{kEpCCQvqbj4Kh(egF!~(JNHB4!aBWZK3`k0 zy>TRfN!HE1L5}fVe1=Gll7#X7&b~O9i}UXERT93DQAs9!yz3?y#jnb1;{HQUjLo7l zb6uSpc>O7SbLicWDOS4^(J+1cZwHmduk&}vXwC* z$FEH;)hK8pKJ#*Kp&%{G*Ym@i%D+^g-zRvhl(=U?Z-t?HOW_>2SNpeP+}>tEAMsNT2_QO zXq32}I8EZO6vT=nVpO%r?S#K1LK^X!{#Y$~kbY!5^OVUBy*hsJAL47*#pNwClu&Hf za$D|ZUfD|>edV&f5zCOWH2JBe4N9c5hC+O_b%I>$wmm&c0&5a8{zd7o>L?3ALrlva zf1d`^RM_`J@rhV80TveGQE9^_GmG%R$>O{@!5s$XdrtBDuv*_cV*J8{^vlP3Y&{iU`@w$)N4PWY zROVA|QZD3?n~@a|OW7#Pm{A3k@N@n%qO0O5trW^8N=P{L z?CjR$VbK@<8kN@>C#~b2%gX?!KM-=7!fvRrdEPE1J^f%9cZ9+di?zwnVBNoIhL zSoX7*ub>AJet!N}bzg(fRX_{dI&^^yB7(4Q6dI*s@F8rZ+T=qiJ%+6|_kM#%0!9vO zhSGg7U+(P|8fKZFb9bO-(-**Vdb&cjJ}4R?-k+*EwQHo!7-ITcO?DK4M<-#_xzb%j zc$d<8MV3*a!c2INfy7X#sjU5;VL4{Vy+|*ElkKekgT97+_cY5R6M|k8A6dgjxYkEH zVERcZQk&EC=i|moBOPrtk#N^GYru@$BtmDfe5`S-B+s6}Pl3|f(OAG5o`?2$ z&cc3=2_$4n{q9BU@^dFvKwh<1XKdj|$0USXk)6>Ln9PrJBOay6ElQ_rB6G)RhvhYj z^++53{1{?RRaySo*;Wg;Fw<=c1; zZ%xV=p!d_Rd2>UF`SrZTmbG=H17cf!C04>20oUQma|yDgidyU7wndbTu-n_bp9!w1Pp|MqH`?Sk<|D2%T?k%l~wD0_F?yMKKo&(!` z4i4q2`KN4h^jxitL~VS&`VNBCq(wWKo*;68;Pf$ z7~ZZ(;Qgh=>$;Mu!<-E@v;?)EH&H1y4k}dSuxSK?IGvO<=?7sq*T-~TTIxO2 zDZL(0&@k|G`}O&ueEgno*OpN(&3D}JB8C~Nve=WdxI*?^031F`vDa3dZXW;^?kkHT-3VQV0MkR+e7xdrer`I zSxq{5@HynY$RZVSudiBsML|>BE9lqjCh2re@%k#~LrmlfSBn-z8_)|($U~QPZbvGP zo6;KF8k{t@nAVCvfk|;>Tk_P`k!OXbwYM(zIS(*Yws^mmtjp+m68wa4>TC$wdegH zM-XT%^n?M(uS9@vigalu5hb_LqnO5uTadQW$VsayiaRXvpd)SV-^O8fCnXI#rG?e*2hSHX_m>pLq|6=Dn z>S^Y}6<9sS$gAdio7Xs^sZx`_i+}jayGw_XoDr=qK5Ib}Xt+r4-a$NVxzs7 zd$-1Hfa!5xHgwtf_9v$|#dpqc{u7jMg+xV-3|#VFHWedvK#k~e9MZ;lJ&_i<&l5?@ z`k89^%%P0vKyWo^_qA(~fDNZb5S|eczRK8#RyDS9Z3xM%y5f*Hu>}i6mH?R0qOV@TZI8FlCvN1s-FBTywIP zg9KN=66%65Wc$Hqe=6?<_;DEM-}t*3eaZP+`A*?&Yv-!lRKZ1|3$6O9cLy!uV~Q%y zbpRH=?^l#Kf&mkJXT}*GNiU5gMU4IOMO`*xJ2)*14b+X|<^1!xkJO)2ly4FD<4!C< z>3hpNcckW^04j86X{^!TQr%xzoq}RSYm0YnXVkucm2P^!A}YHtB7&!`w4HH2d?_uo zq7kSb+2a>VP$8qJ*jFkjEvU>8V*TB&M?!d1hDq*q zkPY;`A|Q`+ETtkXWo$NAKF0_*vUh47av) zXz)1ay)3tF#mm7ADv#0eR}A0$6VmBcLsjl3q;9oec;`Ezy+d{!Pz5p_bv`p<@pUs zOsUzOSJZg6X?=O^9t;pZ3pH_ecCT!zx;#2>>U#R^>FN0_-xT?ogjkrOW4E`b+^(Sn z8X2k-M)k7Wd+Un*dj~0oiz0%NJnRB7I|+%USnW)b6AuZ)1_phPV!|Vvt>jtJVfr&4 z-@+tMWZ;E_%{~<8nQqA;98Kml0Y|IKdONJyX2zccZ*PP-BN_=4|g{nm; z*8Re&qfY1Ma(0Racx|a+DD~SUfiGc#%+6<86#dSBqsUk5o9;GuAx(Ny^Ygy$4(``o z-bb1~*YZHGd%wrt9~7=|k2qMEw+@D=S)o+o>-EtqfGza2YZM+hayMZ0Tb@YQ!;((d zb(SYv`%!+y>I-VX@aV5r4ZYXVX)DBykcawvHKj&v~u9>d-~QI=C>dabY@cMtDD6KMwROP^61h zl&(RB%rR|D#yVrgKXx@=jSJB|E2Lp{QhoEx2vIhFaf`m^}-@9ly|0u6!+ zyXKetlU3iX^hSfnyr!O>&XemZuct}h7o{$;rnlymg2x*SKUX;X4;Y!B*M>0wRFrCh zTJK&nfeN|iJN(m>0@@N$9><^BjKCsxhQw3z4)RwrY zRg_g0()XzJ&CchP03PBF1R?X<%o$_=SCn$ zHz)w5kZZ!4?RWDw?+Oa$L9aXSA$%emD;IOnXU_`o`b!A~Y~BFsn)B%=!{g53AAs-k z=CTESNnBSP3E;c&#Z92=JEu}`nGJqDp0)FQZu__1@@Hs(nYqY}MzzN1fpm4+K#y4@ z`>|hdcmCSU8lEGKjSCN}7;*Ai*UuHX=zAP8IJODD{uC*Y{AupXPmsH93bFn`M#P3r zE$+{A3uJ}9>HvF6u>c3Azs|aG^YsBl8Qck+(WR*-Jp27c+ zNL`*55UZrUVh-l|!d4VdV59+qrs2H|w+a^~ulg{)R-o``zKKV?J~g!=A1zpYe_NFO>399D|hz16^NiCPFEuBSFmlK}FKdEngd9;#i9jBMQpvv(-aP;@kIi3ofDAASe?5eGXIJZrdvBtAeLR2c27Nlf*(^hpL5cncMq@qN}D%NZC4p@Pk|!% zBKps}qFa@L1P30gQnmn}M*o|u0j2i0Y3WWkSZjuUhvR2l26b9oc=Sza;&A>2go?mg>BtZp+rM>5b&|O36CZg^mZ%Mdx>8c#ROS@Kz@Ba+F}_GiD(2)*H#*`auMDqij2^ z=cw`ZWZs~uE`6g*OUT=TiA9=F@}|uh^>b#TX3lS7>^(9*D%uvTr4qsCN~6`S)zA-S zb`<}?rZZ9$;ls!_f-N+SKj6x+nu=jE`gEK`*6ZFmhz&l&#wz6JdDk5r?j%H>%B_aj zp2D9oMIhLEqvm?hiXn3Ey~)cbl+7!hiVEg-$n(yjj_>P(&SNsHY9^;JbYxy$MNnU8}blTF_g!hJXA!;0N;?uGnZVSoy4^l;h!6n;w@%_$H{b z$n~e73e7ouyXf8W>=V=xu}JVtH(3C=MoJfhsK9UeKXwkzcy<9ZZWBw@c5M4E+3HN^ zV*Q}y+wGi8b!kt=iCaARmIVZ<3MfvmTUs>aA}w0V#+{@ReN(7fYgOdUU>` zu;{%QX$K-Uik*MA0SJ%v!@c-V5)lZ$s-TF6 zwG|m1T(wrLA(!V|H$SRYP7jHXLY_#211_r(4Ledhi=)Z?PM58fG1E;YTHz)yqPC9P zQs3|(*2xqjO35)#klQsDyeif7slkDX_mN$zc|(uYT2KqF_GvwuPsWfA8z9#k6i-UL z!N8}jeM>A*p9KnX;UoRv5b$NG>uH1H?FL08GzU8PsZ$-cgxB3Svqg0@*vQ!c$029B zNzsk~h;~Klcz(D{(yKl^)?A0Qu7-w&O1CZ}ZUMqK9eR|VpI0Xweji^xI zHB=@CBI38w=4$L5Ah_F9NB1$rVT&<){L9nSS{EOOr>d#WIsjea})@|M(OU3n23oonY&DFB;`YG>&#E|&);L=}ax ztn6}a2O9%`q+I?*rGb*2QO@`3Rqj$M?rK+)Ke^;@E1;HDmTE%(nU!&Q&|;cQRgebB zs64NQ)tmERs415GVFN1+obLbUznK51u)s0=oIMn6u0H-%YHa+JR7T0m>fg@ap!`K` zbOVtg?eMCbYYFnm+T^QGcdkBTX7eJinu{YMx3LDdNejmr-eL^d;@3*@*S=RNJ_j$C zFCc>`)6RM6;ZuBcyE=&V&&ufT!J71%1f~K{4iqd%uw%%a0#&jJB&ypnhSr!{ zEgdBYC=eR@PGc#_vF6BYC_v^~oZsCM{A&0n9MQ%S#R(jF6z{*UI;DP6mHSWC%#?nr zl=l09_N^l(l*P?}Gq3-*yUYLqbxrs5;4v5!al_OdXfFA{J8ndUIlD=?xXhB z6FJ(=wO>ep z_&*CfTg+;M6Bakz#-cN%rJJ@C{SwYGPJ&8gdpvrjs?82Xmr!jJT+v-Hf$DWA_!Z$T zPgfJ-ctWqA(pYf@TDa$wGyMbqFy$rc#&{3P0DB|_%Y+o*9l&-wW@Z2VXmEEPvk8v= zJ_GtxL~T;Yv^@FiX8?4HCgxKl#t#w2N{?}BPi*{%O=D#iysEIpTjgPAu!HCgO6$tu z+3|0;DPz503_vrU4}%A+FQ(`!IUB~H=e&Mjb7I$Z3OQ->xguuuYH4N}@x5jYV&&21 z6+9Pl*m?8^c3z5f9rA8G>^4KFf41-7LPkNWzNBA{RxUByS*YUy?_`tK%b2TgG#aeNpa6sBCEdtwvI zfwj-T#vSh-1L-S5ML6}nkL&2m3HJEm8kag1Iw1b}`Zc(Aa=k_5&GlE75)DB}WbJ4M zFHAzw3>IaiD!_A|UNtq+ii)Jnt6nX6STmD1n`&QczG}KGNwS;;P&bMil${|@=%ax*!D!X} z8Z;zJ^{TDn)SyFQqx`hb_n@ff=jIJf)i(|L_lZB<=IU;fY}43rFU>C}`?A_uc2f zS~j1g7dPx@bru(RY_!qrQX&QgNUVyh47wEDc8A zdjFH{%S{+!W#+_7kljzUoTjI(r&hDX z?bS}x@2))9U~X90dfiw#U$IBwtnYhzZ`B5~0upb4tMriM9_XJ|+O+g5I%^-czxv*y zbnSLsY`z_#O-o_1xOJgn9nMo!eY?F2I}yE%;?_eWvD?u2xL7N0{Z7z8_W_d_ez6$< zUX-VL)5~t9H<?WZ&-( zBH0wtK4yczu_{7C#lyL=Ti1tmf?s_tH1R%i_q+cD2=P zXea->X9>Gbq`xhRk8iFfCKpTja@qb#|90@nQeN=MROfOvKWVapk+CXo@Xwa4{tj)1 zRr_!A7^1wWOK4g=k>Zh>2IFkF1)LM)*3gBBjl^m3ZJzj5O?KTM)pBb(wL3~J!?H`P za1sdPl?))9jlfUrrHPb5JzAm6@EYUHnI6CN9#ffX(oT|1E!5YikzexJ7v*Oyn{$7xqzV=Fn~ zq~buMYXnCGK))C<<-cF;=f2Ys23`xNz{kkxYHj;R1%RfTEqr$iTJGcYAsi9uZ@ZTe zyS6h5pUti}l11HjwAnXF3MA>k4g*b9TU0Y{b(c%PZDoYhE3s(ova^hK*=m=^j2+nD zwmenKk|smiA2W>!VVHB;{|5P0e zn9%>SiZeg8KvqW58?~kXa{{6k5iSpMt_}h-LS<$^$u$rOeNIIuz3&8#L*<=3`J<%Z|@JgdWCM;YI-dw;8n_UFJiks4Y311itvtf8!|L92o#)Q_%y zkKSV~Edwp|6ux^G4;>Ff!qA`IQz(lJ2knpE)3_xcBPr0fk*l$dTw^gZTJ_dMs#t*NnmW&8dh{){xr z^T)>5S|VL2Z#n+-lrlNC{RGbwcsydZ%z$4}hAUs|Fr3v_c(spdd{EfF*iK0j#4=M< zt+Hjq&_nxeY-Z?9c0nWkbIvFOzNoB5WK2eYA{oltu%Dq2~R#%1&w1HdIY z&u*?;`CMcVc>YX!bY2EU@hp$4dH3gewQ`a-;HJacgsssHrFuR6HdX(3m5VOS!uR@{ z?IOR~Jb%i8x;aks-gN3;A9E+H_ii*VP)X=&8~Ao|`En8J<6ae3ZSTN6)U$)kqIW0u zpy^``FOwo)emiN;T$RdsiYI(bK;q}i-(4Y;w}}Wt29UN|c-LaDUBz_W0lVD6w3-`V z{uAU2VmBLpQ&+o)^cs}+VwtR#*-d({!Fzvbz_I;CaHvr};Qn-c)bhhWf_=2Ox}AZf zCjiFI@BfF>QJTuJIbO`iM}yoJE#@+Vo$W|FEuo3OkB-I!qZYzZFq;YA3X@iF8bayITw!;z>Kn@E0VpO&uYKqnw{aNguiCVZ;}HS5TyT z^nP|>334yd;jJ!-hix|IzY$tXHIWX%2;X8923=BAx6GqL=$~I8YyZ4PK zm8Tf2@dc`0kN!|!d2LPg_)Fe(?Ou9q{@8?8Zr&Y?RdrrOuQuAOBb`7TdBJG8ld*{2 zal{eKfx1thLJqVK^m35!IjA@Ib7bl_jH9a8 zb_B|3$0>>Fx+{bD%1S}U@RM&Aw-{lc2c^0d0I{xc{CmjTW-UaN;X=F{kk*^cJF-pR z=kqG}-3*tD)}!bptjn*!Mx`=+D~v49;bwi#!30&1Do&IqW8^v4)8%zlijNO z>!%Fq5aj)Q>ZUD7@^nNOcgX1ssb35-`L*b<8~JJEbRU; zL;+p8gsu2|2{Sk`+D%#|;EW`n+AXROemA6~q$IC@%NNgSAeeV>l6nhb&?oK8{dwj? zeZ_^}xtNC`O5>5>m1}X7bqUSZY(I$Ua(vK&^gw%juQ(kH*5AwhLqrzwV zkf~{h-yZ5deKQL5Ug-wFu=P3~{nC7{g-Xb=Lq%epNIOftr+00;A8r)W0UUBf!a0!FP_p!m*62pE{-r`h9zL|L9QwH&a{#`Va=!!%@%9wWhpD-53 zE^NSq)7IPrpCx%O0bO8Zp1Glu)1!hNtayD<*L&A}am{|L2r^dW*=V=vJUx7LaPIAm zU;@dBmI@6)=D9}W;YGFn&??4&m%-jNI||S*C@6IpmM(@*9KhE6umxKa=kDU;rL}ue zocNxTXt&vUwYD7yCU=9Tb%Z_$-)`MJ_`F{1{`r&e=tLfo_9v>8?~nz8W7-oqlz%_9 zn@D?me-!BhX8^&UeT*O4Mlk&DC#d|l`{%HG-X9Z6#2geSp~>D*p`9Rc9AaT$do8>$ zJqB#+ZDy>BtnGC$qe;(x+Dym5;rPeldFRomL%Y?^Q&^?WpbI7_%J%g5oZ^^*kT6cc z_&v<3RLKV_MA(Bic#DS-swZJ@O)Lh;kcus*Qtu=t{y@MgiudI;C@5d*R}wm0U(e86 ztZt@(Ry+^=+t)v~&SsV_Ep`PpOdROZDy5OV#)eXPY! zRKIGpE8_I@IKh_yY4=X9EN<&-<_DS*Lr0Pbs?xuoWYUS^b_tXBU3C9VRg5t+M^^lX zgq)3*djV9pASVHWVLH0{`l%TPv7qJg$*jyQvYdD8m27^Wv!ni+T@-q{n5pBHLwqP&q996qX2G_&p(H|lF!v# z)=Bp(YUn^&f;9;MeaQ}xTXXfkq@pi;Zz2W>zhHR~+jBu~NhCdna!GZZ^x1l@xep`R zJDn`tPVZl{+So|rDJ01WlYsSh$zU#^9XS~(G2sOXDLD}-$vO{B7d`LP;^w9Z2Oopy z_?2PR8KHMa0Ol|_>-PS3m<+{7{o4g`xkBK~-Y8>E<9?+ixVYyv&q&{&YEhvtYq)m? zrrUr9k~Gm-t>@{g=V)T|Vn)f4)a|!vfEfXvlGP5`~+dlord_s8G|`R3G88!Un`4pygihTNgS;9egz5BIC%lv@6rTa8$VZl?R@ zX~V9{J5S6vj8GUIuQxTEa(NvhIMU>{ceA#Fs-kOglprM-nrJ3=fCXVN6uq*q1`c-) z$=+b`fdr9_<{jWgm`QMbHIV7WsDx?oCx$+(QLva!`PFU7KGfrY$KXnAc7h5$QOSud z`^BWs__Mqr3qjg|f`t|on`{%_; zbzZ6=$9nB#A6j;8>%0GBvhbLwASd$Gkd$`-rq&cDdF+Rr@npZ7?j#Ap4Xm0vn zGX;Tv+B1A)k?Ut|7ywam(MbNUWjk{>)nu62B1fyUj-=ld`3s=zzk|$&hDA#66?jjAxUJSaQD}FDIMAuRVugi zp**l~UI{jR6{6VKac8Lzs5WRr*fJ6H?*gXKScC_msM9hthKc)8Jcc zNEk34eVT)lylG5|&mMWGO}rAE;UP}`kdHfY^FB2uU7y~CW;O0aTY|$kUNdr7TwJ3F z(!$;p^;PBf#w@9SXpMvkXi4icCw=I{NbBRTpGM4vsXJDM_gnp|*UvwPlA4S-LxjCO zJ(x*r_nguHke%#1ZQ6wfKBW9}`0s57qkY29TkH{*$#ikIh5wQ{XfL?FOY8HM*xj#w z7d^;n%262F!m_v8pu6!PFKjqAHpOGfPqTTNWGZvxI7fS_J}6CM;2fLq_Jg;YlPlNd z=`)A}*fT4n$Ujvr^GAOp)GHiWfaHnRGw~WpOq*F|8E5l(cz^ia%VimItLVX>(^?6O zfBP?rXiMCkpNb^G-{r}ADDc7dQFo~_zbIPKvGTSpuZcEpP8MZ_~%W`}`gM2l_w-zqH;O*TvIipv%D8Wne8Qi?xfYv(#na_{cza{yg^m)NM*0 z`6W9sbv0bxzX5d(k`(x?5XHt#<^H?tC$I|yPNOkA3%Rn;&-DK3vNk=)p zGO*TTc&+W_**x0zbARi(ezX_iwYI<3_SSRXbg`WT8R#aXlc1B2(c`SJcAt3_}v{2Z>0!_{M+j#Qs^I@o34sAk|;OdvSgBpgx52ft(`Ns%q_5RNw^}vTs)+L)i{?8CX#UPUJ~vMG5GHR+WL3jF(BVp3tp$CxHqb9^I=;_VBID&d zyvsnHft5^fwQz@=R2f)qY_#!9^kYRshpp5%JGUfHUYkA7U#U#4b0m5tTkC~(xC_)u z^p=z9#V_$nG~}^FKUTE1czl1CV)OhC?=lcFu-s(VrRn6zz;ffDji1x=ITk=D`nJb_oj10;WrU&8whL~R=#1#{rdV^;mFfUY~^yyuh%7wuosUq9X!D@a57FnCs-F2 zo71abb6&5y=dA``3t2Qw?QGiQ?N!y@y;kRyxGm6E_wHq# zkTLl>al$;#61_xQiC!+Vk~(BqykF8fc!FhsC(;Sltdp`iZGD;3d(}OUI3bcsoUlae z#cRsvSMB-Yt_!=j>237FJ&%(aU1_$~E1}8!!^3gw7jX-uWhTJdV_4QS_C+_2; zcqOu9{rv+&ON(q`&00Ou-&ZZp&(AMZ3x}G@0}?^s+`{7A6nThkX=!nOZf>!b?^~Kb zIJYDw7W?}a7Urg_#ig05zpCaImJTj0EX>a@R@LnE)Pbe`rKP2r>4P)#bMtf4Q*#Rg zl}Jy|&R3!}H8VRqJH4=P-=3LAr}xgy?b|>1;Dh(i?BBn8{%TA5egvc)CElNQ_ehdb7P@f3NLR}3Hi-s#p`E=`eD@9?0ja#YCQIlJPK#(V2=H@C{_Lr)~ z#cHmc^kk$qzj5?0h$auXRFV#U~ zp&!;ec${V+JN=H+R-8OxxAj*}_w^Fqi$>n+p}$qng}!}}&+W4pxu9c`!9h7pz9{`q zc=D4cZ@l6AH=eoe)X6Pd&fKth>!$I6p|On<;~Tb2OiXSa?jPMUINZN^XmE6MwbZw1 zaByIvuV1dX`}&8M7W>Bf2P#?H2gU{l`iHPca}g>n(4Iz|5WxV?>!72^b*&;t`I8A+ zjT@iX)#^bWK95H0`q&xp;Ti^jCkz5Pfmuz_h|6Hw!)-rmP8h_rPxqps9}*n(}J(aOu#_bv-S|OLfptXh&<^ z=W&$*clsPxai46#mij5D`8shz(}mt-wWnCeWPyW=Jn3PwJma#< z#=igA|6@Vklc44*jf$r%hh<_hW@I5 zTo&;OIYxN3?blr50gBh)955aT?Xg;<;p<2XlZAHp;_1N2Cr-ZC@%U=wDeLu*7aW@l zZXN)6AbwA0dq~5^LGC)tEG#S@lr{f=+?9|`gKQWU<{z4!pWQt@J+)(I>d+$x_CEU1 z?!9{+-SyDUefK_a`=g)u#E15M`OBZiMgeJK>HyIt81vlJ3Yzd)x4Q;@W6-=~klUwz z4t)$c4|SAW#?yj;lVP2Zx~z9_oo8UVxnZ4$e?o^>>YJSA>%>92(dx z3-YG^YG`tBuz%yoNS{1eml-KeD;{zYH+bUDJxF3(E7<|)5ERIf)`kbcmJhE3kv6|A z>L(PL8wYL}JY+*TPG@Eo7i6;_n}oT2OMQI@&`EJQZ^MKy+|?Bj;`Y!1g4&RxU`J{-!n)z1ztzxtqVQRtDOEk~#@O6>_&JpV*oRdC`w*K(+5>A?j9!FwP_p~z3+xWPJkHenPdwF&g z7J1(O4EE1F^UU!d`q7^|?XpXsuxl?04~TwgPECysikTOj~nEz#@UBwX6NtUe{koUO0Fx^uu}w*HH#|?ysYePRdY9 z{orXOoHP$tiklo3e73;P+p8>D7}z^g};)#(C$Rclv3ko%XoRo3~y# zJ~nxYywQF}-(cUCfxf}5BGZbICw1d#d-&iaRxYO zj&T?#rTbFua~kWtpyvVaUFQ|P-OkT-&>48SiIdB~5B>OyPyLSTzw@kf&phv(Q?{OQ z`Nr{yD~3jg&mHI=+$Jy9Z5tcI%>tPz;(^7zyMjj=kA2}_@R5{eVaS!}Ua!NCeg<#{ z0h@z-gD^e4xFj2cU5iVL56#cae)Hhe{x9#``OsH)K6L-LKJ=0IJ@C$VzWEV}&Y!o~ z82D~Q_v*pN2DTw!*PDzswXF`W*YWjUICv=3)mry?%ra1#AC6ftCyC=y&OMEIFX(x| zOLe33D&MZ~DUV5llLpe?|Kw*qZ_`h{;AQ7O?)>vF+_rVg6~m(wSB(sfp550saGG4v z{rkWF?~h7|*vMnSl^<^icMG`q;*o*gA+WmxA`&+VDa`4ZJEoT<|r`Mpr#9_@EE{+kg9-v!49qXI*&KIp zr;%TRo(7zy+Xyfdk1_1#^P`{eQjSR@L=?mUbG%fioWYG7`Qo0@^u)%Z58BXw_z!P< z+!a?o_2P5Re*9A=#wVUKGJ*wv;7ojtZV9*UfJWsib z;vItlS=w=@0FM%;<+lpY{0bCMG9u7#bP9aCCTZJKn&ruk5kl^KCs*LV*!~ zHlp5@E(2W#)^`T{Im<}-_#kWwWJ7>oTb!9&czAAR>gxv&?*GL7JMR1R``-8N+yDHn zulu&>;7$Nece^`)M+Nw1!M=SkztTfQYgytY=xM-FO3hGC_qnDS=;rk`O=mDx$|-5R zAb6h7x`+Ms?frDohhr@IC}VGU!~eMC!iz6?%IVwB{Aa@>qgMW#GT1418R#-_ z#2Mh3ou8AWAE^VpLx4wwcz2i zK;3TKxab}ctYkv~edsdmHV8Qu%CO$SBg{ZPhK|tONvEFY&)10q<~V5{a*@}!_!k%Z zB#iT;*fYKb))lmG&4L0C}8+zP?f$om`iJ zE(1p|14mIBA5o=;H*kxA=K7;c9>IKm6vO zJpS3=ar5&wZQlMp@;BzMl0|>`XfFB)&Yyuu5gP(JanQTcWuVKzvB?0>(bD;PG$P_= z02_j-sim2j>FF=++4IOheEicNy!BUK`BPsPpYnQP@`wN-;_w>)kCRU*qV;BgI3NmS ze97yc*WoM7KsT4KFi}n{&l&3_2wLgrweaIgfAQKIzh}!U|J`ribpCmd|G}}b$)}Bs z^l!s6eZ0YYG_UksI}?5$Gyi2D%I!wG42z^XDFEa0LeCE&)lFj42*E?i>q_lD zSDXPqo>r`Mk_zUz@^#>>6E8t7{0h@6eq8PMzxp@-AFfg8@sN8>VoMqaDrw4XTLw^I2(}0`mt#Kz<@p`z@JT?nVb2_-rc+Z`n@0h zhrfHxufFK67|)=*z`Pj0O6V7U;3%GkruYMV)`6qs&CTm7)qO6T0gj(#1y6E;JWs59 z2;A4X*pqJcjT`&rg&EXQu<(z*WNo9=|?wj+Wg(}m-4ppWxiu^wI8Bt`CtP* zn`d8%=jD!jTpAeKSVL_w{*X&35&Akv%#R^ao&W+-u$Cn+hsz4&3P-8VHfQD&a#G0z zQ5AkJQvV!9Y-a%SK?o%LM3tu%OrMA(($N_cj{Cvk*Mg*Lw+z zvIL4r3IPXzz;Up|M%9ZD=%fj{Ft;SfVznSwI`hEifddL}9=xS$QSQ{t&&U!bdh;`j z0nQxMxom!Rv6`C}8Tp3Bocux9N`6~qQFLdfln>q6L%p@V(L)-NuP(wc} zCSgk)hy{=bkU*!yldA3 z2c#WC;qMUWj|*X_p?NskaPmbY$dw>yD92l>H0opYSsH7{D3o>QHqQ;~9s(~NTo_fy2AHVf~KKK0dFZt=w(UI#%hWiJx@UO*J_l`vt(MX(ifR9xP3pjr36#sE> zEsCeVpVYZ1&&uM71r&?$-1K5KD~m7|Z7k3;vhdCvT&U&*P9GH7fyD|7_SAlXB8x>j zEcUWU2Pl3P#mR!ST$cGTbA^{TA`M}H7w+I7z#awM*J?I^ts{a2x|lAQcw7p-Mzf}gG`{t4jYn=gg5?I$l4+bba zf!Lf34ED>WpsyN}jmF@3UnTF!gpI`Lnt^`X&$YDhK?BeJQ% z#s&vA93x}0QNhz{DWRqEK?oW`!_Q;~C?CKd%fhT7CgP8!|0D5*cLlH!!2C8fHT8e) z`Nmh@`m-Mc4906yY zIG|tPS?90zkte6Xwz2p8^{vmn@bQ~VEGD-mOsnU{%tPB1{!llX#DAxgodk7A@4kdj4DH4!q`1>jmzS1{L-HhKfA#XMN*$L`;dy1R1VQm~*@$DiF8!HyY*gy=+U5JTIgVpF{e>Hi^kl+IX2Ll}2D0H`p7?;h&DDG^?6>jJe z`QX?<2=zum65?ZLysL$51ACwK)!I4M^0@Pc`3~mCC42j&*miR)MT^f^Cv zx5MxF%g@|+=|z{mczj~wIYTn(9M21XJ~q&JoNa*177@aPi7SI})h>9LDC4P&MS4mW z@Tq;WsK*8Uz%E(XcP~`?9-gZX3B)3gtNB^EdY==|b295$D%_g;qMw6 zC8%j&e%9v99@zvdomuL?ok*Kv51YO47$VZVEp84F-y(cd500ZQV*q5%3CZI^$-0ML z7ILfuq97;410j4UC87J+qK?;XyV^IK5i8_jxd;%4`Ld`2R>!4u&t-l?yEDJs27(vl zgpYr?12HHAbp(gtBXZYaL^cGQwhmQevQaqYjFD>dHhITw3pNge)%eB%Ik0&c3>%9u zge2GiB^Yd!mSmvf?olDpNqQ`H-`+&zxU2Y$z_e@xzV@}--}u8n{M7eLLy-1?_$P{i z<3I{>2wHv!sm`WPo2LWsN|phfM_thjTJJ$WY(ViL~%*+AKM&17dNS4Z>Qxnh5r{tjn!Y6e2 zFZ4(7Z<5FW?l6q?%N+uNcpR}&HU*ov4^^k0JyLBwbEMjIs%#QA4+eC|e)*QIn_oCG z)W7LOT=>yYOkTJh8gAQ59rGgol<<_?$=m<1EaJQ-5DaJ6D5^6-ChN@DOhFk?ba`=NHL6^l zeQIJr%u;puyci64q7|Iy zqoY+N-x}DzXYbCx_^WsS=^woAb-(gWX(wJ3lwE#59}92;K#1hyU{+XRI=M$`Kis=cznA9w_d{A~5eJ+sxL-<*>xd%2Y_ueeW%Aa2cT662?w^!krMfUX7f zMsbU{pW-R$X{O6XRW16N05Z@qeWhrWlqAWuTps_o~ER$ERVuC~f%0go2K9W_Y>&cDU%Zb9Af;}F3k!4dh3rTY#{-?wAO zgRlRWKYHC??%cU^N;Uz5@op*>dM^GsM8f@n46qXjJq>tQBm+F-PR3;9ZD-v>;5fMG zdmJzM^~*~u;GxXF@r{3d^-VWD=T#FMCZCJpH(&U=mWriYMP6gffLL{C!{nT$ z+P={=Dy?gdj7J-QY;(Y+4%tQdhskB4Pwos14#`Gfs5<-NiR$c&#;fg*8KXX9e#^;cmzD)WKUn{_zj~&dXl*{dg>Z9}nAPb%y6#KWdJtmM@d2{~J2jsTB91q?xU48p2Gx8FiT)<)O{=*PL>Hs3tO8B(2 z#wum&Ds>3K@VE-H@=QV~ zA$}i$am%?-e)uf3U3JWcMr3u1$ypaW&T4FD)Tw>Y1|=h`^GKc0N!)1UQg6BCpFEc_sh>|T$Dg7phOzygT*2Va|e^nSU8f7fjF z%`Z(=kK8?59hjE4@#Ca}lM>5){o)-I>!LU$v=B-{WoMxoQ5tb4Au60a%>v|j$3S(W z=^B`YniP?sE3uUIOqzUJ(rZfa)q+9OA&t7mF^XZv1*WakHbO?_%Fndydl&LA=&lX+X zeQTW=Wp2$x2?JsCB=b%fyj+@q$ z9@-pv;9xrdS;T;U8|2-Bt)nB=*_Vu0=Ra{nb?&9()oHRRz{`kyY=<9_s?DcOS$8%5 z#PEUrQy=-{CqMY=7r!{XB&a_kv|qj=lx_exL;y8mDMb4;@xZj?b<$57$n*DPX>i_N z)&c#&t9{aR6M$R(`e%9^0H(Na#m>INM}2;-)roLA(|&P;#E>&?h8}>TC32 z`J+xMulme-;Kc;A$#cdmXu4RuwpuT>xwNXJ@o6okvCTz`A!)od9xB;~Sxj?dVk(hy z^B9`3n`Mg-R#Q#CP}8SvvLNl{qs?ScdI{mz{Y5^8-Q!0en5`cE>P&U-r>3eMa$En=>3R98 zoi6x($=8LH%Pk<%BBfqnQI9mhnjVDk;R@v(Dd>WZNvpQ50*NgpaT+wA-j?Air*@;U zn#swtCWIJNgZ2TvHe~ZUC+X1Udcsz#4Ou0JPx?@j)eJLXqZtNnTJx5`fp-*@D#;ob zVyOgYO~{}$^QCm<*{4yXkJCbkHH+WuDQ0zQu}#bq=#(NfSz^uj zO%}0f&1l%LVA9ZhdalV(%ld%4np0aw97}PbPqu>b&Ky0}Z4v+UijBg8$c1|ZLdQ1{ zw#s+!F1T{Cy5fdYs>fY6S#6WADB{PE;9@=B9l+fHv~l0Q1Aq1BfA;#{c+;DH{eB?~ z$tFNvObS09gp{M{guwHCLH#To4>ZB0|%(tk5Gg$`Qj`Tf}2&^9kfK{im7Y;GC4k^>2SI@I)O=bDXDbVV~ za9+2a#Z5YVHL0|fbJ99%X}qf29s~H)tS5ftqn@zoU7*m@aaC&up|oat-aBM^wbIMd zf;}ouJ@}f(Rj)~*J@VU55sN`{X?3%ZTKA6vQVujg32lbMVs5FUnPE-6r8QV9$6bn2 zy{m=X4#u+3>VEcouc0y;OYk01_=8-GISER)8IaaHC6MC*m%vff1~wA-7Q&)j4ZzxF zd57R}kDIKX@Ex0FQ?Q{rM_w?*&m*tb%>eF1;@yBl2WIZP{f;mG$_rokv=2!$hUEpp zCHba6>5%}F!9)>SVvrs!q_KWdK#YSEz2t2~oisqd==-`~{DaOd@4fq{&pz|~UmG3k zpOhc4SXX(&^L%`FcuxKh%_DcqqW|fs>gyjpSnb?B7oOweQvzO)wQ76 zC{nL!?g*MZv3Tqj(I~(pJ^JU5un53+fjX>gvMJv&qK_nV=hO zsRnx{aH1Um&`G0asxMNy$p~^H8+1TLTv0!`DC#oxNI;Fbe`pxxb z0ADpM{qZSSKUQT}en3h7@YVhu-@5NNpY`nX|0Kk*aie^PP<}pCuN?r9G!G$08gWZ3 zD9TstB>61OWG7P7^Y)=m8qQ7D2}uLQ?S8<;>#lp=#Oq)8wpVZ6w)Ll$miqeF%PoIg zp2hNM$6ASSSZmz=K^FK6i}`BXT-q<{lv)?lDOl6NioFW=)p=RUy8$MhG~2K)NC=O z7<9Dls%9_+hTa@Iyt|3|#%pE!9=e*ixN&zWhRg z{Eq*^I{JfUcqAaheqqY-1YMHK0LESOAg4_ z^6rx>{jYxDK(*uEnd+eI=&@Xf8~U>N>uq{84~KkW$KY@s`Or{4c~RJ88+^z^n|eSU zE>$f`OV2P&IVD-@08-Cunhwrlv_vP?EDor*6hCo#%RwBIWNbr&Cjx;MCeqY+Nj6HS zg0vB+UaNeS>Ls9}bRjul*HsM=P?r zy^w#}qa8jHKwlXx^jv5YXnG)4m9aWv#bW|IF4!c$L2$u&8>$<=cYAf|wVSF_P8$x7 z2=J5M$GYIc20tgfXU~BTy!$WT@`~U2ombo|G4ZQ{NEm`FK_Lg8Hd=AeSdM+;7|BtF zHme?GN5{atZyRvdxeAiTIv|hmvfuCh-aD_n`R3=pe&eR`CrwSE1;=h&U~R(RK%bXK z4-eltQ{DOg1JylpwZ9v$@(NS_&Y6ICbsw7=mUb@s!UI=Zz*Hb9g2Q+y20@2xphUjT zMU;Vp$J?@_GPSMXr(KqE8fO%CAf=}@h;f>9DiK|7p*35EscKyhlc&&_uP}KwJ+-y2 zwo{KI*+h7Wp|PEI2NvHaXDSyrnj+mglqP=wMJ+F)uZb0R#xDw$qbMmKgho zV@m1dCc5kgPmFlF(3)Sf15AB1J+x$}=nt}`Sj#rjt8)hKBOuQBw62m4uVaB9qX)d^ zp`W$qLOvp0rDKocL4PRGj>xDztHTPuQ7|vE1A=esAE~ar`PAyV@7yN8Juq1f%2JDs zfO8vdKDJ;?z9q14-=WXndh6f+{4f9VkK8Wa^OpnBSR6<}uS3{%vE`76onTS_|QbgEOK$NP#)V9VbEb~)h0Fd zjG#QJDr+_HF*WK2!sro;zEMlAlt_283H+fwiCi>8;LlP{Y2*>=#KxLI(+!@4p%kM# zSr-?nsTQdorj|60Jb&fra!qd)xaZd!fEc6a<$9{ylt?M8$-5YdC$$#1%m@0d)h{I3 zUfOgv65HGWC?B8HPBg_=kuLHYYcGFhoA%R8DqsE4E9?=E`aRq0ZAAHCLF3NHa=_6>oyO#r72BqJsy^qa>6;Y9r4 z*>WPa&9{j-APy|@#98OU4;U8zx4->kH(z!2)Ba#|bl|jO^|D|1&c5uh@#k;v{@(|y zFaP!4>f3u~!&~?R(u{st{JDT*kz}(xp3+35o{XUh9grdn^_nVYf~Tvfo*yj>dtyuF z^D_t*%Qvyh@=A?7XABb)A`KIi7ZRLjvyn?u(WVJSOr1DaiG)p_MlQ>Os;#ADQ%>Vb zr6|prXeD-3XXUYZ+fld9C;ihe7gf>?*6pbm8siy8JGFFRr8SU@VwhP;B%;4k-qH<# zX*F$vQXW611&pO>A>q`Es#4pm=RFQN^jp)qr{!GM0eLP)T8zWAL6=$`U%kXfn(~={ zm2))N7-&t;Dr^AM9^MsL2$SUnTQ*cr`|fSk)1J4z+AM$VaQ-oWPXH0>Xc!*uucoIL z9=iRu&;08bzVP}F$U6b@JIVe2Yl3KiHo~B5uyC1-fEKz&=_kx5GIt^s-yY(CIIzH@ zaKR^y3qRoOU;WKzzUQ)wuX)q($iSAl@SKYS4649UXj*G)`N=DO{MEd#eegha``_-b zcFxU-$G&PH+|CbA_(Kf(Vw}VxbX~d;S3KOpZp%7kS);6v9utc2g;mcPkzHSjM}1}j zY-UJlu-jHtLYHsLrk6ImW;Kt6^0dY}p-xw1K-=18otKt`?bBa5X+XU&}o~8)-`dwT?$E zMeAYVxAQ|X737*Spy)^u=^(?bN+eMSG0n@n0!wn7GdNy7^M}u@uK%9x)hXMK#YR9| zh`%K;JG-#^pYHzh%m2mmpY*?kDsKtMZbcV-G@S>U2%L=vL}6WPS|@(OL5$NArTDf0 zXPr187I{K$1aQ?4d`=es@4o!9YyMzpaA318{z46fV*{$d0oS&0;Xk-*p}O;y{nh8+ zv#)w|aV}ix4~B&wJ~0_!f!1c|)jpdf{J1z&H zWRPJmsJv2|lF=LEiVGvCO+PBDye8NCQAwqw`E)km)A>`o#!s=PKQE7VVO(-Nc->l) z{(@p#CRCG4Rnd+o#_^YsVx{&Q>S-;P4_x+vFndXBkpI^0aM3L;cm<{KB5w@4W40Km3AgunCawFoeZE-v~g#ZvtQ# zG#;p6mWdM>+VU*Dw*8$$*t6kdmD5HE*IMOTNWc z!YCz?A_(R$21E^O3FSQpG_8|LDZ)HH$XPxhW%^BDYNfXAl2y}GBz~se+GJF?`3^uW zpXH;bC%#xAH;rDY8c)ih9$RkpfQfHf^c?MfaGKUi^3W|?%KetL8`PG#7-Pv-!-hnJ z@A6=hsy8yxE?UI`Thz5pfae0IM5Nd8kuNFb;^Gd8P=YRaatM#Ix)A^!a*Nd* zcygRGG**4*zdAc?1n`c4d~ZLPS^E##P5V6C1pK}D9hSf0vM6s0ViSPFg4UqW02+&p zuIrE>Po+P{ZU*4j7Hh;HwJ57W zL>elh$4LFc%h(}lE!FA7Y^$}f_{k)|qL{VarXxxyG>x3zi$WQ<1)&?4~Env(^?NLxaTCX8zst*eE$_{#Kho7-Zn>2#T8Oe0{+DMYGUgvb)u zXh&EaoIs7{k~Y$E4y-n5WnVB4S+k}Nu^8bDvHn+!CA+Fo_S%$hQlJlN5`!BbC5=)G zOLDLs7FHlx;Uj{Acaby?8XG_9Mffg8nAV&D#;nzS2^~4YVh_n!2NpK~VI&G3bc0>U zU<0re-Vwmbd-2u{)%U;Tan+SKZh?_C`cWbHLGjrq4v4Z;VG}SjJOAhxZu{KN{KStu z?fpXZ-wyDb01ez4$;Q%=8pP)bR+z2hxmmsq!1IF(Kj^@D)ekuT=WqY`GoF0)Q~!8m zWbm}~%3rv_6re906W%TOpyxENXE@ere5G&qH)g9(%EJG(FCUZ>M!vur7JdyE8-cJ0 zbJ2%wzZn2GtJnSm9W|VyS~NPX2fJCklv5vSsixuNjyBbr*a;`R+1B<)XAaah91XH1 zdP>V`X|=XiwuEUYEhRZMiBI;MQBrNK1+Hs1WmJnbz?_1*`Y?`SR`>e3h2FB>Bl{(7 z5pB2IF%C2>syvUiCFViK1$~ld(CipZu@a}$N7M^GI0nUcNuRQS8n;T7WZNv1WYumD z4s#I$Xi9>DbFayPN=|KE{G5K;hwEXjRw* zC{G_5P-a2y2+UW*GKXDv^|tEyFFLn6>!R_)d@KO_9Mu+iGkcVQysY@x1k5Zv^o7rV z{AYjSC!X;UA^G14;3EMDqoFudQ6X^F1x;|&kNbi}@OUolZL|S657xQ(1M)zd!{Yzi z*Z$cRH{JN0|28%@d}ed;rwKq5KMqi*YtX%0@Y)N$(zhTl7k=?Qd#m^V-p=ZQhh`)g z`4+$Y;70iA9@zwhU+}|bKu#R}en0L6=v@Jo!M6bPZh+n_RR3W^Aardi<3lW1@}o?E zAc-6%)ObKQ96=g7s)bLgI79{Fr&*>SV=K?<%{&?pvc32wsaW14ky7;+0*Pi2*c&s;i{OJmJt!42|pLihG^J9*t(R=c!LsIm3ZgN=IJU<`Sq zp$1AyO+@O7-kYkcIf8Mz!lM0eQI|AJG?)jy$unDd2!WHu*3?%@o}Jtl>+Rks5n=3e zl`lo^{VJanZK%{@?5T|?Rkus!w4ap+J=DW}IDvQ}8Js|aIQ|7XenFtBzOiF}^|@Pi zg|982bMa&~Jc2*0nzg5AHTq3tme16p++iIV8JIkE+Zj*4;NmBK?7g@A)ebR%`M;zb znvl3iE2SV(iQ}wxJZ}JeXZVgIj+GENS46_l=2yP*znuGm7yRTOPiz>wWOlYR5}{fe zBj{o9@=~HfU+Ho#RD<$e)koy7@xA}IcUE8c_jMzI#wVd`Yt=Hs$ zFq7mWk8A~bsfMe?oV3febJU8CoJP*0i$r^ww(8Y0Tj64Cpd9NBMj))jLRqt&nzW-; zOwT%;$t#g>${=zcaH6=imRWW((tm14XAHfrkGl1EkMdH-`~kbcQsjs2TOjMEK;1@P zLdT4DNbYlf@rygFyFa?GI_HwfYU>#z_KqRk6$>JRVHVb#El=erFOMA-ma4JQ!Hs8~ ze*QH>;~PKt=}*3YpK!T8lnmuGAArZr$#IIE2pa%)2%taM!Cd$Oar>w5#v7kK@r%Fo z+P7@pJo&VlndXww-j_5F2@XU`GYr;^-!EB~yJX?2|9x-RSv|aGUKV~_=?f!U)ceDR z0E;`W_;o?&9RXhbV==e!8&AagB+(dyOgLFZwhqn5k2uMo4aSbv+2S8hVmUpHpR%-|c1Uf3 zNDz-qq-;GCl4>fa3o=F-N}i8Ur$4|I4G8bSLm;F~0@)wELLN~3aX4IsdIFx7o`*zG%AA8fT>dxB_gtz;zS^UX*;Be% zp@dO9=Jm?1R3F(;S<|2$n@`##bV^Zq$RJnkva}l{Vo)kgP6?WXVNJQVY^V-3(N^LG z8-+x*OPBhUt*33(wmg!a$vMB!uq;BpJ%Tn)-o}QU_-oK&Xw6tvvQ1tiY;wyKo=e89 zF_(iFN+ZU$sB0TkbkH_5O3dGbplE6XHOAU5k@t<-h~EmJnm|3*>4CNdhBl*BplMy- zBoIpE>C1uW74Xp-$O;txg1j35`Rkvsy?Ws*9$#%cYc%{N(;zES`A7<+;zv5-N+R&e zGAfTAzWvayKmCsDPkp7(G$4KU&-Vcka11)|A;SY^XpRuH5I+}x zEcW^0KX=P5cfI($$6xT9Sb*FVWP7c zK_zJwHQy5BDyDoLflWE(Qd0S4DV3>Xqe_G&ol<)tp31V8eiabZkh9qK=q7-gR}*QY zCiIY7!PtRMRt#Zz< zX?hHrnp?IbwWT%x32pjL$CcLkc;anel#e^$!CyTtY%~*aPH7} z^^>oKeEtc@fX?p zlmWi7V&TYIF26u2xBUNVZ}ovc+*R#gSd=$m@vI+5xRD=khslNj3xB-bFG!6mU2mD$ zJ_d0?20DOjcA$osi2-UWJk*w?qgLZiX{8pcT#2JJ+DR?3ww!iq7h2Guw$xT=Al0H( zo9syeG+AKgz`UAtn3M`ZG-YY0$uVodl%IM|C9Fo9N=a*KNyma|tQPAPEqK_T0@l6@ zxd1PLpqiHtRcs-a7Od#POxb>VQJg~zCH^>EUpg}(T z7rOjbZ?$J(w))&hc2*RoYFc7lX(y@vJy9tJ1$D8;p;hYpM2n@18o8wa3K7RX!$|^ zm%jAXXFvb>Kk}B*(V;Wv=5>PyA>YHOA8EmN`xoR#FFy9>-PNc5ZjWpw`}FI5vdD*} zp9{Wh2JnJkc+MYadIx|Dzdrw$U4-z^oNx|t;SbHzrXzkm{j~1m_iZEHXoivJq3+aS z8HJfqnC{R`OLeU7HB+it^mJRA9mLfa9l(hldA7PTh9aHPz7It)KnWhw)M%#d4LBS;`WYWR45k@wx#( z;!6isOCGubn4RrEZ`*l;|M7SK>uXQhyx~T?^v7N!WKZRF(pL(x_)k5$P<`OHc2>82 zPM`PV7yb0gKVI$2o`hHYdV+A_2OX4n)sKZgEdByQU_n|!K}91~3O?OQRtKvxb+tzF z@l9K`K!T`sl6XS4*`{0$z0Z~EsxP#?o-cB#A%6=LpAgs6WruH(J4k9fGk{wNJKEk!vPJq&+d*<(E6%07%M z@Yy^%;K`EH$p_Sy=;VhsDaJN<$&}WsW?GgprKNK)Z0JP6C`{Ax3!3tQ1YpJ)M%7IgAscI;v>7{9d@-U~|>EWQU&zaSXu3*o(hOSf#QUjEw4s%>YF$zK?>C}Oxp0IPY+bHQ{J>JJ5d z{ob#=>Uqz;>t5c@{Ch-w*amncdSng>vfN-VYBteJj> zWdkAAq%6I#?#Bsv&|Id#7yS`B70lK+=~2|0*@PE*3Ig#%$99QBsa`x9N&Qf>44aXZ zJoH@r;~1>%OD=5j2eo9eM5`?{@?{+6GPPT`xbBB|bsrSHFW_=OF3Kk7PM8OyNRU%M z_~T~}O&zN4{>Z-S3D<9_Hf$bhy%SK!qGn*ASbS7o06$C zI*e4-NNH-1As&`+2|VN(FhpLD7&&TKe{Ba*$pgn^(xHJhcd$mehXP zQuBHNxopasKk_v5u$`A=l8t%KWwg$in_PNPj@H_w!8M*?O{><=IWP4`ZkbB1dbM`| zjP9^}5pt|DYVnu7mwHDn1$plextDrwhaQ@J@9bp3sSY(WW?jua)eJ>A79#9zt!&G5 zz}H-0=J1>P+G}h-&wahRuDKaf_D3OPQAfP`^&<{>Y-%G?i#FInJaQyR+m5Zlxow~;o>F$;ymb}88wuiuRV6i9ki$C!BxBc0NUUvGKr~S*- z{n(Fa7K(RHcE2qC`yQOD{^3=!_#f0C_X*GYB`hr1dKUn{Jro}W2&I1M4>AZJi@y|b z@J}%|OKPx#vqA={ZUO|4I?!v5PEgbi_^|0^N{c$A^U|DRO#<_=FB&%~-gh2f%A-aZ zdMC{YmD{8@7XZo-rrx}ll#|ssGlA^z73*i_d>=Jfa3P1;HVpKnCFl&q!q@h2sdwd5D&=XW0+0Td$pv$(?rD|5aPfy!&hul>FR4A+*3XAhAq`bYy#v5eNdPihysr)H61c5E%WnB)%e87 zxmREP&kp^?+urao(Zi&TLN^|#%OK!B7=k0MfAo0S05G|L{=iD<53~oJC%69JdCk>N zy8id%?}LpdS8u{RoWcO8J18&w?fdq8^^bDf|J(cU2QPI0QD5nYMIVm>xEZhoKP>!c zMp(#k%4m}mQ%`{sPK@YL@cMlBbOuyI)nDC2SStraPbiJa(w)jQYw9bgw)PIHj5lj{|7Q_m!AGqS7aufDKn(eBl#6 za_g<{!QTrAQ3^yFSUgVJaShSG>$$KKN-^4qvP}S1;l8uZx^nFLo1XV8Bcp?x7UVnl zYh4Cp@jobU``_|w`ph3c@PiQQ#UJ&sxJ!D&2>~8(+XVQlcwvNpfOUx{KwXCfY1zr| zw1Rkf!l5OfHqdfyP?jI(3`MO%zNzD>O|EL@pBjxyvzh4zPN#UtsZ<(&KA?N&g5F_+ zeLz|PqS0x?OG3exG1^;>*cczYmrF{AHE*#V69wf|HqjF4)UK(U9CF!q_L!7urWn0b z5-|6f*Ha6u(E{t#A_Se!&9(nMhgv0QmdhHX5|(U48V74Z#P_Y?<~vtmTXH(;L5sdx z$D@yzc)LIK1HQ?i-+T4H>hP03SO+Z8$d@e0Txeg5h9{Aab7t~rRHR1aE7M=wv%h-N zFMXxjyKA<>#TO&(<%Ql>0 zv4)jvJr^d_Czv=phY)SSG5@Z2f8fW?JpG)P%+2H1t=G0-j4sSn)dyd@v%2TLsW3S8 zDxbIc<3>P2(Z7%t7k?Q)ddpwK*T6v2e_>(a2@QyoJe&|JpDu!JZPL>M7JC=SMZ0n; zHRY+#(=4^d@JOXK)_X(RE=zGi1efVZCB44IhY1Uue8B>(**-tj!8%*uy@PHgc=8jk zu?`#aEomBI+e}K^(v(~|C62Xv`fSrwPkAmWZ4p`K$U8jPZ0mA%s6jd3$4wrk47T-j zuB#elYT$89MAJSu=8a>p$%W;dih9XoBA1_p-dVs)mK#E?1o&#}|7OH61C1D=#HKZ^ zvqmpLs$KsyQ$6V!r&a@l{k$QhdTa2BzZWnu zG4}YYuDId;cfR8fz91%XH7x@dBd*i_RsCFhWq7ZKi=w>#hN%aidg&s!4eob>Zjdnh7x}uGB^g+BCjruEj)d`szW+$6pTf?A+ z*(OF#s+v+!Q~8vxI)T#?2)(4%kG!V2w2Z=!MVZ=fY!2OcGD&KeT4+)>Hkc95Q#@~T z*&?Vsnl@63UD2gAEk5G60xnkr24(Trm-}$# zj{(cY9uOgFJ45pIEqGA!IS|$j(@=VsatHFXLMftCRS2aN`0*6yH=Yc7LD6Eql^h~1a*+veH6zYn zQ5r-aUz<>FM|(GX@;r(cIIvbQb>5Bscj znU`OQotPXw_a}b*hfQxT@ z<9nWT#g$L~jfI6idGT*;^M=3bcmJoRst^9rF1gB(WnBg$|4gsH^7os8aMh3T5f%<@ z3Qq;lR8}b(Eo9hKi@(<-!vs+piybVsH@}@j8?Xr*xoz?lrreU%l%`gp>*`;FaFTiwissm#Ogi>eMxEwGM+R)CCOe{rj;~XO)ENvQYp5>Y?xk& zW?W#5yT=$>HV3K2G&()88L zed32|Ydy&&XJ)C_;JDZ;jL1Qiv-7WA4}B*$R)cp)>3pdm;{{6erZp2 z{^K`PXPq}*9gRD^BO~J%U3B3U|NGWk|LVa?eofXj44uS|0-1P-GRk>8lGZ2i4O^cP z+8ltSGO>VuBM|VXUia*mj*JX!TI~=1EZ1=S#P5E2iE%XHc&X82*rxn@H)m(3rq@i?W9vat*RcRO~WuRtJK^N zB@m7;B{`!3;{mO7DoktOds)aU)mtOCkXwhv+Nw-&P2Wj;ab9Rjc|VDUX9QezDWl&$VE;FSd-QfLiw(1G zn$zf#^F2%?dXF@;JJuCPkBV-fVXnnB9&F*_YoIzLqx8>T^R?>X2dCwIfYe7G%WCV` zP>hZaY`FfW@BD>RPd#-+^bt%9CYF<5Ufs0flv_Ven#-h)uyp zL1=84MVOoxkqjwU;>71D0Yz)I#VcvI$tafjh{i$HO+=HQ&UI!lZ-Hq7;{o~7_Cy`G zbCl0d^J*A)y$7>J1ld#*6y-PR;FU{ik!^%*YK65_UTx-WrYzzyT%}Mqn(D9oQ?H!z zeuBP_3pvz0>dRL}`y7h`Yuz~3O}^$tQeHR7Wik5mK0F=`*n-*g)ugDB=Hu|Qx@_#V z0CI&WnvtXw#G(}cZGfcjnUC7`%8}F;#}lb?aU-UBpGWXj$`@@YKkXjR10R|2G~n@& z#l60BT1SJP#XtQaHAIMSUVU@!Q1#CLa(B1`kotp-7^plMBes2;0KTF1u{<^2?v{^TWga!}YD&HHsUz{%`x+ zebrre914T3Uht)_F(7fu)Qf+hDP4E2I&2Ye7#sD3sT3VAu+d&@Cv7s+HVQHLE0;Qk zz;+XNSv*pkk?Dz+Oh>IgQ`5GHrSyC{VGCrz5;vu}P9>VL<#u^KYF2v5YkTlX@7~tR z#}s{}R!~z9W|fjgA*LL&kk=_^Fi2!Gpy`_&@{n&hz-`8&+O3mA<|}w8byQ$567xbz zU`OwWE2Vp`;%ZK$#D9CAz>oQY4(z5B)OA75^U;hU*MvnaH3#`9<%EI)A*=GWR?Rhv z-*h!2sTI7h*1Xr!31YrTMjo0}aGU+#CJAY81F_3!OVVQP;VczkY>VD?h%!P`UW_2f z2p;dW;?XOj!}|aqzwP1beeYWP_W>}zM&vE;tDpGfpV_kItkKXQTXztEHd3KfL)}B* z>%PPcubVi1QhVV#0<=TsfBn^edh>=&8?ME!Z7mCa=J%nm%v7Iz*Q3(A*oDU1P+@>d z2;r(dRy`+5E$8B& z%df89$C#X2?@Tvam{N3>LH?JWnBhD6Pi?qhLZ3JLAU{`C->l#~DxXW*Pu@crH(M!R zV*xiA=hDBJU2$li35<)qpJOrI*DpbaIdvf1k^S4Z-&@^thrF0Qv~1SWaF(fsy8xRu zKl9Ch_@3vAsJ{!~0`)-$99FfV2AA=4?AU9Yu_J2-la`O3JlTjDv;cASf6)b3{7k=m z?SADG!}5V*&Y7ED3}5*>BoXnsKd<&VF!Q=D6d2xhi1pM9iWb92DZ$X5mPw{0-&7DTE?z(|^5vd#<BQm7VkDJNx$S()d6-Xlo~OPvrnbmH4?GMC9IOXy z`Hyn6z3r%uwzijtm5q75a_gN3dcj1jCs%0^(5NXZurw$fyV;kyg}iRcY<;_g$@<&n zMs0dm9xax8#`M95EDz%VOMcUS**v7W%hmGsFyf#`UosY#YXSXuxn%Jzw5`4WlPGAq z03j!%O`I}R6H@OQ59dkYN?V%aE#^He_I}zAKOqFAFfZ?2ny=pVy02F=(+fO3&D{}T zIJX95UAgFjC;aTCmtH*?9O|jT6vY5T5|6;su~S7u)?4wTj5rg`#1qc{>aV}`*&8-Z zK6P%bum0uM|5yJ0Kz0AOXXMtuxRXPND|VrHP9=8(NDGSsL$q{;;2R4@D(6p3(`?7u zh^Lo~rKG1w~GqeL@~*qq34Cva1=Kq=eRDWNHDzAVbJ1=Y<5v*yHfy@xWjB_A>o z)bh<4Et;(*q|PW6vgZ90_gbW_2G(zf$GCEP^bOdW=hZl*Z0sERQ~PA*&__WHlhL>+ zf*@@RJPeGXMc)&+{p>>sjWJ)p3Ho8fqn0j4K3vGZ5gfAyv(5%RaKQxADgGPPx@sVy(O8oT+J zh1JxuTGPibDF;5A6pf@ALryVG!&H}c%OjC8-X1Lg5~o}Vs;=ugCq%ny2l=tZ2)w8p z#wy1#nh>j&5LTLGAh<)?@HE_nRqKfPtkmN5w|It~y)qRxO> z7t--j5d3;5NF0h!q*9E=#8YPBRj+#eGmgg9KU{wH&-YY&mvBo-cbxTZG%onM+*#k- zq!gQiff@!Yo1v%7a;J{j5j}V2H1(n%Ghf8)G)*lSy{TpsLh1v}30GrI#q5}t(VSpU zl(D7$T=RQsW3eg?WLoX19GUP8`7{D$hBhM@99f@6b*D+MW(gE4T1#ohT``u&ktGZE zEdsR7`iDN)X4Vy2WTl+?F$sD4fR<^hr4of451V?bnb3WEv$lF$C7+3%7-dR>1*Nb( zsm*e&wt?7Ud^N3BDP>-n@UWGmfE)FS+W+@Qfy3lvZfJD2BKG z@BPG~>aH&z)VKb_FjJShi)YFtq%bfM8VjGvDUCrJmsk^`4ZYft+OkuK6PKsZdP=i6 zQ#z&9Qp(F}p-MZN4gI7Im8V3i-TN#^?WjFB7tld~xm^&`E7mlQL|H0~Pw9|tE{?el zCwMM^r5Nnohqg(i{?syPZFH4LKilL}skCMhlzmY;x6NDdPPBy{5kshS6urdxeI`A1fYv}FencZAPecNuPcZ~9P5Gvp%7}# zMg{0hHg5d`UwHlN-~E)y$;q4Mk&>p$fMZ;G^0d^JNu+eMk^*S_l6v3U6%xr z6W*M}__u#_Uv+5zyzUDus3XF#&TQPg@rGBw`Yl(BA-)TM0Ff>n8mvJf)u$mF2im$S z2z)EEPY57wda`fsYns}Qp^+bQP zr_$aEy%3UDUiiH|N-8W~sV>_F+DmPfY9Vq26$3-OdCaD_QoH4?BJ8Zxg877!YPIW@ zIn8#I<><@HL$Y1EovS*%vq*n{wU5%k@PYwngoF)&K&Wk&*+PW`W9%XA#zm_sv8CrE zV^YYP*DH@@CqaL$xyWO+x?#?qpe1^9VbEOIs4YdG+HI?uKiW{+l=r^jua*yUugG%l zt@}r5*t+6{z<+vVPxZ-L9$wQI0?*fco^OT!jC~@*aG%f-@kbt8pY#f`F*GWI7 z(C7MC2Eycc2pk7u^vB{a{nBeMn%K1I=DE4G3%OrB9Ne`~ec>H@Wx(-9obD%rS2)cU!9%Sm0fQKo7njDs?SJ%R`<$`MNZf|kQFnch|r zk;;}dG*hrp;+kd6vE`baWhYDuI;fuLna~M1G{}|{;G~H5wnZtx@+OTEJ{M}!Qz%tK z7aydXQ9#>9aIGgu({9-Ub!vI%lRSg!Pb0BtYF!f)3}dy-NyWrYSOC& zQy%_!&U!7=02OhAl2XB82`!5;fkqATQy8Qpsof_0>sI<87Ac z%b!=?r?)082xFsU)yt8u{hwOHQZ9UHqtYQ;I703nopqU)Z+Y7T)t*OYEBy7=wJKO| zHcp=MyjQ&9KV2X;5t?6@Tp%G>?{@8i@K)CVM+1&!@rlV3nAe%?rJJAgg74cnF?#C4 z!t%p(rH$*CAN{#guKssQFAs)=U2pe?ffwsK0r{|KJx;DN0Ch3vl;)d9SsUua!U@wd zp;Ct09)efmgO+=4lZ8Fn&`A*#E8(27YQWGs>SQcO&m+hstzNR$k`8BI^~(deSA^yl zSjjgf>NXaVQ1d8UXS~_&lmbm%fSNX}FKV-m78?gxeaFbfs2(!8*wT_u_3AkuPwj+S z>>ReMDGd&&Jj_N@)_6GgYi-1b+{gTv?C9*! zG?vGOd~rO71+b}7`$>1^C9kj!y7HR7)VQ80scf%Vwb%K=a5WWe9w`N=G8Sdp4(7sZ za1PeTa;k}62Gm~%M5g;?{vN3AnLbc`?0-E}4a!~JwJKO|CMJhYdCqtK;P=MhxhBC0 za1Dq_Z#rJPV4AiMi&D(-VOd%`C7&cFv`+LTSANIl?Wdjd{CPZuS)&rJ{&y}^xBc}# z{lOpU%P_!XBYl3`|7 zglQgp1SPeUuR0|;q2K@tWq6D?=w6mXo{WG6?N^}b6E&v9Sz$0^d6txe(kB>a4Qm>E}dov`F z2?ktv)i3|QPn#GYy?B1!hSHIS60ZK=d!TwmuKx9FC>TTt0SmwOt(_>k_`|18A{ufU z4a4`5$#f?tE)UNs;i)}HfRa+vTFDADRw8A(yrT3cug6zsv6LJ0r5JOCI90wWQKsi| zX}!JO$}v78@cPIbauGZu0cjGvsKa5??1GU~V2^zgY(vNuqnW1BoS>k=6Bh%*RAfap z*l+Ve18J*B?FGwLy&d(bw|SY9hQ$@+o{OgHdJlPTc*d{Am`XMC>iN(*+(J8qKKHPxwnIJ|{V%+b?;LWO zfNo#7i~FsGL)9n#@nQLiV6}I+(R#hPxy5S3#OS3jd-1Q{By_$EsEdRKPG=g3D(AR2 zc}#Hxeyj_VlPAviC=)z*(M4DNpnUajU^RokR|9$V|K@vd@ zKL?%|3?j8*-5%7{0+OGr82W8(u+zdD()6@Vl%SiQVN9yGl;a_mNDNJkIAZ2O9a<(N z9WbS##sg=DMFOhUjHVQ|kmpKmn-=OUn55E)N;ztYQf;t2HKSj=eu&Zq<$0+$NcuLF z{AwoB{wm2WPYa%6hcPC?rgk|l$n#(imhxF^XL%_5qs1~ekFB+ABuxsG;=$Kf;w5&;hOdO$4>e4U#+;3etIllR(dHfW^8WmjqfAxLx z?0+85{`F5$Vqk^EKV1F85yMfax1BrZ()rRNE6o$8ggUG*z1|0&KTeb0jB}Y+7Z0XwyfM z;}$`Q1DbvtNf7h{{MNZCd9EYO%`ICmFAQu$$(Naop&6lpX}&Zw4Q3>SOEEdewGHMm zVsL`2BL^Y-xDZfUxLA{Qc|hjx2NtHP&wSvKYG4ijP!QIi@v#lp{hL?(CSC@_DdX3v z7+4G@wr0J8az8(I(dNnWb;O1`le_es?|s2@CME|ruhq+c7(=u2mwvt~xBqqLEuV2< zaZn+I@Q(v7gp?-;VOj~Nv7DIs!oi6Q^wktjkzR{5vy;Z4sl)Wul%$UFyk+Gg#8Q~1 zLo01CkEbc8Uh!$NA8S4Vc&R*#h13%*yf@#~@*@QHn!lR^ru1nOin1Wlk@ux2wHdU#Q7= z40D0NFn31?{QZCVV0Cb>{4PLqvgqZkm523bd}3(JcRu%D;I9NCAxN(jz4j8o{mXSEjIIzOQ-c7p#~G- zC*)A&Qic%ub3zei_O(4GfWdqC!vVR0EwdNh)nK(7wcJ*kefTj7ux2jV ztCZ^<+wxM{l%}7$i5a3_L2H{7LNspShkwx0DAQWzz*W&!)n+>!(`>?DJes~tL&rht z+sk`SDIegcad>U>L(JO?G#R+5J|&SK+DawTn*Ir9y$~^7V2Pag3BGZnlunjJHPOsC z?Yu;A)fH{)vZ2*EX?MUY`XJ%PU7Zm}(>_I;aLD`}q5M#6b`ul^{JPE91HGLkq6z}9_0Yr`Yw-_n<+p)F%)gLbyk)c zA4iI-cWpx+iAn}4{P0K()HS_Ltd?qGhWOl;@{vXWTPYo3*LB!^bG@qS zn!2i*J&&YFy4fvKq9sL?NQp{hL81lA8X{_Ns+Y6QI_vJe&pG#g-+T4FB1P7% zy5HGn@3q(7XT0|tpH(qOCi}u*l;(Kz4pcj|^%O5>HZwPCCq$j^9rMr}FbyQnu&7_m z-s={#GQwP5ehvan-?eGP8=uts^?&sF;ri9oy4oGY2RQJ{fV;cL zpZtYi`tRPI-0`L~n2|CQGC8Tns4I#N%GDfoAFqaWo<*IYQfPIYO5~;v9lr7DZ~Aw3 zb~lc!>X-lU?Ei&NUL8LB%;o%*5sXouGUat5%1Xsj<@-yv`6~JGnZIVeN&#G{<;2O4 zN8^u+%{eU;wXGRz&jsyLe4DF=#2QlSw#SguP$tQuQkJ?dJ<6U;_%c6@RE@U)t*6;43HhZ{5zMo~Em8HGf$v*!FF{ct%Vojd zKrgiTG3G@7$6UlTdPQ?no5T5B#2X1bVq7c{GE=kG2A=HE{}G^o!$K*GWv&%IfqpB9 zu$f~X7<)e#+WpC}`;|gllJnv=xa`NdAg@&Bxu05*ZMuo#Wn(Yz<>_|;(kt|T^y$}z zPyOMAmHnka>=8RV>)Y>s*Eiv903=ku4S=q3$&XES$piA`J>oduzFd4KsFQrj5iA|} zXWsp7r%#_g|G^t8{O}*<#wXH;|1R&{Op`r?OHqAYox+sy$VtpPtzH(MIAH5ZZwN@< z7f97r5-kq$u|}D8_(hLJapg@ev7ppC%NIgtz}98k*A#!t=P0shc{<6&8s4q@3lsq$ zV4LcmfF;sfjwV?ceF!jbwC>J??1rP%V-UvMVg^F^()zZ8b$N_F(CHkN6qbqXG4#>~ zT9Nsx%cJ!du#wfOc%S_~)y9vi(TrUSJXD`)JAfi)m#LGH>Qniel>aPA;&JcTsR zmX#l3>%9da@4D=97$)XkW4V&Ombqc)!#TaaEJ9xbr?s=jm<1ARK8qS(*Yg}G7kk(^ z9kJs1Z`{1n9u5r-d>HVJ^p{%%91{bHIgxN%Ft%Lb$0|K~+O zY4{ZD?vkz6TZ6o%KiR3pBNrx1?eOY8TZk=ZIu~DTC=TgBMWs(bEwlE|un~4ahX=f3K-!<;Xn7xbEVY5$H6h z0}C=x7A)m0t;LeXG>|fLRPDPaW{Ww^+Mg%NB!o`w#SN*k(waB0u^$vWc$Nz1QxDP4 zuZQgLDz-`o!T6H}SzG3+nMO|iR{+XYPM}=YH|N2aD=oXnaBgZJ0k+rPsB3%QGe)`7 z9V4L5tgZuyw{_x8h(oyzLpKCDw=pQNJhMH?_;uHh{>DqgOJBIMvYUV#H}-}jyW5X_ z_-B6psT6STHv!NjH9*OE9po>|al3B-)Cp>=>gp8agA;YVq%yqyolkw>$j3?q!Zi78fWxWi{Q2RXuOeMl|y7vOB>Gsn|$`~+OL~FUf6|Zr3qMJG&B^khB(Tvz{?ufkGa9lo0hfw zD!>=gR|5a=U%s5a5?K4me}5&eM>{(ko9}qXH+*MFMoFR6#Y82@ePA!c_GLMc-0eQp zaFvKJS*1E{?Y-~)*3+j>-}4=Kxn)%cKKzHj`iI5Dm;aH0LLv&%NLxj++a)mR{6b8K zsXJmd0!%#iGtK4GmSVAmAWe()n)cS9^@|IZ?avE25w&QtTOOzj#ZomQ>&IwC>=-}y ze(rnqzZh(Dl7=N1BdfQ2OFKFq33)`j@g8*}7(CLVl+4*P!m>b{AzJH{Eh1S1Hsf7> zJ)AOG?j{OrVTq%P+0UDOESqMN@ASMZ#2gX4U zPgH`37olbLp!Ydj`K-1wR0RCMaSb*@#VU4Ci!c?L?VNz5f0^;pNj`{-@`0yu`wAQD~tckx>&0D+PTo3Qu%Dyd zkLeAT`h*5Lw1y?V68OjI!+_8I$(t+tFd*Ir*xlKA?86`aYfq*?Q*Q(K>-)xG?WxBNf#&o2%a(}(}`41e5=bjO(cKxr{W)HJ2C z7#=(}7`l~uQ(m-eX~$!Yv;|RH+Q7)MkLs`2cH@eip3BSZsfcA{kAl4)*F5Wf;?&@h zCQ6%c%`9uL#ppBo^+qjZUlQ!IBSt_oXwmk3xFPj@h>td3!=@TGR?OAv?y0w(dX6!{ zZst=+dgc5|yk6UkyU!DSI0o?P ze%#I*05#ahRA-80}?bF#4kg?M#LT%^Sg%%Y?APrfloB4Eyqx==$;Tg^qS zTI@5CMV`JLZG3w6c;JuC$u=mCRU9viTxZDm>@)Qnb2A@E`Tf;0-aUInFJI&Mw|MJu zL_=oG+{ey0tD1u-(Q#GhdAvm-m(|$|zOH_;evMYtJd2lGWXuA!sG*0(tT)aXueQ~t z3)p*^7-snQa_q$t9wiTW;_PKzAXU+e1xSo1PR>71vD7nvjJd|&J9lN}K9w1rXa!dI zF4?Gz?HScUSUycew>rt&*W5-~$=$|~-Pxwj@tt?`-~SJP@vYlC z$KST9_y6(o|BLCn|1W;&S{e;Dog0zDbMoY|DvQlVN0OhAl706m7m8ZvIT>TEiy%SWBS5z)xBWzOsC zzy4dFK6-TX_{|ml{x9asXa3bie)qpj9yUE0#=26pPRY**t(qjIZ%W(D=+CK2Z8dF+ zzxmdZEe~&rEU2}aVo^sN8PXDQ;Cdgix@}m!jX}(gD$!Frl@IJ)yvTeL_a%cBtp)!y zne~S-F&iHeh>Wrh$#r-{uUK1T^)V<%e`Ut#tl=@DDi-AE2X!FR$$DfVe&#J-Tu1>8Jnfdr~IoD5THi7nce8 z~;v=bp>drzvpZ7iV==;3GEy*d`wJUqWr~hAk_n$BS^JUfE)~D2L0owG3 z4K-@~;xG*}HJ>_jvq(az)6h}lvB0QqcuMBgM_@hpwPXHSd844QoNB6+H^Z6nxJmJO7``XLrHPyJ z8b1S|981~fpf<+M$%IU(;bYUkhzV0YVWjuY6S8l0gtQiGheI}PbhgwI=G^rxK=!1~ z=ZJ;XGLbE6eZ)py*cW0~Nd`sg)qIT63;j^~0(SV1Ht#TMV}t-1E5bx3n+LQ<>*(AS zl%h%xQZ(w8VTFf&4D_z(Bj26^ zZIxj4bxlbqk(lzAsfHwP=b=teOm&7jOXcvPANa9*j_n?MdbKbAUm0F}>1vt^+Bgs% ze>eh%OKrS50m&4VD5oSBU}s7eu1q-lAYbtsSP@KvXu_-X)+mcnNR<&)*DOM9vAUS4 z0k2Ne7+R9NmdhiIgD7UpJ}{nRZsmA!Rjg_U>qikdmJy4Z`XJ&^>sgewM~kz` zGjOP#=2^E`JRSkL_dYbfMy34nt6*hav>e9fCHZ>YoGiRbf5Gef^)ng$M<=f1x-Wz? z7OyewNRtF17U#y*p+|8{vG)2OJ{j-lVv@z7Qrs>(QzdE4IcQY@i!QR#sg$SfgR)GO zP5+)SVU-`zd>jt_n4 zr{7XCMnGN9EO;?i1?*pz<96QwV7%+jP+gsc^2Yak=a0TKef{q~e9dAN2PWq8zkemY z{EwHz@d^MpH%lnh^X#1)i#glkEU|QZt)Ui{*4o#$vB<5L+gl!ZV7=(98PGDu>ic5Y zXpx8n9%G7{50@|^lgBT{r0Qr z=U*cKRJM!s`RjnYJNN$E-}}99OIUQHzn=LZkbQj-^`Y8(>;A9;_8P6uP^nH^d-QFO zzjteEeLZZ>w~!fr{O6h9;>-V-27E?S!ud&*$3_>s6mYa1Jb1kL*d~g}tO?E1y2!SR z{lrDyVvK(Af*Wn9)n0YIhfuaHmi$xt)+#1i^f)~^tw;NweuV+SOnTU<9Hm#OoX7=j zWNUHqV6Id4s5j1f97kL=kkF5|9FiJtb;`lYh?QQih^d}9J8>*K!BT2KvIXGM0l&idjeNF>*YXi%(uWE{8|UeGZkJ$Bl0T{QkdqWx#b|6$jP}u2*k; z?8!fy_z-lwmX$hm^_f&-vOZDhZ_6>g$F45C8VyD@QaY4%N2U6SoinHJ{l*o2_HS6< z9ER6EcWwCMvv~Pm;gk+jiIav4#B@p7GM(kg`J*?;BpOH*GGpVB}6Y6EPk(Aw+*jyKHu->DUeSdkYGVnUd zW|KEJM%h*7i!nAXchQkeo~j$CG%TgB@zH8NV}C&91`Yn=GBhMH_pQWE+hD;d7UIie zZ^Dd#mb2A>w`G<_vC}h>cz;X{Ag`$@)E9JwtmDV?=m-P9`C{S0%0fK26*PPZwxOycPU*TYVUp)i?q02 zoj!H%dlPQE1k=UF0!Nb71dg2hs@3DYFXw%L<9PUyXyu##-cSFP2X?l%A79n`|LYrT z!xuh&b$BDaQNsqK-}xqhMnFFs$+B&kZ-BQ#?`^6}H@|Qc-8QJ&-^YrP;A>GL&T4M& zHl_2^0(ZtBDnd{$U6D6i758y#`xG6<3TJ!q$C93TwIdKJhyF7j^`d{qD~~`k_8C0d z+~Lz|BST#yo_!u3MKjJ)Uz zjYHB;w^kkzA+3*#r#LRcXsYLejT7*ZcXV=sOLNpBK65aC@b)tp-GX4AQkPbZ^Ad=3 zj`N0c?$@jn153OO{L;;<`A-CIrt8Bh4qUHxk8VHym;drFK9u;-mDs*OUY|*obtu2A z2d{^RpMpBTN0LsZ_WQl}e#>{f?a1ztvo~(`rmm-0PEJ4iH$0nO{?8Zp{N51#@*=1v z2a?g)^rB0LlCm_aXM+4di<&2ox}@LK=45QZwHL8^qYrpX&8g|pi|0VOZC&?dQ46Y; zp#uN_KmbWZK~y}xmDO%qdK-g$tPSja>9K-v)`#J1i^jlb2+rx&ju-xMwz(`Rm3 z%BwDgFfWs8Y`<=JMK?{~!?bfqvrYAYc=+~9@7aItX`J${OL-X5Bw^9>y`4n%SjwL} zk7^NPwZ2_h#nCmdp47V`YH=kV)<`-OhjR-xf1=7dUflve-2UKSzA~)nGBez`adX%` zvU}zm-uwQyrf5hC0d-yT$w2;!I1c*;pl)d0392Ix$hmyz@yFk@nf_waxH;cKSEcLy zaQXEc!{`4X{TavRpsZ>LPHFvw|p!L=N)RKHa*z@OSN9 zrJ|Wpxl_|-WJo`T#v@7Ph%fnN{wmiPQyzV-tSp7$8)DIBHK4*<0cPq*P39TX*D8L8 z1}f~#>0E$Hz?@+qS?m+wRb@$Ucfsj-aBj9OZ#7l;xhnRoVxQ*`ieO)(ANM-Rb0J}) zzh{~EsHSKI77zA5zk&_Mn|{2yGv7*jh|m0cbHcE|+ldbY{>ksXK3u$Toqr3?)9gd` znXYdPYmcPsS8~S%CST7|iJ+2`=#roe9gbduXgQ=ue-%8W5P?<=^d)4R>Y(jYr_R0` z&)NkNgYRhR7L|DU|K-nI8(zPfe)_S5z!J~`uw|8^jkDKBuA`!^6VWBB^ciuQx04%2rae&gY!$iPF5GNN&t$%zbB5(&lP@|TD@V*;T9mB-i$HY0#YZ$GvtmxBl|(BZDM4?NP*vF*)C$6W zY?MXeb6*%Ow{-Ma7<(}vli)b^G8|*Mi(OjM&wZD)DszPO;rYv#^49@3SMWIi^Z>3~Cyt+ecM|n`07%83y3R4hWGr?{xA`&d zv9~ukI>2`Z>U%%_(?9pX?$*vDdjFr36;sfrgmlZKi}C*A$ME$({_!7U(Iml~@MGav zoxXj(p3^O0@6Sx}IF)DI7F@hZ(R+w>I)e5K(D|jSEKMh_sletzF6%w-0Tx5{^YS^- zS1&^fKULyZ05-%RhWSUlZYA=oMhJ$Lm{0oZStZN#{6hB39Jv?|I>c6dFB=}YsO_1m zOtpL5uO6Ns zL`19srHtFyEf#oaTT9krUb#I%Xq4?i4n>ze^QbaY0vCMqoVGTQy+>dclX^Jl79;2x zGO5VKK}@^O2^VVlm{pj$YG+I=Uj0O(5G5bXar{xZXa4BIU4ghj(_j^ZWnmV3cxJ z2g#Gayf#$c)$)_x9(7x$@U(+At1mApN0sJT_61W^ zlHKN1hNY@(ozI(^=xkZF);2$5V?VV%V&_J0?DZ`0mT8v6*6d0eWzV3f8d=m~#|d`H zNo)?aiKVqG>Nzu{1BsU#Qd3kLoIHJ|29CWn-9ml%%zK(V-bf{t&k}Mx6k*!p@j`oD zBbP}tkM&s**=Eo%2gN*pfU=@-aPtWqHIHBawXMV2RW*JkcB5~+TZCyOsqa*8NXnS2 zM^&~sYkg|9>lcqSZC7-&be>=rtf$>RnZ)OovRapyJ%HHciRpncNhWHSkEn;8td!GW$odI zpLnOf|L;K{K?kejWrMyxWW(G17hkzCy!eT$X%wJML*7gj=<#erUKRtqiaAvhqpVO{ z&4EpX|G)=+%TE!s-qfVBZ)Ut@Bp#I02W+$(N`2BsZh6eBwZ&efo!N+a-dl`y3%XwF zjN$}L7bnun+Q%Z#mPuHYz9qW77(+P0aK*a}qVx`$Ma9S)T-7#|t3v`u>^qCb z4L9O*4Y5i*Lkl-%B}KPvQ7u!~QL&r$>XZLq2ABBaY>J$17P6Z~lK6zEN-emghKD~R z(s;xyY{aqF+k^w&^@I3&Jk%LNS(rv%(-w~)s`VBomTA_DHX2+F0AuaJ*%z{?WM4a? zk%zjJqgzMKlb$9q@RA!}2mJIOUl?A0IsJjSY9Q2dl#&r|O3t>F$JW-`+QW~%{i#$3 zBLb;ZUS|Fh}+ zfBa5k9s_7o$TT6yM;TfjbsSD6q&a^mhk4XuD|vhtlG_M=Uc0?12T==`nOllbXXiPZ zMODDeO1BEefPDCjpkPQ7rA3(r2CC#7k;0c&(7MSZ!X4d5AA+=NRB2uW6hirtp}BWHM~#Q{SWF zyiwQJYh@WD#Jf?W1uS?~FQZ~qnT|_C2rm_`UuRC-?QZ};p&1k6kOuAlX5tCrpC`+!HYuN}g?6-kKSf zdN`O@qE>$_h{CqxF-BqykN(6uP@TjX=Ppvq#;w!JJhie!uhrJ4-hx%ke_k79jWnbA zv?DII5w<$N*bml!5vO=&&v8QFWE<mF=p5o{@`+%4BkS7ZVAjAzQyJygSYQT#?qNTlqqojgqLh&@cXc(*5ijCxfaJK zcl&6by$HZlAFa9==L=uLs0G|Y_z2&)(4k3(-~3g_N*eD5*(S9OC9{-1JAtS(}OyBlmsZ_y5=Km4ljH% zy| z^0ppR_L^BZa)YO|p(Isj101uFXH}{(sf6#^Sg*%FW+Kk)1HX~hBFYAnyoJ*Zfmud^ zzA)yMn2MZlc93^Dx$~vW!VQbjA!p|8vr)dX3PV33wLwBFooA+uJ|mymS_vDTdRJwG zLK|_E(q28Ix_wm7T7EH4tw6(xBWp0R^Lk{==qsd3y>X*DN{*=4NLJ6Pj*?mUC|>qR zV%w~(oe>Rv%LR>2!$X6y0DBvuX32=uN44JgBpIS+wcHE?ul_{<7@QhAAP;)<+bA}E zK8rQZb>J<>*bdX%=tP8geL3~nFT_q?-5;Ldql5#K zECJYwFtCoKPBY9mcIZ0>T~J+|4Y}? z1XoC8VY2*G$`_@fyw>D;V*y9a!waxy@+ir*7h~|*V&Y6@Tpp3O_T_@e5$WwYES@cR zdf-t=m1Z%kF+?j7yjYVvvBadZgZgH~xTtx7=SEouknH3JPY$22u__PKND^LkvRX}b z1WfY8sf#!}x8-M&7i@wJi zkuF;5r%7A3&^nKCd{ZLEBORK5Dh0QkYYXS3a4M(s0YV%=K~^2M_86@t#}?r=i}+}_ z8@rgP=HIxsu=v{zpak^3Y70%?VspgjX|naL>BKTO`o?R~B&jqE7^yXz#j_luHP(cO zwz8x?GL1&e#v<>>=*KkH^d<)126*Y})!~buxs*Q#pa{2GzU99A-<~+I+;Ae8N=qs! z-NA&M;;=;|bXbRP3}hP{c7{3uCGyy~>*R^IFSWjw$ap<5zMMV)cqu<4V<-%?4{gtE zVCSJE^X8u?icNn={3o;~5~mZJ$XL`!FZ`m?@?f5ZZ~KLD{7pukoxI03NfF9uU6e86 zAx=J!R9;YMx$lb*pj5ED6TUP=xmtrKVe`#W zlz5ZNCnha!Y_lJSD)vY<<}4nT%EIF~S4=Z6ac0hAU`@Cj(jrPyY5l4kTUOo-_3Xw% zFV$7T$NZqxs^H!v8!cllsc$m_%JlL73wzgwFFc#RF215a4~#xJdGhqTQab5R(dEM@ zkV7YJT^8u=D`xkBTS!B_?kVaHKppXM?tS39K6G|xbN9i0zWVR^gNy;d4*=l@MYy2s zrB49M@S8L)HjRm=JK#}2R32Isn$gE3ViC;QL)&|c4X zjujsHp;GKkfAagU=dGfSaF9B#V_RFh_kHlsec##Qk3@7rrNmVhST1@xZ(o@l*VM{r z$HswCjX^ne-hAKtzW0Ico$Yfgy8%c8@Wqeg_y2tgxiTgcYVJZt7o_SZ6489*G@G^q zHRbDhOxCr^G+y#tahvg$=waJm-4a^pDSwobJr8_wL^Kqp0iQ(^Iz7ut7@RlKMLJ>> zO@6AIZBLQ6U7|g^WW|F=bGZKWwh;?^IyCkkW3u&<=_r{WdDuSinFYvLD=hVeuc%RQ z69?y*sk3^qfAJTh@7>pqIaYb~(A43PXAh$smM$m8%KYmnniYod(%2rStS^q5Lk=<_ z_Sg)no~XyG&e%M_7dfY~@-jr3wiScsjpvojzQX4)@G8Kk{@}uJGyRayDh^!7Hn+CU ze#I0#mwQ>pev;w>+(8H#3>KhT?$jMq(@#j)n%x0JJqH-?d*L# z%D*(qEm^JiC@(8wzUaHgF*~1$@`X3_{AXP9pvE#ZgrvEgYFiye9Bm0tMTHj+m4^3> zuzG}(U0;ATGecLOMx0EvtAq)RsQWT+O~7^!av)CTDHv-}+Vm0xH(xP#=^QCAI;FT6bG=vrKV)OKI5qt$i8($ag@ z8}f5~GM%BZQu6CN+R*>?7(@hX*cG@lpg!FSq8DYh$WE5tt0Dd9;A=TuS0(F2&S@O2 zU5v~2?d1;wzVzai;q{l&kH)RwZ2)}`@PUUPe=tSDJ(h;nm-Y2`B5~!q`m#=7RT0z> zmEcvwB_p-U|1;0YL3ao`7lUQZYQ3+elvw#d>;J>jaW(1@61XKSYyv%(|`6Kx7u zH9~$WVHS_d$|YBqq;FikpO@pp;j^S@Z>Zu@tNN(jOvuJ({+LcFsQCgeT}3$l-GR|Aro<(G_!K*I=);^xvBM^8mP-uG zV$@Ya#=$)dJiO?0OzP))8EOxBJ4I*FTBZi0}iAFKIf@(US60U(jo`j3N4&r!E~45MnKuN+l!8IOcsR#crd?v$ZUa0AKpW839kU z!ZUbTF=LtMWmHYp#OAZc*xEil>AIxBGgEH3^IS{gS>_z<`1eZs9KZ{oyF9GxUHc^? z&cW`x?~%79`$jsEj83>HQI*i$_Tw;a0MHm&N%Wf_PP9}7bj8HKI+MVjU;}J&bZU1Y+NzqEIuW!ji~u} zoZ|a<(VqP9Rzi5 zEP_l%COAz)MEz)-vak*C#6i1s!YnG`wI3hGTEuF0!z+?$gz_4pU_)M(n@6d)B0_wz zG=|xm8qKdVmN8mnRFBu%wr;WeKFojacBAD?eUjXiuI!etVnSnDlknPj9|JCrBR6xT zY>#aY;v~i{bY5S$k~7`{xOnsW@cc6ut0#&UW4{t|Pag!-HvyEmO3+DG=|ezw=!f;o z>O!?+7P-|}l!(7}`t$?4M~@u6uQS@wbIBU^@E1RMCG~C!k!PktdV=X`?isMC!#u&IS-`d!3**3Qc#MZV?#blZ&!LBn}RO?xxvkZh(*|)%F znIn2DQtK^?ILDhSDV8i_Jad}6@Hal~mCcB<)Od_zTmc(B=E&3qK=Y&K=-1OMsnUqO z8f$$HX$>1GRjW(+wPl{WdH>gwKXI$X40YFmgnjOlZ{}CcTDJXba2?v+-hJ?qN8Wlo zc_V1P-WA!Bu!Z7v`ww^kkE!uL0M+Q?8v$7-0FqTgxA%iT@{{*%>};J|<`)3ru;{>K zeBn>7rf3LLA3ikgno@>r{lKY7E?IT+lLW?=l~&W61WR0UwT-_0$2ME?&2#p-EH0#U zx<=B*JSBq#7yo$l>bfMEO&e=|=AQ_hCDgzTo44DAi5A)nw(ZrZ7D}L=awa=S%b`E+#Cw$*~R%d{vTHSy?-oN1d&BhugmP1v%yAwNuQ=iYV-IKS zQEIYq@i;dlKGqP)qV-#Y%wks9{4t{S0i3bbR@5y5)3kaIExb@Qw{D2+l>lQgtR$3T zG92=-E)#}6y*8@9z@*k<3_qK0034A8IDmu?0`6>X-TlGu{)=zPVaS1`dN%@XqYe;r zzb>o30bq}_cO|P~b)#X&x%a@MZ$H1YxqYhlCmHwKAnYMmF5DO{e1Y%(YhhxL^8_oK z3<@RIk1=nRPrGN$`PDS+fNtyU$b0iaJKn z%k*rQWv6I~H<5C&Dh7Dytw*8fN-kQ(F{C!j{Grbjs7BOv=UK#(SE@nko$?`4TZy!a zfY^{D1KQZrQ7;ab9vLm#xDz4|l8|7I5f}@P94{bwn2&FP&%?0tK`E_DWAi9!BT8Ch z8GSe@M)pzf1kEl}sjsC$G@PbadrSjttU=Q~{NgUMB6RUCdqt?*kBc4lq4|rQv1>k? z#!G6eWmU%$TK&-Jc|mds9OmuR`XU&uW2C%~P6aKDb$_>Tens2Q&=$kU_J|zR%cfOc zutg5TN>WL0jtM>k@WyM`o4*X`BP=W9dbYK*ed^J-K6y{_LGuu@K1HW=WS9O>%T?b1 zsLxd*dEX$(Ds%kDAAIzoot^cK`N_UqKj0St@#+6Juir?45eA1!)=>8oT;C9ob-qN? z?9BUba8N9j-l&TLbXaQqXNaDUS-r##_T4ONocPuUviD0w4s5u1mUN_7NhCe+Au~pV zRry0UG~1dU6DPINZb<3dl0TY?O91-NpJISvgXQ*XB?#Dm)bmjPNBpm8~LrRs)LbzX*TMt?y<;3 z*`OplJbH)s^mZ|L-D0^Hs~6#5?7jG4;@*&icA*Cs8|(S_y722+Zy-?xu{5qpH|8Mw zMA_>NKrOe{vAzKC;y=aY{n1Y1T&f;WWb2RIpB;qdU~yZl*=!DW$nyLwd1bT3&we@C z6fu{{R!Zc6w!e1s+VG{%rw;7xCtmzgm^8@!i+pcspznKn zreIS`%^(^c^5T{%jW0d6*QCYcK8;}Be(q4Cta|Z?j-0L*21R<6>6SK&tfQ|vpr{K| z+Mam&H+Q;VdY^utQHw~{uhGVuGVr;;!W?@ZhU{CtmSfK>OkZD0l_75sm@%W!A{h+E zrOZp8P4pcWE_!b~@EGaw@g`vFNi193f+$lMrDlisxxDHeQauTq7>*T_0ZlV*34<^h9zM z2m0sCnRAaPE?xL*S_GjDIYcvZ}>_ zcCuHf;aAkCYx}@^AraNVJtdd8lZE#KORRf0UFf_{^0ZgXOn8*$V-mG~PSL|=j>l?2 zQ0Fb^M?Jfv>?0??L^yU|7T|4BBAT^~2#yXll9hIixRMp>`S4jvhg^8MP|jiof*#(l zZw1!yaEzlFY@%?ZAXP6CY>`c9mh@4CDF*A|jBtGx*?KkOG^vMMLYKxOXSy&xBLRye z#zjpQ>vxP|-gw~<4&hNoCc?bk92cfoxO$9Wi8n;bw;oC9;W5Po8xFIh7x84?KBpue zmB3408`7Tx8ov14B`8;Npl?o|Jf#}|B!*zUQ}JN421t8IWq%&rS}^sdSkze5Yk4?z z>ip58M|baAJc&d%UxMeQ&+sn*<{h*QE|XGfb=l{vr=U>Kdz;;1W8`ja?7TN*U$=>> zOiCG_kxppzNF+6oO`>I)$C!8Yn7jB#WUL{N z`iQv+fqr6NP30kE&4+qzix<+wi2kb1){D&CG?k@FX2*Vr(c+jFJjTdpg?iR81|Nk% zSv1N~irLcqm_vv-da_shHuZUlKKeufBVw^+o97j3L0N8$#8soY{!QCvVYXfif$#Y& zH=n1nVodf5BdC9{>9^1PfyYg4FB5XbaX{SE^6{oo7?zfm@tH1Sqla?sVW2ai4rNwgE+CFm6sZ;kKE%XS1Ay7Ea2c19ay0@)FUiA%t5?13> zN3vcgdGC9F;HU1{+}Js@s&4?`lb0_)bG4b6oECUQXfBI!GUZ9^Q$@PVA6ztk;_b(G z%py;1WnpZPtVU+#uNo0VMr?0$B0`a>T(P;kh{k3TJk*QkEj#_8m&PzA&DmNMqP4Hi zKo{Fb9J7n^a`7y3l)#09{XWHIdg19c_oxeg4}(pbCqBZbPy18u;gz5DJTkJ=`bzm( z#)gAS((5dNdN&oo%;jHmbaU1--Hx%oNGV&D(M4^FTEm(|L?18j++Oqn_6C&cs2PN1 zInNid%hkZxXGJFcY$Cp19~QU02_T=%5yQ5UqV#hBe%jKq@pBP<4@DwlA5(^>M1_aE z`gmsGaZ&!~+-}>UBn~s6dbd~ZJ@(LB z&ungNp0Ytacq;e-pi8f&_y1qUhsDq|^{o~{TVHO%A3P^`PEA9vm#i{+DQ{kYlntX_OV3H>?KMxsQ1|}*rlO8KF--9GS@JF${a%7 z$F0${MMR9|ZL+BBjDET4fS32QxbW3Ut*|v7^B{iNtLatb6ii z`du~O+k!&Z)7y9ndGJ#FELHP-&`GZnc9Tt7oS9*BcPAc|W&yhnuQfS6>r9(9U;bwg zYSZPM29g@Pi7;==ojPw}+*X4;^~nR}=F4!gFR{ISv8TByN{(m&{l zrKaaaUp?Aq0OScvZ$qR6!;Aq|45i@C#^7gOZL?0otZ|TFJ zYe|D<8q0C=;Hmkg5%t(P*$=13#ayhNCVBMK=Mm=noYTh`Ef*i%>=u!{hDuVtA)p42 z7t%KY@&^G|@J#?**Nz_D*t+lD2k=(`kq|=Y^w(eMk~tr}FQ+Ad_=s(c2J*^SgP zBgOYfa&bmU_N6W6o|i{b=7KW4d3g!4PQ$W?U{cFGMmo#2UD|51?%GjjT-*fkMu2@` zYbVw@krQ*81h_fKjH$%mf5`QslTA8X{N#fty)#2@q;HLHwU?PKFM=9FGKf*U=7k*4 zXDE1(MKzV|!M+U`4dY4Y2Rf^Lt2}os3r;LH2Edq3QNKXx-GiQyWAv3p|Z1_EP zWg(QZQ7Ju2_}BP)ygYthww3~iTGrTr-Msdi2^`N}lBLmT`pKn(=lox8B~`%4`qTB)$po>X*_V z1zO=Z0nWYU{)bacY*T84Id4cyimjFdm-0*VAY<};kgt`NmvrKhMRnMv^OF2%!z0^kSTgW2_gKjVEjE;RQ1bT2 zYb5vavZC||pL-7zvC7kDWe8ZU=6gOe!y7o@+4tU_+m_6p33hE95xc6IqSb96zpQmM z_D7v-%Q58-PqeFdQ#1N-fsUa^{ix{)%pqbqn{4iR>vSG%YkKj-rLu>CjyfT0i>YdzVx9K+8X1n zE0hoG1^^8}@@TLcgR~`&<+0<(@3+ANV~LJyQI(prz@T(HE@L)YrY~em91^X(dO#ENXg1)`iO~CK1EBw6V3& z@~bz9gAW63>%F03(n@#2=sm()PLd=!cLhL71H&u?tD=*VvoP9023Lmx8c#bE$?C7EUy!7MO z1=9>k4SW9COAFd&aTZDFqvLno^>D)Z1R?k8&|!#r=_mDSkHfkFK$gA6LC>|Fg!>+T z;@H;K*17pf1eYa`)MYQHzXF&K%+8#G(ongGOa0!%%O;K*)3Z~DM=eUq*Z^xhdHY(- z2>uff(B$gP7Ued@##l=|<^igCSjVHiXX-ItuA}gRgWqaAmM*?u_twqfogdg59{&2x z;q-lJdMNeUZKd_;{H~=xBXKQ#75L_jz2VBmz2W+m^x@JAH?#c4t9!%cH}-}LU%EM5 zd~I)d^VOT_+#B9_<>v6ll_aOPpU^J63Uxh?4wJ^D-xz0rZwMO8T;e)lC#;Vg{JB&= zXUx;{sa<`mN17}j(_j!vR->|JtnCCcZ?(yIV5J8Un8m&|urY#uEzc$b{m9=wdKPbu zUUgm`>0}H7_Kw9@x8f9|I3_3J4H+E#XtFMRUC%fP9EU`Bv7fv~@-ijnVtW%1M`NCc z@q4MVO|fOM)?>EzsC6x>&tf@%!ym?(wP`$wt$BpwZ;jHrHoWlMWw)<+-u=snyS24_ z_QA*Aeb;lJ`rQkO!6ysZ*Ac*yLQM;Y@=709eFM;Ih#II84ag1rmiK@AnT?IjyKE3| zt!>w@rH%Ou>35u6e_}xEq!6XA=kWKl@E*eTbYhP&n1lQnA$LL&ReCEnHQx@)$uluN z{p3wM@x>yOkyMfaAaQdjFBPW2>20vU=@MaNh|TRyZ8DoAdLhPp-nKn_=s(#V9(~WM zr=eoqp7Q$Dz2VyBVYqsEZ@BdO-thA0Zw@bh=KAo`=TP2AuS(qG z+@j)F6mfCKXB2Q_fCF#OXf2?Bc&E&s)P}7&p)s+3wlU0PvkwuAn0VGxX)}X#iYr=+ zD~(F?pr)<+=gEPLmv+N%43PGlH^!K`n5X)}GExj#RA;H-idxi+6E*e!sLhVB!~^X| zq!-iQ1h|%N=<(C?6;*UtqK5Ugv9)>P-}<%>ocYyH{qF0@1tF%+B^Xx+IgFiG4O5n= z>yQoQ-q$_wbovV5R{A4AG9SFetGI7|>H2W_&9uF^#mnGIK_%l@4HBjXH$myOsjzMs zpCvHS?yX{_Zk&9nIwi}S(@-_zQ5I%_#N1hunVWjjlt9x9#WVS=aFL32A=zg3j%^0Q z&TS#;-?icUe`t63&@b!`8;3CimFR6MH@DY@&F%E3KToU;C(jSVTb@Y2M?Gwf(eVwr zOBeWR!SjD|WBB~XuMeO5#Pxi0@cc73hF1~*HwOG^<+NF)!2$yg`{srEmwNUxWSf6P z2&a;Gd+h6N922svpRAJ{W-L|;XMJSjoS;pYdEz)XEeq9^y|+O2w~;YMjhwE>wX&Ec zFKIep~F#*u5+ zY+4Gu-*PRzfph7V8^fhN{FtLcV*+Zc0welUHrAlkWvOok0$6QTaTxU$E{_Dldb5XMFZmw!{_J=7{lJg!4&VD9?Rw)^wTzq7 zqbJwWxiOr3cw>0#gXwM|9k?;TTL*Z>;F4V`j*v>^NJ{0>xJ;q~=@Q zKINkrV`eghQt_mO7a}Q&^h=fHB8)4`ls_$q5Ti`t8Mki@+T|8(Xh!f4mPHkfMZIc! zjKJC+%_-5tDWSgxg) z?r-(LCVTPK8^hJKajvhSSq!Pb*uvj;*K<<*0r&14}Gu>h!^kZ9Z5^gzwF-yASU{7?9(~EIa_3Bo`(1RZAgw=f3Fb= z9M-FRh1Gb4iY4vl6|JZ(?3dPMMxz|-R$B%=353=XAd-I?hd0sT<>xKA{mSwD_`kUKlJi2ZUB5EAnU4&631vibK>rm{S5%Doi|>(kv@1e z419i-W4Q-}wMpU(PQ0~&c0{)%PG!MG+NXB=RtBIyYMptaFVGu^>24V)2PrIK@iOf|K>n|Bdvr{}y#Z+$pUG@YgI)-m{+0?cvGyb0)u@ZU{b?ZV3MH z|G7GRJlzyLlRjVYI$j}2{f<`#xUevGb-#LDXKY)QB~nI`3eQs@esV$9#mg~B%+IV+ zYWhH9``$0}#sz_qd^>bgu4KKfo@cSDLpw4u#u;x$ zp~G8mue_LkQa)R6{Q-I2ee&-65)-BavKF{EKB0b9S zJaO0Qdu!l<>#0j$|2#eb=nl8Aq~(0U1uGzB?w0iFnpUQ?7$zCHbjaDuK%`AmDs$-S zoEcuen&t2ep&~UjC2mxd*^~ZRQA3?BR>SblcWe!h{VD&zU)!?7*PJ-JKAd>p_VA8$ z;{C*z(gzAZ{+m~aKlo=?hmWNjf-j_(4RGU&9Rz3p@bkStXZn89+6Y-vpRM?Avhc_$k}~sL}BE@)Y0VMo)Pzp7YKI8|kkk zt*V2;^XljD`Rb7#@zPQ1{$>DRU(Yx-Ay*FV0Z}yJ83g&w9rPd~$_MSc=|$rCeU{LB zo5_&Sf@w~AV)Jz8+(q{=C+8y5aBfI%C1ke25_2GB_&nx+c z;Mq5>r4JPH*8}wJf;e^@0RN4HI+~3xM^<*zL*qw2;qX>K)8;RB`#H(r1X zi^XETG^Lq`pA>=@n-d zwIKF}=WemyD%tZ$*Oo$=g_>4x2=(DKM!Bo)^wkveOyZ(Wei2c;Q(<<@A&yhhJ{a;syXD{MrV939UiT>BT#F*r{F-}a45)dY9{$|&4hO=fmpuCs- z6M30OId)oexaf1%89t|wT(Z79vB8&7K5*d)s5muC{kA`jTD&9N5q)a_!_&eeLcrz0q1WAqv?YgO>o zGvvh_2a98@Kn=Q{Q&i#@fcQGWR2TzqJ(c+NJa(Ea|TRA`}zor2(__y$Qv@)%KLPKI-tu ze@fA_2qT+kGn@BNF43b`Q$N+EV$?qB1?Uk`@qP5#r>@snz3h*Y|7wrvIxhVu&#!8_ zO~?6Kw(J~T8{Ymc+ry849zD- zYW#3Ro*6zYAg+#poIRW{MaGzL-~+X*fWwcQ;i<)=;z;%-s5}R~p!G+uKa^t6MqlwV zJ~OjdIN~!jMPqz!pVaj;5}w`09DF;?zEG0eLf{?1t@gl?V{60H-?fv^3oqRqKKg4{ zhJXGKFAX36%+>S>=bPoLg)vGVe|$AzdZLXEnk$-lG(gKi_Mh`CI=j0QA-R=K8UF?|=M63jf)hGV?${oz!s9rW+*G3d+O00bozE zaY}V;=sjo7oY>mfI_f8pK>IE68vvIt=&u0s!Uql|Kt9&68Sz;Nrc6Y{#exY4yGpXy zqim+31U!zk1dOGJT13pfTRpJYVq2^aEMgnQU;JAuJhhz^c_bT!)~)ncy;axs zj^iEGA@u;hYWRWl+Y8_Rqr1a%AG=p{o$-Ml>AagJ)bp)=Ehp9 z16bkooE(iYPFmB~0*hAjwf5q2pf%g7g(9A|t6=FZCgq^gJV%YYFz6zzybLW#F$|%| ztwuhv9OHVm+Ua$pEN1gGX#^FaZ^51y*Sk1te_s~{ob3Utw+IbnKMQj3{dSM0K0bVF1&VqIP;cU|1E%<>8mi?o7>0lzwZIP0)PhUtka(B zz8ZT=^~1UW(CDdwDv@k%+}X4DoZQ`A-@JS|PRoO|2IF|?_4Hfq`8&z{JAe$+4r!LTiZ;PSMFjZ)s*OQ-ULNc^g<2Ca-78CQGTYmBYkD` z0KQ`Q@RM7^Po(oN{QU9Zw}0*O@azA>#o=S=4`|`{Gq&=CP2(LlMqP|G4_HF0i@wUX zmB4B~$e%YBdbIV(*(WY}js997^`de?;Y&Yq(Ih6tcMctK4$U^hspX0qvBUtt7cZngE{CtOEc7>~o`f6ABS+TP?>;sD7!Y9ULXpA{Hu>%PJau1FTWyy_v_cnT!=G)7k-Q=R#`n4 z)rS0x6aLN6lZ}^Z%9vr~0vI~JiojJY42kp%J;y^J+1Auf7N`^v&n7ysq6pF)baZ?`$1JXM+{dPcrj6SA*`wH>mlIO4*q zAMH5ET3Zkqx=ZP%__dd=4fy8Gs*V$9PoGU(U{n}{!iht2-FWD0U3R$DhgIAFDET@U zHAbbkuN^ya;%=|FB{_A?#h3Boq8;pA_L&mUMGrqrqF*P2O%nydLY)+2rqa5uR&418??u^ID-E0?Oie90_SNglDbg zKqgJ_w(xcL>>L+zB)?$esgSk#D!fp*Zsw8BlhwH}DliY^}FLMd5E)b*nGv9b#7D*muF_N-$T zg(4KK5Bo!}1O#xZjJ>7&Qhc^q$bJUSQ^0+eW8~sj*Qe;Lg-W?ass-fiwE#Zo6ZT7t z9cwlF6q|7Lp6Af#zWn%l-stUL5q>qj0??fl3ct_t_>sGC-NROlpgO^#B#@Y7=yCXD zsaetE{54^@AWF_BEg5B3-rU^MA4XU%${Z?X@cQ%kEi{EnVwtVlN@(FD?(A^LmMdQ| z56n$l4m%f*(jZZFxk~!LqK#K_U5B1bc(fx&XjZsK2&+w)bwfyEiAQPVd8ho-(F6FQ zp&$E|6T^S;kIxT3oNffpr1#jy8-d&en{RAUT~AufrE+nzc~Ee$mARImi6`0r!_2X@ zDQ~YsROn?xJspL!DQV4*$RK-R55NhZvL85DBU(OYs-lmEsf#}V!AEgZY5Fn4k&glq zhKuH&9&6vPe|e~{Ri7`B&s0uTdmK?OAHFRDE<1qhVGXfJTF$%e2em8mdp3;uD=)0} zw*Yo_cks%$IzdU)b)S@MJ~eN@C3zJ$07yVdPRX*PM9lS_qestaz252+K+sF+TL3%| zLgA=E&#CuR(sH{Wx%3bfl0U3YeQ(3@_$V9KG6SmKu^8}AT5U*GZax;aVQU4vEVlJB zJrt+bd%ipJS8flSdvIg;@p2=8R|N8nKuVXJsf{jMfO^r5^JsqL!^`lt8I<|O8%@qp z&N0qA$eMGKRfoyRNDTGPD-SS@N5WKIX3A3C*3rB#A;+lmOAMhO7Q~y%WJ*7u;4)sw z8s{?D`DpEp(NwRCr?q#wuYecj$2KfN*`7#RdD0Em zzW?AIk7ceG9k-xDV(KF$iL#be-+Sa8?>W9XtRK~%Uo>doUAlt5p!R0^LqG}uN4<>0 zVJ8)qxgTKIlsFOo(Oaic-UG62Qndwj%Mf`8tGrO^pzL#yg=Kq3tBpD0+8Q{KhmQ}G z#YU}nOe6Z)G7`O}eS4Aa=)RJA05<}BMc~}*AZ$t{NzIwR2XQDG06`zvyzVv?f61PWGvT{mg zZl}wkLZ+(Xx0YH1q}H`;Q=2>YKx9rY;rk_9xOglrSKlY%#N4nCMoL>}EWL3_GhLSa z7Pe(kdtr}HXAtwXm#z*6`)h!0-(sk@);4w@c;uZ&6SV*g%&tI4)gkME?of`YIdUk$ z5@~M8gAYG;Y<**6mtCNKI9N!mr>k$KKLeb82Gui}9QBBTpCskoC{e<;Naf@&mO#lv zc9zg%aq;5L4TNYtq60X|Y7R2CCJkc%06+jqL_t*UZ=AL1szestgkxT|ksu0UpzO54 zZARY84OyJx025IjOO$E&JWjrM%0JCLaPGm);V1sq$>CT2{<-0U?>#nbryBu0KjMvm zZ&{jSy3l!bSH@h?8E2)mm}hZi$l^|L z=7vpZjF~^UA1Xq$Wn9%TAc%&vHe5|#Q@?UCJ-O_-`Z)43op&H(&OiMc;8X8@4}J^? zN)Li7gF@m_+ofMwxvCogpQvPCLRNk6iF+SEzP_>Pegp_p3H_mSs(tK8Z_Pgf^hUY? zxQ1qnQbu<9ArW%ng)IN4L)a!3AZ?t#a?erx!-zju20rT_Ns;oT4NKAx|?#aWYIg`3zp z>v_ew@jUCtc)g0X~lwGacXip*6lPS$xSlj=5~e zM0RDxIvy|HNIx-uA$`C+{}zDkbqOw@QF>nC$cHv;Y;GL6=luP61po&O^(g{6W!T|8 ztm+0}Dnm6+cGUOI-+TV9jg5`*EdaGxrNAiTfO<=j6XUh?>>tlWVvo668Fcce$6U{; zbO@#5_Q4E%W{y|T5Lp>}pApyyx@oBm{z5N#xxrFg%U>D8qU`E;sz)re;uFG3VKXYM zP;W8(9qFs92j2Dmo#8+GU(XId`wORshc-9UpCrcY!r1F9?HxDWW!hiOH@$z4`4#3E zxu~jq@|>-8#P4Tjf`KfND{{z2ZTmjAi>v%oIdU|8Gg|0aGC-xaK&Kvu?2oC7Ki9vQ zl)Lwg^5U2Ng`eg^pV;0tjZ<%naoG}#J=R_yu;qBvS)%9~;l=~6=3Gs`26*X>bOWF+ z8OgiPCpr5|bV8<{ORCEk(%%6{TzcQ%E~*8eIzGhFLL6y= zb<{2|!2e(z%FItzKPX=B<@tE_%7rqU%1#ShIJBAAzK7RJMkyKos9D|1wBC9A>gj=v z^h+Y&`QyiikNh9!hadd&Cx#2$sN`v80JYXF$GA^aWJ^~WwY~6p0*k2} zRSLx&-f+|t6|ukw2TNs@NyJN)a^fD;vbH45f_}K3T)_=MdW)I;pqx}1KbYE`4t8Oa zHH~$;?ronqd8&Q|0PZ65ve!Ys^21+i4np2b5IUWnbJU$Uaq_OM^eZE}_;`@|!7k#o za`0Sds$&!DmGqYY)3-Itp#`COD!a2-Tn@LCcu>D-axGYtI;YXz;&Xw_dOH!#$9rUL z)i;gZTxa=p^X1{>sz=fz?#N#)J#Z@hrN^K8$jRX&|NZ&ly-)1sn*h#hEcf5gQ}XI`XR8EzPqvBe(fuM5z6x4|;1R zGBK|FV;0f2OgpqMh%d&fYttx1W^~Jl=qVrEfbHj);t_{sAWg62#SPec50by-Pk$em zK9^JG`4xct769z>iCFb5codB~;L0llX>O+L-f;Bj>EpnqgT^RJ=K~ON2#>dV12C16 zPhJfIHp?eZoI0&;tO?YSDrFa;I+gXbxRY=>{T+ZCX<*dx^0C2!Mhq$qwT!gP1gNiJ}aIRT-I~okH6F zB45iqKhbfyjIUHUop~urjMUbAZqXXK#An)iZ#7FT`53iLb;MSDyV-&j`x{?*Hux^~9g4+-zXO1VNKY&tb%7+k z5)hXr2}&K(q3-o{UAC!Ge7+Rc>slGvDZ|>T_OK;vuUcJWr|Z@aDNfjgV38I6%%h)? za#YE9d2@>@8ty!P&Gf*=)-Zhc&m0^6cKSuZ_rLqtc+=Z<0Oy*YaV-WKKLdd-6AaJ# z)BJ@-92uD&@-dSWBi6W|^}8W@ho!Gk;ZQ2^C*0>VeHBv`4j@y5ZSV zzV1Dv4DPT!M6;_IpU+2m^xp<>?pgGnwN3T(6fcKjJc}xOU5ZoXICC6uf~CAlgMhEU z%-;e43(zQatFzx3gZ@0Wd-6=8Q1^gRNk~>bnerjM>MJXi#6bzIXZs9FTn$3GzO#D_ zUxS!91f4J!H3tEe^mm(I<-3mFQv>8D{J6OD;Ci$@GgnkytW|owbag2VO4+=QvSr@d zpow{Dx}jA5D*OIXTMrPUSK4uvTW*R`AsspMlfM;a-6_9nd*J*-o5NrK z2X_xY|2NJI=hilcYk4xId4su8<_FfPt<}0K>eEI!8ObW$F~Xo^8Ai@fGa?qaEP$oFiKEQKh*w5@R&# ztccN|!YADIGI#W;bMaQ6BlTmZsS2yFdrzUU%5{j1d(Zy%D{(9?EnXixZ=bi4aT;U8 zrnbVv%e|b;-e;zkkH8AAl26kwypmo4C~sB;SN@&4!!)Q1`82z{~o7CUV9^ZvX#MgUZa(QEJ|Hw=ygihE9EC1l+4RdBxcz@ z-;kFr@?MOdPM);Inz~=wQo6pVEP1ecQN*r(WL<5R)q6&px7?AxhI$}PgYWu@W5eJ4 zAI}f(c_@8TKoc(P?$dKcaO*K>xZ2G|9a-W&kp0Shw+)^Q;*31f&XsH3w*VTCyf69H z-ypgQwj=h5waT*XIu~V&YBlDc*wsg-tR(m|3rgJBBZx5;UtrWW<)qTsFg=)3YcrHd zb~EPj+)cfvm9%yz)7cM;5LLErj+TO;u0MKxq;z3=9WR&Pe)n#8vxp;gQ zhf!iEz3e@uA5NY;d1P~a-TexnhwWd+-xIj>s{I%#$7u}b2yMbfD{8RWKj)OiXpuJ~ z!6twD$qp?o)U$J_Gd=xeVM)zKK zl{)vIyQ#A`#3T6^Uo#_QD*yQi-p01EhCnLP^Kr-5rehu(g zx(@C*0;J-T^D>DE2EY}( z;M7TBZYGzIm=<;KvaUy#VmCZA!YF@Q^=Mik@$cY=d{u%;vN0=5SiOuo4=e)snXfDp zW$r`IMQ{0gFRvp?alFNQ+>yUldtm#>+VB&9^VIO4{+DO-*9G(Mcr@$P&pXa&%`5dA z=RW5bZ8q2BjdF7Rl`D*Sf!Wt2i>Gg2K7xyP7RSMKQ=lddoLn*h!9tb;63M+O^f z8#{0NGvA;;2ow{rjT?H|R4x%=e~?w(03fP*x0m?XfQKG?@@VRSBlDAN$wnmB>sR-N z8)^4o03W*MAR;HZ#7F|?m#Z2l*4~CvV}!lcQoO#DOm4P~LjVg;V({0L7EP|D@Q<8s9 zXTHGHUSPvBQjbyu=+m!~<*M0fZMD!$zw8fd&4DXNSI-dL$A+Vz#N%5cf_gJI4B}5Q z${S@u&A`lLy-P38v0quN;FLVYG2f54_*)+^M=&<5W&e*he2}v~$Z_@Bjp6b|{2Xv> zca2`Eo`SCJt#6;b`@tgx4^o{=QLGZww)tS}O&>cN^;$0Pe#_lQHa6CF<|dl+ofR=4 zSJSTmULRkd>7}9Yq(ZHSnhkWw#yTI={W%@pdwMyx5sQC0KKiwNByY>aNAt2Fz?Cdv zpyOD0>>c@Qw+HThY;*Xf|M~3jLmx^Xj!tWu&*E&Z=v6E95TBvZrg>`f&r6*1S_!~n zvukex_$hvHGZ$HzBm7KKEouU{+?^6=v`cN1DaMnw<}UTr&<=I(zgiEEtI2XDy#i1r zuORy>(Z?H`Ydh!9-HjW7UO4a}Y;A*n<;T+Vc!|gpy`38)+uo_8XLg6R&6V5$fce^` zz4B{g$WUQaq8HhZp*JTcz!Ggd1c>FQ94&im5d#C^Past{1SDAMMMA{zv3FDy6`F5ERsPwSn>J3XKt*xp?m{?gw*oxUY-_i#M@nc@7)9;viL^H0z2QJ0P= z&MwYMKH_qZgO5Oyk=AU=4|*@3IH>Z?Kmkb0pBpa9Dz>yu8RmauO^T7d$kwL1h0I!b zarrtCuxRaT{}Z|`;lr5uGYU!Ur5v*|pPCOB;`U-}Ihy1>q3u}4L^`(Qp*DIZi#@?v zz}6lRSuIPhjwpC#>dIyPJpdnV|1xd>hP93D}Cx&c^bE^)SE)e+4)dJh{P zE}Xdda}yMwVY1||hFUN5x0QiaU*ZTQp#^LryJ2L7x4;! za-532gg(uO&CQL?-Q!2=ZvnvB2av2%hWbxQsvIM1Zynp2{!4%( zY$*Zbflp{&8$Sa;w;0rFJ~_#CAn8#`2Bwy4yD%OHU3+L*)<|DR&Z>JEY|xWK2x#|n zSdUl~`d5r@^Q-wQWNj|>l;;e`gwq}MogNs@-oH8g@;^L3eCRvw%75vT&*%NjwOayp z7gvUAX>Mcg<@q=Np*?#BZL`|1Qhhc+%(`)4-EuN-AybJ>Y9DI=9LDH)ipUt#MPmPPbRPJj`0(s)qg%YC!-xNyGs92(@TvUl%U-JI?2B)obr{Nq zKJ_eLn^l~dSaDGUW@vEc4+!D{GG0W8k7;O-Ph4$t#uBLg9)@qwL_Y{B8@3o}~ zU)npBDkH|Yh!nGKS-l3{%+70DS=V5ck`-y;c#m{3*HImWm_0mj6JdP^J8SsW%FA!2 zR{(Sl(?Y8_2|3X42S` zHnU(XVMlAJOJAy5B)Pf>3s8*_4{iO0Ni961_x#siJabRRCS=csK%UsgL5PuR|0%22 z*D~r}2hO@)$GKC#(*wO8SWi3DkNxt=;jg4WFT9aH6o?;X<)&4yPp;&w)%^bd*?aS# z>%Ock?7ZVQ{pRjp5A@7r12z^QlR|}7ssj|F(T*4ije(>zRuUsdC?pa|SwS!r%pV$z zkWfLQRk50=83>qIf}s>aA|xP%6cwwR^!%HD-|)WQ_hmo(+0R~UPv@R{-}_B)&Rh5H zwby#qv)0~c59gkH-+R*-KTLHj@O~uff&xC6c^ad}tdZ=kk%Uiq4mlk}tuG^cj#T;0 zRGEP?6p)#+U8T<2ApNKe%z<{4Y8_Gir=B6Jp^%IEwT@^gS(aw0bJH@2i6!`qa~>_L z;lUBLI)5aM&)35Rz&?+wCs*)207_UTLMS0$N>3gPB^(Pi*o; zDP|3SZ|ljz%QtWQ7|?n+-X2G*^Q^5E8nw_PrGwq;QD2X>2e$2julT0Nra%4XUOPP% zen$YG3&6pPe2g?3ZROmKu=u1+(r-{){4hE-LNz<#A{}MK>dwPrEde$hZb!AuXqtQW z5$AAb>wEZC7`7+Tzq8+Uz}AvBDsAZ7!`N?z1yPId&{rzgh1tJkEgftlTN-VS&A205 z=%80$3Eu{2>I+F|TlhiTCj-f*BzpFJr4id;{dS=yZFE*c4lZB0w!gOkLfD%xhhZ0b zwCojy780rT5sEJo=Iq&$`NM4{X~5U-J8}PTvv!L*R9X$JIpunCxOYzXA_h zZulvZ+g0!?)xT2JiUFjJXGz@rc@dDfd=YrFzn@=W?uo&}%XJ?JqB2Jbb{r+5Xj}F{ zE+sCTve3^eo4dj$AAXn;IjKFOQ8p^6@AJ7+lzFT@C4=};_TqYbDpMwujS+UI@*)=iN(g~_N%eHo$?>JV-2uR0!Dj&cVS|O*oMZrJ zIXUP`8G4_ra47YFop@PE!kU=K1FZODDZS{sk7v&tWz8Uz{*d15Hpmr2M58_196)d9 zu?_o*{ms?`_z!_U^Vgo8J}F)VXk=-&h2GEwugYEr6>EVwFlQZ4_OlVpv2Qb4o)Mw> zg}8IEi`gIsTW50EATW|JWo}sY+Qr*4^gMHNy-f76$f1m1OLKYF@vIK7-uVXtw(EPm1Ax3QUAp>sqGo6)?dUb4hck~IrFIPMNR-@s zlImSG2C)uKPmlI?0f3k{U%_>J2oXmzuhdg6Wl>E^-6~X)qf#WYqweLp4x4|#zOO^K z^p(3h7W^@%-fY&wR3)JtD|M$DnG1cZDa^65oWJ#5HER#d^}sv7;L`LR;THwpae8UG zSZ4;hqEU0vLe0&JG!HdT6_=&F9KPWa5?hkT zn@@k?7PgGo)_`LRuq>u@3z$Wcq_Tje3b|^_(ro0XJXBH-mNApv$(hSf5n3Ldb0EAN zJ>s50pX)F0_YOe#K_LAXz&N?K;Y%7RzC~q!94&I4IV<${XHGqTaCCBdWuL9M^=e|P zHh62od^9Cy_y;c`W1n?VPwlkD3*dk#XHB;e1y2{zS7KuDFWFrzpJ(5D)vi6T{~mb9 zyH2O?eE;jF&j>%Nb}P;nZI+zTvzvdFPMk}TgQFP?oGTjLD&c5yF()Etu(`PrRTq3> zG>a*^e*1X)pBD|Il}%hDyCV3ON;iH>0~-k+B`<2FMqDUMOSy#0hJ2Z`G@hZAiB&6G zd#TX{)(Fp-;Vb9k56+doyVelv(n-7%t|s}?%e#hH?01n100lw9{sFxjQOjYF9A0sA z2=)sdXW_?y{J?2XzfWa8)MyaQd+lbeUmb7}(oiQS@`*Y54IXmQ;Q6dGN0a8fpB6Gh zZSF_KGM^^ss#tqqZ$0q(w;fO4{@;g-fF~~J&je(0%n?6N%n&3k`&7p9T5a4|7&)G? z&EwFhuIGXD5tMPYR@S!Urds05TJ59FU!~8@E=oomAzo}N>;XN3rPi0Rhv0a(1^M{T zRmgYjWtLGMKp0AlzQp;^IZ^5Db29Pbw^5OJLQ=3{EZ`@BH)S#xsjvmor*}TVI{_Tk?w8#(X978wb4H=ek*S#=3rdc3K2r~rMlws)Xt!q{$Fz`o z9?;bXGqN0OdtEPEpzLdu>gtlwGQy0&sRuoKnTm6S*z6Os9fJ5aj5g*T%r-FO+CwNq z*pgix5H<2)AM&}y;SRt!PsLbX9v+@0h z(@Sc&Y&(}YO*cFLpQ!;;5XZA;Q%a{jp4DJOg8o%F>>1;Tq8?mf5P3|*27N4ncY_So zyzxVZA{|km_@s-z(A9_IudS%-0XNxI)>N#^%F<=uhgVwfO}RYv(1Fu<^DwBG*6P{= zyY~Pt0{&xM1jM%kwD^=`O=Fc&tVSt4W6s5ShdS2;&Lq}rU%Q0o>t3>q+5xNPhapWG z`I(-tuzMf=BgW>L`D~J^Wh%nwdTtY+dLKAzn@K%?*lkVM>k)5vQYQY8s{-o)K#1y|N17fHw!Q9`69) z#G$g5qwzvd>>rEoBav7dm7{RpZWm-;JMvuhYY%MS1MwmtydCfg#%iNW1#ywyFr2YIHe_FS2@OPlpVP~ND}UAJON;HAnq7S6 zS=k8Xzfsz%MDLPA@u1bCBrKOb^ERo(TTO`cBCZ=j@l~3)onO;YGAty#)qEEAn=MaU zO&aU5tp|5bPM&y8_))xgSg`qejZ5yk7Z<$%P(nHjbtbYyfA^`kyyNun?qTo$A8Wsj zb#jAALBWbcFxhRBlakIU)AyyCP^UBUP z2Gw$v4$={OD4iaZv89FI$fKt>fAnr2QIM~dwFmax1GotI)*pQB^rrA*qqqntejP5) zrMa<5TYOG)v>eOcS2D}~%MM{Q&(vV(Ws|Ng{pK{kP-F9}W3+s=CU;3~h;`+s`y6!y z#N%%)*agYeQH-w7`^!nNRB9=uvtn5?6Sv zsg;LH#W6%HjV?O+5uoi`EMt>e?7?(?JKO=l1weh3(x$di6MORD;PlOJdItXr$iVbc zk}9F~cB1z9JQ`CM4dEM*xt@LOHJ1(#j`#K!zN!ffas}qLi-*hA}16^ocyxR z3&CF8-j~frsx%wRIO!5SJum#`Zf5XKO%)P6dy{{)vpyi zYL(X>xYs@K&d(2D7W7x2jb9s#&u+)DnHO#wr_7_SL(5UF(M}wnWf4X;u$WC+m9X$3 zNtRy8`LhIR-o0GSY7*9=+!dydQm-e}DgYFtxU-U4?WtNGv}N;Qd9m#+55H`POTO

A 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

r2TJ6x`F_)o=eD+b{B|!9 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

Yol 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

)|JoO_2 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

-2n#=k{tEH+Zg$(et#yNW=u32z`&e7f@6c)%FK) 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

sr) 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%0DBT!CO5C# zHOtYti0zWuc?2Z$_9E(Vh{x_bka<>+CyGl|yC7Gw*t&R*J`s}c^#vd}hXUJI-~wcj z9wj)&n?G{wSWiKr#pjyhvBx_-Xyo~-0h7jRU?F<%X6*sQ92 zJa2Nj%p0Dv_mY;#093W~gtO-O5>F7`Rv_CiF_pjk^|nLa!G{>m=Qjbpupi^@NB^L| z0-zMxv|P2JT|OztT{1JH)Wj&DNI{isyNaa-?z@}9({CHar_A%>3J9Xs!{6TRSIpos zBp!nyZ@ne~(AOoOafGCr$V5_u6R;VkL5aNA^h17F0?qMFO5F!+5tCCPA_x2=No{X=KpfX-j zrHmm(>ap+J5C4NT8)Nd=`sj?<_bbvq+Y@ZYnd}*t(Ni`h6V4h~bG>gYu7%Bd4ls+d zu>{m&vYr?&8Hk6^q!qf%P|W^ZpkO$*{UHa~tPgaD2FYjByGO1FN<&ifcn(W%MaK*a zp2&4P@T;JFL*i+Ur75ECOY;`4u_T*I&W&xNOZ9GgoN9a?T9CL!@}BrB0e|x+zZ&y% z|1nzu{zUL^|KV@H-8}8DYNISZPTun?(6FuZbIY~i;)1{kY9=0jL}BPEEAi1e1`eca z&m~UbVQ_1cF+Irf4`%uzz&7)dL4hMxC`hVUuhV+l-Q(4EpV-fF+55oEckDhUKLNJI?ipn{F^wQt+cF#8ew|QwY zYZAA1ja?9kd;Xa(6p;n5n{_E>`mCCfHIfhnew*VeYZQWn7>}|(vf2OkK8HdEZd`Fr zPXOyvn1WM%Q_x^e+e{aqg2?wkXJw?4<1J!=Ct>R@t5r5)jW>dFq_-?<=|z+2wT~e# zEo3ms9RhtvfALADc^9agVuEK}&f-_l$ZaUy*u;^CNjdeH*qyK?^t?HNyfF^aI-}Z$ z0ku`O`mTOgS;^9*m$&(flZT9RDwXEou~}T{`DupsR4VZyqMPTUhgK|M&dU0a~Mv=k53Y*k>pXC#Tw@K61XQI?ANIRv|P_=wRBl>|mwcur+OX z9g0BJYJhZQlEp;|pK7&rU`v4xuT&o+wc>LQHIidt98i9Z8Xf+`p7_dJSjRC6jiIxD}|8>Psi5tZzfy33`^M{h{cK zJQK54w7oR#meGL#=SwgWY7)=2#>ZX0#N{)3?TxVJkukJ0kYG-T3aTFkY&G5dltb&X z-VBw46O)znuxIW>rZEE=d(z)4su7z1;@|wj^Y01J>U{ie`s;v6%ZEYxYgN}+?Al$I zQuaM2mF=w(W7qC&z2yAIUr-fSJD@1Oj9l|Pn|x*obvggwciWCjuX@HAdtKt|D0$YI z+w)wmUGsKU>;MTR1SL{m}y$C&QV-eW6?i4{r8C$CdPKoaC#yr zlh%M^99{Z2JjnwnhFZ4y3P7B0NuvM2{XO3R?Btk4CUTqH#ru5=iC_d{7gR`cngdyS8xA}W2v#$ zN3#x%iHvpL-Ns;`Xz_vx+_MS*gp`b?xG(EbsjgQW~ zEOm2%mw%$Qi-udq?WTz0vilTc{OMzd;z=1Lv8uYZ9lQ;eau-H7CSZ@_mzRj3-Os_S zp${N0PCigX&Ece=(GOk+?yxIC=^%o;av*muI;GS(wOglHoW^iJa+El7NZS_>Wbl^i zp!}jqzL?xP=NfY#ZW~!&SAFJj3&Ri{ecZNgoMk_jywrr^ zrhv8^au<_|=BBVVG#Ny9JC6R+2Xs@Q!o=1NI;_a(>&kqlxQipYc2S2thco^7w?kG$ zT;g6#E8ZL&bc)8IXNg!t9wv_LesNf>wIo*J$*;%y#=4$d_%w`fdT908x97!!Xgd}L3or8J)GD(qm#-hS@v16jiP=G8T zssR(dVvQ~PTmU#(ciBTEcu_hMN%Hace)?+~Ej!xRD5AAtF;54pSBo-cw3d^SJof<=vn6aX)tK6riN`su|$vLmaiG%dSxBBJ? zk8=Uo4SeMnzPQIsuehDh%y9tC<%tv)I`a(#L1`un6e|aY)s!1+)$1ih5^5bbLVf15 zzrjwL@aBq)W1Jg0T`;3~_{KMXZGZP}9H*~!CoqnqJKpz^?)e5FNuJEEUyIEOq!;d2 zzxK7;--dMjM|w;>rV*fh`{BF!O@Px%hq~RnUdDP6FlwW#Phq713YY@BB#-_=at4EM z4i(xlKa#c`&dYe^furorV3{KC1Hd#uXg(s+{G}_k@@f*%>edTXdfYT>mGMMLXHIS;&jfXQRO$U$VD@Hif5OVA!S8HAv#gIo~I(KH~t7*DB`+ky^j4)SvnC^Y;T>m-Qnb@pl1gchu-3OL23r+iAtMHe~J-dd?e)YQ9U)XYI=Xy{Hn zd@uQjMm;#LYlMpf&VunAYFyw=%#iClKKpGg4WPy}2xviy&??<;$2PtB<~Q~mfRIjK z`H&ROqg$kR5%2j1AW6O`v8+=17vbJPPl$I3Q|4?W59~Pr?TIaRY>^f8iAvgbitvnY z^Y9pZFfQ8d^Yqk&Z*CoqhN0IO+a*^`(sQF|6}n%@@MVntrtTUxw%G{f+(GHefV&%m zC1)_YvLQEF)R%e0r)jc28qS(CM@W2GPH1#mfo9m9*B6@PY_wc9P%`FMspl&;8LaHt@OoIIO_${NYc}>yFPxZ@BiZ_fUzl|2#w3a`v~zRfot@(#uC3 zAYZBn>vU37XN_4iXYNLe?HJ%lmmU3r!|V z1rJ9LoI_Du&mTdYQs^AZddrS$c}2=^Ta}@rHH@4=X`18eCy`>0&Wz04jsEZ-{l*0w z-3$9ov_bEgU^~5=e9t!kI~K`O6}4`92g9T?f%L-HqF=_F9MfkDdH`U4p6oDP$uAL!5~x2jITN@w#la}4$Q>rv<>VirMnysiVj@+ zC%(qpUnGX5*ww=){{~b?B9MGr`E7lQ#rA0n^%)mwsE|o0 zMbwEUK@}A-iZX>1AJkz~kxF5HEHto^(ihPnC|IF@|?R zyc$QpFYyR!3?lY91o9&=0+$dqltJ#?{M*GuBouiHQ?M%%3ggDPgka=1V@7FN;q6m@4%{A3!=?=sDknZ^gU=pAB=N-(bB4~1#l8NPm^lLrHf?-`Mt{w;Bf-5_Eil{a` z8?Ro46?9>$`Jgqy+-uGf+L&zE6J z#}F{LhKe@>sT-$tjRQ`hg1i2DVEstofBhf-QyZS|ANdOW-ugZOZz}tzU9MeCEjhhb zYx|8rf23WuE(zD1{6v-dGT+tP(H)z}#)dsLj$Lv34EanLi$0fln&e!C_ZG1mg+7Z z`q2!ZH1KiNZE8C)1SrS$RVgo<+`yE5=3dIkCJw5+OPt!Tcze3xaKZB;(rpCTdN7GL zrpJzJjIe9YtQ{8g>J_}^oH)}PC8Ved-zxjs-;REPgN4GikyYs^+`_Ukd2oXyeh*Z?eA9kOi=`>>GX22SBtKUyjU7{ z&Y$$up_8aoT~o=xqo_Nlq^7qrC;qLF*uG*A2G%y?vi$O+vH!~P)(NhKj~$I-v<{+@3D9tZEQ08~Mdu=n8lrr_aM zU;oN`_$h$L)Q>e@lkfR~Z?BE+7t+*~Ox(KI*-?hFmq1^azL6?Eu?gsC91InT4jXik z(7W6BIYh#iS7CAq&sYw1Ps}DrdJOH;NOgVS+h!LI0aAKvuXl40)mX>Zd=juAdN9?P zkEEqhoSHO$U}$12dm@?32y}-NW(AWGeVXM&w$D(vo8TsVw07Z9-)ql7uYZqQ=^XiF zVBE=vIcQz$&V$V#raGu2DWMLQkf*ls~|`P4k)$%lJ&12yVD5pNy-QuE+1WtK`Mn)QRBq2y&Qi z*U*;N9{%|s;0@gU+FRfJwXYOg{pIrY8LH`5HWM#8`lLU`4=TK>HvmrXIASW(rY@}Q06vH5vD)ck;(wu?qpOOrD1iw!cIFR zLQN3I#ul~{10*Aoy?7sR)IATT0FU+uTMfwbbARXS51)JfDnM52BkRBK2S0hX@}06^ z6WMd^pZkGNEwsM(eC#*Efg@Elb)5rS;_<4O#*jo+54CF1Jz5`__QAND<9Gr`e~Zcy zNIp~dO4#V!&m5B5;YHfqoFYrk9Rg5;rX1$Pz4wiibZjeTQ0 zg&9uqkqgPFZ{O(J6dOaeTl>x$n?m-%tTDqgS2>ra`X$2adBp*U=h`#aQGmZ=w5S-o5Lpil30%nP8Cog85dY0o2`Hr1&0iIjD(#pq0`Yk4pc8le!u&z}%Q3;9ie z(mXXg#}$)Oo&xcx?#)#|_Q~OR+@;pa4#X5CFpQ5sySGp2wP@gNPp0-azllz+AQ}DC z^H%}15+CJFzY(zB1h9YpNH*D}jJwZkKo8GPgxAi`=9e)AvfWKdFdr{DC^duOo#5W;fZW$T*|A(5_q6d4!K zlh{~%pi3&OG$=n{d(t(zz&SB05_(#CM^{c{axS4{6_hz?UN?Q|_8X9sOSqscx#Mj+ zbRIvoUk)aFiCUxXy@KVegp6b4=7(kC9BZVuRE^}J>e_)Vn42D#%0Hah!^|l*yuhS) zKIdM4Kd|ku>8k)g`?+5`#%JjxS%Kg2L!Z{Bu`}7)eB z>-sTAo^@tc@zKaSV-{`^^O`HB(S>EC2eDki8nJ~?&PMcWL-b@U2&4YCu5#(h} z`FPa2G4TSzAPV=WQq(4>u}lPSES!kdqfK7+!`4P@(}Zjr%6=G(J?xEB7DFvm=4yWM zozLPx%!?GVFs5Xvmh6JNjQixzoVRb;&?hP;$%0LxhBL2q8m4< z$!oR4?B~RHv478AJxJ7Ah&slQ;g@Pn448M}qAt z+lm>m>&SQK%H988`!|1*|8jh8ALR;sfB#JY_Ah6Md`@jtF<@LpLiy5Cqu8ReWx>Jq z)owLPEt}N(2@nd!C8yNClPdHf5|-GLyK<@8pM8)&b!CfY1?|Qm9VyvN~n+8eA`_Io=_3W-L^v?$uG2w zqOsr2eaBR+W^X5#a}O%s?HLT4Qc;r8(4%!8$yL^(Q$0^ngF{K@SltPpo7SNQ4r5#< zCs*9X(>8!gj5UO%2G!?T+5-#{vBaBc#;0KufOs2{riv~Ilf;->WzRCw1iWnUd9Z| ze!qB(EPFXM4%)F1N6BE!0d+jbms*(-mUdn!3XzGlKI8QHPH=qV4@cUqJ~53wR_im- z6W2KMPTNw+XQ)F;x``1F{c2vQs{v2$!Lg_4HE#`?RQrNg`}F2#ebCznsWI{q8ye|Z zcU_$W%EU%?$o)_)zZDxyTBp!MSR8|StI>GgyyFdTAv9Lrm?bsDM4Mw?+GRROZVUY**7_iSrCP-Ih^s4cPwzh zZ2bq4@BIdVB$8ZuCC0?oliBP61Bf&z&b@@h;)c5 zE}X?(LiJ)3p>FbUgcBL150wireplQTk57ux^p2+-h7Z)WInGsAN%GHWFB3XimWr`) zeQ;SEff^y1b&N-P3^JSr_`iHD>JMBEflUy{GpAwCE`nT#qqz?sc3|&Ng2~fS}Q=;eRbWCK2z1o1x zYeWd~1t*R+S?1nB@$5zTyFdMU2%h_oUAgvv&sX^B*}=5JiAe z;$%lfb4g9y_@xh8yqB28lKcQg4xeJ?+$JKS9s4-+xc$#_q!2m_WKtN}>xRG1O7-L; zO75qLL#o2hd1~K=ot3tNSXn#h6^{@l2D@cFDJuTXx2f;hv4DChtaDoF&6|M4&@3~k zp3LvkUd0=Lo!D*S+lKsZ_FMdW8>{$2?LH%|t(Ff>U8)zhzT|*p@qmpVbuYWdis7PR zJeW&{<-k9Rvk^Rq8DDtY2M%m%bdgKMFAs48)_vhd+ce2bzMXS;pUT_#x+^|xjT$~| z3>q_to?)5V17|lB?VsL1Qi*}N=`LQckJMhV7vjC4$~m@-q8*_rteXCI99nkKau ztuK^KE56+9XN!H=+Xj;#x!*V|eq&Sz;{ZO)^v%0fwRnN-c^G01)V?}M7Mx*u^GkvF z4S~Wi)39qn^B!(dF_w~ z8KF?hY9@;T%>f)ql=r{+%l-S4~=}ArzutrPRoMY&0-f8NpfIRI22Fg_oQ;5(kCpXrq2j>j54|i7sJZqt+)JeR-Bty z6Z?j*2pzKWM^cFoy#?hadyRunqU|+9IL03>3zMt)Ys{D3fMJo4e)|ppV>j6iVotr( zv1)xpN)i~W&)g>qyW2+zjJpKo--*pbr;~Kn!0gzM$VG4vqa;rTBDU5kpe0W)!Nl$S zvhK4!qG)_|g0cUlcQ)o`-i)(0!nJ9F$p>702uR-N!*I{VY_|Q4zw%E1^*BMEg;%fw z-}znN_VAhdqd?x31mO8nGBs2`j+zoXd)_Zz1-bg!r<88>sI|TBts7Gp68fB__UbXn zptsaDsA9ASF`zpVQc;c3`=Ga>E=74;$42N6~1Igg~R!s zxP{J>LLi zy{`#9p3F>y$-nW&JANa?mFqo@ve8OtvT|76=P*nvDg;K+q~PF+{G#x&r{A~^wN;O0 z3ci8qCy#}sjwvz#06+jqL_t)>hCcnOaHGoT3i3R{fl+HBI~?1BHH(Cvix(91S&!() zdJb-x^02Xo^J^MYA9q9GCO}`TTjqTe_(HyEkS9k5Pc*gHBf2-S9vvB$X3(tBi@W*Pq2 z)I;39>T5W$!WZrWkfZV&5*tHdIz}W$9V1iYk;h~%hUKJb?7(=1bo+|!nq#S^iKj;6 zs6|1XC^=nk!04fqoSV#nsz4<8~xV5FoPF>bWq4cS6FXY z+IOrqwwxT}JqX!x%i+j;2BAab(JA_JC_P&8N>-;YRnIn&83&0~`XW$|fp*iwf3Y{_ zaF(6KbIINhluSq*7~t}yLEpl);0HxM$>|8q0~>txadnoK^}$7`UMo51Ca)Q4%}pmR zPsa+aZ;4)KyU0Fq+i}>4>nk;5Q>}mLU;9KI)8*p*UJAeyuXt$PAqu(X`?H^WJ5t#|dSZ>1PWg&_h!_A2ihqwWP6MbmtjB9OsgG1y)wxHYu1 z)~>{?q5R9Q9vv;hQ(|574Tv7fo+Mz?oC^2)ue|;6{7Zp)c|L-U-w2q-*3q;#)PM$3 zX;1SMu*NlEFiv%~sy(I#GB%(<7hDGE&7)j8A)u}h zSfBhl5r|Kw-+!hhwfzjD|Ako@lNQpAHla2&E*BK9Dv4u0trWt`fCgPe-H`kU|q<*;s7&A)S!lZ-u&r${cD z5a-ODRoovy6SI>md)B~2nsSMc`X~(Z8dYwNhw=u%Fjt$EOXWw3mpKEK{&4tZKdeuz zeiL}l>cw8cf^*MYqY<<6=@@0;8!y$Wd;VzvFU3c3=Nkc2E4?GCk?c*jRyCr*O_pmP zUGK|I0Tihrt_H>E!bttnPqlPyIhO+Rukm8u@(Q)Zfm|FLkx5Q<64@S@xHKXs`7^7` z0S*(`V!MTH&PcW7NIJ(Q_cMlXuoY z;~tGJ$tUhLiD=c!2fKV~lw?pzZC2lnlMIfbZseq4-J(Zv9uAERsB!CHFUy{t)rWt= z1g~(|ZE;!|Yhp>Mb9HFpovQ4(-*Sg@hgvRCs}B|qT}9ZbU2tdMXo`CqxYrh}_PjuR zJbwk?tloz$@r{7m(97O*7HqUOyUusnxze^Q-7#s%zRF!UYKM<&OAV;UMA6Xjk$h?i z^-pFH+*g4 zh2SjlMeRXte|>j5Z8Leqo^V}XY>dWZ5u+3seTo-nhRt7Hi+~(&6k(<3>LLcH#xHoa z-u6R%JWf97fKNYhtQTc&A*f!5r^g&Bt~`ju)jrS>O%a)Q%kn`!sZO4LTRYG2r2TT-yhX;GxX8`%MKl z?aTp0IadObdJc-$F^!1baIX_=iE#RG|KIg}0MG5CS%L4@Zv#9||}J%TtJ?syN|p$D!A_ZYcV!#g91C z+Tqx69F|ERyeaF1^m-+Cof#SZEIwQlMb3is3{Qj|ZQ);?xI+`gX*u4OUpj+(z5%#R z`pM*KV=lNaeesKT{R+VM{J^(g;@)nw#+5H9=BS9_>FJu%1w2k1Yauwd_8l08a1y=3#0b zeQJO(V!|fF@ndMLGB1O(y3L7oqs^LW(6<;hp9~1*7kN;b30j)?6}#43e%3pHQDEkG zFBC1m74Y*v_l*WVcOUKwlQ@=ac-rZ46osQ-R*r+IBbgs&K zoiC*@aH`CecBv`ZQ(K+0ZSYJs zh#Wha8)vD}WkS%@_&e_WV}PG{O@Azpl=Z)+mCq&p&X#WLgeyLzq58S8MIp{1UvVk!5|P-{@yGx4O}Yts)0 zrh{{@Sq`V?9wTCAqvu;^C;n(bMbJsewN}QHjN7BQ%|8}fH*a8btxkn-y)6)!gj7>CcSW-yl_p8it&}xI{AQ$-wq%(ie5AEHEFM9FGFD-H+hD) z%#Kz5v)IV^d0sQmHA_Wq4n+W8m*!If`%* zr)y%ACDhNGtqPB&&|p)qa%PM=Bv-%Wh7X9K03`BMpeFx?Jl`Ndxm_+Sy9@Ve(4 zfK9(vy4JNO!nFXk?*825=YH=0y6aZ}zUTY-DS!&44JBk3&x)Q*qH?iwZw=Yshzy|| zePJ}-GcDP!!z9k8Dtt)`KYFrU2Q>Op%il3OzikAtn}b_Zvf{NJ`HUAy=R<0-%ewLr zg-&7)Vrg#26URcEi*ulj2#~qCIc`QQ(TStHy$S_B7(9Kb&A~lZ^_9R`YBY6`)HI_} z^~%bkowG^$R<@U{RyC##)ioaqCQVlt5y?}b>t@p6`;#&G%toc=sxSM7JW8AT>JMy}x{$NE z4o69fW9}|a1=*+P)ZK}Xn_a_Jqx$CvC3bD@9K|EOfP0EBnAdCmZ>zE@R-=fhsKsS{ zN2xjLc|pCCp90#(^w;oA1T)5NUNgpd1|8eI{~F-``;UK?*S_1=MrBgDpTO5_JJb7g z_k06D!b$q{pV0qvBwO}HwoLGIKlAtR`aJ*%9< z_mdlo!G(donnZ>g`DO}oFkM3zqHz}uOyuW}F%9tu$MgiTaxR2^r7B3~EZ4E1#G)1o z)_mp}Be)J54(1jg`o})wtSGn}b7Cfb=Gr0J@j4;_$APCfpb>;bPORji+|bvBPI{m+ zpvDNv!}Iq5Xca!9+t~&skbTPW+j|S(`b>zk_fdO_s6Fm;UUJSmXq{`3YXlf27P;y9 z)ML6+CtX8AHkdAB&R37jwsLJym!8v$-{!dJhS`X4oQOhux*J>S%7uAo-W4=?(O;&X zL|0tRxf3;O-Rm_t5stK-UQYhp|MB-;_}W+XkSvn|!6L`@atW08J>LM3)J`nP?j)bm zZTtOXZDzKOuh%*p;mSxR4=yP{u&WhSc#^*jE9O&l;Z^rZ`JzFBMH2xN?a zxBl`qxqV9JV1|&MTnrh#hUiJ*7@detOacp_fw2W))V@GB9IEhLXlD;Zb#CO}A4l2J znrnD>zRIJZw|x{R4|x^Rgu!Rey$X(bl{=5_@bg?tTDb+4C|dt0t4|(L5hD-v;ZxFR z7jYT=P-{;m&^ZWZ>~Pdj_1qkq3qIAlYw7KWU-=!+}wU&1w}w0Qaj&m()r-%vYc-f>SF8W%)% z2ISlA%F}BvyZ3)^1@yi-m!~GCy~3ba_MG?PtO9G6q>u}e`01TmMg{0pLEFPlLey>e z6mAf9R4jCm6j)sdo3DmW2_Z5|#NJh1*i-Dj+5cuu#25*40hIz;ONT0|+VyurgUPB1cBFbr)6JfZK!A2%kLl<3gY!Cc@d0f=~xSmv1?&)JT2I9FCj9v zxFk-mq6m<+Zp>k@c!$BUWvzD~iUnaNQZVrc4gfhpOQb1wFlOlxVvNS%=lh4d0^Wj( z=?!x2)^PUFwShHTI3{3^cCSzX3zRbnXG#O3LKa3mswI4I!mst!;~x$ieQ)Ga*&jWT zbhZNupQ0+V157;YW=_k`Y|W==V4EYWQA2#=22AtqIm@k$32C&svNnfT9-0Sl_`PzD z`FO33eVO|=-hA?tUI%dPX&Y@0k5WsGpN~z^-Ws8begv-)Fh_?@R$S|7;pUiV| zYnW`<88iR7^y8QX6z^IRb0mXKwt3RYS~q&|ZtOYI7^KcTg*CAC9^B@-_u(*;hjS5M zi9Lm6hN=jN71ntyYFW5#kTF%mwZFm&B({ivA!?rRqzhGeN z6r8K+!{^xs+**cpUH-eYA?ntY%=Yv>Ft1lXo3d7N@f<(sR{+BQbo58sp1~tMirC&K z<&#{ay!RUbk|WVelJ)Rm938JxM=ry!e(h^d{XAq@&a5?fVjTVMKmFOBma4r2cSKZ( zNdUhTWps>l?JC_nfSiXsy$h(cHedt+eKki6KZj8oC1YDqg3F`yV~-Ytb6XmT$we^| zwt9_mt1Ig0asbgg|Ly}6b8dLWv-%MulsSIg^H4}$+jczS;27x9r1zlLssmm<3eVc( zSBCH^W&^d4SmH<@`NBEMV~?uHk7#MfE5>W9a9PjH0aDN8Kxca)OUopLkMi`ho>Jl0 zzKa4AH1N}ut8&U3k%>Qyc)ow=E1>m!+=^(LcN)6nvzF1nsZCob0k zeyNL7I;qWKmN9W0M)z?N!x)ka{W&EzlSXu6EQOiL;mjvw;V@^AiPv)uxciwS@SdlF zv^!xYlyuyZ$5Wg~*A!Xyo5=5N2uB#x?nX{1YY+`s8NvlN0U>4hv)wh1t?TZ;jDlws6`Dp$4Uk|CHtiL6mI*xgB;iN?*M&orhC5uAS%h_ zdU*K47he0)d8gyGc;k(SU(0&E`R2pdUjNE3W=6Y@jMHAc=m};*q$7X&v**76f=&Hp z41Nkom4YToonA_u+mX`Q9J0qx)neep*2uh3L?iu&qmnEmw5k_~x&D<)L2t>KXG^y%s(^)NA5yUt;jZ zFW$PLeWy^^-&cDTZvb`zp1}2I8fQ{9dC6Jw+nGfVA-3K2SR`dkZ=AioVJg#c&%;qK z(p4NTud}k@PA|tI68|Vm5IIF=rS5gpc#6(`a&}6SC-|nA+{Dp18D)`n{7^xE>&tu+ z*56fOzaigANVY}mQBbc#uZMCdzW(q7r7^)OhZFO}v2+`K>~NDSib|NCyXGTPN!g^c z2%xJKT8?0r7(67PiWgPZ1Ek#OTj`E*Imo{R{F@6LCi|go^3ti50R(ofJu*J>i&taS z@utFj!~`MKQ&4OIwQs;VX+9A$!1CRD9u9d-@2@wlbxca9TPm~w%K}niI+%zQL zdDB;tK0}4!Zbt9}FURE~QcWPjt$H|D1R_DmzEe~RoW9X|#* zYnvrRLy=K3Lr6h%QM7VQg_kQgY;EviVc^4NsfKFiI-`;jzYk~A^da{*v2Z@n$EH-! zNdZAf!d#m6IU4X`v2*wivw56l$qdUUVRD#t^xN47tD&7C9?}y=^3LMO`w>EJp3kJP(;G=5@l8^V?IAe%UdHym-UDFbntYo}PXbz0%U;VH@4{p~uxyNTFV0YxU^|Mm z%%jJobGs-~$JQ*R>wy!=|aOaq%9|hpzf>X!OEa9sGr+Mw*u5i&Ykx%CDEha zs~2t8Q_lfrvupD*yCPCmbddz-Ay3C{U6y3XYXBpbz849uXTPQ4%1E5*-@QL84ESK0=~H zhyS3E=trVK2Sll=DAB?R5X2o+7u%%b*e(hCBo!x4d~6?|tIz#-FUIeA#`C^o&h@Q* z_Bs1o$DRGIJ?A?v&v?doFLQo#t^KWU5jVqH4lMm8{^t(A1{j|7&2pe6 z6#py-Ju&aS|NeT-I|PC`e#0DhLZe@$e~1TwyM^xm0-)&3L;89{#M^IXG?}>Q>E}eo zc1{$GiS3;mj?`^!3P&UkR;RK*T$4zRySmm$FA~?v%=Y(r>%Z#V(LUoSB?mEE* zNMA?zV-BHui~u}NLo;Ro4$8$F3psRP=tk5r@|bYfrsU2L4EGDjta{cjsu)%wZIClbpY^|}8DKS7isQQW;`Ex)W%*L))VxTTNSOSa+u=;A{>0Gz8x(dqB_-+lY7^)z%Z=p1!HPptkVFv^%mZ&;Sd z3yl8gmN#Bz1DKEnKVFEv3G8-!5}`$IVd+AyAPLiV@Te>{9Aq5YnJpXXG}uc5pF=3d z)Nz92G?T4U?>uF4K0*8BER*X}&7cySUgbQ0+)ifObtuW4+$GlqsuD8}^AEQ|lWPyP zGR!0zQ)+8R^DbcK55_S#!Ib5A(oJSKT_N15bvRcsaA4oQl!+&8-@t5{Osc0K)q|*bc#0 zCg*A&2|T+3?FcyS6XfJY^_UQDZ+^Aj1F&NSzH^#>I5+PdC^h0HWQI-o9P~K4Gxmw~ z;~o2n!!816f#-oKfF~P{8IAt-J8zz!h9=~(@`3Dx^k!&dRp-ko^Nu}ov5e}yF^*&x z^HdC*#KD80LziUnjWzWF6D8CGVC(_2?(_=TTc4RVS$^dRHCOU?;mAz@zBXiJZ zos#1ypXal9hD||-lM>6a(#Ib|$I~71g_v69?7)>TV<4`4856K}5xB&2c37q$3yhSazP=(aO16Gq@{j7eUtpm1WfGP~Q_5O$)i^xkKm+j>UaO zU)F&?F68A`e)7W+c^`Dr+=m%7A6>lW13-#hYxbBqsF~M;z=I6x)1R$jLEq0BEMhjo zy4h+okbYl0d-`TsmNO`Cu11EooyxOG#4UBwVdzaM-{G_wEbXidCN9?uW`FfKpl7_@ z;`cbM#rL>A^TafD%DxFiPYrijd;z%z54G}^poaw++Siq;s$KGtaM&GhH#9m9Xux%Y z$l3xx9(w3zf1c%ivuW}Ha2&jc#LDYDID(?Ti}loeJn9*Q z1(NC%2C6wF%@`pS=Q#yDUrnmCs8{}EeG0-#rQBs}b5BfD908&(nPtO8u z`H(WlFva`W7 zg2rnN$(^shLc$I<(8LF;i{N4|o@RGT+_Y>tb3+Z)a)As$E`rqL7p8qP2$Y;0!I765 zkIi~6Kx506Aiz?m!M5-uHu?-^`aFe2a-x-^oG^!-e%2WK<~~Z~&X+t2s6?lcJAv6; z4`^`ISN1k4x8|s9cDo)yI1!*Q`zNO41Gkg{3p4r(pJOzq=fTWP+G(D3pYRN@j+IjT zDr=42@>-qJlAoz$C&mvJG=W1Yfg1E=^zd}yeu+%uy7i}8(yrswWAC3cl zD#~6Iv6b$B%d+_!JD~BY$6Y>B8reV!%cPEg`E!J3F7K9R0lIdRcW|7=5q);u(O-Y# z)i-J_31PzShF-EACh895$1+^=0f0f=J56h_UFKS+(trBWOLv}zKGtYDi1z?wMi0F< z!&v5W@+&7?R2=)F&}AvM*c7!k^u}hy%2TJ{-6wn7QOCZVV+4PBQ@UR2qlJHC_mn$Z z^z=kky>ikws@-(h=q01~QQc~Z!=TXSIb$`C<&zy=gV(qZ<2Wa*AmUa-0aJ(K4$cd~ ziSF7+o)GFtZusYT(9exH9pUn*2^{u9^tuoaXX2KbGOMX`5jNBrtWS=OL-uy5&f+Ch z{BiEkRnKsRHk8&);<0zF3-C<%WM^P4R&NlkP|2vQwYt`OHF0I%&b=(Vd~C zja~WcBz$@tH0HvRl(T}SyBN7?qKHhST#D$XzT^#&NfWwt%L-ep)q=h|5-=c694a|S`kK~{0wA}Ir0 zW9uqgUl+NU%GdbJ@654o4m*XbhHLOc6`rJ5X@!B;z5>l8W?FGlJD7Be4_UBt^zg1P z04m~Gm#rP`?sokJwr!?9aw@4h*P1bHBm)9q3tfu~;yvQM7nCu%X{+#sj{xmsTg}~v zVjfHI%=t7kz#5*nFS};0N$;ZGie7c?|H5f6g`k2Ir_*T%6$b3x7jdee+0YZK>ZE%w z#~#SIkBA#PHEo8JR-WA|9hi;Ut~=J1atW%=Lu^|q=Mfx=a5BYdzDnP_p4r{pscBkp zOTUxkSog=#zVq!r;N$P1*Pp4W>s+#Q>fM9^Gbw~Mjtm7SS&yu{%hb@V}Zcc+ERuFciS zSFWwQ)*#jECSU~m-7M5x_FT2I=CZ5K5Gwk}LXB7m~zYLzQ(pwbWh;z8rS$^PKZFUfd!~aZ)HoxKO{$U8n3rD=dEWwF~-mIp_~lt2#hK@*#T&=Kn7 zm=$TxaZZi17AVn%3@wq6Uix*1K2H)7hji|X13$sl?7z{jE#ApALya&JxPj>$E zAH4AR*SzC@0Z@sm8U2$b`ViE$9{?_;TQgiYK*{MFeEY3$f8+6!af$UqY@g=ifU70B z9;(pGpbaXoH$;mcsmlfrKf&0hjx8HHc;OBxud8uHXPrUr7OVI}TJC9I(HdZBDyaL) znM(qkFNDEgMDi0WexXXN%j9HBR^42HLjxS44)b8FhE5&vflF&y{wst7CpZIHi{5QoJFo4bh^%S#z7DfL z9q;|Z9&(f})n4%|gKHIM)C%UInzx~axy?zhD zEIcIvjhz0T@Q-G`_5;9u#j|;9KJ~v-t3qt8+`jwXJNN!Yz!mTj1icZk=PV1I;#Sf! zZ~?K|J-uEbtYp#_NFb7IZ1){UGs zvEn^V@VTbcJj?8=+O*kICl0KO%K^rhoV*L)wGgAZX^d(O$>h*XLT6)!klu-kt=ucD zRQQ(bfrDHd!8YPUvUqCpH7D>Rvpgt(Dx0_E*W?XML?mccI1su3g?S+JAOm zJ~uG?YVBcI%E#Ww{&2s!orygT^)4j#LqgT#EWUC(#*v2ZBBZDG)Uj&o1a#zP2gW}?HsC+B=~976T5FCJr1~1R}1Q5V_uld(a>-o=CU!$QO*_9JekKqV^Ku#G2h2L z3!A!lgD9EB-!&{y3E&uGKOTeVr9tO7qfePT19b*|OR`mH^L!A@_lQkSpx(A+w`{e)BD+J{BJo(pl0C>-gt zA5($%oUaMP^k<*@CRnpVk?68L|GcKPO!UhS=9=x= z4*;1$YE0qJ-Mswr&1$yzX(XXz4_pSo44PpzmI&y_A9^sOFe62E7V+jCTsbt zlFX_%Wov9Y>gdhJ21Mta&1ijN@Mdo|i(@)=XR;(76;SA4s@LR*+;+-XekmQ_aysYq z*@$?mCy$D26x5^Mh3xsDhAO2(mqf>7yAsUk4QoNkqaFjhT>myp~sq z+QU-hMN+ffHullv*_)uadK;$CwL^bkY=!Hctn3U-=j-3uIcNX*8q#JiYxp+~WMBUs zW`2~F)KcZ1HHAxk4?x!pr%SEO8W&>=CVk^I9n0pi9;l!e57rX&T7j{ef-PR-{PsKb zT34@m)o%4yaqG1ZxevcqzVuRT^bgjq{Qz*OP|spN>FDWZ47LLvz5ky6I?|>1k7eV( z0N6Xdgr`Lu3(MkyWAkkE&e)KFpLK{ zV{bdXa;~uw=@`?Dc#-6E*{*sjhw3OO{NfWo$-`Y%e4(K<>T3FpOg{I*5Y#!^>T!h( z5)NTpi*b#H+!LYNy!P0f-4*+c^}d!g@vG+4P{uXKC3{^N--q*t?^*wFGoUS?pc6 zEUf#+u4|Bj&A~XK<)E$W3nlT$)rfHY(adqK8Bi+h3PzwRg5b_jHn-e5{pzp#PXTYs zRV02Io|aOrkN+AkLwxBIbCv#+dH|>yKmPxJSI?j{u%CZWe;@gZ?bDyLP;-9D^@DNlBec5-h%#g;)k+tq|gCdDg2X5XM20bl)F`4ZSQr7n8X zxZm~aBB~hdt`SB5nlrwAq2Q;9D+&hcpbHVD^%39d5p*<}WOG8L8eFhRWZZt|Zeu!< zHBU_R$q?I|;XRjyB3z9Kj*Pi(8)FRWwV9uphsLB^NEanP4yejc* z1Wmqxs^8-p#vuw_WT8rKNbt!IVE`lcnfM9LfEI?$M^2p%YpZqEI%f63)atR0eW+Tk zBL+0BZqaegn^Tz6Nx!-}t^vDAX32Z5l`@4&oaDn$$05flU01S#m##6uz4^%+%gPO( z4qn>%T4{mrmg2TW61DA;kaoU9h=|LlpTvXOIApZXeEwfJu|J*q&Raj@gYR~EN4j0W zwoluAaU1;+q=$Y0*oEJHs@c9O__a4)z3P7feCl)cTL5I}EVo+Vq)fkOYTdv)Ko{9^ z7rw7LHkPhAEkkV(DY%1ikaTw10`GN0N(JD;5o>%f`66?Ku!xJQkdf1i+gmr6z~qC| z+uHrkji*}kPrHXmo$X&q6){eJ*zh=9P;zb1shYkTvCfp_CY6n9e19Ah~G}kE(F&U-S*~gk*pq`V%CfbvQQ;p!@VC0+hTGz0PsW!0O`-YPIWzu{k_|rk{!En= z5D0xg1R(A;xxM-9l^+25wa&$)sOoF2XI02^5~hz5uhOpl0B|mH4xsjq1~_dQ^zA!u zef&QNbZI8Opw9v*&&*b;H_(poLg$5R+mBL_u;Y5yaZyelWZB^7z@R3_mgO}j{>oCt!=kY4 zZ0WnO@&{CXIrAlU_?i=a&%YEqlXLmouz1Tc+e+tra#hHId*YwTpU4cXea!ldrgf>c z^r~(r`{=L@QI^|yU!#Kzmcks@df)57=ekk8I+2G4{f>htHMLzEyGGIY?#iius^3?C zku9gF*9r4>!8Na|c-#vsv2}OF;7MRCYfe{&2y3F+fDXFy1)_^d;WIQy_nZ#9B;%A)>Hcr3_7ggUZ60>;W*3k(&^iG%hheNe4z1B*T37@&r zd+e9_J%{bfSQqDIL36PNy)hY^me0hRf-Y+Vm(+t=T&`ozjcUIzb|{SNaW77JY7&Y| zyx|;x9xfJ%Kzq&x>Y4Zpn1SpewvYB1HE8|EqV-xC(?#OWA<_8oHOi23v3K^GlfawI zIUTE*&b_v1;g%dBy*QJp`lcaIv+tY~KU+9PYD}uLgzWXy*@UgaYdRt$den{!IUrN%fkmU0rQ0~_GfECs_8whVv38I|+6-!~0F z$~w>yJ%{nmxe8+TvrNq*Nfd04Yy?1CEn{C0;4%X7jnivj{Il~fZUzqPkbS9z^d8Yd z_Xfy*K2+OV;o>z>eBKgGa0s+Q%An#-P2QK$({W?p5CdaQN^BWl;LwKz^aBo#*AB)dow|IVAQl zf~6mRcFS|m^>hZhb}pAiE{uv(Y(U;{Wj!`9$q`TV&Z`RtSm(*6)&;=WG`{$`XiY?~ zBfQLleC=3vm!N?j##%V8B!Y59RSXc(jQF#dT9R$&@`N z53DJAd;o~Q(B|A7+!nf?Dk+X*BM0CFy7q83P1NGpxES~N&n))L7^1}hamcIJ z&Z{~U5qlCm>wlp$z>*(UeJI)v=$F>4ZCU^9lZ4abv(9yYmk4k0ODKb&niVGzM*Dcz z0v~%HY+QsK?s1FsIL47x$$SkF30Iw!*7xUK3+%LVQc%MLmAy}G5Dz#AM1q1?*%g0| zY=4ojx#~FHy8gv4U-u6Iz54QxF8u^RHR`m#S&oeYUF7cLd#e804*)50&hRW=#nu8% zn;r^Y`tc90`V#=&1K?zJKKxe;Tabn3w=k;AbEROX4W&%PgwLC#v^Lo>Ffq^*%f<_s z81ZjD!NHyI=-ITAVbLZQQekkh;a5#vyDox4>H^BBl#_X1V!svXXZ367TU#_w^c=!(nyWe_l{ z4Ao^S>k}~sfw_WIQJGnPuakfKNA?b-o|*4hUyvpvXRx~A;wJ5YHLdfjNXHy`7wVbt z3!4F7S2?SUTCc1VONt!byQuZvYT4e7t#j-$^ojk)KBy)>_F}u(mnd?_*tXhe-fQKQ zi&_%beEJ(@v59w)v~_RK#BPVNVNFjGu#C-V&^nrs_P1qS4}pju90Zym?inrO?AnFX z$@dgO+yGnR&T{Sl0{Dx+{EPibfXCNDl4e-xU5L+@Y}5ad)w8gbS3g)jY;T5GmdrLEMGyDE2e)<0wNi;4%_>34HIs=VM-xy_dib7uhT zL5$>?8;`iP$qdql7hzv`B5RQ3oV=mYCZ+WhWbDp4YVLwGFO^Rb`)X4lHpiMzIyTcD z&eJyK0|=$^?s{4eV+9hM_*EMQ>Q!v@g~*tL#Req$>LU=Q8-aVjfNrO1D*vvnWk_k| zi-=0(#$o|7E5Ne(BVPK;fgo3HQmlLY#o08Bc`yojXO6I@P~qz&3g*nw)EQYM&rZLv z8PHm=WLmR2u{K?eZ75k#%V=IhboL?VyBUUe1;r+ z%DVP%0Fdv0{2%__4{JRR14lpGz&><)s0V=5eHdhn=~?HJ7v}1J58$))tAJT$k|OuK zXG5&y_}N@+2EHh+oLo5cY1w@8$JjZ*9kB)w?zjRrmOdL-9{_TQrH+sarQ>TY8 za+G04EF9JiGg+UhSuykw($YsJ7FYGb<2p8Y#MC@a7*6}3DGdCldK6jqq%zZlIR?J9 zjH;)rD0$Ae(^QQ95!3r%Bvx(MMR6+VIJjrp1J8igwQ~6P@>w*m3Td~vZL-V9+G5Z8 zd9Cqlqg1z}PPmF;Kk6O_KEQ^pynP*O@2-8c=!sm-6dp>=_!HNQ=&tSIc@MW75WA zRfMliyf#S73P2{JT90(;Wg^CUKtke?ePV}4{uETmxNP@+DVcJE246wUs2fZTvYuy(%16Jwx|)TfYe2~KxXzB zGb22RfZw@*hb8w0dZ)x&hf)mU(y#p!fOq!C0Qk0is?;>NDgnriQBTXyH@8jK6zbFHD-=sVy#Ms<`NPO7Dy zFVv+^5<@jYjf*&Ii%$@dhS@>JK~|C`{pbgkwUeiy^y`q27e%3k_q^J^$3(W|={XT; z#Wk;pq3L!UW`Pd|^M``9%@|ZxNC;!yJ9f=s`GXV3x+$i{L$cx~fE?Asr=Ufv+&w}7 zb&Q7Ec&uDD?kG@{uqlqndb z`0(ahEJ@{i49e1TLL_rTt;Sf#KHIhb|NnjeAs`Q7CqidlSS-oyikwA=z9XMfy7mLW zEO5${c^+I!2dCqM_utK%BjS6i{tLhMR}S%f8sMQQO>0>)z4VcZ?46Mff+_Ld#DLjE zaLmAs#HA)^6#IRlwm)4U$t^p^ee*~2Um-jPSQH7O6Tyd zNtnKM(ZTDvShHQKax0~MJGAR7Uoh$yVBi(s4$V2Ka%1bhOpJqC&C~0%_)Y@!1xyxI z(~8rXMToVS8JQml(K2_A899n8zU-NaS=%NBRH$umdQLW5Y5|0`_rwOYC>+=5UNePEmu5W`vKrd3Z4U+^3Cf%d+n-k1mwQ} zy!e%dMa^c*oOX6?d^UpoU~3p0=yZLYZSfm)#9Av)@%Ta<+pu{%iNkd!*TA)nPS&o8 z3k=S4OHPeLG0w1YL15|l=GieB$K!i(76qsrvTf$5>RmHp^VS?Fu1-{2pqAnLt56T^bm2Zq!vrgQ;lkhn^Stf~_#1uHE(9e{=J**Dk+l%=w?4AAAOSeUry) zx*rObosFQ4k^t`-ZxB$?w?)>N2^)v+Ng%3~-FdygZQSR%Cyi@gxTPMHSq|jdpV_0@ zfvJVq3hiwx#WF>q+UZ>j_}GBZw*R@C=j+dS(>TX319}^rV_da>NhM6NDL3x{_@#ti zrSI1`E;=&YDX+#mZIS6I?dt6z9snrvTx#;r|a(ltU{FS zw86`njWaj4id`81tq@s6 zBY%$%{&>8G&a{lmQ6%B1UO4r}Ccd)@s@p`c?u3~b002M$Nklq0l1-|$2It_xjOdExlWKiVro;M$Uhc+J(2IZCf0VmfpLGvE z1K<7T+t~=(Ol$3w+B-)3vh?f@?d=8hih30buM4Nwbd~RW-Ljf?@BPC0ddvNyklt>) zUtCkz*O<2+864`<9|$x+b`#Rort-D;_QCr;AO~W z&Ku}*ORs;|{s}-n2DlTFR1X8SzA2zgYUr6U(pBM(&sl3V1VeNY9fLh$!!LLmLy@-1d)7PT75x+AV+_BkNU;z$V;G!eJ1Rv zyU^l=dZ9&c!#E!}Dp9xwq;06vuz4E(|W z{buj^-Wyq(?pdC_x;C;ubPaCD;>R~z_J*(7@<0JZuu@F9)Khrx)5+Kzt0RNILazF> z_wL@qQXhF{r7+AIQ-|=>*nG#RQ(aQc3SR~{^9*xe7KN;2V0@O)O!y><>a)Lm-M<9* z-S2+u1=#C8oGhp+W!U0N-zx?*t0(!XPY?9~kiws&;Pp`O&;R6wtNs@NKLMDVM@4BW zOJW1aFiXvRdRy>fDcpgjR|{M36d0h@rdqqlIL`Q!MV!Ls)N8AHj0(08cUx}ocwFAX zq@HCc!P&QkUpb`2nFAS|OYeFJK}>n*QDzOzv`h!Q^I%ernzQSgF z8dG7U!!hw=aO6ze=DOuHp5MWVY}_H@z?&2k5$^P!L+U37-y_ut<{lj z7AAD9884e#-U#+pGwxUnGE&h}--G`ndL+0AG9M$9&*jDm#}E z{ceHre0c0eK7n-Y2Y^e3@4@=1ColN3JYl~6wf|+mL1KD8rp#{veERb>L{;Sl&HTA2 zKY{K|vr8pqE~upUrw2wm_Q%NKJ8iZ{{XrcvPv4O5Y|u-yN{S>cKV#4ZRl6KBXGy_y zNxrePk}g+%U#J=40X4 ziz6<@dhJ8Udylr8&Q@uRxL z(hvAza}7b+y!fT&MZD`SmgG9upZj9iGzTkF3+2q^;GK>k^_QR@`_mU*z(OUU>rp`Y z`4zy=e*XAI@^iY6s{iA!{hz$XRRL=Bl51?X^=oANGo`^tZ}-!8*M0z?&=i@XsdCDy zx(Cdw7Ct=`eEU0p@W#(S_~5EPll;Q3`Hupn04LK~b<_{9yW%Rlc4ju8oTt-apPYPA z@5Co@1J0W`akHNrQZgKw)d_4Fj4uX11RSPYV=~aA&e(l<=!HPsNzesLwamCP_G4|$ zOa8(OSEgi;chNR)8Dqkq`9OwSW6>Rgwb-^56q@Q8vs%e96N=S3Qe)ccad_;+?)7p$ z>d{yhBdB{C{5GtOnOyV>FN{yhTVgW}541f%oXINz#r`$C!jMCr zE_~)o9mFY@fJoAGPC77L9lAmaH}5kIU-dYj0(QNne(}nG1&|K``u6|(5A~7v_0F5y zoA=&R@l~4!Ajd1OP#=|A{@M=!=Rz_N?6)^HxHt9_jtBGZyL=#maL;}4 z!R-f+uKNJMUj=lcSqkN_?7o4cA7R2XTFXwpW!Z}EC=7v z*|>o^FyV>7yD=Nf__Vckc1`8o0Qq&n$YzI!a{$%2i>VIz=v{*8NkNT+12<#O_|rE? z@fw$dKogR3B`OB)G$2zi^^8G>Qcz)$>R53RbXzD7{$%eP4~81g#Ty2`OO5f@*Ip~~ z^!PKzPVISov`d1|Wt^DzntHCLyU5^cPR5O>u_vG04IlwINZxkMUD=q4pS||;oB!nB z`@yq^0L{+BoOtp7kN(|%Uf=z{Tg%|Rk<}ri_O2Hx%Y-#ctfE-L{NzRk;gOoD_hHj) zdr8_B5RCmnqST=M(8`1mviK$M6F!9~Zz&^C<0+l5WjC30w&H-Vk(izPNoNKKb$wB- z_!@0Y7cG#z3h0RQ$}y9_@WM}NtNn~LxAjqr~y zKEwk+3e7ZARcz^1X(nTq{ran~>P-^U_B68oD3Jc}Pf}Ok3y*~@<9_UmWyR)AXIwa3 z5k79tG~>*d7%s4ENqXVyd}x$&^35#BiV)7q_J?a*_EoiQuA#B&PI1H5!x7D9=!(JR zs%?resYrr52)pK{@W?WUTr_2GeN%cqbMY$(jE<0OE;xzpls!>~!y$fR1qh1SILt_~ z&{!+@%GZa?c1c`yHIBN%>Uc5W72c_nO{RsXZZ-Ozpzymo+f_?Lgi}zRDlR^h(8XtR|nHO9R7{demj;LmUVr@y1mB3m@igr}bY{?GqE_%~m;`Qe|{_kGNHFH2U3 z#ml`xdnSM^_}XWEeXNqQ{}vNlVeO{{!^9Tg2q!@HIi_m47Oj~kRJJui0s1ka?7h!$d1<^<{8e6V#zV2Hbj|DNPy9FNy?T9=Q*TAp+VLbw_m#X z_y6O6@#bIqw?1?8TmR~(0)Ey%;S9X->d*Dll>g(auigA#Uw!@NyY)T`wv#UJUIVQQ zGOZM^F>7s8FS3I=R<7l5-13#cnnO%WU!vn0sJ$VCw`CPYZto-8ITKEtcTULQ!0Q%F zbd90;7D0o=(M!W&SajkD~6<%qc8vZX-WO0O_N?_t1dqMKWIjm zRyNeU{&s)u2Y^>!d5Lds$+VJJUHbjp>SIg4i`)3KFPM+rKg0t-w#c3Rkt#Fj(iu<& zCHci4{osXP|MG7H`qVw|0r2K_D(#0_ZEb^WGHc%31!G0DVY)OT_s(Cp%wdZkB%9C? zCS-utbB>!Rn(EW6b$kW#qdsa)p9{2^JFq&%Sv{Y_rhF=^ERa^`*N48SY%*1W^>PER zl7Vs97S5Th6QZVzO~D>p?DUwG&weFs(fI=5W>C;NsiIM1>Y>lvLM`G|xApijA6&Ta zYY_iBMWWrOfb>{PrpBGI^f|!t09Vu6kuUf-c2BHIEE6a{ zCiK`vbV@E1@t$v@Uaap_f9F5_38Gja6fc!T?jD2+oa60+8HO7IvDJ0$y1q7`al7+4 zsA-XGr=+f_IGcSV6QX?h%b-c|d}w})!>}1`PUxwxBT)}+Kd>Fl&QqrF!$8}4ta}dP z4L)PF-QzB~-ZZR-bmmP&r#RL@v}KP=Lt-o$wh>-K*R!CfHMl(A>?!m02&zlH!=gF! z+$|ob79M2MO>^ceGbP66QcR7|FONh~d3yMo33DCs5_i~R5Qww-4guqaVHgpM=qtbd z`SxF@d*|J^d4HRe`Gr48)6Yc4P0Itw5AgtSE^`iU$+bAOc3Qo|K;M4%ZM_%bO!9Gc zJ_{goPS00vis7UTZ7?=@8To$^+14iXw(^BI?5PSj}NG$CzM9^WW5 zcjY@=l!A3Wx@wsR5-Ob&%PxPNuscr&;n0W*?0-G>>CgB!R3MXyVV=ZK9zZGtFU6>! zI=he6qeSbL)WDW(MeA3_?8P}}Ykq1X61p=bJA}5b`AXWVRgrSANAwLwd+9o^q{Sj^ z$}FpLd#w~vy6PepY_(N$k4~FTDYOF26S;cLQxlsdT*p55 z8eaTqUH+2EN%K!DiRGa1C9V3IsMNWL%BFEmVhC*=y^MPtR_mO4mqCK&4GD6WD7&)t zoGPWlU9(_4o)xdCoO(SMp>@XvQ$1rZ;~Fb2c=cbvpzB&#c_qjVEYslqQRgyT`!fLk z?EByQ9;y0JT28f=wmtuRE>;(Z^Vk@~eZC{|3AKlM55T1&ZQDQUNQu~UJo=-5`t_^6 z2SA?%uwI}^nbN9X_S#TPZTYlc76u!Q4WY9%?^SOVc|&RM+Dtna4kcI{PS8FP-uqt` zZ${_D;7GZADlpkE*J^w&q*+=vIV9MpPs9K!S?s}tOBmvsh(5C*X(n)S&QnB+fjgKU zusN{%P*@irn+xmIuISOD28cbg6IkgGe<)NUP4=D9PPv zCIRx&2?D=c=51Rnd7jM6dB2<>?R<^yeS^J@&LN!YzZc_^@2-M zu9;DW@ZusC7tS`697*FM{=-_;&6n~v*0JUyhxqq!g%T#VQD0NIyI>dHvRCFM@5rRg znq^HgrsU4J!RgBrqWKbS5qW;XePMkxXM5gu;Z)(`s8O|aZ%+Gw$j`b}glpGXbBa@T zPh<)a_0G`dG8|#ffz6CN97#1H7`8ynPv*#c72O#5h;2;v9W=XR z;{vLOfaJn&HVD8$Z%b21z4lK4UVHu3`5SIdP60luTWyuGEO#-(_3_1rcmSXp&*t5_ z7;H_hUU?4#kL?>@|6i~BVLG{CnlxuIx~l0I1ZRK5~EUf_!Q6t_F1IfYKFVUojS_l>(ho@tMIdr(^3omWtVY| zqzn}@!lXO48M+XS2f5AZqbA3!2Mqq~$V-^SD{)~vT~yV+N0re59P$Dz!R}UfO%+tO z%)nQE`*VZj@nX`Yzl6s3`@i@1^d5j3YoO^FOXPDOIx3DYwx{kN>H*+fX!Pf9UVZg8 zKjZH~^ZSrD`n|{+&bR;YpZtu^0!RQOM4kt>w28xZ*0Fv0ulm0M;>#lAQ$+17-Ak+2 zEt9F%X4rX!ECM(-4^}tZ_6?VKTCi5;jcfaaAs;`gsw)mXHpPvrP-537A6V6GO>vAT z&e1Ph*S0|9gah(A)OVaswjFB156>c1Xh(H7{J@4gN4~&8IyAX_8_AR9+1Z6YJ@UWOmknsLJNmA zTVz!|LkA0O1}axYFQXlI=9d6rOrRBAF(O%{#Jc9@QZnSt9nEz|GdFCHYjdp3nf0Ea z3cTefTjg-JvQJsWUdQY?ts`*`D9K!V4u*4q8r*49Q);ofoyy6nbQPS~_NzYT3_q~$ zI}Y{GWPqcutCCj!RUfw9;>SX`UqGZ#Jp2+yVF zT3pj5Kek7F2EaGJ#!KI={`!VL0|1w3^_p0(jTow(ds`pi{7?@7W2RL#1ImG6ej4Di z*T$Co&bx0NJ`oVIc|b7RA}HfIF*tq|&=6GrW+K8b^JZq$K zoW@kO2v1Fy+MElhlpuukRK%P)*kf$v0^h`Kw@2kV&F7koP-&yQv#y#P+-KrkgMB-b zJXM6ehcREO`I60Cgzl0G2NYX55XtM_c!767<3Jc|HtS8~{ZfGni_4Xpo7g%wZAlY% z7$2FAEo&hwBo*s$c(Nvou!GAUAC@N3JQKf2J8l;z=ysQ1^|31lsI!Q=`Ke?h;5B!K z*O^u%YmnnmYe$N!PJIV%wuJY%qgMZ$7k&b;>q#}cpT0q}#5dk}eSP;|SpR6N!mH-I z7M_Q+Vq5qy{~7;8`a?VbFc%b?g40uw<$j@$UX$>sf)vAqy!z@ZFWfhwXR?W#z9hr; zD_`Q{97SJef8pOh*27(Xc9(j2*C{!0dDFvHO$K*sw~8aa-}@iD`?IP8 z@eH+YU*MN;_^#geo#pD(7nf-bER)eatDYvxTrNRyHC{8xrthliBgL70WN~x;UJJkYeO8 z^5S`I_z@Q%JRGTIVF6$)pPIUslMEjw;pp4ZFygSR`pA=5JcY9#q;d4n8n?h$L9_#V z>1~o-UgHmA81@p?GIuDNIn}l2(e)z|JC9e;9=tXgi(#~NXD z%Ur>&X4(=r2D|6SpgmXhj(#fx4yF#eMM05N;&_~jE3+)%Jh&3l8mq1%TMZ@ixNY#` zsiSbRR}aSZ`NnFB#w87X#RjYSfUE9{e5vUkDWKFwD^5FE_|9i0$0@$^6~;rfKJzu$ zLx>Uv;5~MmR#j4SJvH22&jDqrD)p()HUrxs6c!TsV6B)7@t3S?wsa!ITLSW4(^9%Q zZ}~zONA<3E7*3+sv}>R{G#i}P`JDvNPPX?}*&3W#Lrs0&8-#+x)0~p_$)3_TtexyD zVisZtVK2_*0q70jnD*gcbtIZRlI3md?VVo%jMrGl#1^Xbv8TuW(n~+iX8@q`UH_vx zCY|i~`iM^dVC^9u05Yg^(Xn6Zb63^ifBN!|)_(!8mbCQ9lK?wQzjf-zKMT03jJ}@h`fw9F7&c|koPuMjR_G6oQ zgQ1dn^U$ah0|S?Aa~&Y{k~aueK+BAw$v!DA@(D$#;7 z5?WU)O>zaP&eYl-0*Nq>bE13A(a(Mn?YxDM!na2v*}KI&d}~Z@fEf$ehU4om`@sOt zLELV$E|Z$egy41@k_Oihc=tGiOP^Tkas}qXer$||u|2-QyK&(Cxg54rrfU(bPGwjw z3zu>5&VUPGZ518Av#}mWmq&7-iP0Ary0Ao77z+d_6p9}p8 zjQ}c~>G2I#LF1$Uvp@ZA9sv0L)g)3#%{w=t*c98R*wRnmRpf_w0LZv0@N(fjSPgt* z7WP|z_;r3?@(_%30dRK1M#AZfoc>>`_W*pR{wknvsAa9WmQR%$m7>QzP@9;|rGp$L z=R%4dJ^uK*GwPzPE^#77+*;HokHj@MW_a`}xqoXm6nq*uX;;~) z2QOS6ZzRDF%K#xu0ci=E;&lOW0$u;!pi7MJ50*UBfnb!8To-vUpw zBr{?O$XTtm{{rvcc7V-Yz1L;;)Ig$3V+|d-G#|=}MoVwW7|Vw#!lCl(go9lAg2Q?P z1S`v-EiRL+3Ro%q6W|z?%2LyTuod~dNwognBc3Piy#hden3u9?z9u1MtQx4pwPc>* zlBow`zRt6FypoEfu)-=1xthjG<`h%FcCFfttTVBSSr%!IO;lG2UpN)&+E*SZI>fNV z8)ueO2bKN%8gV#QB37!5>q#JA$1XeB=S*Is|+{40R@i8ts8?PztHl9K2{JN-{BKg0t-219Wfz_HNRUc0UT z5CIpgeiPvR{%?S9{GlMmzOAh5Uq#-*uQF?Kn|V~kv$ zyRG6pLI?)3=X;LjAQ##d94S4H@W5PORU}`ucxDu-jM>(;-ef5NIOfnG&$zbT?xgXCW8*z3}jxSonzYpMbu zBA(6#IeuLKopz-Z7%fcO)6jv4#r@iqUukoK5XWZF0^-H+_`K$k}J_JaA_-_9O0Be7% zCxh2sJ5xY2sir)!PbELZ0{{iFrnBG-CjBfrw){81qi=uvYj3>!-aG3dV1hqPo=L$* z|I%NpUj?ky?Q3Mw=mrBWSKmq288Cug?bB^8m`bjVMGyZuIdm6ntLXA8MzJKr z;FcSswA3~Hj8(bukzc_E1TL3+_WN1_)3FX!q)3#^91`(BGFqo=c=0rRMggb(*qDQh z^DuaqNL&svbPXX}tVC2b_0KhMQ#MHBT!a?pFmwZ-Iohc?FF3txc5LiQG>LHc`jQ#f zDidEf>cdWbuCB@I}C4;QI&_dam?gyTeg1HF1@ci+aU$p@2$MzW5Y4(&$2IV+v9Xam0rUjf>yCiwDpz zbds#^7<6(GLlt7w8CGswYMswYpNTPChB*fZc?2zZZgwesjLW2PR7GR_;_bBJs*{in z*D%P3vuvq9{U}8uFgWk{MQ)lFSE%)~b-7n=TU^*3{wh$ntIRK8%}KwU$=-t!QQtf( z?O}}NOC|FLJI8+bprJ8O#W{H@CsZyef1J6F8yd_x75PBUomb~5(I{oNrdm>`FV*gMN0F;=4p36PkqqDswGcj-c?3Ev#1D{6! zQa%ibANjBW=}6Z<0-EKj2zv{MR;^>KNVPcjo(vA2bRJamh>aKy6b79f5G+1j+Ja#2 zDulJ18`kg%hc}1GjPG-Gy2v3d+?K-wKu({!Hkkmgarlxk$QPwEB)k2g(hY%(fOOF| zeUA*YU5RiR;RG!ig8FB^VDTVxAaj6GSEsfe-HmF@!Zy6kJzSHi?gI8aP=nSk{myil zLZ}Ey%MH*v^Tb*QP`%3M)cgV#s!Yn)bFe~$H=9_xXxE25+$F+cC}i?kuL?8{a)KW* z$a(RZX6zc+G`eP&=#eSiO4`Ylt4k=8+;EAn!Icw9b#>Lq8Gy+Yy*gQ{;-)m@Vpwd(P zO`Os*!X8)IvJ&chQWUN+H%SpJ%3xfaYN5J?02pwk5!{5XK*ex>>A4ACd9m%0hS6lk z6Em*~Q7+y!2Jrx`Soo8pRI_!QYAH8RePOrc>;qk|2fy*#zubZDJc5JiBPlPx{F6Vf zarqlwmqx-IaHW8Af60gKYVDyO0GKn+w@aNemcu|ZIc?X(y!hgu*Z-LWW|F5lf0_RR z=sEWW3b)cVb;*}jyEazP@vi~3W9gk`)mjdGzId9xVsu^MXdfU^jYJV$uYkfUrg_Co zY`{W5%t4DI`o`jM9ry-L;^b)WvIh%4K7Uy|o`ZZ;A8`j-t9Pb(}6PuKW`lxD+*SQy$fX(HguC0_s zX20DflZP6siFS=e7OZ`syFuj{MsI%^y^br9z^>Klvq!KgW1SeY^rWE($-z+Hy+2?knw^W{1kD* zlSgY-)VMr#8`PP~v6$}s7r;itmWK7RCy4l8d+lXD-aY|`pM2MN&i^eyI1@3+r;Pt< z?V%n3Qt)2UU9x?CBFZ}GjK)GwN zd&f%8#;M(LU@3d>+C&wruxuvuPM01PERp&uhb04%Gc2Vg5PTER31fJl+3*`9e)RTw zEYUC`JFBfst?lM+e<#gcfJoaovvEs_ZDtDoL;ET~Su1DaT}8%mw7dTJ!d?;lgF}7n z%z60n+upaw29?&B9c78ki8;?jbYgoF6~uIEN}DY4z;^37Ya87frEGO-pJPy_p$9T; zB#3z@YN_&-Pvcf006ENS&GSS+UX+Q^I0KhcMjVP477Fb zYVV_LdO*Zi#ZCLlwaf2eRGw82MG{-Kns3He|N4YLt+I$8OB&hoMF1#m2T7o?7$MCx z0byF4o+Dh;(7<$wqn0@>!Y!xxa@tvyrOlVIMwD;3HJ7J4z-dobKcGG4VWY+a9cUM> zvDM2D9{}{(U*?13o}|q|U&1@#3*Y-zeg&|$fnMWCoFjqgX;Y5vLHdV!0HD}QgNW_+ z=JnV0?#EiVZL4=j*1IJ2Q1Fd^^uO2to5Xb<`z3p??wxtvvH5QR7Y9de$J$Km>$GvP z3kznXMCO88exZC4tFY9=K}%1ZFJ_{BQI;gUA`2_!LceKJi{rs>{!&)k6^x5l-qIK9 zC}b{dHBL2m#C>t#jdSF>Sy*+2RX7-$dAKbeo90vlssz<4P!- zWckWN4*)wP$Z5hvIrgbj){4`Ydh`rF zb<$k2Ms-kI2dFbu=Y!k1J$I>L^vxxp%4*&+h_`K&5Pqf5sci)FnVyVKc4X?pK?`U5 zF?GCpRKWcri0Jv7e38Mzj}8jCw31~Phi%wh16UL+wxzc9iO$W*T`Bx3X~e0%jz<=q z@?!MiipZa%#A?q!?_2Jwr~HJFZ`}z2iq!Q-ls4C?WW^drl$z%vMr#>o5aMinPo1>_ zW*=cNKY2=m@(}0{opF~xvB!MI;q$zlX;Ld$$ltt0=;}8wo^@@Gxqv%imX5lhLVz-o zvucJETXFp6Z-3shZ0D2X9G{xOO#3O5+Yp4Q^$7o z_K*(%=VH$T;?*EF`uvPow(orB>#x7_?mHLW2Y~x=jfI(u1J*HLsE-6@TbwtD*_76z z@OuN9E_tgjdTYl#Y$k8CrlC7R_U0~^H+pz?%(g_LuY(T0=G4Vj33JgcfQm>l^yc1d zmbkbw%J@Jw+`e*be)C*9YgTJO#ICvqQ2gag9?z5WCtu*ga6ywBz6Ov({I1MCf&@|p zCHp2r4aCbwhn=ML&p!ON`Gx{`H(^j+sCXjo88Uv|kaqZzl55qmtKOJ6+p_qmb)gt} zH4Vz*JkOYIXMSfZt>TXq%v-gdWPCc-E+DUS`>!xt_ev0_e+yHB`MX?Vsm(8@X2P}e zIG!3@R|4JJ25U9(SX}v=-&Qcns4v8Q^$`NmB8T76RGGnaISg)7Y3>@+i21cnfC$E$eAyJF%u*bLGGOHy2_$? zYiM#RIcVIKMfE5_1D!_xO317*c9&ohuMY^v*-OQgSvKX^emUan7`()r%M9Wpm(v(h z7*|^=L!zsP4Mzm%ItZAxDK7O?@39I634&5uBu@@}n{z?oP*6^zZeshp_<b+^G_$ z98z3+>|#V|pY5(EqfAzMS662Njm8SUy=BAPDxy#frjCs1k$!Xm$jz9R8}vv|Zt@ge z&%sJ!+7xkl)JCJ+$?!#esDZlkMd3Pte z*CQLwi_gZr)o%`4GGA81eR9O=&6D$7*A$ru)lfTcZ?=+Z2jb_v8O@Sg6Ff$hP$1*6 zj#ftOa462ZT>$L8`vuBHF6TI{9K#lI#e`R+&WD)eh_7mb&*0E?)!A^&*`y-b36KN08!1OK-Ugb+1o-QDD zV8Eu2n`W?S4HQmtkF6mvyY4dR#g(k=DUXp59|VB${76`N?Atpr$qDJWVG>X2s;Tp| zqRjZ`3n@2nV7v*b%SXL6)67c8f(Xc+%^%^18A_C9T%KXLp6PPK3u* z5;_VA~~teEH^2wR`9P%m2m~OwcGt5*-Fukk4(}w*1mdKYF3+&#(L8kn?2A zH^A{nKPS1m{*Vs<3?_4~L3i~TWCkO>CM16xA^zK!e)7Y5_hcqKX2OtZ=iXM^lREJ? zH=nML1I`-(*#P!rn^bHW#JGMkgRgUlPAB6IyAG3mMzi^Lby7N16;>=N8=IC`5z9g> z4EQ@g+S=s}q7zZ;x-cqI8+u?OI$gAj{my2$bn%wnyx5Mo(vq`txQ4Q;<|NK+5?yn& z!eZYv_%ttIT*^8x?w%w0IgQ*>$9$dV4i3RncZEBr^PO`hxhf?vaVsz5$v86J9fdT? z@t6u08bt_Db#TBS zrDS~lVx_qhxf86I@=F%Z*($7B$+MUOfdi3P_rwQ_mu6f1vV^N3uBk90B&Op=eZ<&4 z{dIdx^6)7)oQ3ZAo!50RZ`?xEi*?N8WcHlP`SpY(Poj%7|}nzV*$2oS$N|<9QIsPXkQgp8zB< zY@5Iq^s_D4r|KW-0bmxK!KF{Zw>NLRar@?R0&Z{Kf1fu3YI6R;-}x_U@(@FpIfxF` zq*Yk+$DVkbFTD|vp9pAk6jYmM?eH#Sr7>qVV0ycg&m4KdVH1MO5h`!~&4;u$ZeOYB zo3;F^3kJ6j(?Ojv9eQ9^?yfO0syAS&z~50>1=W_CVkD+ysRon*!2l198;YDVw?w3x z^+LcM-hk4V0};0%byCe0lNV4yGnc8;DR!Rlw|&tMoa?ve1ot@PN@V&?pkbT?agXt> zb)wz`v}~VwErY4}=G0u4t{z+4Eu@|uAWLBci<_`ASgl>#^JXf}UXR4AzHqtGN-BqY zAu7(g&WqDNa!~&tT5>==Ajlz_0!T-T?SWuW9w#MnH{8-{I@E@AcQq zq;`BTux%C*;)Y+R%PzeR+h)-~x4NX*XU`&|&k2ysn`8~Z+vX`xi625rv5x~*X z-`xDx-~60(HUNvRjnX??-&q0I213}|z+1<*LDvR%J4cIOoite%x)_bV1t(}0#Zah-*5DvyXJCGYHbE3}L_xo*rYc9yQ|*j=D;QpgQfR8Lq(g zBdY)a8mrmgGJZl` zs*J$Qmfl*2H^!Q<#i8NhIdc`jd(kgkJh%v(fa)i&$orJPGq7wasv;6j!k7;{j4Q3g zTn{pp4pv8<3 z*H?uK=y@;s?vMX>ildauQ*`>Etn(4T|LmWAgI9Q`AAOB)zTTY)jzo86C+?^3AL;=h zmCb==65?~;PeW~-7|HLv|I=?C1EOUS(5D>#?Bjp#Mjr^&Pk)5zmQ(${%=YF;G22%- zjG>z;K19w2{Seqka!=0GNO?3aAp98vHp@Xo$J1@y6oO%(OiozZUY&CX7lQPTgHEB- ztjUAmfH0@TN9bn*T;HPq6Mms#RW%MU&xpN2i8D;UhQw z3T#zzp1SS za=eC)h`m{lv0Gail1Ooxr_7N_T%odczRk0wo^N3pL)RgH=U8TpozG(+pAGZcZiwe< zB`P5`mynSzY>ylrub_SJ&rbkErF^&b0YHy?1K>=W4AXX3e*KM?|G2DxbMT^nZt>km zdY}Ew^Re`YegK$7t)(hs2Bp3h`uH?ddTc-V!JqX1q0De-LvfbT&n?(nJpg>_a}2vT zF;d*k>5T*;Wtk6q-#5+)?$vz*uLKpCjlKEr4I9|xKK3(Gyp>|{*MK6IU}#OBXV4dl ztv+K{O<)^-7-#`ZV`LM_Mx4S3?Qn6mJy`Jy1D+%~dL%`i3tl(P!mKegWa+xWbS*H! zhkBurg{G7ipXkgOk`Gz@l|y`;%bYtgI)2JCj=8QDyE4mSkgx^jpJ+Jk7{TYM+^VH) zyFThPD1B=k+3VdH8=a>C%72!fhlB(sYe!eHu6Kmii$CMgkLDHP8*bKg^BZ@Uz+8ti z6;$|4ev_DfAR`uZSK4uOHHOR${f@^;!%}io$u)0!pb5EXgw9F(G|CP(SDmRvdngp$ zyB6~^4~IEoV(Xuv^nh1gE17NOGNsS)I5ds4(fCRzzYJlZCzyLmAzeS%mSEv+Y;|Qi?388JEb}- z;JPPIU4>@A-qB^ySb>2_3aiHBE4zM!L3DMhpjB5Ui(ujhiPYwDhlRF1u4G>~!{s3) zr1G$Cs9mlE%WVnvK;m@d(K=LpDVa*nZB;hLk$QX9DujHgB68PP1mdrLmD5QwXQ5U< zXoT;$$JOZ6o(nh+`9?azAP+FDVj`HF^x5yeN)_SdPZvMu4_kS09EThqPjQ1E^!?(Wq^*^kdZa?4~ zTL1sQUgNO6#fyKv2ku00)=%#1s}K1AkZKuJW;HUskNrkKHmHAH!fym5{`U93s!vfG z6quj1CEXGKrN8>=GL)}3qBlh_wy*tGR{{4&wmonc9c=YIGVHWgg|o$0Z_bgj%)XRz zE}dM4qbM1AZDC=NNPXEX-i$dC1$U!=ma_lt^ZzR5LGNX#00xEV9M zrsN)*YG@L5v7;>Vo`=$VXywde!#j_O85D)_)n+Wz-a948N8fGbZLa9#7o_v4RxCY_ zrPs@Ybk!u7_dGgZT2GNQN5}5k2*;PUIi0C=g{ChYI$iaiA6JRuz1q5AjRo9>>YHL9 z!nIAStmG?~)|fn+-FAex{HcjPhic>`VVAKpZQj^S$(cFa333lS;-Z;{Ji=uF9lqY5 zwc%Zo$D{Meg-*4}uV>tjHD!Ol$u!jWo_+%$fL(9HKX%uzUHcnb|76>R?QeLO|DHs> zQ1^JB()`d50Gi&eG4gqaqkCfZn*o`aNA)I(H{N~ko$;~2A9X%Lzh~-S`M3E|kJDwN zc(Y_JaF<6F?|l0L&5hSSW$4WZpRB}_V{A(7ZN;Oe8?3ZgY~NZUuw(U+s-}05nnGvS zT)(@nIx4m@pj#{#J=;VUe|i0^ose0)E=99hB{}`Tq?4{lq+Log6q%=+L{CQ z4ECK}YcP1X3!^#c=UgY6*o7ybBjT<;#6tqN%ocR+_yp%Bu6hB;PmUZxPG98qxujX; z3~EkH4J>I*U-FPQM{uoc^s37oiuGVz?!qUBYiBOkuMFa1Ix{!{IMIxdH2v{(>g;2Nv?Go(9C?6N-33%_IJyx_nv`Rmv$>{@ zE%T!MYp%oVIJDtXPRds4fxthrJSGZw4V3K#EhV74)9&3EaxgIR5fD%`R%Kx|i zoi7r2PrIA@9^f*+_2%m@@Y+>|PO2V%^o4C(bo{j7=s$V^qWJLBLp}gx08|l~KCiN| z$zO{Z{oFr#>BldQHv-~*y5_$OSViemw49F35Q%dA#8?(>PWzk{lsD<#$k?j&Mi!c# zFDQNc1@c;Q<|dj8bGrr>P^rGy$SIWiRI41ANh5c822N#^!9ArksxjhrF;FT)i>nZAGt~@od@w4jYuoZ=DfQtcy&A>S=n?B6r}N^l%hEi1ojg~7;_DaSFkn~t zFfNddb@nHzl~7GN8;{N!rabi3);#yzmxK9RH@7TO+2Pcz+@U)8FvG`}hf-+jnL{tkaa?wax5oi~trzY(yjMFoV}z&+QdZIxWH*9i!x?X( zBR1Ia*tcU#kf7xti23*YNE5hD{A&(4cSGL#mNo*3*vwDj4}2LjLl+5S^j@*#xk^mf zFXhXeYV0n&IgN(jGuV|6eb>78o^o{}aaSKv76S#c4~>X)6MD>$>mtJm0%F3EAI1=l=Hd-1oiKwXXH&xt~Ap`+ncMt7Zf-H+VCTk7PL$i|&E- zH~9dN1b(hyIg|3Yktu@yi^M9F3ijqVy&+$w@N;o}Kze=;Kz|OX2)%f9iRg$qDRp92 zH2!VTqGhqHj0lq>*1fyZ7l84aZV#E07*naROMV;ILQ-+FHoVTk3D&0oE%ED!j?>i2y4Jtv6mgjnwPAb{MJ}GMtJB$ z22G(7Bu?9_Wy6SEdMX=1v4w;9WxVqUcu;6qFp!E3-{U9xX#y?$H2Lzd#u1uNH3Xxd zfYK|#WrL}YO>+Rl5rHwREgE`_H`75(< zEt?vgy`JY_oo|E1(_?{aM@SjhK==}+p<0lxS<MDn`{x!~2nPkFZDizbt(3-H}Ay_U}6U!n;2S@V&U-;4mkF+pec%BJ?1XeA)Aic%WzvTw-fSbV|TZ%(}21lwQu@bG?a02G!$77rwSi9O+jc z;lNTnKK5cVVUr2a@t9P(Q2XY{|0`fEdlbpcL8*otpb;^xxQ1?H;n)a=Roq+D?|zkN z$OIsSj!zs3cKGqIv~4VkwmHLW?)3}AaT&Z^w~z2m4s-YVFMr0Av?y@D<6mr!f5d}L zHn=&?QX?PEhKZ!IX?!1=1AdvxuwsO)#!MMA4TrJiuav@BpgkBO+NDlZVk!(ht>hzi zNy^qf?2xgVx;c(tWG%0rpVVV{35S_BT?ZJP6gAmwgx6Z+iA^qzQm)cropS-3wiBs2 zl}quhoLbk~ZIgKOqC0myQI_A5e}cXMXc0V+!Ruc8-9J?Ml7xS+tpkg!oTN1Po8u&# z{(;1EFOrnwUiuG3PEs>5xH6B9emxnXr$8!_v3hd)*8k`K{PEjQJfTM>NFGFc!KXi` zPY%8XaEG9USs{6?R(M$`6}p2*aQWD<)++WD9(bKblGnKUu%J8BWUWe7G@kq75j%8g zTo{n!G291Ug@W?}%nbs7Y%-=7xCV%^F}LO&*o+}#)vNT^Pef52b`uk~6EI~7Z`wFu zTPR;Zr{=g|>U_FWft2F~BNd|9OVz0^bLgF8JDPk}j@ljTKrDEhYeG4kLnzMViGRY( zJho=)7^>qxc%q%<64owu!iaQW+kbqapk+YZ`GlfH;nD zA+bz8rS(XPhD}b%u^GY@+j_xuNJkEUtG-Q;an|SLb41ybRvGL5>oHuE!#1iY5ovqr zeLw$i=O0AJ7XbYqU;B#vCxB&J`Of_>X<3fe&WSI{?po4(XTMcAdrHs`nyc(F~VfgqCNU(97^tF!6r>GvQ8cYCVe2@obYTWCV?$PL*T`dYH@OhaQXN=RAhR!X zG>^mrDsHA`s_2y~_H_&|#F0YSzZ@@XW|*V~*KX{Ar^hj^dvR*+m=%`-b37|AWGJ=a zr3tEYodSStw}Vu5HlCOdsTOeM-qdknw)QAHIh7dx3P2^&*xsEazq+9mmQWFDna>_iMhf-pJN6X+-?wgCLH5Mv{4y_vll{4#W77)@8p=i zNEF9008uLh;vBI$YBV0H=Xx)`yI0Tph)1vP|M&li`UCHO=3PI<7Xal3pWZ0(fhwfJi; z+u-1oVeP^XXKlHN#cm*Bx0RCf&@p+u_ltdtE{x=(<-F^%tWNxi$xxE;#a+Qy0{KA? zhih#qvY8`O>6Ldprnfy9+iW>=w<$~UN~7Xc-!&JuppM0nZVof;eepmQql_dN{+2<@ zMxCNswSw}b*Od4G*UrbTfYs2Ef@KU4j*Yow06i>;MD#QrPe{6Dq&^c_FoE2{Z9=u8 z3(Mu+aYl!Y&plZ~9e(l+rD1Vf2H=V?d*-<`;Q~{F8MmlPusORVJGS@|f0?XWL)V+bYJ2QL-F;&5)H#4MF6in@ z1*e)}&Z|Xqi7Vq6bZItc3(V)^L@q8>$Omx`r7>vvTJdweuAg=9j7XMwVCKHz`NdcRbP&kB%TME2U!fJRB|5G(4I( z`G)zTGhEw8YiDST6-V_MGtUW+NyfV;UY0=`F=o7bj>H!GA~7W((I0H7#ir0W@kvY& zF_^%U&6pOvfRJ?aF)NxT7EQRS_mG?-#4rR)Te#MD%*>Weid7s-*6{9RZ5?aQE__Ul zP}nHibtvOj^3pB-0O3koMiy*8HE-UbDK7H&nnX?{)Jr~?jG7>mNa7lG9cpSr$uR-t zewZ(el*E!v0`C%H`lzz@&FvaH8fcjp(rz^uH^L@r6+wkkzdN&Lg$ti}P%NcQq@RJU zT6+!KWE-Upi5(Wnumc8H^_HoeKs=AK695{X^r5hkEHz{YMtWQ^m>Xo>z-5b!;~HZ_ zSr7C4U&tEZ2l8{X^q|96!w&LkUctWPbME)&e`h}**!;CW@Q-=1nisGnqx)Lr9@(tW zy+{*j`g`F%B>BU50LXE;6zD93#wt=Tp*jAxZ~njD_V{}rd;2AlpRepQKkuWwplTtk z?}jE?=Jk`c=~;!x>SpVvQKsiCk(VrR7QPm+I0k{m1#Fo*FLeB>MF%##HITSu=Ehfm zq2pxQG9?5>$s)HFV%qWIY+3DzUGalBTbM7QEt?(;@3Lf0NIPZ&=bu9w;}~Nfzw{ZO zea(47rcVioQE`&L#?-mms0ULAsF8O?l6qO42w;QLeNWVx*u$&?=VH%_L}s-c=55g& ztKsIeP?Zd1 zU}Lvbf_Dr$9&k6ucIP05KOC;Q6>g2k0czG*IfqK^l373u*Wg`iCLJbQ7T(5UhR|ze)S;NZVv%A zjeIt*@Wk8{m?`!(XP_u*Z-Vq-k1cUd9M>^WCF8|i3a(fE#iV)NlWV@e0r1HB^PTbe z|6l#fU&${3=B7_z*+M(#jGwgjFdhKzmEgGu95u-(UJ6*13GVJszT@o=^sfQqT%V2AGu|rIHc-Jq(#-os>YhKBf`8y6e)F zI|Zs3Qv=nkoS@$~TOxP?ItiOSN>bN zjLvw)SG+6EVRP+R;7$J&onr)l29_v(((6Eqdr6GJHffehH|K`Xnc4zi_8eRT#k+UO zWgm1gEkEexGJdOvS3d=n&m4@_m9jwb&T#sMC}fVq`s=b~C41|^@}f>NUBk_$bH3*M ze8ML`>*{&W=UZVPT&wT@*Ps6tqq3u~wR66o#o@w4@UZ)vegMePTE~cjbdoc9kL$A$ zeMVvcvOD|@Wqp>y9r;fGZDpH3nC=Ci{_I+Kz7eSJ@VrDER+7LYk~A=C1z_!Ii+xRW z(P-mf!Ne#FelP5$6xTWdio+M6L{e;vsBGrN0!Vb}ISmT4Y)T$a>am|3MIoAw^w2eD z^z?4LatP}Zwa=DsaAP;#wKBjwC$>_7-WKfx8Tr&k3JU{OO)@ug+b~JaD{pgH-Z&^a zUnIon<3N25j51JcC6-To4_g5?sz!;d^CW(x!{$7nI^vYS$r*usqliGDj%&-pH!FQ9 zFEJ%9%vHNhKNwpmP>;RtjJaO30x4(3LJh=ioNa0!GZzGt-3;2P_MDuYpE)(hDtXk< zbj}{LO=Yw-7;T+r>=RV;EKCBpluNtcrXt4A^|~h-Tw!n>VR@+%OW{mCZLNl49q&~G zXPBC=d}n;nt2MK1;b-nX-WDzR2pQJ|-}WQHJPZ=}yb34A2Lc;ue?gAp+VruTZWWXa znLOK84*~w~-+kuXy?WUfJb&;$nBnVQ`)WR__C4$L_y2WoyL0vKck5nOn3)LU%+nLl z#Up{@LA9HH0GP=TbLQzU$a?^@0v!Ii{b0S6|dcI>o8e8{MA~uq7LYiLFmz0l1<{HU(GvcLYCu8eH zYi=O5>GmMMC6@anGO#cLzE5)<+O4pc@QTbV_^O{J0j}-39o$59)=ZxQ06K@?^X13h zmCuG7ln@#+JmO!KI?+2KYqk|5m!R|v`j`q`^Pj7hN2Ytspx)KH29gi?-Tp56UF&TZ z&TH0oSzkD~X0IekZ`(Q$W$I&ride~;!KDf!u1zfLnr{2+Ymi`ofi$Q)WTt_rgve_P zhd#~)Fs|cRSfAMH?f?7!?|*pt;M?E$`mee64nVPtn~OpZvD3)RY3~hUvt~+QW+)aVJDvEkRm5% z_voENF=z~V`>P?mhpoB9OPtaMzvGdw({5w&Wo1HJeJOHr1>xu#Y(WWYj;rK?JcJtW zU3h_OJVTu01uUWei3Aru7UuLT2)uO+BQzT{Zph7H9=IHKoTu-U%6ysDAa&kC^RqXX zkHM^M#v559@<7n;SU-+qpukXv6@cfDO<4AbB$qjp$@?uY;ao! zw!*Rm!^T`;+%)ndU#vODt{bI>?6}SpZ#z@R&QQe&YxFR$F^b~cnRpInH$cSQtp^92 ztu;vgD}ee3oT=hk6j+DRIiT9I23F@Zf)%Jj7OO!$&KV?+y1t{nd-c4JeMWr*@El_w z(Eg|D-t_v{|I>HX@z9G_^}hLYj7G3ZG7kl$GIb9qnh%WTVLt$z6V067+q>U=_c7o2 z*Ui6lDc0S$zV$~R=mP*{`pnP!#pT?g_#|*BEdiqD60CYZ@!J4rE zbHy`l8&0@&athOaI1%ehUzUB_#EyQo=8jLFBq^*>2lq7zW^t@RJRpdd^bKm`Sv%d} zRg*=eymWDDQSeL;@xWIbUXlZtG|O9$aA40aV}pZ44X6C6*6ob0TAiRKW-do`HE1#8 z#2RRL?b}4g1S!Bog&yWVXeF1kw$PY+^WCeLzU-q6d4T&L`Jp%H?f?1)TfY8RPOBFB{J-vL zcdy>_o^>A^ZWHVftpuj*M#c~G0pL=iBo(;|R&yUA%PAPY2f%kKGrs-W@B7CO`~lzv z^)3MJ)+_yf-e4i|<4_#t47L=dGQw8^BPD0Y$+g$6hOuYDIBG4_D5EQTZvLE5;DUvk z7JK@+N!c=wO?>^t+^0%xHq(}D&#uVQ6s?(;q~kzZ(JEj^-Bl|6(PHOx98o%I!a19M zd#MpLI-z@Lj|*a(qqlSZs<~+7WZBLEE_P%%wOiuDbK+x`NI+R{Tjp~Ed8fb0k1;K= zFwSMr@sfjo#FAjT7cVW~^ z$E+K{7FL6(TP61RqDN4M5)FIc%XAD@Z5pd}+1$G;We2k;!(F;J{?{yy6Ceob+mdh9*&%32Z{0*Q|-_k zE-(!>mAwE0PwNOg>3wtu;kAj0@s;yZ5OIgWy?oSe&rH_QNRe^su-wTFmJIOae(8Ha zexP=FjyU@+0QRWVFj|_Dgum62WV~@P*Rg8*HI8!FCN}%6OHpB*C*a!JW*#?ri`*#Z z*SxYUqICf-Cu>|78uQfElK9Q69SBS*oFnlO6SINXr7DGy=8>PawIuTHtDA8!-u4-# zC+>}pbX~xUYvtK7$_qy1yW;R%I+5&ks{Bf(C<-kdYWjhEmbD>rYAhN+1i}c%vM*|{({mh{+nJ_Ur zUNl^j#k2=sM!z% z{>US@H0HPv|89kmlPSASmE&39#Tjn(aMb2w)S7`!EDhTZ0!)u=jM7DdtF_UkjQW#Itj1;AqTgn42qz-RV(p; zLCB=eUR_yj zee9tmIE-ZI*O2?&-Md$x_uGCcs0Z5K@s78?wg~U=#dTLtJdt~q?qQE3+i(9%R#=LV zLd-F75qWgXZ>&9x2Y@7_!*L;#B)W^>-vh`m16sa62DtOx-}UX^b%EzY620t;KBnwD zl)CwQ5wo~DtXfn)C;66ulC8Is;x_4}FFC_Lk%O8&7utKZ_=n?6;R~nz{T6cJ%vHEZ~t2s%j7rNvVMp##=a~LfeC|BWaoJY9>SMgSE+- z2}`+b9C$FZeqnIk+BKbkt2_AG8~~lu?qj@dBWX&evRZjbLV2p*9b=2C>j)2WGdYez z)Ib|(!Cj-s65$Hfpkv6};dUAC!HZuGjA>%WT=M0J%$!}Lg?HU?1op6E5W0mk^YR@I z_n9>Ss`?SCYg{M8ZR_4aXfbENQAJQ#Kjx+O=T71(4XaJNgW5quLG+aj=}o+48MmmX&8UB_HL4 zSvRNQ7?n=xPL@J?-PjPcEegA8V8YlA7v;Fn!lkb(G#>#Ji;f|0Bo`*XhxeivKewZM zaNR%pj&J?D#ddi6ziP2w$SRm~>i2hlNb&yApTG9rcc7m?=h>2DMlv~eQ$J;l&)a-j zVyx}Yzv4?C_{RWzDNx=8APhe-Zuk6>i5C^&AHxPsV}Z>Dds@4~%w4dypPX<}vf4^~ z)YR;-G|+N!v<-cD4eJYMDdb0#LJ61HW5=Aa(K1>ApmAKK9x)_O+t$9!P$vbmu_w0L zNilj9v{EW|^upkO%bb^Enq!%Y;Jj0^D^%iRLQfcJ3Fi=S5G2Aly^N0b;W_uyM=a>6 zp)azeS96m~E?@CI2v&^9h$RBW1y6ggJ~hOb>$D>iY=JwiQis-YHd%3}&bUjQ;%&;v zGzF8m5F{241zEe`l6dwby#~2o}1*;!e)$zoSnw(b6JI)o72JpsH0a_N@;iBH149;=J z%u@D_5qZzH?;7DpIvLupe4F4}edN1zEB+cCoB5ETkxzVRguqeITb7S$F=(fK>`PLu zP_pS+jbHTQk9^=?{`2_bkG;Jb&-}!N6z89C>XYOOEg~yG49pmkh zwZ{1-?O1plF-E=iS)iN{E*}xD^<( zB{268ITrMC>b`l9NXk4cWo&%MB-dM_cN&T%wGtufvWqM{ z$6RfLZr8VcOpl49`yk}!w3~UsIsGS#X@Y*vTxBMS#ipXaVR%!WYvqD8VtZ7bbTDJ#_>{Q z$^koQBySJMs(G!Q*1uK$i-H~#gN^}2+B&S(iwBr{cQb0 zmA>v_cdqJQ=Ac$;9b`$}m_I4)VLbrcFS$NS>oD(PanyXA{Z>kSvr_$ug$n#Vum1br zIjhEaFQNOnySI*C27Jb|DM`JB)HiuQV|v+pk=fpVGw$$^8$qxotADxRI{fJqn`+%@ z*eAX&a%K=DbDz)>NgH2q$aC2%_r=M;6H}L#lLuLf)L5Osk;$e|txMm*^vSj3$Vlem zZ?A1{JD;rKpqCo*cCg^{Y9a$5rETumMkWZxf^-tyA$0vZW%)|w7RbhhbZv&bV#$nt z#dsRmlpx({KX=VrdSVu;b{;cp{n=WM?cLNhrZyHGcqH4Z>D;XCS2Y{jrdoMzeKf>I z-ri%%h?ZWHf`eK4Nm#pzSC%9Irj52A1Ce)S+9r7Spmb9=T>HgUv!<1t$S1##pb?63 zq7k=o_#ja{Eil*cQ1Wvu*sNC2OCx;DDF%#2a{nCrjbx`v>da?Be}rwx12Gs~J{ z>?WR!tCvmfG1)rD2h}>3n?n4vwoT_yIzDBTT*M8k;(^EX2o!*iho|08XH#C|@E4JX z^vxh{%aKIy+XPB)ynFk+k9}r60DSC;+|&DbU-)}r^7g;(O?8i|nqItztzZ(A`|yW8 z{j69p54?Yv4**Fl{iS3m91VUqoSV}RG(e*gR5Ep+WZ&}!YwIdZm_ebFze zzGwaX;Y%5Mu>+02v~@d8*}3#jz5>G_zhxL|S&W;CYu`Zp~~v~uZj$AM*#3*uxH-jZq7!7whveVm)2I!v+I zS?<`DAM8)W#`}Denw?D@U|h(@W(THxCtr6jS>biuQ77f z&=N(|wshhw720W2Q?Hu_InYuD$c?C?s_Ng$aiQOl-w8v-E8*8^w5gAf9YOX>QBuS5|QL78cpmt}QfWy#05%;O!qk8!xo%$c#EBLu-swHrr_N ztL`K)+L~$L;}X&nR90DQ>anR!$6Q+HoS23i<5n24U_0Ir%yZ6M}A}d z!+Ze9(MvKUn+;itbkzGPN=2)MdYdl=s<&EHNInJtoT{ad?Cdba$mfP#u+Mz5INnEJ$KxbcGh~m|P)u?cV))a z+4TrxdQNmB0QH5bIAl{h;uN)bYrGtpesPoQY|34hivwGS7p(ONp_Qko!q<@GDsg1- z*ER_a9$(;zcgMLy!eEjVo*2R>XU2rHT?nBsVp&+nJ?nx>xpdIWb`7?tL8GDlRrCuR zL?^;DLA7e%AO^OFw?b5Wp`<4Q#SGUWoJ@jl?8<1%7h{RwS~Zws!Nci3vqXCeV2Smu znjI(gbWQDRfzY3}shix3p$x($Y|T?c_0){GRXazk+?ZExV60{z9KB{h+1y9`sl#5jg#t)yC?vDRW?0N?0Cu^KBPHTV{M%PCE%2gP1AAN3 zg+1CdDq!`#UR{clC{|dE97wu$)(C-#5ix9xv0+}f3$E2QkGIeM%^#y*{u5jl(-$_L zqlaSl=#YuJb>aD@Z~VG%ED;|~-#z{L-~4?5;P<=nm-^7+>L9#{$-{gANX=#*v#!Wf zs1%F2e(|psuf{dMT}J5d@G$`2zg*+hX652AJ)o&)K6T7#pZjwEGN9b_?{utp1Z3e* z#HC#?v_)R}mS6X1AA1jW8E$d;(Fr+DLJbQVxKB2GyRBSmkP{Ib*Zu`Gv=HIEYs zY}3PfwL|%i5yeJ+{h)v|*C5L3RpXNjf=w%a>IeU! z&UnRlZ8SQM$`*V4N?*$63swgA*}-EjK=_z*cuP!7s80=<{6^j&GU_m*0qJpws&_TP zVqpa8K(Hg%~*o@o<{_Yx^U#YG7IS@ZkELX0cU;SfLi zY0gy>1{J1kc5sR_Q1vTrf~|?T9k}%dxR#d^>Ouk=e%k#q*lsG?B8Jy3_!{j%4R3|0}@qdq}=%rVKwdC+B4rSo%_|3{r@;QM9Gs zv9!^pZjrlf%7U< zfa>urfO>rtD}$2NHQAL!?_0>HyyPRVKIJ9PUEa~O_4cXJmQOt{=REfR@zNv^z@-iR>&A<9zF6KZ4dJS;C@L(N3l{cWOI|eP89xcfc4h^U;Zcl zyT5h63SByQm+%Py9X7wQ|0Ixbya@9Nf6Z%=xpC2nzEC2mT@GW1uOzhFM}8h}&Lf$& z?8u+%hfLIjIuFOQhvl$scVYVl#77>zq4?TdYfNRpv(B$&vo zcBS*X2AhYv92)bGRUiK%Y3ePr<+V8u2QNb~r^pIYKcyt(eQaD0$qs>yiEb3~=S^1)8qB!EYA_iJrn)Xjc(pZ{PKDifG-Bp5*>?4 z9JSLzEV=$1ke;w>9k3R45mOqB?M3H&*~vfl>nBmPT^X!+P8SN~Y~sL7&bmhkzLnun z-|~4vg$q0;#?^o^yk(;vMURzB^9$HJMI6HT7uX>JbH39Ge$Um_j(M_Azb!yPUK{xk zF(P)v$58r`6p`l=M%rk+DTow?b@>#JI7+L=aKkVfb z1AJqnu~64{${%<5!jHKPOzd#cd{bOl1#OMA1q*Tyw%(Xjqh|Pw|HKNYWHaOylS2eS zpzga4o^<@01iT`OD`~g1EyXkJ3qYjGb%8T5w?c2b__u34I{0u_oT=Bsbj|=^az&Uh z+c6+dnvzRfJ8nXu6lyS5oG#}Zp_lh+YAiclQ-22UHWDHxkKO8Daz5<@B;a;b^* z)i<8$u}$M>EW2lfRf}mAw0*FaPTHF4mA-1xaTNnNeLBoFy06oIXV&-G=Y8yl-}6uZ zj*(_|y`Pap`(tnU;a7e8xBbnx)tuk{*Q@_pGkRy5dsV;vk7;iB^t4IGIP;V>bAfMq z_hdZ)B*{xjrzq)X0rNHs-?w~M-@9A|{FcA{*S~%iD&%Kb2&epBW3c{WAYS2n<5=b= zzhuJkW8LXNK#prySuyk?Mv1)yUf+zdnWI53hY`=&2>Qz zJS9rJ$kL`Pe(f$+LAu}49s#bQZD1I|1}uyxzRffzqP9}3%d zl#aL@54|@8sVxip5PN*ssAur>^&3v;$>od*8dZ(O>wz@OKe_~79Gw5+YgJPfq(LStg}kH5|t;R-rnq_b9RLO``Z`QhuC! zF(^pF3Y*Z{IUGlWUcc?HV^qhf9|X8wf8p#}sWAF!-}fzYO^9cm4DahvY%^>wgK<4P48t z7E`&;yce1mXx-$=s$_=+%u3a2t?)H2%*+y#8SunTdB%0J1h0aOlP4!+diyh)m>5mh za#g~OFFkw!rKGpxX%}6v2VKGQ8{IgAPW5RX_$))7hSD?Fl7|3VjDn=k7=4e6P@}+6 zjTQ?Zm!YU3mij3UE>^)TDsdNrpi8fdHAChF3OD04u)?>Ek&4Sve8u}?<6 zjN(GBN3S55%U2E5f~1a)Pz?AcP}DSRGPY#xWkt*_nInnXHp^rg9tQTW97~P~abDwE zUK~zBN>13(VvAln2Ab3yjvh0k*-r`UpCkmEEu>JCEWTu@T{{(zrOOA**em&s!@XL# z<*OvZ*^hj6e2Fu5Fg4UXwcqS05x3t~L=SIx(ud69u)eT&lO1zyt*CC*HNa(LPlg+R zy=K?ts#$^@z7Z|4d@%EI@b7yk0M}YPvM5}xyWH@5{D^M;p!gjCZV`O(&olJuKYr4O z`RCKa@!tC7zd!dCe^CGVPfZc6ccIUmj{$12Q(*oY;Pw+;{MY}B?|&6!54wHs@BH|pWTEAqfC`P9JU?}2 z@ydWjSA$-R)%8PwlVI=Bij69OmrZL+CZuEnKqM2NXw6YFd}GT=ua>?hy1<0H=8CiS zk^^oU4du`nEy?I4?@42(o#KXo*7`lOPn|pM?-b2w{A(#Ogo(R zAEXbtB8X`9@am`gr5NibzR~SIg>KrNa^Jb^)jV3OB zG>Z?^H4g9LfLPJQWhDpZ!tDOd4mP^y5-?`7mi@+|DqV_G)7jQ>kAZ2$2FpASv0F9> zRCgUsse?~H|0l=TyZ7;m+hf4+ZnW}A{PfzK<3YDgu8%N_lfgZK61N|Bu-n~nP_uB* z7uX-cg6ivXKLF&~jOL5~r(Z$$z}u_8>+gMitwrB^>VN(#2ii(TuM$PpbL6z>(p&0% zGMus-A3y8|fJ=#!Byw8x^eJZg$e4Sh_%8aLzwnh``pPH9Hv>ZUAliNx!0vUk$GsLD zi?2hjoBks#OnyqlHGp(-Vk#EdbX1(k8v4@Z6To(|u~Hns&?7gWZNoi##yUr8M=WyE zw(YKB8*44Y!br}%WhJ`YPI}|;CXXVp50eoSn?CAjTSeCnrd=GOVlzXh0Z#zci?8R0 zoCanZ*qRIBU?~Tab&jVkl+!K@^cgcyX}i9}9^H&DJoK(PK?I4XYMnUJb8Zpt}Sybo^r`-q1*1T%m8A)WMty3q$Na+iOC+jivJt z;Z}%ZVesb-L>kAoA?kO18rAtL^AaKTnhV3RUu)$k)+~N6q*lKDvobk>T&tZWgqjh* zVnjlK$%c6~1zbHCbb7%Bru^NxEw8FT;rQYodhdG^go8xppqQs+oF4fc2Y2KB!+rorLRm{Q=}Y5TJiiOjN4OR$ zI{g9$?*h~>1OChpzTpS>QiccKKKJDxUq{EG_?x9z>^n5ZIytM?y4NBm9dLd0*CH=j zjkD;5$jQPYE(3EojTB{`n6cH#Q808*`ev%ry@DIw&z zN$>ao*b+YuLx_@hQyoes6G-BvD^cO|P$F^V$j_=d5Eo_nR$QvdF`oG8h(}Ni?bJEdSkta$$81z^tkr6z zQ!F*K#=UqtuB8Cepd2N|bO1owZTLl^JdxF=8Z<1P15dxyW01HyJ)2I0i~-cP;;n&e z6F})ZKO~U~UlY{?`}Xa8jY?UhieVvRyBs56TbL%BocL-lvTvPz4BK{^c%aO>!6fpG zfk!4R3N<*9RBM28&BU6ewr&X9EZlGr$BMnFb&T6ju(O6R&M<;O?&HLW=5=Z;C|Igj zT|IXqjO$ukcdv@3ar=4<=8ONp&Aj^miGTCf*S_Hm-}kP%=iKG5(Fmk!qIaYfl#`HL zfz_^C63Be^=xC{^XAfcjWIX`fE8)mzu^8`y_HSA)*?0@)%m2jh{id6!*$Y48-?{pn z-&+4iKz|J2r_>6`&miT}A!@nm3AH4(XtlWK6Kx&dgDmG}VReFc_Da5X$CKkiW&p#< zkTXXd@$JwgYI82eX^OWD_({{O&u}B{BnahI;N_~ulF4Q>fj`H;Jxf_Th3~#YKikDG zp!EerviQJu&bL_P95x0W2{BIxeU)-P#J|-mmp1LrK{ODIj)6S*#vfNIn9A{z+YrlOdUYqUx3_i|cD_whJJXJnJVmOL@5 z58%3QC+C>a`eV}?lu5Ywohprqf~UN zf(KJ5b+z+!L8TO6jf^cWY;$wj+8Q;7%m%+MVuN4vj!9ze5+pQ(NDd+aW1Z-0stXMw z2^fIMR>Q)ti>PMdLKimovMxePL=aq$Yzk@?JxwwNK8gN~}}x$vIUX4JeDAF<(RjF>KaW~4*XN&ZW1$=I~-o%vUm9qn_Sm5zk!+u=2{+N4`CCVa?}Z&cJ8dHasM+#C zfGuCL=%<`9TXU03eoES{B&r$H1QC_j$&xDIBJ2WrT62laBg^IkhHb+`iljD-T3-Wx zs4uZ(UABQbEYdb#+dxDsN^P3dE@A@hrh4^!an`^Hq9`+V;i|Eti?E5BGSsrew$*e# zHsoX>*o^(+%FJk`v-jqhjE&2~K)oR2&;IhA|7L%1d;awA?|<7jzE!XO_syPr&~4q5 z>i5)YJhz!Pfz05U)4F2U#uD80?#XxnNKUgj8P9$eE8|t%IvLit0Y1{-!c?+5|L`At z^DE=vLH7I~0s6B*y~6J>*1~gmdLoUCW6gs=wLX3N=-0`43QX3V0cS;UrP5MgXGs** zV^fcg!M^5_TqOUUI0<@YVaV^?RqC$2Lsd8vO^AQL=-4zQ^ z={nw;<2SzHRUPHCF_B@YmVVA0(@JxPWWQ~>y3Qe#ZJnuNQ1lDeJ_|@L{Go|jjS8tP z#bB*rd>M3ozde*riPEtqJze%(JE6uvW4~}x^`-EueO*^|ZS8sAb9q6=pZ(qNtA8&3 zx92^FtZK`>i7^`uS#%Oy*r88}Vs`;{GomNs0bnMX#OWhXPsWU|Z|{AtpSbEkl-`B! zC$g{l$}fG*PyN(8^!30HJjnJrU-bN|7rwauqab}0z_KmZYoYpB>trnr?-2CsejQye z@=h&TgF{qv>1(VWePktZvk^0IAZ5_!RB*euMey#~=SCpkV#7nw^l)U3&YYHbY}*Zs zSI}mt=vtTyOE8N$0F2Ud5iOswJH)c|jtWlK!R+4eNPVHHOGx>O|G1TH?U-kC+GvUc z8eOH@N6p1T4_}B9d-{%%X)yWy3Mgqa9?vDNT@aGW-!(UhLkSe_8esb!clDKj@R%2J z#A4r$&Dt1|cW(N#N3WU)n40VyV4)maBB#BJ)W^q6E2;^U?+94Gq^vE%)L>D1Fb;)~ zIv$GI`Wy%6fUM=9*Oc6eE;|OEKXS`>OHO+X*a{+B4-INF#Up(oDu={zX3aTkWB7u% zBZC=m|ej$Zp$l^qPw|+ado<{k5R3uuk?OyX*`ASqLU5zjm0}I?2 zx5#v|m90*s=J1Ki-ls>4SLYMXI!)y-ncsUA7M<$+&2nq#iL zuvuw|6v*IKe3r;qu$|3tXKwHaTVdqLquFtx8=+!Wl=7-N)M$GUt-1HGM#WkGuGJ;7 z?yEbr!}`HVibLE3+2LE#B=I$os8T@%RP)7C+#1u@0DI=)iEHw}N*IQV0lX}Vn!Yai zy7_a-_nZd;?!RpR?H~J8jBlj%XaC20j$d@@{!>oVcfccwoe;4_&)DT-^dz-=Ux6QP zKq(3ZVN8o`_8b7_`g03CdHF%W#qR@B?*jb5O}z{7i=O}C`riWY_VM=X{JPQeuE6@8 z0FG>hsl}N^?1up-W}WdQ6kVlHcD0KYV@?!_QB`BSP9C2$qLRhqlPco9!+&J5nY~k=# zfZD{(c<6iqLkWp?oqOY?Do^nP>{2V7F6!tQ)5&(RHhY{GU<^UwLz^kA4yNNM)qNW^ zkz-ICLmkKJ35Kr3Gp;p9h)F=2d531@CI&9*#s{|Nma|8e@Pl(ar3HXe!`d-EfB|$X zdECiQ=QI!kFh`$QJT?b)LBH~ouSmm$ZTq}ehwla*e8CRq+T>uxvgjOyoMJF$qjj~{e98oPSi7gn1Hic;DHL_cO+N*s*OORubrN`9f0m)QXiER(f9ijH z1;#hg{87Mjikbyqu5Yn$_XD7%$SPDG0yxe(o4A=1ep&XS2kYj=?N{e6hfhFR8-iFW zu-I)|jU`(k+r+nG;lyw8?8+3mgR%esKmbWZK~xdqTug96T=!1KASu9eP^upF|(V}p^v5l#c^}$%8HJEbh*ae1G zE?mJiGu#?+G+yIUN>3IR76@I zN0`Zh-Yqj{=Jg~^IkSuzqdmiXF+_ZVh(kLglXpatc8xH-*E$1#OWw$+kg{3cMWl(6sVuMe|wNbX`d^g|X+z`3j^{r_+JLcEm1c5JE1s8?;a0Sy)b;P5PlNeG7 zt5^KaL*ODnm00LqGiggf9DsUW-56RZKbEEjSXtt_h*N?ZB^Io?X08`g0DSZJwG%5} z?$f`h{LhQyE*J>nvWCuU_w)L2-~-zp@Ft4IQxaXJq7d=$)A6}m&_ZoYaS@9l zP2BiW2udG>1T9C)#cJk)EVU2KbQ~-^8Jk;{h({}pd?n$+biRs6aSeD4!+Df;oI@C# zy*kqD@#e96LJ*!_9Eqn1D<)mwdrEAxF0tCZfzG>)E_T!w1$E$%dF~iI*Mc~Oqj(iV znNlw{6CK8f7|$GK&tnZ<+~T#Tk=B1JEDbKADqpGZ7PVHm9w(-n#!GUIDx_yV@nQrY zV8+YHGQ8Lm`*=vzGdE@(-{OcbdTMlNCX{I6jH%;OKUoXWD@G!jRQSP2F%MFSm;>fx zQ3Sw{)z%zXi=pmF7BTaKDA&Ywoz*qgw#c6M{Abi#|NkzmH`4mm|Bu(r<#v7czkl(( zUUa^Xgs#8(9~XQQ)JAM`JY($&EZo%H`1q;t05A(ck!UH_r9KYK#0P+X?y|`wKzrS9gBFi41)Qc&#s$Wb`y}vz_Nt zcjMzH^8p~MECpcBmU)tAerfYTsfPfb#ABEwX zgz%7cyK^UT6_?M>s6OZ(?#{iC7N{+Q9!cn+&V&C1dKmj3s+o?)O=oR0+7Dz zmAc7EWU7v9c&JqeJ97!=x$nqD0^Tv%L1u~boCot&hawJMK9*6J;yf+;4)RQpheJ1!!}Rx%=s zP{CZ=0V9S`819)lY@!+!F3SO_>(W>YkoJFM)r^pok+@m})a@v64M{I?Tc5hDDB%mm zU^-@6h;r)Ox{`_o{?h5%=xe60sW9@DzqQqiaIT}ip5DIt&wuv|ZshBJlfpmyf$#YS z-bwCP|MMPmyY4x90jgkh16U6R1e^^j#+TZZA4_xzeM7RR-4O4GLOY9*aUN9CBWFzi z$kn5dK2nJ=-`lNQe7DxEAAJ4yzv(~u{NMT?KKkf~eMIRWc;g}9U;W_EUcLRzkNHq{ z#6DFx#`B~|I*2`HKUabSY+RqDmccaaBc#_^JOuZEtqbaa+T0Q70$0(*52q(%o#@4> z@v`M4-j0?oB6QMQpndChS|xCaRtlJ`bZU%=<_y@gx7)vwqJp&Z-6j6bxO z`ev0%@r@cC@d>0n%Kr$$nFwya(uH`NnQocg(l2fK|Cs35L8O4?L=<**vaM-&tm&BI z*2)}nRjEX|*k3bl4tE{6;y|(;u~y6}#?p1RhZw4{LCxBS)ivpP zLXdpK%KUn~C4>qi4^0DIZTR%LE_Lr%TWBsfG~uh!0)>xWTXXmrm~ZiNv)2X#H+D4s z@rx?1KU)sg^ZKs;`kw#a^@m=x{66Rb-}(Qaecq>jS@GVkulc)ub>~j~&%ck<>q>vr ztcQZzw|PaEjX?bh&kqD7JGYu18bwRt(jNd1W5NydsnRGy3YM6d)6!F98gs7<#(o!o z&u2IQ{=I;D7l4CQ?*hE*Rp0VAz6PV4YX8;$^1`c+`{erS$-E0t3zetUy!t0Z|1LmY zfp?mfu@g&rr#WX32>l)bqmA1qUqOU(c9E}(=m(fIcH+lfT~Kr(ul0#Xbo(L-At$2O zY+`aRw3js(B{ngjZC%?n$Q_hL=hzt_s74$fn1*rbB!ynO+J#z%rS15)^+Gx?@*&^C z*=LA$k45Z}$_2igYA`M%@RnDv*qQAuBSb=q59zUO33V=P;yuDNQ~dH((-j|9)kHL+ z>bX^pl{nqp->)7GtgiVbP=r-SCa$OIs+l(5Y36L5Emw-oM$%W~ zrmi)jhXK}5UD_E-j;=22eQibdQT4h1@A+e&BHNqVz4|*}rMLd?@#;VS=}&I@`(EVq z9Q+=i!$hC-(arH0d!zHG!aD#gi?oa{^~l)r&_W*>?*iQFcL9RAb#<#+;s4=xe9OQ7 zEnoOwf7&yj`K$;3-$PXpANJw5u71_8{m82y_>Om8Jy!os1Sh&qDV-%gxe8X%Ub|#S zrYuU$i7OMJ;MH>RKo6V~Y@FnU@r7YqV0P(ZBf7C;)h(+U?C2Ua;@zif>0+2=E0V>u zJ(S277~&aXQs= z&!I16*$G!YHzdZocsJjKYi%Hq!O(z>Z1&5yh1n;1Q7u2MC5{|01LWv>OieEP<*2Y_ zUq4!(vP4qcf!K{Sn8uaI1!6ET0A-3yx}*MloK+*fm`5Br@0cTiX|#4HtehNOi%WK8 zGwqAY=Ua0%y~0BjUU5>7`?t6dj)D;CD#Er_u1w|6bq@1BW>sz3gX;mkc+F3jzzZalnb)x71sIOlL)Dv9U zeEFaJAHMRYej%`8sb30w#?^oSmtLg*FTj5in9r-YTvSNoiI(v7I{~%N$)LBlIAQpy z6N}G6oitx^ku_q;KH>3Pt;EvAh7%KU&oyBue*3V@xLhp6iz{vH6pi-F7(-Omm(+O)^Ur^g+({+GFut#C* z3vh8sm%6u_$z{^;J1xsStfB}(`xaqjU|jEo6`}P5 zwT^>>ZB2E+*A1~lMxxLIqaU%<@SbgPVd8?+s2w#gT(dB#1=8IBQOgd3*$fI!a^Gs3 zirW!#+{U!2k=p2*EeKSlz*l?=Eh5&lL1GbsMhL5M{6KRlufAU8`ys*CPJQk#2k7gS ze|^vYAN|^w-q_#!mlL(G_AmbN+uYnscxRaa(mNR6hcc#DCAzOp^Yqa%cd;3{p?lKW zQ{f$ebMh$={r$%1=DPs5>S^uP?WaAB-v@~9R(*E!$la$s?bcg<_zmy=)t~{Ur_&Fz>7ZX7hZkuE8lVT#QSPZaA=c(hBa9yxm5> zoKM0HAIe2@@XW0Ap82xwVKlxlZokO4&fud7sVJT9lbX)+m?}p6R*THD(>!rSX}nBA zJXOfb1S{H(+w;R765@!kY-1Nuw1(~*aXh%znHde13-}@5O&Ch`TpZ=UgG}$Typ355F$jh6CqO5*IxdqiICbk{w;8Q_T0%DAD#Q=g&ep@ ziO^G9#J6X&R!A1Q>{b6ZQjQ|c@!RpJem&jNGQ|H(O#C~R+|=5Ipsq_KJ%d!l$>fL% z`kYJT$DWuhQFt0|S+H7Dr`V&TZ;COPTG!DvYjlNtulSS=ggJ;Z>6qxW`N;TScK+4r z|B{3@3sV)we&eD`1b z^I!2L-&IlE8L$5JAW*OVpBS(H2~6_tckq;UZ#6tLbWfS)WAWsNlQ>eGyfcA38?xN+ zd2q@0XpPxEO#RcJ{^--MuAW{ehh*yOHvpe@_pbEc{P%zOe|_$ApLcWL8CgO6*blz@ z>J`89pItrnGqpZ=9J$N)1$ZL`#yb_K7ric2q)a91*BH^d1QPT(l9oj!Z0*i-$zeiK z`|3Cu(@S{R1#6J4Ho&&I$Zk2K1NQ7-DH#+uZmM0s$Zo&Bz}t|E(-|uQc$&MJCC)B( z(XAUu0P33{L9HT2-Y!B?HZ!&Z)oDV^ilOEFQeR&UC5Z>65#8vc4{eTJ<_h@$V&dZV zCApy`^FJN?RkRS54!74^v6LLY<~_6WKy@-)Az-w1d(>V++>T{-kK>%Yf&x_P|K;C&+-Nd1Yy$HAe9U{r8h zUpyDKdW~k>2v@$v3Omi1%p$pPT!r*`*ri(5BljbQV!0OKS&`x2|A7)*R{Eq_w>+(e%W?NTA;Y9z+Y^AN!Nyzwr;BmFMG zzRTXZbF1D3;I9H6dFxx>^3Ol}WuN~WZawX3^_*}s?K!{b>HSW?Pxzex0_{khM6Cgq z0h8)2BbI#u8bX6wu(9B)iKnFLk+rjXT<<_EdZ6HzOtu!Zcql!Tv2Zn)-wr#D>7x5SaZTsqbwBgqTJFkQ#rfawewjV5)ukx@oHCQ2R&)d9b8a z<2(q=F|v9-yM619|1_}95ih|ypttVUp9a3=N8a$BPy02W^~A^B;70+3upRJQoUOD z9dZjpic$>1D~}k)9*g#Q^vwljgXXxMT<*%IJxZVPil``8RGYrAL7Zt9y6o01VZ|mZ zAP26^;bXUz!XJv>SV7p8MS+xh_25St3=W-?vKW!IP<93q!@XL#gKz{poy={Y*esCYVth>6dEGT8Mf5?4yDl+{Uh2f^Emqrbi)F`CktniRDzF zMy}(JN43Hk%KA&T9J}Qu$b0v}Vp1E^lw+zc5hU(|zYtWfI><2bMt3 z!U158+d%2323?JU`4zraO`Cx!J#Y0ZoZ9K;@Bhfh7K~Byje)-T|Ix3#`B(mAqPPBE zp|}3?i+_CP&rSVy|Kgv<%zNX;&o~bN4&2!>rlmiRrF<&vE*#M(B`o5Y#hP(3&c+RX zZoJLULkzO>M*$!9Vf`@xx;kk-to~Afj{=0Nj{^SGO@9RyX;}Z~LS7J@upbZU2Yx3x6c_&Y$=dZNBfR&VcDoz&L@ z!29d7|M`}G-uj>K2;@P41Zd1@={<8bGk((hr@}h`=OP?ev`!bD|Hz}eH0Ev^xjqW0 zcLDg*KxDqt`WFKAQNX|c*FX9%Zu+AD65|&GfAwcS_v$tO;3uyh`&rh@Y(6=X6dJ~y z9(Vc88Rpf2@_EjMQDV!b@Ek?11+rLvv-qIb(aWrFCG0HCv3#$Vko6a%7&{wbg37$4>zyyC%0sUZ_@}JxoCx$_!>d9x- z0zI$X%g&~E>w=5Yo2sQ(_F#CMKle&(E!!`o?_7PtulR_oKlIfvzVYAoCmlZb|C4|G z_x$mG>tEmZUt(VUbHmT8e=gkI*x7Q^XPiXnlW+~!O&?i~PI~lDnf6qA0MIh3%A{y1 z5^`D!7~HuJX3PB-0eUc~18VvDDBz9ne#SGN`9q)f>7VuM#eRc39s+*t=RdE$E$}^8 zKmMlo_#~-l`W|1Nx#`xvGFS41%0DnA_es?OO74r7Q?eYKfI86tc|eB0Y|n|eZA&H$ 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°_Q~gfk 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

SeW&l--xuTo-S! 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

{xMc|?txZ?DfmQf_VA`w+IZl6@P4$sw2K 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 + + + +---- +- **If you've already downloaded the app you don't need to download it again!** You will automatically get prompted to upgrade to new versions +- If you're installing the app for the first time, download the right file for your operating system and execute it: + - MacOS: `Spectrum-{version}-mac.zip` + - Windows: `Spectrum-{version}.exe` + - Linux: `Spectrum-{version}_amd64.deb` or `Spectrum-{version}-x86_64.AppImage` + +Enjoy! diff --git a/desktop/src/autoUpdate.js b/desktop/src/autoUpdate.js new file mode 100644 index 0000000000..b987545b94 --- /dev/null +++ b/desktop/src/autoUpdate.js @@ -0,0 +1,7 @@ +// @flow +const { app, dialog } = require('electron'); +const { autoUpdater } = require('electron-updater'); + +module.exports = function checkForUpdates() { + return autoUpdater.checkForUpdatesAndNotify(); +}; diff --git a/desktop/src/config.js b/desktop/src/config.js new file mode 100644 index 0000000000..67c6ba2521 --- /dev/null +++ b/desktop/src/config.js @@ -0,0 +1,29 @@ +// @flow +const { app } = require('electron'); +const { resolve } = require('path'); + +/** + * Applications Configuration + **/ + +module.exports = { + APP_NAME: 'Spectrum', + APP_VERSION: app.getVersion(), + APP_REMOTE_URL: 'https://spectrum.chat/login', + APP_DEV_URL: 'http://localhost:3000/login', + APP_REMOTE_HOME_URL: 'https://spectrum.chat', + APP_DEV_HOME_URL: 'http://localhost:3000', + + GITHUB_URL: 'https://github.com/withspectrum/spectrum', + GITHUB_URL_LICENSE: + 'https://github.com/withspectrum/spectrum/blob/alpha/LICENSE', + GITHUB_URL_ISSUES: 'https://github.com/withspectrum/spectrum/issues', + + WINDOW_DEFAULT_HEIGHT: 800, + WINDOW_DEFAULT_WIDTH: 1300, + WINDOW_MIN_HEIGHT: 500, + WINDOW_MIN_WIDTH: 770, + WINDOW_BG_COLOR: '#F5F8FC', + + ICON: resolve(__dirname, '../resources/icons/png/icon-512x512.png'), +}; diff --git a/desktop/src/main.js b/desktop/src/main.js new file mode 100644 index 0000000000..13ea760858 --- /dev/null +++ b/desktop/src/main.js @@ -0,0 +1,108 @@ +// @flow +const electron = require('electron'); +const windowStateKeeper = require('electron-window-state'); +const { app, BrowserWindow, shell } = electron; +const isDev = require('electron-is-dev'); +const contextMenu = require('electron-context-menu'); + +const FIFTEEN_MINUTES = 900000; + +const checkForUpdates = require('./autoUpdate'); +const buildMenu = require('./menu'); +const CONFIG = require('./config'); + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +// eslint-disable-next-line +let win; +let mainWindow; + +const startUrl = isDev ? CONFIG.APP_DEV_URL : CONFIG.APP_REMOTE_URL; + +function createWindow() { + if (!isDev) { + // Check for updates on startup and then every 15 minutes + checkForUpdates(); + setInterval(() => { + checkForUpdates(); + }, FIFTEEN_MINUTES); + } + + let mainWindowState = windowStateKeeper({ + defaultWidth: CONFIG.WINDOW_DEFAULT_WIDTH, + defaultHeight: CONFIG.WINDOW_DEFAULT_HEIGHT, + }); + + const { width, height, x, y } = mainWindowState; + + // Create the main browser window. + mainWindow = new BrowserWindow({ + width, + height, + x, + y, + titleBarStyle: 'hiddenInset', + minHeight: CONFIG.WINDOW_MIN_HEIGHT, + minWidth: CONFIG.WINDOW_MIN_WIDTH, + backgroundColor: CONFIG.WINDOW_BG_COLOR, + /** + * Disable Electron's Node integration to prevent untrusted client + * code from having access to the process and file system: + * - https://github.com/atom/electron/issues/254 + * - https://github.com/atom/electron/issues/1753 + */ + webPreferences: { + nodeIntegration: false, + preload: __dirname + '/preload.js', + }, + show: false, + }); + + // Load Remote Url + mainWindow.loadURL(startUrl); + + // Build application menu + buildMenu(); + + mainWindow.on('closed', () => { + win = null; + mainWindow = null; + }); + + // if main window is ready to show, show up the main window + mainWindow.once('ready-to-show', () => { + mainWindow && mainWindow.show(); + }); + + contextMenu(); + mainWindowState.manage(mainWindow); + + mainWindow.webContents.on('new-window', (event, url) => { + event.preventDefault(); + shell.openExternal(url); + }); +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', createWindow); + +// Quit when all windows are closed. +app.on('window-all-closed', () => { + // On OS X it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +app.on('activate', () => { + // On OS X it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow !== null) mainWindow.show(); + + if (win === null && mainWindow === null) { + createWindow(); + } +}); diff --git a/desktop/src/menu.js b/desktop/src/menu.js new file mode 100644 index 0000000000..5170eee8a3 --- /dev/null +++ b/desktop/src/menu.js @@ -0,0 +1,203 @@ +// @flow +const { dialog, Menu, MenuItem, shell, clipboard } = require('electron'); +const checkForUpdates = require('./autoUpdate'); +const isDev = require('electron-is-dev'); + +const CONFIG = require('./config'); + +const UpdateMenuItem = new MenuItem({ + label: 'Check for updates', + click() { + this.enabled = false; + checkForUpdates().then(() => { + this.enabled = true; + }); + }, +}); + +/** + * Applications menu + **/ + +const template = [ + { + label: CONFIG.APP_NAME, + submenu: [ + { + label: `About ${CONFIG.APP_NAME}`, + click() { + showAbout(); + }, + }, + { type: 'separator' }, + { + label: 'License', + click() { + shell.openExternal(CONFIG.GITHUB_URL_LICENSE); + }, + }, + UpdateMenuItem, + { type: 'separator' }, + { role: 'hide' }, + { role: 'quit' }, + ], + }, + { + label: 'Go', + submenu: [ + { + label: 'Home', + accelerator: 'CmdOrCtrl+Shift+H', + click: function(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.webContents.loadURL( + isDev ? CONFIG.APP_DEV_HOME_URL : CONFIG.APP_REMOTE_HOME_URL + ); + } + }, + }, + { + label: 'Forward', + accelerator: 'CmdOrCtrl+]', + click: function(item, focusedWindow) { + if (focusedWindow) { + const wc = focusedWindow.webContents; + if (wc && wc.canGoForward()) wc.goForward(); + } + }, + }, + { + label: 'Back', + accelerator: 'CmdOrCtrl+[', + click: function(item, focusedWindow) { + if (focusedWindow) { + const wc = focusedWindow.webContents; + if (wc && wc.canGoBack()) wc.goBack(); + } + }, + }, + ], + }, + { + label: 'Edit', + submenu: [ + { role: 'undo' }, + { role: 'redo' }, + { type: 'separator' }, + { role: 'cut' }, + { role: 'copy' }, + { role: 'paste' }, + { role: 'selectall' }, + ], + }, + { + label: 'View', + submenu: [ + { + label: 'Toggle Full Screen', + accelerator: (function() { + if (process.platform === 'darwin') { + return 'Ctrl+Command+F'; + } else { + return 'F11'; + } + })(), + click: function(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); + } + }, + }, + { + label: 'Toggle Developer Tools', + accelerator: (function() { + if (process.platform === 'darwin') { + return 'Alt+Command+I'; + } else { + return 'Ctrl+Shift+I'; + } + })(), + click: function(item, focusedWindow) { + if (focusedWindow) { + focusedWindow.toggleDevTools(); + } + }, + }, + ], + }, + { + label: 'Window', + role: 'window', + submenu: [ + { + label: 'Minimize', + accelerator: 'CmdOrCtrl+M', + role: 'minimize', + }, + { + label: 'Reload', + accelerator: 'CmdOrCtrl+R', + role: 'reload', + }, + { + label: 'Force Reload', + accelerator: 'CmdOrCtrl+Shift+R', + role: 'forceReload', + }, + { + label: 'Close', + accelerator: 'CmdOrCtrl+W', + role: 'close', + }, + ], + }, + { + label: 'Help', + submenu: [ + { + label: 'Learn More', + click() { + shell.openExternal(CONFIG.GITHUB_URL); + }, + }, + { + label: 'Contribute on GitHub', + click() { + shell.openExternal(CONFIG.GITHUB_URL_ISSUES); + }, + }, + ], + }, + { + label: 'Share', + submenu: [ + { + label: 'Copy link to current page', + click: function(item, focusedWindow) { + const url = focusedWindow.webContents.getURL(); + if (url) { + clipboard.writeText(url); + } + }, + accelerator: 'CmdOrCtrl+S', + }, + ], + }, +]; + +function showAbout() { + dialog.showMessageBox({ + title: `About ${CONFIG.APP_NAME}`, + message: `${CONFIG.APP_NAME} ${CONFIG.APP_VERSION}`, + detail: `The community platform for the future.`, + buttons: [], + icon: CONFIG.ICON, + }); +} + +function createMenu() { + const menu = Menu.buildFromTemplate(template); + return Menu.setApplicationMenu(menu); +} + +module.exports = createMenu; diff --git a/desktop/src/preload.js b/desktop/src/preload.js new file mode 100644 index 0000000000..e4df39be17 --- /dev/null +++ b/desktop/src/preload.js @@ -0,0 +1,33 @@ +// @flow +/* + * Preload script + * + * in preload scripts, we have access to node.js and electron APIs + * the remote web app will not have access, so this is safe + */ +const { remote } = require('electron'); + +init(); + +function init() { + // Expose a bridging API to by setting an global on `window`. + // We'll add methods to it here first, and when the remote web app loads, + // it'll add some additional methods as well. + // + // !CAREFUL! do not expose any functionality or APIs that could compromise the + // user's computer. E.g. don't directly expose core Electron (even IPC) or node.js modules. + window.interop = { + setBadgeCount: setBadgeCount, + isFocused: isFocused, + }; +} + +function isFocused() { + return remote.getCurrentWindow().isFocused(); +} + +function setBadgeCount(count) { + if (process.platform === 'darwin') { + remote.app.setBadgeCount(count); + } +} diff --git a/desktop/yarn.lock b/desktop/yarn.lock new file mode 100644 index 0000000000..50ee0c1423 --- /dev/null +++ b/desktop/yarn.lock @@ -0,0 +1,3255 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"7zip-bin@~4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-4.1.0.tgz#33eff662a5c39c0c2061170cc003c5120743fff0" + integrity sha512-AsnBZN3a8/JcNt+KPkGGODaA4c7l3W5+WpeKgGSbstSLxqWtTXqd1ieJGBQ8IFCtRg8DmmKUcSkIkUc0A4p3YA== + +"@types/node@^8.0.24": + version "8.10.38" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.38.tgz#e05c201a668492e534b48102aca0294898f449f6" + integrity sha512-EibsnbJerd0hBFaDjJStFrVbVBAtOy4dgL8zZFw0uOvPqzBAX59Ci8cgjg3+RgJIWhsB5A4c+pi+D4P9tQQh/A== + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +ajv-keywords@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" + integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= + +ajv@^6.5.5: + version "6.5.5" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.5.tgz#cf97cdade71c6399a92c6d6c4177381291b781a1" + integrity sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.7.0.tgz#e3ce7bb372d6577bb1839f1dfdfcbf5ad2948d96" + integrity sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-align@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" + integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= + dependencies: + string-width "^2.0.0" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +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" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +app-builder-bin@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-2.6.3.tgz#428557e8fd517ef6272b3d85593ebb288c2aed90" + integrity sha512-JL8C41e6yGIchFsHP/q15aGNedAaUakLhkV6ER0Yxafx08sRnlDnlkAkEIKjX7edg/4i7swpGa6CBv1zX9GgCA== + +app-builder-lib@20.38.5, app-builder-lib@~20.38.5: + version "20.38.5" + resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-20.38.5.tgz#bdfbbc35e10571c6cf1f62daae95991d27686a03" + integrity sha512-vVgM9d9twwlhr+8vNAJOAD9dyVBRk7reuVa1BE1OmvaHb1M+fS8KpvcDKVdBqX9KDHy7zSc57mnIcHgax4/XMA== + dependencies: + "7zip-bin" "~4.1.0" + app-builder-bin "2.6.3" + async-exit-hook "^2.0.1" + bluebird-lst "^1.0.6" + builder-util "9.6.2" + builder-util-runtime "8.1.1" + chromium-pickle-js "^0.2.0" + debug "^4.1.1" + ejs "^2.6.1" + electron-osx-sign "0.4.11" + electron-publish "20.38.5" + fs-extra-p "^7.0.0" + hosted-git-info "^2.7.1" + is-ci "^2.0.0" + isbinaryfile "^4.0.0" + js-yaml "^3.12.1" + lazy-val "^1.0.3" + minimatch "^3.0.4" + normalize-package-data "^2.4.0" + plist "^3.0.1" + read-config-file "3.2.1" + sanitize-filename "^1.6.1" + semver "^5.6.0" + temp-file "^3.3.2" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +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" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + 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= + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + integrity sha1-GdOGodntxufByF04iu28xW0zYC0= + +async-exit-hook@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3" + integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +atob@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +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.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + +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= + +base64-js@^1.2.3: + version "1.3.0" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" + integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.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" + +binary-extensions@^1.0.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" + integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg== + +bluebird-lst@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/bluebird-lst/-/bluebird-lst-1.0.6.tgz#89bc4de0a357373605c8781f293f7b06d454f869" + integrity sha512-CBWFoPuUPpcvMUxfyr8DKdI5d4kjxFl1h39+VbKxP3KJWJHEsLtuT4pPLkjpxCGU6Ask21tvbnftWXdqIxYldQ== + dependencies: + bluebird "^3.5.2" + +bluebird@^3.5.0, bluebird@^3.5.2: + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + +boxen@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" + integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== + dependencies: + ansi-align "^2.0.0" + camelcase "^4.0.0" + chalk "^2.0.1" + cli-boxes "^1.0.0" + string-width "^2.0.0" + term-size "^1.2.0" + widest-line "^2.0.0" + +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" + +braces@^2.3.0, braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== + +buffer-alloc@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + +buffer-fill@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= + +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== + +builder-util-runtime@8.1.1, builder-util-runtime@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.1.1.tgz#f2f6fc43e33d26892bd491667fc746ad69bccc50" + integrity sha512-+ieS4PMB33vVE2S3ZNWBEQJ1zKmAs/agrBdh7XadE1lKLjrH4aXYuOh9OOGdxqIRldhlhNBaF+yKMMEFOdNVig== + dependencies: + bluebird-lst "^1.0.6" + debug "^4.1.1" + fs-extra-p "^7.0.0" + sax "^1.2.4" + +builder-util-runtime@~8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.1.0.tgz#dd7fca995d48ceee7580b4851ca057566c94601e" + integrity sha512-s1mlJ28mv+56Iebh6c9aXjVe11O3Z0cDTwAGeB0PCcUzHA37fDxGgS8ZGoYNMZP+rBHj21d/od1wuYofTVLaQg== + dependencies: + bluebird-lst "^1.0.6" + debug "^4.1.0" + fs-extra-p "^7.0.0" + sax "^1.2.4" + +builder-util@9.6.2, builder-util@~9.6.2: + version "9.6.2" + resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-9.6.2.tgz#3366aefea1b5ce292840be727a094e96fa25802f" + integrity sha512-cWl/0/Q851lesMmXp1IjreeAX1QAWA9e+iU2IT61oh+CvMYJnDwao2m9ZCHammdw2zllrwWu4fOC3gvsb/yOCw== + dependencies: + "7zip-bin" "~4.1.0" + app-builder-bin "2.6.3" + bluebird-lst "^1.0.6" + builder-util-runtime "^8.1.1" + chalk "^2.4.2" + debug "^4.1.1" + fs-extra-p "^7.0.0" + is-ci "^2.0.0" + js-yaml "^3.12.1" + source-map-support "^0.5.10" + stat-mode "^0.2.2" + temp-file "^3.3.2" + +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= + +camelcase@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= + +camelcase@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42" + integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA== + +capture-stack-trace@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" + integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^2.0.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" + integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.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" + +chokidar@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" + integrity sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.0" + braces "^2.3.0" + glob-parent "^3.1.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + lodash.debounce "^4.0.8" + normalize-path "^2.1.1" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + upath "^1.0.5" + optionalDependencies: + fsevents "^1.2.2" + +chownr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" + integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== + +chromium-pickle-js@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205" + integrity sha1-BKEGZywYsIWrd02YPfo+oTjyIgU= + +ci-info@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" + integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cli-boxes@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" + integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= + +cliui@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" + integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== + dependencies: + string-width "^2.1.1" + strip-ansi "^4.0.0" + wrap-ansi "^2.0.0" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.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@~0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd" + integrity sha1-vbbGnOZg+t/+CwAHzER+G59ygr0= + +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= + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" + integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== + dependencies: + delayed-stream "~1.0.0" + +compare-version@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/compare-version/-/compare-version-0.1.2.tgz#0162ec2d9351f5ddd59a9202cba935366a725080" + integrity sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA= + +component-emitter@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= + +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= + +concat-stream@1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +configstore@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" + integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw== + dependencies: + dot-prop "^4.1.0" + graceful-fs "^4.1.2" + make-dir "^1.0.0" + unique-string "^1.0.0" + write-file-atomic "^2.0.0" + xdg-basedir "^3.0.0" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +create-error-class@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" + integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= + dependencies: + capture-stack-trace "^1.0.0" + +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^6.0.0: + 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" + +crypto-random-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" + integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= + +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= + dependencies: + array-find-index "^1.0.1" + +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" + +debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.0.0, debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" + integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg== + dependencies: + ms "^2.1.1" + +debug@^4.1.1: + 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" + +decamelize@^1.1.2, decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +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= + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +dmg-builder@6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-6.5.4.tgz#18c573a5e777cbb39d84d7eaa84d965e1bb5b01f" + integrity sha512-EaEkF8weXez3iAwgYffjcYfumauUh5x+BggMgn/IuihNIA5/WfzRAUR4wMq9aII2zwArlw+rIrX6ZHKbmtkQmA== + dependencies: + app-builder-lib "~20.38.5" + bluebird-lst "^1.0.6" + builder-util "~9.6.2" + fs-extra-p "^7.0.0" + iconv-lite "^0.4.24" + js-yaml "^3.12.1" + parse-color "^1.0.0" + sanitize-filename "^1.6.1" + +dot-prop@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" + integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== + dependencies: + is-obj "^1.0.0" + +dotenv-expand@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-4.2.0.tgz#def1f1ca5d6059d24a766e587942c21106ce1275" + integrity sha1-3vHxyl1gWdJKdm5YeULCEQbOEnU= + +dotenv@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" + integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w== + +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + +duplexer@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" + integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= + +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" + +ejs@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" + integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ== + +electron-builder@^20.38.5: + version "20.38.5" + resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-20.38.5.tgz#31b3913a68b4911afd4cfc7bcd2522c5808040cd" + integrity sha512-p88IDHhH2J4hA6KwRBJY+OfVZuFtFIShY3Uh/TwYAfbX0v1RhKZytuGdO8sty2zcWxDYX74xDBv+X9oN6qEIRQ== + dependencies: + app-builder-lib "20.38.5" + bluebird-lst "^1.0.6" + builder-util "9.6.2" + builder-util-runtime "8.1.1" + chalk "^2.4.2" + dmg-builder "6.5.4" + fs-extra-p "^7.0.0" + is-ci "^2.0.0" + lazy-val "^1.0.3" + read-config-file "3.2.1" + sanitize-filename "^1.6.1" + update-notifier "^2.5.0" + yargs "^12.0.5" + +electron-context-menu@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/electron-context-menu/-/electron-context-menu-0.10.1.tgz#9e202f1f65c0972cca8c0ef2047a13a100770368" + integrity sha512-KFkKwFbT6iJUgarEknYuXQlJAT+naJZtSWFBtHf9RiSb70wscWdDNpYoUERzF7FgqYE1Mil4npfRWsjqGLwtog== + dependencies: + electron-dl "^1.2.0" + electron-is-dev "^1.0.1" + +electron-dl@^1.2.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/electron-dl/-/electron-dl-1.12.0.tgz#328c7f12d3e458ed4ddc773d8ffc28d59ab35d2e" + integrity sha512-UMc2CL45Ybpvu66LDPYzwmDRmYK4Ivz+wdnTM0eXcNMztvQwhixAk2UPme1c7McqG8bAlKEkQpZn3epmQy4EWg== + dependencies: + ext-name "^5.0.0" + pupa "^1.0.0" + unused-filename "^1.0.0" + +electron-download@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8" + integrity sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg== + dependencies: + debug "^3.0.0" + env-paths "^1.0.0" + fs-extra "^4.0.1" + minimist "^1.2.0" + nugget "^2.0.1" + path-exists "^3.0.0" + rc "^1.2.1" + semver "^5.4.1" + sumchecker "^2.0.2" + +electron-is-dev@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-1.0.1.tgz#6e0a184736fe7aea77d18210b0b0f6a02402c4bc" + integrity sha512-iwM3EotA9HTXqMGpQRkR/kT8OZqBbdfHTnlwcxsjSLYqY8svvsq0MuujsWCn3/vtgRmDv/PC/gKUUpoZvi5C1w== + +electron-log@^2.2.17: + version "2.2.17" + resolved "https://registry.yarnpkg.com/electron-log/-/electron-log-2.2.17.tgz#e71e2ebb949fc96ded7cdb99eeee7202e48981d2" + integrity sha512-v+Af5W5z99ehhaLOfE9eTSXUwjzh2wFlQjz51dvkZ6ZIrET6OB/zAZPvsuwT6tm3t5x+M1r+Ed3U3xtPZYAyuQ== + +electron-osx-sign@0.4.11: + version "0.4.11" + resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.4.11.tgz#8377732fe7b207969f264b67582ee47029ce092f" + integrity sha512-VVd40nrnVqymvFrY9ZkOYgHJOvexHHYTR3di/SN+mjJ0OWhR1I8BRVj3U+Yamw6hnkZZNKZp52rqL5EFAAPFkQ== + dependencies: + bluebird "^3.5.0" + compare-version "^0.1.2" + debug "^2.6.8" + isbinaryfile "^3.0.2" + minimist "^1.2.0" + plist "^3.0.1" + +electron-publish@20.38.5: + version "20.38.5" + resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-20.38.5.tgz#c6ed7ea12bc80796b1f36489995f4651f730b1df" + integrity sha512-EhdPm6t0nKDfa0r3KjV1kSFcz03VrzgJRv7v5nHkkpQZB6OSmDNlHq7k66NBwQhPK3i4CK+uvehljZAP28vbCA== + dependencies: + bluebird-lst "^1.0.6" + builder-util "~9.6.2" + builder-util-runtime "^8.1.1" + chalk "^2.4.2" + fs-extra-p "^7.0.0" + lazy-val "^1.0.3" + mime "^2.4.0" + +electron-updater@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.0.6.tgz#9c4f495ae0e80bf4425e3e1b801c5ed2ab933c2d" + integrity sha512-JPGLME6fxJcHG8hX7HWFl6Aew6iVm0DkcrENreKa5SUJCHG+uUaAhxDGDt+YGcNkyx1uJ6eBGMvFxDTLUv67pg== + dependencies: + bluebird-lst "^1.0.6" + builder-util-runtime "~8.1.0" + fs-extra-p "^7.0.0" + js-yaml "^3.12.0" + lazy-val "^1.0.3" + lodash.isequal "^4.5.0" + pako "^1.0.7" + semver "^5.6.0" + source-map-support "^0.5.9" + +electron-window-state@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/electron-window-state/-/electron-window-state-5.0.3.tgz#4f36d09e3f953d87aff103bf010f460056050aa8" + integrity sha512-1mNTwCfkolXl3kMf50yW3vE2lZj0y92P/HYWFBrb+v2S/pCka5mdwN3cagKm458A7NjndSwijynXgcLWRodsVg== + dependencies: + jsonfile "^4.0.0" + mkdirp "^0.5.1" + +electron@^3.0.13: + version "3.0.13" + resolved "https://registry.yarnpkg.com/electron/-/electron-3.0.13.tgz#7b065a3d130c6b6379dc78d49515e03f392c1303" + integrity sha512-tfx5jFgXhCmpe6oPjcesaRj7geHqQxrJdbpseanRzL9BbyYUtsj0HoxwPAUvCx4+52P6XryBwWTvne/1eBVf9Q== + dependencies: + "@types/node" "^8.0.24" + electron-download "^4.1.0" + extract-zip "^1.0.3" + +env-paths@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" + integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA= + +error-ex@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +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= + +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== + +event-stream@3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" + integrity sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE= + dependencies: + duplexer "~0.1.1" + from "~0" + map-stream "~0.1.0" + pause-stream "0.0.11" + split "0.3" + stream-combiner "~0.0.4" + through "~2.3.1" + +execa@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" + integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw== + dependencies: + cross-spawn "^6.0.0" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +ext-list@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" + integrity sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA== + dependencies: + mime-db "^1.28.0" + +ext-name@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ext-name/-/ext-name-5.0.0.tgz#70781981d183ee15d13993c8822045c506c8f0a6" + integrity sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ== + dependencies: + ext-list "^2.0.0" + sort-keys-length "^1.0.0" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +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== + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extract-zip@^1.0.3: + version "1.6.7" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" + integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= + dependencies: + concat-stream "1.6.2" + debug "2.6.9" + mkdirp "0.5.1" + yauzl "2.4.1" + +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.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + +fast-json-stable-stringify@^2.0.0: + version "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= + +fd-slicer@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" + integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU= + dependencies: + pend "~1.2.0" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.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" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +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" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +from@~0: + version "0.1.7" + resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" + integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4= + +fs-extra-p@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/fs-extra-p/-/fs-extra-p-7.0.0.tgz#da9a72df71dc77fb938162025a5fc658713c98ab" + integrity sha512-5tg5jBOd0xIXjwj4PDnafOXL5TyPVzjxLby4DPKev53wurEXp7IsojBaD4Lj5M5w7jxw0pbkEU0fFEPmcKoMnA== + dependencies: + bluebird-lst "^1.0.6" + fs-extra "^7.0.0" + +fs-extra@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" + integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== + dependencies: + minipass "^2.2.1" + +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= + +fsevents@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" + integrity sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg== + dependencies: + nan "^2.9.2" + node-pre-gyp "^0.10.0" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== + +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +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" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob@^7.0.5, glob@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + 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" + +global-dirs@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" + integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= + dependencies: + ini "^1.3.4" + +got@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" + integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= + dependencies: + create-error-class "^3.0.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + is-redirect "^1.0.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + lowercase-keys "^1.0.0" + safe-buffer "^5.0.1" + timed-out "^4.0.0" + unzip-response "^2.0.1" + url-parse-lax "^1.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== + +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.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +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-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" + integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== + +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" + +iconv-lite@^0.4.24, iconv-lite@^0.4.4: + 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-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= + +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== + dependencies: + minimatch "^3.0.4" + +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= + +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@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= + dependencies: + repeating "^2.0.0" + +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.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@^1.3.4, ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +invert-kv@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" + integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74= + dependencies: + builtin-modules "^1.0.0" + +is-ci@^1.0.10: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" + integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== + dependencies: + ci-info "^1.5.0" + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, 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-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +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-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= + dependencies: + is-extglob "^2.1.1" + +is-installed-globally@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" + integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= + dependencies: + global-dirs "^0.1.0" + is-path-inside "^1.0.0" + +is-npm@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" + integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= + +is-path-inside@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" + integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= + dependencies: + path-is-inside "^1.0.1" + +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + +is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-redirect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" + integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= + +is-retry-allowed@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" + integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ= + +is-stream@^1.0.0, is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +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= + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isbinaryfile@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.3.tgz#5d6def3edebf6e8ca8cae9c30183a804b5f8be80" + integrity sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw== + dependencies: + buffer-alloc "^1.2.0" + +isbinaryfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.0.tgz#07d1061c21598b41292b0f5c68add5eab601ad8e" + integrity sha512-RBtmso6l2mCaEsUvXngMTIjg3oheXo0MgYzzfT6sk44RYggPnm9fT+cQJAmzRnJIxPHXg9FZglqDJGW28dvcqA== + +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= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +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= + +js-yaml@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" + integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^3.12.1: + version "3.12.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600" + integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +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.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +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= + +json5@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850" + integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ== + dependencies: + minimist "^1.2.0" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + +latest-version@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" + integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= + dependencies: + package-json "^4.0.0" + +lazy-val@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.3.tgz#bb97b200ef00801d94c317e29dc6ed39e31c5edc" + integrity sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg== + +lcid@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" + integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== + dependencies: + invert-kv "^2.0.0" + +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + +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.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + +lowercase-keys@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lru-cache@^4.0.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" + integrity sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +make-dir@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" + integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== + dependencies: + pify "^3.0.0" + +map-age-cleaner@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + +map-stream@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" + integrity sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +mem@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-4.0.0.tgz#6437690d9471678f6cc83659c00cbafcd6b0cdaf" + integrity sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA== + dependencies: + map-age-cleaner "^0.1.1" + mimic-fn "^1.0.0" + p-is-promise "^1.1.0" + +meow@^3.1.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + +micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +mime-db@^1.28.0, mime-db@~1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" + integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.21" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" + integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== + dependencies: + mime-db "~1.37.0" + +mime@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.0.tgz#e051fd881358585f3279df333fe694da0bcffdd6" + integrity sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +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@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +minipass@^2.2.1, minipass@^2.3.4: + version "2.3.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" + integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.1.tgz#6734acc045a46e61d596a43bb9d9cd326e19cc42" + integrity sha512-TrfjCjk4jLhcJyGMYymBH6oTXcWjYbUAXTHDbtnWHjZC25h0cdajHuPE1zxb4DVmu8crfh+HwH/WMuyLG0nHBg== + dependencies: + minipass "^2.2.1" + +mixin-deep@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" + integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +modify-filename@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/modify-filename/-/modify-filename-1.1.0.tgz#9a2dec83806fbb2d975f22beec859ca26b393aa1" + integrity sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE= + +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.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +nan@^2.9.2: + version "2.11.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766" + integrity sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +needle@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" + integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" + +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-pre-gyp@^0.10.0: + version "0.10.3" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" + integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +nodemon@^1.18.9: + version "1.18.9" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.9.tgz#90b467efd3b3c81b9453380aeb2a2cba535d0ead" + integrity sha512-oj/eEVTEI47pzYAjGkpcNw0xYwTl4XSTUQv2NPQI6PpN3b75PhpuYk3Vb3U80xHCyM2Jm+1j68ULHXl4OR3Afw== + dependencies: + chokidar "^2.0.4" + debug "^3.1.0" + ignore-by-default "^1.0.1" + minimatch "^3.0.4" + pstree.remy "^1.1.6" + semver "^5.5.0" + supports-color "^5.2.0" + touch "^3.1.0" + undefsafe "^2.0.2" + update-notifier "^2.5.0" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= + dependencies: + abbrev "1" + osenv "^0.1.4" + +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= + dependencies: + abbrev "1" + +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw== + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +npm-bundled@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" + integrity sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g== + +npm-packlist@^1.1.6: + version "1.1.12" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a" + integrity sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +nugget@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0" + integrity sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA= + dependencies: + debug "^2.1.3" + minimist "^1.1.0" + pretty-bytes "^1.0.2" + progress-stream "^1.1.0" + request "^2.45.0" + single-line-log "^1.1.2" + throttleit "0.0.2" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +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-assign@^4.0.1, object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-keys@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" + integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY= + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.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" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-locale@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.0.1.tgz#3b014fbf01d87f60a1e5348d80fe870dc82c4620" + integrity sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw== + dependencies: + execa "^0.10.0" + lcid "^2.0.0" + mem "^4.0.0" + +os-tmpdir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-is-promise@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" + integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= + +p-limit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.0.0.tgz#e624ed54ee8c460a778b3c9f3670496ff8a57aec" + integrity sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A== + 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: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" + integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ== + +package-json@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" + integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= + dependencies: + got "^6.7.1" + registry-auth-token "^3.0.1" + registry-url "^3.0.3" + semver "^5.1.0" + +pako@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.7.tgz#2473439021b57f1516c82f58be7275ad8ef1bb27" + integrity sha512-3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ== + +parse-color@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-color/-/parse-color-1.0.0.tgz#7b748b95a83f03f16a94f535e52d7f3d94658619" + integrity sha1-e3SLlag/A/FqlPU15S1/PZRlhhk= + dependencies: + color-convert "~0.5.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= + dependencies: + pinkie-promise "^2.0.0" + +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-is-inside@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + +path-key@^2.0.0, 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-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +pause-stream@0.0.11: + version "0.0.11" + resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" + integrity sha1-/lo0sMvOErWqaitAPuLnO2AvFEU= + dependencies: + through "~2.3" + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + +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= + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + +plist@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.1.tgz#a9b931d17c304e8912ef0ba3bdd6182baf2e1f8c" + integrity sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ== + dependencies: + base64-js "^1.2.3" + xmlbuilder "^9.0.7" + xmldom "0.1.x" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +prepend-http@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= + +pretty-bytes@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84" + integrity sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ= + dependencies: + get-stdin "^4.0.1" + meow "^3.1.0" + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + +progress-stream@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77" + integrity sha1-LNPP6jO6OonJwSHsM0er6asSX3c= + dependencies: + speedometer "~0.1.2" + through2 "~0.2.3" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + +psl@^1.1.24: + version "1.1.29" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" + integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ== + +pstree.remy@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.6.tgz#73a55aad9e2d95814927131fbf4dc1b62d259f47" + integrity sha512-NdF35+QsqD7EgNEI5mkI/X+UwaxVEbQaz9f4IooEmMUv6ZPmlTQYGjBPJGgrlzNdjSvIy4MWMg6Q6vCgBO2K+w== + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +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== + +pupa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-1.0.0.tgz#9a9568a5af7e657b8462a6e9d5328743560ceff6" + integrity sha1-mpVopa9+ZXuEYqbp1TKHQ1YM7/Y= + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +rc@^1.0.1, rc@^1.1.6, rc@^1.2.1, rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read-config-file@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/read-config-file/-/read-config-file-3.2.1.tgz#112dc8636121fa71fd524e1a8a5b4470ef7a2732" + integrity sha512-yW4hZZXdNN+Paij5JVAiTv1lUsAN5QRBU5NqotQqwYdVkUczSmDzm66VLu0eojiZt2zFeYptTFDAYlalDGuHdA== + dependencies: + ajv "^6.7.0" + ajv-keywords "^3.2.0" + bluebird-lst "^1.0.6" + dotenv "^6.2.0" + dotenv-expand "^4.2.0" + fs-extra-p "^7.0.0" + js-yaml "^3.12.1" + json5 "^2.1.0" + lazy-val "^1.0.3" + +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.2: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@~1.1.9: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readdirp@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +registry-auth-token@^3.0.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" + integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ== + dependencies: + rc "^1.1.6" + safe-buffer "^5.0.1" + +registry-url@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" + integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= + dependencies: + rc "^1.0.1" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= + dependencies: + is-finite "^1.0.0" + +request@^2.45.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + 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.0" + 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.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rimraf@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== + dependencies: + glob "^7.0.5" + +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" + +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.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== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", 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== + +sanitize-filename@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.1.tgz#612da1c96473fa02dccda92dcd5b4ab164a6772a" + integrity sha1-YS2hyWRz+gLczaktzVtKsWSmdyo= + dependencies: + truncate-utf8-bytes "^1.0.0" + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +semver-diff@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" + integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= + dependencies: + semver "^5.0.3" + +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" + integrity sha1-fbCPnT0i3H945Trzw79GZuzfzPE= + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.1" + to-object-path "^0.3.0" + +set-value@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" + integrity sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +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.0, signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +single-line-log@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364" + integrity sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q= + dependencies: + string-width "^1.0.1" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +sort-keys-length@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" + integrity sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg= + dependencies: + sort-keys "^1.0.0" + +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= + dependencies: + is-plain-obj "^1.0.0" + +source-map-resolve@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== + dependencies: + atob "^2.1.1" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.5.10: + version "0.5.10" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" + integrity sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@^0.5.9: + version "0.5.9" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" + integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.5.6: + 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== + +spdx-correct@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.2.tgz#19bb409e91b47b1ad54159243f7312a858db3c2e" + integrity sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz#a59efc09784c2a5bada13cfeaf5c75dd214044d2" + integrity sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg== + +speedometer@~0.1.2: + version "0.1.4" + resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" + integrity sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0= + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +split@0.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" + integrity sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8= + dependencies: + through "2" + +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= + +sshpk@^1.7.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.2.tgz#c946d6bd9b1a39d0e8635763f5242d6ed6dcb629" + integrity sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA== + 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" + +stat-mode@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-0.2.2.tgz#e6c80b623123d7d80cf132ce538f346289072502" + integrity sha1-5sgLYjEj19gM8TLOU480YokHJQI= + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +stream-combiner@~0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" + integrity sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ= + dependencies: + duplexer "~0.1.1" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= + dependencies: + is-utf8 "^0.2.0" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= + dependencies: + get-stdin "^4.0.1" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +sumchecker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e" + integrity sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4= + dependencies: + debug "^2.2.0" + +supports-color@^5.2.0, 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" + +tar@^4: + version "4.4.8" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" + integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ== + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.3.4" + minizlib "^1.1.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.2" + +temp-file@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/temp-file/-/temp-file-3.3.2.tgz#69b6daf1bbe23231d0a5d03844e3d96f3f531aaa" + integrity sha512-FGKccAW0Mux9hC/2bdUIe4bJRv4OyVo4RpVcuplFird1V/YoplIFbnPZjfzbJSf/qNvRZIRB9/4n/RkI0GziuQ== + dependencies: + async-exit-hook "^2.0.1" + bluebird-lst "^1.0.6" + fs-extra-p "^7.0.0" + +term-size@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" + integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= + dependencies: + execa "^0.7.0" + +throttleit@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" + integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8= + +through2@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f" + integrity sha1-6zKE2k6jEbbMis42U3SKUqvyWj8= + dependencies: + readable-stream "~1.1.9" + xtend "~2.1.1" + +through@2, through@~2.3, through@~2.3.1: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +timed-out@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= + +truncate-utf8-bytes@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" + integrity sha1-QFkjkJWS1W94pYGENLC3hInKXys= + dependencies: + utf8-byte-length "^1.0.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= + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +undefsafe@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" + integrity sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY= + dependencies: + debug "^2.2.0" + +union-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" + integrity sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ= + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^0.4.3" + +unique-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" + integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= + dependencies: + crypto-random-string "^1.0.0" + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +unused-filename@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unused-filename/-/unused-filename-1.0.0.tgz#d340880f71ae2115ebaa1325bef05cc6684469c6" + integrity sha1-00CID3GuIRXrqhMlvvBcxmhEacY= + dependencies: + modify-filename "^1.1.0" + path-exists "^3.0.0" + +unzip-response@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" + integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= + +upath@^1.0.5: + version "1.1.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" + integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw== + +update-notifier@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" + integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== + dependencies: + boxen "^1.2.1" + chalk "^2.0.1" + configstore "^3.0.0" + import-lazy "^2.1.0" + is-ci "^1.0.10" + is-installed-globally "^0.1.0" + is-npm "^1.0.0" + latest-version "^3.0.0" + semver-diff "^2.0.0" + xdg-basedir "^3.0.0" + +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" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +url-parse-lax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= + dependencies: + prepend-http "^1.0.1" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +utf8-byte-length@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" + integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E= + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +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" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +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" + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +widest-line@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" + integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== + dependencies: + string-width "^2.1.1" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab" + integrity sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA== + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +xdg-basedir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" + integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= + +xmlbuilder@^9.0.7: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= + +xmldom@0.1.x: + version "0.1.27" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" + integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk= + +xtend@~2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b" + integrity sha1-bv7MKk2tjmlixJAbM3znuoe10os= + dependencies: + object-keys "~0.4.0" + +"y18n@^3.2.1 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + +yallist@^3.0.0, yallist@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" + integrity sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k= + +yargs-parser@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" + integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^12.0.5: + version "12.0.5" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" + integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== + dependencies: + cliui "^4.0.0" + decamelize "^1.2.0" + find-up "^3.0.0" + get-caller-file "^1.0.1" + os-locale "^3.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1 || ^4.0.0" + yargs-parser "^11.1.1" + +yauzl@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" + integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU= + dependencies: + fd-slicer "~1.0.1" diff --git a/docs/admin/intro.md b/docs/admin/intro.md new file mode 100644 index 0000000000..53be92650f --- /dev/null +++ b/docs/admin/intro.md @@ -0,0 +1,5 @@ +[Table of contents](../readme.md) + +# Admin + +This directory runs an internal application for viewing core metrics on the platform. While it can be built and run locally, we don't encourage contributors to spend much time here as the dashboard itself will only ever be used internally by the Spectrum team. The code can of course be reviewed for those interested to see what kind of numbers and tools are accessible to our team. \ No newline at end of file diff --git a/docs/api/graphql/fragments.md b/docs/api/graphql/fragments.md new file mode 100644 index 0000000000..7fa955ca6f --- /dev/null +++ b/docs/api/graphql/fragments.md @@ -0,0 +1,152 @@ +[Table of contents](../../readme.md) / [API](../intro.md) / [GraphQL](./intro.md) + +# Fragments + +Fragments help us to always know exactly what kind of data will be returned for a given query and ensure that we are consistent in what data should be returned to any given component. + +Imagine we wrote two queries like this: + +``` +# query 1 +user { + uid + displayName +} + +# query 2 +user { + uid + username +} +``` + +If these queries get implemented in different places, for different components, we're going to end up with a confusing mess where we never really know what information we have access to when writing our react components. We might accidentally write `{ user.username }` expecting that data to exist, but alas we wrote the featuring using query 1. Oops. + +Fragments are an important part of how we query our GraphQL API from the frontend and you'll be using them constantly. + +## Example + +Here's a real-life example of how you would use fragments to query our API: + +```js +import { gql } from 'react-apollo'; + +export const userInfoFragment = gql` + fragment userInfo on User { + uid + photoURL + displayName + username + } +`; +``` + +And here's how that would look in a query file: + +```js +const getUser = gql` + query getUser($username: String, $after: String) { + user(username: $username) { + ...userInfo + } + } + ${userInfoFragment} +`; +``` + +By using this fragment, we can always ensure we're getting back the data we need and start to build up an internal mental model of what data we have access to regardless of where we're working in the app. + +This is especially useful for when we want to handle some pagination logic. We can just use a fragment to both a) save ourselves the hassle of writing a big query and 2) ensure we haven't forgotten a key field (like `cursor`) during implementation. Like this: + +```js +export const userCommunitiesFragment = gql` + fragment userCommunities on User { + communityConnection { + pageInfo { + hasNextPage + hasPreviousPage + } + edges { + node { + ...communityInfo + } + } + } + } + ${communityInfoFragment} +`; +``` + +Notice there that we're using a sub-fragment to get the `communityInfo`, too! + +That looks like: + +```js +export const communityInfoFragment = gql` + fragment communityInfo on Community { + id + name + slug + } +`; +``` + +And now we've saved ourselves time, and ensured that we will always have access to critical data about the communities, even if we are writing a query against the user. + +## Structure + +![screenshot 2017-05-02 17 06 31](https://cloud.githubusercontent.com/assets/1923260/25644273/b6ac5daa-2f59-11e7-916d-2f985fff6237.png) + +The reason these fragments are so obnoxiously granular is because there are times when you might have circular fragment requirements, which don't play well with webpack at the moment. For example, a user might require a story, which might require a user. So the files are importing each other and it breaks. + +To solve this I've made super granular fragments in individual files. I've found it's somewhat useful from a naming convention point of view, and makes it super clear when importing files and actually using the fragments in a query. + +## Performance notes + +Fragments make it tempting to create really deep queries, but that's a bad idea for performance reasons. Therefore, fragments themselves should be as shallow as possible to combat this, ideally never going more than one level of resources deep. (`story.id` and `story.content.title` are great, `story.community.channels.stories` not so much) + +When writing the fragments + queries, I tried to think to myself: +***If I were using this fragment, what data would I absolutely expect to be returned?*** + +For example, the `storyInfo` fragment looks like: +```js +import { gql } from 'react-apollo'; +import { userInfoFragment } from '../user/userInfo'; + +export const storyInfoFragment = gql` + fragment storyInfo on Story { + id + messageCount + createdAt + modifiedAt + published + deleted + locked + content { + title + description + } + author { + ...userInfo + } + } + ${userInfoFragment} +`; +``` + +I included the `author` and `...userInfo` fragment on `storyInfo` because I can't think of a single use case we have where we'd want to show a story *without* the author info. However, note that I didn't include any information about the channel this story was posted under. That's because if I'm *viewing that channel* it would be a massively underperforming query to also include channel data with *each story*. + +So instead, if I *do* need the channel data for a story, I add that at the query layer: + +```js +const getStory = gql` + query getStory($id: String) { + ...storyInfo + channel { + ...frequencyInfo + } + } + ${storyInfoFragment} + ${frequencyInfoFragment} +`; +``` diff --git a/docs/api/graphql/intro.md b/docs/api/graphql/intro.md new file mode 100644 index 0000000000..3238361c66 --- /dev/null +++ b/docs/api/graphql/intro.md @@ -0,0 +1,31 @@ +[Table of contents](../../readme.md) / [API](../intro.md) + +# GraphQL Intro + +We use [`graphql-tools`](http://dev.apollodata.com/tools/graphql-tools/index.html) which lets us use the GraphQL schema language to design our schema. This schema written in the schema language is then combined with our resolvers (which live somewhere else) using `graphql-tools`, which outputs our finished schema that's then used. + +### Folder Setup + +This folder setup was inspired by a bunch of open (and some closed) source projects @mxstbr looked at before embarking on the journey of building this. It seems to work well, even for big projects, but nothing here is set in stone and we're always open for new ideas and discussions. + +This is the current folder structure annotated: + +```sh +api/ +├── migrations # Migrations for seeding the database with some initial data +├── models # Handle talking to the database +├── mutations # Mutation resolvers +├── queries # Query resolvers +├── subscriptions # Subscription resolvers +├── types # The schema, split up into many smaller parts +│ └── scalars.js # The custom scalars we use in our schema and their resolvers +├── README.md +├── index.js # Runs the actual servers (GraphQL + WebSocket for subscriptions) +└── schema.js # Combines the types from types/ and the resolvers together with graphql-tools +``` + +Learn more about: +- [Fragments](fragments.md) +- [Pagination](pagination.md) +- [Testing](testing.md) +- [Tips & Tricks](tips-and-tricks.md) \ No newline at end of file diff --git a/docs/api/graphql/pagination.md b/docs/api/graphql/pagination.md new file mode 100644 index 0000000000..b622cbeb76 --- /dev/null +++ b/docs/api/graphql/pagination.md @@ -0,0 +1,104 @@ +[Table of contents](../../readme.md) / [API](../intro.md) / [GraphQL](./intro.md) + +# Pagination with GraphQL + +Even though there is no specific built-in way to paginate GraphQL queries there is a quasi-standard that most people (including us) follow called [Relay Connections Specification](https://facebook.github.io/relay/graphql/connections.htm). Rather than reading a spec that might not help you in pratical applications we recommend reading these two article to get a grasp of the why and how: + +- [Understanding pagination: REST, GraphQL and Relay](https://dev-blog.apollodata.com/understanding-pagination-rest-graphql-and-relay-b10f835549e7): Get a sense of the issues we're facing with the app and how to solve them +- [Explaining GraphQL Connections](https://dev-blog.apollodata.com/explaining-graphql-connections-c48b7c3d6976): We follow this specific structure to the dot. (with one tiny change in naming) + +## TL;DR + +The TL;DR of how to use it is: + +```GraphQL +{ + thread(id: "some-thread-id") { + # Fetch the messages of a certain thread + messageConnection { + pageInfo { + # Do we have another page to fetch after this + hasNextPage + } + edges { + # Pass the cursor of the last message to messageConnections + # to fetch the next page + cursor + # The actual message: + node { + id + message { + content + } + } + } + } + } +} +``` + +This query would get the first 10 (or less if there's less in total) messages of that thread. To get the next page you have to take the `cursor` of the last message edge and pass that to messageConnections: + +```GraphQL +{ + thread(id: "some-thread-id") { + # Fetch the next messages after the last message in the thread + messageConnection(after: $lastMessageCursor) { + edges { + node { + message { + content + } + } + } + } + } +} +``` + +> Note: The cursor is an opaque data structure, meaning it might refer to something you understand or it might not. It's also not promised to be consistent, especially between sessions, resources, etc. This boils down to **do not use the cursor for anything other than passing it to the query for the next page**, no matter if you want to use it for something else. + +To specify how many messages you want to load use the `first` parameter: + +```GraphQL +{ + thread(id: "some-thread-id") { + # Fetch the first 5 messages after the last message + messageConnection(first: 5, after: $lastMessageCursor) { + edges { + node { + message { + content + } + } + } + } + } +} +``` + +> Note: The default for `first` is normally 10, but might be changed depending on the resource being fetched. Make sure to check GraphiQL/the types to figure out what the default is. + +## Naming conventions + +Connections and edges have standard names and structures across all resources: + +```GraphQL +# A connection of a story to messages +type StoryMessagesConnection { + pageInfo: PageInfo! + edges: [StoryMessageEdge!] +} + +# An edge from a story to a message +type StoryMessageEdge { + cursor: String! + node: Message! +} + +type Story { + messageConnection(first: Int = 10, after: String): StoryMessagesConnection! +} +``` + +> Note: This is where we diverge slightly from the article linked above, it recommends naming your edges in plural (`StoryMessagesEdge`) to make it consistent with the connection but we've found that the singular (`StoryMessageEdge`) makes it clear only a single resource is being fetched and think that's more important. diff --git a/docs/api/graphql/testing.md b/docs/api/graphql/testing.md new file mode 100644 index 0000000000..b5eb269828 --- /dev/null +++ b/docs/api/graphql/testing.md @@ -0,0 +1,40 @@ +[Table of contents](../../readme.md) / [API](../intro.md) / [GraphQL](./intro.md) + +# Testing + +We use [Jest](https://facebook.github.io/jest/) for testing. (we recommend reading through the documentation, since it has a lot of great features you might not expect) Most of our tests are "e2e". That's in quotes because with GraphQL "e2e" tests don't do any network request, they still hit our database though and all that. (which is awesome since it's much faster!) + +We create a separate database for testing (called `"testing"`, surprisingly) that gets populated with some test data that we then verify against. A typical test suite for a certain type might look like this: + +```javascript +import { graphql } from 'graphql'; +import createLoaders from '../loaders'; +import schema from '../schema'; + +// Nice little helper function for tests +const request = query => + graphql(schema, query, undefined, { loaders: createLoaders() }); + +describe('queries', () => { + it('should fetch a user', () => { + // Define your query + const query = /* GraphQL */ ` + { + user(id: "gVk5mYwccUOEKiN5vtOouqroGKo1") { + name + username + profilePhoto + } + } + `; + + // Make sure the assertion below is called and we don't run into a race condition + expect.assertions(1); + // Return the Promise returned from the request + return request(query).then(result => { + // Use Jest snapshot testing for neater tests and easier diffs + expect(result).toMatchSnapshot(); + }); + }); +}) +``` diff --git a/docs/api/graphql/tips-and-tricks.md b/docs/api/graphql/tips-and-tricks.md new file mode 100644 index 0000000000..d636a463f0 --- /dev/null +++ b/docs/api/graphql/tips-and-tricks.md @@ -0,0 +1,20 @@ +[Table of contents](../../readme.md) / [API](../intro.md) / [GraphQL](./intro.md) + +# Tips and Tricks + +## Keep resolvers as small as possible + +What we mean by this is that resolvers should (for the most part) only be a mapping between a certain path and the model function that takes care of it. Make sure the least amount of business logic possible lives in resolvers. (these are reminiscent of Controllers in a traditional MVC setup) + +## Error management + +We mask internal errors in production. (GraphQL schema errors are still visible of course) Instead of seeing "Database limit exceeded, please upgrade your account xyz.", the user only sees "Internal Error: asdf123-asdf-asdf-asdf1235" (where `"asdf123-asdf-asdf-asdf1235"` is the Sentry UUID that helps us reference that error to a stacktrace on Sentry) which means no sensitive information is leaked. + +Sometimes you want to show the user an error though, for example for permissions. In that case you have to use the `UserError` util, which will not be masked: + +```javascript +import UserError from '../utils/UserError'; + +// The user will see this full error message +return new UserError('You do not have permission to access this!') +``` diff --git a/docs/api/intro.md b/docs/api/intro.md new file mode 100644 index 0000000000..5f71036110 --- /dev/null +++ b/docs/api/intro.md @@ -0,0 +1,16 @@ +[Table of contents](../readme.md) + +# API + +The Spectrum API is a Node.js web server based on Express.js and GraphQL. It's also houses a websocket server for all of our subscription needs. + +## Structure + +This server follows a GraphQL-first philosophy. That means we design the GraphQL schema first and then start implementing business logic. This is great because it gives us a clear separation of concerns (business logic vs. schema), and it's how Facebook recommends to use GraphQL. + +## GraphQL +- [Intro](graphql/intro.md) + - [Fragments](graphql/fragments.md) + - [Pagination](graphql/pagination.md) + - [Testing](graphql/testing.md) + - [Tips & Tricks](graphql/tips-and-tricks.md) \ No newline at end of file diff --git a/docs/backend/graphql/README.md b/docs/backend/api/README.md similarity index 100% rename from docs/backend/graphql/README.md rename to docs/backend/api/README.md diff --git a/docs/backend/graphql/fragments.md b/docs/backend/api/fragments.md similarity index 100% rename from docs/backend/graphql/fragments.md rename to docs/backend/api/fragments.md diff --git a/docs/backend/graphql/pagination.md b/docs/backend/api/pagination.md similarity index 100% rename from docs/backend/graphql/pagination.md rename to docs/backend/api/pagination.md diff --git a/docs/backend/graphql/testing.md b/docs/backend/api/testing.md similarity index 100% rename from docs/backend/graphql/testing.md rename to docs/backend/api/testing.md diff --git a/docs/backend/graphql/tips-and-tricks.md b/docs/backend/api/tips-and-tricks.md similarity index 92% rename from docs/backend/graphql/tips-and-tricks.md rename to docs/backend/api/tips-and-tricks.md index 7089f07c11..dbcbda5561 100644 --- a/docs/backend/graphql/tips-and-tricks.md +++ b/docs/backend/api/tips-and-tricks.md @@ -14,5 +14,5 @@ Sometimes you want to show the user an error though, for example for permissions import UserError from '../utils/UserError'; // The user will see this full error message -throw new UserError('You do not have permission to access this!') +return new UserError('You do not have permission to access this!') ``` diff --git a/docs/backend/athena/README.md b/docs/backend/athena/README.md deleted file mode 100644 index d054cebe2d..0000000000 --- a/docs/backend/athena/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `athena`: The notifications worker - -Athena is responsible for processing notifications. diff --git a/docs/backend/background-jobs.md b/docs/backend/background-jobs.md deleted file mode 100644 index 3829de4726..0000000000 --- a/docs/backend/background-jobs.md +++ /dev/null @@ -1,7 +0,0 @@ -# Background jobs - -We use [`bull`](https://github.com/OptimalBits/bull) for all of our background job needs. (at the moment that mostly means notification processing and emails) `bull` uses redis under the hood (see docs/setup.md for instructions on how to install redis) to store information about these jobs. - -All of our servers and workers connect to the same redis instance, adding and taking jobs as they see fit. (in development that's your local instance, in production that's a remote instance hosted on compose.com) Thusly, redis acts kind of like shared global state that helps our disparate processes talk to each other. - -Because `bull` implements job locking we can run as many of these servers and workers in parallel without any job getting done more than one time, which is very neat and should help us a lot when we run into scaling problems. diff --git a/docs/backend/chronos/README.md b/docs/backend/chronos/README.md deleted file mode 100644 index 26568ed29d..0000000000 --- a/docs/backend/chronos/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `chronos`: The cron job worker - -Chronos is responsible for scheduling and processing repeating jobs, like the weekly digest. diff --git a/docs/backend/hermes/README.md b/docs/backend/hermes/README.md deleted file mode 100644 index 1cdf9b29ed..0000000000 --- a/docs/backend/hermes/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `hermes`: The email worker - -`hermes` is our background worker responsible for sending emails. It implements some `bull` queues (see docs/background-jobs.md) that can be contacted to send transactional (!) emails with Postmark. diff --git a/docs/backend/hyperion/README.md b/docs/backend/hyperion/README.md deleted file mode 100644 index 42791ee62d..0000000000 --- a/docs/backend/hyperion/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `hyperion`: The server-side renderer - -Hyperion is a Node.js web server based on Express.js. It's responsible for server-side rendering our frontend. diff --git a/docs/backend/mercury/README.md b/docs/backend/mercury/README.md deleted file mode 100644 index b776cba9eb..0000000000 --- a/docs/backend/mercury/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `mercury`: The reputation worker - -Mercury is responsible for processing all reputation-related events. diff --git a/docs/backend/vulcan/README.md b/docs/backend/vulcan/README.md deleted file mode 100644 index c0be93df56..0000000000 --- a/docs/backend/vulcan/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `vulcan`: The search worker - -Vulcan is responsible for syncing our production data with Algolia search indexing diff --git a/docs/deletingUsers.md b/docs/deletingUsers.md deleted file mode 100644 index f9da17c275..0000000000 --- a/docs/deletingUsers.md +++ /dev/null @@ -1,54 +0,0 @@ -# How to delete users on Spectrum - -Occassionally people request that their account gets deleted on Spectrum. We have a safe way to do this so that it respects the integrity of data across the rest of the database. - -**Do NOT ever `.delete()` a user record from the database!!** - -Follow these steps to safely delete a user from Spectrum: - -1. Find the user in the database and **write down their user id** -2. Check to see if the user owns any communities. - 2a. If the user owns communities, please try to convince them not to delete their account. - 2b. If they *really* want to delete their account, reach out to @brian to handle this -3. When it's confirmed that they don't own any communities, clear all necessary fields from the user record, and add a `deletedAt` field - this will trigger Vulcan to remove the user from search indexes -``` -r.db('spectrum') -.table('users') -.get(ID) -.update({ - username: null, - email: null, - deletedAt: new Date(), - providerId: null, - fbProviderId: null, - googleProviderId: null, -}) -``` -4. Remove that user as a member from all communities and channels: -``` -// usersCommunities -.table('usersCommunities') -.getAll(ID, { index: 'userId' }) -.update({ - isMember: false, - receiveNotifications: false -}) - -// usersChannels -.table('usersChannels') -.getAll(ID, { index: 'userId' }) -.update({ - isMember: false, - receiveNotifications: false, -}) -``` -5. Remove all notifications from threads to save worker processing: -``` -// usersThreads -.table('usersThreads') -.getAll(ID, { index: 'userId' }) -.update({ - receiveNotifications: false, -}) -``` -6. Done! The user now can't be messaged, searched for, or re-logged into. The deleted user no longer affects community or channel member counts, and will not ever get pulled into Athena for notifications processing. \ No newline at end of file diff --git a/docs/backend/deployment.md b/docs/deployments.md similarity index 92% rename from docs/backend/deployment.md rename to docs/deployments.md index 855846fb81..b0f29733a1 100644 --- a/docs/backend/deployment.md +++ b/docs/deployments.md @@ -1,108 +1,110 @@ -# Deployment - -We use [`now`](https://now.sh) by Zeit for all of our deployments. - -## Installation - -We recommend installing the [now desktop app](http://zeit.co/download) as it'll keep the command line tool up to date automatically! - -If you'd rather not install an app (or are using Windows Substem for Linux) you can also install now with npm: - -``` -npm install -g now -``` - -## Naming scheme - -All our workers are aliased to `.workers.spectrum.chat`, with the one exception being the API (the API server) which runs at `api.spectrum.chat`. - -### Path aliases - -[Now's path alias feature](https://zeit.co/docs/features/path-aliases) takes care of routing requests to the right workers. To see our current production aliases check the `rules.json` file in the root of the project. - -To deploy new rules, simply run the following command: (assuming `rules.json` has the changes) - -```sh -now alias spectrum.chat -r rules.json -``` - -The same command with a different URL and slightly adapted rules can also be used to create an alpha/beta/staging/... version of the site. - -## Frontend and Hyperion - -Since the frontend is the part that changes the most often the repo is set up such that typing `now` in the root directory will deploy the frontend with hyperion. (the server-side rendering worker) - -### Deploying - -First, make sure you're in the Space Program `now` team: - -``` -now switch spaceprogram -``` - -> Note: If you haven't been added to the now team yet ask one of your coworkers to add you! - -Second, deploy a new version of the app: - -``` -now -``` - -This will do an _immutable deploy_ of the hyperion and will return a unique URL that this specific version is accessible at. (something like `spectrum-grtertb34.now.sh`) - -Third, test that version of the app to make sure the new feature(s) work(s) as expected in production. Note that nobody except for people will access to the unique URL from the last step can access it at this point. - -Fourth, if you're happy with the new feature(s) alias your unique URL to `hyperion.workers.spectrum.chat` to make it immediately available to all users: - -``` -now alias spectrum-grtertb34.now.sh hyperion.workers.spectrum.chat -``` - -And that's it, you've now deployed a new version of hyperion and the frontend! - -## Other workers - -We use [`backpack`](https://github.com/palmerhq/backpack) to get a nice development setup and build process in place for our workers. Since this is a mono-repo and `now` doesn't have any special functionality to handle monorepos we have to deploy the _built_ version of the workers. (compared to the frontend deploy where you deploy the raw code which gets built on the server) - -### Deploying - -First, build the worker you want to deploy to get a bundle with the newest code rather than deploying a stale version: - -```sh -yarn run build: -``` - -> Note: Replace `` with the name of the worker you want to deploy - -Then deploy the `build-` folder with `now`: - -```sh -now build- -``` - -As an example, to deploy `athena` I'd run these commands: - -```sh -# First: Build the worker -yarn run build:athena -# Second: Deploy the built bundle -now build-athena -``` - -This will give you an immutable deploy with a unique URL of this worker, something like `https://build-athena-ahsgut23sdyf.now.sh`. - -### Replacing the old deploy - -Since we want to keep the workers alive even if nobody sends a request to their healthcheck endpoints we alias them to `.workers.spectrum.chat`. This ensures `now` overrides the older deploy and only the newest code is running. - -To alias your deploy run `now alias`: - -```sh -now alias .now.sh .workers.spectrum.chat -``` - -For example, for our `athena` deploy from above this would be: - -```sh -now alias build-athena-ahsgut23sdyf.now.sh athena.workers.spectrum.chat -``` +[Table of contents](./readme.md) + +# Deployments + +We use [`now`](https://now.sh) by Zeit for all of our deployments. + +### Installation + +We recommend installing the [now desktop app](http://zeit.co/download) as it'll keep the command line tool up to date automatically! + +If you'd rather not install an app (or are using Windows Substem for Linux) you can also install now with npm: + +``` +npm install -g now +``` + +### Naming scheme + +All our workers are aliased to `.workers.spectrum.chat`, with the one exception being the API (the API server) which runs at `api.spectrum.chat`. + +#### Path aliases + +[Now's path alias feature](https://zeit.co/docs/features/path-aliases) takes care of routing requests to the right workers. To see our current production aliases check the `rules.json` file in the root of the project. + +To deploy new rules, simply run the following command: (assuming `rules.json` has the changes) + +```sh +now alias spectrum.chat -r rules.json +``` + +The same command with a different URL and slightly adapted rules can also be used to create an alpha/beta/staging/... version of the site. + +### Frontend and Hyperion + +Since the frontend is the part that changes the most often the repo is set up such that typing `now` in the root directory will deploy the frontend with hyperion. (the server-side rendering worker) + +#### Deploying + +First, make sure you're in the Space Program `now` team: + +``` +now switch spaceprogram +``` + +> Note: If you haven't been added to the now team yet ask one of your coworkers to add you! + +Second, deploy a new version of the app: + +``` +now +``` + +This will do an _immutable deploy_ of the hyperion and will return a unique URL that this specific version is accessible at. (something like `spectrum-grtertb34.now.sh`) + +Third, test that version of the app to make sure the new feature(s) work(s) as expected in production. Note that nobody except for people will access to the unique URL from the last step can access it at this point. + +Fourth, if you're happy with the new feature(s) alias your unique URL to `hyperion.workers.spectrum.chat` to make it immediately available to all users: + +``` +now alias spectrum-grtertb34.now.sh hyperion.workers.spectrum.chat +``` + +And that's it, you've now deployed a new version of hyperion and the frontend! + +### Other workers + +We use [`backpack`](https://github.com/palmerhq/backpack) to get a nice development setup and build process in place for our workers. Since this is a mono-repo and `now` doesn't have any special functionality to handle monorepos we have to deploy the _built_ version of the workers. (compared to the frontend deploy where you deploy the raw code which gets built on the server) + +#### Deploying + +First, build the worker you want to deploy to get a bundle with the newest code rather than deploying a stale version: + +```sh +yarn run build: +``` + +> Note: Replace `` with the name of the worker you want to deploy + +Then deploy the `build-` folder with `now`: + +```sh +now build- +``` + +As an example, to deploy `athena` I'd run these commands: + +```sh +# First: Build the worker +yarn run build:athena +# Second: Deploy the built bundle +now build-athena +``` + +This will give you an immutable deploy with a unique URL of this worker, something like `https://build-athena-ahsgut23sdyf.now.sh`. + +#### Replacing the old deploy + +Since we want to keep the workers alive even if nobody sends a request to their healthcheck endpoints we alias them to `.workers.spectrum.chat`. This ensures `now` overrides the older deploy and only the newest code is running. + +To alias your deploy run `now alias`: + +```sh +now alias .now.sh .workers.spectrum.chat +``` + +For example, for our `athena` deploy from above this would be: + +```sh +now alias build-athena-ahsgut23sdyf.now.sh athena.workers.spectrum.chat +``` diff --git a/docs/backend/hyperion/server-side-rendering.md b/docs/hyperion (server side rendering)/development.md similarity index 83% rename from docs/backend/hyperion/server-side-rendering.md rename to docs/hyperion (server side rendering)/development.md index 3dc26323b9..f91f69b341 100644 --- a/docs/backend/hyperion/server-side-rendering.md +++ b/docs/hyperion (server side rendering)/development.md @@ -1,16 +1,16 @@ -# Server-side rendering +[Table of contents](../readme.md) / [Hyperion](./intro.md) -In production we server our React-based frontend (`src/`) server-side rendered, meaning we do an initial render on the server and send down static HTML, then rehydrate with the JS bundle. +# Hyperion development -## Normal development workflow +### Normal development workflow When you develop Spectrum you're running two processes, `yarn run dev:web` for the frontend and `yarn run dev:api` for API, the GraphQL API. This means in your browser you access `localhost:3000`, which is a fully client-side React app, and that then fetches data from `localhost:3001/api`. -In production, the client-side app is bundled and server-side rendered by hyperion. +In production, the client-side app is bundled and server-side rendered by Hyperion. This means we get the best development experience locally with hot module reloading etc, and in production we have single server responsible for serving files, rendering and the API. -## When developing SSR +### When developing SSR **DO NOT USE THIS UNLESS YOU'RE DEVELOPING SSR** diff --git a/docs/hyperion (server side rendering)/intro.md b/docs/hyperion (server side rendering)/intro.md new file mode 100644 index 0000000000..6263f69063 --- /dev/null +++ b/docs/hyperion (server side rendering)/intro.md @@ -0,0 +1,10 @@ +[Table of contents](../readme.md) + +# Hyperion + +*Hyperion: (/haɪˈpɪəriən/) is one of the twelve Titan children of Gaia and Uranus.* + +Hyperion is the server responsible for server-side rendering the front end. In production, Hyperion does an initial render of a requested view on the server and responds with static HTML. The static HTML is then rehydrated with our JS bundle. + +Learn more about [development with Hyperion](development.md) + diff --git a/docs/rethinkdb.md b/docs/operations/importing-rethinkdb-backups.md similarity index 83% rename from docs/rethinkdb.md rename to docs/operations/importing-rethinkdb-backups.md index 1f0255c1c5..d53700127c 100644 --- a/docs/rethinkdb.md +++ b/docs/operations/importing-rethinkdb-backups.md @@ -1,3 +1,5 @@ +[Table of contents](../readme.md) / [Operations](./index.md) + # Importing production data locally Sometimes it's useful to have production data running locally in rethinkdb for debugging and testing. To get production data running locally, follow these steps: @@ -9,4 +11,4 @@ Sometimes it's useful to have production data running locally in rethinkdb for d 5. Unzip that backup onto your desktop 6. Rename the unzipped directory to 'prod-backup' 6. In your terminal, run: `cd ~/Desktop && rethinkdb import -d prod-backup` -7. The import will take ~10-15 minutes, at which point you can clear localstorage at localhost:3000 to re-authenticate +7. The import will take a couple hours, at which point you can clear localstorage at localhost:3000 to re-authenticate diff --git a/docs/operations/intro.md b/docs/operations/intro.md new file mode 100644 index 0000000000..7a0d4b45f5 --- /dev/null +++ b/docs/operations/intro.md @@ -0,0 +1,8 @@ +# Operations + +This directory is for docs related to operating the Spectrum platform. Common questions about how to perform non-code-related tasks should go here. + +Learn more about: +- [Banning users](banning-users.md) +- [Deleting users](deleting-users.md) +- [Importing a RethinkDB backup locally](importing-rethinkdb-backups.md) \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md index 66ca6bce32..dc0aa8be78 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,14 +1,24 @@ -# Documentation - -This folder contains most of the technical documentation around Spectrum. - ## Table of Contents -- [Unit and e2e Testing](testing.md) -- [Background Jobs](backend/background-jobs.md) -- [Deployment](backend/deployment.md) -- [GraphQL](backend/api/) - - [Fragments](backend/api/fragments.md) - - [Pagination](backend/api/pagination.md) - - [GraphQL Testing](backend/api/testing.md) - - [Tips and Tricks](backend/api/tips-and-tricks.md) +- [Deployments](deployments.md) +- [Admin](admin/intro.md) +- [API](api/intro.md) + - [GraphQL](api/graphql/intro.md) + - [Fragments](api/graphql/fragments.md) + - [Pagination](api/graphql/pagination.md) + - [Testing](api/graphql/testing.md) + - [Tips & Tricks](api/graphql/tips-and-tricks.md) +- [Hyperion (server side rendering)](hyperion%20(server%20side%20rendering)/intro.md) + - [Development](hyperion%20(server%20side%20rendering)/development.md) +- [Operations](operations/intro.md) + - [Deleting users](operations/deleting-users.md) + - [Importing RethinkDB backups](operations/importing-rethinkdb-backups.md) +- [Testing](testing/intro.md) + - [Integration](testing/integration.md) + - [Unit](testing/unit.md) +- [Workers](workers/intro.md) + - [Background jobs with Redis](workers/background-jobs.md) + - [Athena](workers/athena/intro.md) + - [Chronos](workers/chronos/intro.md) + - [Mercury](workers/mercury/intro.md) + - [Vulcan](workers/vulcan/intro.md) diff --git a/docs/testing.md b/docs/testing.md deleted file mode 100644 index 325f7b1b05..0000000000 --- a/docs/testing.md +++ /dev/null @@ -1,87 +0,0 @@ -# Testing - -We have a test suite consisting of a bunch of unit tests (mostly for the API) and integration tests to verify Spectrum keeps working as expected. The entire test suite is run in CI for every commit and PR, so if you introduce a breaking change the CI will fail and the PR will not be merge-able. - -## Unit tests - -We use [`Jest`](https://github.com/facebook/jest) for our unit testing needs. To run Jest in watch mode locally so the tests run automatically while you're developing run: - -```sh -yarn run test -``` - -Before running the tests this will set up a RethinkDB database locally called `"testing"`. It will run the migrations over it and then insert some dummy data. This is important because we test our GraphQL API against the real database, we don't mock anything, to make sure everything is working 100%. - -When you exit the testing process with `CTRL+C` it will clean the database from all its data. It leaves the migrations in tact though, since there's no reason we should have to run them everytime we run the test suite. - -### Stale data - -In edge cases it could happen that you end up with bad data locally and that tests fail/pass even though they shouldn't. To get rid of the stale data just stop the testing process and optionally run `yarn run posttest` to make extra sure there's nothing in the database anymore, before restarting the testing process to start from a clean slate. - -## End-to-end tests - -We use [Cypress](https://cypress.io) to run our e2e tests, which gives you a nice GUI that you can use for your test runs. To run e2e tests you have to have both api and the client running. You also need API to be connected to the test database, which you do by setting `TEST_DB`: - -```sh -# In one tab -TEST_DB=true yarn run dev:api -# In another tab -yarn run dev:web -``` - -Then open the Cypress GUI and you're good to start running the e2e tests: - -```sh -yarn run cypress:open -``` - -### Writing e2e tests - -**It is highly recommend to read the [Best Practices section of the Cypress docs](https://docs.cypress.io/guides/references/best-practices.html) before starting to write them!** - -All our integration tests live in `cypress/integration/`. This is what a normal integration test might look like: - -```JS -// cypress/integration/home_spec.js -describe('Home View', () => { - before(() => { - cy.visit('/'); - }); - - it('should render the home page', () => { - cy.get('[data-cy="home-page"]').should('be.visible'); - cy.get('[href*="/login"]').should('be.visible'); - cy.get('[href*="/new/community"]').should('be.visible'); - }); -}); -``` - -Note that while the Cypress API looks synchronous, it's actually totally asynchronous under the hood. They build up a queue of incoming assertions and wait for them to happen in order. While that's a bit confusing at the beginning, you get used to it very fast. - -Also note that Cypress uses Mocha under the hood, where our unit tests use Jest. This means rather than `expect().toEqual()` you'd have to write `expect().to.equal()` due to the syntax difference between the `expect` implementations. - -#### Test IDs - -To verify that certain elements are or aren't on the page we use custom `data-cy` attributes. You render them from React like so: - -```JS -class HomePage extends Component { - render() { - return ( - - {/*...*/} - - ) - } -} -``` - -Then you can make sure the splash page rendered correctly by waiting for that selector to appear on the page: - -```JS -// cypress/integration/home_spec.js -it('should render', () => { - cy.get('[data-cy="home-page"]').should('be.visible'); -}); -``` - diff --git a/docs/testing/integration.md b/docs/testing/integration.md new file mode 100644 index 0000000000..71a086c3de --- /dev/null +++ b/docs/testing/integration.md @@ -0,0 +1,71 @@ +[Table of contents](../readme.md) / [Testing](./intro.md) + +# Integration tests + +We use [Cypress](https://cypress.io) to run our integration tests, which gives you a nice GUI that you can use for your test runs. To run integration tests you have to have the api running in production mode and connected to the test database and the client running. + +```sh +# First, build the API +yarn run build:api +# Then, in one tab start the API in test mode +yarn run start:api:test +# In another tab start the web client +yarn run dev:web +``` + +Then open the Cypress GUI and you're good to start running the integration tests: + +```sh +yarn run cypress:open +``` + +### Writing integration tests + +**It is highly recommend to read the [Best Practices section of the Cypress docs](https://docs.cypress.io/guides/references/best-practices.html) before starting to write them!** + +All our integration tests live in `cypress/integration/`. This is what a normal integration test might look like: + +```JS +// cypress/integration/home_spec.js +describe('Home View', () => { + before(() => { + cy.visit('/'); + }); + + it('should render the home page', () => { + cy.get('[data-cy="home-page"]').should('be.visible'); + cy.get('[href*="/login"]').should('be.visible'); + cy.get('[href*="/new/community"]').should('be.visible'); + }); +}); +``` + +Note that while the Cypress API looks synchronous, it's actually totally asynchronous under the hood. They build up a queue of incoming assertions and wait for them to happen in order. While that's a bit confusing at the beginning, you get used to it very fast. + +Also note that Cypress uses Mocha under the hood, where our unit tests use Jest. This means rather than `expect().toEqual()` you'd have to write `expect().to.equal()` due to the syntax difference between the `expect` implementations. + +### Test IDs + +To verify that certain elements are or aren't on the page we use custom `data-cy` attributes. You render them from React like so: + +```JS +class HomePage extends Component { + render() { + return ( + + {/*...*/} + + ) + } +} +``` + +Then you can make sure the splash page rendered correctly by waiting for that selector to appear on the page: + +```JS +// cypress/integration/home_spec.js +it('should render', () => { + cy.get('[data-cy="home-page"]').should('be.visible'); +}); +``` + diff --git a/docs/testing/intro.md b/docs/testing/intro.md new file mode 100644 index 0000000000..a43fcc50de --- /dev/null +++ b/docs/testing/intro.md @@ -0,0 +1,10 @@ +[Table of contents](../readme.md) + +# Testing + +We have a test suite consisting of a bunch of unit tests (mostly for the API) and integration tests to verify Spectrum keeps working as expected. The entire test suite is run in CI for every commit and PR, so if you introduce a breaking change the CI will fail and the PR will not be merge-able. + +Learn more about: +- [Unit testing](./unit.md) +- [Integration testing](./integration.md) + diff --git a/docs/testing/unit.md b/docs/testing/unit.md new file mode 100644 index 0000000000..5223ad4985 --- /dev/null +++ b/docs/testing/unit.md @@ -0,0 +1,17 @@ +[Table of contents](../readme.md) / [Testing](./intro.md) + +# Unit tests + +We use [`Jest`](https://github.com/facebook/jest) for our unit testing needs. To run Jest in watch mode locally so the tests run automatically while you're developing run: + +```sh +yarn run test +``` + +Before running the tests this will set up a RethinkDB database locally called `"testing"`. It will run the migrations over it and then insert some dummy data. This is important because we test our GraphQL API against the real database, we don't mock anything, to make sure everything is working 100%. + +When you exit the testing process with `CTRL+C` it will clean the database from all its data. It leaves the migrations in tact though, since there's no reason we should have to run them everytime we run the test suite. + +### Stale data + +In edge cases it could happen that you end up with bad data locally and that tests fail/pass even though they shouldn't. To get rid of the stale data just stop the testing process and optionally run `yarn run posttest` to make extra sure there's nothing in the database anymore, before restarting the testing process to start from a clean slate. diff --git a/docs/workers/analytics/intro.md b/docs/workers/analytics/intro.md new file mode 100644 index 0000000000..cc306e8062 --- /dev/null +++ b/docs/workers/analytics/intro.md @@ -0,0 +1,12 @@ +[Table of contents](../../readme.md) / [Workers](../intro.md) + +# Analytics + +The Analytics worker is responsible for helping us process actions happening on Spectrum so that we can make better decisions about product development, usage, and failures. Event tracking is abstracted to allow any number of downstream providers (including future first-party pipelines) to all process the same events. + +All event tracking is anonymized completely, ensuring that an individual user’s actions cannot be replayed and tracked back to a single person. This anonymization happens in two places: + +1. On the client, when a user is identified, we hash the userId using `sha1` via the anonymizomatic microservice. +2. In the worker, every identify call has the userId automatically hashed before being dispatched to downstream analytics providers + +Analytics is built to read off of our [Redis queue](../background-jobs.md). This allows any worker to dispatch jobs to be processed asynchronously in the worker - we currently do this from the api, Hermes, and Athena. \ No newline at end of file diff --git a/docs/workers/athena/intro.md b/docs/workers/athena/intro.md new file mode 100644 index 0000000000..7820f8fe16 --- /dev/null +++ b/docs/workers/athena/intro.md @@ -0,0 +1,9 @@ +[Table of contents](../../readme.md) / [Workers](../intro.md) + +# Athena + +*Athena (/əˈθiːnə/) is the goddess of wisdom, craft, and war.* + +Athena is our notifications worker, processing things like new message notifications, new thread notifications, new member notifications, etc. Athena is built to read off of our [Redis queue](../background-jobs.md). Most queues in Athena handle the sending of both in-app notifications as well as email notifications (by dispatching a new job to the Redis queue for [Hermes](../hermes/intro.md) to read). + +This server also does the heavy lifting of keeping track of the `lastSeen` field on `usersThreads`, which is what powers the small 'New Messages' badge that users see when viewing threads on Spectrum. While not directly a notification, the `lastSeen` field is a critical piece of Spectrum that helps people keep up with the most important and active conversations in their communities. \ No newline at end of file diff --git a/docs/workers/background-jobs.md b/docs/workers/background-jobs.md new file mode 100644 index 0000000000..0b5cd41582 --- /dev/null +++ b/docs/workers/background-jobs.md @@ -0,0 +1,9 @@ +[Table of contents](../readme.md) / [Workers](intro.md) + +# Background jobs + +We use [`bull`](https://github.com/OptimalBits/bull) for all of our background job needs (at the moment that mostly means notification processing and emails). `bull` uses redis under the hood to store information about these jobs. (follow the instructions on [redis.io/download](https://redis.io/download) to install Redis) + +All of our servers and workers connect to the same redis instance, adding and taking jobs as they see fit. In development, that's your local instance. In production, that's a remote instance hosted on [compose.com](https://compose.com). Thusly, redis acts kind of like shared global state that helps our disparate processes talk to each other. + +Because `bull` implements job locking we can run as many of these servers and workers in parallel without any job getting done more than one time, which is very neat and should help us a lot when we run into scaling problems. diff --git a/docs/workers/chronos/intro.md b/docs/workers/chronos/intro.md new file mode 100644 index 0000000000..3a44bac0f2 --- /dev/null +++ b/docs/workers/chronos/intro.md @@ -0,0 +1,12 @@ +[Table of contents](../../readme.md) / [Workers](../intro.md) + +# Chronos + +*Chronos (/ˈkroʊnɒs/) is the personification of Time in pre-Socratic philosophy* + +Chronos powers all the cron jobs needed to run Spectrum. These jobs currently include: + +- daily and weekly digest emails for Spectrum users +- daily core metrics reports for the Spectrum team + +Any recurring jobs should go here. Chronos also runs off of our [Redis queue](../background-jobs.md) which handles the cron schedule, job locking, and retry logic. \ No newline at end of file diff --git a/docs/workers/hermes/intro.md b/docs/workers/hermes/intro.md new file mode 100644 index 0000000000..09572a1ae3 --- /dev/null +++ b/docs/workers/hermes/intro.md @@ -0,0 +1,8 @@ +[Table of contents](../../readme.md) / [Workers](../intro.md) + +# Hermes + +*Hermes (/ˈhɜːrmiːz/) is the messenger god, moving between the worlds of the mortal and the divine.* + +Hermes is our email worker. Hermes reads off of our [Redis queue](../background-jobs.md) to process email content and deliver emails via [SendGrid](https://sendgrid.com/). Hermes is responsible for formatting content before it enters the email templates. All inbound jobs should contain all the required data for the final email. + diff --git a/docs/backend/README.md b/docs/workers/intro.md similarity index 50% rename from docs/backend/README.md rename to docs/workers/intro.md index 645b6147bc..48a56375a7 100644 --- a/docs/backend/README.md +++ b/docs/workers/intro.md @@ -1,22 +1,31 @@ -# Backend +[Table of contents](../readme.md) -The backend of Spectrum.chat is built on RethinkDB (data storage) and Redis. (background job queue) There are currently three parts to it: +# Workers -- API: A Node.js server running our API based on Express.js and GraphQL. -- Hyperion: A Node.js server for server-side rendering our frontend. -- Athena: A Node.js worker for notification processing. -- Hermes: A Node.js worker for sending emails. -- Chronos: A Node.js worker for scheduling repeating jobs. -- Mercury: A node.js worker for processing reputation events. +Our asynchronos background job processing is powered by a series of worker servers. + +- [Analytics](analytics/intro.md): processes analytics +- [Athena](athena/intro.md): processes notifications +- [Chronos](chronos/intro.md): processes cron jobs +- [Hermes](hermes/intro.md): sends emails +- [Mercury](mercury/intro.md): processes reputation events +- [Vulcan](vulcan/intro.md): indexes content for search Each one of these can be run and developed independently with matching `npm run dev:x` and `npm run build:x` commands. (where `x` is the name of the server) -## Naming scheme +### Naming scheme As you can see we follow a loose naming scheme based on ancient Greek, Roman, and philosophical figures that are somewhat related to what our servers do: -- Hyperion: (/haɪˈpɪəriən/) is one of the twelve Titan children of Gaia and Uranus. +- Analytics is...for analytics - Athena (/əˈθiːnə/) is the goddess of wisdom, craft, and war. -- Hermes (/ˈhɜːrmiːz/) is the messenger god, moving between the worlds of the mortal and the divine. - Chronos (/ˈkroʊnɒs/) is the personification of Time in pre-Socratic philosophy +- Hermes (/ˈhɜːrmiːz/) is the messenger god, moving between the worlds of the mortal and the divine. - Mercury (/ˈmɜːrkjʊri/) is the patron god of financial gain, commerce, eloquence (and thus poetry), messages/communication (including divination), travelers, boundaries, luck, trickery and thieves +- Vulcan is the god of fire, including the fire of volcanoes, metalworking, and the forge. + +### Background jobs + +Many of our workers run off of our [Redis queue](background-jobs.md) to handle asynchronous events. + +[Learn more about background jobs](background-jobs.md) diff --git a/docs/workers/mercury/intro.md b/docs/workers/mercury/intro.md new file mode 100644 index 0000000000..be193e2ac2 --- /dev/null +++ b/docs/workers/mercury/intro.md @@ -0,0 +1,7 @@ +[Table of contents](../../readme.md) / [Workers](../intro.md) + +# Mercury + +*Mercury (/ˈmɜːrkjʊri/) is the patron god of financial gain, commerce, eloquence (and thus poetry), messages/communication (including divination), travelers, boundaries, luck, trickery and thieves* + +Mercury is our reputation worker. This server reads off of our [Redis queue](../background-jobs.md) to increment and decrement reputation-related events that are triggered by users. The reputation system is how we can start to provide insights into the contributions of individual people back to the communities where they are members. diff --git a/docs/workers/vulcan/intro.md b/docs/workers/vulcan/intro.md new file mode 100644 index 0000000000..aa73ec265a --- /dev/null +++ b/docs/workers/vulcan/intro.md @@ -0,0 +1,7 @@ +[Table of contents](../../readme.md) / [Workers](../intro.md) + +# Vulcan + +*Vulcan is the god of fire, including the fire of volcanoes, metalworking, and the forge.* + +Vulcan is our search-indexing worker. Its job is to keep our data indexed properly on our search provider, [Algolia](https://www.algolia.com/). It does this by using RethinkDB's [changefeeds](https://rethinkdb.com/docs/changefeeds/javascript/) feature to listen for new documents, changed documents, and deleted documents in our database to signal that a new index event needs to occur. \ No newline at end of file diff --git a/email-template-scripts/README.md b/email-template-scripts/README.md new file mode 100644 index 0000000000..f355d4706b --- /dev/null +++ b/email-template-scripts/README.md @@ -0,0 +1,23 @@ +# Syncing Email Templates with SendGrid + +All email templates must have their CSS inlined before being added to SendGrid. SendGrid does *not* automatically inline the CSS. Additionally, to save time while testing email template changes it is tedious to copy/paste templates between a code editor and the SendGrid UI, with the CSS-inlining script being required to run with each modification. + +The scripts in this directory solve both of these problems. To get started: + +- Make sure you have python3 installed: `brew install python3` +- Install `premailer` with python3: `sudo python3 -m pip install premailer` + +From the root directory of the Spectrum repo, run: +`SENDGRID_API_KEY= yarn run process:emails:test` +You will need a `SENDGRID_API_KEY` from the SendGrid app for this to work. + +The `:test` script inlines all the CSS for each email template then syncs it with the `[Test]` template on SendGrid. When done testing your changes, you can sync both test and production templates by running: +`SENDGRID_API_KEY= yarn run process:emails:prod` + +# Sending test emails + +Rather than spinning up the local web app, clicking around and trying to replicate each email's conditions, you can instead run the `send-test-emails` script: + +`SENDGRID_API_KEY= yarn run process:emails:send` + +**Be sure to edit the email address in `package.json` first** \ No newline at end of file diff --git a/email-template-scripts/inline-html-emails.py b/email-template-scripts/inline-html-emails.py new file mode 100644 index 0000000000..12032b155a --- /dev/null +++ b/email-template-scripts/inline-html-emails.py @@ -0,0 +1,43 @@ +from premailer import transform +import glob, os, os.path + +output_path = '../built-email-templates' +input_path = '../email-templates' + +# remove all previously-built email templates +old_files = glob.glob(os.path.join(output_path, "*.html")) +for old_file in old_files: + print(f'Removing previously built file {old_file}') + os.remove(old_file) + +# get the path names of all email templates in the 'email-templates' dir +raw_template_paths = glob.glob(os.path.join(input_path, "*.html")) +for raw_template in raw_template_paths: + # constuct a new filename so that that the transformed file gets written to the output dir + file_name = raw_template.replace(input_path, output_path) + file_obj = open(raw_template) + html = file_obj.read() + + # premailer escapes curly braces, which we don't want since we use handlebars + # so after we transform our html, we then replace escaped braces with the actual + # curly brace character + triplemapping = (('%7B%7B%7B', '{{{ '), ('%7D%7D%7D', ' }}}')) + doublemapping = (('%7B%7B', '{{ '), ('%7D%7D', ' }}')) + + transformed = transform(html) + + for k, v in triplemapping: + transformed = transformed.replace(k, v) + + for k, v in doublemapping: + transformed = transformed.replace(k, v) + + # open a writeable file in the output directory + new_file = open(file_name, 'w') + new_file.write(transformed) + + # clean up + new_file.close() + file_obj.close() + + diff --git a/email-template-scripts/send-test-emails.js b/email-template-scripts/send-test-emails.js new file mode 100644 index 0000000000..4a672e0ba1 --- /dev/null +++ b/email-template-scripts/send-test-emails.js @@ -0,0 +1,118 @@ +// @flow +require('now-env'); +const fetch = require('isomorphic-fetch'); +const { SENDGRID_API_KEY } = process.env; +const processArgs = process.argv.slice(2); +const TO = processArgs.find(arg => arg.indexOf('@') > 0); +const sg = require('@sendgrid/mail'); + +if (!TO) { + console.error('❌ Be sure to provide a valid email'); + return; +} + +if (!SENDGRID_API_KEY) { + console.error('❌ Be sure to provide a SendGrid API key'); + return; +} + +sg.setApiKey(SENDGRID_API_KEY); + +const sendEmail = (templateId, dynamic_template_data) => { + return sg.send({ + from: { + email: 'hi@spectrum.chat', + name: 'Spectrum', + }, + tracking_settings: { + click_tracking: { + enable: false, + }, + }, + templateId, + to: TO, + dynamic_template_data, + }); +}; + +const init = () => { + const templates = [ + { + filename: 'message-in-threads', + id: 'd-7a4c14fd440146f1b1cfcafb633bb040', + }, + { filename: 'mention-in-thread', id: 'd-ff421ea0112a4525b6615bcc666ede00' }, + { + filename: 'mention-in-message', + id: 'd-637189bc871744e9846694bff9f572ae', + }, + { + filename: 'direct-message-received', + id: 'd-3e289af9efe748308be2dde1d3786c0d', + }, + { filename: 'new-user-welcome', id: 'd-2e46e5b65abc42b78941fbe027be4cd5' }, + { + filename: 'community-invitation', + id: 'd-69b2e17b7a0f46048dcf4083ad4f9c48', + }, + { filename: 'community-created', id: 'd-dc7b4f048f4c460f9dd368fd3796421b' }, + { filename: 'thread-created', id: 'd-084c11332981443388ebdae05d0a2ff4' }, + { filename: 'digest', id: 'd-5e52250c25be4654af82172970551919' }, + { + filename: 'user-email-validation', + id: 'd-9fbb3cc969364050aac891c255d31209', + }, + { + filename: 'community-admin-email-validation', + id: 'd-a60e1df2d5294c73818759be13f09df4', + }, + { + filename: 'private-community-request-approved', + id: 'd-d91de18c257344d2bf9ff0c628d1a92e', + }, + { + filename: 'private-community-request-sent', + id: 'd-743d07e016ee4798a87c06b5dd0a27a1', + }, + { + filename: 'private-channel-request-approved', + id: 'd-6bc3fffa3fa64e369035bc906b3975dd', + }, + { + filename: 'private-channel-request-sent', + id: 'd-29f3f62815004e0bb3b9f884c9fb3901', + }, + { + filename: 'admin-user-reported-alert', + id: 'd-7340d2f62edd4af6a4c95f87a8d4e1c6', + }, + { + filename: 'admin-user-spamming-threads-alert', + id: 'd-65de04a810d84af7b76a57f7b4b6ebbe', + }, + { + filename: 'admin-active-community-report', + id: 'd-82812e47e2ea458c8ded5be8d3de4f48', + }, + { + filename: 'admin-slack-import-completed', + id: 'd-b3f8d36ef3354ce987a352ce39893432', + }, + { + filename: 'admin-toxic-content', + id: 'd-f6e52c81dd8d49e29f23c5c6112d676b', + }, + { + filename: 'admin-community-created-notification', + id: 'd-8220ddfc3d3a436a9ea974348c9c2edd', + }, + ]; + + return templates.map(template => { + const json = require(`./test-email-data/${template.filename}`); + console.error(`✅ Sending test email for ${template.filename}`); + return sendEmail(template.id, json); + }); +}; + +init(); diff --git a/email-template-scripts/sendgrid-sync.js b/email-template-scripts/sendgrid-sync.js new file mode 100644 index 0000000000..97c5e23b90 --- /dev/null +++ b/email-template-scripts/sendgrid-sync.js @@ -0,0 +1,114 @@ +// @flow +require('now-env'); +const fs = require('fs'); +const util = require('util'); +const fetch = require('isomorphic-fetch'); +const textversion = require('textversionjs'); +const { SENDGRID_API_KEY } = process.env; +const RELATIVE_PATH_TO_TEMPLATES = '../built-email-templates'; +const processArgs = process.argv.slice(2); +const UPDATE_PROD_TEMPLATES = processArgs.some(arg => arg === 'prod'); + +if (!SENDGRID_API_KEY) { + console.error('❌ Be sure to provide a SendGrid API key'); + return; +} + +const readdirAsync = path => { + return new Promise((resolve, reject) => { + fs.readdir(path, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); +}; + +const processFile = (file, config) => { + const plainTextConfig = { + uIndentationChar: ' ', + oIndentationChar: ' ', + listIndentationTabs: 2, + keepNbsps: true, + linkProcess: (href, text) => `${text} (${href})`, + }; + + const { version, template } = config; + + const hostname = 'https://api.sendgrid.com/v3'; + const path = `/templates/${template}/versions/${version}`; + const url = hostname + path; + + const html_content = file; + const plain_content = textversion(html_content, plainTextConfig); + + const data = { + html_content, + plain_content, + }; + + const options = { + method: 'PATCH', + headers: { + Authorization: `Bearer ${SENDGRID_API_KEY}`, + 'Content-Type': 'application/json; charset=utf-8', + }, + body: JSON.stringify(data), + }; + + console.error('♻️ Uploading template to SendGrid'); + + return fetch(url, options) + .then(res => res.json()) + .then(res => { + console.error('✅ Saved template on SendGrid'); + }) + .catch(err => { + console.error({ err }); + }); +}; + +const processPath = path => { + const file = fs.readFile( + `${RELATIVE_PATH_TO_TEMPLATES}/${path}`, + { encoding: 'utf-8' }, + (err, file) => { + if (err) { + console.error({ err }); + return; + } + + const configStart = file.indexOf('START_CONFIG'); + const configEnd = file.indexOf('END_CONFIG'); + const configString = file + .slice(configStart, configEnd) + .replace('START_CONFIG', '') + .replace(/(\r\n\t|\n|\r\t)/gm, ''); + const config = JSON.parse(configString); + + if (!UPDATE_PROD_TEMPLATES && !config.test) { + console.error('🔅 No test config for this template, skipping'); + return; + } + + const { test: testConfig, production: prodConfig } = config; + + return Promise.all([ + testConfig && processFile(file, testConfig), + UPDATE_PROD_TEMPLATES && processFile(file, prodConfig), + ]); + } + ); +}; + +const init = async () => { + const paths = await readdirAsync(RELATIVE_PATH_TO_TEMPLATES); + + return paths.map(async path => { + return await processPath(path); + }); +}; + +init(); diff --git a/email-template-scripts/test-email-data/admin-active-community-report.json b/email-template-scripts/test-email-data/admin-active-community-report.json new file mode 100644 index 0000000000..efacb09e94 --- /dev/null +++ b/email-template-scripts/test-email-data/admin-active-community-report.json @@ -0,0 +1,14 @@ +{ + "subject": "Active Community Report", + "data": { + "dacCount": 3, + "wacCount": 6, + "macCount": 9, + "newDac": "one, two three", + "newWac": "four five six", + "newMac": "seven eight nine", + "lostDac": "ten", + "lostWac": "eleven", + "lostMac": "twelve" + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/admin-community-created-notification.json b/email-template-scripts/test-email-data/admin-community-created-notification.json new file mode 100644 index 0000000000..933ee517d1 --- /dev/null +++ b/email-template-scripts/test-email-data/admin-community-created-notification.json @@ -0,0 +1,39 @@ +{ + "subject": "New community: Community Name", + "user": { + "coverPhoto": "", + "createdAt": "2018-08-12T19:08:00.405Z", + "description": "description", + "email": "email", + "fbProviderId": null, + "githubProviderId": null, + "githubUsername": null, + "googleProviderId": null, + "id": "1", + "isOnline": false, + "lastSeen": "2018-12-06T21:45:55.870Z", + "modifiedAt": "2018-08-12T19:08:40.616Z", + "name": "User Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2018-08-12T19:08:00.405Z", + "timezone": -420, + "username": "username", + "website": "website" + }, + "community": { + "administratorEmail": "email", + "coverPhoto": null, + "createdAt": "2018-12-09T19:31:18.615Z", + "creatorId": "1", + "description": "Description", + "id": "1", + "isPrivate": false, + "memberCount": 0, + "modifiedAt": null, + "name": "Community Name", + "profilePhoto": null, + "slug": "community-slug", + "website": "website" + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/admin-slack-import-completed.json b/email-template-scripts/test-email-data/admin-slack-import-completed.json new file mode 100644 index 0000000000..7ec1108637 --- /dev/null +++ b/email-template-scripts/test-email-data/admin-slack-import-completed.json @@ -0,0 +1,23 @@ +{ + "subject": "New Slack import: 2 invites from the blah Slack team", + "preheader": "2 invites sent for the blah community", + "data": { + "community": { + "administratorEmail": "email", + "coverPhoto": "", + "createdAt": "2018-12-09T16:01:18.435Z", + "creatorId": "24695e16-c001-4c66-93fe-0cb69554a212", + "description": "description", + "id": "1", + "isPrivate": false, + "memberCount": 1, + "modifiedAt": "2018-12-09T16:30:36.422Z", + "name": "Community Name", + "profilePhoto": "", + "slug": "community-slug", + "website": "website" + }, + "invitedCount": 2, + "teamName": "blah" + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/admin-toxic-content.json b/email-template-scripts/test-email-data/admin-toxic-content.json new file mode 100644 index 0000000000..c31e7f8f1f --- /dev/null +++ b/email-template-scripts/test-email-data/admin-toxic-content.json @@ -0,0 +1,81 @@ +{ + "subject": "Toxic alert (96.5%): This is tosic", + "preheader": "This is tosic", + "data": { + "type": "message", + "text": "This is tosic", + "user": { + "coverPhoto": "", + "createdAt": "2018-10-21T19:11:19.582Z", + "description": "", + "email": "email", + "fbProviderId": null, + "firstName": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "isOnline": true, + "lastName": null, + "lastSeen": "2018-12-04T20:37:06.217Z", + "modifiedAt": "2018-11-24T05:52:03.036Z", + "name": "User Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2018-10-21T19:11:19.582Z", + "timezone": -300, + "username": "username", + "website": "" + }, + "thread": { + "channelId": "1", + "communityId": "1", + "content": { + "body": "{\"blocks\":[{\"key\":\"foo\",\"text\":\"This is toxic \",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"70o57\",\"text\":\"This is also toxic.\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}", + "title": "Thread title" + }, + "createdAt": "2018-11-28T22:08:14.305Z", + "creatorId": "1", + "edits": [], + "id": "1", + "isLocked": false, + "isPublished": true, + "lastActive": "2018-12-04T20:38:28.829Z", + "messageCount": 1, + "modifiedAt": null, + "reactionCount": 1, + "score": 500, + "scoreUpdatedAt": "2018-12-04T20:38:29.064Z", + "type": "DRAFTJS" + }, + "community": { + "administratorEmail": "email", + "coverPhoto": "", + "createdAt": "2018-10-17T08:28:52.961Z", + "creatorId": "2d8bc47d-6d7b-43bc-ad40-79534bfc27ed", + "description": "Descritpion", + "id": "1", + "isPrivate": false, + "memberCount": 1, + "modifiedAt": "2018-11-19T19:35:41.897Z", + "name": "Community Name", + "pinnedThreadId": "1", + "profilePhoto": "", + "slug": "community-slug", + "website": "" + }, + "channel": { + "communityId": "1", + "createdAt": "2018-10-17T08:28:53.380Z", + "description": "", + "id": "1", + "isDefault": true, + "isPrivate": false, + "memberCount": 1, + "name": "General", + "slug": "general" + }, + "toxicityConfidence": { + "perspectivePercent": 93 + } + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/admin-user-reported-alert.json b/email-template-scripts/test-email-data/admin-user-reported-alert.json new file mode 100644 index 0000000000..406ddaa8e4 --- /dev/null +++ b/email-template-scripts/test-email-data/admin-user-reported-alert.json @@ -0,0 +1,48 @@ +{ + "subject": "☠️ a person was reported", + "preheader": "Reason: Being a meanie", + "reportedUser": { + "coverPhoto": "", + "createdAt": "2018-12-07T02:41:06.564Z", + "description": "", + "email": "email", + "fbProviderId": null, + "firstName": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "isOnline": false, + "lastName": null, + "lastSeen": "2018-12-07T02:44:13.031Z", + "modifiedAt": "2018-12-07T02:41:25.997Z", + "name": "A name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2018-12-07T02:41:06.564Z", + "username": "username", + "website": "" + }, + "reportingUser": { + "coverPhoto": "", + "createdAt": "2017-10-11T17:54:50.206Z", + "description": "", + "email": "email", + "fbProviderId": null, + "firstName": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "isOnline": true, + "lastName": null, + "lastSeen": "2018-12-07T16:12:10.741Z", + "modifiedAt": "2018-03-28T16:58:29.930Z", + "name": "Person Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2017-10-11T17:54:50.206Z", + "timezone": -420, + "username": "username", + "website": "" + }, + "reason": "Being a meanie" +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/admin-user-spamming-threads-alert.json b/email-template-scripts/test-email-data/admin-user-spamming-threads-alert.json new file mode 100644 index 0000000000..5acfe85f25 --- /dev/null +++ b/email-template-scripts/test-email-data/admin-user-spamming-threads-alert.json @@ -0,0 +1,85 @@ +{ + "subject": "🐟 User spamming threads alert: Someone has published one thread in previous 10 minutes", + "preheader": "Someone is attempting to publish a new thread in the made up community", + "data": { + "user": { + "createdAt": "2018-04-30T23:27:15.910Z", + "description": null, + "email": "email", + "fbProviderId": null, + "githubProviderId": null, + "githubUsername": null, + "googleProviderId": null, + "id": "1", + "isOnline": true, + "lastSeen": "2018-12-06T01:36:04.565Z", + "modifiedAt": "2018-05-11T21:08:49.440Z", + "name": "User Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2018-04-30T23:27:15.910Z", + "timezone": -180, + "username": "username", + "website": "" + }, + "threads": [ + { + "channelId": "1", + "communityId": "1", + "content": { + "body": "Body", + "title": "Title" + }, + "createdAt": "2018-12-09T18:09:17.723Z", + "creatorId": "1", + "deletedAt": "2018-12-09T18:12:06.441Z", + "deletedBy": "1", + "id": "1", + "isLocked": false, + "isPublished": true, + "lastActive": "2018-12-09T18:09:17.723Z", + "messageCount": 0, + "modifiedAt": "2018-12-09T18:10:38.496Z", + "reactionCount": 0, + "score": 20, + "scoreUpdatedAt": "2018-12-09T18:09:21.301Z", + "type": "DRAFTJS" + } + ], + "publishing": { + "channelId": "1", + "communityId": "1", + "type": "DRAFTJS", + "content": { + "title": "Title", + "body": "Body" + } + }, + "community": { + "administratorEmail": "email", + "coverPhoto": "", + "createdAt": "2018-04-26T06:10:12.502Z", + "creatorId": "1", + "description": "Description", + "id": "1", + "isPrivate": false, + "memberCount": 1, + "modifiedAt": "2018-05-11T21:10:59.072Z", + "name": "Community Name", + "profilePhoto": "", + "slug": "community-slug", + "website": "website" + }, + "channel": { + "communityId": "1", + "createdAt": "2018-04-26T06:10:12.965Z", + "description": "Description", + "id": "1", + "isDefault": true, + "isPrivate": false, + "memberCount": 1, + "name": "General", + "slug": "general" + } + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/community-admin-email-validation.json b/email-template-scripts/test-email-data/community-admin-email-validation.json new file mode 100644 index 0000000000..45119d52f9 --- /dev/null +++ b/email-template-scripts/test-email-data/community-admin-email-validation.json @@ -0,0 +1,20 @@ +{ + "subject": "Confirm new administrator email for the Test community", + "preheader": "Confirm the new administrator email below", + "validateToken": "1", + "community": { + "administratorEmail": "email", + "coverPhoto": null, + "createdAt": "2018-12-09T19:31:18.615Z", + "creatorId": "1", + "description": "Description", + "id": "1", + "isPrivate": false, + "memberCount": 0, + "modifiedAt": null, + "name": "Test", + "profilePhoto": null, + "slug": "slug", + "website": "website" + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/community-created.json b/email-template-scripts/test-email-data/community-created.json new file mode 100644 index 0000000000..5d7fb125af --- /dev/null +++ b/email-template-scripts/test-email-data/community-created.json @@ -0,0 +1,38 @@ +{ + "subject": "Your new community is live on Spectrum!", + "user": { + "coverPhoto": "", + "createdAt": "2017-11-08T19:30:25.375Z", + "description": "Description", + "email": "email", + "fbProviderId": null, + "githubProviderId": null, + "githubUsername": null, + "googleProviderId": null, + "id": "1", + "isOnline": false, + "lastSeen": "2018-11-09T19:27:21.826Z", + "modifiedAt": "2018-09-10T02:11:09.783Z", + "name": "Name", + "profilePhoto": "", + "providerId": null, + "timezone": -300, + "username": "username", + "website": "website" + }, + "community": { + "administratorEmail": "email", + "coverPhoto": null, + "createdAt": "2018-12-09T05:40:10.251Z", + "creatorId": "1", + "description": "Description", + "id": "1", + "isPrivate": false, + "memberCount": 1, + "modifiedAt": null, + "name": "Community Name", + "profilePhoto": null, + "slug": "slug", + "website": "website" + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/community-invitation.json b/email-template-scripts/test-email-data/community-invitation.json new file mode 100644 index 0000000000..25344ebbf1 --- /dev/null +++ b/email-template-scripts/test-email-data/community-invitation.json @@ -0,0 +1,48 @@ +{ + "subject": "Someone has invited you to join the Test community on Spectrum", + "preheader": "Come join the conversation with Someone", + "sender": { + "createdAt": "2018-08-10T13:47:29.509Z", + "description": "", + "email": "email", + "fbProviderId": null, + "firstName": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "isOnline": false, + "lastName": null, + "lastSeen": "2018-12-01T16:22:38.178Z", + "modifiedAt": "2018-08-10T13:48:11.240Z", + "name": "Someone", + "providerId": null, + "termsLastAcceptedAt": "2018-08-10T13:47:29.509Z", + "timezone": -180, + "username": "username", + "website": "website", + "profilePhoto": "", + "coverPhoto": "" + }, + "recipient": { + "email": "email", + "firstName": "First", + "lastName": "Last" + }, + "community": { + "administratorEmail": "email", + "createdAt": "2018-12-01T16:17:50.271Z", + "creatorId": "1", + "description": "Description", + "id": "1", + "isPrivate": false, + "memberCount": 1, + "modifiedAt": "2018-12-01T16:20:14.754Z", + "name": "Community Name", + "slug": "community-slug", + "website": "website", + "profilePhoto": "", + "coverPhoto": "" + }, + "customMessage": "This is a custom message!", + "joinPath": "community-slug" +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/digest.json b/email-template-scripts/test-email-data/digest.json new file mode 100644 index 0000000000..6395cf777e --- /dev/null +++ b/email-template-scripts/test-email-data/digest.json @@ -0,0 +1,49 @@ +{ + "subject": "Spectrum Daily Digest: Something, Another Thing, and 9 more active conversations in your communities", + "preheader": "Your Spectrum daily digest · December 9, 2018", + "unsubscribeToken": "foo", + "data": { + "username": "username", + "hasOverflowThreads": true, + "threads": [ + { + "id": "1", + "content": { + "title": "Thread title" + }, + "community": { + "slug": "community-slug", + "name": "Community Name", + "profilePhoto": "" + }, + "channel": { + "slug": "general", + "name": "General" + }, + "messageCountString": "100 messages (3 new)" + } + ], + "communities": [ + { + "administratorEmail": "email", + "coverPhoto": "", + "createdAt": "2017-07-25T05:06:23.563Z", + "creatorId": "1", + "description": "Description", + "id": "1", + "isPrivate": false, + "memberCount": 1, + "modifiedAt": "2017-12-23T05:01:46.216Z", + "name": "Community Name", + "profilePhoto": "", + "slug": "community-slug", + "website": "website" + } + ], + "reputationString": "You were a little quiet yesterday and haven't gained any reputation – join some of the conversations below, your communities would love to hear from you. Reputation is an indicator of how active and constructive you are across all your communities. The more great conversations you start or join, the more reputation you will have.", + "timeframe": { + "subject": "daily", + "time": "day" + } + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/direct-message-received.json b/email-template-scripts/test-email-data/direct-message-received.json new file mode 100644 index 0000000000..272c98511e --- /dev/null +++ b/email-template-scripts/test-email-data/direct-message-received.json @@ -0,0 +1,45 @@ +{ + "subject": "New direct message from Someone on Spectrum", + "user": { + "betaSupporter": true, + "createdAt": "2017-06-02T20:57:24.362Z", + "description": "description", + "email": "email", + "githubProviderId": null, + "githubUsername": null, + "id": "1", + "isOnline": false, + "lastSeen": "2018-12-07T00:15:17.953Z", + "modifiedAt": "2018-11-01T17:38:10.236Z", + "name": "Someone", + "providerId": null, + "termsLastAcceptedAt": "2017-06-02T20:57:24.362Z", + "timezone": -420, + "username": "username", + "website": "website", + "profilePhoto": "", + "coverPhoto": "" + }, + "thread": { + "content": { + "title": "Conversation with Someone and Someone Else" + }, + "path": "messages/1", + "id": "1" + }, + "username": "username", + "message": { + "content": { + "body": "a message" + }, + "id": "1", + "messageType": "draftjs", + "parentId": null, + "senderId": "1", + "threadId": "1", + "threadType": "directMessageThread", + "timestamp": "2018-12-07T00:21:38.750Z" + }, + "muteThreadToken": "foo", + "unsubscribeToken": "foo" +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/mention-in-message.json b/email-template-scripts/test-email-data/mention-in-message.json new file mode 100644 index 0000000000..a915f55f16 --- /dev/null +++ b/email-template-scripts/test-email-data/mention-in-message.json @@ -0,0 +1,145 @@ +{ + "subject": "Someone mentioned you in \"Thread title\"", + "preheader": "@hey team", + "sender": { + "createdAt": "2018-06-28T09:27:13.234Z", + "description": null, + "email": "email", + "fbProviderId": null, + "githubProviderId": null, + "githubUsername": null, + "googleProviderId": null, + "id": "1", + "isOnline": true, + "lastSeen": "2018-12-09T21:36:36.582Z", + "modifiedAt": "2018-06-28T09:27:23.442Z", + "name": "Name", + "providerId": null, + "termsLastAcceptedAt": "2018-06-28T09:27:13.234Z", + "timezone": 180, + "username": "username", + "website": "", + "profilePhoto": "", + "coverPhoto": "" + }, + "data": { + "thread": { + "channelId": "1", + "communityId": "1", + "content": { + "title": "Thread title", + "body": "thread body" + }, + "createdAt": "2018-12-08T00:36:49.553Z", + "creatorId": "1", + "id": "1", + "isLocked": false, + "isPublished": true, + "lastActive": "2018-12-09T21:36:37.631Z", + "messageCount": 1, + "modifiedAt": null, + "reactionCount": 0, + "score": 50, + "scoreUpdatedAt": "2018-12-09T21:36:38.318Z", + "type": "DRAFTJS", + "creator": { + "createdAt": "2018-06-28T09:27:13.234Z", + "description": null, + "email": "email", + "fbProviderId": null, + "githubProviderId": null, + "githubUsername": null, + "googleProviderId": null, + "id": "1", + "isOnline": true, + "lastSeen": "2018-12-09T21:36:36.582Z", + "modifiedAt": "2018-06-28T09:27:23.442Z", + "name": "Name", + "providerId": null, + "termsLastAcceptedAt": "2018-06-28T09:27:13.234Z", + "timezone": 180, + "username": "username", + "website": "", + "profilePhoto": "", + "coverPhoto": "" + }, + "community": { + "administratorEmail": "email", + "createdAt": "2017-11-08T19:26:29.401Z", + "creatorId": "1", + "description": "Community description", + "id": "1", + "isPrivate": false, + "memberCount": 1, + "modifiedAt": "2018-08-28T08:08:37.492Z", + "name": "Community name", + "pinnedThreadId": "1", + "slug": "community-slug", + "watercoolerId": "2", + "website": "website", + "profilePhoto": "", + "coverPhoto": "" + }, + "channel": { + "communityId": "1", + "createdAt": "2017-11-08T19:26:29.753Z", + "description": "General Chatter", + "id": "1", + "isDefault": true, + "isPrivate": false, + "memberCount": 1, + "name": "General", + "slug": "general" + } + }, + "message": { + "content": { + "body": "@hey" + }, + "id": "1", + "messageType": "draftjs", + "parentId": null, + "senderId": "1", + "threadId": "1", + "threadType": "story", + "timestamp": "2018-12-09T21:36:37.631Z", + "sender": { + "createdAt": "2018-06-28T09:27:13.234Z", + "description": null, + "email": "email", + "fbProviderId": null, + "githubProviderId": null, + "githubUsername": null, + "googleProviderId": null, + "id": "1", + "isOnline": true, + "lastSeen": "2018-12-09T21:36:36.582Z", + "modifiedAt": "2018-06-28T09:27:23.442Z", + "name": "Name", + "providerId": null, + "termsLastAcceptedAt": "2018-06-28T09:27:13.234Z", + "timezone": 180, + "username": "username", + "website": "", + "profilePhoto": "", + "coverPhoto": "" + } + } + }, + "recipient": { + "createdAt": "2017-04-17T20:51:09.834Z", + "email": "email", + "id": "1", + "isOnline": false, + "lastSeen": "2017-08-04T18:30:02.096Z", + "modifiedAt": "2017-12-15T21:25:24.121Z", + "name": "Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2017-04-17T20:51:09.834Z", + "timezone": -420, + "username": "username" + }, + "unsubscribeToken": "foo", + "muteThreadToken": "bar" +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/mention-in-thread.json b/email-template-scripts/test-email-data/mention-in-thread.json new file mode 100644 index 0000000000..d3bc0fa852 --- /dev/null +++ b/email-template-scripts/test-email-data/mention-in-thread.json @@ -0,0 +1,114 @@ +{ + "subject": "Someone mentioned you in \"Thread title\"", + "preheader": "Thread body preview", + "sender": { + "betaSupporter": true, + "createdAt": "2017-05-31T11:13:22.203Z", + "description": "description", + "email": "email", + "id": "1", + "isOnline": true, + "lastSeen": "2018-12-07T04:46:27.198Z", + "modifiedAt": "2017-12-15T21:25:24.121Z", + "name": "someone", + "providerId": null, + "termsLastAcceptedAt": "2017-05-31T11:13:22.203Z", + "timezone": -420, + "username": "someone", + "website": "website", + "profilePhoto": "", + "coverPhoto": "" + }, + "data": { + "thread": { + "channelId": "1", + "communityId": "1", + "content": { + "title": "Thread title", + "body": "thread body" + }, + "createdAt": "2018-12-07T04:53:41.537Z", + "creatorId": "1", + "id": "1", + "isLocked": false, + "isPublished": true, + "lastActive": "2018-12-07T04:53:41.537Z", + "messageCount": 0, + "modifiedAt": null, + "reactionCount": 0, + "score": 16.59145454222499, + "scoreUpdatedAt": "2018-12-07T04:53:42.596Z", + "type": "DRAFTJS", + "creator": { + "betaSupporter": true, + "createdAt": "2017-05-31T11:13:22.203Z", + "description": "description", + "email": "email", + "id": "1", + "isOnline": true, + "lastSeen": "2018-12-07T04:46:27.198Z", + "modifiedAt": "2017-12-15T21:25:24.121Z", + "name": "someone", + "providerId": null, + "termsLastAcceptedAt": "2017-05-31T11:13:22.203Z", + "timezone": -420, + "username": "someone", + "website": "website", + "profilePhoto": "", + "coverPhoto": "" + }, + "community": { + "administratorEmail": "email", + "createdAt": "2017-04-07T08:05:51.941Z", + "creatorId": "1", + "description": "description", + "id": "1", + "isPrivate": false, + "memberCount": 1, + "modifiedAt": "2017-12-15T21:25:24.121Z", + "name": "Community name", + "pinnedThreadId": "5cdcb945-32b7-4fe5-a389-aea3f9a7e363", + "slug": "community-slug", + "website": "website", + "profilePhoto": "", + "coverPhoto": "" + }, + "channel": { + "communityId": "1", + "createdAt": "2018-01-19T06:02:10.352Z", + "description": "Description", + "id": "1", + "isDefault": true, + "isPrivate": false, + "memberCount": 1, + "name": "Channel Name", + "slug": "channel-slug" + } + } + }, + "recipient": { + "coverPhoto": "", + "createdAt": "2017-12-06T02:58:51.955Z", + "description": "", + "email": "email", + "fbProviderId": null, + "firstName": null, + "githubProviderId": null, + "githubUsername": null, + "googleProviderId": null, + "id": "1", + "isOnline": false, + "lastName": null, + "lastSeen": "2018-11-28T02:52:08.865Z", + "modifiedAt": "2018-02-03T03:35:27.758Z", + "name": "Recipient Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2017-12-06T02:58:51.955Z", + "timezone": -480, + "username": "username", + "website": "website" + }, + "unsubscribeToken": "foo", + "muteThreadToken": "bar" +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/message-in-threads.json b/email-template-scripts/test-email-data/message-in-threads.json new file mode 100644 index 0000000000..00d90dedb2 --- /dev/null +++ b/email-template-scripts/test-email-data/message-in-threads.json @@ -0,0 +1,100 @@ +{ + "subject": "Someone replied in 'thead title'", + "preheader": "View 1 new message from Someone", + "recipient": { + "coverPhoto": "", + "createdAt": "2018-08-12T16:13:06.326Z", + "description": "", + "email": "email", + "fbProviderId": null, + "firstName": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "isOnline": false, + "isParticipant": true, + "lastName": null, + "lastSeen": "2018-08-15T17:26:00.057Z", + "modifiedAt": "2018-08-12T16:11:44.726Z", + "name": "Name", + "profilePhoto": "", + "providerId": null, + "receiveNotifications": true, + "termsLastAcceptedAt": "2018-08-12T16:11:38.286Z", + "threadId": "1", + "timezone": -240, + "userId": "1", + "username": "username", + "website": "" + }, + "unsubscribeToken": "foo", + "singleThreadMuteToken": "bar", + "data": { + "threads": [ + { + "channelId": "1", + "communityId": "1", + "content": { + "body": "{\"blocks\":[{\"key\":\"foo\",\"text\":\"content\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}", + "title": "thread title" + }, + "createdAt": "2018-08-12T16:13:06.126Z", + "creatorId": "1", + "id": "1", + "isLocked": false, + "isPublished": true, + "lastActive": "2018-12-09T22:17:51.405Z", + "messageCount": 12, + "modifiedAt": null, + "reactionCount": 0, + "score": -1132.408545457775, + "scoreUpdatedAt": "2018-12-09T04:02:10.253Z", + "type": "DRAFTJS", + "community": { + "administratorEmail": "email", + "coverPhoto": "", + "createdAt": "2017-11-08T19:26:29.401Z", + "creatorId": "1", + "description": "Description", + "id": "1", + "isPrivate": false, + "memberCount": 1, + "modifiedAt": "2018-08-28T08:08:37.492Z", + "name": "Community Name", + "pinnedThreadId": "1", + "profilePhoto": "", + "slug": "community-slug", + "watercoolerId": "1", + "website": "website" + }, + "channel": { + "communityId": "1", + "createdAt": "2017-11-08T19:26:29.753Z", + "description": "General Chatter", + "id": "1", + "isDefault": true, + "isPrivate": false, + "memberCount": 1, + "name": "General", + "slug": "general" + }, + "replies": [ + { + "id": "1", + "sender": { + "id": "1", + "profilePhoto": "", + "name": "User Name", + "username": "username" + }, + "content": { + "body": "

this is the message

" + } + } + ], + "repliesCount": 1, + "muteThreadToken": "foo" + } + ] + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/new-user-welcome.json b/email-template-scripts/test-email-data/new-user-welcome.json new file mode 100644 index 0000000000..ca5b52c32b --- /dev/null +++ b/email-template-scripts/test-email-data/new-user-welcome.json @@ -0,0 +1,21 @@ +{ + "subject": "Welcome to Spectrum!", + "user": { + "coverPhoto": null, + "createdAt": "2018-12-09T22:28:17.872Z", + "description": "", + "email": "email", + "fbProviderId": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "lastSeen": "2018-12-09T22:28:17.872Z", + "modifiedAt": null, + "name": "Full Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2018-12-09T22:28:17.872Z", + "username": null, + "website": "" + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/private-channel-request-approved.json b/email-template-scripts/test-email-data/private-channel-request-approved.json new file mode 100644 index 0000000000..7d9e5556e3 --- /dev/null +++ b/email-template-scripts/test-email-data/private-channel-request-approved.json @@ -0,0 +1,54 @@ +{ + "subject": "Your request to join the Test channel in the Test community was approved!", + "preheader": "You can now join the conversations happening in the Test channel!", + "data": { + "recipient": { + "coverPhoto": "", + "createdAt": "2018-10-10T09:02:49.421Z", + "description": "", + "email": "email", + "fbProviderId": null, + "firstName": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "isOnline": false, + "lastName": null, + "lastSeen": "2018-10-28T22:11:11.461Z", + "modifiedAt": "2018-10-10T09:09:53.940Z", + "name": "Full Name", + "profilePhoto": "", + "providerId": null, + "timezone": 180, + "username": "username", + "website": "" + }, + "channel": { + "communityId": "1", + "createdAt": "2018-10-12T19:16:03.198Z", + "description": "Channel Description", + "id": "1", + "isDefault": false, + "isPrivate": true, + "memberCount": 1, + "name": "Test Channel", + "slug": "test-channel" + }, + "community": { + "administratorEmail": "email", + "coverPhoto": "", + "createdAt": "2018-10-04T13:10:23.828Z", + "creatorId": "1", + "description": "Community description", + "id": "1", + "isPrivate": false, + "memberCount": 1, + "modifiedAt": "2018-10-08T11:23:19.135Z", + "name": "Community Name", + "pinnedThreadId": "1", + "profilePhoto": "", + "slug": "community-slug", + "website": "" + } + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/private-channel-request-sent.json b/email-template-scripts/test-email-data/private-channel-request-sent.json new file mode 100644 index 0000000000..fd7070f87b --- /dev/null +++ b/email-template-scripts/test-email-data/private-channel-request-sent.json @@ -0,0 +1,77 @@ +{ + "subject": "Someone has requested to join the Test channel in the Test community", + "preheader": "View your channel settings to approve or block this request", + "data": { + "user": { + "coverPhoto": "", + "createdAt": "2018-10-15T15:33:19.045Z", + "description": "", + "email": "email", + "fbProviderId": null, + "firstName": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "isOnline": true, + "lastName": null, + "lastSeen": "2018-11-22T19:10:07.115Z", + "modifiedAt": "2018-10-15T15:34:08.611Z", + "name": "Full Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2018-10-15T15:33:19.045Z", + "timezone": 120, + "username": "username", + "website": "" + }, + "recipient": { + "coverPhoto": "", + "createdAt": "2018-10-04T05:12:15.727Z", + "description": "Description", + "email": "email", + "fbProviderId": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "isOnline": false, + "lastSeen": "2018-11-20T18:03:52.157Z", + "modifiedAt": "2018-10-09T07:20:11.949Z", + "name": "Full Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2018-10-04T05:12:15.727Z", + "timezone": 60, + "username": "username", + "website": "" + }, + "channel": { + "communityId": "1", + "createdAt": "2018-10-12T20:10:24.573Z", + "description": "Test Channel", + "id": "1", + "isDefault": false, + "isPrivate": true, + "memberCount": 1, + "name": "Test Channel", + "slug": "test" + }, + "community": { + "administratorEmail": "email", + "awsStaticReplaced": "2018-11-03T03:42:20.456Z", + "coverPhoto": "", + "createdAt": "2018-10-04T13:10:23.828Z", + "creatorId": "1", + "description": "Description", + "id": "1", + "imageReplaced": "2018-11-03T03:42:00.540Z", + "isPrivate": true, + "memberCount": 1, + "modifiedAt": "2018-10-08T11:23:19.135Z", + "name": "Community Name", + "pinnedThreadId": "1", + "profilePhoto": "", + "slug": "community-slug", + "website": "" + } + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/private-community-request-approved.json b/email-template-scripts/test-email-data/private-community-request-approved.json new file mode 100644 index 0000000000..47ac1378ad --- /dev/null +++ b/email-template-scripts/test-email-data/private-community-request-approved.json @@ -0,0 +1,46 @@ +{ + "subject": "Your request to join the Test community was approved!", + "preheader": "You can now join the conversations happening in the Test community!", + "data": { + "recipient": { + "coverPhoto": "", + "createdAt": "2018-10-15T15:33:19.045Z", + "description": "", + "email": "email", + "fbProviderId": null, + "firstName": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "isOnline": true, + "lastName": null, + "lastSeen": "2018-11-22T19:10:07.115Z", + "modifiedAt": "2018-10-15T15:34:08.611Z", + "name": "Full Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2018-10-15T15:33:19.045Z", + "timezone": 120, + "username": "username", + "website": "" + }, + "community": { + "administratorEmail": "email", + "awsStaticReplaced": "2018-11-03T03:42:20.456Z", + "coverPhoto": "", + "createdAt": "2018-10-04T13:10:23.828Z", + "creatorId": "1", + "description": "Description", + "id": "1", + "imageReplaced": "2018-11-03T03:42:00.540Z", + "isPrivate": true, + "memberCount": 1, + "modifiedAt": "2018-10-08T11:23:19.135Z", + "name": "Community Name", + "pinnedThreadId": "1", + "profilePhoto": "", + "slug": "community-slug", + "website": "" + } + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/private-community-request-sent.json b/email-template-scripts/test-email-data/private-community-request-sent.json new file mode 100644 index 0000000000..2c01e554ce --- /dev/null +++ b/email-template-scripts/test-email-data/private-community-request-sent.json @@ -0,0 +1,68 @@ +{ + "subject": "username has requested to join the Test community", + "preheader": "View your community settings to approve or block this request", + "data": { + "user": { + "coverPhoto": "", + "createdAt": "2018-10-15T15:33:19.045Z", + "description": "", + "email": "email", + "fbProviderId": null, + "firstName": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "isOnline": true, + "lastName": null, + "lastSeen": "2018-11-22T19:10:07.115Z", + "modifiedAt": "2018-10-15T15:34:08.611Z", + "name": "Full Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2018-10-15T15:33:19.045Z", + "timezone": 120, + "username": "username", + "website": "" + }, + "recipient": { + "coverPhoto": "", + "createdAt": "2018-10-15T15:33:19.045Z", + "description": "", + "email": "email", + "fbProviderId": null, + "firstName": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "isOnline": true, + "lastName": null, + "lastSeen": "2018-11-22T19:10:07.115Z", + "modifiedAt": "2018-10-15T15:34:08.611Z", + "name": "Full Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2018-10-15T15:33:19.045Z", + "timezone": 120, + "username": "username", + "website": "" + }, + "community": { + "administratorEmail": "email", + "awsStaticReplaced": "2018-11-03T03:42:20.456Z", + "coverPhoto": "", + "createdAt": "2018-10-04T13:10:23.828Z", + "creatorId": "1", + "description": "Description", + "id": "1", + "imageReplaced": "2018-11-03T03:42:00.540Z", + "isPrivate": true, + "memberCount": 1, + "modifiedAt": "2018-10-08T11:23:19.135Z", + "name": "Community Name", + "pinnedThreadId": "1", + "profilePhoto": "", + "slug": "community-slug", + "website": "" + } + } +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/thread-created.json b/email-template-scripts/test-email-data/thread-created.json new file mode 100644 index 0000000000..c9e097aebb --- /dev/null +++ b/email-template-scripts/test-email-data/thread-created.json @@ -0,0 +1,101 @@ +{ + "subject": "Thread Title by Someone · Community (General)", + "preheader": "Thread body preview...", + "data": { + "thread": { + "channelId": "1", + "communityId": "1", + "createdAt": "2018-12-09T22:45:17.928Z", + "creatorId": "1", + "id": "1", + "isLocked": false, + "isPublished": true, + "lastActive": "2018-12-09T22:45:17.928Z", + "messageCount": 0, + "modifiedAt": null, + "reactionCount": 0, + "type": "DRAFTJS", + "content": { + "title": "Thread Title", + "body": "Thread body..." + }, + "creator": { + "coverPhoto": "", + "createdAt": "2018-10-15T15:33:19.045Z", + "description": "", + "email": "email", + "fbProviderId": null, + "firstName": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "isOnline": true, + "lastName": null, + "lastSeen": "2018-11-22T19:10:07.115Z", + "modifiedAt": "2018-10-15T15:34:08.611Z", + "name": "Full Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2018-10-15T15:33:19.045Z", + "timezone": 120, + "username": "username", + "website": "" + }, + "community": { + "administratorEmail": "email", + "awsStaticReplaced": "2018-11-03T03:42:20.456Z", + "coverPhoto": "", + "createdAt": "2018-10-04T13:10:23.828Z", + "creatorId": "1", + "description": "Description", + "id": "1", + "imageReplaced": "2018-11-03T03:42:00.540Z", + "isPrivate": true, + "memberCount": 1, + "modifiedAt": "2018-10-08T11:23:19.135Z", + "name": "Community Name", + "pinnedThreadId": "1", + "profilePhoto": "", + "slug": "community-slug", + "website": "" + }, + "channel": { + "communityId": "1", + "createdAt": "2017-11-08T19:26:29.753Z", + "description": "General Chatter", + "id": "1", + "isDefault": true, + "isPrivate": false, + "memberCount": 1, + "name": "General", + "slug": "general" + } + }, + "primaryActionLabel": "View conversation" + }, + "recipient": { + "coverPhoto": "", + "createdAt": "2018-10-15T15:33:19.045Z", + "description": "", + "email": "email", + "fbProviderId": null, + "firstName": null, + "githubProviderId": null, + "googleProviderId": null, + "id": "1", + "isOnline": true, + "lastName": null, + "lastSeen": "2018-11-22T19:10:07.115Z", + "modifiedAt": "2018-10-15T15:34:08.611Z", + "name": "Full Name", + "profilePhoto": "", + "providerId": null, + "termsLastAcceptedAt": "2018-10-15T15:33:19.045Z", + "timezone": 120, + "username": "username", + "website": "" + }, + "unsubscribeToken": "foo", + "muteChannelToken": "bar", + "muteCommunityToken": "biz" +} \ No newline at end of file diff --git a/email-template-scripts/test-email-data/user-email-validation.json b/email-template-scripts/test-email-data/user-email-validation.json new file mode 100644 index 0000000000..0bf1c57a2e --- /dev/null +++ b/email-template-scripts/test-email-data/user-email-validation.json @@ -0,0 +1,4 @@ +{ + "subject": "Confirm your email address on Spectrum", + "validateToken": "foo" +} \ No newline at end of file diff --git a/email-templates/adminActiveCommunityReport.html b/email-templates/adminActiveCommunityReport.html index 0f040d5337..98ae2f5ffa 100644 --- a/email-templates/adminActiveCommunityReport.html +++ b/email-templates/adminActiveCommunityReport.html @@ -1,9 +1,22 @@ + + + + - {{subject}} + {{{subject}}} +
{{preheader}} @@ -37,27 +55,30 @@

Daily active communities:

    -
  • Total ({{data.allDacCount}}):

    {{data.allDac}}

  • -
  • Same:

    {{data.overlappingDac}}

  • -
  • New:

    {{data.newDac}}

  • -
  • Lost:

    {{data.lostDac}}

  • +
  • Total ({{data.dacCount}})
  • +
  • New:

    {{data.newDac}}

    +
  • +
  • Lost:

    {{data.lostDac}}

    +
- +

Weekly active communities:

    -
  • Total ({{data.allWacCount}}):

    {{data.allWac}}

  • -
  • Same:

    {{data.overlappingWac}}

  • -
  • New:

    {{data.newWac}}

  • -
  • Lost:

    {{data.lostWac}}

  • +
  • Total ({{data.wacCount}})
  • +
  • New:

    {{data.newWac}}

    +
  • +
  • Lost:

    {{data.lostWac}}

    +
- +

Monthly active communities:

    -
  • Total ({{data.allMacCount}}):

    {{data.allMac}}

  • -
  • Same:

    {{data.overlappingMac}}

  • -
  • New:

    {{data.newMac}}

  • -
  • Lost:

    {{data.lostMac}}

  • +
  • Total ({{data.macCount}})
  • +
  • New:

    {{data.newMac}}

    +
  • +
  • Lost:

    {{data.lostMac}}

    +
- - + + \ No newline at end of file diff --git a/email-templates/adminCommunityCreated.html b/email-templates/adminCommunityCreated.html index 5d16a94bc0..64cb812deb 100644 --- a/email-templates/adminCommunityCreated.html +++ b/email-templates/adminCommunityCreated.html @@ -1,10 +1,24 @@ + + + + - New community: {{community.name}} + {{{subject}}} +
New community {{community.name}} created by {{user.name}} @@ -41,4 +55,5 @@
  • Email: {{user.email}}
  • - + + \ No newline at end of file diff --git a/email-templates/adminSlackImportProcessed.html b/email-templates/adminSlackImportProcessed.html index fc1f8a9df2..c4dc680321 100644 --- a/email-templates/adminSlackImportProcessed.html +++ b/email-templates/adminSlackImportProcessed.html @@ -1,10 +1,24 @@ + + + + - {{subject}} + {{{subject}}} +
    {{preheader}} @@ -41,4 +55,5 @@
  • Email: {{data.user.email}}
  • - + + \ No newline at end of file diff --git a/email-templates/adminToxicContent.html b/email-templates/adminToxicContent.html index 76aa140d88..5695f205dd 100644 --- a/email-templates/adminToxicContent.html +++ b/email-templates/adminToxicContent.html @@ -1,10 +1,24 @@ + + + + - {{subject}} + {{{subject}}} +
    {{preheader}} @@ -31,12 +45,8 @@

    Info:

      - {{#data.toxicityConfidence.spectrumPercent}} -
    • Spectrum: {{.}}% confident
    • - {{/data.toxicityConfidence.spectrumPercent}} - {{#data.toxicityConfidence.perspectivePercent}} -
    • Perspective: {{.}}% confident
    • +
    • Perspective: {{.}}% confident
    • {{/data.toxicityConfidence.perspectivePercent}}
    @@ -47,4 +57,5 @@
  • Channel: {{data.channel.name}}
  • - + + \ No newline at end of file diff --git a/email-templates/adminUserReported.html b/email-templates/adminUserReported.html new file mode 100644 index 0000000000..c73cf3899b --- /dev/null +++ b/email-templates/adminUserReported.html @@ -0,0 +1,53 @@ + + + + + + + + + + {{{subject}}} + + + +
    + {{preheader}} +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + +

    {{reportedUser.username}} was reported by {{reportingUser.username}} for reason: "{{reason}}"

    + + + + + \ No newline at end of file diff --git a/email-templates/adminUserSpammingThreadsNotification.html b/email-templates/adminUserSpammingThreadsNotification.html new file mode 100644 index 0000000000..efd27e962c --- /dev/null +++ b/email-templates/adminUserSpammingThreadsNotification.html @@ -0,0 +1,78 @@ + + + + + + + + + + {{{subject}}} + + + + +
    + {{preheader}} +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + +

    User:

    +

    {{data.user.name}}

    + +
    +
    + +

    Attempting to publish:

    +

    {{data.publishing.content.title}}

    +

    {{data.publishing.content.body}}

    + +
    +
    + +

    Previous threads published:

    + {{#each data.threads}} +

    {{content.title}}

    + {{/each}} + +
    +
    + +

    Publishing in

    + {{data.community.name}} / {{data.channel.name}} + + + \ No newline at end of file diff --git a/email-templates/communityCardExpiringWarning.html b/email-templates/communityCardExpiringWarning.html deleted file mode 100644 index d685c436e7..0000000000 --- a/email-templates/communityCardExpiringWarning.html +++ /dev/null @@ -1,432 +0,0 @@ - - - - - - {{subject}} - - - - -
    - {{preheader}} -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - - diff --git a/email-templates/communityInvite.html b/email-templates/communityInvite.html index 586986f4df..dabe44dc88 100644 --- a/email-templates/communityInvite.html +++ b/email-templates/communityInvite.html @@ -1,30 +1,43 @@ + + + - - - - You've been invited to join {{community.name}} on Spectrum - - - - -
    - Come join the conversation with {{sender.name}}! -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - - + + + + +
    + {{preheader}} +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + + + + + + + + \ No newline at end of file diff --git a/email-templates/communityInvoiceReceipt.html b/email-templates/communityInvoiceReceipt.html deleted file mode 100644 index 746257396a..0000000000 --- a/email-templates/communityInvoiceReceipt.html +++ /dev/null @@ -1,459 +0,0 @@ - - - - - - Spectrum Receipt - - - - -
    - Your payment for {{invoice.amount}} was received. -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - - diff --git a/email-templates/communityPaymentFailed.html b/email-templates/communityPaymentFailed.html deleted file mode 100644 index c9831efd9a..0000000000 --- a/email-templates/communityPaymentFailed.html +++ /dev/null @@ -1,432 +0,0 @@ - - - - - - {{subject}} - - - - -
    - {{preheader}} -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - - diff --git a/email-templates/communityPaymentSucceeded.html b/email-templates/communityPaymentSucceeded.html deleted file mode 100644 index 2a40542f1c..0000000000 --- a/email-templates/communityPaymentSucceeded.html +++ /dev/null @@ -1,528 +0,0 @@ - - - - - - {{subject}} - - - - -
    - {{preheader}} -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - - diff --git a/email-templates/mentionInMessage.html b/email-templates/mentionInMessage.html index f70a0576d0..12058901f0 100644 --- a/email-templates/mentionInMessage.html +++ b/email-templates/mentionInMessage.html @@ -1,30 +1,45 @@ + + + - - - - {{subject}} - - - - -
    - {{preheader}} -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - + + + + +
    + {{preheader}} +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + + + + + + + \ No newline at end of file diff --git a/email-templates/mentionInThread.html b/email-templates/mentionInThread.html index 3bedc0fa4e..895a3c2225 100644 --- a/email-templates/mentionInThread.html +++ b/email-templates/mentionInThread.html @@ -1,30 +1,45 @@ + + + - - - - {{subject}} - - - - -
    - {{preheader}} -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - + + + + +
    + {{preheader}} +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + + + + + + + \ No newline at end of file diff --git a/email-templates/newCommunityWelcome.html b/email-templates/newCommunityWelcome.html index 5c4c4de7a7..9a68bbcd07 100644 --- a/email-templates/newCommunityWelcome.html +++ b/email-templates/newCommunityWelcome.html @@ -1,15 +1,28 @@ + + + - - - - Your community is live on Spectrum! - - - - -
    - Your {{community.name}} is live on Spectrum! -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - - + + + + +
    + Your {{community.name}} is live on Spectrum! +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + + + + + + + + \ No newline at end of file diff --git a/email-templates/newDirectMessage.html b/email-templates/newDirectMessage.html index 4a04973cb5..5420d72344 100644 --- a/email-templates/newDirectMessage.html +++ b/email-templates/newDirectMessage.html @@ -1,15 +1,28 @@ + + + - - - - New direct message on Spectrum! - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/email-templates/newRepliesInThreads.html b/email-templates/newRepliesInThreads.html index b29190228f..47abba0c26 100644 --- a/email-templates/newRepliesInThreads.html +++ b/email-templates/newRepliesInThreads.html @@ -1,30 +1,45 @@ + + + - - - - {{subject}} - - - - -
    - {{preheader}} -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/email-templates/newThreadNotification.html b/email-templates/newThreadNotification.html index e464360fac..8291c982a5 100644 --- a/email-templates/newThreadNotification.html +++ b/email-templates/newThreadNotification.html @@ -1,30 +1,45 @@ + + + - - - - {{subject}} - - - - -
    - {{preheader}} -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - + + + + +
    + {{preheader}} +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + + + + + + + \ No newline at end of file diff --git a/email-templates/newUserWelcome.html b/email-templates/newUserWelcome.html index 1dafd93047..33e934ae40 100644 --- a/email-templates/newUserWelcome.html +++ b/email-templates/newUserWelcome.html @@ -1,30 +1,43 @@ + + + - - - - Welcome to Spectrum! - - - - -
    - Here's what you need to know to get started on Spectrum... -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - - + + + + +
    + Here's what you need to know to get started on Spectrum... +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + + + + + + + + \ No newline at end of file diff --git a/email-templates/privateChannelRequestApproved.html b/email-templates/privateChannelRequestApproved.html index 7cdd0bc3c9..50d5fdc86e 100644 --- a/email-templates/privateChannelRequestApproved.html +++ b/email-templates/privateChannelRequestApproved.html @@ -1,30 +1,45 @@ + + + - - - - {{subject}} - - - - -
    - {{preheader}} -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - - + + + + +
    + {{preheader}} +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + + + + + + + + \ No newline at end of file diff --git a/email-templates/privateChannelRequestSent.html b/email-templates/privateChannelRequestSent.html index 8a0c66ad2c..1ad228fdf9 100644 --- a/email-templates/privateChannelRequestSent.html +++ b/email-templates/privateChannelRequestSent.html @@ -1,30 +1,45 @@ + + + - - - - {{subject}} - - - - -
    - {{preheader}} -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - - + + + + +
    + {{preheader}} +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + + + + + + + + \ No newline at end of file diff --git a/email-templates/privateCommunityRequestApproved.html b/email-templates/privateCommunityRequestApproved.html new file mode 100644 index 0000000000..5ea957d64f --- /dev/null +++ b/email-templates/privateCommunityRequestApproved.html @@ -0,0 +1,431 @@ + + + + + + + + + + {{ subject }} + + + + + +
    + {{preheader}} +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + + + + + + + + \ No newline at end of file diff --git a/email-templates/privateCommunityRequestSent.html b/email-templates/privateCommunityRequestSent.html new file mode 100644 index 0000000000..527016e334 --- /dev/null +++ b/email-templates/privateCommunityRequestSent.html @@ -0,0 +1,439 @@ + + + + + + + + + + {{ subject }} + + + + +
    + {{preheader}} +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + + + + + + + + \ No newline at end of file diff --git a/email-templates/proInvoiceReceipt.html b/email-templates/proInvoiceReceipt.html deleted file mode 100644 index ca3219f341..0000000000 --- a/email-templates/proInvoiceReceipt.html +++ /dev/null @@ -1,459 +0,0 @@ - - - - - - Spectrum Receipt - - - - -
    - Your payment for {{invoice.amount}} was received. -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - - diff --git a/email-templates/validate-community-administrator-email.html b/email-templates/validate-community-administrator-email.html index e4fa9c38b8..13c3af3695 100644 --- a/email-templates/validate-community-administrator-email.html +++ b/email-templates/validate-community-administrator-email.html @@ -1,30 +1,42 @@ + + + - - - - {{ subject }}} - - - - -
    - {{preheader}} -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - - + + + + +
    + {{preheader}} +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + + + + + + + + \ No newline at end of file diff --git a/email-templates/validate-email.html b/email-templates/validate-email.html index 9248d360f3..ee3ec6865a 100644 --- a/email-templates/validate-email.html +++ b/email-templates/validate-email.html @@ -1,30 +1,43 @@ + + + - - - - Confirm your email address on Spectrum - - - - -
    - Confirm your email address in one click to start getting updates -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - - + + + + +
    + Confirm your email address in one click to start getting updates +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + + + + + + + + \ No newline at end of file diff --git a/email-templates/weeklyDigest.html b/email-templates/weeklyDigest.html index 8c8ac51744..7197c52955 100644 --- a/email-templates/weeklyDigest.html +++ b/email-templates/weeklyDigest.html @@ -1,30 +1,45 @@ + + + - - - - {{subject}} - - - - -
    - {{preheader}} -
    - - -
    -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  -
    - - - - - - - - + + .reputation-string { + margin-bottom: 24px; + color: #16171A; + } + + + + +
    + {{preheader}} +
    + + +
    +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
    + + + + + + + + + \ No newline at end of file diff --git a/flow-typed/npm/@sendgrid/mail_vx.x.x.js b/flow-typed/npm/@sendgrid/mail_vx.x.x.js new file mode 100644 index 0000000000..4535cd2dba --- /dev/null +++ b/flow-typed/npm/@sendgrid/mail_vx.x.x.js @@ -0,0 +1,52 @@ +// flow-typed signature: 9853dc0239ef405daa7097373b292e98 +// flow-typed version: <>/@sendgrid/mail_v6.3.1/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * '@sendgrid/mail' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module '@sendgrid/mail' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module '@sendgrid/mail/src/classes/mail-service' { + declare module.exports: any; +} + +declare module '@sendgrid/mail/src/mail' { + declare module.exports: any; +} + +declare module '@sendgrid/mail/src/mail.spec' { + declare module.exports: any; +} + +// Filename aliases +declare module '@sendgrid/mail/index' { + declare module.exports: $Exports<'@sendgrid/mail'>; +} +declare module '@sendgrid/mail/index.js' { + declare module.exports: $Exports<'@sendgrid/mail'>; +} +declare module '@sendgrid/mail/src/classes/mail-service.js' { + declare module.exports: $Exports<'@sendgrid/mail/src/classes/mail-service'>; +} +declare module '@sendgrid/mail/src/mail.js' { + declare module.exports: $Exports<'@sendgrid/mail/src/mail'>; +} +declare module '@sendgrid/mail/src/mail.spec.js' { + declare module.exports: $Exports<'@sendgrid/mail/src/mail.spec'>; +} diff --git a/flow-typed/npm/amplitude_vx.x.x.js b/flow-typed/npm/amplitude_vx.x.x.js new file mode 100644 index 0000000000..7d3d5c7570 --- /dev/null +++ b/flow-typed/npm/amplitude_vx.x.x.js @@ -0,0 +1,32 @@ +// flow-typed signature: fed729f4c8392f815391ae2316878ed8 +// flow-typed version: <>/amplitude_v3.5.0/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'amplitude' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'amplitude' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'amplitude/amplitude' { + declare module.exports: any; +} + +// Filename aliases +declare module 'amplitude/amplitude.js' { + declare module.exports: $Exports<'amplitude/amplitude'>; +} diff --git a/flow-typed/npm/apollo-link-schema_vx.x.x.js b/flow-typed/npm/apollo-link-schema_vx.x.x.js new file mode 100644 index 0000000000..e821b43740 --- /dev/null +++ b/flow-typed/npm/apollo-link-schema_vx.x.x.js @@ -0,0 +1,53 @@ +// flow-typed signature: 61e2076f557fee75ba8e0dd7a7f21ae8 +// flow-typed version: <>/apollo-link-schema_v1.1.1/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'apollo-link-schema' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'apollo-link-schema' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'apollo-link-schema/lib/bundle.umd' { + declare module.exports: any; +} + +declare module 'apollo-link-schema/lib/index' { + declare module.exports: any; +} + +declare module 'apollo-link-schema/lib/schemaLink' { + declare module.exports: any; +} + +declare module 'apollo-link-schema/rollup.config' { + declare module.exports: any; +} + +// Filename aliases +declare module 'apollo-link-schema/lib/bundle.umd.js' { + declare module.exports: $Exports<'apollo-link-schema/lib/bundle.umd'>; +} +declare module 'apollo-link-schema/lib/index.js' { + declare module.exports: $Exports<'apollo-link-schema/lib/index'>; +} +declare module 'apollo-link-schema/lib/schemaLink.js' { + declare module.exports: $Exports<'apollo-link-schema/lib/schemaLink'>; +} +declare module 'apollo-link-schema/rollup.config.js' { + declare module.exports: $Exports<'apollo-link-schema/rollup.config'>; +} diff --git a/flow-typed/npm/apollo-server-express_vx.x.x.js b/flow-typed/npm/apollo-server-express_vx.x.x.js new file mode 100644 index 0000000000..8710fb295b --- /dev/null +++ b/flow-typed/npm/apollo-server-express_vx.x.x.js @@ -0,0 +1,53 @@ +// flow-typed signature: f45ccc8a02282556985652543a099375 +// flow-typed version: <>/apollo-server-express_v2.0.4/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'apollo-server-express' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'apollo-server-express' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'apollo-server-express/dist/ApolloServer' { + declare module.exports: any; +} + +declare module 'apollo-server-express/dist/connectApollo' { + declare module.exports: any; +} + +declare module 'apollo-server-express/dist/expressApollo' { + declare module.exports: any; +} + +declare module 'apollo-server-express/dist/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'apollo-server-express/dist/ApolloServer.js' { + declare module.exports: $Exports<'apollo-server-express/dist/ApolloServer'>; +} +declare module 'apollo-server-express/dist/connectApollo.js' { + declare module.exports: $Exports<'apollo-server-express/dist/connectApollo'>; +} +declare module 'apollo-server-express/dist/expressApollo.js' { + declare module.exports: $Exports<'apollo-server-express/dist/expressApollo'>; +} +declare module 'apollo-server-express/dist/index.js' { + declare module.exports: $Exports<'apollo-server-express/dist/index'>; +} diff --git a/flow-typed/npm/b2a_vx.x.x.js b/flow-typed/npm/b2a_vx.x.x.js new file mode 100644 index 0000000000..b85865a586 --- /dev/null +++ b/flow-typed/npm/b2a_vx.x.x.js @@ -0,0 +1,32 @@ +// flow-typed signature: 458f54b9229cd74dc54679aad2fb6f13 +// flow-typed version: <>/b2a_v1.0.10/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'b2a' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'b2a' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'b2a/lib/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'b2a/lib/index.js' { + declare module.exports: $Exports<'b2a/lib/index'>; +} diff --git a/flow-typed/npm/bluebird_vx.x.x.js b/flow-typed/npm/bluebird_vx.x.x.js new file mode 100644 index 0000000000..1d7a969a60 --- /dev/null +++ b/flow-typed/npm/bluebird_vx.x.x.js @@ -0,0 +1,312 @@ +// flow-typed signature: fd6015492cc31b2750e3b5e1b048858f +// flow-typed version: <>/bluebird_v3.5.3/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'bluebird' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'bluebird' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'bluebird/js/browser/bluebird.core' { + declare module.exports: any; +} + +declare module 'bluebird/js/browser/bluebird.core.min' { + declare module.exports: any; +} + +declare module 'bluebird/js/browser/bluebird' { + declare module.exports: any; +} + +declare module 'bluebird/js/browser/bluebird.min' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/any' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/assert' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/async' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/bind' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/bluebird' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/call_get' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/cancel' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/catch_filter' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/context' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/debuggability' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/direct_resolve' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/each' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/errors' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/es5' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/filter' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/finally' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/generators' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/join' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/map' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/method' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/nodeback' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/nodeify' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/promise_array' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/promise' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/promisify' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/props' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/queue' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/race' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/reduce' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/schedule' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/settle' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/some' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/synchronous_inspection' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/thenables' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/timers' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/using' { + declare module.exports: any; +} + +declare module 'bluebird/js/release/util' { + declare module.exports: any; +} + +// Filename aliases +declare module 'bluebird/js/browser/bluebird.core.js' { + declare module.exports: $Exports<'bluebird/js/browser/bluebird.core'>; +} +declare module 'bluebird/js/browser/bluebird.core.min.js' { + declare module.exports: $Exports<'bluebird/js/browser/bluebird.core.min'>; +} +declare module 'bluebird/js/browser/bluebird.js' { + declare module.exports: $Exports<'bluebird/js/browser/bluebird'>; +} +declare module 'bluebird/js/browser/bluebird.min.js' { + declare module.exports: $Exports<'bluebird/js/browser/bluebird.min'>; +} +declare module 'bluebird/js/release/any.js' { + declare module.exports: $Exports<'bluebird/js/release/any'>; +} +declare module 'bluebird/js/release/assert.js' { + declare module.exports: $Exports<'bluebird/js/release/assert'>; +} +declare module 'bluebird/js/release/async.js' { + declare module.exports: $Exports<'bluebird/js/release/async'>; +} +declare module 'bluebird/js/release/bind.js' { + declare module.exports: $Exports<'bluebird/js/release/bind'>; +} +declare module 'bluebird/js/release/bluebird.js' { + declare module.exports: $Exports<'bluebird/js/release/bluebird'>; +} +declare module 'bluebird/js/release/call_get.js' { + declare module.exports: $Exports<'bluebird/js/release/call_get'>; +} +declare module 'bluebird/js/release/cancel.js' { + declare module.exports: $Exports<'bluebird/js/release/cancel'>; +} +declare module 'bluebird/js/release/catch_filter.js' { + declare module.exports: $Exports<'bluebird/js/release/catch_filter'>; +} +declare module 'bluebird/js/release/context.js' { + declare module.exports: $Exports<'bluebird/js/release/context'>; +} +declare module 'bluebird/js/release/debuggability.js' { + declare module.exports: $Exports<'bluebird/js/release/debuggability'>; +} +declare module 'bluebird/js/release/direct_resolve.js' { + declare module.exports: $Exports<'bluebird/js/release/direct_resolve'>; +} +declare module 'bluebird/js/release/each.js' { + declare module.exports: $Exports<'bluebird/js/release/each'>; +} +declare module 'bluebird/js/release/errors.js' { + declare module.exports: $Exports<'bluebird/js/release/errors'>; +} +declare module 'bluebird/js/release/es5.js' { + declare module.exports: $Exports<'bluebird/js/release/es5'>; +} +declare module 'bluebird/js/release/filter.js' { + declare module.exports: $Exports<'bluebird/js/release/filter'>; +} +declare module 'bluebird/js/release/finally.js' { + declare module.exports: $Exports<'bluebird/js/release/finally'>; +} +declare module 'bluebird/js/release/generators.js' { + declare module.exports: $Exports<'bluebird/js/release/generators'>; +} +declare module 'bluebird/js/release/join.js' { + declare module.exports: $Exports<'bluebird/js/release/join'>; +} +declare module 'bluebird/js/release/map.js' { + declare module.exports: $Exports<'bluebird/js/release/map'>; +} +declare module 'bluebird/js/release/method.js' { + declare module.exports: $Exports<'bluebird/js/release/method'>; +} +declare module 'bluebird/js/release/nodeback.js' { + declare module.exports: $Exports<'bluebird/js/release/nodeback'>; +} +declare module 'bluebird/js/release/nodeify.js' { + declare module.exports: $Exports<'bluebird/js/release/nodeify'>; +} +declare module 'bluebird/js/release/promise_array.js' { + declare module.exports: $Exports<'bluebird/js/release/promise_array'>; +} +declare module 'bluebird/js/release/promise.js' { + declare module.exports: $Exports<'bluebird/js/release/promise'>; +} +declare module 'bluebird/js/release/promisify.js' { + declare module.exports: $Exports<'bluebird/js/release/promisify'>; +} +declare module 'bluebird/js/release/props.js' { + declare module.exports: $Exports<'bluebird/js/release/props'>; +} +declare module 'bluebird/js/release/queue.js' { + declare module.exports: $Exports<'bluebird/js/release/queue'>; +} +declare module 'bluebird/js/release/race.js' { + declare module.exports: $Exports<'bluebird/js/release/race'>; +} +declare module 'bluebird/js/release/reduce.js' { + declare module.exports: $Exports<'bluebird/js/release/reduce'>; +} +declare module 'bluebird/js/release/schedule.js' { + declare module.exports: $Exports<'bluebird/js/release/schedule'>; +} +declare module 'bluebird/js/release/settle.js' { + declare module.exports: $Exports<'bluebird/js/release/settle'>; +} +declare module 'bluebird/js/release/some.js' { + declare module.exports: $Exports<'bluebird/js/release/some'>; +} +declare module 'bluebird/js/release/synchronous_inspection.js' { + declare module.exports: $Exports<'bluebird/js/release/synchronous_inspection'>; +} +declare module 'bluebird/js/release/thenables.js' { + declare module.exports: $Exports<'bluebird/js/release/thenables'>; +} +declare module 'bluebird/js/release/timers.js' { + declare module.exports: $Exports<'bluebird/js/release/timers'>; +} +declare module 'bluebird/js/release/using.js' { + declare module.exports: $Exports<'bluebird/js/release/using'>; +} +declare module 'bluebird/js/release/util.js' { + declare module.exports: $Exports<'bluebird/js/release/util'>; +} diff --git a/flow-typed/npm/cryptr_vx.x.x.js b/flow-typed/npm/cryptr_vx.x.x.js new file mode 100644 index 0000000000..f49155b458 --- /dev/null +++ b/flow-typed/npm/cryptr_vx.x.x.js @@ -0,0 +1,38 @@ +// flow-typed signature: 75dc078152e6e97382b34bbe99dcaf4f +// flow-typed version: <>/cryptr_v3.0.0/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'cryptr' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'cryptr' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'cryptr/tests/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'cryptr/index' { + declare module.exports: $Exports<'cryptr'>; +} +declare module 'cryptr/index.js' { + declare module.exports: $Exports<'cryptr'>; +} +declare module 'cryptr/tests/index.js' { + declare module.exports: $Exports<'cryptr/tests/index'>; +} diff --git a/flow-typed/npm/datadog-metrics_vx.x.x.js b/flow-typed/npm/datadog-metrics_vx.x.x.js new file mode 100644 index 0000000000..1bbb2fa429 --- /dev/null +++ b/flow-typed/npm/datadog-metrics_vx.x.x.js @@ -0,0 +1,59 @@ +// flow-typed signature: 4193c068fe558edea2fe8b024db8448c +// flow-typed version: <>/datadog-metrics_v0.8.1/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'datadog-metrics' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'datadog-metrics' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'datadog-metrics/lib/aggregators' { + declare module.exports: any; +} + +declare module 'datadog-metrics/lib/loggers' { + declare module.exports: any; +} + +declare module 'datadog-metrics/lib/metrics' { + declare module.exports: any; +} + +declare module 'datadog-metrics/lib/reporters' { + declare module.exports: any; +} + +// Filename aliases +declare module 'datadog-metrics/index' { + declare module.exports: $Exports<'datadog-metrics'>; +} +declare module 'datadog-metrics/index.js' { + declare module.exports: $Exports<'datadog-metrics'>; +} +declare module 'datadog-metrics/lib/aggregators.js' { + declare module.exports: $Exports<'datadog-metrics/lib/aggregators'>; +} +declare module 'datadog-metrics/lib/loggers.js' { + declare module.exports: $Exports<'datadog-metrics/lib/loggers'>; +} +declare module 'datadog-metrics/lib/metrics.js' { + declare module.exports: $Exports<'datadog-metrics/lib/metrics'>; +} +declare module 'datadog-metrics/lib/reporters.js' { + declare module.exports: $Exports<'datadog-metrics/lib/reporters'>; +} diff --git a/flow-typed/npm/dataloader_vx.x.x.js b/flow-typed/npm/dataloader_vx.x.x.js new file mode 100644 index 0000000000..8afa5c5b89 --- /dev/null +++ b/flow-typed/npm/dataloader_vx.x.x.js @@ -0,0 +1,33 @@ +// flow-typed signature: 62d3fa71e344ce71db81037ecb6264ae +// flow-typed version: <>/dataloader_v1.4.0/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'dataloader' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'dataloader' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ + + +// Filename aliases +declare module 'dataloader/index' { + declare module.exports: $Exports<'dataloader'>; +} +declare module 'dataloader/index.js' { + declare module.exports: $Exports<'dataloader'>; +} diff --git a/flow-typed/npm/decode-uri-component_vx.x.x.js b/flow-typed/npm/decode-uri-component_vx.x.x.js new file mode 100644 index 0000000000..bde8488de3 --- /dev/null +++ b/flow-typed/npm/decode-uri-component_vx.x.x.js @@ -0,0 +1,33 @@ +// flow-typed signature: faef99bbaaff986e9b4077753c74e5b6 +// flow-typed version: <>/decode-uri-component_v0.2.0/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'decode-uri-component' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'decode-uri-component' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ + + +// Filename aliases +declare module 'decode-uri-component/index' { + declare module.exports: $Exports<'decode-uri-component'>; +} +declare module 'decode-uri-component/index.js' { + declare module.exports: $Exports<'decode-uri-component'>; +} diff --git a/flow-typed/npm/draft-js-export-markdown_vx.x.x.js b/flow-typed/npm/draft-js-export-markdown_vx.x.x.js new file mode 100644 index 0000000000..222c52e49e --- /dev/null +++ b/flow-typed/npm/draft-js-export-markdown_vx.x.x.js @@ -0,0 +1,39 @@ +// flow-typed signature: 62c300788f61ec7382ffcd7ba580e1e3 +// flow-typed version: <>/draft-js-export-markdown_vx.x.x/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'draft-js-export-markdown' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'draft-js-export-markdown' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'draft-js-export-markdown/lib/main' { + declare module.exports: any; +} + +declare module 'draft-js-export-markdown/lib/stateToMarkdown' { + declare module.exports: any; +} + +// Filename aliases +declare module 'draft-js-export-markdown/lib/main.js' { + declare module.exports: $Exports<'draft-js-export-markdown/lib/main'>; +} +declare module 'draft-js-export-markdown/lib/stateToMarkdown.js' { + declare module.exports: $Exports<'draft-js-export-markdown/lib/stateToMarkdown'>; +} diff --git a/flow-typed/npm/react-navigation_vx.x.x.js b/flow-typed/npm/electron-context-menu_vx.x.x.js similarity index 60% rename from flow-typed/npm/react-navigation_vx.x.x.js rename to flow-typed/npm/electron-context-menu_vx.x.x.js index 7ef53e8b23..efae92b65f 100644 --- a/flow-typed/npm/react-navigation_vx.x.x.js +++ b/flow-typed/npm/electron-context-menu_vx.x.x.js @@ -1,10 +1,10 @@ -// flow-typed signature: e67b2128378a7fb62bd52e66a117c624 -// flow-typed version: <>/react-navigation_v1.0.0-beta.26/flow_v0.63.1 +// flow-typed signature: fa2daa44307abd285e881b992fa9d76c +// flow-typed version: <>/electron-context-menu_v0.9.1/flow_v0.66.0 /** * This is an autogenerated libdef stub for: * - * 'react-navigation' + * 'electron-context-menu' * * Fill this stub out by replacing all the `any` types. * @@ -13,6 +13,6 @@ * https://github.com/flowtype/flow-typed */ -declare module 'react-navigation' { +declare module 'electron-context-menu' { declare module.exports: any; } diff --git a/flow-typed/npm/electron-is-dev_vx.x.x.js b/flow-typed/npm/electron-is-dev_vx.x.x.js new file mode 100644 index 0000000000..f5228c6e82 --- /dev/null +++ b/flow-typed/npm/electron-is-dev_vx.x.x.js @@ -0,0 +1,33 @@ +// flow-typed signature: 56cb87472f7011e6a22325af9a351724 +// flow-typed version: <>/electron-is-dev_v1.0.1/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'electron-is-dev' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'electron-is-dev' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ + + +// Filename aliases +declare module 'electron-is-dev/index' { + declare module.exports: $Exports<'electron-is-dev'>; +} +declare module 'electron-is-dev/index.js' { + declare module.exports: $Exports<'electron-is-dev'>; +} diff --git a/flow-typed/npm/electron-updater_vx.x.x.js b/flow-typed/npm/electron-updater_vx.x.x.js new file mode 100644 index 0000000000..d84baf6c66 --- /dev/null +++ b/flow-typed/npm/electron-updater_vx.x.x.js @@ -0,0 +1,186 @@ +// flow-typed signature: be94c12fa70b80b91f6bef7efa92a8fe +// flow-typed version: <>/electron-updater_v4.0.5/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'electron-updater' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'electron-updater' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'electron-updater/out/AppAdapter' { + declare module.exports: any; +} + +declare module 'electron-updater/out/AppImageUpdater' { + declare module.exports: any; +} + +declare module 'electron-updater/out/AppUpdater' { + declare module.exports: any; +} + +declare module 'electron-updater/out/BaseUpdater' { + declare module.exports: any; +} + +declare module 'electron-updater/out/differentialDownloader/DataSplitter' { + declare module.exports: any; +} + +declare module 'electron-updater/out/differentialDownloader/DifferentialDownloader' { + declare module.exports: any; +} + +declare module 'electron-updater/out/differentialDownloader/downloadPlanBuilder' { + declare module.exports: any; +} + +declare module 'electron-updater/out/differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader' { + declare module.exports: any; +} + +declare module 'electron-updater/out/differentialDownloader/GenericDifferentialDownloader' { + declare module.exports: any; +} + +declare module 'electron-updater/out/differentialDownloader/multipleRangeDownloader' { + declare module.exports: any; +} + +declare module 'electron-updater/out/DownloadedUpdateHelper' { + declare module.exports: any; +} + +declare module 'electron-updater/out/ElectronAppAdapter' { + declare module.exports: any; +} + +declare module 'electron-updater/out/electronHttpExecutor' { + declare module.exports: any; +} + +declare module 'electron-updater/out/MacUpdater' { + declare module.exports: any; +} + +declare module 'electron-updater/out/main' { + declare module.exports: any; +} + +declare module 'electron-updater/out/NsisUpdater' { + declare module.exports: any; +} + +declare module 'electron-updater/out/providerFactory' { + declare module.exports: any; +} + +declare module 'electron-updater/out/providers/BintrayProvider' { + declare module.exports: any; +} + +declare module 'electron-updater/out/providers/GenericProvider' { + declare module.exports: any; +} + +declare module 'electron-updater/out/providers/GitHubProvider' { + declare module.exports: any; +} + +declare module 'electron-updater/out/providers/PrivateGitHubProvider' { + declare module.exports: any; +} + +declare module 'electron-updater/out/providers/Provider' { + declare module.exports: any; +} + +declare module 'electron-updater/out/windowsExecutableCodeSignatureVerifier' { + declare module.exports: any; +} + +// Filename aliases +declare module 'electron-updater/out/AppAdapter.js' { + declare module.exports: $Exports<'electron-updater/out/AppAdapter'>; +} +declare module 'electron-updater/out/AppImageUpdater.js' { + declare module.exports: $Exports<'electron-updater/out/AppImageUpdater'>; +} +declare module 'electron-updater/out/AppUpdater.js' { + declare module.exports: $Exports<'electron-updater/out/AppUpdater'>; +} +declare module 'electron-updater/out/BaseUpdater.js' { + declare module.exports: $Exports<'electron-updater/out/BaseUpdater'>; +} +declare module 'electron-updater/out/differentialDownloader/DataSplitter.js' { + declare module.exports: $Exports<'electron-updater/out/differentialDownloader/DataSplitter'>; +} +declare module 'electron-updater/out/differentialDownloader/DifferentialDownloader.js' { + declare module.exports: $Exports<'electron-updater/out/differentialDownloader/DifferentialDownloader'>; +} +declare module 'electron-updater/out/differentialDownloader/downloadPlanBuilder.js' { + declare module.exports: $Exports<'electron-updater/out/differentialDownloader/downloadPlanBuilder'>; +} +declare module 'electron-updater/out/differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader.js' { + declare module.exports: $Exports<'electron-updater/out/differentialDownloader/FileWithEmbeddedBlockMapDifferentialDownloader'>; +} +declare module 'electron-updater/out/differentialDownloader/GenericDifferentialDownloader.js' { + declare module.exports: $Exports<'electron-updater/out/differentialDownloader/GenericDifferentialDownloader'>; +} +declare module 'electron-updater/out/differentialDownloader/multipleRangeDownloader.js' { + declare module.exports: $Exports<'electron-updater/out/differentialDownloader/multipleRangeDownloader'>; +} +declare module 'electron-updater/out/DownloadedUpdateHelper.js' { + declare module.exports: $Exports<'electron-updater/out/DownloadedUpdateHelper'>; +} +declare module 'electron-updater/out/ElectronAppAdapter.js' { + declare module.exports: $Exports<'electron-updater/out/ElectronAppAdapter'>; +} +declare module 'electron-updater/out/electronHttpExecutor.js' { + declare module.exports: $Exports<'electron-updater/out/electronHttpExecutor'>; +} +declare module 'electron-updater/out/MacUpdater.js' { + declare module.exports: $Exports<'electron-updater/out/MacUpdater'>; +} +declare module 'electron-updater/out/main.js' { + declare module.exports: $Exports<'electron-updater/out/main'>; +} +declare module 'electron-updater/out/NsisUpdater.js' { + declare module.exports: $Exports<'electron-updater/out/NsisUpdater'>; +} +declare module 'electron-updater/out/providerFactory.js' { + declare module.exports: $Exports<'electron-updater/out/providerFactory'>; +} +declare module 'electron-updater/out/providers/BintrayProvider.js' { + declare module.exports: $Exports<'electron-updater/out/providers/BintrayProvider'>; +} +declare module 'electron-updater/out/providers/GenericProvider.js' { + declare module.exports: $Exports<'electron-updater/out/providers/GenericProvider'>; +} +declare module 'electron-updater/out/providers/GitHubProvider.js' { + declare module.exports: $Exports<'electron-updater/out/providers/GitHubProvider'>; +} +declare module 'electron-updater/out/providers/PrivateGitHubProvider.js' { + declare module.exports: $Exports<'electron-updater/out/providers/PrivateGitHubProvider'>; +} +declare module 'electron-updater/out/providers/Provider.js' { + declare module.exports: $Exports<'electron-updater/out/providers/Provider'>; +} +declare module 'electron-updater/out/windowsExecutableCodeSignatureVerifier.js' { + declare module.exports: $Exports<'electron-updater/out/windowsExecutableCodeSignatureVerifier'>; +} diff --git a/flow-typed/npm/react-native-typography_vx.x.x.js b/flow-typed/npm/electron-window-state_vx.x.x.js similarity index 59% rename from flow-typed/npm/react-native-typography_vx.x.x.js rename to flow-typed/npm/electron-window-state_vx.x.x.js index d72ca945d9..9f887ac608 100644 --- a/flow-typed/npm/react-native-typography_vx.x.x.js +++ b/flow-typed/npm/electron-window-state_vx.x.x.js @@ -1,10 +1,10 @@ -// flow-typed signature: f9d66914c978f2b6c7fd409a7b9b9b42 -// flow-typed version: <>/react-native-typography_v1.2.1/flow_v0.63.1 +// flow-typed signature: 77130022186123cfc81d76787330354e +// flow-typed version: <>/electron-window-state_vx.x.x/flow_v0.66.0 /** * This is an autogenerated libdef stub for: * - * 'react-native-typography' + * 'electron-window-state' * * Fill this stub out by replacing all the `any` types. * @@ -13,6 +13,6 @@ * https://github.com/flowtype/flow-typed */ -declare module 'react-native-typography' { +declare module 'electron-window-state' { declare module.exports: any; } diff --git a/flow-typed/npm/electron_vx.x.x.js b/flow-typed/npm/electron_vx.x.x.js new file mode 100644 index 0000000000..84da4b2d0d --- /dev/null +++ b/flow-typed/npm/electron_vx.x.x.js @@ -0,0 +1,45 @@ +// flow-typed signature: 8dd143dc1ccb8d078e45dd9625583512 +// flow-typed version: <>/electron_v3.0.10/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'electron' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'electron' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'electron/cli' { + declare module.exports: any; +} + +declare module 'electron/install' { + declare module.exports: any; +} + +// Filename aliases +declare module 'electron/cli.js' { + declare module.exports: $Exports<'electron/cli'>; +} +declare module 'electron/index' { + declare module.exports: $Exports<'electron'>; +} +declare module 'electron/index.js' { + declare module.exports: $Exports<'electron'>; +} +declare module 'electron/install.js' { + declare module.exports: $Exports<'electron/install'>; +} diff --git a/flow-typed/npm/express-enforces-ssl_vx.x.x.js b/flow-typed/npm/express-enforces-ssl_vx.x.x.js new file mode 100644 index 0000000000..452b146321 --- /dev/null +++ b/flow-typed/npm/express-enforces-ssl_vx.x.x.js @@ -0,0 +1,33 @@ +// flow-typed signature: e36181803629e2d4387bbfd37640c8e6 +// flow-typed version: <>/express-enforces-ssl_v1.1.0/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'express-enforces-ssl' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'express-enforces-ssl' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ + + +// Filename aliases +declare module 'express-enforces-ssl/index' { + declare module.exports: $Exports<'express-enforces-ssl'>; +} +declare module 'express-enforces-ssl/index.js' { + declare module.exports: $Exports<'express-enforces-ssl'>; +} diff --git a/flow-typed/npm/express-hot-shots_vx.x.x.js b/flow-typed/npm/express-hot-shots_vx.x.x.js new file mode 100644 index 0000000000..0e866fd66a --- /dev/null +++ b/flow-typed/npm/express-hot-shots_vx.x.x.js @@ -0,0 +1,53 @@ +// flow-typed signature: 1cdaa5e3b96efaeacdd3cbc4b45b40d3 +// flow-typed version: <>/express-hot-shots_vx.x.x/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'express-hot-shots' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'express-hot-shots' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'express-hot-shots/Gruntfile' { + declare module.exports: any; +} + +declare module 'express-hot-shots/lib/express-hot-shots' { + declare module.exports: any; +} + +declare module 'express-hot-shots/test/express-hot-shots' { + declare module.exports: any; +} + +declare module 'express-hot-shots/test/utils' { + declare module.exports: any; +} + +// Filename aliases +declare module 'express-hot-shots/Gruntfile.js' { + declare module.exports: $Exports<'express-hot-shots/Gruntfile'>; +} +declare module 'express-hot-shots/lib/express-hot-shots.js' { + declare module.exports: $Exports<'express-hot-shots/lib/express-hot-shots'>; +} +declare module 'express-hot-shots/test/express-hot-shots.js' { + declare module.exports: $Exports<'express-hot-shots/test/express-hot-shots'>; +} +declare module 'express-hot-shots/test/utils.js' { + declare module.exports: $Exports<'express-hot-shots/test/utils'>; +} diff --git a/flow-typed/npm/graphql-rate-limit_vx.x.x.js b/flow-typed/npm/graphql-rate-limit_vx.x.x.js new file mode 100644 index 0000000000..a8ee115b8c --- /dev/null +++ b/flow-typed/npm/graphql-rate-limit_vx.x.x.js @@ -0,0 +1,130 @@ +// flow-typed signature: 690cd5de27aeb0c40ca045d36dffdc7b +// flow-typed version: <>/graphql-rate-limit_vx.x/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'graphql-rate-limit' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'graphql-rate-limit' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'graphql-rate-limit/build/main/index' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/build/main/lib/field-directive' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/build/main/lib/in-memory-store' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/build/main/lib/rate-limit-error' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/build/main/lib/redis-store' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/build/main/lib/store' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/build/main/lib/types' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/build/module/index' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/build/module/lib/field-directive' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/build/module/lib/in-memory-store' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/build/module/lib/rate-limit-error' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/build/module/lib/redis-store' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/build/module/lib/store' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/build/module/lib/types' { + declare module.exports: any; +} + +declare module 'graphql-rate-limit/example/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'graphql-rate-limit/build/main/index.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/main/index'>; +} +declare module 'graphql-rate-limit/build/main/lib/field-directive.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/main/lib/field-directive'>; +} +declare module 'graphql-rate-limit/build/main/lib/in-memory-store.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/main/lib/in-memory-store'>; +} +declare module 'graphql-rate-limit/build/main/lib/rate-limit-error.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/main/lib/rate-limit-error'>; +} +declare module 'graphql-rate-limit/build/main/lib/redis-store.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/main/lib/redis-store'>; +} +declare module 'graphql-rate-limit/build/main/lib/store.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/main/lib/store'>; +} +declare module 'graphql-rate-limit/build/main/lib/types.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/main/lib/types'>; +} +declare module 'graphql-rate-limit/build/module/index.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/module/index'>; +} +declare module 'graphql-rate-limit/build/module/lib/field-directive.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/module/lib/field-directive'>; +} +declare module 'graphql-rate-limit/build/module/lib/in-memory-store.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/module/lib/in-memory-store'>; +} +declare module 'graphql-rate-limit/build/module/lib/rate-limit-error.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/module/lib/rate-limit-error'>; +} +declare module 'graphql-rate-limit/build/module/lib/redis-store.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/module/lib/redis-store'>; +} +declare module 'graphql-rate-limit/build/module/lib/store.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/module/lib/store'>; +} +declare module 'graphql-rate-limit/build/module/lib/types.js' { + declare module.exports: $Exports<'graphql-rate-limit/build/module/lib/types'>; +} +declare module 'graphql-rate-limit/example/index.js' { + declare module.exports: $Exports<'graphql-rate-limit/example/index'>; +} diff --git a/flow-typed/npm/helmet_vx.x.x.js b/flow-typed/npm/helmet_vx.x.x.js new file mode 100644 index 0000000000..54145ca294 --- /dev/null +++ b/flow-typed/npm/helmet_vx.x.x.js @@ -0,0 +1,33 @@ +// flow-typed signature: 19d40d2c859c3e988d6ee67e3677f306 +// flow-typed version: <>/helmet_v3.14.0/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'helmet' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'helmet' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ + + +// Filename aliases +declare module 'helmet/index' { + declare module.exports: $Exports<'helmet'>; +} +declare module 'helmet/index.js' { + declare module.exports: $Exports<'helmet'>; +} diff --git a/flow-typed/npm/host-validation_vx.x.x.js b/flow-typed/npm/host-validation_vx.x.x.js new file mode 100644 index 0000000000..efaa6c172f --- /dev/null +++ b/flow-typed/npm/host-validation_vx.x.x.js @@ -0,0 +1,45 @@ +// flow-typed signature: fc353630324340213e1109c7c5d291c2 +// flow-typed version: <>/host-validation_v1.1.0/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'host-validation' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'host-validation' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'host-validation/example' { + declare module.exports: any; +} + +declare module 'host-validation/test' { + declare module.exports: any; +} + +// Filename aliases +declare module 'host-validation/example.js' { + declare module.exports: $Exports<'host-validation/example'>; +} +declare module 'host-validation/index' { + declare module.exports: $Exports<'host-validation'>; +} +declare module 'host-validation/index.js' { + declare module.exports: $Exports<'host-validation'>; +} +declare module 'host-validation/test.js' { + declare module.exports: $Exports<'host-validation/test'>; +} diff --git a/flow-typed/npm/hot-shots_vx.x.x.js b/flow-typed/npm/hot-shots_vx.x.x.js new file mode 100644 index 0000000000..c8c07c6213 --- /dev/null +++ b/flow-typed/npm/hot-shots_vx.x.x.js @@ -0,0 +1,52 @@ +// flow-typed signature: ddf4dd814629ae123ef685c9db868bef +// flow-typed version: <>/hot-shots_v5.x.x/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'hot-shots' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'hot-shots' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'hot-shots/lib/helpers' { + declare module.exports: any; +} + +declare module 'hot-shots/lib/statsd' { + declare module.exports: any; +} + +declare module 'hot-shots/lib/statsFunctions' { + declare module.exports: any; +} + +// Filename aliases +declare module 'hot-shots/index' { + declare module.exports: $Exports<'hot-shots'>; +} +declare module 'hot-shots/index.js' { + declare module.exports: $Exports<'hot-shots'>; +} +declare module 'hot-shots/lib/helpers.js' { + declare module.exports: $Exports<'hot-shots/lib/helpers'>; +} +declare module 'hot-shots/lib/statsd.js' { + declare module.exports: $Exports<'hot-shots/lib/statsd'>; +} +declare module 'hot-shots/lib/statsFunctions.js' { + declare module.exports: $Exports<'hot-shots/lib/statsFunctions'>; +} diff --git a/flow-typed/npm/hpp_vx.x.x.js b/flow-typed/npm/hpp_vx.x.x.js new file mode 100644 index 0000000000..1d6374d0ab --- /dev/null +++ b/flow-typed/npm/hpp_vx.x.x.js @@ -0,0 +1,32 @@ +// flow-typed signature: df2e71f248afaf87aae6d3100e3eefc6 +// flow-typed version: <>/hpp_v0.2.2/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'hpp' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'hpp' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'hpp/lib/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'hpp/lib/index.js' { + declare module.exports: $Exports<'hpp/lib/index'>; +} diff --git a/flow-typed/npm/hsts_vx.x.x.js b/flow-typed/npm/hsts_vx.x.x.js new file mode 100644 index 0000000000..d23537aaa7 --- /dev/null +++ b/flow-typed/npm/hsts_vx.x.x.js @@ -0,0 +1,33 @@ +// flow-typed signature: 9df3b65568692b49a7bd33daa52cb704 +// flow-typed version: <>/hsts_v2.1.0/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'hsts' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'hsts' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ + + +// Filename aliases +declare module 'hsts/index' { + declare module.exports: $Exports<'hsts'>; +} +declare module 'hsts/index.js' { + declare module.exports: $Exports<'hsts'>; +} diff --git a/flow-typed/npm/idx_v2.x.x.js b/flow-typed/npm/idx_v2.x.x.js new file mode 100644 index 0000000000..94e571b5b8 --- /dev/null +++ b/flow-typed/npm/idx_v2.x.x.js @@ -0,0 +1,8 @@ +// flow-typed signature: aeb1419f64937fdd64367e23af4f80d0 +// flow-typed version: 75f67f047c/idx_v2.x.x/flow_>=v0.33.x + +// From: https://github.com/facebookincubator/idx/blob/master/packages/idx/src/idx.js.flow + +declare module idx { + declare module.exports: $Facebookism$Idx; +} diff --git a/flow-typed/npm/ioredis_v3.x.x.js b/flow-typed/npm/ioredis_v3.x.x.js deleted file mode 100644 index 64709b5c91..0000000000 --- a/flow-typed/npm/ioredis_v3.x.x.js +++ /dev/null @@ -1,765 +0,0 @@ -// flow-typed signature: 8199b1c780133e0e23f622a5a6d85a88 -// flow-typed version: da30fe6876/ioredis_v3.x.x/flow_>=v0.46.x - -// @flow -// Flowtype definitions for ioredis -// Project: https://github.com/luin/ioredis -// Definitions by: York Yao -// Christopher Eck -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// -// Ported to Flow by Samuel Reed - -/* =================== USAGE =================== - import * as Redis from "ioredis"; - const redis = new Redis(); - =============================================== */ - -declare module "ioredis" { - declare class Commander extends events$EventEmitter { - getBuiltinCommands(): string[]; - createBuiltinCommand(commandName: string): {}; - defineCommand( - name: string, - definition: { - numberOfKeys?: number, - lua?: string - } - ): any; - sendCommand(): void; - } - - declare class Redis extends Commander { - constructor(port?: number, host?: string, options?: RedisOptions): void; - constructor(host?: string, options?: RedisOptions): void; - constructor(options: RedisOptions): void; - constructor(url: string): void; - - status: - | "close" - | "disconnecting" - | "wait" - | "connecting" - | "connect" - | "ready" - | "end"; - connect(callback?: Function): Promise; - disconnect(): void; - duplicate(): Redis; - monitor( - callback?: (error: Error, monitor: events$EventEmitter) => void - ): Promise; - - send_command(command: string, ...args: any[]): any; - auth(password: string, callback?: ResCallbackT): Promise; - ping(callback?: ResCallbackT): Promise; - append( - key: string, - value: string, - callback?: ResCallbackT - ): Promise; - bitcount(key: string, callback?: ResCallbackT): Promise; - bitcount( - key: string, - start: number, - end: number, - callback?: ResCallbackT - ): Promise; - set( - key: string, - value: string, - nx?: "NX", - ex?: "EX", - expires?: number, - callback?: ResCallbackT - ): Promise; - get(key: string, callback?: ResCallbackT): Promise; - exists( - key: string, - value: string, - callback?: ResCallbackT - ): Promise; - publish(channel: string, value: any): Promise; - subscribe(channel: string): Promise; - psubscribe(channel: string): Promise; - getBuffer(key: string, callback?: ResCallbackT): Promise; - - // These are mostly stubbed, TODO - setnx(args: any[], callback: ResCallbackT): void; - setnx(...args: any[]): Promise; - setex(args: any[], callback: ResCallbackT): void; - setex(...args: any[]): Promise; - psetex(args: any[], callback: ResCallbackT): void; - psetex(...args: any[]): Promise; - append(args: any[], callback: ResCallbackT): void; - append(...args: any[]): Promise; - strlen(args: any[], callback: ResCallbackT): void; - strlen(...args: any[]): Promise; - del(args: any[], callback: ResCallbackT): void; - del(...args: any[]): Promise; - exists(args: any[], callback: ResCallbackT): void; - exists(...args: any[]): Promise; - setbit(args: any[], callback: ResCallbackT): void; - setbit(...args: any[]): Promise; - getbit(args: any[], callback: ResCallbackT): void; - getbit(...args: any[]): Promise; - setrange(args: any[], callback: ResCallbackT): void; - setrange(...args: any[]): Promise; - getrange(args: any[], callback: ResCallbackT): void; - getrange(...args: any[]): Promise; - substr(args: any[], callback: ResCallbackT): void; - substr(...args: any[]): Promise; - incr(args: any[], callback: ResCallbackT): void; - incr(...args: any[]): Promise; - decr(args: any[], callback: ResCallbackT): void; - decr(...args: any[]): Promise; - mget(args: any[], callback: ResCallbackT): void; - mget(...args: any[]): Promise; - rpush(...args: any[]): Promise; - lpush(args: any[], callback: ResCallbackT): void; - lpush(...args: any[]): Promise; - rpushx(args: any[], callback: ResCallbackT): void; - rpushx(...args: any[]): Promise; - lpushx(args: any[], callback: ResCallbackT): void; - lpushx(...args: any[]): Promise; - linsert(args: any[], callback: ResCallbackT): void; - linsert(...args: any[]): Promise; - rpop(args: any[], callback: ResCallbackT): void; - rpop(...args: any[]): Promise; - lpop(args: any[], callback: ResCallbackT): void; - lpop(...args: any[]): Promise; - brpop(args: any[], callback: ResCallbackT): void; - brpop(...args: any[]): Promise; - brpoplpush(args: any[], callback: ResCallbackT): void; - brpoplpush(...args: any[]): Promise; - blpop(args: any[], callback: ResCallbackT): void; - blpop(...args: any[]): Promise; - llen(args: any[], callback: ResCallbackT): void; - llen(...args: any[]): Promise; - lindex(args: any[], callback: ResCallbackT): void; - lindex(...args: any[]): Promise; - lset(args: any[], callback: ResCallbackT): void; - lset(...args: any[]): Promise; - lrange(args: any[], callback: ResCallbackT): void; - lrange(...args: any[]): Promise; - ltrim(args: any[], callback: ResCallbackT): void; - ltrim(...args: any[]): Promise; - lrem(args: any[], callback: ResCallbackT): void; - lrem(...args: any[]): Promise; - rpoplpush(args: any[], callback: ResCallbackT): void; - rpoplpush(...args: any[]): Promise; - sadd(args: any[], callback: ResCallbackT): void; - sadd(...args: any[]): Promise; - srem(args: any[], callback: ResCallbackT): void; - srem(...args: any[]): Promise; - smove(args: any[], callback: ResCallbackT): void; - smove(...args: any[]): Promise; - sismember(args: any[], callback: ResCallbackT): void; - sismember(...args: any[]): Promise; - scard(args: any[], callback: ResCallbackT): void; - scard(...args: any[]): Promise; - spop(args: any[], callback: ResCallbackT): void; - spop(...args: any[]): Promise; - srandmember(args: any[], callback: ResCallbackT): void; - srandmember(...args: any[]): Promise; - sinter(args: any[], callback: ResCallbackT): void; - sinter(...args: any[]): Promise; - sinterstore(args: any[], callback: ResCallbackT): void; - sinterstore(...args: any[]): Promise; - sunion(args: any[], callback: ResCallbackT): void; - sunion(...args: any[]): Promise; - sunionstore(args: any[], callback: ResCallbackT): void; - sunionstore(...args: any[]): Promise; - sdiff(args: any[], callback: ResCallbackT): void; - sdiff(...args: any[]): Promise; - sdiffstore(args: any[], callback: ResCallbackT): void; - sdiffstore(...args: any[]): Promise; - smembers(args: any[], callback: ResCallbackT): void; - smembers(...args: any[]): Promise; - zadd(args: any[], callback: ResCallbackT): void; - zadd(...args: any[]): Promise; - zincrby(args: any[], callback: ResCallbackT): void; - zincrby(...args: any[]): Promise; - zrem(args: any[], callback: ResCallbackT): void; - zrem(...args: any[]): Promise; - zremrangebyscore(args: any[], callback: ResCallbackT): void; - zremrangebyscore(...args: any[]): Promise; - zremrangebyrank(args: any[], callback: ResCallbackT): void; - zremrangebyrank(...args: any[]): Promise; - zunionstore(args: any[], callback: ResCallbackT): void; - zunionstore(...args: any[]): Promise; - zinterstore(args: any[], callback: ResCallbackT): void; - zinterstore(...args: any[]): Promise; - zrange(args: any[], callback: ResCallbackT): void; - zrange(...args: any[]): Promise; - zrangebyscore(args: any[], callback: ResCallbackT): void; - zrangebyscore(...args: any[]): Promise; - zrevrangebyscore(args: any[], callback: ResCallbackT): void; - zrevrangebyscore(...args: any[]): Promise; - zcount(args: any[], callback: ResCallbackT): void; - zcount(...args: any[]): Promise; - zrevrange(args: any[], callback: ResCallbackT): void; - zrevrange(...args: any[]): Promise; - zcard(args: any[], callback: ResCallbackT): void; - zcard(...args: any[]): Promise; - zscore(args: any[], callback: ResCallbackT): void; - zscore(...args: any[]): Promise; - zrank(args: any[], callback: ResCallbackT): void; - zrank(...args: any[]): Promise; - zrevrank(args: any[], callback: ResCallbackT): void; - zrevrank(...args: any[]): Promise; - hset(args: any[], callback: ResCallbackT): void; - hset(...args: any[]): Promise; - hsetnx(args: any[], callback: ResCallbackT): void; - hsetnx(...args: any[]): Promise; - hget(args: any[], callback: ResCallbackT): void; - hget(...args: any[]): Promise; - hmset(args: any[], callback: ResCallbackT): void; - hmset(key: string, hash: any, callback: ResCallbackT): void; - hmset(...args: any[]): Promise; - hmget(args: any[], callback: ResCallbackT): void; - hmget(...args: any[]): Promise; - hincrby(args: any[], callback: ResCallbackT): void; - hincrby(...args: any[]): Promise; - hincrbyfloat(args: any[], callback: ResCallbackT): void; - hincrbyfloat(...args: any[]): Promise; - hdel(args: any[], callback: ResCallbackT): void; - hdel(...args: any[]): Promise; - hlen(args: any[], callback: ResCallbackT): void; - hlen(...args: any[]): Promise; - hkeys(args: any[], callback: ResCallbackT): void; - hkeys(...args: any[]): Promise; - hvals(args: any[], callback: ResCallbackT): void; - hvals(...args: any[]): Promise; - hgetall(args: any[], callback: ResCallbackT): void; - hgetall(...args: any[]): Promise; - hgetall(key: string, callback: ResCallbackT): void; - hexists(args: any[], callback: ResCallbackT): void; - hexists(...args: any[]): Promise; - incrby(args: any[], callback: ResCallbackT): void; - incrby(...args: any[]): Promise; - incrbyfloat(args: any[], callback: ResCallbackT): void; - incrbyfloat(...args: any[]): Promise; - decrby(args: any[], callback: ResCallbackT): void; - decrby(...args: any[]): Promise; - getset(args: any[], callback: ResCallbackT): void; - getset(...args: any[]): Promise; - mset(args: any[], callback: ResCallbackT): void; - mset(...args: any[]): Promise; - msetnx(args: any[], callback: ResCallbackT): void; - msetnx(...args: any[]): Promise; - randomkey(args: any[], callback: ResCallbackT): void; - randomkey(...args: any[]): Promise; - select(args: any[], callback: ResCallbackT): void; - select(...args: any[]): Promise; - move(args: any[], callback: ResCallbackT): void; - move(...args: any[]): Promise; - rename(args: any[], callback: ResCallbackT): void; - rename(...args: any[]): Promise; - renamenx(args: any[], callback: ResCallbackT): void; - renamenx(...args: any[]): Promise; - expire(args: any[], callback: ResCallbackT): void; - expire(...args: any[]): Promise; - pexpire(args: any[], callback: ResCallbackT): void; - pexpire(...args: any[]): Promise; - expireat(args: any[], callback: ResCallbackT): void; - expireat(...args: any[]): Promise; - pexpireat(args: any[], callback: ResCallbackT): void; - pexpireat(...args: any[]): Promise; - keys(args: any[], callback: ResCallbackT): void; - keys(...args: any[]): Promise; - dbsize(args: any[], callback: ResCallbackT): void; - dbsize(...args: any[]): Promise; - auth(args: any[], callback: ResCallbackT): void; - auth(...args: any[]): Promise; - ping(args: any[], callback: ResCallbackT): void; - ping(...args: any[]): Promise; - echo(args: any[], callback: ResCallbackT): void; - echo(...args: any[]): Promise; - save(args: any[], callback: ResCallbackT): void; - save(...args: any[]): Promise; - bgsave(args: any[], callback: ResCallbackT): void; - bgsave(...args: any[]): Promise; - bgrewriteaof(args: any[], callback: ResCallbackT): void; - bgrewriteaof(...args: any[]): Promise; - shutdown(args: any[], callback: ResCallbackT): void; - shutdown(...args: any[]): Promise; - lastsave(args: any[], callback: ResCallbackT): void; - lastsave(...args: any[]): Promise; - type(args: any[], callback: ResCallbackT): void; - type(...args: any[]): Promise; - multi(args: any[], callback: ResCallbackT): void; - multi(...args: any[]): Promise; - exec(args: any[], callback: ResCallbackT): void; - exec(...args: any[]): Promise; - discard(args: any[], callback: ResCallbackT): void; - discard(...args: any[]): Promise; - sync(args: any[], callback: ResCallbackT): void; - sync(...args: any[]): Promise; - flushdb(args: any[], callback: ResCallbackT): void; - flushdb(...args: any[]): Promise; - flushall(args: any[], callback: ResCallbackT): void; - flushall(...args: any[]): Promise; - sort(args: any[], callback: ResCallbackT): void; - sort(...args: any[]): Promise; - info(args: any[], callback: ResCallbackT): void; - info(...args: any[]): Promise; - time(args: any[], callback: ResCallbackT): void; - time(...args: any[]): Promise; - monitor(args: any[], callback: ResCallbackT): void; - monitor(...args: any[]): Promise; - ttl(args: any[], callback: ResCallbackT): void; - ttl(...args: any[]): Promise; - persist(args: any[], callback: ResCallbackT): void; - persist(...args: any[]): Promise; - slaveof(args: any[], callback: ResCallbackT): void; - slaveof(...args: any[]): Promise; - debug(args: any[], callback: ResCallbackT): void; - debug(...args: any[]): Promise; - config(args: any[], callback: ResCallbackT): void; - config(...args: any[]): Promise; - subscribe(args: any[], callback: ResCallbackT): void; - subscribe(...args: any[]): Promise; - unsubscribe(args: any[], callback: ResCallbackT): void; - unsubscribe(...args: any[]): Promise; - psubscribe(args: any[], callback: ResCallbackT): void; - psubscribe(...args: any[]): Promise; - punsubscribe(args: any[], callback: ResCallbackT): void; - punsubscribe(...args: any[]): Promise; - publish(args: any[], callback: ResCallbackT): void; - publish(...args: any[]): Promise; - watch(args: any[], callback: ResCallbackT): void; - watch(...args: any[]): Promise; - unwatch(args: any[], callback: ResCallbackT): void; - unwatch(...args: any[]): Promise; - cluster(args: any[], callback: ResCallbackT): void; - cluster(...args: any[]): Promise; - restore(args: any[], callback: ResCallbackT): void; - restore(...args: any[]): Promise; - migrate(args: any[], callback: ResCallbackT): void; - migrate(...args: any[]): Promise; - dump(args: any[], callback: ResCallbackT): void; - dump(...args: any[]): Promise; - object(args: any[], callback: ResCallbackT): void; - object(...args: any[]): Promise; - client(args: any[], callback: ResCallbackT): void; - client(...args: any[]): Promise; - eval(args: any[], callback: ResCallbackT): void; - eval(...args: any[]): Promise; - evalsha(args: any[], callback: ResCallbackT): void; - evalsha(...args: any[]): Promise; - script(args: any[], callback: ResCallbackT): void; - script(...args: any[]): Promise; - script(key: string, callback: ResCallbackT): void; - quit(...args: any[]): Promise; - quit(args: any[], callback: ResCallbackT): void; - scan(...args: any[]): Promise; - scan(args: any[], callback: ResCallbackT): void; - hscan(...args: any[]): Promise; - hscan(args: any[], callback: ResCallbackT): void; - zscan(...args: any[]): Promise; - zscan(args: any[], callback: ResCallbackT): void; - - pipeline(): Pipeline; - pipeline(commands: string[][]): Pipeline; - - scanStream(options?: ScanStreamOption): events$EventEmitter; - hscanStream(key: string, options?: ScanStreamOption): events$EventEmitter; - zscanStream(key: string, options?: ScanStreamOption): events$EventEmitter; - } - - declare type Pipeline = { - exec(callback?: ResCallbackT): any, - - get(args: any[], callback?: ResCallbackT): Pipeline, - get(...args: any[]): Pipeline, - set(args: any[], callback?: ResCallbackT): Pipeline, - set(...args: any[]): Pipeline, - setnx(args: any[], callback?: ResCallbackT): Pipeline, - setnx(...args: any[]): Pipeline, - setex(args: any[], callback?: ResCallbackT): Pipeline, - setex(...args: any[]): Pipeline, - psetex(args: any[], callback?: ResCallbackT): Pipeline, - psetex(...args: any[]): Pipeline, - append(args: any[], callback?: ResCallbackT): Pipeline, - append(...args: any[]): Pipeline, - strlen(args: any[], callback?: ResCallbackT): Pipeline, - strlen(...args: any[]): Pipeline, - del(args: any[], callback?: ResCallbackT): Pipeline, - del(...args: any[]): Pipeline, - exists(args: any[], callback?: ResCallbackT): Pipeline, - exists(...args: any[]): Pipeline, - setbit(args: any[], callback?: ResCallbackT): Pipeline, - setbit(...args: any[]): Pipeline, - getbit(args: any[], callback?: ResCallbackT): Pipeline, - getbit(...args: any[]): Pipeline, - setrange(args: any[], callback?: ResCallbackT): Pipeline, - setrange(...args: any[]): Pipeline, - getrange(args: any[], callback?: ResCallbackT): Pipeline, - getrange(...args: any[]): Pipeline, - substr(args: any[], callback?: ResCallbackT): Pipeline, - substr(...args: any[]): Pipeline, - incr(args: any[], callback?: ResCallbackT): Pipeline, - incr(...args: any[]): Pipeline, - decr(args: any[], callback?: ResCallbackT): Pipeline, - decr(...args: any[]): Pipeline, - mget(args: any[], callback?: ResCallbackT): Pipeline, - mget(...args: any[]): Pipeline, - rpush(...args: any[]): Pipeline, - lpush(args: any[], callback?: ResCallbackT): Pipeline, - lpush(...args: any[]): Pipeline, - rpushx(args: any[], callback?: ResCallbackT): Pipeline, - rpushx(...args: any[]): Pipeline, - lpushx(args: any[], callback?: ResCallbackT): Pipeline, - lpushx(...args: any[]): Pipeline, - linsert(args: any[], callback?: ResCallbackT): Pipeline, - linsert(...args: any[]): Pipeline, - rpop(args: any[], callback?: ResCallbackT): Pipeline, - rpop(...args: any[]): Pipeline, - lpop(args: any[], callback?: ResCallbackT): Pipeline, - lpop(...args: any[]): Pipeline, - brpop(args: any[], callback?: ResCallbackT): Pipeline, - brpop(...args: any[]): Pipeline, - brpoplpush(args: any[], callback?: ResCallbackT): Pipeline, - brpoplpush(...args: any[]): Pipeline, - blpop(args: any[], callback?: ResCallbackT): Pipeline, - blpop(...args: any[]): Pipeline, - llen(args: any[], callback?: ResCallbackT): Pipeline, - llen(...args: any[]): Pipeline, - lindex(args: any[], callback?: ResCallbackT): Pipeline, - lindex(...args: any[]): Pipeline, - lset(args: any[], callback?: ResCallbackT): Pipeline, - lset(...args: any[]): Pipeline, - lrange(args: any[], callback?: ResCallbackT): Pipeline, - lrange(...args: any[]): Pipeline, - ltrim(args: any[], callback?: ResCallbackT): Pipeline, - ltrim(...args: any[]): Pipeline, - lrem(args: any[], callback?: ResCallbackT): Pipeline, - lrem(...args: any[]): Pipeline, - rpoplpush(args: any[], callback?: ResCallbackT): Pipeline, - rpoplpush(...args: any[]): Pipeline, - sadd(args: any[], callback?: ResCallbackT): Pipeline, - sadd(...args: any[]): Pipeline, - srem(args: any[], callback?: ResCallbackT): Pipeline, - srem(...args: any[]): Pipeline, - smove(args: any[], callback?: ResCallbackT): Pipeline, - smove(...args: any[]): Pipeline, - sismember(args: any[], callback?: ResCallbackT): Pipeline, - sismember(...args: any[]): Pipeline, - scard(args: any[], callback?: ResCallbackT): Pipeline, - scard(...args: any[]): Pipeline, - spop(args: any[], callback?: ResCallbackT): Pipeline, - spop(...args: any[]): Pipeline, - srandmember(args: any[], callback?: ResCallbackT): Pipeline, - srandmember(...args: any[]): Pipeline, - sinter(args: any[], callback?: ResCallbackT): Pipeline, - sinter(...args: any[]): Pipeline, - sinterstore(args: any[], callback?: ResCallbackT): Pipeline, - sinterstore(...args: any[]): Pipeline, - sunion(args: any[], callback?: ResCallbackT): Pipeline, - sunion(...args: any[]): Pipeline, - sunionstore(args: any[], callback?: ResCallbackT): Pipeline, - sunionstore(...args: any[]): Pipeline, - sdiff(args: any[], callback?: ResCallbackT): Pipeline, - sdiff(...args: any[]): Pipeline, - sdiffstore(args: any[], callback?: ResCallbackT): Pipeline, - sdiffstore(...args: any[]): Pipeline, - smembers(args: any[], callback?: ResCallbackT): Pipeline, - smembers(...args: any[]): Pipeline, - zadd(args: any[], callback?: ResCallbackT): Pipeline, - zadd(...args: any[]): Pipeline, - zincrby(args: any[], callback?: ResCallbackT): Pipeline, - zincrby(...args: any[]): Pipeline, - zrem(args: any[], callback?: ResCallbackT): Pipeline, - zrem(...args: any[]): Pipeline, - zremrangebyscore(args: any[], callback?: ResCallbackT): Pipeline, - zremrangebyscore(...args: any[]): Pipeline, - zremrangebyrank(args: any[], callback?: ResCallbackT): Pipeline, - zremrangebyrank(...args: any[]): Pipeline, - zunionstore(args: any[], callback?: ResCallbackT): Pipeline, - zunionstore(...args: any[]): Pipeline, - zinterstore(args: any[], callback?: ResCallbackT): Pipeline, - zinterstore(...args: any[]): Pipeline, - zrange(args: any[], callback?: ResCallbackT): Pipeline, - zrange(...args: any[]): Pipeline, - zrangebyscore(args: any[], callback?: ResCallbackT): Pipeline, - zrangebyscore(...args: any[]): Pipeline, - zrevrangebyscore(args: any[], callback?: ResCallbackT): Pipeline, - zrevrangebyscore(...args: any[]): Pipeline, - zcount(args: any[], callback?: ResCallbackT): Pipeline, - zcount(...args: any[]): Pipeline, - zrevrange(args: any[], callback?: ResCallbackT): Pipeline, - zrevrange(...args: any[]): Pipeline, - zcard(args: any[], callback?: ResCallbackT): Pipeline, - zcard(...args: any[]): Pipeline, - zscore(args: any[], callback?: ResCallbackT): Pipeline, - zscore(...args: any[]): Pipeline, - zrank(args: any[], callback?: ResCallbackT): Pipeline, - zrank(...args: any[]): Pipeline, - zrevrank(args: any[], callback?: ResCallbackT): Pipeline, - zrevrank(...args: any[]): Pipeline, - hset(args: any[], callback?: ResCallbackT): Pipeline, - hset(...args: any[]): Pipeline, - hsetnx(args: any[], callback?: ResCallbackT): Pipeline, - hsetnx(...args: any[]): Pipeline, - hget(args: any[], callback?: ResCallbackT): Pipeline, - hget(...args: any[]): Pipeline, - hmset(args: any[], callback?: ResCallbackT): Pipeline, - hmset(key: string, hash: any, callback?: ResCallbackT): Pipeline, - hmset(...args: any[]): Pipeline, - hmget(args: any[], callback?: ResCallbackT): Pipeline, - hmget(...args: any[]): Pipeline, - hincrby(args: any[], callback?: ResCallbackT): Pipeline, - hincrby(...args: any[]): Pipeline, - hincrbyfloat(args: any[], callback?: ResCallbackT): Pipeline, - hincrbyfloat(...args: any[]): Pipeline, - hdel(args: any[], callback?: ResCallbackT): Pipeline, - hdel(...args: any[]): Pipeline, - hlen(args: any[], callback?: ResCallbackT): Pipeline, - hlen(...args: any[]): Pipeline, - hkeys(args: any[], callback?: ResCallbackT): Pipeline, - hkeys(...args: any[]): Pipeline, - hvals(args: any[], callback?: ResCallbackT): Pipeline, - hvals(...args: any[]): Pipeline, - hgetall(args: any[], callback?: ResCallbackT): Pipeline, - hgetall(...args: any[]): Pipeline, - hgetall(key: string, callback?: ResCallbackT): Pipeline, - hexists(args: any[], callback?: ResCallbackT): Pipeline, - hexists(...args: any[]): Pipeline, - incrby(args: any[], callback?: ResCallbackT): Pipeline, - incrby(...args: any[]): Pipeline, - incrbyfloat(args: any[], callback?: ResCallbackT): Pipeline, - incrbyfloat(...args: any[]): Pipeline, - decrby(args: any[], callback?: ResCallbackT): Pipeline, - decrby(...args: any[]): Pipeline, - getset(args: any[], callback?: ResCallbackT): Pipeline, - getset(...args: any[]): Pipeline, - mset(args: any[], callback?: ResCallbackT): Pipeline, - mset(...args: any[]): Pipeline, - msetnx(args: any[], callback?: ResCallbackT): Pipeline, - msetnx(...args: any[]): Pipeline, - randomkey(args: any[], callback?: ResCallbackT): Pipeline, - randomkey(...args: any[]): Pipeline, - select(args: any[], callback?: ResCallbackT): void, - select(...args: any[]): Pipeline, - move(args: any[], callback?: ResCallbackT): Pipeline, - move(...args: any[]): Pipeline, - rename(args: any[], callback?: ResCallbackT): Pipeline, - rename(...args: any[]): Pipeline, - renamenx(args: any[], callback?: ResCallbackT): Pipeline, - renamenx(...args: any[]): Pipeline, - expire(args: any[], callback?: ResCallbackT): Pipeline, - expire(...args: any[]): Pipeline, - pexpire(args: any[], callback?: ResCallbackT): Pipeline, - pexpire(...args: any[]): Pipeline, - expireat(args: any[], callback?: ResCallbackT): Pipeline, - expireat(...args: any[]): Pipeline, - pexpireat(args: any[], callback?: ResCallbackT): Pipeline, - pexpireat(...args: any[]): Pipeline, - keys(args: any[], callback?: ResCallbackT): Pipeline, - keys(...args: any[]): Pipeline, - dbsize(args: any[], callback?: ResCallbackT): Pipeline, - dbsize(...args: any[]): Pipeline, - auth(args: any[], callback?: ResCallbackT): void, - auth(...args: any[]): void, - ping(args: any[], callback?: ResCallbackT): Pipeline, - ping(...args: any[]): Pipeline, - echo(args: any[], callback?: ResCallbackT): Pipeline, - echo(...args: any[]): Pipeline, - save(args: any[], callback?: ResCallbackT): Pipeline, - save(...args: any[]): Pipeline, - bgsave(args: any[], callback?: ResCallbackT): Pipeline, - bgsave(...args: any[]): Pipeline, - bgrewriteaof(args: any[], callback?: ResCallbackT): Pipeline, - bgrewriteaof(...args: any[]): Pipeline, - shutdown(args: any[], callback?: ResCallbackT): Pipeline, - shutdown(...args: any[]): Pipeline, - lastsave(args: any[], callback?: ResCallbackT): Pipeline, - lastsave(...args: any[]): Pipeline, - type(args: any[], callback?: ResCallbackT): Pipeline, - type(...args: any[]): Pipeline, - multi(args: any[], callback?: ResCallbackT): Pipeline, - multi(...args: any[]): Pipeline, - exec(args: any[], callback?: ResCallbackT): Pipeline, - exec(...args: any[]): Pipeline, - discard(args: any[], callback?: ResCallbackT): Pipeline, - discard(...args: any[]): Pipeline, - sync(args: any[], callback?: ResCallbackT): Pipeline, - sync(...args: any[]): Pipeline, - flushdb(args: any[], callback?: ResCallbackT): Pipeline, - flushdb(...args: any[]): Pipeline, - flushall(args: any[], callback?: ResCallbackT): Pipeline, - flushall(...args: any[]): Pipeline, - sort(args: any[], callback?: ResCallbackT): Pipeline, - sort(...args: any[]): Pipeline, - info(args: any[], callback?: ResCallbackT): Pipeline, - info(...args: any[]): Pipeline, - time(args: any[], callback?: ResCallbackT): Pipeline, - time(...args: any[]): Pipeline, - monitor(args: any[], callback?: ResCallbackT): Pipeline, - monitor(...args: any[]): Pipeline, - ttl(args: any[], callback?: ResCallbackT): Pipeline, - ttl(...args: any[]): Pipeline, - persist(args: any[], callback?: ResCallbackT): Pipeline, - persist(...args: any[]): Pipeline, - slaveof(args: any[], callback?: ResCallbackT): Pipeline, - slaveof(...args: any[]): Pipeline, - debug(args: any[], callback?: ResCallbackT): Pipeline, - debug(...args: any[]): Pipeline, - config(args: any[], callback?: ResCallbackT): Pipeline, - config(...args: any[]): Pipeline, - subscribe(args: any[], callback?: ResCallbackT): Pipeline, - subscribe(...args: any[]): Pipeline, - unsubscribe(args: any[], callback?: ResCallbackT): Pipeline, - unsubscribe(...args: any[]): Pipeline, - psubscribe(args: any[], callback?: ResCallbackT): Pipeline, - psubscribe(...args: any[]): Pipeline, - punsubscribe(args: any[], callback?: ResCallbackT): Pipeline, - punsubscribe(...args: any[]): Pipeline, - publish(args: any[], callback?: ResCallbackT): Pipeline, - publish(...args: any[]): Pipeline, - watch(args: any[], callback?: ResCallbackT): Pipeline, - watch(...args: any[]): Pipeline, - unwatch(args: any[], callback?: ResCallbackT): Pipeline, - unwatch(...args: any[]): Pipeline, - cluster(args: any[], callback?: ResCallbackT): Pipeline, - cluster(...args: any[]): Pipeline, - restore(args: any[], callback?: ResCallbackT): Pipeline, - restore(...args: any[]): Pipeline, - migrate(args: any[], callback?: ResCallbackT): Pipeline, - migrate(...args: any[]): Pipeline, - dump(args: any[], callback?: ResCallbackT): Pipeline, - dump(...args: any[]): Pipeline, - object(args: any[], callback?: ResCallbackT): Pipeline, - object(...args: any[]): Pipeline, - client(args: any[], callback?: ResCallbackT): Pipeline, - client(...args: any[]): Pipeline, - eval(args: any[], callback?: ResCallbackT): Pipeline, - eval(...args: any[]): Pipeline, - evalsha(args: any[], callback?: ResCallbackT): Pipeline, - evalsha(...args: any[]): Pipeline, - quit(args: any[], callback?: ResCallbackT): Pipeline, - quit(...args: any[]): Pipeline, - scan(...args: any[]): Pipeline, - scan(args: any[], callback?: ResCallbackT): Pipeline, - hscan(...args: any[]): Pipeline, - hscan(args: any[], callback?: ResCallbackT): Pipeline, - zscan(...args: any[]): Pipeline, - zscan(args: any[], callback?: ResCallbackT): Pipeline - }; - - declare class Cluster extends Redis { - constructor( - nodes: { host: string, port: number }[], - options?: ClusterOptions - ): void; - nodes(role: string): Redis[]; - } - - declare type ResCallbackT = (err: Error, res: R) => void; - - declare type RedisOptions = { - port?: number, - host?: string, - /** - * 4 (IPv4) or 6 (IPv6), Defaults to 4. - */ - family?: number, - /** - * Local domain socket path. If set the port, host and family will be ignored. - */ - path?: string, - /** - * TCP KeepAlive on the socket with a X ms delay before start. Set to a non-number value to disable keepAlive. - */ - keepAlive?: number, - connectionName?: string, - /** - * If set, client will send AUTH command with the value of this option when connected. - */ - password?: string, - /** - * Database index to use. - */ - db?: number, - /** - * When a connection is established to the Redis server, the server might still be loading - * the database from disk. While loading, the server not respond to any commands. - * To work around this, when this option is true, ioredis will check the status of the Redis server, - * and when the Redis server is able to process commands, a ready event will be emitted. - */ - enableReadyCheck?: boolean, - keyPrefix?: string, - /** - * When the return value isn't a number, ioredis will stop trying to reconnect. - * Fixed in: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/15858 - */ - retryStrategy?: (times: number) => number | false, - reconnectOnError?: (error: Error) => boolean, - /** - * By default, if there is no active connection to the Redis server, commands are added to a queue - * and are executed once the connection is "ready" (when enableReadyCheck is true, "ready" means - * the Redis server has loaded the database from disk, otherwise means the connection to the Redis - * server has been established). If this option is false, when execute the command when the connection - * isn't ready, an error will be returned. - */ - enableOfflineQueue?: boolean, - /** - * The milliseconds before a timeout occurs during the initial connection to the Redis server. - * default: 10000. - */ - connectTimeout?: number, - /** - * After reconnected, if the previous connection was in the subscriber mode, client will auto re-subscribe these channels. - * default: true. - */ - autoResubscribe?: boolean, - /** - * If true, client will resend unfulfilled commands(e.g. block commands) in the previous connection when reconnected. - * default: true. - */ - autoResendUnfulfilledCommands?: boolean, - lazyConnect?: boolean, - tls?: { - ca: Buffer - }, - sentinels?: { host: string, port: number }[], - name?: string, - /** - * Enable READONLY mode for the connection. Only available for cluster mode. - * default: false. - */ - readOnly?: boolean, - /** - * If you are using the hiredis parser, it's highly recommended to enable this option. - * Create another instance with dropBufferSupport disabled for other commands that you want to return binary instead of string: - */ - dropBufferSupport?: boolean - }; - - declare type ScanStreamOption = { - match?: string, - count?: number - }; - - declare type ClusterNodeType = "all" | "slave" | "master"; - - declare type ClusterOptions = { - clusterRetryStrategy?: (times: number) => number, - enableOfflineQueue?: boolean, - enableReadyCheck?: boolean, - scaleReads?: ClusterNodeType, - maxRedirections?: number, - retryDelayOnFailover?: number, - retryDelayOnClusterDown?: number, - retryDelayOnTryAgain?: number, - redisOptions?: RedisOptions - }; - - declare class RedisStatic extends Redis { - static Cluster: Class; - static Commander: Class; - } - - declare module.exports: typeof RedisStatic; -} diff --git a/flow-typed/npm/ioredis_vx.x.x.js b/flow-typed/npm/ioredis_vx.x.x.js index 66321ea93e..8b63c6f770 100644 --- a/flow-typed/npm/ioredis_vx.x.x.js +++ b/flow-typed/npm/ioredis_vx.x.x.js @@ -1,5 +1,5 @@ -// flow-typed signature: dd839dddc088c685479cec804cc45744 -// flow-typed version: <>/ioredis_v3.1.4/flow_v0.57.3 +// flow-typed signature: 212434ef140f4ad1df5b2591c14eae35 +// flow-typed version: <>/ioredis_v4.2.0/flow_v0.66.0 /** * This is an autogenerated libdef stub for: @@ -22,136 +22,221 @@ declare module 'ioredis' { * require those files directly. Feel free to delete any files that aren't * needed. */ -declare module 'ioredis/lib/cluster/connection_pool' { +declare module 'ioredis/built/cluster/ClusterOptions' { declare module.exports: any; } -declare module 'ioredis/lib/cluster/delay_queue' { +declare module 'ioredis/built/cluster/ClusterSubscriber' { declare module.exports: any; } -declare module 'ioredis/lib/cluster/index' { +declare module 'ioredis/built/cluster/ConnectionPool' { declare module.exports: any; } -declare module 'ioredis/lib/command' { +declare module 'ioredis/built/cluster/DelayQueue' { declare module.exports: any; } -declare module 'ioredis/lib/commander' { +declare module 'ioredis/built/cluster/index' { declare module.exports: any; } -declare module 'ioredis/lib/connectors/connector' { +declare module 'ioredis/built/cluster/util' { declare module.exports: any; } -declare module 'ioredis/lib/connectors/sentinel_connector' { +declare module 'ioredis/built/command' { declare module.exports: any; } -declare module 'ioredis/lib/pipeline' { +declare module 'ioredis/built/commander' { declare module.exports: any; } -declare module 'ioredis/lib/redis' { +declare module 'ioredis/built/connectors/AbstractConnector' { declare module.exports: any; } -declare module 'ioredis/lib/redis/event_handler' { +declare module 'ioredis/built/connectors/index' { declare module.exports: any; } -declare module 'ioredis/lib/redis/parser' { +declare module 'ioredis/built/connectors/SentinelConnector/index' { declare module.exports: any; } -declare module 'ioredis/lib/reply_error' { +declare module 'ioredis/built/connectors/SentinelConnector/SentinelIterator' { declare module.exports: any; } -declare module 'ioredis/lib/scan_stream' { +declare module 'ioredis/built/connectors/SentinelConnector/types' { declare module.exports: any; } -declare module 'ioredis/lib/script' { +declare module 'ioredis/built/connectors/StandaloneConnector' { declare module.exports: any; } -declare module 'ioredis/lib/subscription_set' { +declare module 'ioredis/built/errors/ClusterAllFailedError' { declare module.exports: any; } -declare module 'ioredis/lib/transaction' { +declare module 'ioredis/built/errors/index' { declare module.exports: any; } -declare module 'ioredis/lib/utils/index' { +declare module 'ioredis/built/errors/MaxRetriesPerRequestError' { declare module.exports: any; } -declare module 'ioredis/lib/utils/lodash' { +declare module 'ioredis/built/index' { + declare module.exports: any; +} + +declare module 'ioredis/built/pipeline' { + declare module.exports: any; +} + +declare module 'ioredis/built/promiseContainer' { + declare module.exports: any; +} + +declare module 'ioredis/built/redis' { + declare module.exports: any; +} + +declare module 'ioredis/built/redis/event_handler' { + declare module.exports: any; +} + +declare module 'ioredis/built/redis/parser' { + declare module.exports: any; +} + +declare module 'ioredis/built/ScanStream' { + declare module.exports: any; +} + +declare module 'ioredis/built/script' { + declare module.exports: any; +} + +declare module 'ioredis/built/SubscriptionSet' { + declare module.exports: any; +} + +declare module 'ioredis/built/transaction' { + declare module.exports: any; +} + +declare module 'ioredis/built/types' { + declare module.exports: any; +} + +declare module 'ioredis/built/utils/debug' { + declare module.exports: any; +} + +declare module 'ioredis/built/utils/index' { + declare module.exports: any; +} + +declare module 'ioredis/built/utils/lodash' { declare module.exports: any; } // Filename aliases -declare module 'ioredis/index' { - declare module.exports: $Exports<'ioredis'>; +declare module 'ioredis/built/cluster/ClusterOptions.js' { + declare module.exports: $Exports<'ioredis/built/cluster/ClusterOptions'>; +} +declare module 'ioredis/built/cluster/ClusterSubscriber.js' { + declare module.exports: $Exports<'ioredis/built/cluster/ClusterSubscriber'>; +} +declare module 'ioredis/built/cluster/ConnectionPool.js' { + declare module.exports: $Exports<'ioredis/built/cluster/ConnectionPool'>; +} +declare module 'ioredis/built/cluster/DelayQueue.js' { + declare module.exports: $Exports<'ioredis/built/cluster/DelayQueue'>; +} +declare module 'ioredis/built/cluster/index.js' { + declare module.exports: $Exports<'ioredis/built/cluster/index'>; +} +declare module 'ioredis/built/cluster/util.js' { + declare module.exports: $Exports<'ioredis/built/cluster/util'>; +} +declare module 'ioredis/built/command.js' { + declare module.exports: $Exports<'ioredis/built/command'>; +} +declare module 'ioredis/built/commander.js' { + declare module.exports: $Exports<'ioredis/built/commander'>; +} +declare module 'ioredis/built/connectors/AbstractConnector.js' { + declare module.exports: $Exports<'ioredis/built/connectors/AbstractConnector'>; +} +declare module 'ioredis/built/connectors/index.js' { + declare module.exports: $Exports<'ioredis/built/connectors/index'>; +} +declare module 'ioredis/built/connectors/SentinelConnector/index.js' { + declare module.exports: $Exports<'ioredis/built/connectors/SentinelConnector/index'>; +} +declare module 'ioredis/built/connectors/SentinelConnector/SentinelIterator.js' { + declare module.exports: $Exports<'ioredis/built/connectors/SentinelConnector/SentinelIterator'>; } -declare module 'ioredis/index.js' { - declare module.exports: $Exports<'ioredis'>; +declare module 'ioredis/built/connectors/SentinelConnector/types.js' { + declare module.exports: $Exports<'ioredis/built/connectors/SentinelConnector/types'>; } -declare module 'ioredis/lib/cluster/connection_pool.js' { - declare module.exports: $Exports<'ioredis/lib/cluster/connection_pool'>; +declare module 'ioredis/built/connectors/StandaloneConnector.js' { + declare module.exports: $Exports<'ioredis/built/connectors/StandaloneConnector'>; } -declare module 'ioredis/lib/cluster/delay_queue.js' { - declare module.exports: $Exports<'ioredis/lib/cluster/delay_queue'>; +declare module 'ioredis/built/errors/ClusterAllFailedError.js' { + declare module.exports: $Exports<'ioredis/built/errors/ClusterAllFailedError'>; } -declare module 'ioredis/lib/cluster/index.js' { - declare module.exports: $Exports<'ioredis/lib/cluster/index'>; +declare module 'ioredis/built/errors/index.js' { + declare module.exports: $Exports<'ioredis/built/errors/index'>; } -declare module 'ioredis/lib/command.js' { - declare module.exports: $Exports<'ioredis/lib/command'>; +declare module 'ioredis/built/errors/MaxRetriesPerRequestError.js' { + declare module.exports: $Exports<'ioredis/built/errors/MaxRetriesPerRequestError'>; } -declare module 'ioredis/lib/commander.js' { - declare module.exports: $Exports<'ioredis/lib/commander'>; +declare module 'ioredis/built/index.js' { + declare module.exports: $Exports<'ioredis/built/index'>; } -declare module 'ioredis/lib/connectors/connector.js' { - declare module.exports: $Exports<'ioredis/lib/connectors/connector'>; +declare module 'ioredis/built/pipeline.js' { + declare module.exports: $Exports<'ioredis/built/pipeline'>; } -declare module 'ioredis/lib/connectors/sentinel_connector.js' { - declare module.exports: $Exports<'ioredis/lib/connectors/sentinel_connector'>; +declare module 'ioredis/built/promiseContainer.js' { + declare module.exports: $Exports<'ioredis/built/promiseContainer'>; } -declare module 'ioredis/lib/pipeline.js' { - declare module.exports: $Exports<'ioredis/lib/pipeline'>; +declare module 'ioredis/built/redis.js' { + declare module.exports: $Exports<'ioredis/built/redis'>; } -declare module 'ioredis/lib/redis.js' { - declare module.exports: $Exports<'ioredis/lib/redis'>; +declare module 'ioredis/built/redis/event_handler.js' { + declare module.exports: $Exports<'ioredis/built/redis/event_handler'>; } -declare module 'ioredis/lib/redis/event_handler.js' { - declare module.exports: $Exports<'ioredis/lib/redis/event_handler'>; +declare module 'ioredis/built/redis/parser.js' { + declare module.exports: $Exports<'ioredis/built/redis/parser'>; } -declare module 'ioredis/lib/redis/parser.js' { - declare module.exports: $Exports<'ioredis/lib/redis/parser'>; +declare module 'ioredis/built/ScanStream.js' { + declare module.exports: $Exports<'ioredis/built/ScanStream'>; } -declare module 'ioredis/lib/reply_error.js' { - declare module.exports: $Exports<'ioredis/lib/reply_error'>; +declare module 'ioredis/built/script.js' { + declare module.exports: $Exports<'ioredis/built/script'>; } -declare module 'ioredis/lib/scan_stream.js' { - declare module.exports: $Exports<'ioredis/lib/scan_stream'>; +declare module 'ioredis/built/SubscriptionSet.js' { + declare module.exports: $Exports<'ioredis/built/SubscriptionSet'>; } -declare module 'ioredis/lib/script.js' { - declare module.exports: $Exports<'ioredis/lib/script'>; +declare module 'ioredis/built/transaction.js' { + declare module.exports: $Exports<'ioredis/built/transaction'>; } -declare module 'ioredis/lib/subscription_set.js' { - declare module.exports: $Exports<'ioredis/lib/subscription_set'>; +declare module 'ioredis/built/types.js' { + declare module.exports: $Exports<'ioredis/built/types'>; } -declare module 'ioredis/lib/transaction.js' { - declare module.exports: $Exports<'ioredis/lib/transaction'>; +declare module 'ioredis/built/utils/debug.js' { + declare module.exports: $Exports<'ioredis/built/utils/debug'>; } -declare module 'ioredis/lib/utils/index.js' { - declare module.exports: $Exports<'ioredis/lib/utils/index'>; +declare module 'ioredis/built/utils/index.js' { + declare module.exports: $Exports<'ioredis/built/utils/index'>; } -declare module 'ioredis/lib/utils/lodash.js' { - declare module.exports: $Exports<'ioredis/lib/utils/lodash'>; +declare module 'ioredis/built/utils/lodash.js' { + declare module.exports: $Exports<'ioredis/built/utils/lodash'>; } diff --git a/flow-typed/npm/is-electron_vx.x.x.js b/flow-typed/npm/is-electron_vx.x.x.js new file mode 100644 index 0000000000..c2050de8bc --- /dev/null +++ b/flow-typed/npm/is-electron_vx.x.x.js @@ -0,0 +1,33 @@ +// flow-typed signature: 35018a159b106afca8b00a8771686100 +// flow-typed version: <>/is-electron_v2.1.0/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'is-electron' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'is-electron' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ + + +// Filename aliases +declare module 'is-electron/index' { + declare module.exports: $Exports<'is-electron'>; +} +declare module 'is-electron/index.js' { + declare module.exports: $Exports<'is-electron'>; +} diff --git a/flow-typed/npm/markdown-draft-js_vx.x.x.js b/flow-typed/npm/markdown-draft-js_vx.x.x.js new file mode 100644 index 0000000000..767c24c196 --- /dev/null +++ b/flow-typed/npm/markdown-draft-js_vx.x.x.js @@ -0,0 +1,95 @@ +// flow-typed signature: 72b2dfcb381160ae074fc97df0df8724 +// flow-typed version: <>/markdown-draft-js_v0.6.3/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'markdown-draft-js' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'markdown-draft-js' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'markdown-draft-js/karma.conf' { + declare module.exports: any; +} + +declare module 'markdown-draft-js/lib/draft-to-markdown' { + declare module.exports: any; +} + +declare module 'markdown-draft-js/lib/index' { + declare module.exports: any; +} + +declare module 'markdown-draft-js/lib/markdown-to-draft' { + declare module.exports: any; +} + +declare module 'markdown-draft-js/src/draft-to-markdown' { + declare module.exports: any; +} + +declare module 'markdown-draft-js/src/index' { + declare module.exports: any; +} + +declare module 'markdown-draft-js/src/markdown-to-draft' { + declare module.exports: any; +} + +declare module 'markdown-draft-js/test/draft-to-markdown.spec' { + declare module.exports: any; +} + +declare module 'markdown-draft-js/test/idempotency.spec' { + declare module.exports: any; +} + +declare module 'markdown-draft-js/test/markdown-to-draft.spec' { + declare module.exports: any; +} + +// Filename aliases +declare module 'markdown-draft-js/karma.conf.js' { + declare module.exports: $Exports<'markdown-draft-js/karma.conf'>; +} +declare module 'markdown-draft-js/lib/draft-to-markdown.js' { + declare module.exports: $Exports<'markdown-draft-js/lib/draft-to-markdown'>; +} +declare module 'markdown-draft-js/lib/index.js' { + declare module.exports: $Exports<'markdown-draft-js/lib/index'>; +} +declare module 'markdown-draft-js/lib/markdown-to-draft.js' { + declare module.exports: $Exports<'markdown-draft-js/lib/markdown-to-draft'>; +} +declare module 'markdown-draft-js/src/draft-to-markdown.js' { + declare module.exports: $Exports<'markdown-draft-js/src/draft-to-markdown'>; +} +declare module 'markdown-draft-js/src/index.js' { + declare module.exports: $Exports<'markdown-draft-js/src/index'>; +} +declare module 'markdown-draft-js/src/markdown-to-draft.js' { + declare module.exports: $Exports<'markdown-draft-js/src/markdown-to-draft'>; +} +declare module 'markdown-draft-js/test/draft-to-markdown.spec.js' { + declare module.exports: $Exports<'markdown-draft-js/test/draft-to-markdown.spec'>; +} +declare module 'markdown-draft-js/test/idempotency.spec.js' { + declare module.exports: $Exports<'markdown-draft-js/test/idempotency.spec'>; +} +declare module 'markdown-draft-js/test/markdown-to-draft.spec.js' { + declare module.exports: $Exports<'markdown-draft-js/test/markdown-to-draft.spec'>; +} diff --git a/flow-typed/npm/ms_vx.x.x.js b/flow-typed/npm/ms_vx.x.x.js new file mode 100644 index 0000000000..dfdcb77a42 --- /dev/null +++ b/flow-typed/npm/ms_vx.x.x.js @@ -0,0 +1,33 @@ +// flow-typed signature: 920f2fd7d370e63b28ba6b9002a0ad7c +// flow-typed version: <>/ms_vx.x.x/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'ms' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'ms' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ + + +// Filename aliases +declare module 'ms/index' { + declare module.exports: $Exports<'ms'>; +} +declare module 'ms/index.js' { + declare module.exports: $Exports<'ms'>; +} diff --git a/flow-typed/npm/query-string_v5.x.x.js b/flow-typed/npm/query-string_v5.x.x.js deleted file mode 100644 index c3f69bae8b..0000000000 --- a/flow-typed/npm/query-string_v5.x.x.js +++ /dev/null @@ -1,21 +0,0 @@ -// flow-typed signature: c1bd04fbd22ec1a035ddf9f29c05396a -// flow-typed version: 838df4e5fd/query-string_v5.x.x/flow_>=v0.32.x - -declare module 'query-string' { - declare type ArrayFormat = 'none' | 'bracket' | 'index'; - declare type ParseOptions = {| - arrayFormat?: ArrayFormat, - |}; - - declare type StringifyOptions = {| - arrayFormat?: ArrayFormat, - encode?: boolean, - strict?: boolean, - |}; - - declare module.exports: { - extract(str: string): ?string, - parse(str: string, opts?: ParseOptions): Object, - stringify(obj: Object, opts?: StringifyOptions): string, - }; -} diff --git a/flow-typed/npm/query-string_vx.x.x.js b/flow-typed/npm/query-string_vx.x.x.js new file mode 100644 index 0000000000..0a7b3f8d35 --- /dev/null +++ b/flow-typed/npm/query-string_vx.x.x.js @@ -0,0 +1,33 @@ +// flow-typed signature: 4e8e8bebea560639ddc15b6087ce2baf +// flow-typed version: <>/query-string_v6.1.0/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'query-string' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'query-string' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ + + +// Filename aliases +declare module 'query-string/index' { + declare module.exports: $Exports<'query-string'>; +} +declare module 'query-string/index.js' { + declare module.exports: $Exports<'query-string'>; +} diff --git a/flow-typed/npm/raf_vx.x.x.js b/flow-typed/npm/raf_vx.x.x.js new file mode 100644 index 0000000000..ed4abf8d0f --- /dev/null +++ b/flow-typed/npm/raf_vx.x.x.js @@ -0,0 +1,52 @@ +// flow-typed signature: 887ce984c0a5fd424aeca109bae9ea61 +// flow-typed version: <>/raf_v3.4.0/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'raf' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'raf' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'raf/polyfill' { + declare module.exports: any; +} + +declare module 'raf/test' { + declare module.exports: any; +} + +declare module 'raf/window' { + declare module.exports: any; +} + +// Filename aliases +declare module 'raf/index' { + declare module.exports: $Exports<'raf'>; +} +declare module 'raf/index.js' { + declare module.exports: $Exports<'raf'>; +} +declare module 'raf/polyfill.js' { + declare module.exports: $Exports<'raf/polyfill'>; +} +declare module 'raf/test.js' { + declare module.exports: $Exports<'raf/test'>; +} +declare module 'raf/window.js' { + declare module.exports: $Exports<'raf/window'>; +} diff --git a/flow-typed/npm/react-hot-loader_vx.x.x.js b/flow-typed/npm/react-hot-loader_vx.x.x.js new file mode 100644 index 0000000000..ffb4b7e411 --- /dev/null +++ b/flow-typed/npm/react-hot-loader_vx.x.x.js @@ -0,0 +1,73 @@ +// flow-typed signature: e4fabee0a29fd025cb9354e1b63344aa +// flow-typed version: <>/react-hot-loader_v4.3.11/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'react-hot-loader' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'react-hot-loader' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'react-hot-loader/babel' { + declare module.exports: any; +} + +declare module 'react-hot-loader/dist/babel.development' { + declare module.exports: any; +} + +declare module 'react-hot-loader/dist/babel.production.min' { + declare module.exports: any; +} + +declare module 'react-hot-loader/dist/react-hot-loader.development' { + declare module.exports: any; +} + +declare module 'react-hot-loader/dist/react-hot-loader.production.min' { + declare module.exports: any; +} + +declare module 'react-hot-loader/patch' { + declare module.exports: any; +} + +// Filename aliases +declare module 'react-hot-loader/babel.js' { + declare module.exports: $Exports<'react-hot-loader/babel'>; +} +declare module 'react-hot-loader/dist/babel.development.js' { + declare module.exports: $Exports<'react-hot-loader/dist/babel.development'>; +} +declare module 'react-hot-loader/dist/babel.production.min.js' { + declare module.exports: $Exports<'react-hot-loader/dist/babel.production.min'>; +} +declare module 'react-hot-loader/dist/react-hot-loader.development.js' { + declare module.exports: $Exports<'react-hot-loader/dist/react-hot-loader.development'>; +} +declare module 'react-hot-loader/dist/react-hot-loader.production.min.js' { + declare module.exports: $Exports<'react-hot-loader/dist/react-hot-loader.production.min'>; +} +declare module 'react-hot-loader/index' { + declare module.exports: $Exports<'react-hot-loader'>; +} +declare module 'react-hot-loader/index.js' { + declare module.exports: $Exports<'react-hot-loader'>; +} +declare module 'react-hot-loader/patch.js' { + declare module.exports: $Exports<'react-hot-loader/patch'>; +} diff --git a/flow-typed/npm/react-image_vx.x.x.js b/flow-typed/npm/react-image_vx.x.x.js new file mode 100644 index 0000000000..f6d17e9253 --- /dev/null +++ b/flow-typed/npm/react-image_vx.x.x.js @@ -0,0 +1,88 @@ +// flow-typed signature: 70f7fe7601a56f8bfeb1f08c45ca056a +// flow-typed version: <>/react-image_v1.5.1/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'react-image' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'react-image' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'react-image/cjs/index' { + declare module.exports: any; +} + +declare module 'react-image/cjs/index.ssr-test' { + declare module.exports: any; +} + +declare module 'react-image/cjs/index.test' { + declare module.exports: any; +} + +declare module 'react-image/es/index' { + declare module.exports: any; +} + +declare module 'react-image/es/index.ssr-test' { + declare module.exports: any; +} + +declare module 'react-image/es/index.test' { + declare module.exports: any; +} + +declare module 'react-image/umd/index' { + declare module.exports: any; +} + +declare module 'react-image/umd/index.min' { + declare module.exports: any; +} + +declare module 'react-image/umd/index.test' { + declare module.exports: any; +} + +// Filename aliases +declare module 'react-image/cjs/index.js' { + declare module.exports: $Exports<'react-image/cjs/index'>; +} +declare module 'react-image/cjs/index.ssr-test.js' { + declare module.exports: $Exports<'react-image/cjs/index.ssr-test'>; +} +declare module 'react-image/cjs/index.test.js' { + declare module.exports: $Exports<'react-image/cjs/index.test'>; +} +declare module 'react-image/es/index.js' { + declare module.exports: $Exports<'react-image/es/index'>; +} +declare module 'react-image/es/index.ssr-test.js' { + declare module.exports: $Exports<'react-image/es/index.ssr-test'>; +} +declare module 'react-image/es/index.test.js' { + declare module.exports: $Exports<'react-image/es/index.test'>; +} +declare module 'react-image/umd/index.js' { + declare module.exports: $Exports<'react-image/umd/index'>; +} +declare module 'react-image/umd/index.min.js' { + declare module.exports: $Exports<'react-image/umd/index.min'>; +} +declare module 'react-image/umd/index.test.js' { + declare module.exports: $Exports<'react-image/umd/index.test'>; +} diff --git a/flow-typed/npm/react-mentions_vx.x.x.js b/flow-typed/npm/react-mentions_vx.x.x.js new file mode 100644 index 0000000000..8343d6a1f6 --- /dev/null +++ b/flow-typed/npm/react-mentions_vx.x.x.js @@ -0,0 +1,151 @@ +// flow-typed signature: 12ab11615dc44ebfea392e40b67a020e +// flow-typed version: <>/react-mentions_v2.4.1/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'react-mentions' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'react-mentions' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'react-mentions/es/Highlighter' { + declare module.exports: any; +} + +declare module 'react-mentions/es/index' { + declare module.exports: any; +} + +declare module 'react-mentions/es/LoadingIndicator' { + declare module.exports: any; +} + +declare module 'react-mentions/es/Mention' { + declare module.exports: any; +} + +declare module 'react-mentions/es/MentionsInput' { + declare module.exports: any; +} + +declare module 'react-mentions/es/Suggestion' { + declare module.exports: any; +} + +declare module 'react-mentions/es/SuggestionsOverlay' { + declare module.exports: any; +} + +declare module 'react-mentions/es/utils' { + declare module.exports: any; +} + +declare module 'react-mentions/lib/Highlighter' { + declare module.exports: any; +} + +declare module 'react-mentions/lib/index' { + declare module.exports: any; +} + +declare module 'react-mentions/lib/LoadingIndicator' { + declare module.exports: any; +} + +declare module 'react-mentions/lib/Mention' { + declare module.exports: any; +} + +declare module 'react-mentions/lib/MentionsInput' { + declare module.exports: any; +} + +declare module 'react-mentions/lib/Suggestion' { + declare module.exports: any; +} + +declare module 'react-mentions/lib/SuggestionsOverlay' { + declare module.exports: any; +} + +declare module 'react-mentions/lib/utils' { + declare module.exports: any; +} + +declare module 'react-mentions/umd/react-mentions' { + declare module.exports: any; +} + +declare module 'react-mentions/umd/react-mentions.min' { + declare module.exports: any; +} + +// Filename aliases +declare module 'react-mentions/es/Highlighter.js' { + declare module.exports: $Exports<'react-mentions/es/Highlighter'>; +} +declare module 'react-mentions/es/index.js' { + declare module.exports: $Exports<'react-mentions/es/index'>; +} +declare module 'react-mentions/es/LoadingIndicator.js' { + declare module.exports: $Exports<'react-mentions/es/LoadingIndicator'>; +} +declare module 'react-mentions/es/Mention.js' { + declare module.exports: $Exports<'react-mentions/es/Mention'>; +} +declare module 'react-mentions/es/MentionsInput.js' { + declare module.exports: $Exports<'react-mentions/es/MentionsInput'>; +} +declare module 'react-mentions/es/Suggestion.js' { + declare module.exports: $Exports<'react-mentions/es/Suggestion'>; +} +declare module 'react-mentions/es/SuggestionsOverlay.js' { + declare module.exports: $Exports<'react-mentions/es/SuggestionsOverlay'>; +} +declare module 'react-mentions/es/utils.js' { + declare module.exports: $Exports<'react-mentions/es/utils'>; +} +declare module 'react-mentions/lib/Highlighter.js' { + declare module.exports: $Exports<'react-mentions/lib/Highlighter'>; +} +declare module 'react-mentions/lib/index.js' { + declare module.exports: $Exports<'react-mentions/lib/index'>; +} +declare module 'react-mentions/lib/LoadingIndicator.js' { + declare module.exports: $Exports<'react-mentions/lib/LoadingIndicator'>; +} +declare module 'react-mentions/lib/Mention.js' { + declare module.exports: $Exports<'react-mentions/lib/Mention'>; +} +declare module 'react-mentions/lib/MentionsInput.js' { + declare module.exports: $Exports<'react-mentions/lib/MentionsInput'>; +} +declare module 'react-mentions/lib/Suggestion.js' { + declare module.exports: $Exports<'react-mentions/lib/Suggestion'>; +} +declare module 'react-mentions/lib/SuggestionsOverlay.js' { + declare module.exports: $Exports<'react-mentions/lib/SuggestionsOverlay'>; +} +declare module 'react-mentions/lib/utils.js' { + declare module.exports: $Exports<'react-mentions/lib/utils'>; +} +declare module 'react-mentions/umd/react-mentions.js' { + declare module.exports: $Exports<'react-mentions/umd/react-mentions'>; +} +declare module 'react-mentions/umd/react-mentions.min.js' { + declare module.exports: $Exports<'react-mentions/umd/react-mentions.min'>; +} diff --git a/flow-typed/npm/react-navigation-props-mapper_vx.x.x.js b/flow-typed/npm/react-navigation-props-mapper_vx.x.x.js deleted file mode 100644 index ddd9a7e9bd..0000000000 --- a/flow-typed/npm/react-navigation-props-mapper_vx.x.x.js +++ /dev/null @@ -1,18 +0,0 @@ -// flow-typed signature: 9ab0e60355c0dbfc2a67ab1bb2229719 -// flow-typed version: <>/react-navigation-props-mapper_v0.1.2/flow_v0.63.1 - -/** - * This is an autogenerated libdef stub for: - * - * 'react-navigation-props-mapper' - * - * Fill this stub out by replacing all the `any` types. - * - * Once filled out, we encourage you to share your work with the - * community by sending a pull request to: - * https://github.com/flowtype/flow-typed - */ - -declare module 'react-navigation-props-mapper' { - declare module.exports: any; -} diff --git a/flow-typed/npm/react-popper_vx.x.x.js b/flow-typed/npm/react-popper_vx.x.x.js new file mode 100644 index 0000000000..4498ed0b74 --- /dev/null +++ b/flow-typed/npm/react-popper_vx.x.x.js @@ -0,0 +1,123 @@ +// flow-typed signature: 3616931cff0dd9b73bbe98046f6a84d6 +// flow-typed version: <>/react-popper_v1.0.2/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'react-popper' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'react-popper' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'react-popper/dist/index.umd' { + declare module.exports: any; +} + +declare module 'react-popper/dist/index.umd.min' { + declare module.exports: any; +} + +declare module 'react-popper/lib/cjs/__typings__/main-test' { + declare module.exports: any; +} + +declare module 'react-popper/lib/cjs/index' { + declare module.exports: any; +} + +declare module 'react-popper/lib/cjs/Manager' { + declare module.exports: any; +} + +declare module 'react-popper/lib/cjs/Popper' { + declare module.exports: any; +} + +declare module 'react-popper/lib/cjs/Reference' { + declare module.exports: any; +} + +declare module 'react-popper/lib/cjs/utils' { + declare module.exports: any; +} + +declare module 'react-popper/lib/esm/__typings__/main-test' { + declare module.exports: any; +} + +declare module 'react-popper/lib/esm/index' { + declare module.exports: any; +} + +declare module 'react-popper/lib/esm/Manager' { + declare module.exports: any; +} + +declare module 'react-popper/lib/esm/Popper' { + declare module.exports: any; +} + +declare module 'react-popper/lib/esm/Reference' { + declare module.exports: any; +} + +declare module 'react-popper/lib/esm/utils' { + declare module.exports: any; +} + +// Filename aliases +declare module 'react-popper/dist/index.umd.js' { + declare module.exports: $Exports<'react-popper/dist/index.umd'>; +} +declare module 'react-popper/dist/index.umd.min.js' { + declare module.exports: $Exports<'react-popper/dist/index.umd.min'>; +} +declare module 'react-popper/lib/cjs/__typings__/main-test.js' { + declare module.exports: $Exports<'react-popper/lib/cjs/__typings__/main-test'>; +} +declare module 'react-popper/lib/cjs/index.js' { + declare module.exports: $Exports<'react-popper/lib/cjs/index'>; +} +declare module 'react-popper/lib/cjs/Manager.js' { + declare module.exports: $Exports<'react-popper/lib/cjs/Manager'>; +} +declare module 'react-popper/lib/cjs/Popper.js' { + declare module.exports: $Exports<'react-popper/lib/cjs/Popper'>; +} +declare module 'react-popper/lib/cjs/Reference.js' { + declare module.exports: $Exports<'react-popper/lib/cjs/Reference'>; +} +declare module 'react-popper/lib/cjs/utils.js' { + declare module.exports: $Exports<'react-popper/lib/cjs/utils'>; +} +declare module 'react-popper/lib/esm/__typings__/main-test.js' { + declare module.exports: $Exports<'react-popper/lib/esm/__typings__/main-test'>; +} +declare module 'react-popper/lib/esm/index.js' { + declare module.exports: $Exports<'react-popper/lib/esm/index'>; +} +declare module 'react-popper/lib/esm/Manager.js' { + declare module.exports: $Exports<'react-popper/lib/esm/Manager'>; +} +declare module 'react-popper/lib/esm/Popper.js' { + declare module.exports: $Exports<'react-popper/lib/esm/Popper'>; +} +declare module 'react-popper/lib/esm/Reference.js' { + declare module.exports: $Exports<'react-popper/lib/esm/Reference'>; +} +declare module 'react-popper/lib/esm/utils.js' { + declare module.exports: $Exports<'react-popper/lib/esm/utils'>; +} diff --git a/flow-typed/npm/react-visibility-sensor_vx.x.x.js b/flow-typed/npm/react-visibility-sensor_vx.x.x.js new file mode 100644 index 0000000000..26729ec43a --- /dev/null +++ b/flow-typed/npm/react-visibility-sensor_vx.x.x.js @@ -0,0 +1,116 @@ +// flow-typed signature: dfcf03dc1a3f237551ec04c69d552049 +// flow-typed version: <>/react-visibility-sensor_v5.0.1/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'react-visibility-sensor' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'react-visibility-sensor' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'react-visibility-sensor/dist/visibility-sensor' { + declare module.exports: any; +} + +declare module 'react-visibility-sensor/dist/visibility-sensor.min' { + declare module.exports: any; +} + +declare module 'react-visibility-sensor/example-umd/main' { + declare module.exports: any; +} + +declare module 'react-visibility-sensor/example/dist/bundle' { + declare module.exports: any; +} + +declare module 'react-visibility-sensor/example/main' { + declare module.exports: any; +} + +declare module 'react-visibility-sensor/gulpfile' { + declare module.exports: any; +} + +declare module 'react-visibility-sensor/karma.conf' { + declare module.exports: any; +} + +declare module 'react-visibility-sensor/lib/is-visible-with-offset' { + declare module.exports: any; +} + +declare module 'react-visibility-sensor/testconf' { + declare module.exports: any; +} + +declare module 'react-visibility-sensor/tests/bundle' { + declare module.exports: any; +} + +declare module 'react-visibility-sensor/tests/visibility-sensor-spec' { + declare module.exports: any; +} + +declare module 'react-visibility-sensor/visibility-sensor' { + declare module.exports: any; +} + +declare module 'react-visibility-sensor/webpack.config' { + declare module.exports: any; +} + +// Filename aliases +declare module 'react-visibility-sensor/dist/visibility-sensor.js' { + declare module.exports: $Exports<'react-visibility-sensor/dist/visibility-sensor'>; +} +declare module 'react-visibility-sensor/dist/visibility-sensor.min.js' { + declare module.exports: $Exports<'react-visibility-sensor/dist/visibility-sensor.min'>; +} +declare module 'react-visibility-sensor/example-umd/main.js' { + declare module.exports: $Exports<'react-visibility-sensor/example-umd/main'>; +} +declare module 'react-visibility-sensor/example/dist/bundle.js' { + declare module.exports: $Exports<'react-visibility-sensor/example/dist/bundle'>; +} +declare module 'react-visibility-sensor/example/main.js' { + declare module.exports: $Exports<'react-visibility-sensor/example/main'>; +} +declare module 'react-visibility-sensor/gulpfile.js' { + declare module.exports: $Exports<'react-visibility-sensor/gulpfile'>; +} +declare module 'react-visibility-sensor/karma.conf.js' { + declare module.exports: $Exports<'react-visibility-sensor/karma.conf'>; +} +declare module 'react-visibility-sensor/lib/is-visible-with-offset.js' { + declare module.exports: $Exports<'react-visibility-sensor/lib/is-visible-with-offset'>; +} +declare module 'react-visibility-sensor/testconf.js' { + declare module.exports: $Exports<'react-visibility-sensor/testconf'>; +} +declare module 'react-visibility-sensor/tests/bundle.js' { + declare module.exports: $Exports<'react-visibility-sensor/tests/bundle'>; +} +declare module 'react-visibility-sensor/tests/visibility-sensor-spec.jsx' { + declare module.exports: $Exports<'react-visibility-sensor/tests/visibility-sensor-spec'>; +} +declare module 'react-visibility-sensor/visibility-sensor.js' { + declare module.exports: $Exports<'react-visibility-sensor/visibility-sensor'>; +} +declare module 'react-visibility-sensor/webpack.config.js' { + declare module.exports: $Exports<'react-visibility-sensor/webpack.config'>; +} diff --git a/flow-typed/npm/redis-tag-cache_vx.x.x.js b/flow-typed/npm/redis-tag-cache_vx.x.x.js new file mode 100644 index 0000000000..bf361c7a40 --- /dev/null +++ b/flow-typed/npm/redis-tag-cache_vx.x.x.js @@ -0,0 +1,36 @@ +// flow-typed signature: c7f5b43c5e1b103e542648906166804f +// flow-typed version: <>/redis-tag-cache_v1.x.x/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'redis-tag-cache' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ +import type { RedisOptions } from 'ioredis'; + +declare module 'redis-tag-cache' { + declare type Options = { + defaultTimeout?: number, + redis?: RedisOptions, + } + + declare type SetOptions = { + timeout?: number + } + + declare class TagCache { + constructor(options?: Options): void; + get(...keys: Array): Promise; + set(key: string, data: any, tags: Array, options?: SetOptions): Promise; + invalidate(...tags: Array): Promise; + } + + declare module.exports: typeof TagCache; +} + diff --git a/flow-typed/npm/request-ip_vx.x.x.js b/flow-typed/npm/request-ip_vx.x.x.js new file mode 100644 index 0000000000..bcc9eb4563 --- /dev/null +++ b/flow-typed/npm/request-ip_vx.x.x.js @@ -0,0 +1,32 @@ +// flow-typed signature: 78b6b0fc3ee7604a3e67412a6cd6a121 +// flow-typed version: <>/request-ip_vx.x.x/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'request-ip' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'request-ip' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'request-ip/dist/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'request-ip/dist/index.js' { + declare module.exports: $Exports<'request-ip/dist/index'>; +} diff --git a/flow-typed/npm/rethinkhaberdashery_vx.x.x.js b/flow-typed/npm/rethinkhaberdashery_vx.x.x.js new file mode 100644 index 0000000000..49006afb5f --- /dev/null +++ b/flow-typed/npm/rethinkhaberdashery_vx.x.x.js @@ -0,0 +1,480 @@ +// flow-typed signature: 3bbe1f5fd24a38093d3be861a7f3eeb7 +// flow-typed version: <>/rethinkhaberdashery_v2.3.32/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'rethinkhaberdashery' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'rethinkhaberdashery' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'rethinkhaberdashery/browserify' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/connection' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/cursor' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/dequeue' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/error' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/helper' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/index' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/metadata' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/pool_master' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/pool' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/protodef' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/stream' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/term' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/transform_stream' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/lib/writable_stream' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/long_test/config' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/long_test/discovery' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/long_test/static' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/long_test/writable_stream' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/accessing-reql' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/administration' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/aggregation' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/backtrace' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/client-backtrace' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/config' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/control-structures' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/coverage' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/cursor' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/dates-and-times' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/datum' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/dequeue' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/document-manipulation' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/error' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/extra' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/geo' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/joins' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/manipulating-databases' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/manipulating-tables' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/math-and-logic' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/multiple-require' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/nodeify' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/pool_legacy' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/selecting-data' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/stable' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/stream' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/string-manipulation' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/transform-stream' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/transformations' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/util/common' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/util/fake_server/database' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/util/fake_server/document' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/util/fake_server/error' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/util/fake_server/group' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/util/fake_server/helper' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/util/fake_server/index' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/util/fake_server/node' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/util/fake_server/protodef' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/util/fake_server/query' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/util/fake_server/selection' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/util/fake_server/sequence' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/util/fake_server/table' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/writable-stream' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/test/writing-data' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/tool/generateTest' { + declare module.exports: any; +} + +declare module 'rethinkhaberdashery/tool/ulimit' { + declare module.exports: any; +} + +// Filename aliases +declare module 'rethinkhaberdashery/browserify.js' { + declare module.exports: $Exports<'rethinkhaberdashery/browserify'>; +} +declare module 'rethinkhaberdashery/lib/connection.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/connection'>; +} +declare module 'rethinkhaberdashery/lib/cursor.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/cursor'>; +} +declare module 'rethinkhaberdashery/lib/dequeue.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/dequeue'>; +} +declare module 'rethinkhaberdashery/lib/error.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/error'>; +} +declare module 'rethinkhaberdashery/lib/helper.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/helper'>; +} +declare module 'rethinkhaberdashery/lib/index.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/index'>; +} +declare module 'rethinkhaberdashery/lib/metadata.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/metadata'>; +} +declare module 'rethinkhaberdashery/lib/pool_master.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/pool_master'>; +} +declare module 'rethinkhaberdashery/lib/pool.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/pool'>; +} +declare module 'rethinkhaberdashery/lib/protodef.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/protodef'>; +} +declare module 'rethinkhaberdashery/lib/stream.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/stream'>; +} +declare module 'rethinkhaberdashery/lib/term.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/term'>; +} +declare module 'rethinkhaberdashery/lib/transform_stream.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/transform_stream'>; +} +declare module 'rethinkhaberdashery/lib/writable_stream.js' { + declare module.exports: $Exports<'rethinkhaberdashery/lib/writable_stream'>; +} +declare module 'rethinkhaberdashery/long_test/config.js' { + declare module.exports: $Exports<'rethinkhaberdashery/long_test/config'>; +} +declare module 'rethinkhaberdashery/long_test/discovery.js' { + declare module.exports: $Exports<'rethinkhaberdashery/long_test/discovery'>; +} +declare module 'rethinkhaberdashery/long_test/static.js' { + declare module.exports: $Exports<'rethinkhaberdashery/long_test/static'>; +} +declare module 'rethinkhaberdashery/long_test/writable_stream.js' { + declare module.exports: $Exports<'rethinkhaberdashery/long_test/writable_stream'>; +} +declare module 'rethinkhaberdashery/test/accessing-reql.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/accessing-reql'>; +} +declare module 'rethinkhaberdashery/test/administration.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/administration'>; +} +declare module 'rethinkhaberdashery/test/aggregation.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/aggregation'>; +} +declare module 'rethinkhaberdashery/test/backtrace.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/backtrace'>; +} +declare module 'rethinkhaberdashery/test/client-backtrace.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/client-backtrace'>; +} +declare module 'rethinkhaberdashery/test/config.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/config'>; +} +declare module 'rethinkhaberdashery/test/control-structures.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/control-structures'>; +} +declare module 'rethinkhaberdashery/test/coverage.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/coverage'>; +} +declare module 'rethinkhaberdashery/test/cursor.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/cursor'>; +} +declare module 'rethinkhaberdashery/test/dates-and-times.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/dates-and-times'>; +} +declare module 'rethinkhaberdashery/test/datum.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/datum'>; +} +declare module 'rethinkhaberdashery/test/dequeue.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/dequeue'>; +} +declare module 'rethinkhaberdashery/test/document-manipulation.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/document-manipulation'>; +} +declare module 'rethinkhaberdashery/test/error.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/error'>; +} +declare module 'rethinkhaberdashery/test/extra.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/extra'>; +} +declare module 'rethinkhaberdashery/test/geo.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/geo'>; +} +declare module 'rethinkhaberdashery/test/joins.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/joins'>; +} +declare module 'rethinkhaberdashery/test/manipulating-databases.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/manipulating-databases'>; +} +declare module 'rethinkhaberdashery/test/manipulating-tables.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/manipulating-tables'>; +} +declare module 'rethinkhaberdashery/test/math-and-logic.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/math-and-logic'>; +} +declare module 'rethinkhaberdashery/test/multiple-require.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/multiple-require'>; +} +declare module 'rethinkhaberdashery/test/nodeify.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/nodeify'>; +} +declare module 'rethinkhaberdashery/test/pool_legacy.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/pool_legacy'>; +} +declare module 'rethinkhaberdashery/test/selecting-data.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/selecting-data'>; +} +declare module 'rethinkhaberdashery/test/stable.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/stable'>; +} +declare module 'rethinkhaberdashery/test/stream.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/stream'>; +} +declare module 'rethinkhaberdashery/test/string-manipulation.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/string-manipulation'>; +} +declare module 'rethinkhaberdashery/test/transform-stream.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/transform-stream'>; +} +declare module 'rethinkhaberdashery/test/transformations.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/transformations'>; +} +declare module 'rethinkhaberdashery/test/util/common.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/util/common'>; +} +declare module 'rethinkhaberdashery/test/util/fake_server/database.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/util/fake_server/database'>; +} +declare module 'rethinkhaberdashery/test/util/fake_server/document.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/util/fake_server/document'>; +} +declare module 'rethinkhaberdashery/test/util/fake_server/error.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/util/fake_server/error'>; +} +declare module 'rethinkhaberdashery/test/util/fake_server/group.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/util/fake_server/group'>; +} +declare module 'rethinkhaberdashery/test/util/fake_server/helper.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/util/fake_server/helper'>; +} +declare module 'rethinkhaberdashery/test/util/fake_server/index.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/util/fake_server/index'>; +} +declare module 'rethinkhaberdashery/test/util/fake_server/node.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/util/fake_server/node'>; +} +declare module 'rethinkhaberdashery/test/util/fake_server/protodef.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/util/fake_server/protodef'>; +} +declare module 'rethinkhaberdashery/test/util/fake_server/query.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/util/fake_server/query'>; +} +declare module 'rethinkhaberdashery/test/util/fake_server/selection.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/util/fake_server/selection'>; +} +declare module 'rethinkhaberdashery/test/util/fake_server/sequence.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/util/fake_server/sequence'>; +} +declare module 'rethinkhaberdashery/test/util/fake_server/table.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/util/fake_server/table'>; +} +declare module 'rethinkhaberdashery/test/writable-stream.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/writable-stream'>; +} +declare module 'rethinkhaberdashery/test/writing-data.js' { + declare module.exports: $Exports<'rethinkhaberdashery/test/writing-data'>; +} +declare module 'rethinkhaberdashery/tool/generateTest.js' { + declare module.exports: $Exports<'rethinkhaberdashery/tool/generateTest'>; +} +declare module 'rethinkhaberdashery/tool/ulimit.js' { + declare module.exports: $Exports<'rethinkhaberdashery/tool/ulimit'>; +} diff --git a/flow-typed/npm/sanitize-filename_vx.x.x.js b/flow-typed/npm/sanitize-filename_vx.x.x.js new file mode 100644 index 0000000000..2551a42948 --- /dev/null +++ b/flow-typed/npm/sanitize-filename_vx.x.x.js @@ -0,0 +1,38 @@ +// flow-typed signature: caab09a417f5e378fe417ec757b5b726 +// flow-typed version: <>/sanitize-filename_v1.6.1/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'sanitize-filename' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'sanitize-filename' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'sanitize-filename/test' { + declare module.exports: any; +} + +// Filename aliases +declare module 'sanitize-filename/index' { + declare module.exports: $Exports<'sanitize-filename'>; +} +declare module 'sanitize-filename/index.js' { + declare module.exports: $Exports<'sanitize-filename'>; +} +declare module 'sanitize-filename/test.js' { + declare module.exports: $Exports<'sanitize-filename/test'>; +} diff --git a/flow-typed/npm/abab_vx.x.x.js b/flow-typed/npm/sha1_vx.x.x.js similarity index 50% rename from flow-typed/npm/abab_vx.x.x.js rename to flow-typed/npm/sha1_vx.x.x.js index dc19fb7bab..6a08e0d473 100644 --- a/flow-typed/npm/abab_vx.x.x.js +++ b/flow-typed/npm/sha1_vx.x.x.js @@ -1,10 +1,10 @@ -// flow-typed signature: 229d182ee18954e4b62113295241ada3 -// flow-typed version: <>/abab_v1.x/flow_v0.66.0 +// flow-typed signature: 25c9e66214b761887c569c0570c603df +// flow-typed version: <>/sha1_v1.1.1/flow_v0.66.0 /** * This is an autogenerated libdef stub for: * - * 'abab' + * 'sha1' * * Fill this stub out by replacing all the `any` types. * @@ -13,7 +13,7 @@ * https://github.com/flowtype/flow-typed */ -declare module 'abab' { +declare module 'sha1' { declare module.exports: any; } @@ -22,24 +22,18 @@ declare module 'abab' { * require those files directly. Feel free to delete any files that aren't * needed. */ -declare module 'abab/lib/atob' { +declare module 'sha1/sha1' { declare module.exports: any; } -declare module 'abab/lib/btoa' { +declare module 'sha1/test' { declare module.exports: any; } // Filename aliases -declare module 'abab/index' { - declare module.exports: $Exports<'abab'>; +declare module 'sha1/sha1.js' { + declare module.exports: $Exports<'sha1/sha1'>; } -declare module 'abab/index.js' { - declare module.exports: $Exports<'abab'>; -} -declare module 'abab/lib/atob.js' { - declare module.exports: $Exports<'abab/lib/atob'>; -} -declare module 'abab/lib/btoa.js' { - declare module.exports: $Exports<'abab/lib/btoa'>; +declare module 'sha1/test.js' { + declare module.exports: $Exports<'sha1/test'>; } diff --git a/flow-typed/npm/string-similarity_vx.x.x.js b/flow-typed/npm/string-similarity_vx.x.x.js new file mode 100644 index 0000000000..fc078580b7 --- /dev/null +++ b/flow-typed/npm/string-similarity_vx.x.x.js @@ -0,0 +1,46 @@ +// flow-typed signature: 68358bc2bb6add3943d67e0592a63d74 +// flow-typed version: <>/string-similarity_v1.2.0/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'string-similarity' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'string-similarity' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'string-similarity/compare-strings' { + declare module.exports: any; +} + +declare module 'string-similarity/compare-strings.spec' { + declare module.exports: any; +} + +declare module 'string-similarity/gulpfile' { + declare module.exports: any; +} + +// Filename aliases +declare module 'string-similarity/compare-strings.js' { + declare module.exports: $Exports<'string-similarity/compare-strings'>; +} +declare module 'string-similarity/compare-strings.spec.js' { + declare module.exports: $Exports<'string-similarity/compare-strings.spec'>; +} +declare module 'string-similarity/gulpfile.js' { + declare module.exports: $Exports<'string-similarity/gulpfile'>; +} diff --git a/flow-typed/react-native.js b/flow-typed/react-native.js index d7a89a1370..4db7b13496 100644 --- a/flow-typed/react-native.js +++ b/flow-typed/react-native.js @@ -1,3 +1,3 @@ declare module 'react-native' { - declare module.exports: any; + declare module.exports: any; } diff --git a/hermes/README.md b/hermes/README.md index 3f3f8d7c72..cc496b335c 100644 --- a/hermes/README.md +++ b/hermes/README.md @@ -2,27 +2,6 @@ In an effort to maintain consistency across our emails, please following the following guidelines: -1. Abort all repeating emails that do not have an `unsubscribeToken` in the email's TemplateModel -2. All email TemplateModels should have the following data shape: - -``` -{ - to: string!, - subject: string!, - preheader: string!, - recipient: { - username: string! - }, - unsubscribeToken: string, - data: mixed -} -``` - -To break down these fields: - -`to` => the recipient's email address -`subject` => always calculate subject strings inside of Hermes for consistency, even if it is a static string -`preheader` => represents the "email preview" that gets displayed in email clients and push notifications. The content of this string is meant to draw users into the email, but **will not** be displayed in the email body itself -`recipient` => contains user data about the person receiving the email. The only field that is absolutely required is the `username` field. If a `username` field does not exist for the recipient, the email should always be aborted. The `username` is used to link to the user's notification preferences, and if it doesn't exist they have no way to change their preferences. -`unsubscribeToken` => this is a required field for all *repeating* emails. A repeating email is one that a user might receive regularly based on actions taken by others on Spectrum. Examples include: new direct message notifications, new thread notifications. The `unsubscribeToken` is *not required* for non-repeating emails, which should be one-off emails like receipts, community invitations, email validations, etc. -`data` => can contain any abstract data needed for the email to render its body content. \ No newline at end of file +1. Abort all emails that do not have an `unsubscribeToken` in the email's dynamic_template_data +2. Abort all emails that are being sent to a user without a username - they probably aren't active. +3. Pre-process and upload to SendGrid via the local syncing script. See `email-template-scripts/README.md`. \ No newline at end of file diff --git a/hermes/events/index.js b/hermes/events/index.js new file mode 100644 index 0000000000..74a21396cd --- /dev/null +++ b/hermes/events/index.js @@ -0,0 +1,37 @@ +// @flow +import { sendgridEventQueue } from 'shared/bull/queues'; + +// $FlowIssue +export default (req, res) => { + const finished = () => { + res.writeHead(200, { 'Content-Type': 'application/json' }); + return res.end('okay'); + }; + + let body = []; + + // $FlowIssue + req.on('data', chunk => body.push(chunk)); + + req.on('end', () => { + // $FlowIssue + body = Buffer.concat(body).toString(); + try { + body = JSON.parse(body); + } catch (err) { + console.error(err.message); + return finished(); + } + + if (!body || body.length === 0) { + return finished(); + } + + body + .filter(event => event && event.hasOwnProperty('status')) + .filter(event => event.status === '5.0.0') + .map(event => sendgridEventQueue.add({ event })); + + return finished(); + }); +}; diff --git a/hermes/index.js b/hermes/index.js index 059de68c6b..4b0735baae 100644 --- a/hermes/index.js +++ b/hermes/index.js @@ -1,6 +1,10 @@ // @flow const debug = require('debug')('hermes'); const createWorker = require('../shared/bull/create-worker'); + +import handleSendGridWebhooks from './events'; +import processSendGridWebhookEvent from './queues/sendgrid-webhook-events'; + import processSendNewMessageEmail from './queues/send-new-message-email'; import processSendNewMentionThreadEmail from './queues/send-mention-thread-email'; import processSendNewMentionMessageEmail from './queues/send-mention-message-email'; @@ -9,12 +13,6 @@ import processSendCommunityInviteEmail from './queues/send-community-invite-emai import processSendUserWelcomeEmail from './queues/send-user-welcome-email'; import processSendNewCommunityWelcomeEmail from './queues/send-new-community-welcome-email'; -import processSendCommunityInvoiceReceiptEmail from './queues/send-community-invoice-receipt-email'; -import processSendCommunityPaymentSucceededEmail from './queues/send-community-payment-succeeded-email'; -import processSendCommunityPaymentFailedEmail from './queues/send-community-payment-failed-email'; -import processSendCommunityCardExpiringWarningEmail from './queues/send-community-card-expiring-warning-email'; - -import processSendProInvoiceReceiptEmail from './queues/send-pro-invoice-receipt-email'; import processSendNewThreadEmail from './queues/send-new-thread-email'; import processSendDigestEmail from './queues/send-digest-email'; import processSendEmailValidationEmail from './queues/send-email-validation-email'; @@ -22,16 +20,15 @@ import processSendAdministratorEmailValidationEmail from './queues/send-administ import processSendAdminCommunityCreatedEmail from './queues/send-admin-community-created-email'; import processSendAdminToxicContentEmail from './queues/send-admin-toxic-content-email'; import processSendAdminSlackImportProcessedEmail from './queues/send-admin-slack-import-email'; +import processSendAdminUserSpammingThreadsNotificationEmail from './queues/send-admin-user-spamming-threads-notification-email'; import processSendAdminActiveCommunityReportEmail from './queues/send-admin-active-community-report-email'; +import processSendAdminUserReportedEmail from './queues/send-admin-user-reported-email'; import processSendRequestJoinPrivateChannelEmail from './queues/send-private-channel-request-sent-email'; import processSendPrivateChannelRequestApprovedEmail from './queues/send-private-channel-request-approved-email'; +import processSendRequestJoinPrivateCommunityEmail from './queues/send-private-community-request-sent-email'; +import processSendPrivateCommunityRequestApprovedEmail from './queues/send-private-community-request-approved-email'; import { - SEND_COMMUNITY_INVOICE_RECEIPT_EMAIL, - SEND_COMMUNITY_CARD_EXPIRING_WARNING_EMAIL, - SEND_COMMUNITY_PAYMENT_FAILED_EMAIL, - SEND_COMMUNITY_PAYMENT_SUCCEEDED_EMAIL, - SEND_PRO_INVOICE_RECEIPT_EMAIL, SEND_COMMUNITY_INVITE_EMAIL, SEND_NEW_MESSAGE_EMAIL, SEND_NEW_MENTION_THREAD_EMAIL, @@ -46,45 +43,64 @@ import { SEND_ADMIN_COMMUNITY_CREATED_EMAIL, SEND_ADMIN_TOXIC_MESSAGE_EMAIL, SEND_ADMIN_SLACK_IMPORT_PROCESSED_EMAIL, + SEND_ADMIN_USER_SPAMMING_THREADS_NOTIFICATION_EMAIL, SEND_ACTIVE_COMMUNITY_ADMIN_REPORT_EMAIL, SEND_PRIVATE_CHANNEL_REQUEST_SENT_EMAIL, SEND_PRIVATE_CHANNEL_REQUEST_APPROVED_EMAIL, + SEND_PRIVATE_COMMUNITY_REQUEST_SENT_EMAIL, + SEND_PRIVATE_COMMUNITY_REQUEST_APPROVED_EMAIL, + SEND_ADMIN_USER_REPORTED_EMAIL, + SENDGRID_WEBHOOK_EVENT, } from './queues/constants'; const PORT = process.env.PORT || 3002; -console.log('\n✉️ Hermes, the email worker, is starting...'); +debug('\n✉️ Hermes, the email worker, is starting...'); debug('Logging with debug enabled!'); -console.log(''); +debug(''); -const server = createWorker({ - [SEND_COMMUNITY_INVITE_EMAIL]: processSendCommunityInviteEmail, - [SEND_NEW_MESSAGE_EMAIL]: processSendNewMessageEmail, - [SEND_NEW_MENTION_THREAD_EMAIL]: processSendNewMentionThreadEmail, - [SEND_NEW_MENTION_MESSAGE_EMAIL]: processSendNewMentionMessageEmail, - [SEND_NEW_DIRECT_MESSAGE_EMAIL]: processSendNewDirectMessageEmail, - [SEND_NEW_USER_WELCOME_EMAIL]: processSendUserWelcomeEmail, - [SEND_NEW_COMMUNITY_WELCOME_EMAIL]: processSendNewCommunityWelcomeEmail, +const requestHandler = (req, res, defaultResponse) => { + if (req.url === '/favicon.ico') return; - [SEND_COMMUNITY_INVOICE_RECEIPT_EMAIL]: processSendCommunityInvoiceReceiptEmail, - [SEND_COMMUNITY_PAYMENT_SUCCEEDED_EMAIL]: processSendCommunityPaymentSucceededEmail, - [SEND_COMMUNITY_PAYMENT_FAILED_EMAIL]: processSendCommunityPaymentFailedEmail, - [SEND_COMMUNITY_CARD_EXPIRING_WARNING_EMAIL]: processSendCommunityCardExpiringWarningEmail, + if (req.url === '/sendgrid') { + return handleSendGridWebhooks(req, res); + } else { + return defaultResponse(); + } +}; - [SEND_PRO_INVOICE_RECEIPT_EMAIL]: processSendProInvoiceReceiptEmail, - [SEND_THREAD_CREATED_NOTIFICATION_EMAIL]: processSendNewThreadEmail, - [SEND_DIGEST_EMAIL]: processSendDigestEmail, - [SEND_EMAIL_VALIDATION_EMAIL]: processSendEmailValidationEmail, - [SEND_ADMINISTRATOR_EMAIL_VALIDATION_EMAIL]: processSendAdministratorEmailValidationEmail, - [SEND_ADMIN_COMMUNITY_CREATED_EMAIL]: processSendAdminCommunityCreatedEmail, - [SEND_ADMIN_TOXIC_MESSAGE_EMAIL]: processSendAdminToxicContentEmail, - [SEND_ADMIN_SLACK_IMPORT_PROCESSED_EMAIL]: processSendAdminSlackImportProcessedEmail, - [SEND_ACTIVE_COMMUNITY_ADMIN_REPORT_EMAIL]: processSendAdminActiveCommunityReportEmail, - [SEND_PRIVATE_CHANNEL_REQUEST_SENT_EMAIL]: processSendRequestJoinPrivateChannelEmail, - [SEND_PRIVATE_CHANNEL_REQUEST_APPROVED_EMAIL]: processSendPrivateChannelRequestApprovedEmail, -}); +const server = createWorker( + { + [SEND_COMMUNITY_INVITE_EMAIL]: processSendCommunityInviteEmail, + [SEND_NEW_MESSAGE_EMAIL]: processSendNewMessageEmail, + [SEND_NEW_MENTION_THREAD_EMAIL]: processSendNewMentionThreadEmail, + [SEND_NEW_MENTION_MESSAGE_EMAIL]: processSendNewMentionMessageEmail, + [SEND_NEW_DIRECT_MESSAGE_EMAIL]: processSendNewDirectMessageEmail, + [SEND_NEW_USER_WELCOME_EMAIL]: processSendUserWelcomeEmail, + [SEND_NEW_COMMUNITY_WELCOME_EMAIL]: processSendNewCommunityWelcomeEmail, + + [SEND_THREAD_CREATED_NOTIFICATION_EMAIL]: processSendNewThreadEmail, + [SEND_DIGEST_EMAIL]: processSendDigestEmail, + [SEND_EMAIL_VALIDATION_EMAIL]: processSendEmailValidationEmail, + [SEND_ADMINISTRATOR_EMAIL_VALIDATION_EMAIL]: processSendAdministratorEmailValidationEmail, + [SEND_ADMIN_COMMUNITY_CREATED_EMAIL]: processSendAdminCommunityCreatedEmail, + [SEND_ADMIN_TOXIC_MESSAGE_EMAIL]: processSendAdminToxicContentEmail, + [SEND_ADMIN_SLACK_IMPORT_PROCESSED_EMAIL]: processSendAdminSlackImportProcessedEmail, + [SEND_ADMIN_USER_SPAMMING_THREADS_NOTIFICATION_EMAIL]: processSendAdminUserSpammingThreadsNotificationEmail, + [SEND_ADMIN_USER_REPORTED_EMAIL]: processSendAdminUserReportedEmail, + [SEND_ACTIVE_COMMUNITY_ADMIN_REPORT_EMAIL]: processSendAdminActiveCommunityReportEmail, + [SEND_PRIVATE_CHANNEL_REQUEST_SENT_EMAIL]: processSendRequestJoinPrivateChannelEmail, + [SEND_PRIVATE_CHANNEL_REQUEST_APPROVED_EMAIL]: processSendPrivateChannelRequestApprovedEmail, + [SEND_PRIVATE_COMMUNITY_REQUEST_SENT_EMAIL]: processSendRequestJoinPrivateCommunityEmail, + [SEND_PRIVATE_COMMUNITY_REQUEST_APPROVED_EMAIL]: processSendPrivateCommunityRequestApprovedEmail, + + [SENDGRID_WEBHOOK_EVENT]: processSendGridWebhookEvent, + }, + {}, + requestHandler +); -console.log( +debug( // $FlowIssue `🗄 Queues open for business ${(process.env.NODE_ENV === 'production' && // $FlowIssue @@ -94,7 +110,7 @@ console.log( // $FlowIssue server.listen(PORT, 'localhost', () => { - console.log( + debug( `💉 Healthcheck server running at ${server.address().address}:${ server.address().port }` diff --git a/hermes/models/usersSettings.js b/hermes/models/usersSettings.js new file mode 100644 index 0000000000..0368448895 --- /dev/null +++ b/hermes/models/usersSettings.js @@ -0,0 +1,32 @@ +// @flow +import { db } from 'shared/db'; + +export const deactivateUserEmailNotifications = async (userId: string) => { + return await db + .table('usersSettings') + .getAll(userId, { index: 'userId' }) + .update({ + notifications: { + types: { + dailyDigest: { + email: false, + }, + newDirectMessage: { + email: false, + }, + newMention: { + email: false, + }, + newMessageInThreads: { + email: false, + }, + newThreadCreated: { + email: false, + }, + weeklyDigest: { + email: false, + }, + }, + }, + }); +}; diff --git a/hermes/package.json b/hermes/package.json index 9d80f41bb7..fed0c9c426 100644 --- a/hermes/package.json +++ b/hermes/package.json @@ -3,17 +3,31 @@ "start": "NODE_ENV=production node main.js" }, "dependencies": { + "@sendgrid/mail": "^6.3.1", + "aws-sdk": "^2.383.0", "bull": "3.3.10", - "debug": "^2.6.8", - "jsonwebtoken": "^8.0.1", + "datadog-metrics": "^0.8.1", + "debug": "^4.1.1", + "draft-js": "^0.10.5", + "escape-html": "^1.0.3", + "faker": "^4.1.0", + "ioredis": "3.2.2", + "jsonwebtoken": "^8.4.0", + "lodash.intersection": "^4.4.0", "node-env-file": "^0.1.8", - "now-env": "^3.0.2", - "postmark": "^1.3.1", - "raven": "^2.1.1", - "rethinkdbdash": "^2.3.29", - "source-map-support": "^0.4.15" + "now-env": "^3.1.0", + "performance-now": "^2.1.0", + "raven": "^2.6.4", + "redis-tag-cache": "^1.2.1", + "rethinkdb-inspector": "^0.3.3", + "rethinkdbdash": "^2.3.31", + "rethinkhaberdashery": "^2.3.32", + "sanitize-filename": "^1.6.1", + "source-map-support": "^0.4.18", + "toobusy-js": "^0.5.1", + "validator": "^10.11.0" }, "devDependencies": { - "json-stringify-pretty-compact": "^1.0.4" + "json-stringify-pretty-compact": "^1.2.0" } } diff --git a/hermes/queues/constants.js b/hermes/queues/constants.js index e97bef1ce8..da70fd75dd 100644 --- a/hermes/queues/constants.js +++ b/hermes/queues/constants.js @@ -8,16 +8,6 @@ export const SEND_NEW_USER_WELCOME_EMAIL = 'send new user welcome email'; export const SEND_NEW_COMMUNITY_WELCOME_EMAIL = 'send new community welcome email'; -export const SEND_COMMUNITY_INVOICE_RECEIPT_EMAIL = - 'send community invoice receipt email'; -export const SEND_COMMUNITY_PAYMENT_SUCCEEDED_EMAIL = - 'send community payment succeeded email'; -export const SEND_COMMUNITY_PAYMENT_FAILED_EMAIL = - 'send community payment failed email'; -export const SEND_COMMUNITY_CARD_EXPIRING_WARNING_EMAIL = - 'send community card expiring warning email'; - -export const SEND_PRO_INVOICE_RECEIPT_EMAIL = 'send pro invoice receipt email'; export const SEND_THREAD_CREATED_NOTIFICATION_EMAIL = 'send thread created notification email'; export const SEND_DIGEST_EMAIL = 'send digest email'; @@ -36,40 +26,76 @@ export const SEND_PRIVATE_CHANNEL_REQUEST_SENT_EMAIL = 'send request join private channel email'; export const SEND_PRIVATE_CHANNEL_REQUEST_APPROVED_EMAIL = 'send private channel request approved email'; +export const SEND_PRIVATE_COMMUNITY_REQUEST_SENT_EMAIL = + 'send request join private community email'; +export const SEND_PRIVATE_COMMUNITY_REQUEST_APPROVED_EMAIL = + 'send private community request approved email'; +export const SEND_ADMIN_USER_SPAMMING_THREADS_NOTIFICATION_EMAIL = + 'send admin user spamming threads notification email'; +export const SEND_ADMIN_USER_REPORTED_EMAIL = 'send admin user reported email'; +export const SENDGRID_WEBHOOK_EVENT = 'process sendgrid webhook event'; -export const NEW_MESSAGE_TEMPLATE = IS_PROD ? 2266041 : 3788381; -export const NEW_MENTION_THREAD_TEMPLATE = IS_PROD ? 3776541 : 3844623; -export const NEW_MENTION_MESSAGE_TEMPLATE = IS_PROD ? 3844364 : 3844624; -export const NEW_DIRECT_MESSAGE_TEMPLATE = 2911541; -export const NEW_USER_WELCOME_TEMPLATE = 2462726; -export const COMMUNITY_INVITE_TEMPLATE = 2302401; -export const NEW_COMMUNITY_WELCOME_TEMPLATE = 2600301; - -export const COMMUNITY_INVOICE_RECEIPT_TEMPLATE = IS_PROD ? 2647483 : 12345; -export const COMMUNITY_PAYMENT_SUCCEEDED_TEMPLATE = IS_PROD ? 5436583 : 5360481; -export const COMMUNITY_PAYMENT_FAILED_TEMPLATE = IS_PROD ? 5436762 : 5359821; -export const COMMUNITY_CARD_EXPIRING_WARNING_TEMPLATE = IS_PROD - ? 5436582 - : 5381761; +export const NEW_MESSAGE_TEMPLATE = IS_PROD + ? 'd-1379042b64ba4c6093fecaec7161480a' + : 'd-7a4c14fd440146f1b1cfcafb633bb040'; +export const NEW_MENTION_THREAD_TEMPLATE = IS_PROD + ? 'd-343ec51df23943e88d0709aa8e30312b' + : 'd-ff421ea0112a4525b6615bcc666ede00'; +export const NEW_MENTION_MESSAGE_TEMPLATE = IS_PROD + ? 'd-5ef46b71baee4c67bc46ab867e14d5bb' + : 'd-637189bc871744e9846694bff9f572ae'; +export const NEW_DIRECT_MESSAGE_TEMPLATE = IS_PROD + ? 'd-d959535dd2304830ad14e1850297f46d' + : 'd-3e289af9efe748308be2dde1d3786c0d'; +export const NEW_USER_WELCOME_TEMPLATE = IS_PROD + ? 'd-57c4111b847d4554b5d71f9fcf9170f8' + : 'd-2e46e5b65abc42b78941fbe027be4cd5'; +export const COMMUNITY_INVITE_TEMPLATE = IS_PROD + ? 'd-566d2a20ca9246108a55a4b4037b9e53' + : 'd-69b2e17b7a0f46048dcf4083ad4f9c48'; +export const NEW_COMMUNITY_WELCOME_TEMPLATE = IS_PROD + ? 'd-3a72aa9dec664ab4badd20e17586f477' + : 'd-dc7b4f048f4c460f9dd368fd3796421b'; -export const PRO_INVOICE_RECEIPT_TEMPLATE = 3037461; -export const NEW_THREAD_CREATED_TEMPLATE = IS_PROD ? 2713302 : 3786781; -export const DIGEST_TEMPLATE = IS_PROD ? 3071361 : 4165801; -export const DEBUG_TEMPLATE = 3374841; -export const EMAIL_VALIDATION_TEMPLATE = 3578681; -export const ADMINISTRATOR_EMAIL_VALIDATION_TEMPLATE = IS_PROD ? null : 4952721; +export const NEW_THREAD_CREATED_TEMPLATE = IS_PROD + ? 'd-2809d28ff1cc4d89a503e04de3388411' + : 'd-084c11332981443388ebdae05d0a2ff4'; +export const DIGEST_TEMPLATE = IS_PROD + ? 'd-54e8d4905da64158a98ddb92c9330583' + : 'd-5e52250c25be4654af82172970551919'; +export const EMAIL_VALIDATION_TEMPLATE = IS_PROD + ? 'd-8cd6c640e2d944c7a4bb8877722ff00f' + : 'd-9fbb3cc969364050aac891c255d31209'; +export const ADMINISTRATOR_EMAIL_VALIDATION_TEMPLATE = IS_PROD + ? 'd-c7ab6234c65b47d88413d9deaae0f7f1' + : 'd-a60e1df2d5294c73818759be13f09df4'; -export const ADMIN_COMMUNITY_CREATED_TEMPLATE = 3037441; -export const ADMIN_TOXIC_MESSAGE_TEMPLATE = 3867921; -export const ADMIN_SLACK_IMPORT_PROCESSED_TEMPLATE = 3934361; -export const ADMIN_ACTIVE_COMMUNITY_REPORT_TEMPLATE = 3947362; +export const ADMIN_COMMUNITY_CREATED_TEMPLATE = + 'd-8220ddfc3d3a436a9ea974348c9c2edd'; +export const ADMIN_TOXIC_MESSAGE_TEMPLATE = + 'd-f6e52c81dd8d49e29f23c5c6112d676b'; +export const ADMIN_SLACK_IMPORT_PROCESSED_TEMPLATE = + 'd-b3f8d36ef3354ce987a352ce39893432'; +export const ADMIN_ACTIVE_COMMUNITY_REPORT_TEMPLATE = + 'd-82812e47e2ea458c8ded5be8d3de4f48'; +export const ADMIN_USER_SPAMMING_THREADS_NOTIFICATION_TEMPLATE = + 'd-65de04a810d84af7b76a57f7b4b6ebbe'; +export const ADMIN_USER_REPORTED_TEMPLATE = + 'd-7340d2f62edd4af6a4c95f87a8d4e1c6'; export const PRIVATE_CHANNEL_REQUEST_SENT_TEMPLATE = IS_PROD - ? 4550702 - : 4543221; + ? 'd-fe75baaf293541889bd3a573c2d9b1c5' + : 'd-29f3f62815004e0bb3b9f884c9fb3901'; export const PRIVATE_CHANNEL_REQUEST_APPROVED_TEMPLATE = IS_PROD - ? 4550804 - : 4543861; + ? 'd-b419b76ca66046b3b24ead7b3a0cbb36' + : 'd-6bc3fffa3fa64e369035bc906b3975dd'; + +export const PRIVATE_COMMUNITY_REQUEST_SENT_TEMPLATE = IS_PROD + ? 'd-0e21a47bdce348a093c86b8779d84687' + : 'd-743d07e016ee4798a87c06b5dd0a27a1'; +export const PRIVATE_COMMUNITY_REQUEST_APPROVED_TEMPLATE = IS_PROD + ? 'd-73135e016dbf47bbb37ed45eb7860b81' + : 'd-d91de18c257344d2bf9ff0c628d1a92e'; // types used to generate unsubscribe tokens export const TYPE_DAILY_DIGEST = 'dailyDigest'; diff --git a/hermes/queues/send-admin-active-community-report-email.js b/hermes/queues/send-admin-active-community-report-email.js index 95b8cc7972..a4d89c2650 100644 --- a/hermes/queues/send-admin-active-community-report-email.js +++ b/hermes/queues/send-admin-active-community-report-email.js @@ -1,21 +1,27 @@ +// @flow const debug = require('debug')( 'hermes:queue:send-admin-active-community-report-email' ); import sendEmail from '../send-email'; +import Raven from 'shared/raven'; import { ADMIN_ACTIVE_COMMUNITY_REPORT_TEMPLATE, SEND_ACTIVE_COMMUNITY_ADMIN_REPORT_EMAIL, } from './constants'; +import type { + AdminActiveCommunityReportEmailJobData, + Job, +} from 'shared/bull/types'; +import formatDate from '../utils/format-date'; -export default job => { +export default ( + job: Job +): Promise => { debug(`\nnew job: ${job.id}`); const { - allDac, - allWac, - allMac, - overlappingDac, - overlappingWac, - overlappingMac, + dacCount, + wacCount, + macCount, newDac, newWac, newMac, @@ -23,33 +29,34 @@ export default job => { lostWac, lostMac, } = job.data; + const { day, month, year } = formatDate(); try { return sendEmail({ - TemplateId: ADMIN_ACTIVE_COMMUNITY_REPORT_TEMPLATE, - To: 'brian@spectrum.chat, max@spectrum.chat, bryn@spectrum.chat', - Tag: SEND_ACTIVE_COMMUNITY_ADMIN_REPORT_EMAIL, - TemplateModel: { + templateId: ADMIN_ACTIVE_COMMUNITY_REPORT_TEMPLATE, + to: [ + { email: 'brian@spectrum.chat ' }, + { email: 'max@spectrum.chat ' }, + { email: 'bryn@spectrum.chat ' }, + ], + dynamic_template_data: { + subject: `Active Community Report: ${month} ${day}, ${year}`, data: { - allDac, - allDacCount: allDac.length, - overlappingDac: overlappingDac.join(', '), + dacCount, + wacCount, + macCount, newDac: newDac.join(', '), - lostDac: lostDac.join(', '), - allWac: allWac.join(', '), - allWacCount: allWac.length, - overlappingWac: overlappingWac.join(', '), newWac: newWac.join(', '), - lostWac: lostWac.join(', '), - allMac: allMac.join(', '), - allMacCount: allMac.length, - overlappingMac: overlappingMac.join(', '), newMac: newMac.join(', '), + lostDac: lostDac.join(', '), + lostWac: lostWac.join(', '), lostMac: lostMac.join(', '), }, }, }); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-admin-community-created-email.js b/hermes/queues/send-admin-community-created-email.js index 237d51bdb4..e1e4f633f9 100644 --- a/hermes/queues/send-admin-community-created-email.js +++ b/hermes/queues/send-admin-community-created-email.js @@ -1,22 +1,27 @@ -const debug = require('debug')( - 'hermes:queue:send-admin-community-created-email' -); +// @flow +const debug = require('debug')('hermes:queue:admin-community-created-email'); +import Raven from 'shared/raven'; import sendEmail from '../send-email'; +import type { Job, AdminCommunityCreatedEmailJobData } from 'shared/bull/types'; import { ADMIN_COMMUNITY_CREATED_TEMPLATE, SEND_ADMIN_COMMUNITY_CREATED_EMAIL, } from './constants'; -export default job => { +export default (job: Job): Promise => { debug(`\nnew job: ${job.id}`); const { user, community } = job.data; try { return sendEmail({ - TemplateId: ADMIN_COMMUNITY_CREATED_TEMPLATE, - To: 'brian@spectrum.chat, max@spectrum.chat, bryn@spectrum.chat', - Tag: SEND_ADMIN_COMMUNITY_CREATED_EMAIL, - TemplateModel: { + templateId: ADMIN_COMMUNITY_CREATED_TEMPLATE, + to: [ + { email: 'brian@spectrum.chat ' }, + { email: 'max@spectrum.chat ' }, + { email: 'bryn@spectrum.chat ' }, + ], + dynamic_template_data: { + subject: `New community: ${community.name}`, user: { ...user, createdAt: new Date(user.createdAt), @@ -25,6 +30,8 @@ export default job => { }, }); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-admin-slack-import-email.js b/hermes/queues/send-admin-slack-import-email.js index b884c25a15..5049a64e28 100644 --- a/hermes/queues/send-admin-slack-import-email.js +++ b/hermes/queues/send-admin-slack-import-email.js @@ -1,23 +1,31 @@ +// @flow const debug = require('debug')( - 'hermes:queue:send-admin-slack-import-processed-email' + 'hermes:queue:admin-slack-import-processed-email' ); +import Raven from 'shared/raven'; import sendEmail from '../send-email'; import { ADMIN_SLACK_IMPORT_PROCESSED_TEMPLATE, SEND_ADMIN_SLACK_IMPORT_PROCESSED_EMAIL, } from './constants'; +import type { Job, AdminSlackImportJobData } from 'shared/bull/types'; -export default job => { +export default (job: Job): Promise => { debug(`\nnew job: ${job.id}`); const { user, community, invitedCount, teamName } = job.data; const subject = `New Slack import: ${invitedCount} invites from the ${teamName} Slack team`; - const preheader = `${invitedCount} invites sent for the ${community.name} community`; + const preheader = `${invitedCount} invites sent for the ${ + community.name + } community`; try { return sendEmail({ - TemplateId: ADMIN_SLACK_IMPORT_PROCESSED_TEMPLATE, - To: 'brian@spectrum.chat, max@spectrum.chat, bryn@spectrum.chat', - Tag: SEND_ADMIN_SLACK_IMPORT_PROCESSED_EMAIL, - TemplateModel: { + templateId: ADMIN_SLACK_IMPORT_PROCESSED_TEMPLATE, + to: [ + { email: 'brian@spectrum.chat ' }, + { email: 'max@spectrum.chat ' }, + { email: 'bryn@spectrum.chat ' }, + ], + dynamic_template_data: { subject, preheader, data: { @@ -29,6 +37,8 @@ export default job => { }, }); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-admin-toxic-content-email.js b/hermes/queues/send-admin-toxic-content-email.js index c7d149a4ee..372bed1363 100644 --- a/hermes/queues/send-admin-toxic-content-email.js +++ b/hermes/queues/send-admin-toxic-content-email.js @@ -1,13 +1,14 @@ // @flow const debug = require('debug')('hermes:queue:send-admin-toxic-content-email'); +import Raven from 'shared/raven'; import sendEmail from '../send-email'; import { ADMIN_TOXIC_MESSAGE_TEMPLATE, SEND_ADMIN_TOXIC_MESSAGE_EMAIL, } from './constants'; +import type { Job, AdminToxicContentEmailJobData } from 'shared/bull/types'; -// $FlowFixMe -export default job => { +export default (job: Job): Promise => { debug(`\nnew job: ${job.id}`); const { type, @@ -16,29 +17,22 @@ export default job => { thread, community, channel, - toxicityConfidence: { spectrumScore, perspectiveScore }, + toxicityConfidence: { perspectiveScore }, } = job.data; - const toPercent = (num: number) => Math.round(num * 100); - const spectrumPercent = spectrumScore ? toPercent(spectrumScore) : null; - const perspectivePercent = perspectiveScore - ? toPercent(perspectiveScore) - : null; - let avgPercent; - if (spectrumPercent && perspectivePercent) { - avgPercent = (spectrumPercent + perspectivePercent) / 2; - } else { - avgPercent = spectrumPercent || perspectivePercent || 0; - } - - const subject = `Toxic alert (${avgPercent.toString()}%): ${text}`; + const toPercent = (num: number): number => Math.round(num * 100); + const perspectivePercent = toPercent(perspectiveScore); + const subject = `Toxic alert (${perspectivePercent.toString()}%): ${text}`; try { return sendEmail({ - TemplateId: ADMIN_TOXIC_MESSAGE_TEMPLATE, - To: 'brian@spectrum.chat, bryn@spectrum.chat, max@spectrum.chat', - Tag: SEND_ADMIN_TOXIC_MESSAGE_EMAIL, - TemplateModel: { + templateId: ADMIN_TOXIC_MESSAGE_TEMPLATE, + to: [ + { email: 'brian@spectrum.chat ' }, + { email: 'max@spectrum.chat ' }, + { email: 'bryn@spectrum.chat ' }, + ], + dynamic_template_data: { subject, preheader: text, data: { @@ -49,13 +43,14 @@ export default job => { community, channel, toxicityConfidence: { - spectrumPercent, perspectivePercent, }, }, }, }); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-admin-user-reported-email.js b/hermes/queues/send-admin-user-reported-email.js new file mode 100644 index 0000000000..3a54ba93d1 --- /dev/null +++ b/hermes/queues/send-admin-user-reported-email.js @@ -0,0 +1,47 @@ +// @flow +const debug = require('debug')('hermes:queue:send-admin-user-reported-email'); +import sendEmail from '../send-email'; +import { getUserById } from 'shared/db/queries/user'; +import Raven from 'shared/raven'; +import { + SEND_ADMIN_USER_REPORTED_EMAIL, + ADMIN_USER_REPORTED_TEMPLATE, +} from './constants'; +import type { Job, AdminProcessUserReportedJobData } from 'shared/bull/types'; + +export default async ( + job: Job +): Promise => { + debug(`\nnew job: ${job.id}`); + const { userId, reason, reportedBy, reportedAt } = job.data; + + const [reportedUser, reportingUser] = await Promise.all([ + getUserById(userId), + getUserById(reportedBy), + ]); + + const subject = `☠️ ${reportedUser.username} was reported`; + const preheader = `Reason: ${reason}`; + + try { + return sendEmail({ + templateId: ADMIN_USER_REPORTED_TEMPLATE, + to: [ + { email: 'brian@spectrum.chat ' }, + { email: 'max@spectrum.chat ' }, + { email: 'bryn@spectrum.chat ' }, + ], + dynamic_template_data: { + subject, + preheader, + reportedUser, + reportingUser, + reason, + }, + }); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); + } +}; diff --git a/hermes/queues/send-admin-user-spamming-threads-notification-email.js b/hermes/queues/send-admin-user-spamming-threads-notification-email.js new file mode 100644 index 0000000000..0f07ec50c3 --- /dev/null +++ b/hermes/queues/send-admin-user-spamming-threads-notification-email.js @@ -0,0 +1,61 @@ +// @flow +const debug = require('debug')( + 'hermes:queue:send-admin-user-spamming-threads-notification' +); +import Raven from 'shared/raven'; +import sendEmail from '../send-email'; +import { + SEND_ADMIN_USER_SPAMMING_THREADS_NOTIFICATION_EMAIL, + ADMIN_USER_SPAMMING_THREADS_NOTIFICATION_TEMPLATE, +} from './constants'; +import { toPlainText, toState } from 'shared/draft-utils'; +import type { Job, AdminUserSpammingThreadsJobData } from 'shared/bull/types'; + +const threadBodyToPlainText = (body: any): string => + toPlainText(toState(JSON.parse(body))); + +export default (job: Job): Promise => { + debug(`\nnew job: ${job.id}`); + const { user, threads, publishing, community, channel } = job.data; + + const subject = `🐟 User spamming threads alert: ${user.name} has published ${ + threads.length === 1 ? 'one thread' : `${threads.length} threads` + } in previous 10 minutes`; + const preheader = `${ + user.name + } is attempting to publish a new thread in the ${community.name} community`; + + const cleanThread = (thread: any) => + Object.assign({}, thread, { + content: { + ...thread.content, + body: threadBodyToPlainText(thread.content.body), + }, + }); + + try { + return sendEmail({ + templateId: ADMIN_USER_SPAMMING_THREADS_NOTIFICATION_TEMPLATE, + to: [ + { email: 'brian@spectrum.chat ' }, + { email: 'max@spectrum.chat ' }, + { email: 'bryn@spectrum.chat ' }, + ], + dynamic_template_data: { + subject, + preheader, + data: { + user, + threads: threads.map(t => cleanThread(t)), + publishing: cleanThread(publishing), + community, + channel, + }, + }, + }); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); + } +}; diff --git a/hermes/queues/send-administrator-email-validation-email.js b/hermes/queues/send-administrator-email-validation-email.js index 6d1a379df3..cd2d7f4714 100644 --- a/hermes/queues/send-administrator-email-validation-email.js +++ b/hermes/queues/send-administrator-email-validation-email.js @@ -2,6 +2,7 @@ const debug = require('debug')( 'hermes:queue:send-administrator-email-validation-email' ); +import Raven from 'shared/raven'; import sendEmail from '../send-email'; import { ADMINISTRATOR_EMAIL_VALIDATION_TEMPLATE, @@ -13,7 +14,9 @@ import type { AdministratorEmailValidationEmailJobData, } from 'shared/bull/types'; -export default async (job: Job) => { +export default async ( + job: Job +): Promise => { debug(`\nnew job: ${job.id}`); debug(`\nsending email validation email to: ${job.data.email}`); @@ -22,7 +25,7 @@ export default async (job: Job) => { debug( '\nno email or userId or communityId found for this request, returning' ); - return; + return Promise.resolve(); } const subject = `Confirm new administrator email for the ${ @@ -37,22 +40,24 @@ export default async (job: Job) => { ); if (!validateToken) { - return; - } else { - try { - return sendEmail({ - TemplateId: ADMINISTRATOR_EMAIL_VALIDATION_TEMPLATE, - To: email, - Tag: SEND_ADMINISTRATOR_EMAIL_VALIDATION_EMAIL, - TemplateModel: { - subject, - preheader, - validateToken, - community, - }, - }); - } catch (err) { - console.log(err); - } + return Promise.resolve(); + } + + try { + return sendEmail({ + templateId: ADMINISTRATOR_EMAIL_VALIDATION_TEMPLATE, + to: [{ email }], + dynamic_template_data: { + subject, + preheader, + validateToken, + community, + }, + userId, + }); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-community-card-expiring-warning-email.js b/hermes/queues/send-community-card-expiring-warning-email.js deleted file mode 100644 index 79b03abb25..0000000000 --- a/hermes/queues/send-community-card-expiring-warning-email.js +++ /dev/null @@ -1,45 +0,0 @@ -// @flow -const debug = require('debug')( - 'hermes:queue:send-community-card-expiring-warning-email' -); -import sendEmail from '../send-email'; -import type { Job, CardExpiringWarningEmailJobData } from 'shared/bull/types'; -import { - COMMUNITY_CARD_EXPIRING_WARNING_TEMPLATE, - SEND_COMMUNITY_CARD_EXPIRING_WARNING_EMAIL, -} from './constants'; -import Raven from 'shared/raven'; - -export default (job: Job) => { - debug(`\nnew job: ${job.id}`); - const { to, community, source } = job.data; - - if (!to) { - debug('user does not have an email, aborting'); - return; - } - - const subject = `Action required: your credit card is about to expire`; - const preheader = `Your payment method for the ${ - community.name - } community will expire at the end of the month`; - - try { - return sendEmail({ - TemplateId: COMMUNITY_CARD_EXPIRING_WARNING_TEMPLATE, - To: to, - Tag: SEND_COMMUNITY_CARD_EXPIRING_WARNING_EMAIL, - TemplateModel: { - subject, - preheader, - data: { - community, - source, - }, - }, - }); - } catch (err) { - console.log(err); - Raven.captureException(err); - } -}; diff --git a/hermes/queues/send-community-invite-email.js b/hermes/queues/send-community-invite-email.js index 4f320bc027..869a4af5b7 100644 --- a/hermes/queues/send-community-invite-email.js +++ b/hermes/queues/send-community-invite-email.js @@ -1,5 +1,6 @@ // @flow -const debug = require('debug')('hermes:queue:send-new-message-email'); +const debug = require('debug')('hermes:queue:send-community-invite-email'); +import Raven from 'shared/raven'; import sendEmail from '../send-email'; import { COMMUNITY_INVITE_TEMPLATE, @@ -13,8 +14,10 @@ type SendCommunityInviteJobData = { firstName: string, lastName: string, email: string, + userId?: string, }, community: Object, + communitySettings: Object, customMessage: string, }; @@ -23,26 +26,50 @@ type SendCommunityInviteEmailJob = { id: string, }; -export default (job: SendCommunityInviteEmailJob) => { +export default (job: SendCommunityInviteEmailJob): Promise => { debug(`\nnew job: ${job.id}`); debug(`\nsending community invite to: ${job.data.to}`); - const subject = `${job.data.sender.name} has invited you to join the ${job - .data.community.name} community on Spectrum`; + + const { + sender, + recipient, + community, + communitySettings, + customMessage, + to, + } = job.data; + + const subject = `${job.data.sender.name} has invited you to join the ${ + job.data.community.name + } community on Spectrum`; + + const preheader = `Come join the conversation with ${sender.name}!`; + const joinPath = communitySettings + ? community.isPrivate && + communitySettings.joinSettings && + communitySettings.joinSettings.token + ? `${community.slug}/join/${communitySettings.joinSettings.token}` + : `${community.slug}` + : `${community.slug}`; try { return sendEmail({ - TemplateId: COMMUNITY_INVITE_TEMPLATE, - To: job.data.to, - Tag: SEND_COMMUNITY_INVITE_EMAIL, - TemplateModel: { + templateId: COMMUNITY_INVITE_TEMPLATE, + to: [{ email: to }], + dynamic_template_data: { subject, - sender: job.data.sender, - recipient: job.data.recipient, - community: job.data.community, - customMessage: job.data.customMessage, + preheader, + sender, + recipient, + community, + customMessage, + joinPath, }, + userId: recipient.userId, }); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-community-invoice-receipt-email.js b/hermes/queues/send-community-invoice-receipt-email.js deleted file mode 100644 index 57515e9274..0000000000 --- a/hermes/queues/send-community-invoice-receipt-email.js +++ /dev/null @@ -1,32 +0,0 @@ -const debug = require('debug')( - 'hermes:queue:send-community-invoice-receipt-email' -); -import sendEmail from '../send-email'; -import { - COMMUNITY_INVOICE_RECEIPT_TEMPLATE, - SEND_COMMUNITY_INVOICE_RECEIPT_EMAIL, -} from './constants'; - -export default job => { - debug(`\nnew job: ${job.id}`); - const { invoice, community, to } = job.data; - - if (!to) { - debug('user does not have an email, aborting'); - return Promise.resolve(); - } - - try { - return sendEmail({ - TemplateId: COMMUNITY_INVOICE_RECEIPT_TEMPLATE, - To: to, - Tag: SEND_COMMUNITY_INVOICE_RECEIPT_EMAIL, - TemplateModel: { - invoice, - community, - }, - }); - } catch (err) { - console.log(err); - } -}; diff --git a/hermes/queues/send-community-payment-failed-email.js b/hermes/queues/send-community-payment-failed-email.js deleted file mode 100644 index 355577bf63..0000000000 --- a/hermes/queues/send-community-payment-failed-email.js +++ /dev/null @@ -1,53 +0,0 @@ -// @flow -const debug = require('debug')( - 'hermes:queue:send-community-payment-failed-email' -); -import sendEmail from '../send-email'; -import { - COMMUNITY_PAYMENT_FAILED_TEMPLATE, - SEND_COMMUNITY_PAYMENT_FAILED_EMAIL, -} from './constants'; -import type { Job, PaymentFailedEmailJobData } from 'shared/bull/types'; -import Raven from 'shared/raven'; -import { formatNumbersToDollars } from '../utils/number-to-dollars'; - -export default (job: Job) => { - debug(`\nnew job: ${job.id}`); - const { to, charge, community } = job.data; - - if (!to) { - debug('community does not have an administrator email, aborting'); - // $FlowIssue - return Promise.resolve(); - } - - const subject = `Your payment failed for the ${community.name} community`; - const preheader = `${charge.failure_message}`; - - const amount = formatNumbersToDollars(charge.amount); - - try { - return sendEmail({ - TemplateId: COMMUNITY_PAYMENT_FAILED_TEMPLATE, - To: to, - Tag: SEND_COMMUNITY_PAYMENT_FAILED_EMAIL, - TemplateModel: { - subject, - preheader, - data: { - amount: amount, - source: { - brand: charge.source.brand, - exp_month: charge.source.exp_month, - exp_year: charge.source.exp_year, - last4: charge.source.last4, - }, - community, - }, - }, - }); - } catch (err) { - console.log(err); - Raven.captureException(err); - } -}; diff --git a/hermes/queues/send-community-payment-succeeded-email.js b/hermes/queues/send-community-payment-succeeded-email.js deleted file mode 100644 index 0fe2035a42..0000000000 --- a/hermes/queues/send-community-payment-succeeded-email.js +++ /dev/null @@ -1,115 +0,0 @@ -// @flow -const debug = require('debug')( - 'hermes:queue:send-community-payment-succeeded-email' -); -import sendEmail from '../send-email'; -import { - COMMUNITY_PAYMENT_SUCCEEDED_TEMPLATE, - SEND_COMMUNITY_PAYMENT_SUCCEEDED_EMAIL, -} from './constants'; -import type { Job, PaymentSucceededEmailJobData } from 'shared/bull/types'; -import Raven from 'shared/raven'; -import { formatNumbersToDollars } from '../utils/number-to-dollars'; -import formatDate from '../utils/format-date'; -import { COMMUNITY_ANALYTICS } from 'pluto/queues/constants'; - -export default (job: Job) => { - debug(`\nnew job: ${job.id}`); - const { to, invoice, community, source } = job.data; - - if (!to) { - debug('community does not have an administrator email, aborting'); - // $FlowIssue - return Promise.resolve(); - } - - const { month, year } = formatDate(); - - const subject = `Receipt for the ${ - community.name - } community · ${month}, ${year}`; - const preheader = 'Thank you for building your community with Spectrum'; - - const amount = formatNumbersToDollars(invoice.total); - - const lineItems = invoice.lines.data - .map(item => { - if (!item) return null; - if (item.plan.amount === 0) return null; - - let quantity; - if (item.plan.id === COMMUNITY_ANALYTICS) { - quantity = null; - } else if (item.plan.id === 'priority-support') { - quantity = null; - } else { - quantity = item.quantity; - } - - return { - amount: - item.amount < 0 - ? `-$${formatNumbersToDollars(Math.abs(item.amount))}` - : `$${formatNumbersToDollars(item.amount)}`, - description: item.description, - quantity: quantity, - }; - }) - .filter(Boolean); - - if (lineItems.length === 0) { - debug('No line items on this invoice'); - return; - } - - let viaSource = null; - if (source && invoice.total > 0) { - debug('Creating string for default source'); - viaSource = `Paid with ${source.card.brand} ending in ${source.card.last4}`; - } - - const introCopy = - invoice.total < 0 - ? `Thanks to our Fair Price Promise, we've added a $${formatNumbersToDollars( - Math.abs(invoice.total) - )} credit to future invoices for the ${community.name} community.` - : invoice.total === 0 - ? `Thanks to our Fair Price Promise, this month's invoice for the ${ - community.name - } community was covered by existing credit.` - : `Your recent payment for $${formatNumbersToDollars( - invoice.total - )} for the ${ - community.name - } community on Spectrum was received. Thank you for building your community on Spectrum!`; - - try { - debug('Sending email...'); - return sendEmail({ - TemplateId: COMMUNITY_PAYMENT_SUCCEEDED_TEMPLATE, - To: to, - Tag: SEND_COMMUNITY_PAYMENT_SUCCEEDED_EMAIL, - TemplateModel: { - subject, - preheader, - data: { - introCopy: introCopy, - showButton: invoice.total > 0, - amount: - invoice.total < 0 - ? `-$${formatNumbersToDollars(Math.abs(invoice.total))} credit` - : `$${formatNumbersToDollars(invoice.total)}`, - lineItems, - community, - invoice: { - id: invoice.id, - }, - viaSource, - }, - }, - }); - } catch (err) { - console.error(err); - Raven.captureException(err); - } -}; diff --git a/hermes/queues/send-digest-email.js b/hermes/queues/send-digest-email.js index b008399cb5..b55dff255d 100644 --- a/hermes/queues/send-digest-email.js +++ b/hermes/queues/send-digest-email.js @@ -6,79 +6,33 @@ import Raven from 'shared/raven'; import { generateUnsubscribeToken } from '../utils/generate-jwt'; import { TYPE_DAILY_DIGEST, TYPE_WEEKLY_DIGEST } from './constants'; import formatDate from '../utils/format-date'; +import type { Job, SendDigestEmailJobData } from 'shared/bull/types'; -type ChannelType = { - name: string, - slug: string, -}; - -type CommunityType = { - name: string, - slug: string, - profilePhoto: string, -}; - -type TopCommunityType = { - name: string, - slug: string, - profilePhoto: string, - coverPhoto: string, - description: string, - id: string, -}; - -type ThreadType = { - community: CommunityType, - channel: ChannelType, - channelId: string, - title: string, - threadId: string, - messageCountString: string, -}; - -type SendWeeklyDigestJobData = { - email: string, - name?: string, - username: string, - userId: string, - userId: string, - threads: Array, - reputationString: string, - communities: ?Array, - timeframe: 'daily' | 'weekly', -}; - -type SendWeeklyDigestJob = { - data: SendWeeklyDigestJobData, - id: string, -}; - -export default async (job: SendWeeklyDigestJob) => { - debug(`\nnew job: ${job.id}`); - debug(`\nsending weekly digest to: ${job.data.email}`); - +export default async (job: Job): Promise => { const { email, - userId, username, + userId, threads, + reputationString, communities, timeframe, - reputationString, + hasOverflowThreads, } = job.data; - if (!email || !userId) { - debug('\nno email or userId found for this weekly digest, returning'); - return; + + if (!email || !userId || !username) { + return Promise.resolve(); } const unsubscribeType = timeframe === 'daily' ? TYPE_DAILY_DIGEST : TYPE_WEEKLY_DIGEST; + const unsubscribeToken = await generateUnsubscribeToken( userId, unsubscribeType ); - if (!unsubscribeToken) return; + if (!unsubscribeToken) return Promise.resolve(); const tag = timeframe === 'daily' @@ -86,30 +40,35 @@ export default async (job: SendWeeklyDigestJob) => { : 'send weekly digest email'; const subjectPrefix = timeframe === 'daily' ? 'Spectrum Daily Digest' : 'Spectrum Weekly Digest'; + const subjectStart = threads.length > 2 - ? `${threads[0].title}, ${threads[1].title}` - : `${threads[0].title}`; + ? `${threads[0].content.title}, ${threads[1].content.title}` + : `${threads[0].content.title}`; + const subjectEnd = ` and ${ threads.length > 2 ? threads.length - 2 : threads.length - 1 } more active conversations in your communities`; + const subject = `${subjectPrefix}: ${subjectStart}${subjectEnd}`; + const { day, month, year } = formatDate(); + const preheader = timeframe === 'daily' ? `Your Spectrum daily digest · ${month} ${day}, ${year}` - : 'Your Spectrum weekly digest'; + : `Your Spectrum weekly digest · ${month} ${day}, ${year}`; try { return sendEmail({ - TemplateId: DIGEST_TEMPLATE, - To: email, - Tag: tag, - TemplateModel: { + templateId: DIGEST_TEMPLATE, + to: [{ email }], + dynamic_template_data: { subject, preheader, unsubscribeToken, data: { + hasOverflowThreads, username, threads, communities, @@ -120,11 +79,11 @@ export default async (job: SendWeeklyDigestJob) => { }, }, }, + userId, }); } catch (err) { - debug('❌ Error in job:\n'); - debug(err); - Raven.captureException(err); - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-email-validation-email.js b/hermes/queues/send-email-validation-email.js index 33e4fcf259..92d4dbe25c 100644 --- a/hermes/queues/send-email-validation-email.js +++ b/hermes/queues/send-email-validation-email.js @@ -1,5 +1,6 @@ // @flow const debug = require('debug')('hermes:queue:send-email-validation-email'); +import Raven from 'shared/raven'; import sendEmail from '../send-email'; import { EMAIL_VALIDATION_TEMPLATE, @@ -15,32 +16,31 @@ type SendEmailValidationJob = { id: string, }; -export default async (job: SendEmailValidationJob) => { +export default async (job: SendEmailValidationJob): Promise => { debug(`\nnew job: ${job.id}`); debug(`\nsending email validation email to: ${job.data.email}`); const { email, userId } = job.data; if (!email || !userId) { debug('\nno email or userId found for this request, returning'); - return; + return Promise.resolve(); } const validateToken = await generateEmailValidationToken(userId, email); - if (!validateToken) { - return; - } else { - try { - return sendEmail({ - TemplateId: EMAIL_VALIDATION_TEMPLATE, - To: email, - Tag: SEND_EMAIL_VALIDATION_EMAIL, - TemplateModel: { - validateToken, - }, - }); - } catch (err) { - console.log(err); - } + if (!validateToken) return Promise.resolve(); + try { + return sendEmail({ + templateId: EMAIL_VALIDATION_TEMPLATE, + to: [{ email }], + dynamic_template_data: { + subject: 'Confirm your email address on Spectrum', + validateToken, + }, + }); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-mention-message-email.js b/hermes/queues/send-mention-message-email.js index e3b7dbd1af..280789a37a 100644 --- a/hermes/queues/send-mention-message-email.js +++ b/hermes/queues/send-mention-message-email.js @@ -1,5 +1,6 @@ // @flow -const debug = require('debug')('hermes:queue:send-new-direct-message-email'); +const debug = require('debug')('hermes:queue:send-mention-message-email'); +import Raven from 'shared/raven'; import sendEmail from '../send-email'; import { generateUnsubscribeToken } from '../utils/generate-jwt'; import { @@ -8,21 +9,11 @@ import { TYPE_MUTE_THREAD, SEND_NEW_MENTION_MESSAGE_EMAIL, } from './constants'; -import type { DBThread, DBUser, DBMessage } from 'shared/types'; +import type { SendNewMessageMentionEmailJobData, Job } from 'shared/bull/types'; -type SendNewMentionEmailJobData = { - recipient: DBUser, - sender: DBUser, - thread: DBThread, - message: DBMessage, -}; - -type SendNewMentionEmailJob = { - data: SendNewMentionEmailJobData, - id: string, -}; - -export default async (job: SendNewMentionEmailJob) => { +export default async ( + job: Job +): Promise => { debug(`\nnew job: ${job.id}`); const { recipient, sender, thread, message } = job.data; @@ -40,14 +31,13 @@ export default async (job: SendNewMentionEmailJob) => { thread.id ); - if (!recipient.email || !unsubscribeToken) return; + if (!recipient.email || !unsubscribeToken) return Promise.resolve(); try { return sendEmail({ - TemplateId: NEW_MENTION_MESSAGE_TEMPLATE, - To: recipient.email, - Tag: SEND_NEW_MENTION_MESSAGE_EMAIL, - TemplateModel: { + templateId: NEW_MENTION_MESSAGE_TEMPLATE, + to: [{ email: recipient.email }], + dynamic_template_data: { subject, preheader, sender, @@ -59,8 +49,11 @@ export default async (job: SendNewMentionEmailJob) => { unsubscribeToken, muteThreadToken, }, + userId: recipient.id, }); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-mention-thread-email.js b/hermes/queues/send-mention-thread-email.js index ca0dbcc7ba..a237144b7f 100644 --- a/hermes/queues/send-mention-thread-email.js +++ b/hermes/queues/send-mention-thread-email.js @@ -9,20 +9,11 @@ import { TYPE_MUTE_THREAD, SEND_NEW_MENTION_THREAD_EMAIL, } from './constants'; -import type { DBThread, DBUser } from 'shared/types'; +import type { SendNewMessageMentionEmailJobData, Job } from 'shared/bull/types'; -type SendNewMentionEmailJobData = { - recipient: DBUser, - sender: DBUser, - thread: DBThread, -}; - -type SendNewMentionEmailJob = { - data: SendNewMentionEmailJobData, - id: string, -}; - -export default async (job: SendNewMentionEmailJob) => { +export default async ( + job: Job +): Promise => { debug(`\nnew job: ${job.id}`); const { recipient, sender, thread } = job.data; @@ -34,14 +25,13 @@ export default async (job: SendNewMentionEmailJob) => { generateUnsubscribeToken(recipient.id, TYPE_MUTE_THREAD, thread.id), ]); - if (!recipient.email || !unsubscribeToken) return; + if (!recipient.email || !unsubscribeToken) return Promise.resolve(); try { return sendEmail({ - TemplateId: NEW_MENTION_THREAD_TEMPLATE, - To: recipient.email, - Tag: SEND_NEW_MENTION_THREAD_EMAIL, - TemplateModel: { + templateId: NEW_MENTION_THREAD_TEMPLATE, + to: [{ email: recipient.email }], + dynamic_template_data: { subject, preheader, sender, @@ -52,11 +42,11 @@ export default async (job: SendNewMentionEmailJob) => { unsubscribeToken, muteThreadToken, }, + userId: recipient.id, }); } catch (err) { - debug('❌ Error in job:\n'); - debug(err); - Raven.captureException(err); - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-new-community-welcome-email.js b/hermes/queues/send-new-community-welcome-email.js index 8a18be7877..8937b2fd50 100644 --- a/hermes/queues/send-new-community-welcome-email.js +++ b/hermes/queues/send-new-community-welcome-email.js @@ -1,11 +1,14 @@ +// @flow const debug = require('debug')('hermes:queue:send-new-community-welcome-email'); import sendEmail from '../send-email'; +import Raven from 'shared/raven'; import { NEW_COMMUNITY_WELCOME_TEMPLATE, SEND_NEW_COMMUNITY_WELCOME_EMAIL, } from './constants'; +import type { Job, NewCommunityWelcomeEmailJobData } from 'shared/bull/types'; -export default job => { +export default (job: Job): Promise => { debug(`\nnew job: ${job.id}`); const { user, community } = job.data; @@ -16,15 +19,17 @@ export default job => { try { return sendEmail({ - TemplateId: NEW_COMMUNITY_WELCOME_TEMPLATE, - To: user.email, - Tag: SEND_NEW_COMMUNITY_WELCOME_EMAIL, - TemplateModel: { + templateId: NEW_COMMUNITY_WELCOME_TEMPLATE, + to: [{ email: user.email }], + dynamic_template_data: { user, community, + subject: 'Your new community is live on Spectrum!', }, }); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-new-direct-message-email.js b/hermes/queues/send-new-direct-message-email.js index e9b8c13e0b..eceed8c602 100644 --- a/hermes/queues/send-new-direct-message-email.js +++ b/hermes/queues/send-new-direct-message-email.js @@ -1,5 +1,6 @@ // @flow const debug = require('debug')('hermes:queue:send-new-direct-message-email'); +import Raven from 'shared/raven'; import sendEmail from '../send-email'; import { generateUnsubscribeToken } from '../utils/generate-jwt'; import { @@ -8,40 +9,11 @@ import { TYPE_MUTE_DIRECT_MESSAGE_THREAD, SEND_NEW_DIRECT_MESSAGE_EMAIL, } from './constants'; +import type { Job, SendNewDirectMessageEmailJobData } from 'shared/bull/types'; -type SendNewMessageEmailJobData = { - recipient: { - email: string, - name: string, - username: string, - userId: string, - }, - user: { - displayName: string, - username: string, - id: string, - name: string, - }, - thread: { - content: { - title: string, - }, - path: string, - id: string, - }, - message: { - content: { - body: string, - }, - }, -}; - -type SendNewMessageEmailJob = { - data: SendNewMessageEmailJobData, - id: string, -}; - -export default async (job: SendNewMessageEmailJob) => { +export default async ( + job: Job +): Promise => { debug(`\nnew job: ${job.id}`); const { recipient, user, thread, message } = job.data; const subject = `New direct message from ${user.name} on Spectrum`; @@ -57,14 +29,14 @@ export default async (job: SendNewMessageEmailJob) => { thread.id ); - if (!recipient.email || !unsubscribeToken || !muteThreadToken) return; + if (!recipient.email || !unsubscribeToken || !muteThreadToken) + return Promise.resolve(); try { return sendEmail({ - TemplateId: NEW_DIRECT_MESSAGE_TEMPLATE, - To: recipient.email, - Tag: SEND_NEW_DIRECT_MESSAGE_EMAIL, - TemplateModel: { + templateId: NEW_DIRECT_MESSAGE_TEMPLATE, + to: [{ email: recipient.email }], + dynamic_template_data: { subject, user, thread, @@ -73,8 +45,11 @@ export default async (job: SendNewMessageEmailJob) => { muteThreadToken, unsubscribeToken, }, + userId: recipient.userId, }); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-new-message-email.js b/hermes/queues/send-new-message-email.js index fcbd8f6386..486d3f53dd 100644 --- a/hermes/queues/send-new-message-email.js +++ b/hermes/queues/send-new-message-email.js @@ -1,56 +1,18 @@ // @flow const debug = require('debug')('hermes:queue:send-new-message-email'); +import Raven from 'shared/raven'; import sendEmail from '../send-email'; import { generateUnsubscribeToken } from '../utils/generate-jwt'; +import smarten from 'hermes/utils/smarten-string'; import { NEW_MESSAGE_TEMPLATE, TYPE_NEW_MESSAGE_IN_THREAD, TYPE_MUTE_THREAD, SEND_NEW_MESSAGE_EMAIL, } from './constants'; +import type { Job, SendNewMessageEmailJobData } from 'shared/bull/types'; -type ReplyData = { - sender: { - name: string, - username: string, - profilePhoto: string, - }, - content: { - body: string, - }, -}; - -type ThreadData = { - id: string, - content: { - title: string, - }, - community: { - slug: string, - name: string, - }, - channel: { - name: string, - }, - replies: Array, - repliesCount: number, -}; - -type SendNewMessageEmailJobData = { - recipient: { - userId: string, - email: string, - username: string, - }, - threads: Array, -}; - -type SendNewMessageEmailJob = { - data: SendNewMessageEmailJobData, - id: string, -}; - -export default async (job: SendNewMessageEmailJob) => { +export default async (job: Job): Promise => { debug(`\nnew job: ${job.id}`); const { recipient, threads } = job.data; @@ -69,13 +31,16 @@ export default async (job: SendNewMessageEmailJob) => { const firstName = totalNames.splice(0, 1)[0]; const restNames = totalNames.length > 0 ? totalNames : null; const numUsersText = restNames - ? ` and ${restNames.length === 1 - ? `${restNames.length} other person` - : `${restNames.length} others`}` + ? ` and ${ + restNames.length === 1 + ? `${restNames.length} other person` + : `${restNames.length} others` + }` : ''; + const threadsText = threadsAmount === 1 - ? `'${threads[0].content.title}'` + ? `‘${smarten(threads[0].content.title)}’` : `${threadsAmount} conversations`; // Brian and 3 others replied in 4 conversations // Brian replied in 'Thread title' @@ -86,11 +51,17 @@ export default async (job: SendNewMessageEmailJob) => { 0 ); const preheaderSubtext = restNames - ? ` and ${restNames.length === 1 - ? `${restNames.length} other person` - : `${restNames.length} others`}...` + ? ` and ${ + restNames.length === 1 + ? `${restNames.length} other person` + : `${restNames.length} others` + }...` : ''; - const preheader = `View ${newMessagesLength} new messages from ${firstName}${preheaderSubtext}`; + const preheader = `View ${ + newMessagesLength === 1 + ? `1 new message from ` + : `${newMessagesLength} new messages from ` + }${firstName}${preheaderSubtext}`; const unsubscribeToken = await generateUnsubscribeToken( recipient.userId, @@ -106,13 +77,13 @@ export default async (job: SendNewMessageEmailJob) => { ) : null; - if (!unsubscribeToken || !recipient.email || !recipient.username) return; + if (!unsubscribeToken || !recipient.email || !recipient.username) + return Promise.resolve(); try { return sendEmail({ - TemplateId: NEW_MESSAGE_TEMPLATE, - To: recipient.email, - Tag: SEND_NEW_MESSAGE_EMAIL, - TemplateModel: { + templateId: NEW_MESSAGE_TEMPLATE, + to: [{ email: recipient.email }], + dynamic_template_data: { subject, preheader, recipient, @@ -129,8 +100,11 @@ export default async (job: SendNewMessageEmailJob) => { })), }, }, + userId: recipient.userId, }); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-new-thread-email.js b/hermes/queues/send-new-thread-email.js index d8491757db..34907f58be 100644 --- a/hermes/queues/send-new-thread-email.js +++ b/hermes/queues/send-new-thread-email.js @@ -1,8 +1,10 @@ // @flow const debug = require('debug')('hermes:queue:send-new-thread-email'); +import Raven from 'shared/raven'; import sendEmail from '../send-email'; import truncate from 'shared/truncate'; import { generateUnsubscribeToken } from '../utils/generate-jwt'; +import smarten from 'hermes/utils/smarten-string'; import { NEW_THREAD_CREATED_TEMPLATE, TYPE_NEW_THREAD_CREATED, @@ -47,7 +49,7 @@ type SendNewThreadEmailJob = { id: string, }; -export default async (job: SendNewThreadEmailJob) => { +export default async (job: SendNewThreadEmailJob): Promise => { const { recipient, thread, primaryActionLabel } = job.data; debug(`\nnew job: ${job.id}`); @@ -58,20 +60,23 @@ export default async (job: SendNewThreadEmailJob) => { TYPE_NEW_THREAD_CREATED ); - if (!unsubscribeToken || !recipient.email) return; + if (!unsubscribeToken || !recipient.email) { + console.error('Aborting no unsub token or recipient email'); + return Promise.resolve(); + } - const subject = `${truncate(thread.content.title, 80)} by ${thread.creator - .name} · ${thread.community.name} (${thread.channel.name})`; + const subject = `‘${truncate(smarten(thread.content.title), 80)}’ by ${ + thread.creator.name + } · ${thread.community.name} (${thread.channel.name})`; const preheader = thread.content.body ? truncate(thread.content.body, 80) : 'Published just now'; try { return sendEmail({ - TemplateId: NEW_THREAD_CREATED_TEMPLATE, - To: recipient.email, - Tag: SEND_THREAD_CREATED_NOTIFICATION_EMAIL, - TemplateModel: { + templateId: NEW_THREAD_CREATED_TEMPLATE, + to: [{ email: recipient.email }], + dynamic_template_data: { subject, preheader, data: { @@ -91,8 +96,11 @@ export default async (job: SendNewThreadEmailJob) => { thread.community.id ), }, + userId: recipient.id, }); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-private-channel-request-approved-email.js b/hermes/queues/send-private-channel-request-approved-email.js index 521438f3bd..5ae2530728 100644 --- a/hermes/queues/send-private-channel-request-approved-email.js +++ b/hermes/queues/send-private-channel-request-approved-email.js @@ -2,32 +2,20 @@ const debug = require('debug')( 'hermes:queue:send-request-join-private-channel' ); +import Raven from 'shared/raven'; import sendEmail from '../send-email'; import { SEND_PRIVATE_CHANNEL_REQUEST_APPROVED_EMAIL, PRIVATE_CHANNEL_REQUEST_APPROVED_TEMPLATE, } from './constants'; +import type { + Job, + SendPrivateChannelRequestApprovedEmailJobData, +} from 'shared/bull/types'; -type JobData = { - recipient: { - email: string, - }, - channel: { - name: string, - slug: string, - }, - community: { - name: string, - slug: string, - }, -}; - -type SendEmail = { - data: JobData, - id: string, -}; - -export default (job: SendEmail) => { +export default ( + job: Job +): Promise => { debug(`\nnew job: ${job.id}`); const { recipient, channel, community } = job.data; debug(`\nsending notification to user: ${recipient.email}`); @@ -41,10 +29,9 @@ export default (job: SendEmail) => { try { return sendEmail({ - TemplateId: PRIVATE_CHANNEL_REQUEST_APPROVED_TEMPLATE, - To: recipient.email, - Tag: SEND_PRIVATE_CHANNEL_REQUEST_APPROVED_EMAIL, - TemplateModel: { + templateId: PRIVATE_CHANNEL_REQUEST_APPROVED_TEMPLATE, + to: [{ email: recipient.email }], + dynamic_template_data: { subject, preheader, data: { @@ -55,6 +42,8 @@ export default (job: SendEmail) => { }, }); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-private-channel-request-sent-email.js b/hermes/queues/send-private-channel-request-sent-email.js index 84da4e9438..96508ff5ad 100644 --- a/hermes/queues/send-private-channel-request-sent-email.js +++ b/hermes/queues/send-private-channel-request-sent-email.js @@ -2,36 +2,20 @@ const debug = require('debug')( 'hermes:queue:send-request-join-private-channel' ); +import Raven from 'shared/raven'; import sendEmail from '../send-email'; import { PRIVATE_CHANNEL_REQUEST_SENT_TEMPLATE, SEND_PRIVATE_CHANNEL_REQUEST_SENT_EMAIL, } from './constants'; +import type { + Job, + SendPrivateChannelRequestEmailJobData, +} from 'shared/bull/types'; -type JobData = { - recipient: { - email: string, - }, - user: { - username: string, - name: string, - }, - channel: { - name: string, - slug: string, - }, - community: { - name: string, - slug: string, - }, -}; - -type SendRequestPrivateChannelEmail = { - data: JobData, - id: string, -}; - -export default (job: SendRequestPrivateChannelEmail) => { +export default ( + job: Job +): Promise => { debug(`\nnew job: ${job.id}`); const { user, recipient, channel, community } = job.data; debug(`\nsending notification to private channel owner: ${recipient.email}`); @@ -44,10 +28,9 @@ export default (job: SendRequestPrivateChannelEmail) => { try { return sendEmail({ - TemplateId: PRIVATE_CHANNEL_REQUEST_SENT_TEMPLATE, - To: recipient.email, - Tag: SEND_PRIVATE_CHANNEL_REQUEST_SENT_EMAIL, - TemplateModel: { + templateId: PRIVATE_CHANNEL_REQUEST_SENT_TEMPLATE, + to: [{ email: recipient.email }], + dynamic_template_data: { subject, preheader, data: { @@ -59,6 +42,8 @@ export default (job: SendRequestPrivateChannelEmail) => { }, }); } catch (err) { - console.log(err); + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); } }; diff --git a/hermes/queues/send-private-community-request-approved-email.js b/hermes/queues/send-private-community-request-approved-email.js new file mode 100644 index 0000000000..68d678a897 --- /dev/null +++ b/hermes/queues/send-private-community-request-approved-email.js @@ -0,0 +1,48 @@ +// @flow +const debug = require('debug')( + 'hermes:queue:send-request-join-private-channel' +); +import Raven from 'shared/raven'; +import sendEmail from '../send-email'; +import { + SEND_PRIVATE_COMMUNITY_REQUEST_APPROVED_EMAIL, + PRIVATE_COMMUNITY_REQUEST_APPROVED_TEMPLATE, +} from './constants'; +import type { + Job, + SendPrivateCommunityRequestApprovedEmailJobData, +} from 'shared/bull/types'; + +export default ( + job: Job +): Promise => { + debug(`\nnew job: ${job.id}`); + const { recipient, community } = job.data; + debug(`\nsending notification to user: ${recipient.email}`); + + const subject = `Your request to join the ${ + community.name + } community was approved!`; + const preheader = `You can now join the conversations happening in the ${ + community.name + } community!`; + + try { + return sendEmail({ + templateId: PRIVATE_COMMUNITY_REQUEST_APPROVED_TEMPLATE, + to: [{ email: recipient.email }], + dynamic_template_data: { + subject, + preheader, + data: { + recipient, + community, + }, + }, + }); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); + } +}; diff --git a/hermes/queues/send-private-community-request-sent-email.js b/hermes/queues/send-private-community-request-sent-email.js new file mode 100644 index 0000000000..5211010f72 --- /dev/null +++ b/hermes/queues/send-private-community-request-sent-email.js @@ -0,0 +1,50 @@ +// @flow +const debug = require('debug')( + 'hermes:queue:send-request-join-private-channel' +); +import Raven from 'shared/raven'; +import sendEmail from '../send-email'; +import { + PRIVATE_COMMUNITY_REQUEST_SENT_TEMPLATE, + SEND_PRIVATE_COMMUNITY_REQUEST_SENT_EMAIL, +} from './constants'; +import type { + Job, + SendPrivateCommunityRequestEmailJobData, +} from 'shared/bull/types'; + +export default ( + job: Job +): Promise => { + debug(`\nnew job: ${job.id}`); + const { user, recipient, community } = job.data; + debug( + `\nsending notification to private community owner: ${recipient.email}` + ); + + const subject = `${user.name} has requested to join the ${ + community.name + } community`; + const preheader = + 'View your community settings to approve or block this request'; + + try { + return sendEmail({ + templateId: PRIVATE_COMMUNITY_REQUEST_SENT_TEMPLATE, + to: [{ email: recipient.email }], + dynamic_template_data: { + subject, + preheader, + data: { + user, + recipient, + community, + }, + }, + }); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); + } +}; diff --git a/hermes/queues/send-pro-invoice-receipt-email.js b/hermes/queues/send-pro-invoice-receipt-email.js deleted file mode 100644 index aceb5a8d20..0000000000 --- a/hermes/queues/send-pro-invoice-receipt-email.js +++ /dev/null @@ -1,29 +0,0 @@ -const debug = require('debug')('hermes:queue:send-pro-invoice-receipt-email'); -import sendEmail from '../send-email'; -import { - PRO_INVOICE_RECEIPT_TEMPLATE, - SEND_PRO_INVOICE_RECEIPT_EMAIL, -} from './constants'; - -export default job => { - debug(`\nnew job: ${job.id}`); - const { invoice, to } = job.data; - - if (!to) { - debug(`user#${to} does not have an email, aborting`); - return Promise.resolve(); - } - - try { - return sendEmail({ - TemplateId: PRO_INVOICE_RECEIPT_TEMPLATE, - To: to, - Tag: SEND_PRO_INVOICE_RECEIPT_EMAIL, - TemplateModel: { - invoice, - }, - }); - } catch (err) { - console.log(err); - } -}; diff --git a/hermes/queues/send-user-welcome-email.js b/hermes/queues/send-user-welcome-email.js index 8415e0a4d0..4ef48068a0 100644 --- a/hermes/queues/send-user-welcome-email.js +++ b/hermes/queues/send-user-welcome-email.js @@ -1,29 +1,35 @@ -const debug = require('debug')('hermes:queue:send-user-welcome-email'); -import sendEmail from '../send-email'; -import { - NEW_USER_WELCOME_TEMPLATE, - SEND_NEW_USER_WELCOME_EMAIL, -} from './constants'; - -export default job => { - debug(`\nnew job: ${job.id}`); - const { user } = job.data; - - if (!user.email) { - debug(`user#${user.id} does not have an email, aborting`); - return Promise.resolve(); - } - - try { - return sendEmail({ - TemplateId: NEW_USER_WELCOME_TEMPLATE, - To: user.email, - Tag: SEND_NEW_USER_WELCOME_EMAIL, - TemplateModel: { - user, - }, - }); - } catch (err) { - console.log(err); - } -}; +// @flow +const debug = require('debug')('hermes:queue:send-user-welcome-email'); +import sendEmail from '../send-email'; +import Raven from 'shared/raven'; +import type { Job, NewUserWelcomeEmailJobData } from 'shared/bull/types'; +import { + NEW_USER_WELCOME_TEMPLATE, + SEND_NEW_USER_WELCOME_EMAIL, +} from './constants'; + +export default (job: Job): Promise => { + debug(`\nnew job: ${job.id}`); + const { user } = job.data; + + if (!user.email) { + debug(`user#${user.id} does not have an email, aborting`); + return Promise.resolve(); + } + + try { + return sendEmail({ + templateId: NEW_USER_WELCOME_TEMPLATE, + to: [{ email: user.email }], + Tag: SEND_NEW_USER_WELCOME_EMAIL, + dynamic_template_data: { + user, + subject: 'Welcome to Spectrum!', + }, + }); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); + } +}; diff --git a/hermes/queues/sendgrid-webhook-events.js b/hermes/queues/sendgrid-webhook-events.js new file mode 100644 index 0000000000..4c6bb7be17 --- /dev/null +++ b/hermes/queues/sendgrid-webhook-events.js @@ -0,0 +1,32 @@ +// @flow +const debug = require('debug')('hermes:sendgrid-webhook-events'); +import Raven from 'shared/raven'; +import { getUserByEmail } from 'shared/db/queries/user'; +import { deactivateUserEmailNotifications } from '../models/usersSettings'; +import type { Job, SendGridWebhookEventJobData } from 'shared/bull/types'; + +const processEvent = async ( + job: Job +): Promise => { + const { event } = job.data; + + const user = await getUserByEmail(event.email); + + if (!user) { + debug(`No user found with email ${event.email}`); + throw new Error(`No user found with email ${event.email}`); + } + + return await deactivateUserEmailNotifications(user.id); +}; + +export default (job: Job): Promise => { + try { + debug('Processing SendGrid webhook event'); + return processEvent(job); + } catch (err) { + console.error('❌ Error in job:\n'); + console.error(err); + return Raven.captureException(err); + } +}; diff --git a/hermes/send-email.js b/hermes/send-email.js index 7b6202d6c3..0ef03033d0 100644 --- a/hermes/send-email.js +++ b/hermes/send-email.js @@ -1,56 +1,84 @@ -import postmark from 'postmark'; +// @flow +import isEmail from 'validator/lib/isEmail'; +import sg from '@sendgrid/mail'; +import fs from 'fs'; const debug = require('debug')('hermes:send-email'); const stringify = require('json-stringify-pretty-compact'); +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import { userCanReceiveEmail } from './user-can-receive-email'; +const { SENDGRID_API_KEY } = process.env; -let client; -if (process.env.POSTMARK_SERVER_KEY) { - client = new postmark.Client(process.env.POSTMARK_SERVER_KEY); -} else { - console.log( - '\nℹ️ POSTMARK_SERVER_KEY not provided, debug mode enabled. Will log emails instead of actually sending them.' - ); - // If no postmark API key is provided don't crash the server but log instead - client = { - sendEmailWithTemplate: ({ To, TemplateModel, Tag }, cb) => { - debug('debug mode enabled, mocking email sending'); - cb(); - }, - }; -} +export type ToType = { + email: string, + name?: string, +}; type Options = { - TemplateId: number, - To: string, - TemplateModel?: Object, - Tag: string, + templateId: string, + to: Array, + dynamic_template_data: Object, + userId?: string, }; -const sendEmail = (options: Options) => { - const { TemplateId, To, TemplateModel, Tag } = options; - debug( - `--Send email with template ${TemplateId}--\nTo: ${To}\nRe: ${TemplateModel.subject}\nTemplateModel: ${stringify( - TemplateModel - )}` - ); - return new Promise((res, rej) => { - client.sendEmailWithTemplate( - { - From: 'hi@spectrum.chat', - TemplateId: TemplateId, - To: To, - TemplateModel: TemplateModel, - Tag: Tag, - }, - err => { - if (err) { - console.log('Error sending email:'); - console.log(err); - return rej(err); - } - res(); - debug(`email to ${To} sent successfully`); - } +const defaultOptions = { + from: { + email: 'hi@spectrum.chat', + name: 'Spectrum', + }, + tracking_settings: { + click_tracking: { + enable: false, + }, + }, +}; + +const sendEmail = async (options: Options): Promise => { + let { templateId, to, dynamic_template_data, userId } = options; + + if (SENDGRID_API_KEY !== 'undefined') { + debug( + `--Send LIVE email with templateId ${templateId}--\nto: ${to + .map(t => t.email) + .join(', ')}\ndynamic_template_data: ${stringify( + dynamic_template_data + )}` + ); + sg.setApiKey(SENDGRID_API_KEY); + } else { + debug( + `--Send TEST email with templateId ${templateId}--\n--to: ${to + .map(t => t.email) + .join(', ')}--` ); + + // eslint-disable-next-line + debug( + stringify({ + templateId, + to, + dynamic_template_data, + userId, + }) + ); + + return Promise.resolve(); + } + + if (userId) { + trackQueue.add({ + userId: userId, + event: events.EMAIL_RECEIVED, + }); + } + + if (await !userCanReceiveEmail({ to, userId })) return Promise.resolve(); + + return sg.send({ + ...defaultOptions, + templateId, + to, + dynamic_template_data, }); }; diff --git a/hermes/user-can-receive-email.js b/hermes/user-can-receive-email.js new file mode 100644 index 0000000000..5493c904d7 --- /dev/null +++ b/hermes/user-can-receive-email.js @@ -0,0 +1,38 @@ +// @flow +import { getUserById } from 'shared/db/queries/user'; +import { events } from 'shared/analytics'; +import { trackQueue } from 'shared/bull/queues'; +import type { ToType } from './send-email'; + +type Props = { + to: Array, + userId?: string, +}; + +export const userCanReceiveEmail = async ({ to, userId }: Props) => { + if (!to) { + if (userId) { + trackQueue.add({ + userId: userId, + event: events.EMAIL_BOUNCED, + properties: { error: 'To field was not provided' }, + }); + } + + return false; + } + + // qq.com email addresses are isp blocked, which raises our error rate + // on sendgrid. prevent sending these emails at all + to = to.filter(toType => { + return toType.email.substr(to.length - 7) !== '@qq.com'; + }); + + if (!userId) return false; + if (!to || to.length === 0) return false; + + const user = await getUserById(userId); + if (!user || user.bannedAt || user.deletedAt || !user.email) return false; + + return true; +}; diff --git a/hermes/utils/smarten-string.js b/hermes/utils/smarten-string.js new file mode 100644 index 0000000000..7f1cbb16a3 --- /dev/null +++ b/hermes/utils/smarten-string.js @@ -0,0 +1,9 @@ +// @flow +export default (string: string) => { + string = string.replace(/(^|[-\u2014\s(\["])'/g, '$1\u2018'); // opening singles + string = string.replace(/'/g, '\u2019'); // closing singles & apostrophes + string = string.replace(/(^|[-\u2014/\[(\u2018\s])"/g, '$1\u201c'); // opening doubles + string = string.replace(/"/g, '\u201d'); // closing doubles + string = string.replace(/--/g, '\u2014'); // em-dashes + return string; +}; diff --git a/hermes/yarn.lock b/hermes/yarn.lock index 8a40cd66ac..b3d986e9a9 100644 --- a/hermes/yarn.lock +++ b/hermes/yarn.lock @@ -2,21 +2,167 @@ # yarn lockfile v1 -base64url@2.0.0, base64url@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" +"@sendgrid/client@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-6.3.0.tgz#25c34b11bec392ab43ca7e52fb35e4105fb00901" + integrity sha512-fTy8vRpA9Whtf8ULQr/0vkSZaQvGQ97rY5N5PrevKRtugJMsJqFMKO0pwzEWeqITSg71aMMTj57QTgw3SjZvnQ== + dependencies: + "@sendgrid/helpers" "^6.3.0" + "@types/request" "^2.0.3" + request "^2.81.0" + +"@sendgrid/helpers@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@sendgrid/helpers/-/helpers-6.3.0.tgz#1b1798af22aa7a4c98257fab3dd2a6a6afd8b467" + integrity sha512-uTFcmhCDFg/2Uhz+z/cLwyLHH0UsblG49hKwdR7nKbWsGKWv4js7W32FlPdXqy2C/plTJ20vcPLgKM1m3F/MjQ== + dependencies: + chalk "^2.0.1" + deepmerge "^2.1.1" + +"@sendgrid/mail@^6.3.1": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@sendgrid/mail/-/mail-6.3.1.tgz#e5003af167ca4dd358f04075aad4cfc30cef6c34" + integrity sha512-5zIeAV9iU+0hQkrOQ/D4RB2MfpK+lNbOortIfQdCh95aMDF/TRc9WB8FGNhmQrx9YMuJTms5eiBklF0Fi/dbVg== + dependencies: + "@sendgrid/client" "^6.3.0" + "@sendgrid/helpers" "^6.3.0" + +"@types/caseless@*": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.1.tgz#9794c69c8385d0192acc471a540d1f8e0d16218a" + integrity sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A== + +"@types/form-data@*": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" + integrity sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "10.12.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.12.tgz#e15a9d034d9210f00320ef718a50c4a799417c47" + integrity sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A== + +"@types/request@^2.0.3": + version "2.48.1" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.1.tgz#e402d691aa6670fbbff1957b15f1270230ab42fa" + integrity sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg== + dependencies: + "@types/caseless" "*" + "@types/form-data" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + +"@types/tough-cookie@*": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.4.tgz#821878b81bfab971b93a265a561d54ea61f9059f" + integrity sha512-Set5ZdrAaKI/qHdFlVMgm/GsAv/wkXhSTuZFkJ+JI7HK+wIkIlOaUXSXieIvJ0+OvGIqtREFoE+NHJtEq0gtEw== + +ajv@^6.5.5: + version "6.6.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61" + integrity sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +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" + +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + 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-sdk@^2.383.0: + version "2.383.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.383.0.tgz#86045c0a4a4898dca84a4877cbe765b7dc0f8bba" + integrity sha512-PN+s+NTABtBloS46c7C2dvoEzrdY2NZ5nsfljL3xDX2rvjJEQxdchS2jcCpyc5ZNudFwta66wY4EGBZqf4Attw== + dependencies: + buffer "4.9.1" + events "1.1.1" + ieee754 "1.1.8" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.1.0" + xml2js "0.4.19" + +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.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + +base64-js@^1.0.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" + integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== + +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" + +bignumber.js@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-1.1.1.tgz#1a415d9ac014c13256af1feed9d1a3e5717a8cf7" + integrity sha1-GkFdmsAUwTJWrx/u2dGj5XF6jPc= "bluebird@>= 3.0.1", bluebird@^3.3.4, bluebird@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + +buffer@4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" bull@3.3.10: version "3.3.10" resolved "https://registry.yarnpkg.com/bull/-/bull-3.3.10.tgz#32e76281902070b4720bc37b3793f09db635f646" + integrity sha512-bO1w83BONVTE3Rb10e0wPf11lXH1fGFNGmZH4Ys9jR7jGN4qmTNo7odxm7ELhjKXofjiFLWZFuTdONCs8kV8ug== dependencies: bluebird "^3.5.0" cron-parser "^2.4.1" @@ -26,70 +172,335 @@ bull@3.3.10: semver "^5.4.1" uuid "^3.1.0" +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^2.0.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" + integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + cluster-key-slot@^1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.8.tgz#7654556085a65330932a2e8b5976f8e2d0b3e414" + version "1.0.12" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.12.tgz#d5deff2a520717bc98313979b687309b2d368e29" + integrity sha512-21O0kGmvED5OJ7ZTdqQ5lQQ+sjuez33R+d35jZKLwqUb5mqcPHUsxOSzj61+LHVtxGZd1kShbQM3MjB/gBJkVg== + +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-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= + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" + integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== + dependencies: + delayed-stream "~1.0.0" cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= + +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= + +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= cron-parser@^2.4.1: - version "2.4.3" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.4.3.tgz#cae844c20117fc72c678f63ac83c7884be199e78" + version "2.7.3" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.7.3.tgz#12603f89f5375af353a9357be2543d3172eac651" + integrity sha512-t9Kc7HWBWPndBzvbdQ1YG9rpPRB37Tb/tTviziUOh1qs3TARGh3b1p+tnkOHNe1K5iI3oheBPgLqwotMM7+lpg== dependencies: is-nan "^1.2.1" - moment-timezone "^0.5.0" + moment-timezone "^0.5.23" + +crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + +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" + +datadog-metrics@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/datadog-metrics/-/datadog-metrics-0.8.1.tgz#be87237109a7084193c668d80112533ef00e3f21" + integrity sha512-qTSKnddO6GxTJW9FYpmWjvvift3qfyMurDjwNjJnJhBk76pBdDhC0B5V9V+XwPdn4r42qu48kwXNuHJslXlDOA== + dependencies: + debug "3.1.0" + dogapi "1.1.0" -debug@^2.6.8: - version "2.6.8" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" +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@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.1: + 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" + debuglog@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deepmerge@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== define-properties@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + 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: - foreach "^2.0.5" - object-keys "^1.0.8" + object-keys "^1.0.12" + +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= denque@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/denque/-/denque-1.1.1.tgz#10229c2b88eec1bd15ff82c5fde356e7beb6db9e" + version "1.4.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.0.tgz#79e2f0490195502107f24d9553f374837dabc916" + integrity sha512-gh513ac7aiKrAgjiIBWZG0EASyDF9p4JMWwKA8YU5s9figrL5SRNEMT6FDynsegakuhWd1wVqTvqvqAoDxw7wQ== + +dogapi@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/dogapi/-/dogapi-1.1.0.tgz#71a43865ad4bb4cb18bc3e13cf769971f501030a" + integrity sha1-caQ4Za1LtMsYvD4Tz3aZcfUBAwo= + dependencies: + extend "^3.0.0" + json-bigint "^0.1.4" + minimist "^1.1.1" + rc "^1.0.0" + +draft-js@^0.10.5: + version "0.10.5" + resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.5.tgz#bfa9beb018fe0533dbb08d6675c371a6b08fa742" + integrity sha512-LE6jSCV9nkPhfVX2ggcRLA4FKs6zWq9ceuO/88BpXdNCS7mjRTgs0NsV6piUCJX9YxMsB9An33wnkMmU2sD2Zg== + dependencies: + fbjs "^0.8.15" + immutable "~3.7.4" + object-assign "^4.1.0" + +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" -ecdsa-sig-formatter@1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + integrity sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM= dependencies: - base64url "^2.0.0" safe-buffer "^5.0.1" +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= + dependencies: + iconv-lite "~0.4.13" + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +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= + +events@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= + +extend@^3.0.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.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +faker@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" + integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8= + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + +fast-json-stable-stringify@^2.0.0: + version "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= + +fbjs@^0.8.15: + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" + flexbuffer@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/flexbuffer/-/flexbuffer-0.0.6.tgz#039fdf23f8823e440c38f3277e6fef1174215b30" + integrity sha1-A5/fI/iCPkQMOPMnfm/vEXQhWzA= -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" +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= -git-rev@0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/git-rev/-/git-rev-0.2.1.tgz#8ccbd2092b345bc2c9149548396df549646ca63f" +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" + +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.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +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= + +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" + +iconv-lite@~0.4.13: + 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" + +ieee754@1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + integrity sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q= + +ieee754@^1.1.4: + version "1.1.12" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" + integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== + +immutable@~3.7.4: + version "3.7.6" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" + integrity sha1-E7TTyxK++hVIKib+Gy665kAHHks= -ioredis@^3.1.4: +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +ioredis@3.2.2, ioredis@^3.1.4: version "3.2.2" resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-3.2.2.tgz#b7d5ff3afd77bb9718bb2821329b894b9a44c00b" + integrity sha512-g+ShTQYLsCcOUkNOK6CCEZbj3aRDVPw3WOwXk+LxlUKvuS9ujEqP2MppBHyRVYrNNFW/vcPaTBUZ2ctGNSiOCA== dependencies: bluebird "^3.3.4" cluster-key-slot "^1.0.6" @@ -115,25 +526,110 @@ ioredis@^3.1.4: redis-commands "^1.2.0" redis-parser "^2.4.0" +ioredis@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.3.0.tgz#a92850dd8794eaee4f38a265c830ca823a09d345" + integrity sha512-TwTp93UDKlKVQeg9ThuavNh4Vs31JTlqn+cI/J6z21OtfghyJm5I349ZlsKobOeEyS4INITMLQ1fhR7xwf9Fxg== + dependencies: + cluster-key-slot "^1.0.6" + debug "^3.1.0" + denque "^1.1.0" + flexbuffer "0.0.6" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + redis-commands "1.4.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^1.0.0" + +is-buffer@~1.1.1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + is-nan@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.2.1.tgz#9faf65b6fb6db24b7f5c0628475ea71f988401e2" + integrity sha1-n69ltvttskt/XAYoR16nH5iEAeI= dependencies: define-properties "^1.1.1" -json-stringify-pretty-compact@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.0.4.tgz#d5161131be27fd9748391360597fcca250c6c5ce" +is-stream@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -json-stringify-safe@5.0.1: +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= + +isarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + +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= + +jmespath@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= + +"js-tokens@^3.0.0 || ^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== + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +json-bigint@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-0.1.4.tgz#b5d40b8a9009e92f157f7c079db097001830e01e" + integrity sha1-tdQLipAJ6S8Vf3wHnbCXABgw4B4= + dependencies: + bignumber.js "~1.1.1" + +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.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-pretty-compact@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.2.0.tgz#0bc316b5e6831c07041fc35612487fb4e9ab98b8" + integrity sha512-/11Pj1OyX814QMKO7K8l85SHPTr/KsFxHp8GE2zVa0BtJgGimDjXHfM3FhC7keQdWDea7+nXf+f1de7ATZcZkQ== + +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= -jsonwebtoken@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.0.1.tgz#50daef8d0a8c7de2cd06bc1013b75b04ccf3f0cf" +jsonwebtoken@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz#8757f7b4cb7440d86d5e2f3becefa70536c8e46a" + integrity sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg== dependencies: - jws "^3.1.4" + jws "^3.1.5" lodash.includes "^4.3.0" lodash.isboolean "^3.0.3" lodash.isinteger "^4.0.4" @@ -141,222 +637,571 @@ jsonwebtoken@^8.0.1: lodash.isplainobject "^4.0.6" lodash.isstring "^4.0.1" lodash.once "^4.0.0" - ms "^2.0.0" - xtend "^4.0.1" + ms "^2.1.1" -jwa@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5" +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + integrity sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw== dependencies: - base64url "2.0.0" buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.9" + ecdsa-sig-formatter "1.0.10" safe-buffer "^5.0.1" -jws@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2" +jws@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + integrity sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ== dependencies: - base64url "^2.0.0" - jwa "^1.1.4" + jwa "^1.1.5" safe-buffer "^5.0.1" lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= lodash.bind@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" + integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU= lodash.clone@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" + integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= lodash.difference@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= lodash.foreach@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + +lodash.intersection@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.intersection/-/lodash.intersection-4.4.0.tgz#0a11ba631d0e95c23c7f2f4cbb9a692ed178e705" + integrity sha1-ChG6Yx0OlcI8fy9Mu5ppLtF45wU= lodash.isboolean@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= lodash.isnumber@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= lodash.keys@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" + integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU= lodash.noop@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" + integrity sha1-OBiPTWUKOkdCWEObluxFsyYXEzw= lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= lodash.partial@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.partial/-/lodash.partial-4.2.1.tgz#49f3d8cfdaa3bff8b3a91d127e923245418961d4" + integrity sha1-SfPYz9qjv/izqR0SfpIyRUGJYdQ= lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= lodash.sample@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.sample/-/lodash.sample-4.2.1.tgz#5e4291b0c753fa1abeb0aab8fb29df1b66f07f6d" + integrity sha1-XkKRsMdT+hq+sKq4+ynfG2bwf20= lodash.shuffle@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz#145b5053cf875f6f5c2a33f48b6e9948c6ec7b4b" + integrity sha1-FFtQU8+HX29cKjP0i26ZSMbse0s= lodash.values@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" + integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= lodash@^4.17.4: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + +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" -lsmod@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b" +md5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + +mime-db@~1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" + integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.21" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" + integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== + dependencies: + mime-db "~1.37.0" -merge@1.2.0: +minimist@^1.1.1, minimist@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -moment-timezone@^0.5.0: - version "0.5.13" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.13.tgz#99ce5c7d827262eb0f1f702044177f60745d7b90" +moment-timezone@^0.5.23: + version "0.5.23" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" + integrity sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w== dependencies: moment ">= 2.9.0" "moment@>= 2.9.0": - version "2.18.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= -ms@2.0.0, ms@^2.0.0: +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.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== node-env-file@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/node-env-file/-/node-env-file-0.1.8.tgz#fccb7b050f735b5a33da9eb937cf6f1ab457fb69" + integrity sha1-/Mt7BQ9zW1oz2p65N89vGrRX+2k= -now-env@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/now-env/-/now-env-3.0.2.tgz#b0725023a799240c5c7e4a43515eb0a65c2c3663" +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" -object-keys@^1.0.8: - version "1.0.11" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" +now-env@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/now-env/-/now-env-3.1.0.tgz#e0198b67279d387229cfd4b25de4c2fc7156943f" + integrity sha512-f+jXC+UkoxD/g9Nlig99Bxswoh7UUuQxw0EsPfuueHnVpVE0LfgQ4el5dxY4TSXwrL9mEF9GGm0gb7r3K8r/ug== -postmark@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/postmark/-/postmark-1.3.1.tgz#ffd0c36c6a7459295e8d1bf1ca97cfe9e4aaf144" +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-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-keys@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" + integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== + +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= + +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== dependencies: - git-rev "0.2.1" - merge "1.2.0" + asap "~2.0.3" -raven@^2.1.1: +psl@^1.1.24: + version "1.1.29" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" + integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ== + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/raven/-/raven-2.1.1.tgz#b3a974c6c29315c677c079e168435ead196525cd" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +raven@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/raven/-/raven-2.6.4.tgz#458d4a380c8fbb59e0150c655625aaf60c167ea3" + integrity sha512-6PQdfC4+DQSFncowthLf+B6Hr0JpPsFBgTVYTAOq7tCmx/kR4SXbeawtPch20+3QfUcQDoJBLjWW1ybvZ4kXTw== dependencies: cookie "0.3.1" - json-stringify-safe "5.0.1" - lsmod "1.0.0" - stack-trace "0.0.9" + md5 "^2.2.1" + stack-trace "0.0.10" timed-out "4.0.1" - uuid "3.0.0" + uuid "3.3.2" + +rc@^1.0.0: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" -redis-commands@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b" +redis-commands@1.4.0, redis-commands@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.4.0.tgz#52f9cf99153efcce56a8f86af986bd04e988602f" + integrity sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= redis-parser@^2.4.0: version "2.6.0" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" + integrity sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs= + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" -rethinkdbdash@^2.3.29: - version "2.3.29" - resolved "https://registry.yarnpkg.com/rethinkdbdash/-/rethinkdbdash-2.3.29.tgz#252e454c89a86783301eb4171959386bdb268c0c" +redis-tag-cache@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/redis-tag-cache/-/redis-tag-cache-1.2.1.tgz#07d6e1f945d9d5c8186602129cb10573167d7534" + integrity sha512-0F+rLtoIkz4NOWGt9k3qaPcbpvr6+wcEC9aWV78newMKTRN/nIYHgvfyO1Q0ySEepiEYbqj9DhjvQ4CUAtT9/g== + dependencies: + ioredis "^4.0.0" + +request@^2.81.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + 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.0" + 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.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +rethinkdb-inspector@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/rethinkdb-inspector/-/rethinkdb-inspector-0.3.3.tgz#f0d88c66d17e0234b5518ca51cd8c272cb787003" + integrity sha512-1R0S5maattWOptfkHsU5ulXnt6FIKqjgyEdd8WgW9QbYMiHaXNqkHTAsmtXXSAf0j5iFH3F2qq/7eg4xwb8Euw== + +rethinkdbdash@^2.3.31: + version "2.3.31" + resolved "https://registry.yarnpkg.com/rethinkdbdash/-/rethinkdbdash-2.3.31.tgz#fe2f73d1fa6e6f5d96d8e881292013cf6dca914d" + integrity sha512-6nXrKFjdg2Ug0YpdmPWSvyD/2EisHnFNt4FWZ74dcXGK48ievSv+cNFTmVv+KjLi6I9CCf6w4CKZ6yCYTfMfdQ== dependencies: bluebird ">= 3.0.1" -safe-buffer@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +rethinkhaberdashery@^2.3.32: + version "2.3.32" + resolved "https://registry.yarnpkg.com/rethinkhaberdashery/-/rethinkhaberdashery-2.3.32.tgz#ddcc4ba1342e653a9d3bb6982b526087e93d537e" + integrity sha512-scDswDEu7R47WqomjScq46LkCdhaFhpaGQDP3P44GDF32iCFgYbsL4fnJdaieE115qLeinRhEjna08XWCtV0iQ== + dependencies: + bluebird ">= 3.0.1" -semver@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +safe-buffer@^5.0.1, safe-buffer@^5.1.2: + 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", 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== -source-map-support@^0.4.15: - version "0.4.15" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" +sanitize-filename@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.1.tgz#612da1c96473fa02dccda92dcd5b4ab164a6772a" + integrity sha1-YS2hyWRz+gLczaktzVtKsWSmdyo= + dependencies: + truncate-utf8-bytes "^1.0.0" + +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o= + +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +semver@^5.4.1: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + +source-map-support@^0.4.18: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== dependencies: source-map "^0.5.6" source-map@^0.5.6: - version "0.5.6" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" - -stack-trace@0.0.9: - version "0.0.9" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695" + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +sshpk@^1.7.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.2.tgz#c946d6bd9b1a39d0e8635763f5242d6ed6dcb629" + integrity sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA== + 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" + +stack-trace@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= + +standard-as-callback@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-1.0.1.tgz#2e9e1e9d278d7d77580253faaec42269015e3c1d" + integrity sha512-izxEITSyc7S+5oOiF/URiYaNkemPUxIndCNv66jJ548Y1TVxhBvioNMSPrZIQdaZDlhnguOdUzHA/7hJ3xFhuQ== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +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" timed-out@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= -uuid@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728" +toobusy-js@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/toobusy-js/-/toobusy-js-0.5.1.tgz#5511f78f6a87a6a512d44fdb0efa13672217f659" + integrity sha1-VRH3j2qHpqUS1E/bDvoTZyIX9lk= -uuid@^3.1.0: +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +truncate-utf8-bytes@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" + integrity sha1-QFkjkJWS1W94pYGENLC3hInKXys= + dependencies: + utf8-byte-length "^1.0.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= + +ua-parser-js@^0.7.18: + version "0.7.19" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b" + integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ== + +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@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +utf8-byte-length@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" + integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E= + +uuid@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + integrity sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g== + +uuid@3.3.2, uuid@^3.1.0, uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + +validator@^10.11.0: + version "10.11.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-10.11.0.tgz#003108ea6e9a9874d31ccc9e5006856ccd76b228" + integrity sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw== + +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" -xtend@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" +whatwg-fetch@>=0.10.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" + integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== + +xml2js@0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= diff --git a/hyperion/cache.js b/hyperion/cache.js deleted file mode 100644 index a4b3126796..0000000000 --- a/hyperion/cache.js +++ /dev/null @@ -1,46 +0,0 @@ -// @flow -// Cache unauthenticated requests in Redis -import redis from './redis'; -const debug = require('debug')('hyperion:cache'); - -if (process.env.DISABLE_CACHE) { - console.log( - 'Cache disabled, either unset DISABLE_CACHE env variable or run in production mode to enable.' - ); -} - -const cache = ( - req: express$Request, - res: express$Response, - next: express$NextFunction -) => { - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - - if (process.env.DISABLE_CACHE) return next(); - if (req.method !== 'GET') { - debug(`${req.method} request came in, not caching`); - return next(); - } - if (req.user) { - req.user.id && - typeof req.user.id === 'string' && - debug(`authenticated request by ${req.user.id}, not checking cache`); - return next(); - } - - // NOTE(@mxstbr): Using req.path here (instead of req.url or req.originalUrl) to avoid sending unique pages - // for query params, i.e. /spectrum?bla=xyz will be treated the same as /spectrum - const key = req.path; - debug(`unauthenticated request, checking cache for ${req.path}`); - redis.get(key).then(result => { - if (result) { - debug(`cached html found, sending to user`); - res.send(result); - } else { - debug(`no result in cache found, forwarding to renderer`); - next(); - } - }); -}; - -export default cache; diff --git a/hyperion/create-cache-stream.js b/hyperion/create-cache-stream.js deleted file mode 100644 index 70c10c3ab3..0000000000 --- a/hyperion/create-cache-stream.js +++ /dev/null @@ -1,24 +0,0 @@ -// @flow -import { Transform } from 'stream'; -import redis from './redis'; -const debug = require('debug')('hyperion:create-cache-stream'); - -const createCacheStream = (key: string) => { - debug('create cache stream for', key); - const buffer: Array = []; - return new Transform({ - transform(data, enc, cb) { - buffer.push(data); - cb(null, data); - }, - flush(cb) { - if (!process.env.DISABLE_CACHE) { - debug('stream ended, caching result to redis'); - redis.set(key, Buffer.concat(buffer), 'ex', 3600); - } - cb(); - }, - }); -}; - -export default createCacheStream; diff --git a/hyperion/index.js b/hyperion/index.js index 286a0b515b..aec8ad3df4 100644 --- a/hyperion/index.js +++ b/hyperion/index.js @@ -1,25 +1,40 @@ // @flow -console.log('Hyperion starting...'); const debug = require('debug')('hyperion'); +import 'raf/polyfill'; +debug('Hyperion starting...'); debug('logging with debug enabled'); -// $FlowFixMe -require('isomorphic-fetch'); +require('isomorphic-fetch'); // prevent https://github.com/withspectrum/spectrum/issues/3032 +import fs from 'fs'; +import statsd from 'shared/middlewares/statsd'; import express from 'express'; import Loadable from 'react-loadable'; import path from 'path'; -import { getUser } from 'api/models/user'; +// TODO: This is the only thing that connects hyperion to the db +// we should get rid of this if at all possible +import { getUserById } from 'shared/db/queries/user'; import Raven from 'shared/raven'; import toobusy from 'shared/middlewares/toobusy'; +import addSecurityMiddleware from 'shared/middlewares/security'; const PORT = process.env.PORT || 3006; +const ONE_HOUR = 3600; const app = express(); +// Instantiate the statsd middleware as soon as possible to get accurate time tracking +app.use(statsd); + // Trust the now proxy app.set('trust proxy', true); app.use(toobusy); +// Security middleware. +addSecurityMiddleware(app, { enableNonce: true, enableCSP: true }); + +import bodyParser from 'body-parser'; +app.use(bodyParser.json()); + if (process.env.NODE_ENV === 'development') { const logging = require('shared/middlewares/logging'); app.use(logging); @@ -74,19 +89,32 @@ if (process.env.NODE_ENV === 'development') { import cookieParser from 'cookie-parser'; app.use(cookieParser()); -import bodyParser from 'body-parser'; -app.use(bodyParser.json()); - import session from 'shared/middlewares/session'; app.use(session); import passport from 'passport'; +// Setup use serialization passport.serializeUser((user, done) => { - done(null, user.id); + done(null, typeof user === 'string' ? user : JSON.stringify(user)); }); -passport.deserializeUser((id, done) => { - getUser({ id }) +// NOTE(@mxstbr): `data` used to be just the userID, but is now the full user data +// to avoid having to go to the db on every single request. We have to handle both +// cases here, as more and more users use Spectrum again we go to the db less and less +passport.deserializeUser((data, done) => { + // Fast path: try to JSON.parse the data if it works, we got the user data, yay! + try { + const user = JSON.parse(data); + // Make sure more than the user ID is in the data by checking any other required + // field for existance + if (user.id && user.createdAt) { + return done(null, user); + } + // Ignore JSON parsing errors + } catch (err) {} + + // Slow path: data is just the userID (legacy), so we have to go to the db to get the full data + getUserById(data) .then(user => { done(null, user); }) @@ -102,9 +130,53 @@ import threadParamRedirect from 'shared/middlewares/thread-param'; app.use(threadParamRedirect); // Static files +// This route handles the case where our ServiceWorker requests main.asdf123.js, but +// we've deployed a new version of the app so the filename changed to main.dfyt975.js +let jsFiles; +try { + jsFiles = fs.readdirSync( + path.resolve(__dirname, '..', 'build', 'static', 'js') + ); +} catch (err) { + // In development that folder might not exist, so ignore errors here + console.error(err); +} app.use( - express.static(path.resolve(__dirname, '..', 'build'), { index: false }) + express.static(path.resolve(__dirname, '..', 'build'), { + index: false, + setHeaders: (res, path) => { + // Don't cache the serviceworker in the browser + if (path.indexOf('sw.js') > -1) { + res.setHeader('Cache-Control', 'no-store, no-cache'); + return; + } + + if (path.endsWith('.js')) { + // Cache static files in now CDN for seven days + // (the filename changes if the file content changes, so we can cache these forever) + res.setHeader('Cache-Control', `s-maxage=${ONE_HOUR}`); + } + }, + }) ); +app.get('/static/js/:name', (req: express$Request, res, next) => { + if (!req.params.name) return next(); + const existingFile = jsFiles.find(file => file.startsWith(req.params.name)); + if (existingFile) { + if (existingFile.endsWith('.js')) { + res.setHeader('Cache-Control', `s-maxage=${ONE_HOUR}`); + } + return res.sendFile( + path.resolve(__dirname, '..', 'build', 'static', 'js', req.params.name) + ); + } + // Match the first part of the file name, i.e. from "UserSettings.asdf123.chunk.js" match "UserSettings" + const match = req.params.name.match(/(\w+?)\..+js/i); + if (!match) return next(); + const actualFilename = jsFiles.find(file => file.startsWith(match[1])); + if (!actualFilename) return next(); + res.redirect(`/static/js/${actualFilename}`); +}); // In dev the static files from the root public folder aren't moved to the build folder by create-react-app // so we just tell Express to serve those too @@ -114,8 +186,16 @@ if (process.env.NODE_ENV === 'development') { ); } -import cache from './cache'; -app.use(cache); +app.get('*', (req: express$Request, res, next) => { + // Electron requests should only be client-side rendered + if ( + req.headers['user-agent'] && + req.headers['user-agent'].indexOf('Electron') > -1 + ) { + return res.sendFile(path.resolve(__dirname, '../build/index.html')); + } + next(); +}); import renderer from './renderer'; app.get('*', renderer); @@ -144,7 +224,7 @@ process.on('uncaughtException', async err => { Loadable.preloadAll().then(() => { app.listen(PORT); - console.log( + debug( `Hyperion, the server-side renderer, running at http://localhost:${PORT}` ); }); diff --git a/hyperion/redis.js b/hyperion/redis.js deleted file mode 100644 index ad9ab5d79d..0000000000 --- a/hyperion/redis.js +++ /dev/null @@ -1,32 +0,0 @@ -// @flow -import createRedis from 'shared/bull/create-redis'; - -const config = - process.env.NODE_ENV === 'production' && !process.env.FORCE_DEV - ? { - port: process.env.REDIS_CACHE_PORT, - host: process.env.REDIS_CACHE_URL, - password: process.env.REDIS_CACHE_PASSWORD, - } - : undefined; - -// Turn the NOW_URL into the deployment ID -// https://spectrum-asdf123.now.sh -> spectrum-asdf123 -const deploymentId = - process.env.NOW_URL && - process.env.NOW_URL.replace(/^https:\/\//, '').replace(/\.now\.sh$/, ''); -// Locally key the cache only with "cache:", when deployed key the cache with the -// deployment's NOW_URL to avoid serving HTML that refers to non-existant scripts. -// e.g. "cache:spectrum-asdf123:" -const getKeyPrefix = () => { - if (!deploymentId) return 'cache:'; - - return `cache:${deploymentId}:`; -}; - -const redis = createRedis({ - keyPrefix: getKeyPrefix(), - ...config, -}); - -export default redis; diff --git a/hyperion/renderer/browser-shim.js b/hyperion/renderer/browser-shim.js index bc976994c8..929adb9865 100644 --- a/hyperion/renderer/browser-shim.js +++ b/hyperion/renderer/browser-shim.js @@ -7,6 +7,7 @@ global.window = { host: 'spectrum.chat', hash: '', }, + addEventListener: () => {}, }; global.localStorage = { getItem: () => null, diff --git a/hyperion/renderer/html-template.js b/hyperion/renderer/html-template.js index d6f3944b1d..eb8e34cb2d 100644 --- a/hyperion/renderer/html-template.js +++ b/hyperion/renderer/html-template.js @@ -5,9 +5,7 @@ import { html } from 'common-tags'; import serialize from 'serialize-javascript'; // Match main.asdf123.js in production mode or bundle.js in dev mode -const mainBundleRegex = new RegExp( - `${process.env.NODE_ENV === 'production' ? 'main' : 'bundle'}\.(?:.*\.)?js$` -); +const mainBundleRegex = /(main|bundle)\.(?:.*\.)?js$/; let bundles; try { @@ -29,7 +27,13 @@ if (!mainBundle) { export const createScriptTag = ({ src }: { src: string }) => ``; -export const getHeader = ({ metaTags }: { metaTags: string }) => { +export const getHeader = ({ + metaTags, + nonce, +}: { + metaTags: string, + nonce: string, +}) => { return html` @@ -39,8 +43,7 @@ export const getHeader = ({ metaTags }: { metaTags: string }) => { - - + @@ -49,8 +52,32 @@ export const getHeader = ({ metaTags }: { metaTags: string }) => { ${metaTags} - + @@ -61,17 +88,21 @@ export const getFooter = ({ state, data, bundles, + nonce, }: { state: Object, data: Object, bundles: Array, + nonce: string, }) => { return html`
    - - - + + + ${bundles.map(src => createScriptTag({ src }))} ${createScriptTag({ src: `/static/js/${mainBundle}` })} diff --git a/hyperion/renderer/index.js b/hyperion/renderer/index.js index a2c951943f..7661251137 100644 --- a/hyperion/renderer/index.js +++ b/hyperion/renderer/index.js @@ -1,6 +1,5 @@ // @flow // Server-side renderer for our React code -import fs from 'fs'; const debug = require('debug')('hyperion:renderer'); import React from 'react'; // $FlowIssue @@ -8,16 +7,17 @@ import { renderToNodeStream } from 'react-dom/server'; import { ServerStyleSheet } from 'styled-components'; import { ApolloProvider, getDataFromTree } from 'react-apollo'; import { ApolloClient } from 'apollo-client'; +import { SchemaLink } from 'apollo-link-schema'; +import schema from 'api/schema'; +import createLoaders from 'api/loaders'; import { createHttpLink } from 'apollo-link-http'; import { InMemoryCache, IntrospectionFragmentMatcher, } from 'apollo-cache-inmemory'; import { StaticRouter } from 'react-router'; -import { createStore } from 'redux'; import { Provider } from 'react-redux'; import { HelmetProvider } from 'react-helmet-async'; -import * as graphql from 'graphql'; import Loadable from 'react-loadable'; import { getBundles } from 'react-loadable/webpack'; import Raven from 'shared/raven'; @@ -26,8 +26,7 @@ import introspectionQueryResultData from 'shared/graphql/schema.json'; import stats from '../../build/react-loadable.json'; import getSharedApolloClientOptions from 'shared/graphql/apollo-client-options'; -import { getFooter, getHeader, createScriptTag } from './html-template'; -import createCacheStream from '../create-cache-stream'; +import { getFooter, getHeader } from './html-template'; // Browser shim has to come before any client imports import './browser-shim'; @@ -38,23 +37,29 @@ const IN_MAINTENANCE_MODE = process.env.REACT_APP_MAINTENANCE_MODE === 'enabled'; const IS_PROD = process.env.NODE_ENV === 'production'; const FORCE_DEV = process.env.FORCE_DEV; +const FIVE_MINUTES = 300; +const ONE_HOUR = 3600; -if (!IS_PROD || FORCE_DEV) console.log('Querying API at localhost:3001/api'); +if (!IS_PROD || FORCE_DEV) debug('Querying API at localhost:3001/api'); const renderer = (req: express$Request, res: express$Response) => { res.setHeader('Content-Type', 'text/html; charset=utf-8'); + if (IN_MAINTENANCE_MODE) { + res.status(500); + res.send( + `Spectrum
    🛠

    We are currently undergoing maintenance.

    We'll be back shortly. Follow @withspectrum on Twitter to stay up to date.

    ` + ); + return; + } + debug(`server-side render ${req.url}`); debug(`querying API at https://${req.hostname}/api`); - // HTTP Link for queries and mutations including file uploads - const httpLink = createHttpLink({ - uri: - IS_PROD && !FORCE_DEV - ? `https://${req.hostname}/api` - : 'http://localhost:3001/api', - credentials: 'include', - headers: { - cookie: req.headers.cookie, + const schemaLink = new SchemaLink({ + schema, + context: { + user: req.user || null, + loaders: createLoaders(), }, }); @@ -65,16 +70,29 @@ const renderer = (req: express$Request, res: express$Response) => { ...getSharedApolloClientOptions(), }); + // Get the nonce attached to every request + // This nonce is generated by our security middleware + const nonce = + typeof res.locals.nonce === 'string' ? res.locals.nonce : undefined; + + if (!nonce) throw new Error('Security nonce not set.'); + // Create an Apollo Client with a local network interface const client = new ApolloClient({ ssrMode: true, - link: httpLink, + link: schemaLink, cache, }); // Define the initial redux state + const { t } = req.query; + const initialReduxState = { - users: { - currentUser: req.user ? req.user : null, + dashboardFeed: { + activeThread: t ? t : '', + mountedWithActiveThread: t ? t : '', + search: { + isOpen: false, + }, }, }; // Create the Redux store @@ -123,18 +141,24 @@ const renderer = (req: express$Request, res: express$Response) => { const data = client.extract(); const { helmet } = helmetContext; debug('write header'); - let response = res; + // Use now's CDN to cache the rendered pages in CloudFlare for half an hour + // Ref https://zeit.co/docs/features/cdn if (!req.user) { - response = createCacheStream(req.path); - response.pipe(res); + res.setHeader( + 'Cache-Control', + `s-maxage=${ONE_HOUR}, stale-while-revalidate=${FIVE_MINUTES}, must-revalidate` + ); + } else { + res.setHeader('Cache-Control', 's-maxage=0'); } - response.write( + res.write( getHeader({ metaTags: helmet.title.toString() + helmet.meta.toString() + helmet.link.toString(), + nonce: nonce, }) ); @@ -142,7 +166,10 @@ const renderer = (req: express$Request, res: express$Response) => { renderToNodeStream(frontend) ); - stream.pipe(response, { end: false }); + stream.pipe( + res, + { end: false } + ); const bundles = getBundles(stats, modules) // Create - - - -
    - - diff --git a/jest.config.js b/jest.config.js index cde69f506b..ae29cceeef 100644 --- a/jest.config.js +++ b/jest.config.js @@ -9,5 +9,6 @@ module.exports = { ), globalSetup: path.resolve(__dirname, './shared/testing/setup'), globalTeardown: path.resolve(__dirname, './shared/testing/teardown'), - testPathIgnorePatterns: ['/node_modules/', '/mutations/', '/mobile/'], + testPathIgnorePatterns: ['/node_modules/', '/mutations/'], + testURL: 'http://localhost/', }; diff --git a/mercury/constants.js b/mercury/constants.js index 13faf2afbd..48406c66dc 100644 --- a/mercury/constants.js +++ b/mercury/constants.js @@ -2,6 +2,7 @@ // queues export const PROCESS_REPUTATION_EVENT = 'process reputation event'; +export const CALCULATE_THREAD_SCORE = 'calculate thread score'; // reputation event types export const THREAD_CREATED = 'thread created'; @@ -20,6 +21,9 @@ export const REACTION_DELETED = 'reaction deleted'; export const REACTION_DELETED_POST_AUTHOR_BONUS = 'reaction deleted post author bonus'; +export const THREAD_REACTION_CREATED = 'thread reaction created'; +export const THREAD_REACTION_DELETED = 'thread reaction deleted'; + // scores export const THREAD_CREATED_SCORE = 20; export const THREAD_DELETED_SCORE = -20; @@ -33,3 +37,5 @@ export const REACTION_CREATED_SCORE = 30; export const REACTION_CREATED_POST_AUTHOR_SCORE = 2; export const REACTION_DELETED_SCORE = -30; export const REACTION_DELETED_POST_AUTHOR_SCORE = -2; +export const THREAD_REACTION_CREATED_SCORE = 2; +export const THREAD_REACTION_DELETED_SCORE = -2; diff --git a/mercury/functions/processMessageCreated.js b/mercury/functions/processMessageCreated.js index 74a1b1b54c..7a8260c568 100644 --- a/mercury/functions/processMessageCreated.js +++ b/mercury/functions/processMessageCreated.js @@ -1,3 +1,4 @@ +// @flow const debug = require('debug')('mercury:queue:process-message-created'); import { updateReputation } from '../models/usersCommunities'; import { getThread } from '../models/thread'; @@ -7,8 +8,9 @@ import { MESSAGE_CREATED_POST_AUTHOR_SCORE, MESSAGE_CREATED_POST_AUTHOR_BONUS, } from '../constants'; +import type { ReputationEventJobData } from 'shared/bull/types'; -export default async data => { +export default async (data: ReputationEventJobData) => { // entityId represents the threadId const { userId, entityId } = data; diff --git a/mercury/functions/processMessageDeleted.js b/mercury/functions/processMessageDeleted.js index ffb5764e61..ce74cba17c 100644 --- a/mercury/functions/processMessageDeleted.js +++ b/mercury/functions/processMessageDeleted.js @@ -14,9 +14,9 @@ import { MESSAGE_DELETED_SCORE, MESSAGE_DELETED_POST_AUTHOR_SCORE, } from '../constants'; -import type { Data } from './types'; +import type { ReputationEventJobData } from 'shared/bull/types'; -export default async (data: Data) => { +export default async (data: ReputationEventJobData) => { // entityId represents the messageId const { entityId } = data; diff --git a/mercury/functions/processReactionCreated.js b/mercury/functions/processReactionCreated.js index f622280864..3b7a2ee4de 100644 --- a/mercury/functions/processReactionCreated.js +++ b/mercury/functions/processReactionCreated.js @@ -1,3 +1,4 @@ +// @flow const debug = require('debug')('mercury:queue:process-reaction-created'); import { updateReputation } from '../models/usersCommunities'; import { getMessage } from '../models/message'; @@ -8,6 +9,7 @@ import { REACTION_CREATED_SCORE, REACTION_CREATED_POST_AUTHOR_SCORE, } from '../constants'; +import type { ReputationEventJobData } from 'shared/bull/types'; /* If a reaction was created, it is the message creator who should receive reputation, not the reaction leaver. Therefore the userId passed in from reaction mutation doesn't matter; instead we need to get the userId of the person who left the message. @@ -15,7 +17,7 @@ import { In addition, we should reward the person who created the parent thread for sparking a conversation that resulted in reactions and productive messages */ -export default async data => { +export default async (data: ReputationEventJobData) => { // entityId represents the messageId const { entityId } = data; diff --git a/mercury/functions/processReactionDeleted.js b/mercury/functions/processReactionDeleted.js index 503e2c2e0a..c1330b3aab 100644 --- a/mercury/functions/processReactionDeleted.js +++ b/mercury/functions/processReactionDeleted.js @@ -1,3 +1,4 @@ +// @flow const debug = require('debug')('mercury:queue:process-reaction-deleted'); import { updateReputation } from '../models/usersCommunities'; import { getMessage } from '../models/message'; @@ -8,8 +9,9 @@ import { REACTION_DELETED_SCORE, REACTION_DELETED_POST_AUTHOR_SCORE, } from '../constants'; +import type { ReputationEventJobData } from 'shared/bull/types'; -export default async data => { +export default async (data: ReputationEventJobData) => { // entityId represents the messageId const { entityId } = data; diff --git a/mercury/functions/processThreadCreated.js b/mercury/functions/processThreadCreated.js index 0902030383..940bb4e6c0 100644 --- a/mercury/functions/processThreadCreated.js +++ b/mercury/functions/processThreadCreated.js @@ -1,9 +1,11 @@ +// @flow const debug = require('debug')('mercury:queue:process-thread-created'); import { updateReputation } from '../models/usersCommunities'; import { getThread } from '../models/thread'; import { THREAD_CREATED, THREAD_CREATED_SCORE } from '../constants'; +import type { ReputationEventJobData } from 'shared/bull/types'; -export default async data => { +export default async (data: ReputationEventJobData) => { // entityId represents the communityId const { userId, entityId } = data; const { communityId } = await getThread(entityId); diff --git a/mercury/functions/processThreadDeleted.js b/mercury/functions/processThreadDeleted.js index 3121001af5..ae6118b8a5 100644 --- a/mercury/functions/processThreadDeleted.js +++ b/mercury/functions/processThreadDeleted.js @@ -1,3 +1,4 @@ +// @flow const debug = require('debug')('mercury:queue:process-thread-deleted'); import { updateReputation } from '../models/usersCommunities'; import { getThread } from '../models/thread'; @@ -15,6 +16,7 @@ import { REACTION_DELETED_POST_AUTHOR_SCORE, REACTION_DELETED_POST_AUTHOR_BONUS, } from '../constants'; +import type { ReputationEventJobData } from 'shared/bull/types'; /* If a thread is deleted, not only do we need to remove the reputation from the post author, but we also need to remove all of the reputation that was gained by users within the thread: @@ -24,7 +26,7 @@ import { */ -export default async data => { +export default async (data: ReputationEventJobData) => { // entityId represents the threadId const { userId, entityId } = data; diff --git a/mercury/functions/processThreadDeletedByModeration.js b/mercury/functions/processThreadDeletedByModeration.js index be9a599d21..f5d5e92d73 100644 --- a/mercury/functions/processThreadDeletedByModeration.js +++ b/mercury/functions/processThreadDeletedByModeration.js @@ -1,14 +1,15 @@ +// @flow const debug = require('debug')( 'mercury:queue:process-thread-deleted-by-moderation' ); import { updateReputation } from '../models/usersCommunities'; -import { getThread } from '../models/thread'; import { THREAD_DELETED_BY_MODERATION, THREAD_DELETED_BY_MODERATION_SCORE, } from '../constants'; +import type { ReputationEventJobData } from 'shared/bull/types'; -export default async data => { +export default async (data: ReputationEventJobData) => { // entityId represents the communityId const { userId, entityId } = data; const communityId = entityId; diff --git a/mercury/functions/processThreadReactionCreated.js b/mercury/functions/processThreadReactionCreated.js new file mode 100644 index 0000000000..83b0be2a0c --- /dev/null +++ b/mercury/functions/processThreadReactionCreated.js @@ -0,0 +1,24 @@ +// @flow +const debug = require('debug')('mercury:queue:process-thread-reaction-created'); +import { updateReputation } from '../models/usersCommunities'; +import { getThread } from '../models/thread'; +import { + THREAD_REACTION_CREATED, + THREAD_REACTION_CREATED_SCORE, +} from '../constants'; +import type { ReputationEventJobData } from 'shared/bull/types'; + +export default async (data: ReputationEventJobData) => { + // entityId represents the threadId + const { entityId } = data; + const { communityId, creatorId } = await getThread(entityId); + + debug(`Processing thread reaction created reputation event`); + debug(`Got communityId: ${communityId}`); + return updateReputation( + creatorId, + communityId, + THREAD_REACTION_CREATED_SCORE, + THREAD_REACTION_CREATED + ); +}; diff --git a/mercury/functions/processThreadReactionDeleted.js b/mercury/functions/processThreadReactionDeleted.js new file mode 100644 index 0000000000..55280f9ea9 --- /dev/null +++ b/mercury/functions/processThreadReactionDeleted.js @@ -0,0 +1,24 @@ +// @flow +const debug = require('debug')('mercury:queue:process-thread-reaction-deleted'); +import { updateReputation } from '../models/usersCommunities'; +import { getThread } from '../models/thread'; +import { + THREAD_REACTION_DELETED, + THREAD_REACTION_DELETED_SCORE, +} from '../constants'; +import type { ReputationEventJobData } from 'shared/bull/types'; + +export default async (data: ReputationEventJobData) => { + // entityId represents the threadId + const { entityId } = data; + const { creatorId, communityId } = await getThread(entityId); + + debug(`Processing thread reaction deleted reputation event`); + debug(`Got communityId: ${communityId}`); + return updateReputation( + creatorId, + communityId, + THREAD_REACTION_DELETED_SCORE, + THREAD_REACTION_DELETED + ); +}; diff --git a/mercury/functions/types.js b/mercury/functions/types.js deleted file mode 100644 index bdacef9c00..0000000000 --- a/mercury/functions/types.js +++ /dev/null @@ -1,6 +0,0 @@ -// @flow -export type Data = { - userId: string, - type: string, - entityId: string, -}; diff --git a/mercury/index.js b/mercury/index.js index f2162aa5f3..980b80191a 100644 --- a/mercury/index.js +++ b/mercury/index.js @@ -1,28 +1,29 @@ -// $FlowFixMe const debug = require('debug')('mercury'); const createWorker = require('../shared/bull/create-worker'); import processReputationEvent from './queues/processReputationEvent'; -import { PROCESS_REPUTATION_EVENT } from './constants'; +import calculateThreadScore from './queues/calculateThreadScore'; +import { PROCESS_REPUTATION_EVENT, CALCULATE_THREAD_SCORE } from './constants'; const PORT = process.env.PORT || 3005; -console.log('\n✉️ Mercury, the reputation worker, is starting...'); +debug('\n✉️ Mercury, the reputation worker, is starting...'); debug('Logging with debug enabled!'); -console.log(''); const server = createWorker({ [PROCESS_REPUTATION_EVENT]: processReputationEvent, + [CALCULATE_THREAD_SCORE]: calculateThreadScore, }); -console.log( +debug( `🗄 Mercury open for business ${(process.env.NODE_ENV === 'production' && `at ${process.env.COMPOSE_REDIS_URL}:${process.env.COMPOSE_REDIS_PORT}`) || 'locally'}` ); server.listen(PORT, 'localhost', () => { - console.log( - `💉 Healthcheck server running at ${server.address() - .address}:${server.address().port}` + debug( + `💉 Healthcheck server running at ${server.address().address}:${ + server.address().port + }` ); }); diff --git a/mercury/models/db.js b/mercury/models/db.js deleted file mode 100644 index f1b7fc93b0..0000000000 --- a/mercury/models/db.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Database setup is done here - */ -const fs = require('fs'); -const path = require('path'); -const IS_PROD = !process.env.FORCE_DEV && process.env.NODE_ENV === 'production'; - -const DEFAULT_CONFIG = { - db: 'spectrum', - max: 100, // Maximum number of connections, default is 1000 - buffer: 10, // Minimum number of connections open at any given moment, default is 50 - timeoutGb: 60 * 1000, // How long should an unused connection stick around, default is an hour, this is a minute -}; - -const PRODUCTION_CONFIG = { - password: process.env.AWS_RETHINKDB_PASSWORD, - host: process.env.AWS_RETHINKDB_URL, - port: process.env.AWS_RETHINKDB_PORT, -}; - -const config = IS_PROD - ? { - ...DEFAULT_CONFIG, - ...PRODUCTION_CONFIG, - } - : { - ...DEFAULT_CONFIG, - }; - -var r = require('rethinkdbdash')(config); - -module.exports = { db: r }; diff --git a/mercury/models/message.js b/mercury/models/message.js index 9cee9dae72..ae36a5494f 100644 --- a/mercury/models/message.js +++ b/mercury/models/message.js @@ -1,5 +1,5 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); export const getMessage = (id: string): Promise => { return db diff --git a/mercury/models/reaction.js b/mercury/models/reaction.js index 00f71faae3..723abd7d87 100644 --- a/mercury/models/reaction.js +++ b/mercury/models/reaction.js @@ -1,5 +1,5 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); export const getAllReactionsInThread = ( messageIds: Array diff --git a/mercury/models/reputationEvent.js b/mercury/models/reputationEvent.js index 7f234e918b..26dd07eec2 100644 --- a/mercury/models/reputationEvent.js +++ b/mercury/models/reputationEvent.js @@ -1,5 +1,5 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); export const saveReputationEvent = ({ userId, diff --git a/mercury/models/thread.js b/mercury/models/thread.js index 9b31d36f51..83ff68a448 100644 --- a/mercury/models/thread.js +++ b/mercury/models/thread.js @@ -1,9 +1,103 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); +import type { DBThread } from 'shared/types'; -export const getThread = (id: string): Promise => { +export const getThread = (id: string): Promise => { return db .table('threads') .get(id) .run(); }; + +export const getParticipantCount = (threadId: string): Promise => { + return db + .table('usersThreads') + .getAll(threadId, { index: 'threadId' }) + .filter({ isParticipant: true }) + .count() + .default(0) + .run(); +}; + +const timeRanges = { + hourly: { start: 3600, end: 0 }, + daily: { start: 86400, end: 3600 }, + weekly: { start: 604800, end: 86400 }, + rest: { start: Date.now(), end: 604800 }, +}; + +export const getParticipantCountByTime = ( + threadId: string, + range: 'hourly' | 'daily' | 'weekly' | 'rest' +): Promise => { + return db + .table('messages') + .between( + [threadId, db.now().sub(timeRanges[range].start)], + [threadId, db.now().sub(timeRanges[range].end)], + { index: 'threadIdAndTimestamp' } + ) + .filter(db.row.hasFields('deletedAt').not()) + .map(rec => rec('senderId')) + .distinct() + .count() + .default(0) + .run(); +}; + +export const getReactionCountByTime = ( + threadId: string, + range: 'hourly' | 'daily' | 'weekly' | 'rest' +): Promise => { + return db + .table('threadReactions') + .getAll(threadId, { index: 'threadId' }) + .filter( + db.row + .hasFields('deletedAt') + .not() + .and( + db + .row('createdAt') + .ge(db.now().sub(timeRanges[range].start)) + .and(db.row('createdAt').le(db.now().sub(timeRanges[range].end))) + ) + ) + .count() + .default(0) + .run(); +}; + +export const getMessageCount = (threadId: string): Promise => { + return db + .table('messages') + .getAll(threadId, { index: 'threadId' }) + .filter(db.row.hasFields('deletedAt').not()) + .count() + .default(0) + .run(); +}; + +export const getReactionCount = (threadId: string): Promise => { + return db + .table('threadReactions') + .getAll(threadId, { index: 'threadId' }) + .filter(row => row.hasFields('deletedAt').not()) + .count() + .default(0) + .run(); +}; + +export const storeThreadScore = ( + threadId: string, + score: number +): Promise => { + return db + .table('threads') + .get(threadId) + .update({ + score, + scoreUpdatedAt: new Date(), + }) + .run(); +}; diff --git a/mercury/models/usersCommunities.js b/mercury/models/usersCommunities.js index 4a0d46513e..6fa013ef79 100644 --- a/mercury/models/usersCommunities.js +++ b/mercury/models/usersCommunities.js @@ -1,5 +1,5 @@ // @flow -const { db } = require('./db'); +const { db } = require('shared/db'); import { saveReputationEvent } from './reputationEvent'; export const updateReputation = ( @@ -10,8 +10,7 @@ export const updateReputation = ( ): Promise => { return db .table('usersCommunities') - .getAll(userId, { index: 'userId' }) - .filter({ communityId }) + .getAll([userId, communityId], { index: 'userIdAndCommunityId' }) .update({ reputation: db.row('reputation').add(score), }) diff --git a/mercury/package.json b/mercury/package.json index be4b1aadf3..fd3d6aaa0e 100644 --- a/mercury/package.json +++ b/mercury/package.json @@ -3,13 +3,21 @@ "start": "NODE_ENV=production node main.js" }, "dependencies": { - "bull": "^3.3.10", - "debug": "^2.6.8", - "raven": "^2.1.1", - "rethinkdbdash": "^2.3.29", - "source-map-support": "^0.4.15" + "bull": "^3.6.0", + "datadog-metrics": "^0.8.1", + "debug": "^4.1.1", + "ioredis": "3.2.2", + "now-env": "^3.1.0", + "performance-now": "^2.1.0", + "raven": "^2.6.4", + "redis-tag-cache": "^1.2.1", + "rethinkdb-inspector": "^0.3.3", + "rethinkdbdash": "^2.3.31", + "rethinkhaberdashery": "^2.3.32", + "source-map-support": "^0.4.18", + "toobusy-js": "^0.5.1" }, "devDependencies": { - "json-stringify-pretty-compact": "^1.0.4" + "json-stringify-pretty-compact": "^1.2.0" } } diff --git a/mercury/queues/calculateThreadScore.js b/mercury/queues/calculateThreadScore.js new file mode 100644 index 0000000000..5f8e279eb4 --- /dev/null +++ b/mercury/queues/calculateThreadScore.js @@ -0,0 +1,87 @@ +// @flow +// Process jobs to calculate a threads score based on participant count, message count, like count and age. +import { + getThread, + getParticipantCount, + getMessageCount, + storeThreadScore, + getParticipantCountByTime, + getReactionCountByTime, +} from '../models/thread'; +import type { Job, CalculateThreadScoreJobData } from 'shared/bull/types'; + +const MS_PER_DAY = 1000 * 60 * 60 * 24; +// NOTE: We use Math.floor to make sure it goes from 0 to 1 once it crosses 24h, not when it crosses 12h +const getAgeInDays = (date: Date) => + Math.floor((Date.now() - new Date(date).getTime()) / MS_PER_DAY); + +const timeRanges = { + hourly: { start: 3600, end: 0 }, + daily: { start: 86400, end: 3600 }, + weekly: { start: 604800, end: 86400 }, + rest: { start: Date.now(), end: 604800 }, +}; +const weighTimeRange = { + hourly: num => num * 10, + daily: num => num * 5, + weekly: num => num * 2, + rest: num => num * 1, +}; + +export default async (job: Job) => { + const { threadId } = job.data; + + const [ + thread, + hourlyParticipantCount, + dailyParticipantCount, + weeklyParticipantCount, + remainingParticipantCount, + hourlyReactionCount, + dailyReactionCount, + weeklyReactionCount, + remainingReactionCount, + messageCount, + ] = await Promise.all([ + getThread(threadId), + getParticipantCountByTime(threadId, 'hourly'), + getParticipantCountByTime(threadId, 'daily'), + getParticipantCountByTime(threadId, 'weekly'), + getParticipantCountByTime(threadId, 'rest'), + getReactionCountByTime(threadId, 'hourly'), + getReactionCountByTime(threadId, 'daily'), + getReactionCountByTime(threadId, 'weekly'), + getReactionCountByTime(threadId, 'rest'), + getMessageCount(threadId), + ]); + + const score = + // We use Math.exp(+1) * 5 on the hourly participant count so that threads + // that have active conversations right now will rise to the top. + // It goes: 36 (1st participant), 100 (2nd participant), 272 (3rd participant), 742 (4th participant), etc. + // That way as soon as 2+ people start talking in a thread right now it shoots to the top +1 + Math.exp(hourlyParticipantCount + 1) * 5 + + // We Math.exp * 2 the daily participant count so that threads that were active over a day rise to the top + // This goes: 5, 14, 40, 109, etc. + Math.exp(dailyParticipantCount) * 2 + + // We * 10 the weekly and * 5 the remaining participant count because participants should carry more weight than messages or likes + weeklyParticipantCount * 10 + + remainingParticipantCount * 5 + + // We use Math.exp on the hourly reaction count so that threads that are liked a ton right now (5+ likes in the last hour) shoot to the top + // This will go 2, 7, 20, 54, 148, 403, 1096,... + Math.exp(hourlyReactionCount) + + // We use Math.exp(-10) on the daily reaction count so that threads that have been liked 15+ times in the past day shoot to the top + Math.exp(dailyReactionCount - 10) + + // We use Math.exp(-30) on the daily reaction count so that threads that have been liked 35+ times in the past week shoot to the top + Math.exp(weeklyReactionCount - 35) + + // We *1 the message count since they act as the "baseline" of activity, + // and * 0.2 the reactionCount since likes shouldn't matter as much as + // messages + messageCount * 1 + + remainingReactionCount * 0.2 + + // A thread that's old should have a harder time shooting to the top, + // so we subtract the age in days * 10 + getAgeInDays(thread.createdAt) * -10; + + return storeThreadScore(threadId, score); +}; diff --git a/mercury/queues/processReputationEvent.js b/mercury/queues/processReputationEvent.js index 679bbc43bb..b317b0bd69 100644 --- a/mercury/queues/processReputationEvent.js +++ b/mercury/queues/processReputationEvent.js @@ -5,8 +5,11 @@ import processThreadDeleted from '../functions/processThreadDeleted'; import processMessageCreated from '../functions/processMessageCreated'; import processReactionCreated from '../functions/processReactionCreated'; import processReactionDeleted from '../functions/processReactionDeleted'; +import processThreadReactionCreated from '../functions/processThreadReactionCreated'; +import processThreadReactionDeleted from '../functions/processThreadReactionDeleted'; import processMessageDeleted from '../functions/processMessageDeleted'; import processThreadDeletedByModeration from '../functions/processThreadDeletedByModeration'; +import Raven from 'shared/raven'; import { THREAD_CREATED, THREAD_DELETED, @@ -15,14 +18,12 @@ import { MESSAGE_DELETED, REACTION_CREATED, REACTION_DELETED, + THREAD_REACTION_CREATED, + THREAD_REACTION_DELETED, } from '../constants'; -import type { Data } from '../functions/types'; +import type { Job, ReputationEventJobData } from 'shared/bull/types'; -type Job = { - data: Data, -}; - -export default async (job: Job) => { +export default async (job: Job) => { const { type, userId, entityId } = job.data; debug(`\nnew job: ${job.id}`); debug(`\nprocessing reputation type: ${type}`); @@ -52,6 +53,12 @@ export default async (job: Job) => { case REACTION_DELETED: { return await processReactionDeleted(job.data); } + case THREAD_REACTION_CREATED: { + return await processThreadReactionCreated(job.data); + } + case THREAD_REACTION_DELETED: { + return await processThreadReactionDeleted(job.data); + } case MESSAGE_DELETED: { return await processMessageDeleted(job.data); } @@ -61,6 +68,8 @@ export default async (job: Job) => { } } } catch (err) { - debug('❌ Error processing reputation event: ', err); + console.error('❌ Error in job:\n'); + console.error(err); + Raven.captureException(err); } }; diff --git a/mercury/yarn.lock b/mercury/yarn.lock index 4d9c003da3..172287fb21 100644 --- a/mercury/yarn.lock +++ b/mercury/yarn.lock @@ -2,93 +2,188 @@ # yarn lockfile v1 -"bluebird@>= 3.0.1", bluebird@^3.3.4, bluebird@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" - -bull@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/bull/-/bull-3.3.1.tgz#6a15daf08ccfd5bf0e543eeb4544323157c0e1db" +bignumber.js@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-1.1.1.tgz#1a415d9ac014c13256af1feed9d1a3e5717a8cf7" + integrity sha1-GkFdmsAUwTJWrx/u2dGj5XF6jPc= + +"bluebird@>= 3.0.1", bluebird@^3.3.4: + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + +bull@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/bull/-/bull-3.6.0.tgz#9d137a4470d9f5a0df54801ca4390656e5054a42" + integrity sha512-705Vf3weiRr8D49/+lsPSxV/1NejhjfmVviv9qG2srIYPr7IS2euLwHa+2GNfaVDA2tmx8xyJFW9bPw3fPfHPg== dependencies: - bluebird "^3.5.0" - cron-parser "^2.4.1" + cron-parser "^2.7.3" debuglog "^1.0.0" - ioredis "^3.1.4" - lodash "^4.17.4" - semver "^5.4.1" - uuid "^3.1.0" - -bull@^3.3.10: - version "3.3.10" - resolved "https://registry.yarnpkg.com/bull/-/bull-3.3.10.tgz#32e76281902070b4720bc37b3793f09db635f646" - dependencies: - bluebird "^3.5.0" - cron-parser "^2.4.1" - debuglog "^1.0.0" - ioredis "^3.1.4" - lodash "^4.17.4" - semver "^5.4.1" - uuid "^3.1.0" + ioredis "^4.5.1" + lodash "^4.17.11" + p-timeout "^2.0.1" + promise.prototype.finally "^3.1.0" + semver "^5.6.0" + util.promisify "^1.0.0" + uuid "^3.2.1" charenc@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= cluster-key-slot@^1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.8.tgz#7654556085a65330932a2e8b5976f8e2d0b3e414" + version "1.0.12" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.12.tgz#d5deff2a520717bc98313979b687309b2d368e29" + integrity sha512-21O0kGmvED5OJ7ZTdqQ5lQQ+sjuez33R+d35jZKLwqUb5mqcPHUsxOSzj61+LHVtxGZd1kShbQM3MjB/gBJkVg== cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= -cron-parser@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.4.1.tgz#022befce1af293e4d3144ff04c2cbd2edb491271" +cron-parser@^2.7.3: + version "2.7.3" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.7.3.tgz#12603f89f5375af353a9357be2543d3172eac651" + integrity sha512-t9Kc7HWBWPndBzvbdQ1YG9rpPRB37Tb/tTviziUOh1qs3TARGh3b1p+tnkOHNe1K5iI3oheBPgLqwotMM7+lpg== dependencies: is-nan "^1.2.1" - moment-timezone "^0.5.0" + moment-timezone "^0.5.23" crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + +datadog-metrics@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/datadog-metrics/-/datadog-metrics-0.8.1.tgz#be87237109a7084193c668d80112533ef00e3f21" + integrity sha512-qTSKnddO6GxTJW9FYpmWjvvift3qfyMurDjwNjJnJhBk76pBdDhC0B5V9V+XwPdn4r42qu48kwXNuHJslXlDOA== + dependencies: + debug "3.1.0" + dogapi "1.1.0" + +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@^2.2.0, debug@^2.6.8: - version "2.6.8" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" +debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.1: + 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" + debuglog@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= -define-properties@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +define-properties@^1.1.1, define-properties@^1.1.2: + 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: - foreach "^2.0.5" - object-keys "^1.0.8" + object-keys "^1.0.12" denque@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/denque/-/denque-1.2.2.tgz#e06cf7cf0da8badc88cbdaabf8fc0a70d659f1d4" + version "1.4.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.0.tgz#79e2f0490195502107f24d9553f374837dabc916" + integrity sha512-gh513ac7aiKrAgjiIBWZG0EASyDF9p4JMWwKA8YU5s9figrL5SRNEMT6FDynsegakuhWd1wVqTvqvqAoDxw7wQ== + +dogapi@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/dogapi/-/dogapi-1.1.0.tgz#71a43865ad4bb4cb18bc3e13cf769971f501030a" + integrity sha1-caQ4Za1LtMsYvD4Tz3aZcfUBAwo= + dependencies: + extend "^3.0.0" + json-bigint "^0.1.4" + minimist "^1.1.1" + rc "^1.0.0" + +es-abstract@^1.5.1, es-abstract@^1.9.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" + integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== + dependencies: + es-to-primitive "^1.2.0" + function-bind "^1.1.1" + has "^1.0.3" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-keys "^1.0.12" + +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== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== flexbuffer@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/flexbuffer/-/flexbuffer-0.0.6.tgz#039fdf23f8823e440c38f3277e6fef1174215b30" + integrity sha1-A5/fI/iCPkQMOPMnfm/vEXQhWzA= + +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== + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= + +has@^1.0.1, 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" -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -ioredis@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-3.1.4.tgz#8688293f5f2f1757e1c812ad17cce49f46d811bc" +ioredis@3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-3.2.2.tgz#b7d5ff3afd77bb9718bb2821329b894b9a44c00b" + integrity sha512-g+ShTQYLsCcOUkNOK6CCEZbj3aRDVPw3WOwXk+LxlUKvuS9ujEqP2MppBHyRVYrNNFW/vcPaTBUZ2ctGNSiOCA== dependencies: bluebird "^3.3.4" cluster-key-slot "^1.0.6" - debug "^2.2.0" + debug "^2.6.9" denque "^1.1.0" flexbuffer "0.0.6" lodash.assign "^4.2.0" @@ -110,164 +205,371 @@ ioredis@^3.1.4: redis-commands "^1.2.0" redis-parser "^2.4.0" +ioredis@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.2.0.tgz#f0f76fa5067a51c365ef1411f6572478a825971d" + integrity sha512-PdxZGNJBfPiR2RI6DkqmiacL1+ML3gaqEiaC5QXWQt9eSTlGj+BwDCct0s8irn1ed8GyzAHTzcjvU9fmnl6D7A== + dependencies: + cluster-key-slot "^1.0.6" + debug "^3.1.0" + denque "^1.1.0" + flexbuffer "0.0.6" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + redis-commands "1.4.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^1.0.0" + +ioredis@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.5.1.tgz#b1c1c1657697caa3a617acb9370e3c0694edb775" + integrity sha512-p1BblrFZdb5Oc5EBsEb4EoycDqn7xi/NTNT4bDvo/w6B08eMNO1E7RAOOEA1GAb65+8Hbs2LgUyz3cZOTiP3xg== + dependencies: + cluster-key-slot "^1.0.6" + debug "^3.1.0" + denque "^1.1.0" + flexbuffer "0.0.6" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + redis-commands "1.4.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^1.0.0" + is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-callable@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= is-nan@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.2.1.tgz#9faf65b6fb6db24b7f5c0628475ea71f988401e2" + integrity sha1-n69ltvttskt/XAYoR16nH5iEAeI= dependencies: define-properties "^1.1.1" -json-stringify-pretty-compact@^1.0.4: +is-regex@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.0.4.tgz#d5161131be27fd9748391360597fcca250c6c5ce" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= + dependencies: + has "^1.0.1" + +is-symbol@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" + integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + dependencies: + has-symbols "^1.0.0" + +json-bigint@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-0.1.4.tgz#b5d40b8a9009e92f157f7c079db097001830e01e" + integrity sha1-tdQLipAJ6S8Vf3wHnbCXABgw4B4= + dependencies: + bignumber.js "~1.1.1" + +json-stringify-pretty-compact@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.2.0.tgz#0bc316b5e6831c07041fc35612487fb4e9ab98b8" + integrity sha512-/11Pj1OyX814QMKO7K8l85SHPTr/KsFxHp8GE2zVa0BtJgGimDjXHfM3FhC7keQdWDea7+nXf+f1de7ATZcZkQ== lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= lodash.bind@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" + integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU= lodash.clone@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" + integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= lodash.difference@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= lodash.foreach@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= lodash.keys@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" + integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU= lodash.noop@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" + integrity sha1-OBiPTWUKOkdCWEObluxFsyYXEzw= lodash.partial@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.partial/-/lodash.partial-4.2.1.tgz#49f3d8cfdaa3bff8b3a91d127e923245418961d4" + integrity sha1-SfPYz9qjv/izqR0SfpIyRUGJYdQ= lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= lodash.sample@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.sample/-/lodash.sample-4.2.1.tgz#5e4291b0c753fa1abeb0aab8fb29df1b66f07f6d" + integrity sha1-XkKRsMdT+hq+sKq4+ynfG2bwf20= lodash.shuffle@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz#145b5053cf875f6f5c2a33f48b6e9948c6ec7b4b" + integrity sha1-FFtQU8+HX29cKjP0i26ZSMbse0s= lodash.values@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" + integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= -lodash@^4.17.4: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" +lodash@^4.17.11: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== md5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= dependencies: charenc "~0.0.1" crypt "~0.0.1" is-buffer "~1.1.1" -moment-timezone@^0.5.0: - version "0.5.13" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.13.tgz#99ce5c7d827262eb0f1f702044177f60745d7b90" +minimist@^1.1.1, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +moment-timezone@^0.5.23: + version "0.5.23" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" + integrity sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w== dependencies: moment ">= 2.9.0" "moment@>= 2.9.0": - version "2.18.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= 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.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -object-keys@^1.0.8: - version "1.0.11" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" +now-env@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/now-env/-/now-env-3.1.0.tgz#e0198b67279d387229cfd4b25de4c2fc7156943f" + integrity sha512-f+jXC+UkoxD/g9Nlig99Bxswoh7UUuQxw0EsPfuueHnVpVE0LfgQ4el5dxY4TSXwrL9mEF9GGm0gb7r3K8r/ug== + +object-keys@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" + integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== + +object.getownpropertydescriptors@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" + integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.1" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-timeout@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" + integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== + dependencies: + p-finally "^1.0.0" -raven@^2.1.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/raven/-/raven-2.4.1.tgz#7a6a6ff1c42d0a3892308f44c94273e7f88677fd" +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= + +promise.prototype.finally@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.0.tgz#66f161b1643636e50e7cf201dc1b84a857f3864e" + integrity sha512-7p/K2f6dI+dM8yjRQEGrTQs5hTQixUAdOGpMEA3+pVxpX5oHKRSKAXyLw9Q9HUWDTdwtoo39dSHGQtN90HcEwQ== + dependencies: + define-properties "^1.1.2" + es-abstract "^1.9.0" + function-bind "^1.1.1" + +raven@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/raven/-/raven-2.6.4.tgz#458d4a380c8fbb59e0150c655625aaf60c167ea3" + integrity sha512-6PQdfC4+DQSFncowthLf+B6Hr0JpPsFBgTVYTAOq7tCmx/kR4SXbeawtPch20+3QfUcQDoJBLjWW1ybvZ4kXTw== dependencies: cookie "0.3.1" md5 "^2.2.1" - stack-trace "0.0.9" + stack-trace "0.0.10" timed-out "4.0.1" - uuid "3.0.0" + uuid "3.3.2" + +rc@^1.0.0: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +redis-commands@1.4.0, redis-commands@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.4.0.tgz#52f9cf99153efcce56a8f86af986bd04e988602f" + integrity sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw== -redis-commands@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b" +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= redis-parser@^2.4.0: version "2.6.0" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" + integrity sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs= -rethinkdbdash@^2.3.29: +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" + +redis-tag-cache@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/redis-tag-cache/-/redis-tag-cache-1.2.1.tgz#07d6e1f945d9d5c8186602129cb10573167d7534" + integrity sha512-0F+rLtoIkz4NOWGt9k3qaPcbpvr6+wcEC9aWV78newMKTRN/nIYHgvfyO1Q0ySEepiEYbqj9DhjvQ4CUAtT9/g== + dependencies: + ioredis "^4.0.0" + +rethinkdb-inspector@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/rethinkdb-inspector/-/rethinkdb-inspector-0.3.3.tgz#f0d88c66d17e0234b5518ca51cd8c272cb787003" + integrity sha512-1R0S5maattWOptfkHsU5ulXnt6FIKqjgyEdd8WgW9QbYMiHaXNqkHTAsmtXXSAf0j5iFH3F2qq/7eg4xwb8Euw== + +rethinkdbdash@^2.3.31: version "2.3.31" resolved "https://registry.yarnpkg.com/rethinkdbdash/-/rethinkdbdash-2.3.31.tgz#fe2f73d1fa6e6f5d96d8e881292013cf6dca914d" + integrity sha512-6nXrKFjdg2Ug0YpdmPWSvyD/2EisHnFNt4FWZ74dcXGK48ievSv+cNFTmVv+KjLi6I9CCf6w4CKZ6yCYTfMfdQ== dependencies: bluebird ">= 3.0.1" -semver@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +rethinkhaberdashery@^2.3.32: + version "2.3.32" + resolved "https://registry.yarnpkg.com/rethinkhaberdashery/-/rethinkhaberdashery-2.3.32.tgz#ddcc4ba1342e653a9d3bb6982b526087e93d537e" + integrity sha512-scDswDEu7R47WqomjScq46LkCdhaFhpaGQDP3P44GDF32iCFgYbsL4fnJdaieE115qLeinRhEjna08XWCtV0iQ== + dependencies: + bluebird ">= 3.0.1" -source-map-support@^0.4.15: - version "0.4.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.16.tgz#16fecf98212467d017d586a2af68d628b9421cd8" +semver@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + +source-map-support@^0.4.18: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== dependencies: source-map "^0.5.6" source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +stack-trace@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= + +standard-as-callback@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-1.0.1.tgz#2e9e1e9d278d7d77580253faaec42269015e3c1d" + integrity sha512-izxEITSyc7S+5oOiF/URiYaNkemPUxIndCNv66jJ548Y1TVxhBvioNMSPrZIQdaZDlhnguOdUzHA/7hJ3xFhuQ== -stack-trace@0.0.9: - version "0.0.9" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695" +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= timed-out@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= -uuid@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728" +toobusy-js@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/toobusy-js/-/toobusy-js-0.5.1.tgz#5511f78f6a87a6a512d44fdb0efa13672217f659" + integrity sha1-VRH3j2qHpqUS1E/bDvoTZyIX9lk= -uuid@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" +util.promisify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + +uuid@3.3.2, uuid@^3.2.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== diff --git a/mobile/.expo/Exponent.app/.gitkeep b/mobile/.expo/Exponent.app/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/mobile/.expo/settings.json b/mobile/.expo/settings.json deleted file mode 100644 index 86d79bb168..0000000000 --- a/mobile/.expo/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "hostType": "tunnel", - "lanType": "ip", - "dev": true, - "minify": false, - "urlRandomness": "v5-9z3" -} \ No newline at end of file diff --git a/mobile/.gitignore b/mobile/.gitignore deleted file mode 100644 index 5d2e94e432..0000000000 --- a/mobile/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Ignore Expo client that is downloaded locally -.expo/Exponent.app/** -.expo/packager-info.json \ No newline at end of file diff --git a/mobile/.idea/mobile.iml b/mobile/.idea/mobile.iml deleted file mode 100644 index d6ebd48059..0000000000 --- a/mobile/.idea/mobile.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/mobile/.idea/modules.xml b/mobile/.idea/modules.xml deleted file mode 100644 index 348e586c61..0000000000 --- a/mobile/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/mobile/.idea/workspace.xml b/mobile/.idea/workspace.xml deleted file mode 100644 index 90f6fd0a0e..0000000000 --- a/mobile/.idea/workspace.xml +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1515660491426 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/mobile/App.js b/mobile/App.js deleted file mode 100644 index 426119baca..0000000000 --- a/mobile/App.js +++ /dev/null @@ -1,95 +0,0 @@ -// @flow -import Sentry from 'sentry-expo'; -import React from 'react'; -import { SecureStore, AppLoading } from 'expo'; -import { createStore } from 'redux'; -import { Provider } from 'react-redux'; -import { ApolloProvider } from 'react-apollo'; -import { ThemeProvider } from 'styled-components'; -import { type ApolloClient } from 'apollo-client'; - -import theme from './components/theme'; -import { createClient } from '../shared/graphql'; -import TabBar from './views/TabBar'; -import reducers from './reducers'; -import { authenticate } from './actions/authentication'; - -let sentry = Sentry.config( - 'https://3bd8523edd5d43d7998f9b85562d6924@sentry.io/154812' -); - -// Need to guard this for HMR to work -if (sentry && sentry.install) sentry.install(); - -export const store = createStore(reducers); - -type State = { - authLoaded: ?boolean, - token: ?string, - client: ApolloClient, -}; - -class App extends React.Component<{}, State> { - state = { - authLoaded: null, - token: null, - client: createClient(), - }; - - componentDidMount = async () => { - // Subscribe to Redux state changes - store.subscribe(this.listen); - - let token; - try { - token = await SecureStore.getItemAsync('token'); - } catch (err) { - // TODO: Sentry - console.log(err); - this.setState({ - authLoaded: true, - }); - } - - if (token) store.dispatch(authenticate(token)); - this.setState({ - authLoaded: true, - token, - }); - }; - - listen = () => { - const { authentication } = store.getState(); - const { token: oldToken } = this.state; - if (authentication.token !== oldToken) { - this.setState({ - token: authentication.token, - // Create a new Apollo Client with the token - // NOTE(@mxstbr): This wipes out the cache as this creates an entirely new client - // Ideally this would only change link.headers.authorization, but that doesn't seem possible currently - // Ref apollographql/apollo-link#461 - client: createClient({ token: authentication.token }), - }); - } - }; - - render() { - if (!this.state.authLoaded) { - return ; - } - - const { client } = this.state; - - return ( - - - - - - - - ); - } -} - -export default App; diff --git a/mobile/actions/authentication.js b/mobile/actions/authentication.js deleted file mode 100644 index 46feaec911..0000000000 --- a/mobile/actions/authentication.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow -import { SecureStore } from 'expo'; - -export type AuthenticateAction = { - type: 'AUTHENTICATE', - token: ?string, -}; - -export const authenticate = (token: ?string) => { - return { - type: 'AUTHENTICATE', - token, - }; -}; - -export const logout = () => { - SecureStore.deleteItemAsync('token'); - return { - type: 'LOGOUT', - }; -}; diff --git a/mobile/app.json b/mobile/app.json deleted file mode 100644 index bf7d986677..0000000000 --- a/mobile/app.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "expo": { - "name": "spectrum", - "slug": "spectrum", - "privacy": "unlisted", - "sdkVersion": "25.0.0", - "platforms": ["ios", "android"], - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/icon.png", - "splash": { - "image": "./assets/splash.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, - "ios": { - "supportsTablet": true, - "bundleIdentifier": "com.spectrum.mobile" - }, - "packagerOpts": { - "projectRoots": "", - "config": "rn-cli.config.js" - }, - "hooks": { - "postPublish": [ - { - "file": "sentry-expo/upload-sourcemaps", - "config": { - "organization": "space-program", - "project": "spectrum", - "authToken": "8f7c6317a1024bbf9d7fd4806c7b7f73913bc1b3fd9142ddaa21e51aa4b3b9fb" - } - } - ] - } - } -} diff --git a/mobile/assets/icon.png b/mobile/assets/icon.png deleted file mode 100644 index 3f5bbc0ca199c3181d1f889011c7602e2daf51b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2976 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 diff --git a/mobile/assets/splash.png b/mobile/assets/splash.png deleted file mode 100644 index 4f9ade699a4dc43aaf3c97ad983115cccd0e0640..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7178 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 Fe*iU3xyt|m diff --git a/mobile/components/.gitkeep b/mobile/components/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/mobile/components/Anchor/index.js b/mobile/components/Anchor/index.js deleted file mode 100644 index b3f5b521ad..0000000000 --- a/mobile/components/Anchor/index.js +++ /dev/null @@ -1,32 +0,0 @@ -// @flow -import React, { type Node } from 'react'; -import { Linking, Text } from 'react-native'; -import { WebBrowser } from 'expo'; - -type LinkProps = { - href: string, - children: Node, -}; - -type ButtonProps = { - onPress: () => void, - children: Node, -}; - -type Props = LinkProps | ButtonProps; - -export default class Anchor extends React.Component { - handlePress = () => { - if (typeof this.props.onPress === 'function') return this.props.onPress(); - if (typeof this.props.href === 'string') - return WebBrowser.openBrowserAsync(this.props.href); - }; - - render() { - return ( - - {this.props.children} - - ); - } -} diff --git a/mobile/components/Avatar/image.js b/mobile/components/Avatar/image.js deleted file mode 100644 index 0b3ecc3dbb..0000000000 --- a/mobile/components/Avatar/image.js +++ /dev/null @@ -1,191 +0,0 @@ -// @flow -import * as React from 'react'; -import { Img, ImgPlaceholder } from './style'; - -type Props = { - loader: any, - unloader: any, - decode: boolean, - src: any, -}; - -type State = { - currentIndex?: number, - isLoading: boolean, - isLoaded: boolean, -}; - -const cache = {}; - -class AvatarImage extends React.Component { - sourceList: any; - i: any; - - static defaultProps = { - loader: false, - unloader: false, - decode: true, - src: [], - }; - - constructor(props: Props) { - super(props); - - this.sourceList = this.srcToArray(this.props.src); - - // check cache to decide at which index to start - for (let i = 0; i < this.sourceList.length; i++) { - // if we've never seen this image before, the cache wont help. - // no need to look further, just start loading - /* istanbul ignore else */ - if (!(this.sourceList[i] in cache)) break; - - // if we have loaded this image before, just load it again - /* istanbul ignore else */ - if (cache[this.sourceList[i]] === true) { - this.state = { currentIndex: i, isLoading: false, isLoaded: true }; - return true; - } - } - - this.state = this.sourceList.length - ? // 'normal' opperation: start at 0 and try to load - { currentIndex: 0, isLoading: true, isLoaded: false } - : // if we dont have any sources, jump directly to unloaded - { isLoading: false, isLoaded: false }; - } - - srcToArray = (src: any) => (Array.isArray(src) ? src : [src]).filter(x => x); - - onLoad = () => { - cache[this.sourceList[this.state.currentIndex]] = true; - /* istanbul ignore else */ - if (this.i) this.setState({ isLoaded: true }); - }; - - onError = () => { - cache[this.sourceList[this.state.currentIndex]] = false; - // if the current image has already been destroyed, we are probably no longer mounted - // no need to do anything then - /* istanbul ignore else */ - if (!this.i) return false; - - // before loading the next image, check to see if it was ever loaded in the past - for ( - var nextIndex = this.state.currentIndex + 1; - nextIndex < this.sourceList.length; - nextIndex++ - ) { - // get next img - let src = this.sourceList[nextIndex]; - - // if we have never seen it, its the one we want to try next - if (!(src in cache)) { - this.setState({ currentIndex: nextIndex }); - break; - } - - // if we know it exists, use it! - if (cache[src] === true) { - this.setState({ - currentIndex: nextIndex, - isLoading: false, - isLoaded: true, - }); - return true; - } - - // if we know it doesn't exist, skip it! - /* istanbul ignore else */ - if (cache[src] === false) continue; - } - - // currentIndex is zero bases, length is 1 based. - // if we have no more sources to try, return - we are done - if (nextIndex === this.sourceList.length) - return this.setState({ isLoading: false }); - - // otherwise, try the next img - this.loadImg(); - }; - - loadImg = () => { - this.i = new Image(); - this.i.src = this.sourceList[this.state.currentIndex]; - - if (this.props.decode && this.i.decode) { - // $FlowFixMe - this.i - .decode() - .then(this.onLoad) - .catch(this.onError); - } else { - this.i.onload = this.onLoad; - } - - this.i.onerror = this.onError; - }; - - unloadImg = () => { - try { - delete this.i.onerror; - delete this.i.onload; - delete this.i.src; - delete this.i; - } catch (err) { - console.log(err); - delete this.i; - } - }; - - componentDidMount() { - // kick off process - /* istanbul ignore else */ - if (this.state.isLoading) this.loadImg(); - } - - componentWillUnmount() { - // ensure that we dont leave any lingering listeners - /* istanbul ignore else */ - if (this.i) this.unloadImg(); - } - - componentWillReceiveProps(nextProps: Props) { - let src = this.srcToArray(nextProps.src); - - let srcAdded = src.filter(s => this.sourceList.indexOf(s) === -1); - let srcRemoved = this.sourceList.filter(s => src.indexOf(s) === -1); - - // if src prop changed, restart the loading process - if (srcAdded.length || srcRemoved.length) { - this.sourceList = src; - - // if we dont have any sources, jump directly to unloader - if (!src.length) - return this.setState({ isLoading: false, isLoaded: false }); - this.setState( - { currentIndex: 0, isLoading: true, isLoaded: false }, - this.loadImg - ); - } - } - - render() { - // if we have loaded, show img - if (this.state.isLoaded) { - // clear non img props - let { src, loader, unloader, decode, ...rest } = this.props; //eslint-disable-line - return ; - } - - // if we are still trying to load, show img and a loader if requested - if (!this.state.isLoaded && this.state.isLoading) return ; - - // if we have given up on loading, show a place holder if requested, or nothing - /* istanbul ignore else */ - if (!this.state.isLoaded && !this.state.isLoading) - return this.props.unloader ? this.props.unloader : null; - } -} - -export default AvatarImage; diff --git a/mobile/components/Avatar/img/default_avatar.svg b/mobile/components/Avatar/img/default_avatar.svg deleted file mode 100644 index 12584728fd..0000000000 --- a/mobile/components/Avatar/img/default_avatar.svg +++ /dev/null @@ -1,73 +0,0 @@ - -default_avatar -Created using Figma - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mobile/components/Avatar/img/default_community.svg b/mobile/components/Avatar/img/default_community.svg deleted file mode 100644 index e7379f2083..0000000000 --- a/mobile/components/Avatar/img/default_community.svg +++ /dev/null @@ -1,34 +0,0 @@ - -default_community -Created using Figma - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mobile/components/Avatar/index.js b/mobile/components/Avatar/index.js deleted file mode 100644 index a6a85a91cd..0000000000 --- a/mobile/components/Avatar/index.js +++ /dev/null @@ -1,44 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import { optimize } from './utils'; -import AvatarImage from './image'; -import { Status } from './style'; - -type AvatarProps = { - src: string, - community?: any, - user?: any, - size: number, - link?: ?string, - noLink?: boolean, -}; - -export default class Avatar extends Component { - render() { - const { src, community, user, size } = this.props; - - // $FlowFixMe - const optimizedAvatar = optimize(src, { - w: size, - dpr: '2', - format: 'png', - }); - - const communityFallback = './img/default_community.svg'; - const userFallback = './img/default_avatar.svg'; - - let source; - - if (community && !user) { - source = [optimizedAvatar, communityFallback]; - } else { - source = [optimizedAvatar, userFallback]; - } - - return ( - - - - ); - } -} diff --git a/mobile/components/Avatar/style.js b/mobile/components/Avatar/style.js deleted file mode 100644 index 9cd737a489..0000000000 --- a/mobile/components/Avatar/style.js +++ /dev/null @@ -1,48 +0,0 @@ -import styled from 'styled-components/native'; - -export const Status = styled.View` - position: relative; - flex-wrap: 'wrap'; - align-items: 'flex-start'; - flex-direction: 'row'; - width: ${props => (props.size ? `${props.size}px` : '32px')}; - height: ${props => (props.size ? `${props.size}px` : '32px')}; - border-radius: ${props => (props.community ? `25%` : '100%')}; - background-color: ${({ theme }) => theme.bg.default}; - - &:after { - content: ''; - position: absolute; - display: ${props => (props.isOnline ? 'inline-block' : 'none')}; - width: ${props => (props.onlineSize === 'large' ? '8px' : '6px')}; - height: ${props => (props.onlineSize === 'large' ? '8px' : '6px')}; - background: ${props => props.theme.success.alt}; - border-radius: 100%; - border: 2px solid ${props => props.theme.text.reverse}; - bottom: ${props => - props.onlineSize === 'large' - ? '0' - : props.onlineSize === 'small' ? '-1px' : '1px'}; - right: ${props => - props.onlineSize === 'large' - ? '0' - : props.onlineSize === 'small' ? '-6px' : '-3px'}; - } -`; - -export const Img = styled.Image` - display: inline-block; - width: ${props => (props.size ? `${props.size}px` : '32px')}; - height: ${props => (props.size ? `${props.size}px` : '32px')}; - border-radius: ${props => (props.community ? `25%` : '100%')}; - object-fit: cover; -`; - -export const ImgPlaceholder = styled.View` - display: inline-block; - background-color: ${props => props.theme.bg.border}; - width: ${props => (props.size ? `${props.size}px` : '32px')}; - height: ${props => (props.size ? `${props.size}px` : '32px')}; - border-radius: ${props => (props.community ? `25%` : '100%')}; - object-fit: cover; -`; diff --git a/mobile/components/Avatar/utils.js b/mobile/components/Avatar/utils.js deleted file mode 100644 index 24c4d9a015..0000000000 --- a/mobile/components/Avatar/utils.js +++ /dev/null @@ -1,24 +0,0 @@ -// @flow -type QueryParams = { - [key: string]: string, -}; - -/** - * Optimize an image - */ -export const optimize = (src: string, params?: QueryParams = {}): string => { - if (src.indexOf('spectrum.imgix.net') < 0) return src; - const queryparams = Object.keys(params).reduce((string, key) => { - return `${string}&${key}=${params[key]}`; - }, ''); - return `${src}?auto=compress${queryparams}`; -}; - -export const FREE_USER_MAX_IMAGE_SIZE_BYTES = 3000000; -export const PRO_USER_MAX_IMAGE_SIZE_BYTES = 25000000; -export const FREE_USER_MAX_IMAGE_SIZE_STRING = `${Math.floor( - FREE_USER_MAX_IMAGE_SIZE_BYTES / 1000000 -)}mb`; -export const PRO_USER_MAX_IMAGE_SIZE_STRING = `${Math.floor( - PRO_USER_MAX_IMAGE_SIZE_BYTES / 1000000 -)}mb`; diff --git a/mobile/components/Codeblock/index.js b/mobile/components/Codeblock/index.js deleted file mode 100644 index 4e2f2a62d6..0000000000 --- a/mobile/components/Codeblock/index.js +++ /dev/null @@ -1,15 +0,0 @@ -// @flow -import React from 'react'; -import Text from '../Text'; - -type Props = { - children: string, -}; - -export default (props: Props) => { - return ( - - {props.children} - - ); -}; diff --git a/mobile/components/IFrame/index.js b/mobile/components/IFrame/index.js deleted file mode 100644 index 8d35af383c..0000000000 --- a/mobile/components/IFrame/index.js +++ /dev/null @@ -1,18 +0,0 @@ -// @flow -import React from 'react'; -import { WebView } from 'react-native'; - -type Props = { - src: string, -}; - -const IFrame = (props: Props) => { - return ( - - ); -}; - -export default IFrame; diff --git a/mobile/components/InfiniteList/index.js b/mobile/components/InfiniteList/index.js deleted file mode 100644 index bc7e8c08f4..0000000000 --- a/mobile/components/InfiniteList/index.js +++ /dev/null @@ -1,101 +0,0 @@ -// @flow -import React, { type Node, type ElementType } from 'react'; -import { FlatList } from 'react-native'; -import Text from '../Text'; -import type { FlatListProps } from 'react-native'; - -type ID = number | string; - -type Item = { - id?: ID, - node?: { - id: ID, - }, -}; - -type Props = { - data: Array, - renderItem: Function, - hasNextPage: boolean, - fetchMore: Function, - loadingIndicator: Node, - keyExtractor?: (item: any, index: number) => ID, // This defaults to using item.id or item.node.id. If your data doesn't have either of those you need to pass a custom keyExtractor function - refetching?: boolean, - refetch?: Function, - style?: Object, - separator?: ElementType, - emptyState?: ElementType, - threshold?: number, - ...$Exact, -}; - -class InfiniteList extends React.Component { - static defaultProps = { - refreshing: false, - threshold: 0.75, - keyExtractor: (item: Item, index: number) => { - const key = item.id || (item.node && item.node.id); - - if (!key) - throw new Error( - `[InfiniteList] Could not guess key for item #${index}, neither item.id nor item.node.id exist. Please provide a custom keyExtractor method to extract the key from these items. Item data received:\n${JSON.stringify( - item, - null, - 2 - )}` - ); - return key; - }, - }; - - onEndReached = ({ distanceFromEnd }: { distanceFromEnd: number }) => { - // NOTE(@mxstbr): FlatList calls onEndReached 5 times synchronously on first render with a negative - // distanceFromEnd for reasons I don't fully understand. This makes sure we don't overfetch. - if ( - this.props.hasNextPage && - this.props.refetching !== true && - distanceFromEnd > 0 - ) { - this.props.fetchMore(); - } - }; - - render() { - const { - refetching, - refetch, - renderItem, - data, - threshold, - hasNextPage, - loadingIndicator, - keyExtractor, - separator, - style = {}, - emptyState, - ...rest - } = this.props; - - return ( - Nothing here} - removeClippedSubviews={true} - style={{ flex: 1, ...style }} - /> - ); - } -} - -export default InfiniteList; diff --git a/mobile/components/Login/index.js b/mobile/components/Login/index.js deleted file mode 100644 index 442455f0b4..0000000000 --- a/mobile/components/Login/index.js +++ /dev/null @@ -1,56 +0,0 @@ -// @flow -import React from 'react'; -import { connect } from 'react-redux'; -import { Text, View, Button } from 'react-native'; -import { AuthSession, SecureStore } from 'expo'; -import { authenticate } from '../../actions/authentication'; - -const API_URL = - process.env.NODE_ENV === 'production' - ? 'https://spectrum.chat' - : 'http://localhost:3001'; - -type Props = { - dispatch: Function, -}; - -class Login extends React.Component { - authenticate = (provider: 'twitter' | 'facebook' | 'google') => async () => { - const redirectUrl = AuthSession.getRedirectUrl(); - const result = await AuthSession.startAsync({ - authUrl: `${API_URL}/auth/${provider}?r=${redirectUrl}&authType=token`, - }); - if (result.type === 'success') { - const { params } = result; - this.props.dispatch(authenticate(params.accessToken)); - await SecureStore.setItemAsync('token', params.accessToken); - } - if (result.type === 'error') { - // Some error happened - // TODO(@mxstbr): Error UI - } - // User cancelled the login request - // TODO(@mxstbr): Cancel UI - }; - - render() { - return ( - -

    S-@zl)GvEShU-vxSmt+V#5RolqB+)S6=m>{3sf z9DVWPL-7rm=0d7UD?K@>f{T60gS9gwigV-eg*~j=H4Gh7#=xAX>b0rbAv&M#ElLJtxag`8G-M`@X z%Dl>^knS&8fF|B`gIeSOE~|YaC^@x(8CTuY{UO`NU~e3G=R5_3!_HuKYH09g-@fN@ z%|*`sNQ`=3M7n2+8JyP5hmD#%dN9VF@s?fyFyfXB#wQ9LD?Z6}kv(x3YYAiV`PHfA zgWbIuY8ThL)eTK(36X_to!oq|Qu6?1^$hIw`Du1Fh}4~U;F5>v^p@NwNt4UqT2mo2 z)tuQU08G8a%?#{IZoL->{DdX0U>2Py?@bp^ptHwTX~^r@SytLkuY262hoob7oyfvg z0?jfpG!Iw!j;J&zV*=W&ZP_Y*<|j%MYGw^fADaO=JugAV#h&u~9#OrzRCJ+qfRbQS zEVn~)hUy7ff|F6m9FKX#B-DZEM zKjRVe;fIO?^khBr?V}+iI@rihsNSCG)xH4mzTwWHJ{fUh_Cdx5eBUG|Jnw{KNxb&@ z*^;eqo{#_H&;RV-{@!=L`{4bE|yk4dbn>C%AZ3m1iLiI(={dKJjKp_&d2|FG<9Ez*U)7r~U zkIGdSc3;?~d9#RDDNkDeCI{|g6RPu7pbBuD2q}<$9P(Dfb$qL-%HGjBz z@0_cd$+`t|bWA8RfNJQ{lV;U*3R{=-)}#ktj&^JAB*KZgWWw*~VQVS|YmU^bdy&|1 zv9k(iuH!QVd+~)T_zrV$UBSdxSeEaJRD>aQ$xVO+;E_0^4!_tsLY$FB*eKgzS?~V? zlyncEiZJ+4k5}#LtbExH~Pr)UYscuNv-53mUSYiFUD94pa6Ptyn>4JU_(MZj?2B0 zIvvB+eMv_IUpveWMX4wZEYaC!C9>Uf?Yya4wZaje23~u3^bcPI%2Ub^g)}=rXANTd z2}e0Rk3r1Lr@qwR4Wy5mXAdNE9Rh$|N_u8*y4hW0SWg|QesQ?u#uW)4`wYz;o;2uv z&-UDti0={^|ZJL$|tuY)qA051|{!x<>y{3tPihb^hRJqWG^9c);cA` z7<@taJ_{a^E}WTD_i|XI3_()C%2u@e6dEQy+v_aNAukJmM(7 ze*aJK$M1gnkA6NhFS~#5d*A!;PyL7gUcTGf!=U&q|ETu1%UAcr{8)FOy`R{_gRLic zLmxG9;5I(?gM4Gst9=20@iEjrScaYh+VcCbv9l*`+eYR_DJ3uLJW!(Z)H<@f#(w>4 zf8+oD)`!3L^8XINkNy77zTwgoWl9jgNVwp zdHiVQde+o`;}2$b{8^Dxf9b>1IfKrg(Dawk*wat;6IW6h(a+0XuHYHKwIus-$sQ+$ zTJf^S6`TB}#E#BPd2L(lQ(PJBS4FsMSyp@+Z+lL`K5FvE3X_;(b?0@F+UoMi>kwTV zd*;l&V58#Naqnp{H2<*>-L%zfeSNaerGONZ0azwXc7iIZUgXt6N6c>P=-}pl60EZ( zIaM^3I7@{pw$MG@hXY~|$DkG#x8f&{G+j>FnW#BV&Ec$I#WxArR49I4#}4sTt9fDU zm54B7)*VxPI?n)aduj?+Ix&SC{?fzmL6)XOolRho&gyKjqGb;s&0dL1v zPxbFG=>6Lkw(tzc@&yY+`ch2sAG{fqg}iwsFYW>dFEp4k?9-pHmH&vvPJ((lzCERX zL?8>w{?I)(owYw8?H%wSLqZ}i#P>yST%!}#dq;)SZ-GJWdj;kcirY$L_cWR1B z2q`C8&Y7>dRBHw1o&>G?w}ggv%}=&PeZPaHlV3c(C#VlYb~E}ps=Rm`zlS^XaKK!1 z-zl~SIJVLsdjiP_s}PIk5X4f4LdE2=1-Sd)z_2~-EyksDzXDWN?TCoLGI~iHzk8Htb;JS>tUV{#~ zbEfX8C$Jrit-`>Z=#{_Rsb690@7Pi+JAV`rwtCZd&C+A{sl71FO?kjT*fxpCGWy&9 z`5ypYef{dM{-0lY_r32?bzYQg`T3vC;%8@Q{N*nz@q4#NQ@Z$rk3V~288LE}MqWzx z*x$tdYF_}@gUtZ9Er$?Wh8}wkoY>fcJ+=?^m-qY_Kz#Qf1vuEkzgqA z^sz4(ktCP-x(b*fB!OPyLdK@JMh4TiuqY0NTw^@Ksm&NX+a_6@j#h7A>P72$m3Unh zGx%hdI2# z{vi7~h0R6e(kVwJVOw@!-IVWD`@$((;oaZq35sCSi*!Mnur|&*IAU0tmFux>ifC1F z0(vGB(|*kf{=r*N`VwVd!`$~@Fp3Cqu+f|Mav=DUxct5+U`rKSI$4rip+Jwza_g=+ zyv3z_O`kLY)s84t7w~0ho8fJ+xjk2eTth;XU*_WdhI75|6mW8E1VTV`5T_X79h zVvh1Fl`*nyXqn~7OweS=e!%_>WOUCUrMNHKj1WoMmPy{aWS-QLcnJWMwl2teaj6x$ z32^T-lY$`Y-a#fttKTIZckISMOFgQo({?I6rl{+&VLEm>rl#0?$6lh|sc{XXS$6$q zF0^7QXT?w0uD|W_r7%)WBa}EKu2;1Xv0r4YOJ@0 zoPz>=PoBEXqL7m=X=m3x)2AQVbvr1vqyz%DA6@_UipQqDIp9+Vh+18PW82iAyq9bR zTHyE9TzNLf0Ku=A9*HP*M&oZ#ZF3G+uwzn8?P~2ZfQs$feLonC47*3cH4E9#;)Ayh zM5rC%`pk1Us8#hIQ^>VrW-c?EkYeZK78g9h^kN;2O5KBM7N^W(HCYH1+4Jzd?R>5U z8Nz%1`~49%ci;JzGJU0#+Tj@9U@=7I?1kChZtj=bcF7sIZ>p2UEf*bdRw zA(GoJ$_C3VIsKETR*0<~J@zDHFB@~ri$W|rc+U>|nHzal-O0sHtz9oEk%^5BG3$t) zrSJu`rW73(OCtB)gGz#Nb#mB9Dwv;NK++2}@H)K~Q1zbb9C~!(S>m<$p!>b#EQkBr z$Ha?TRlr)WiqqMcT+h!k8{h9KyL{ZpRV4ER<2f3jm{7~aJe=i>3_#C7%}VPU?3-GH z1CmAFmM6Rnl6&^4i3towF?~KI$45xk65Hha{nT@vhwycQOY52}wBEs0vT&)!DfHOQ z*?dGQrm4z&Nm21W@5ZbD3!($z@oV=h=St-^Y@jDTw2CtFHO0yG!0U8%B{-ajW)i!8 z1F#Dq+jYiRSj3%d82IH6IdHZOycX06>NISp){1XMM-gXZ#gQ{YVczVi1*Ko=YvPg= zaIKqq(UlWOgS62rLI|l-Onr&&U`o`hQw0ix=!hz#l=h)pw>!pxKe)=!o$vYhw((3RW_jv4U1Y6IF z4yeJ@VW=6~p?F!PmzrJi3zx^K*!%<81XR~ey zv)hz(F7X?0J94ImCAz)VBR|`TO?>2MxHn^a#V-Kt17ncrtnA495bi$otUK4X7mI#Y zWdH4d@Y?H5&YoP`S2yR+eej#V^&8&|;j8ZF#{ek5R&DDsXOo^7?UcnAMbLe%^N;nF zqFcWm5JuMnD0Xlq4>v5e?z*klD{+1b9{VkhEUqv4Xsl~VRL6h_DlXZ$h@`ER(+|s_ zR$OEC;J^2kxZ3Y|(ua7f9zq{5*mo(VQx>_p1?kv!x$&ji_+l3xu6A5OT6G)r-8wz=Y(LUBhH6Vq!4xhgGhv9Ob`2l0@mj#HL~F+r{J?8o!`FV{44C^n zDa$My`py|STszN-6*YRt10z&tgDjq$)H!?Nya>E4Ovx)3BzZMHVS_u|ab0>HT<1a` zn~Icrzt4Q%I!3;bcSY`$KKs5GZ|9h;g38Z_&Zaq>6%ckopz|sxQwzp(Y+ttKbWkAy zCmx~nkN6RjTKC#ztV`~dJu7T{^x*~ixOYmDl3F<=r3e?Kt7dJK3KN_CM;D1Noj!e= zTS2~5sA@zLsB3OW`a)GTg;8JXtwC(W^6HYQh?f>MV=w>A2WQ#Q6RPlzci?-j;v#V9LQ=9l(BYA~l zN(q@e4$Tn^Ltszrz1i5IE55vz8In^A<+^0qbs0UmQ~^$dx=u(r({Juv{e!oXP_TQ>q=}J?sw!)l+9A9U$i$7xITqN79*q*CSK*5 z?*H+3-o$qS{Gs|W0I!sf)@|y^$Rn@yM$UUp=J;xF{J=sL9`<^0iBGm5!!eor;g#bI z&Svrup(ybk|CaO#LWbrcU>>N4`($6y+X%FBCL%6&$!+i_)Oee#9hQbT?B~J4)JTnzD!R=&8X*TK+U#vmbX;Ywy_H`mlIRCgvcA^6waqzf9eJ5kGqXf zpK?5jZ$28VUtsbZ6?S>su6W6?m9RD|Sld2M>(Ik>=2*y5j_x{kbT|bXN8`|AW5;Zl z!e)MG8z=9ogF{jL3U18PEwCz_(Bv_;oxxyCZM6?}`*)JQPt*vQCphQCRUf4=KS3AP zB8gY>oo9&qw2DU@VCbthbNH&z5~uzi=<>EjNPyyL?Y2=AD&RU5 zVauub6xe-WlO2DvY7Z%yCnm|t89#ewQZmp{B?Jgjt*E`TO`R@bfD=6>CvA3xCP3IasnU6=7JLPk4!u?<06U&91Zar0m#o@K zKXHk`^6{Jb_J95Ne{R}^SM~Mn|CY}m{>P{0+W#B)dHF}Z8NkM&XEA(ySzC^6@4;^z zat?80--P^%UjW#{d^TKcd1c7Y^!V80XJ^gL(f~hva-~r!1dvf^-(_iy{QK6Vu zzVKrJw4BER|J5JsV;&gw;oFx7zV*RrDm|M0P-*+%;9%)NTe2QVHs#s~spo+Gkkb>H zr#P=#dWhA2aQMX@^ESEyQucG`FEQYF!k8t^{ zadMZkeD%3(>MqVrZpVWxNKq<}sf8gwEOUnHDV6jzho2e=KJ@6a9n+DzJQ;?g-6ULo zC{5cr%85=GF!o^b{)g#M2vrudjA!zu&f3-EASNVzWQTvRnNb;&CH}3aPKKKMY$B$f zL_!9Rt?{QRgSs^^m&6N4&3yPi+SOD{;jkoicJOB{nM4Ae1b9=QtRaCwg_$X0`gV@% z{S8JbQkX+ke~S;Bcq%bxulGkv+{h5eR=R3cG6lP@J5G2yd|XsMt0Ag|9(sP54=VTX zL@jS}6b4Zo9b0|bYbMF5zo|6SJ@>Ys_d{aMw5w8qWmZ49RX}#?ag4XbcY@ivw~ISd z7|;>UP>wB9@}n0H7BOiKm7x3d1`#h3*O}JOqo3uuWh~H6c?DCy8xF(DE&S5SD}RVW zOhSWCY9#rq^2M=X>PfXbO!w}oP;II63d+YdLE>)K&Nr`51I&^nX>$k zZ-3O|2ki(+)L1Y7(Ph=6e8zJ9;v4wa|Ea$Jt92D#=i&y^mcRWUTlK4d=udK~*>NR? zeU1W0uY5;A^m{D!Z3_;rXMFL$sr?nd0I-M7pmTuR&X98m+rH)dLBz)%IT+d2%{2b{ z?*RR$t0Lc_`_l3}Zvq7YH&UcH{h5 zFSQ@Q69Z&Fl+kN3EMtgY!{>p8&O<7%(q}ant}O7FwIfS+MGGc)barqo9`dR2o-+7V zq3bQ6dgN-p!Kq?IkWXXp-<0Y$VBSQ6LEw}sCyI($$MF{FYgz-4~96`dkK=F-)tcj zeE88RBzaO)?}~{HY^`%v)_omz&sC(`PmK`y9!bSXtNa+_NT?2p@{YqS0eHXk6W9F= zZaRH9<>5Y3k*vOj>>4)aQWtofiaWu=luG#h<&uhhxC=xmuTIq4@cN7!2aH(w6A&Bb zThHY>3u64dI=^c-BqlV7ipq4ggfGa9W!y^I&?KOR(d$U~Zi60RU6|p01;O zP1;0W>MTfRq$VhHvhhBEoiA>2(=^4sO`*s>huAUM!h7%I zUXpk_=+wLKD>Atc{FLH_{rF;K4@QW-U(Te2P%nvbt%XitJE&bgI7C(slX@Pg=Y)^n zREA$s4l?&NdWvO!*IW=(Wa3hELR?B|m7#p7SBSMU9qT$PS0tv%Iebn4=Uz8e=Z)=R zR}p3Dx^I+2M~p_WaYIBrZP%_MM5;_k4gq7_slfWb@P!|G_j}&JzyAMQzxkUV{OSMT z-~DnArHb!SUjML<8y_owF)JCphH>H!4*&bs`;H{|$yJ8P@x9so6~6$mhnYd|9XT(s zc^BjWgT)uzwqa+DtsXFuH$HN((Sv# z$lxbXR%zoZ6EzJVkXE)m__2XWP&d`voUgONmsf<+0Vkl{wLPxF%zQlF!MKFt#NNJB z(upIhR%7;WpyV4sp|(sAG1y|S{fIteU|u=*(kd>s7++ZBM+@nM9ieC9`yiR@-g9`u z(j4Ty#*O1Wg3xuD@(L%8;^9NTVOOjWg{!^K#kg|f3PJLBFACMv91jrJ#jQ+6&Q&!z zX0R!?bC7?|Q@)CB8$6z)_dS=>(_~ZJ92#kh4@zFDVv4Xn>Ds^CiYarS1<~$<-OmNQ zUa3pD=&OknA(Nco%W7bPJBMA=Fnk2A6-|D8DfqJU8LyYPkg3G?uV-z4^2lG1&6Hku;BRR-3KLlf)+&*_AiOLH- zHL4dmxX9ss52r1&74-2vc&nq-!SsP@SJ_Oi77v^}K-x6A+U@b2AvI5*NMc2H2$Ur; z-nklJJzx)&_-7C8a14Ki5s7`j86PHhaI&WD{^XkK&PxRU!-9Xl=8tT`E5yuC$rxx8C zLh*KS-N~IDE3)#g3~v;)Do=?8ZW`Iu z?>01!e(&6xL9jQcdxO6DDT)`f+A;9U?y}|@d}4?!S@V(2B+5hXTABlCq{yVp@B>r8 z%}2Z_6zP})QOGLe;#Zh1C%`Ni>s7xdKbFRd@50n`0^d{(Z7p{0kH@ub_c)TYWFa291N>fT>nDeJU zRqy{wN`3PlDu3>q3Mn`RdPP?5V~$%`${;5T2rsx(FRpgG&gf;dJ>GJ3G!jcShO{)d zvT85P)Tjsyw-@&GR6F?8Mgh^o1DuM~N6kvrDZcBx+$Vic778;kxC7zmb`?5|AqID3 z#C>t9ky|;1=_18h2J(ST4(-f^unbDtxBjyEdnz-uc`?ZIw z?+a8ulGv@#l)`k;Fy6iUnLqX~JJpL{zw+f@eD;^B4Cv!`-}#RBlIx!E(A>tF^Rw1<9`!-{xtEb#b(IV_eq0}k3I4|Ciabako%^J-TU>&0CMBW_vil1 zpZLq){mys3O|n;B{20LJfBc{B=K>G9wOV1*L$KagG{?_#>EVK14;X9q_Mi*c&ljG< zmt~xD;kvn9O9hi_uRH{??U>-m2PXQCof!P}O8gaTP1jZQ6?0_;X0CjK0L?2If`2~? zb?>E5YRj-n=}&^;KY>062k8@6<mh32^-%fu0W|R_T*B_B1zsblB{0J z*@@fb>S4eRbSAW1O35%}DL;~Zb|<)DchYFHGbv?)-2b4-6ChpL>N-od*1(rJZB?a95vkZ(E! zs-bqBSjS&akMrp>sv<(|^)%Gx>_s_lmLM%igU=J^L!)GWUb%dXteJ zsp~u^kk9H&wW*%M3SGK9*fFqr?=jid*KG?PxX;BP_(Fw{GsC7c2Y^iGkdzKietexM zyYOY}P#3Mlc)ZRR8mtRT{;npm3aiWRCBAU6yEM?OW+|X0o*CVP{J!jZlUE*+@q&&( zt#G$VHG%OQHkU%mt_T&rh$Nac)XSty?iF5Cv2+E+S!&J?8Ay5*w&6_woWRa>_(U4! za6!uI`qfv-VyJ@Hol~gyTsoh)EK@Vgx+VphFs3mg{ZyU`^$g12lpt*nuevrtU*{~8 zur8egew%V;m$2fk_hZ*~^@Nz*p6eB6X4!Vl3R{IQQu1uikoOjjJc1jO`^R`}=AtHirt0{n6@RDHmU=`I(CO*19%sun{*Ihk_B zY^SJ1DMu-slzQrZ|lFEN3hy`#f@*j!5k#nsJF z|12qWueH&8ah4vO;PCg$89T3ta=xy)OOXeZcuEitmzL=%JtFhxUFRgKJR-8JpyuLV z|NmEi_h(Cc{q=J{`=9aO|EY+Fj{F0xSsBDJduLaaV-B` zU#_A?F2w1BhY#HI5K8PBrY-f*Dgj28cr4CU z6s3WA=6|0R0{x~oL3tvF5WrK$RHGL3+!nMiga_dVJ_(X!BcItE>9~XSoE~*%dF}kp zS^j0>w>?wap(|^Hv;_fk&IlS~PHk`ta1pHh6adV7yF0G6@?E?Ys$6wJ zOsF{E#EqjDx@@&?yJ{6fkPbTbMIs2gXHA&9g1O}N{--Kiag9{k?a4NL32Ae#(|3tn$6wx(RLOwRf1zurOClt|p zSG^NFLYj165ElO2UtR3lp3N4wayL$OlA>O0l#_*?1uiT1kt49$Cwps&4QHakXckRI zjkaZP$KHB);0Q()A8O(B;(}yI$?e3A5KmDq^aJQwO$71&ZPG|G)L`{A(q@{`&jB_A6iE z&s0^Z8?Ig7dHwtg0W>6B&E(_y_J7Clo!!@ke*c0%5crs8V%YPFJ$quDk+ z0LWo5_~lc~^B{W{Mnx9;bf+U@GMOi1+Jn}97obMz;}!JlCenY>|(mj_%HQvAmnZ#>o$ z=H2LdIF@l_IIF;C_%MI+eqsmrSlqZQbghlcoUWj8mig5Ch%MY|{rEAOmeFlU&y%kN*=~3!6*>haiRW^7m@#Z2K(2)vlgLUx6h{Wz zYtFrJKohq8U_yHVlySfJ3<8;?VTnF0cDPx~1v%bp6~kC~JYfs_uHkUR+xwi!RbQRS zp}8vkVG^cnJI|c)s#*l(fkfdxC)eA2L(6-mYpE^ejSU|(gWuqeJ5CsUPgRiKD?lI< zhkdz!vWunoCq-h|i~E8LDrHQej>im-vdb4BWTMkPwKdvNsX)a}{hMKU%c!~%Qapp} znmDH^Z_NlCVG12U8A=w1usp~1vUYMh--HigdhBXaRQbgo9qN+zY_Inq`~kZbu)g<> z-4$LdP1RUIdQXgP!DK(<(S)6QXe+klecywkULd+MYqBMVeNx)!SrxieTMQB}&U#G< zg`uRd+I^m7R4(8bwETo=Il+VMid{QZNEfy)6T^LDZlzyoFcZ^Wg%Fld_62u-uJ_(1 z0=&;x)pWnfKV-2V#z~6nl5^&wV(n~zT*!(!QD+WLko+_A`xr4ue{`QA}v3A zEi%28pa1`xU;gV~n!??Hn?SB_|F8QaI<0LPy_&uFu>_-&SbFT)X(@3RN<{row^;MsYiVb4pxzS(SBKYIdh`~K7a_>XhbzWn;kkNmcG zzpH)}AZ_aTQEl_^n#OB-=zctvm9eycH+X&Eb-W0T$|ndPqd9jxH2~+Z?rYJguK1~O zujqWzV4b*Utd)!2l$Yb?lYrUWODoSq4V>$V9aD|vIkcB#Ke~H?7^_h=DIc<*{`}HCO21)V6Rv?&&RcGCNjgi5%aQf-$ReX{(E{U7I0PK#kr#@SG7* zO~BrTgOyc0=e>?yKFz%O;D%*4-K=3QwfIU)`~`2G)OT1@*|A)5v{`fNdctulh4e1c zQeTUJJ}EK*X&QvfwSdk#rMNiZ?Mk)5TwH;4{N0B>ue7 zN(jo|+|_oUSD*7h%*j<44Iwt~{LBBJ`}}7n;+2J8_=W#s{{w)O%FUd=Y_(ptnwOiy zpY{Gf{0AH4iLvGG4=#HSF#0_{_BXe`>K6bQc!s>+60zL}9XmU(#K#_d{CnR%$$fM6 zm)gV^JnymxPQGR3*XmnWLiEb}ANnKpzX9lW=D|~spLCNy=wpzdE=nF9U|#JjorjKA z_EN^yexkO`0p{X*T~~`gfn06W;Rh#4&9ga9E?bYzrygo?oH!)QYH+7=hPuvx?lpAA z3I)r`E_IS7E_>lhq!L#nrDLhXJ_;Q^S>=f2x++wdLybYe#HM`uh)sFHIo)tZNYAbh zYk||x;t97Ar-~^dq;pCAM0Fxi^2fFc7#`wy zEgw35Vd^3`9A(+EdIPIiIKYy%R&R)zxA-uyP+|bPWASHQcw+R<`S~o)iI83|`3fgq z<0+F`T%(CoTO+)!YolaIxg>B#C!>zEol1g@4uNcObPngg8i`4r?AIDQur}Ynj#VCt z%}dRC7GqP@;Oiw`&!bflPajj#nE;e5a%qN5Wvd(#T}ujaN#x6r6kBffrsTu$exZ}k zEX3FKQ&IfIK<3PY65YX}4%YL-o_oTwkTPTj%ZBGXBmJ1_7zd{E;$Z)B?FdYh8 zN(cRD7K8eT_h7hJ3Fp#TZ7^q8-s)5>MZ&IEXG)0urcN6dA3bD#$SF>F+=DnO$YrKj z*LF)6YVAbl-VBHPJscju3e&Kbzg;^wvd6GGnvi32^#x8(NmJPPGS0a6ewhV8sKez( z3>S$CZ=KC`81C52z#J5@UK(UoRW`-k|M;QY@KSlj>4ol0*uMBjKX1c}Uw`%Q|MFM= zyZ`nt{8GxSIbR!cbFs14OoaKUzy1H6{$ckkE_Ba!aV$r;F~zrS3;O>L@m0S7z;ttK z?*s6=C-0)?XRr?r#9hBf@xKAcHa|mhVuHKealsZ;3MlZ?{Aq=)Qek=%7=#@eyf zt6fQ_{h962u~A2!8E3xbikG||GZtQ{IS5Vg_`t+8g701O$i;jl6;|ZoQpFFKp~G`g zm|;__D7er3k7H*+KC=Y_(ZPy%oliWTg*8f1e(fo&_u5=rk=|THlN*glq$P`xxW)*=Ih9( zw1B9i2$Em&I)HEzw?a7RyM*H@S9M0^Wf41Q_pgXpENSF$9IbPK!DMFi@iZ{GQX~Aac{l_R<>x9scWheIOa#DM{S3A; zSJht8%2kECo4C!Yf0rnx+rD$BenUDx6#QEy6vb@ItX!t9s(RkSl@nNf@Jla|4a(HV z(Vzxv;(n;*G8-~Qi?Pm^x`Hofk3W5c`05Pxhs zktn_Qsp)n@j(&r^P4!j30FZ;p;WFL(;o!U5atb>xw)?(w@T5>oy`iu6^S-+L7ZgR7 z9K!!6f9j9_MH0k#PxdN0KL+qq^<4n(d%X0csmrZy=5|+`$6k`L$L`*JO}`I(#5IKH zV?X~JKFL;I+Y)176KH!D)2K_3AN20~T4N`#joW}v>MVH8txpierC!RJp8Sc^=)+6Q zu^DY0R;}hpOm^ijfOYTSi5`Zu+q>=k8xg>@sMnvEI*M%uEk!tAN^f5vKTJCL-(Yu>XapfC&ayKk>heQ$Oti)VBFW(dJt0eW5 zmW5LL(=&Or%ro-jK=6AgGw5o{N{sVba&Bjl%XL`ti^fXUBI4q@a!``$yAhT z${=76rSvreZTkQKKmbWZK~xM*LNn})K;&n4YKK+LF5A0c=>r0&Zgg^EI|)nnY#>ul zy`TPy8C!o4P5klOGFK%5qC_^+gc`95SGQ8qv?ZH;?d=#-Ek4ue>YA4U7NvuOS)0rC zS_XZ&&yIPBcKO6AzUtBaQ92>!JoQiTU7&Ob>4!ahZH5f9mffwKdr`R-rXmJUkIXIc z?ADeY+awV(5ms_t=kz$1U1)N+t)n$JxkNx^HLv0&zeW(9UCmcam+zPq)AlYT<-=hf zcJp?!c4QXpTOLh9_>v7S@x`$Bu3_pe-ouV`tphk*&gdFWK9i}3E^nv0$O2s~?h)oD zyzIrN8VR{^3wMeTX}6{O57&MP0L}1>(A<+LP=VF;3DO)q3&Vz`fFqYnab+2-=GXZ* zP&BUGX^zv@8BuX{&H_pd0CPK&4;E%5Hi#44cE|TQm(RHu;S!JU)CQmJ^liKMvX6dz zF1T?Cr0!aa_2{c!8BI`P6_(l-#`jIn?t`pdoF z`;))JrM-dcm%j90&cFWOhf>wfBvgFTEY-`g}GpZRF^oyf?2r3EMVeV;sSD>V}q zd4+}#miWxb8M_2Wn@bo2WXNpnt!w4aoY}jDagWdr*E`l3Hty8qy)jDI1(ba1Dcdd- z1%H_pk`$eH+o{3xRr!rKP?h>idrv3K-7TW#`fhe;0nr z$ot1VU_U(gf;0y?4t7#nll zj^-&pANaIidN*dSaD_KU^m^$O``jj_c#F8~Ix{(3@wr#vY*KJVX9Qu^N!18>WiQ{% zG5|mX*Dj9m;%Al7JOQd|*JgySCoGkeIh>7?r)rpDv75YXcpVE@&Xe7J8*2nIft>(j zcuIuEa~L$%hm{% ziO&A1Pkq||8xy&nEalLA%b6V<2_`p&{`^pw>9>=Jqch*@A~Hp(KgBGLawpB@{!0TQ zIG13L<&z{x60r!&bhMTNYdZdClQBssT%i1KDfd6?mTBHy`h9wVwSOd@pZR0|$|n{2 zPt4aXO@6-p|1bUg&wWq^T>5j@`~LO+x({}^|Mh=tzy9m7v**u-*~L2vXR6M(>|0C> zdyWM;Ys;~{sr{9|0KmXAbcXs&j*UHs5gReA*ds@0KeB%efSW0}?7@9hH=h4wT%P_h zfWdH6{(tdf04WIKtY^0P{-5kWR{szn{}|vr_~-pp51+XsI%_?U_Vbxj;KA#MW?_2T zd>YY`&czQha-e~S$9h@n*YJ*Dig~chPISS~6aFHHj~IWLBZUzi3^uB~a8X%tpZbi4 z#n$eu+rthYzwm3-mOSX;69`oEUww+~i+NmwXR91d?|A^F2qo4gOzC{2mJVs&$IUT> zHy4nqZyy1pM&GtAZ`Rm%UYN?J6>JKLCIfWTPjR_ zZ+jfXZQd;tt$SZF#WZAztcQId)ZLBXr6kBH0d~A(|)oz%{pO zK`ke*oC2|Yh_bNhHM4?oWD*OAwU!I9O-V75_^gy+OYQ@QhKl@Nwm?OFuF<|=Z9}t# z64C=4&R3{%epFu!+~=k^5}983-45}3xvThzmc`lsxzWcOAQt+6K8nsce$CRg02PP%lH1UiQsp;xu=u64DE9B63H$KokO%5L7}6ZBdg- z+@M~Ns;Ei>v?gi0+k#X#KJk*)VIe`CxspSjll@8j1=EZ*&B zpS7O(G3J;bYyH=M?|t^!r|dZh>g8)x(QTT(V*p1SdhNmsTT0cruW?wj6W~xR%ewm$ z4X9eR8RdQ^s&Y5NI8awZ+r8agyG9}C1&Y*>T|M)fsk#?QaN;RDb$J2m8T5Q`o*7vd zZ9?|mHON1_&ff)!KOw$0t~)(t4~CtpT;m&F|A2ak$)7&ukv|A_UrRzx{~&9X4X^$g z4YKBEg3YOWh-4W^hHU6?T@#dZ5+Yb}Xa|npbsV*%;r2bG*YGihPPqr_mfVM{n)AjS zBlk;=w0Dq59H}eU3_ZS{t|*cD`gUfgzgR&hAQ6$h!}a_B#rM7X=l=k|^!wehs5C3R zMt}6<|MBtL|4UAIWWFAY3^a0ZvE5T)$M#MZxI6Z)zZ0H!EoQ#ldly!i3!2iRXOEpd z7yV7XNBC`v9Ikw^f93q*7a!ZJhM)K|KlYIXhwPo~JLb{d^~m;4c;AHKrvMn6a@VOP zhfG30csyR@l6c_AzpIX|{O!v_jRb>Syd)Y-SF&qx3=iqb`@l~;%b$c%o5|7nIP1u% zK(ZD&#HR#ym?NNrPpv@_ z%gSzh&W;m*&nX`7jj7jM}n5fP^+{$J$1qWbzb)fJ+V`CcFrXTR>5PZ1n;^k&Cc zveY_V?h_cQPF>Ru77w-|Fs9kA+w^I(YD278@^PdljYF=d*9oNRE59N-h`A=xt5NJ+a*OI~dKxpFZ zfjdNgT-EZjPH&s6`NeT$OVgpsNR;>TDRHbd@m#Ul}0#OC$rcrA8BcEU<_j&K-bPI7s{ zHT9M$b!Bguw&cO58p@iK%r2bU3Si=zjJh4Cr+~(eOYtFLZ*tWlo>S|txr#P(c9Gc@ zI&-;i_<(M>vk%(UM5PX78mT{Ynh!f{1NPjy%oCnU9{hDDQ62Tg`J^v^Lu~1(O)6?( zIR~-fKK3p>NYIM$$=$f1vXig;AUJ>MUGTU6yV}ev4Q!ns|F3&PUt9GK%&nAAw#eDz zzwbNc-G|N!CcCZYyi1EU*zAum!M>RN-TV!J`w;H~&xHVY_hG}&arY%|@5b^YBz2>G z!8aQmto_dbPR#jf0LK>YQ=j_9|2O{|K!B!r{tWJ3;2r&yaNT2{)Bo^?-v8=%zVGYG zJ>^%Umh(W8rsO*Hg9^sh_g*ITQ4g^iSb6*)I{A6Hxl>2yQ3YEbe2wmbgaQ1-!k>?p zc~dc`x#Cam#HMb_Fn1@at)(z4R6Lc#*%v6_+VchhmOS@5%xtT2iiQ&;2i*xTKQ=df zKx-wJX1rtigeD0R%ghbjO*+W}Q^p~K5H(B5I{9bf6szW&RnYgZs@R-J8A2^h<| zbGj&yw}nYTwYOXMoXpf=qw8|4dF!euk87|e=Z-10DceCJr`s1N80Ao7jcfi6zHHRR zM_v=sG|UUk3JFZutc){i<-!ScuFkdpG8p#R!uAorO)6N-eH}nZI*3ZpS#+ z;X|S?cF7eJ{$_cyIlBVoGn=hCE3cV7gR+YE#DG)IDq1y*sZVljLxa1vN7v!pLtu+Q zHM9&LtM;8M|CBIpgL1~|`(J^o7GKY&+~k~Cx1V6`HVtBYvTz@?1^rT2+vBGdWH4SG zTdGY)&Rm4A(}7d9;vESdi{r$Zd0|ySWmP<6#_{C~|LxyffAslZ{ujpVuJ_Gnuv`^W#g3I=ii%hr4?21MoPYc(1^KmLzz$DyLYUwI#Q-4J;*PwaH z<6OxJhn(KXcp!uGKYXjPh5E z?;UuOG<%sEEb0_?u{p|B_$(|1319vij{3!9qRE6w3(P4j*2|uI&-kI58p)@$i4q^S zZCwR`D;L+!*AJilvNcafWE8Rr*$!psTo8$ilsSS+e<=qn`4rH}CZ~Psxgffy5_g44 z)AcPL&9L&M=T7ZfRvc90YkSIz=ahA>&9)Jid!1gD6Osb)fi9^NrKV1IHex7f796c) zFO^)VIY1C5Nc*ld{aR>#*IreivlcieJ2)3>#~oWqld!Ey4%&j}Z=9})>?>!7Io~Rv zXMDE-p|DWDp)wQ(;=R%qsJ=a{-iNLS2Vu+1Eok zjOQFaA|&szEP3~HCa$RFSYXU^ftiI2PfnU;Iq?Ugol}Cc-=mj3_tfQe2!59&xlY=h zP!yB!p1>u5>G3t80{S{aR!4!z?fZ`_tR&M@^rsnYG1{{4i_?BdE}-11cafaym7Fk9 z-=o+63TzQ1HD`W7%(YHJb|otWvIB{h9+z}TrXgm6fo1ug3vjA8N>JC;L_;B+YaVg*>pI*W@0QLnzzF!dLzcG(=Y*}C*;bYr=!n zY+t=L=X)VTWW3Jv+J)IM*mI>Mw@VMalA#guJjp?58N(+Q;95%5NoHuarFZZen1?E&*ZIhk~ggZ8T*uZKd6By!z-`86f`H9fN#3I-PwKqYVsL9updVCO~ z)$xyJ5(qne=bngtQdhd9j&161uzPfAMkfNBWn#RjnAMo_C%>m+l+F{gY!T&yV}nXB zRoA=rl2*;el(#r`{hG=N35?=~inN_NW7Te3ssZ2kZG-cARWLoQ zAty@pL@5eEqNlFzBUkVgNr|=+H#G{dNgUzyU877U&V7a{Q5s_i*~einhdZ8mYNn;8 zXV2LF6>(+&U-L_>gy{iuQWKl>zTQTgGeu=DtDCfYon6l|JR^!}PEUDHT@<-VJcIPT zB5$~gS#kGkI&n#qJz*U?{b*>^;rUte7#!NqRmC-Cl7L1@?dZJ18Y;fJ3%#qo*oS5vy{>A_FKjJS51bgQ|-#5y^^^cO={~16N1ee&? zU%c|X;(r79+~@x9PX|C8h304;Z6QNRGubz8%ZYh}ix2!=M&C9tAN&{onOFQmz_g$T zPoezKNdynTj?{iHG(K|xQRe^5@(OD@J__Fq{pUIjIVE56adMjwDQdP+!tnpSC!FdE&Qo7 z6d-MFjPyRqlp<;`FtfpO<-u}Z6Qmm)u3D;1vA&lRZ!Tj}iDrh0;#gh3WNaK2sMzH@ zoF&m~9hxn3K!D%c_3LPaHiw26jll#z!k~@Pspj$=@JM^^t4k zBqRdQIRv(0$%HqFizp;jPB;Z6QSn z?jk&I3>#Q-*N=JbW#op@Lk_UiJh+})t|?@a%bxhA5#v)Ed|g0*57dK+v-XNZn;9!( z{3d(&`d>vLPA@Azwr!VOHr0_mvCWtH4*e-C%&XR#&j90AHR@tqC7j+#$RB%|aD4X> zg1{&L8i_@DH9uumFgEyP>DtOxdN}<1vGCh&Wcg`?KkSOHomH~$1r%(l{ASnJ$H)5z zzvG{M_5S)N09lSSLu~A^-)-H3{CD5hBX_kfy^QGL$PN}cIkW$R|NVda@Fzd{KmS_0 z_yYe`eNDv*WbfGXO@LSWh3)(^fq(i>gHMW$BmU8f{Z0Gb2hSthBmNg*dx_rwV6gkX z+Jole&6#`p%K#~J+Z{|-Fhp<20a4Nr_&CSPImKo@g6ye)Ev z&3VVzc*j=9lhT4??!^tI1Y$TqDZYhVuZ<3W(s=izV*?)r{x?Y!ygjkh#omYd=^I#2#B-315bT43N~g4xZ{ zAoy5{JGs%}Js(bOc%2jHXq~TDskFgF$PV1sZSRX}#6)NhVJYbLq4Yga7TdZVVal>* zGXE}nG8`e%nSVUYf!FrTulc}-I+(;z-Q~Ja81-up3MDIYaccHlH+;zuT&CE-8 zzT}a&PJK`2TG(=m?=F&0JE5{qa#?b6zok(lAH_X&1SHQWOQ1g2=$MwVkxrX=SCD-e z43`VhAvUJPfv7Hr$flW=1n>;dIioiQykgj!IC>nhOK;3vb5(X?sgzkIjT#_OwOHdk zIgaVEv@XqvO&)#fTdZRPl!Ls~G0DSlQf~`s(#PBjKsN-tM{L`WlfPxU$k%?8qFfCcpG&kIvZPQ%Db^L!SIzkJt$m z-!c%oRQm<&`jpKj1_y`cM{H{En1+*RM1+mIdJMw`KfJ;YZ?b}OWT-D#@H)W3MO9?uiJ0>_Cxg z>GfQIJO#&&voWW8!ZN@aikt17)&B=ng*rLylg z3&YK)aXg8?Wbc-a?Zg~g8c$9o-|!;B+0Ru?^nq+ysh{(WA&bWO5shB|hLJ zkA8{u+dt}ZNl*~g|3MJiwH?7+{xB=;o_OEcRkok>xTO_JL# z+YTsMpJ)NEWXh#kR6y;CB~BG4R(fjo-mNMyaoAW1yJ}eDX`nX9TX%p^AY*;93-3=O zc4#srOVdr(=`AOy|_w9!zyW z5t`(e4ldXvCbVtA%2cGfc96&4Il=j!^nHRn387PL30-1`<1iP+AP0gf*#UzWqE2NX z#NLEb0{~90)%AxXEWTPwS-9a)9Pzb)0@y3HZd>HSdj_sa6@V`e&7ymgPK=6g{`j(& zSux>Wb=Y3L6}8~wGuO;Hv9oiJ5!7+mOP}~Yy!>6P|+afY_R_*GAjr-cxobS5w$2t26 zqBpd;Crh!n&PvItb7N`|lyO5E)EErCqsHx?yWp8eu~O5%j;?6&EOBsCSMkg5*<^H@ zKgx=w8HF`|C=i4#2YatmV=QOaG!E1b#h%x{+~J)<66C@>j(uJ2?-D(;q%GI-r4+#6ap^ zRELKq@ARjBPHxpuMY)!)j;#)HOmoJhf2X7ZUMJ2QTR8Fgb8X}BzyDi)$E!c|-T$2C z(Ag9gIXgyVjznkQnAoxthrBV^qO(WdIOO~K^)Gz>|M(C7y?^cgRuvCD)8K5ud9U~5XgTT=~_xC=e*aCY#92vVc3dG_EC#=$HN7e{y zuT`p_v$yy-l?CPQe`k6g-1!?#wN44otURxABl{p%bz58B6E;p*;f-;rPLde$0Mx>F z{JRtQ<*{3e-xSXTmdo`(Fon71@N90Sj-d()&$c^vd%`IIycL{=$|kDPWp8ZgJH*t% zR7RL{O>iRn%~nt>IyNSr&M5p7zla;zxyNIyU6r>1`PAK@&H?1^Q*6V;#I-D->6&!18J)X;D)_L@J@7aRdSfK4 zY%BW`0jO8##$N0ytc+`B$KTLk;+Tfr*W=fJN}6Obh}0CzSK?fYlGEG0e@(H&e4R7T zrk_5DJk8$ki77_8d~KlBQ5>D3EkR#%4XVw=1TnqHwg51Sd}Sz+dDw2O>ebvkcV;fo zfZJ(OA~{qCkmD8qr~-`kjF4!aMFlB7=7~&>`wH5qc-vaF6O#k4Ex|qJjF(yZK5fe+ z+l)ciUVb3)=301!6Rd!Yl>wzn;)E)}GWU7>9bbbmzA!@~$|tAi?md`%4^*)f!7akTViH|O0om5?yXeAzEs^U5>^`v$clfdR}GWQ;G=RT~f^fEdJURQP)PXs+@ zcH5d#u+2CQKt}kp;67R@1WllOH6lHe_blpgeO(O-6oP=v{f^&yJLN+*Us%t+f8jP4 z%bc%X{lJfY+sL3}-?Gv>L-X4LmLu3?!y6uCRxs95=h^S}=`V0-m$cc$!Z!=#dkD&y$$V zo47yxp%1+J`rq~2#G(gaIp%@Xk}b}n#~i^ru$84ALV1koIf+&-*Ac;5tkhx|J7lb2 ztQ~v&WnUtwJd@Y6JHe`9lo753#D&8U`}0P&D+*4#V0rjgJW@?+18;S(+iF|bnmCb8!ooptSkq*p#vWD_c% zYf-ah7Dne=da%@^hD%6o_dWS)CyT-gBN-pUUc@)`Hrp8T^0estmKcc2$RBEYz>)s< zoRcHArj7ZsUwq_K&MrlA&#t1VCg-Pnd@zFK2z%W-bAy=$G%KH~?mayj;qz26JjW~U zq>8z4vR4n|O6s!lF*VU=gA7Sbp=99HfFUbOWCta)_P7`Wvg!}64D@T2v(fIGoB=LM z)r{^*iLVN{Bhh866Bv4g&$ix&R#y8c!DT$nuSQk0Bb$5EbZD6>2mU^0MtCqP8))RL z1qO2sN?DhP135gohv+h$9*K-#`)sj$?&qoe)I@#m=&;8!t}=Aj>WjFwcHlDK#eMIl z{_HBJQ_K)bPi5eeI;DueV{8LugYcIETx7#_efGUK_sxn)TlYP41&Z&=%&3xOkx9JP z=KHlVD@1&CdclqS}?oWYRe~yPT!sJOJfee;Fr!xt-|) z^|$|Teg6kl#)*=8nMHi|oHKTgF7G&&{bFqFwrPfq-}cBm_s{(FfA%wV?UPo2+s{br zLd7;=jf-A4%o;?jXIQc9bkEs>N$J6{$HpFc+mT=NdMV!kxDR<>w0&XjxObxGW@Dc( z;PZywz#Sj9l9fjV zuD;5}8$OF&?bW$WvfJ4r81cFUtYTBs#_Y7REMFt8eUctb9oNcic1)dBCJ4aSMNM}* zb=Y?B*iLfZw+d4>_8j=b<@->Hm2gY;VP;;(L!G3FCE24ZLiHMF2F_1c{z(j;lkWj; z=Px()OilDhHNmLgE|N+q44(@1eVqPr;rHAGz;dXLsjRIOp14qogCG{ZI!;(+g1ipl za199i+pa>*5p?ew)DeZM)@r&2Am;fY>c)h(AF{mol3DfEguzxA9*ib zPYTe1=Mq?Y=7jGoSm2!7qx3Lq2CE=5mRDe-lEpp3TY2#Fr*>b|9|Q){wJRGA{l%}B@C|@mpbVP}5nC=+eA)NKij6(^NB(eT4>ob(k)0b+ z2LGta+x^b~!Yj=GZ|mXL;3b<6=oAMlcxs+F^36I?&U5s9 z0wGIrq`oa38LwlhABnSL>fA%ao=(}VPciRE*wRLq7k|fTD`(<1t!oW1h~8C)dvq_68=pE87MgdsQ?F`n9?hcU znGZFi!&f_4t=&Mn8YhTWDlkyEAC`&np~Jb&W=k}LI}W=)xMMz?w6o13K#U~-LgeY;e@7LHGF6?h38 zy5#b;D@^l%jxBY`C$8!d<6x~jjg5299Z*RV(8YL5AJS3~`7yNKcV`*GznlUAWcHar zX9HfY$C>R@ICKR{uc;}0+;!kwbn(_cc`H|!+(}c9BAS0!Q~o3^$QA9FM`!8^%Z^t~ zeWS%cdjtrDNu@Z7uQvDVeolr}LDib5`3tblM|1v5u0fnYecp|I`z-qxXT!_0$(Z!? zp&$Nz(`U-h9`Z0|k3ae&+?#yF-~HhTcl7Mof8k>v{m*M7^y%@H|M-8X*`M;+`>4!d zcRc0o(jUcb{EL=f!Z!f+1=w;1&&7&8dwh@dTwHwdN51ZSk?$|`$6hb`kvm?PBE9^i z0Jxw0v;WRVzEr;qFa>6ZAS-0iAMM0(WJSKmh9BJBcaM#pTE6~$U-#;}KJ@;6!18b@ z4n3rJ&{?K++ctas)_(k#QHGu0=${PXiI3gB`P+VyV(pOZZ7wc9X0f}b#w+Ih?*Z?5 zk8f+o^|`k6qXbP#D!4f6ysDL|68)g>q`-vgj+@6bfakmwz|Fy$y^2>Z=&+ZLPqKh$ znKOo7E|3I@iCMujsA35YKqu|Zab2(C!G^EvnD}reQDaqTY}w1VX=8S0?l&?%%Rw;% z%i?fJ#do_F4cCv)A+LnPmqr`Ax5+^tEMe>9KQYK9ZrOPqFmIc&OXL(=?NAznaTV5b z{M;ME6`Z(2Ml9V#&+hz*_5J7)23=6`ckJPC>`L5*=eCnm+?^CT{Jv)oE>|Cm^LhqT zgP>*WL7Yfxc#nT0G$9U~Qqynj1{{;&U*YRGc_9*GXzqPkvqD0nL#< zvM1X6o%8BN6VJWZ(o-3;9Xa7mD?6uJD!=S*BYE3theG?jfvAX}B(6-nKi>$x(*Y9QHCj-vk{s$8r`%WJjdk$h)_g1$a zJ|n+I&oAK{02w_OCl}~`abn-tjmd@29$a+dxxrZe@e-u-%K-Nm_jp)|oqhcjfNY=q z$%%Z-WPyMW%te-^Nfu7Q zDN3Bdbb`GNp>vJee913{;>@)Z1vwTv1I9Xj{?dfNt}G zqMI(luUYXoUNbIvazk4>HR;k+Xm%xps)CrFV*#698>c#yu>j`9fZR9{TAs+kUTfK? zkvl$fdvqjJCHtMN`u2^LXAm*z0R zt{3Fo4+<~}5!R%$tF)CX88jOK`XooZ#c{-@IXP74)Z4()&+8+-_`Wq_5rWmx_f^TP z%uUE$zzXfaOMqrk_`p`X!g*Gmr1RL(G~S$FEOf$s8b$-cE9;?aCtG<00#VVm!6Cd+ zeQrx(B^B3WlyTJW9-wz^BHApEwcZO)fzk3h z!Y*6+_By$amENABy7z{ixF9QP^%uWw6ng>@ITR_Eo&1vIaAXGwZFF{_nd_~WDcW== zhf;f%U4;rzxnPCwAgSc=2Q?yHa*=zI4WopqbD3c_kB!18d4uRlpv;opUn&@uYP!ah zN?eDCQ*tnHTJ|39E{4?ewQ=FZl|CKoh$Qnb|2rB4UXN_??L9bnBX57?RjEwDK4(wd z-v7a`e)=PS^S}A&e^fTYHO%?i>A~Or*YyRLufaV0Q)Wsa#odQ(`_b>E-gPj0eL4Te zzBu`%d;@@spqrp}xFZfZH z-}DM;h_;?a<70nhi~l*_H-5`+fAyX9QviPAnZu7a2%Qlfax}l~An{OwT_>ISEu~xoZGf!l@q;d zOc^n=yA;=>H&#H&DWIdyo0^R4wESg-4-5N~h0SoeJdR~}67awfc=PLej?5T$;sX26 zA<`_sU9pbKClj3{xxk8c8SZ&bhMr%FVkeHnw)RMyPiD%UPh{{@4 z#Ma5_HPv;0r;Vs84Z} z;ZlOqmlMNTf>#%+d#LK+b84GpY0zu^;y?cOf3FVRrjg&|OGN{|3ERJUw*3|&bz}eg zzw>h+E+Zici#&VHd<|uNVSoJzfqVQt_4dcNZOONN@uy_;$k~H`@%u~p2Ee{3xoG>s z>40~P`!7%iF@^W6FGaym z&g3JEzTFysD=g1BsXCnsWV6mPg$)Hc>mY2&^-$(ZJ+3BkjXo__MtHeS;fF(cho>|p zY{W%I5xVwN)qP-zspKqdsrSr2K)7mNfID5{h8_a|`F+0%pJb(|pIomSk6s^82(L&O zT~j#Xzr_mJCtUQweprxueq$}f@S1R+4<{^#DLiU4y@?=9=Z>gt=~XbE*sX zw~}-l3Y#9$a0Dm*YhSwz1t+HV+)HD})*pFAY41U8ysC!WJP`v74llS4z1L7BQ@-D8-Ru9+!_s1tPA5zZu2*185Nf-ZBA|QzG>~sJV zE6R?2^HrrcWPKrhhD$kP+8t^q!< z^*J@Eq_Ge2H3@VqM-k}*`ycz!Z^bqJA&VQKSK?9czF@x6Jv^KHIgViY<$v`H(mzr6 zQ@(b3`%C?`Q^y91y1$;{m;c%K`OnLTzYp{*Y~fC6cN}_riQD^Eir=v>N`5Kd0AQ$G zFfPn}-7nVt!fySJ-F(}Z8;D%ILeXoRFZ^xi?DhIp|L98pC`;nS`KeES;%5TTlI1RuLrFk zg2xV@mqK&dCzpM~m#FlOpRJsV1q6UR2|yiSyV1cpVJpj~I!n)9;uVjf7XEP5KDF(H z8&e_7XWaNk5#!-#KH&@Ja(lkymbg$HX6Bc;)VPW$X5)o&rFtT-4KgmnA*2xaMRc`^Z`!%{`F0 zKt&9<00nvqJoD*bGg=8P@iP;OuC;NttXcLiRbX2l`E115eHuh6rz?mH1W5<1BM+j-a;jA0v9V;M5vN+P=8b`QYp zb^TDKsFE*>I1bj-XVoSf#n(7e9%$x`6-Rk)xeoTE5)^oS1nyGGQUWoFlU+#S?RSuK z8r``e?nN96{@e@fw#n!m1-Q`8sF(&LEVsVZTM&S!eY&IiWJz5oRRQ%(BUaOTuOY zL-=5qy~X2$C#kvzsb|GNAW?zByBf_(eQWX@!NC80}@ub#1it%vAMMO=MFZ z7t}G1kWcP?bF~;KAs|{qg_sPyEbpR!u?m`c3aLC0m@KL|E{ZGwCFragj;=IrC}8u;RuxW7&=BWX$&1VNmRp2P z6@T_Nv#HFDT|W8at354UYAKK^5$?R<0LBECw0mjZZS)&E*y0Dz$M`y`;;|_OT#tAg zquBDZ!eh!xJ!4h#~q=L>_B7PgKNwTVA4izu{vD+kCI6cQUs%AcU?@m;Y zGYeOucG(NXF7QMwe^*+reGQOvOfRbA;8k3lP-Ey?a({^vpUP8ViAeEBe;^TdxXveG znWt!1T0D?62+>KPL7ZKI~rDX~7h zsJis1HtKAT4HtJ`kZrrmj^(^rx`hTiXI0;j^pTjJD zUY#`8Zi=X)h`Gd}w?TLkWCfTVJ30EpuyWQPY`_e4(?_;sd=F%yZNHr9On1@%06+jqL_t(LAnUq(ZF!A|O$52V z+0EeuQ>WC937`tt|21oqm6!Z`o#z+UzT^U|N*zF{CAlBsY}S5DZx7h2yn?6|Is4_c zEX=_vmZ00Z#5~C78Q~zdcJgVKXL?5p`BNh2G4fKygPLXt(6wLLn|9|*{jTsjch@Wu z*POo;yIik|jD5-`TOFmJic@vlrE;>JI)30szwORU@@E&3lk!>ASI_a$kNg#W`Cq<% z($`3pElp*ofXLd{-~OW~9+`E)+Vt?eaRG!Q`rh#|uKzzrw(I#VmtWF10Mh*ZVxaGf zc-OZNj{8Nu^CeH>*m;$E?_6~gZ{650eMx@_T0hc~8&o&=CqMa%zxIFq>(+7C|e#_(f;c@Pt(qY{au zc0Nr=UgS_5W512^?zEk7)jDsoW@)pX;1!ddfR2!z0uHZwF=?62XQJDgSp0P;U(2#> zsB#?(}|R_N)a^>n_#37@PhJ(~B6ajxB7;$nJ3E zxwNE0iX^xa->d(DVR0nMgI!^q!c!vmMW-A4q92>-3fbV)<+)UZg8KRh9BdMrf%D;q z3|t)|M9)G7Y4kalBn7`Qgwn^`_2c}pg0USwti6_qWv*ZR+Lhp9c}^kzAxGDArQ5tE zuUe>od%MY#R`UVTC^ffZsH6M2LNs287_*c5oP%(!d0tU5Q3&|6+8VUn*D&7=I9 ziMY+SXB3%T_fJ8zQ_O=ED@=6EZq^33A-F?)EHmOc7swX)dfbYx!5{R=J>jh^H>qRtE7IRif=H7@J}Zd2-H7w?jN-nEKm*9vdc`>#SNDBx}ps zDmw+ZNrd4jMN4i#!LUaz#9Z%X>(HJzHIyZF5DuWrK=I7#s|qmUZ(c&dtpJOSD+g}Z zQ=ZfSNV2>j9xmMnN^smW^MvB(Pwg?moqJ0CD_35}L8(#^Z>;erpKGwL{t-jbu*{e= zQ(yTecY4qWoEKaw$li2otDY*C%5N)O4jxDSNjdDPB_!$XpeA~-$I|uhgdt*AK5`Pl zYmS-Xa81amMb|ASHu&5!@r*G`>BcKkK}ZR5H`pZJb>ZUdihG6bV5 zRZ6(fBeQ1=*@@eB^skxqlD+}3FI+BIF2wyJ#-9C-N6!HUIXBAPzI{9tq6f!~nlJcc z3umyeKJqvI%72OR-MlC^>wEZJ0DAh_+HZB`)+1@XOgf6OxV4S zFzNzAK`gc+$iG)jO-cYTN5?9e6Dn@-knN)#@Y`N{$F)njk%ftoy?OQ<;C7p*q86Vx zq7%QC`vcMQkOZJPg!h|za;ok0(-qo>r{dHuC(2NE2qp7oHno*3U0zyqp6q+RB5T}G z7S*hz>7Be!+}nWMdd&N=e%{av2WK7NT2u?WH~B?$?xBJU&we6H>Kc#T()C;*0h1L9 zXW!weeX1)1_pvZ>%2x5ZX5N%5c2Rc>j%$H}{zgxi89~A|uaDS$oign%xQe%AEFF2Z zzr8b__A2V$(N&=SgkH zdX*lgObt^^#fWGe-E(EF0&vAMb!cpyI$s0EU*^OJ0Y@38pNrERm^3>Hr8KwIIwx0L zRhVYSj*UX=STNhlcQK;(0!z)8C}{@i0_CTA_`lsBc8B2UV;7bZD6%#nq0z5zqotMDS$37}M+bx+)41aItN2-#&9 zs>9hh66q`(o;;mjKwmHauOrCaGhtCuBlu9@U(0orvxb6fYMs@ujpiq8Fcs1$LUowd ziE@u)JNHoty7p$!^<4q((mdu&rlVmQ#JQjbJULI*N7F*WyLrW;~XT-zx@yEZ+0icS9AS$fBUb0r1?)XnqD`3{hR$@ zUjKT&<8l43|5EDU%5ay#%mfqAxTwV3S9q|;pZ$(~(fUjJ2Ee`mT-03HT&(C^kS*`H zeX(NCjxSt$cTC@W^`j_z1&7UYe=~sZY{gb@K5zfTpZ&3qfA+JV`NR%>H_N}feiy)Z zec${0;pgvf=wYkL`muX`ZD6aG@#_KI-%ptl5~lMEs08fV+J}Cg%D!pJruf3Cf~6CyGzhi8@ri}-;n7~aaD$n1*Du{X z34xT((Fr34&eTDOeAw+Zec>GgR?ZWn;_+KhT*=`z56t0o&Ng7Qn1Y(`;xA12jb7)C zUuM-=lNEhqI@Q>wHvC;b6jHT2kTbEKlR1G?(+caz+O!;d7wvN;?g<%e*V@|A&Er~2 zc4MZ=xf{!Mlv=pxu5;CsxV6iBZDx6|BDI8*J#&=3pz5gY_G7CU(HP$)PwbPvF6Bl^ zlBgNA;p~x>F)T!@X6RxBhaFscf+&7rx!xGy^7S%Lof8MHSLPr6vKKzk zq!>S3r39hW%13QR$sYnS*+I!J+PdrQAcwY}nlX^Yna~d}PTN7Tw&qydb?y{{*?<%c zaLLR^Gh#27&iFev6Gm4bDo#L{2)OW3J>jOn=`|_wjdAtUF>z(jH6}>)sV-OLJh>kd z@s;hq&s>x0>b_5v>0zqYXab8@Sxd?6wMVD7{&=ps%*^-74W;mE?>tZAMDB({$-LFd*wJ65Dx|xfI8S@YJ1#FMskS2X-Xxqu6E7A0d}vmuUX%Z z#i?H4uMgiZ7*mFlT1kqSz)X(1r=(XUg|nFej3hsE6hQSDD7SP&4`&;bw^g>7Tzkji zP~yXDZq7t-Ah&gDFHALc|OVImEU5PBV{L!d0H`$JZ647k#*wvcNT- zX50Sq3euQIfBSFNcjuX3{>Rrqb-&DCmhzL*)zjOr{I~y%C4ZSZ7;NjHxvfXW&O&F! zp54}S4(?cvw0$0VMt?A0Gtx`?1^^c$7w3LqbD?r!wtxE)zj2ZG{s_ktbo1s%RIqWw z`YYc0F99pzqZ|0uFMsNP`T3pX-7fP}0AK&U-&RxiLyX_@%tK6Lm{Ih@sxFNOUE?T0 zgDOlOda?=Nap}{w_vQgxHn?jbdRTTI=96EVBAF9QPfgIZg6()rep8|d^J=$)v;4|~ z0>*`#x42!VYu|YF(rJmyo8mmgLj)!~5R{!XS@0j-$zy|4yaCq^zseLRNMoo}Jf@g9 z&(H+ZXY&pBsz-xwj8#Po8Rj-Pv)6AU# zpmS2KCoch~ZU8H<)yp}x#pwINeE874m}l9^rya5LV#cPU@%Y-U_`%xVEp!YB;s|j3 z@zW1}`sXBZY}VJchNA;qUj=k6gi@z^N&<10wJHypS4vKfz&dq3CGOUHEwDVVF5Z^y(P);SjaV3 z9!#X-AdTX?=G3!?38wKHmoPO);-;9$fY+)3Fq%hr$}MsAJM&thkHoOgz0wU?_x;tS zI;TihVyp#F?VTX68?{xo!`*;!Y~4H_4E9upSaKzShr=@}5)wBObY_PYSyeVHK5>OR zDF_hPxhKjptC>c>3zWy&bdj?g@yxkEQ(v)++`-j#w;F@%?6EXy`MOuyQ8oH}Id1zq z(nb|-OGn6lKL5p+FDqW@-~Kn-i`ahQV;{{g|6_3bp#JEQxwo)I=jIP)@5gWKJ~R5w z5g#^RmgA2v_UJnv-`yYFi``$+Hvn=m?!)GyJm&*@_o3&&&x*dWcUyeJ~;HrR$ZVry><4w#4;D zAy2=gh(pwyO3Cl#%>}>>kLj#JbUZjgrpXgk>c}buiBn~X2&k_G0Sf8@OI0sGv~b!dIib>!t53ouMt|MwItkl@B5Px(fq<*ePM_y(WWBUdqmcv7D zhTa1xi@B>j&!B5et^Tjr4p&lTmxmM7P5++v*6TkufS0~8T$g&H2G!X_i*K$CVa27K z%P%$=HL6_lNwQ~VSe@xR1;eNsnFWP38R?vDxJDp5$U7xYS!QBNhTWfpHpe`hb zuB26h?3cQ+sDNVVLX3}I_1_7?iA&eR73I1&o$GAQzHB5TW3P%^U&H2`jK;#5FYb-w z>s>y)*mJyk^`RgBeXl-n`?voMd{=J#?f;+rH-3m8|DT4wwB$5b_ly4$)qjCXFK_pc z|G(=0jy>`CSXrQrfHadI0*7UZc^&4pZJpsOmh_f{TUct{-^)rcw{YJc|zpycJoT z=Sa7GIXVbGk8IY#cATuXTkP1jUpDa^Y^#Q(z{-=GO>bIL-(eyS_~1%Vjh)){o;-T< zB%B!aY@cUj`(ba11WI%~rY9>@-nr|s%SVU|Mh2q%jf>5AUpe?3ht&NSU~(9sB$^5M zPC=Y2!C*d*vg;$Z5tGU(fCAaXo>`-g8i3 zUESD9?=bJBkT&;H@&seO?*)+*n1q>)i8ZIpFgvr;(amI*XGVUGByj_$nJ3%XPzZ`XMJ+MF@3HYV4>LWv;`AGyHkYnBT7veDWtGY$<}=^)Ur4+_Xgw z>Z=2H?14fBiyc?1Tp?Gk#@0q0yy1gGGglIv_`&y5B4+4}-dYZZ9TP{zdM1!iJNr|- zXMgNRD)^4*LXF4a*W)g6*Q;I%7JT-(w<{63)K8PRMyPYGl}13Yn?G})EbJUz;@~a# zUa0V14O9>&C-?baF7?KI;K#B9OHK~~P&X^{plMwf#c@h~LGp`XBlkYKt5xrTh{61D z&#D;rklpt{`f}MYC1~AS^ilH>)dKPVhcQ5_-&)r8uoS>9>z7`)*%th2U8LwwP3|%*R z^5q&u57R;TiZa{A1PVX z?{V5Aw+%@6AUq2g{>!U?{^GwtbGM-`5HdI#BTs_fu6f~`%>*20Qj87WQ8&-V%Kqbj`d@!h-vHo?|F?edn`#zmg|S%nL$b6|bu3IC zpt?}y)OR#_u<3sV=#gRXDQh^9g)^s2ycv~{hcpj$;{0$=BoAd`bn*?-JWYGq;9^}L z=r*c$cv$2r^L`^yei0aByy3{fCWk%U4}A3@`b!N#HIF*3ykJ|Fhm^$8W`Ce7C;c`j z*wlfXl^E65SaPt^vK5`3O1a>d*%94^j-rb*}cu=_qSpRV_!UL_499I+3y1oFgw8e8DZqdOJz3?7_%v1VU#aTMsem93U3B&^v}aQBHkBH-d4aUIZA(;4H(Yw4J7nxxczsIV(!_1?f&z z<0VT`RISR@{ooF6>I35eh*xsy;KrU*i$AqvB7CJ-D8wMWn~J)+HX-7(HP=qqTW;z~ z50^yr(b+DwEX8fom*-&pmH<+f({_W(SbuL{GA=w*e;HCeLgtsh6f5XjJF@F_usHMI z1?KDF@BLR^*5Cfq=wJHFfBN#{|NXcBZrqdT!Fr5!E#<4-UnpF^<_dPr@FG6q1;A8$ zvKM?5;vV_2eYu?Bcyaqn{06|jzzqC;(e}mKzHQ$ZYTGw=LA0?3c1Tzt84i~Mzd zvBkdGxuNT)06zDtzw*l;-37ecWqu0ach`3w{okB)%e+Q4U3P5s-~I8ShhzKj%jbun zp1ZQu1GhfB*dw=THuL38XYYxC^E@98Bkw*n?_omN`aHu6BHo53#||898&97EU2~v@ zLvf{(H@Q!2h0?^GB6QuLwlv%MiNhq`A|T&D5L%Hn_4F+AMk-91c`AxDwwHCdOTelcHZyh(df{kdWwBjOP8p^Q!PyI=+^X7foKqhu>^GidsU-}; zaJnrrU97EKE4T(-@XIy-&C;?(JiW(q_PL+Rw5R8qjiVm=9sd;GHj2^^1~FJ$>bwiT zG|0T1hny^5FQhVHYkxS7-#)oH>8Ml1$Ogl$t_GFIzUjZoqYKcdyN!9iX{LmwoYS~_ zVIH)UA|3PDA$@pLo|y z(xc+k(1qA(l8;>kjp-iQun|IiQr{^EK$i~suXZ~eD_^&eHm-MnhI>|g%Jpz_bE zaR&Fgf8qMuZ}{((N&GP!sVTOBURPt9IsTZ}EcP3%OYzJk za~i?rG|iM2D(s0LTN$y(bM~?ex?9-cYb~zM-=F^EP zAz4_|wMJ)SpLZqURLlkSyfSrFg2<21bC2AdsnW5jruM~`Sg`7!z6GUXgj&(>-B(RC47b2eX_dZx`8I_>LqSzuMsrR)kKtDh>8UefdUN=T1$eXBTZnQ3-- zSmNm-AMpvaPu;6cX(T;o$A0y>E*iTvH{4}conuhViEwS_zP_`_f51+Uo-%5WcruVO z%;oQy;1|F2j*5?6CfnO<$|w1IqGeBb<{+q&>}opkvcuqO*_qmhEk1FrxpvkYS;ZGH zb);_XWn3xMU-ax4RXYT_Up7N!?0bkLx%Wg=Rk+q`pQlFK6Qdn!4w9b&1WcavAtR^Y z9^5^)0`mGl&x zqO%84uGGw~9^|9eT6L12+!dRdQuk%<_?t5+uCs!z5eta*LbP(o^~~wXtg9Gka}Z{3 zY}?v8MJgBlNmRh}B`)H^BEw-KMq1fxWkw^%R`Ku;3koYkV#hQ(TrqXsE>&SqPC%NS zdZ=S^HMj4%=3F(0D(hKS z*g>_aNA{G1|MB5_)%Cb^?rCFF=MVhow~6Z|o&Lg4{`)^g({)`=<0bd#u(9I1e4W(x z?GG+HK2~_F9X{+_9ai=`)?T0E3wHayCgqp-4S-xEE(jMYM=sFz2lL3camaW59uv&# zS6}#oNNb%qP9pCWk+yCWns44lu$++~<4^eeU`*k(+ zWB!a&4@!>&tYV1o4`TALliMWg42M-m!+m(1%WovID|fL2VRfwJi)}d&J5IRelK$Xy z-eev)W70cIE1N%!;Obm@8;red!w&==aJ_ks1V3159DI`t-@Ru9p1{wDuY>8vfr%0I#CEMnTRcD=o6-T+$+LQVm zBaBLkUiCHBo3HtkaOH6>8dY@XQXLo&YFE87l4yYygsuB1mtbv2=9qr%1+Eo)deF}G zOR?E6cx7lyZLO|Sx|u-Yxy7|xvvv1P1}<1)6QBLmNB!v`*OrSFBq_#@OfnsExX+{l zTx-YLQfC)gPG=J)vti%z2^uNwC@%Gdvr7UKnyHfj>dPTUlwK{;W99%j@^x2s1@32J zsJs_u(Z{FA6o-)>ZqS688)qzvCg_-Ujm@ijPMnUTr+lcW@}(cWzM$nOI9rzLQhm0< zYwps6?Eqz>&aUBbfThO;@>mqVYxT9?Wlpu7?({;g5NvrORG$&c26V0a z{hGQLt+GE<7)v66cxY}6^Wh{(Px2nX|sJoqLf}!*rrXJZq*6QTc z10D>AW&00&RoS9yG#WN%g3A}ex;!~8j+oX?UY7D(yl^~<-t{JLhgN$Pp&%IRKvoiF zXOv@FDHHtyQ{N?UJ%r)&;QZIs0A>*@lmh)#ajp=_P&xAXDd}>>DBWy~mF&7c;i#cVFUz+x|zs_;q8~LnU$Z&j9L)FgI!Y zU;W&F{g?Q6F)wQU@ju0PAWwVzw}9w8hBSExi;s}Rx9kmqwS>Rf%B^t$ zE5B-ni;r=bL%z;AzQ+2E(;P-a*Wii4Qvv->q#UKU`vi}{wHnv8iNRlhraX3#!j*!Y z5tVpbmOt3P9M@q68 zTEtL;$dUX2#@6}CM=_}C9ndVJGe?`(H5go)F+XNScA&gvP%ic>q3z<@uNO$3-bcXp zY@DaDI9c%T*e>r>X;^TnhcL|mL5p68PL|7z0>gv6boE*Zhr67+ZiOs7*I=*2ZJT3? zrv!zrV#d}1iJ!8n5!JMd9Qi~WZ>;YM&pUi17f%7PUo{KWhEa3@YR7FuFN*OK~dT&IIKa*8l_2Ys;sGn zrrcBWvDKlP*q`ALC(aUf|8S{304o^VY=5X2ID;#a+{JU^hc?JnENmJLQ9`ISFiAMF7 z6^JJC@gvBVCQ4tTykN4DN;U5y3J{;L?VJ2~s|Lz&znwS4?3Gx)*dXLcLF2cPvlhI3 zh(FfaNyh4UK3C2iVoxw=jn&?IeU zwquXZ9vl0XHzsoY=((E6*rT%t7h80&UsLO)egojqMdHHj3;E?PS~xZ?ws7TQgW1Qc z`p%Rvzn<@C83~4)S~qs>{!N#)_wNE2d;Ju^CqDbx&+ywZU(Dja1^jOQTfiEHhf!y} zL~7?Dhg`=z0QKMl$zt~da#U>na6E5Fjb{@#goM^f{CRYW&TsI-VlP2|yep4lBhM-Tag z5c19~(QojXL3;NmOR)7U6XU9-`TaD-Duq5NF<0`NqchU1F5T$TT%T>{nx>c7sf86g zyV)H>%|?_x4J|w~*s*YFxshl#U9>kV%3BwYO{a*Yy_cQbNU{ z+g5SP3l?L6wa`i_tooBEMY^tQ`6=P?!t+fgU>fxE4w>^Rm`8mQNu)%ySLlX(9;v+L)|*>Uk$6oYc_f@q@z-#a{Gv zKFU+6y4+Xq;hXR>$ztcF;X}6P&kUW%GY_AM&3~^$iEbzNzh{#3rX}?b%w3-;^YWfd{Hf{0m<@b*xoB{QKUvRS$Rcnb zMoKqe&dyapk`+w+O+2~EC0r+iGqq8psur2v1T<63&!jhk4=br4`A`yB?SZN>#@p4L+2kYk7u%>T#UyT)w$W@lZ` ze`eb0Fw6)Y+QPIzTMCp&8&QN7K0qsyC^g0%4HQ(GSkMm`XhcyqN@ODiilhjdkobYt zgfD_g`9K>A0inpYv?x|V5d7rjQ`PCik{gr^?Dd#J@Q_kJF~Iz$=noee5lpAk@Mq%uf@Fhsi27q zR&pztcs$#m)(cJOgs<6Rl+7SsGl9751?r08e3}@QMnz~aQ}zrQH|fABsCh+K>vl6R z7#Ufh?ghRrDwa+*C`B5R^FUR0a#b?8Rv`C{xfHEqzT<|hYL};1m6$rWg{F9 zhUCIr``NbA<9;7eUGjuC2J)Fjlw^kN?8hu}W9HmL=dzrCEiX3tTn~u{ljIJos1;25 zxRmnzXvKQ|EAN2L>|C98;rmySpD z(^uyUjFCLOvY!og6bMd$Mr7XkY#Ba9Jpl<~4rq?pnKv&Jp>)1GenjVjtV^E`fAQcWa`vMhrnIv4!z)LWR3@X3 z^HxBBKG;6HzA&@Dt^1Tuf6voP{Mr9+{uAG*-~3;_c2l~O)x!OTpRf448D%fo@2jpp z7yoKtzu@mCKVPTO@nbzZ7h}&GGkGjb{4y`o>mk0x4*;@Jdk3;XZ)XFa4bQwekoU)5Z)&YwKm@F`|WUdF(9^#g=K+N$)zJYV0-u{vcCus0{ zmQ9C?n{O%|gItL#zpfWF`sQLHKB~SuA+1qzHG-Q8RKBAHLdSt6uuKzhEkNV?Fo<<35wUY z#KMm@OU)c#jy^zJc)Zu*0GE|F$xF{fijkM%l~)m61IbMC?5&Q4yj=^VIG z^pYHmPv6ahUisCsH6?TmPcalzSy3rEA;)HGEDgye@q6j=e@!%**fI<(v^_{x^0bNZapyc!TiadKctDbMo*T|5~rF{>K(PGdf0QudeHD%hsWboH@Stb@a^P z%Dio_h5Qmf07&EarffO(%-CYjW@iqDIk;fA?U8-s;}c!*b7D9Def`#-{I9=(pObk} zmsq9d92N=qbxiX}1c&+!sb@ zpqHN_Thfa%L;?bcKRQF2k4eO3r~!Uq1z)j%5$zgOg}q7U=Jcr8lr_do)(mBD&&wA5QPK z@0QIkhNg*C5yXEfU*?l#(4HH9Rc7_Dju@-*?0c)ib%|BV%le)(9DR;>m-BL3MBUoq>sYwR4cC6K)>!cnN8d=kx~N!gNXhjN zT`9e{yr7F$>xD`z0f$&f5nkh)t901(r~}}Qy)Cb%?ss979FEI>u&To=jx5sx*W`8N zlYo7KB@82#6OP`@D8sBf5BDt?lx=7~^*;eH-G)+My3MfEBRW#zjk*M%d)6dSaw)5d zKlrHw&rX6pN3WU=^dh-lt6UqAyJsjmwOzN*-Oj0EO?FKnmy)fI)iILWB?%S_rkoRa zfUadugwpSt2eyfhvLpiZqjFY_wPfxRg=J`H`rf(G2P4JndU&Zt?u~o2H@hx2n75?yiEYowc?7%H(J{ZXv1k6IPkKjP z9p3ToveiWbJ&$*^ET6aD^)BfO_s(}YpV;5|P2c<-|M@3<(tAFwd|u4>&cF4cr{DPV zzvbzB{~n*{o%Z;4;?_y-FV(*>7%q3%X*hm0a}`8aJH^G=*IC>k)Tsn>f16u{}y-&b0!8CnjCWz<$P~{$Y*B#vU)EIX44sbB7#o*2$eU zqqo%y<#dQ*)7*%GB&C9fGZ4*fPn|jk@47xG9_?86M1Dn>9xB!)&{~js@@jRKF{!%F zS#i^tMPm{nyFajaeSyuwQFbiwIS108>BZDN1Qs6Va>G1YwqN1|qMp@adWd1ikc{rM zjVh;(Ua$aSK%Kwd`w`w!du*qhE~k*HGc3*~@zG=`uPkbwydUVI zB2_k(T$E4Aj)~zg#FqTEcL|sos`@7z?lV*vMvZWdxdqzKP<(`hmr%~_k4gI`VI8^- zG(h7%^=u`WTJyox^-6?yd~GvN_8_xyMqY_^8bO}C1r3Y+VhziyW_rcQe$Tl}-mBj6 zpSbd>6v{urdWI2LOf39W*$o`V65zlgeqv}qln4KO|MBxV21@&{F9jbT>1WyeN|2oH z1N$DyyL7~P^Yo_EKH!~tY4^F9aLg+9cuCqdxF07@Xf@L+aU25WF}$fMUX7EV9p{{E z-TirW$)(*dwV>;Qh;~b-HO?cs*&j#iS>qY|^*myi%U1t9<5y_qE2;d-ALl}li^N*x zN71Ced?72Go@Ez%y?<3o&gZW;0K;JR zNulMeW4HD-Kc074FSF2WAai6Y$tGr#nImHlHZ!`6VE4RjoK&%)kG$uNk-kp;XS(>l z#P0(5z>j$M(?9(cpP^3j?M$?R%ck3Ln&M8^#V;4d>Nq$zzFSfkQr)ak>Z0mP@N77=cpus3GPNDDFHFuuL3s7)HFue+Y>C~HW z6gd4#&y;8E;aYo^JxN~`qj=x3FsXIsiof|u2kN_vYsOLtO_R8Fb@V7UnUY}i%oy@e zl%n##uo>m&n(}J+&JoOvPBgWncl<#-7RoO^>DLB2YzHSsk)DcWLF{M>e6i4jIccbN4b>&)xY3`@ALQMv>q;r|+h$ zFFtZ{BC?k;Ws98sv14Yff~kc>9S*UVwMLbC?z3qPX`YF3sJf>OF5;LntL7w4{VjVh z@B?XdQfRhG&1-s0Pr(w%K0e07v+6Ty48-%kj7nV5X~r>+&dCZQuhA(aU&X6~a*I;% zcrmxEi8@LZtYV6V3g;gb$uHP_zRFfS@c@4v-EGAIsNfAz0t-%jm}2H!#icaN(KBBb zHYCqfVeJDT?&2(@0|>g2TQ*->Rr!rE7Tni2WZS$tNA%#fJ#m5xCi<*n z|G1c6`Ue2nBsP(;H~i5$xIJ$fpRBh(x~&h+djGb{CqBWd3rMi%ms;e6{r>Og8UJQI zDZ~}*zx$P6_6;xXcLC6VFaCKS)QdY=M4EJen-n> zs$Agl#t*AyDLj+gMkGIN7v_E%??;}Z8xc&XEjQ`g0q#kG!TFPsaK9+9hFl&qwkYqBq< z648whJ&~g->rf&pnIJsu?_=XZ_9lrd8-~7y`R!r_UahP;=-7x){vof~+d^IV6|!wiJftp(S+)S=0KQNy$9Z(YHjg;M z>wvX7BeiI69ITB_3O$2*B-c_tNddc|oeFsSc%DwU51qI7RMeLPo=w+rvbw-#5%$S% zEFrT0rSzl+6T(l1jDP?+Dn&|*K@4vk^2As#@1}|mfk(Fd=Wl$iyO{Y$5 z?-$R_*5LzHKN3HEYoeKeF&whkE{(H|qf-gm`n^(E6eWyoFN5#Lc20gd@0iJff7ff* zji4}zpIJFR%RkoRZ;jAL*%DuAvxIK#W zcEM3FiZ5%~*BY~I#m%gSN?>sv&hzNJ*QZ8aM4$`qDih~QyzPheDuo^H-UI=zOkKC? z!cslzTm%+LGnLtWFQHV6)dHv$=$UbHE^2L{SoEEuC4;VR79K(-RWVz_Azbz#Ci5PDSMECu^nJLEGZG<=4(K z41t~G*kLm{@u?^fX^2SnkTRd-PM{=+CRl>#!PJ1InhZK1`P^xe+s=Q^l^v=hDRg3$ZpILE z4_hfNK-|(7CsU5fuYk?r{q;3;=QUIaB6A}yIic@BwmO#}kNH?1=SvV{noeu=uoTRx zLsF6OPuNgHu}5JjG3IBr;SA3J8rungKR#EGd~hd+X=GstSi=a9b*gBH5?zW)=Re#LM9Nr*3Q^m`aLgW~h3_eS+x z$$G!{NfT@{=5CK)`q7Qg@}thZvmX;K`FvFH>jHSG9{}9@pG|(d4ZPd#C)Vb?+qQ3P znSH|g=T`Jd);igP$^7P%KM8x%&&AXeB-oewT>u~WAU_K5BYgqYE_A8uBFJS^7iF#q z{W%Fb;5_I1x(#Y@b8&AR*Akcs6kl_qimk6Wat>NfeZaT2OMwe(h?Z6ct3n{I+;|vk zILH0Kr-=|XsL6iO9G`HXy5R>ejHtq2nxVFzETt5l7#(k5R&zUTU&);;S%I^M1RvVLwU)}Dwr#fM1FQ--H+0JD`Wxn6cro1VM~v;C zikZb(b|UXb(n?10Q3QHMoo&Ph{^W;&phEl5s&SG7PQg&0IhZr7%ZfU{gnFG{@@?FX zn*caQ-*G_uTNg28zn#+V3%KcNbDUb8bL^S%xr|DBe8^>wUXHdK;5x1K5f0z`X%RXw z$Wt3~4abtj3ZcfUb@un-oUyD~v9FT#T{DU{x;DxF?BHZey*YO-$3y*E{n)7KSKx(< z9oxmX*0$A=rm#wLZ1A$xXP??gj1jv4@g%sb@#^%(Zk+$|>8dN&6XS41iY=`obizNJ z>Cw&yx%(y#WC=%4EI}}p;C>3EH0YRgM>TrYlSEpp0EJas%eK?O1{W^Jt%;@Jumd4C zx@(btb2#LkPg=!wxy7+5ZPqdBFwFjxRl14;hkLRdCwu2hYCP^ksy4upcyGL;UQT9l zcVb;Zv-e(-ng8KOZqC`ck)wueT(3GACTy%*m02p`+7ON*Dt&bEO%70oT6j`+&dCdP z+%h+Grf{go;}G--j@>n)J9?0mU`_Y87G|hS%BVh;ZufH%kiT)Wfo_b5k56*ik_m2g z002M$Nkl@uBP>Cw^1nmUuHSH<}b`sR5~R7j4&e zajXUVhY6o$t0nh8si2uh^$k2w9ePr{qJXE9t~UzL1IliOx1PTA zg?{V*qp$wwJx;yn@q47{=xIuw3;F8*Q~m6Jnnf>>8PR37_8fzYCb+vk_C14PN3<*3 z@Cw}x^75B2`2&D#S~fcCSM3V+U^74R!4`XFICBysM}MF1w!Fa~Jtur!k??!=_=Z3D z`~EY4FKvAB&-)QiKd$~NfG@Hx>)D0zQm$L6i(X$)-N@6DTo85f?sm^b9Odj>A=^jyobl3Ju>z7$92+$HJKU_QLw!KP7aZp&d+7&(r*}=ugkAluIeh^; z!*reD(#v^J5@N*#`OGDif;A5x5Qx8*SqOzy7$NSCWlOED%+;x$_DapLgi%6h!EHHy zrPRN&Z8O{#ms+d8?T3{(eqP(wriqU|oWhmDxvl4XDZm654(7y@4cvf+s8C__EWcvo z-*Z_ixd_oUwboNVUpPM!E;9h4uuinhXzwi!Te?Cvi-|)L~-yx`l~+ zqP~TNp1A)5_)`sE!*Rl`^I-1IDXd-$ zTmPy2ZNB=S?|jbtr>tqv59C1@I2e!oq8DHtvJt4fP0QF~_+kOK{i1*E=2tc3r4_n2 z?4#8b`=c4%e((WF*2q~$em=kZ-TH0+^%nu=xBS<|3f%eY0PlJie>FgB{vtrx{TBjC z{`x=sSAO#+fAXh(Ug2NV_|Csse-+@%>aPO){SQ}ZzDb>SxrOzOKvffuchvF7F4UUS z?)H%}Z8uW9dV2af>YE)mQ`W_!i+n$SOXlCXVZZ^mv8>KIGV`%-IxPFzK&%ccR#{QV z31n^##FTY2k9|d3>7acuIwAWMZ_0s9p{-EOU~61@LO;kt*i+5WfUigRgtVNic_ zE`_`IP_bq|PrqRv2JCwbSM5}pvPXtQ*DLwD{+!=f92W_0+gaU*&IL8IEe%a?sh$5` zV+`M(@2m$E3us0dB1>5Zv0dWC;@EBp-fJwfIj7Yy;uwowBk9I{uSZ^l!L3m8pwAYw+aY*2@lzHQ|!Zb*28 zUpBqG6)CSHu;x84Wc5LWipqP~*lI5Qr+n~}o_^i$`6(~*7ytEd{{L51WEEO_!EgT8 zZ})$y-~3-|npP?`gf+!8e?-6eAN_|uWPD@Fj!$&R_@rs9^SJBEG&V39TSm9n+L;ET zXT9xhdi^HeS-tde-z+w6KVDT|HaPPm+_oWevN4c9*2l9?yy`Cka5oI!{)HHC{lWk0 z>wd2yys!g53h)bm?dPm#&AmOeX*^DAEzLSl(`udns(_osb=eo~dTCb} zWG=ya9*+%d>m(u*k&)Re!PYgv++d9JvHUBT?g{Aqzt?56!BllT^*Re5oh4E0Vkmau zgIM&S%z-;kX%G`^Blv z^khaQlsuy&A29rTR$D2i4{fhWLs)##chNJ0n79Njr{i>eL|b+lKv!|Prh9FkY%QUA?@HxFA5x>VGue?c58lB)veL0>Mz0i@ct zoqr(NFZb6h=q9t?`1DY?I5T5k7&DxFv_HXjk}Rzviih&V?YPF|Cdkx)c2kyc#VKax z?`zsQKQ{4HcVaw{nohmI%oVp@A`P(~m z7)rJcAKNpt8s8|lFZR*5A@-^boBhwZ?FiRnDA2mzo1J=Eg`~P#V?A0$DpVsyYT2Bv zr{Ee%GkDf)M^FM?aM3)@5$p6qew(ahEl(X{D_urrQR`~8 z`?&K0wr#BM_E|$W+#qYz+Gk3m|72?XIfwW8cZqXbxL(3n?PcQuSJvd^$eniQX_|^& zMpRZCncnFu|LKA9wb${*xqDSgYD+Bf>E*dG%0gT_1~%5@QIFuo;w&7tF$m)D=a+HY z!q3cL-gYTp#{*w^e(B?5*{C~)b#(Xn-jCSP$1l2kfO7Po@ClOZEBi-x=_4O` zoX1;ct0O~LC&)DrlM;jE~UfO(LHTud6TvtPK76FrDunwqmEIh%ewHknD#G+5^db@7l3 zh7z`Wt6cvb%lhz%y4I<2uiZYtJ+@!PJ9Y=3b>WUz1Ea_+N`S3kP7NiikC}^&f1~T3 zMmOofk%!1^QO^(wMT{Qk#}?&n-u9dOLXyd$b_(R{f$enUsx{(%gby#CLzHCpc!D-3 zv->g(0R^D->D{i!-1bz6Cb5eNLUZ`b8gS+@E&*FkszoC84YrmR!FL_(c?OdEBBA9b zCtUp-dz>CJ45g7qr$?1_j5T7nfhA9wAeE0W~h@b;U` zRYO=4f?4;TPD1)23feQVa-Tx8V-quLq2VL+8sgTWmB@wb&?$3yAfEhBUHBcC;?{cl zcT5-Qp8sMNPG=Up%A35rr!M25t;NOQ$zSV}SUj3eF{T`oN{2r!-!);l=X)9@Hy`9| zaZ#4S&u`HdKUwj}x}~ix`T3yM)O+QJ560_I9i>W7He%wJ-CVf_DJz{@+DrT!V;-~k zSXjdg(I8E5c_c4!p%h8kOZK_mK=lZ@2!&&YJnP0u*Ej_?X3m$^`Tg+0LHQ-W;733G zb6@`1P4u#+|KmIV&R_iUFZ&t%=(66tS3ln@f#Tn+um0zYn|Wh@S$|*tQox5kH1_iA z=3Zu+@+$qd-DTDpw3-1AGIK_3uUbd{+Rb?b`;rUW8<{%xhHg3bJ;y(DY*}YUkGvn8 zxQv7GFS6iI0@lSOSBmiomQTXQcRn%eC(+mc{@?p`0AJ$xIbZRko<8}5pIE8yHd&?} zTwL>=09+jTx3O8DZ=WhnLHwek1X@}OHdPyYx$x4l!>3r%;DeAG01K<;S`Z$B3CuD0 zgGSQmvub`ZQ7Lnxi9+5T$J4Ne9}j$7^U*qhkKCZdJGmFG`4X>*<PHb-fh{uI@SC6lZ~>`c#7VC3?T`4uNb2x5_vbyka`;_N@*G z>Q4CBzAWU6(2v0ynLSA!!1It!cn;ZZ!buqy@9W?kDPeVZ@xlf_ zL()fd-V@_bX1z|FTXEw<8bFmKDHX%QI=Z=ZBuy|&^(t|rpj@=w!LUpdnhO1XunM+j zwIOSk@%MbNO@lfRMX+!Ds5j?waOuHBN%EP+)3n)7a@_Xrsd_89?3D+cfd$fZ&aQ)A zY^?*3ye+N58nf#cwjkRF4lo<%tgt*7Nw!StRYQnONap>(%$mA2BhR4MELK4`l=1>p z5abI>6QYw|u){Uf(KntF($7H))0wnm!#mqr;iShD;LOvLCJkrQLGQSE{*yQO!4jhW zF?0->G}I3kCC_!IBaj$LFZw|=$5#q3zMVA zF1XVYii6TBeyN$^Zih>c(+q5dyVxecN!2O)NE{%+ zzD*ZaW*+1qT^%$}+*(zV(y5-dhRETRfA+LhCMx(qaqY*>54>{hKbOqw)qb`aUvXt8 zNd|jQPI>Lv9Xvh-RGRK=UjNP)#LM<6pZ=bwpIcx3f2rfE{-dw_>dKw>GJ130?`8B? z(drzOzH4(63dg!^n(=#JzhD=pp_<~ib$2(_R?4j%w~w9U!p zXG8Z!zsfh*_-@?RMP|;)&J_XqtozB)y6EetVm^Gw5$CPn`5*t9zxcQR*8lR&EMD4p z&->o>^vizt=hdGEj(>nz!t2WDt zr!#?gkNdi8r^*d%`4PVR`T#wu{i0&a1Ni?^s6MD?#n~+}do8>#oHJ*Cz|bq{_#5N& zSv9pcvexeXkijmSH9EFS85~>v8l`C-z_ag|f+i=0mCjn|6f$w8=CaX_idQnrcAgYM z+`(kW7~jG>rgKMDPv9vG?vM6PxLp9j@9S6TI>)32tUGu5fKI_zssIOJnOqrf@O@Fw zMk3T6M!fjj;n=#~;v^rrj31HCxsZG>A?ep9!$?m^(P;{3_`peC zJ&DxWLfQ`pe6%IT_Yqil?9%=lHHKEYI~>!04C+aRcW=rfH}WB6b7XF#Y!}&@iyqG| zS92=d(CKMow@n2O!?CE>Rl!?&{@c;HH;Ga|dp@2?fW+$IZ7iJdt!oOxN*@~}0bVx8 z0M!wD91DlIYlLQc$)ZPGwVasjlLs7(^ptwd+ws*W5aVog2ry}^y)P(cL7Zq~g1jte z;aq&4C$Bw|o6fIiv5TV9Qwz&}|IQ5T>4p15ofCQ`y!Y1tJ9nmr9tX&}=HgM@_B*mk-&?Ph`MLl1zV8z{%FCMm`d|AWzww*? z(0}>2E9-n^pN9JVP5-qsY#?E?=z25R26I;%P z%p3P{AwPPR^*R)u4Nfg=a5gzM=KTPhHS!+Y2mQwR-Kljx!OwRA@Wcpq&)|%0JD;2_ z?3=&itAEvh`r+^Y{_oyNycC(=2k>+BrvY8EFOWI9Y*zg9EqJ%aRpw&p?*yQ^SFZUs ztb%J}v%Ad+3m*2gq~+FeQ|PtPz1EE-F&k3T;cK~Y&+aRg2e>@+0-@HVmm7j>D@BjJ z){jWUu;vS<^x=0-=I16nZCJjq5-*%xbFka?NcYG-xJ#$V)&IsmeYa-Ul6o{RocF)* z^j!RzOQ%gJ4|+d7UJIJQYEHjMXv4s@L2wbn{T!Q;aTus*RpNjVEw0GDh)z}(O~w@8 zaG8L5Y6y!^V-zgP(4_2hr(f|}S;Xo~tw)Djjr4BE-7*+_q=Rp9VpH6^PKHTIF*=iW z@d`KMvy!YlkDnzKsz8xWexs3H+a>rX_(YA zq9y|@tc7j=^p$yR02%bB^cpqIpXe-wv@zjZG7tCBa$M1o>*RqS4=FGz!W>*Z0EW*Q zodY-();XOXLKYo>ni0Ie7I&aiT-x0(_igq<3H4U-ILhMo&{vt+lr% z0Xl;}Sx0;x?&&gDj_8#75SO#~nk<~nXlnOGe&Vo^l2hv>fUxpWE@Qls+joHnXW_no zAoBn*p44iWqn+Ha7^z&^Y4ScK+8Xg3m+oaTC>g+I?3e27l*gWtvqmaeq|y0xtuc*- zGB5x20RvvOk9xfI^mAY6pZx3QJpHcU`Rl%RH7Y;*zl!enIC-ygnuA+6N%LNBd1KP( z*qIr@cy*52owIxMJ6?n7uhDFXyW%mtEwO}@G%faMC2}fq^ z$oSlKZ`E(LM3+yxkoTu#wd^xSKY@zpYSLhiUVbiCa3q`n@?u6&Zz)jD}6PUhrw;GI`;I0i|{pG8N! zA-sflbF1E}1f5@!h(NOYP>Cn0#fVC3)%Tz{sp7b2_~#GWj+5!Y9((p&Y>EAB0*1TYVRAa~4LupX6_pcseo9 zjf;+?C8KXj?PL2~*a|Zx1-t-qU%5GkN$nT!4%BQ({ov_9^0w^rv$O(-hv0CTFRW-k zrqMLhi*`!s`#FILoOBJCb0K>5=ZX;}Im-q~mw+KU>=_c*y7b8h$cD0337bBDw`Y5oGCZC^G4|Zo82|wb z(HJK5aVc?+=|N%WB@g$v6*!~ENryEfj)R^hg)V$=(DboXjGnxxqg~ziDBCql&Dt9= zI~SPX@-e(lZgNku$@$8d6pwZVCf>!Z620dhN7wiYYmvlqIj@|&DGH-lr3+RX)}7O& zh9FQKXkHCX=)AO|WyKU~bGPDE6I>I}ehOJ??KQJ}=}AAgwdP=*q|Ofp=MfhB+uS-9 zkk%u63dbIajc$4#%3HqnGp8WMKdtLfVEC|scwl62KGTs$#@-!%c0X+6_;LZH)SorRs~tNlWIdAJ#X}R zjq2Jv=?+35d+6_l@kT%O$p1XA{+ar@|1bFkpIP{qIlk>%{+q9@_bC5cWuH$zD8vn@ z_cXO9mRq;hZ?2#H2gA5vAFKts>CweM_N|L;&yhDia%}8W~H0&56&*6Cn1?v1fgsvHN!c@K*sUetj1}em8*DU-j$%%|Gx`zYBoI z{EJ`t*-t<6$G;b`M*Ub2E$&Z3X}30o0}r*oFP{Ad+?&=qU^g5V-MQqhReh?mnP_9K z-D;_#L>FdT1eM)4GHxuyRM7DYo6&@KjI1+nJ+gT2P`I&`6ZYg0*z?$~O*K2MgtPC; zB^;RtID%u(a`}!f!i~h8wvil9o(1GTT&l;8r#5O;wQ|O41at2OJ@E-)jPinyZclh>5%O12z`MCda zOpLugdvXa^grHKLYEuyB4i+6MpA+$Jj}DQ%2G>PL-vR8r_I}Qt&}vT4ATNYsIG!`@ zpp9jcW_U@~`J#_!WY%Gb4GsqDL_erltg+pm?uj*^G$5~s`?ex<(DX#-Mw^krrv$H? zuQ0OJafcfr>z6AaqO_sICly#OVjJ;CAdn%^X@ zg`7R^Io?8aU$YnSO3j!_knqgl3d4V(Henty>SY41!7P7qEkyUnBnOQ;@e~v$-10EM zCeanTbo(I&a2-ya;q_^~cO{aJc^@mB^>b2?dEu|`b)7?cyKQ0ptj~Sl(=YlJKLPI- zIry`GU-z|N^Np3f-uu-4>%B{@-|8Q0cKcZ09$;@eakQ?cXYZ%b#fP0jW@Ps2ta~@3 z&hE|}!yOm>oj3Z|a(*2?0LaEx**Ctl^z zPXJDOM)3PdFyATg?ce@w-}UGI%pcFU+1TTS9lzqY{)8ufB!C?`UsvZ+Sf%Si>-n_8 zl5aD7Xuv3RF5X?g8FGXQQTX*CH`MHA-O z#aleVre1=oW&+65^W>34u$MD>*D?hdK|mn==yIjgq4}dICdX|>;)%bxvhT6Q_oL38 zt8kL-ftUyJRYBJTVnJ1-YjwZP;QFR#_~4{*m|-%zjr6>uDF*O#&v~5V7=}|qImE;3 zoKIR|BF4gOd4d9?Uc=r0wH8-duaVXo{*E!2_E(HjPCJS$y~!MFQ)rnzcI@Xz??Lr5 zPTl59{c^+LK5Cu>!}&~M1H4JFD98t7>eybBLntqEJfhrdvOR0XXkv9YrqWG}71%xK z))$s^(+=jyWNkM<=8|nd2VCYEmp$lS5Hl$RYno`sU0qKLy#vNPV?fQL3DuKlbPEum zig2CVngkO&8L2-pM#)?TYIXmeDeB}&K9V~>{XT}$@$b+%;ft#6O7w=}G3q>R@+e3>!n)HfXCW|s=51UNW_!kyAk8AYnN<^bV zn1#=eCiT>|TAbfCbzB-+aT{H5^{`JgKkFM+Q`YITzVDO<<~3{a_`NK}$U#-+^Kk5Kl3b3B z*VH`GDW1YOrgsTYWmNtrRXIxUh)o-Hlpt`VsHKCD zoK0;7?(;4)W`?_GxfaSrt5rkxJ9{-n08Bx0@?m1cwt3~#2Q}i9L~!rfB6}uMAj71h zC~@{$`dEz=x-jyLyH{5olr@HH_tK{8G4{+?t5z?r-0D?wZVuY3^P&5k=cvx3jcr`wl3=hWcFxIYAV{F-Jfj`@ zooMTIZg!92dDy}k&4~+@-MTjgQ7uI>=Y8e$=$sf`0?H?ROW9nipi{{9sHkgkg_M#k3`yB^ZmOY$>tf%*x8#pb6k?fc3#zi~9BEm7jm$iI%fdNW z!gS5C!-^tuu$hxj&QTcClVI+%>nV>_uX&cDoLGsgYnDcGpPvQjrO@Ve{lsKV4DUpr zhd3}wuO7=k=ul`5p^}%TUeKdz(1o)v3#m*c0$TaLO zpC-EL`B0&t&l-&`cw`1^MrLodb^qX49pH`V>oVBfJa$cbS}w(2N6rik`MzH6&ukli zeYc9&M?UffZ|px(SKn&3uzewpUoHDm`3`;0DZeU{U-J|D(+~Y2KkPd`|0jLn&)P}6 zB>DXx_{1k3-||QQ?z85dIw<#ybHhm+Ja?xGX#CYH{-yO5H;OfLW5SCIzAyQYIhKn= zt|1>A^6n|GPTV)GE6=vyeed|3DtFvogYk!?A62X=w`fsRLxtVZmq1t}iI?{L`ojY1Y(mLEQhlx^n zp=kiwdc*NWXPflc&^4B&hyVY zr#)_^C{`yNz?!irt>;A?qCBxglYH!$+R!==+m&_^Lpvk&FJ0;rX6dyCAG&2rIwD_o z>pMsSmohY(OOK1r4}t`@#H9kEfsXoYJIU<)r+yLvTb|Cw$7UL>*ExbdywO5HOx4zA zm^b5Kw;(6yY&E(25!hsS3yrJBn!6;WIC8_3Tgxh;{Orn+g;xW)uZ>cE!Q5x})>^Yp z_8rT%(-)+~J=&wgX8q=dMf{f>3&(m>f(xd@WGRkXxG(EkqsC!nloJm4Lhs43G53hx z%Ot>j24Cn%E#^Hoq{&n4#|C$XFr|o2kt#o}_Xh7Gan2fF$<4q-UHa-zM@iZZQG?oAG*Iqglnj`6eF-q(RcJ^;?a7v)&UOpGz~%DX!B@ z8nqkyXpN2$oL7&J>{QO0$B2(3?d$Xbz@u$rqd6(|hQCV2A6p(-XO4c)cN=T2pkNr5 z{UHD!Z}A}jBlgYp#?zbiym*KH2o4>v4X8)c1j1yEUqX@*Ph%^7d4)*<@)9Hzv@?wCo0^Gi9Gnw~^2@`kQJM~tm! zopU%Rg@YU^&nFc++7{B+%td6X3XoVsj)i^o@T%qZXQH3?p&D;W=^DFxFf|qa`usn{ z$&z`?w-B}5+1fg%ap#;e!yXUN$a`&u(zQ~)lCRHy?9K~gN09DDxPykQ;fl4~llQn_ zTO&DXk60AYx_B&biS#_7f=IX^g^4jr=8kQS0_eYi*^j`rx42Zi;m?yUL^H?hKA&8% zba^FJOf8u8|L$0j#>g^P3gz=Ctg@6#iZo@ZuJD*|GvO{C*vGjSU4YK-?h8I~bzjXw zKIx$~=Xkm29Q7qnS6p$^^9rJLlsD%sE!3hJwv5XAHuRVW(n6-@NnX140Kf1{VTx{+ z?I!W_-_ew(#pw;%U->3D#Es6x+B)?Sxk;}a!9SnFh+BqqG1LrqY8D2YYDPF!xDDjr zYJt}QJ!8%4VXbqn7*L!=0?&N=zI;Go1I65HJGI_6KcmRaYxyDSeJ^@H^5!2qv|svf ze9qH9|K*;Hc9 zA8clNgUo}mW&9qEjnxi50+sIVY_D*6{nfldeO*R2ICJXSYvi0TvES!?(%o&WADxKz zlR6yN*-sAXZcmhaa)wP@{6fHz$NxY4HNW_4U-B;mq;X&J6+i0f3%}%3^;G1-_-6g{ z1++`mF9<9k*N}SnAGS*?+V((C5ZxTpHYv=fRTJ#v$kqO-t@mMf>aOXMaZwQd|a zmCnr+j2;5L`jB{UcLNO<84BlwF0$+EIBjPIKH2Ro&|$%z98@8F(*Kae`_!X8h^#Q> z&j<&;9*!_vO*6Q_YL+fxdTrXxj|1cl3?5*qW0Bv=sm)1KGbJ`ebdsBcC@6Kh5MzLj zA+rwj4l@U9B`XuM0q;Owq3F)-1lm^)>oDqe#*_V`SB?YSFpuZX=O{oUJj`z$a>pylk;349DP37?H$E79V1cGPtff@hza%mi z<5?c2=l-#S5e_~jeQskr;N^*T(2Ia#{-Xel_(tZ9?snbx$bk0r=O?3F1!ff zr2-8v6!hpy8Tg)2_gd{KnYeo>o-p;CYeVM2m9w93CPNN}nMa3_%cLJI;P9nP_$DU#_}PO z;0V3YRLX?PY-EGvUp{-_V>UU9w`yszGx3oEGG~C5BRI z8X|j;XSkS?wn%^l8j#(T7q#eo19pOnL|kCY-@MiccfkVkaH!tT#7%aau<_2E1;(zV z{dV8HyX|zFvZuHuWM|rW7juE8zRunJAlpmtCyfhdLfNIT7=XvE*O~;vP>{$Xg0p?G z2>~v;mRa}fQEa<6lDDmS_2U4I?c`@Ga zQSNX4r*YNq!MfSi4DV8A?2N4M^R{gnT-J}~twYW!c$ZmcY)o{o{roz80I*MrZ00_p z&;^r?-RtCxYNKD_D<|cNBtaUKXbQxMac`4CnfbrK&=9yW!pe6r(W4`CE_$g8} z#6z)^QmyseT?2ED;_jQCsC3|#SH6$RXr1T_oBMHmp-P=wXW5+cDFU#m4IQ$0rMKOB z%W#xw2C{tO#9G!S&{`(#<{|W^V?W;}S&`=h7=j zDC?27GfT#=^;Zh7I8HnXDC)sM1YR1A%97@i6*jrZD@ZubXt(S0x1p-Nh;;p!PUN|B zjjv*yV39+xABksTa$^|bdsOm3uiOPN-bW1fOszmNqBG}cOK2ZOD;a=Q)3a$)+wOZF zHph3q;n;lOpyrXB!^z|GC>nAxQYERK75S3vEw9v zKPL|C3NNsNno39f(dAJ~mDaq2kpLFKtXJ-=+E#p)I6EPuFP@PwCTDE%MrQ2x#x~w| zH;BA@Af}gQEQS9YQGQcf874k_a`vo!s5Ni!^(UPPp_qQ&iVWWPz@-+ASF-t+V? zMQ@Jn7nyMIFf!j|8E^LJGH+jW$t`kld%pW@+iNS=)8e%p)b*+jeN=OFk7jgv(5%RO zH-BViK1|7jHTJACzf->y@U6Gr@rkzGe<2`iu>3+m`IO?q^U{AIVAb~YJ%9hhPru>I z{=(CD{;&NzLF=l{F9$sBw5%JBcCp)B@9zCGYJQQVUm92@-$!!%?HkHtKalw9X5Yz@86*B)Jzoc7J6?(BoRd?p z%3c`7z{B`6w+=hj+6PNMN*R9x%q6)Fft}<{Eys>4*-*bIa1LGe>{Y{_O-e0km(?66 zGz2H_1S7F>iXSuMQNHMfD`UuoHCRkrkFAIHuw-G1M0;0Q>rZ^03x%6=s2oPud1~K6 ze4kUtO+V+)O|N>;xdd6A;~nUAYzw}e)4a#Nss02@vfYnoZqXYz`@3*?Zl*_LN}?#@ z_#nOXa{4W}&l9_vDBBv^J%dwCrIbG4=&_bT;jw9vy|)ig=K#ZXDE+N+JGSP?sZoiP zx|}V>aG9 zzQ)h{U*3gRIW?;bNj^M#orEBfeswZLH@~ffM>_>R zM<@7rm!L8Z^lhZ7eTS?3_H$DEQcZfcKQN}|u8F0KMDR$f9V3C3qnrvGlZ3u*^j?f9 zq(6Z9RnHzclZELV#rZxyWu=>&axJlx6%P;5cvvU=ms&VQ|v$nq*cQh;B_#JCW zsqCU$`u3U=1?F-~Hde<6D2u&-yX{P8Gl#bv^0>0RO_i4}X|-E%nv@{1U%ezv37F4NvpoM>C_} zjlJs%wl>2uItG4>%y*e}pO6>KoR$DSo<`mwy>2z{6W~#8cl+C|vBjQ~2hQlipY@(O zd9#iW>*$z+iGJ(etY^~Y3Gh|F@hiXjrT-|v4|?Cbo_^Wy{)tZ?_;mg}U|PD%=c3NV zxk{JWHh<`dju#)Lt2s@Tptj;$4K9;E{cYa@!01nEjXp>7u$E8Z^uq}zSvhPE-+27O z$T8sOKLfpPOkV1EqLjUoJZth&cJc#^O?<9&uoFW7GaR0BQa{sEa)_?x9@1q2GYWc! zj+$#If0VML01zztHSA){sKM5vgPoYoAL^lsz)jyI3CAk_?Ja=m<7XcgaRs|D9OY1R z$;Yb}3fLNO!T^9kf4>@h-$P-A;T$YXVwIDyWfM~!^5O;k=3x)B_8xccqfl-bgWpk?J+M@;OQLS|#o^41BRt=TeWp@v(5#rR7nEO0OCNjfP`e6Q!31$bg z!|0~(k1&M|r=C;VTPd|4=wyF|uj<_m#8vT=w-#!wd9d1+Ug3Zi`{+P51(W+%LXVAm zRyz)I^4v7t!7ya@GuJE5J0u-uzQ;DUj+J1zawWqoD?hL>S||78P8kw8J5)xa;K< z`Xo^Dlqp~MaHm2On#OrWb|kMrKO5n11541%oKH1r=-es;>xXQ^1ET#FaQ7Pq5Za&e zbFZ6N!4*;mQPe^7olHU{H~SX|aGF#`$CS|Ii=c7hKjBU;rOWdvaeyHw_q#84hW(7R zB58%mo~+0{=bC-x;X}bv&S*nX3i-{tvg#(Gmzwo73r&MdmsM-Vq#lZUB6fb)YMnEf z=$Y$Lgl2bL2j>v64SP~*McBqpf3oOiOwMUeW+82L*{^#3``i<4kvqrA%;_tCQx=zk zbHFDrv+=~1KKi%xr$SkA(Q8Zc@graG)@xEg8po~}dioQ~Z~lMX@A)Y&{>?vS{^sBG z%YS=KQ*6H{$vTbA96d9G`H^kov{Q}6CnK1wnZvtvciEf%YAhVu<#zm7IPWoDi}ku= zy%zPPh@4ROb@Z8IXMS|Dp$~T3k?$up*nJ{J4%T?S4W_Op`ef|j!=*o~A#Q)N2Iv0$ z0B`YaIRD*W`g4B(!%H3?_~0kihX9|~9|CXzr%idBi*mmeFN^=F=K7>Hm+$xlm$fe7 z>|F02H&}^vkngg=X}*Hj-6JM_3fwm+Zc56)cc;RLP3gp6nz^CblvvEzFE32U34C$; zMrO{*m3Cm%CczMI@t+|crHA6^m4kdb z87oex;%*A*CKROQ$J#nj;fM}U>N`UEgU9h_(HNA<)$1;)7Cp*#>ItxX;ulrV?%{Fn z&Xj_HJ8LaVL0z^n-(`9n-Inp*_^g{%lUYklQOPmZUSCQ`yi$rEci~;K#bK^x>hcRg z+2Lp(?8*P^E=JC~;E2G(=c0W#19&l65dX2SV2*V$t%bZ(jj1@22ffYt{D$_{xa1Bl zOz31SA9@=&1-vEG zl}unTMJQ~qYMOyHW9r6FzP4=NgLlCkm3Bxp8>O7v!I{NdJJi|6(R{3wx1dKClxwnn z%9GFParcvHw^m-I5Qi6`b-e+j#V%~k3M2LnREy@v5+@MAsyiJ`}tOkJKGgZ?5PQm|?2n#$(AR&Rhq2@fK4nu+OAjs-$keS_ze~IP z%cn&rT5Dcwil2JL?f8DZhg>hmMao~TYg>ZAFI`@;+g@7xlVJOxnO1Dvmf_^V*!sI{#KB}8zpU|N zYxm=B3wQJ%m$T3R*SB=)W20X+?)B${0l#g0P!e18%;@j7=$P~E`(X1U1N+ASWUq@+ zaO2xLeiT5y^`ii>3HuFy?63chAN;-_^4Z1vqQ`gstq(nY^;dlB(|7(&en&zs_uPb# z`MbEK)m5D~96ORP-$&<0cE` z!6@|j>!#E=6(gX0jo<5%HQ?z{qHfB%0hLZvby!@Zi;wcCh0Rf^+=vuRKB}Qyo;892V%gm~B#dH*=z-AG;53 zUo164w3>IvW%Zx!uwYQ8YdxSLW-p8DO`@DP9t8*?xkP3(F2ubQ_9!g0RbcpB^OLd*6ed zQ$vMBdz}YUYV%g1{_m#*@ZC4b&fc`=I&o$8O4nG#`~Kd+D~5P$ZI_*VRu|4jJwTAV=Yh;7FZFjUxh_0NF5%XAF*Y= z`$(_aA*e^`TBD)(7Dtml2KrM$AO3K@ z-JdT|M%K6T*Bbms>H~m})W!47`u%6I(_UeE4lnx1%#5tD?FY8|IyzzDfDOXSVu$IIX(*yFy&79I1Jw_k8~`}X0@`rUr~{AU66tf>vC=S!gS**x_g*nbv~ zkJ*ZJfZz9h|M$0l!B77u|EYUoFKx|_1N^w3{0}|-nQ!!p6ExR9^l^&nCc;3d4EIr)SANm#ET~KVxqs2m(&yJ73MxVUi*C=nZ5;e!(A5fcf zd!5>x>9(mu1K13SvCuMa3^D25Rfk7(*x92l&14VoiJ}Z;zjC6_z(SAcJG}&6TzfsFkFE+hRqLlZr<%6@H%!^hLdC`rwjVJ4g*A;ZSH@2Zd*z zID$Vv;P9%^HeXv(d4a|6J4|efc;mXapD@-PpwgoO8Z}33&D%|J^PK1G>Rpl3N1X>n zl=E%&23h;Xw8n8AsgC9oHJTom1A#v$tD$lMmO;P)qd-wGw=8w`Nym zkF<5t7Gx@SBSh=#m3RugdzaYjOmKezcf+Q~A~=WW4~N?gf}C)7bRb#dn{9-oO>MC%$x2Lehm7S|1_es@bHy+|LNrV zs$7S*gEu)joyHbmt0RQ>)w2m4*}#` zXK_9Rc;hd9>!0~Qf9e*EPXXirzKX++>p1CC; zGj!w3LW*9<1-XR!ukOiNP9SS6$K1(Kx9_>3t=n_k0O`d@QA`fGYiU>PqX-HPF(b)% zK@y{@7I(=LV+ARg z_{tXeV)&TF?48Pb=H+-3{@PtKP&+sb=9;4PU5(gfo(A2n8<30*@pw{emeX_XkdvOB zPa>&h<+DWR+eMqV7k%d1{lq^7cC8&JUhc)h7Xk5X-;tv`5qzGpu71eNwh^Z%#Un!a z!kPKXD~2-3%0a2MB6?VMzY23%x7{IeTG?$s1@l4JtWP*;Vq^9wfD>Em7P~&*4}y05 zzTeCz+${I-AZo2v@F zd0#*GpFjJjCe*&|H}~x0oAcw%-}imkV&6@BB-60Fp52KqcIHO|Tn2J{qsy!v&*I~K zejS^Un(imT{e;Lmc+Q1KGP*p1$vSgBNXb|J(W7HVw;$-ee)qfi9{2qh0nm@VEZqP<223jc@<%r{DIAzxC;R{$73OP+HDMy}lz90qgtn9mn44Q(Y;^+0 z;m81=@RFM+HfF{JW0j5_PcJ@w7hJ~0wY2jZNV->J#-$(=zTzcJ>T;djIBm;}ze+0a zya2^leocs8{;ER<_uFDKz;u$3Pq-%;V(Ba}R`$JiCZ#rRB@(|038;tmzH>|scU#tb zj^EZrj_qVr7Wz?&lLNVTlPcH5jxSv8f>bI1TtJHP8;nF|McR5UFj&tT?dDyRA-+?O4Fa(nIf>lDd;$@^r z(3q)_K5%|YQwN``=se7!Z*j*8RvENa9EB~ID1@m)FD^Q}C~k|*H-;8EzamK%!7D%X zFLj0WzwZqw=i9ZP*Va=|5Yh|H`8drs=&! z*)hbdL{aWhfMI*Ab~;Dbe%45MLd($kL`|LZuwEQ_4%EBB?!oS5IPN)|wHb$3f!UMN zbP|NtXntpy3ggLqWPQhgG+Te(uZo{n+4$%MAv4SKJE(Xj=gt3T{rL9-e(|Hf_5T-D zAv8vB;`;-Dk39M3{?V1He;mM=tohb|et81l`Y&8K9?ZjEHsq}NF#$%_ky%I1jE-F) z)*iv4)2^*~<36rs-eA6tgAKoryN>lEUHoFt2^MVhnZdtG=JIj(J9vH+;GOT}M*;Xw zfN(sX^P>QTE7HMy=tJN8KYZa&|Jg78M*(OG-wE)OfA*(5eaj#HPW`DsHBO(rN&@nH z)RkX1*;s&T$0BS)=}>dC%C2U6&?EOvWo}TpjLaPdV4O>qAHIyW0H23!^$%gtf@JlBG6W7*d02yKR0Es2;b?A&iq=iav*gfxslPvr=rL92wo# zPr-&gKXzWv4UTrHgE$SOjy?7YnMZt$OJ1(@Sg)Qs&8r6QIX-mhr>Oa~`ZEUy~wJOr>ALGkK{%&0cA3khI6%o)?=T%TxSW%Q|({s^naA zcMXvB9(^;#69K;UYqKIVEzRR>W;VHcnttm`p7W|`QTN9Q2NUWSfa(iT~YXHiJ! zLz2qeG2!a`{91{`;>Nkjb9M!csplOlRJi+m;oJ$i`|gablZ~IFFN}X)|4NR2t{W1{ zj%!mgD>JQ2YJFnEM)os==Xsclu*Hs!97{E?%#8(O9^>WBi^Rh6o$n4CE;#()7ykX~ z-}RH9UY9rj^l^RbKLx*8?@uU}H`w(lw!ifsx%SRF^o6VS`Q_%{_kGgI$M!U@RP$zi z`^Ua#u=jzTk=d&`GsfmEi}fGO4?FNWe*kbl0a)ijGw${@Xk)f+`)uFHukzda=koed zfO-OjOR?6nuAcf)0NttU%%2Yd_)&n*`}{BX?$7vmIDq;&FbTq+mg)lEmkoRo`(-Cyj&tA+SC)2hybf>v zERto`SUcBIv95>sb!bem*6liUvx*O=2LH*sK%hn4Le89-McY^~HQ>$=UF)MWf8!D$ z#>R?&@tm5X46qy*NAe1taxcK>+s*c}jscC)(+^Z--|KUhI1lfLF@~pa?T0^o>^SLb zu=jQJdf}n)_D6po&bGD1Z!WJdW!@a!uFjulc#pJ^RKp!zFrH(L78DPUZSAsU4&pIq zyv<~e?Q!gpv=@P(lv61tDCb!BK1l`N7|vCRtq(=u7%_z>`yzefXpVf6lSEaM{LQeBptt4c0 z{P;Mn)t8+>4o!D2XT|`I9g`fF>EC9*pw+L$Arzd=tEteMt=newHZ4XJ6MyNq8b3Wh@?0;Y2Ke~g z=6b{KbFPjZm$S@gnrHW?{Kz2>`1l||ylXC9e&OHi@@@aL@Gt)PKlxvL%~$`bKT)FI zoY$Lwe>I;5eE7p@K>jjW-oVdV9~MCLk&j^G!PxVbvBAj*ch=F#_%R%|AG`N_l^3i0OD9b3ZV7Z|KY##8$S7yKlR1`K7guGgTD&Ej{`947RWBm-GkP63&ti? zwq?y1Kd61{Lkfo_>wPkmzx)nnzI9b9_^$(R-D_Lz`{Mt@-n$3g)@5aVZ-3oiH%$X6 zZY|OpzSdaDIcJ`;*Bo<LSea1w#2jM0l2^t86|wRp zHw<-99vh@7Y#XOl=D~@z0{Q>)HQ2#3A5o2*3@)v79hc#92&rwr43?ny(tz*g3nve9 z;Wu@`-PhyeIo7<8yI7iu^1R1$RzK;d9DvpVgR-AJh;X_m zH`Z)dJS@lFd2Sp&&-0U>`p5sPHft8Ua$}(UqII9l7^&8UN%kArDE3;}<3q2;8pY`% z#(Dl86VEBleq*;SVLUgkZCn#)8moK4!{^+5#4>A^J%u(%dtV=60r{Y|IFo=J?(<4s zwrhRLFRqR=qfVKO$8%$tnB!qjwe3bfCURB#l}FNtXSUX7&aBI7MHqPW!7a=K1n;k6 z=-llKf1Wef&dzy7E_lf!tt+pfV^frl8(sPb)eub9FnFy|ClGn{$?co~&YtSMxvyFt zYTi-HH||~p2S^xfi?{}htKA=|TLD;+G^7|DC6?>nc-PE4@)lupUC^)wy#CQ-o;4`l z+RFeK(A6VU-diYbi=F<6w8qC^!!m496-2u}!O{=tgT%FWyom)31$`RfJl@a$)Ns$z zxd_`N_M1#FDq|BiI0(Hjt^sihvg&0-dlm=3rcdPdE?qe2ectoQHhk0PEPGo;n zvf9XLD;wJoVcX%xuZg2yFbkT8ikE2QTQ(&fUFE>P((Ma`#&rsG$C?bo5GO{m3vUGc z^^aJ}T6qlT5>uvV8CT@`mu7!yOL@izkN&6RB<6}}F~Jn!)-&{`W&H%>J=`!L?R&2Jrh4X(BC zNu=SL4>|R}=y%pb+99kI56{;EZ)dT(nSX4Nm90rLV&-qTw$-`_Yj}D%$tv;UY%CC~j>mc*tpQkYpCiRyK|Fr-O1{wY@P~G1liywNeE+jOTS}qF#;z@Ze{hk87?2KJ8mR0`Qx8?dN}c z95zECYa9mk8SDVI>xP_?HlSP)vri2|X-91P!Tvx`W{sI+yA-DFjIl2a3VyKd9uEE) zgAdKV$2i7t)Bm{qsXx#4?vBs>|L!mPHDA=<`mf2?{RoA2A)bGJ-Kf`^q05^9`Y6EZ zi~kPxE@u1*X-@YU(+7iHtAnx|I@-Cn_VyTDENcg};ye!SiRvHsHvm}V^G3)5$3~wP z`C)zV^pAoC6I^_vbN{lJN#6?+8@hT9>Sf5f{8fNb)x_3E0qTkS_7C~6*Z%Vl{*aIO zsd9P@4!jBQ37`4uyKng$|LpExd|$o9FkuzMlN;w!znII2tzNh0s-tHdyId#<`Jnp0Hp;)R+U1 ztfTw(Ej@Irm0EjOio~fw;3Der^3!Fc{O~oT=h?(Y^q`R$?HwCx4Wy3Os;9M^EsTV>%}>k%tB!~Ot}=7ImvtSAg_s!d>jS~EglP9!H&-l<8qw)37z)eVlkin z!&smM35mwNLl2WXWGr-}>>0{4UwEt6YghHF-Y0oLx2QQg%|%^;>Wn|gWq>=bWaq)w z#Wwnakk7fUc^(hsy=%Kd7WnL^@e!~nwF6|!K4dDLNw&w>45}Y^9w`7~jYU(wHL3@B zAX#=_GN5(ghn8y&{Efqd_~zJstoDtbC^W`>Us`0Z{n+=`&HsD9-z#K&Tn=CJSN_8P z^vAyR_x#=Hsa};b9n=*l?O?$@l(;mHI)_~qy z0Lz2h#GZZ5;|DW60oEts4S=gjWUPDis3vI+oGy>r|o>2fn%BNiSmY*k*p@Ivs~> z1n0}`%#G0}q;(Q4m8k59dBruxzD+==!@R7($&!u^GZZB%2&x87Jjtn%TgQuvUVNT` z9<%J?{ybr5LoBD3epeFZ=t-?;YrHPwz6JI>^?{ldZCj(RbR zXJYX;gM|2`m-6CpVfTETuXbF`Juun!lTC!mLP^$;I>oM<#j|bI4bAdjy7o~gtLa*d zrsMQbF-s;9h-gb*%&nBQ%$ZXG!RE0A0ktACnX)sa4!7%W?ium?93f0nG|VM@Qu@O`LGs_%iMiK5To@_il}j+bMF8_dgNN-BTOm7 zD6SWA*snQeW`@!AY)S6Gc(aB{xbm4u)h3>uYXAovn5k$V?SnDLJY58jz8sy;t`Jmu zItkchrA)0_b9l;`yAVqH)T2{=I>25JX(l(09b$JRq{E%5aE_bd+x3R#ZRFWqjhCN_n_wecT7w`=V3XR-2>jfHL8|h|1Z6hxO?4az2@#W|Nf7;d-*FKuiK_Kzv=IN#jpF- zum3YOX^Oy?{>{(*uN!^rwRVj2$WQ-WyyF-D6-+p6McE5u9X9(Qxg`#x*yz)uV|>;J zbM9HW+0Nr>dm`n>YbU?BC1f*XBlS~!5u7(l?BHXIE){!%~^>UR=k`18i__VM_Gq|CnBYd`V5Royp#@xKTP*(V%)3f?%$+g0&|E{xPVh(Lbp;;T+xijAos z_|bgPWU9pSsCCsPZ((r@pKRa=Y(7R3Ase~M%0WWoOV%(x1d!jh3$Z{ae|a|+j%u2N z6Ud-O9jH3h#K=ldt5YL-S_cwbhh7IPtVPyEV2x87c{{lDMnKt|tyuWdW6_0$1OpDU ziSO#0WOky@JbDJDLmf9bX@Xk3JvKN#^1t+OhTb>S(Qy5RadD@Fk;>_3(9g8oiw1?p6 z6o+1cobr_McTOcVMw~YXF%`u7LA~bT`X@8>BvEiz-b8baLXFN3@|(dki=9$a_Pks_ zc!bR4QNYvV%_2jiJr8?Mg!RK6C-X~!w(-?vz5tdS>cZ#sQLZx#oIUoGyMCo~4Qv#K zLxXp0M^h+#C^VenjjwxZfxF`fwho)GRqe6>2(4T5TQfy-c3^zgHCxm@mx*)sjSE9B z!pd=*hvd4Y&WopF;Wzi`aMFfS?NF*XZI$2NK65SPq}-Xviq!s#t%>M7;h96`f+Z}< zb1shE4tpmYi(GSCBe8ic+BpFPVNU_E&1LmIL)B{43wfntc&6cCJx%3P;8pi>^4yxR zY`CjRi*V;)vOa<(aCwNYq>*mtCnjOqTIfU|rcKs$T&c_*?@~QdyC3i8l1Ma10;h&f zLVG4ZdDlb=kX~pGRYAL!NHxu&y?_j)w3J1q>9GylSL+~e<2f3SZ?Qte+7M4Tj@)ln z!>$WxaAdF_!Nc_nKJP>Ce#LM42)R5?2Y&VcH+}wR|Gw}0zJL5fH80(SR|Q0LM{4~80@m0 zUoM85Fyg2KI9S`RUN&AQQEFhB*UtK<6V32*OqQ#Nl&C1JUcWtH9xDl`|MKxg*nX== zVNKDX=3UPCH$l&nTos}ETx2MJcsewq8sqr4%$&e$ZqtTCVPY?#hH@&y@DZi`#luOt za}hxLBD({nOkfbsb6`8?yb)7Jt$c zOx7@>+)Z;(L67=mu=BIsxy<^*)oZqCCCP)sCOOK%gSihU8waNA^^8ov)=z7J!c@|G z2u$=6G$;`3rUEUk$Foez+p!_={%gF(mR-Zhx3nul`A|O?r_XIsK)8XkNBfLg>UmPI zBNx8APDG>yGq-CS)MeVjf|oGzsPV*LKG)B=%HXxSz@T!gS@i}b3UgdbQi0Z-or%X| z?yVM;c*-Voab}-{&#!HyYg^*elY;Ff?CBa($3sZvYaRg(eCAq?k0|7MZMYNgk;v6N zsR-3$?W|^UTYJRRh&G|(M}anTVt8)ZERDgOTGP)p(q0rNpeCvw*A+W5eXxR-VztQe zV$35Bwy(WEEQ0~G>Xa@@U4D}cCKMd0jz6EHK^!w}aoA(Tc9)^nh)9B#fe{Cmn~lePfOu6m*}{D^MBhKIDI|$0!e8i&(*SWYd?P7 zpPC6$iI;>i-_sum&1SZ-)aP)CItzX2{^E+>&1UQmh!! zY3VjB@ZFb<3o5O{1@5pul?3BNCp>xKW?k`UgrYRy zOB9FpZHJXpJL3tc+98%zXl*90D4hclJB0cH-(vGh;RGE^dg6=4aU&<0X2A`(_dU4? z*wz=@!L%ErJfcmSM6@-Q^C)=@yU30)Vl^9US5i}JF2EB?eH5*Spj4?f!$nbgmXfyD zF67H#fKg65GDIOLUDAj{Gh}G5!tYq6x&j?M_JFy21+#~V2*o5RdF6!O7y%7l;>EC3 z_L*~8EC|^BpTTXb*zz6NRhTkl?&fczU=tJ^%sr1aosPyB`_{)%UTqXXd$^NNd4f(O zpZ1ap`NrOXQhyx6g_9Y04dOB?xnMEQcr*J?#UZCz&i+&=lZ$ue5)btN>;HP~E9I2I zKm)JhFX1?UaB1MvBG1`$84M91sCdykWBYA861Dd0kntKTuZ-Q_cZ5UiDwgM`O`vgE z(~F=YIhBU^9AW>CprnS}xl7ZwE_0R8WLoRK7gaWdU4CFZ3SNsKLfgbQ{e>@^;u#~} z;o~v9FWkNQHLtw;!aww}cR%T;y}uBT+u`f}=3n^}U;01%&aba|sTjKXN9JbVKmA`f z^R<7#WX%5JqXO1lf9`)4I5H)?PsWx8hL#?gReO}yyGg!}>hmbPk0s_weFK2S%tpz= zGG@a~eKt^fu#tmHkL}zh7F))76Cl3Pd%XWD0KWwgo4D(V7a#NGO@JB|>9YOB|Lx1) z{J4D|fC^BD_j=!#-+lC_yw4pU2ly9ntv3OxsVb5fc&w9>UDNW{N{GKD@ZMπTCT29rFvqeWoOES)FxrU)Xj$rjCcKEAD12>1K}{ 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%0A-YK05FWjfN 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