Compare commits
49 commits
activitypu
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
|
bfd0b77e9a | ||
|
bd2e33f358 | ||
|
f06cb96098 | ||
|
7be872a0b4 | ||
|
dbe589f983 | ||
|
6fd471295d | ||
|
8a3c60672f | ||
|
ac398426fc | ||
|
e02602e445 | ||
|
ac32e5ec1d | ||
|
8d3ceafe34 | ||
|
58d83a8cb1 | ||
|
240160f877 | ||
|
a622b0b947 | ||
|
f727ee6a45 | ||
|
25008e0555 | ||
|
995c69a45b | ||
|
291027c1c3 | ||
|
a84f182cf4 | ||
|
12abf5e142 | ||
|
638c1ed7d8 | ||
|
e90505cfdf | ||
|
d73bf5770d | ||
|
fd92599890 | ||
|
5560887862 | ||
|
426e096a9b | ||
|
9785c2849a | ||
|
57617faa27 | ||
|
d5408766cc | ||
|
7a30154bc5 | ||
|
f1ee1eadd9 | ||
|
e884c39a03 | ||
|
f643515fdd | ||
|
078688149a | ||
|
f94db7a54f | ||
|
cb83422a8a | ||
|
7a0e5c9900 | ||
|
14570da001 | ||
|
0a479aa734 | ||
|
b9df613b31 | ||
|
faf7925ce0 | ||
|
bfd9f4938d | ||
|
4a01c00ef2 | ||
|
12c4213ecf | ||
|
3f4a72e7f3 | ||
|
a414adc582 | ||
|
313af50864 | ||
|
3142c2b31a | ||
|
e35b438f06 |
55 changed files with 1532 additions and 159 deletions
2
Gemfile
2
Gemfile
|
@ -155,3 +155,5 @@ gem 'concurrent-ruby', require: false
|
|||
gem 'connection_pool', require: false
|
||||
gem 'xorcist', '~> 1.1'
|
||||
gem 'cocoon', '~> 1.2'
|
||||
|
||||
gem 'random_name_generator'
|
||||
|
|
|
@ -535,6 +535,7 @@ GEM
|
|||
thor (~> 1.0)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
random_name_generator (2.0.1)
|
||||
rdf (3.2.9)
|
||||
link_header (~> 0.0, >= 0.0.8)
|
||||
rdf-normalize (0.5.0)
|
||||
|
@ -820,6 +821,7 @@ DEPENDENCIES
|
|||
rails-controller-testing (~> 1.0)
|
||||
rails-i18n (~> 6.0)
|
||||
rails-settings-cached (~> 0.6)
|
||||
random_name_generator
|
||||
rdf-normalize (~> 0.5)
|
||||
redcarpet (~> 3.5)
|
||||
redis (~> 4.5)
|
||||
|
@ -855,3 +857,9 @@ DEPENDENCIES
|
|||
webpacker (~> 5.4)
|
||||
webpush!
|
||||
xorcist (~> 1.1)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.0.4p208
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.33
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
require 'redis'
|
||||
|
||||
class ActivityPub::InboxesController < ActivityPub::BaseController
|
||||
include SignatureVerification
|
||||
|
@ -9,6 +10,10 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
|
|||
before_action :require_actor_signature!
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
def initialize
|
||||
@activity_log_publisher = ActivityLogPublisher.new
|
||||
end
|
||||
|
||||
def create
|
||||
upgrade_account
|
||||
process_collection_synchronization
|
||||
|
@ -71,6 +76,10 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
|
|||
end
|
||||
|
||||
def process_payload
|
||||
event = ActivityLogEvent.new('inbound', "https://#{Rails.configuration.x.web_domain}#{request.path}", Oj.load(body, mode: :strict))
|
||||
|
||||
@activity_log_publisher.publish(event)
|
||||
|
||||
ActivityPub::ProcessingWorker.perform_async(signed_request_actor.id, body, @account&.id, signed_request_actor.class.name)
|
||||
end
|
||||
end
|
||||
|
|
36
app/controllers/api/v1/activity_log_controller.rb
Normal file
36
app/controllers/api/v1/activity_log_controller.rb
Normal file
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::ActivityLogController < Api::BaseController
|
||||
include ActionController::Live
|
||||
|
||||
# before_action -> { doorkeeper_authorize! :read }, only: [:show]
|
||||
before_action :require_user!
|
||||
|
||||
rescue_from ArgumentError do |e|
|
||||
render json: { error: e.to_s }, status: 422
|
||||
end
|
||||
|
||||
def show
|
||||
response.headers['Content-Type'] = 'text/event-stream'
|
||||
# hack to avoid computing Etag, which delays sending of data
|
||||
response.headers['Last-Modified'] = Time.now.httpdate
|
||||
|
||||
sse = SSE.new(response.stream)
|
||||
|
||||
# id = current_account.local_username_and_domain
|
||||
id = current_account.username
|
||||
|
||||
begin
|
||||
ActivityLogger.register(id, sse)
|
||||
|
||||
while true
|
||||
event = ActivityLogEvent.new('keep-alive', nil, nil)
|
||||
ActivityLogger.log(id, event)
|
||||
sleep 10
|
||||
end
|
||||
ensure
|
||||
ActivityLogger.unregister(id, sse)
|
||||
sse.close
|
||||
end
|
||||
end
|
||||
end
|
21
app/controllers/api/v1/json_ld_controller.rb
Normal file
21
app/controllers/api/v1/json_ld_controller.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'faraday'
|
||||
|
||||
class Api::V1::JsonLdController < Api::BaseController
|
||||
include ActionController::Live
|
||||
|
||||
rescue_from ArgumentError do |e|
|
||||
render json: { error: e.to_s }, status: 422
|
||||
end
|
||||
|
||||
def show
|
||||
url = params[:url]
|
||||
|
||||
api_response = Faraday.get(url, nil, {'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'})
|
||||
|
||||
response.headers['Content-Type'] = api_response.headers['Content-Type']
|
||||
|
||||
render body: api_response.body, status: api_response.status
|
||||
end
|
||||
end
|
|
@ -61,7 +61,7 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def after_sign_out_path_for(_resource_or_scope)
|
||||
new_user_session_path
|
||||
"/auth/sign_up"
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'random_name_generator'
|
||||
require 'securerandom'
|
||||
|
||||
class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
include RegistrationSpamConcern
|
||||
|
||||
|
@ -45,6 +48,21 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
end
|
||||
|
||||
def build_resource(hash = nil)
|
||||
|
||||
# hack to always use auto-generated usernames and passwords
|
||||
if !hash.nil?
|
||||
username = generate_name
|
||||
password = SecureRandom.hex
|
||||
|
||||
hash["account_attributes"] = {
|
||||
"username": username.parameterize(separator: '_'),
|
||||
"display_name": username
|
||||
}
|
||||
hash["email"] = "#{hash["account_attributes"]["username"]}@#{Rails.configuration.x.web_domain}"
|
||||
hash["password"] = password
|
||||
hash["password_confirmation"] = password
|
||||
end
|
||||
|
||||
super(hash)
|
||||
|
||||
resource.locale = I18n.locale
|
||||
|
@ -62,7 +80,9 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
end
|
||||
|
||||
def after_sign_up_path_for(_resource)
|
||||
auth_setup_path
|
||||
# Hack to automatically visit the confirmation link after successful sign-up.
|
||||
# This way we can use the default configuration but still get away without an email server.
|
||||
"/auth/confirmation?confirmation_token=#{@user.confirmation_token}"
|
||||
end
|
||||
|
||||
def after_sign_in_path_for(_resource)
|
||||
|
@ -156,4 +176,16 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||
end
|
||||
|
||||
@@first_name_generator = RandomNameGenerator.new(File.new("#{File.dirname(__FILE__)}/roman.txt"))
|
||||
@@last_name_generator = RandomNameGenerator.new(RandomNameGenerator::FANTASY)
|
||||
|
||||
def generate_name
|
||||
# When there is a name collision, the user will be shown
|
||||
# "Something isn't quite right yet! Please review 2 errors below"
|
||||
# When they sign up again, it will most probably succeed (since there is no collision anymore)
|
||||
# While this isn't the best UX, it's only a minor issue
|
||||
# (collisions happen after > 1k of users and there's an easy fix) # so not worth fixing for now
|
||||
"#{@@first_name_generator.compose(3)} #{@@last_name_generator.compose(3)}"
|
||||
end
|
||||
end
|
||||
|
|
44
app/controllers/auth/roman.txt
Normal file
44
app/controllers/auth/roman.txt
Normal file
|
@ -0,0 +1,44 @@
|
|||
-a
|
||||
-al
|
||||
-au +c
|
||||
-an
|
||||
-ba
|
||||
-be
|
||||
-bi
|
||||
-br +v
|
||||
-da
|
||||
-di
|
||||
-do
|
||||
-du
|
||||
-e
|
||||
-eu +c
|
||||
-fa
|
||||
bi
|
||||
be
|
||||
bo
|
||||
bu
|
||||
nul +v
|
||||
gu
|
||||
da
|
||||
au +c -c
|
||||
fri
|
||||
gus
|
||||
+tus
|
||||
+ta
|
||||
+lus
|
||||
+la
|
||||
+lius
|
||||
+lia
|
||||
+nus
|
||||
+na
|
||||
+es
|
||||
+ius -c
|
||||
+ia -c
|
||||
+cus
|
||||
+ca
|
||||
+tor
|
||||
+cio
|
||||
+cia
|
||||
+tin
|
||||
+tia
|
||||
+ssia -v
|
|
@ -6,7 +6,9 @@ class HomeController < ApplicationController
|
|||
before_action :set_instance_presenter
|
||||
|
||||
def index
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
if !user_signed_in?
|
||||
redirect_to "/auth/sign_up"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
11
app/javascript/mastodon/actions/activity_log.js
Normal file
11
app/javascript/mastodon/actions/activity_log.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export const ACTIVITY_LOG_RESET ='ACTIVITY_LOG_RESET';
|
||||
export const ACTIVITY_LOG_ADD = 'ACTIVITY_LOG_ADD';
|
||||
|
||||
export const resetActivityLog = () => ({
|
||||
type: ACTIVITY_LOG_RESET,
|
||||
});
|
||||
|
||||
export const addActivityLog = value => ({
|
||||
type: ACTIVITY_LOG_ADD,
|
||||
value,
|
||||
});
|
12
app/javascript/mastodon/actions/activitypub_explorer.js
Normal file
12
app/javascript/mastodon/actions/activitypub_explorer.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
export const ACTIVITYPUB_EXPLORER_DATA ='ACTIVITYPUB_EXPLORER_DATA';
|
||||
export const ACTIVITYPUB_EXPLORER_URL ='ACTIVITYPUB_EXPLORER_URL';
|
||||
|
||||
export const setExplorerData = (value) => ({
|
||||
type: ACTIVITYPUB_EXPLORER_DATA,
|
||||
value,
|
||||
});
|
||||
|
||||
export const setExplorerUrl = (value) => ({
|
||||
type: ACTIVITYPUB_EXPLORER_URL,
|
||||
value,
|
||||
});
|
|
@ -13,6 +13,7 @@ import { connectUserStream } from 'mastodon/actions/streaming';
|
|||
import ErrorBoundary from 'mastodon/components/error_boundary';
|
||||
import initialState, { title as siteTitle } from 'mastodon/initial_state';
|
||||
import { getLocale } from 'mastodon/locales';
|
||||
import { addActivityLog } from 'mastodon/actions/activity_log';
|
||||
|
||||
const { localeData, messages } = getLocale();
|
||||
addLocaleData(localeData);
|
||||
|
@ -59,6 +60,14 @@ export default class Mastodon extends React.PureComponent {
|
|||
componentDidMount() {
|
||||
if (this.identity.signedIn) {
|
||||
this.disconnect = store.dispatch(connectUserStream());
|
||||
|
||||
this.eventSource = new EventSource('/api/v1/activity_log');
|
||||
this.eventSource.onmessage = (event) => {
|
||||
const parsed = JSON.parse(event.data);
|
||||
if (parsed.type !== 'keep-alive') {
|
||||
store.dispatch(addActivityLog(parsed));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,6 +75,7 @@ export default class Mastodon extends React.PureComponent {
|
|||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
this.eventSource.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
100
app/javascript/mastodon/features/activity_log/index.js
Normal file
100
app/javascript/mastodon/features/activity_log/index.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import Column from 'mastodon/components/column';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { setExplorerData, setExplorerUrl } from 'mastodon/actions/activitypub_explorer';
|
||||
import DismissableBanner from 'mastodon/components/dismissable_banner';
|
||||
|
||||
import { ActivityPubVisualization } from 'activitypub-visualization';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
logs: state.getIn(['activity_log', 'logs']),
|
||||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
class ActivityLog extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
multiColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const { dispatch, logs, multiColumn } = this.props;
|
||||
|
||||
const darkMode = !(document.body && document.body.classList.contains('theme-mastodon-light'));
|
||||
|
||||
// hijack the toggleHidden shortcut to copy the logs to clipbaord
|
||||
const handlers = {
|
||||
toggleHidden: () => navigator.clipboard.writeText(JSON.stringify(logs, null, 2)),
|
||||
};
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label='Activity Log'>
|
||||
<ColumnHeader
|
||||
icon='comments'
|
||||
title='Activity Log'
|
||||
onClick={this.handleHeaderClick}
|
||||
multiColumn={multiColumn}
|
||||
/>
|
||||
|
||||
<DismissableBanner id='activity_log'>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='dismissable_banner.activity_log_information'
|
||||
defaultMessage='When you interact with another instance (for example, follow an account on another instance), the resulting Activities will be shown here. You can find more information on my {blog}.'
|
||||
values={{
|
||||
blog: <a href='//seb.jambor.dev/' style={{ color: darkMode ? '#8c8dff' : '#3a3bff', textDecoration: 'none' }}>blog</a>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p style={{ paddingTop: '5px' }}>
|
||||
<FormattedMessage
|
||||
id='dismissable_banner.activity_log_clear'
|
||||
defaultMessage='Note: Activities will only be logged while Mastodon is open. When you navigate elsewhere or reload the page, the log will be cleared.'
|
||||
/>
|
||||
</p>
|
||||
</DismissableBanner>
|
||||
|
||||
<HotKeys handlers={handlers}>
|
||||
<div className={`${darkMode ? 'dark' : ''}`}>
|
||||
<ActivityPubVisualization
|
||||
logs={logs}
|
||||
clickableLinks
|
||||
onLinkClick={(url) => {
|
||||
dispatch(setExplorerUrl(url));
|
||||
this.context.router.history.push('/activitypub_explorer');
|
||||
}}
|
||||
showExplorerLink
|
||||
onExplorerLinkClick={(data) => {
|
||||
dispatch(setExplorerData(data));
|
||||
this.context.router.history.push('/activitypub_explorer');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import Column from 'mastodon/components/column';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import DismissableBanner from 'mastodon/components/dismissable_banner';
|
||||
import { setExplorerData, setExplorerUrl } from 'mastodon/actions/activitypub_explorer';
|
||||
|
||||
import { ActivityPubExplorer as Explorer } from 'activitypub-visualization';
|
||||
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
data: state.getIn(['activitypub_explorer', 'data']),
|
||||
url: state.getIn(['activitypub_explorer', 'url']),
|
||||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
class ActivityPubExplorer extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
multiColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
// clear explorer data on unbound so that we start with a clean slate on next navigation
|
||||
this.props.dispatch(setExplorerData(null));
|
||||
this.props.dispatch(setExplorerUrl(''));
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const { data, url, multiColumn } = this.props;
|
||||
|
||||
const darkMode = !(document.body && document.body.classList.contains('theme-mastodon-light'));
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label='ActivityPub Explorer'>
|
||||
<ColumnHeader
|
||||
icon='wpexplorer'
|
||||
title='ActivityPub Explorer'
|
||||
onClick={this.handleHeaderClick}
|
||||
multiColumn={multiColumn}
|
||||
/>
|
||||
|
||||
<DismissableBanner id='activity_log'>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='dismissable_banner.activity_pub_explorer_information'
|
||||
defaultMessage='TODO. You can find more information on my {blog}.'
|
||||
values={{
|
||||
blog: <a href='//seb.jambor.dev/' style={{ color: darkMode ? '#8c8dff' : '#3a3bff', textDecoration: 'none' }}>blog</a>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</DismissableBanner>
|
||||
|
||||
<div className={`${darkMode ? 'dark' : ''}`}>
|
||||
<Explorer initialValue={data} initialUrl={url} />
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ const messages = defineMessages({
|
|||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
activity_log: { id: 'navigation_bar.activity_log', defaultMessage: 'Activity log' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
|
@ -45,6 +46,7 @@ class ActionBar extends React.PureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
|
||||
menu.push({ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' });
|
||||
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
|
||||
menu.push({ text: intl.formatMessage(messages.activity_log), to: '/activity_log' });
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
|
||||
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
|
||||
|
|
|
@ -158,6 +158,10 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
|||
<td><kbd>g</kbd>+<kbd>r</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.requests' defaultMessage='to open follow requests list' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>a</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.activity_log' defaultMessage='to open activity log' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>?</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.legend' defaultMessage='to display this legend' /></td>
|
||||
|
|
|
@ -86,6 +86,10 @@ class NavigationPanel extends React.Component {
|
|||
<ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
|
||||
<ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
|
||||
|
||||
<ColumnLink transparent to='/activity_log' icon='comments' text='Activity Log' />
|
||||
|
||||
<ColumnLink transparent to='/activitypub_explorer' icon='wpexplorer' text='ActivityPub Explorer' />
|
||||
|
||||
<ListPanel />
|
||||
|
||||
<hr />
|
||||
|
|
|
@ -48,6 +48,8 @@ import {
|
|||
Mutes,
|
||||
PinnedStatuses,
|
||||
Lists,
|
||||
ActivityLog,
|
||||
ActivityPubExplorer,
|
||||
Directory,
|
||||
Explore,
|
||||
FollowRecommendations,
|
||||
|
@ -105,6 +107,8 @@ const keyMap = {
|
|||
goToBlocked: 'g b',
|
||||
goToMuted: 'g m',
|
||||
goToRequests: 'g r',
|
||||
goToActivityLog: 'g a',
|
||||
goToActivityPubExplorer: 'g e',
|
||||
toggleHidden: 'x',
|
||||
toggleSensitive: 'h',
|
||||
openMedia: 'e',
|
||||
|
@ -156,11 +160,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
let redirect;
|
||||
|
||||
if (signedIn) {
|
||||
if (mobile) {
|
||||
redirect = <Redirect from='/' to='/home' exact />;
|
||||
} else {
|
||||
redirect = <Redirect from='/' to='/getting-started' exact />;
|
||||
}
|
||||
redirect = <Redirect from='/' to='/activity_log' exact />;
|
||||
} else if (singleUserMode && owner && initialState?.accounts[owner]) {
|
||||
redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />;
|
||||
} else if (showTrends) {
|
||||
|
@ -218,6 +218,8 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
<WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} />
|
||||
<WrappedRoute path='/mutes' component={Mutes} content={children} />
|
||||
<WrappedRoute path='/lists' component={Lists} content={children} />
|
||||
<WrappedRoute path='/activity_log' component={ActivityLog} content={children} />
|
||||
<WrappedRoute path='/activitypub_explorer' component={ActivityPubExplorer} content={children} />
|
||||
|
||||
<Route component={BundleColumnError} />
|
||||
</WrappedSwitch>
|
||||
|
@ -387,12 +389,6 @@ class UI extends React.PureComponent {
|
|||
navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
|
||||
}
|
||||
|
||||
// On first launch, redirect to the follow recommendations page
|
||||
if (signedIn && this.props.firstLaunch) {
|
||||
this.context.router.history.replace('/start');
|
||||
this.props.dispatch(closeOnboarding());
|
||||
}
|
||||
|
||||
if (signedIn) {
|
||||
this.props.dispatch(fetchMarkers());
|
||||
this.props.dispatch(expandHomeTimeline());
|
||||
|
@ -495,6 +491,14 @@ class UI extends React.PureComponent {
|
|||
this.context.router.history.push('/home');
|
||||
}
|
||||
|
||||
handleHotkeyGoToActivityLog = () => {
|
||||
this.context.router.history.push('/activity_log');
|
||||
}
|
||||
|
||||
handleHotkeyGoToActivityPubExplorer = () => {
|
||||
this.context.router.history.push('/activitypub_explorer');
|
||||
}
|
||||
|
||||
handleHotkeyGoToNotifications = () => {
|
||||
this.context.router.history.push('/notifications');
|
||||
}
|
||||
|
@ -552,6 +556,8 @@ class UI extends React.PureComponent {
|
|||
focusColumn: this.handleHotkeyFocusColumn,
|
||||
back: this.handleHotkeyBack,
|
||||
goToHome: this.handleHotkeyGoToHome,
|
||||
goToActivityLog: this.handleHotkeyGoToActivityLog,
|
||||
goToActivityPubExplorer: this.handleHotkeyGoToActivityPubExplorer,
|
||||
goToNotifications: this.handleHotkeyGoToNotifications,
|
||||
goToLocal: this.handleHotkeyGoToLocal,
|
||||
goToFederated: this.handleHotkeyGoToFederated,
|
||||
|
|
|
@ -38,6 +38,14 @@ export function Lists () {
|
|||
return import(/* webpackChunkName: "features/lists" */'../../lists');
|
||||
}
|
||||
|
||||
export function ActivityLog () {
|
||||
return import(/* webpackChunkName: "features/activity_log" */'../../activity_log');
|
||||
}
|
||||
|
||||
export function ActivityPubExplorer () {
|
||||
return import(/* webpackChunkName: "features/activity_log" */'../../activitypub_explorer');
|
||||
}
|
||||
|
||||
export function Status () {
|
||||
return import(/* webpackChunkName: "features/status" */'../../status');
|
||||
}
|
||||
|
|
17
app/javascript/mastodon/reducers/activity_log.js
Normal file
17
app/javascript/mastodon/reducers/activity_log.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
import { ACTIVITY_LOG_ADD, ACTIVITY_LOG_RESET } from '../actions/activity_log';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
logs: [],
|
||||
});
|
||||
|
||||
export default function activity_log(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case ACTIVITY_LOG_ADD:
|
||||
return state.set('logs', [...state.get('logs'), action.value]);
|
||||
case ACTIVITY_LOG_RESET:
|
||||
return state.set('logs', []);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
18
app/javascript/mastodon/reducers/activitypub_explorer.js
Normal file
18
app/javascript/mastodon/reducers/activitypub_explorer.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
import { ACTIVITYPUB_EXPLORER_DATA, ACTIVITYPUB_EXPLORER_URL } from '../actions/activitypub_explorer';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
data: null,
|
||||
url: '',
|
||||
});
|
||||
|
||||
export default function activitypub_explorer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case ACTIVITYPUB_EXPLORER_DATA:
|
||||
return state.set('data', action.value);
|
||||
case ACTIVITYPUB_EXPLORER_URL:
|
||||
return state.set('url', action.value);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -9,6 +9,8 @@ import user_lists from './user_lists';
|
|||
import domain_lists from './domain_lists';
|
||||
import accounts from './accounts';
|
||||
import accounts_counters from './accounts_counters';
|
||||
import activity_log from './activity_log';
|
||||
import activitypub_explorer from './activitypub_explorer';
|
||||
import statuses from './statuses';
|
||||
import relationships from './relationships';
|
||||
import settings from './settings';
|
||||
|
@ -43,6 +45,8 @@ import tags from './tags';
|
|||
|
||||
const reducers = {
|
||||
announcements,
|
||||
activity_log,
|
||||
activitypub_explorer,
|
||||
dropdown_menu,
|
||||
timelines,
|
||||
meta,
|
||||
|
|
|
@ -23,3 +23,5 @@
|
|||
@import 'mastodon/dashboard';
|
||||
@import 'mastodon/rtl';
|
||||
@import 'mastodon/accessibility';
|
||||
|
||||
@import 'activitypub-visualization';
|
||||
|
|
|
@ -8608,3 +8608,321 @@ noscript {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
$blueGray-50: #F8FAFC;
|
||||
$blueGray-100:#F1F5F9;
|
||||
$blueGray-200: #E2E8F0;
|
||||
$blueGray-300: #CBD5E1;
|
||||
$blueGray-400: #94A3B8;
|
||||
$blueGray-500: #64748B;
|
||||
$blueGray-600: #475569;
|
||||
$blueGray-700: #334155;
|
||||
$blueGray-800: #1E293B;
|
||||
$blueGray-900: #0F172A;
|
||||
|
||||
$coolGray-50: #F9FAFB;
|
||||
$coolGray-100:#F3F4F6;
|
||||
$coolGray-200: #E5E7EB;
|
||||
$coolGray-300: #D1D5DB;
|
||||
$coolGray-400: #9CA3AF;
|
||||
$coolGray-500: #6B7280;
|
||||
$coolGray-600: #4B5563;
|
||||
$coolGray-700: #374151;
|
||||
$coolGray-800: #1F2937;
|
||||
$coolGray-900: #111827;
|
||||
|
||||
$gray-50: #FAFAFA;
|
||||
$gray-100:#F4F4F5;
|
||||
$gray-200: #E4E4E7;
|
||||
$gray-300: #D4D4D8;
|
||||
$gray-400: #A1A1AA;
|
||||
$gray-500: #71717A;
|
||||
$gray-600: #52525B;
|
||||
$gray-700: #3F3F46;
|
||||
$gray-800: #27272A;
|
||||
$gray-900: #18181B;
|
||||
|
||||
$trueGray-50: #FAFAFA;
|
||||
$trueGray-100:#F5F5F5;
|
||||
$trueGray-200: #E5E5E5;
|
||||
$trueGray-300: #D4D4D4;
|
||||
$trueGray-400: #A3A3A3;
|
||||
$trueGray-500: #737373;
|
||||
$trueGray-600: #525252;
|
||||
$trueGray-700: #404040;
|
||||
$trueGray-800: #262626;
|
||||
$trueGray-900: #171717;
|
||||
|
||||
$warmGray-50: #FAFAF9;
|
||||
$warmGray-100:#F5F5F4;
|
||||
$warmGray-200: #E7E5E4;
|
||||
$warmGray-300: #D6D3D1;
|
||||
$warmGray-400: #A8A29E;
|
||||
$warmGray-500: #78716C;
|
||||
$warmGray-600: #57534E;
|
||||
$warmGray-700: #44403C;
|
||||
$warmGray-800: #292524;
|
||||
$warmGray-900: #1C1917;
|
||||
|
||||
$red-50: #FEF2F2;
|
||||
$red-100:#FEE2E2;
|
||||
$red-200: #FECACA;
|
||||
$red-300: #FCA5A5;
|
||||
$red-400: #F87171;
|
||||
$red-500: #EF4444;
|
||||
$red-600: #DC2626;
|
||||
$red-700: #B91C1C;
|
||||
$red-800: #991B1B;
|
||||
$red-900: #7F1D1D;
|
||||
|
||||
$orange-50: #FFF7ED;
|
||||
$orange-100:#FFEDD5;
|
||||
$orange-200: #FED7AA;
|
||||
$orange-300: #FDBA74;
|
||||
$orange-400: #FB923C;
|
||||
$orange-500: #F97316;
|
||||
$orange-600: #EA580C;
|
||||
$orange-700: #C2410C;
|
||||
$orange-800: #9A3412;
|
||||
$orange-900: #7C2D12;
|
||||
|
||||
$amber-50: #FFFBEB;
|
||||
$amber-100:#FEF3C7;
|
||||
$amber-200: #FDE68A;
|
||||
$amber-300: #FCD34D;
|
||||
$amber-400: #FBBF24;
|
||||
$amber-500: #F59E0B;
|
||||
$amber-600: #D97706;
|
||||
$amber-700: #B45309;
|
||||
$amber-800: #92400E;
|
||||
$amber-900: #78350F;
|
||||
|
||||
$yellow-50: #FEFCE8;
|
||||
$yellow-100:#FEF9C3;
|
||||
$yellow-200: #FEF08A;
|
||||
$yellow-300: #FDE047;
|
||||
$yellow-400: #FACC15;
|
||||
$yellow-500: #EAB308;
|
||||
$yellow-600: #CA8A04;
|
||||
$yellow-700: #A16207;
|
||||
$yellow-800: #854D0E;
|
||||
$yellow-900: #713F12;
|
||||
|
||||
$lime-50: #F7FEE7;
|
||||
$lime-100: #ECFCCB;
|
||||
$lime-200: #D9F99D;
|
||||
$lime-300: #BEF264;
|
||||
$lime-400: #A3E635;
|
||||
$lime-500: #84CC16;
|
||||
$lime-600: #65A30D;
|
||||
$lime-700: #4D7C0F;
|
||||
$lime-800: #3F6212;
|
||||
$lime-900: #365314;
|
||||
|
||||
$green-50: #F0FDF4;
|
||||
$green-100: #DCFCE7;
|
||||
$green-200: #BBF7D0;
|
||||
$green-300: #86EFAC;
|
||||
$green-400: #4ADE80;
|
||||
$green-500: #22C55E;
|
||||
$green-600: #16A34A;
|
||||
$green-700: #15803D;
|
||||
$green-800: #166534;
|
||||
$green-900: #14532D;
|
||||
|
||||
$emerald-50: #ECFDF5;
|
||||
$emerald-100: #D1FAE5;
|
||||
$emerald-200: #A7F3D0;
|
||||
$emerald-300: #6EE7B7;
|
||||
$emerald-400: #34D399;
|
||||
$emerald-500: #10B981;
|
||||
$emerald-600: #059669;
|
||||
$emerald-700: #047857;
|
||||
$emerald-800: #065F46;
|
||||
$emerald-900: #064E3B;
|
||||
|
||||
$teal-50: #F0FDFA;
|
||||
$teal-100: #CCFBF1;
|
||||
$teal-200: #99F6E4;
|
||||
$teal-300: #5EEAD4;
|
||||
$teal-400: #2DD4BF;
|
||||
$teal-500: #14B8A6;
|
||||
$teal-600: #0D9488;
|
||||
$teal-700: #0F766E;
|
||||
$teal-800: #115E59;
|
||||
$teal-900: #134E4A;
|
||||
|
||||
$cyan-50: #ECFEFF;
|
||||
$cyan-100: #CFFAFE;
|
||||
$cyan-200: #A5F3FC;
|
||||
$cyan-300: #67E8F9;
|
||||
$cyan-400: #22D3EE;
|
||||
$cyan-500: #06B6D4;
|
||||
$cyan-600: #0891B2;
|
||||
$cyan-700: #0E7490;
|
||||
$cyan-800: #155E75;
|
||||
$cyan-900: #164E63;
|
||||
|
||||
$lightBlue-50: #F0F9FF;
|
||||
$lightBlue-100: #E0F2FE;
|
||||
$lightBlue-200: #BAE6FD;
|
||||
$lightBlue-300: #7DD3FC;
|
||||
$lightBlue-400: #38BDF8;
|
||||
$lightBlue-500: #0EA5E9;
|
||||
$lightBlue-600: #0284C7;
|
||||
$lightBlue-700: #0369A1;
|
||||
$lightBlue-800: #075985;
|
||||
$lightBlue-900: #0C4A6E;
|
||||
|
||||
$blue-50: #EFF6FF;
|
||||
$blue-100: #DBEAFE;
|
||||
$blue-200: #BFDBFE;
|
||||
$blue-300: #93C5FD;
|
||||
$blue-400: #60A5FA;
|
||||
$blue-500: #3B82F6;
|
||||
$blue-600: #2563EB;
|
||||
$blue-700: #1D4ED8;
|
||||
$blue-800: #1E40AF;
|
||||
$blue-900: #1E3A8A;
|
||||
|
||||
$indigo-50: #EEF2FF;
|
||||
$indigo-100: #E0E7FF;
|
||||
$indigo-200: #C7D2FE;
|
||||
$indigo-300: #A5B4FC;
|
||||
$indigo-400: #818CF8;
|
||||
$indigo-500: #6366F1;
|
||||
$indigo-600: #4F46E5;
|
||||
$indigo-700: #4338CA;
|
||||
$indigo-800: #3730A3;
|
||||
$indigo-900: #312E81;
|
||||
|
||||
$violet-50: #F5F3FF;
|
||||
$violet-100: #EDE9FE;
|
||||
$violet-200: #DDD6FE;
|
||||
$violet-300: #C4B5FD;
|
||||
$violet-400: #A78BFA;
|
||||
$violet-500: #8B5CF6;
|
||||
$violet-600: #7C3AED;
|
||||
$violet-700: #6D28D9;
|
||||
$violet-800: #5B21B6;
|
||||
$violet-900: #4C1D95;
|
||||
|
||||
$purple-50: #FAF5FF;
|
||||
$purple-100: #F3E8FF;
|
||||
$purple-200: #E9D5FF;
|
||||
$purple-300: #D8B4FE;
|
||||
$purple-400: #C084FC;
|
||||
$purple-500: #A855F7;
|
||||
$purple-600: #9333EA;
|
||||
$purple-700: #7E22CE;
|
||||
$purple-800: #6B21A8;
|
||||
$purple-900: #581C87;
|
||||
|
||||
$fuchsia-50: #FDF4FF;
|
||||
$fuchsia-100: #FAE8FF;
|
||||
$fuchsia-200: #F5D0FE;
|
||||
$fuchsia-300: #F0ABFC;
|
||||
$fuchsia-400: #E879F9;
|
||||
$fuchsia-500: #D946EF;
|
||||
$fuchsia-600: #C026D3;
|
||||
$fuchsia-700: #A21CAF;
|
||||
$fuchsia-800: #86198F;
|
||||
$fuchsia-900: #701A75;
|
||||
|
||||
$pink-50: #FDF2F8;
|
||||
$pink-100: #FCE7F3;
|
||||
$pink-200: #FBCFE8;
|
||||
$pink-300: #F9A8D4;
|
||||
$pink-400: #F472B6;
|
||||
$pink-500: #EC4899;
|
||||
$pink-600: #DB2777;
|
||||
$pink-700: #BE185D;
|
||||
$pink-800: #9D174D;
|
||||
$pink-900: #831843;
|
||||
|
||||
$rose-50: #FFF1F2;
|
||||
$rose-100: #FFE4E6;
|
||||
$rose-200: #FECDD3;
|
||||
$rose-300: #FDA4AF;
|
||||
$rose-400: #FB7185;
|
||||
$rose-500: #F43F5E;
|
||||
$rose-600: #E11D48;
|
||||
$rose-700: #BE123C;
|
||||
$rose-800: #9F1239;
|
||||
$rose-900: #881337;
|
||||
|
||||
.activity-log {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.log-event {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: 80%;
|
||||
padding: 4px;
|
||||
margin: 4px;
|
||||
border-radius: 5px;
|
||||
border-top-left-radius: 0;
|
||||
color: black;
|
||||
|
||||
.activity {
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
|
||||
.actor {
|
||||
font-style: italic;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.type {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.object {
|
||||
}
|
||||
|
||||
.activity {
|
||||
border-left: 4px solid $blue-300;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
background-color: $gray-300;
|
||||
}
|
||||
}
|
||||
|
||||
.source {
|
||||
font-family: monospace;
|
||||
overflow: auto;
|
||||
background-color: $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
.metadata {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.path {
|
||||
color: $gray-500;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.source-button {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background-color: inherit;
|
||||
color: $blue-400;
|
||||
}
|
||||
}
|
||||
|
||||
.inbound {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.outbound {
|
||||
align-self: flex-end;
|
||||
background-color: #d9fdd3;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -919,6 +919,17 @@ code {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $highlight-text-color;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-pagination {
|
||||
|
|
58
app/lib/activity_log_audience_helper.rb
Normal file
58
app/lib/activity_log_audience_helper.rb
Normal file
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityLogAudienceHelper
|
||||
|
||||
def self.audience(activity_log_event)
|
||||
domain = Rails.configuration.x.web_domain
|
||||
|
||||
if activity_log_event.type == 'outbound'
|
||||
actor = activity_log_event.data['actor']
|
||||
|
||||
if actor and match = actor.match(Regexp.new("https://#{domain}/users/([^/]*)"))
|
||||
[match.captures[0]]
|
||||
else
|
||||
[]
|
||||
end
|
||||
elsif activity_log_event.type == 'inbound'
|
||||
if match = activity_log_event.path.match(Regexp.new("https://#{domain}/users/([^/]*)/inbox"))
|
||||
[match.captures[0]]
|
||||
elsif activity_log_event.path == "https://#{domain}/inbox"
|
||||
['to', 'bto', 'cc', 'bcc']
|
||||
.map { |target| actors(activity_log_event.data[target]) }
|
||||
.flatten
|
||||
.uniq
|
||||
else
|
||||
[]
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.actors(string_or_array)
|
||||
domain = Rails.configuration.x.web_domain
|
||||
|
||||
if string_or_array.nil?
|
||||
[]
|
||||
elsif string_or_array.is_a?(String)
|
||||
self.actors([string_or_array])
|
||||
else
|
||||
string_or_array.map do |string|
|
||||
if match = string.match(Regexp.new("https://#{domain}/users/([^/]*)"))
|
||||
match.captures[0]
|
||||
elsif string.ends_with?("/followers")
|
||||
Account
|
||||
.joins(
|
||||
"JOIN follows ON follows.account_id = accounts.id
|
||||
JOIN accounts AS followed ON follows.target_account_id = followed.id
|
||||
WHERE followed.followers_url = '#{string}'")
|
||||
.map { |account| account.username }
|
||||
else
|
||||
nil
|
||||
end
|
||||
end.flatten.compact
|
||||
end
|
||||
end
|
||||
end
|
18
app/lib/activity_log_event.rb
Normal file
18
app/lib/activity_log_event.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityLogEvent
|
||||
attr_accessor :type, :path, :data, :timestamp
|
||||
|
||||
def self.from_json_string(json_string)
|
||||
json = Oj.load(json_string, mode: :strict)
|
||||
ActivityLogEvent.new(json['type'], json['path'], json['data'])
|
||||
end
|
||||
|
||||
|
||||
def initialize(type, path, data, timestamp = Time.now.utc.iso8601)
|
||||
@type = type
|
||||
@path = path
|
||||
@data = data
|
||||
@timestamp = timestamp
|
||||
end
|
||||
end
|
10
app/lib/activity_log_publisher.rb
Normal file
10
app/lib/activity_log_publisher.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class ActivityLogPublisher
|
||||
|
||||
def initialize
|
||||
@redis = RedisConfiguration.new.connection
|
||||
end
|
||||
|
||||
def publish(log_event)
|
||||
@redis.publish('activity_log', Oj.dump(log_event))
|
||||
end
|
||||
end
|
26
app/lib/activity_logger.rb
Normal file
26
app/lib/activity_logger.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityLogger
|
||||
|
||||
@@loggers = Hash.new { |hash, key| hash[key] = [] }
|
||||
|
||||
def self.register(id, sse)
|
||||
@@loggers[id] << sse
|
||||
end
|
||||
|
||||
def self.unregister(id, sse)
|
||||
@@loggers[id].delete(sse)
|
||||
end
|
||||
|
||||
def self.log(id, event)
|
||||
@@loggers[id].each do |logger|
|
||||
logger.write event
|
||||
rescue
|
||||
puts 'rescued'
|
||||
end
|
||||
end
|
||||
|
||||
def self.reset
|
||||
@@loggers.clear
|
||||
end
|
||||
end
|
|
@ -34,14 +34,6 @@ class AccountMigration < ApplicationRecord
|
|||
attr_accessor :current_password, :current_username
|
||||
|
||||
def save_with_challenge(current_user)
|
||||
if current_user.encrypted_password.present?
|
||||
errors.add(:current_password, :invalid) unless current_user.valid_password?(current_password)
|
||||
else
|
||||
errors.add(:current_username, :invalid) unless account.username == current_username
|
||||
end
|
||||
|
||||
return false unless errors.empty?
|
||||
|
||||
with_lock("account_migration:#{account.id}") do
|
||||
save
|
||||
end
|
||||
|
|
|
@ -12,14 +12,6 @@ class Form::Redirect
|
|||
validate :validate_target_account
|
||||
|
||||
def valid_with_challenge?(current_user)
|
||||
if current_user.encrypted_password.present?
|
||||
errors.add(:current_password, :invalid) unless current_user.valid_password?(current_password)
|
||||
else
|
||||
errors.add(:current_username, :invalid) unless account.username == current_username
|
||||
end
|
||||
|
||||
return false unless errors.empty?
|
||||
|
||||
set_target_account
|
||||
valid?
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RegistrationFormTimeValidator < ActiveModel::Validator
|
||||
REGISTRATION_FORM_MIN_TIME = 3.seconds.freeze
|
||||
REGISTRATION_FORM_MIN_TIME = 0.5.seconds.freeze
|
||||
|
||||
def validate(user)
|
||||
user.errors.add(:base, I18n.t('auth.too_fast')) if user.registration_form_time.present? && user.registration_form_time > REGISTRATION_FORM_MIN_TIME.ago
|
||||
|
|
|
@ -3,31 +3,6 @@
|
|||
|
||||
= render 'status'
|
||||
|
||||
%h3= t('auth.security')
|
||||
|
||||
= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit', novalidate: false }) do |f|
|
||||
= render 'shared/error_messages', object: resource
|
||||
|
||||
- if !use_seamless_external_login? || resource.encrypted_password.present?
|
||||
.fields-row
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
= f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, disabled: current_account.suspended?
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
= f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'current-password' }, required: true, disabled: current_account.suspended?, hint: false
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
= f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended?
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
= f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'new-password' }, disabled: current_account.suspended?
|
||||
|
||||
.actions
|
||||
= f.button :button, t('generic.save_changes'), type: :submit, class: 'button', disabled: current_account.suspended?
|
||||
- else
|
||||
%p.hint= t('users.seamless_external_login')
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
= render 'sessions'
|
||||
|
||||
- unless current_account.suspended?
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { novalidate: false }) do |f|
|
||||
%h1.title= t('auth.sign_up.title', domain: site_hostname)
|
||||
%p.lead= t('auth.sign_up.preamble')
|
||||
%p.lead= t('auth.sign_up.activity_log_preamble_html')
|
||||
|
||||
= render 'shared/error_messages', object: resource
|
||||
|
||||
|
@ -16,12 +16,6 @@
|
|||
= render 'application/card', account: @invite.user.account
|
||||
|
||||
.fields-group
|
||||
= f.simple_fields_for :account do |ff|
|
||||
= ff.input :display_name, wrapper: :with_label, label: false, required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.display_name'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.display_name') }
|
||||
= ff.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: false
|
||||
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'username' }, hint: false
|
||||
= f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: false
|
||||
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'new-password' }, hint: false
|
||||
= f.input :confirm_password, as: :string, placeholder: t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), :autocomplete => 'off' }, hint: false
|
||||
= f.input :website, as: :url, wrapper: :with_label, label: t('simple_form.labels.defaults.honeypot', label: 'Website'), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: 'Website'), :autocomplete => 'off' }
|
||||
|
||||
|
@ -39,5 +33,3 @@
|
|||
|
||||
.actions
|
||||
= f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit
|
||||
|
||||
.form-footer= render 'auth/shared/links'
|
||||
|
|
|
@ -17,11 +17,5 @@
|
|||
.fields-row__column.fields-group.fields-row__column-6
|
||||
= f.input :acct, wrapper: :with_block_label, input_html: { autocapitalize: 'none', autocorrect: 'off' }, label: t('simple_form.labels.account_migration.acct'), hint: t('simple_form.hints.account_migration.acct')
|
||||
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
- if current_user.encrypted_password.present?
|
||||
= f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, required: true
|
||||
- else
|
||||
= f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true
|
||||
|
||||
.actions
|
||||
= f.button :button, t('migrations.set_redirect'), type: :submit, class: 'button button--destructive'
|
||||
|
|
|
@ -46,12 +46,6 @@
|
|||
.fields-row__column.fields-group.fields-row__column-6
|
||||
= f.input :acct, wrapper: :with_block_label, input_html: { autocapitalize: 'none', autocorrect: 'off' }, disabled: on_cooldown?
|
||||
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
- if current_user.encrypted_password.present?
|
||||
= f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, required: true, disabled: on_cooldown?
|
||||
- else
|
||||
= f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true, disabled: on_cooldown?
|
||||
|
||||
.actions
|
||||
= f.button :button, t('migrations.proceed_with_move'), type: :submit, class: 'button button--destructive', disabled: on_cooldown?
|
||||
|
||||
|
|
|
@ -12,9 +12,17 @@ class ActivityPub::DeliveryWorker
|
|||
|
||||
HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze
|
||||
|
||||
def initialize
|
||||
@activity_log_publisher = ActivityLogPublisher.new
|
||||
end
|
||||
|
||||
def perform(json, source_account_id, inbox_url, options = {})
|
||||
return unless DeliveryFailureTracker.available?(inbox_url)
|
||||
|
||||
event = ActivityLogEvent.new('outbound', inbox_url, Oj.load(json, mode: :strict))
|
||||
|
||||
@activity_log_publisher.publish(event)
|
||||
|
||||
@options = options.with_indifferent_access
|
||||
@json = json
|
||||
@source_account = Account.find(source_account_id)
|
||||
|
|
40
app/workers/scheduler/old_account_cleanup_scheduler.rb
Normal file
40
app/workers/scheduler/old_account_cleanup_scheduler.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Scheduler::OldAccountCleanupScheduler
|
||||
include Sidekiq::Worker
|
||||
|
||||
# Each processed deletion request may enqueue an enormous
|
||||
# amount of jobs in the `pull` queue, so only enqueue when
|
||||
# the queue is empty or close to being so.
|
||||
MAX_PULL_SIZE = 50
|
||||
|
||||
# Since account deletion is very expensive, we want to avoid
|
||||
# overloading the server by queing too much at once.
|
||||
MAX_DELETIONS_PER_JOB = 5
|
||||
|
||||
sidekiq_options retry: 0
|
||||
|
||||
def perform
|
||||
return if Sidekiq::Queue.new('pull').size > MAX_PULL_SIZE
|
||||
|
||||
clean_old_accounts!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def clean_old_accounts!
|
||||
Account
|
||||
# only fetch local accounts
|
||||
.where("domain IS NULL")
|
||||
# id -99 is the instance actor
|
||||
.where("id <> -99")
|
||||
# don't delete admin
|
||||
.where("username <> 'admin'")
|
||||
.where("created_at < ?", 1.day.ago)
|
||||
.order(created_at: :asc)
|
||||
.limit(MAX_DELETIONS_PER_JOB)
|
||||
.each do |account|
|
||||
AccountDeletionWorker.perform_async(account.id, { :reserve_username => false })
|
||||
end
|
||||
end
|
||||
end
|
|
@ -74,7 +74,8 @@ Rails.application.configure do
|
|||
# If using a Heroku, Vagrant or generic remote development environment,
|
||||
# use letter_opener_web, accessible at /letter_opener.
|
||||
# Otherwise, use letter_opener, which launches a browser window to view sent mail.
|
||||
config.action_mailer.delivery_method = (ENV['HEROKU'] || ENV['VAGRANT'] || ENV['REMOTE_DEV']) ? :letter_opener_web : :letter_opener
|
||||
# config.action_mailer.delivery_method = (ENV['HEROKU'] || ENV['VAGRANT'] || ENV['REMOTE_DEV']) ? :letter_opener_web : :letter_opener
|
||||
config.action_mailer.delivery_method = :file
|
||||
|
||||
config.after_initialize do
|
||||
Bullet.enable = true
|
||||
|
|
|
@ -130,7 +130,7 @@ Rails.application.configure do
|
|||
:ssl => ENV['SMTP_SSL'].presence && ENV['SMTP_SSL'] == 'true',
|
||||
}
|
||||
|
||||
config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym
|
||||
config.action_mailer.delivery_method = :file
|
||||
|
||||
config.action_dispatch.default_headers = {
|
||||
'Server' => 'Mastodon',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
en:
|
||||
devise:
|
||||
confirmations:
|
||||
confirmed: Your email address has been successfully confirmed.
|
||||
confirmed: Your account is fully functional.
|
||||
send_instructions: You will receive an email with instructions for how to confirm your email address in a few minutes. Please check your spam folder if you didn't receive this email.
|
||||
send_paranoid_instructions: If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes. Please check your spam folder if you didn't receive this email.
|
||||
failure:
|
||||
|
|
|
@ -953,6 +953,7 @@ en:
|
|||
title: Setup
|
||||
sign_up:
|
||||
preamble: With an account on this Mastodon server, you'll be able to follow any other person on the network, regardless of where their account is hosted.
|
||||
activity_log_preamble_html: This instance is intended to help you learn <a href="https://www.w3.org/TR/activitypub/">ActivityPub</a>, by showing Activities between different instances in real time. After signing up, you are given a fully functional Mastodon account. You can follow other accounts, create posts, repost, like, etc. These actions generate Activities following the ActivityPub spec, which will be shown to you in real time. You can learn more about this on my <a href="//seb.jambor.dev">blog</a>. Note that your account is only valid for 24 hours and will be deleted automatically afterwards.
|
||||
title: Let's get you set up on %{domain}.
|
||||
status:
|
||||
account_status: Account status
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require_relative '../lib/activity_log_subscriber'
|
||||
|
||||
persistent_timeout ENV.fetch('PERSISTENT_TIMEOUT') { 20 }.to_i
|
||||
|
||||
threads_count = ENV.fetch('MAX_THREADS') { 5 }.to_i
|
||||
|
@ -18,6 +20,10 @@ on_worker_boot do
|
|||
ActiveSupport.on_load(:active_record) do
|
||||
ActiveRecord::Base.establish_connection
|
||||
end
|
||||
|
||||
Thread.new {
|
||||
ActivityLogSubscriber.new.start
|
||||
}
|
||||
end
|
||||
|
||||
plugin :tmp_restart
|
||||
|
|
|
@ -24,6 +24,8 @@ Rails.application.routes.draw do
|
|||
/search
|
||||
/publish
|
||||
/follow_requests
|
||||
/activity_log
|
||||
/activitypub_explorer
|
||||
/blocks
|
||||
/domain_blocks
|
||||
/mutes
|
||||
|
@ -513,6 +515,10 @@ Rails.application.routes.draw do
|
|||
resources :confirmations, only: [:create]
|
||||
end
|
||||
|
||||
resource :activity_log, only: [:show], controller: 'activity_log'
|
||||
|
||||
get '/json_ld', to: 'json_ld#show'
|
||||
|
||||
resource :instance, only: [:show] do
|
||||
resources :peers, only: [:index], controller: 'instances/peers'
|
||||
resources :rules, only: [:index], controller: 'instances/rules'
|
||||
|
|
|
@ -58,3 +58,7 @@
|
|||
interval: 1 minute
|
||||
class: Scheduler::SuspendedUserCleanupScheduler
|
||||
queue: scheduler
|
||||
old_account_cleanup_scheduler:
|
||||
interval: 1 minute
|
||||
class: Scheduler::OldAccountCleanupScheduler
|
||||
queue: scheduler
|
||||
|
|
18
lib/activity_log_subscriber.rb
Normal file
18
lib/activity_log_subscriber.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
require 'redis'
|
||||
|
||||
class ActivityLogSubscriber
|
||||
def start
|
||||
redis = RedisConfiguration.new.connection
|
||||
|
||||
redis.subscribe('activity_log') do |on|
|
||||
on.message do |channel, message|
|
||||
json = Oj.load(message, mode: :strict)
|
||||
event = ActivityLogEvent.new(json['type'], json['path'], json['data'])
|
||||
|
||||
ActivityLogAudienceHelper.audience(event)
|
||||
.each { |username| ActivityLogger.log(username, event) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -35,6 +35,7 @@
|
|||
"@github/webauthn-json": "^0.5.7",
|
||||
"@rails/ujs": "^6.1.7",
|
||||
"abortcontroller-polyfill": "^1.7.5",
|
||||
"activitypub-visualization": "^1.1.0",
|
||||
"array-includes": "^3.1.5",
|
||||
"arrow-key-navigation": "^1.2.0",
|
||||
"autoprefixer": "^9.8.8",
|
||||
|
@ -152,7 +153,7 @@
|
|||
"eslint-plugin-import": "~2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "~6.6.1",
|
||||
"eslint-plugin-promise": "~6.1.1",
|
||||
"eslint-plugin-react": "~7.31.10",
|
||||
"eslint-plugin-react": "~7.32.2",
|
||||
"jest": "^29.2.2",
|
||||
"jest-environment-jsdom": "^29.2.1",
|
||||
"postcss-scss": "^4.0.5",
|
||||
|
|
12
spec/fixtures/activity_log_events/inbound-to-users-inbox.json
vendored
Normal file
12
spec/fixtures/activity_log_events/inbound-to-users-inbox.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"timestamp":"2022-12-08T17:12:38Z",
|
||||
"type": "inbound",
|
||||
"path": "https://example.com/users/bob/inbox",
|
||||
"data": {
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://other.org/a5f25e0a-98d6-4e5c-baad-65318cd4d67d",
|
||||
"type": "Follow",
|
||||
"actor": "https://other.org/users/alice",
|
||||
"object": "https://example.com/users/bob"
|
||||
}
|
||||
}
|
43
spec/fixtures/activity_log_events/inbound-with-duplicate-recipients.json
vendored
Normal file
43
spec/fixtures/activity_log_events/inbound-with-duplicate-recipients.json
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"timestamp":"2022-12-08T17:12:38Z",
|
||||
"type": "inbound",
|
||||
"path": "https://example.com/inbox",
|
||||
"data": {
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"actor": "https://other.org/users/bob/",
|
||||
"id": "https://other.org/users/bob/statuses/109473290785654613/activity",
|
||||
"object": {
|
||||
"attributedTo": "https://other.org/users/bob/",
|
||||
"content": "A post to selected audiences",
|
||||
"id": "https://other.org/users/bob/statuses/109473290785654613",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public",
|
||||
"https://example.com/users/first-to",
|
||||
"https://example.com/users/first-to",
|
||||
"https://example.com/users/second-to"
|
||||
],
|
||||
"bto": "https://example.com/users/first-to",
|
||||
"cc": [
|
||||
"https://example.com/users/second-to"
|
||||
],
|
||||
"bcc": [
|
||||
"https://example.com/users/first-to"
|
||||
],
|
||||
"type": "Note"
|
||||
},
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public",
|
||||
"https://example.com/users/first-to",
|
||||
"https://example.com/users/first-to",
|
||||
"https://example.com/users/second-to"
|
||||
],
|
||||
"bto": "https://example.com/users/first-to",
|
||||
"cc": [
|
||||
"https://example.com/users/second-to"
|
||||
],
|
||||
"bcc": [
|
||||
"https://example.com/users/first-to"
|
||||
],
|
||||
"type": "Create"
|
||||
}
|
||||
}
|
25
spec/fixtures/activity_log_events/inbound-with-follower-recipients.json
vendored
Normal file
25
spec/fixtures/activity_log_events/inbound-with-follower-recipients.json
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"timestamp":"2022-12-08T17:12:38Z",
|
||||
"type": "inbound",
|
||||
"path": "https://example.com/inbox",
|
||||
"data": {
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"actor": "https://other.org/users/bob/",
|
||||
"id": "https://other.org/users/bob/statuses/109473290785654613/activity",
|
||||
"object": {
|
||||
"attributedTo": "https://other.org/users/bob/",
|
||||
"content": "A post to selected audiences",
|
||||
"id": "https://other.org/users/bob/statuses/109473290785654613",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public",
|
||||
"https://other.org/users/bob/followers"
|
||||
],
|
||||
"type": "Note"
|
||||
},
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public",
|
||||
"https://other.org/users/bob/followers"
|
||||
],
|
||||
"type": "Create"
|
||||
}
|
||||
}
|
43
spec/fixtures/activity_log_events/inbound-with-multiple-recipients.json
vendored
Normal file
43
spec/fixtures/activity_log_events/inbound-with-multiple-recipients.json
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"timestamp":"2022-12-08T17:12:38Z",
|
||||
"type": "inbound",
|
||||
"path": "https://example.com/inbox",
|
||||
"data": {
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"actor": "https://other.org/users/bob/",
|
||||
"id": "https://other.org/users/bob/statuses/109473290785654613/activity",
|
||||
"object": {
|
||||
"attributedTo": "https://other.org/users/bob/",
|
||||
"content": "A post to selected audiences",
|
||||
"id": "https://other.org/users/bob/statuses/109473290785654613",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public",
|
||||
"https://example.com/users/first-to",
|
||||
"https://example.com/users/second-to",
|
||||
"https://other.org/users/other-instance"
|
||||
],
|
||||
"bto": "https://example.com/users/single-bto",
|
||||
"cc": [
|
||||
"https://example.com/users/one-cc"
|
||||
],
|
||||
"bcc": [
|
||||
"https://example.com/users/one-bcc"
|
||||
],
|
||||
"type": "Note"
|
||||
},
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public",
|
||||
"https://example.com/users/first-to",
|
||||
"https://example.com/users/second-to",
|
||||
"https://other.org/users/other-instance"
|
||||
],
|
||||
"bto": "https://example.com/users/single-bto",
|
||||
"cc": [
|
||||
"https://example.com/users/one-cc"
|
||||
],
|
||||
"bcc": [
|
||||
"https://example.com/users/one-bcc"
|
||||
],
|
||||
"type": "Create"
|
||||
}
|
||||
}
|
12
spec/fixtures/activity_log_events/outbound.json
vendored
Normal file
12
spec/fixtures/activity_log_events/outbound.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"timestamp":"2022-12-08T17:12:38Z",
|
||||
"type": "outbound",
|
||||
"path": "https://other.org/users/bob/inbox",
|
||||
"data": {
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://example.com/a5f25e0a-98d6-4e5c-baad-65318cd4d67d",
|
||||
"type": "Follow",
|
||||
"actor": "https://example.com/users/alice",
|
||||
"object": "https://other.org/users/bob"
|
||||
}
|
||||
}
|
89
spec/lib/activity_log_audience_helper_spec.rb
Normal file
89
spec/lib/activity_log_audience_helper_spec.rb
Normal file
|
@ -0,0 +1,89 @@
|
|||
require 'json'
|
||||
require 'rails_helper'
|
||||
|
||||
def activity_log_event_fixture(name)
|
||||
json_string = File.read(Rails.root.join('spec', 'fixtures', 'activity_log_events', name))
|
||||
|
||||
ActivityLogEvent.from_json_string(json_string)
|
||||
end
|
||||
|
||||
RSpec.describe ActivityLogAudienceHelper do
|
||||
describe '#audience' do
|
||||
around do |example|
|
||||
before = Rails.configuration.x.web_domain
|
||||
example.run
|
||||
Rails.configuration.x.web_domain = before
|
||||
end
|
||||
|
||||
describe 'for inbound events' do
|
||||
it 'returns the author if the domain matches' do
|
||||
Rails.configuration.x.web_domain = 'example.com'
|
||||
outbound_event = activity_log_event_fixture('outbound.json')
|
||||
|
||||
expect(ActivityLogAudienceHelper.audience(outbound_event)).to eq ['alice']
|
||||
end
|
||||
|
||||
it 'returns nothing if the domain does not match' do
|
||||
Rails.configuration.x.web_domain = 'does-not-match.com'
|
||||
outbound_event = activity_log_event_fixture('outbound.json')
|
||||
|
||||
expect(ActivityLogAudienceHelper.audience(outbound_event)).to eq []
|
||||
end
|
||||
|
||||
it 'returns nothing if the activity does not have an actor' do
|
||||
Rails.configuration.x.web_domain = 'example.com'
|
||||
outbound_event = activity_log_event_fixture('outbound.json')
|
||||
outbound_event.data.delete('actor')
|
||||
|
||||
expect(ActivityLogAudienceHelper.audience(outbound_event)).to eq []
|
||||
end
|
||||
end
|
||||
|
||||
describe 'for outbound events' do
|
||||
it 'returns the inbox owner if it is sent to a personal inbox' do
|
||||
Rails.configuration.x.web_domain = 'example.com'
|
||||
inbound_event = activity_log_event_fixture('inbound-to-users-inbox.json')
|
||||
|
||||
expect(ActivityLogAudienceHelper.audience(inbound_event)).to eq ['bob']
|
||||
end
|
||||
|
||||
it 'returns direct audience from to, bto, cc, bcc if sent to public inbox' do
|
||||
Rails.configuration.x.web_domain = 'example.com'
|
||||
inbound_event = activity_log_event_fixture('inbound-with-multiple-recipients.json')
|
||||
|
||||
expect(ActivityLogAudienceHelper.audience(inbound_event)).to match_array([
|
||||
'first-to',
|
||||
'second-to',
|
||||
'single-bto',
|
||||
'one-cc',
|
||||
'one-bcc'
|
||||
])
|
||||
end
|
||||
|
||||
it 'returns followers from to, bto, cc, bcc if sent to public inbox' do
|
||||
Rails.configuration.x.web_domain = 'example.com'
|
||||
inbound_event = activity_log_event_fixture('inbound-with-follower-recipients.json')
|
||||
|
||||
bob = Fabricate(:account, username: 'bob', followers_url: 'https://other.org/users/bob/followers')
|
||||
Fabricate(:account, username: 'first_follower').follow!(bob)
|
||||
Fabricate(:account, username: 'second_follower').follow!(bob)
|
||||
|
||||
expect(ActivityLogAudienceHelper.audience(inbound_event)).to match_array([
|
||||
'first_follower',
|
||||
'second_follower',
|
||||
])
|
||||
end
|
||||
|
||||
it 'removes duplicates from audience' do
|
||||
Rails.configuration.x.web_domain = 'example.com'
|
||||
inbound_event = activity_log_event_fixture('inbound-with-duplicate-recipients.json')
|
||||
|
||||
expect(ActivityLogAudienceHelper.audience(inbound_event)).to match_array([
|
||||
'first-to',
|
||||
'second-to'
|
||||
])
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
73
spec/lib/activity_logger_spec.rb
Normal file
73
spec/lib/activity_logger_spec.rb
Normal file
|
@ -0,0 +1,73 @@
|
|||
require 'json'
|
||||
require 'rails_helper'
|
||||
|
||||
def activity_log_event_fixture(name)
|
||||
json_string = File.read(Rails.root.join('spec', 'fixtures', 'activity_log_events', name))
|
||||
|
||||
ActivityLogEvent.from_json_string(json_string)
|
||||
end
|
||||
|
||||
RSpec.describe ActivityLogger do
|
||||
|
||||
after(:each) do
|
||||
ActivityLogger.reset
|
||||
end
|
||||
|
||||
describe 'log' do
|
||||
|
||||
it 'sends events to all listeners of the same id' do
|
||||
sse1 = spy('sse1')
|
||||
sse2 = spy('sse2')
|
||||
event = double('event')
|
||||
|
||||
ActivityLogger.register('test_id', sse1)
|
||||
ActivityLogger.register('test_id', sse2)
|
||||
|
||||
ActivityLogger.log('test_id', event)
|
||||
|
||||
expect(sse1).to have_received(:write).with(event)
|
||||
expect(sse2).to have_received(:write).with(event)
|
||||
end
|
||||
|
||||
it 'sends events to all listeners even if some fail' do
|
||||
sse1 = spy('sse1')
|
||||
sse2 = spy('sse2')
|
||||
event = double('event')
|
||||
|
||||
allow(sse1).to receive(:write).and_throw(:error)
|
||||
|
||||
ActivityLogger.register('test_id', sse1)
|
||||
ActivityLogger.register('test_id', sse2)
|
||||
|
||||
ActivityLogger.log('test_id', event)
|
||||
|
||||
expect(sse2).to have_received(:write).with(event)
|
||||
end
|
||||
|
||||
it 'does not send events to listeners of a different id' do
|
||||
sse = spy('sse')
|
||||
event = double('event')
|
||||
|
||||
ActivityLogger.register('test_id', sse)
|
||||
|
||||
ActivityLogger.log('other_id', event)
|
||||
|
||||
expect(sse).to_not have_received(:write).with(event)
|
||||
end
|
||||
|
||||
it 'does not send events to listeners after unregistering' do
|
||||
sse1 = spy('sse1')
|
||||
sse2 = spy('sse2')
|
||||
event = double('event')
|
||||
|
||||
ActivityLogger.register('test_id', sse1)
|
||||
ActivityLogger.register('test_id', sse2)
|
||||
ActivityLogger.unregister('test_id', sse1)
|
||||
|
||||
ActivityLogger.log('test_id', event)
|
||||
|
||||
expect(sse1).to_not have_received(:write).with(event)
|
||||
expect(sse2).to have_received(:write).with(event)
|
||||
end
|
||||
end
|
||||
end
|
337
yarn.lock
337
yarn.lock
|
@ -2322,6 +2322,11 @@ acorn@^8.0.4, acorn@^8.1.0, acorn@^8.5.0, acorn@^8.8.0:
|
|||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
|
||||
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
|
||||
|
||||
activitypub-visualization@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/activitypub-visualization/-/activitypub-visualization-1.1.0.tgz#db7875657aa3215f6d7be16795964c82c1021efe"
|
||||
integrity sha512-0DrnCdmpx5551q0vZiQYHu3ZsVVFP0JJKLHQpbLmgetVJxGjRoZC7iwjh+tvyYixBhUneIAhVJ2VH0bugiAwFA==
|
||||
|
||||
agent-base@6:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||
|
@ -2533,15 +2538,15 @@ array-flatten@^2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
|
||||
integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==
|
||||
|
||||
array-includes@^3.1.4, array-includes@^3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb"
|
||||
integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==
|
||||
array-includes@^3.1.4, array-includes@^3.1.5, array-includes@^3.1.6:
|
||||
version "3.1.6"
|
||||
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f"
|
||||
integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.19.5"
|
||||
get-intrinsic "^1.1.1"
|
||||
es-abstract "^1.20.4"
|
||||
get-intrinsic "^1.1.3"
|
||||
is-string "^1.0.7"
|
||||
|
||||
array-union@^1.0.1:
|
||||
|
@ -2575,16 +2580,27 @@ array.prototype.flat@^1.2.5:
|
|||
define-properties "^1.1.3"
|
||||
es-abstract "^1.19.0"
|
||||
|
||||
array.prototype.flatmap@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz#a7e8ed4225f4788a70cd910abcf0791e76a5534f"
|
||||
integrity sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==
|
||||
array.prototype.flatmap@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183"
|
||||
integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.19.2"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
es-shim-unscopables "^1.0.0"
|
||||
|
||||
array.prototype.tosorted@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz#ccf44738aa2b5ac56578ffda97c03fd3e23dd532"
|
||||
integrity sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
es-shim-unscopables "^1.0.0"
|
||||
get-intrinsic "^1.1.3"
|
||||
|
||||
arrify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
|
||||
|
@ -2678,6 +2694,11 @@ autoprefixer@^9.8.8:
|
|||
postcss "^7.0.32"
|
||||
postcss-value-parser "^4.1.0"
|
||||
|
||||
available-typed-arrays@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
||||
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
|
||||
|
||||
axe-core@^4.4.3:
|
||||
version "4.4.3"
|
||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f"
|
||||
|
@ -4571,34 +4592,53 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.18.0-next.0, es-
|
|||
string.prototype.trimstart "^1.0.5"
|
||||
unbox-primitive "^1.0.2"
|
||||
|
||||
es-abstract@^1.19.2:
|
||||
version "1.20.1"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814"
|
||||
integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==
|
||||
es-abstract@^1.20.4:
|
||||
version "1.21.1"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.1.tgz#e6105a099967c08377830a0c9cb589d570dd86c6"
|
||||
integrity sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==
|
||||
dependencies:
|
||||
available-typed-arrays "^1.0.5"
|
||||
call-bind "^1.0.2"
|
||||
es-set-tostringtag "^2.0.1"
|
||||
es-to-primitive "^1.2.1"
|
||||
function-bind "^1.1.1"
|
||||
function.prototype.name "^1.1.5"
|
||||
get-intrinsic "^1.1.1"
|
||||
get-intrinsic "^1.1.3"
|
||||
get-symbol-description "^1.0.0"
|
||||
globalthis "^1.0.3"
|
||||
gopd "^1.0.1"
|
||||
has "^1.0.3"
|
||||
has-property-descriptors "^1.0.0"
|
||||
has-proto "^1.0.1"
|
||||
has-symbols "^1.0.3"
|
||||
internal-slot "^1.0.3"
|
||||
is-callable "^1.2.4"
|
||||
internal-slot "^1.0.4"
|
||||
is-array-buffer "^3.0.1"
|
||||
is-callable "^1.2.7"
|
||||
is-negative-zero "^2.0.2"
|
||||
is-regex "^1.1.4"
|
||||
is-shared-array-buffer "^1.0.2"
|
||||
is-string "^1.0.7"
|
||||
is-typed-array "^1.1.10"
|
||||
is-weakref "^1.0.2"
|
||||
object-inspect "^1.12.0"
|
||||
object-inspect "^1.12.2"
|
||||
object-keys "^1.1.1"
|
||||
object.assign "^4.1.2"
|
||||
object.assign "^4.1.4"
|
||||
regexp.prototype.flags "^1.4.3"
|
||||
string.prototype.trimend "^1.0.5"
|
||||
string.prototype.trimstart "^1.0.5"
|
||||
safe-regex-test "^1.0.0"
|
||||
string.prototype.trimend "^1.0.6"
|
||||
string.prototype.trimstart "^1.0.6"
|
||||
typed-array-length "^1.0.4"
|
||||
unbox-primitive "^1.0.2"
|
||||
which-typed-array "^1.1.9"
|
||||
|
||||
es-set-tostringtag@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8"
|
||||
integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==
|
||||
dependencies:
|
||||
get-intrinsic "^1.1.3"
|
||||
has "^1.0.3"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
es-shim-unscopables@^1.0.0:
|
||||
version "1.0.0"
|
||||
|
@ -4738,25 +4778,26 @@ eslint-plugin-promise@~6.1.1:
|
|||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz#269a3e2772f62875661220631bd4dafcb4083816"
|
||||
integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==
|
||||
|
||||
eslint-plugin-react@~7.31.10:
|
||||
version "7.31.10"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz#6782c2c7fe91c09e715d536067644bbb9491419a"
|
||||
integrity sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==
|
||||
eslint-plugin-react@~7.32.2:
|
||||
version "7.32.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz#e71f21c7c265ebce01bcbc9d0955170c55571f10"
|
||||
integrity sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==
|
||||
dependencies:
|
||||
array-includes "^3.1.5"
|
||||
array.prototype.flatmap "^1.3.0"
|
||||
array-includes "^3.1.6"
|
||||
array.prototype.flatmap "^1.3.1"
|
||||
array.prototype.tosorted "^1.1.1"
|
||||
doctrine "^2.1.0"
|
||||
estraverse "^5.3.0"
|
||||
jsx-ast-utils "^2.4.1 || ^3.0.0"
|
||||
minimatch "^3.1.2"
|
||||
object.entries "^1.1.5"
|
||||
object.fromentries "^2.0.5"
|
||||
object.hasown "^1.1.1"
|
||||
object.values "^1.1.5"
|
||||
object.entries "^1.1.6"
|
||||
object.fromentries "^2.0.6"
|
||||
object.hasown "^1.1.2"
|
||||
object.values "^1.1.6"
|
||||
prop-types "^15.8.1"
|
||||
resolve "^2.0.0-next.3"
|
||||
resolve "^2.0.0-next.4"
|
||||
semver "^6.3.0"
|
||||
string.prototype.matchall "^4.0.7"
|
||||
string.prototype.matchall "^4.0.8"
|
||||
|
||||
eslint-scope@5.1.1, eslint-scope@^5.1.1:
|
||||
version "5.1.1"
|
||||
|
@ -5272,6 +5313,13 @@ font-awesome@^4.7.0:
|
|||
resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133"
|
||||
integrity sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=
|
||||
|
||||
for-each@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
|
||||
integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
|
||||
dependencies:
|
||||
is-callable "^1.1.3"
|
||||
|
||||
for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
|
@ -5433,6 +5481,15 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
|
|||
has "^1.0.3"
|
||||
has-symbols "^1.0.1"
|
||||
|
||||
get-intrinsic@^1.1.3:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f"
|
||||
integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
has "^1.0.3"
|
||||
has-symbols "^1.0.3"
|
||||
|
||||
get-own-enumerable-property-symbols@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
|
||||
|
@ -5554,6 +5611,13 @@ globals@^13.6.0, globals@^13.9.0:
|
|||
dependencies:
|
||||
type-fest "^0.20.2"
|
||||
|
||||
globalthis@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf"
|
||||
integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==
|
||||
dependencies:
|
||||
define-properties "^1.1.3"
|
||||
|
||||
globby@^11.1.0:
|
||||
version "11.1.0"
|
||||
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
|
||||
|
@ -5582,6 +5646,13 @@ globjoin@^0.1.4:
|
|||
resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
|
||||
integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==
|
||||
|
||||
gopd@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
|
||||
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
|
||||
dependencies:
|
||||
get-intrinsic "^1.1.3"
|
||||
|
||||
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9:
|
||||
version "4.2.9"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
|
||||
|
@ -5638,6 +5709,11 @@ has-property-descriptors@^1.0.0:
|
|||
dependencies:
|
||||
get-intrinsic "^1.1.1"
|
||||
|
||||
has-proto@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
|
||||
integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
|
||||
|
||||
has-symbols@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
|
||||
|
@ -6110,6 +6186,15 @@ internal-slot@^1.0.3:
|
|||
has "^1.0.3"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
internal-slot@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.4.tgz#8551e7baf74a7a6ba5f749cfb16aa60722f0d6f3"
|
||||
integrity sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==
|
||||
dependencies:
|
||||
get-intrinsic "^1.1.3"
|
||||
has "^1.0.3"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
interpret@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
|
||||
|
@ -6212,6 +6297,15 @@ is-arguments@^1.0.4:
|
|||
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3"
|
||||
integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==
|
||||
|
||||
is-array-buffer@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.1.tgz#deb1db4fcae48308d54ef2442706c0393997052a"
|
||||
integrity sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
get-intrinsic "^1.1.3"
|
||||
is-typed-array "^1.1.10"
|
||||
|
||||
is-arrayish@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
||||
|
@ -6248,6 +6342,11 @@ is-boolean-object@^1.1.0:
|
|||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
|
||||
is-callable@^1.1.3, is-callable@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
|
||||
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
|
||||
|
||||
is-callable@^1.1.4:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9"
|
||||
|
@ -6270,17 +6369,24 @@ is-color-stop@^1.0.0:
|
|||
rgb-regex "^1.0.1"
|
||||
rgba-regex "^1.0.0"
|
||||
|
||||
is-core-module@^2.2.0, is-core-module@^2.8.1:
|
||||
is-core-module@^2.5.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
|
||||
integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
is-core-module@^2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
|
||||
integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
is-core-module@^2.5.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
|
||||
integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
|
||||
is-core-module@^2.9.0:
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
|
||||
integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
|
@ -6526,6 +6632,17 @@ is-symbol@^1.0.3:
|
|||
dependencies:
|
||||
has-symbols "^1.0.2"
|
||||
|
||||
is-typed-array@^1.1.10, is-typed-array@^1.1.9:
|
||||
version "1.1.10"
|
||||
resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f"
|
||||
integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==
|
||||
dependencies:
|
||||
available-typed-arrays "^1.0.5"
|
||||
call-bind "^1.0.2"
|
||||
for-each "^0.3.3"
|
||||
gopd "^1.0.1"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-url@^1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
|
||||
|
@ -8050,6 +8167,11 @@ object-inspect@^1.12.0:
|
|||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
|
||||
integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==
|
||||
|
||||
object-inspect@^1.12.2:
|
||||
version "1.12.3"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9"
|
||||
integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
|
||||
|
||||
object-inspect@^1.9.0:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a"
|
||||
|
@ -8095,23 +8217,33 @@ object.assign@^4.1.2:
|
|||
has-symbols "^1.0.1"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
object.entries@^1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861"
|
||||
integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==
|
||||
object.assign@^4.1.4:
|
||||
version "4.1.4"
|
||||
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
|
||||
integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.19.1"
|
||||
define-properties "^1.1.4"
|
||||
has-symbols "^1.0.3"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
object.fromentries@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.5.tgz#7b37b205109c21e741e605727fe8b0ad5fa08251"
|
||||
integrity sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==
|
||||
object.entries@^1.1.6:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.6.tgz#9737d0e5b8291edd340a3e3264bb8a3b00d5fa23"
|
||||
integrity sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.19.1"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
object.fromentries@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.6.tgz#cdb04da08c539cffa912dcd368b886e0904bfa73"
|
||||
integrity sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
object.getownpropertydescriptors@^2.1.0:
|
||||
version "2.1.0"
|
||||
|
@ -8121,13 +8253,13 @@ object.getownpropertydescriptors@^2.1.0:
|
|||
define-properties "^1.1.3"
|
||||
es-abstract "^1.17.0-next.1"
|
||||
|
||||
object.hasown@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.1.tgz#ad1eecc60d03f49460600430d97f23882cf592a3"
|
||||
integrity sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==
|
||||
object.hasown@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.2.tgz#f919e21fad4eb38a57bc6345b3afd496515c3f92"
|
||||
integrity sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==
|
||||
dependencies:
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.19.5"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
object.pick@^1.3.0:
|
||||
version "1.3.0"
|
||||
|
@ -8136,14 +8268,14 @@ object.pick@^1.3.0:
|
|||
dependencies:
|
||||
isobject "^3.0.1"
|
||||
|
||||
object.values@^1.1.0, object.values@^1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac"
|
||||
integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==
|
||||
object.values@^1.1.0, object.values@^1.1.5, object.values@^1.1.6:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d"
|
||||
integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.19.1"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
obuf@^1.0.0, obuf@^1.1.2:
|
||||
version "1.1.2"
|
||||
|
@ -8459,7 +8591,7 @@ path-key@^3.0.0, path-key@^3.1.0:
|
|||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
|
||||
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
|
||||
|
||||
path-parse@^1.0.6, path-parse@^1.0.7:
|
||||
path-parse@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
@ -9895,13 +10027,14 @@ resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.2
|
|||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
resolve@^2.0.0-next.3:
|
||||
version "2.0.0-next.3"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46"
|
||||
integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==
|
||||
resolve@^2.0.0-next.4:
|
||||
version "2.0.0-next.4"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660"
|
||||
integrity sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==
|
||||
dependencies:
|
||||
is-core-module "^2.2.0"
|
||||
path-parse "^1.0.6"
|
||||
is-core-module "^2.9.0"
|
||||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
ret@~0.1.10:
|
||||
version "0.1.15"
|
||||
|
@ -9991,6 +10124,15 @@ safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0,
|
|||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
safe-regex-test@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295"
|
||||
integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
get-intrinsic "^1.1.3"
|
||||
is-regex "^1.1.4"
|
||||
|
||||
safe-regex@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
|
||||
|
@ -10620,18 +10762,18 @@ string-width@^3.0.0, string-width@^3.1.0:
|
|||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^5.1.0"
|
||||
|
||||
string.prototype.matchall@^4.0.6, string.prototype.matchall@^4.0.7:
|
||||
version "4.0.7"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d"
|
||||
integrity sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==
|
||||
string.prototype.matchall@^4.0.6, string.prototype.matchall@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3"
|
||||
integrity sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.19.1"
|
||||
get-intrinsic "^1.1.1"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
get-intrinsic "^1.1.3"
|
||||
has-symbols "^1.0.3"
|
||||
internal-slot "^1.0.3"
|
||||
regexp.prototype.flags "^1.4.1"
|
||||
regexp.prototype.flags "^1.4.3"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
string.prototype.trimend@^1.0.5:
|
||||
|
@ -10643,6 +10785,15 @@ string.prototype.trimend@^1.0.5:
|
|||
define-properties "^1.1.4"
|
||||
es-abstract "^1.19.5"
|
||||
|
||||
string.prototype.trimend@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533"
|
||||
integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
string.prototype.trimstart@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef"
|
||||
|
@ -10652,6 +10803,15 @@ string.prototype.trimstart@^1.0.5:
|
|||
define-properties "^1.1.4"
|
||||
es-abstract "^1.19.5"
|
||||
|
||||
string.prototype.trimstart@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4"
|
||||
integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
string_decoder@^1.0.0, string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
||||
|
@ -11312,6 +11472,15 @@ type@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3"
|
||||
integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==
|
||||
|
||||
typed-array-length@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb"
|
||||
integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
for-each "^0.3.3"
|
||||
is-typed-array "^1.1.9"
|
||||
|
||||
typedarray@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
|
@ -11879,6 +12048,18 @@ which-module@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
|
||||
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
|
||||
|
||||
which-typed-array@^1.1.9:
|
||||
version "1.1.9"
|
||||
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6"
|
||||
integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==
|
||||
dependencies:
|
||||
available-typed-arrays "^1.0.5"
|
||||
call-bind "^1.0.2"
|
||||
for-each "^0.3.3"
|
||||
gopd "^1.0.1"
|
||||
has-tostringtag "^1.0.0"
|
||||
is-typed-array "^1.1.10"
|
||||
|
||||
which@^1.2.14, which@^1.2.9, which@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
|
|
Loading…
Reference in a new issue