@netsapiens/horizon-sdk 0.1.2 → 0.1.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/README.md +263 -84
- package/dist/index.cjs +77 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +199 -25
- package/dist/index.d.ts +199 -25
- package/dist/index.js +77 -16
- package/dist/index.js.map +1 -1
- package/package.json +10 -3
package/dist/index.cjs
CHANGED
|
@@ -9,7 +9,9 @@ var loglevel__default = /*#__PURE__*/_interopDefault(loglevel);
|
|
|
9
9
|
|
|
10
10
|
// src/utils/logger.ts
|
|
11
11
|
var isDevelopment = process.env.NODE_ENV === "development";
|
|
12
|
-
loglevel__default.default.setDefaultLevel(
|
|
12
|
+
loglevel__default.default.setDefaultLevel(
|
|
13
|
+
isDevelopment ? loglevel__default.default.levels.DEBUG : loglevel__default.default.levels.WARN
|
|
14
|
+
);
|
|
13
15
|
var createLogger = (namespace) => {
|
|
14
16
|
const moduleLogger = loglevel__default.default.getLogger(`HorizonSDK:${namespace}`);
|
|
15
17
|
if (isDevelopment) {
|
|
@@ -47,6 +49,7 @@ var RemoteAppSDK = class {
|
|
|
47
49
|
routes = /* @__PURE__ */ new Set();
|
|
48
50
|
dynamicExtensions = /* @__PURE__ */ new Set();
|
|
49
51
|
dynamicColumns = /* @__PURE__ */ new Set();
|
|
52
|
+
callEventsSubscribed = false;
|
|
50
53
|
constructor(eventBus, appId) {
|
|
51
54
|
this.eventBus = eventBus;
|
|
52
55
|
this.appId = appId;
|
|
@@ -59,7 +62,9 @@ var RemoteAppSDK = class {
|
|
|
59
62
|
const route = { ...config, appId: this.appId };
|
|
60
63
|
this.eventBus.emit("route:register", route);
|
|
61
64
|
this.routes.add(route.id);
|
|
62
|
-
log.info(
|
|
65
|
+
log.info(
|
|
66
|
+
`[${this.appId}] Route registered: ${route.id} at ${route.parentPath}/${route.path}`
|
|
67
|
+
);
|
|
63
68
|
}
|
|
64
69
|
unregisterRoute(routeId) {
|
|
65
70
|
this.eventBus.emit("route:unregister", { id: routeId });
|
|
@@ -104,7 +109,9 @@ var RemoteAppSDK = class {
|
|
|
104
109
|
const column = { ...config, appId: this.appId };
|
|
105
110
|
this.eventBus.emit("dynamic-column:register", column);
|
|
106
111
|
this.dynamicColumns.add(column.id);
|
|
107
|
-
log.info(
|
|
112
|
+
log.info(
|
|
113
|
+
`[${this.appId}] Dynamic column registered: ${column.id} \u2192 zone ${column.zone}`
|
|
114
|
+
);
|
|
108
115
|
}
|
|
109
116
|
unregisterDynamicColumn(columnId) {
|
|
110
117
|
this.eventBus.emit("dynamic-column:unregister", { id: columnId });
|
|
@@ -112,6 +119,38 @@ var RemoteAppSDK = class {
|
|
|
112
119
|
log.info(`[${this.appId}] Dynamic column unregistered: ${columnId}`);
|
|
113
120
|
}
|
|
114
121
|
// -------------------------------------------------------------------------
|
|
122
|
+
// Call events (capability-gated, app-scoped)
|
|
123
|
+
// -------------------------------------------------------------------------
|
|
124
|
+
/**
|
|
125
|
+
* Subscribe to live SIP call events. Routes through the host's
|
|
126
|
+
* `CallEventsManager`, which enforces the `call-events:listen` capability and
|
|
127
|
+
* records the subscription against this app — so the platform knows which
|
|
128
|
+
* apps consume call events and can disable the capability platform-wide.
|
|
129
|
+
*
|
|
130
|
+
* Prefer this over listening to `eventBus.on('call-event')` directly: the raw
|
|
131
|
+
* bus is neither gated nor attributed to your app.
|
|
132
|
+
*
|
|
133
|
+
* @returns an unsubscribe function (also torn down by `cleanup()`).
|
|
134
|
+
*/
|
|
135
|
+
subscribeToCallEvents(eventTypes, callback) {
|
|
136
|
+
this.eventBus.emit("call-events:subscribe", {
|
|
137
|
+
appId: this.appId,
|
|
138
|
+
eventTypes,
|
|
139
|
+
callback
|
|
140
|
+
});
|
|
141
|
+
this.callEventsSubscribed = true;
|
|
142
|
+
log.info(
|
|
143
|
+
`[${this.appId}] Subscribed to call events: ${eventTypes.join(", ")}`
|
|
144
|
+
);
|
|
145
|
+
return () => this.unsubscribeFromCallEvents();
|
|
146
|
+
}
|
|
147
|
+
unsubscribeFromCallEvents() {
|
|
148
|
+
if (!this.callEventsSubscribed) return;
|
|
149
|
+
this.eventBus.emit("call-events:unsubscribe", { appId: this.appId });
|
|
150
|
+
this.callEventsSubscribed = false;
|
|
151
|
+
log.info(`[${this.appId}] Unsubscribed from call events`);
|
|
152
|
+
}
|
|
153
|
+
// -------------------------------------------------------------------------
|
|
115
154
|
// Lifecycle
|
|
116
155
|
// -------------------------------------------------------------------------
|
|
117
156
|
/**
|
|
@@ -126,7 +165,10 @@ var RemoteAppSDK = class {
|
|
|
126
165
|
this.dynamicExtensions.forEach(
|
|
127
166
|
(id) => this.eventBus.emit("dynamic-extension:unregister", { id })
|
|
128
167
|
);
|
|
129
|
-
this.dynamicColumns.forEach(
|
|
168
|
+
this.dynamicColumns.forEach(
|
|
169
|
+
(id) => this.eventBus.emit("dynamic-column:unregister", { id })
|
|
170
|
+
);
|
|
171
|
+
this.unsubscribeFromCallEvents();
|
|
130
172
|
this.routes.clear();
|
|
131
173
|
this.dynamicExtensions.clear();
|
|
132
174
|
this.dynamicColumns.clear();
|
|
@@ -152,13 +194,16 @@ function HorizonContextProvider({
|
|
|
152
194
|
context,
|
|
153
195
|
children
|
|
154
196
|
}) {
|
|
155
|
-
const [theme, setTheme] = react.useState(
|
|
197
|
+
const [theme, setTheme] = react.useState(
|
|
198
|
+
context.theme ?? "light"
|
|
199
|
+
);
|
|
156
200
|
const [locale, setLocale] = react.useState(context.locale ?? "en-US");
|
|
157
201
|
react.useEffect(() => {
|
|
158
202
|
if (!context.eventBus) return;
|
|
159
203
|
const themeHandler = (data) => {
|
|
160
204
|
const payload = data;
|
|
161
|
-
if (payload?.theme === "light" || payload?.theme === "dark")
|
|
205
|
+
if (payload?.theme === "light" || payload?.theme === "dark")
|
|
206
|
+
setTheme(payload.theme);
|
|
162
207
|
};
|
|
163
208
|
const localeHandler = (data) => {
|
|
164
209
|
const payload = data;
|
|
@@ -177,7 +222,11 @@ function HorizonContextProvider({
|
|
|
177
222
|
// theme/locale live. context spread gives access to t, user, api, navigate, etc.
|
|
178
223
|
[theme, locale]
|
|
179
224
|
);
|
|
180
|
-
return react.createElement(
|
|
225
|
+
return react.createElement(
|
|
226
|
+
HorizonContextReact.Provider,
|
|
227
|
+
{ value: liveContext },
|
|
228
|
+
children
|
|
229
|
+
);
|
|
181
230
|
}
|
|
182
231
|
function useHorizonContext() {
|
|
183
232
|
const ctx = react.useContext(HorizonContextReact);
|
|
@@ -222,13 +271,13 @@ function useRemoteApp(horizonContext, appId) {
|
|
|
222
271
|
sdkRef.current = createRemoteAppSDK(horizonContext.eventBus, appId);
|
|
223
272
|
}
|
|
224
273
|
const sdk = sdkRef.current;
|
|
225
|
-
react.useEffect(() => {
|
|
226
|
-
return () => sdk.cleanup();
|
|
227
|
-
}, [sdk]);
|
|
228
274
|
return { sdk, ...horizonContext };
|
|
229
275
|
}
|
|
230
276
|
function useRoute(eventBus, appId, config) {
|
|
231
|
-
const sdk = react.useMemo(
|
|
277
|
+
const sdk = react.useMemo(
|
|
278
|
+
() => createRemoteAppSDK(eventBus, appId),
|
|
279
|
+
[eventBus, appId]
|
|
280
|
+
);
|
|
232
281
|
react.useEffect(() => {
|
|
233
282
|
void sdk.registerRoute(config);
|
|
234
283
|
return () => sdk.unregisterRoute(config.id);
|
|
@@ -236,7 +285,10 @@ function useRoute(eventBus, appId, config) {
|
|
|
236
285
|
return sdk;
|
|
237
286
|
}
|
|
238
287
|
function useRouteFromModule(eventBus, appId, routeConfig, moduleConfig) {
|
|
239
|
-
const sdk = react.useMemo(
|
|
288
|
+
const sdk = react.useMemo(
|
|
289
|
+
() => createRemoteAppSDK(eventBus, appId),
|
|
290
|
+
[eventBus, appId]
|
|
291
|
+
);
|
|
240
292
|
const [loading, setLoading] = react.useState(true);
|
|
241
293
|
const [error, setError] = react.useState(null);
|
|
242
294
|
react.useEffect(() => {
|
|
@@ -257,15 +309,24 @@ function useRouteFromModule(eventBus, appId, routeConfig, moduleConfig) {
|
|
|
257
309
|
return { loading, error, sdk };
|
|
258
310
|
}
|
|
259
311
|
function useDynamicExtension(eventBus, appId, config) {
|
|
260
|
-
const sdk = react.useMemo(
|
|
312
|
+
const sdk = react.useMemo(
|
|
313
|
+
() => createRemoteAppSDK(eventBus, appId),
|
|
314
|
+
[eventBus, appId]
|
|
315
|
+
);
|
|
261
316
|
react.useEffect(() => {
|
|
262
317
|
sdk.registerDynamicExtension(config);
|
|
263
318
|
return () => sdk.unregisterDynamicExtension(config.id);
|
|
264
319
|
}, [sdk, config]);
|
|
265
320
|
return sdk;
|
|
266
321
|
}
|
|
322
|
+
function usePageContext(context) {
|
|
323
|
+
return context.pageContext;
|
|
324
|
+
}
|
|
267
325
|
function useDynamicColumn(eventBus, appId, config) {
|
|
268
|
-
const sdk = react.useMemo(
|
|
326
|
+
const sdk = react.useMemo(
|
|
327
|
+
() => createRemoteAppSDK(eventBus, appId),
|
|
328
|
+
[eventBus, appId]
|
|
329
|
+
);
|
|
269
330
|
react.useEffect(() => {
|
|
270
331
|
sdk.registerDynamicColumn(config);
|
|
271
332
|
return () => sdk.unregisterDynamicColumn(config.id);
|
|
@@ -451,7 +512,7 @@ var ANCHORS = {
|
|
|
451
512
|
|
|
452
513
|
// package.json
|
|
453
514
|
var package_default = {
|
|
454
|
-
version: "0.1.
|
|
515
|
+
version: "0.1.4"};
|
|
455
516
|
|
|
456
517
|
// src/index.ts
|
|
457
518
|
var VERSION = package_default.version;
|
|
@@ -482,6 +543,7 @@ exports.useDynamicColumn = useDynamicColumn;
|
|
|
482
543
|
exports.useDynamicExtension = useDynamicExtension;
|
|
483
544
|
exports.useHorizonContext = useHorizonContext;
|
|
484
545
|
exports.useLocale = useLocale;
|
|
546
|
+
exports.usePageContext = usePageContext;
|
|
485
547
|
exports.useRemoteApp = useRemoteApp;
|
|
486
548
|
exports.useRoute = useRoute;
|
|
487
549
|
exports.useRouteFromModule = useRouteFromModule;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/logger.ts","../src/RemoteAppSDK.ts","../src/hooks.tsx","../src/errors/HorizonSDKError.ts","../src/anchors.ts","../package.json","../src/index.ts"],"names":["loglevel","createContext","useState","useEffect","useMemo","createElement","useContext","useRef","log"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Logging utility using loglevel for Horizon SDK\n * Consistent with netsapiens-monorepo logging pattern\n *\n * Usage:\n * import { createLogger } from '../utils/logger';\n * const log = createLogger('ModuleLoader');\n * log.info('Loading module...');\n * log.error('Failed to load:', error);\n */\nimport loglevel from 'loglevel';\n\n// Configure default log level based on environment\nconst isDevelopment = process.env.NODE_ENV === 'development';\n\n// Set default level\nloglevel.setDefaultLevel(isDevelopment ? loglevel.levels.DEBUG : loglevel.levels.WARN);\n\n/**\n * Create a namespaced logger for a specific module\n * Follows the monorepo pattern from @netsapiens/ns-sdk\n *\n * @example\n * const log = createLogger('ModuleLoader');\n * log.debug('Starting load...');\n * log.info('Module loaded successfully');\n * log.warn('Slow load detected');\n * log.error('Load failed:', error);\n */\nexport const createLogger = (namespace: string) => {\n const moduleLogger = loglevel.getLogger(`HorizonSDK:${namespace}`);\n\n if (isDevelopment) {\n moduleLogger.setLevel(loglevel.levels.DEBUG);\n } else {\n moduleLogger.setLevel(loglevel.levels.WARN);\n }\n\n return moduleLogger;\n};\n\n/**\n * Default logger instance for the SDK\n */\nexport const logger = loglevel.getLogger('horizon-sdk');\n\n/**\n * Set log level at runtime\n * Useful for debugging\n *\n * @example\n * import { setLogLevel } from './utils/logger';\n * setLogLevel('DEBUG'); // Enable all logs\n * setLogLevel('WARN'); // Only warnings and errors\n */\nexport const setLogLevel = (level: 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'SILENT') => {\n loglevel.setLevel(level);\n};\n\n/**\n * Get current log level\n */\nexport const getLogLevel = () => {\n return loglevel.getLevel();\n};\n\n// Extend Window interface for debug tools\ndeclare global {\n interface Window {\n __horizonSDKLogger__: typeof logger;\n __setHorizonSDKLogLevel__: typeof setLogLevel;\n }\n}\n\n// Make logger available in browser console for debugging\nif (typeof window !== 'undefined') {\n window.__horizonSDKLogger__ = logger;\n window.__setHorizonSDKLogLevel__ = setLogLevel;\n\n if (isDevelopment) {\n logger.info(\n 'Horizon SDK logger initialized. Use window.__setHorizonSDKLogLevel__(\"DEBUG\") to adjust log level.',\n );\n }\n}\n","/**\n * Remote App SDK.\n *\n * Wraps Horizon's event bus with a typed, app-scoped API for registering routes\n * and dynamic extensions. Tracks every registration so `cleanup()` can tear\n * everything down on unmount — essential when a remote app is reloaded or its\n * webpack module is replaced during HMR.\n */\nimport type {\n DynamicColumnConfig,\n DynamicExtensionConfig,\n HorizonEventBus,\n RemoteModuleConfig,\n RouteConfig,\n} from './types';\nimport { createLogger } from './utils/logger';\n\nconst log = createLogger('RemoteAppSDK');\n\nexport class RemoteAppSDK {\n private eventBus: HorizonEventBus;\n private appId: string;\n\n // Registrations are tracked separately so cleanup() emits the right\n // unregister event for each. (A single set keyed by id-only would let dynamic\n // registrations leak — they're unregistered on a different channel.)\n private routes = new Set<string>();\n private dynamicExtensions = new Set<string>();\n private dynamicColumns = new Set<string>();\n\n constructor(eventBus: HorizonEventBus, appId: string) {\n this.eventBus = eventBus;\n this.appId = appId;\n log.debug(`Initialized for app: ${appId}`);\n }\n\n // -------------------------------------------------------------------------\n // Routes\n // -------------------------------------------------------------------------\n\n async registerRoute(config: Omit<RouteConfig, 'appId'>): Promise<void> {\n const route: RouteConfig = { ...config, appId: this.appId };\n this.eventBus.emit('route:register', route);\n this.routes.add(route.id);\n log.info(`[${this.appId}] Route registered: ${route.id} at ${route.parentPath}/${route.path}`);\n }\n\n unregisterRoute(routeId: string): void {\n this.eventBus.emit('route:unregister', { id: routeId });\n this.routes.delete(routeId);\n log.info(`[${this.appId}] Route unregistered: ${routeId}`);\n }\n\n /**\n * Convenience: load a component out of a federated module's webpack\n * container and register it as a route in one step. Useful when the route\n * component lives in a sibling exposed module rather than the entry App.\n */\n async registerRouteFromModule(\n routeConfig: Omit<RouteConfig, 'appId' | 'component'>,\n moduleConfig: RemoteModuleConfig,\n ): Promise<void> {\n const container = (window as unknown as Record<string, ModuleFederationContainer>)[\n moduleConfig.scope\n ];\n if (!container) {\n throw new Error(`Remote container not found: ${moduleConfig.scope}`);\n }\n\n const factory = await container.get(moduleConfig.module);\n const moduleExports = factory();\n const component = (moduleExports.default ?? moduleExports) as React.ComponentType<unknown>;\n\n await this.registerRoute({ ...routeConfig, component });\n }\n\n // -------------------------------------------------------------------------\n // Dynamic extensions (pattern-based UI injection)\n // -------------------------------------------------------------------------\n\n registerDynamicExtension(config: Omit<DynamicExtensionConfig, 'appId'>): void {\n const extension = { ...config, appId: this.appId };\n this.eventBus.emit('dynamic-extension:register', extension);\n this.dynamicExtensions.add(extension.id);\n log.info(\n `[${this.appId}] Dynamic extension registered: ${extension.id} → zone ${extension.zone}`,\n );\n }\n\n unregisterDynamicExtension(extensionId: string): void {\n this.eventBus.emit('dynamic-extension:unregister', { id: extensionId });\n this.dynamicExtensions.delete(extensionId);\n log.info(`[${this.appId}] Dynamic extension unregistered: ${extensionId}`);\n }\n\n // -------------------------------------------------------------------------\n // Dynamic columns (table column injection)\n // -------------------------------------------------------------------------\n\n registerDynamicColumn(config: Omit<DynamicColumnConfig, 'appId'>): void {\n const column = { ...config, appId: this.appId };\n this.eventBus.emit('dynamic-column:register', column);\n this.dynamicColumns.add(column.id);\n log.info(`[${this.appId}] Dynamic column registered: ${column.id} → zone ${column.zone}`);\n }\n\n unregisterDynamicColumn(columnId: string): void {\n this.eventBus.emit('dynamic-column:unregister', { id: columnId });\n this.dynamicColumns.delete(columnId);\n log.info(`[${this.appId}] Dynamic column unregistered: ${columnId}`);\n }\n\n // -------------------------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------------------------\n\n /**\n * Unregister everything this SDK instance has registered. Call from your\n * remote app's unmount/cleanup path — `useRemoteApp` does this for you.\n */\n cleanup(): void {\n log.info(\n `[${this.appId}] Cleanup: ${this.routes.size} routes, ${this.dynamicExtensions.size} extensions, ${this.dynamicColumns.size} columns`,\n );\n this.routes.forEach((id) => this.eventBus.emit('route:unregister', { id }));\n this.dynamicExtensions.forEach((id) =>\n this.eventBus.emit('dynamic-extension:unregister', { id }),\n );\n this.dynamicColumns.forEach((id) => this.eventBus.emit('dynamic-column:unregister', { id }));\n this.routes.clear();\n this.dynamicExtensions.clear();\n this.dynamicColumns.clear();\n }\n\n getAppId(): string {\n return this.appId;\n }\n\n getRegisteredRoutes(): string[] {\n return Array.from(this.routes);\n }\n\n getRegisteredDynamicExtensions(): string[] {\n return Array.from(this.dynamicExtensions);\n }\n\n getRegisteredDynamicColumns(): string[] {\n return Array.from(this.dynamicColumns);\n }\n}\n\n/** Factory: equivalent to `new RemoteAppSDK(...)`. */\nexport function createRemoteAppSDK(eventBus: HorizonEventBus, appId: string): RemoteAppSDK {\n return new RemoteAppSDK(eventBus, appId);\n}\n\ninterface ModuleFederationContainer {\n get: (module: string) => Promise<() => { default?: unknown; [key: string]: unknown }>;\n}\n","/**\n * React hooks for remote apps.\n *\n * Each hook handles its own cleanup so remote apps don't have to track\n * unregistration manually.\n */\nimport { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\nimport { createElement } from 'react';\nimport type {\n DynamicColumnConfig,\n DynamicExtensionConfig,\n HorizonContext,\n RemoteModuleConfig,\n RouteConfig,\n} from './types';\nimport { RemoteAppSDK, createRemoteAppSDK } from './RemoteAppSDK';\n\n// ---------------------------------------------------------------------------\n// HorizonContextProvider + useHorizonContext\n// ---------------------------------------------------------------------------\n\n/**\n * Internal React context that carries the live HorizonContext.\n * Updated automatically when dark mode changes — remote pages never need to\n * subscribe to theme events manually.\n */\nconst HorizonContextReact = createContext<HorizonContext | null>(null);\n\n/**\n * Wrap your registered page components with this provider so they receive a\n * reactive HorizonContext without any extra event-listener boilerplate.\n *\n * The component memoization pattern in App.tsx captures `horizonContext` once\n * (to keep stable component identity), but `eventBus` is a singleton — the\n * Provider subscribes to it and pushes theme updates to all descendant pages.\n *\n * @example\n * // In App.tsx, inside useMemo with empty deps:\n * return (\n * <HorizonContextProvider context={horizonContext}>\n * <MyPage />\n * </HorizonContextProvider>\n * );\n */\nexport function HorizonContextProvider({\n context,\n children,\n}: {\n context: HorizonContext;\n children: ReactNode;\n}) {\n const [theme, setTheme] = useState<'light' | 'dark'>(context.theme ?? 'light');\n const [locale, setLocale] = useState<string>(context.locale ?? 'en-US');\n\n useEffect(() => {\n if (!context.eventBus) return;\n\n const themeHandler = (data: unknown) => {\n const payload = data as { theme: 'light' | 'dark' };\n if (payload?.theme === 'light' || payload?.theme === 'dark') setTheme(payload.theme);\n };\n const localeHandler = (data: unknown) => {\n const payload = data as { locale: string };\n if (payload?.locale) setLocale(payload.locale);\n };\n\n context.eventBus.on('theme:changed', themeHandler);\n context.eventBus.on('locale:changed', localeHandler);\n return () => {\n context.eventBus.off('theme:changed', themeHandler);\n context.eventBus.off('locale:changed', localeHandler);\n };\n }, [context.eventBus]);\n\n const liveContext = useMemo<HorizonContext>(\n () => ({ ...context, theme, locale }),\n // Intentionally omit `context` — eventBus is stable and subscriptions keep\n // theme/locale live. context spread gives access to t, user, api, navigate, etc.\n [theme, locale],\n );\n\n return createElement(HorizonContextReact.Provider, { value: liveContext }, children);\n}\n\n/**\n * Read the live HorizonContext inside any page registered via the SDK.\n * Returns a context that updates automatically when dark mode changes.\n *\n * Must be called inside a component rendered within `HorizonContextProvider`.\n *\n * @example\n * export default function MyPage() {\n * const { ui, theme, user, navigate } = useHorizonContext();\n * const { PageTemplate, DatagridTemplate } = ui.templates;\n * // `theme` is always 'light' | 'dark', reactive to system/user toggle\n * }\n */\nexport function useHorizonContext(): HorizonContext {\n const ctx = useContext(HorizonContextReact);\n if (!ctx) {\n throw new Error(\n '[Horizon SDK] useHorizonContext() must be called inside a component wrapped by HorizonContextProvider.',\n );\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// useTheme — reactive theme for pages and standalone extensions\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the current color scheme, reactive to host theme changes.\n *\n * Works in **both** contexts with the same import — no manual event wiring:\n *\n * - **Page components** (inside `HorizonContextProvider`): reads directly from\n * the provider's React context. No subscription overhead.\n * - **Standalone extension components** (rendered by the host via\n * `registerDynamicExtension`): falls back to `eventBus` subscription.\n * Pass `context.eventBus` as the argument.\n *\n * @example\n * // In a page component (HorizonContextProvider ancestor required)\n * export default function MyPage() {\n * const { theme } = useTheme();\n * return <div style={{ color: theme === 'dark' ? '#fff' : '#000' }}>...</div>;\n * }\n *\n * @example\n * // In a standalone extension component\n * export default function MyButton({ context }: ExtensionComponentProps) {\n * const { theme } = useTheme(context.eventBus);\n * const { Button } = context.ui ?? {};\n * return <Button>{theme === 'dark' ? '🌙' : '☀️'} Export</Button>;\n * }\n */\n/**\n * Returns the current color scheme, reactive to host theme changes.\n *\n * @param eventBus - Pass `context.eventBus` when called from a standalone\n * extension component (outside a HorizonContextProvider).\n * @param initialTheme - Pass `context.theme` when called from a standalone\n * extension component so the hook initialises with the correct value on\n * first render rather than defaulting to `'light'`.\n *\n * Inside a page component wrapped by `HorizonContextProvider`, both params\n * can be omitted — the provider context is used automatically.\n */\nexport function useTheme(\n eventBus?: HorizonContext['eventBus'],\n initialTheme?: 'light' | 'dark',\n): { theme: 'light' | 'dark' } {\n const providerTheme = useContext(HorizonContextReact)?.theme;\n\n // Prefer provider > caller-supplied initial > safe default.\n // Passing initialTheme = context.theme from an extension component ensures\n // the correct value is used on the very first render.\n const [localTheme, setLocalTheme] = useState<'light' | 'dark'>(\n providerTheme ?? initialTheme ?? 'light',\n );\n\n useEffect(() => {\n // Inside a HorizonContextProvider the provider re-renders with the new\n // theme value — no subscription needed here.\n if (providerTheme !== undefined) return;\n if (!eventBus) return;\n\n const handler = (data: unknown) => {\n const payload = data as { theme: 'light' | 'dark' };\n if (payload?.theme === 'light' || payload?.theme === 'dark') {\n setLocalTheme(payload.theme);\n }\n };\n eventBus.on('theme:changed', handler);\n return () => {\n eventBus.off('theme:changed', handler);\n };\n }, [providerTheme, eventBus]);\n\n return { theme: providerTheme ?? localTheme };\n}\n\n// ---------------------------------------------------------------------------\n// useLocale — access host translations and current locale\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the host's translation function and current locale.\n *\n * Because `i18next` is shared as a singleton, this hook reads directly from\n * the host's already-initialized i18next instance — all host translations\n * (common, telecom, admin, etc.) are immediately available with no extra\n * fetch or setup required.\n *\n * Reactivity is handled internally by react-i18next: the returned `t` and\n * `locale` update automatically when the user switches language.\n *\n * @param ns - Optional namespace(s). Defaults to `'common'` (the host\n * namespace that contains all standard UI strings). Pass your app's own\n * registered namespace to access remote-app-specific keys.\n *\n * @example\n * // In a page component — access any host string\n * export default function MyPage() {\n * const { t, locale } = useLocale();\n * return <Button>{t('SAVE')}</Button>;\n * }\n *\n * @example\n * // In an extension component — same API\n * export function MyButton({ context }: ExtensionComponentProps) {\n * const { t } = useLocale();\n * const { Button } = context.ui ?? {};\n * return <Button>{t('EXPORT')}</Button>;\n * }\n */\n/**\n * Returns the host's `t` translation function and current locale string.\n *\n * The `t` function is the host's i18next instance — all 1,375+ host strings\n * across the common, telecom, admin, and validation namespaces are immediately\n * available. No i18next dependency, no package install, no init required in\n * the remote app.\n *\n * Both `t` and `locale` update automatically when the user switches language.\n *\n * @example\n * export default function MyPage() {\n * const { t, locale } = useLocale();\n * return <Button>{t('SAVE')}</Button>;\n * }\n *\n * @example\n * // In a standalone extension component\n * export function MyButton({ context }: ExtensionComponentProps) {\n * const { t } = useLocale();\n * return <span>{t('EXPORT')}</span>;\n * }\n */\nexport function useLocale(): { t: HorizonContext['t']; locale: string } {\n const ctx = useContext(HorizonContextReact);\n return {\n t: ctx?.t,\n locale: ctx?.locale ?? 'en-US',\n };\n}\n\n// ---------------------------------------------------------------------------\n// useRemoteApp\n// ---------------------------------------------------------------------------\n\n/**\n * Get an SDK instance bound to your app, plus a flat view of the Horizon\n * context. Returned `sdk.cleanup()` is called automatically on unmount.\n *\n * @example\n * export default function MyApp(horizonContext: HorizonContext) {\n * const { sdk, user } = useRemoteApp(horizonContext, 'my-app');\n *\n * useEffect(() => {\n * sdk.registerDynamicExtension({\n * id: 'my-app.button',\n * zone: 'page-header-actions',\n * routes: [{ pattern: '/manage/*\\/users' }],\n * component: MyButton,\n * });\n * }, [sdk]);\n *\n * return <div>Hello {user.displayName}</div>;\n * }\n */\nexport function useRemoteApp(horizonContext: HorizonContext, appId: string) {\n const sdkRef = useRef<RemoteAppSDK | null>(null);\n if (!sdkRef.current) {\n sdkRef.current = createRemoteAppSDK(horizonContext.eventBus, appId);\n }\n const sdk = sdkRef.current;\n\n useEffect(() => {\n return () => sdk.cleanup();\n }, [sdk]);\n\n return { sdk, ...horizonContext };\n}\n\n/**\n * Register a route for the lifetime of the calling component.\n */\nexport function useRoute(\n eventBus: HorizonContext['eventBus'],\n appId: string,\n config: Omit<RouteConfig, 'appId'>,\n): RemoteAppSDK {\n const sdk = useMemo(() => createRemoteAppSDK(eventBus, appId), [eventBus, appId]);\n\n useEffect(() => {\n void sdk.registerRoute(config);\n return () => sdk.unregisterRoute(config.id);\n }, [sdk, config]);\n\n return sdk;\n}\n\n/**\n * Register a route by pulling its component out of a federated module's\n * webpack container.\n */\nexport function useRouteFromModule(\n eventBus: HorizonContext['eventBus'],\n appId: string,\n routeConfig: Omit<RouteConfig, 'appId' | 'component'>,\n moduleConfig: RemoteModuleConfig,\n) {\n const sdk = useMemo(() => createRemoteAppSDK(eventBus, appId), [eventBus, appId]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n let mounted = true;\n sdk\n .registerRouteFromModule(routeConfig, moduleConfig)\n .then(() => {\n if (mounted) setLoading(false);\n })\n .catch((err: Error) => {\n if (mounted) {\n setError(err);\n setLoading(false);\n }\n });\n\n return () => {\n mounted = false;\n sdk.unregisterRoute(routeConfig.id);\n };\n }, [sdk, moduleConfig, routeConfig]);\n\n return { loading, error, sdk };\n}\n\n/**\n * Register a dynamic extension (pattern-based UI injection) for the lifetime\n * of the calling component.\n */\nexport function useDynamicExtension(\n eventBus: HorizonContext['eventBus'],\n appId: string,\n config: Omit<DynamicExtensionConfig, 'appId'>,\n): RemoteAppSDK {\n const sdk = useMemo(() => createRemoteAppSDK(eventBus, appId), [eventBus, appId]);\n\n useEffect(() => {\n sdk.registerDynamicExtension(config);\n return () => sdk.unregisterDynamicExtension(config.id);\n }, [sdk, config]);\n\n return sdk;\n}\n\n/**\n * Register a dynamic table column for the lifetime of the calling component.\n */\nexport function useDynamicColumn(\n eventBus: HorizonContext['eventBus'],\n appId: string,\n config: Omit<DynamicColumnConfig, 'appId'>,\n): RemoteAppSDK {\n const sdk = useMemo(() => createRemoteAppSDK(eventBus, appId), [eventBus, appId]);\n\n useEffect(() => {\n sdk.registerDynamicColumn(config);\n return () => sdk.unregisterDynamicColumn(config.id);\n }, [sdk, config]);\n\n return sdk;\n}\n","/**\n * Federation Error\n * Structured error class with error codes for better error handling\n */\n\nexport type HorizonSDKErrorCode =\n | 'PERMISSION_DENIED'\n | 'RATE_LIMIT_EXCEEDED'\n | 'INVALID_MESSAGE'\n | 'SIGNATURE_VERIFICATION_FAILED'\n | 'API_ERROR'\n | 'NETWORK_ERROR'\n | 'INVALID_EXTENSION_POINT'\n | 'INVALID_CONFIGURATION'\n | 'APP_NOT_FOUND'\n | 'MODULE_LOAD_FAILED'\n | 'INITIALIZATION_FAILED'\n | 'UNKNOWN_ERROR';\n\nexport interface HorizonSDKErrorOptions {\n code: HorizonSDKErrorCode;\n message: string;\n details?: Record<string, unknown>;\n cause?: Error;\n statusCode?: number;\n}\n\n/**\n * HorizonSDKError class\n * Provides structured errors with error codes and additional context\n */\nexport class HorizonSDKError extends Error {\n public readonly code: HorizonSDKErrorCode;\n public readonly details?: Record<string, unknown>;\n public readonly cause?: Error;\n public readonly statusCode?: number;\n public readonly timestamp: string;\n\n constructor(options: HorizonSDKErrorOptions) {\n super(options.message);\n\n this.name = 'HorizonSDKError';\n this.code = options.code;\n this.details = options.details;\n this.cause = options.cause;\n this.statusCode = options.statusCode;\n this.timestamp = new Date().toISOString();\n\n // Maintain proper stack trace (only for V8 engines)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, HorizonSDKError);\n }\n }\n\n /**\n * Check if error is a HorizonSDKError\n */\n static isHorizonSDKError(error: unknown): error is HorizonSDKError {\n return error instanceof HorizonSDKError;\n }\n\n /**\n * Get user-friendly error message\n */\n getUserMessage(): string {\n switch (this.code) {\n case 'PERMISSION_DENIED':\n return 'You do not have permission to access this resource.';\n case 'RATE_LIMIT_EXCEEDED':\n return 'Too many requests. Please try again later.';\n case 'INVALID_MESSAGE':\n return 'Invalid message format.';\n case 'SIGNATURE_VERIFICATION_FAILED':\n return 'Message signature verification failed.';\n case 'API_ERROR':\n return 'An error occurred while communicating with the API.';\n case 'NETWORK_ERROR':\n return 'Network error. Please check your connection.';\n case 'INVALID_EXTENSION_POINT':\n return 'Invalid extension point.';\n case 'INVALID_CONFIGURATION':\n return 'Invalid configuration provided.';\n case 'APP_NOT_FOUND':\n return 'Application not found.';\n case 'MODULE_LOAD_FAILED':\n return 'Failed to load application module.';\n case 'INITIALIZATION_FAILED':\n return 'Application initialization failed.';\n default:\n return 'An unknown error occurred.';\n }\n }\n\n /**\n * Serialize error to JSON\n */\n toJSON(): object {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n details: this.details,\n statusCode: this.statusCode,\n timestamp: this.timestamp,\n stack: this.stack,\n };\n }\n\n /**\n * Get formatted error message for logging\n */\n toLogString(): string {\n let log = `[${this.code}] ${this.message}`;\n\n if (this.statusCode) {\n log += ` (status: ${this.statusCode})`;\n }\n\n if (this.details) {\n log += `\\nDetails: ${JSON.stringify(this.details, null, 2)}`;\n }\n\n if (this.cause) {\n log += `\\nCaused by: ${this.cause.message}`;\n }\n\n return log;\n }\n}\n\n/**\n * Helper function to create HorizonSDKError instances\n */\nexport function createHorizonSDKError(\n code: HorizonSDKErrorCode,\n message: string,\n details?: Record<string, unknown>,\n cause?: Error,\n): HorizonSDKError {\n return new HorizonSDKError({ code, message, details, cause });\n}\n\n/**\n * Permission Denied Error\n */\nexport function permissionDeniedError(\n resource: string,\n details?: Record<string, unknown>,\n): HorizonSDKError {\n return new HorizonSDKError({\n code: 'PERMISSION_DENIED',\n message: `Permission denied: ${resource}`,\n details,\n statusCode: 403,\n });\n}\n\n/**\n * Rate Limit Exceeded Error\n */\nexport function rateLimitError(\n resetTime: number,\n details?: Record<string, unknown>,\n): HorizonSDKError {\n return new HorizonSDKError({\n code: 'RATE_LIMIT_EXCEEDED',\n message: `Rate limit exceeded. Resets at ${new Date(resetTime).toISOString()}`,\n details: { resetTime, ...details },\n statusCode: 429,\n });\n}\n\n/**\n * API Error\n */\nexport function apiError(\n message: string,\n statusCode: number,\n details?: Record<string, unknown>,\n cause?: Error,\n): HorizonSDKError {\n return new HorizonSDKError({\n code: 'API_ERROR',\n message,\n details,\n cause,\n statusCode,\n });\n}\n\n/**\n * Invalid Extension Point Error\n */\nexport function invalidExtensionPointError(\n pointId: string,\n validPoints: string[],\n): HorizonSDKError {\n return new HorizonSDKError({\n code: 'INVALID_EXTENSION_POINT',\n message: `Invalid extension point: ${pointId}`,\n details: { pointId, validPoints },\n });\n}\n\n/**\n * Signature Verification Failed Error\n */\nexport function signatureVerificationError(reason: string): HorizonSDKError {\n return new HorizonSDKError({\n code: 'SIGNATURE_VERIFICATION_FAILED',\n message: `Signature verification failed: ${reason}`,\n details: { reason },\n });\n}\n\n/**\n * Module Load Failed Error\n */\nexport function moduleLoadError(appId: string, url: string, cause?: Error): HorizonSDKError {\n return new HorizonSDKError({\n code: 'MODULE_LOAD_FAILED',\n message: `Failed to load module for app: ${appId}`,\n details: { appId, url },\n cause,\n });\n}\n","/**\n * Stable Anchor IDs for Extension Placement\n *\n * These anchor IDs are guaranteed to be stable and can be used by extensions\n * to specify their menu placement using semantic anchors.\n *\n * @example\n * ```typescript\n * import { MANAGE_ANCHORS } from '@netsapiens/horizon-sdk';\n *\n * sdk.registerRoute({\n * id: 'my-crm',\n * parentPath: '/apps',\n * path: 'crm',\n * label: 'CRM',\n * placement: { after: MANAGE_ANCHORS.contacts }\n * });\n * ```\n */\n\n/**\n * Manage Menu Anchors\n * Available in both domain and no-domain contexts\n */\nexport const MANAGE_ANCHORS = {\n dashboard: 'manage-dashboard',\n users: 'manage-users',\n contacts: 'manage-contacts',\n devices: 'manage-devices',\n phoneNumbers: 'manage-phone-numbers',\n callLogs: 'manage-call-logs',\n voicemail: 'manage-voicemail',\n fax: 'manage-fax',\n settings: 'manage-settings',\n} as const;\n\n/**\n * Platform Menu Anchors\n * Available to Admin, Super User, and Reseller scopes\n */\nexport const PLATFORM_ANCHORS = {\n dashboard: 'platform-dashboard',\n codeManagement: 'platform-code-management',\n configManagement: 'platform-config-management',\n sdkManagement: 'platform-ui-sdk',\n branding: 'platform-branding',\n recording: 'platform-recording',\n logsAndDiagnostics: 'platform-logs-and-diagnostics',\n} as const;\n\n/**\n * Apps Menu Anchors\n * Extension apps typically register here\n */\nexport const APPS_ANCHORS = {\n home: 'apps-home',\n} as const;\n\n/**\n * My Account Menu Anchors\n * User-specific settings and preferences\n */\nexport const MY_ACCOUNT_ANCHORS = {\n profile: 'my-account-profile',\n preferences: 'my-account-preferences',\n security: 'my-account-security',\n} as const;\n\n/**\n * All anchor constants in one object for convenience\n */\nexport const ANCHORS = {\n manage: MANAGE_ANCHORS,\n platform: PLATFORM_ANCHORS,\n apps: APPS_ANCHORS,\n myAccount: MY_ACCOUNT_ANCHORS,\n} as const;\n\n/**\n * Type-safe anchor ID union\n */\nexport type AnchorId =\n | (typeof MANAGE_ANCHORS)[keyof typeof MANAGE_ANCHORS]\n | (typeof PLATFORM_ANCHORS)[keyof typeof PLATFORM_ANCHORS]\n | (typeof APPS_ANCHORS)[keyof typeof APPS_ANCHORS]\n | (typeof MY_ACCOUNT_ANCHORS)[keyof typeof MY_ACCOUNT_ANCHORS];\n","{\n \"name\": \"@netsapiens/horizon-sdk\",\n \"description\": \"SDK for building remote applications that integrate with NetSapiens Horizon\",\n \"version\": \"0.1.2\",\n \"type\": \"module\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"types\": \"dist/index.d.ts\",\n \"files\": [\n \"dist\"\n ],\n \"author\": \"NetSapiens Inc.\",\n \"license\": \"MIT\",\n \"keywords\": [\n \"netsapiens\",\n \"horizon\",\n \"remote-apps\",\n \"module-federation\",\n \"sdk\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"clean\": \"rimraf .turbo node_modules dist\",\n \"lint\": \"eslint . --max-warnings 0\",\n \"format\": \"prettier --check . --ignore-path ../../.gitignore\",\n \"format:fix\": \"prettier --write . --ignore-path ../../.gitignore\",\n \"typecheck\": \"tsc --noEmit\",\n \"validate\": \"pnpm run lint && pnpm run format && pnpm run typecheck\"\n },\n \"publishConfig\": {\n \"access\": \"restricted\"\n },\n \"peerDependencies\": {\n \"loglevel\": \"^1.9.2\",\n \"react\": \"^18.0.0 || ^19.0.0\",\n \"react-dom\": \"^18.0.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@repo/eslint-config\": \"workspace:*\",\n \"@repo/typescript-config\": \"workspace:*\",\n \"@types/node\": \"^22.7.5\",\n \"@types/react\": \"^19.0.0\",\n \"@types/react-dom\": \"^19.0.0\",\n \"eslint\": \"^8.57.0\",\n \"loglevel\": \"^1.9.2\",\n \"prettier\": \"3.3.3\",\n \"tsup\": \"^8.0.0\",\n \"typescript\": \"^5.4.5\"\n }\n}\n","/**\n * @netsapiens/horizon-sdk — public entry point.\n *\n * Single export surface. Remote app developers import everything from\n * `@netsapiens/horizon-sdk`.\n *\n * @example\n * import { useRemoteApp, type HorizonContext } from '@netsapiens/horizon-sdk';\n *\n * export default function MyApp(horizonContext: HorizonContext) {\n * const { sdk, user } = useRemoteApp(horizonContext, 'my-app');\n * useEffect(() => {\n * sdk.registerDynamicExtension({\n * id: 'my-app.export',\n * zone: 'page-header-actions',\n * routes: [{ pattern: '/manage/*\\/call-logs' }],\n * component: MyButton,\n * });\n * }, [sdk]);\n * return <div>Hello {user.displayName}</div>;\n * }\n */\nimport { createLogger } from './utils/logger';\n\n// SDK\nexport { RemoteAppSDK, createRemoteAppSDK } from './RemoteAppSDK';\nexport {\n useRemoteApp,\n useRoute,\n useRouteFromModule,\n useDynamicExtension,\n useDynamicColumn,\n // Dark-mode-aware context — use these instead of prop drilling\n HorizonContextProvider,\n useHorizonContext,\n // Reactive theme for pages and standalone extensions\n useTheme,\n // Host translations via shared i18next singleton\n useLocale,\n} from './hooks';\n\n// Types — runtime contract with Horizon\nexport type {\n HorizonContext,\n HorizonUser,\n HorizonAuth,\n HorizonApiClient,\n HorizonEventBus,\n HorizonUI,\n HorizonUITemplates,\n ThemeTokens,\n BreadcrumbItem,\n // Routes\n RouteConfig,\n RemoteModuleConfig,\n SemanticPlacement,\n // Dynamic extensions\n ExtensionZone,\n ExtensionZoneId,\n RoutePattern,\n ExtensionContext,\n ExtensionComponentProps,\n DynamicExtensionConfig,\n DynamicColumnConfig,\n DynamicColumnDefinition,\n // Remote Authentication\n RemoteAuthRequest,\n RemoteAuthResponse,\n RemoteAuthError,\n RemoteAuthOptions,\n} from './types';\n\n// Errors\nexport {\n HorizonSDKError,\n createHorizonSDKError,\n permissionDeniedError,\n rateLimitError,\n apiError,\n invalidExtensionPointError,\n signatureVerificationError,\n moduleLoadError,\n} from './errors';\nexport type { HorizonSDKErrorCode, HorizonSDKErrorOptions } from './errors';\n\n// Utilities\nexport { createLogger, setLogLevel, getLogLevel } from './utils/logger';\n\n// Stable anchor IDs for menu placement\nexport {\n MANAGE_ANCHORS,\n PLATFORM_ANCHORS,\n APPS_ANCHORS,\n MY_ACCOUNT_ANCHORS,\n ANCHORS,\n type AnchorId,\n} from './anchors';\n\n// @ts-ignore - This is resolved at build time by tsup\nimport pkg from '../package.json';\n\nexport const VERSION = pkg.version;\n\nconst log = createLogger('FederationSDK');\nlog.info(`SDK v${VERSION} loaded`);\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/logger.ts","../src/RemoteAppSDK.ts","../src/hooks.tsx","../src/errors/HorizonSDKError.ts","../src/anchors.ts","../package.json","../src/index.ts"],"names":["loglevel","createContext","useState","useEffect","useMemo","createElement","useContext","useRef","log"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Logging utility using loglevel for Horizon SDK\n * Consistent with netsapiens-monorepo logging pattern\n *\n * Usage:\n * import { createLogger } from '../utils/logger';\n * const log = createLogger('ModuleLoader');\n * log.info('Loading module...');\n * log.error('Failed to load:', error);\n */\nimport loglevel from \"loglevel\";\n\n// Configure default log level based on environment\nconst isDevelopment = process.env.NODE_ENV === \"development\";\n\n// Set default level\nloglevel.setDefaultLevel(\n isDevelopment ? loglevel.levels.DEBUG : loglevel.levels.WARN,\n);\n\n/**\n * Create a namespaced logger for a specific module\n * Follows the monorepo pattern from @netsapiens/ns-sdk\n *\n * @example\n * const log = createLogger('ModuleLoader');\n * log.debug('Starting load...');\n * log.info('Module loaded successfully');\n * log.warn('Slow load detected');\n * log.error('Load failed:', error);\n */\nexport const createLogger = (namespace: string) => {\n const moduleLogger = loglevel.getLogger(`HorizonSDK:${namespace}`);\n\n if (isDevelopment) {\n moduleLogger.setLevel(loglevel.levels.DEBUG);\n } else {\n moduleLogger.setLevel(loglevel.levels.WARN);\n }\n\n return moduleLogger;\n};\n\n/**\n * Default logger instance for the SDK\n */\nexport const logger = loglevel.getLogger(\"horizon-sdk\");\n\n/**\n * Set log level at runtime\n * Useful for debugging\n *\n * @example\n * import { setLogLevel } from './utils/logger';\n * setLogLevel('DEBUG'); // Enable all logs\n * setLogLevel('WARN'); // Only warnings and errors\n */\nexport const setLogLevel = (\n level: \"TRACE\" | \"DEBUG\" | \"INFO\" | \"WARN\" | \"ERROR\" | \"SILENT\",\n) => {\n loglevel.setLevel(level);\n};\n\n/**\n * Get current log level\n */\nexport const getLogLevel = () => {\n return loglevel.getLevel();\n};\n\n// Extend Window interface for debug tools\ndeclare global {\n interface Window {\n __horizonSDKLogger__: typeof logger;\n __setHorizonSDKLogLevel__: typeof setLogLevel;\n }\n}\n\n// Make logger available in browser console for debugging\nif (typeof window !== \"undefined\") {\n window.__horizonSDKLogger__ = logger;\n window.__setHorizonSDKLogLevel__ = setLogLevel;\n\n if (isDevelopment) {\n logger.info(\n 'Horizon SDK logger initialized. Use window.__setHorizonSDKLogLevel__(\"DEBUG\") to adjust log level.',\n );\n }\n}\n","/**\n * Remote App SDK.\n *\n * Wraps Horizon's event bus with a typed, app-scoped API for registering routes\n * and dynamic extensions. Tracks every registration so `cleanup()` can tear\n * everything down on unmount — essential when a remote app is reloaded or its\n * webpack module is replaced during HMR.\n */\nimport type {\n CallEvent,\n CallEventType,\n DynamicColumnConfig,\n DynamicExtensionConfig,\n HorizonEventBus,\n RemoteModuleConfig,\n RouteConfig,\n} from \"./types\";\nimport { createLogger } from \"./utils/logger\";\n\nconst log = createLogger(\"RemoteAppSDK\");\n\nexport class RemoteAppSDK {\n private eventBus: HorizonEventBus;\n private appId: string;\n\n // Registrations are tracked separately so cleanup() emits the right\n // unregister event for each. (A single set keyed by id-only would let dynamic\n // registrations leak — they're unregistered on a different channel.)\n private routes = new Set<string>();\n private dynamicExtensions = new Set<string>();\n private dynamicColumns = new Set<string>();\n private callEventsSubscribed = false;\n\n constructor(eventBus: HorizonEventBus, appId: string) {\n this.eventBus = eventBus;\n this.appId = appId;\n log.debug(`Initialized for app: ${appId}`);\n }\n\n // -------------------------------------------------------------------------\n // Routes\n // -------------------------------------------------------------------------\n\n async registerRoute(config: Omit<RouteConfig, \"appId\">): Promise<void> {\n const route: RouteConfig = { ...config, appId: this.appId };\n this.eventBus.emit(\"route:register\", route);\n this.routes.add(route.id);\n log.info(\n `[${this.appId}] Route registered: ${route.id} at ${route.parentPath}/${route.path}`,\n );\n }\n\n unregisterRoute(routeId: string): void {\n this.eventBus.emit(\"route:unregister\", { id: routeId });\n this.routes.delete(routeId);\n log.info(`[${this.appId}] Route unregistered: ${routeId}`);\n }\n\n /**\n * Convenience: load a component out of a federated module's webpack\n * container and register it as a route in one step. Useful when the route\n * component lives in a sibling exposed module rather than the entry App.\n */\n async registerRouteFromModule(\n routeConfig: Omit<RouteConfig, \"appId\" | \"component\">,\n moduleConfig: RemoteModuleConfig,\n ): Promise<void> {\n const container = (\n window as unknown as Record<string, ModuleFederationContainer>\n )[moduleConfig.scope];\n if (!container) {\n throw new Error(`Remote container not found: ${moduleConfig.scope}`);\n }\n\n const factory = await container.get(moduleConfig.module);\n const moduleExports = factory();\n const component = (moduleExports.default ??\n moduleExports) as React.ComponentType<unknown>;\n\n await this.registerRoute({ ...routeConfig, component });\n }\n\n // -------------------------------------------------------------------------\n // Dynamic extensions (pattern-based UI injection)\n // -------------------------------------------------------------------------\n\n registerDynamicExtension(\n config: Omit<DynamicExtensionConfig, \"appId\">,\n ): void {\n const extension = { ...config, appId: this.appId };\n this.eventBus.emit(\"dynamic-extension:register\", extension);\n this.dynamicExtensions.add(extension.id);\n log.info(\n `[${this.appId}] Dynamic extension registered: ${extension.id} → zone ${extension.zone}`,\n );\n }\n\n unregisterDynamicExtension(extensionId: string): void {\n this.eventBus.emit(\"dynamic-extension:unregister\", { id: extensionId });\n this.dynamicExtensions.delete(extensionId);\n log.info(`[${this.appId}] Dynamic extension unregistered: ${extensionId}`);\n }\n\n // -------------------------------------------------------------------------\n // Dynamic columns (table column injection)\n // -------------------------------------------------------------------------\n\n registerDynamicColumn(config: Omit<DynamicColumnConfig, \"appId\">): void {\n const column = { ...config, appId: this.appId };\n this.eventBus.emit(\"dynamic-column:register\", column);\n this.dynamicColumns.add(column.id);\n log.info(\n `[${this.appId}] Dynamic column registered: ${column.id} → zone ${column.zone}`,\n );\n }\n\n unregisterDynamicColumn(columnId: string): void {\n this.eventBus.emit(\"dynamic-column:unregister\", { id: columnId });\n this.dynamicColumns.delete(columnId);\n log.info(`[${this.appId}] Dynamic column unregistered: ${columnId}`);\n }\n\n // -------------------------------------------------------------------------\n // Call events (capability-gated, app-scoped)\n // -------------------------------------------------------------------------\n\n /**\n * Subscribe to live SIP call events. Routes through the host's\n * `CallEventsManager`, which enforces the `call-events:listen` capability and\n * records the subscription against this app — so the platform knows which\n * apps consume call events and can disable the capability platform-wide.\n *\n * Prefer this over listening to `eventBus.on('call-event')` directly: the raw\n * bus is neither gated nor attributed to your app.\n *\n * @returns an unsubscribe function (also torn down by `cleanup()`).\n */\n subscribeToCallEvents(\n eventTypes: CallEventType[],\n callback: (event: CallEvent) => void,\n ): () => void {\n this.eventBus.emit(\"call-events:subscribe\", {\n appId: this.appId,\n eventTypes,\n callback,\n });\n this.callEventsSubscribed = true;\n log.info(\n `[${this.appId}] Subscribed to call events: ${eventTypes.join(\", \")}`,\n );\n return () => this.unsubscribeFromCallEvents();\n }\n\n unsubscribeFromCallEvents(): void {\n if (!this.callEventsSubscribed) return;\n this.eventBus.emit(\"call-events:unsubscribe\", { appId: this.appId });\n this.callEventsSubscribed = false;\n log.info(`[${this.appId}] Unsubscribed from call events`);\n }\n\n // -------------------------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------------------------\n\n /**\n * Unregister everything this SDK instance has registered. Call from your\n * remote app's unmount/cleanup path — `useRemoteApp` does this for you.\n */\n cleanup(): void {\n log.info(\n `[${this.appId}] Cleanup: ${this.routes.size} routes, ${this.dynamicExtensions.size} extensions, ${this.dynamicColumns.size} columns`,\n );\n this.routes.forEach((id) => this.eventBus.emit(\"route:unregister\", { id }));\n this.dynamicExtensions.forEach((id) =>\n this.eventBus.emit(\"dynamic-extension:unregister\", { id }),\n );\n this.dynamicColumns.forEach((id) =>\n this.eventBus.emit(\"dynamic-column:unregister\", { id }),\n );\n this.unsubscribeFromCallEvents();\n this.routes.clear();\n this.dynamicExtensions.clear();\n this.dynamicColumns.clear();\n }\n\n getAppId(): string {\n return this.appId;\n }\n\n getRegisteredRoutes(): string[] {\n return Array.from(this.routes);\n }\n\n getRegisteredDynamicExtensions(): string[] {\n return Array.from(this.dynamicExtensions);\n }\n\n getRegisteredDynamicColumns(): string[] {\n return Array.from(this.dynamicColumns);\n }\n}\n\n/** Factory: equivalent to `new RemoteAppSDK(...)`. */\nexport function createRemoteAppSDK(\n eventBus: HorizonEventBus,\n appId: string,\n): RemoteAppSDK {\n return new RemoteAppSDK(eventBus, appId);\n}\n\ninterface ModuleFederationContainer {\n get: (\n module: string,\n ) => Promise<() => { default?: unknown; [key: string]: unknown }>;\n}\n","/**\n * React hooks for remote apps.\n *\n * Each hook handles its own cleanup so remote apps don't have to track\n * unregistration manually.\n */\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createElement } from \"react\";\nimport type {\n DynamicColumnConfig,\n DynamicExtensionConfig,\n ExtensionContext,\n HorizonContext,\n RemoteModuleConfig,\n RouteConfig,\n} from \"./types\";\nimport { RemoteAppSDK, createRemoteAppSDK } from \"./RemoteAppSDK\";\n\n// ---------------------------------------------------------------------------\n// HorizonContextProvider + useHorizonContext\n// ---------------------------------------------------------------------------\n\n/**\n * Internal React context that carries the live HorizonContext.\n * Updated automatically when dark mode changes — remote pages never need to\n * subscribe to theme events manually.\n */\nconst HorizonContextReact = createContext<HorizonContext | null>(null);\n\n/**\n * Wrap your registered page components with this provider so they receive a\n * reactive HorizonContext without any extra event-listener boilerplate.\n *\n * The component memoization pattern in App.tsx captures `horizonContext` once\n * (to keep stable component identity), but `eventBus` is a singleton — the\n * Provider subscribes to it and pushes theme updates to all descendant pages.\n *\n * @example\n * // In App.tsx, inside useMemo with empty deps:\n * return (\n * <HorizonContextProvider context={horizonContext}>\n * <MyPage />\n * </HorizonContextProvider>\n * );\n */\nexport function HorizonContextProvider({\n context,\n children,\n}: {\n context: HorizonContext;\n children: ReactNode;\n}) {\n const [theme, setTheme] = useState<\"light\" | \"dark\">(\n context.theme ?? \"light\",\n );\n const [locale, setLocale] = useState<string>(context.locale ?? \"en-US\");\n\n useEffect(() => {\n if (!context.eventBus) return;\n\n const themeHandler = (data: unknown) => {\n const payload = data as { theme: \"light\" | \"dark\" };\n if (payload?.theme === \"light\" || payload?.theme === \"dark\")\n setTheme(payload.theme);\n };\n const localeHandler = (data: unknown) => {\n const payload = data as { locale: string };\n if (payload?.locale) setLocale(payload.locale);\n };\n\n context.eventBus.on(\"theme:changed\", themeHandler);\n context.eventBus.on(\"locale:changed\", localeHandler);\n return () => {\n context.eventBus.off(\"theme:changed\", themeHandler);\n context.eventBus.off(\"locale:changed\", localeHandler);\n };\n }, [context.eventBus]);\n\n const liveContext = useMemo<HorizonContext>(\n () => ({ ...context, theme, locale }),\n // Intentionally omit `context` — eventBus is stable and subscriptions keep\n // theme/locale live. context spread gives access to t, user, api, navigate, etc.\n [theme, locale],\n );\n\n return createElement(\n HorizonContextReact.Provider,\n { value: liveContext },\n children,\n );\n}\n\n/**\n * Read the live HorizonContext inside any page registered via the SDK.\n * Returns a context that updates automatically when dark mode changes.\n *\n * Must be called inside a component rendered within `HorizonContextProvider`.\n *\n * @example\n * export default function MyPage() {\n * const { ui, theme, user, navigate } = useHorizonContext();\n * const { PageTemplate, DatagridTemplate } = ui.templates;\n * // `theme` is always 'light' | 'dark', reactive to system/user toggle\n * }\n */\nexport function useHorizonContext(): HorizonContext {\n const ctx = useContext(HorizonContextReact);\n if (!ctx) {\n throw new Error(\n \"[Horizon SDK] useHorizonContext() must be called inside a component wrapped by HorizonContextProvider.\",\n );\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// useTheme — reactive theme for pages and standalone extensions\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the current color scheme, reactive to host theme changes.\n *\n * Works in **both** contexts with the same import — no manual event wiring:\n *\n * - **Page components** (inside `HorizonContextProvider`): reads directly from\n * the provider's React context. No subscription overhead.\n * - **Standalone extension components** (rendered by the host via\n * `registerDynamicExtension`): falls back to `eventBus` subscription.\n * Pass `context.eventBus` as the argument.\n *\n * @example\n * // In a page component (HorizonContextProvider ancestor required)\n * export default function MyPage() {\n * const { theme } = useTheme();\n * return <div style={{ color: theme === 'dark' ? '#fff' : '#000' }}>...</div>;\n * }\n *\n * @example\n * // In a standalone extension component\n * export default function MyButton({ context }: ExtensionComponentProps) {\n * const { theme } = useTheme(context.eventBus);\n * const { Button } = context.ui ?? {};\n * return <Button>{theme === 'dark' ? '🌙' : '☀️'} Export</Button>;\n * }\n */\n/**\n * Returns the current color scheme, reactive to host theme changes.\n *\n * @param eventBus - Pass `context.eventBus` when called from a standalone\n * extension component (outside a HorizonContextProvider).\n * @param initialTheme - Pass `context.theme` when called from a standalone\n * extension component so the hook initialises with the correct value on\n * first render rather than defaulting to `'light'`.\n *\n * Inside a page component wrapped by `HorizonContextProvider`, both params\n * can be omitted — the provider context is used automatically.\n */\nexport function useTheme(\n eventBus?: HorizonContext[\"eventBus\"],\n initialTheme?: \"light\" | \"dark\",\n): { theme: \"light\" | \"dark\" } {\n const providerTheme = useContext(HorizonContextReact)?.theme;\n\n // Prefer provider > caller-supplied initial > safe default.\n // Passing initialTheme = context.theme from an extension component ensures\n // the correct value is used on the very first render.\n const [localTheme, setLocalTheme] = useState<\"light\" | \"dark\">(\n providerTheme ?? initialTheme ?? \"light\",\n );\n\n useEffect(() => {\n // Inside a HorizonContextProvider the provider re-renders with the new\n // theme value — no subscription needed here.\n if (providerTheme !== undefined) return;\n if (!eventBus) return;\n\n const handler = (data: unknown) => {\n const payload = data as { theme: \"light\" | \"dark\" };\n if (payload?.theme === \"light\" || payload?.theme === \"dark\") {\n setLocalTheme(payload.theme);\n }\n };\n eventBus.on(\"theme:changed\", handler);\n return () => {\n eventBus.off(\"theme:changed\", handler);\n };\n }, [providerTheme, eventBus]);\n\n return { theme: providerTheme ?? localTheme };\n}\n\n// ---------------------------------------------------------------------------\n// useLocale — access host translations and current locale\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the host's translation function and current locale.\n *\n * Because `i18next` is shared as a singleton, this hook reads directly from\n * the host's already-initialized i18next instance — all host translations\n * (common, telecom, admin, etc.) are immediately available with no extra\n * fetch or setup required.\n *\n * Reactivity is handled internally by react-i18next: the returned `t` and\n * `locale` update automatically when the user switches language.\n *\n * @param ns - Optional namespace(s). Defaults to `'common'` (the host\n * namespace that contains all standard UI strings). Pass your app's own\n * registered namespace to access remote-app-specific keys.\n *\n * @example\n * // In a page component — access any host string\n * export default function MyPage() {\n * const { t, locale } = useLocale();\n * return <Button>{t('SAVE')}</Button>;\n * }\n *\n * @example\n * // In an extension component — same API\n * export function MyButton({ context }: ExtensionComponentProps) {\n * const { t } = useLocale();\n * const { Button } = context.ui ?? {};\n * return <Button>{t('EXPORT')}</Button>;\n * }\n */\n/**\n * Returns the host's `t` translation function and current locale string.\n *\n * The `t` function is the host's i18next instance — all 1,375+ host strings\n * across the common, telecom, admin, and validation namespaces are immediately\n * available. No i18next dependency, no package install, no init required in\n * the remote app.\n *\n * Both `t` and `locale` update automatically when the user switches language.\n *\n * @example\n * export default function MyPage() {\n * const { t, locale } = useLocale();\n * return <Button>{t('SAVE')}</Button>;\n * }\n *\n * @example\n * // In a standalone extension component\n * export function MyButton({ context }: ExtensionComponentProps) {\n * const { t } = useLocale();\n * return <span>{t('EXPORT')}</span>;\n * }\n */\nexport function useLocale(): { t: HorizonContext[\"t\"]; locale: string } {\n const ctx = useContext(HorizonContextReact);\n return {\n t: ctx?.t,\n locale: ctx?.locale ?? \"en-US\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// useRemoteApp\n// ---------------------------------------------------------------------------\n\n/**\n * Get an SDK instance bound to your app, plus a flat view of the Horizon\n * context. Returned `sdk.cleanup()` is called automatically on unmount.\n *\n * @example\n * export default function MyApp(horizonContext: HorizonContext) {\n * const { sdk, user } = useRemoteApp(horizonContext, 'my-app');\n *\n * useEffect(() => {\n * sdk.registerDynamicExtension({\n * id: 'my-app.button',\n * zone: 'page-header-actions',\n * routes: [{ pattern: '/manage/*\\/users' }],\n * component: MyButton,\n * });\n * }, [sdk]);\n *\n * return <div>Hello {user.displayName}</div>;\n * }\n */\nexport function useRemoteApp(horizonContext: HorizonContext, appId: string) {\n const sdkRef = useRef<RemoteAppSDK | null>(null);\n if (!sdkRef.current) {\n sdkRef.current = createRemoteAppSDK(horizonContext.eventBus, appId);\n }\n const sdk = sdkRef.current;\n\n return { sdk, ...horizonContext };\n}\n\n/**\n * Register a route for the lifetime of the calling component.\n */\nexport function useRoute(\n eventBus: HorizonContext[\"eventBus\"],\n appId: string,\n config: Omit<RouteConfig, \"appId\">,\n): RemoteAppSDK {\n const sdk = useMemo(\n () => createRemoteAppSDK(eventBus, appId),\n [eventBus, appId],\n );\n\n useEffect(() => {\n void sdk.registerRoute(config);\n return () => sdk.unregisterRoute(config.id);\n }, [sdk, config]);\n\n return sdk;\n}\n\n/**\n * Register a route by pulling its component out of a federated module's\n * webpack container.\n */\nexport function useRouteFromModule(\n eventBus: HorizonContext[\"eventBus\"],\n appId: string,\n routeConfig: Omit<RouteConfig, \"appId\" | \"component\">,\n moduleConfig: RemoteModuleConfig,\n) {\n const sdk = useMemo(\n () => createRemoteAppSDK(eventBus, appId),\n [eventBus, appId],\n );\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n let mounted = true;\n sdk\n .registerRouteFromModule(routeConfig, moduleConfig)\n .then(() => {\n if (mounted) setLoading(false);\n })\n .catch((err: Error) => {\n if (mounted) {\n setError(err);\n setLoading(false);\n }\n });\n\n return () => {\n mounted = false;\n sdk.unregisterRoute(routeConfig.id);\n };\n }, [sdk, moduleConfig, routeConfig]);\n\n return { loading, error, sdk };\n}\n\n/**\n * Register a dynamic extension (pattern-based UI injection) for the lifetime\n * of the calling component.\n */\nexport function useDynamicExtension(\n eventBus: HorizonContext[\"eventBus\"],\n appId: string,\n config: Omit<DynamicExtensionConfig, \"appId\">,\n): RemoteAppSDK {\n const sdk = useMemo(\n () => createRemoteAppSDK(eventBus, appId),\n [eventBus, appId],\n );\n\n useEffect(() => {\n sdk.registerDynamicExtension(config);\n return () => sdk.unregisterDynamicExtension(config.id);\n }, [sdk, config]);\n\n return sdk;\n}\n\n// ---------------------------------------------------------------------------\n// usePageContext — typed access to host-supplied page context\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the current page context as a typed value.\n *\n * The host passes page-specific data (e.g. active filters, date range, selected\n * row) through `ExtensionContext.pageContext`. This hook casts it to the caller's\n * expected shape and keeps it reactive — when the host updates `pageContext` the\n * extension re-renders automatically because it flows in as a React prop.\n *\n * Define a typed interface for each page's context in your remote app and pass\n * it as the type parameter. Returns `undefined` when the host has not populated\n * any context for the current zone.\n *\n * @example\n * interface CallLogsPageContext {\n * dateRange?: [Date | null, Date | null];\n * }\n *\n * export function AnalyticsWidget({ context }: ExtensionComponentProps) {\n * const pageCtx = usePageContext<CallLogsPageContext>(context);\n * const [start, end] = pageCtx?.dateRange ?? [null, null];\n * // ...\n * }\n */\nexport function usePageContext<T = unknown>(\n context: ExtensionContext,\n): T | undefined {\n return context.pageContext as T | undefined;\n}\n\n/**\n * Register a dynamic table column for the lifetime of the calling component.\n */\nexport function useDynamicColumn(\n eventBus: HorizonContext[\"eventBus\"],\n appId: string,\n config: Omit<DynamicColumnConfig, \"appId\">,\n): RemoteAppSDK {\n const sdk = useMemo(\n () => createRemoteAppSDK(eventBus, appId),\n [eventBus, appId],\n );\n\n useEffect(() => {\n sdk.registerDynamicColumn(config);\n return () => sdk.unregisterDynamicColumn(config.id);\n }, [sdk, config]);\n\n return sdk;\n}\n","/**\n * Federation Error\n * Structured error class with error codes for better error handling\n */\n\nexport type HorizonSDKErrorCode =\n | \"PERMISSION_DENIED\"\n | \"RATE_LIMIT_EXCEEDED\"\n | \"INVALID_MESSAGE\"\n | \"SIGNATURE_VERIFICATION_FAILED\"\n | \"API_ERROR\"\n | \"NETWORK_ERROR\"\n | \"INVALID_EXTENSION_POINT\"\n | \"INVALID_CONFIGURATION\"\n | \"APP_NOT_FOUND\"\n | \"MODULE_LOAD_FAILED\"\n | \"INITIALIZATION_FAILED\"\n | \"UNKNOWN_ERROR\";\n\nexport interface HorizonSDKErrorOptions {\n code: HorizonSDKErrorCode;\n message: string;\n details?: Record<string, unknown>;\n cause?: Error;\n statusCode?: number;\n}\n\n/**\n * HorizonSDKError class\n * Provides structured errors with error codes and additional context\n */\nexport class HorizonSDKError extends Error {\n public readonly code: HorizonSDKErrorCode;\n public readonly details?: Record<string, unknown>;\n public readonly cause?: Error;\n public readonly statusCode?: number;\n public readonly timestamp: string;\n\n constructor(options: HorizonSDKErrorOptions) {\n super(options.message);\n\n this.name = \"HorizonSDKError\";\n this.code = options.code;\n this.details = options.details;\n this.cause = options.cause;\n this.statusCode = options.statusCode;\n this.timestamp = new Date().toISOString();\n\n // Maintain proper stack trace (only for V8 engines)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, HorizonSDKError);\n }\n }\n\n /**\n * Check if error is a HorizonSDKError\n */\n static isHorizonSDKError(error: unknown): error is HorizonSDKError {\n return error instanceof HorizonSDKError;\n }\n\n /**\n * Get user-friendly error message\n */\n getUserMessage(): string {\n switch (this.code) {\n case \"PERMISSION_DENIED\":\n return \"You do not have permission to access this resource.\";\n case \"RATE_LIMIT_EXCEEDED\":\n return \"Too many requests. Please try again later.\";\n case \"INVALID_MESSAGE\":\n return \"Invalid message format.\";\n case \"SIGNATURE_VERIFICATION_FAILED\":\n return \"Message signature verification failed.\";\n case \"API_ERROR\":\n return \"An error occurred while communicating with the API.\";\n case \"NETWORK_ERROR\":\n return \"Network error. Please check your connection.\";\n case \"INVALID_EXTENSION_POINT\":\n return \"Invalid extension point.\";\n case \"INVALID_CONFIGURATION\":\n return \"Invalid configuration provided.\";\n case \"APP_NOT_FOUND\":\n return \"Application not found.\";\n case \"MODULE_LOAD_FAILED\":\n return \"Failed to load application module.\";\n case \"INITIALIZATION_FAILED\":\n return \"Application initialization failed.\";\n default:\n return \"An unknown error occurred.\";\n }\n }\n\n /**\n * Serialize error to JSON\n */\n toJSON(): object {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n details: this.details,\n statusCode: this.statusCode,\n timestamp: this.timestamp,\n stack: this.stack,\n };\n }\n\n /**\n * Get formatted error message for logging\n */\n toLogString(): string {\n let log = `[${this.code}] ${this.message}`;\n\n if (this.statusCode) {\n log += ` (status: ${this.statusCode})`;\n }\n\n if (this.details) {\n log += `\\nDetails: ${JSON.stringify(this.details, null, 2)}`;\n }\n\n if (this.cause) {\n log += `\\nCaused by: ${this.cause.message}`;\n }\n\n return log;\n }\n}\n\n/**\n * Helper function to create HorizonSDKError instances\n */\nexport function createHorizonSDKError(\n code: HorizonSDKErrorCode,\n message: string,\n details?: Record<string, unknown>,\n cause?: Error,\n): HorizonSDKError {\n return new HorizonSDKError({ code, message, details, cause });\n}\n\n/**\n * Permission Denied Error\n */\nexport function permissionDeniedError(\n resource: string,\n details?: Record<string, unknown>,\n): HorizonSDKError {\n return new HorizonSDKError({\n code: \"PERMISSION_DENIED\",\n message: `Permission denied: ${resource}`,\n details,\n statusCode: 403,\n });\n}\n\n/**\n * Rate Limit Exceeded Error\n */\nexport function rateLimitError(\n resetTime: number,\n details?: Record<string, unknown>,\n): HorizonSDKError {\n return new HorizonSDKError({\n code: \"RATE_LIMIT_EXCEEDED\",\n message: `Rate limit exceeded. Resets at ${new Date(resetTime).toISOString()}`,\n details: { resetTime, ...details },\n statusCode: 429,\n });\n}\n\n/**\n * API Error\n */\nexport function apiError(\n message: string,\n statusCode: number,\n details?: Record<string, unknown>,\n cause?: Error,\n): HorizonSDKError {\n return new HorizonSDKError({\n code: \"API_ERROR\",\n message,\n details,\n cause,\n statusCode,\n });\n}\n\n/**\n * Invalid Extension Point Error\n */\nexport function invalidExtensionPointError(\n pointId: string,\n validPoints: string[],\n): HorizonSDKError {\n return new HorizonSDKError({\n code: \"INVALID_EXTENSION_POINT\",\n message: `Invalid extension point: ${pointId}`,\n details: { pointId, validPoints },\n });\n}\n\n/**\n * Signature Verification Failed Error\n */\nexport function signatureVerificationError(reason: string): HorizonSDKError {\n return new HorizonSDKError({\n code: \"SIGNATURE_VERIFICATION_FAILED\",\n message: `Signature verification failed: ${reason}`,\n details: { reason },\n });\n}\n\n/**\n * Module Load Failed Error\n */\nexport function moduleLoadError(\n appId: string,\n url: string,\n cause?: Error,\n): HorizonSDKError {\n return new HorizonSDKError({\n code: \"MODULE_LOAD_FAILED\",\n message: `Failed to load module for app: ${appId}`,\n details: { appId, url },\n cause,\n });\n}\n","/**\n * Stable Anchor IDs for Extension Placement\n *\n * These anchor IDs are guaranteed to be stable and can be used by extensions\n * to specify their menu placement using semantic anchors.\n *\n * @example\n * ```typescript\n * import { MANAGE_ANCHORS } from '@netsapiens/horizon-sdk';\n *\n * sdk.registerRoute({\n * id: 'my-crm',\n * parentPath: '/apps',\n * path: 'crm',\n * label: 'CRM',\n * placement: { after: MANAGE_ANCHORS.contacts }\n * });\n * ```\n */\n\n/**\n * Manage Menu Anchors\n * Available in both domain and no-domain contexts\n */\nexport const MANAGE_ANCHORS = {\n dashboard: \"manage-dashboard\",\n users: \"manage-users\",\n contacts: \"manage-contacts\",\n devices: \"manage-devices\",\n phoneNumbers: \"manage-phone-numbers\",\n callLogs: \"manage-call-logs\",\n voicemail: \"manage-voicemail\",\n fax: \"manage-fax\",\n settings: \"manage-settings\",\n} as const;\n\n/**\n * Platform Menu Anchors\n * Available to Admin, Super User, and Reseller scopes\n */\nexport const PLATFORM_ANCHORS = {\n dashboard: \"platform-dashboard\",\n codeManagement: \"platform-code-management\",\n configManagement: \"platform-config-management\",\n sdkManagement: \"platform-ui-sdk\",\n branding: \"platform-branding\",\n recording: \"platform-recording\",\n logsAndDiagnostics: \"platform-logs-and-diagnostics\",\n} as const;\n\n/**\n * Apps Menu Anchors\n * Extension apps typically register here\n */\nexport const APPS_ANCHORS = {\n home: \"apps-home\",\n} as const;\n\n/**\n * My Account Menu Anchors\n * User-specific settings and preferences\n */\nexport const MY_ACCOUNT_ANCHORS = {\n profile: \"my-account-profile\",\n preferences: \"my-account-preferences\",\n security: \"my-account-security\",\n} as const;\n\n/**\n * All anchor constants in one object for convenience\n */\nexport const ANCHORS = {\n manage: MANAGE_ANCHORS,\n platform: PLATFORM_ANCHORS,\n apps: APPS_ANCHORS,\n myAccount: MY_ACCOUNT_ANCHORS,\n} as const;\n\n/**\n * Type-safe anchor ID union\n */\nexport type AnchorId =\n | (typeof MANAGE_ANCHORS)[keyof typeof MANAGE_ANCHORS]\n | (typeof PLATFORM_ANCHORS)[keyof typeof PLATFORM_ANCHORS]\n | (typeof APPS_ANCHORS)[keyof typeof APPS_ANCHORS]\n | (typeof MY_ACCOUNT_ANCHORS)[keyof typeof MY_ACCOUNT_ANCHORS];\n","{\n \"name\": \"@netsapiens/horizon-sdk\",\n \"description\": \"SDK for building remote applications that integrate with NetSapiens Horizon\",\n \"version\": \"0.1.4\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"module\": \"dist/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"author\": \"NetSapiens Inc.\",\n \"license\": \"MIT\",\n \"keywords\": [\n \"netsapiens\",\n \"horizon\",\n \"remote-apps\",\n \"module-federation\",\n \"sdk\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"clean\": \"rimraf .turbo node_modules dist\",\n \"lint\": \"eslint . --max-warnings 0\",\n \"format\": \"prettier --check . --ignore-path ../../.gitignore\",\n \"format:fix\": \"prettier --write . --ignore-path ../../.gitignore\",\n \"typecheck\": \"tsc --noEmit\",\n \"validate\": \"pnpm run lint && pnpm run format && pnpm run typecheck\"\n },\n \"publishConfig\": {\n \"access\": \"restricted\"\n },\n \"peerDependencies\": {\n \"loglevel\": \"^1.9.2\",\n \"react\": \"^18.0.0 || ^19.0.0\",\n \"react-dom\": \"^18.0.0 || ^19.0.0\"\n },\n \"devDependencies\": {\n \"@repo/eslint-config\": \"workspace:*\",\n \"@repo/typescript-config\": \"workspace:*\",\n \"@types/node\": \"^22.7.5\",\n \"@types/react\": \"^19.0.0\",\n \"@types/react-dom\": \"^19.0.0\",\n \"eslint\": \"^8.57.0\",\n \"loglevel\": \"^1.9.2\",\n \"prettier\": \"3.3.3\",\n \"tsup\": \"^8.0.0\",\n \"typescript\": \"^5.4.5\"\n }\n}\n","/**\n * @netsapiens/horizon-sdk — public entry point.\n *\n * Single export surface. Remote app developers import everything from\n * `@netsapiens/horizon-sdk`.\n *\n * @example\n * import { useRemoteApp, type HorizonContext } from '@netsapiens/horizon-sdk';\n *\n * export default function MyApp(horizonContext: HorizonContext) {\n * const { sdk, user } = useRemoteApp(horizonContext, 'my-app');\n * useEffect(() => {\n * sdk.registerDynamicExtension({\n * id: 'my-app.export',\n * zone: 'page-header-actions',\n * routes: [{ pattern: '/manage/*\\/call-logs' }],\n * component: MyButton,\n * });\n * }, [sdk]);\n * return <div>Hello {user.displayName}</div>;\n * }\n */\nimport { createLogger } from \"./utils/logger\";\n\n// SDK\nexport { RemoteAppSDK, createRemoteAppSDK } from \"./RemoteAppSDK\";\nexport {\n useRemoteApp,\n useRoute,\n useRouteFromModule,\n useDynamicExtension,\n useDynamicColumn,\n usePageContext,\n // Dark-mode-aware context — use these instead of prop drilling\n HorizonContextProvider,\n useHorizonContext,\n // Reactive theme for pages and standalone extensions\n useTheme,\n // Host translations via shared i18next singleton\n useLocale,\n} from \"./hooks\";\n\n// Types — runtime contract with Horizon\nexport type {\n HorizonContext,\n HorizonUser,\n HorizonAuth,\n HorizonApiClient,\n HorizonEventBus,\n HorizonUI,\n HorizonUITemplates,\n FormPanelStep,\n ThemeTokens,\n BreadcrumbItem,\n // Routes\n RouteConfig,\n RemoteModuleConfig,\n SemanticPlacement,\n // Dynamic extensions\n ExtensionZone,\n ExtensionZoneId,\n RoutePattern,\n ExtensionContext,\n ExtensionComponentProps,\n DynamicExtensionConfig,\n DynamicColumnConfig,\n DynamicColumnDefinition,\n TableFilterBarContext,\n // Call events\n CallEvent,\n CallEventType,\n // Remote Authentication\n RemoteAuthRequest,\n RemoteAuthResponse,\n RemoteAuthError,\n RemoteAuthOptions,\n} from \"./types\";\n\n// Errors\nexport {\n HorizonSDKError,\n createHorizonSDKError,\n permissionDeniedError,\n rateLimitError,\n apiError,\n invalidExtensionPointError,\n signatureVerificationError,\n moduleLoadError,\n} from \"./errors\";\nexport type { HorizonSDKErrorCode, HorizonSDKErrorOptions } from \"./errors\";\n\n// Utilities\nexport { createLogger, setLogLevel, getLogLevel } from \"./utils/logger\";\n\n// Stable anchor IDs for menu placement\nexport {\n MANAGE_ANCHORS,\n PLATFORM_ANCHORS,\n APPS_ANCHORS,\n MY_ACCOUNT_ANCHORS,\n ANCHORS,\n type AnchorId,\n} from \"./anchors\";\n\n// @ts-ignore - This is resolved at build time by tsup\nimport pkg from \"../package.json\";\n\nexport const VERSION = pkg.version;\n\nconst log = createLogger(\"FederationSDK\");\nlog.info(`SDK v${VERSION} loaded`);\n"]}
|