@l.x/notifications 1.0.3 → 1.0.4
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.
- package/.depcheckrc +14 -0
- package/.eslintrc.js +20 -0
- package/README.md +548 -0
- package/package.json +42 -1
- package/project.json +24 -0
- package/src/getIsNotificationServiceLocalOverrideEnabled.ts +7 -0
- package/src/global.d.ts +2 -0
- package/src/index.ts +41 -0
- package/src/notification-data-source/NotificationDataSource.ts +10 -0
- package/src/notification-data-source/getNotificationQueryOptions.ts +85 -0
- package/src/notification-data-source/implementations/createIntervalNotificationDataSource.ts +77 -0
- package/src/notification-data-source/implementations/createLocalTriggerDataSource.test.ts +492 -0
- package/src/notification-data-source/implementations/createLocalTriggerDataSource.ts +177 -0
- package/src/notification-data-source/implementations/createNotificationDataSource.ts +19 -0
- package/src/notification-data-source/implementations/createPollingNotificationDataSource.test.ts +398 -0
- package/src/notification-data-source/implementations/createPollingNotificationDataSource.ts +74 -0
- package/src/notification-data-source/implementations/createReactiveDataSource.ts +113 -0
- package/src/notification-data-source/types/ReactiveCondition.ts +60 -0
- package/src/notification-processor/NotificationProcessor.ts +26 -0
- package/src/notification-processor/implementations/createBaseNotificationProcessor.test.ts +854 -0
- package/src/notification-processor/implementations/createBaseNotificationProcessor.ts +254 -0
- package/src/notification-processor/implementations/createNotificationProcessor.test.ts +130 -0
- package/src/notification-processor/implementations/createNotificationProcessor.ts +15 -0
- package/src/notification-renderer/NotificationRenderer.ts +8 -0
- package/src/notification-renderer/components/BannerTemplate.tsx +188 -0
- package/src/notification-renderer/components/InlineBannerNotification.tsx +123 -0
- package/src/notification-renderer/implementations/createNotificationRenderer.ts +16 -0
- package/src/notification-renderer/utils/iconUtils.ts +103 -0
- package/src/notification-service/NotificationService.ts +49 -0
- package/src/notification-service/implementations/createNotificationService.test.ts +1092 -0
- package/src/notification-service/implementations/createNotificationService.ts +368 -0
- package/src/notification-telemetry/NotificationTelemetry.ts +44 -0
- package/src/notification-telemetry/implementations/createNotificationTelemetry.test.ts +99 -0
- package/src/notification-telemetry/implementations/createNotificationTelemetry.ts +33 -0
- package/src/notification-tracker/NotificationTracker.ts +14 -0
- package/src/notification-tracker/implementations/createApiNotificationTracker.test.ts +465 -0
- package/src/notification-tracker/implementations/createApiNotificationTracker.ts +154 -0
- package/src/notification-tracker/implementations/createNoopNotificationTracker.ts +44 -0
- package/src/notification-tracker/implementations/createNotificationTracker.ts +31 -0
- package/src/utils/formatNotificationType.test.ts +25 -0
- package/src/utils/formatNotificationType.ts +25 -0
- package/tsconfig.json +34 -0
- package/tsconfig.lint.json +8 -0
- package/vitest-setup.ts +1 -0
- package/vitest.config.ts +14 -0
- package/index.d.ts +0 -1
- package/index.js +0 -1
package/src/index.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export { getIsNotificationServiceLocalOverrideEnabled } from './getIsNotificationServiceLocalOverrideEnabled'
|
|
2
|
+
export { getNotificationQueryOptions } from './notification-data-source/getNotificationQueryOptions'
|
|
3
|
+
export { createIntervalNotificationDataSource } from './notification-data-source/implementations/createIntervalNotificationDataSource'
|
|
4
|
+
export {
|
|
5
|
+
type CreateLocalTriggerDataSourceContext,
|
|
6
|
+
createLocalTriggerDataSource,
|
|
7
|
+
getTriggerById,
|
|
8
|
+
type TriggerCondition,
|
|
9
|
+
} from './notification-data-source/implementations/createLocalTriggerDataSource'
|
|
10
|
+
export { createNotificationDataSource } from './notification-data-source/implementations/createNotificationDataSource'
|
|
11
|
+
export { createPollingNotificationDataSource } from './notification-data-source/implementations/createPollingNotificationDataSource'
|
|
12
|
+
export {
|
|
13
|
+
type CreateReactiveDataSourceContext,
|
|
14
|
+
createReactiveDataSource,
|
|
15
|
+
} from './notification-data-source/implementations/createReactiveDataSource'
|
|
16
|
+
export { type NotificationDataSource } from './notification-data-source/NotificationDataSource'
|
|
17
|
+
export { type ReactiveCondition } from './notification-data-source/types/ReactiveCondition'
|
|
18
|
+
export { createBaseNotificationProcessor } from './notification-processor/implementations/createBaseNotificationProcessor'
|
|
19
|
+
export { type NotificationProcessor } from './notification-processor/NotificationProcessor'
|
|
20
|
+
export { BannerTemplate } from './notification-renderer/components/BannerTemplate'
|
|
21
|
+
export { InlineBannerNotification } from './notification-renderer/components/InlineBannerNotification'
|
|
22
|
+
export { createNotificationRenderer } from './notification-renderer/implementations/createNotificationRenderer'
|
|
23
|
+
export { type NotificationRenderer } from './notification-renderer/NotificationRenderer'
|
|
24
|
+
export { createNotificationService } from './notification-service/implementations/createNotificationService'
|
|
25
|
+
export {
|
|
26
|
+
type NotificationClickTarget,
|
|
27
|
+
type NotificationService,
|
|
28
|
+
type NotificationServiceConfig,
|
|
29
|
+
} from './notification-service/NotificationService'
|
|
30
|
+
export { createNotificationTelemetry } from './notification-telemetry/implementations/createNotificationTelemetry'
|
|
31
|
+
export {
|
|
32
|
+
createNoopNotificationTelemetry,
|
|
33
|
+
type NotificationTelemetry,
|
|
34
|
+
} from './notification-telemetry/NotificationTelemetry'
|
|
35
|
+
export {
|
|
36
|
+
type ApiNotificationTrackerContext,
|
|
37
|
+
createApiNotificationTracker,
|
|
38
|
+
} from './notification-tracker/implementations/createApiNotificationTracker'
|
|
39
|
+
export { createNoopNotificationTracker } from './notification-tracker/implementations/createNoopNotificationTracker'
|
|
40
|
+
export { createNotificationTracker } from './notification-tracker/implementations/createNotificationTracker'
|
|
41
|
+
export { type NotificationTracker } from './notification-tracker/NotificationTracker'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type InAppNotification } from '@luxfi/api'
|
|
2
|
+
|
|
3
|
+
export interface NotificationDataSource {
|
|
4
|
+
// Start receiving notifications (implementation determines mechanism: fetch, websocket, polling, etc.)
|
|
5
|
+
start(onNotifications: (notifications: InAppNotification[], source: string) => void): void
|
|
6
|
+
// Stop receiving notifications and cleanup
|
|
7
|
+
stop(): Promise<void>
|
|
8
|
+
// Trigger an immediate poll outside of the normal interval (optional)
|
|
9
|
+
refresh?(): Promise<void>
|
|
10
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { toPlainMessage } from '@bufbuild/protobuf'
|
|
2
|
+
import { queryOptions } from '@tanstack/react-query'
|
|
3
|
+
import { PlatformType } from '@luxamm/client-notification-service/dist/lx/notificationservice/v1/api_pb'
|
|
4
|
+
import type { InAppNotification, NotificationsApiClient } from '@l.x/api'
|
|
5
|
+
import { getLogger } from 'utilities/src/logger/logger'
|
|
6
|
+
import { ReactQueryCacheKey } from 'utilities/src/reactQuery/cache'
|
|
7
|
+
import { type QueryOptionsResult } from 'utilities/src/reactQuery/queryOptions'
|
|
8
|
+
import { ONE_MINUTE_MS } from 'utilities/src/time/time'
|
|
9
|
+
|
|
10
|
+
const DEFAULT_POLL_INTERVAL_MS = 2 * ONE_MINUTE_MS
|
|
11
|
+
|
|
12
|
+
interface GetNotificationQueryOptionsContext {
|
|
13
|
+
apiClient: NotificationsApiClient
|
|
14
|
+
getPlatformType: () => PlatformType
|
|
15
|
+
pollIntervalMs?: number
|
|
16
|
+
getIsSessionInitialized?: () => boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates query options for polling notifications.
|
|
21
|
+
* This can be used directly in hooks or injected into the notification data source.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { getNotificationQueryOptions } from '@l.x/notifications'
|
|
26
|
+
* import { useQuery } from '@tanstack/react-query'
|
|
27
|
+
*
|
|
28
|
+
* // Use in a hook
|
|
29
|
+
* const queryOptions = getNotificationQueryOptions({ apiClient })
|
|
30
|
+
* const { data } = useQuery(queryOptions)
|
|
31
|
+
*
|
|
32
|
+
* // Or inject into data source
|
|
33
|
+
* const dataSource = createFetchNotificationDataSource({
|
|
34
|
+
* queryClient,
|
|
35
|
+
* queryOptions,
|
|
36
|
+
* })
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function getNotificationQueryOptions(
|
|
40
|
+
ctx: GetNotificationQueryOptionsContext,
|
|
41
|
+
): QueryOptionsResult<InAppNotification[], Error, InAppNotification[], [ReactQueryCacheKey.Notifications]> {
|
|
42
|
+
const { apiClient, getPlatformType, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, getIsSessionInitialized } = ctx
|
|
43
|
+
|
|
44
|
+
return queryOptions({
|
|
45
|
+
queryKey: [ReactQueryCacheKey.Notifications],
|
|
46
|
+
queryFn: async (): Promise<InAppNotification[]> => {
|
|
47
|
+
const isSessionInitialized = getIsSessionInitialized?.() ?? true
|
|
48
|
+
|
|
49
|
+
if (getIsSessionInitialized && !isSessionInitialized) {
|
|
50
|
+
return []
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const platformType = getPlatformType()
|
|
55
|
+
const response = await apiClient.getNotifications({ platform_type: platformType })
|
|
56
|
+
// Convert protobuf Messages to plain objects for React Query caching
|
|
57
|
+
// toPlainMessage strips the Message prototype chain and preserves numeric enum values
|
|
58
|
+
// It's schema-aware and automatically handles nested messages, making it resilient to schema changes
|
|
59
|
+
const serialized: InAppNotification[] = response.notifications.map((notification) =>
|
|
60
|
+
toPlainMessage(notification),
|
|
61
|
+
)
|
|
62
|
+
return serialized
|
|
63
|
+
} catch (error) {
|
|
64
|
+
getLogger().error(error, {
|
|
65
|
+
tags: { file: 'notificationQueryOptions', function: 'queryFn' },
|
|
66
|
+
})
|
|
67
|
+
throw error
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
refetchInterval: getIsSessionInitialized
|
|
71
|
+
? (): number => {
|
|
72
|
+
const isInit = getIsSessionInitialized()
|
|
73
|
+
// Poll faster (2s) when waiting for session, normal interval once initialized
|
|
74
|
+
return isInit ? pollIntervalMs : 2000
|
|
75
|
+
}
|
|
76
|
+
: (): number => pollIntervalMs,
|
|
77
|
+
refetchIntervalInBackground: true,
|
|
78
|
+
refetchOnWindowFocus: false,
|
|
79
|
+
// Use short staleTime when session check is enabled - allows faster refetches when session becomes ready
|
|
80
|
+
// Without this, empty results from pre-session fetches would be cached too long
|
|
81
|
+
staleTime: getIsSessionInitialized ? 1000 : pollIntervalMs - 1000,
|
|
82
|
+
retry: 2,
|
|
83
|
+
retryDelay: (attemptIndex: number): number => Math.min(1000 * 2 ** attemptIndex, 30000),
|
|
84
|
+
})
|
|
85
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type InAppNotification } from '@l.x/api'
|
|
2
|
+
import { createNotificationDataSource } from '@l.x/notifications/src/notification-data-source/implementations/createNotificationDataSource'
|
|
3
|
+
import { type NotificationDataSource } from '@l.x/notifications/src/notification-data-source/NotificationDataSource'
|
|
4
|
+
import { getLogger } from 'utilities/src/logger/logger'
|
|
5
|
+
|
|
6
|
+
interface CreateIntervalNotificationDataSourceContext {
|
|
7
|
+
pollIntervalMs: number
|
|
8
|
+
source: string
|
|
9
|
+
logFileTag: string
|
|
10
|
+
getNotifications: () => Promise<InAppNotification[]>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Helper for building interval-based notification data sources.
|
|
15
|
+
* Handles start/stop lifecycle, immediate initial poll, and consistent error logging.
|
|
16
|
+
*/
|
|
17
|
+
export function createIntervalNotificationDataSource(
|
|
18
|
+
ctx: CreateIntervalNotificationDataSourceContext,
|
|
19
|
+
): NotificationDataSource {
|
|
20
|
+
const { pollIntervalMs, source, logFileTag, getNotifications } = ctx
|
|
21
|
+
|
|
22
|
+
let intervalId: ReturnType<typeof setInterval> | null = null
|
|
23
|
+
let currentCallback: ((notifications: InAppNotification[], source: string) => void) | null = null
|
|
24
|
+
|
|
25
|
+
const pollAndEmit = async (logFunctionTag: string): Promise<void> => {
|
|
26
|
+
if (!currentCallback) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const notifications = await getNotifications()
|
|
32
|
+
currentCallback(notifications, source)
|
|
33
|
+
} catch (error) {
|
|
34
|
+
getLogger().error(error, {
|
|
35
|
+
tags: { file: logFileTag, function: logFunctionTag },
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const start = (onNotifications: (notifications: InAppNotification[], source: string) => void): void => {
|
|
41
|
+
if (intervalId) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
currentCallback = onNotifications
|
|
46
|
+
|
|
47
|
+
// Check immediately on start
|
|
48
|
+
pollAndEmit('start').catch((error) => {
|
|
49
|
+
getLogger().error(error, {
|
|
50
|
+
tags: { file: logFileTag, function: 'start' },
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// Then poll at interval
|
|
55
|
+
intervalId = setInterval(() => {
|
|
56
|
+
pollAndEmit('setInterval').catch((error) => {
|
|
57
|
+
getLogger().error(error, {
|
|
58
|
+
tags: { file: logFileTag, function: 'setInterval' },
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
}, pollIntervalMs)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const stop = async (): Promise<void> => {
|
|
65
|
+
if (intervalId) {
|
|
66
|
+
clearInterval(intervalId)
|
|
67
|
+
intervalId = null
|
|
68
|
+
}
|
|
69
|
+
currentCallback = null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const refresh = async (): Promise<void> => {
|
|
73
|
+
await pollAndEmit('refresh')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { ...createNotificationDataSource({ start, stop }), refresh }
|
|
77
|
+
}
|