diff --git a/locales/en.js b/locales/en.js
index 935bce61..751ed567 100644
--- a/locales/en.js
+++ b/locales/en.js
@@ -87,12 +87,8 @@ export default {
description: `I movimenti hanno bisogno di organizzarsi e autofinanziarsi.
Questo è un dono per voi, usatelo solo per eventi non commerciali e ovviamente antifascisti, antisessisti, antirazzisti.
Prima di poter pubblicare dobbiamo approvare l'account, considera che dietro questo sito ci sono delle persone di
carne e sangue, scrivici quindi due righe per farci capire che eventi vorresti pubblicare.`,
- error: 'Errore: ',
- admin_complete: 'Sei il primo utente e quindi sei amministratore!',
- complete: 'Confermeremo la registrazione quanto prima.',
- request: 'Richiesta di registrazione',
- registration_email: `Ciao,
- ci è arrivata una richiesta di registrazione su gancio, la confermeremo quanto prima.`
+ error: 'Error: ',
+ complete: 'Registration has to be confirmed.'
},
event: {
@@ -103,79 +99,79 @@ export default {
Puoi invece fare il login o registrarti,
altrimenti vai avanti e riceverai una risposta il prima possibile. `,
same_day: 'Same day',
- what_description: 'Event\' name',
+ what_description: 'Event\'s name',
description_description: 'Description',
- tag_description: 'Tag...',
- media_description: 'Puoi aggiungere un volantino',
- added: 'Evento aggiunto',
- added_anon: 'Evento aggiunto, verrà confermato quanto prima.',
- where_description: `Dov'è il gancio? Se il posto non è presente, scrivilo e premi invio. `,
- confirmed: 'Evento confermato',
- not_found: 'Evento non trovato',
- remove_confirmation: `Sicura di voler eliminare questo evento?`,
- recurrent: `Ricorrente`,
- recurrent_description: 'Scegli la frequenza e seleziona i giorni',
- multidate_description: 'Un festival o una tre giorni? Scegli quando comincia e quando finisce.',
- multidate: 'Più giorni',
- normal: 'Normale',
- normal_description: 'Scegli il giorno.',
- recurrent_1w_days: 'Ogni {days}',
- recurrent_2w_days: 'Un {days} ogni due',
- recurrent_1m_days: '|Il giorno {days} di ogni mese|I giorni {days} di ogni mese',
- recurrent_2m_days: '|Il giorno {days} ogni due mesi|I giorni {days} ogni due mesi',
- recurrent_1m_ordinal: 'Il {n} {days} di ogni mese',
- recurrent_2m_ordinal: '|Il {n} {days} un mese sì e uno no|Il {n} {days} un mese sì e uno no',
- due: 'alle',
- from: 'Dalle',
- image_too_big: 'Immagine troppo grande! Massimo 4M'
+ tag_description: 'Tag',
+ media_description: 'You could add an event\'s flyer (optional)',
+ added: 'Event added',
+ added_anon: 'Event added but has to be confirmed.',
+ where_description: `Where's the event? If not present, write it and press enter. `,
+ confirmed: 'Event confirmed',
+ not_found: 'Event not found',
+ remove_confirmation: `Are you sure to remove this event?`,
+ recurrent: `Recurrent`,
+ recurrent_description: 'Choose the frequency and select the days',
+ multidate_description: 'It\'s a festival? Choose when it starts and when it ends?',
+ multidate: 'More days',
+ normal: 'Normal',
+ normal_description: 'Choose the day.',
+ recurrent_1w_days: 'Each {days}',
+ recurrent_2w_days: 'A {days} each two',
+ recurrent_1m_days: '|The {days} of each month|{days} of each month',
+ recurrent_2m_days: '|The {days} a month each two|The {days} a month each two',
+ recurrent_1m_ordinal: 'The {n} {days} of each month',
+ recurrent_2m_ordinal: '|The {n} {days} a month each two|The {n} {days} a month each two',
+ due: 'due',
+ from: 'From',
+ image_too_big: 'Image too big! Max 4M'
},
admin: {
- mastodon_instance: 'Istanza',
- mastodon_description: 'Puoi associare un account mastodon a questa istanza di gancio, ogni evento verrà pubblicato lì.',
place_description: `Nel caso in cui un luogo sia errato o cambi indirizzo, puoi modificarlo.
Considera che tutti gli eventi associati a questo luogo cambieranno indirizzo (anche quelli passati!)`,
event_confirm_description: 'Puoi confermare qui gli eventi inseriti da utenti anonimi',
- delete_user: 'Elimina',
- remove_admin: 'Rimuovi admin',
- delete_user_confirm: 'Sicura di rimuovere questo utente?',
- user_remove_ok: 'Utente eliminato',
- user_create_ok: 'Utente creato',
- allow_registration_description : 'Vuoi abilitare la registrazione?',
- allow_anon_event: 'Si possono inserire eventi anonimi (previa conferma)?',
- allow_comments: 'Abilita commenti',
- allow_recurrent_event: 'Abilita eventi fissi',
- recurrent_event_visible: 'Appuntamenti fissi visibili di default',
- federation: 'Federazione / ActivityPub'
+ delete_user: 'Remove',
+ remove_admin: 'Remove admin',
+ delete_user_confirm: 'Are you sure to remove this user?',
+ user_remove_ok: 'User removed',
+ user_create_ok: 'User created',
+ allow_registration_description : 'Allow open registrations?',
+ allow_anon_event: 'Allow anon events (has to be confirmed)?',
+ allow_recurrent_event: 'Enable recurrent events',
+ recurrent_event_visible: 'Show recurrent events by default',
+ federation: 'Federation / ActivityPub',
+ enable_federation: 'Enable federation'
},
auth: {
- not_confirmed: 'Non abbiamo ancora confermato questa mail...',
- fail: 'Autenticazione fallita. Sicura la password è giusta? E la mail?'
+ not_confirmed: 'Not confirmed yet',
+ fail: 'Auth failed!. Are you sure password is correct?'
},
settings: {
- change_password: 'Cambia password',
- password_updated: 'Password modificata',
- danger_section: 'Sezione pericolosa',
+ change_password: 'Modify your password',
+ password_updated: 'Password updated',
+ danger_section: 'Dangerous section',
remove_account: 'Premendo il seguente tasto il tuo utente verrà eliminato. Gli eventi da te pubblicati invece no.',
remove_account_confirm: 'Stai per eliminare definitivamente il tuo account',
},
err: {
- register_error: 'Errore nella registrazione'
+ register_error: 'Error during registration'
},
ordinal: {
- 1: 'primo',
- 2: 'secondo',
- 3: 'terzo',
- 4: 'quarto',
- 5: 'quinto',
- [-1]: 'ultimo',
+ 1: 'first',
+ 2: 'second',
+ 3: 'third',
+ 4: 'fourth',
+ 5: 'fifth',
+ [-1]: 'last',
},
about: `
+ Gancio is a shared agenda for local communities.
+
Gancio e' un progetto dell'underscore hacklab e uno dei
servizi di cisti.org.
diff --git a/locales/it.js b/locales/it.js
index a63fd5eb..e21e56c2 100644
--- a/locales/it.js
+++ b/locales/it.js
@@ -90,11 +90,7 @@ export default {
Prima di poter pubblicare dobbiamo approvare l'account, considera che dietro questo sito ci sono delle persone di
carne e sangue, scrivici quindi due righe per farci capire che eventi vorresti pubblicare.`,
error: 'Errore: ',
- admin_complete: 'Sei il primo utente e quindi sei amministratore!',
- complete: 'Confermeremo la registrazione quanto prima.',
- request: 'Richiesta di registrazione',
- registration_email: `Ciao,
- ci è arrivata una richiesta di registrazione su gancio, la confermeremo quanto prima.`
+ complete: 'Confermeremo la registrazione quanto prima.'
},
event: {
@@ -150,7 +146,8 @@ export default {
allow_comments: 'Abilita commenti',
allow_recurrent_event: 'Abilita eventi fissi',
recurrent_event_visible: 'Appuntamenti fissi visibili di default',
- federation: 'Federazione / ActivityPub'
+ federation: 'Federazione / ActivityPub',
+ enable_federation: 'Abilita la federazione!'
},
auth: {
diff --git a/package.json b/package.json
index 2c527174..12b8a6af 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,7 @@
"email-templates": "^6.0.0",
"express": "^4.17.1",
"express-jwt": "^5.3.1",
+ "fetch": "^1.1.0",
"ics": "^2.15.1",
"inquirer": "^6.5.0",
"jsonwebtoken": "^8.5.1",
diff --git a/pages/add/_edit.vue b/pages/add/_edit.vue
index 508091fd..d0fcd9e4 100644
--- a/pages/add/_edit.vue
+++ b/pages/add/_edit.vue
@@ -127,6 +127,11 @@ export default {
validate ({store}) {
return (store.state.auth.loggedIn || store.state.settings.allow_anon_event)
},
+ head () {
+ return {
+ title: `${this.settings.title} - ${this.$t('common.add_event')}`
+ }
+ },
data() {
const month = moment().month()+1
const year = moment().year()
@@ -197,7 +202,7 @@ export default {
data.event.type = 'normal'
data.date = new Date(event.start_datetime)
}
-
+
data.time.start = moment(event.start_datetime).format('HH:mm')
data.time.end = moment(event.end_datetime).format('HH:mm')
data.event.title = event.title
@@ -217,6 +222,7 @@ export default {
places: state => state.places,
user: state => state.user,
events: state => state.events,
+ settings: state => state.settings
}),
whenPatterns () {
const dates = this.date
diff --git a/server/federation/follows.js b/server/federation/follows.js
new file mode 100644
index 00000000..027b7c70
--- /dev/null
+++ b/server/federation/follows.js
@@ -0,0 +1,28 @@
+const config = require('config')
+const Helpers = require('./helpers')
+const { user: User } = require('../api/models')
+
+module.exports = {
+ // follow request from fediverse
+ async follow (req, res, body, targetOrigin, domain) {
+ if (typeof b.object !== 'string') return
+ const username = body.object.replace(`${config.baseurl}/federation/u/`, '')
+ console.error('someone wants to follow ' + username)
+ const user = await User.findOne({ where: { username }})
+ if (!user) {
+ console.error('No user found!')
+ return
+ }
+ Helpers.sendAcceptMessage(body, user, domain, req, res, targetOrigin)
+ console.error('FOLLOWERS ', user.followers)
+ if (user.followers.indexOf(body.actor) === -1) {
+ console.error('ok this is a new follower: ', body.actor)
+ await user.update({ followers: [...user.followers, body.actor] })
+ }
+
+ },
+ // unfollow request from fediverse
+ unfollow () {
+ console.error('inside unfollow')
+ }
+}
\ No newline at end of file
diff --git a/server/federation/helpers.js b/server/federation/helpers.js
new file mode 100644
index 00000000..9222bdea
--- /dev/null
+++ b/server/federation/helpers.js
@@ -0,0 +1,54 @@
+const fetch = require('fetch')
+const request = require('request')
+const crypto = require('crypto')
+
+const Helpers = {
+ async signAndSend(message, user, domain, req, res, targetOrigin) {
+ // get the URI of the actor object and append 'inbox' to it
+ let inbox = message.object.actor+'/inbox'
+ let inboxFragment = inbox.replace(targetOrigin,'')
+ const targetDomain = new URL(targetOrigin).host
+ // get the private key
+ const privkey = user.rsa.privateKey
+ const signer = crypto.createSign('sha256')
+ let d = new Date()
+ let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}`
+ signer.update(stringToSign)
+ signer.end()
+ const signature = signer.sign(privkey)
+ const signature_b64 = signature.toString('base64')
+ let header = `keyId="${config.baseurl}/federation/u/${user.username}",headers="(request-target) host date",signature="${signature_b64}"`
+ request({
+ url: inbox,
+ headers: {
+ 'Host': targetDomain,
+ 'Date': d.toUTCString(),
+ 'Signature': header
+ },
+ method: 'POST',
+ json: true,
+ body: message
+ }, function (error, response){
+ if (error) {
+ console.log('Error:', error, response.body)
+ }
+ else {
+ console.log('Response:', response.body)
+ }
+ })
+ return res.status(200)
+ },
+ async sendAcceptMessage (body, user, domain, req, res, targetOrigin) {
+ const guid = crypto.randomBytes(16).toString('hex')
+ let message = {
+ '@context': 'https://www.w3.org/ns/activitystreams',
+ 'id': `${config.baseurl}/federation/${guid}`,
+ 'type': 'Accept',
+ 'actor': `${config.baseurl}/federation/u/${user.username}`,
+ 'object': body,
+ }
+ Helpers.signAndSend(message, user, domain, req, res, targetOrigin)
+ }
+}
+
+module.exports = Helpers
\ No newline at end of file
diff --git a/server/federation/index.js b/server/federation/index.js
index 41096a67..784c9c18 100644
--- a/server/federation/index.js
+++ b/server/federation/index.js
@@ -1,130 +1,47 @@
const express = require('express')
const router = express.Router()
-const { user: User } = require('../api/models')
const config = require('config')
-const get = require('lodash/get')
-const crypto = require('crypto')
-const request = require('request')
+const bodyParser = require('body-parser')
+const cors = require('cors')
+const Follows = require('./follows')
+const Users = require('./users')
-function signAndSend(message, user, domain, req, res, targetOrigin) {
- // get the URI of the actor object and append 'inbox' to it
- let inbox = message.object.actor+'/inbox'
- let inboxFragment = inbox.replace(targetOrigin,'')
- const targetDomain = new URL(targetOrigin).host
- // get the private key
- const privkey = user.rsa.privateKey
- const signer = crypto.createSign('sha256')
- let d = new Date()
- let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}`
- signer.update(stringToSign)
- signer.end()
- const signature = signer.sign(privkey)
- const signature_b64 = signature.toString('base64')
- let header = `keyId="${config.baseurl}/federation/u/${user.username}",headers="(request-target) host date",signature="${signature_b64}"`
- request({
- url: inbox,
- headers: {
- 'Host': targetDomain,
- 'Date': d.toUTCString(),
- 'Signature': header
- },
- method: 'POST',
- json: true,
- body: message
- }, function (error, response){
- if (error) {
- console.log('Error:', error, response.body)
- }
- else {
- console.log('Response:', response.body)
- }
- })
- return res.status(200);
-}
+/**
+ * Federation is calling!
+ * ref: https://www.w3.org/TR/activitypub/#Overview
+ */
+router.use(cors())
+router.use(bodyParser.json({type: 'application/activity+json'}))
-function sendAcceptMessage (body, user, domain, req, res, targetOrigin) {
- const guid = crypto.randomBytes(16).toString('hex')
- let message = {
- '@context': 'https://www.w3.org/ns/activitystreams',
- 'id': `${config.baseurl}/federation/${guid}`,
- 'type': 'Accept',
- 'actor': `${config.baseurl}/federation/u/${user.username}`,
- 'object': body,
- }
- signAndSend(message, user, domain, req, res, targetOrigin)
-}
-
-router.post('/inbox', async (req, res) => {
+// get any message coming from federation
+// Federation is calling!
+router.post('/u/:name/inbox', async (req, res) => {
const b = req.body
- console.error('> INBOX ', b)
+ console.error('> INBOX ', b.type, b)
const targetOrigin = new URL(b.actor).origin
const domain = new URL(config.baseurl).host
+
switch(b.type) {
case 'Follow':
- if (typeof b.object !== 'string') return
- const username = b.object.replace(`${config.baseurl}/federation/u/`, '')
- console.error('someone wants to follow ' + username)
- const user = await User.findOne({ where: { username }})
- if (!user) {
- console.error('No user found!')
- return
- }
- sendAcceptMessage(b, user, domain, req, res, targetOrigin)
- console.error('FOLLOWERS ', user.followers)
- if (user.followers.indexOf(b.actor) === -1) {
- console.error('ok this is a new follower: ', b.actor)
- await user.update({ followers: [...user.followers, b.actor] })
- }
-
+ Follows.follow(req, res, b, targetOrigin, domain)
+ break
+ case 'Undo':
+ Follows.unfollow(req, res, b, targetOrigin, domain)
+ break
+ case 'Announce':
+ console.error('This is a boost ?')
+ break
+ case 'Note':
+ console.error('this is a note ! I should not receive this')
+ break
+ case 'Create':
+ console.error('Create what? This is probably a reply', b.object.type)
break
}
})
-router.get('/u/:name', async (req, res) => {
- const name = req.params.name
- if (!name) return res.status(400).send('Bad request.')
- const user = await User.findOne({where: { username: name }})
- if (!user) return res.status(404).send(`No record found for ${name}`)
- const ret = {
- '@context': [
- 'https://www.w3.org/ns/activitystreams',
- 'https://w3id.org/security/v1'
- ],
- id: `${config.baseurl}/federation/u/${name}`,
- type: 'Person',
- preferredUsername: name,
- nodeInfo2Url: `${config.baseurl}/.well-known/x-nodeinfo2`,
- inbox: `${config.baseurl}/federation/inbox`,
- followers: `${config.baseurl}/federation/u/${name}/followers`,
- publicKey: {
- id: `${config.baseurl}/federation/u/${name}#main-key`,
- owner: `${config.baseurl}/federation/u/${name}`,
- publicKeyPem: get(user, 'rsa.publicKey', '')
- }
- }
- res.json(ret)
-})
-
-router.get('/u/:name/followers', async (req, res) => {
- const name = req.params.name
- if (!name) return res.status(400).send('Bad request.')
- const user = await User.findOne({where: { username: name }})
- if (!user) return res.status(404).send(`No record found for ${name}`)
- const ret = {
- '@context': [ 'https://www.w3.org/ns/activitystreams' ],
- id: `${config.baseurl}/federation/u/${name}/followers`,
- type: 'OrderedCollection',
- totalItems: user.followers.length,
- first: {
- id: `${config.baseurl}/federation/u/${name}/followers?page=1`,
- type: 'OrderedCollectionPage',
- totalItems: user.followers.length,
- partOf: `${config.baseurl}/federation/u/${name}/followers`,
- orderedItems: user.followers,
- }
- }
- res.json(ret)
-})
-
+router.get('/u/:name/outbox', Users.outbox)
+router.get('/u/:name/followers', Users.followers)
+router.get('/u/:name', Users.get)
module.exports = router
diff --git a/server/federation/users.js b/server/federation/users.js
new file mode 100644
index 00000000..227cf926
--- /dev/null
+++ b/server/federation/users.js
@@ -0,0 +1,53 @@
+const { user: User } = require('../api/models')
+const config = require('config')
+
+module.exports = {
+ async get (req, res) {
+ const name = req.params.name
+ if (!name) return res.status(400).send('Bad request.')
+ const user = await User.findOne({where: { username: name }})
+ if (!user) return res.status(404).send(`No record found for ${name}`)
+ const ret = {
+ '@context': [
+ 'https://www.w3.org/ns/activitystreams',
+ 'https://w3id.org/security/v1'
+ ],
+ id: `${config.baseurl}/federation/u/${name}`,
+ type: 'Person',
+ preferredUsername: name,
+ nodeInfo2Url: `${config.baseurl}/.well-known/x-nodeinfo2`,
+ inbox: `${config.baseurl}/federation/u/${name}/inbox`,
+ outbox: `${config.baseurl}/federation/u/${name}/outbox`,
+ followers: `${config.baseurl}/federation/u/${name}/followers`,
+ publicKey: {
+ id: `${config.baseurl}/federation/u/${name}#main-key`,
+ owner: `${config.baseurl}/federation/u/${name}`,
+ publicKeyPem: get(user, 'rsa.publicKey', '')
+ }
+ }
+ res.json(ret)
+ },
+ async followers (req, res) {
+ const name = req.params.name
+ if (!name) return res.status(400).send('Bad request.')
+ const user = await User.findOne({where: { username: name }})
+ if (!user) return res.status(404).send(`No record found for ${name}`)
+ const ret = {
+ '@context': [ 'https://www.w3.org/ns/activitystreams' ],
+ id: `${config.baseurl}/federation/u/${name}/followers`,
+ type: 'OrderedCollection',
+ totalItems: user.followers.length,
+ first: {
+ id: `${config.baseurl}/federation/u/${name}/followers?page=1`,
+ type: 'OrderedCollectionPage',
+ totalItems: user.followers.length,
+ partOf: `${config.baseurl}/federation/u/${name}/followers`,
+ orderedItems: user.followers,
+ }
+ }
+ res.json(ret)
+ },
+ outbox (req, res) {
+ console.error('Inside outbox, should return all events from this user')
+ }
+}
\ No newline at end of file
diff --git a/server/index.js b/server/index.js
index d8e1790d..b34e0e67 100644
--- a/server/index.js
+++ b/server/index.js
@@ -3,7 +3,6 @@ const path = require('path')
const express = require('express')
const consola = require('consola')
const morgan = require('morgan')
-const bodyParser = require('body-parser')
const cors = require('cors')
const { Nuxt, Builder } = require('nuxt')
@@ -37,7 +36,7 @@ async function start() {
// federation api / activitypub / webfinger / nodeinfo
app.use('/.well-known/webfinger', cors(), require('./federation/webfinger'))
app.use('/.well-known/x-nodeinfo2', cors(), require('./federation/nodeinfo'))
- app.use('/federation', cors(), bodyParser.join({type: 'applicatoin/activity+json'}), require('./federation'))
+ app.use('/federation', require('./federation'))
// Give nuxt middleware to express
app.use(nuxt.render)
diff --git a/yarn.lock b/yarn.lock
index 749c6d24..efbb9686 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1953,6 +1953,13 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
+biskviit@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/biskviit/-/biskviit-1.0.1.tgz#037a0cd4b71b9e331fd90a1122de17dc49e420a7"
+ integrity sha1-A3oM1LcbnjMf2QoRIt4X3EnkIKc=
+ dependencies:
+ psl "^1.1.7"
+
bl@^1.0.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
@@ -3681,6 +3688,13 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
+encoding@0.1.12:
+ version "0.1.12"
+ resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
+ integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
+ dependencies:
+ iconv-lite "~0.4.13"
+
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
@@ -4422,6 +4436,14 @@ fast-levenshtein@~2.0.4:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
+fetch@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/fetch/-/fetch-1.1.0.tgz#0a8279f06be37f9f0ebb567560a30a480da59a2e"
+ integrity sha1-CoJ58Gvjf58Ou1Z1YKMKSA2lmi4=
+ dependencies:
+ biskviit "1.0.1"
+ encoding "0.1.12"
+
figgy-pudding@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
@@ -5315,7 +5337,7 @@ i18n@^0.8.3:
mustache "*"
sprintf-js ">=1.0.3"
-iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
+iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -8620,7 +8642,7 @@ pseudomap@^1.0.2:
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
-psl@^1.1.24:
+psl@^1.1.24, psl@^1.1.7:
version "1.2.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.2.0.tgz#df12b5b1b3a30f51c329eacbdef98f3a6e136dc6"
integrity sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==