@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/notification",
3
- "version": "8.0.0-beta.0",
3
+ "version": "8.0.0-beta.2",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -29,16 +29,16 @@
29
29
  "dependencies": {
30
30
  "@material/web": "^2.0.0",
31
31
  "@operato/layout": "^8.0.0-beta",
32
- "@things-factory/auth-base": "^8.0.0-beta.0",
33
- "@things-factory/codelingua": "^8.0.0-beta.0",
34
- "@things-factory/i18n-base": "^8.0.0-beta.0",
35
- "@things-factory/organization": "^8.0.0-beta.0",
36
- "@things-factory/setting-base": "^8.0.0-beta.0",
37
- "@things-factory/shell": "^8.0.0-beta.0",
32
+ "@things-factory/auth-base": "^8.0.0-beta.2",
33
+ "@things-factory/codelingua": "^8.0.0-beta.2",
34
+ "@things-factory/i18n-base": "^8.0.0-beta.2",
35
+ "@things-factory/organization": "^8.0.0-beta.2",
36
+ "@things-factory/setting-base": "^8.0.0-beta.2",
37
+ "@things-factory/shell": "^8.0.0-beta.2",
38
38
  "clipboard": "^2.0.6",
39
39
  "firebase": "^9.14.0",
40
40
  "firebase-admin": "^11.3.0",
41
41
  "google": "^2.1.0"
42
42
  },
43
- "gitHead": "add6fb8224b2cb19cbea47bed6a5ecb0424c9a28"
43
+ "gitHead": "f03431a09435511b2595515658f9cb8f78ba4ebb"
44
44
  }
