@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.
- package/README.md +643 -0
- package/dist/index.cjs +486 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +861 -0
- package/dist/index.d.ts +861 -0
- package/dist/index.js +453 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import loglevel from 'loglevel';
|
|
2
|
+
import { createContext, useState, useEffect, useMemo, createElement, useContext, useRef } from 'react';
|
|
3
|
+
|
|
4
|
+
// src/utils/logger.ts
|
|
5
|
+
var isDevelopment = process.env.NODE_ENV === "development";
|
|
6
|
+
loglevel.setDefaultLevel(isDevelopment ? loglevel.levels.DEBUG : loglevel.levels.WARN);
|
|
7
|
+
var createLogger = (namespace) => {
|
|
8
|
+
const moduleLogger = loglevel.getLogger(`HorizonSDK:${namespace}`);
|
|
9
|
+
if (isDevelopment) {
|
|
10
|
+
moduleLogger.setLevel(loglevel.levels.DEBUG);
|
|
11
|
+
} else {
|
|
12
|
+
moduleLogger.setLevel(loglevel.levels.WARN);
|
|
13
|
+
}
|
|
14
|
+
return moduleLogger;
|
|
15
|
+
};
|
|
16
|
+
var logger = loglevel.getLogger("horizon-sdk");
|
|
17
|
+
var setLogLevel = (level) => {
|
|
18
|
+
loglevel.setLevel(level);
|
|
19
|
+
};
|
|
20
|
+
var getLogLevel = () => {
|
|
21
|
+
return loglevel.getLevel();
|
|
22
|
+
};
|
|
23
|
+
if (typeof window !== "undefined") {
|
|
24
|
+
window.__horizonSDKLogger__ = logger;
|
|
25
|
+
window.__setHorizonSDKLogLevel__ = setLogLevel;
|
|
26
|
+
if (isDevelopment) {
|
|
27
|
+
logger.info(
|
|
28
|
+
'Horizon SDK logger initialized. Use window.__setHorizonSDKLogLevel__("DEBUG") to adjust log level.'
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/RemoteAppSDK.ts
|
|
34
|
+
var log = createLogger("RemoteAppSDK");
|
|
35
|
+
var RemoteAppSDK = class {
|
|
36
|
+
eventBus;
|
|
37
|
+
appId;
|
|
38
|
+
// Registrations are tracked separately so cleanup() emits the right
|
|
39
|
+
// unregister event for each. (A single set keyed by id-only would let dynamic
|
|
40
|
+
// registrations leak — they're unregistered on a different channel.)
|
|
41
|
+
routes = /* @__PURE__ */ new Set();
|
|
42
|
+
dynamicExtensions = /* @__PURE__ */ new Set();
|
|
43
|
+
dynamicColumns = /* @__PURE__ */ new Set();
|
|
44
|
+
constructor(eventBus, appId) {
|
|
45
|
+
this.eventBus = eventBus;
|
|
46
|
+
this.appId = appId;
|
|
47
|
+
log.debug(`Initialized for app: ${appId}`);
|
|
48
|
+
}
|
|
49
|
+
// -------------------------------------------------------------------------
|
|
50
|
+
// Routes
|
|
51
|
+
// -------------------------------------------------------------------------
|
|
52
|
+
async registerRoute(config) {
|
|
53
|
+
const route = { ...config, appId: this.appId };
|
|
54
|
+
this.eventBus.emit("route:register", route);
|
|
55
|
+
this.routes.add(route.id);
|
|
56
|
+
log.info(`[${this.appId}] Route registered: ${route.id} at ${route.parentPath}/${route.path}`);
|
|
57
|
+
}
|
|
58
|
+
unregisterRoute(routeId) {
|
|
59
|
+
this.eventBus.emit("route:unregister", { id: routeId });
|
|
60
|
+
this.routes.delete(routeId);
|
|
61
|
+
log.info(`[${this.appId}] Route unregistered: ${routeId}`);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Convenience: load a component out of a federated module's webpack
|
|
65
|
+
* container and register it as a route in one step. Useful when the route
|
|
66
|
+
* component lives in a sibling exposed module rather than the entry App.
|
|
67
|
+
*/
|
|
68
|
+
async registerRouteFromModule(routeConfig, moduleConfig) {
|
|
69
|
+
const container = window[moduleConfig.scope];
|
|
70
|
+
if (!container) {
|
|
71
|
+
throw new Error(`Remote container not found: ${moduleConfig.scope}`);
|
|
72
|
+
}
|
|
73
|
+
const factory = await container.get(moduleConfig.module);
|
|
74
|
+
const moduleExports = factory();
|
|
75
|
+
const component = moduleExports.default ?? moduleExports;
|
|
76
|
+
await this.registerRoute({ ...routeConfig, component });
|
|
77
|
+
}
|
|
78
|
+
// -------------------------------------------------------------------------
|
|
79
|
+
// Dynamic extensions (pattern-based UI injection)
|
|
80
|
+
// -------------------------------------------------------------------------
|
|
81
|
+
registerDynamicExtension(config) {
|
|
82
|
+
const extension = { ...config, appId: this.appId };
|
|
83
|
+
this.eventBus.emit("dynamic-extension:register", extension);
|
|
84
|
+
this.dynamicExtensions.add(extension.id);
|
|
85
|
+
log.info(
|
|
86
|
+
`[${this.appId}] Dynamic extension registered: ${extension.id} \u2192 zone ${extension.zone}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
unregisterDynamicExtension(extensionId) {
|
|
90
|
+
this.eventBus.emit("dynamic-extension:unregister", { id: extensionId });
|
|
91
|
+
this.dynamicExtensions.delete(extensionId);
|
|
92
|
+
log.info(`[${this.appId}] Dynamic extension unregistered: ${extensionId}`);
|
|
93
|
+
}
|
|
94
|
+
// -------------------------------------------------------------------------
|
|
95
|
+
// Dynamic columns (table column injection)
|
|
96
|
+
// -------------------------------------------------------------------------
|
|
97
|
+
registerDynamicColumn(config) {
|
|
98
|
+
const column = { ...config, appId: this.appId };
|
|
99
|
+
this.eventBus.emit("dynamic-column:register", column);
|
|
100
|
+
this.dynamicColumns.add(column.id);
|
|
101
|
+
log.info(`[${this.appId}] Dynamic column registered: ${column.id} \u2192 zone ${column.zone}`);
|
|
102
|
+
}
|
|
103
|
+
unregisterDynamicColumn(columnId) {
|
|
104
|
+
this.eventBus.emit("dynamic-column:unregister", { id: columnId });
|
|
105
|
+
this.dynamicColumns.delete(columnId);
|
|
106
|
+
log.info(`[${this.appId}] Dynamic column unregistered: ${columnId}`);
|
|
107
|
+
}
|
|
108
|
+
// -------------------------------------------------------------------------
|
|
109
|
+
// Lifecycle
|
|
110
|
+
// -------------------------------------------------------------------------
|
|
111
|
+
/**
|
|
112
|
+
* Unregister everything this SDK instance has registered. Call from your
|
|
113
|
+
* remote app's unmount/cleanup path — `useRemoteApp` does this for you.
|
|
114
|
+
*/
|
|
115
|
+
cleanup() {
|
|
116
|
+
log.info(
|
|
117
|
+
`[${this.appId}] Cleanup: ${this.routes.size} routes, ${this.dynamicExtensions.size} extensions, ${this.dynamicColumns.size} columns`
|
|
118
|
+
);
|
|
119
|
+
this.routes.forEach((id) => this.eventBus.emit("route:unregister", { id }));
|
|
120
|
+
this.dynamicExtensions.forEach(
|
|
121
|
+
(id) => this.eventBus.emit("dynamic-extension:unregister", { id })
|
|
122
|
+
);
|
|
123
|
+
this.dynamicColumns.forEach((id) => this.eventBus.emit("dynamic-column:unregister", { id }));
|
|
124
|
+
this.routes.clear();
|
|
125
|
+
this.dynamicExtensions.clear();
|
|
126
|
+
this.dynamicColumns.clear();
|
|
127
|
+
}
|
|
128
|
+
getAppId() {
|
|
129
|
+
return this.appId;
|
|
130
|
+
}
|
|
131
|
+
getRegisteredRoutes() {
|
|
132
|
+
return Array.from(this.routes);
|
|
133
|
+
}
|
|
134
|
+
getRegisteredDynamicExtensions() {
|
|
135
|
+
return Array.from(this.dynamicExtensions);
|
|
136
|
+
}
|
|
137
|
+
getRegisteredDynamicColumns() {
|
|
138
|
+
return Array.from(this.dynamicColumns);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
function createRemoteAppSDK(eventBus, appId) {
|
|
142
|
+
return new RemoteAppSDK(eventBus, appId);
|
|
143
|
+
}
|
|
144
|
+
var HorizonContextReact = createContext(null);
|
|
145
|
+
function HorizonContextProvider({
|
|
146
|
+
context,
|
|
147
|
+
children
|
|
148
|
+
}) {
|
|
149
|
+
const [theme, setTheme] = useState(context.theme ?? "light");
|
|
150
|
+
const [locale, setLocale] = useState(context.locale ?? "en-US");
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
if (!context.eventBus) return;
|
|
153
|
+
const themeHandler = (data) => {
|
|
154
|
+
const payload = data;
|
|
155
|
+
if (payload?.theme === "light" || payload?.theme === "dark") setTheme(payload.theme);
|
|
156
|
+
};
|
|
157
|
+
const localeHandler = (data) => {
|
|
158
|
+
const payload = data;
|
|
159
|
+
if (payload?.locale) setLocale(payload.locale);
|
|
160
|
+
};
|
|
161
|
+
context.eventBus.on("theme:changed", themeHandler);
|
|
162
|
+
context.eventBus.on("locale:changed", localeHandler);
|
|
163
|
+
return () => {
|
|
164
|
+
context.eventBus.off("theme:changed", themeHandler);
|
|
165
|
+
context.eventBus.off("locale:changed", localeHandler);
|
|
166
|
+
};
|
|
167
|
+
}, [context.eventBus]);
|
|
168
|
+
const liveContext = useMemo(
|
|
169
|
+
() => ({ ...context, theme, locale }),
|
|
170
|
+
// Intentionally omit `context` — eventBus is stable and subscriptions keep
|
|
171
|
+
// theme/locale live. context spread gives access to t, user, api, navigate, etc.
|
|
172
|
+
[theme, locale]
|
|
173
|
+
);
|
|
174
|
+
return createElement(HorizonContextReact.Provider, { value: liveContext }, children);
|
|
175
|
+
}
|
|
176
|
+
function useHorizonContext() {
|
|
177
|
+
const ctx = useContext(HorizonContextReact);
|
|
178
|
+
if (!ctx) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
"[Horizon SDK] useHorizonContext() must be called inside a component wrapped by HorizonContextProvider."
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return ctx;
|
|
184
|
+
}
|
|
185
|
+
function useTheme(eventBus, initialTheme) {
|
|
186
|
+
const providerTheme = useContext(HorizonContextReact)?.theme;
|
|
187
|
+
const [localTheme, setLocalTheme] = useState(
|
|
188
|
+
providerTheme ?? initialTheme ?? "light"
|
|
189
|
+
);
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (providerTheme !== void 0) return;
|
|
192
|
+
if (!eventBus) return;
|
|
193
|
+
const handler = (data) => {
|
|
194
|
+
const payload = data;
|
|
195
|
+
if (payload?.theme === "light" || payload?.theme === "dark") {
|
|
196
|
+
setLocalTheme(payload.theme);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
eventBus.on("theme:changed", handler);
|
|
200
|
+
return () => {
|
|
201
|
+
eventBus.off("theme:changed", handler);
|
|
202
|
+
};
|
|
203
|
+
}, [providerTheme, eventBus]);
|
|
204
|
+
return { theme: providerTheme ?? localTheme };
|
|
205
|
+
}
|
|
206
|
+
function useLocale() {
|
|
207
|
+
const ctx = useContext(HorizonContextReact);
|
|
208
|
+
return {
|
|
209
|
+
t: ctx?.t,
|
|
210
|
+
locale: ctx?.locale ?? "en-US"
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function useRemoteApp(horizonContext, appId) {
|
|
214
|
+
const sdkRef = useRef(null);
|
|
215
|
+
if (!sdkRef.current) {
|
|
216
|
+
sdkRef.current = createRemoteAppSDK(horizonContext.eventBus, appId);
|
|
217
|
+
}
|
|
218
|
+
const sdk = sdkRef.current;
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
return () => sdk.cleanup();
|
|
221
|
+
}, [sdk]);
|
|
222
|
+
return { sdk, ...horizonContext };
|
|
223
|
+
}
|
|
224
|
+
function useRoute(eventBus, appId, config) {
|
|
225
|
+
const sdk = useMemo(() => createRemoteAppSDK(eventBus, appId), [eventBus, appId]);
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
void sdk.registerRoute(config);
|
|
228
|
+
return () => sdk.unregisterRoute(config.id);
|
|
229
|
+
}, [sdk, config]);
|
|
230
|
+
return sdk;
|
|
231
|
+
}
|
|
232
|
+
function useRouteFromModule(eventBus, appId, routeConfig, moduleConfig) {
|
|
233
|
+
const sdk = useMemo(() => createRemoteAppSDK(eventBus, appId), [eventBus, appId]);
|
|
234
|
+
const [loading, setLoading] = useState(true);
|
|
235
|
+
const [error, setError] = useState(null);
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
let mounted = true;
|
|
238
|
+
sdk.registerRouteFromModule(routeConfig, moduleConfig).then(() => {
|
|
239
|
+
if (mounted) setLoading(false);
|
|
240
|
+
}).catch((err) => {
|
|
241
|
+
if (mounted) {
|
|
242
|
+
setError(err);
|
|
243
|
+
setLoading(false);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
return () => {
|
|
247
|
+
mounted = false;
|
|
248
|
+
sdk.unregisterRoute(routeConfig.id);
|
|
249
|
+
};
|
|
250
|
+
}, [sdk, moduleConfig, routeConfig]);
|
|
251
|
+
return { loading, error, sdk };
|
|
252
|
+
}
|
|
253
|
+
function useDynamicExtension(eventBus, appId, config) {
|
|
254
|
+
const sdk = useMemo(() => createRemoteAppSDK(eventBus, appId), [eventBus, appId]);
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
sdk.registerDynamicExtension(config);
|
|
257
|
+
return () => sdk.unregisterDynamicExtension(config.id);
|
|
258
|
+
}, [sdk, config]);
|
|
259
|
+
return sdk;
|
|
260
|
+
}
|
|
261
|
+
function useDynamicColumn(eventBus, appId, config) {
|
|
262
|
+
const sdk = useMemo(() => createRemoteAppSDK(eventBus, appId), [eventBus, appId]);
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
sdk.registerDynamicColumn(config);
|
|
265
|
+
return () => sdk.unregisterDynamicColumn(config.id);
|
|
266
|
+
}, [sdk, config]);
|
|
267
|
+
return sdk;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/errors/HorizonSDKError.ts
|
|
271
|
+
var HorizonSDKError = class _HorizonSDKError extends Error {
|
|
272
|
+
code;
|
|
273
|
+
details;
|
|
274
|
+
cause;
|
|
275
|
+
statusCode;
|
|
276
|
+
timestamp;
|
|
277
|
+
constructor(options) {
|
|
278
|
+
super(options.message);
|
|
279
|
+
this.name = "HorizonSDKError";
|
|
280
|
+
this.code = options.code;
|
|
281
|
+
this.details = options.details;
|
|
282
|
+
this.cause = options.cause;
|
|
283
|
+
this.statusCode = options.statusCode;
|
|
284
|
+
this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
285
|
+
if (Error.captureStackTrace) {
|
|
286
|
+
Error.captureStackTrace(this, _HorizonSDKError);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Check if error is a HorizonSDKError
|
|
291
|
+
*/
|
|
292
|
+
static isHorizonSDKError(error) {
|
|
293
|
+
return error instanceof _HorizonSDKError;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Get user-friendly error message
|
|
297
|
+
*/
|
|
298
|
+
getUserMessage() {
|
|
299
|
+
switch (this.code) {
|
|
300
|
+
case "PERMISSION_DENIED":
|
|
301
|
+
return "You do not have permission to access this resource.";
|
|
302
|
+
case "RATE_LIMIT_EXCEEDED":
|
|
303
|
+
return "Too many requests. Please try again later.";
|
|
304
|
+
case "INVALID_MESSAGE":
|
|
305
|
+
return "Invalid message format.";
|
|
306
|
+
case "SIGNATURE_VERIFICATION_FAILED":
|
|
307
|
+
return "Message signature verification failed.";
|
|
308
|
+
case "API_ERROR":
|
|
309
|
+
return "An error occurred while communicating with the API.";
|
|
310
|
+
case "NETWORK_ERROR":
|
|
311
|
+
return "Network error. Please check your connection.";
|
|
312
|
+
case "INVALID_EXTENSION_POINT":
|
|
313
|
+
return "Invalid extension point.";
|
|
314
|
+
case "INVALID_CONFIGURATION":
|
|
315
|
+
return "Invalid configuration provided.";
|
|
316
|
+
case "APP_NOT_FOUND":
|
|
317
|
+
return "Application not found.";
|
|
318
|
+
case "MODULE_LOAD_FAILED":
|
|
319
|
+
return "Failed to load application module.";
|
|
320
|
+
case "INITIALIZATION_FAILED":
|
|
321
|
+
return "Application initialization failed.";
|
|
322
|
+
default:
|
|
323
|
+
return "An unknown error occurred.";
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Serialize error to JSON
|
|
328
|
+
*/
|
|
329
|
+
toJSON() {
|
|
330
|
+
return {
|
|
331
|
+
name: this.name,
|
|
332
|
+
code: this.code,
|
|
333
|
+
message: this.message,
|
|
334
|
+
details: this.details,
|
|
335
|
+
statusCode: this.statusCode,
|
|
336
|
+
timestamp: this.timestamp,
|
|
337
|
+
stack: this.stack
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Get formatted error message for logging
|
|
342
|
+
*/
|
|
343
|
+
toLogString() {
|
|
344
|
+
let log3 = `[${this.code}] ${this.message}`;
|
|
345
|
+
if (this.statusCode) {
|
|
346
|
+
log3 += ` (status: ${this.statusCode})`;
|
|
347
|
+
}
|
|
348
|
+
if (this.details) {
|
|
349
|
+
log3 += `
|
|
350
|
+
Details: ${JSON.stringify(this.details, null, 2)}`;
|
|
351
|
+
}
|
|
352
|
+
if (this.cause) {
|
|
353
|
+
log3 += `
|
|
354
|
+
Caused by: ${this.cause.message}`;
|
|
355
|
+
}
|
|
356
|
+
return log3;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
function createHorizonSDKError(code, message, details, cause) {
|
|
360
|
+
return new HorizonSDKError({ code, message, details, cause });
|
|
361
|
+
}
|
|
362
|
+
function permissionDeniedError(resource, details) {
|
|
363
|
+
return new HorizonSDKError({
|
|
364
|
+
code: "PERMISSION_DENIED",
|
|
365
|
+
message: `Permission denied: ${resource}`,
|
|
366
|
+
details,
|
|
367
|
+
statusCode: 403
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
function rateLimitError(resetTime, details) {
|
|
371
|
+
return new HorizonSDKError({
|
|
372
|
+
code: "RATE_LIMIT_EXCEEDED",
|
|
373
|
+
message: `Rate limit exceeded. Resets at ${new Date(resetTime).toISOString()}`,
|
|
374
|
+
details: { resetTime, ...details },
|
|
375
|
+
statusCode: 429
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function apiError(message, statusCode, details, cause) {
|
|
379
|
+
return new HorizonSDKError({
|
|
380
|
+
code: "API_ERROR",
|
|
381
|
+
message,
|
|
382
|
+
details,
|
|
383
|
+
cause,
|
|
384
|
+
statusCode
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
function invalidExtensionPointError(pointId, validPoints) {
|
|
388
|
+
return new HorizonSDKError({
|
|
389
|
+
code: "INVALID_EXTENSION_POINT",
|
|
390
|
+
message: `Invalid extension point: ${pointId}`,
|
|
391
|
+
details: { pointId, validPoints }
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
function signatureVerificationError(reason) {
|
|
395
|
+
return new HorizonSDKError({
|
|
396
|
+
code: "SIGNATURE_VERIFICATION_FAILED",
|
|
397
|
+
message: `Signature verification failed: ${reason}`,
|
|
398
|
+
details: { reason }
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
function moduleLoadError(appId, url, cause) {
|
|
402
|
+
return new HorizonSDKError({
|
|
403
|
+
code: "MODULE_LOAD_FAILED",
|
|
404
|
+
message: `Failed to load module for app: ${appId}`,
|
|
405
|
+
details: { appId, url },
|
|
406
|
+
cause
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// src/anchors.ts
|
|
411
|
+
var MANAGE_ANCHORS = {
|
|
412
|
+
dashboard: "manage-dashboard",
|
|
413
|
+
users: "manage-users",
|
|
414
|
+
contacts: "manage-contacts",
|
|
415
|
+
devices: "manage-devices",
|
|
416
|
+
phoneNumbers: "manage-phone-numbers",
|
|
417
|
+
callLogs: "manage-call-logs",
|
|
418
|
+
voicemail: "manage-voicemail",
|
|
419
|
+
fax: "manage-fax",
|
|
420
|
+
settings: "manage-settings"
|
|
421
|
+
};
|
|
422
|
+
var PLATFORM_ANCHORS = {
|
|
423
|
+
dashboard: "platform-dashboard",
|
|
424
|
+
codeManagement: "platform-code-management",
|
|
425
|
+
configManagement: "platform-config-management",
|
|
426
|
+
sdkManagement: "platform-ui-sdk",
|
|
427
|
+
branding: "platform-branding",
|
|
428
|
+
recording: "platform-recording",
|
|
429
|
+
logsAndDiagnostics: "platform-logs-and-diagnostics"
|
|
430
|
+
};
|
|
431
|
+
var APPS_ANCHORS = {
|
|
432
|
+
home: "apps-home"
|
|
433
|
+
};
|
|
434
|
+
var MY_ACCOUNT_ANCHORS = {
|
|
435
|
+
profile: "my-account-profile",
|
|
436
|
+
preferences: "my-account-preferences",
|
|
437
|
+
security: "my-account-security"
|
|
438
|
+
};
|
|
439
|
+
var ANCHORS = {
|
|
440
|
+
manage: MANAGE_ANCHORS,
|
|
441
|
+
platform: PLATFORM_ANCHORS,
|
|
442
|
+
apps: APPS_ANCHORS,
|
|
443
|
+
myAccount: MY_ACCOUNT_ANCHORS
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
// src/index.ts
|
|
447
|
+
var VERSION = "1.0.0";
|
|
448
|
+
var log2 = createLogger("FederationSDK");
|
|
449
|
+
log2.info(`SDK v${VERSION} loaded`);
|
|
450
|
+
|
|
451
|
+
export { ANCHORS, APPS_ANCHORS, HorizonContextProvider, HorizonSDKError, MANAGE_ANCHORS, MY_ACCOUNT_ANCHORS, PLATFORM_ANCHORS, RemoteAppSDK, VERSION, apiError, createHorizonSDKError, createLogger, createRemoteAppSDK, getLogLevel, invalidExtensionPointError, moduleLoadError, permissionDeniedError, rateLimitError, setLogLevel, signatureVerificationError, useDynamicColumn, useDynamicExtension, useHorizonContext, useLocale, useRemoteApp, useRoute, useRouteFromModule, useTheme };
|
|
452
|
+
//# sourceMappingURL=index.js.map
|
|
453
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/logger.ts","../src/RemoteAppSDK.ts","../src/hooks.tsx","../src/errors/HorizonSDKError.ts","../src/anchors.ts","../src/index.ts"],"names":["log"],"mappings":"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","file":"index.js","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 * @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\nexport const VERSION = '1.0.0';\n\nconst log = createLogger('FederationSDK');\nlog.info(`SDK v${VERSION} loaded`);\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@netsapiens/horizon-sdk",
|
|
3
|
+
"description": "SDK for building remote applications that integrate with NetSapiens Horizon",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"author": "NetSapiens Inc.",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"netsapiens",
|
|
16
|
+
"horizon",
|
|
17
|
+
"remote-apps",
|
|
18
|
+
"module-federation",
|
|
19
|
+
"sdk"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
24
|
+
"clean": "rimraf .turbo node_modules dist",
|
|
25
|
+
"lint": "eslint . --max-warnings 0",
|
|
26
|
+
"format": "prettier --check . --ignore-path ../../.gitignore",
|
|
27
|
+
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"validate": "pnpm run lint && pnpm run format && pnpm run typecheck"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/netsapiens/netsapiens-monorepo.git",
|
|
34
|
+
"directory": "packages/horizon-sdk"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "restricted"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"loglevel": "^1.9.2",
|
|
41
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
42
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@repo/eslint-config": "workspace:*",
|
|
46
|
+
"@repo/typescript-config": "workspace:*",
|
|
47
|
+
"@types/node": "^22.7.5",
|
|
48
|
+
"@types/react": "^19.0.0",
|
|
49
|
+
"@types/react-dom": "^19.0.0",
|
|
50
|
+
"eslint": "^8.57.0",
|
|
51
|
+
"loglevel": "^1.9.2",
|
|
52
|
+
"prettier": "3.3.3",
|
|
53
|
+
"tsup": "^8.0.0",
|
|
54
|
+
"typescript": "^5.4.5"
|
|
55
|
+
}
|
|
56
|
+
}
|