From 1291c3afe968830dfc8ea043f889d27725e8ffcb Mon Sep 17 00:00:00 2001 From: nimbleghost <132819643+nimbleghost@users.noreply.github.com> Date: Wed, 24 May 2023 17:48:39 +0200 Subject: [PATCH 01/26] Make async for loops performant using Promise.all --- web/src/app/Poller.js | 19 +++++----- web/src/app/SubscriptionManager.js | 57 ++++++++++++++---------------- 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/web/src/app/Poller.js b/web/src/app/Poller.js index b956821..372e46e 100644 --- a/web/src/app/Poller.js +++ b/web/src/app/Poller.js @@ -21,15 +21,16 @@ class Poller { async pollAll() { console.log(`[Poller] Polling all subscriptions`); const subscriptions = await subscriptionManager.all(); - for (const s of subscriptions) { - try { - // TODO(eslint): Switch to Promise.all - // eslint-disable-next-line no-await-in-loop - await this.poll(s); - } catch (e) { - console.log(`[Poller] Error polling ${s.id}`, e); - } - } + + await Promise.all( + subscriptions.map(async (s) => { + try { + await this.poll(s); + } catch (e) { + console.log(`[Poller] Error polling ${s.id}`, e); + } + }) + ); } async poll(subscription) { diff --git a/web/src/app/SubscriptionManager.js b/web/src/app/SubscriptionManager.js index 7762753..03617e0 100644 --- a/web/src/app/SubscriptionManager.js +++ b/web/src/app/SubscriptionManager.js @@ -5,13 +5,12 @@ class SubscriptionManager { /** All subscriptions, including "new count"; this is a JOIN, see https://dexie.org/docs/API-Reference#joining */ async all() { const subscriptions = await db.subscriptions.toArray(); - await Promise.all( - subscriptions.map(async (s) => { - // eslint-disable-next-line no-param-reassign - s.new = await db.notifications.where({ subscriptionId: s.id, new: 1 }).count(); - }) + return Promise.all( + subscriptions.map(async (s) => ({ + ...s, + new: await db.notifications.where({ subscriptionId: s.id, new: 1 }).count(), + })) ); - return subscriptions; } async get(subscriptionId) { @@ -40,33 +39,31 @@ class SubscriptionManager { console.log(`[SubscriptionManager] Syncing subscriptions from remote`, remoteSubscriptions); // Add remote subscriptions - const remoteIds = []; // = topicUrl(baseUrl, topic) - for (let i = 0; i < remoteSubscriptions.length; i += 1) { - const remote = remoteSubscriptions[i]; - // TODO(eslint): Switch to Promise.all - // eslint-disable-next-line no-await-in-loop - const local = await this.add(remote.base_url, remote.topic, false); - const reservation = remoteReservations?.find((r) => remote.base_url === config.base_url && remote.topic === r.topic) || null; - // TODO(eslint): Switch to Promise.all - // eslint-disable-next-line no-await-in-loop - await this.update(local.id, { - displayName: remote.display_name, // May be undefined - reservation, // May be null! - }); - remoteIds.push(local.id); - } + const remoteIds = await Promise.all( + remoteSubscriptions.map(async (remote) => { + const local = await this.add(remote.base_url, remote.topic, false); + const reservation = remoteReservations?.find((r) => remote.base_url === config.base_url && remote.topic === r.topic) || null; + + await this.update(local.id, { + displayName: remote.display_name, // May be undefined + reservation, // May be null! + }); + + return local.id; + }) + ); // Remove local subscriptions that do not exist remotely const localSubscriptions = await db.subscriptions.toArray(); - for (let i = 0; i < localSubscriptions.length; i += 1) { - const local = localSubscriptions[i]; - const remoteExists = remoteIds.includes(local.id); - if (!local.internal && !remoteExists) { - // TODO(eslint): Switch to Promise.all - // eslint-disable-next-line no-await-in-loop - await this.remove(local.id); - } - } + + await Promise.all( + localSubscriptions.map(async (local) => { + const remoteExists = remoteIds.includes(local.id); + if (!local.internal && !remoteExists) { + await this.remove(local.id); + } + }) + ); } async updateState(subscriptionId, state) { From 25d6725d8f38974502ba76b4ab44554cbfd7fdc7 Mon Sep 17 00:00:00 2001 From: nimbleghost <132819643+nimbleghost@users.noreply.github.com> Date: Wed, 24 May 2023 17:49:04 +0200 Subject: [PATCH 02/26] Use es6 destructuring swap for shuffling --- web/src/app/utils.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/web/src/app/utils.js b/web/src/app/utils.js index 0af1033..481d603 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -139,17 +139,14 @@ export const maybeAppendActionErrors = (message, notification) => { }; export const shuffle = (arr) => { - let j; - let x; - for (let index = arr.length - 1; index > 0; index -= 1) { - j = Math.floor(Math.random() * (index + 1)); - x = arr[index]; - // eslint-disable-next-line no-param-reassign - arr[index] = arr[j]; - // eslint-disable-next-line no-param-reassign - arr[j] = x; + const returnArr = [...arr]; + + for (let index = returnArr.length - 1; index > 0; index -= 1) { + const j = Math.floor(Math.random() * (index + 1)); + [returnArr[index], returnArr[j]] = [returnArr[j], returnArr[index]]; } - return arr; + + return returnArr; }; export const splitNoEmpty = (s, delimiter) => From 4a0a22566a297dea324a84fc395a219199076770 Mon Sep 17 00:00:00 2001 From: nimbleghost <132819643+nimbleghost@users.noreply.github.com> Date: Wed, 24 May 2023 17:49:16 +0200 Subject: [PATCH 03/26] Fix jsx key issue --- web/src/components/PublishDialog.jsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/web/src/components/PublishDialog.jsx b/web/src/components/PublishDialog.jsx index 4800286..3fa7f85 100644 --- a/web/src/components/PublishDialog.jsx +++ b/web/src/components/PublishDialog.jsx @@ -477,10 +477,8 @@ const PublishDialog = (props) => { "aria-label": t("publish_dialog_call_label"), }} > - {account?.phone_numbers?.map((phoneNumber, i) => ( - // TODO(eslint): Possibly just use the phone number as a key? - // eslint-disable-next-line react/no-array-index-key - + {account?.phone_numbers?.map((phoneNumber) => ( + {t("publish_dialog_call_item", { number: phoneNumber })} ))} From be6d962cc3462d5eab9b18a9bec1f0140f7d7a72 Mon Sep 17 00:00:00 2001 From: nimbleghost <132819643+nimbleghost@users.noreply.github.com> Date: Wed, 24 May 2023 18:08:59 +0200 Subject: [PATCH 04/26] Fix param reassignment issue --- web/src/app/SubscriptionManager.js | 9 ++++++--- web/src/components/Notifications.jsx | 11 +++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/web/src/app/SubscriptionManager.js b/web/src/app/SubscriptionManager.js index 03617e0..ecbe4da 100644 --- a/web/src/app/SubscriptionManager.js +++ b/web/src/app/SubscriptionManager.js @@ -105,9 +105,12 @@ class SubscriptionManager { return false; } try { - // eslint-disable-next-line no-param-reassign - notification.new = 1; // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation - await db.notifications.add({ ...notification, subscriptionId }); // FIXME consider put() for double tab + await db.notifications.add({ + ...notification, + subscriptionId, + // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation + new: 1, + }); // FIXME consider put() for double tab await db.subscriptions.update(subscriptionId, { last: notification.id, }); diff --git a/web/src/components/Notifications.jsx b/web/src/components/Notifications.jsx index 7d4da06..2faf2fd 100644 --- a/web/src/components/Notifications.jsx +++ b/web/src/components/Notifications.jsx @@ -436,15 +436,10 @@ const ACTION_LABEL_SUFFIX = { }; const updateActionStatus = (notification, action, progress, error) => { - // TODO(eslint): Fix by spreading? Does the code depend on the change, though? - // eslint-disable-next-line no-param-reassign - notification.actions = notification.actions.map((a) => { - if (a.id !== action.id) { - return a; - } - return { ...a, progress, error }; + subscriptionManager.updateNotification({ + ...notification, + actions: notification.actions.map((a) => (a.id === action.id ? { ...a, progress, error } : a)), }); - subscriptionManager.updateNotification(notification); }; const performHttpAction = async (notification, action) => { From 025ea3c1d6060891d4939f6857146fa8872fdac1 Mon Sep 17 00:00:00 2001 From: nimbleghost <132819643+nimbleghost@users.noreply.github.com> Date: Wed, 24 May 2023 20:47:19 +0200 Subject: [PATCH 05/26] Make small code style improvements --- web/src/app/ConnectionManager.js | 4 +--- web/src/app/utils.js | 4 ++-- web/src/components/EmojiPicker.jsx | 12 +----------- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/web/src/app/ConnectionManager.js b/web/src/app/ConnectionManager.js index 751c7bd..2033cbe 100644 --- a/web/src/app/ConnectionManager.js +++ b/web/src/app/ConnectionManager.js @@ -61,9 +61,7 @@ class ConnectionManager { const { connectionId } = subscription; const added = !this.connections.get(connectionId); if (added) { - const { baseUrl } = subscription; - const { topic } = subscription; - const { user } = subscription; + const { baseUrl, topic, user } = subscription; const since = subscription.last; const connection = new Connection( connectionId, diff --git a/web/src/app/utils.js b/web/src/app/utils.js index 481d603..ab7551b 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -118,10 +118,10 @@ export const maybeWithBearerAuth = (headers, token) => { export const withBasicAuth = (headers, username, password) => ({ ...headers, Authorization: basicAuth(username, password) }); export const maybeWithAuth = (headers, user) => { - if (user && user.password) { + if (user?.password) { return withBasicAuth(headers, user.username, user.password); } - if (user && user.token) { + if (user?.token) { return withBearerAuth(headers, user.token); } return headers; diff --git a/web/src/components/EmojiPicker.jsx b/web/src/components/EmojiPicker.jsx index 7dead7f..c2f3c46 100644 --- a/web/src/components/EmojiPicker.jsx +++ b/web/src/components/EmojiPicker.jsx @@ -125,17 +125,7 @@ const Category = (props) => { ); }; -const emojiMatches = (emoji, words) => { - if (words.length === 0) { - return true; - } - for (const word of words) { - if (emoji.searchBase.indexOf(word) === -1) { - return false; - } - } - return true; -}; +const emojiMatches = (emoji, words) => words.length === 0 || words.some((word) => emoji.searchBase.includes(word)); const Emoji = (props) => { const { emoji } = props; From 9056d68fc93679c288a1ca333701acc0ca674422 Mon Sep 17 00:00:00 2001 From: nimbleghost <132819643+nimbleghost@users.noreply.github.com> Date: Wed, 24 May 2023 17:48:39 +0200 Subject: [PATCH 06/26] Make async for loops performant using Promise.all --- web/src/app/Poller.js | 19 +++++----- web/src/app/SubscriptionManager.js | 57 ++++++++++++++---------------- 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/web/src/app/Poller.js b/web/src/app/Poller.js index b956821..372e46e 100644 --- a/web/src/app/Poller.js +++ b/web/src/app/Poller.js @@ -21,15 +21,16 @@ class Poller { async pollAll() { console.log(`[Poller] Polling all subscriptions`); const subscriptions = await subscriptionManager.all(); - for (const s of subscriptions) { - try { - // TODO(eslint): Switch to Promise.all - // eslint-disable-next-line no-await-in-loop - await this.poll(s); - } catch (e) { - console.log(`[Poller] Error polling ${s.id}`, e); - } - } + + await Promise.all( + subscriptions.map(async (s) => { + try { + await this.poll(s); + } catch (e) { + console.log(`[Poller] Error polling ${s.id}`, e); + } + }) + ); } async poll(subscription) { diff --git a/web/src/app/SubscriptionManager.js b/web/src/app/SubscriptionManager.js index 7762753..03617e0 100644 --- a/web/src/app/SubscriptionManager.js +++ b/web/src/app/SubscriptionManager.js @@ -5,13 +5,12 @@ class SubscriptionManager { /** All subscriptions, including "new count"; this is a JOIN, see https://dexie.org/docs/API-Reference#joining */ async all() { const subscriptions = await db.subscriptions.toArray(); - await Promise.all( - subscriptions.map(async (s) => { - // eslint-disable-next-line no-param-reassign - s.new = await db.notifications.where({ subscriptionId: s.id, new: 1 }).count(); - }) + return Promise.all( + subscriptions.map(async (s) => ({ + ...s, + new: await db.notifications.where({ subscriptionId: s.id, new: 1 }).count(), + })) ); - return subscriptions; } async get(subscriptionId) { @@ -40,33 +39,31 @@ class SubscriptionManager { console.log(`[SubscriptionManager] Syncing subscriptions from remote`, remoteSubscriptions); // Add remote subscriptions - const remoteIds = []; // = topicUrl(baseUrl, topic) - for (let i = 0; i < remoteSubscriptions.length; i += 1) { - const remote = remoteSubscriptions[i]; - // TODO(eslint): Switch to Promise.all - // eslint-disable-next-line no-await-in-loop - const local = await this.add(remote.base_url, remote.topic, false); - const reservation = remoteReservations?.find((r) => remote.base_url === config.base_url && remote.topic === r.topic) || null; - // TODO(eslint): Switch to Promise.all - // eslint-disable-next-line no-await-in-loop - await this.update(local.id, { - displayName: remote.display_name, // May be undefined - reservation, // May be null! - }); - remoteIds.push(local.id); - } + const remoteIds = await Promise.all( + remoteSubscriptions.map(async (remote) => { + const local = await this.add(remote.base_url, remote.topic, false); + const reservation = remoteReservations?.find((r) => remote.base_url === config.base_url && remote.topic === r.topic) || null; + + await this.update(local.id, { + displayName: remote.display_name, // May be undefined + reservation, // May be null! + }); + + return local.id; + }) + ); // Remove local subscriptions that do not exist remotely const localSubscriptions = await db.subscriptions.toArray(); - for (let i = 0; i < localSubscriptions.length; i += 1) { - const local = localSubscriptions[i]; - const remoteExists = remoteIds.includes(local.id); - if (!local.internal && !remoteExists) { - // TODO(eslint): Switch to Promise.all - // eslint-disable-next-line no-await-in-loop - await this.remove(local.id); - } - } + + await Promise.all( + localSubscriptions.map(async (local) => { + const remoteExists = remoteIds.includes(local.id); + if (!local.internal && !remoteExists) { + await this.remove(local.id); + } + }) + ); } async updateState(subscriptionId, state) { From 4d90e32fe9f584b056fceea763aae2a39ea10396 Mon Sep 17 00:00:00 2001 From: nimbleghost <132819643+nimbleghost@users.noreply.github.com> Date: Wed, 24 May 2023 17:49:04 +0200 Subject: [PATCH 07/26] Use es6 destructuring swap for shuffling --- web/src/app/utils.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/web/src/app/utils.js b/web/src/app/utils.js index 0af1033..481d603 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -139,17 +139,14 @@ export const maybeAppendActionErrors = (message, notification) => { }; export const shuffle = (arr) => { - let j; - let x; - for (let index = arr.length - 1; index > 0; index -= 1) { - j = Math.floor(Math.random() * (index + 1)); - x = arr[index]; - // eslint-disable-next-line no-param-reassign - arr[index] = arr[j]; - // eslint-disable-next-line no-param-reassign - arr[j] = x; + const returnArr = [...arr]; + + for (let index = returnArr.length - 1; index > 0; index -= 1) { + const j = Math.floor(Math.random() * (index + 1)); + [returnArr[index], returnArr[j]] = [returnArr[j], returnArr[index]]; } - return arr; + + return returnArr; }; export const splitNoEmpty = (s, delimiter) => From d178be7576ea152386f9b41591df969eb6f18f29 Mon Sep 17 00:00:00 2001 From: nimbleghost <132819643+nimbleghost@users.noreply.github.com> Date: Wed, 24 May 2023 18:08:59 +0200 Subject: [PATCH 08/26] Fix param reassignment issue --- web/src/app/SubscriptionManager.js | 9 ++++++--- web/src/components/Notifications.jsx | 11 +++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/web/src/app/SubscriptionManager.js b/web/src/app/SubscriptionManager.js index 03617e0..ecbe4da 100644 --- a/web/src/app/SubscriptionManager.js +++ b/web/src/app/SubscriptionManager.js @@ -105,9 +105,12 @@ class SubscriptionManager { return false; } try { - // eslint-disable-next-line no-param-reassign - notification.new = 1; // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation - await db.notifications.add({ ...notification, subscriptionId }); // FIXME consider put() for double tab + await db.notifications.add({ + ...notification, + subscriptionId, + // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation + new: 1, + }); // FIXME consider put() for double tab await db.subscriptions.update(subscriptionId, { last: notification.id, }); diff --git a/web/src/components/Notifications.jsx b/web/src/components/Notifications.jsx index 7d4da06..2faf2fd 100644 --- a/web/src/components/Notifications.jsx +++ b/web/src/components/Notifications.jsx @@ -436,15 +436,10 @@ const ACTION_LABEL_SUFFIX = { }; const updateActionStatus = (notification, action, progress, error) => { - // TODO(eslint): Fix by spreading? Does the code depend on the change, though? - // eslint-disable-next-line no-param-reassign - notification.actions = notification.actions.map((a) => { - if (a.id !== action.id) { - return a; - } - return { ...a, progress, error }; + subscriptionManager.updateNotification({ + ...notification, + actions: notification.actions.map((a) => (a.id === action.id ? { ...a, progress, error } : a)), }); - subscriptionManager.updateNotification(notification); }; const performHttpAction = async (notification, action) => { From da17e4ee8a9c0c6f954a61fdab581a32fb887c1b Mon Sep 17 00:00:00 2001 From: nimbleghost <132819643+nimbleghost@users.noreply.github.com> Date: Wed, 24 May 2023 20:47:19 +0200 Subject: [PATCH 09/26] Make small code style improvements --- web/src/app/ConnectionManager.js | 4 +--- web/src/app/utils.js | 4 ++-- web/src/components/EmojiPicker.jsx | 12 +----------- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/web/src/app/ConnectionManager.js b/web/src/app/ConnectionManager.js index 751c7bd..2033cbe 100644 --- a/web/src/app/ConnectionManager.js +++ b/web/src/app/ConnectionManager.js @@ -61,9 +61,7 @@ class ConnectionManager { const { connectionId } = subscription; const added = !this.connections.get(connectionId); if (added) { - const { baseUrl } = subscription; - const { topic } = subscription; - const { user } = subscription; + const { baseUrl, topic, user } = subscription; const since = subscription.last; const connection = new Connection( connectionId, diff --git a/web/src/app/utils.js b/web/src/app/utils.js index 481d603..ab7551b 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -118,10 +118,10 @@ export const maybeWithBearerAuth = (headers, token) => { export const withBasicAuth = (headers, username, password) => ({ ...headers, Authorization: basicAuth(username, password) }); export const maybeWithAuth = (headers, user) => { - if (user && user.password) { + if (user?.password) { return withBasicAuth(headers, user.username, user.password); } - if (user && user.token) { + if (user?.token) { return withBearerAuth(headers, user.token); } return headers; diff --git a/web/src/components/EmojiPicker.jsx b/web/src/components/EmojiPicker.jsx index f9e8b5e..d1fb170 100644 --- a/web/src/components/EmojiPicker.jsx +++ b/web/src/components/EmojiPicker.jsx @@ -127,17 +127,7 @@ const Category = (props) => { ); }; -const emojiMatches = (emoji, words) => { - if (words.length === 0) { - return true; - } - for (const word of words) { - if (emoji.searchBase.indexOf(word) === -1) { - return false; - } - } - return true; -}; +const emojiMatches = (emoji, words) => words.length === 0 || words.some((word) => emoji.searchBase.includes(word)); const Emoji = (props) => { const { emoji } = props; From 38bd4f3ce374455287b3ee3ed27e277a353be043 Mon Sep 17 00:00:00 2001 From: Rogelio Dominguez Date: Thu, 25 May 2023 12:57:35 +0000 Subject: [PATCH 10/26] Translated using Weblate (Spanish) Currently translated at 100.0% (381 of 381 strings) Translation: ntfy/Web app Translate-URL: https://hosted.weblate.org/projects/ntfy/web/es/ --- web/public/static/langs/es.json | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/web/public/static/langs/es.json b/web/public/static/langs/es.json index 3166a52..065a50e 100644 --- a/web/public/static/langs/es.json +++ b/web/public/static/langs/es.json @@ -355,5 +355,29 @@ "account_upgrade_dialog_billing_contact_email": "Para preguntas sobre facturación, por favor contáctenos directamente.", "account_upgrade_dialog_tier_features_messages_one": "{{messages}} mensaje diario", "account_upgrade_dialog_tier_features_emails_one": "{{emails}} correo electrónico diario", - "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} tema reservado" + "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} tema reservado", + "publish_dialog_call_label": "Llamada telefónica", + "publish_dialog_call_placeholder": "Número de teléfono al cual llamar con el mensaje, por ejemplo +12223334444, o \"sí\"", + "publish_dialog_chip_call_label": "Llamada telefónica", + "account_basics_phone_numbers_title": "Números de teléfono", + "account_basics_phone_numbers_description": "Para notificaciones por llamada teléfonica", + "account_basics_phone_numbers_no_phone_numbers_yet": "Aún no hay números de teléfono", + "account_basics_phone_numbers_dialog_number_label": "Número de teléfono", + "account_basics_phone_numbers_dialog_number_placeholder": "p. ej. +1222333444", + "account_basics_phone_numbers_dialog_verify_button_sms": "Envía SMS", + "account_basics_phone_numbers_dialog_verify_button_call": "Llámame", + "account_basics_phone_numbers_dialog_code_label": "Código de verificación", + "account_basics_phone_numbers_dialog_channel_sms": "SMS", + "account_basics_phone_numbers_dialog_channel_call": "Llamar", + "account_usage_calls_title": "Llamadas telefónicas realizadas", + "account_usage_calls_none": "No se pueden hacer llamadas telefónicas con esta cuenta", + "account_upgrade_dialog_tier_features_calls_one": "{{llamadas}} llamadas telefónicas diarias", + "account_upgrade_dialog_tier_features_calls_other": "{{llamadas}} llamadas telefónicas diarias", + "account_upgrade_dialog_tier_features_no_calls": "No hay llamadas telefónicas", + "publish_dialog_call_reset": "Eliminar llamada telefónica", + "account_basics_phone_numbers_dialog_description": "Para utilizar la función de notificación de llamadas, tiene que añadir y verificar al menos un número de teléfono. La verificación puede realizarse mediante un SMS o una llamada telefónica.", + "account_basics_phone_numbers_copied_to_clipboard": "Número de teléfono copiado al portapapeles", + "account_basics_phone_numbers_dialog_check_verification_button": "Confirmar código", + "account_basics_phone_numbers_dialog_title": "Agregar número de teléfono", + "account_basics_phone_numbers_dialog_code_placeholder": "p.ej. 123456" } From 02524ca101f29f92234637a78089413c1f90db9d Mon Sep 17 00:00:00 2001 From: Kalil Maciel Date: Wed, 24 May 2023 17:20:33 +0000 Subject: [PATCH 11/26] Translated using Weblate (Portuguese) Currently translated at 59.8% (228 of 381 strings) Translation: ntfy/Web app Translate-URL: https://hosted.weblate.org/projects/ntfy/web/pt/ --- web/public/static/langs/pt.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/web/public/static/langs/pt.json b/web/public/static/langs/pt.json index bf753c9..57d5656 100644 --- a/web/public/static/langs/pt.json +++ b/web/public/static/langs/pt.json @@ -214,5 +214,17 @@ "login_link_signup": "Registar", "action_bar_reservation_add": "Reservar tópico", "action_bar_sign_up": "Registar", - "nav_button_account": "Conta" + "nav_button_account": "Conta", + "common_copy_to_clipboard": "Copiar", + "nav_upgrade_banner_label": "Atualizar para ntfy Pro", + "alert_not_supported_context_description": "Notificações são suportadas apenas sobre HTTPS. Essa é uma limitação da API de Notificações.", + "display_name_dialog_title": "Alterar nome mostrado", + "display_name_dialog_description": "Configura um nome alternativo ao tópico que é mostrado na lista de assinaturas. Isto ajuda a identificar tópicos com nomes complicados mais facilmente.", + "display_name_dialog_placeholder": "Nome exibido", + "reserve_dialog_checkbox_label": "Reservar tópico e configurar acesso", + "publish_dialog_call_label": "Chamada telefônica", + "publish_dialog_call_placeholder": "Número de telefone para ligar com a mensagem, ex: +12223334444, ou 'Sim'", + "publish_dialog_call_reset": "Remover chamada telefônica", + "publish_dialog_chip_call_label": "Chamada telefônica", + "subscribe_dialog_subscribe_button_generate_topic_name": "Gerar nome" } From 232c889ce31f825c72b645f63cc262d69c32ca1e Mon Sep 17 00:00:00 2001 From: nimbleghost <132819643+nimbleghost@users.noreply.github.com> Date: Fri, 26 May 2023 21:14:59 +0200 Subject: [PATCH 12/26] Use `apt-get` in makefile `apt` is for interactive shell usage, using it in a script results in a warning as the CLI interface is not stable > WARNING: apt does not have a stable CLI interface. > Use with caution in scripts. --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 7398844..a5f8d9f 100644 --- a/Makefile +++ b/Makefile @@ -85,13 +85,13 @@ update: web-deps-update cli-deps-update docs-deps-update # Ubuntu-specific build-deps-ubuntu: - sudo apt update - sudo apt install -y \ + sudo apt-get update + sudo apt-get install -y \ curl \ gcc-aarch64-linux-gnu \ gcc-arm-linux-gnueabi \ jq - which pip3 || sudo apt install -y python3-pip + which pip3 || sudo apt-get install -y python3-pip # Documentation From 11f898412748f4477caf8b260e0575cb768091dc Mon Sep 17 00:00:00 2001 From: nimbleghost <132819643+nimbleghost@users.noreply.github.com> Date: Fri, 26 May 2023 21:40:18 +0200 Subject: [PATCH 13/26] Add a way to use Docker for building everything MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’d like to test #751 on my own instance, but installing all the build dependencies on my server isn’t ideal - having this script in the repo would make it possible to simply point my compose file to the git repo and have it build the Linux binary itself. Note that it uses a somewhat “inefficient” builder step, i.e. not combining steps together to reduce layers, as it uses a multi-stage build to have a lean final image. This makes it easier to re-build if something needs to change, as the cache is used more optimally. For example, if only some go files change, most of the build is already cached and only the go step gets re-run. The more “efficient” builder step would look like this, but would have to build the docs, web app and go CLI for any change in any file: ```Dockerfile FROM golang:1.19-bullseye as builder RUN apt-get update && \ curl -fsSL https://deb.nodesource.com/setup_18.x | bash && \ apt-get install -y \ build-essential \ nodejs \ python3-pip WORKDIR /app ADD . . RUN make web docs cli-linux-server ``` --- .dockerignore | 3 +++ Dockerfile-build | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 17 ++++++++++++++-- docs/develop.md | 9 ++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile-build diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3bf2a12 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +dist +*/node_modules +Dockerfile* diff --git a/Dockerfile-build b/Dockerfile-build new file mode 100644 index 0000000..f8af256 --- /dev/null +++ b/Dockerfile-build @@ -0,0 +1,53 @@ +FROM golang:1.19-bullseye as builder + +ARG VERSION=dev +ARG COMMIT=unknown + +RUN apt-get update +RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash +RUN apt-get install -y \ + build-essential \ + nodejs \ + python3-pip + +WORKDIR /app +ADD Makefile . + +# docs +ADD ./requirements.txt . +RUN make docs-deps +ADD ./mkdocs.yml . +ADD ./docs ./docs +RUN make docs-build + +# web +ADD ./web/package.json ./web/package-lock.json ./web/ +RUN make web-deps +ADD ./web ./web +RUN make web-build + +# cli & server +ADD go.mod go.sum main.go ./ +ADD ./client ./client +ADD ./cmd ./cmd +ADD ./log ./log +ADD ./server ./server +ADD ./user ./user +ADD ./util ./util +RUN make VERSION=$VERSION COMMIT=$COMMIT cli-linux-server + +FROM alpine + +LABEL org.opencontainers.image.authors="philipp.heckel@gmail.com" +LABEL org.opencontainers.image.url="https://ntfy.sh/" +LABEL org.opencontainers.image.documentation="https://docs.ntfy.sh/" +LABEL org.opencontainers.image.source="https://github.com/binwiederhier/ntfy" +LABEL org.opencontainers.image.vendor="Philipp C. Heckel" +LABEL org.opencontainers.image.licenses="Apache-2.0, GPL-2.0" +LABEL org.opencontainers.image.title="ntfy" +LABEL org.opencontainers.image.description="Send push notifications to your phone or desktop using PUT/POST" + +COPY --from=builder /app/dist/ntfy_linux_server/ntfy /usr/bin/ntfy + +EXPOSE 80/tcp +ENTRYPOINT ["ntfy"] diff --git a/Makefile b/Makefile index a5f8d9f..8cb7523 100644 --- a/Makefile +++ b/Makefile @@ -31,12 +31,16 @@ help: @echo " make cli-darwin-server - Build client & server (no GoReleaser, current arch, macOS)" @echo " make cli-client - Build client only (no GoReleaser, current arch, Linux/macOS/Windows)" @echo + @echo "Build dev Docker:" + @echo " make docker-dev - Build client & server for current architecture using Docker only" + @echo @echo "Build web app:" @echo " make web - Build the web app" @echo " make web-deps - Install web app dependencies (npm install the universe)" @echo " make web-build - Actually build the web app" - @echo " make web-format - Run prettier on the web app - @echo " make web-format-check - Run prettier on the web app, but don't change anything + @echo " make web-lint - Run eslint on the web app" + @echo " make web-format - Run prettier on the web app" + @echo " make web-format-check - Run prettier on the web app, but don't change anything" @echo @echo "Build documentation:" @echo " make docs - Build the documentation" @@ -82,6 +86,15 @@ build: web docs cli update: web-deps-update cli-deps-update docs-deps-update docker pull alpine +docker-dev: + docker build \ + --file ./Dockerfile-build \ + --tag binwiederhier/ntfy:$(VERSION) \ + --tag binwiederhier/ntfy:dev \ + --build-arg VERSION=$(VERSION) \ + --build-arg COMMIT=$(COMMIT) \ + ./ + # Ubuntu-specific build-deps-ubuntu: diff --git a/docs/develop.md b/docs/develop.md index a53c503..baab3f3 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -163,6 +163,15 @@ $ make release-snapshot During development, you may want to be more picky and build only certain things. Here are a few examples. +### Build a Docker image only for Linux + +This is useful to test the final build with web app, docs, and server without any dependencies locally + +``` shell +$ make docker-dev +$ docker run --rm -p 80:80 binwiederhier/ntfy:dev serve +``` + ### Build the ntfy binary To build only the `ntfy` binary **without the web app or documentation**, use the `make cli-...` targets: From 217c660ba0a1584e371969e45404cc3ebcf684af Mon Sep 17 00:00:00 2001 From: Linerly Date: Thu, 25 May 2023 22:44:32 +0000 Subject: [PATCH 14/26] Translated using Weblate (Indonesian) Currently translated at 100.0% (382 of 382 strings) Translation: ntfy/Web app Translate-URL: https://hosted.weblate.org/projects/ntfy/web/id/ --- web/public/static/langs/id.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/public/static/langs/id.json b/web/public/static/langs/id.json index 76c9d1d..48fcda0 100644 --- a/web/public/static/langs/id.json +++ b/web/public/static/langs/id.json @@ -379,5 +379,7 @@ "account_basics_phone_numbers_dialog_channel_sms": "SMS", "account_upgrade_dialog_tier_features_calls_one": "{{calls}} panggilan telepon harian", "account_upgrade_dialog_tier_features_no_calls": "Tidak ada panggilan telepon", - "account_basics_phone_numbers_dialog_code_label": "Kode verifikasi" + "account_basics_phone_numbers_dialog_code_label": "Kode verifikasi", + "publish_dialog_call_item": "Panggil nomor telepon {{number}}", + "publish_dialog_chip_call_no_verified_numbers_tooltip": "Tidak ada nomor telepon terverifikasi" } From 578ccf1643a10f2806a3afa46103012fa5e50847 Mon Sep 17 00:00:00 2001 From: iTentalce Date: Thu, 25 May 2023 14:52:03 +0000 Subject: [PATCH 15/26] Translated using Weblate (Czech) Currently translated at 96.0% (367 of 382 strings) Translation: ntfy/Web app Translate-URL: https://hosted.weblate.org/projects/ntfy/web/cs/ --- web/public/static/langs/cs.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/web/public/static/langs/cs.json b/web/public/static/langs/cs.json index aeff195..6b967c8 100644 --- a/web/public/static/langs/cs.json +++ b/web/public/static/langs/cs.json @@ -355,5 +355,15 @@ "account_upgrade_dialog_billing_contact_website": "Otázky týkající se fakturace naleznete na našich webových stránkách.", "account_upgrade_dialog_tier_features_reservations_one": "{{reservations}} rezervované téma", "account_upgrade_dialog_tier_features_messages_one": "{{messages}} denní zpráva", - "account_upgrade_dialog_tier_features_emails_one": "{{emails}} denní e-mail" + "account_upgrade_dialog_tier_features_emails_one": "{{emails}} denní e-mail", + "publish_dialog_call_label": "Telefonát", + "publish_dialog_call_reset": "Odstranit telefonát", + "publish_dialog_chip_call_label": "Telefonát", + "account_basics_phone_numbers_title": "Telefonní čísla", + "account_basics_phone_numbers_dialog_description": "Pro oznámení prostřednictvím tel. hovoru, musíte přidat a ověřit alespoň jedno telefonní číslo. Ověření lze provést pomocí SMS nebo telefonátu.", + "account_basics_phone_numbers_description": "K oznámení telefonátem", + "account_basics_phone_numbers_no_phone_numbers_yet": "Zatím žádná telefonní čísla", + "account_basics_phone_numbers_copied_to_clipboard": "Telefonní číslo zkopírováno do schránky", + "publish_dialog_chip_call_no_verified_numbers_tooltip": "Žádná ověřená telefonní čísla", + "publish_dialog_call_item": "Vytočit číslo {{number}}" } From bd3907259685071d2c02b531d8d8e10628ad32a9 Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Sat, 27 May 2023 11:34:03 +0000 Subject: [PATCH 16/26] Translated using Weblate (Spanish) Currently translated at 100.0% (382 of 382 strings) Translation: ntfy/Web app Translate-URL: https://hosted.weblate.org/projects/ntfy/web/es/ --- web/public/static/langs/es.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/public/static/langs/es.json b/web/public/static/langs/es.json index 065a50e..62ecdaf 100644 --- a/web/public/static/langs/es.json +++ b/web/public/static/langs/es.json @@ -379,5 +379,7 @@ "account_basics_phone_numbers_copied_to_clipboard": "Número de teléfono copiado al portapapeles", "account_basics_phone_numbers_dialog_check_verification_button": "Confirmar código", "account_basics_phone_numbers_dialog_title": "Agregar número de teléfono", - "account_basics_phone_numbers_dialog_code_placeholder": "p.ej. 123456" + "account_basics_phone_numbers_dialog_code_placeholder": "p.ej. 123456", + "publish_dialog_call_item": "Llamar al número de teléfono {{number}}", + "publish_dialog_chip_call_no_verified_numbers_tooltip": "No hay números de teléfono verificados" } From 2e7f4747755e0b45aae3ad121e3409d3eddb768f Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 27 May 2023 09:03:34 +0000 Subject: [PATCH 17/26] Translated using Weblate (Ukrainian) Currently translated at 100.0% (382 of 382 strings) Translation: ntfy/Web app Translate-URL: https://hosted.weblate.org/projects/ntfy/web/uk/ --- web/public/static/langs/uk.json | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/web/public/static/langs/uk.json b/web/public/static/langs/uk.json index 7d995dd..32a3079 100644 --- a/web/public/static/langs/uk.json +++ b/web/public/static/langs/uk.json @@ -352,5 +352,34 @@ "account_upgrade_dialog_reservations_warning_other": "Обраний рівень дозволяє менше зарезервованих тем, ніж ваш поточний рівень. Перш ніж змінити свій рівень, будь ласка, видаліть принаймні {{count}} резервувань. Ви можете видалити резервування в Налаштуваннях.", "account_upgrade_dialog_button_cancel": "Скасувати", "account_upgrade_dialog_button_redirect_signup": "Зареєструватися зараз", - "account_upgrade_dialog_button_pay_now": "Оплатити зараз і підписатися" + "account_upgrade_dialog_button_pay_now": "Оплатити зараз і підписатися", + "prefs_reservations_add_button": "Додати зарезервовану тему", + "prefs_reservations_edit_button": "Редагувати доступ до теми", + "prefs_reservations_limit_reached": "Ви досягли ліміту зарезервованих тем.", + "prefs_reservations_table_click_to_subscribe": "Натисніть, щоб підписатися", + "prefs_reservations_table_topic_header": "Тема", + "prefs_reservations_description": "Тут ви можете зарезервувати назви тем для особистого користування. Резервування теми дає вам право власності на тему і дозволяє визначати права доступу до неї інших користувачів.", + "prefs_reservations_table": "Таблиця зарезервованих тем", + "prefs_reservations_table_access_header": "Доступ", + "prefs_reservations_table_everyone_deny_all": "Тільки я можу публікувати та підписуватись", + "prefs_reservations_table_everyone_read_only": "Я можу публікувати та підписуватись, кожен може підписатися", + "prefs_reservations_table_everyone_write_only": "Я можу публікувати і підписуватися, кожен може публікувати", + "prefs_reservations_table_everyone_read_write": "Кожен може публікувати та підписуватися", + "prefs_reservations_table_not_subscribed": "Не підписаний", + "prefs_reservations_dialog_title_add": "Зарезервувати тему", + "prefs_reservations_dialog_title_edit": "Редагувати зарезервовану тему", + "prefs_reservations_title": "Зарезервовані теми", + "prefs_reservations_delete_button": "Скинути доступ до теми", + "prefs_reservations_dialog_description": "Резервування теми дає вам право власності на цю тему і дозволяє визначати права доступу до неї інших користувачів.", + "prefs_reservations_dialog_topic_label": "Тема", + "prefs_reservations_dialog_access_label": "Доступ", + "reservation_delete_dialog_description": "Видалення резервування позбавляє вас права власності на тему і дозволяє іншим зарезервувати її. Ви можете зберегти або видалити існуючі повідомлення і вкладення.", + "reservation_delete_dialog_submit_button": "Видалити резервування", + "publish_dialog_call_item": "Телефонувати за номером {{номер}}", + "publish_dialog_chip_call_no_verified_numbers_tooltip": "Немає підтверджених номерів телефонів", + "prefs_reservations_dialog_title_delete": "Видалити резервування теми", + "reservation_delete_dialog_action_delete_title": "Видалення кешованих повідомлень і вкладень", + "reservation_delete_dialog_action_keep_title": "Збереження кешованих повідомлень і вкладень", + "reservation_delete_dialog_action_keep_description": "Повідомлення і вкладення, які кешуються на сервері, стають загальнодоступними для людей, які знають назву теми.", + "reservation_delete_dialog_action_delete_description": "Кешовані повідомлення та вкладення будуть видалені назавжди. Ця дія не може бути скасована." } From afb585e6fd47e45161bbd073d221f799882ec40a Mon Sep 17 00:00:00 2001 From: Shjosan Date: Sat, 27 May 2023 22:24:06 +0000 Subject: [PATCH 18/26] Translated using Weblate (Swedish) Currently translated at 100.0% (382 of 382 strings) Translation: ntfy/Web app Translate-URL: https://hosted.weblate.org/projects/ntfy/web/sv/ --- web/public/static/langs/sv.json | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/web/public/static/langs/sv.json b/web/public/static/langs/sv.json index 31e809c..bc4a540 100644 --- a/web/public/static/langs/sv.json +++ b/web/public/static/langs/sv.json @@ -355,5 +355,30 @@ "reservation_delete_dialog_action_keep_title": "Behåll cachade meddelanden och bilagor", "reservation_delete_dialog_action_keep_description": "Meddelanden och bilagor som lagras på servern blir offentligt synliga för personer som känner till ämnesnamnet.", "reservation_delete_dialog_action_delete_title": "Ta bort meddelanden och bilagor som sparats i cacheminnet", - "reservation_delete_dialog_description": "Om du tar bort en reservation ger du upp äganderätten till ämnet och låter andra reservera det. Du kan behålla eller radera befintliga meddelanden och bilagor." + "reservation_delete_dialog_description": "Om du tar bort en reservation ger du upp äganderätten till ämnet och låter andra reservera det. Du kan behålla eller radera befintliga meddelanden och bilagor.", + "publish_dialog_call_label": "Telefonsamtal", + "publish_dialog_call_reset": "Ta bort telefonsamtal", + "publish_dialog_chip_call_label": "Telefonsamtal", + "account_basics_phone_numbers_title": "Telefonnummer", + "account_basics_phone_numbers_description": "För notifieringar via telefonsamtal", + "account_basics_phone_numbers_no_phone_numbers_yet": "Inga telefonnummer ännu", + "account_basics_phone_numbers_copied_to_clipboard": "Telefonnummer kopierat till urklipp", + "account_basics_phone_numbers_dialog_title": "Lägga till telefonnummer", + "account_basics_phone_numbers_dialog_number_label": "Telefonnummer", + "account_basics_phone_numbers_dialog_number_placeholder": "t.ex. +1222333444", + "account_basics_phone_numbers_dialog_verify_button_sms": "Skicka SMS", + "account_basics_phone_numbers_dialog_verify_button_call": "Ring mig", + "account_basics_phone_numbers_dialog_code_label": "Verifieringskod", + "account_basics_phone_numbers_dialog_channel_call": "Ring", + "account_usage_calls_title": "Telefonsamtal som gjorts", + "account_usage_calls_none": "Inga telefonsamtal kan göras med detta konto", + "publish_dialog_call_item": "Ring telefonnummer {{number}}", + "publish_dialog_chip_call_no_verified_numbers_tooltip": "Inga verifierade telefonnummer", + "account_basics_phone_numbers_dialog_description": "För att använda funktionen för samtalsavisering måste du lägga till och verifiera minst ett telefonnummer. Verifieringen kan göras via SMS eller ett telefonsamtal.", + "account_basics_phone_numbers_dialog_code_placeholder": "t.ex. 123456", + "account_basics_phone_numbers_dialog_check_verification_button": "Bekräfta kod", + "account_basics_phone_numbers_dialog_channel_sms": "SMS", + "account_upgrade_dialog_tier_features_calls_other": "{{calls}} dagliga telefonsamtal", + "account_upgrade_dialog_tier_features_no_calls": "Inga telefonsamtal", + "account_upgrade_dialog_tier_features_calls_one": "{{calls}} dagliga telefonsamtal" } From ca25b80bfbba4a012df992d1f3f350beae18a6a4 Mon Sep 17 00:00:00 2001 From: arjan-s Date: Tue, 30 May 2023 07:40:29 +0000 Subject: [PATCH 19/26] Translated using Weblate (Dutch) Currently translated at 100.0% (382 of 382 strings) Translation: ntfy/Web app Translate-URL: https://hosted.weblate.org/projects/ntfy/web/nl/ --- web/public/static/langs/nl.json | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/web/public/static/langs/nl.json b/web/public/static/langs/nl.json index ca7a2a1..8ccb629 100644 --- a/web/public/static/langs/nl.json +++ b/web/public/static/langs/nl.json @@ -355,5 +355,30 @@ "prefs_reservations_table_topic_header": "Onderwerp", "prefs_reservations_table_access_header": "Toegang", "prefs_reservations_table_everyone_read_write": "Iedereen kan publiceren en abonneren", - "prefs_reservations_table_not_subscribed": "Niet geabonneerd" + "prefs_reservations_table_not_subscribed": "Niet geabonneerd", + "publish_dialog_call_label": "Telefoongesprek", + "publish_dialog_call_reset": "Telefoongesprek verwijderen", + "publish_dialog_chip_call_label": "Telefoongesprek", + "account_basics_phone_numbers_title": "Telefoonnummers", + "account_basics_phone_numbers_description": "Voor meldingen via telefoongesprekken", + "account_basics_phone_numbers_no_phone_numbers_yet": "Nog geen telefoonnummers", + "account_basics_phone_numbers_dialog_verify_button_call": "Bel me", + "account_upgrade_dialog_tier_features_calls_one": "{{calls}} dagelijkse telefoontjes", + "account_basics_phone_numbers_copied_to_clipboard": "Telefoonnummer gekopieerd naar klembord", + "publish_dialog_call_item": "Bel telefoonnummer {{nummer}}", + "account_basics_phone_numbers_dialog_check_verification_button": "Bevestig code", + "publish_dialog_chip_call_no_verified_numbers_tooltip": "Geen geverifieerde telefoonnummers", + "account_basics_phone_numbers_dialog_channel_call": "Telefoongesprek", + "account_basics_phone_numbers_dialog_number_label": "Telefoonnummer", + "account_basics_phone_numbers_dialog_channel_sms": "SMS", + "account_basics_phone_numbers_dialog_code_placeholder": "bijv. 123456", + "account_upgrade_dialog_tier_features_calls_other": "{{calls}} dagelijkse telefoontjes", + "account_upgrade_dialog_tier_features_no_calls": "Geen telefoontjes", + "account_basics_phone_numbers_dialog_description": "Als u de functie voor oproepmeldingen wilt gebruiken, moet u ten minste één telefoonnummer toevoegen en verifiëren. Verificatie kan worden gedaan via sms of een telefoontje.", + "account_basics_phone_numbers_dialog_title": "Telefoonnummer toevoegen", + "account_basics_phone_numbers_dialog_number_placeholder": "bijv. +1222333444", + "account_basics_phone_numbers_dialog_verify_button_sms": "Stuur SMS", + "account_basics_phone_numbers_dialog_code_label": "Verificatiecode", + "account_usage_calls_title": "Aantal telefoontjes", + "account_usage_calls_none": "Met dit account kan niet worden gebeld" } From 453bf435b0be9f8ed459e4fb43abbb87eb6d1f64 Mon Sep 17 00:00:00 2001 From: nimbleghost <132819643+nimbleghost@users.noreply.github.com> Date: Wed, 31 May 2023 18:55:17 +0200 Subject: [PATCH 20/26] Fix account sync race condition --- web/src/components/hooks.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/src/components/hooks.js b/web/src/components/hooks.js index b9c5536..6b68188 100644 --- a/web/src/components/hooks.js +++ b/web/src/components/hooks.js @@ -47,6 +47,13 @@ export const useConnectionListeners = (account, subscriptions, users) => { const handleMessage = async (subscriptionId, message) => { const subscription = await subscriptionManager.get(subscriptionId); + + // Race condition: sometimes the subscription is already unsubscribed from account + // sync before the message is handled + if (!subscription) { + return; + } + if (subscription.internal) { await handleInternalMessage(message); } else { From d084a415f354c7895324b098450f27e0de857f23 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Wed, 31 May 2023 15:36:02 -0400 Subject: [PATCH 21/26] Do not forward UP messages to upstream --- docs/releases.md | 1 + server/server.go | 2 +- server/server_test.go | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/releases.md b/docs/releases.md index 71fceb1..6c88dd1 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1225,6 +1225,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release **Bug fixes:** * Support encoding any header as RFC 2047 ([#737](https://github.com/binwiederhier/ntfy/issues/737), thanks to [@cfouche3005](https://github.com/cfouche3005) for reporting) +* Do not forward poll requests for UnifiedPush messages (no ticket, thanks to NoName for reporting) **Maintenance:** diff --git a/server/server.go b/server/server.go index ac54aa5..d2fac01 100644 --- a/server/server.go +++ b/server/server.go @@ -760,7 +760,7 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e if s.config.TwilioAccount != "" && call != "" { go s.callPhone(v, r, m, call) } - if s.config.UpstreamBaseURL != "" { + if s.config.UpstreamBaseURL != "" && !unifiedpush { // UP messages are not sent to upstream go s.forwardPollRequest(v, m) } } else { diff --git a/server/server_test.go b/server/server_test.go index 73df276..d7c4a7c 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -2559,6 +2559,29 @@ func TestServer_UpstreamBaseURL_With_Access_Token_Success(t *testing.T) { }) } +func TestServer_UpstreamBaseURL_DoNotForwardUnifiedPush(t *testing.T) { + upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Fatal("UnifiedPush messages should not be forwarded") + })) + defer upstreamServer.Close() + + c := newTestConfigWithAuthFile(t) + c.BaseURL = "http://myserver.internal" + c.UpstreamBaseURL = upstreamServer.URL + s := newTestServer(t, c) + + // Send UP message, this should not forward to upstream server + response := request(t, s, "PUT", "/mytopic?up=1", `hi there`, nil) + require.Equal(t, 200, response.Code) + m := toMessage(t, response.Body.String()) + require.NotEmpty(t, m.ID) + require.Equal(t, "hi there", m.Message) + + // Forwarding is done asynchronously, so wait a bit. + // This ensures that the t.Fatal above is actually not triggered. + time.Sleep(500 * time.Millisecond) +} + func newTestConfig(t *testing.T) *Config { conf := NewConfig() conf.BaseURL = "http://127.0.0.1:12345" From 04cc71af90c92a42589c603c24920bea95b4db54 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Thu, 1 Jun 2023 13:56:32 -0400 Subject: [PATCH 22/26] .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b0c2d33..f695607 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ dist/ +dev-dist/ build/ .idea/ .vscode/ From dc8932cd953025708111c222d1c898202fbb1276 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Thu, 1 Jun 2023 14:08:51 -0400 Subject: [PATCH 23/26] Fix segault in ntfy pub --- client/client.go | 5 ++++- cmd/app.go | 1 + cmd/publish.go | 4 ++++ cmd/subscribe.go | 4 +++- docs/releases.md | 1 + 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/client/client.go b/client/client.go index b744fa1..e719e9e 100644 --- a/client/client.go +++ b/client/client.go @@ -97,7 +97,10 @@ func (c *Client) Publish(topic, message string, options ...PublishOption) (*Mess // WithNoFirebase, and the generic WithHeader. func (c *Client) PublishReader(topic string, body io.Reader, options ...PublishOption) (*Message, error) { topicURL := c.expandTopicURL(topic) - req, _ := http.NewRequest("POST", topicURL, body) + req, err := http.NewRequest("POST", topicURL, body) + if err != nil { + return nil, err + } for _, option := range options { if err := option(req); err != nil { return nil, err diff --git a/cmd/app.go b/cmd/app.go index edef5b4..fd99263 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -29,6 +29,7 @@ var flagsDefault = []cli.Flag{ var ( logLevelOverrideRegex = regexp.MustCompile(`(?i)^([^=\s]+)(?:\s*=\s*(\S+))?\s*->\s*(TRACE|DEBUG|INFO|WARN|ERROR)$`) + topicRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) // Same as in server/server.go ) // New creates a new CLI application diff --git a/cmd/publish.go b/cmd/publish.go index 0179f9f..bce27e0 100644 --- a/cmd/publish.go +++ b/cmd/publish.go @@ -249,6 +249,10 @@ func parseTopicMessageCommand(c *cli.Context) (topic string, message string, com if c.String("message") != "" { message = c.String("message") } + if !topicRegex.MatchString(topic) { + err = fmt.Errorf("topic %s contains invalid characters", topic) + return + } return } diff --git a/cmd/subscribe.go b/cmd/subscribe.go index 2691e6a..81f5988 100644 --- a/cmd/subscribe.go +++ b/cmd/subscribe.go @@ -72,7 +72,7 @@ ntfy subscribe TOPIC COMMAND $NTFY_TITLE $title, $t Message title $NTFY_PRIORITY $priority, $prio, $p Message priority (1=min, 5=max) $NTFY_TAGS $tags, $tag, $ta Message tags (comma separated list) - $NTFY_RAW $raw Raw JSON message + $NTFY_RAW $raw Raw JSON message Examples: ntfy sub mytopic 'notify-send "$m"' # Execute command for incoming messages @@ -108,6 +108,8 @@ func execSubscribe(c *cli.Context) error { // Checks if user != "" && token != "" { return errors.New("cannot set both --user and --token") + } else if !topicRegex.MatchString(topic) { + return fmt.Errorf("topic %s contains invalid characters", topic) } if !fromConfig { diff --git a/docs/releases.md b/docs/releases.md index 6c88dd1..0e93b67 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1226,6 +1226,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release * Support encoding any header as RFC 2047 ([#737](https://github.com/binwiederhier/ntfy/issues/737), thanks to [@cfouche3005](https://github.com/cfouche3005) for reporting) * Do not forward poll requests for UnifiedPush messages (no ticket, thanks to NoName for reporting) +* Fix `ntfy pub %` segfaulting ([#760](https://github.com/binwiederhier/ntfy/issues/760), thanks to [@clesmian](https://github.com/clesmian) for reporting) **Maintenance:** From f58c1e4c84e80f7fa1a2bea2e1b2bfae4240c7d0 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Thu, 1 Jun 2023 16:01:39 -0400 Subject: [PATCH 24/26] Fix previous fix --- client/client.go | 62 ++++++++++++++++++++----------------------- client/client_test.go | 2 +- cmd/app.go | 1 - cmd/publish.go | 4 --- cmd/subscribe.go | 12 ++++++--- 5 files changed, 38 insertions(+), 43 deletions(-) diff --git a/client/client.go b/client/client.go index e719e9e..93cf7da 100644 --- a/client/client.go +++ b/client/client.go @@ -11,23 +11,25 @@ import ( "heckel.io/ntfy/util" "io" "net/http" + "regexp" "strings" "sync" "time" ) -// Event type constants const ( - MessageEvent = "message" - KeepaliveEvent = "keepalive" - OpenEvent = "open" - PollRequestEvent = "poll_request" + // MessageEvent identifies a message event + MessageEvent = "message" ) const ( maxResponseBytes = 4096 ) +var ( + topicRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) // Same as in server/server.go +) + // Client is the ntfy client that can be used to publish and subscribe to ntfy topics type Client struct { Messages chan *Message @@ -96,7 +98,10 @@ func (c *Client) Publish(topic, message string, options ...PublishOption) (*Mess // To pass title, priority and tags, check out WithTitle, WithPriority, WithTagsList, WithDelay, WithNoCache, // WithNoFirebase, and the generic WithHeader. func (c *Client) PublishReader(topic string, body io.Reader, options ...PublishOption) (*Message, error) { - topicURL := c.expandTopicURL(topic) + topicURL, err := c.expandTopicURL(topic) + if err != nil { + return nil, err + } req, err := http.NewRequest("POST", topicURL, body) if err != nil { return nil, err @@ -136,11 +141,14 @@ func (c *Client) PublishReader(topic string, body io.Reader, options ...PublishO // By default, all messages will be returned, but you can change this behavior using a SubscribeOption. // See WithSince, WithSinceAll, WithSinceUnixTime, WithScheduled, and the generic WithQueryParam. func (c *Client) Poll(topic string, options ...SubscribeOption) ([]*Message, error) { + topicURL, err := c.expandTopicURL(topic) + if err != nil { + return nil, err + } ctx := context.Background() messages := make([]*Message, 0) msgChan := make(chan *Message) errChan := make(chan error) - topicURL := c.expandTopicURL(topic) log.Debug("%s Polling from topic", util.ShortTopicURL(topicURL)) options = append(options, WithPoll()) go func() { @@ -169,15 +177,18 @@ func (c *Client) Poll(topic string, options ...SubscribeOption) ([]*Message, err // Example: // // c := client.New(client.NewConfig()) -// subscriptionID := c.Subscribe("mytopic") +// subscriptionID, _ := c.Subscribe("mytopic") // for m := range c.Messages { // fmt.Printf("New message: %s", m.Message) // } -func (c *Client) Subscribe(topic string, options ...SubscribeOption) string { +func (c *Client) Subscribe(topic string, options ...SubscribeOption) (string, error) { + topicURL, err := c.expandTopicURL(topic) + if err != nil { + return "", err + } c.mu.Lock() defer c.mu.Unlock() subscriptionID := util.RandomString(10) - topicURL := c.expandTopicURL(topic) log.Debug("%s Subscribing to topic", util.ShortTopicURL(topicURL)) ctx, cancel := context.WithCancel(context.Background()) c.subscriptions[subscriptionID] = &subscription{ @@ -186,7 +197,7 @@ func (c *Client) Subscribe(topic string, options ...SubscribeOption) string { cancel: cancel, } go handleSubscribeConnLoop(ctx, c.Messages, topicURL, subscriptionID, options...) - return subscriptionID + return subscriptionID, nil } // Unsubscribe unsubscribes from a topic that has been previously subscribed to using the unique @@ -202,31 +213,16 @@ func (c *Client) Unsubscribe(subscriptionID string) { sub.cancel() } -// UnsubscribeAll unsubscribes from a topic that has been previously subscribed with Subscribe. -// If there are multiple subscriptions matching the topic, all of them are unsubscribed from. -// -// A topic can be either a full URL (e.g. https://myhost.lan/mytopic), a short URL which is then prepended https:// -// (e.g. myhost.lan -> https://myhost.lan), or a short name which is expanded using the default host in the -// config (e.g. mytopic -> https://ntfy.sh/mytopic). -func (c *Client) UnsubscribeAll(topic string) { - c.mu.Lock() - defer c.mu.Unlock() - topicURL := c.expandTopicURL(topic) - for _, sub := range c.subscriptions { - if sub.topicURL == topicURL { - delete(c.subscriptions, sub.ID) - sub.cancel() - } - } -} - -func (c *Client) expandTopicURL(topic string) string { +func (c *Client) expandTopicURL(topic string) (string, error) { if strings.HasPrefix(topic, "http://") || strings.HasPrefix(topic, "https://") { - return topic + return topic, nil } else if strings.Contains(topic, "/") { - return fmt.Sprintf("https://%s", topic) + return fmt.Sprintf("https://%s", topic), nil } - return fmt.Sprintf("%s/%s", c.config.DefaultHost, topic) + if !topicRegex.MatchString(topic) { + return "", fmt.Errorf("invalid topic name: %s", topic) + } + return fmt.Sprintf("%s/%s", c.config.DefaultHost, topic), nil } func handleSubscribeConnLoop(ctx context.Context, msgChan chan *Message, topicURL, subcriptionID string, options ...SubscribeOption) { diff --git a/client/client_test.go b/client/client_test.go index a71ea5c..f0b15a3 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -21,7 +21,7 @@ func TestClient_Publish_Subscribe(t *testing.T) { defer test.StopServer(t, s, port) c := client.New(newTestConfig(port)) - subscriptionID := c.Subscribe("mytopic") + subscriptionID, _ := c.Subscribe("mytopic") time.Sleep(time.Second) msg, err := c.Publish("mytopic", "some message") diff --git a/cmd/app.go b/cmd/app.go index fd99263..edef5b4 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -29,7 +29,6 @@ var flagsDefault = []cli.Flag{ var ( logLevelOverrideRegex = regexp.MustCompile(`(?i)^([^=\s]+)(?:\s*=\s*(\S+))?\s*->\s*(TRACE|DEBUG|INFO|WARN|ERROR)$`) - topicRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) // Same as in server/server.go ) // New creates a new CLI application diff --git a/cmd/publish.go b/cmd/publish.go index bce27e0..0179f9f 100644 --- a/cmd/publish.go +++ b/cmd/publish.go @@ -249,10 +249,6 @@ func parseTopicMessageCommand(c *cli.Context) (topic string, message string, com if c.String("message") != "" { message = c.String("message") } - if !topicRegex.MatchString(topic) { - err = fmt.Errorf("topic %s contains invalid characters", topic) - return - } return } diff --git a/cmd/subscribe.go b/cmd/subscribe.go index 81f5988..c85c468 100644 --- a/cmd/subscribe.go +++ b/cmd/subscribe.go @@ -108,8 +108,6 @@ func execSubscribe(c *cli.Context) error { // Checks if user != "" && token != "" { return errors.New("cannot set both --user and --token") - } else if !topicRegex.MatchString(topic) { - return fmt.Errorf("topic %s contains invalid characters", topic) } if !fromConfig { @@ -196,7 +194,10 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic, topicOptions = append(topicOptions, auth) } - subscriptionID := cl.Subscribe(s.Topic, topicOptions...) + subscriptionID, err := cl.Subscribe(s.Topic, topicOptions...) + if err != nil { + return err + } if s.Command != "" { cmds[subscriptionID] = s.Command } else if conf.DefaultCommand != "" { @@ -206,7 +207,10 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic, } } if topic != "" { - subscriptionID := cl.Subscribe(topic, options...) + subscriptionID, err := cl.Subscribe(topic, options...) + if err != nil { + return err + } cmds[subscriptionID] = command } for m := range cl.Messages { From 77f5dd705c471f12c79527d5f3fdff4687103bf5 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Sat, 3 Jun 2023 15:29:51 -0400 Subject: [PATCH 25/26] *: python pip in debian bookworm/testing needs venv When testing this build using the upcoming debian bookworm/testing, the python pip v3.11 fails to install requirements that may clobber the host install packages. It prints out that venv must be used. This change works fine on the current debian bullseye, and will continue to work once folks switch to the upcoming debian bookworm release. Signed-off-by: Vincent Batts --- Dockerfile-build | 3 ++- Makefile | 14 ++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Dockerfile-build b/Dockerfile-build index f8af256..dec2d96 100644 --- a/Dockerfile-build +++ b/Dockerfile-build @@ -8,7 +8,8 @@ RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash RUN apt-get install -y \ build-essential \ nodejs \ - python3-pip + python3-pip \ + python3-venv WORKDIR /app ADD Makefile . diff --git a/Makefile b/Makefile index 8cb7523..440bfa6 100644 --- a/Makefile +++ b/Makefile @@ -110,8 +110,9 @@ build-deps-ubuntu: docs: docs-deps docs-build -docs-build: .PHONY - @if ! /bin/echo -e "import sys\nif sys.version_info < (3,8):\n exit(1)" | python3; then \ +docs-build: venv .PHONY + @. venv/bin/activate && \ + if ! /bin/echo -e "import sys\nif sys.version_info < (3,8):\n exit(1)" | python3; then \ if which python3.8; then \ echo "python3.8 $(shell which mkdocs) build"; \ python3.8 $(shell which mkdocs) build; \ @@ -124,10 +125,15 @@ docs-build: .PHONY mkdocs build; \ fi -docs-deps: .PHONY +venv: + python3 -m venv ./venv + +docs-deps: venv .PHONY + . venv/bin/activate && \ pip3 install -r requirements.txt -docs-deps-update: .PHONY +docs-deps-update: venv .PHONY + . venv/bin/activate && \ pip3 install -r requirements.txt --upgrade From 8eecd3c72a10a46da750d80f36a465694ff54361 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Sat, 3 Jun 2023 15:33:31 -0400 Subject: [PATCH 26/26] Dockerfile*: use my images Signed-off-by: Vincent Batts --- Dockerfile | 2 +- Dockerfile-build | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7c2052e..feb813f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine +FROM r.batts.cloud/debian:testing LABEL org.opencontainers.image.authors="philipp.heckel@gmail.com" LABEL org.opencontainers.image.url="https://ntfy.sh/" diff --git a/Dockerfile-build b/Dockerfile-build index dec2d96..9c6d1bc 100644 --- a/Dockerfile-build +++ b/Dockerfile-build @@ -1,4 +1,4 @@ -FROM golang:1.19-bullseye as builder +FROM r.batts.cloud/golang:1.19 as builder ARG VERSION=dev ARG COMMIT=unknown @@ -37,7 +37,7 @@ ADD ./user ./user ADD ./util ./util RUN make VERSION=$VERSION COMMIT=$COMMIT cli-linux-server -FROM alpine +FROM r.batts.cloud/debian:testing LABEL org.opencontainers.image.authors="philipp.heckel@gmail.com" LABEL org.opencontainers.image.url="https://ntfy.sh/"