@xtremax/pigeon 0.0.0-stage → 0.3.0

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 (101) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +56 -2
  3. package/dist/cjs/browser-stub.js +10 -0
  4. package/dist/cjs/client.js +112 -0
  5. package/dist/cjs/contract.js +5 -0
  6. package/dist/cjs/errors.js +48 -0
  7. package/dist/cjs/events.js +62 -0
  8. package/dist/cjs/idempotency.js +14 -0
  9. package/dist/cjs/inbox/browser-stub.js +10 -0
  10. package/dist/cjs/inbox/client.js +39 -0
  11. package/dist/cjs/inbox/errors.js +44 -0
  12. package/dist/cjs/inbox/handler.js +127 -0
  13. package/dist/cjs/inbox/index.js +15 -0
  14. package/dist/cjs/inbox/origin.js +22 -0
  15. package/dist/cjs/inbox/project.js +84 -0
  16. package/dist/cjs/inbox/validate.js +59 -0
  17. package/dist/cjs/inbox-react/format.js +72 -0
  18. package/dist/cjs/inbox-react/inbox-client.js +63 -0
  19. package/dist/cjs/inbox-react/inbox-context.js +13 -0
  20. package/dist/cjs/inbox-react/inbox-full-list.js +13 -0
  21. package/dist/cjs/inbox-react/inbox-list.js +37 -0
  22. package/dist/cjs/inbox-react/inbox-panel.js +13 -0
  23. package/dist/cjs/inbox-react/inbox-provider.js +157 -0
  24. package/dist/cjs/inbox-react/inbox-state.js +88 -0
  25. package/dist/cjs/inbox-react/index.js +25 -0
  26. package/dist/cjs/inbox-react/labels.js +38 -0
  27. package/dist/cjs/inbox-react/notification-bell.js +32 -0
  28. package/dist/cjs/inbox-react/notification-item.js +17 -0
  29. package/dist/cjs/inbox-react/parse.js +94 -0
  30. package/dist/cjs/inbox-react/preference-dialog.js +49 -0
  31. package/dist/cjs/inbox-react/types.js +4 -0
  32. package/dist/cjs/inbox-react/use-inbox-polling.js +94 -0
  33. package/dist/cjs/index.js +14 -0
  34. package/dist/cjs/package.json +1 -0
  35. package/dist/cjs/retry.js +14 -0
  36. package/dist/esm/browser-stub.js +6 -0
  37. package/dist/esm/client.js +107 -0
  38. package/dist/esm/contract.js +2 -0
  39. package/dist/esm/errors.js +42 -0
  40. package/dist/esm/events.js +57 -0
  41. package/dist/esm/idempotency.js +10 -0
  42. package/dist/esm/inbox/browser-stub.js +7 -0
  43. package/dist/esm/inbox/client.js +35 -0
  44. package/dist/esm/inbox/errors.js +38 -0
  45. package/dist/esm/inbox/handler.js +124 -0
  46. package/dist/esm/inbox/index.js +5 -0
  47. package/dist/esm/inbox/origin.js +19 -0
  48. package/dist/esm/inbox/project.js +76 -0
  49. package/dist/esm/inbox/validate.js +49 -0
  50. package/dist/esm/inbox-react/format.js +64 -0
  51. package/dist/esm/inbox-react/inbox-client.js +58 -0
  52. package/dist/esm/inbox-react/inbox-context.js +9 -0
  53. package/dist/esm/inbox-react/inbox-full-list.js +10 -0
  54. package/dist/esm/inbox-react/inbox-list.js +34 -0
  55. package/dist/esm/inbox-react/inbox-panel.js +10 -0
  56. package/dist/esm/inbox-react/inbox-provider.js +154 -0
  57. package/dist/esm/inbox-react/inbox-state.js +83 -0
  58. package/dist/esm/inbox-react/index.js +18 -0
  59. package/dist/esm/inbox-react/labels.js +34 -0
  60. package/dist/esm/inbox-react/notification-bell.js +29 -0
  61. package/dist/esm/inbox-react/notification-item.js +14 -0
  62. package/dist/esm/inbox-react/parse.js +85 -0
  63. package/dist/esm/inbox-react/preference-dialog.js +46 -0
  64. package/dist/esm/inbox-react/types.js +1 -0
  65. package/dist/esm/inbox-react/use-inbox-polling.js +88 -0
  66. package/dist/esm/index.js +4 -0
  67. package/dist/esm/retry.js +9 -0
  68. package/dist/inbox-react/styles.css +65 -0
  69. package/dist/types/browser-stub.d.ts +4 -0
  70. package/dist/types/client.d.ts +94 -0
  71. package/dist/types/contract.d.ts +182 -0
  72. package/dist/types/errors.d.ts +29 -0
  73. package/dist/types/events.d.ts +26 -0
  74. package/dist/types/idempotency.d.ts +4 -0
  75. package/dist/types/inbox/browser-stub.d.ts +3 -0
  76. package/dist/types/inbox/client.d.ts +34 -0
  77. package/dist/types/inbox/errors.d.ts +13 -0
  78. package/dist/types/inbox/handler.d.ts +25 -0
  79. package/dist/types/inbox/index.d.ts +6 -0
  80. package/dist/types/inbox/origin.d.ts +5 -0
  81. package/dist/types/inbox/project.d.ts +7 -0
  82. package/dist/types/inbox/validate.d.ts +25 -0
  83. package/dist/types/inbox-react/format.d.ts +13 -0
  84. package/dist/types/inbox-react/inbox-client.d.ts +24 -0
  85. package/dist/types/inbox-react/inbox-context.d.ts +29 -0
  86. package/dist/types/inbox-react/inbox-full-list.d.ts +7 -0
  87. package/dist/types/inbox-react/inbox-list.d.ts +12 -0
  88. package/dist/types/inbox-react/inbox-panel.d.ts +2 -0
  89. package/dist/types/inbox-react/inbox-provider.d.ts +18 -0
  90. package/dist/types/inbox-react/inbox-state.d.ts +70 -0
  91. package/dist/types/inbox-react/index.d.ts +19 -0
  92. package/dist/types/inbox-react/labels.d.ts +40 -0
  93. package/dist/types/inbox-react/notification-bell.d.ts +4 -0
  94. package/dist/types/inbox-react/notification-item.d.ts +9 -0
  95. package/dist/types/inbox-react/parse.d.ts +9 -0
  96. package/dist/types/inbox-react/preference-dialog.d.ts +5 -0
  97. package/dist/types/inbox-react/types.d.ts +55 -0
  98. package/dist/types/inbox-react/use-inbox-polling.d.ts +33 -0
  99. package/dist/types/index.d.ts +6 -0
  100. package/dist/types/retry.d.ts +13 -0
  101. package/package.json +33 -4
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The in-app inbox for an app: a bell with an unread badge, a panel, a full list and the person's preference.
3
+ * The bell shows what this app sent the person, and the tenant's announcements.
4
+ *
5
+ * import { InboxProvider, NotificationBell } from '@xtremax/pigeon/inbox-react';
6
+ * import '@xtremax/pigeon/inbox-react/styles.css';
7
+ *
8
+ * <InboxProvider>
9
+ * <NotificationBell />
10
+ * </InboxProvider>
11
+ *
12
+ * The components call the host app's own routes (default `/api/communication/inbox`), which
13
+ * `createInboxHandler` from `@xtremax/pigeon/inbox` serves. They need React 19, Mantine 8 and Tabler icons. Nothing here holds a key or talks to another origin.
14
+ */
15
+ export { InboxProvider, type InboxProviderProps } from './inbox-provider.js';
16
+ export { NotificationBell } from './notification-bell.js';
17
+ export { InboxFullList } from './inbox-full-list.js';
18
+ export { DEFAULT_INBOX_LABELS, type InboxLabels } from './labels.js';
19
+ export type { InboxNotification, InboxPreference, InboxSummary } from './types.js';
@@ -0,0 +1,40 @@
1
+ /** Every visible and accessible text of the inbox (15.9). Pass `labels` to `InboxProvider` to replace any of them. `{n}` and `{label}` are filled in. */
2
+ export interface InboxLabels {
3
+ /** The bell's accessible name when nothing is unread. */
4
+ bell: string;
5
+ /** The bell's accessible name with a count: "{label}, {n} unread". */
6
+ bellWithCount: string;
7
+ heading: string;
8
+ tabAll: string;
9
+ tabUnread: string;
10
+ /** The Unread tab with its count: "Unread ({n})". */
11
+ tabUnreadWithCount: string;
12
+ markAllRead: string;
13
+ viewAll: string;
14
+ preferences: string;
15
+ emptyAll: string;
16
+ emptyUnread: string;
17
+ loading: string;
18
+ loadError: string;
19
+ retry: string;
20
+ actionError: string;
21
+ dismiss: string;
22
+ /** The accessible name of a dialog's close button. */
23
+ close: string;
24
+ /** Read out before an unread row, so unread is not shown by colour alone. */
25
+ unread: string;
26
+ announcement: string;
27
+ loadMore: string;
28
+ /** Announced when the count rises while the panel is closed. */
29
+ newOne: string;
30
+ newMany: string;
31
+ fullListTitle: string;
32
+ preferencesTitle: string;
33
+ preferenceToggle: string;
34
+ preferenceOn: string;
35
+ preferenceOff: string;
36
+ preferenceLoadError: string;
37
+ preferenceSaveError: string;
38
+ }
39
+ export declare const DEFAULT_INBOX_LABELS: InboxLabels;
40
+ export declare function mergeLabels(labels?: Partial<InboxLabels>): InboxLabels;
@@ -0,0 +1,4 @@
1
+ /** The bell with its unread badge. Opens the inbox panel. Place it inside `InboxProvider`. */
2
+ export declare function NotificationBell({ size }: {
3
+ size?: number;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import type { InboxNotification } from './types.js';
2
+ /**
3
+ * One notification. The whole row is one button: selecting it marks the notification read and opens its link when
4
+ * it has a safe one. Title and body are text nodes, never markup (12.14).
5
+ */
6
+ export declare function NotificationItem({ item, now }: {
7
+ item: InboxNotification;
8
+ now: Date;
9
+ }): import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { type InboxErrorCode, type InboxMarkReadResult, type InboxNotification, type InboxNotificationPage, type InboxPreference, type InboxReadAllResult, type InboxSummary } from './types.js';
2
+ export declare function parseNotification(v: unknown): InboxNotification | null;
3
+ export declare function parseSummary(v: unknown): InboxSummary | null;
4
+ export declare function parsePage(v: unknown): InboxNotificationPage | null;
5
+ export declare function parseMarkRead(v: unknown): InboxMarkReadResult | null;
6
+ export declare function parseReadAll(v: unknown): InboxReadAllResult | null;
7
+ export declare function parsePreference(v: unknown): InboxPreference | null;
8
+ /** The code of an error body, or the nearest one for the status. Anything unknown is `unavailable`. */
9
+ export declare function parseErrorCode(status: number, body: unknown): InboxErrorCode;
@@ -0,0 +1,5 @@
1
+ /** The person's own choice for this app's in-app notifications. Other apps are not affected. */
2
+ export declare function PreferenceDialog({ opened, onClose }: {
3
+ opened: boolean;
4
+ onClose: () => void;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The inbox formats as the component reads them (IB-2). This module imports nothing from the SDK or the Gateway:
3
+ * it talks only to the host xApp's own routes.
4
+ */
5
+ export interface InboxNotification {
6
+ id: string;
7
+ kind: 'notification' | 'announcement';
8
+ /** Plain text. Never rendered as markup. */
9
+ title: string;
10
+ body: string;
11
+ /** Absolute https, or null. */
12
+ link: string | null;
13
+ source: {
14
+ type: 'xapp' | 'tenant';
15
+ name: string;
16
+ };
17
+ createdAt: string;
18
+ read: boolean;
19
+ readAt: string | null;
20
+ }
21
+ export interface InboxSummary {
22
+ unreadCount: number;
23
+ newestUnreadAt: string | null;
24
+ pollSeconds: number;
25
+ }
26
+ export interface InboxNotificationPage {
27
+ notifications: InboxNotification[];
28
+ nextCursor: string | null;
29
+ unreadCount: number;
30
+ }
31
+ export interface InboxPreference {
32
+ state: 'opted_in' | 'opted_out' | 'not_set';
33
+ changedAt: string | null;
34
+ delivers: boolean;
35
+ }
36
+ export interface InboxMarkReadResult {
37
+ notification: InboxNotification;
38
+ unreadCount: number;
39
+ }
40
+ export interface InboxReadAllResult {
41
+ marked: number;
42
+ remaining: number;
43
+ unreadCount: number;
44
+ }
45
+ /** The six answers of the host routes (IB-8). */
46
+ export type InboxErrorCode = 'not_signed_in' | 'not_permitted' | 'not_found' | 'invalid_input' | 'rate_limited' | 'unavailable';
47
+ export declare const INBOX_ERROR_CODES: readonly InboxErrorCode[];
48
+ export type InboxResult<T> = {
49
+ ok: true;
50
+ data: T;
51
+ } | {
52
+ ok: false;
53
+ code: InboxErrorCode;
54
+ };
55
+ export type InboxTab = 'all' | 'unread';
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Visibility-aware polling (13.1 to 13.9). This is the only file a push transport would replace.
3
+ * No realtime connection and no web storage are used.
4
+ */
5
+ export type PollOutcome = {
6
+ result: 'ok';
7
+ pollSeconds: number;
8
+ } | {
9
+ result: 'failed';
10
+ } | {
11
+ result: 'stop';
12
+ };
13
+ export declare const POLL_FLOOR_SECONDS = 15;
14
+ export declare const POLL_CAP_SECONDS = 300;
15
+ /**
16
+ * Seconds until the next poll. After a success: the recommended interval, at least 15. After failures:
17
+ * `min(300, base * 2^failures)` with plus or minus 20 percent jitter, still between 15 and 300.
18
+ */
19
+ export declare function nextPollDelay(failures: number, recommendedSeconds: number, random?: () => number): number;
20
+ export interface PollingEnvironment {
21
+ isVisible: () => boolean;
22
+ onVisibilityChange: (listener: () => void) => () => void;
23
+ setTimer: (fn: () => void, ms: number) => unknown;
24
+ clearTimer: (handle: unknown) => void;
25
+ random: () => number;
26
+ }
27
+ /**
28
+ * Polls now, then after each answer waits `nextPollDelay`. Pauses while the page is hidden and polls at once when it
29
+ * is visible again. `stop` (nobody is signed in) ends polling silently. Returns a function that stops it.
30
+ */
31
+ export declare function startPolling(poll: () => Promise<PollOutcome>, env: PollingEnvironment): () => void;
32
+ /** Runs `poll` for as long as the component is mounted and `enabled` is true. `poll` may change between renders. */
33
+ export declare function useInboxPolling(poll: () => Promise<PollOutcome>, enabled?: boolean): void;
@@ -0,0 +1,6 @@
1
+ export { createCommunicationClient, CommunicationError, type ClientOptions, type CommunicationClient } from './client.js';
2
+ export { createEventHandler, verifyEventSignature, type EventHandlerOptions, type VerifyResult } from './events.js';
3
+ export { deriveIdempotencyKey, newIdempotencyKey } from './idempotency.js';
4
+ export { DEFAULT_RETRY, type RetryPolicy } from './retry.js';
5
+ export type * from './contract.js';
6
+ export type { ErrorCode } from './errors.js';
@@ -0,0 +1,13 @@
1
+ export interface RetryPolicy {
2
+ maxAttempts: number;
3
+ baseDelayMs: number;
4
+ capDelayMs: number;
5
+ /** Total time across attempts and waits. */
6
+ totalTimeoutMs: number;
7
+ perAttemptTimeoutMs: number;
8
+ }
9
+ export declare const DEFAULT_RETRY: RetryPolicy;
10
+ /** Exponential backoff with full jitter, honouring Retry-After when the server sent one. */
11
+ export declare function backoffMs(attempt: number, policy: RetryPolicy, retryAfterSeconds: number | null, random?: () => number): number;
12
+ /** Only transient failures are retried: network errors, timeouts, 429 and 5xx. Client errors never are. */
13
+ export declare const isRetryableStatus: (status: number) => boolean;
package/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
2
  "name": "@xtremax/pigeon",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.0",
4
+ "description": "Client for Pigeon, the Buildpad Communication Gateway: send messages, verify signed events, serve an in-app inbox and show its notification bell.",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "sideEffects": ["**/*.css"],
8
+ "engines": { "node": ">=20" },
9
+ "main": "./dist/cjs/index.js",
10
+ "module": "./dist/esm/index.js",
11
+ "types": "./dist/types/index.d.ts",
12
+ "exports": {
13
+ ".": { "browser": "./dist/esm/browser-stub.js", "types": "./dist/types/index.d.ts", "import": "./dist/esm/index.js", "require": "./dist/cjs/index.js" },
14
+ "./client": { "browser": "./dist/esm/browser-stub.js", "types": "./dist/types/client.d.ts", "import": "./dist/esm/client.js", "require": "./dist/cjs/client.js" },
15
+ "./inbox": { "browser": "./dist/esm/inbox/browser-stub.js", "types": "./dist/types/inbox/index.d.ts", "import": "./dist/esm/inbox/index.js", "require": "./dist/cjs/inbox/index.js" },
16
+ "./inbox-react": { "types": "./dist/types/inbox-react/index.d.ts", "import": "./dist/esm/inbox-react/index.js", "require": "./dist/cjs/inbox-react/index.js" },
17
+ "./inbox-react/styles.css": "./dist/inbox-react/styles.css",
18
+ "./events": { "types": "./dist/types/events.d.ts", "import": "./dist/esm/events.js", "require": "./dist/cjs/events.js" },
19
+ "./contract": { "types": "./dist/types/contract.d.ts" }
20
+ },
21
+ "files": ["dist", "README.md", "CHANGELOG.md"],
22
+ "scripts": {
23
+ "build": "node scripts/build.mjs",
24
+ "prepublishOnly": "node scripts/build.mjs"
25
+ },
26
+ "publishConfig": { "access": "public" },
27
+ "peerDependencies": { "@mantine/core": "^8", "@mantine/hooks": "^8", "@tabler/icons-react": "^3", "react": "^19" },
28
+ "peerDependenciesMeta": {
29
+ "@mantine/core": { "optional": true },
30
+ "@mantine/hooks": { "optional": true },
31
+ "@tabler/icons-react": { "optional": true },
32
+ "react": { "optional": true }
33
+ },
34
+ "devDependencies": { "@types/node": "^20", "typescript": "^5" }
35
+ }