@discloai/core 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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +218 -0
  3. package/dist/discloai.min.js +1 -0
  4. package/dist/index.d.mts +152 -0
  5. package/dist/index.d.ts +152 -0
  6. package/dist/index.js +795 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/index.mjs +764 -0
  9. package/dist/index.mjs.map +1 -0
  10. package/package.json +58 -0
  11. package/src/__tests__/audit.test.ts +117 -0
  12. package/src/__tests__/init.test.ts +49 -0
  13. package/src/__tests__/wcag.test.ts +260 -0
  14. package/src/audit.ts +155 -0
  15. package/src/components/AIContentLabel.ts +108 -0
  16. package/src/components/BiometricNotice.ts +82 -0
  17. package/src/components/ChatbotDisclosure.ts +188 -0
  18. package/src/components/DeepfakeLabel.ts +123 -0
  19. package/src/config.ts +191 -0
  20. package/src/i18n/bg.json +9 -0
  21. package/src/i18n/cs.json +9 -0
  22. package/src/i18n/da.json +9 -0
  23. package/src/i18n/de.json +9 -0
  24. package/src/i18n/el.json +9 -0
  25. package/src/i18n/en.json +9 -0
  26. package/src/i18n/es.json +9 -0
  27. package/src/i18n/et.json +9 -0
  28. package/src/i18n/fi.json +9 -0
  29. package/src/i18n/fr.json +9 -0
  30. package/src/i18n/ga.json +9 -0
  31. package/src/i18n/hr.json +9 -0
  32. package/src/i18n/hu.json +9 -0
  33. package/src/i18n/index.ts +145 -0
  34. package/src/i18n/it.json +9 -0
  35. package/src/i18n/lt.json +9 -0
  36. package/src/i18n/lv.json +9 -0
  37. package/src/i18n/mt.json +9 -0
  38. package/src/i18n/nl.json +9 -0
  39. package/src/i18n/pl.json +9 -0
  40. package/src/i18n/pt.json +9 -0
  41. package/src/i18n/ro.json +9 -0
  42. package/src/i18n/sk.json +9 -0
  43. package/src/i18n/sl.json +9 -0
  44. package/src/i18n/sv.json +9 -0
  45. package/src/index.ts +19 -0
  46. package/src/init.ts +56 -0
  47. package/src/vendors.ts +29 -0
  48. package/src/version.ts +1 -0
  49. package/src/wcag.ts +46 -0
