@herazgo/sdk 0.1.0 → 0.2.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 +20 -0
- package/lib/facade.d.ts +25 -0
- package/lib/facade.js +137 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,6 +43,26 @@ await loadHG({ key: "hz-XXXXXXXXXX", module: "feedback" });
|
|
|
43
43
|
HG.feedback?.show();
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
## Always-safe facade (default export)
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import HG from "@herazgo/sdk";
|
|
50
|
+
HG.feedback.open(); // safe anytime
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The facade is a view of the live `window.HG` API, resolved per call. Before
|
|
54
|
+
the snippet runs it never throws: `open()` is deferred (drains when the
|
|
55
|
+
widget lands), `on()` subscriptions are buffered (the returned unsubscribe
|
|
56
|
+
cancels in either phase), `hide()`/`show()` are vacuous, `opened`/`hidden`
|
|
57
|
+
read `false`. Each pre-load call path warns once (subsequent calls stay
|
|
58
|
+
quiet); `HG.module("name")` builds the same safe stub for any other module.
|
|
59
|
+
Server-side (no window) everything is silently inert, so SSR code can call
|
|
60
|
+
freely — nothing is logged and intents are dropped.
|
|
61
|
+
|
|
62
|
+
The facade never becomes `window.HG` and never touches the loader's
|
|
63
|
+
claim/lock semantics; snippet apps can keep using typed-optional
|
|
64
|
+
`window.HG`. Multi-instance hosts that need isolated state use `createHG()`.
|
|
65
|
+
|
|
46
66
|
## Notes
|
|
47
67
|
|
|
48
68
|
- `loadHG` is idempotent; the loader itself dedupes double loads
|
package/lib/facade.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The npm-path facade (`HG.feedback.open()` is safe pre-load by contract):
|
|
3
|
+
* every member resolves the live `window.HG` API per call — delegates when
|
|
4
|
+
* the snippet has run, no-ops + one-time warn when it hasn't. Never throws,
|
|
5
|
+
* never touches the loader's claim/lock semantics (this is not `window.HG`;
|
|
6
|
+
* it's a view of it). `open()` and `on()` intents buffer and drain once the
|
|
7
|
+
* live API appears; `hide()`/`show()` pre-live are vacuous (nothing mounted).
|
|
8
|
+
* Server-side (no window) everything is silently inert — SSR calls log
|
|
9
|
+
* nothing.
|
|
10
|
+
*/
|
|
11
|
+
import type { HGModuleApi } from "./host-api-types.js";
|
|
12
|
+
export interface HGFacade {
|
|
13
|
+
/** Feedback module sub-API — safe stub until the live API exists. */
|
|
14
|
+
readonly feedback: HGModuleApi;
|
|
15
|
+
/** Safe sub-API for any other module name (cached per name). */
|
|
16
|
+
module(name: string): HGModuleApi;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Builds a facade. The default export is one long-lived instance; npm tests
|
|
20
|
+
* and multi-instance hosts use the factory to get isolated state.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createHG(): HGFacade;
|
|
23
|
+
/** The shared facade instance behind the package's default export. */
|
|
24
|
+
declare const HG: HGFacade;
|
|
25
|
+
export default HG;
|
package/lib/facade.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/** Live module API off window.HG, or undefined while absent. */
|
|
2
|
+
function liveModule(name) {
|
|
3
|
+
if (typeof window === "undefined")
|
|
4
|
+
return undefined;
|
|
5
|
+
const live = window.HG?.[name];
|
|
6
|
+
return liveObject(live) ? live : undefined;
|
|
7
|
+
}
|
|
8
|
+
function liveObject(value) {
|
|
9
|
+
return typeof value === "object" && value != null;
|
|
10
|
+
}
|
|
11
|
+
function warnOnce(slot, name, method) {
|
|
12
|
+
if (typeof window === "undefined")
|
|
13
|
+
return; // SSR: silent everywhere
|
|
14
|
+
if (slot.warned.has(method))
|
|
15
|
+
return;
|
|
16
|
+
slot.warned.add(method);
|
|
17
|
+
console.warn(`[herazgo] ${name}.${method}() before the Herazgo snippet loaded — ` +
|
|
18
|
+
(method === "open" || method === "on" ? "deferred until it loads" : "ignored"));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Builds a facade. The default export is one long-lived instance; npm tests
|
|
22
|
+
* and multi-instance hosts use the factory to get isolated state.
|
|
23
|
+
*/
|
|
24
|
+
export function createHG() {
|
|
25
|
+
const slots = new Map();
|
|
26
|
+
let poller;
|
|
27
|
+
const slotFor = (name) => {
|
|
28
|
+
const existing = slots.get(name);
|
|
29
|
+
if (existing)
|
|
30
|
+
return existing;
|
|
31
|
+
const slot = { openRequested: false, buffered: [], warned: new Set() };
|
|
32
|
+
slots.set(name, slot);
|
|
33
|
+
return slot;
|
|
34
|
+
};
|
|
35
|
+
/** Drains buffered intents once the live API exists; stops itself when no
|
|
36
|
+
* slot has pending state left. */
|
|
37
|
+
const ensurePoller = () => {
|
|
38
|
+
poller ?? (poller = setInterval(() => {
|
|
39
|
+
let pending = false;
|
|
40
|
+
for (const [name, slot] of slots) {
|
|
41
|
+
const live = liveModule(name);
|
|
42
|
+
if (live == null) {
|
|
43
|
+
pending = pending || slot.openRequested || slot.buffered.length > 0;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (slot.openRequested) {
|
|
47
|
+
slot.openRequested = false;
|
|
48
|
+
live.open();
|
|
49
|
+
}
|
|
50
|
+
for (const entry of slot.buffered.splice(0)) {
|
|
51
|
+
entry.unsub = live.on(entry.type, entry.cb);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!pending) {
|
|
55
|
+
clearInterval(poller);
|
|
56
|
+
poller = undefined;
|
|
57
|
+
}
|
|
58
|
+
}, 200));
|
|
59
|
+
};
|
|
60
|
+
/** The safe sub-API for one module name: per-call delegation. */
|
|
61
|
+
const apiFor = (name) => ({
|
|
62
|
+
get hidden() {
|
|
63
|
+
return liveModule(name)?.hidden ?? false;
|
|
64
|
+
},
|
|
65
|
+
get opened() {
|
|
66
|
+
return liveModule(name)?.opened ?? false;
|
|
67
|
+
},
|
|
68
|
+
hide() {
|
|
69
|
+
const live = liveModule(name);
|
|
70
|
+
if (live != null)
|
|
71
|
+
return live.hide();
|
|
72
|
+
warnOnce(slotFor(name), name, "hide");
|
|
73
|
+
},
|
|
74
|
+
show() {
|
|
75
|
+
const live = liveModule(name);
|
|
76
|
+
if (live != null)
|
|
77
|
+
return live.show();
|
|
78
|
+
// pre-live there is nothing suppressed that hide() would have set
|
|
79
|
+
},
|
|
80
|
+
open() {
|
|
81
|
+
if (typeof window === "undefined")
|
|
82
|
+
return; // SSR: intents are lost
|
|
83
|
+
const live = liveModule(name);
|
|
84
|
+
const slot = slotFor(name);
|
|
85
|
+
if (live != null)
|
|
86
|
+
return live.open();
|
|
87
|
+
warnOnce(slot, name, "open");
|
|
88
|
+
slot.openRequested = true;
|
|
89
|
+
ensurePoller();
|
|
90
|
+
},
|
|
91
|
+
close() {
|
|
92
|
+
// last call wins: a close pre-live also cancels a buffered open
|
|
93
|
+
slotFor(name).openRequested = false;
|
|
94
|
+
liveModule(name)?.close();
|
|
95
|
+
},
|
|
96
|
+
on(type, cb) {
|
|
97
|
+
if (typeof window === "undefined")
|
|
98
|
+
return () => { }; // SSR: silent no-op
|
|
99
|
+
const slot = slotFor(name);
|
|
100
|
+
const live = liveModule(name);
|
|
101
|
+
if (live != null)
|
|
102
|
+
return live.on(type, cb);
|
|
103
|
+
const entry = { type, cb };
|
|
104
|
+
slot.buffered.push(entry);
|
|
105
|
+
warnOnce(slot, name, "on");
|
|
106
|
+
ensurePoller();
|
|
107
|
+
return () => {
|
|
108
|
+
// cancel pre-drain or live subscription, whichever phase we're in
|
|
109
|
+
if (entry.unsub != null) {
|
|
110
|
+
entry.unsub();
|
|
111
|
+
entry.unsub = undefined;
|
|
112
|
+
}
|
|
113
|
+
const at = slot.buffered.indexOf(entry);
|
|
114
|
+
if (at >= 0)
|
|
115
|
+
slot.buffered.splice(at, 1);
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
const feedbackCache = apiFor("feedback");
|
|
120
|
+
const otherCache = new Map();
|
|
121
|
+
return {
|
|
122
|
+
get feedback() {
|
|
123
|
+
return feedbackCache;
|
|
124
|
+
},
|
|
125
|
+
module(name) {
|
|
126
|
+
const cached = otherCache.get(name);
|
|
127
|
+
if (cached)
|
|
128
|
+
return cached;
|
|
129
|
+
const api = apiFor(name);
|
|
130
|
+
otherCache.set(name, api);
|
|
131
|
+
return api;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** The shared facade instance behind the package's default export. */
|
|
136
|
+
const HG = createHG();
|
|
137
|
+
export default HG;
|
package/lib/index.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ export { loadHG, seedHG } from "./load-hg.js";
|
|
|
2
2
|
export type { HGSeedEntry, LoadHGOptions } from "./load-hg.js";
|
|
3
3
|
import type { HGGlobal } from "./host-api-types.js";
|
|
4
4
|
export type { HGGlobal, HGModuleApi } from "./host-api-types.js";
|
|
5
|
+
import HG from "./facade.js";
|
|
6
|
+
export type { HGFacade } from "./facade.js";
|
|
7
|
+
export { createHG } from "./facade.js";
|
|
8
|
+
export default HG;
|
|
5
9
|
declare global {
|
|
6
10
|
interface Window {
|
|
7
11
|
/** Herazgo host API, set by the embeddable script (hg.js). */
|
package/lib/index.js
CHANGED
package/package.json
CHANGED