@things-factory/notification 8.0.0-beta.0 → 8.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/package.json +8 -8
  2. package/client/actions/notification-fcm.ts +0 -148
  3. package/client/bootstrap.ts +0 -135
  4. package/client/index.ts +0 -6
  5. package/client/pages/notification/notification-list-page.ts +0 -258
  6. package/client/pages/notification-rule/notification-rule-importer.ts +0 -87
  7. package/client/pages/notification-rule/notification-rule-list-page.ts +0 -386
  8. package/client/reducers/notification.ts +0 -27
  9. package/client/route.ts +0 -10
  10. package/client/tsconfig.json +0 -13
  11. package/client/viewparts/notification-badge.ts +0 -54
  12. package/client/viewparts/notification-item.ts +0 -246
  13. package/client/viewparts/notification-list.ts +0 -160
  14. package/client/viewparts/notification-sender.ts +0 -142
  15. package/client/viewparts/notification-setting-let.ts +0 -222
  16. package/docs/images/config-app-1.png +0 -0
  17. package/docs/images/config-app-2.png +0 -0
  18. package/docs/images/config-server-key.png +0 -0
  19. package/docs/images/config-service-account.png +0 -0
  20. package/docs/images/config-vapidkey-1.png +0 -0
  21. package/docs/images/config-vapidkey-2-get-public-key.png +0 -0
  22. package/docs/images/config-vapidkey-3-get-private-key.png +0 -0
  23. package/docs/images/element-notification-badge.png +0 -0
  24. package/docs/images/element-notification-list.png +0 -0
  25. package/docs/images/element-notification-setting-let.png +0 -0
  26. package/docs/images/push-test-on-chrome-1.png +0 -0
  27. package/docs/images/push-test-on-chrome-2.png +0 -0
  28. package/docs/images/push-test-on-firebase-1.png +0 -0
  29. package/docs/images/push-test-on-firebase-2.png +0 -0
  30. package/docs/images/push-test-on-firebase-3.png +0 -0
  31. package/docs/images/push-test-on-firebase-4.png +0 -0
  32. package/server/controllers/fcm.ts +0 -214
  33. package/server/controllers/index.ts +0 -1
  34. package/server/index.ts +0 -5
  35. package/server/middlewares/index.ts +0 -5
  36. package/server/middlewares/notification-middleware.ts +0 -73
  37. package/server/routers/notification-router.ts +0 -67
  38. package/server/routes.ts +0 -11
  39. package/server/service/index.ts +0 -42
  40. package/server/service/notification/directive-notification.ts +0 -71
  41. package/server/service/notification/index.ts +0 -14
  42. package/server/service/notification/notification-mutation.ts +0 -119
  43. package/server/service/notification/notification-query.ts +0 -76
  44. package/server/service/notification/notification-subscription.ts +0 -44
  45. package/server/service/notification/notification-type.ts +0 -55
  46. package/server/service/notification/notification.ts +0 -105
  47. package/server/service/notification-rule/event-subscriber.ts +0 -20
  48. package/server/service/notification-rule/index.ts +0 -9
  49. package/server/service/notification-rule/notification-rule-history.ts +0 -136
  50. package/server/service/notification-rule/notification-rule-mutation.ts +0 -203
  51. package/server/service/notification-rule/notification-rule-query.ts +0 -65
  52. package/server/service/notification-rule/notification-rule-type.ts +0 -71
  53. package/server/service/notification-rule/notification-rule.ts +0 -125
  54. package/server/tsconfig.json +0 -9
