@netsapiens/horizon-sdk 0.1.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.
@@ -0,0 +1,861 @@
1
+ import * as React from 'react';
2
+ import { ReactNode } from 'react';
3
+ import loglevel from 'loglevel';
4
+
5
+ /**
6
+ * Public type contract for @netsapiens/horizon-sdk.
7
+ *
8
+ * This file defines the shape of the runtime context that Horizon passes to
9
+ * remote applications, plus the registration payloads remote apps emit back
10
+ * through the event bus.
11
+ *
12
+ * Horizon's host implementation imports these same types so the host and the
13
+ * SDK cannot drift out of sync.
14
+ */
15
+
16
+ /**
17
+ * User information passed to remote apps. Additional fields may be present.
18
+ */
19
+ interface HorizonUser {
20
+ displayName: string;
21
+ domain: string;
22
+ email?: string;
23
+ extension?: string;
24
+ scope?: string;
25
+ department?: string;
26
+ site?: string;
27
+ [key: string]: unknown;
28
+ }
29
+ /**
30
+ * Configuration for remote vendor authentication request
31
+ */
32
+ interface RemoteAuthRequest {
33
+ /** Unique identifier for the vendor system */
34
+ vendorId: string;
35
+ /** Callback URL where vendor will receive authcode webhook */
36
+ callbackUrl: string;
37
+ /** Optional scopes/permissions requested from vendor */
38
+ scopes?: string[];
39
+ /** Optional metadata to pass to vendor */
40
+ metadata?: Record<string, unknown>;
41
+ }
42
+ /**
43
+ * Response from remote authentication flow
44
+ */
45
+ interface RemoteAuthResponse {
46
+ /** Unique identifier for the vendor system */
47
+ vendorId: string;
48
+ /** Access token or JWT from vendor */
49
+ accessToken: string;
50
+ /** Token type (Bearer, etc.) */
51
+ tokenType?: string;
52
+ /** Token expiration timestamp (Unix seconds) */
53
+ expiresAt?: number;
54
+ /** Refresh token if provided by vendor */
55
+ refreshToken?: string;
56
+ /** Additional vendor-specific data */
57
+ metadata?: Record<string, unknown>;
58
+ }
59
+ /**
60
+ * Error response from remote authentication
61
+ */
62
+ interface RemoteAuthError {
63
+ vendorId: string;
64
+ error: string;
65
+ errorDescription?: string;
66
+ }
67
+ /**
68
+ * Options for remote auth request
69
+ */
70
+ interface RemoteAuthOptions {
71
+ /** Timeout in milliseconds (default: 60000) */
72
+ timeout?: number;
73
+ }
74
+ /**
75
+ * Authentication interface with remote vendor authentication support.
76
+ * Remote apps use this to authenticate with third-party systems.
77
+ */
78
+ interface HorizonAuth {
79
+ isAuthenticated: () => boolean;
80
+ /**
81
+ * Request authentication with a remote vendor system.
82
+ * Returns a promise that resolves when vendor auth completes.
83
+ *
84
+ * @param request - Configuration for the auth request
85
+ * @param options - Optional timeout and retry configuration
86
+ * @returns Promise<RemoteAuthResponse>
87
+ * @throws RemoteAuthError if authentication fails
88
+ *
89
+ * @example
90
+ * const response = await auth.requestRemoteAuth({
91
+ * vendorId: 'salesforce',
92
+ * callbackUrl: 'https://vendor.example.com/oauth/callback',
93
+ * scopes: ['read', 'write']
94
+ * });
95
+ * // Use response.accessToken for vendor API calls
96
+ */
97
+ requestRemoteAuth: (request: RemoteAuthRequest, options?: RemoteAuthOptions) => Promise<RemoteAuthResponse>;
98
+ /**
99
+ * Get stored token for a vendor (if previously authenticated)
100
+ */
101
+ getRemoteAuthToken: (vendorId: string) => RemoteAuthResponse | null;
102
+ /**
103
+ * Clear stored token for a vendor (logout)
104
+ */
105
+ clearRemoteAuthToken: (vendorId: string) => void;
106
+ }
107
+ /**
108
+ * Authenticated client for NetSapiens v2 API calls. Routed through the host's
109
+ * audited proxy — remote apps never see credentials directly.
110
+ */
111
+ interface HorizonApiClient {
112
+ get: <T = unknown>(path: string, params?: Record<string, unknown>) => Promise<T>;
113
+ post: <T = unknown>(path: string, data: unknown) => Promise<T>;
114
+ put: <T = unknown>(path: string, data: unknown) => Promise<T>;
115
+ delete: <T = unknown>(path: string) => Promise<T>;
116
+ getBaseUrl: () => string;
117
+ }
118
+ /**
119
+ * Pub/sub event bus used for everything that crosses the host/remote boundary:
120
+ * route registration, dynamic extension registration, theme changes, call
121
+ * events, and app-defined custom events.
122
+ */
123
+ interface HorizonEventBus {
124
+ emit: (event: string, data?: unknown) => void;
125
+ on: (event: string, handler: (data: unknown) => void) => void;
126
+ off: (event: string, handler: (data: unknown) => void) => void;
127
+ }
128
+ /**
129
+ * Theme tokens for advanced styling. Remote apps usually consume these
130
+ * indirectly through `ui.styles`.
131
+ */
132
+ interface ThemeTokens {
133
+ colors: Record<string, unknown>;
134
+ spacing: Record<string, string>;
135
+ typography: Record<string, unknown>;
136
+ borderRadius: Record<string, string>;
137
+ shadows: Record<string, string>;
138
+ }
139
+ /**
140
+ * UI templates and primitives provided by the host. Remote apps render these
141
+ * components instead of bringing their own MUI/styling stack — that keeps the
142
+ * remote bundle small and ensures visual consistency with Horizon.
143
+ */
144
+ interface HorizonUITemplates {
145
+ PageTemplate: React.ComponentType<unknown>;
146
+ PageTemplateWithExtensions: React.ComponentType<unknown>;
147
+ FormTemplate: React.ComponentType<unknown>;
148
+ SideTrayTemplate: React.ComponentType<unknown>;
149
+ DatagridTemplate: React.ComponentType<unknown>;
150
+ Icon: React.ComponentType<{
151
+ name: string;
152
+ size?: number | string;
153
+ color?: string;
154
+ }>;
155
+ SideTrayComponents: {
156
+ Section: React.ComponentType<{
157
+ title?: string;
158
+ children: React.ReactNode;
159
+ }>;
160
+ Field: React.ComponentType<{
161
+ label: string;
162
+ value?: React.ReactNode;
163
+ }>;
164
+ Input: React.ComponentType<{
165
+ label?: string;
166
+ placeholder?: string;
167
+ value?: string;
168
+ onChange?: (value: string) => void;
169
+ helperText?: string;
170
+ error?: boolean;
171
+ required?: boolean;
172
+ disabled?: boolean;
173
+ type?: string;
174
+ multiline?: boolean;
175
+ rows?: number;
176
+ }>;
177
+ Button: React.ComponentType<unknown>;
178
+ UserCard: React.ComponentType<{
179
+ avatarUrl?: string;
180
+ name: string;
181
+ subtitle?: string;
182
+ trailing?: React.ReactNode;
183
+ }>;
184
+ Divider: React.ComponentType;
185
+ };
186
+ PageComponents: Record<string, React.ComponentType<unknown>>;
187
+ }
188
+ interface HorizonUI {
189
+ templates: HorizonUITemplates;
190
+ theme?: ThemeTokens;
191
+ /** Pre-built semantic style objects derived from the active theme */
192
+ styles?: Record<string, unknown>;
193
+ /** Individual MUI-backed components for direct use */
194
+ Button?: React.ComponentType<unknown>;
195
+ /**
196
+ * Pre-themed IconButton. Takes the icon name as a string prop — children
197
+ * are intentionally not supported. Renders an Iconify icon inside an MUI
198
+ * IconButton with the host's Aurora theming applied.
199
+ *
200
+ * ```tsx
201
+ * <IconButton icon="mdi:account" aria-label="Account" />
202
+ * ```
203
+ *
204
+ * Do NOT use the MUI children pattern (`<IconButton><Icon ... /></IconButton>`) —
205
+ * it will render an empty button at 0×0 because children are dropped.
206
+ */
207
+ IconButton?: React.ComponentType<{
208
+ icon: string;
209
+ iconSize?: number | string;
210
+ 'aria-label'?: string;
211
+ color?: 'default' | 'inherit' | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning';
212
+ disabled?: boolean;
213
+ edge?: 'start' | 'end' | false;
214
+ size?: 'small' | 'medium' | 'large';
215
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
216
+ sx?: Record<string, unknown>;
217
+ }>;
218
+ TextField?: React.ComponentType<unknown>;
219
+ Select?: React.ComponentType<unknown>;
220
+ Checkbox?: React.ComponentType<unknown>;
221
+ Radio?: React.ComponentType<unknown>;
222
+ RadioGroup?: React.ComponentType<unknown>;
223
+ Switch?: React.ComponentType<unknown>;
224
+ ToggleButton?: React.ComponentType<unknown>;
225
+ ToggleButtonGroup?: React.ComponentType<unknown>;
226
+ FormLabel?: React.ComponentType<unknown>;
227
+ Typography?: React.ComponentType<unknown>;
228
+ Chip?: React.ComponentType<unknown>;
229
+ Avatar?: React.ComponentType<unknown>;
230
+ Divider?: React.ComponentType<unknown>;
231
+ Tooltip?: React.ComponentType<unknown>;
232
+ Stack?: React.ComponentType<unknown>;
233
+ Paper?: React.ComponentType<unknown>;
234
+ /** General-purpose layout primitive with sx prop support. Use this instead of
235
+ * importing Box directly from @mui/material in a remote app. */
236
+ Box?: React.ComponentType<unknown>;
237
+ Alert?: React.ComponentType<unknown>;
238
+ Icon?: React.ComponentType<unknown>;
239
+ }
240
+ interface BreadcrumbItem {
241
+ label: string;
242
+ url?: string;
243
+ }
244
+ /**
245
+ * The object passed to a remote app's exported `App` component as props.
246
+ * Construct this in the host (see HorizonAppsLoader) — never construct it
247
+ * inside a remote app.
248
+ */
249
+ interface HorizonContext {
250
+ user: HorizonUser;
251
+ auth: HorizonAuth;
252
+ api: HorizonApiClient;
253
+ theme: 'light' | 'dark';
254
+ locale: string;
255
+ /**
256
+ * Host's i18next translation function. All host strings (common, telecom,
257
+ * admin, validation namespaces) are available immediately — no i18next
258
+ * dependency or install required in the remote app.
259
+ * Use via the `useLocale()` SDK hook rather than calling directly.
260
+ */
261
+ t?: (key: string, options?: Record<string, unknown>) => string;
262
+ navigate: (path: string) => void;
263
+ eventBus: HorizonEventBus;
264
+ ui: HorizonUI;
265
+ breadcrumbs?: BreadcrumbItem[];
266
+ }
267
+ /**
268
+ * Semantic placement for menu items.
269
+ * Allows positioning relative to existing items using anchor-based placement.
270
+ */
271
+ interface SemanticPlacement {
272
+ /** Place immediately after this anchor */
273
+ after?: string;
274
+ /** Place immediately before this anchor */
275
+ before?: string;
276
+ /** Force to start of menu */
277
+ first?: boolean;
278
+ /** Force to end of menu */
279
+ last?: boolean;
280
+ }
281
+ interface RouteConfig {
282
+ /** Unique route identifier (recommend prefixing with appId) */
283
+ id: string;
284
+ /** App identifier — set automatically by RemoteAppSDK */
285
+ appId: string;
286
+ /** Parent path to attach under (e.g., '/apps', '/home/settings') */
287
+ parentPath: string;
288
+ /** Route segment (e.g., 'my-feature') */
289
+ path: string;
290
+ /** Navigation label */
291
+ label: string;
292
+ /** Component rendered when this route is active */
293
+ component: React.ComponentType<unknown>;
294
+ /** Iconify icon name (e.g., 'mdi:cog') */
295
+ icon?: string;
296
+ /**
297
+ * Semantic placement for positioning in menus (e.g., { after: 'contacts' }).
298
+ * Supports fuzzy matching with 0.8 similarity threshold.
299
+ * If not specified or anchor not found, item is placed at end of menu.
300
+ */
301
+ placement?: SemanticPlacement;
302
+ }
303
+ /**
304
+ * Webpack Module Federation reference for `useRouteFromModule`.
305
+ */
306
+ interface RemoteModuleConfig {
307
+ scope: string;
308
+ module: string;
309
+ }
310
+ /**
311
+ * Generic extension zones available throughout Horizon. Pages may also define
312
+ * custom string zone IDs — `ExtensionZone` accepts both.
313
+ */
314
+ type ExtensionZoneId = 'page-header-actions' | 'page-header-secondary' | 'page-content-before' | 'page-content-after' | 'page-sidebar' | 'table-row-actions' | 'table-toolbar' | 'detail-panel-tabs' | 'detail-panel-actions' | 'inbound-call-content' | 'topbar-actions' | 'form-section-before' | 'form-section-after';
315
+ type ExtensionZone = ExtensionZoneId | (string & {});
316
+ /**
317
+ * Route pattern with wildcard (`*`) and named-parameter (`:param`) support.
318
+ * Example patterns: `/manage/call-logs`, `/manage/*\/call-logs`, `/manage/:domain/users`.
319
+ */
320
+ interface RoutePattern {
321
+ pattern: string;
322
+ /** Require an exact match (default: prefix match) */
323
+ exact?: boolean;
324
+ }
325
+ /**
326
+ * Context passed to extension components when they render.
327
+ */
328
+ interface ExtensionContext {
329
+ route: string;
330
+ params: Record<string, string>;
331
+ user: {
332
+ domain: string;
333
+ username: string;
334
+ scopes: string[];
335
+ };
336
+ pageContext?: unknown;
337
+ ui?: HorizonUI;
338
+ eventBus?: HorizonEventBus;
339
+ /** Current color scheme — reactive to host theme changes. */
340
+ theme: 'light' | 'dark';
341
+ /** Host's i18next translation function — all host strings available immediately. */
342
+ t?: (key: string, options?: Record<string, unknown>) => string;
343
+ }
344
+ /**
345
+ * Props passed to extension components.
346
+ */
347
+ interface ExtensionComponentProps {
348
+ context: ExtensionContext;
349
+ zone: ExtensionZone;
350
+ /** Provided by modal/dialog hosts to allow extensions to dismiss themselves */
351
+ close?: () => void;
352
+ }
353
+ /**
354
+ * Payload accepted by `sdk.registerDynamicExtension`. `appId` is filled in by
355
+ * the SDK from the value passed to `createRemoteAppSDK`.
356
+ */
357
+ interface DynamicExtensionConfig {
358
+ /** Unique extension ID (recommend prefixing with appId) */
359
+ id: string;
360
+ /** Target zone — generic or page-defined */
361
+ zone: ExtensionZone;
362
+ /** Routes this extension applies to */
363
+ routes: RoutePattern[];
364
+ /** Render order within a zone (higher = first) */
365
+ priority?: number;
366
+ /** Component rendered for each match */
367
+ component: React.ComponentType<ExtensionComponentProps>;
368
+ /** Permissions the user must have for this extension to render */
369
+ requiredPermissions?: string[];
370
+ /** Optional gate evaluated at render time */
371
+ condition?: (context: ExtensionContext) => boolean;
372
+ }
373
+ /**
374
+ * Dynamic table column definition. Mirrors a subset of MUI X DataGrid's column
375
+ * shape — kept as `Record<string, unknown>`-friendly so the SDK doesn't pull
376
+ * in MUI types.
377
+ */
378
+ interface DynamicColumnDefinition {
379
+ field: string;
380
+ headerName: string;
381
+ width?: number;
382
+ minWidth?: number;
383
+ maxWidth?: number;
384
+ flex?: number;
385
+ sortable?: boolean;
386
+ filterable?: boolean;
387
+ hideable?: boolean;
388
+ resizable?: boolean;
389
+ type?: 'string' | 'number' | 'date' | 'dateTime' | 'boolean';
390
+ align?: 'left' | 'center' | 'right';
391
+ headerAlign?: 'left' | 'center' | 'right';
392
+ renderCell?: (params: {
393
+ row: Record<string, unknown>;
394
+ value?: unknown;
395
+ }) => React.ReactNode;
396
+ valueGetter?: (value: unknown, row: Record<string, unknown>) => unknown;
397
+ valueFormatter?: (value: unknown) => string;
398
+ initiallyVisible?: boolean;
399
+ }
400
+ /**
401
+ * Payload accepted by `sdk.registerDynamicColumn`.
402
+ */
403
+ interface DynamicColumnConfig {
404
+ id: string;
405
+ zone: string;
406
+ routes: RoutePattern[];
407
+ column: DynamicColumnDefinition;
408
+ priority?: number;
409
+ condition?: (context: ExtensionContext) => boolean;
410
+ }
411
+
412
+ /**
413
+ * Remote App SDK.
414
+ *
415
+ * Wraps Horizon's event bus with a typed, app-scoped API for registering routes
416
+ * and dynamic extensions. Tracks every registration so `cleanup()` can tear
417
+ * everything down on unmount — essential when a remote app is reloaded or its
418
+ * webpack module is replaced during HMR.
419
+ */
420
+
421
+ declare class RemoteAppSDK {
422
+ private eventBus;
423
+ private appId;
424
+ private routes;
425
+ private dynamicExtensions;
426
+ private dynamicColumns;
427
+ constructor(eventBus: HorizonEventBus, appId: string);
428
+ registerRoute(config: Omit<RouteConfig, 'appId'>): Promise<void>;
429
+ unregisterRoute(routeId: string): void;
430
+ /**
431
+ * Convenience: load a component out of a federated module's webpack
432
+ * container and register it as a route in one step. Useful when the route
433
+ * component lives in a sibling exposed module rather than the entry App.
434
+ */
435
+ registerRouteFromModule(routeConfig: Omit<RouteConfig, 'appId' | 'component'>, moduleConfig: RemoteModuleConfig): Promise<void>;
436
+ registerDynamicExtension(config: Omit<DynamicExtensionConfig, 'appId'>): void;
437
+ unregisterDynamicExtension(extensionId: string): void;
438
+ registerDynamicColumn(config: Omit<DynamicColumnConfig, 'appId'>): void;
439
+ unregisterDynamicColumn(columnId: string): void;
440
+ /**
441
+ * Unregister everything this SDK instance has registered. Call from your
442
+ * remote app's unmount/cleanup path — `useRemoteApp` does this for you.
443
+ */
444
+ cleanup(): void;
445
+ getAppId(): string;
446
+ getRegisteredRoutes(): string[];
447
+ getRegisteredDynamicExtensions(): string[];
448
+ getRegisteredDynamicColumns(): string[];
449
+ }
450
+ /** Factory: equivalent to `new RemoteAppSDK(...)`. */
451
+ declare function createRemoteAppSDK(eventBus: HorizonEventBus, appId: string): RemoteAppSDK;
452
+
453
+ /**
454
+ * Wrap your registered page components with this provider so they receive a
455
+ * reactive HorizonContext without any extra event-listener boilerplate.
456
+ *
457
+ * The component memoization pattern in App.tsx captures `horizonContext` once
458
+ * (to keep stable component identity), but `eventBus` is a singleton — the
459
+ * Provider subscribes to it and pushes theme updates to all descendant pages.
460
+ *
461
+ * @example
462
+ * // In App.tsx, inside useMemo with empty deps:
463
+ * return (
464
+ * <HorizonContextProvider context={horizonContext}>
465
+ * <MyPage />
466
+ * </HorizonContextProvider>
467
+ * );
468
+ */
469
+ declare function HorizonContextProvider({ context, children, }: {
470
+ context: HorizonContext;
471
+ children: ReactNode;
472
+ }): React.FunctionComponentElement<React.ProviderProps<HorizonContext | null>>;
473
+ /**
474
+ * Read the live HorizonContext inside any page registered via the SDK.
475
+ * Returns a context that updates automatically when dark mode changes.
476
+ *
477
+ * Must be called inside a component rendered within `HorizonContextProvider`.
478
+ *
479
+ * @example
480
+ * export default function MyPage() {
481
+ * const { ui, theme, user, navigate } = useHorizonContext();
482
+ * const { PageTemplate, DatagridTemplate } = ui.templates;
483
+ * // `theme` is always 'light' | 'dark', reactive to system/user toggle
484
+ * }
485
+ */
486
+ declare function useHorizonContext(): HorizonContext;
487
+ /**
488
+ * Returns the current color scheme, reactive to host theme changes.
489
+ *
490
+ * Works in **both** contexts with the same import — no manual event wiring:
491
+ *
492
+ * - **Page components** (inside `HorizonContextProvider`): reads directly from
493
+ * the provider's React context. No subscription overhead.
494
+ * - **Standalone extension components** (rendered by the host via
495
+ * `registerDynamicExtension`): falls back to `eventBus` subscription.
496
+ * Pass `context.eventBus` as the argument.
497
+ *
498
+ * @example
499
+ * // In a page component (HorizonContextProvider ancestor required)
500
+ * export default function MyPage() {
501
+ * const { theme } = useTheme();
502
+ * return <div style={{ color: theme === 'dark' ? '#fff' : '#000' }}>...</div>;
503
+ * }
504
+ *
505
+ * @example
506
+ * // In a standalone extension component
507
+ * export default function MyButton({ context }: ExtensionComponentProps) {
508
+ * const { theme } = useTheme(context.eventBus);
509
+ * const { Button } = context.ui ?? {};
510
+ * return <Button>{theme === 'dark' ? '🌙' : '☀️'} Export</Button>;
511
+ * }
512
+ */
513
+ /**
514
+ * Returns the current color scheme, reactive to host theme changes.
515
+ *
516
+ * @param eventBus - Pass `context.eventBus` when called from a standalone
517
+ * extension component (outside a HorizonContextProvider).
518
+ * @param initialTheme - Pass `context.theme` when called from a standalone
519
+ * extension component so the hook initialises with the correct value on
520
+ * first render rather than defaulting to `'light'`.
521
+ *
522
+ * Inside a page component wrapped by `HorizonContextProvider`, both params
523
+ * can be omitted — the provider context is used automatically.
524
+ */
525
+ declare function useTheme(eventBus?: HorizonContext['eventBus'], initialTheme?: 'light' | 'dark'): {
526
+ theme: 'light' | 'dark';
527
+ };
528
+ /**
529
+ * Returns the host's translation function and current locale.
530
+ *
531
+ * Because `i18next` is shared as a singleton, this hook reads directly from
532
+ * the host's already-initialized i18next instance — all host translations
533
+ * (common, telecom, admin, etc.) are immediately available with no extra
534
+ * fetch or setup required.
535
+ *
536
+ * Reactivity is handled internally by react-i18next: the returned `t` and
537
+ * `locale` update automatically when the user switches language.
538
+ *
539
+ * @param ns - Optional namespace(s). Defaults to `'common'` (the host
540
+ * namespace that contains all standard UI strings). Pass your app's own
541
+ * registered namespace to access remote-app-specific keys.
542
+ *
543
+ * @example
544
+ * // In a page component — access any host string
545
+ * export default function MyPage() {
546
+ * const { t, locale } = useLocale();
547
+ * return <Button>{t('SAVE')}</Button>;
548
+ * }
549
+ *
550
+ * @example
551
+ * // In an extension component — same API
552
+ * export function MyButton({ context }: ExtensionComponentProps) {
553
+ * const { t } = useLocale();
554
+ * const { Button } = context.ui ?? {};
555
+ * return <Button>{t('EXPORT')}</Button>;
556
+ * }
557
+ */
558
+ /**
559
+ * Returns the host's `t` translation function and current locale string.
560
+ *
561
+ * The `t` function is the host's i18next instance — all 1,375+ host strings
562
+ * across the common, telecom, admin, and validation namespaces are immediately
563
+ * available. No i18next dependency, no package install, no init required in
564
+ * the remote app.
565
+ *
566
+ * Both `t` and `locale` update automatically when the user switches language.
567
+ *
568
+ * @example
569
+ * export default function MyPage() {
570
+ * const { t, locale } = useLocale();
571
+ * return <Button>{t('SAVE')}</Button>;
572
+ * }
573
+ *
574
+ * @example
575
+ * // In a standalone extension component
576
+ * export function MyButton({ context }: ExtensionComponentProps) {
577
+ * const { t } = useLocale();
578
+ * return <span>{t('EXPORT')}</span>;
579
+ * }
580
+ */
581
+ declare function useLocale(): {
582
+ t: HorizonContext['t'];
583
+ locale: string;
584
+ };
585
+ /**
586
+ * Get an SDK instance bound to your app, plus a flat view of the Horizon
587
+ * context. Returned `sdk.cleanup()` is called automatically on unmount.
588
+ *
589
+ * @example
590
+ * export default function MyApp(horizonContext: HorizonContext) {
591
+ * const { sdk, user } = useRemoteApp(horizonContext, 'my-app');
592
+ *
593
+ * useEffect(() => {
594
+ * sdk.registerDynamicExtension({
595
+ * id: 'my-app.button',
596
+ * zone: 'page-header-actions',
597
+ * routes: [{ pattern: '/manage/*\/users' }],
598
+ * component: MyButton,
599
+ * });
600
+ * }, [sdk]);
601
+ *
602
+ * return <div>Hello {user.displayName}</div>;
603
+ * }
604
+ */
605
+ declare function useRemoteApp(horizonContext: HorizonContext, appId: string): {
606
+ user: HorizonUser;
607
+ auth: HorizonAuth;
608
+ api: HorizonApiClient;
609
+ theme: "light" | "dark";
610
+ locale: string;
611
+ t?: (key: string, options?: Record<string, unknown>) => string;
612
+ navigate: (path: string) => void;
613
+ eventBus: HorizonEventBus;
614
+ ui: HorizonUI;
615
+ breadcrumbs?: BreadcrumbItem[];
616
+ sdk: RemoteAppSDK;
617
+ };
618
+ /**
619
+ * Register a route for the lifetime of the calling component.
620
+ */
621
+ declare function useRoute(eventBus: HorizonContext['eventBus'], appId: string, config: Omit<RouteConfig, 'appId'>): RemoteAppSDK;
622
+ /**
623
+ * Register a route by pulling its component out of a federated module's
624
+ * webpack container.
625
+ */
626
+ declare function useRouteFromModule(eventBus: HorizonContext['eventBus'], appId: string, routeConfig: Omit<RouteConfig, 'appId' | 'component'>, moduleConfig: RemoteModuleConfig): {
627
+ loading: boolean;
628
+ error: Error | null;
629
+ sdk: RemoteAppSDK;
630
+ };
631
+ /**
632
+ * Register a dynamic extension (pattern-based UI injection) for the lifetime
633
+ * of the calling component.
634
+ */
635
+ declare function useDynamicExtension(eventBus: HorizonContext['eventBus'], appId: string, config: Omit<DynamicExtensionConfig, 'appId'>): RemoteAppSDK;
636
+ /**
637
+ * Register a dynamic table column for the lifetime of the calling component.
638
+ */
639
+ declare function useDynamicColumn(eventBus: HorizonContext['eventBus'], appId: string, config: Omit<DynamicColumnConfig, 'appId'>): RemoteAppSDK;
640
+
641
+ /**
642
+ * Federation Error
643
+ * Structured error class with error codes for better error handling
644
+ */
645
+ type HorizonSDKErrorCode = 'PERMISSION_DENIED' | 'RATE_LIMIT_EXCEEDED' | 'INVALID_MESSAGE' | 'SIGNATURE_VERIFICATION_FAILED' | 'API_ERROR' | 'NETWORK_ERROR' | 'INVALID_EXTENSION_POINT' | 'INVALID_CONFIGURATION' | 'APP_NOT_FOUND' | 'MODULE_LOAD_FAILED' | 'INITIALIZATION_FAILED' | 'UNKNOWN_ERROR';
646
+ interface HorizonSDKErrorOptions {
647
+ code: HorizonSDKErrorCode;
648
+ message: string;
649
+ details?: Record<string, unknown>;
650
+ cause?: Error;
651
+ statusCode?: number;
652
+ }
653
+ /**
654
+ * HorizonSDKError class
655
+ * Provides structured errors with error codes and additional context
656
+ */
657
+ declare class HorizonSDKError extends Error {
658
+ readonly code: HorizonSDKErrorCode;
659
+ readonly details?: Record<string, unknown>;
660
+ readonly cause?: Error;
661
+ readonly statusCode?: number;
662
+ readonly timestamp: string;
663
+ constructor(options: HorizonSDKErrorOptions);
664
+ /**
665
+ * Check if error is a HorizonSDKError
666
+ */
667
+ static isHorizonSDKError(error: unknown): error is HorizonSDKError;
668
+ /**
669
+ * Get user-friendly error message
670
+ */
671
+ getUserMessage(): string;
672
+ /**
673
+ * Serialize error to JSON
674
+ */
675
+ toJSON(): object;
676
+ /**
677
+ * Get formatted error message for logging
678
+ */
679
+ toLogString(): string;
680
+ }
681
+ /**
682
+ * Helper function to create HorizonSDKError instances
683
+ */
684
+ declare function createHorizonSDKError(code: HorizonSDKErrorCode, message: string, details?: Record<string, unknown>, cause?: Error): HorizonSDKError;
685
+ /**
686
+ * Permission Denied Error
687
+ */
688
+ declare function permissionDeniedError(resource: string, details?: Record<string, unknown>): HorizonSDKError;
689
+ /**
690
+ * Rate Limit Exceeded Error
691
+ */
692
+ declare function rateLimitError(resetTime: number, details?: Record<string, unknown>): HorizonSDKError;
693
+ /**
694
+ * API Error
695
+ */
696
+ declare function apiError(message: string, statusCode: number, details?: Record<string, unknown>, cause?: Error): HorizonSDKError;
697
+ /**
698
+ * Invalid Extension Point Error
699
+ */
700
+ declare function invalidExtensionPointError(pointId: string, validPoints: string[]): HorizonSDKError;
701
+ /**
702
+ * Signature Verification Failed Error
703
+ */
704
+ declare function signatureVerificationError(reason: string): HorizonSDKError;
705
+ /**
706
+ * Module Load Failed Error
707
+ */
708
+ declare function moduleLoadError(appId: string, url: string, cause?: Error): HorizonSDKError;
709
+
710
+ /**
711
+ * Logging utility using loglevel for Horizon SDK
712
+ * Consistent with netsapiens-monorepo logging pattern
713
+ *
714
+ * Usage:
715
+ * import { createLogger } from '../utils/logger';
716
+ * const log = createLogger('ModuleLoader');
717
+ * log.info('Loading module...');
718
+ * log.error('Failed to load:', error);
719
+ */
720
+
721
+ /**
722
+ * Create a namespaced logger for a specific module
723
+ * Follows the monorepo pattern from @netsapiens/ns-sdk
724
+ *
725
+ * @example
726
+ * const log = createLogger('ModuleLoader');
727
+ * log.debug('Starting load...');
728
+ * log.info('Module loaded successfully');
729
+ * log.warn('Slow load detected');
730
+ * log.error('Load failed:', error);
731
+ */
732
+ declare const createLogger: (namespace: string) => loglevel.Logger;
733
+ /**
734
+ * Default logger instance for the SDK
735
+ */
736
+ declare const logger: loglevel.Logger;
737
+ /**
738
+ * Set log level at runtime
739
+ * Useful for debugging
740
+ *
741
+ * @example
742
+ * import { setLogLevel } from './utils/logger';
743
+ * setLogLevel('DEBUG'); // Enable all logs
744
+ * setLogLevel('WARN'); // Only warnings and errors
745
+ */
746
+ declare const setLogLevel: (level: "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR" | "SILENT") => void;
747
+ /**
748
+ * Get current log level
749
+ */
750
+ declare const getLogLevel: () => 0 | 1 | 2 | 3 | 4 | 5;
751
+ declare global {
752
+ interface Window {
753
+ __horizonSDKLogger__: typeof logger;
754
+ __setHorizonSDKLogLevel__: typeof setLogLevel;
755
+ }
756
+ }
757
+
758
+ /**
759
+ * Stable Anchor IDs for Extension Placement
760
+ *
761
+ * These anchor IDs are guaranteed to be stable and can be used by extensions
762
+ * to specify their menu placement using semantic anchors.
763
+ *
764
+ * @example
765
+ * ```typescript
766
+ * import { MANAGE_ANCHORS } from '@netsapiens/horizon-sdk';
767
+ *
768
+ * sdk.registerRoute({
769
+ * id: 'my-crm',
770
+ * parentPath: '/apps',
771
+ * path: 'crm',
772
+ * label: 'CRM',
773
+ * placement: { after: MANAGE_ANCHORS.contacts }
774
+ * });
775
+ * ```
776
+ */
777
+ /**
778
+ * Manage Menu Anchors
779
+ * Available in both domain and no-domain contexts
780
+ */
781
+ declare const MANAGE_ANCHORS: {
782
+ readonly dashboard: "manage-dashboard";
783
+ readonly users: "manage-users";
784
+ readonly contacts: "manage-contacts";
785
+ readonly devices: "manage-devices";
786
+ readonly phoneNumbers: "manage-phone-numbers";
787
+ readonly callLogs: "manage-call-logs";
788
+ readonly voicemail: "manage-voicemail";
789
+ readonly fax: "manage-fax";
790
+ readonly settings: "manage-settings";
791
+ };
792
+ /**
793
+ * Platform Menu Anchors
794
+ * Available to Admin, Super User, and Reseller scopes
795
+ */
796
+ declare const PLATFORM_ANCHORS: {
797
+ readonly dashboard: "platform-dashboard";
798
+ readonly codeManagement: "platform-code-management";
799
+ readonly configManagement: "platform-config-management";
800
+ readonly sdkManagement: "platform-ui-sdk";
801
+ readonly branding: "platform-branding";
802
+ readonly recording: "platform-recording";
803
+ readonly logsAndDiagnostics: "platform-logs-and-diagnostics";
804
+ };
805
+ /**
806
+ * Apps Menu Anchors
807
+ * Extension apps typically register here
808
+ */
809
+ declare const APPS_ANCHORS: {
810
+ readonly home: "apps-home";
811
+ };
812
+ /**
813
+ * My Account Menu Anchors
814
+ * User-specific settings and preferences
815
+ */
816
+ declare const MY_ACCOUNT_ANCHORS: {
817
+ readonly profile: "my-account-profile";
818
+ readonly preferences: "my-account-preferences";
819
+ readonly security: "my-account-security";
820
+ };
821
+ /**
822
+ * All anchor constants in one object for convenience
823
+ */
824
+ declare const ANCHORS: {
825
+ readonly manage: {
826
+ readonly dashboard: "manage-dashboard";
827
+ readonly users: "manage-users";
828
+ readonly contacts: "manage-contacts";
829
+ readonly devices: "manage-devices";
830
+ readonly phoneNumbers: "manage-phone-numbers";
831
+ readonly callLogs: "manage-call-logs";
832
+ readonly voicemail: "manage-voicemail";
833
+ readonly fax: "manage-fax";
834
+ readonly settings: "manage-settings";
835
+ };
836
+ readonly platform: {
837
+ readonly dashboard: "platform-dashboard";
838
+ readonly codeManagement: "platform-code-management";
839
+ readonly configManagement: "platform-config-management";
840
+ readonly sdkManagement: "platform-ui-sdk";
841
+ readonly branding: "platform-branding";
842
+ readonly recording: "platform-recording";
843
+ readonly logsAndDiagnostics: "platform-logs-and-diagnostics";
844
+ };
845
+ readonly apps: {
846
+ readonly home: "apps-home";
847
+ };
848
+ readonly myAccount: {
849
+ readonly profile: "my-account-profile";
850
+ readonly preferences: "my-account-preferences";
851
+ readonly security: "my-account-security";
852
+ };
853
+ };
854
+ /**
855
+ * Type-safe anchor ID union
856
+ */
857
+ type AnchorId = (typeof MANAGE_ANCHORS)[keyof typeof MANAGE_ANCHORS] | (typeof PLATFORM_ANCHORS)[keyof typeof PLATFORM_ANCHORS] | (typeof APPS_ANCHORS)[keyof typeof APPS_ANCHORS] | (typeof MY_ACCOUNT_ANCHORS)[keyof typeof MY_ACCOUNT_ANCHORS];
858
+
859
+ declare const VERSION = "1.0.0";
860
+
861
+ export { ANCHORS, APPS_ANCHORS, type AnchorId, type BreadcrumbItem, type DynamicColumnConfig, type DynamicColumnDefinition, type DynamicExtensionConfig, type ExtensionComponentProps, type ExtensionContext, type ExtensionZone, type ExtensionZoneId, type HorizonApiClient, type HorizonAuth, type HorizonContext, HorizonContextProvider, type HorizonEventBus, HorizonSDKError, type HorizonSDKErrorCode, type HorizonSDKErrorOptions, type HorizonUI, type HorizonUITemplates, type HorizonUser, MANAGE_ANCHORS, MY_ACCOUNT_ANCHORS, PLATFORM_ANCHORS, RemoteAppSDK, type RemoteAuthError, type RemoteAuthOptions, type RemoteAuthRequest, type RemoteAuthResponse, type RemoteModuleConfig, type RouteConfig, type RoutePattern, type SemanticPlacement, type ThemeTokens, VERSION, apiError, createHorizonSDKError, createLogger, createRemoteAppSDK, getLogLevel, invalidExtensionPointError, moduleLoadError, permissionDeniedError, rateLimitError, setLogLevel, signatureVerificationError, useDynamicColumn, useDynamicExtension, useHorizonContext, useLocale, useRemoteApp, useRoute, useRouteFromModule, useTheme };