Merge pull request #40 from Gimanh/ver/1-30

chore: version 1.32.0
This commit is contained in:
Gimanh
2026-03-22 23:30:20 +01:00
committed by GitHub
13 changed files with 92 additions and 35 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "taskview-ce-api-server",
"version": "1.24.0",
"version": "1.32.0",
"scripts": {
"dev": "bun run --watch ./server.ts",
"start": "NODE_ENV=production node ./dist/taskview-server.js",
@@ -74,4 +74,4 @@
"engines": {
"node": ">=24 <25"
}
}
}
+2 -2
View File
@@ -1,3 +1,4 @@
import jwt from 'jsonwebtoken';
import { $logger } from '../modules/logget';
interface CentrifugoPublishPayload {
@@ -57,8 +58,7 @@ export class CentrifugoClient {
}
static generateConnectionToken(userId: number): string {
const jwt = require('jsonwebtoken');
const secret = process.env.CENTRIFUGO_TOKEN_SECRET || process.env.JWT_SIGN;
const secret = process.env.CENTRIFUGO_TOKEN_SECRET || process.env.JWT_SIGN || '';
return jwt.sign(
{ sub: String(userId) },
secret,
@@ -89,15 +89,12 @@ export class NotificationsController {
return res.status(401).send('Unauthorized');
}
const publicPort = process.env.CENTRIFUGO_PUBLIC_PORT;
if (!publicPort) {
const publicUrl = process.env.CENTRIFUGO_PUBLIC_URL;
if (!publicUrl) {
return res.tvJson({ token: null, url: null });
}
const appUrl = process.env.APP_URL || 'http://localhost:3000';
const parsed = new URL(appUrl);
const protocol = parsed.protocol === 'http:' ? 'ws' : 'wss';
const url = `${protocol}://${parsed.hostname}:${publicPort}/connection/websocket`;
const url = publicUrl;
const token = CentrifugoClient.generateConnectionToken(userId);
return res.tvJson({ token, url });
+1 -1
View File
@@ -24,7 +24,7 @@ Notifications can be sent through two channels (with email planned for the futur
| Channel | Description | Required configuration |
|---|---|---|
| **Push** | Native push notifications on iOS/Android via Firebase Cloud Messaging | `FIREBASE_CREDENTIALS_PATH` |
| **In-app (WebSocket)** | Real-time delivery to the browser via Centrifugo | `CENTRIFUGO_API_URL`, `CENTRIFUGO_API_KEY`, `CENTRIFUGO_TOKEN_SECRET`, `CENTRIFUGO_PUBLIC_PORT` |
| **In-app (WebSocket)** | Real-time delivery to the browser via Centrifugo | `CENTRIFUGO_API_URL`, `CENTRIFUGO_API_KEY`, `CENTRIFUGO_TOKEN_SECRET`, `CENTRIFUGO_PUBLIC_URL` |
Both channels are optional. If Firebase is not configured, push notifications are silently skipped. If Centrifugo is not configured, in-app real-time delivery is skipped. Notifications are always saved to the database regardless of channel availability.
@@ -105,9 +105,9 @@ Required for in-app real-time notification delivery. Without Centrifugo, notific
| Variable | Required | Default | Description |
|---|---|---|---|
| `CENTRIFUGO_API_URL` | No | - | Internal Centrifugo API URL (e.g. `http://centrifugo:8000`) |
| `CENTRIFUGO_API_KEY` | No | - | Centrifugo API key for server-to-server communication |
| `CENTRIFUGO_TOKEN_SECRET` | No | - | Secret for generating client connection tokens (HMAC) |
| `CENTRIFUGO_PUBLIC_PORT` | No | - | Public port that browser clients use to connect to Centrifugo WebSocket |
| `CENTRIFUGO_API_KEY` | No | - | Centrifugo API key for server-to-server communication. Must match `http_api.key` in Centrifugo config. |
| `CENTRIFUGO_TOKEN_SECRET` | No | - | Secret for generating client connection tokens (HMAC). Must match `client.token.hmac_secret_key` in Centrifugo config. |
| `CENTRIFUGO_PUBLIC_URL` | No | - | Full WebSocket URL for browser clients (e.g. `wss://api.example.com/centrifugo/connection/websocket`) |
### Firebase Cloud Messaging (mobile push notifications)
@@ -117,6 +117,56 @@ Required for native push notifications on iOS and Android. Without Firebase, pus
|---|---|---|---|
| `FIREBASE_CREDENTIALS_PATH` | No | - | Path to Firebase service account JSON file (e.g. `./firebase-credentials.json`) |
### Centrifugo configuration file
Centrifugo v6 uses a nested JSON config. Create `centrifugo/config.json`:
```json
{
"client": {
"allowed_origins": ["https://app.example.com"],
"token": {
"hmac_secret_key": "your_centrifugo_token_secret"
}
},
"channel": {
"namespaces": [
{
"name": "personal",
"presence": false,
"join_leave": false,
"history_size": 0,
"history_ttl": "0s"
}
]
},
"http_api": {
"key": "your_centrifugo_api_key"
}
}
```
- `http_api.key` must match `CENTRIFUGO_API_KEY` in `.env.taskview`
- `client.token.hmac_secret_key` must match `CENTRIFUGO_TOKEN_SECRET` in `.env.taskview`
- `client.allowed_origins` should list your frontend domain(s). Use `["*"]` only for development.
### Nginx WebSocket proxy
To serve Centrifugo through your existing HTTPS domain, add to your nginx server block:
```nginx
location /centrifugo/ {
proxy_pass http://localhost:8000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
```
Then set `CENTRIFUGO_PUBLIC_URL=wss://api.example.com/centrifugo/connection/websocket`.
::callout{icon="i-lucide-info" color="info"}
Both Centrifugo and Firebase are optional. If not configured, their respective channels are skipped. Notifications are always persisted to the database.
::
@@ -178,5 +228,5 @@ ENCRYPTION_KEY=
# CENTRIFUGO_API_URL=http://centrifugo:8000
# CENTRIFUGO_API_KEY=your_centrifugo_api_key
# CENTRIFUGO_TOKEN_SECRET=your_centrifugo_token_secret
# CENTRIFUGO_PUBLIC_PORT=8000
# CENTRIFUGO_PUBLIC_URL=wss://api.example.com/centrifugo/connection/websocket
```
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "taskview-ce-monorepo",
"version": "1.24.0",
"version": "1.32.0",
"private": true,
"description": "TaskView CE monorepo containing web, API, and packages",
"workspaces": [
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "com.handscreamgnl.taskview.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1201
versionName "1.20.1"
versionCode 1300
versionName "1.30.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
+2
View File
@@ -9,10 +9,12 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-firebase-messaging')
implementation project(':capacitor-app')
implementation project(':capacitor-browser')
implementation project(':capacitor-device')
implementation project(':capacitor-preferences')
implementation project(':capacitor-push-notifications')
implementation project(':capacitor-splash-screen')
implementation project(':capgo-capacitor-updater')
+13 -7
View File
@@ -1,21 +1,27 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@8.1.0_@capacitor+core@8.1.0/node_modules/@capacitor/android/capacitor')
project(':capacitor-android').projectDir = new File('../../node_modules/.pnpm/@capacitor+android@8.1.0_@capacitor+core@8.1.0/node_modules/@capacitor/android/capacitor')
include ':capacitor-firebase-messaging'
project(':capacitor-firebase-messaging').projectDir = new File('../../node_modules/.pnpm/@capacitor-firebase+messaging@8.1.0_@capacitor+core@8.1.0_firebase@12.10.0/node_modules/@capacitor-firebase/messaging/android')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../../../node_modules/.pnpm/@capacitor+app@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/app/android')
project(':capacitor-app').projectDir = new File('../../node_modules/.pnpm/@capacitor+app@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/app/android')
include ':capacitor-browser'
project(':capacitor-browser').projectDir = new File('../../../node_modules/.pnpm/@capacitor+browser@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/browser/android')
project(':capacitor-browser').projectDir = new File('../../node_modules/.pnpm/@capacitor+browser@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/browser/android')
include ':capacitor-device'
project(':capacitor-device').projectDir = new File('../../../node_modules/.pnpm/@capacitor+device@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/device/android')
project(':capacitor-device').projectDir = new File('../../node_modules/.pnpm/@capacitor+device@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/device/android')
include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../../../node_modules/.pnpm/@capacitor+preferences@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/preferences/android')
project(':capacitor-preferences').projectDir = new File('../../node_modules/.pnpm/@capacitor+preferences@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/preferences/android')
include ':capacitor-push-notifications'
project(':capacitor-push-notifications').projectDir = new File('../../node_modules/.pnpm/@capacitor+push-notifications@8.0.2_@capacitor+core@8.1.0/node_modules/@capacitor/push-notifications/android')
include ':capacitor-splash-screen'
project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/.pnpm/@capacitor+splash-screen@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/splash-screen/android')
project(':capacitor-splash-screen').projectDir = new File('../../node_modules/.pnpm/@capacitor+splash-screen@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/splash-screen/android')
include ':capgo-capacitor-updater'
project(':capgo-capacitor-updater').projectDir = new File('../../../node_modules/.pnpm/@capgo+capacitor-updater@8.43.2_@capacitor+core@8.1.0/node_modules/@capgo/capacitor-updater/android')
project(':capgo-capacitor-updater').projectDir = new File('../../node_modules/.pnpm/@capgo+capacitor-updater@8.43.8_@capacitor+core@8.1.0/node_modules/@capgo/capacitor-updater/android')
+6 -4
View File
@@ -301,9 +301,10 @@
baseConfigurationReference = 958DCC722DB07C7200EA8C5F /* debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.20.1;
CURRENT_PROJECT_VERSION = 1.30.0;
DEVELOPMENT_TEAM = H2W2SG48JT;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -311,7 +312,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.20.1;
MARKETING_VERSION = 1.30.0;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = com.handscream.taskview.app;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -325,9 +326,10 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.20.1;
CURRENT_PROJECT_VERSION = 1.30.0;
DEVELOPMENT_TEAM = H2W2SG48JT;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -335,7 +337,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.20.1;
MARKETING_VERSION = 1.30.0;
PRODUCT_BUNDLE_IDENTIFIER = com.handscream.taskview.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "web-nuxt-ui",
"private": true,
"type": "module",
"version": "1.24.0",
"version": "1.32.0",
"scripts": {
"dev": "vite",
"build:packages": "pnpm --filter taskview-db-schemas build && pnpm --filter taskview-api build",
@@ -81,7 +81,7 @@
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useClipboard } from '@vueuse/core'
import { WEBHOOK_EVENTS } from 'taskview-api'
import { WEBHOOK_EVENTS, type WebhookEvent } from 'taskview-api'
import { useWebhooksStore } from '@/stores/webhooks.store'
import { useTaskView } from '@/composables/useTaskView'
@@ -101,7 +101,7 @@ const webhooksStore = useWebhooksStore()
const { copy } = useClipboard()
const url = ref('')
const selectedEvents = ref<string[]>([])
const selectedEvents = ref<WebhookEvent[]>([])
const saving = ref(false)
const showSecret = ref(false)
const createdSecret = ref('')
@@ -48,7 +48,7 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { WEBHOOK_EVENTS } from 'taskview-api'
import { WEBHOOK_EVENTS, type WebhookEvent } from 'taskview-api'
import type { WebhookItem } from 'taskview-api'
import { useWebhooksStore } from '@/stores/webhooks.store'
import { useTaskView } from '@/composables/useTaskView'
@@ -68,7 +68,7 @@ const { isMobile } = useTaskView()
const webhooksStore = useWebhooksStore()
const url = ref('')
const selectedEvents = ref<string[]>([])
const selectedEvents = ref<WebhookEvent[]>([])
const saving = ref(false)
const eventOptions = WEBHOOK_EVENTS.map((e) => ({
@@ -79,7 +79,7 @@ const eventOptions = WEBHOOK_EVENTS.map((e) => ({
watch(isOpen, (open) => {
if (open && props.webhook) {
url.value = props.webhook.url
selectedEvents.value = [...props.webhook.events]
selectedEvents.value = [...props.webhook.events] as WebhookEvent[]
}
})