@@ -1,222 +0,0 @@
1
- import '@material/web/icon/icon.js'
2
- import '@material/web/switch/switch.js'
3
- import '@operato/i18n/ox-i18n.js'
4
- import '@things-factory/setting-base'
5
- import './notification-sender'
6
-
7
- import Clipboard from 'clipboard'
8
- import { css, html, LitElement } from 'lit'
9
- import { customElement, property } from 'lit/decorators.js'
10
-
11
- import { notify, openPopup } from '@operato/layout'
12
- import { i18next, localize } from '@operato/i18n'
13
-
14
- import { getSubscriptionToken, isSupportingMessaging, subscribe, unsubscribe } from '../actions/notification-fcm'
15
-
16
- @customElement('notification-setting-let')
17
- export class NotificationSettingLet extends localize(i18next)(LitElement) {
18
- static styles = [
19
- css`
20
- div[onoff] {
21
- display: flex;
22
- align-items: center;
23
- border-bottom: var(--border-dim-color);
24
- margin-bottom: var(--spacing-medium);
25
- padding-bottom: var(--spacing-medium);
26
- }
27
-
28
- div[info] {
29
- display: flex;
30
- flex-direction: column;
31
- position: relative;
32
- }
33
-
34
- label {
35
- display: flex;
36
- gap: 10px;
37
- align-items: center;
38
-
39
- font: var(--label-font);
40
- color: var(--md-sys-color-on-surface);
41
- text-transform: var(--label-text-transform);
42
- }
43
-
44
- [test] {
45
- margin-left: auto;
46
- }
47
-
48
- input {
49
- border: var(--border-dim-color);
50
- border-radius: var(--border-radius);
51
- margin: var(--input-margin);
52
- padding: var(--input-padding);
53
- font: var(--input-font);
54
-
55
- flex: 1;
56
- }
57
-
58
- [button-in-field] {
59
- --md-icon-size: 18px;
60
- position: absolute;
61
- top: 24px;
62
- right: 4px;
63
- width: 30px;
64
- padding: 2px;
65
- border-radius: var(--border-radius, 5px);
66
- color: var(--md-sys-color-on-secondary, #fff);
67
- background-color: var(--md-sys-color-secondary, #394e64);
68
- }
69
- `
70
- ]
71
-
72
- @property({ type: Object }) subscription?: any
73
- @property({ type: Boolean }) supported?: boolean
74
-
75
- clipboard: any
76
-
77
- render() {
78
- const supported = !!this.supported
79
-
80
- return html`
81
- <setting-let>
82
- <ox-i18n slot="title" msgid="title.notification subscription"></ox-i18n>
83
-
84
- ${supported
85
- ? html`
86
- <div slot="content" onoff>
87
- <label>
88
- <md-switch
89
- label="allow message"
90
- ?selected=${!!this.subscription}
91
- @click=${e => this.onClickSwitch(e)}
92
- ></md-switch>
93
- ${String(i18next.t('label.allow push message'))}
94
- </label>
95
- <md-outlined-button test @click=${e => this.ontest()}
96
- >${String(i18next.t('button.push message test'))}</md-outlined-button
97
- >
98
- </div>
99
-
100
- <div slot="content" info>
101
- <label for="subscription-token"><ox-i18n msgid="label.subscription token"></ox-i18n></label>
102
- <input
103
- id="subscription-token"
104
- type="text"
105
- name="subscription-token"
106
- .value=${this.subscription || ''}
107
- readonly
108
- />
109
- <md-icon button-in-field clipboard-copy @click=${e => e.preventDefault()}>content_copy</md-icon>
110
- </div>
111
- `
112
- : html` <div slot="content">${i18next.t('text.push notification is not supported in your browser')}</div>`}
113
- </setting-let>
114
- `
115
- }
116
-
117
- updated(changes) {
118
- if (changes.has('supported') && this.supported) {
119
- const copybuttons = this.renderRoot.querySelectorAll('[clipboard-copy]')
120
-
121
- this.clipboard = new Clipboard(copybuttons, {
122
- target: trigger => trigger.parentElement!.querySelector('input')!
123
- })
124
- }
125
- }
126
-
127
- async connectedCallback() {
128
- super.connectedCallback()
129
-
130
- if (!('Notification' in window)) {
131
- return
132
- }
133
-
134
- this.supported = await isSupportingMessaging()
135
- this.subscription = await getSubscriptionToken()
136
- }
137
-
138
- async onClickSwitch(e) {
139
- const target = e.target
140
- const on = target.selected
141
-
142
- if (!('Notification' in window)) {
143
- notify({
144
- level: 'error',
145
- message: i18next.t('This browser does not support desktop notification')
146
- })
147
-
148
- return
149
- }
150
-
151
- try {
152
- if (Notification.permission !== 'granted') {
153
- const permission = await Notification.requestPermission()
154
- if (permission === 'denied') {
155
- notify({
156
- level: 'error',
157
- message: i18next.t('text.web push notification unblock guide')
158
- })
159
- }
160
- return
161
- }
162
-
163
- if (Notification.permission === 'granted') {
164
- if (on) {
165
- const subscription = await getSubscriptionToken()
166
-
167
- if (subscription) {
168
- this.subscription = subscription
169
- notify({
170
- level: 'info',
171
- message: i18next.t('text.notification subscribed already')
172
- })
173
- } else {
174
- this.subscription = await subscribe()
175
-
176
- this.subscription &&
177
- notify({
178
- level: 'info',
179
- message: i18next.t('text.notification subscribe started')
180
- })
181
- }
182
- } else {
183
- if (this.subscription) {
184
- await unsubscribe()
185
-
186
- this.subscription = await getSubscriptionToken()
187
-
188
- notify({
189
- level: 'info',
190
- message: i18next.t('text.notification unsubscribed')
191
- })
192
- } else {
193
- notify({
194
- level: 'info',
195
- message: i18next.t('text.notification unsubscribed already')
196
- })
197
- }
198
- }
199
- } else {
200
- notify({
201
- level: 'error',
202
- message: i18next.t('text.notification subscribing failed')
203
- })
204
- }
205
- } catch (err) {
206
- notify({
207
- level: 'error',
208
- message: String(err)
209
- })
210
- } finally {
211
- target.checked = !!this.subscription
212
- }
213
- }
214
-
215
- ontest() {
216
- openPopup(html` <notification-sender></notification-sender> `, {
217
- backdrop: true,
218
- size: 'large',
219
- title: i18next.t('title.notification-sender')
220
- })
221
- }
222
- }
Binary file
Binary file
Binary file
Binary file
@@ -1,214 +0,0 @@
1
- import fetch from 'node-fetch'
2
-
3
- import { config, logger } from '@things-factory/env'
4
-
5
- import { initializeApp, cert } from 'firebase-admin/app'
6
- import { getMessaging } from 'firebase-admin/messaging'
7
-
8
- const notificationConfig = config.get('notification') || {}
9
- const { fcm, vapidKey } = notificationConfig
10
- const { serviceAccount, appConfig, serverKey } = fcm || {}
11
- const { publicKey } = vapidKey || {}
12
-
13
- var messaging
14
-
15
- if (serviceAccount) {
16
- try {
17
- const app = initializeApp({
18
- credential: cert(serviceAccount)
19
- })
20
-
21
- messaging = getMessaging(app)
22
- } catch (err) {
23
- logger.error('incorrect notification configuration')
24
- logger.error(err)
25
- }
26
- }
27
-
28
- export function getConfig() {
29
- return messaging
30
- ? {
31
- appConfig,
32
- vapidPublicKey: publicKey
33
- }
34
- : {}
35
- }
36
-
37
- /**
38
- * sendNotification
39
- *
40
- * @param receiver user.id who will receive notification
41
- * @param message message object to be sent
42
- */
43
- export async function sendNotification({ receivers, message }) {
44
- if (!messaging) {
45
- return
46
- }
47
-
48
- notify({
49
- receivers,
50
- ...message
51
- })
52
- }
53
-
54
- export async function register(user, { subscription }) {
55
- if (!messaging || !subscription || !user) {
56
- return false
57
- }
58
-
59
- try {
60
- var notification_key = await getDeviceGroup(user.id)
61
-
62
- if (!notification_key) {
63
- await postDeviceGroup({
64
- operation: 'create',
65
- notification_key_name: user.id,
66
- registration_ids: [subscription]
67
- })
68
- } else {
69
- await postDeviceGroup({
70
- operation: 'add',
71
- notification_key_name: user.id,
72
- notification_key,
73
- registration_ids: [subscription]
74
- })
75
- }
76
-
77
- return true
78
- } catch (err) {
79
- return
80
- }
81
- }
82
-
83
- export async function unregister(user, { subscription }) {
84
- if (!messaging || !subscription || !user) {
85
- return false
86
- }
87
-
88
- try {
89
- var notification_key = await getDeviceGroup(user.id)
90
-
91
- if (notification_key) {
92
- await postDeviceGroup({
93
- operation: 'remove',
94
- notification_key_name: user.id,
95
- notification_key,
96
- registration_ids: [subscription]
97
- })
98
- }
99
-
100
- return true
101
- } catch (err) {
102
- return
103
- }
104
- }
105
-
106
- export async function notify({ receivers, privileges, tokens, topic, title, body, data, image, actions }) {
107
- if (!messaging) {
108
- return
109
- }
110
-
111
- // Caution: non-string attributes are not allowed in FCM payload validation
112
- var notification = {
113
- title: title || '',
114
- body: body || '',
115
- image: image || ''
116
- }
117
-
118
- if (tokens && tokens instanceof Array && tokens.length > 0) {
119
- if (tokens.length > 1) {
120
- const response = await messaging.sendMulticast({
121
- notification,
122
- data,
123
- tokens
124
- })
125
-
126
- if (response.failureCount > 0) {
127
- const failedTokens = []
128
- response.responses.forEach((resp, idx) => {
129
- if (!resp.success) {
130
- failedTokens.push(tokens[idx])
131
- }
132
- })
133
- logger.error('List of tokens that caused failures: ' + failedTokens)
134
- }
135
- } else {
136
- const token = tokens[0]
137
-
138
- try {
139
- const response = await messaging.send({
140
- notification,
141
- data,
142
- token
143
- })
144
- logger.log('Successfully sent message:', response)
145
- } catch (err) {
146
- logger.error('Error sending message:', err)
147
- }
148
- }
149
- }
150
-
151
- if (topic) {
152
- try {
153
- const response = await messaging.send({
154
- notification,
155
- data,
156
- topic
157
- })
158
- logger.log('Successfully sent message:', response)
159
- } catch (err) {
160
- logger.error('Error sending message:', err)
161
- }
162
- }
163
-
164
- if (receivers && receivers instanceof Array) {
165
- for (let receiver of receivers) {
166
- const notification_key = await getDeviceGroup(receiver)
167
-
168
- if (notification_key) {
169
- await messaging.sendToDeviceGroup(notification_key, {
170
- notification,
171
- data
172
- })
173
- }
174
- }
175
- }
176
- }
177
-
178
- async function postDeviceGroup(body) {
179
- var response = await fetch('https://fcm.googleapis.com/fcm/notification', {
180
- method: 'POST',
181
- headers: {
182
- 'Content-Type': 'application/json',
183
- Authorization: `key=${serverKey}`,
184
- project_id: appConfig.messagingSenderId
185
- },
186
- body: JSON.stringify(body)
187
- })
188
-
189
- if (response.ok) {
190
- const { notification_key } = await response.json()
191
- return notification_key
192
- } else {
193
- console.error('postDeviceGroup-notok', response.status, await response.text())
194
- }
195
- }
196
-
197
- async function getDeviceGroup(notificationKeyName) {
198
- const response = await fetch(
199
- `https://fcm.googleapis.com/fcm/notification?notification_key_name=${notificationKeyName}`,
200
- {
201
- method: 'GET',
202
- headers: {
203
- 'Content-Type': 'application/json',
204
- Authorization: `key=${serverKey}`,
205
- project_id: appConfig.messagingSenderId
206
- }
207
- }
208
- )
209
-
210
- if (response.ok) {
211
- const { notification_key } = await response.json()
212
- return notification_key
213
- }
214
- }
@@ -1 +0,0 @@
1
- export * from './fcm'
package/server/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export * from './service'
2
- export * from './middlewares'
3
- export * from './controllers'
4
-
5
- import './routes'
@@ -1,5 +0,0 @@
1
- import { notificationMiddleware } from './notification-middleware'
2
-
3
- export function initMiddlewares(app) {
4
- app.use(notificationMiddleware)
5
- }
@@ -1,73 +0,0 @@
1
- import { pubsub } from '@things-factory/shell'
2
- import { logger } from '@things-factory/env'
3
-
4
- import { notify } from '../controllers/fcm'
5
-
6
- const debug = require('debug')('things-factory:notification:notification-middleware')
7
-
8
- function notifier(context) {
9
- return async function ({
10
- receivers,
11
- privileges,
12
- tokens,
13
- topic,
14
- type,
15
- subject = 'info',
16
- title,
17
- body,
18
- image,
19
- url,
20
- actions,
21
- timestamp = Date.now(),
22
- mode = 'background'
23
- }) {
24
- const { domain, user } = context.state
25
-
26
- try {
27
- if (mode === 'background') {
28
- /* for send webpush notification message */
29
-
30
- await notify({
31
- receivers,
32
- privileges,
33
- tokens,
34
- topic,
35
- title,
36
- body,
37
- data: {
38
- type,
39
- url,
40
- timestamp: String(timestamp)
41
- },
42
- image,
43
- actions
44
- })
45
- } else {
46
- /* for send publish notification to clients */
47
- // TODO CONFIRM data format
48
- await pubsub.publish('notification', {
49
- notification: {
50
- domain,
51
- subject,
52
- type,
53
- title,
54
- body,
55
- image,
56
- url,
57
- timestamp
58
- }
59
- })
60
- }
61
- } catch (err) {
62
- logger.error(err)
63
- }
64
- }
65
- }
66
-
67
- export async function notificationMiddleware(context: any, next: any) {
68
- if (!context.state.notify) {
69
- context.state.notify = notifier(context)
70
- }
71
-
72
- await next()
73
- }
@@ -1,67 +0,0 @@
1
- import Router from 'koa-router'
2
- import { ILike } from 'typeorm'
3
-
4
- import { User } from '@things-factory/auth-base'
5
- import { getRepository } from '@things-factory/shell'
6
-
7
- import { getConfig, register, unregister } from '../controllers/fcm'
8
-
9
- const debug = require('debug')('things-factory:notification:notification-router')
10
-
11
- export const notificationRouter = new Router({
12
- prefix: '/notification'
13
- })
14
-
15
- notificationRouter.get('/config', async (context, next) => {
16
- const config = getConfig()
17
-
18
- context.type = 'application/json'
19
- context.body = config
20
- })
21
-
22
- notificationRouter.post('/register', async (context, next) => {
23
- const { user } = context.state
24
- const { body } = context.request
25
- context.body = await register(user, body)
26
- context.status = 200
27
- })
28
-
29
- notificationRouter.post('/unregister', async (context, next) => {
30
- const { user } = context.state
31
- const { body } = context.request
32
- context.body = await unregister(user, body)
33
- context.status = 200
34
- })
35
-
36
- notificationRouter.post('/notify', async (context, next) => {
37
- const { user, domain, notify } = context.state
38
- const repository = getRepository(User)
39
-
40
- var { receivers, ...options } = context.request.body
41
-
42
- debug('post:/notify', receivers, receivers.split(','), options)
43
-
44
- // TODO filter only users having current domain privilege
45
- receivers = (
46
- await Promise.all(
47
- receivers
48
- .split(',')
49
- .map(email => email.trim())
50
- .map(async email => {
51
- var receiver: User = await repository.findOneBy({ email: ILike(email) })
52
- return receiver && receiver.id
53
- })
54
- )
55
- ).filter(receiver => !!receiver)
56
-
57
- debug('post:/notify', receivers, user.id)
58
-
59
- await notify({
60
- receivers: receivers.length > 0 ? receivers : [user.id],
61
- ...options
62
- })
63
-
64
- context.body = {
65
- success: true
66
- }
67
- })
package/server/routes.ts DELETED
@@ -1,11 +0,0 @@
1
- import { notificationRouter } from './routers/notification-router'
2
-
3
- process.on('bootstrap-module-global-public-route' as any, (app, globalPublicRouter) => {})
4
-
5
- process.on('bootstrap-module-global-private-route' as any, (app, globalPrivateRouter) => {})
6
-
7
- process.on('bootstrap-module-domain-public-route' as any, (app, domainPublicRouter) => {})
8
-
9
- process.on('bootstrap-module-domain-private-route' as any, (app, domainPrivateRouter) => {
10
- domainPrivateRouter.use('', notificationRouter.routes(), notificationRouter.allowedMethods())
11
- })
@@ -1,42 +0,0 @@
1
- /* EXPORT ENTITY TYPES */
2
- export * from './notification-rule/notification-rule'
3
- export * from './notification/notification'
4
-
5
- /* IMPORT ENTITIES AND RESOLVERS */
6
- import {
7
- entities as NotificationRuleEntities,
8
- resolvers as NotificationRuleResolvers,
9
- subscribers as NotificationRuleSubscribers
10
- } from './notification-rule'
11
- import {
12
- entities as NotificationEntities,
13
- typeDefs as NotificationTypeDefs,
14
- resolvers as NotificationResolvers,
15
- directives as NotificationDirectives
16
- } from './notification'
17
-
18
- export const entities = [
19
- /* ENTITIES */
20
- ...NotificationRuleEntities,
21
- ...NotificationEntities
22
- ]
23
-
24
- export const subscribers = [
25
- /* SUBSCRIBERS */
26
- ...NotificationRuleSubscribers
27
- ]
28
-
29
- export const schema = {
30
- typeDefs: {
31
- ...NotificationTypeDefs
32
- },
33
- resolverClasses: [
34
- /* RESOLVER CLASSES */
35
- ...NotificationRuleResolvers,
36
- ...NotificationResolvers,
37
- ...NotificationResolvers
38
- ],
39
- directives: {
40
- ...NotificationDirectives
41
- }
42
- }