diff options
Diffstat (limited to 'vnext/server')
-rw-r--r-- | vnext/server/Dockerfile | 18 | ||||
-rw-r--r-- | vnext/server/index.js | 2 | ||||
-rw-r--r-- | vnext/server/middleware/android.js | 43 | ||||
-rw-r--r-- | vnext/server/middleware/android.spec.js | 37 | ||||
-rw-r--r-- | vnext/server/middleware/event.js | 3 | ||||
-rw-r--r-- | vnext/server/middleware/legacy.json | 142 | ||||
-rw-r--r-- | vnext/server/sape.js | 4 | ||||
-rw-r--r-- | vnext/server/sender.js | 20 | ||||
-rw-r--r-- | vnext/server/webpack.config.js | 59 |
9 files changed, 274 insertions, 54 deletions
diff --git a/vnext/server/Dockerfile b/vnext/server/Dockerfile new file mode 100644 index 00000000..37f8c896 --- /dev/null +++ b/vnext/server/Dockerfile @@ -0,0 +1,18 @@ +FROM node:20.17.0-bookworm-slim + +# Install app dependencies +COPY package.json . +COPY package-lock.json . +RUN npm install + +# Bundle APP files +COPY public/server.js . + +ENV NODE_ENV production +ENV NPM_CONFIG_LOGLEVEL warn +ENV DEBUG http + +# Expose the listening port of your app +EXPOSE 8081 + +CMD [ "node", "server.js" ] diff --git a/vnext/server/index.js b/vnext/server/index.js index 16abc8db..692fbd76 100644 --- a/vnext/server/index.js +++ b/vnext/server/index.js @@ -9,6 +9,7 @@ import serverRenderer from './middleware/renderer' import event from './middleware/event' import oembed from './middleware/oembed' import urlExpand from './middleware/urlexpand' +import releases from './middleware/android' const PORT = process.env.LISTEN_PORT || 8081 import path from 'path' @@ -23,6 +24,7 @@ const router = express.Router() router.post('/api/v2/sender', event) router.get('/api/v2/oembed', oembed) router.get('/api/v2/urlexpand', urlExpand) +router.get('/api/apps/android/releases', releases) const durov_webhook = webhookPath() if (durov_webhook) { diff --git a/vnext/server/middleware/android.js b/vnext/server/middleware/android.js new file mode 100644 index 00000000..ae5f8fe8 --- /dev/null +++ b/vnext/server/middleware/android.js @@ -0,0 +1,43 @@ +import debug from 'debug' +var log = debug('android') + +import * as legacy_data from './legacy.json' + +const releases_url = 'https://api.github.com/repos/Juick/Juick-Android/releases' + +/** + * Return android releases + * @type {import('express').RequestParamHandler} + */ +const releases = async (req, res) => { + let agent = req.headers['user-agent'] || 'unknown' + let android_version = parse_agent_android_sdk_version(agent) + log(`releases request from ${android_version || agent}`) + if (android_version > 0) { + if (is_legacy_android(android_version)) { + log('responding with legacy stub') + return res.json([legacy_data]) + } else { + log('redirecting to Github') + return res.redirect(releases_url) + } + } + return res.json([]) +} + +const parse_agent_android_sdk_version = (agent = '') => { + let version = agent.split(' ', 3) + let is_android_app = version.length == 3 && version[2].startsWith('Android') + if (is_android_app) { + let android_version = version[2].split('/') + let is_valid_version = android_version.length == 2 + return is_valid_version ? +(android_version[1]) : NaN + } + return NaN +} + +const is_legacy_android = (version) => { + return version < 24 +} + +export default releases diff --git a/vnext/server/middleware/android.spec.js b/vnext/server/middleware/android.spec.js new file mode 100644 index 00000000..448714ac --- /dev/null +++ b/vnext/server/middleware/android.spec.js @@ -0,0 +1,37 @@ +import request from 'supertest' +import express from 'express' + +import releases from './android' + +const app = express() +app.get('/releases', releases) + +describe('Releases helper', () => { + it('Should respond with empty array to unknown user agents', async () => { + return request(app) + .get('/releases') + .expect(200) + .then(response => { + expect(response.body).toStrictEqual([]) + }) + }) + it('Should respond with a single legacy version data to old Android app', async () => { + return request(app) + .get('/releases') + .set('User-Agent', 'Juick/100 okhttp/3.12 Android/19') + .expect(200) + .then(response => { + expect(response.body.length).toBe(1) + expect(response.body[0].name).toBe('3.1.216') + }) + }) + it('Should redirect to Github when Android version is ok', async () => { + return request(app) + .get('/releases') + .set('User-Agent', 'Juick/100 okhttp/3.12 Android/24') + .expect(302) + .then(response => { + expect(response.redirect).toBe(true) + }) + }) +}) diff --git a/vnext/server/middleware/event.js b/vnext/server/middleware/event.js index 8280f32b..c80f2249 100644 --- a/vnext/server/middleware/event.js +++ b/vnext/server/middleware/event.js @@ -26,8 +26,9 @@ function processMessageEvent(msg) { subscribers(new URLSearchParams(JSON.parse(JSON.stringify(params)))).then(users => { return users.map(user => { log(`${user.uname}: ${user.unreadCount}`) + let tokenTypes = msg.service ? ['mpns', 'apns', 'fcm'] : ['mpns', 'apns', 'fcm', 'web'] let [sandboxTokens, productionTokens] = (user.tokens || []) - .filter(t => ['mpns', 'apns', 'fcm', 'web'].includes(t.type)) + .filter(t => tokenTypes.includes(t.type)) .map(t => t.type === 'web' ? JSON.parse(t.token) : t.token) .reduce((result, element) => { allSandboxIds.includes(user.uid) diff --git a/vnext/server/middleware/legacy.json b/vnext/server/middleware/legacy.json new file mode 100644 index 00000000..840708e6 --- /dev/null +++ b/vnext/server/middleware/legacy.json @@ -0,0 +1,142 @@ +{ + "url": "https://api.github.com/repos/juick/Juick-Android/releases/164308344", + "assets_url": "https://api.github.com/repos/juick/Juick-Android/releases/164308344/assets", + "upload_url": "https://uploads.github.com/repos/juick/Juick-Android/releases/164308344/assets{?name,label}", + "html_url": "https://github.com/juick/Juick-Android/releases/tag/v3.1.216", + "id": 164308344, + "author": { + "login": "vitalyster", + "id": 1052407, + "node_id": "MDQ6VXNlcjEwNTI0MDc=", + "avatar_url": "https://avatars.githubusercontent.com/u/1052407?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vitalyster", + "html_url": "https://github.com/vitalyster", + "followers_url": "https://api.github.com/users/vitalyster/followers", + "following_url": "https://api.github.com/users/vitalyster/following{/other_user}", + "gists_url": "https://api.github.com/users/vitalyster/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vitalyster/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vitalyster/subscriptions", + "organizations_url": "https://api.github.com/users/vitalyster/orgs", + "repos_url": "https://api.github.com/users/vitalyster/repos", + "events_url": "https://api.github.com/users/vitalyster/events{/privacy}", + "received_events_url": "https://api.github.com/users/vitalyster/received_events", + "type": "User", + "site_admin": false + }, + "node_id": "RE_kwDOADJkWc4JyyV4", + "tag_name": "v3.1.216", + "target_commitish": "master", + "name": "3.1.216", + "draft": false, + "prerelease": true, + "created_at": "2024-07-07T19:47:32Z", + "published_at": "2024-07-07T19:51:29Z", + "assets": [ + { + "url": "https://api.github.com/repos/juick/Juick-Android/releases/assets/178215973", + "id": 178215973, + "node_id": "RA_kwDOADJkWc4Kn1wl", + "name": "Juick-free-v3.1.216.apk", + "label": null, + "uploader": { + "login": "vitalyster", + "id": 1052407, + "node_id": "MDQ6VXNlcjEwNTI0MDc=", + "avatar_url": "https://avatars.githubusercontent.com/u/1052407?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vitalyster", + "html_url": "https://github.com/vitalyster", + "followers_url": "https://api.github.com/users/vitalyster/followers", + "following_url": "https://api.github.com/users/vitalyster/following{/other_user}", + "gists_url": "https://api.github.com/users/vitalyster/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vitalyster/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vitalyster/subscriptions", + "organizations_url": "https://api.github.com/users/vitalyster/orgs", + "repos_url": "https://api.github.com/users/vitalyster/repos", + "events_url": "https://api.github.com/users/vitalyster/events{/privacy}", + "received_events_url": "https://api.github.com/users/vitalyster/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 7807858, + "download_count": 2, + "created_at": "2024-07-07T19:51:12Z", + "updated_at": "2024-07-07T19:51:25Z", + "browser_download_url": "https://github.com/juick/Juick-Android/releases/download/v3.1.216/Juick-free-v3.1.216.apk" + }, + { + "url": "https://api.github.com/repos/juick/Juick-Android/releases/assets/178215894", + "id": 178215894, + "node_id": "RA_kwDOADJkWc4Kn1vW", + "name": "Juick-google-v3.1.216.apk", + "label": null, + "uploader": { + "login": "vitalyster", + "id": 1052407, + "node_id": "MDQ6VXNlcjEwNTI0MDc=", + "avatar_url": "https://avatars.githubusercontent.com/u/1052407?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vitalyster", + "html_url": "https://github.com/vitalyster", + "followers_url": "https://api.github.com/users/vitalyster/followers", + "following_url": "https://api.github.com/users/vitalyster/following{/other_user}", + "gists_url": "https://api.github.com/users/vitalyster/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vitalyster/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vitalyster/subscriptions", + "organizations_url": "https://api.github.com/users/vitalyster/orgs", + "repos_url": "https://api.github.com/users/vitalyster/repos", + "events_url": "https://api.github.com/users/vitalyster/events{/privacy}", + "received_events_url": "https://api.github.com/users/vitalyster/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 8421195, + "download_count": 7, + "created_at": "2024-07-07T19:50:49Z", + "updated_at": "2024-07-07T19:51:04Z", + "browser_download_url": "https://github.com/juick/Juick-Android/releases/download/v3.1.216/Juick-google-v3.1.216.apk" + }, + { + "url": "https://api.github.com/repos/juick/Juick-Android/releases/assets/178215877", + "id": 178215877, + "node_id": "RA_kwDOADJkWc4Kn1vF", + "name": "Juick-huawei-v3.1.216.apk", + "label": null, + "uploader": { + "login": "vitalyster", + "id": 1052407, + "node_id": "MDQ6VXNlcjEwNTI0MDc=", + "avatar_url": "https://avatars.githubusercontent.com/u/1052407?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vitalyster", + "html_url": "https://github.com/vitalyster", + "followers_url": "https://api.github.com/users/vitalyster/followers", + "following_url": "https://api.github.com/users/vitalyster/following{/other_user}", + "gists_url": "https://api.github.com/users/vitalyster/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vitalyster/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vitalyster/subscriptions", + "organizations_url": "https://api.github.com/users/vitalyster/orgs", + "repos_url": "https://api.github.com/users/vitalyster/repos", + "events_url": "https://api.github.com/users/vitalyster/events{/privacy}", + "received_events_url": "https://api.github.com/users/vitalyster/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 8275073, + "download_count": 3, + "created_at": "2024-07-07T19:50:27Z", + "updated_at": "2024-07-07T19:50:41Z", + "browser_download_url": "https://github.com/juick/Juick-Android/releases/download/v3.1.216/Juick-huawei-v3.1.216.apk" + } + ], + "tarball_url": "https://api.github.com/repos/juick/Juick-Android/tarball/v3.1.216", + "zipball_url": "https://api.github.com/repos/juick/Juick-Android/zipball/v3.1.216", + "body": "* Bugfixes and performance improvements" +} diff --git a/vnext/server/sape.js b/vnext/server/sape.js index cff9b48a..d88ee75a 100644 --- a/vnext/server/sape.js +++ b/vnext/server/sape.js @@ -2,6 +2,8 @@ import { parseStringPromise } from 'xml2js' import axios from 'axios' import { setupCache } from 'axios-cache-interceptor' import config from 'config' +import debug from 'debug' +const log = debug('sape') const token = config.get('service.sape.token') || process.env.SAPE_TOKEN @@ -14,7 +16,7 @@ const token = config.get('service.sape.token') || process.env.SAPE_TOKEN */ export const getLinks = async (uri, sapeCookie) => { if (!token) { - console.warn('Sape is not configured') + log('Sape is not configured') return [] } const response = await sape.get(`http://dispencer-01.sape.ru/code.php?user=${token}&host=juick.com&charset=UTF-8&as_xml=true`) diff --git a/vnext/server/sender.js b/vnext/server/sender.js index be907059..fa8c4853 100644 --- a/vnext/server/sender.js +++ b/vnext/server/sender.js @@ -19,20 +19,22 @@ const apnConfig = (production = true) => { } return apn } -const gcmConfig = { - ...cfg.gcm, - id: cfg.gcm?.id || process.env.JUICK_GCM_ID +const fcmConfig = { + ...cfg.fcm, + serviceAccountKey: process.env.JUICK_FCM_SERVICE_ACCOUNT_FILE } +console.log(`fcm config: ${JSON.stringify(fcmConfig)}`) + const push = new PushNotifications({ ...config, apn: apnConfig(true), - gcm: gcmConfig, + fcm: fcmConfig, }) const sandbox = new PushNotifications({ ...config, apn: apnConfig(false), - gcm: gcmConfig + fcm: fcmConfig }) /** @type {string} */ @@ -139,14 +141,18 @@ export function buildNotification(user, msg) { template.text2 = body template.title = title template.body = body - template.badge = user.unreadCount || 0 + //template.badge = user.unreadCount || 0 template.mutableContent = 1 template.color = '#3c77aa' template.icon = 'ic_notification' template.clickAction = 'com.juick.NEW_EVENT_ACTION' const tag = msg.mid == 0 ? msg.user.uname : msg.mid template.tag = `${tag}` - template.android_channel_id = 'default' + template.fcm_notification = { + title: title, + body: body, + channel_id: 'default' + } } return template } diff --git a/vnext/server/webpack.config.js b/vnext/server/webpack.config.js index 9ffb3360..78c90050 100644 --- a/vnext/server/webpack.config.js +++ b/vnext/server/webpack.config.js @@ -1,21 +1,17 @@ -const ESLintPlugin = require('eslint-webpack-plugin') -const TerserPlugin = require('terser-webpack-plugin') +const nodeExternals = require('webpack-node-externals') +const path = require('path') -module.exports = () => { - const node_env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development' - const dev = node_env !== 'production' - const config = { - mode: node_env, - devtool: dev ? 'cheap-module-source-map' : false, +module.exports = { + devtool: false, entry: { 'server': [ - __dirname + '/index.js' + path.resolve(__dirname, 'index.js') ] }, target: 'node', output: { - path: __dirname + '/../../public', - filename: '[name].js' + path: path.resolve(__dirname, '../../public'), + filename: '[name].js', }, module: { rules: [{ @@ -26,7 +22,8 @@ module.exports = () => { loader: 'babel-loader' }, { test: /\.(png|jpe?g|gif|svg)$/i, - type: 'asset/resource' + type: 'asset/resource', + dependency: { not: ['url'] }, }] }, plugins: [ @@ -34,37 +31,9 @@ module.exports = () => { resolve: { symlinks: false, extensions: ['.js'] - } - } - if (dev) { - config.plugins.push( - new ESLintPlugin({ - files: __dirname + '/src', - lintDirtyModulesOnly: true, - failOnWarning: false, - failOnError: true, - fix: false - })) - config.devServer = { - hot: true, - historyApiFallback: true, - client: { - overlay: { - runtimeErrors: true - } - } - } - } - config.optimization = { - minimize: !dev, - minimizer: [ - new TerserPlugin({ - minify: TerserPlugin.swcMinify, - // `terserOptions` options will be passed to `swc` (`@swc/core`) - // Link to options - https://swc.rs/docs/config-js-minify - terserOptions: {}, - }), - ] + }, + externalsPresets: { node: true }, // in order to ignore built-in modules like path, fs, etc. + externals: [nodeExternals({ + allowlist: [/\.(?!(?:jsx?|json)$).{1,5}$/i] + })], } - return config -} |