@@ -1,148 +0,0 @@
1
- export const UPDATE_NOTIFICATION = 'UPDATE_NOTIFICATION'
2
- export const CONFIRM_NOTIFICATION = 'CONFIRM_NOTIFICATION'
3
-
4
- import { initializeApp } from 'firebase/app'
5
- import { getMessaging, getToken, deleteToken } from 'firebase/messaging'
6
-
7
- import { synchronize } from '@things-factory/utils'
8
-
9
- var vapidKey
10
- var appConfig
11
- var messaging
12
-
13
- async function _getVapidKey() {
14
- if (!vapidKey || !appConfig) {
15
- if (Notification.permission === 'denied') {
16
- console.warn('Notification permission denied')
17
- return
18
- }
19
-
20
- const response = await fetch('/notification/config', {
21
- headers: {
22
- Accept: 'application/json'
23
- }
24
- })
25
- if (!response.ok) {
26
- throw new Error(`fail to get notification configuration: ${response.status}`)
27
- }
28
- const conf = await response.json()
29
-
30
- if (!conf || !conf.vapidPublicKey || !conf.appConfig) {
31
- throw new Error('correct notification configuration required.')
32
- }
33
-
34
- try {
35
- const app = await initializeApp(conf.appConfig)
36
- messaging = getMessaging(app)
37
-
38
- // messaging = _messaging.isSupported() ? _messaging() : null
39
- if (!messaging) {
40
- console.warn('###################################################')
41
- console.warn('push notification is not supported in this browser.')
42
- console.warn('###################################################')
43
- }
44
-
45
- vapidKey = conf.vapidPublicKey
46
- appConfig = conf.appConfig
47
- } catch (err) {
48
- throw new Error(`failed to initialize firebase app.`)
49
- }
50
- }
51
-
52
- return { vapidKey, messaging }
53
- }
54
-
55
- // to avoid race condition
56
- const getVapidKey = synchronize(_getVapidKey) as any
57
-
58
- export async function isSupportingMessaging() {
59
- const { messaging } = (await getVapidKey()) || {}
60
- return !!messaging
61
- }
62
-
63
- export async function getSubscriptionToken(force = false) {
64
- // const registration = await navigator.serviceWorker.getRegistration('/')
65
- const registration = await navigator.serviceWorker?.ready
66
-
67
- if (!registration) {
68
- console.warn('service worker not registered.')
69
- return
70
- }
71
-
72
- if (!registration.pushManager || (!force && !(await registration.pushManager.getSubscription()))) {
73
- return
74
- }
75
-
76
- const { vapidKey, messaging } = (await getVapidKey()) || {}
77
-
78
- try {
79
- return (
80
- vapidKey &&
81
- (await getToken(messaging, {
82
- vapidKey,
83
- serviceWorkerRegistration: registration
84
- }))
85
- )
86
- } catch (err) {
87
- console.error(err)
88
- }
89
- }
90
-
91
- export async function subscribe() {
92
- const token = await getSubscriptionToken(true)
93
-
94
- if (!token) {
95
- return
96
- }
97
-
98
- await fetch('/notification/register', {
99
- method: 'post',
100
- headers: {
101
- 'Content-type': 'application/json',
102
- Accept: 'application/json'
103
- },
104
- body: JSON.stringify({
105
- subscription: token
106
- })
107
- })
108
-
109
- return token
110
- }
111
-
112
- export async function unsubscribe() {
113
- const token = await getSubscriptionToken()
114
- if (!token) {
115
- return
116
- }
117
-
118
- try {
119
- const { vapidKey, messaging } = (await getVapidKey()) || {}
120
- vapidKey && (await deleteToken(messaging))
121
- } catch (err) {
122
- console.error(err)
123
- }
124
-
125
- await fetch('/notification/unregister', {
126
- method: 'post',
127
- headers: {
128
- 'Content-type': 'application/json',
129
- Accept: 'application/json'
130
- },
131
- body: JSON.stringify({
132
- subscription: token
133
- })
134
- })
135
- }
136
-
137
- export async function notify(options) {
138
- const response = await fetch('/notification/notify', {
139
- method: 'post',
140
- headers: {
141
- 'Content-type': 'application/json',
142
- Accept: 'application/json'
143
- },
144
- body: JSON.stringify(options)
145
- })
146
-
147
- return response.ok
148
- }
@@ -1,135 +0,0 @@
1
- import gql from 'graphql-tag'
2
-
3
- import { notify } from '@operato/layout'
4
- import { auth } from '@things-factory/auth-base/dist-client/auth.js'
5
-
6
- import { i18next } from '@operato/i18n'
7
- import { notificationStore, route, store } from '@operato/shell'
8
- import { subscribe as graphqlSubscribe } from '@operato/graphql'
9
-
10
- import { subscribe, unsubscribe, UPDATE_NOTIFICATION } from './actions/notification-fcm'
11
- import notification from './reducers/notification'
12
-
13
- var graphqlSubscription: any
14
-
15
- export default async function bootstrap() {
16
- /* initialize reducers */
17
- store.addReducers({ notification })
18
-
19
- async function onnotification(notification?: any) {
20
- store.dispatch((async dispatch => {
21
- if (notification) {
22
- await notificationStore.add(notification)
23
- await notificationStore.limit()
24
- }
25
-
26
- const history = await notificationStore.getAll()
27
-
28
- dispatch({
29
- type: UPDATE_NOTIFICATION,
30
- history: [...history]
31
- })
32
- }) as any)
33
- }
34
-
35
- auth.on('presignout', async () => {
36
- try {
37
- await unsubscribe()
38
- /* await */ notificationStore.clear()
39
- } catch (err) {
40
- console.warn(err)
41
- }
42
- })
43
-
44
- auth.on('profile', async ({ credential }) => {
45
- try {
46
- await subscribe()
47
- } catch (err) {
48
- console.warn(err)
49
- }
50
-
51
- /*
52
- * subscription graphql notifications
53
- */
54
- var query = gql`
55
- subscription notification($subjects: [String!]) {
56
- notification(subjects: $subjects) {
57
- subject
58
- type
59
- title
60
- body
61
- url
62
- image
63
- timestamp
64
- }
65
- }
66
- `
67
-
68
- graphqlSubscription = await graphqlSubscribe(
69
- {
70
- query,
71
- variables: {
72
- subjects: ['info', 'warn', 'error', 'reload']
73
- }
74
- },
75
- {
76
- next({ data }) {
77
- const { notification } = data || {}
78
- onnotification({
79
- timestamp: Date.now(),
80
- ...notification
81
- })
82
- }
83
- }
84
- )
85
- })
86
-
87
- /* subscription web-push notification */
88
- const channel = new BroadcastChannel('notification')
89
- channel.addEventListener('message', async ({ data }) => {
90
- const { type } = data || {}
91
-
92
- switch (type) {
93
- case 'notificationclick':
94
- const { action, notification } = data
95
-
96
- // TODO how to follow action with notification.data
97
- notification.url && route(notification.url)
98
- break
99
-
100
- case 'show-reload-snackbar':
101
- notify({
102
- level: 'info',
103
- message: i18next.t('text.the application has been updated'),
104
- option: {
105
- action: {
106
- label: i18next.t('button.reload'),
107
- callback: () => window.location.reload()
108
- },
109
- timer: -1
110
- }
111
- })
112
- break
113
-
114
- case 'notification':
115
- default:
116
- onnotification()
117
- }
118
- })
119
-
120
- document.addEventListener('notify', (event: Event) => {
121
- let { message, level, ex = '', option = {} } = (event as CustomEvent).detail
122
- const title = message.split(' .\n')[0]
123
-
124
- if (level === 'error') {
125
- onnotification({
126
- timestamp: Date.now(),
127
- type: 'ERROR',
128
- title,
129
- body: ex ? ex.message : message
130
- })
131
- }
132
- })
133
-
134
- await onnotification()
135
- }
package/client/index.ts DELETED
@@ -1,6 +0,0 @@
1
- export * from './viewparts/notification-badge'
2
- export * from './viewparts/notification-list'
3
- export * from './viewparts/notification-setting-let'
4
- export * from './viewparts/notification-sender'
5
-
6
- export * from './actions/notification-fcm'
@@ -1,258 +0,0 @@
1
- import '@operato/data-grist'
2
-
3
- import { CommonButtonStyles, CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'
4
- import { PageView, store } from '@operato/shell'
5
- import { css, html } from 'lit'
6
- import { customElement, property, query } from 'lit/decorators.js'
7
- import { ScopedElementsMixin } from '@open-wc/scoped-elements'
8
- import { ColumnConfig, DataGrist, FetchOption } from '@operato/data-grist'
9
- import { client } from '@operato/graphql'
10
- import { i18next, localize } from '@operato/i18n'
11
- import { notify } from '@operato/layout'
12
- import { isMobileDevice } from '@operato/utils'
13
-
14
- import { connect } from 'pwa-helpers/connect-mixin'
15
- import gql from 'graphql-tag'
16
-
17
- @customElement('notification-list-page')
18
- export class NotificationListPage extends connect(store)(localize(i18next)(ScopedElementsMixin(PageView))) {
19
- static styles = [
20
- ScrollbarStyles,
21
- CommonGristStyles,
22
- CommonHeaderStyles,
23
- css`
24
- :host {
25
- display: flex;
26
-
27
- width: 100%;
28
-
29
- --grid-record-emphasized-background-color: #8b0000;
30
- --grid-record-emphasized-color: #ff6b6b;
31
- }
32
-
33
- ox-grist {
34
- overflow-y: auto;
35
- flex: 1;
36
- }
37
-
38
- ox-filters-form {
39
- flex: 1;
40
- }
41
- `
42
- ]
43
-
44
- @property({ type: Object }) gristConfig: any
45
- @property({ type: String }) mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'CARD' : 'GRID'
46
-
47
- @query('ox-grist') private grist!: DataGrist
48
-
49
- get context() {
50
- return {
51
- title: i18next.t('title.notification list'),
52
- search: {
53
- handler: (search: string) => {
54
- this.grist.searchText = search
55
- },
56
- value: this.grist?.searchText || ''
57
- },
58
- filter: {
59
- handler: () => {
60
- this.grist.toggleHeadroom()
61
- }
62
- },
63
- help: 'notification/notification',
64
- actions: [
65
- {
66
- title: i18next.t('button.save'),
67
- action: this._updateNotification.bind(this),
68
- ...CommonButtonStyles.save
69
- },
70
- {
71
- title: i18next.t('button.delete'),
72
- action: this._deleteNotification.bind(this),
73
- ...CommonButtonStyles.delete
74
- }
75
- ]
76
- }
77
- }
78
-
79
- render() {
80
- const mode = this.mode || (isMobileDevice() ? 'CARD' : 'GRID')
81
-
82
- return html`
83
- <ox-grist .mode=${mode} .config=${this.gristConfig} .fetchHandler=${this.fetchHandler.bind(this)}>
84
- <div slot="headroom" class="header">
85
- <div class="filters">
86
- <ox-filters-form autofocus></ox-filters-form>
87
-
88
- <div id="modes">
89
- <md-icon @click=${() => (this.mode = 'GRID')} ?active=${mode == 'GRID'}>grid_on</md-icon>
90
- <md-icon @click=${() => (this.mode = 'LIST')} ?active=${mode == 'LIST'}>format_list_bulleted</md-icon>
91
- <md-icon @click=${() => (this.mode = 'CARD')} ?active=${mode == 'CARD'}>apps</md-icon>
92
- </div>
93
- </div>
94
- </div>
95
- </ox-grist>
96
- `
97
- }
98
-
99
- async pageInitialized(lifecycle: any) {
100
- this.gristConfig = {
101
- list: {
102
- fields: ['title', 'body'],
103
- details: ['state', 'updatedAt']
104
- },
105
- columns: [
106
- { type: 'gutter', gutterName: 'sequence' },
107
- { type: 'gutter', gutterName: 'row-selector', multiple: true },
108
- {
109
- type: 'string',
110
- name: 'title',
111
- header: i18next.t('field.title'),
112
- record: {
113
- editable: false
114
- },
115
- filter: 'search',
116
- sortable: true,
117
- width: 150
118
- },
119
- {
120
- type: 'string',
121
- name: 'body',
122
- header: i18next.t('field.body'),
123
- record: {
124
- editable: false
125
- },
126
- filter: 'search',
127
- width: 200
128
- },
129
- {
130
- type: 'checkbox',
131
- name: 'state',
132
- label: true,
133
- header: i18next.t('field.state'),
134
- record: {
135
- editable: true
136
- },
137
- filter: true,
138
- sortable: true,
139
- width: 60
140
- },
141
- {
142
- type: 'datetime',
143
- name: 'createdAt',
144
- header: i18next.t('field.created_at'),
145
- record: {
146
- editable: false
147
- },
148
- sortable: true,
149
- width: 180
150
- }
151
- ],
152
- rows: {
153
- selectable: {
154
- multiple: true
155
- }
156
- },
157
- sorters: [
158
- {
159
- name: 'createdAt',
160
- desc: true
161
- }
162
- ]
163
- }
164
- }
165
-
166
- async pageUpdated(changes: any, lifecycle: any) {
167
- if (this.active) {
168
- // do something here when this page just became as active
169
- }
170
- }
171
-
172
- async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [] }: FetchOption) {
173
- const response = await client.query({
174
- query: gql`
175
- query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
176
- responses: notiBoxes(filters: $filters, pagination: $pagination, sortings: $sortings) {
177
- items {
178
- id
179
- title
180
- body
181
- state
182
- createdAt
183
- }
184
- total
185
- }
186
- }
187
- `,
188
- variables: {
189
- filters,
190
- pagination: { page, limit },
191
- sortings
192
- }
193
- })
194
-
195
- return {
196
- total: response.data.responses.total || 0,
197
- records: response.data.responses.items || []
198
- }
199
- }
200
-
201
- async _deleteNotification() {
202
- if (confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }))) {
203
- const ids = this.grist.selected.map(record => record.id)
204
- if (ids && ids.length > 0) {
205
- const response = await client.mutate({
206
- mutation: gql`
207
- mutation ($ids: [String!]!) {
208
- deleteNotificationes(ids: $ids)
209
- }
210
- `,
211
- variables: {
212
- ids
213
- }
214
- })
215
-
216
- if (!response.errors) {
217
- this.grist.fetch()
218
- notify({
219
- message: i18next.t('text.info_x_successfully', { x: i18next.t('text.delete') })
220
- })
221
- }
222
- }
223
- }
224
- }
225
-
226
- async _updateNotification() {
227
- let patches = this.grist.dirtyRecords
228
- if (patches && patches.length) {
229
- patches = patches.map(patch => {
230
- let patchField: any = patch.id ? { id: patch.id } : {}
231
- const dirtyFields = patch.__dirtyfields__
232
- for (let key in dirtyFields) {
233
- patchField[key] = dirtyFields[key].after
234
- }
235
- patchField.cuFlag = patch.__dirty__
236
-
237
- return patchField
238
- })
239
-
240
- const response = await client.mutate({
241
- mutation: gql`
242
- mutation ($patches: [NotificationPatch!]!) {
243
- updateMultipleNotification(patches: $patches) {
244
- name
245
- }
246
- }
247
- `,
248
- variables: {
249
- patches
250
- }
251
- })
252
-
253
- if (!response.errors) {
254
- this.grist.fetch()
255
- }
256
- }
257
- }
258
- }
@@ -1,87 +0,0 @@
1
- import '@material/web/icon/icon.js'
2
- import '@operato/data-grist'
3
-
4
- import gql from 'graphql-tag'
5
- import { css, html, LitElement } from 'lit'
6
- import { property } from 'lit/decorators.js'
7
-
8
- import { client } from '@operato/graphql'
9
- import { i18next } from '@operato/i18n'
10
- import { isMobileDevice } from '@operato/utils'
11
- import { CommonHeaderStyles } from '@operato/styles'
12
-
13
- export class NotificationRuleImporter extends LitElement {
14
- static styles = [
15
- CommonHeaderStyles,
16
- css`
17
- :host {
18
- display: flex;
19
- flex-direction: column;
20
-
21
- background-color: var(--md-sys-color-surface);
22
- }
23
-
24
- ox-grist {
25
- flex: 1;
26
- }
27
- `
28
- ]
29
-
30
- @property({ type: Array }) notificationRules: any[] = []
31
- @property({ type: Object }) columns = {
32
- list: { fields: ['name', 'description'] },
33
- pagination: { infinite: true },
34
- columns: [
35
- {
36
- type: 'string',
37
- name: 'name',
38
- header: i18next.t('field.name'),
39
- width: 150
40
- },
41
- {
42
- type: 'string',
43
- name: 'description',
44
- header: i18next.t('field.description'),
45
- width: 200
46
- },
47
- {
48
- type: 'checkbox',
49
- name: 'active',
50
- header: i18next.t('field.active'),
51
- width: 60
52
- }
53
- ]
54
- }
55
-
56
- render() {
57
- return html`
58
- <ox-grist
59
- .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
60
- .config=${this.columns}
61
- .data=${{
62
- records: this.notificationRules
63
- }}
64
- ></ox-grist>
65
-
66
- <div class="footer">
67
- <div filler></div>
68
- <button @click=${this.save.bind(this)} done><md-icon>save</md-icon>${i18next.t('button.save')}</button>
69
- </div>
70
- `
71
- }
72
-
73
- async save() {
74
- const response = await client.mutate({
75
- mutation: gql`
76
- mutation importNotificationRules($notificationRules: [NotificationRulePatch!]!) {
77
- importNotificationRules(notificationRules: $notificationRules)
78
- }
79
- `,
80
- variables: { notificationRules: this.notificationRules }
81
- })
82
-
83
- if (response.errors?.length) return
84
-
85
- this.dispatchEvent(new CustomEvent('imported'))
86
- }
87
- }