@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.
Files changed (47) hide show
  1. package/.depcheckrc +14 -0
  2. package/.eslintrc.js +20 -0
  3. package/README.md +548 -0
  4. package/package.json +42 -1
  5. package/project.json +24 -0
  6. package/src/getIsNotificationServiceLocalOverrideEnabled.ts +7 -0
  7. package/src/global.d.ts +2 -0
  8. package/src/index.ts +41 -0
  9. package/src/notification-data-source/NotificationDataSource.ts +10 -0
  10. package/src/notification-data-source/getNotificationQueryOptions.ts +85 -0
  11. package/src/notification-data-source/implementations/createIntervalNotificationDataSource.ts +77 -0
  12. package/src/notification-data-source/implementations/createLocalTriggerDataSource.test.ts +492 -0
  13. package/src/notification-data-source/implementations/createLocalTriggerDataSource.ts +177 -0
  14. package/src/notification-data-source/implementations/createNotificationDataSource.ts +19 -0
  15. package/src/notification-data-source/implementations/createPollingNotificationDataSource.test.ts +398 -0
  16. package/src/notification-data-source/implementations/createPollingNotificationDataSource.ts +74 -0
  17. package/src/notification-data-source/implementations/createReactiveDataSource.ts +113 -0
  18. package/src/notification-data-source/types/ReactiveCondition.ts +60 -0
  19. package/src/notification-processor/NotificationProcessor.ts +26 -0
  20. package/src/notification-processor/implementations/createBaseNotificationProcessor.test.ts +854 -0
  21. package/src/notification-processor/implementations/createBaseNotificationProcessor.ts +254 -0
  22. package/src/notification-processor/implementations/createNotificationProcessor.test.ts +130 -0
  23. package/src/notification-processor/implementations/createNotificationProcessor.ts +15 -0
  24. package/src/notification-renderer/NotificationRenderer.ts +8 -0
  25. package/src/notification-renderer/components/BannerTemplate.tsx +188 -0
  26. package/src/notification-renderer/components/InlineBannerNotification.tsx +123 -0
  27. package/src/notification-renderer/implementations/createNotificationRenderer.ts +16 -0
  28. package/src/notification-renderer/utils/iconUtils.ts +103 -0
  29. package/src/notification-service/NotificationService.ts +49 -0
  30. package/src/notification-service/implementations/createNotificationService.test.ts +1092 -0
  31. package/src/notification-service/implementations/createNotificationService.ts +368 -0
  32. package/src/notification-telemetry/NotificationTelemetry.ts +44 -0
  33. package/src/notification-telemetry/implementations/createNotificationTelemetry.test.ts +99 -0
  34. package/src/notification-telemetry/implementations/createNotificationTelemetry.ts +33 -0
  35. package/src/notification-tracker/NotificationTracker.ts +14 -0
  36. package/src/notification-tracker/implementations/createApiNotificationTracker.test.ts +465 -0
  37. package/src/notification-tracker/implementations/createApiNotificationTracker.ts +154 -0
  38. package/src/notification-tracker/implementations/createNoopNotificationTracker.ts +44 -0
  39. package/src/notification-tracker/implementations/createNotificationTracker.ts +31 -0
  40. package/src/utils/formatNotificationType.test.ts +25 -0
  41. package/src/utils/formatNotificationType.ts +25 -0
  42. package/tsconfig.json +34 -0
  43. package/tsconfig.lint.json +8 -0
  44. package/vitest-setup.ts +1 -0
  45. package/vitest.config.ts +14 -0
  46. package/index.d.ts +0 -1
  47. 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
+ }