@haven-team/helix-sdk 1.0.12
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 +27 -0
- package/helix-sdk.js +422 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @haven-team/helix-sdk
|
|
2
|
+
|
|
3
|
+
Client-side SDK for Helix Apps embedded via iframe. Vanilla JS, zero
|
|
4
|
+
dependencies, UMD (script tag or `require`).
|
|
5
|
+
|
|
6
|
+
- Auth context from the Helix shell (`onReady`, `getContext`, `getToken`)
|
|
7
|
+
- Authenticated API helpers (`apiGet` / `apiPost` / `apiPatch` / `apiDelete`)
|
|
8
|
+
- Automatic URL + title sync with the shell (deep links, refresh restore)
|
|
9
|
+
- Opt-in LogRocket session replay with automatic Helix-user identify
|
|
10
|
+
|
|
11
|
+
Most apps load it from the shell and never install anything:
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<script src="https://app.havenhelix.com/helix-sdk.js"></script>
|
|
15
|
+
<script>
|
|
16
|
+
const helix = new HelixSDK();
|
|
17
|
+
helix.onReady(async (ctx) => {
|
|
18
|
+
const stores = await helix.apiGet('/stores');
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The npm package exists so hosts (the Helix shell itself, apps that bundle)
|
|
24
|
+
can consume the SDK as a dependency instead of copying the file.
|
|
25
|
+
|
|
26
|
+
Canonical source and docs: [haven-team/helix-toolkit](https://github.com/haven-team/helix-toolkit)
|
|
27
|
+
(`sdk/helix-sdk.js`, `docs/`). Versioning follows the toolkit `VERSION` file.
|
package/helix-sdk.js
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helix SDK — Client-side SDK for Helix Apps embedded via iframe.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* <script src="https://your-helix-toolkit/sdk/helix-sdk.js"></script>
|
|
6
|
+
* <script>
|
|
7
|
+
* const helix = new HelixSDK();
|
|
8
|
+
* helix.onReady((ctx) => {
|
|
9
|
+
* console.log('Authenticated as:', ctx.user);
|
|
10
|
+
* console.log('Token:', ctx.token);
|
|
11
|
+
* // Use ctx.token for API calls to Helix
|
|
12
|
+
* });
|
|
13
|
+
* </script>
|
|
14
|
+
*
|
|
15
|
+
* Or as ES module:
|
|
16
|
+
* import { HelixSDK } from '@haven-team/helix-sdk';
|
|
17
|
+
*
|
|
18
|
+
* URL + title sync (auto):
|
|
19
|
+
* Once the SDK is loaded inside a Helix iframe, it automatically mirrors the
|
|
20
|
+
* inner app's URL and document.title up to the Helix shell. The shell uses
|
|
21
|
+
* that to keep the browser address bar fragment and tab title in sync with
|
|
22
|
+
* what the user is actually looking at, so deep-linking, sharing, and
|
|
23
|
+
* refresh-restore all work. No per-app code needed — any framework router
|
|
24
|
+
* (React, Angular, Vue) that uses the History API is captured automatically.
|
|
25
|
+
* Pass `{ autoBroadcast: false }` to disable.
|
|
26
|
+
*
|
|
27
|
+
* LogRocket session replay (opt-in):
|
|
28
|
+
* const helix = new HelixSDK({ logrocket: true });
|
|
29
|
+
* // or with options:
|
|
30
|
+
* const helix = new HelixSDK({ logrocket: { appId: 'lto7os/my-app' } });
|
|
31
|
+
* // or enable later:
|
|
32
|
+
* const lr = await helix.enableLogRocket({ excludeEmails: ['ai@myhavenbot.com'] });
|
|
33
|
+
*
|
|
34
|
+
* The SDK injects the LogRocket script, initializes it, and identifies the
|
|
35
|
+
* session with the Helix user (email) once auth is ready — recordings show
|
|
36
|
+
* up tied to the acting user with no per-app code. Recording is skipped on
|
|
37
|
+
* localhost unless `force: true`. Failures never break the app; the SDK
|
|
38
|
+
* logs a warning and `enableLogRocket()` resolves to null. See
|
|
39
|
+
* docs/logrocket.md for the full manual.
|
|
40
|
+
*/
|
|
41
|
+
(function (root, factory) {
|
|
42
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
43
|
+
module.exports = factory();
|
|
44
|
+
} else {
|
|
45
|
+
root.HelixSDK = factory().HelixSDK;
|
|
46
|
+
}
|
|
47
|
+
}(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
48
|
+
'use strict';
|
|
49
|
+
|
|
50
|
+
// Haven's existing LogRocket project (the Helix shell records here too).
|
|
51
|
+
// Apps with their own LogRocket project pass { logrocket: { appId } }.
|
|
52
|
+
const DEFAULT_LOGROCKET_APP_ID = 'lto7os/helix-frontend';
|
|
53
|
+
const LOGROCKET_CDN = 'https://cdn.lr-ingest.com/LogRocket.min.js';
|
|
54
|
+
|
|
55
|
+
class HelixSDK {
|
|
56
|
+
constructor(options = {}) {
|
|
57
|
+
this._ready = false;
|
|
58
|
+
this._readyCallbacks = [];
|
|
59
|
+
this._urlCallbacks = [];
|
|
60
|
+
this._context = null;
|
|
61
|
+
this._apiBase = options.apiBase || null;
|
|
62
|
+
this._instrumented = false;
|
|
63
|
+
this._suspendBroadcast = false;
|
|
64
|
+
this._lastBroadcast = null;
|
|
65
|
+
this._autoBroadcast = options.autoBroadcast !== false;
|
|
66
|
+
this._logrocket = null;
|
|
67
|
+
this._lrShouldRecord = true;
|
|
68
|
+
this._lrPromise = null;
|
|
69
|
+
|
|
70
|
+
if (options.logrocket) {
|
|
71
|
+
this.enableLogRocket(options.logrocket === true ? {} : options.logrocket);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Parse auth + restore-path from URL params (injected by Helix iframe loader)
|
|
75
|
+
const params = new URLSearchParams(window.location.search);
|
|
76
|
+
const token = params.get('helix_token');
|
|
77
|
+
const user = params.get('helix_user');
|
|
78
|
+
const appSecret = params.get('helix_app_secret');
|
|
79
|
+
const apiBase = params.get('helix_api_base');
|
|
80
|
+
const initPath = params.get('helix_init_path');
|
|
81
|
+
|
|
82
|
+
if (apiBase) this._apiBase = apiBase;
|
|
83
|
+
|
|
84
|
+
const stripAuthParams = () => {
|
|
85
|
+
for (const k of [
|
|
86
|
+
'helix_token', 'helix_user', 'helix_app_secret',
|
|
87
|
+
'helix_api_base', 'helix_init_path'
|
|
88
|
+
]) params.delete(k);
|
|
89
|
+
const clean = params.toString();
|
|
90
|
+
const path = window.location.pathname + (clean ? '?' + clean : '');
|
|
91
|
+
window.history.replaceState({}, '', path);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (token && user) {
|
|
95
|
+
this._context = { token, user, appSecret };
|
|
96
|
+
this._ready = true;
|
|
97
|
+
stripAuthParams();
|
|
98
|
+
// If the shell told us where to land (deep link / refresh), apply it
|
|
99
|
+
// before the app's own router has a chance to read location.
|
|
100
|
+
if (initPath) this._applyRestoredPath(initPath);
|
|
101
|
+
this._instrumentNavigation();
|
|
102
|
+
this._instrumentTitle();
|
|
103
|
+
this._fireReady();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Also listen for postMessage auth + shell-driven URL restore
|
|
107
|
+
window.addEventListener('message', (event) => {
|
|
108
|
+
const data = event.data;
|
|
109
|
+
if (!data || typeof data !== 'object') return;
|
|
110
|
+
|
|
111
|
+
if (data.type === 'helix-auth') {
|
|
112
|
+
this._context = {
|
|
113
|
+
token: data.token,
|
|
114
|
+
user: data.user,
|
|
115
|
+
appSecret: data.appSecret
|
|
116
|
+
};
|
|
117
|
+
this._ready = true;
|
|
118
|
+
this._instrumentNavigation();
|
|
119
|
+
this._instrumentTitle();
|
|
120
|
+
this._fireReady();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (data.type === 'helix-restore-url' && typeof data.path === 'string') {
|
|
125
|
+
this._applyRestoredPath(data.path);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Apply a path (path + search + hash) from the shell without re-broadcasting it back. */
|
|
131
|
+
_applyRestoredPath(target) {
|
|
132
|
+
try {
|
|
133
|
+
this._suspendBroadcast = true;
|
|
134
|
+
const url = new URL(target, window.location.origin);
|
|
135
|
+
const next = url.pathname + url.search + url.hash;
|
|
136
|
+
if (next !== window.location.pathname + window.location.search + window.location.hash) {
|
|
137
|
+
window.history.replaceState({}, '', next);
|
|
138
|
+
// Nudge framework routers (React/Vue/Angular) to pick up the change.
|
|
139
|
+
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
140
|
+
}
|
|
141
|
+
} catch (e) {
|
|
142
|
+
// Bad input from shell — ignore rather than break the app.
|
|
143
|
+
} finally {
|
|
144
|
+
this._suspendBroadcast = false;
|
|
145
|
+
}
|
|
146
|
+
this._fireUrlCallbacks();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Monkey-patch History API + listen for popstate/hashchange so any router we sit
|
|
150
|
+
* on top of (React, Angular, Vue, plain anchors) is auto-mirrored to the shell. */
|
|
151
|
+
_instrumentNavigation() {
|
|
152
|
+
if (this._instrumented || window.parent === window || !this._autoBroadcast) return;
|
|
153
|
+
this._instrumented = true;
|
|
154
|
+
|
|
155
|
+
const sdk = this;
|
|
156
|
+
const origPush = window.history.pushState;
|
|
157
|
+
const origReplace = window.history.replaceState;
|
|
158
|
+
window.history.pushState = function patchedPush() {
|
|
159
|
+
const result = origPush.apply(this, arguments);
|
|
160
|
+
sdk._postUrl();
|
|
161
|
+
return result;
|
|
162
|
+
};
|
|
163
|
+
window.history.replaceState = function patchedReplace() {
|
|
164
|
+
const result = origReplace.apply(this, arguments);
|
|
165
|
+
sdk._postUrl();
|
|
166
|
+
return result;
|
|
167
|
+
};
|
|
168
|
+
window.addEventListener('popstate', () => this._postUrl());
|
|
169
|
+
window.addEventListener('hashchange', () => this._postUrl());
|
|
170
|
+
|
|
171
|
+
// Broadcast once on boot so the shell can mirror the current URL even if
|
|
172
|
+
// the app never navigates.
|
|
173
|
+
setTimeout(() => this._postUrl(), 0);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Observe <title> and broadcast changes so the browser tab reflects the inner view. */
|
|
177
|
+
_instrumentTitle() {
|
|
178
|
+
if (this._titleInstrumented || window.parent === window || !this._autoBroadcast) return;
|
|
179
|
+
this._titleInstrumented = true;
|
|
180
|
+
const post = () => this._postTitle();
|
|
181
|
+
post();
|
|
182
|
+
const titleEl = document.head && document.head.querySelector('title');
|
|
183
|
+
if (titleEl && typeof MutationObserver !== 'undefined') {
|
|
184
|
+
const obs = new MutationObserver(post);
|
|
185
|
+
obs.observe(titleEl, { childList: true, characterData: true, subtree: true });
|
|
186
|
+
}
|
|
187
|
+
// Some apps swap the <title> element entirely; watch <head> for that.
|
|
188
|
+
if (document.head && typeof MutationObserver !== 'undefined') {
|
|
189
|
+
const headObs = new MutationObserver(post);
|
|
190
|
+
headObs.observe(document.head, { childList: true });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
_postUrl() {
|
|
195
|
+
if (this._suspendBroadcast) return;
|
|
196
|
+
if (window.parent === window) return;
|
|
197
|
+
const payload = {
|
|
198
|
+
type: 'helix-url',
|
|
199
|
+
path: window.location.pathname,
|
|
200
|
+
search: window.location.search,
|
|
201
|
+
hash: window.location.hash
|
|
202
|
+
};
|
|
203
|
+
const key = payload.path + payload.search + payload.hash;
|
|
204
|
+
if (key === this._lastBroadcast) return;
|
|
205
|
+
this._lastBroadcast = key;
|
|
206
|
+
try { window.parent.postMessage(payload, '*'); } catch (e) { /* ignore */ }
|
|
207
|
+
this._fireUrlCallbacks();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
_postTitle() {
|
|
211
|
+
if (window.parent === window) return;
|
|
212
|
+
const title = document.title;
|
|
213
|
+
if (!title) return;
|
|
214
|
+
if (title === this._lastTitle) return;
|
|
215
|
+
this._lastTitle = title;
|
|
216
|
+
try { window.parent.postMessage({ type: 'helix-title', title }, '*'); } catch (e) { /* ignore */ }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Push an explicit URL state (path + optional search/hash). Useful for apps
|
|
220
|
+
* that don't use the History API (e.g. server-rendered or query-only state). */
|
|
221
|
+
setUrlState(target) {
|
|
222
|
+
if (typeof target !== 'string' || !target) return;
|
|
223
|
+
try {
|
|
224
|
+
const url = new URL(target, window.location.origin);
|
|
225
|
+
const next = url.pathname + url.search + url.hash;
|
|
226
|
+
window.history.replaceState({}, '', next);
|
|
227
|
+
this._postUrl();
|
|
228
|
+
} catch (e) { /* ignore */ }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Subscribe to URL changes (inner or shell-driven). */
|
|
232
|
+
onUrlState(callback) {
|
|
233
|
+
if (typeof callback === 'function') this._urlCallbacks.push(callback);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
_fireUrlCallbacks() {
|
|
237
|
+
const state = {
|
|
238
|
+
path: window.location.pathname,
|
|
239
|
+
search: window.location.search,
|
|
240
|
+
hash: window.location.hash
|
|
241
|
+
};
|
|
242
|
+
for (const cb of this._urlCallbacks) {
|
|
243
|
+
try { cb(state); } catch (e) { console.error('HelixSDK: onUrlState callback error', e); }
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Register a callback for when auth is ready. Fires immediately if already authenticated. */
|
|
248
|
+
onReady(callback) {
|
|
249
|
+
if (this._ready && this._context) {
|
|
250
|
+
callback(this._context);
|
|
251
|
+
} else {
|
|
252
|
+
this._readyCallbacks.push(callback);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Get the current auth context, or null if not authenticated. */
|
|
257
|
+
getContext() {
|
|
258
|
+
return this._context;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Check if the SDK has authenticated. */
|
|
262
|
+
isReady() {
|
|
263
|
+
return this._ready;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Get the Helix auth token for API calls. */
|
|
267
|
+
getToken() {
|
|
268
|
+
return this._context?.token || null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Make an authenticated GET request to the Helix API. */
|
|
272
|
+
async apiGet(path) {
|
|
273
|
+
return this._apiFetch('GET', path);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Make an authenticated POST request to the Helix API. */
|
|
277
|
+
async apiPost(path, body) {
|
|
278
|
+
return this._apiFetch('POST', path, body);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Make an authenticated PATCH request to the Helix API. */
|
|
282
|
+
async apiPatch(path, body) {
|
|
283
|
+
return this._apiFetch('PATCH', path, body);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Make an authenticated DELETE request to the Helix API. */
|
|
287
|
+
async apiDelete(path) {
|
|
288
|
+
return this._apiFetch('DELETE', path);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Internal: make an authenticated fetch to the Helix API. */
|
|
292
|
+
async _apiFetch(method, path, body) {
|
|
293
|
+
if (!this._context) throw new Error('HelixSDK: not authenticated');
|
|
294
|
+
const base = this._apiBase || this._guessApiBase();
|
|
295
|
+
const url = base + (path.startsWith('/') ? path : '/' + path);
|
|
296
|
+
const opts = {
|
|
297
|
+
method,
|
|
298
|
+
headers: {
|
|
299
|
+
'Authorization': 'Bearer ' + this._context.token,
|
|
300
|
+
'Content-Type': 'application/json',
|
|
301
|
+
'Accept': 'application/json'
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
if (body && method !== 'GET') {
|
|
305
|
+
opts.body = JSON.stringify(body);
|
|
306
|
+
}
|
|
307
|
+
const res = await fetch(url, opts);
|
|
308
|
+
if (!res.ok) {
|
|
309
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
310
|
+
throw new Error(err.error || 'API request failed: ' + res.status);
|
|
311
|
+
}
|
|
312
|
+
return res.json();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Guess the Helix API base URL from the parent window origin. */
|
|
316
|
+
_guessApiBase() {
|
|
317
|
+
// If we're in an iframe, the parent origin is the Helix app
|
|
318
|
+
if (window.parent !== window) {
|
|
319
|
+
try {
|
|
320
|
+
// Can't access parent.location in cross-origin, use document.referrer
|
|
321
|
+
const referrer = document.referrer;
|
|
322
|
+
if (referrer) {
|
|
323
|
+
const url = new URL(referrer);
|
|
324
|
+
return url.origin + '/api';
|
|
325
|
+
}
|
|
326
|
+
} catch (e) { /* ignore */ }
|
|
327
|
+
}
|
|
328
|
+
return '/api';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
_fireReady() {
|
|
332
|
+
for (const cb of this._readyCallbacks) {
|
|
333
|
+
try { cb(this._context); } catch (e) { console.error('HelixSDK: onReady callback error', e); }
|
|
334
|
+
}
|
|
335
|
+
this._readyCallbacks = [];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Enable LogRocket session replay. Loads the LogRocket script (unless one
|
|
340
|
+
* is already on the page), initializes it, and identifies the session with
|
|
341
|
+
* the Helix user email once auth is ready.
|
|
342
|
+
*
|
|
343
|
+
* Options:
|
|
344
|
+
* appId — LogRocket project id (default: Haven's lto7os/helix-frontend)
|
|
345
|
+
* excludeEmails — array of emails whose sessions should not be recorded
|
|
346
|
+
* identify — false to skip the automatic LogRocket.identify call
|
|
347
|
+
* force — true to record even on localhost
|
|
348
|
+
*
|
|
349
|
+
* Resolves to the LogRocket object, or null if disabled/failed. Never
|
|
350
|
+
* rejects and never throws — session replay must not break the app.
|
|
351
|
+
*/
|
|
352
|
+
enableLogRocket(options = {}) {
|
|
353
|
+
if (this._lrPromise) return this._lrPromise;
|
|
354
|
+
this._lrPromise = this._initLogRocket(options).catch((e) => {
|
|
355
|
+
console.warn('HelixSDK: LogRocket setup failed', e);
|
|
356
|
+
return null;
|
|
357
|
+
});
|
|
358
|
+
return this._lrPromise;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** The LogRocket object once enableLogRocket has resolved, else null. */
|
|
362
|
+
getLogRocket() {
|
|
363
|
+
return this._logrocket;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async _initLogRocket(options) {
|
|
367
|
+
if (typeof document === 'undefined') return null;
|
|
368
|
+
const host = window.location && window.location.hostname;
|
|
369
|
+
const isLocal = host === 'localhost' || host === '127.0.0.1' || host === '0.0.0.0';
|
|
370
|
+
if (isLocal && !options.force) {
|
|
371
|
+
console.info('HelixSDK: LogRocket disabled on localhost (pass force: true to override)');
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const excludeEmails = options.excludeEmails || [];
|
|
376
|
+
const lr = await this._loadLogRocketScript();
|
|
377
|
+
if (!lr) return null;
|
|
378
|
+
|
|
379
|
+
lr.init(options.appId || DEFAULT_LOGROCKET_APP_ID, {
|
|
380
|
+
shouldSendData: () => this._lrShouldRecord
|
|
381
|
+
});
|
|
382
|
+
this._logrocket = lr;
|
|
383
|
+
|
|
384
|
+
// Identify with the Helix user as soon as auth context exists. If the
|
|
385
|
+
// user is excluded, flip the shouldSendData gate instead of identifying.
|
|
386
|
+
if (options.identify !== false) {
|
|
387
|
+
this.onReady((ctx) => {
|
|
388
|
+
try {
|
|
389
|
+
const email = ctx && ctx.user;
|
|
390
|
+
if (email && excludeEmails.indexOf(email) !== -1) {
|
|
391
|
+
this._lrShouldRecord = false;
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (email) lr.identify(email, { email, via: 'helix-sdk' });
|
|
395
|
+
} catch (e) {
|
|
396
|
+
console.warn('HelixSDK: LogRocket identify failed', e);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
return lr;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
_loadLogRocketScript() {
|
|
404
|
+
// Reuse an instance the page already loaded (e.g. app vendored its own).
|
|
405
|
+
if (window.LogRocket) return Promise.resolve(window.LogRocket);
|
|
406
|
+
return new Promise((resolve) => {
|
|
407
|
+
const script = document.createElement('script');
|
|
408
|
+
script.src = LOGROCKET_CDN;
|
|
409
|
+
script.async = true;
|
|
410
|
+
script.crossOrigin = 'anonymous';
|
|
411
|
+
script.onload = () => resolve(window.LogRocket || null);
|
|
412
|
+
script.onerror = () => {
|
|
413
|
+
console.warn('HelixSDK: failed to load LogRocket from ' + LOGROCKET_CDN);
|
|
414
|
+
resolve(null);
|
|
415
|
+
};
|
|
416
|
+
(document.head || document.body || document.documentElement).appendChild(script);
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return { HelixSDK };
|
|
422
|
+
}));
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haven-team/helix-sdk",
|
|
3
|
+
"version": "1.0.12",
|
|
4
|
+
"description": "Client-side SDK for Helix Apps embedded via iframe — auth context, API helpers, URL/title sync, LogRocket session replay.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "helix-sdk.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./helix-sdk.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"helix-sdk.js",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node --test --test-concurrency=1 test/*.test.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"helix",
|
|
19
|
+
"iframe",
|
|
20
|
+
"sdk"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/haven-team/helix-toolkit.git",
|
|
25
|
+
"directory": "sdk"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
}
|
|
33
|
+
}
|