package/src/init.ts ADDED
@@ -0,0 +1,56 @@
1
+ // @lighthouse-budget: TTI delta < 100ms
2
+
3
+ import { resolveConfig } from "./config.js";
4
+ import type { DiscloAIConfig, DiscloAIInitOptions } from "./config.js";
5
+ import { renderAIContentLabel } from "./components/AIContentLabel.js";
6
+ import { renderDeepfakeLabel } from "./components/DeepfakeLabel.js";
7
+ import { renderChatbotDisclosure } from "./components/ChatbotDisclosure.js";
8
+ import { renderBiometricNotice } from "./components/BiometricNotice.js";
9
+
10
+ export { resolveLocale, t, SUPPORTED_LOCALES } from "./i18n/index.js";
11
+
12
+ /**
13
+ * Initialise DiscloAI EU AI Act Article 50 disclosure widgets.
14
+ *
15
+ * Non-blocking contract:
16
+ * - init() itself is synchronous and returns immediately
17
+ * - Config resolution (remote fetch + merge) is fire-and-forget
18
+ * - Component injection is always deferred — never blocks DOMContentLoaded
19
+ *
20
+ * @param options - Init options including siteId and per-component config
21
+ */
22
+ export function init(options?: Partial<DiscloAIInitOptions>): void {
23
+ if (!options?.siteId) {
24
+ console.warn("[DiscloAI] siteId is required");
25
+ return;
26
+ }
27
+
28
+ const cspNonce = options.cspNonce;
29
+
30
+ // Fire-and-forget: resolveConfig is async (remote fetch + merge).
31
+ // Using void to explicitly discard the promise — never block render.
32
+ void resolveConfig(options as DiscloAIInitOptions).then((config) => {
33
+ injectComponents(config, cspNonce);
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Inject configured disclosure components after config is resolved.
39
+ * Always called asynchronously — never on the critical render path.
40
+ */
41
+ function injectComponents(config: DiscloAIConfig, cspNonce?: string): void {
42
+ const { siteId } = config;
43
+
44
+ if (config.aiContentLabel?.enabled !== false) {
45
+ renderAIContentLabel(siteId, config.aiContentLabel ?? {}, cspNonce);
46
+ }
47
+ if (config.deepfakeLabel?.enabled !== false) {
48
+ renderDeepfakeLabel(siteId, config.deepfakeLabel ?? {}, cspNonce);
49
+ }
50
+ if (config.chatbotDisclosure?.enabled !== false) {
51
+ renderChatbotDisclosure(siteId, config.chatbotDisclosure ?? {}, cspNonce);
52
+ }
53
+ if (config.biometricNotice?.enabled !== false) {
54
+ renderBiometricNotice(siteId, config.biometricNotice ?? {}, cspNonce);
55
+ }
56
+ }
package/src/vendors.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Built-in chatbot vendor selector presets.
3
+ * Used by ChatbotDisclosure to locate the widget element automatically.
4
+ * When no selector matches, the component falls back to global banner mode.
5
+ */
6
+ export const VENDOR_PRESETS = {
7
+ intercom: "#intercom-frame, .intercom-launcher",
8
+ crisp: ".crisp-client, #crisp-chatbox",
9
+ tidio: "#tidio-chat, #tidio-chat-iframe",
10
+ zendesk: "#launcher, .zEWidget-launcher",
11
+ drift: "#drift-widget, .drift-widget-welcome",
12
+ livechat: "#chat-widget-container, .livechat-widget",
13
+ } as const;
14
+
15
+ export type VendorPreset = keyof typeof VENDOR_PRESETS;
16
+
17
+ /**
18
+ * Resolve the CSS selector for a given vendor preset key.
19
+ * Returns null if the key is not a known preset.
20
+ *
21
+ * SECURITY: The returned selector string must never be passed to eval() or
22
+ * used in a way that allows script injection. Use only with querySelector().
23
+ */
24
+ export function resolveVendorSelector(vendor: string): string | null {
25
+ if (vendor in VENDOR_PRESETS) {
26
+ return VENDOR_PRESETS[vendor as VendorPreset];
27
+ }
28
+ return null;
29
+ }
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const VERSION = "0.1.0";
package/src/wcag.ts ADDED
@@ -0,0 +1,46 @@
1
+ // WCAG 2.1 AA ARIA helpers for disclosure components.
2
+ // Full axe-core test suite implemented in Wave 3 (task 1.12).
3
+
4
+ /**
5
+ * Set the accessible label on an element.
6
+ * Uses aria-label (not innerHTML) — safe against XSS.
7
+ */
8
+ export function setAriaLabel(element: HTMLElement, label: string): void {
9
+ element.setAttribute("aria-label", label);
10
+ }
11
+
12
+ /**
13
+ * Set the aria-live region politeness on an element.
14
+ * Use 'polite' for disclosure widgets (role="alert" uses 'assertive' implicitly).
15
+ */
16
+ export function setAriaLive(
17
+ element: HTMLElement,
18
+ value: "polite" | "assertive" | "off",
19
+ ): void {
20
+ element.setAttribute("aria-live", value);
21
+ }
22
+
23
+ /** Set the ARIA role on an element. */
24
+ export function setRole(element: HTMLElement, role: string): void {
25
+ element.setAttribute("role", role);
26
+ }
27
+
28
+ /**
29
+ * Apply a nonce attribute to a <style> element for CSP compliance.
30
+ * Only applied when a nonce string is provided.
31
+ */
32
+ export function applyNonce(styleElement: HTMLElement, nonce?: string): void {
33
+ if (nonce !== undefined && nonce.length > 0) {
34
+ styleElement.setAttribute("nonce", nonce);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Trap focus within a container element (for modal disclosure overlays).
40
+ * Returns a cleanup function that removes the trap.
41
+ * STUB — full implementation in Wave 3.
42
+ */
43
+ export function trapFocus(_container: HTMLElement): () => void {
44
+ // STUB — implementation pending Wave 3
45
+ return () => {};
46
+ }