@chatlychat/react-native 0.0.0-stage → 0.1.1

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 (114) hide show
  1. package/README.md +208 -2
  2. package/dist/ChatlyProvider.d.ts +69 -0
  3. package/dist/ChatlyProvider.d.ts.map +1 -0
  4. package/dist/ChatlyProvider.js +146 -0
  5. package/dist/ChatlyProvider.js.map +1 -0
  6. package/dist/ChatlyWidget.d.ts +15 -0
  7. package/dist/ChatlyWidget.d.ts.map +1 -0
  8. package/dist/ChatlyWidget.js +122 -0
  9. package/dist/ChatlyWidget.js.map +1 -0
  10. package/dist/blocks/money.d.ts +56 -0
  11. package/dist/blocks/money.d.ts.map +1 -0
  12. package/dist/blocks/money.js +82 -0
  13. package/dist/blocks/money.js.map +1 -0
  14. package/dist/blocks/money.parity.spec.d.ts +2 -0
  15. package/dist/blocks/money.parity.spec.d.ts.map +1 -0
  16. package/dist/blocks/money.parity.spec.js +87 -0
  17. package/dist/blocks/money.parity.spec.js.map +1 -0
  18. package/dist/blocks/read-envelope.d.ts +15 -0
  19. package/dist/blocks/read-envelope.d.ts.map +1 -0
  20. package/dist/blocks/read-envelope.js +196 -0
  21. package/dist/blocks/read-envelope.js.map +1 -0
  22. package/dist/blocks/read-envelope.parity.spec.d.ts +2 -0
  23. package/dist/blocks/read-envelope.parity.spec.d.ts.map +1 -0
  24. package/dist/blocks/read-envelope.parity.spec.js +146 -0
  25. package/dist/blocks/read-envelope.parity.spec.js.map +1 -0
  26. package/dist/blocks/types.d.ts +171 -0
  27. package/dist/blocks/types.d.ts.map +1 -0
  28. package/dist/blocks/types.js +3 -0
  29. package/dist/blocks/types.js.map +1 -0
  30. package/dist/client/RealtimeClient.d.ts +29 -0
  31. package/dist/client/RealtimeClient.d.ts.map +1 -0
  32. package/dist/client/RealtimeClient.js +113 -0
  33. package/dist/client/RealtimeClient.js.map +1 -0
  34. package/dist/client/RealtimeClient.spec.d.ts +2 -0
  35. package/dist/client/RealtimeClient.spec.d.ts.map +1 -0
  36. package/dist/client/RealtimeClient.spec.js +123 -0
  37. package/dist/client/RealtimeClient.spec.js.map +1 -0
  38. package/dist/client/RestClient.d.ts +160 -0
  39. package/dist/client/RestClient.d.ts.map +1 -0
  40. package/dist/client/RestClient.js +395 -0
  41. package/dist/client/RestClient.js.map +1 -0
  42. package/dist/client/RestClient.spec.d.ts +2 -0
  43. package/dist/client/RestClient.spec.d.ts.map +1 -0
  44. package/dist/client/RestClient.spec.js +267 -0
  45. package/dist/client/RestClient.spec.js.map +1 -0
  46. package/dist/client/types.d.ts +148 -0
  47. package/dist/client/types.d.ts.map +1 -0
  48. package/dist/client/types.js +14 -0
  49. package/dist/client/types.js.map +1 -0
  50. package/dist/components/Composer.d.ts +35 -0
  51. package/dist/components/Composer.d.ts.map +1 -0
  52. package/dist/components/Composer.js +180 -0
  53. package/dist/components/Composer.js.map +1 -0
  54. package/dist/components/HelpCenter.d.ts +13 -0
  55. package/dist/components/HelpCenter.d.ts.map +1 -0
  56. package/dist/components/HelpCenter.js +96 -0
  57. package/dist/components/HelpCenter.js.map +1 -0
  58. package/dist/components/MessageBlocks.d.ts +41 -0
  59. package/dist/components/MessageBlocks.d.ts.map +1 -0
  60. package/dist/components/MessageBlocks.js +167 -0
  61. package/dist/components/MessageBlocks.js.map +1 -0
  62. package/dist/components/MessageBubble.d.ts +14 -0
  63. package/dist/components/MessageBubble.d.ts.map +1 -0
  64. package/dist/components/MessageBubble.js +87 -0
  65. package/dist/components/MessageBubble.js.map +1 -0
  66. package/dist/components/MessageBubble.spec.d.ts +2 -0
  67. package/dist/components/MessageBubble.spec.d.ts.map +1 -0
  68. package/dist/components/MessageBubble.spec.js +207 -0
  69. package/dist/components/MessageBubble.spec.js.map +1 -0
  70. package/dist/components/MessageCanvas.d.ts +39 -0
  71. package/dist/components/MessageCanvas.d.ts.map +1 -0
  72. package/dist/components/MessageCanvas.js +144 -0
  73. package/dist/components/MessageCanvas.js.map +1 -0
  74. package/dist/components/MessageCanvas.spec.d.ts +2 -0
  75. package/dist/components/MessageCanvas.spec.d.ts.map +1 -0
  76. package/dist/components/MessageCanvas.spec.js +142 -0
  77. package/dist/components/MessageCanvas.spec.js.map +1 -0
  78. package/dist/components/MessagesList.d.ts +8 -0
  79. package/dist/components/MessagesList.d.ts.map +1 -0
  80. package/dist/components/MessagesList.js +58 -0
  81. package/dist/components/MessagesList.js.map +1 -0
  82. package/dist/index.d.ts +48 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +35 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/push/registerPushToken.d.ts +12 -0
  87. package/dist/push/registerPushToken.d.ts.map +1 -0
  88. package/dist/push/registerPushToken.js +32 -0
  89. package/dist/push/registerPushToken.js.map +1 -0
  90. package/dist/storage/TokenStore.d.ts +53 -0
  91. package/dist/storage/TokenStore.d.ts.map +1 -0
  92. package/dist/storage/TokenStore.js +89 -0
  93. package/dist/storage/TokenStore.js.map +1 -0
  94. package/dist/test/react-native-dom.d.ts +97 -0
  95. package/dist/test/react-native-dom.d.ts.map +1 -0
  96. package/dist/test/react-native-dom.js +155 -0
  97. package/dist/test/react-native-dom.js.map +1 -0
  98. package/dist/test/react-native-dom.spec.d.ts +2 -0
  99. package/dist/test/react-native-dom.spec.d.ts.map +1 -0
  100. package/dist/test/react-native-dom.spec.js +83 -0
  101. package/dist/test/react-native-dom.spec.js.map +1 -0
  102. package/dist/theme/ChatlyTheme.d.ts +37 -0
  103. package/dist/theme/ChatlyTheme.d.ts.map +1 -0
  104. package/dist/theme/ChatlyTheme.js +2 -0
  105. package/dist/theme/ChatlyTheme.js.map +1 -0
  106. package/dist/theme/defaultTheme.d.ts +7 -0
  107. package/dist/theme/defaultTheme.d.ts.map +1 -0
  108. package/dist/theme/defaultTheme.js +37 -0
  109. package/dist/theme/defaultTheme.js.map +1 -0
  110. package/dist/useChatly.d.ts +3 -0
  111. package/dist/useChatly.d.ts.map +1 -0
  112. package/dist/useChatly.js +10 -0
  113. package/dist/useChatly.js.map +1 -0
  114. package/package.json +49 -3
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Money on a card, restated for the published package.
3
+ *
4
+ * ── Why this is a copy and not an import ───────────────────────────────
5
+ *
6
+ * The canonical implementation is `packages/shared/src/widget-blocks/money.ts`
7
+ * and this file is deliberately identical in behaviour. `@livechat/shared` is
8
+ * `private: true` — it is the monorepo's internal package with a hundred
9
+ * export paths — so a published npm package cannot depend on it. The same
10
+ * reason `client/types.ts` restates the realtime types rather than importing
11
+ * them.
12
+ *
13
+ * A copy is a drift risk, so it is pinned rather than trusted:
14
+ * `money.parity.spec.ts` imports BOTH this and the shared original and
15
+ * asserts they agree over a currency table that includes the cases a
16
+ * hand-rolled version gets wrong. `check-block-field-parity.mjs` knows about
17
+ * the second `Intl.NumberFormat` by name, with that spec as the reason.
18
+ *
19
+ * ── Minor units, never a float ─────────────────────────────────────────
20
+ *
21
+ * `amount` is an integer count of the currency's smallest unit: 4820 is
22
+ * $48.20, 4820 is also ¥4,820, and 4820 is 4.820 KWD.
23
+ *
24
+ * The DIVISOR IS NOT 100. Three currencies have no minor unit (JPY, KRW, VND)
25
+ * and four have three digits (KWD, BHD, JOD, OMR), so `(amount / 100)`
26
+ * renders ¥4,820 as "¥48" and 4.820 KWD as "48.200 KWD" — wrong by two orders
27
+ * of magnitude in opposite directions, and perfect in USD, which is why it
28
+ * would ship. The digit count is read off the formatter, so a currency added
29
+ * to CLDR after this was written is right with no table here.
30
+ *
31
+ * React Native ships a full ICU on iOS and, since 0.73 with Hermes
32
+ * `intl` enabled, on Android too. The `catch` below is what covers an engine
33
+ * without it: a card with an unrecognised currency still shows a price rather
34
+ * than taking the thread's render down.
35
+ */
36
+ const CURRENCY_RE = /^[A-Z]{3}$/;
37
+ const isMinor = (v) => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
38
+ /** Read a price off a stored card, or null. Refuses what cannot be drawn. */
39
+ export function readMoney(raw) {
40
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
41
+ return null;
42
+ }
43
+ const bag = raw;
44
+ const currency = bag['currency'];
45
+ if (!isMinor(bag['amount']) || typeof currency !== 'string' || !CURRENCY_RE.test(currency)) {
46
+ return null;
47
+ }
48
+ const compareAt = bag['compareAt'];
49
+ return {
50
+ amount: bag['amount'],
51
+ currency,
52
+ ...(isMinor(compareAt) ? { compareAt } : {}),
53
+ };
54
+ }
55
+ function minorPerMajor(formatter) {
56
+ const digits = formatter.resolvedOptions().maximumFractionDigits;
57
+ return 10 ** (typeof digits === 'number' ? digits : 2);
58
+ }
59
+ /** The locale-free rendition, used as the fallback and for accessibility text. */
60
+ export function moneyToPlainText(money) {
61
+ return `${money.currency} ${(money.amount / 100).toFixed(2)}`;
62
+ }
63
+ /**
64
+ * Format one amount in the DEVICE's locale.
65
+ *
66
+ * `locale` undefined means "whatever this device is set to", which is the
67
+ * right default: the app runs on the shopper's phone and a German shopper
68
+ * reads `48,20 $`.
69
+ */
70
+ export function formatMoney(money, locale) {
71
+ try {
72
+ const formatter = new Intl.NumberFormat(locale, {
73
+ style: 'currency',
74
+ currency: money.currency,
75
+ });
76
+ return formatter.format(money.amount / minorPerMajor(formatter));
77
+ }
78
+ catch {
79
+ return moneyToPlainText(money);
80
+ }
81
+ }
82
+ //# sourceMappingURL=money.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"money.js","sourceRoot":"","sources":["../../src/blocks/money.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAWH,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC,MAAM,OAAO,GAAG,CAAC,CAAU,EAAe,EAAE,CAC1C,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAE7D,6EAA6E;AAC7E,MAAM,UAAU,SAAS,CAAC,GAAY;IACpC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,GAAG,GAAG,GAA8B,CAAC;IAC3C,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;IACjC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3F,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC;IACnC,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC;QACrB,QAAQ;QACR,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7C,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,SAA4B;IACjD,MAAM,MAAM,GAAG,SAAS,CAAC,eAAe,EAAE,CAAC,qBAAqB,CAAC;IACjE,OAAO,EAAE,IAAI,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,gBAAgB,CAAC,KAAY;IAC3C,OAAO,GAAG,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;AAChE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,KAAY,EAAE,MAAe;IACvD,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE;YAC9C,KAAK,EAAE,UAAU;YACjB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACzB,CAAC,CAAC;QACH,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=money.parity.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"money.parity.spec.d.ts","sourceRoot":"","sources":["../../src/blocks/money.parity.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The RN copy of the money formatter against the canonical one.
3
+ *
4
+ * ── Why a parity spec and not an import ────────────────────────────────
5
+ *
6
+ * `@livechat/shared` is `private: true`, so the published package cannot
7
+ * depend on it and `blocks/money.ts` is a deliberate copy. A copy with no
8
+ * test is a copy that drifts, and the drift is invisible: both versions look
9
+ * right in USD, and the cases they disagree on are the ones a hand-rolled
10
+ * divisor gets wrong.
11
+ *
12
+ * The shared package is a DEV dependency, so nothing here ships.
13
+ *
14
+ * This is the control that justifies the second `Intl.NumberFormat` in the
15
+ * repo — see `check-block-field-parity.mjs`, which otherwise fails the build
16
+ * on one.
17
+ */
18
+ import { formatMoney as sharedFormat, moneyToPlainText as sharedPlain, readMoney as sharedRead, } from '@livechat/shared/widget-blocks';
19
+ import { describe, expect, it } from 'vitest';
20
+ import { formatMoney, moneyToPlainText, readMoney } from './money';
21
+ /**
22
+ * Every shape worth disagreeing about.
23
+ *
24
+ * The three zero-decimal currencies and the four three-decimal ones are the
25
+ * whole point: a `/ 100` divisor is wrong by 100× for JPY and by 10× for KWD,
26
+ * and right for USD.
27
+ */
28
+ const CASES = [
29
+ { amount: 4820, currency: 'USD' },
30
+ { amount: 4820, currency: 'EUR' },
31
+ { amount: 4820, currency: 'GBP' },
32
+ { amount: 4820, currency: 'JPY' },
33
+ { amount: 4820, currency: 'KRW' },
34
+ { amount: 4820, currency: 'VND' },
35
+ { amount: 4820, currency: 'KWD' },
36
+ { amount: 4820, currency: 'BHD' },
37
+ { amount: 4820, currency: 'JOD' },
38
+ { amount: 4820, currency: 'OMR' },
39
+ { amount: 0, currency: 'USD' },
40
+ { amount: 1, currency: 'USD' },
41
+ { amount: 100000000, currency: 'USD' },
42
+ { amount: 4820, currency: 'ZZZ' },
43
+ ];
44
+ const LOCALES = [undefined, 'en-US', 'de-DE', 'ja-JP', 'ar-EG'];
45
+ describe('RN money formatter ≡ shared money formatter', () => {
46
+ it('formats every currency identically, in every locale', () => {
47
+ for (const money of CASES) {
48
+ for (const locale of LOCALES) {
49
+ expect(formatMoney(money, locale)).toBe(sharedFormat(money, locale));
50
+ }
51
+ }
52
+ });
53
+ it('agrees on the locale-free rendition', () => {
54
+ for (const money of CASES) {
55
+ expect(moneyToPlainText(money)).toBe(sharedPlain(money));
56
+ }
57
+ });
58
+ it('agrees on what is readable, and on what is refused', () => {
59
+ const raws = [
60
+ { amount: 4820, currency: 'USD' },
61
+ { amount: 4820, currency: 'USD', compareAt: 6000 },
62
+ // Refused: a float, a negative, a lower-case code, a string amount, a
63
+ // non-object. Each would reach a shopper as `NaN` or as a price off by
64
+ // orders of magnitude.
65
+ { amount: 48.2, currency: 'USD' },
66
+ { amount: -1, currency: 'USD' },
67
+ { amount: 4820, currency: 'usd' },
68
+ { amount: '4820', currency: 'USD' },
69
+ { currency: 'USD' },
70
+ { amount: 4820 },
71
+ null,
72
+ [],
73
+ 'nope',
74
+ { amount: 4820, currency: 'USD', compareAt: -3 },
75
+ ];
76
+ for (const raw of raws) {
77
+ expect(readMoney(raw)).toEqual(sharedRead(raw));
78
+ }
79
+ });
80
+ it('is a real comparison — the shared module is actually loaded', () => {
81
+ // A parity spec whose import resolved to the local copy would pass
82
+ // trivially and forever.
83
+ expect(sharedFormat).not.toBe(formatMoney);
84
+ expect(sharedFormat({ amount: 4820, currency: 'JPY' }, 'en-US')).toBe('¥4,820');
85
+ });
86
+ });
87
+ //# sourceMappingURL=money.parity.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"money.parity.spec.js","sourceRoot":"","sources":["../../src/blocks/money.parity.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { WidgetBlocksEnvelope, WidgetCanvas } from './types';
2
+ /** `https:` and nothing else. Mirrors `isSafeMediaSrc` in the shared guard. */
3
+ export declare function isSafeMediaSrc(src: string): boolean;
4
+ export declare function readBlocksEnvelope(raw: unknown): WidgetBlocksEnvelope | null;
5
+ /**
6
+ * Read a stored canvas.
7
+ *
8
+ * Looser than the block reader on one point and stricter on another. Looser:
9
+ * a canvas with no fields and no actions still renders, because a step can be
10
+ * a read-only confirmation. Stricter: `canvasId` is required, because it is
11
+ * what a submission is posted against — a canvas without one is a form with
12
+ * nowhere to go, which is worse than no form.
13
+ */
14
+ export declare function readCanvas(raw: unknown): WidgetCanvas | null;
15
+ //# sourceMappingURL=read-envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-envelope.d.ts","sourceRoot":"","sources":["../../src/blocks/read-envelope.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAA6B,oBAAoB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAO7F,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAenD;AAqHD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,oBAAoB,GAAG,IAAI,CAM5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAoB5D"}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Read a stored block envelope, refusing what cannot be drawn.
3
+ *
4
+ * Mirrors `packages/shared/src/widget-blocks/read-envelope.ts` exactly, and
5
+ * `read-envelope.parity.spec.ts` asserts that over a table of accepted and
6
+ * refused shapes. See `blocks/money.ts` for why this package restates rather
7
+ * than imports.
8
+ *
9
+ * ── The contract, which is the same on every renderer ──────────────────
10
+ *
11
+ * Never throw, never render a hole. A row written before a field was
12
+ * validated — or by a future server this client does not know about — must
13
+ * produce the most of the message that CAN be drawn, not an exception and not
14
+ * a blank space. So:
15
+ *
16
+ * an unreadable BLOCK is dropped, the rest of the message renders;
17
+ * an unsafe card image drops the IMAGE, not the card;
18
+ * an unreadable price drops the PRICE, because `formatMoney` handed a
19
+ * string renders `NaN` at a shopper;
20
+ * an unknown `type` is expected rather than exceptional — an old app meets
21
+ * a newer server — and is dropped silently.
22
+ *
23
+ * ── The media guard ────────────────────────────────────────────────────
24
+ *
25
+ * `https:` only, no control characters, no untrimmed string, length capped.
26
+ * React Native's `Image` will happily fetch `http:` and `data:`, and a card
27
+ * is authored by whoever holds an API key — so the same refusal the widget
28
+ * applies in a browser applies here. There is no `javascript:` hazard in an
29
+ * `Image` source, but `http:` on a phone is a downgrade a shopper cannot see
30
+ * and `file:` is the device's own disk.
31
+ */
32
+ import { readMoney } from './money';
33
+ import { BLOCK_PROTOCOL_VERSION } from './types';
34
+ const isBag = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
35
+ const str = (v) => typeof v === 'string' && v.length > 0;
36
+ /** `https:` and nothing else. Mirrors `isSafeMediaSrc` in the shared guard. */
37
+ export function isSafeMediaSrc(src) {
38
+ if (src.length === 0 || src.length > 2000) {
39
+ return false;
40
+ }
41
+ // eslint-disable-next-line no-control-regex -- matching control characters IS the check
42
+ if (/[\u0000-\u001F\u007F]/.test(src) || src !== src.trim()) {
43
+ return false;
44
+ }
45
+ let parsed;
46
+ try {
47
+ parsed = new URL(src);
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ return parsed.protocol === 'https:';
53
+ }
54
+ function readAction(raw) {
55
+ if (!isBag(raw) || !str(raw['label'])) {
56
+ return null;
57
+ }
58
+ switch (raw['kind']) {
59
+ case 'url':
60
+ // An `href` is opened with `Linking.openURL`, which will launch any
61
+ // scheme the OS has a handler for — so the same https-only rule.
62
+ return str(raw['href']) && isSafeMediaSrc(raw['href'])
63
+ ? raw
64
+ : null;
65
+ case 'postback':
66
+ return str(raw['value']) ? raw : null;
67
+ case 'host_event':
68
+ return str(raw['event']) ? raw : null;
69
+ case 'step':
70
+ return str(raw['canvasId']) ? raw : null;
71
+ case 'copy':
72
+ return str(raw['text']) ? raw : null;
73
+ default:
74
+ return null;
75
+ }
76
+ }
77
+ function readActions(raw) {
78
+ return Array.isArray(raw) ? raw.map(readAction).filter((a) => a !== null) : [];
79
+ }
80
+ function readPairs(raw) {
81
+ return Array.isArray(raw)
82
+ ? raw.filter((f) => isBag(f) && str(f['label']) && str(f['value']))
83
+ : [];
84
+ }
85
+ function readCard(raw) {
86
+ if (!isBag(raw) || !str(raw['title'])) {
87
+ return null;
88
+ }
89
+ const card = {
90
+ ...raw,
91
+ actions: readActions(raw['actions']),
92
+ fields: readPairs(raw['fields']),
93
+ };
94
+ if (card['image'] !== undefined && !(str(card['image']) && isSafeMediaSrc(card['image']))) {
95
+ delete card['image'];
96
+ }
97
+ if (card['price'] !== undefined) {
98
+ const price = readMoney(card['price']);
99
+ if (price === null) {
100
+ delete card['price'];
101
+ }
102
+ else {
103
+ card['price'] = price;
104
+ }
105
+ }
106
+ return card;
107
+ }
108
+ function readBlock(raw) {
109
+ if (!isBag(raw)) {
110
+ return null;
111
+ }
112
+ switch (raw['type']) {
113
+ case 'text':
114
+ return str(raw['markdown']) ? raw : null;
115
+ case 'media':
116
+ // Unlike a card's image, a media block IS its image: nothing is left to
117
+ // render once the src is refused, so the block goes.
118
+ return str(raw['url']) &&
119
+ isSafeMediaSrc(raw['url']) &&
120
+ str(raw['alt']) &&
121
+ (raw['kind'] === 'image' || raw['kind'] === 'video')
122
+ ? raw
123
+ : null;
124
+ case 'fields': {
125
+ const items = readPairs(raw['items']);
126
+ return items.length > 0 ? { ...raw, items } : null;
127
+ }
128
+ case 'card': {
129
+ const card = readCard(raw);
130
+ return card === null ? null : card;
131
+ }
132
+ case 'card_grid': {
133
+ const cards = Array.isArray(raw['cards'])
134
+ ? raw['cards'].map(readCard).filter((c) => c !== null)
135
+ : [];
136
+ if (cards.length === 0) {
137
+ return null;
138
+ }
139
+ const grid = { ...raw, cards };
140
+ if (grid['layout'] !== 'grid' && grid['layout'] !== 'carousel') {
141
+ delete grid['layout'];
142
+ }
143
+ return grid;
144
+ }
145
+ case 'actions': {
146
+ const actions = readActions(raw['actions']);
147
+ return actions.length > 0 ? { ...raw, actions } : null;
148
+ }
149
+ case 'quick_replies': {
150
+ const options = readPairs(raw['options']);
151
+ return options.length > 0 ? { ...raw, options } : null;
152
+ }
153
+ case 'status':
154
+ // `id` is required: a status block exists to be patched.
155
+ return str(raw['id']) && str(raw['text']) ? raw : null;
156
+ case 'divider':
157
+ return raw;
158
+ default:
159
+ return null;
160
+ }
161
+ }
162
+ export function readBlocksEnvelope(raw) {
163
+ if (!isBag(raw) || raw['v'] !== BLOCK_PROTOCOL_VERSION || !Array.isArray(raw['blocks'])) {
164
+ return null;
165
+ }
166
+ const blocks = raw['blocks'].map(readBlock).filter((b) => b !== null);
167
+ return blocks.length > 0 ? { v: BLOCK_PROTOCOL_VERSION, blocks } : null;
168
+ }
169
+ /**
170
+ * Read a stored canvas.
171
+ *
172
+ * Looser than the block reader on one point and stricter on another. Looser:
173
+ * a canvas with no fields and no actions still renders, because a step can be
174
+ * a read-only confirmation. Stricter: `canvasId` is required, because it is
175
+ * what a submission is posted against — a canvas without one is a form with
176
+ * nowhere to go, which is worse than no form.
177
+ */
178
+ export function readCanvas(raw) {
179
+ if (!isBag(raw) || raw['v'] !== BLOCK_PROTOCOL_VERSION || !str(raw['canvasId'])) {
180
+ return null;
181
+ }
182
+ const fields = Array.isArray(raw['fields'])
183
+ ? raw['fields'].filter((f) => isBag(f) && str(f['name']) && str(f['label']) && str(f['type']))
184
+ : [];
185
+ const actions = Array.isArray(raw['actions'])
186
+ ? raw['actions'].filter((a) => isBag(a) &&
187
+ str(a['id']) &&
188
+ str(a['label']) &&
189
+ (a['kind'] === 'submit' || a['kind'] === 'cancel'))
190
+ : [];
191
+ const blocks = Array.isArray(raw['blocks'])
192
+ ? raw['blocks'].map(readBlock).filter((b) => b !== null)
193
+ : [];
194
+ return { ...raw, fields, actions, blocks };
195
+ }
196
+ //# sourceMappingURL=read-envelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-envelope.js","sourceRoot":"","sources":["../../src/blocks/read-envelope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=read-envelope.parity.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-envelope.parity.spec.d.ts","sourceRoot":"","sources":["../../src/blocks/read-envelope.parity.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * The RN envelope reader against the canonical one.
3
+ *
4
+ * ── What this is really guarding ───────────────────────────────────────
5
+ *
6
+ * A reader that is slightly more permissive than the server's is a renderer
7
+ * that draws something the rest of the product refuses — and a reader that is
8
+ * slightly stricter silently drops content the shopper was supposed to see.
9
+ * Neither produces an error anywhere. The two readers are separate files
10
+ * because `@livechat/shared` is private and cannot be a runtime dependency of
11
+ * a published package, so this is the only thing that keeps them the same.
12
+ *
13
+ * Every case below is a shape a real author can produce. The refusals matter
14
+ * more than the acceptances: they are the ones that would otherwise reach a
15
+ * phone as a blank space, a broken image, or `NaN` where a price goes.
16
+ */
17
+ import { readBlocksEnvelope as sharedRead } from '@livechat/shared/widget-blocks';
18
+ import { describe, expect, it } from 'vitest';
19
+ import { readBlocksEnvelope } from './read-envelope';
20
+ const env = (...blocks) => ({ v: 1, blocks });
21
+ const CASES = [
22
+ { why: 'a plain text block', raw: env({ type: 'text', markdown: 'hello' }) },
23
+ { why: 'text with no markdown is dropped', raw: env({ type: 'text' }) },
24
+ {
25
+ why: 'an https image',
26
+ raw: env({ type: 'media', kind: 'image', url: 'https://cdn.test/a.png', alt: 'a' }),
27
+ },
28
+ {
29
+ why: 'an http image is refused — the block IS its image',
30
+ raw: env({ type: 'media', kind: 'image', url: 'http://cdn.test/a.png', alt: 'a' }),
31
+ },
32
+ {
33
+ why: 'a media block with no alt is refused',
34
+ raw: env({ type: 'media', kind: 'image', url: 'https://cdn.test/a.png' }),
35
+ },
36
+ {
37
+ why: 'an unknown media kind is refused',
38
+ raw: env({ type: 'media', kind: 'audio', url: 'https://cdn.test/a.mp3', alt: 'a' }),
39
+ },
40
+ {
41
+ why: 'fields with one readable pair',
42
+ raw: env({ type: 'fields', items: [{ label: 'a', value: 'b' }] }),
43
+ },
44
+ {
45
+ why: 'fields with no readable pair is dropped',
46
+ raw: env({ type: 'fields', items: [{ label: 'a' }] }),
47
+ },
48
+ { why: 'a card', raw: env({ type: 'card', title: 'Hat', subtitle: 'wool' }) },
49
+ { why: 'a card with no title is dropped', raw: env({ type: 'card', subtitle: 'wool' }) },
50
+ {
51
+ why: 'an unsafe card image drops the IMAGE, not the card',
52
+ raw: env({ type: 'card', title: 'Hat', image: 'javascript:alert(1)' }),
53
+ },
54
+ {
55
+ why: 'an unreadable price drops the PRICE, not the card',
56
+ raw: env({ type: 'card', title: 'Hat', price: { amount: 48.2, currency: 'USD' } }),
57
+ },
58
+ {
59
+ why: 'a readable price is normalised',
60
+ raw: env({
61
+ type: 'card',
62
+ title: 'Hat',
63
+ price: { amount: 4820, currency: 'USD', compareAt: 6000 },
64
+ }),
65
+ },
66
+ {
67
+ why: 'a carousel keeps its layout',
68
+ raw: env({ type: 'card_grid', layout: 'carousel', cards: [{ title: 'a' }, { title: 'b' }] }),
69
+ },
70
+ {
71
+ why: 'an unknown layout is dropped so the grid stacks',
72
+ raw: env({ type: 'card_grid', layout: 'spiral', cards: [{ title: 'a' }] }),
73
+ },
74
+ { why: 'an empty grid is dropped', raw: env({ type: 'card_grid', cards: [] }) },
75
+ {
76
+ why: 'a grid drops only the unreadable cards',
77
+ raw: env({ type: 'card_grid', cards: [{ title: 'a' }, { subtitle: 'no title' }] }),
78
+ },
79
+ {
80
+ why: 'quick replies',
81
+ raw: env({ type: 'quick_replies', options: [{ label: 'a', value: 'b' }] }),
82
+ },
83
+ {
84
+ why: 'quick replies with nothing readable is dropped',
85
+ raw: env({ type: 'quick_replies', options: [] }),
86
+ },
87
+ { why: 'a status block', raw: env({ type: 'status', id: 's1', text: 'Looking…' }) },
88
+ { why: 'a status block with no id is dropped', raw: env({ type: 'status', text: 'Looking…' }) },
89
+ { why: 'a divider', raw: env({ type: 'divider' }) },
90
+ { why: 'an unknown type is dropped silently', raw: env({ type: 'hologram', spin: true }) },
91
+ {
92
+ why: 'the wrong protocol version is refused whole',
93
+ raw: { v: 2, blocks: [{ type: 'divider' }] },
94
+ },
95
+ { why: 'no blocks at all is null, not an empty envelope', raw: env() },
96
+ { why: 'a non-object is null', raw: 'nope' },
97
+ { why: 'null is null', raw: null },
98
+ {
99
+ why: 'a postback action',
100
+ raw: env({ type: 'actions', actions: [{ kind: 'postback', label: 'Yes', value: 'yes' }] }),
101
+ },
102
+ {
103
+ why: 'a host_event action — the one the native app exists for',
104
+ raw: env({ type: 'actions', actions: [{ kind: 'host_event', label: 'Pay', event: 'pay' }] }),
105
+ },
106
+ {
107
+ why: 'an action with no label is dropped',
108
+ raw: env({ type: 'actions', actions: [{ kind: 'postback', value: 'yes' }] }),
109
+ },
110
+ {
111
+ why: 'an actions block with nothing readable is dropped',
112
+ raw: env({ type: 'actions', actions: [{ kind: 'teleport', label: 'Go' }] }),
113
+ },
114
+ ];
115
+ describe('RN block reader ≡ shared block reader', () => {
116
+ for (const c of CASES) {
117
+ it(c.why, () => {
118
+ expect(readBlocksEnvelope(c.raw)).toEqual(sharedRead(c.raw));
119
+ });
120
+ }
121
+ it('is a real comparison — the shared module is actually loaded', () => {
122
+ expect(sharedRead).not.toBe(readBlocksEnvelope);
123
+ expect(sharedRead(env({ type: 'divider' }))).toEqual({ v: 1, blocks: [{ type: 'divider' }] });
124
+ });
125
+ /**
126
+ * The ONE place the two readers differ, stated as a test so it cannot drift
127
+ * into an accident.
128
+ *
129
+ * A `url` action is opened here with `Linking.openURL`, which launches
130
+ * whatever scheme the OS has a handler for — `tel:`, `itms-apps:`, a
131
+ * third-party app's own scheme. In a browser the widget's own href policy
132
+ * (`permittedDecodedHref`) runs at RENDER time and admits a wider set
133
+ * safely because an `<a>` is inert until clicked and the browser itself
134
+ * refuses the dangerous schemes. On a phone neither is true, so this reader
135
+ * refuses anything but `https:` at READ time.
136
+ */
137
+ it('refuses a non-https url action that the shared reader admits', () => {
138
+ const raw = env({
139
+ type: 'actions',
140
+ actions: [{ kind: 'url', label: 'Call', href: 'tel:+15550100' }],
141
+ });
142
+ expect(sharedRead(raw)).not.toBeNull();
143
+ expect(readBlocksEnvelope(raw)).toBeNull();
144
+ });
145
+ });
146
+ //# sourceMappingURL=read-envelope.parity.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-envelope.parity.spec.js","sourceRoot":"","sources":["../../src/blocks/read-envelope.parity.spec.ts"],"names":[],"mappings":"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"}