mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
Push notification (#5364)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
d2681eba2e
commit
bb1abdc8cc
@ -935,6 +935,9 @@ dependencies:
|
||||
'@types/uuid':
|
||||
specifier: ^8.3.1
|
||||
version: 8.3.4
|
||||
'@types/web-push':
|
||||
specifier: ~3.6.3
|
||||
version: 3.6.3
|
||||
'@types/ws':
|
||||
specifier: ^8.5.3
|
||||
version: 8.5.10
|
||||
@ -1268,6 +1271,9 @@ dependencies:
|
||||
uuid:
|
||||
specifier: ^8.3.2
|
||||
version: 8.3.2
|
||||
web-push:
|
||||
specifier: ~3.6.7
|
||||
version: 3.6.7
|
||||
webpack:
|
||||
specifier: ^5.75.0
|
||||
version: 5.90.3(esbuild@0.20.1)(webpack-cli@5.1.4)
|
||||
@ -6548,6 +6554,12 @@ packages:
|
||||
resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==}
|
||||
dev: false
|
||||
|
||||
/@types/web-push@3.6.3:
|
||||
resolution: {integrity: sha512-v3oT4mMJsHeJ/rraliZ+7TbZtr5bQQuxcgD7C3/1q/zkAj29c8RE0F9lVZVu3hiQe5Z9fYcBreV7TLnfKR+4mg==}
|
||||
dependencies:
|
||||
'@types/node': 20.11.19
|
||||
dev: false
|
||||
|
||||
/@types/webidl-conversions@7.0.3:
|
||||
resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==}
|
||||
dev: false
|
||||
@ -7016,6 +7028,15 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/agent-base@7.1.1:
|
||||
resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==}
|
||||
engines: {node: '>= 14'}
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/aggregate-error@3.1.0:
|
||||
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
||||
engines: {node: '>=8'}
|
||||
@ -7276,6 +7297,15 @@ packages:
|
||||
is-shared-array-buffer: 1.0.3
|
||||
dev: false
|
||||
|
||||
/asn1.js@5.4.1:
|
||||
resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==}
|
||||
dependencies:
|
||||
bn.js: 4.12.0
|
||||
inherits: 2.0.4
|
||||
minimalistic-assert: 1.0.1
|
||||
safer-buffer: 2.1.2
|
||||
dev: false
|
||||
|
||||
/assert@2.1.0:
|
||||
resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==}
|
||||
dependencies:
|
||||
@ -7594,6 +7624,10 @@ packages:
|
||||
readable-stream: 3.6.2
|
||||
dev: false
|
||||
|
||||
/bn.js@4.12.0:
|
||||
resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==}
|
||||
dev: false
|
||||
|
||||
/body-parser@1.20.2:
|
||||
resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
|
||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||
@ -7714,6 +7748,10 @@ packages:
|
||||
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||
dev: false
|
||||
|
||||
/buffer-equal-constant-time@1.0.1:
|
||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||
dev: false
|
||||
|
||||
/buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
dev: false
|
||||
@ -8869,6 +8907,12 @@ packages:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
dev: false
|
||||
|
||||
/ecdsa-sig-formatter@1.0.11:
|
||||
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/ee-first@1.1.1:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
dev: false
|
||||
@ -10784,6 +10828,11 @@ packages:
|
||||
resolve-alpn: 1.2.1
|
||||
dev: false
|
||||
|
||||
/http_ece@1.2.0:
|
||||
resolution: {integrity: sha512-JrF8SSLVmcvc5NducxgyOrKXe3EsyHMgBFgSaIUGmArKe+rwr0uphRkRXvwiom3I+fpIfoItveHrfudL8/rxuA==}
|
||||
engines: {node: '>=16'}
|
||||
dev: false
|
||||
|
||||
/https-proxy-agent@4.0.0:
|
||||
resolution: {integrity: sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
@ -10804,6 +10853,16 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/https-proxy-agent@7.0.4:
|
||||
resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==}
|
||||
engines: {node: '>= 14'}
|
||||
dependencies:
|
||||
agent-base: 7.1.1
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/human-signals@2.1.0:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
@ -11981,6 +12040,21 @@ packages:
|
||||
resolution: {integrity: sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==}
|
||||
dev: false
|
||||
|
||||
/jwa@2.0.0:
|
||||
resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==}
|
||||
dependencies:
|
||||
buffer-equal-constant-time: 1.0.1
|
||||
ecdsa-sig-formatter: 1.0.11
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/jws@4.0.0:
|
||||
resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==}
|
||||
dependencies:
|
||||
jwa: 2.0.0
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/jwt-simple@0.5.6:
|
||||
resolution: {integrity: sha512-40aUybvhH9t2h71ncA1/1SbtTNCVZHgsTsTgqPUxGWDmUDrXyDf2wMNQKEbdBjbf4AI+fQhbECNTV6lWxQKUzg==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
@ -16341,6 +16415,20 @@ packages:
|
||||
'@zxing/text-encoding': 0.9.0
|
||||
dev: false
|
||||
|
||||
/web-push@3.6.7:
|
||||
resolution: {integrity: sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==}
|
||||
engines: {node: '>= 16'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
asn1.js: 5.4.1
|
||||
http_ece: 1.2.0
|
||||
https-proxy-agent: 7.0.4
|
||||
jws: 4.0.0
|
||||
minimist: 1.2.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
dev: false
|
||||
@ -22283,12 +22371,13 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-notification-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-syXpJRNP0osGwuo3+uTE08SmRqjBPEo4mv6B6hqSvwTXLc7ZbmkEAXbonr+ykj1YQk15u5hS8JjRnWSGStuy7A==, tarball: file:projects/server-notification-resources.tgz}
|
||||
resolution: {integrity: sha512-9ctaiwEU+M3RZ4Qz0/OuQ1lLh4Os9V8vlpf2FK/cpGOvAt5RUfV0qwiJLaImsLVbS62uz6hbZ1oy7Q3T+qex8Q==, tarball: file:projects/server-notification-resources.tgz}
|
||||
id: file:projects/server-notification-resources.tgz
|
||||
name: '@rush-temp/server-notification-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@types/jest': 29.5.12
|
||||
'@types/web-push': 3.6.3
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
|
||||
eslint: 8.56.0
|
||||
@ -22301,6 +22390,7 @@ packages:
|
||||
prettier-plugin-svelte: 3.2.1(prettier@3.2.5)(svelte@4.2.11)
|
||||
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
|
||||
typescript: 5.3.3
|
||||
web-push: 3.6.7
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
|
@ -156,6 +156,7 @@ services:
|
||||
- MINIO_SECRET_KEY=minioadmin
|
||||
- REKONI_URL=http://rekoni:4004
|
||||
- FRONT_URL=http://localhost:8087
|
||||
- UPLOAD_URL=http://localhost:8087/files
|
||||
# - APM_SERVER_URL=http://apm-server:8200
|
||||
- SERVER_PROVIDER=ws
|
||||
- ACCOUNTS_URL=http://account:3000
|
||||
|
@ -30,7 +30,7 @@ import { imageCropperId } from '@hcengineering/image-cropper'
|
||||
import { inventoryId } from '@hcengineering/inventory'
|
||||
import { leadId } from '@hcengineering/lead'
|
||||
import login, { loginId } from '@hcengineering/login'
|
||||
import { notificationId } from '@hcengineering/notification'
|
||||
import notification, { notificationId } from '@hcengineering/notification'
|
||||
import { recruitId } from '@hcengineering/recruit'
|
||||
import rekoni from '@hcengineering/rekoni'
|
||||
import { requestId } from '@hcengineering/request'
|
||||
@ -96,6 +96,7 @@ interface Config {
|
||||
CALENDAR_URL: string
|
||||
COLLABORATOR_URL: string
|
||||
COLLABORATOR_API_URL: string
|
||||
PUSH_PUBLIC_KEY: string
|
||||
TITLE?: string
|
||||
LANGUAGES?: string
|
||||
DEFAULT_LANGUAGE?: string
|
||||
@ -158,6 +159,7 @@ export async function configurePlatform() {
|
||||
setMetadata(telegram.metadata.TelegramURL, config.TELEGRAM_URL ?? 'http://localhost:8086')
|
||||
setMetadata(gmail.metadata.GmailURL, config.GMAIL_URL ?? 'http://localhost:8087')
|
||||
setMetadata(calendar.metadata.CalendarServiceURL, config.CALENDAR_URL ?? 'http://localhost:8095')
|
||||
setMetadata(notification.metadata.PushPublicKey, config.PUSH_PUBLIC_KEY)
|
||||
|
||||
setMetadata(login.metadata.OverrideEndpoint, process.env.LOGIN_ENDPOINT)
|
||||
|
||||
|
@ -36,7 +36,42 @@ const doValidate = !prod || (process.env.DO_VALIDATE === 'true')
|
||||
/**
|
||||
* @type {Configuration}
|
||||
*/
|
||||
module.exports = {
|
||||
module.exports = [
|
||||
{
|
||||
mode: dev ? 'development' : mode,
|
||||
entry: {
|
||||
serviceWorker: '@hcengineering/notification/src/serviceWorker.ts'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /(node_modules|\.webpack)/,
|
||||
use: {
|
||||
loader: 'esbuild-loader',
|
||||
options: {
|
||||
target: 'es2021',
|
||||
keepNames: true,
|
||||
minify: !prod,
|
||||
sourcemap: !prod
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/dist',
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[name].js',
|
||||
publicPath: '/',
|
||||
pathinfo: false
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js'],
|
||||
conditionNames: ['svelte', 'browser', 'import']
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
bundle: [
|
||||
'@hcengineering/theme/styles/global.scss',
|
||||
@ -278,4 +313,4 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
@ -73,7 +73,9 @@ import {
|
||||
type NotificationSetting,
|
||||
type NotificationStatus,
|
||||
type NotificationTemplate,
|
||||
type NotificationType
|
||||
type PushSubscription,
|
||||
type NotificationType,
|
||||
type PushSubscriptionKeys
|
||||
} from '@hcengineering/notification'
|
||||
import { getEmbeddedLabel, type Asset, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
import setting from '@hcengineering/setting'
|
||||
@ -96,6 +98,13 @@ export class TBrowserNotification extends TDoc implements BrowserNotification {
|
||||
status!: NotificationStatus
|
||||
}
|
||||
|
||||
@Model(notification.class.PushSubscription, core.class.Doc, DOMAIN_NOTIFICATION)
|
||||
export class TPushSubscription extends TDoc implements PushSubscription {
|
||||
user!: Ref<Account>
|
||||
endpoint!: string
|
||||
keys!: PushSubscriptionKeys
|
||||
}
|
||||
|
||||
@Model(notification.class.BaseNotificationType, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TBaseNotificationType extends TDoc implements BaseNotificationType {
|
||||
generated!: boolean
|
||||
@ -335,7 +344,8 @@ export function createModel (builder: Builder): void {
|
||||
TActivityNotificationViewlet,
|
||||
TBaseNotificationType,
|
||||
TCommonNotificationType,
|
||||
TMentionInboxNotification
|
||||
TMentionInboxNotification,
|
||||
TPushSubscription
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
|
@ -29,7 +29,13 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import contact, { AvatarProvider, AvatarType, getFirstName, getLastName } from '@hcengineering/contact'
|
||||
import contact, {
|
||||
AvatarProvider,
|
||||
AvatarType,
|
||||
getFirstName,
|
||||
getLastName,
|
||||
getAvatarProviderId
|
||||
} from '@hcengineering/contact'
|
||||
import { Asset, getMetadata, getResource } from '@hcengineering/platform'
|
||||
import { getBlobURL, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
@ -43,7 +49,6 @@
|
||||
themeStore,
|
||||
resizeObserver
|
||||
} from '@hcengineering/ui'
|
||||
import { getAvatarProviderId } from '../utils'
|
||||
import AvatarIcon from './icons/Avatar.svelte'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
|
@ -15,43 +15,42 @@
|
||||
//
|
||||
|
||||
import {
|
||||
type AvatarProvider,
|
||||
AvatarType,
|
||||
type ChannelProvider,
|
||||
type Contact,
|
||||
type Employee,
|
||||
type Person,
|
||||
type PersonAccount,
|
||||
contactId,
|
||||
formatName,
|
||||
getFirstName,
|
||||
getLastName,
|
||||
getName,
|
||||
type Channel
|
||||
type Channel,
|
||||
type ChannelProvider,
|
||||
type Contact,
|
||||
type Employee,
|
||||
type Person,
|
||||
type PersonAccount
|
||||
} from '@hcengineering/contact'
|
||||
import {
|
||||
getCurrentAccount,
|
||||
toIdMap,
|
||||
type Class,
|
||||
type Client,
|
||||
type Doc,
|
||||
type IdMap,
|
||||
type ObjQueryType,
|
||||
type Ref,
|
||||
type Timestamp,
|
||||
type TxOperations,
|
||||
getCurrentAccount,
|
||||
toIdMap,
|
||||
type Class
|
||||
type TxOperations
|
||||
} from '@hcengineering/core'
|
||||
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
||||
import { getEmbeddedLabel, getResource, translate } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { type TemplateDataProvider } from '@hcengineering/templates'
|
||||
import {
|
||||
type Location,
|
||||
type ResolvedLocation,
|
||||
type TabItem,
|
||||
getCurrentResolvedLocation,
|
||||
getPanelURI,
|
||||
type LabelAndProps
|
||||
type LabelAndProps,
|
||||
type Location,
|
||||
type ResolvedLocation,
|
||||
type TabItem
|
||||
} from '@hcengineering/ui'
|
||||
import view, { type Filter } from '@hcengineering/view'
|
||||
import { FilterQuery } from '@hcengineering/view-resources'
|
||||
@ -370,24 +369,6 @@ export function getAvatarTypeDropdownItems (hasGravatar: boolean, imageOnly?: bo
|
||||
]
|
||||
}
|
||||
|
||||
export function getAvatarProviderId (avatar?: string | null): Ref<AvatarProvider> | undefined {
|
||||
if (avatar === null || avatar === undefined || avatar === '') {
|
||||
return
|
||||
}
|
||||
if (!avatar.includes('://')) {
|
||||
return contact.avatarProvider.Image
|
||||
}
|
||||
const [schema] = avatar.split('://')
|
||||
|
||||
switch (schema) {
|
||||
case AvatarType.GRAVATAR:
|
||||
return contact.avatarProvider.Gravatar
|
||||
case AvatarType.COLOR:
|
||||
return contact.avatarProvider.Color
|
||||
}
|
||||
return contact.avatarProvider.Image
|
||||
}
|
||||
|
||||
export async function contactTitleProvider (client: Client, ref: Ref<Contact>, doc?: Contact): Promise<string> {
|
||||
const object = doc ?? (await client.findOne(contact.class.Contact, { _id: ref }))
|
||||
if (object === undefined) return ''
|
||||
|
@ -16,7 +16,7 @@
|
||||
import { AttachedData, Class, Client, Doc, FindResult, Ref, Hierarchy } from '@hcengineering/core'
|
||||
import { IconSize, ColorDefinition } from '@hcengineering/ui'
|
||||
import { MD5 } from 'crypto-js'
|
||||
import { Channel, Contact, contactPlugin, Person } from '.'
|
||||
import { AvatarProvider, AvatarType, Channel, Contact, contactPlugin, Person } from '.'
|
||||
import { AVATAR_COLORS, GravatarPlaceholderType } from './types'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
|
||||
@ -55,6 +55,27 @@ export function buildGravatarId (email: string): string {
|
||||
return MD5(email.trim().toLowerCase()).toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function getAvatarProviderId (avatar?: string | null): Ref<AvatarProvider> | undefined {
|
||||
if (avatar === null || avatar === undefined || avatar === '') {
|
||||
return
|
||||
}
|
||||
if (!avatar.includes('://')) {
|
||||
return contactPlugin.avatarProvider.Image
|
||||
}
|
||||
const [schema] = avatar.split('://')
|
||||
|
||||
switch (schema) {
|
||||
case AvatarType.GRAVATAR:
|
||||
return contactPlugin.avatarProvider.Gravatar
|
||||
case AvatarType.COLOR:
|
||||
return contactPlugin.avatarProvider.Color
|
||||
}
|
||||
return contactPlugin.avatarProvider.Image
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -14,60 +14,76 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getCurrentAccount } from '@hcengineering/core'
|
||||
import notification, { BrowserNotification, NotificationStatus } from '@hcengineering/notification'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { getCurrentLocation, navigate } from '@hcengineering/ui'
|
||||
import { askPermission } from '../utils'
|
||||
|
||||
let notifications: BrowserNotification[] = []
|
||||
|
||||
const query = createQuery()
|
||||
query.query(
|
||||
notification.class.BrowserNotification,
|
||||
{
|
||||
user: getCurrentAccount()._id,
|
||||
status: NotificationStatus.New
|
||||
},
|
||||
(res) => {
|
||||
notifications = res
|
||||
}
|
||||
)
|
||||
import notification from '@hcengineering/notification'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { getCurrentLocation, navigate, parseLocation } from '@hcengineering/ui'
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: process(notifications)
|
||||
const publicKey = getMetadata(notification.metadata.PushPublicKey)
|
||||
|
||||
async function process (notifications: BrowserNotification[]): Promise<void> {
|
||||
if (notifications.length === 0) return
|
||||
await askPermission()
|
||||
if ('Notification' in window && Notification?.permission === 'granted') {
|
||||
for (const value of notifications) {
|
||||
const req: NotificationOptions = {
|
||||
body: value.body,
|
||||
tag: value._id,
|
||||
silent: false
|
||||
async function subscribe (): Promise<void> {
|
||||
if ('serviceWorker' in navigator && 'PushManager' in window && publicKey !== undefined) {
|
||||
try {
|
||||
const loc = getCurrentLocation()
|
||||
const registration = await navigator.serviceWorker.register('/serviceWorker.js', {
|
||||
scope: `./${loc.path[0]}/${loc.path[1]}`
|
||||
})
|
||||
const current = await registration.pushManager.getSubscription()
|
||||
if (current == null) {
|
||||
const subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: publicKey
|
||||
})
|
||||
await client.createDoc(notification.class.PushSubscription, notification.space.Notifications, {
|
||||
user: getCurrentAccount()._id,
|
||||
endpoint: subscription.endpoint,
|
||||
keys: {
|
||||
p256dh: arrayBufferToBase64(subscription.getKey('p256dh')),
|
||||
auth: arrayBufferToBase64(subscription.getKey('auth'))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const exists = await client.findOne(notification.class.PushSubscription, {
|
||||
user: getCurrentAccount()._id,
|
||||
endpoint: current.endpoint
|
||||
})
|
||||
if (exists === undefined) {
|
||||
await client.createDoc(notification.class.PushSubscription, notification.space.Notifications, {
|
||||
user: getCurrentAccount()._id,
|
||||
endpoint: current.endpoint,
|
||||
keys: {
|
||||
p256dh: arrayBufferToBase64(current.getKey('p256dh')),
|
||||
auth: arrayBufferToBase64(current.getKey('auth'))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const notification = new Notification(value.title, req)
|
||||
if (value.onClickLocation !== undefined) {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path.length = 3
|
||||
loc.path[2] = value.onClickLocation.path[2]
|
||||
if (value.onClickLocation.path[3]) {
|
||||
loc.path[3] = value.onClickLocation.path[3]
|
||||
if (value.onClickLocation.path[4]) {
|
||||
loc.path[4] = value.onClickLocation.path[4]
|
||||
navigator.serviceWorker.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'notification-click') {
|
||||
const { url } = event.data
|
||||
if (url !== undefined) {
|
||||
navigate(parseLocation(new URL(url)))
|
||||
}
|
||||
}
|
||||
loc.query = value.onClickLocation.query
|
||||
loc.fragment = value.onClickLocation.fragment
|
||||
const onClick = () => {
|
||||
navigate(loc)
|
||||
window.parent.parent.focus()
|
||||
}
|
||||
notification.onclick = onClick
|
||||
}
|
||||
await client.update(value, { status: NotificationStatus.Notified })
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Service Worker registration failed:', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function arrayBufferToBase64 (buffer: ArrayBuffer | null): string {
|
||||
if (buffer) {
|
||||
const bytes = new Uint8Array(buffer)
|
||||
const array = Array.from(bytes)
|
||||
const binary = String.fromCharCode.apply(null, array)
|
||||
return btoa(binary)
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
subscribe()
|
||||
</script>
|
||||
|
@ -30,7 +30,7 @@ import {
|
||||
TxCUD,
|
||||
TxOperations
|
||||
} from '@hcengineering/core'
|
||||
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
|
||||
import type { Asset, IntlString, Metadata, Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { Preference } from '@hcengineering/preference'
|
||||
import { IntegrationType } from '@hcengineering/setting'
|
||||
@ -51,6 +51,26 @@ export interface BrowserNotification extends Doc {
|
||||
onClickLocation?: Location
|
||||
}
|
||||
|
||||
export interface PushData {
|
||||
tag?: string
|
||||
title: string
|
||||
body: string
|
||||
icon?: string
|
||||
domain?: string
|
||||
url?: string
|
||||
}
|
||||
|
||||
export interface PushSubscriptionKeys {
|
||||
p256dh: string
|
||||
auth: string
|
||||
}
|
||||
|
||||
export interface PushSubscription extends Doc {
|
||||
user: Ref<Account>
|
||||
endpoint: string
|
||||
keys: PushSubscriptionKeys
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -332,6 +352,7 @@ const notification = plugin(notificationId, {
|
||||
},
|
||||
class: {
|
||||
BrowserNotification: '' as Ref<Class<BrowserNotification>>,
|
||||
PushSubscription: '' as Ref<Class<PushSubscription>>,
|
||||
BaseNotificationType: '' as Ref<Class<BaseNotificationType>>,
|
||||
NotificationType: '' as Ref<Class<NotificationType>>,
|
||||
CommonNotificationType: '' as Ref<Class<CommonNotificationType>>,
|
||||
@ -353,6 +374,9 @@ const notification = plugin(notificationId, {
|
||||
CollaboratoAddNotification: '' as Ref<NotificationType>,
|
||||
MentionCommonNotificationType: '' as Ref<CommonNotificationType>
|
||||
},
|
||||
metadata: {
|
||||
PushPublicKey: '' as Metadata<string>
|
||||
},
|
||||
providers: {
|
||||
PlatformNotification: '' as Ref<NotificationProvider>,
|
||||
BrowserNotification: '' as Ref<NotificationProvider>,
|
||||
|
58
plugins/notification/src/serviceWorker.ts
Normal file
58
plugins/notification/src/serviceWorker.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import type { PushData } from './index'
|
||||
|
||||
declare const self: any
|
||||
|
||||
interface PushEvent extends Event {
|
||||
data: PushMessageData
|
||||
}
|
||||
|
||||
interface PushMessageData {
|
||||
json: () => any
|
||||
}
|
||||
|
||||
self.addEventListener('push', (event: PushEvent) => {
|
||||
const payload: PushData = event.data.json()
|
||||
self.registration.showNotification(payload.title, {
|
||||
body: payload.body,
|
||||
icon: payload.icon,
|
||||
tag: payload.tag,
|
||||
data: {
|
||||
domain: payload.domain,
|
||||
url: payload.url
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Listen for notification click event
|
||||
self.addEventListener('notificationclick', (event: any) => {
|
||||
const clickedNotification = event.notification
|
||||
const notificationData = clickedNotification.data
|
||||
const notificationUrl = notificationData.url
|
||||
const domain = notificationData.domain
|
||||
if (notificationUrl !== undefined && domain !== undefined) {
|
||||
// Check if any client with the same origin is already open
|
||||
event.waitUntil(
|
||||
// Check all active clients (browser windows or tabs)
|
||||
self.clients
|
||||
.matchAll({
|
||||
type: 'window',
|
||||
includeUncontrolled: true
|
||||
})
|
||||
.then((clientList: any) => {
|
||||
// Loop through each client
|
||||
for (const client of clientList) {
|
||||
// If a client has the same URL origin, focus and navigate to it
|
||||
if ((client.url as string)?.startsWith(domain)) {
|
||||
client.postMessage({
|
||||
type: 'notification-click',
|
||||
url: notificationUrl
|
||||
})
|
||||
return client.focus()
|
||||
}
|
||||
}
|
||||
// If no client with the same URL origin is found, open a new window/tab
|
||||
return self.clients.openWindow(notificationUrl)
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
@ -22,49 +22,42 @@ import serverCore, { type StorageConfiguration } from '@hcengineering/server-cor
|
||||
import serverNotification from '@hcengineering/server-notification'
|
||||
import serverToken from '@hcengineering/server-token'
|
||||
import { start } from '.'
|
||||
import notification from '@hcengineering/notification'
|
||||
|
||||
const {
|
||||
url,
|
||||
frontUrl,
|
||||
serverSecret,
|
||||
sesUrl,
|
||||
elasticUrl,
|
||||
elasticIndexName,
|
||||
accountsUrl,
|
||||
rekoniUrl,
|
||||
serverFactory,
|
||||
serverPort,
|
||||
enableCompression
|
||||
} = serverConfigFromEnv()
|
||||
const config = serverConfigFromEnv()
|
||||
const storageConfig: StorageConfiguration = storageConfigFromEnv()
|
||||
|
||||
const cursorMaxTime = process.env.SERVER_CURSOR_MAXTIMEMS
|
||||
|
||||
const lastNameFirst = process.env.LAST_NAME_FIRST === 'true'
|
||||
setMetadata(serverCore.metadata.CursorMaxTimeMS, cursorMaxTime)
|
||||
setMetadata(serverCore.metadata.FrontUrl, frontUrl)
|
||||
setMetadata(serverToken.metadata.Secret, serverSecret)
|
||||
setMetadata(serverNotification.metadata.SesUrl, sesUrl ?? '')
|
||||
setMetadata(serverCore.metadata.FrontUrl, config.frontUrl)
|
||||
setMetadata(serverCore.metadata.UploadURL, config.uploadUrl)
|
||||
setMetadata(serverToken.metadata.Secret, config.serverSecret)
|
||||
setMetadata(serverNotification.metadata.SesUrl, config.sesUrl ?? '')
|
||||
setMetadata(notification.metadata.PushPublicKey, config.pushPublicKey)
|
||||
setMetadata(serverNotification.metadata.PushPrivateKey, config.pushPrivateKey)
|
||||
setMetadata(serverNotification.metadata.PushSubject, config.pushSubject)
|
||||
setMetadata(contactPlugin.metadata.LastNameFirst, lastNameFirst)
|
||||
setMetadata(serverCore.metadata.ElasticIndexName, elasticIndexName)
|
||||
setMetadata(serverCore.metadata.ElasticIndexName, config.elasticIndexName)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
console.log(
|
||||
`starting server on ${serverPort} git_version: ${process.env.GIT_REVISION ?? ''} model_version: ${
|
||||
`starting server on ${config.serverPort} git_version: ${process.env.GIT_REVISION ?? ''} model_version: ${
|
||||
process.env.MODEL_VERSION ?? ''
|
||||
}`
|
||||
)
|
||||
const shutdown = start(url, {
|
||||
fullTextUrl: elasticUrl,
|
||||
const shutdown = start(config.url, {
|
||||
fullTextUrl: config.elasticUrl,
|
||||
storageConfig,
|
||||
rekoniUrl,
|
||||
port: serverPort,
|
||||
serverFactory,
|
||||
rekoniUrl: config.rekoniUrl,
|
||||
port: config.serverPort,
|
||||
serverFactory: config.serverFactory,
|
||||
indexParallel: 2,
|
||||
indexProcessing: 50,
|
||||
productId: '',
|
||||
enableCompression,
|
||||
accountsUrl
|
||||
enableCompression: config.enableCompression,
|
||||
accountsUrl: config.accountsUrl
|
||||
})
|
||||
|
||||
const close = (): void => {
|
||||
|
@ -29,7 +29,8 @@
|
||||
"typescript": "^5.3.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5"
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/web-push": "~3.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
@ -38,8 +39,10 @@
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/workbench": "^0.6.9",
|
||||
"@hcengineering/chunter": "^0.6.12",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/contact": "^0.6.20"
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"web-push": "~3.6.7"
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,16 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||
import chunter, { ChatMessage } from '@hcengineering/chunter'
|
||||
import contact, { Employee, formatName, Person, PersonAccount } from '@hcengineering/contact'
|
||||
import contact, {
|
||||
Employee,
|
||||
formatName,
|
||||
getAvatarProviderId,
|
||||
getGravatarUrl,
|
||||
Person,
|
||||
PersonAccount
|
||||
} from '@hcengineering/contact'
|
||||
import core, {
|
||||
Account,
|
||||
AnyAttribute,
|
||||
@ -45,25 +53,25 @@ import core, {
|
||||
import notification, {
|
||||
ActivityInboxNotification,
|
||||
BaseNotificationType,
|
||||
BrowserNotification,
|
||||
ClassCollaborators,
|
||||
Collaborators,
|
||||
CommonInboxNotification,
|
||||
DocNotifyContext,
|
||||
InboxNotification,
|
||||
notificationId,
|
||||
NotificationStatus,
|
||||
NotificationType
|
||||
NotificationType,
|
||||
PushData
|
||||
} from '@hcengineering/notification'
|
||||
import { getMetadata, getResource, translate } from '@hcengineering/platform'
|
||||
import type { TriggerControl } from '@hcengineering/server-core'
|
||||
import serverCore from '@hcengineering/server-core'
|
||||
import serverNotification, {
|
||||
getEmployee,
|
||||
getPersonAccount,
|
||||
getPersonAccountById
|
||||
} from '@hcengineering/server-notification'
|
||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
import webpush, { WebPushError } from 'web-push'
|
||||
import { Content, NotifyResult } from './types'
|
||||
import {
|
||||
getHTMLPresenter,
|
||||
@ -139,6 +147,7 @@ export async function getCommonNotificationTxes (
|
||||
data,
|
||||
_class,
|
||||
modifiedOn,
|
||||
sender as Ref<PersonAccount>,
|
||||
notifyResult.push
|
||||
)
|
||||
}
|
||||
@ -369,6 +378,7 @@ export async function pushInboxNotifications (
|
||||
data: Partial<Data<InboxNotification>>,
|
||||
_class: Ref<Class<InboxNotification>>,
|
||||
modifiedOn: Timestamp,
|
||||
senderId: Ref<PersonAccount>,
|
||||
shouldPush: boolean,
|
||||
shouldUpdateTimestamp = true
|
||||
): Promise<void> {
|
||||
@ -410,10 +420,15 @@ export async function pushInboxNotifications (
|
||||
const notificationTx = control.txFactory.createTxCreateDoc(_class, space, notificationData)
|
||||
res.push(notificationTx)
|
||||
if (shouldPush) {
|
||||
const pushTx = await createPushFromInbox(control, targetUser, space, docNotifyContextId, notificationData, _class)
|
||||
if (pushTx !== undefined) {
|
||||
res.push(pushTx)
|
||||
}
|
||||
await createPushFromInbox(
|
||||
control,
|
||||
targetUser,
|
||||
docNotifyContextId,
|
||||
notificationData,
|
||||
_class,
|
||||
senderId,
|
||||
notificationTx.objectId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -467,11 +482,12 @@ async function commonInboxNotificationToText (doc: Data<CommonInboxNotification>
|
||||
export async function createPushFromInbox (
|
||||
control: TriggerControl,
|
||||
targetUser: Ref<Account>,
|
||||
space: Ref<Space>,
|
||||
docNotifyContextId: Ref<DocNotifyContext>,
|
||||
data: Data<InboxNotification>,
|
||||
_class: Ref<Class<InboxNotification>>
|
||||
): Promise<Tx | undefined> {
|
||||
_class: Ref<Class<InboxNotification>>,
|
||||
senderId: Ref<PersonAccount>,
|
||||
_id: string
|
||||
): Promise<void> {
|
||||
let title: string = ''
|
||||
let body: string = ''
|
||||
if (control.hierarchy.isDerived(_class, notification.class.ActivityInboxNotification)) {
|
||||
@ -482,9 +498,14 @@ export async function createPushFromInbox (
|
||||
if (title === '' || body === '') {
|
||||
return
|
||||
}
|
||||
return await createPushNotification(control, targetUser, space, title, body, [
|
||||
'',
|
||||
'',
|
||||
const sender = (await control.modelDb.findAll(contact.class.PersonAccount, { _id: senderId }))[0]
|
||||
|
||||
let senderPerson: Person | undefined
|
||||
|
||||
if (sender !== undefined) {
|
||||
senderPerson = (await control.findAll(contact.class.Person, { _id: sender.person }))[0]
|
||||
}
|
||||
await createPushNotification(control, targetUser, title, body, _id, senderPerson?.avatar, [
|
||||
notificationId,
|
||||
docNotifyContextId
|
||||
])
|
||||
@ -493,24 +514,62 @@ export async function createPushFromInbox (
|
||||
export async function createPushNotification (
|
||||
control: TriggerControl,
|
||||
targetUser: Ref<Account>,
|
||||
space: Ref<Space>,
|
||||
title: string,
|
||||
body: string,
|
||||
onClick?: string[]
|
||||
): Promise<TxCreateDoc<BrowserNotification>> {
|
||||
const data: Data<BrowserNotification> = {
|
||||
user: targetUser,
|
||||
status: NotificationStatus.New,
|
||||
_id: string,
|
||||
senderAvatar?: string | null,
|
||||
subPath?: string[]
|
||||
): Promise<void> {
|
||||
const publicKey = getMetadata(notification.metadata.PushPublicKey)
|
||||
const privateKey = getMetadata(serverNotification.metadata.PushPrivateKey)
|
||||
const subject = getMetadata(serverNotification.metadata.PushSubject) ?? 'mailto:hey@huly.io'
|
||||
if (privateKey === undefined || publicKey === undefined) return
|
||||
const subscriptions = (await control.queryFind(notification.class.PushSubscription, {})).filter(
|
||||
(p) => p.user === targetUser
|
||||
)
|
||||
const data: PushData = {
|
||||
title,
|
||||
body
|
||||
}
|
||||
if (onClick !== undefined) {
|
||||
data.onClickLocation = {
|
||||
path: onClick
|
||||
if (_id !== undefined) {
|
||||
data.tag = _id
|
||||
}
|
||||
const front = getMetadata(serverCore.metadata.FrontUrl) ?? ''
|
||||
const uploadUrl = getMetadata(serverCore.metadata.UploadURL) ?? ''
|
||||
const domainPath = `${workbenchId}/${control.workspace.workspaceUrl}`
|
||||
const domain = concatLink(front, domainPath)
|
||||
data.domain = domain
|
||||
if (subPath !== undefined) {
|
||||
const path = [domainPath, ...subPath].join('/')
|
||||
const url = concatLink(front, path)
|
||||
data.url = url
|
||||
}
|
||||
if (senderAvatar != null) {
|
||||
const provider = getAvatarProviderId(senderAvatar)
|
||||
if (provider === contact.avatarProvider.Image) {
|
||||
if (senderAvatar.includes('://')) {
|
||||
data.icon = senderAvatar
|
||||
} else {
|
||||
data.icon = concatLink(uploadUrl, `?file=${senderAvatar}`)
|
||||
}
|
||||
} else if (provider === contact.avatarProvider.Gravatar) {
|
||||
data.icon = getGravatarUrl(senderAvatar.split('://')[1], 'medium')
|
||||
}
|
||||
}
|
||||
|
||||
webpush.setVapidDetails(subject, publicKey, privateKey)
|
||||
|
||||
for (const subscription of subscriptions) {
|
||||
try {
|
||||
await webpush.sendNotification(subscription, JSON.stringify(data))
|
||||
} catch (err) {
|
||||
console.log('Cannot send push notification to', targetUser, err)
|
||||
if (err instanceof WebPushError && err.body.includes('expired')) {
|
||||
const tx = control.txFactory.createTxRemoveDoc(subscription._class, subscription.space, subscription._id)
|
||||
await control.apply([tx], true)
|
||||
}
|
||||
}
|
||||
}
|
||||
const res = control.txFactory.createTxCreateDoc(notification.class.BrowserNotification, space, data)
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
@ -555,6 +614,7 @@ export async function pushActivityInboxNotifications (
|
||||
data,
|
||||
notification.class.ActivityInboxNotification,
|
||||
activityMessage.modifiedOn,
|
||||
originTx.modifiedBy as Ref<PersonAccount>,
|
||||
shouldPush,
|
||||
shouldUpdateTimestamp
|
||||
)
|
||||
|
@ -134,7 +134,9 @@ export interface NotificationPresenter extends Class<Doc> {
|
||||
*/
|
||||
export default plugin(serverNotificationId, {
|
||||
metadata: {
|
||||
SesUrl: '' as Metadata<string>
|
||||
SesUrl: '' as Metadata<string>,
|
||||
PushPrivateKey: '' as Metadata<string>,
|
||||
PushSubject: '' as Metadata<string>
|
||||
},
|
||||
mixin: {
|
||||
HTMLPresenter: '' as Ref<Mixin<HTMLPresenter>>,
|
||||
|
@ -41,6 +41,7 @@ const serverCore = plugin(serverCoreId, {
|
||||
},
|
||||
metadata: {
|
||||
FrontUrl: '' as Metadata<string>,
|
||||
UploadURL: '' as Metadata<string>,
|
||||
CursorMaxTimeMS: '' as Metadata<string>,
|
||||
ElasticIndexName: '' as Metadata<string>
|
||||
}
|
||||
|
@ -50,19 +50,25 @@ export function storageConfigFromEnv (): StorageConfiguration {
|
||||
return storageConfig
|
||||
}
|
||||
|
||||
export function serverConfigFromEnv (): {
|
||||
export interface ServerEnv {
|
||||
url: string
|
||||
elasticUrl: string
|
||||
serverSecret: string
|
||||
rekoniUrl: string
|
||||
frontUrl: string
|
||||
uploadUrl: string
|
||||
sesUrl: string | undefined
|
||||
accountsUrl: string
|
||||
serverPort: number
|
||||
serverFactory: ServerFactory
|
||||
enableCompression: boolean
|
||||
elasticIndexName: string
|
||||
} {
|
||||
pushPublicKey: string | undefined
|
||||
pushPrivateKey: string | undefined
|
||||
pushSubject: string | undefined
|
||||
}
|
||||
|
||||
export function serverConfigFromEnv (): ServerEnv {
|
||||
const serverPort = parseInt(process.env.SERVER_PORT ?? '3333')
|
||||
const serverFactory = serverFactories[(process.env.SERVER_PROVIDER as string) ?? 'ws'] ?? serverFactories.ws
|
||||
const enableCompression = (process.env.ENABLE_COMPRESSION ?? 'true') === 'true'
|
||||
@ -102,6 +108,12 @@ export function serverConfigFromEnv (): {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const uploadUrl = process.env.UPLOAD_URL
|
||||
if (uploadUrl === undefined) {
|
||||
console.log('Please provide UPLOAD_URL url')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const sesUrl = process.env.SES_URL
|
||||
|
||||
const accountsUrl = process.env.ACCOUNTS_URL
|
||||
@ -109,6 +121,10 @@ export function serverConfigFromEnv (): {
|
||||
console.log('Please provide ACCOUNTS_URL url')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const pushPublicKey = process.env.PUSH_PUBLIC_KEY
|
||||
const pushPrivateKey = process.env.PUSH_PRIVATE_KEY
|
||||
const pushSubject = process.env.PUSH_SUBJECT
|
||||
return {
|
||||
url,
|
||||
elasticUrl,
|
||||
@ -116,11 +132,15 @@ export function serverConfigFromEnv (): {
|
||||
serverSecret,
|
||||
rekoniUrl,
|
||||
frontUrl,
|
||||
uploadUrl,
|
||||
sesUrl,
|
||||
accountsUrl,
|
||||
serverPort,
|
||||
serverFactory,
|
||||
enableCompression
|
||||
enableCompression,
|
||||
pushPublicKey,
|
||||
pushPrivateKey,
|
||||
pushSubject
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,6 +102,7 @@ services:
|
||||
- MINIO_SECRET_KEY=minioadmin
|
||||
- REKONI_URL=http://rekoni:4005
|
||||
- FRONT_URL=http://localhost:8083
|
||||
- UPLOAD_URL=http://localhost:8083/files
|
||||
- ACCOUNTS_URL=http://account:3003
|
||||
- LAST_NAME_FIRST=true
|
||||
- ELASTIC_INDEX_NAME=local_storage_index
|
||||
|
Loading…
Reference in New Issue
Block a user