@stromcom/react-snippet 0.1.0 → 0.4.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 +131 -13
- package/dist/index.cjs +17 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +17 -6
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/src/StromcomConf.jsx +19 -4
- package/src/StromcomProvider.jsx +5 -1
- package/src/__tests__/StromcomConf.test.jsx +51 -0
- package/src/__tests__/StromcomProvider.test.jsx +20 -0
- package/src/index.d.ts +21 -1
package/README.md
CHANGED
|
@@ -137,12 +137,13 @@ Never pass raw internal IDs from the client.
|
|
|
137
137
|
|
|
138
138
|
### `<StromcomProvider>`
|
|
139
139
|
|
|
140
|
-
| Prop | Type | Required | Description
|
|
141
|
-
| -------------- | ----------------------------------------- | -------- |
|
|
142
|
-
| `clientKey` | `string` | ✅ | Project client key.
|
|
143
|
-
| `clientSecret` | `string` | ✅ | Project bearer token.
|
|
144
|
-
| `environment` | `"production"` \| `"staging"` \| `string` | | Default `"production"`. Pass any custom URL to point at a self-hosted loader.
|
|
145
|
-
| `dataLayer` | `string` | | Default `"stromCom"`. The global JS variable name. Change it if `stromCom` collides with something else on the page.
|
|
140
|
+
| Prop | Type | Required | Description |
|
|
141
|
+
| -------------- | ----------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
142
|
+
| `clientKey` | `string` | ✅ | Project client key. |
|
|
143
|
+
| `clientSecret` | `string` | ✅ | Project bearer token. |
|
|
144
|
+
| `environment` | `"production"` \| `"staging"` \| `string` | | Default `"production"`. Pass any custom URL to point at a self-hosted loader. |
|
|
145
|
+
| `dataLayer` | `string` | | Default `"stromCom"`. The global JS variable name. Change it if `stromCom` collides with something else on the page. |
|
|
146
|
+
| `language` | `string` | | Initial UI language (e.g. `"cs"`, `"en"`, `"sk"`). Any code is accepted; one without a translation falls back to the browser language, then English. Omit to auto-detect from the browser. To change it at runtime, use `<StromcomConf language>`. |
|
|
146
147
|
|
|
147
148
|
### `<StromcomUser>`
|
|
148
149
|
|
|
@@ -184,16 +185,38 @@ Sends SDK configuration. Place once inside the provider, before threads.
|
|
|
184
185
|
|
|
185
186
|
Notable options:
|
|
186
187
|
|
|
187
|
-
| Prop | Type | Description
|
|
188
|
-
| ----------------------------- | ----------------------------------------- |
|
|
189
|
-
| `notificationRenderer` | `Function` | Custom notification rendering function.
|
|
190
|
-
| `onNotification` | `Function` | Called when unread count changes.
|
|
191
|
-
| `
|
|
192
|
-
| `
|
|
193
|
-
| `
|
|
188
|
+
| Prop | Type | Description |
|
|
189
|
+
| ----------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
190
|
+
| `notificationRenderer` | `Function` | Custom notification rendering function. |
|
|
191
|
+
| `onNotification` | `Function` | Called when unread count changes. |
|
|
192
|
+
| `appFrameCSSPath` | `string\|string[]` | CSS for the widget frame — the shadow root holding the container, the backdrop and the iframe. |
|
|
193
|
+
| `appCSSPath` | `string\|string[]` | CSS for the app itself (inside the iframe), on top of the CSS from the project settings. |
|
|
194
|
+
| `notificationElementPosition` | `1\|2\|3\|4` | Icon position: 1=TL, 2=TR, 3=BR, 4=BL. |
|
|
195
|
+
| `language` | `string\|null` | UI language. Any code is accepted; one without a translation falls back to the browser language, then English. `null` follows the browser. Overrides `<StromcomProvider language>`. |
|
|
196
|
+
| `theme` | `"stromcom-light"\|"stromcom-dark"\|null` | Theme. `null` follows browser preference. |
|
|
197
|
+
| `entityResolve` | `Function` | `async ({type, id}) => detail` — resolves business-entity detail (order, ticket…) for the message editor's chip hover-card. |
|
|
194
198
|
|
|
195
199
|
See [`src/StromcomConf.jsx`](./src/StromcomConf.jsx) for the full list.
|
|
196
200
|
|
|
201
|
+
#### Custom CSS
|
|
202
|
+
|
|
203
|
+
The widget renders into a shadow root and the app runs in an iframe, so neither is
|
|
204
|
+
reachable from your page stylesheet. `appFrameCSSPath` covers the frame — container,
|
|
205
|
+
backdrop, the iframe element itself — and `appCSSPath` the UI inside it. Both accept one
|
|
206
|
+
URL or a list, and both load after the widget's own stylesheet, so your rules win.
|
|
207
|
+
|
|
208
|
+
```jsx
|
|
209
|
+
const APP_CSS = ['https://cdn.example.com/widget.css', 'https://cdn.example.com/brand.css'];
|
|
210
|
+
|
|
211
|
+
<StromcomConf appFrameCSSPath="https://cdn.example.com/frame.css" appCSSPath={APP_CSS} />;
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Keep the array identity stable (a module constant or `useMemo`) — like every option prop,
|
|
215
|
+
a new value re-sends the configuration, and an inline array is a new value on every render.
|
|
216
|
+
|
|
217
|
+
`appCSSPath` is applied on top of the `customCSSPath` from the project settings and, like
|
|
218
|
+
it, only on a plan that allows appearance customization; without it the app ignores both.
|
|
219
|
+
|
|
197
220
|
### `useStromcom()` — escape hatch
|
|
198
221
|
|
|
199
222
|
Returns the global `stromCom` object directly. Use when you need to call something the components don't expose (e.g. switching the active user from a button click).
|
|
@@ -230,6 +253,99 @@ The provider is SSR-safe — it only touches `window` inside `useEffect`. The lo
|
|
|
230
253
|
|
|
231
254
|
For the App Router, mount `<StromcomProvider>` in a Client Component placed inside your root layout. Server Components above it stay server-rendered. See [`examples/02-nextjs-app-router/`](./examples/02-nextjs-app-router).
|
|
232
255
|
|
|
256
|
+
## Content Security Policy
|
|
257
|
+
|
|
258
|
+
If your site sends a `Content-Security-Policy` header, the STROMCOM origins have to be allowed. The directives below are **additive** — merge them into your existing policy. With a restrictive `default-src` (e.g. `'none'` or `'self'`) all five must be listed explicitly.
|
|
259
|
+
|
|
260
|
+
### Production
|
|
261
|
+
|
|
262
|
+
| Directive | Add | Why |
|
|
263
|
+
| ------------- | ------------------------- | ----------------------------------------------------------- |
|
|
264
|
+
| `script-src` | `https://cdn.stromcom.cz` | Loader script, and the widget snippet it pulls in |
|
|
265
|
+
| `connect-src` | `https://www.stromcom.cz` | Notification polling against `/api/app/v1/` |
|
|
266
|
+
| `style-src` | `https://cdn.stromcom.cz` | Widget stylesheets (shadow root + `client.css` in `<head>`) |
|
|
267
|
+
| `img-src` | `data:` | SVG icons embedded in the widget CSS (loading spinner) |
|
|
268
|
+
| `frame-src` | `https://app.stromcom.cz` | The iframe hosting threads and the notification center |
|
|
269
|
+
|
|
270
|
+
As a single header:
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
Content-Security-Policy:
|
|
274
|
+
script-src 'self' https://cdn.stromcom.cz;
|
|
275
|
+
connect-src 'self' https://www.stromcom.cz;
|
|
276
|
+
style-src 'self' https://cdn.stromcom.cz;
|
|
277
|
+
img-src 'self' data:;
|
|
278
|
+
frame-src https://app.stromcom.cz;
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Staging
|
|
282
|
+
|
|
283
|
+
Same directives with the staging hosts — use these when the provider runs with `environment="staging"`.
|
|
284
|
+
|
|
285
|
+
| Directive | Add |
|
|
286
|
+
| ------------- | --------------------------------- |
|
|
287
|
+
| `script-src` | `https://cdn.staging.stromcom.cz` |
|
|
288
|
+
| `connect-src` | `https://staging.stromcom.cz` |
|
|
289
|
+
| `style-src` | `https://cdn.staging.stromcom.cz` |
|
|
290
|
+
| `frame-src` | `https://app.staging.stromcom.cz` |
|
|
291
|
+
|
|
292
|
+
`img-src data:` is the same in both environments.
|
|
293
|
+
|
|
294
|
+
If you pass a full URL to `environment` (self-hosted or branch-preview loader, see [`examples/09-staging-environment.jsx`](./examples/09-staging-environment.jsx)), put that host in `script-src` instead.
|
|
295
|
+
|
|
296
|
+
### `'unsafe-inline'` is not required
|
|
297
|
+
|
|
298
|
+
`<StromcomProvider>` injects the loader with `document.createElement('script')` from `useEffect` — it never emits an inline `<script>` block, so **`script-src 'unsafe-inline'` is not needed**. This is a deliberate difference from [`stromcom/php-snippet`](https://github.com/stromcom/php-snippet), which renders the same bootstrap as an inline `<script>` and therefore needs `'unsafe-inline'` or a nonce.
|
|
299
|
+
|
|
300
|
+
`style-src 'unsafe-inline'` is not needed either: widget styles are applied as constructable stylesheets inside the shadow root. The `style` props on `<StromcomThread>` and `<StromcomHome>` are applied by React through the CSSOM, which CSP does not restrict.
|
|
301
|
+
|
|
302
|
+
### What you do **not** have to allow
|
|
303
|
+
|
|
304
|
+
Avatars, attachments, fonts, and uploaded media are loaded **inside the widget iframe**, on the STROMCOM app origin. Your page's CSP does not apply to them — keep `font-src`, `media-src` and attachment hosts out of your policy.
|
|
305
|
+
|
|
306
|
+
Two `<StromcomConf>` options are worth knowing about:
|
|
307
|
+
|
|
308
|
+
| Option | Loaded where | Effect on your CSP |
|
|
309
|
+
| ---------------------------- | ----------------- | --------------------------------------------------------------------------- |
|
|
310
|
+
| `notificationElementCSSPath` | Your page | Its host must be in your `style-src` (`'self'` if you host the file) |
|
|
311
|
+
| `appFrameCSSPath` | Your page | Its host must be in your `style-src` — a shadow root is still your document |
|
|
312
|
+
| `appCSSPath` | Inside the iframe | None — your page's CSP does not reach inside the iframe |
|
|
313
|
+
|
|
314
|
+
### Next.js
|
|
315
|
+
|
|
316
|
+
Static headers in `next.config.js` are the simplest fit — STROMCOM needs no per-request nonce.
|
|
317
|
+
|
|
318
|
+
```js
|
|
319
|
+
// next.config.js
|
|
320
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
321
|
+
|
|
322
|
+
// 'unsafe-inline' / 'unsafe-eval' below are Next.js' own requirements
|
|
323
|
+
// (hydration bootstrap, styled-jsx, dev HMR) — STROMCOM needs neither.
|
|
324
|
+
const contentSecurityPolicy = [
|
|
325
|
+
"default-src 'self'",
|
|
326
|
+
`script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ''} https://cdn.stromcom.cz`,
|
|
327
|
+
"style-src 'self' 'unsafe-inline' https://cdn.stromcom.cz",
|
|
328
|
+
`connect-src 'self' https://www.stromcom.cz${isDev ? ' ws:' : ''}`,
|
|
329
|
+
"img-src 'self' data:",
|
|
330
|
+
'frame-src https://app.stromcom.cz',
|
|
331
|
+
"base-uri 'self'",
|
|
332
|
+
"form-action 'self'",
|
|
333
|
+
].join('; ');
|
|
334
|
+
|
|
335
|
+
export default {
|
|
336
|
+
async headers() {
|
|
337
|
+
return [
|
|
338
|
+
{
|
|
339
|
+
source: '/:path*',
|
|
340
|
+
headers: [{ key: 'Content-Security-Policy', value: contentSecurityPolicy }],
|
|
341
|
+
},
|
|
342
|
+
];
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Swap in the staging hosts when you build for staging. If you already generate a per-request nonce in `middleware.js` (the approach in the Next.js CSP guide), just add the same STROMCOM entries to the directives you build there — the nonce itself is irrelevant to the widget.
|
|
348
|
+
|
|
233
349
|
## Troubleshooting
|
|
234
350
|
|
|
235
351
|
**The loader script never appears.** Check that `clientKey` and `clientSecret` are non-empty. The provider does nothing on the server.
|
|
@@ -240,6 +356,8 @@ For the App Router, mount `<StromcomProvider>` in a Client Component placed insi
|
|
|
240
356
|
|
|
241
357
|
**Two providers warn / double script.** Don't mount the provider twice. The provider deduplicates the `<script>` tag, but mounting two providers with different `clientKey`s is unsupported.
|
|
242
358
|
|
|
359
|
+
**Console shows `Refused to load…` / CSP violations.** Your page sends a `Content-Security-Policy` header that doesn't list the STROMCOM origins. See [Content Security Policy](#content-security-policy) — the reported directive tells you which line is missing.
|
|
360
|
+
|
|
243
361
|
**Notification count never fires.** Make sure `<StromcomConf onNotification={...}>` is mounted as a child of the provider, and that `<StromcomUser>` runs (notifications are user-scoped).
|
|
244
362
|
|
|
245
363
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -73,6 +73,7 @@ function StromcomProvider({
|
|
|
73
73
|
clientSecret,
|
|
74
74
|
dataLayer = "stromCom",
|
|
75
75
|
environment = "production",
|
|
76
|
+
language,
|
|
76
77
|
children
|
|
77
78
|
}) {
|
|
78
79
|
const layer = setupLayer(dataLayer);
|
|
@@ -89,10 +90,11 @@ function StromcomProvider({
|
|
|
89
90
|
script.dataset.dl = dl;
|
|
90
91
|
script.dataset.ck = clientKey;
|
|
91
92
|
script.dataset.cs = clientSecret;
|
|
93
|
+
if (language) script.dataset.lang = language;
|
|
92
94
|
script.src = `${base}?${clientKey}`;
|
|
93
95
|
document.head.appendChild(script);
|
|
94
96
|
return () => script.remove();
|
|
95
|
-
}, [clientKey, clientSecret, dataLayer, environment]);
|
|
97
|
+
}, [clientKey, clientSecret, dataLayer, environment, language]);
|
|
96
98
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StromcomContext.Provider, { value: layer, children });
|
|
97
99
|
}
|
|
98
100
|
function useStromcom() {
|
|
@@ -120,7 +122,8 @@ var import_react3 = require("react");
|
|
|
120
122
|
function StromcomConf({
|
|
121
123
|
notificationRenderer,
|
|
122
124
|
onNotification,
|
|
123
|
-
|
|
125
|
+
appFrameCSSPath,
|
|
126
|
+
appCSSPath,
|
|
124
127
|
notificationElementTargetElement,
|
|
125
128
|
notificationElementShowAlways,
|
|
126
129
|
notificationElementPosition,
|
|
@@ -130,7 +133,9 @@ function StromcomConf({
|
|
|
130
133
|
homeBeforeRender,
|
|
131
134
|
notificationElementBeforeRender,
|
|
132
135
|
notificationElementAfterRender,
|
|
133
|
-
|
|
136
|
+
language,
|
|
137
|
+
theme,
|
|
138
|
+
entityResolve
|
|
134
139
|
}) {
|
|
135
140
|
const sc = useStromcom();
|
|
136
141
|
(0, import_react3.useEffect)(() => {
|
|
@@ -138,7 +143,8 @@ function StromcomConf({
|
|
|
138
143
|
const opts = {};
|
|
139
144
|
if (notificationRenderer !== void 0) opts.notificationRenderer = notificationRenderer;
|
|
140
145
|
if (onNotification !== void 0) opts.onNotification = onNotification;
|
|
141
|
-
if (
|
|
146
|
+
if (appFrameCSSPath !== void 0) opts.appFrameCSSPath = appFrameCSSPath;
|
|
147
|
+
if (appCSSPath !== void 0) opts.appCSSPath = appCSSPath;
|
|
142
148
|
if (notificationElementTargetElement !== void 0)
|
|
143
149
|
opts.notificationElementTargetElement = notificationElementTargetElement;
|
|
144
150
|
if (notificationElementShowAlways !== void 0)
|
|
@@ -156,13 +162,16 @@ function StromcomConf({
|
|
|
156
162
|
opts.notificationElementBeforeRender = notificationElementBeforeRender;
|
|
157
163
|
if (notificationElementAfterRender !== void 0)
|
|
158
164
|
opts.notificationElementAfterRender = notificationElementAfterRender;
|
|
165
|
+
if (language !== void 0) opts.language = language;
|
|
159
166
|
if (theme !== void 0) opts.theme = theme;
|
|
167
|
+
if (entityResolve !== void 0) opts.entityResolve = entityResolve;
|
|
160
168
|
sc.conf(opts);
|
|
161
169
|
}, [
|
|
162
170
|
sc,
|
|
163
171
|
notificationRenderer,
|
|
164
172
|
onNotification,
|
|
165
|
-
|
|
173
|
+
appFrameCSSPath,
|
|
174
|
+
appCSSPath,
|
|
166
175
|
notificationElementTargetElement,
|
|
167
176
|
notificationElementShowAlways,
|
|
168
177
|
notificationElementPosition,
|
|
@@ -172,7 +181,9 @@ function StromcomConf({
|
|
|
172
181
|
homeBeforeRender,
|
|
173
182
|
notificationElementBeforeRender,
|
|
174
183
|
notificationElementAfterRender,
|
|
175
|
-
|
|
184
|
+
language,
|
|
185
|
+
theme,
|
|
186
|
+
entityResolve
|
|
176
187
|
]);
|
|
177
188
|
return null;
|
|
178
189
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.js","../src/StromcomProvider.jsx","../src/StromcomUser.jsx","../src/StromcomConf.jsx","../src/StromcomThread.jsx","../src/StromcomHome.jsx"],"sourcesContent":["export { StromcomProvider, useStromcom } from './StromcomProvider.jsx';\nexport { StromcomUser } from './StromcomUser.jsx';\nexport { StromcomConf } from './StromcomConf.jsx';\nexport { StromcomThread } from './StromcomThread.jsx';\nexport { StromcomHome } from './StromcomHome.jsx';\n","import { createContext, useContext, useEffect } from 'react';\n\nexport const StromcomContext = createContext(null);\n\nconst LOADER_URLS = {\n production: 'https://cdn.stromcom.cz/loader.js',\n staging: 'https://cdn.staging.stromcom.cz/loader.js',\n};\n\n/**\n * Sets up the stromCom stub layer synchronously so that child components can\n * queue calls (initUser, thread, conf, home) before the async loader.js arrives.\n * Mirrors what the PHP snippet() method generates.\n */\nfunction setupLayer(dataLayer) {\n if (typeof window === 'undefined') {\n return null;\n }\n\n if (window[dataLayer]?.__sc_ready) {\n return window[dataLayer];\n }\n\n const dl = dataLayer + 'DL';\n window[dl] = window[dl] || {};\n window[dataLayer] = window[dataLayer] || {};\n const layer = window[dataLayer];\n\n const stub = (method, dlKey, isArray) => {\n dlKey = dlKey || method;\n if (isArray) {\n window[dl][dlKey] = window[dl][dlKey] || [];\n }\n\n layer[method] = (...args) => {\n if (isArray) {\n window[dl][dlKey].push(args);\n } else {\n window[dl][dlKey] = args;\n }\n };\n };\n\n stub('initUser', 'user');\n stub('thread', 'threads', true);\n stub('conf');\n stub('home', 'home', true);\n\n layer.__sc_ready = true;\n return layer;\n}\n\n/**\n * Wraps the app and loads the Stromcom widget SDK.\n *\n * @param {object} props\n * @param {string} props.clientKey - Client key from the Stromcom dashboard\n * @param {string} props.clientSecret - Bearer token from the Stromcom dashboard\n * @param {string} [props.dataLayer] - Global JS variable name (default: \"stromCom\")\n * @param {string} [props.environment] - \"production\" | \"staging\" | full custom loader URL\n * @param {React.ReactNode} props.children\n *\n * @example\n * <StromcomProvider clientKey=\"ck_xxx\" clientSecret=\"cs_xxx\">\n * <App />\n * </StromcomProvider>\n */\nexport function StromcomProvider({\n clientKey,\n clientSecret,\n dataLayer = 'stromCom',\n environment = 'production',\n children,\n}) {\n // Set up stubs synchronously so children can queue calls immediately on render.\n const layer = setupLayer(dataLayer);\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n // Avoid injecting the script twice (React StrictMode, HMR, etc.)\n if (document.querySelector(`script[data-ck=\"${clientKey}\"][data-type=\"stromcom\"]`)) return;\n\n const base = LOADER_URLS[environment] ?? environment;\n const dl = dataLayer + 'DL';\n\n const script = document.createElement('script');\n script.async = true;\n script.dataset.type = 'stromcom';\n script.dataset.l = dataLayer;\n script.dataset.dl = dl;\n script.dataset.ck = clientKey;\n script.dataset.cs = clientSecret;\n script.src = `${base}?${clientKey}`;\n document.head.appendChild(script);\n\n return () => script.remove();\n }, [clientKey, clientSecret, dataLayer, environment]);\n\n return <StromcomContext.Provider value={layer}>{children}</StromcomContext.Provider>;\n}\n\nexport function useStromcom() {\n return useContext(StromcomContext);\n}\n","import { useEffect } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Identifies the currently logged-in user.\n * Re-calls initUser whenever any prop changes.\n *\n * IMPORTANT: The `code` should already be hashed server-side (HMAC + base-62).\n * Never pass your raw internal user ID directly from the client.\n *\n * @param {object} props\n * @param {string} props.code - Hashed unique user identifier (max 100 chars, [a-zA-Z0-9-_])\n * @param {string} [props.name] - Display name\n * @param {string} [props.emailAddress]- Used for email notifications\n * @param {boolean} [props.readOnly] - When true, user can read but not send messages\n * @param {string} [props.avatarURL] - Full URL to avatar image\n *\n * @example\n * <StromcomUser\n * code={user.stromcomHash}\n * name={user.name}\n * emailAddress={user.email}\n * />\n */\nexport function StromcomUser({ code, name, emailAddress, readOnly, avatarURL }) {\n const sc = useStromcom();\n\n useEffect(() => {\n if (!sc) return;\n\n const opts = { code };\n if (name !== undefined) opts.name = name;\n if (emailAddress !== undefined) opts.emailAddress = emailAddress;\n if (readOnly !== undefined) opts.readOnly = readOnly;\n if (avatarURL !== undefined) opts.avatarURL = avatarURL;\n\n sc.initUser(opts);\n }, [sc, code, name, emailAddress, readOnly, avatarURL]);\n\n return null;\n}\n","import { useEffect } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Sends SDK configuration to the Stromcom widget.\n * Should be placed once inside <StromcomProvider>, before threads or the home component.\n * Re-sends conf whenever any option prop changes.\n *\n * Options that accept functions (notificationRenderer, onNotification, etc.) take\n * actual JS functions here — no need for the string-wrapping used in the PHP library.\n *\n * @param {object} [props.notificationRenderer] - Function(notification) for custom notification rendering\n * @param {object} [props.onNotification] - Function(count) called when unread count changes\n * @param {string} [props.pageCSSPath] - URL of CSS file injected into the widget iframe\n * @param {Function} [props.notificationElementTargetElement] - Function/Promise/Element returning the icon mount target\n * @param {boolean} [props.notificationElementShowAlways] - Show icon even with zero notifications\n * @param {number} [props.notificationElementPosition] - Icon position: 1=top-left 2=top-right 3=bottom-right 4=bottom-left\n * @param {object} [props.notificationElementStyles] - Inline styles object for the default notification button\n * @param {string} [props.notificationElementCSSPath] - CSS URL for the default notification renderer\n * @param {boolean} [props.notificationElementNoShadowRoot]- Disable shadow-root for notifications\n * @param {Function} [props.homeBeforeRender] - Callback before notification center opens\n * @param {Function} [props.notificationElementBeforeRender]- Callback before notification icon renders\n * @param {Function} [props.notificationElementAfterRender] - Callback after notification icon renders\n * @param {('stromcom-light'|'stromcom-dark'|null)} [props.theme] - Theme. null follows browser preference.\n *\n * @example\n * <StromcomConf\n * notificationElementPosition={4}\n * onNotification={(count) => setBadge(count)}\n * />\n */\nexport function StromcomConf({\n notificationRenderer,\n onNotification,\n pageCSSPath,\n notificationElementTargetElement,\n notificationElementShowAlways,\n notificationElementPosition,\n notificationElementStyles,\n notificationElementCSSPath,\n notificationElementNoShadowRoot,\n homeBeforeRender,\n notificationElementBeforeRender,\n notificationElementAfterRender,\n theme,\n}) {\n const sc = useStromcom();\n\n useEffect(() => {\n if (!sc) return;\n\n const opts = {};\n\n if (notificationRenderer !== undefined) opts.notificationRenderer = notificationRenderer;\n if (onNotification !== undefined) opts.onNotification = onNotification;\n if (pageCSSPath !== undefined) opts.pageCSSPath = pageCSSPath;\n if (notificationElementTargetElement !== undefined)\n opts.notificationElementTargetElement = notificationElementTargetElement;\n if (notificationElementShowAlways !== undefined)\n opts.notificationElementShowAlways = notificationElementShowAlways;\n if (notificationElementPosition !== undefined)\n opts.notificationElementPosition = notificationElementPosition;\n if (notificationElementStyles !== undefined)\n opts.notificationElementStyles = notificationElementStyles;\n if (notificationElementCSSPath !== undefined)\n opts.notificationElementCSSPath = notificationElementCSSPath;\n if (notificationElementNoShadowRoot !== undefined)\n opts.notificationElementNoShadowRoot = notificationElementNoShadowRoot;\n if (homeBeforeRender !== undefined) opts.homeBeforeRender = homeBeforeRender;\n if (notificationElementBeforeRender !== undefined)\n opts.notificationElementBeforeRender = notificationElementBeforeRender;\n if (notificationElementAfterRender !== undefined)\n opts.notificationElementAfterRender = notificationElementAfterRender;\n if (theme !== undefined) opts.theme = theme;\n\n sc.conf(opts);\n }, [\n sc,\n notificationRenderer,\n onNotification,\n pageCSSPath,\n notificationElementTargetElement,\n notificationElementShowAlways,\n notificationElementPosition,\n notificationElementStyles,\n notificationElementCSSPath,\n notificationElementNoShadowRoot,\n homeBeforeRender,\n notificationElementBeforeRender,\n notificationElementAfterRender,\n theme,\n ]);\n\n return null;\n}\n","import { useEffect, useRef } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Embeds a Stromcom conversation thread into the rendered div.\n * The thread is initialized once on mount.\n *\n * IMPORTANT: The `code` should already be hashed server-side (HMAC + base-62).\n *\n * @param {object} props\n * @param {string} props.code - Hashed unique thread identifier (max 100 chars, [a-zA-Z0-9-_])\n * @param {string} [props.name] - Display name shown in the thread header\n * @param {string} [props.url] - Canonical URL of the page (shown as link in header)\n * @param {boolean} [props.userHint] - Enable @mention suggestions (default: true)\n * @param {string} [props.className] - CSS class for the container div\n * @param {object} [props.style] - Inline styles for the container div\n *\n * @example\n * <StromcomThread\n * code={order.stromcomHash}\n * name={`Order #${order.id}`}\n * url={window.location.href}\n * style={{ height: '400px' }}\n * />\n */\nexport function StromcomThread({ code, name, url, userHint, className, style, ...rest }) {\n const sc = useStromcom();\n const containerRef = useRef(null);\n const initializedRef = useRef(false);\n\n useEffect(() => {\n if (!sc || !containerRef.current || initializedRef.current) return;\n\n const opts = { code };\n if (name !== undefined) opts.name = name;\n if (url !== undefined) opts.url = url;\n if (userHint !== undefined) opts.userHint = userHint;\n\n sc.thread(containerRef.current, opts);\n initializedRef.current = true;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally omit option deps, thread cannot be re-initialized\n }, [sc]);\n\n return <div ref={containerRef} className={className} style={style} {...rest} />;\n}\n","import { useEffect, useRef } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Embeds the Stromcom notification center into the rendered div.\n * Initialized once on mount.\n *\n * @param {object} [props.className] - CSS class for the container div\n * @param {object} [props.style] - Inline styles for the container div\n *\n * @example\n * <StromcomHome style={{ position: 'fixed', bottom: 24, right: 24 }} />\n */\nexport function StromcomHome({ className, style, ...rest }) {\n const sc = useStromcom();\n const containerRef = useRef(null);\n const initializedRef = useRef(false);\n\n useEffect(() => {\n if (!sc || !containerRef.current || initializedRef.current) return;\n sc.home(containerRef.current);\n initializedRef.current = true;\n }, [sc]);\n\n return <div ref={containerRef} className={className} style={style} {...rest} />;\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_jsx_runtime","import_react","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.js","../src/StromcomProvider.jsx","../src/StromcomUser.jsx","../src/StromcomConf.jsx","../src/StromcomThread.jsx","../src/StromcomHome.jsx"],"sourcesContent":["export { StromcomProvider, useStromcom } from './StromcomProvider.jsx';\nexport { StromcomUser } from './StromcomUser.jsx';\nexport { StromcomConf } from './StromcomConf.jsx';\nexport { StromcomThread } from './StromcomThread.jsx';\nexport { StromcomHome } from './StromcomHome.jsx';\n","import { createContext, useContext, useEffect } from 'react';\n\nexport const StromcomContext = createContext(null);\n\nconst LOADER_URLS = {\n production: 'https://cdn.stromcom.cz/loader.js',\n staging: 'https://cdn.staging.stromcom.cz/loader.js',\n};\n\n/**\n * Sets up the stromCom stub layer synchronously so that child components can\n * queue calls (initUser, thread, conf, home) before the async loader.js arrives.\n * Mirrors what the PHP snippet() method generates.\n */\nfunction setupLayer(dataLayer) {\n if (typeof window === 'undefined') {\n return null;\n }\n\n if (window[dataLayer]?.__sc_ready) {\n return window[dataLayer];\n }\n\n const dl = dataLayer + 'DL';\n window[dl] = window[dl] || {};\n window[dataLayer] = window[dataLayer] || {};\n const layer = window[dataLayer];\n\n const stub = (method, dlKey, isArray) => {\n dlKey = dlKey || method;\n if (isArray) {\n window[dl][dlKey] = window[dl][dlKey] || [];\n }\n\n layer[method] = (...args) => {\n if (isArray) {\n window[dl][dlKey].push(args);\n } else {\n window[dl][dlKey] = args;\n }\n };\n };\n\n stub('initUser', 'user');\n stub('thread', 'threads', true);\n stub('conf');\n stub('home', 'home', true);\n\n layer.__sc_ready = true;\n return layer;\n}\n\n/**\n * Wraps the app and loads the Stromcom widget SDK.\n *\n * @param {object} props\n * @param {string} props.clientKey - Client key from the Stromcom dashboard\n * @param {string} props.clientSecret - Bearer token from the Stromcom dashboard\n * @param {string} [props.dataLayer] - Global JS variable name (default: \"stromCom\")\n * @param {string} [props.environment] - \"production\" | \"staging\" | full custom loader URL\n * @param {string} [props.language] - Initial UI language (e.g. \"cs\", \"en\", \"sk\"). Omit to auto-detect from the\n * browser. Use StromcomConf's `language` prop to change it at runtime.\n * @param {React.ReactNode} props.children\n *\n * @example\n * <StromcomProvider clientKey=\"ck_xxx\" clientSecret=\"cs_xxx\">\n * <App />\n * </StromcomProvider>\n */\nexport function StromcomProvider({\n clientKey,\n clientSecret,\n dataLayer = 'stromCom',\n environment = 'production',\n language,\n children,\n}) {\n // Set up stubs synchronously so children can queue calls immediately on render.\n const layer = setupLayer(dataLayer);\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n // Avoid injecting the script twice (React StrictMode, HMR, etc.)\n if (document.querySelector(`script[data-ck=\"${clientKey}\"][data-type=\"stromcom\"]`)) return;\n\n const base = LOADER_URLS[environment] ?? environment;\n const dl = dataLayer + 'DL';\n\n const script = document.createElement('script');\n script.async = true;\n script.dataset.type = 'stromcom';\n script.dataset.l = dataLayer;\n script.dataset.dl = dl;\n script.dataset.ck = clientKey;\n script.dataset.cs = clientSecret;\n if (language) script.dataset.lang = language;\n script.src = `${base}?${clientKey}`;\n document.head.appendChild(script);\n\n return () => script.remove();\n }, [clientKey, clientSecret, dataLayer, environment, language]);\n\n return <StromcomContext.Provider value={layer}>{children}</StromcomContext.Provider>;\n}\n\nexport function useStromcom() {\n return useContext(StromcomContext);\n}\n","import { useEffect } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Identifies the currently logged-in user.\n * Re-calls initUser whenever any prop changes.\n *\n * IMPORTANT: The `code` should already be hashed server-side (HMAC + base-62).\n * Never pass your raw internal user ID directly from the client.\n *\n * @param {object} props\n * @param {string} props.code - Hashed unique user identifier (max 100 chars, [a-zA-Z0-9-_])\n * @param {string} [props.name] - Display name\n * @param {string} [props.emailAddress]- Used for email notifications\n * @param {boolean} [props.readOnly] - When true, user can read but not send messages\n * @param {string} [props.avatarURL] - Full URL to avatar image\n *\n * @example\n * <StromcomUser\n * code={user.stromcomHash}\n * name={user.name}\n * emailAddress={user.email}\n * />\n */\nexport function StromcomUser({ code, name, emailAddress, readOnly, avatarURL }) {\n const sc = useStromcom();\n\n useEffect(() => {\n if (!sc) return;\n\n const opts = { code };\n if (name !== undefined) opts.name = name;\n if (emailAddress !== undefined) opts.emailAddress = emailAddress;\n if (readOnly !== undefined) opts.readOnly = readOnly;\n if (avatarURL !== undefined) opts.avatarURL = avatarURL;\n\n sc.initUser(opts);\n }, [sc, code, name, emailAddress, readOnly, avatarURL]);\n\n return null;\n}\n","import { useEffect } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Sends SDK configuration to the Stromcom widget.\n * Should be placed once inside <StromcomProvider>, before threads or the home component.\n * Re-sends conf whenever any option prop changes.\n *\n * Options that accept functions (notificationRenderer, onNotification, etc.) take\n * actual JS functions here — no need for the string-wrapping used in the PHP library.\n *\n * @param {object} [props.notificationRenderer] - Function(notification) for custom notification rendering\n * @param {object} [props.onNotification] - Function(count) called when unread count changes\n * @param {(string|string[])} [props.appFrameCSSPath] - CSS URL(s) for the widget frame — the shadow root holding the container, the backdrop and the iframe\n * @param {(string|string[])} [props.appCSSPath] - CSS URL(s) for the app itself (inside the iframe), on top of the CSS from the project settings\n * @param {Function} [props.notificationElementTargetElement] - Function/Promise/Element returning the icon mount target\n * @param {boolean} [props.notificationElementShowAlways] - Show icon even with zero notifications\n * @param {number} [props.notificationElementPosition] - Icon position: 1=top-left 2=top-right 3=bottom-right 4=bottom-left\n * @param {object} [props.notificationElementStyles] - Inline styles object for the default notification button\n * @param {string} [props.notificationElementCSSPath] - CSS URL for the default notification renderer\n * @param {boolean} [props.notificationElementNoShadowRoot]- Disable shadow-root for notifications\n * @param {Function} [props.homeBeforeRender] - Callback before notification center opens\n * @param {Function} [props.notificationElementBeforeRender]- Callback before notification icon renders\n * @param {Function} [props.notificationElementAfterRender] - Callback after notification icon renders\n * @param {(string|null)} [props.language] - UI language. Any code is accepted; one without a translation falls\n * back to the browser language, then English. null follows the browser. Overrides the `language`\n * prop on StromcomProvider, which is only the initial value; this one\n * can be changed at runtime.\n * @param {('stromcom-light'|'stromcom-dark'|null)} [props.theme] - Theme. null follows browser preference.\n * @param {Function} [props.entityResolve] - async ({type, id}) => detail; resolves business-entity detail for the message editor's chip hover-card\n *\n * @example\n * <StromcomConf\n * notificationElementPosition={4}\n * onNotification={(count) => setBadge(count)}\n * />\n */\nexport function StromcomConf({\n notificationRenderer,\n onNotification,\n appFrameCSSPath,\n appCSSPath,\n notificationElementTargetElement,\n notificationElementShowAlways,\n notificationElementPosition,\n notificationElementStyles,\n notificationElementCSSPath,\n notificationElementNoShadowRoot,\n homeBeforeRender,\n notificationElementBeforeRender,\n notificationElementAfterRender,\n language,\n theme,\n entityResolve,\n}) {\n const sc = useStromcom();\n\n useEffect(() => {\n if (!sc) return;\n\n const opts = {};\n\n if (notificationRenderer !== undefined) opts.notificationRenderer = notificationRenderer;\n if (onNotification !== undefined) opts.onNotification = onNotification;\n if (appFrameCSSPath !== undefined) opts.appFrameCSSPath = appFrameCSSPath;\n if (appCSSPath !== undefined) opts.appCSSPath = appCSSPath;\n if (notificationElementTargetElement !== undefined)\n opts.notificationElementTargetElement = notificationElementTargetElement;\n if (notificationElementShowAlways !== undefined)\n opts.notificationElementShowAlways = notificationElementShowAlways;\n if (notificationElementPosition !== undefined)\n opts.notificationElementPosition = notificationElementPosition;\n if (notificationElementStyles !== undefined)\n opts.notificationElementStyles = notificationElementStyles;\n if (notificationElementCSSPath !== undefined)\n opts.notificationElementCSSPath = notificationElementCSSPath;\n if (notificationElementNoShadowRoot !== undefined)\n opts.notificationElementNoShadowRoot = notificationElementNoShadowRoot;\n if (homeBeforeRender !== undefined) opts.homeBeforeRender = homeBeforeRender;\n if (notificationElementBeforeRender !== undefined)\n opts.notificationElementBeforeRender = notificationElementBeforeRender;\n if (notificationElementAfterRender !== undefined)\n opts.notificationElementAfterRender = notificationElementAfterRender;\n if (language !== undefined) opts.language = language;\n if (theme !== undefined) opts.theme = theme;\n if (entityResolve !== undefined) opts.entityResolve = entityResolve;\n\n sc.conf(opts);\n }, [\n sc,\n notificationRenderer,\n onNotification,\n appFrameCSSPath,\n appCSSPath,\n notificationElementTargetElement,\n notificationElementShowAlways,\n notificationElementPosition,\n notificationElementStyles,\n notificationElementCSSPath,\n notificationElementNoShadowRoot,\n homeBeforeRender,\n notificationElementBeforeRender,\n notificationElementAfterRender,\n language,\n theme,\n entityResolve,\n ]);\n\n return null;\n}\n","import { useEffect, useRef } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Embeds a Stromcom conversation thread into the rendered div.\n * The thread is initialized once on mount.\n *\n * IMPORTANT: The `code` should already be hashed server-side (HMAC + base-62).\n *\n * @param {object} props\n * @param {string} props.code - Hashed unique thread identifier (max 100 chars, [a-zA-Z0-9-_])\n * @param {string} [props.name] - Display name shown in the thread header\n * @param {string} [props.url] - Canonical URL of the page (shown as link in header)\n * @param {boolean} [props.userHint] - Enable @mention suggestions (default: true)\n * @param {string} [props.className] - CSS class for the container div\n * @param {object} [props.style] - Inline styles for the container div\n *\n * @example\n * <StromcomThread\n * code={order.stromcomHash}\n * name={`Order #${order.id}`}\n * url={window.location.href}\n * style={{ height: '400px' }}\n * />\n */\nexport function StromcomThread({ code, name, url, userHint, className, style, ...rest }) {\n const sc = useStromcom();\n const containerRef = useRef(null);\n const initializedRef = useRef(false);\n\n useEffect(() => {\n if (!sc || !containerRef.current || initializedRef.current) return;\n\n const opts = { code };\n if (name !== undefined) opts.name = name;\n if (url !== undefined) opts.url = url;\n if (userHint !== undefined) opts.userHint = userHint;\n\n sc.thread(containerRef.current, opts);\n initializedRef.current = true;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally omit option deps, thread cannot be re-initialized\n }, [sc]);\n\n return <div ref={containerRef} className={className} style={style} {...rest} />;\n}\n","import { useEffect, useRef } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Embeds the Stromcom notification center into the rendered div.\n * Initialized once on mount.\n *\n * @param {object} [props.className] - CSS class for the container div\n * @param {object} [props.style] - Inline styles for the container div\n *\n * @example\n * <StromcomHome style={{ position: 'fixed', bottom: 24, right: 24 }} />\n */\nexport function StromcomHome({ className, style, ...rest }) {\n const sc = useStromcom();\n const containerRef = useRef(null);\n const initializedRef = useRef(false);\n\n useEffect(() => {\n if (!sc || !containerRef.current || initializedRef.current) return;\n sc.home(containerRef.current);\n initializedRef.current = true;\n }, [sc]);\n\n return <div ref={containerRef} className={className} style={style} {...rest} />;\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_jsx_runtime","import_react","import_jsx_runtime"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,12 @@ export interface StromcomProviderProps {
|
|
|
19
19
|
dataLayer?: string;
|
|
20
20
|
/** `"production"`, `"staging"`, or a full custom loader URL. Default `"production"`. */
|
|
21
21
|
environment?: StromcomEnvironment;
|
|
22
|
+
/**
|
|
23
|
+
* Initial UI language (e.g. `"cs"`, `"en"`, `"sk"`). Any code is accepted; one without a
|
|
24
|
+
* translation falls back to the browser language, then English. Omit to auto-detect from the browser.
|
|
25
|
+
* Use `StromcomConf`'s `language` prop to change it at runtime.
|
|
26
|
+
*/
|
|
27
|
+
language?: string;
|
|
22
28
|
children?: ReactNode;
|
|
23
29
|
}
|
|
24
30
|
|
|
@@ -66,7 +72,13 @@ export function StromcomHome(props: StromcomHomeProps): JSX.Element;
|
|
|
66
72
|
export interface StromcomConfOptions {
|
|
67
73
|
notificationRenderer?: (notification: unknown) => unknown;
|
|
68
74
|
onNotification?: (count: number) => void;
|
|
69
|
-
|
|
75
|
+
/** CSS for the widget frame — the shadow root holding the container, the backdrop and the iframe. */
|
|
76
|
+
appFrameCSSPath?: string | string[];
|
|
77
|
+
/**
|
|
78
|
+
* CSS for the app itself (inside the iframe), on top of the CSS from the project
|
|
79
|
+
* settings. Applied only on a plan that allows appearance customization.
|
|
80
|
+
*/
|
|
81
|
+
appCSSPath?: string | string[];
|
|
70
82
|
notificationElementTargetElement?:
|
|
71
83
|
(() => HTMLElement | Promise<HTMLElement>) | HTMLElement | Promise<HTMLElement>;
|
|
72
84
|
notificationElementShowAlways?: boolean;
|
|
@@ -78,8 +90,16 @@ export interface StromcomConfOptions {
|
|
|
78
90
|
homeBeforeRender?: () => void | Promise<void>;
|
|
79
91
|
notificationElementBeforeRender?: () => void | Promise<void>;
|
|
80
92
|
notificationElementAfterRender?: () => void;
|
|
93
|
+
/**
|
|
94
|
+
* UI language, or `null` to follow the browser. Any language code is accepted;
|
|
95
|
+
* one the widget has no translation for falls back to the browser language, then English.
|
|
96
|
+
* Overrides `StromcomProviderProps.language`, which is only the initial value.
|
|
97
|
+
*/
|
|
98
|
+
language?: string | null;
|
|
81
99
|
/** Theme: `"stromcom-light"`, `"stromcom-dark"`, or `null` to follow browser preference. */
|
|
82
100
|
theme?: 'stromcom-light' | 'stromcom-dark' | null;
|
|
101
|
+
/** Resolves business-entity detail (order, ticket…) for the message editor's chip hover-card. */
|
|
102
|
+
entityResolve?: (entity: { type: string; id: string }) => unknown | Promise<unknown>;
|
|
83
103
|
}
|
|
84
104
|
|
|
85
105
|
export function StromcomConf(props: StromcomConfOptions): null;
|
package/dist/index.js
CHANGED
|
@@ -43,6 +43,7 @@ function StromcomProvider({
|
|
|
43
43
|
clientSecret,
|
|
44
44
|
dataLayer = "stromCom",
|
|
45
45
|
environment = "production",
|
|
46
|
+
language,
|
|
46
47
|
children
|
|
47
48
|
}) {
|
|
48
49
|
const layer = setupLayer(dataLayer);
|
|
@@ -59,10 +60,11 @@ function StromcomProvider({
|
|
|
59
60
|
script.dataset.dl = dl;
|
|
60
61
|
script.dataset.ck = clientKey;
|
|
61
62
|
script.dataset.cs = clientSecret;
|
|
63
|
+
if (language) script.dataset.lang = language;
|
|
62
64
|
script.src = `${base}?${clientKey}`;
|
|
63
65
|
document.head.appendChild(script);
|
|
64
66
|
return () => script.remove();
|
|
65
|
-
}, [clientKey, clientSecret, dataLayer, environment]);
|
|
67
|
+
}, [clientKey, clientSecret, dataLayer, environment, language]);
|
|
66
68
|
return /* @__PURE__ */ jsx(StromcomContext.Provider, { value: layer, children });
|
|
67
69
|
}
|
|
68
70
|
function useStromcom() {
|
|
@@ -90,7 +92,8 @@ import { useEffect as useEffect3 } from "react";
|
|
|
90
92
|
function StromcomConf({
|
|
91
93
|
notificationRenderer,
|
|
92
94
|
onNotification,
|
|
93
|
-
|
|
95
|
+
appFrameCSSPath,
|
|
96
|
+
appCSSPath,
|
|
94
97
|
notificationElementTargetElement,
|
|
95
98
|
notificationElementShowAlways,
|
|
96
99
|
notificationElementPosition,
|
|
@@ -100,7 +103,9 @@ function StromcomConf({
|
|
|
100
103
|
homeBeforeRender,
|
|
101
104
|
notificationElementBeforeRender,
|
|
102
105
|
notificationElementAfterRender,
|
|
103
|
-
|
|
106
|
+
language,
|
|
107
|
+
theme,
|
|
108
|
+
entityResolve
|
|
104
109
|
}) {
|
|
105
110
|
const sc = useStromcom();
|
|
106
111
|
useEffect3(() => {
|
|
@@ -108,7 +113,8 @@ function StromcomConf({
|
|
|
108
113
|
const opts = {};
|
|
109
114
|
if (notificationRenderer !== void 0) opts.notificationRenderer = notificationRenderer;
|
|
110
115
|
if (onNotification !== void 0) opts.onNotification = onNotification;
|
|
111
|
-
if (
|
|
116
|
+
if (appFrameCSSPath !== void 0) opts.appFrameCSSPath = appFrameCSSPath;
|
|
117
|
+
if (appCSSPath !== void 0) opts.appCSSPath = appCSSPath;
|
|
112
118
|
if (notificationElementTargetElement !== void 0)
|
|
113
119
|
opts.notificationElementTargetElement = notificationElementTargetElement;
|
|
114
120
|
if (notificationElementShowAlways !== void 0)
|
|
@@ -126,13 +132,16 @@ function StromcomConf({
|
|
|
126
132
|
opts.notificationElementBeforeRender = notificationElementBeforeRender;
|
|
127
133
|
if (notificationElementAfterRender !== void 0)
|
|
128
134
|
opts.notificationElementAfterRender = notificationElementAfterRender;
|
|
135
|
+
if (language !== void 0) opts.language = language;
|
|
129
136
|
if (theme !== void 0) opts.theme = theme;
|
|
137
|
+
if (entityResolve !== void 0) opts.entityResolve = entityResolve;
|
|
130
138
|
sc.conf(opts);
|
|
131
139
|
}, [
|
|
132
140
|
sc,
|
|
133
141
|
notificationRenderer,
|
|
134
142
|
onNotification,
|
|
135
|
-
|
|
143
|
+
appFrameCSSPath,
|
|
144
|
+
appCSSPath,
|
|
136
145
|
notificationElementTargetElement,
|
|
137
146
|
notificationElementShowAlways,
|
|
138
147
|
notificationElementPosition,
|
|
@@ -142,7 +151,9 @@ function StromcomConf({
|
|
|
142
151
|
homeBeforeRender,
|
|
143
152
|
notificationElementBeforeRender,
|
|
144
153
|
notificationElementAfterRender,
|
|
145
|
-
|
|
154
|
+
language,
|
|
155
|
+
theme,
|
|
156
|
+
entityResolve
|
|
146
157
|
]);
|
|
147
158
|
return null;
|
|
148
159
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/StromcomProvider.jsx","../src/StromcomUser.jsx","../src/StromcomConf.jsx","../src/StromcomThread.jsx","../src/StromcomHome.jsx"],"sourcesContent":["import { createContext, useContext, useEffect } from 'react';\n\nexport const StromcomContext = createContext(null);\n\nconst LOADER_URLS = {\n production: 'https://cdn.stromcom.cz/loader.js',\n staging: 'https://cdn.staging.stromcom.cz/loader.js',\n};\n\n/**\n * Sets up the stromCom stub layer synchronously so that child components can\n * queue calls (initUser, thread, conf, home) before the async loader.js arrives.\n * Mirrors what the PHP snippet() method generates.\n */\nfunction setupLayer(dataLayer) {\n if (typeof window === 'undefined') {\n return null;\n }\n\n if (window[dataLayer]?.__sc_ready) {\n return window[dataLayer];\n }\n\n const dl = dataLayer + 'DL';\n window[dl] = window[dl] || {};\n window[dataLayer] = window[dataLayer] || {};\n const layer = window[dataLayer];\n\n const stub = (method, dlKey, isArray) => {\n dlKey = dlKey || method;\n if (isArray) {\n window[dl][dlKey] = window[dl][dlKey] || [];\n }\n\n layer[method] = (...args) => {\n if (isArray) {\n window[dl][dlKey].push(args);\n } else {\n window[dl][dlKey] = args;\n }\n };\n };\n\n stub('initUser', 'user');\n stub('thread', 'threads', true);\n stub('conf');\n stub('home', 'home', true);\n\n layer.__sc_ready = true;\n return layer;\n}\n\n/**\n * Wraps the app and loads the Stromcom widget SDK.\n *\n * @param {object} props\n * @param {string} props.clientKey - Client key from the Stromcom dashboard\n * @param {string} props.clientSecret - Bearer token from the Stromcom dashboard\n * @param {string} [props.dataLayer] - Global JS variable name (default: \"stromCom\")\n * @param {string} [props.environment] - \"production\" | \"staging\" | full custom loader URL\n * @param {React.ReactNode} props.children\n *\n * @example\n * <StromcomProvider clientKey=\"ck_xxx\" clientSecret=\"cs_xxx\">\n * <App />\n * </StromcomProvider>\n */\nexport function StromcomProvider({\n clientKey,\n clientSecret,\n dataLayer = 'stromCom',\n environment = 'production',\n children,\n}) {\n // Set up stubs synchronously so children can queue calls immediately on render.\n const layer = setupLayer(dataLayer);\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n // Avoid injecting the script twice (React StrictMode, HMR, etc.)\n if (document.querySelector(`script[data-ck=\"${clientKey}\"][data-type=\"stromcom\"]`)) return;\n\n const base = LOADER_URLS[environment] ?? environment;\n const dl = dataLayer + 'DL';\n\n const script = document.createElement('script');\n script.async = true;\n script.dataset.type = 'stromcom';\n script.dataset.l = dataLayer;\n script.dataset.dl = dl;\n script.dataset.ck = clientKey;\n script.dataset.cs = clientSecret;\n script.src = `${base}?${clientKey}`;\n document.head.appendChild(script);\n\n return () => script.remove();\n }, [clientKey, clientSecret, dataLayer, environment]);\n\n return <StromcomContext.Provider value={layer}>{children}</StromcomContext.Provider>;\n}\n\nexport function useStromcom() {\n return useContext(StromcomContext);\n}\n","import { useEffect } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Identifies the currently logged-in user.\n * Re-calls initUser whenever any prop changes.\n *\n * IMPORTANT: The `code` should already be hashed server-side (HMAC + base-62).\n * Never pass your raw internal user ID directly from the client.\n *\n * @param {object} props\n * @param {string} props.code - Hashed unique user identifier (max 100 chars, [a-zA-Z0-9-_])\n * @param {string} [props.name] - Display name\n * @param {string} [props.emailAddress]- Used for email notifications\n * @param {boolean} [props.readOnly] - When true, user can read but not send messages\n * @param {string} [props.avatarURL] - Full URL to avatar image\n *\n * @example\n * <StromcomUser\n * code={user.stromcomHash}\n * name={user.name}\n * emailAddress={user.email}\n * />\n */\nexport function StromcomUser({ code, name, emailAddress, readOnly, avatarURL }) {\n const sc = useStromcom();\n\n useEffect(() => {\n if (!sc) return;\n\n const opts = { code };\n if (name !== undefined) opts.name = name;\n if (emailAddress !== undefined) opts.emailAddress = emailAddress;\n if (readOnly !== undefined) opts.readOnly = readOnly;\n if (avatarURL !== undefined) opts.avatarURL = avatarURL;\n\n sc.initUser(opts);\n }, [sc, code, name, emailAddress, readOnly, avatarURL]);\n\n return null;\n}\n","import { useEffect } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Sends SDK configuration to the Stromcom widget.\n * Should be placed once inside <StromcomProvider>, before threads or the home component.\n * Re-sends conf whenever any option prop changes.\n *\n * Options that accept functions (notificationRenderer, onNotification, etc.) take\n * actual JS functions here — no need for the string-wrapping used in the PHP library.\n *\n * @param {object} [props.notificationRenderer] - Function(notification) for custom notification rendering\n * @param {object} [props.onNotification] - Function(count) called when unread count changes\n * @param {string} [props.pageCSSPath] - URL of CSS file injected into the widget iframe\n * @param {Function} [props.notificationElementTargetElement] - Function/Promise/Element returning the icon mount target\n * @param {boolean} [props.notificationElementShowAlways] - Show icon even with zero notifications\n * @param {number} [props.notificationElementPosition] - Icon position: 1=top-left 2=top-right 3=bottom-right 4=bottom-left\n * @param {object} [props.notificationElementStyles] - Inline styles object for the default notification button\n * @param {string} [props.notificationElementCSSPath] - CSS URL for the default notification renderer\n * @param {boolean} [props.notificationElementNoShadowRoot]- Disable shadow-root for notifications\n * @param {Function} [props.homeBeforeRender] - Callback before notification center opens\n * @param {Function} [props.notificationElementBeforeRender]- Callback before notification icon renders\n * @param {Function} [props.notificationElementAfterRender] - Callback after notification icon renders\n * @param {('stromcom-light'|'stromcom-dark'|null)} [props.theme] - Theme. null follows browser preference.\n *\n * @example\n * <StromcomConf\n * notificationElementPosition={4}\n * onNotification={(count) => setBadge(count)}\n * />\n */\nexport function StromcomConf({\n notificationRenderer,\n onNotification,\n pageCSSPath,\n notificationElementTargetElement,\n notificationElementShowAlways,\n notificationElementPosition,\n notificationElementStyles,\n notificationElementCSSPath,\n notificationElementNoShadowRoot,\n homeBeforeRender,\n notificationElementBeforeRender,\n notificationElementAfterRender,\n theme,\n}) {\n const sc = useStromcom();\n\n useEffect(() => {\n if (!sc) return;\n\n const opts = {};\n\n if (notificationRenderer !== undefined) opts.notificationRenderer = notificationRenderer;\n if (onNotification !== undefined) opts.onNotification = onNotification;\n if (pageCSSPath !== undefined) opts.pageCSSPath = pageCSSPath;\n if (notificationElementTargetElement !== undefined)\n opts.notificationElementTargetElement = notificationElementTargetElement;\n if (notificationElementShowAlways !== undefined)\n opts.notificationElementShowAlways = notificationElementShowAlways;\n if (notificationElementPosition !== undefined)\n opts.notificationElementPosition = notificationElementPosition;\n if (notificationElementStyles !== undefined)\n opts.notificationElementStyles = notificationElementStyles;\n if (notificationElementCSSPath !== undefined)\n opts.notificationElementCSSPath = notificationElementCSSPath;\n if (notificationElementNoShadowRoot !== undefined)\n opts.notificationElementNoShadowRoot = notificationElementNoShadowRoot;\n if (homeBeforeRender !== undefined) opts.homeBeforeRender = homeBeforeRender;\n if (notificationElementBeforeRender !== undefined)\n opts.notificationElementBeforeRender = notificationElementBeforeRender;\n if (notificationElementAfterRender !== undefined)\n opts.notificationElementAfterRender = notificationElementAfterRender;\n if (theme !== undefined) opts.theme = theme;\n\n sc.conf(opts);\n }, [\n sc,\n notificationRenderer,\n onNotification,\n pageCSSPath,\n notificationElementTargetElement,\n notificationElementShowAlways,\n notificationElementPosition,\n notificationElementStyles,\n notificationElementCSSPath,\n notificationElementNoShadowRoot,\n homeBeforeRender,\n notificationElementBeforeRender,\n notificationElementAfterRender,\n theme,\n ]);\n\n return null;\n}\n","import { useEffect, useRef } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Embeds a Stromcom conversation thread into the rendered div.\n * The thread is initialized once on mount.\n *\n * IMPORTANT: The `code` should already be hashed server-side (HMAC + base-62).\n *\n * @param {object} props\n * @param {string} props.code - Hashed unique thread identifier (max 100 chars, [a-zA-Z0-9-_])\n * @param {string} [props.name] - Display name shown in the thread header\n * @param {string} [props.url] - Canonical URL of the page (shown as link in header)\n * @param {boolean} [props.userHint] - Enable @mention suggestions (default: true)\n * @param {string} [props.className] - CSS class for the container div\n * @param {object} [props.style] - Inline styles for the container div\n *\n * @example\n * <StromcomThread\n * code={order.stromcomHash}\n * name={`Order #${order.id}`}\n * url={window.location.href}\n * style={{ height: '400px' }}\n * />\n */\nexport function StromcomThread({ code, name, url, userHint, className, style, ...rest }) {\n const sc = useStromcom();\n const containerRef = useRef(null);\n const initializedRef = useRef(false);\n\n useEffect(() => {\n if (!sc || !containerRef.current || initializedRef.current) return;\n\n const opts = { code };\n if (name !== undefined) opts.name = name;\n if (url !== undefined) opts.url = url;\n if (userHint !== undefined) opts.userHint = userHint;\n\n sc.thread(containerRef.current, opts);\n initializedRef.current = true;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally omit option deps, thread cannot be re-initialized\n }, [sc]);\n\n return <div ref={containerRef} className={className} style={style} {...rest} />;\n}\n","import { useEffect, useRef } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Embeds the Stromcom notification center into the rendered div.\n * Initialized once on mount.\n *\n * @param {object} [props.className] - CSS class for the container div\n * @param {object} [props.style] - Inline styles for the container div\n *\n * @example\n * <StromcomHome style={{ position: 'fixed', bottom: 24, right: 24 }} />\n */\nexport function StromcomHome({ className, style, ...rest }) {\n const sc = useStromcom();\n const containerRef = useRef(null);\n const initializedRef = useRef(false);\n\n useEffect(() => {\n if (!sc || !containerRef.current || initializedRef.current) return;\n sc.home(containerRef.current);\n initializedRef.current = true;\n }, [sc]);\n\n return <div ref={containerRef} className={className} style={style} {...rest} />;\n}\n"],"mappings":"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","names":["useEffect","useEffect","useEffect","useEffect","useEffect","jsx","useEffect","useEffect","useRef","jsx","useRef","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/StromcomProvider.jsx","../src/StromcomUser.jsx","../src/StromcomConf.jsx","../src/StromcomThread.jsx","../src/StromcomHome.jsx"],"sourcesContent":["import { createContext, useContext, useEffect } from 'react';\n\nexport const StromcomContext = createContext(null);\n\nconst LOADER_URLS = {\n production: 'https://cdn.stromcom.cz/loader.js',\n staging: 'https://cdn.staging.stromcom.cz/loader.js',\n};\n\n/**\n * Sets up the stromCom stub layer synchronously so that child components can\n * queue calls (initUser, thread, conf, home) before the async loader.js arrives.\n * Mirrors what the PHP snippet() method generates.\n */\nfunction setupLayer(dataLayer) {\n if (typeof window === 'undefined') {\n return null;\n }\n\n if (window[dataLayer]?.__sc_ready) {\n return window[dataLayer];\n }\n\n const dl = dataLayer + 'DL';\n window[dl] = window[dl] || {};\n window[dataLayer] = window[dataLayer] || {};\n const layer = window[dataLayer];\n\n const stub = (method, dlKey, isArray) => {\n dlKey = dlKey || method;\n if (isArray) {\n window[dl][dlKey] = window[dl][dlKey] || [];\n }\n\n layer[method] = (...args) => {\n if (isArray) {\n window[dl][dlKey].push(args);\n } else {\n window[dl][dlKey] = args;\n }\n };\n };\n\n stub('initUser', 'user');\n stub('thread', 'threads', true);\n stub('conf');\n stub('home', 'home', true);\n\n layer.__sc_ready = true;\n return layer;\n}\n\n/**\n * Wraps the app and loads the Stromcom widget SDK.\n *\n * @param {object} props\n * @param {string} props.clientKey - Client key from the Stromcom dashboard\n * @param {string} props.clientSecret - Bearer token from the Stromcom dashboard\n * @param {string} [props.dataLayer] - Global JS variable name (default: \"stromCom\")\n * @param {string} [props.environment] - \"production\" | \"staging\" | full custom loader URL\n * @param {string} [props.language] - Initial UI language (e.g. \"cs\", \"en\", \"sk\"). Omit to auto-detect from the\n * browser. Use StromcomConf's `language` prop to change it at runtime.\n * @param {React.ReactNode} props.children\n *\n * @example\n * <StromcomProvider clientKey=\"ck_xxx\" clientSecret=\"cs_xxx\">\n * <App />\n * </StromcomProvider>\n */\nexport function StromcomProvider({\n clientKey,\n clientSecret,\n dataLayer = 'stromCom',\n environment = 'production',\n language,\n children,\n}) {\n // Set up stubs synchronously so children can queue calls immediately on render.\n const layer = setupLayer(dataLayer);\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n // Avoid injecting the script twice (React StrictMode, HMR, etc.)\n if (document.querySelector(`script[data-ck=\"${clientKey}\"][data-type=\"stromcom\"]`)) return;\n\n const base = LOADER_URLS[environment] ?? environment;\n const dl = dataLayer + 'DL';\n\n const script = document.createElement('script');\n script.async = true;\n script.dataset.type = 'stromcom';\n script.dataset.l = dataLayer;\n script.dataset.dl = dl;\n script.dataset.ck = clientKey;\n script.dataset.cs = clientSecret;\n if (language) script.dataset.lang = language;\n script.src = `${base}?${clientKey}`;\n document.head.appendChild(script);\n\n return () => script.remove();\n }, [clientKey, clientSecret, dataLayer, environment, language]);\n\n return <StromcomContext.Provider value={layer}>{children}</StromcomContext.Provider>;\n}\n\nexport function useStromcom() {\n return useContext(StromcomContext);\n}\n","import { useEffect } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Identifies the currently logged-in user.\n * Re-calls initUser whenever any prop changes.\n *\n * IMPORTANT: The `code` should already be hashed server-side (HMAC + base-62).\n * Never pass your raw internal user ID directly from the client.\n *\n * @param {object} props\n * @param {string} props.code - Hashed unique user identifier (max 100 chars, [a-zA-Z0-9-_])\n * @param {string} [props.name] - Display name\n * @param {string} [props.emailAddress]- Used for email notifications\n * @param {boolean} [props.readOnly] - When true, user can read but not send messages\n * @param {string} [props.avatarURL] - Full URL to avatar image\n *\n * @example\n * <StromcomUser\n * code={user.stromcomHash}\n * name={user.name}\n * emailAddress={user.email}\n * />\n */\nexport function StromcomUser({ code, name, emailAddress, readOnly, avatarURL }) {\n const sc = useStromcom();\n\n useEffect(() => {\n if (!sc) return;\n\n const opts = { code };\n if (name !== undefined) opts.name = name;\n if (emailAddress !== undefined) opts.emailAddress = emailAddress;\n if (readOnly !== undefined) opts.readOnly = readOnly;\n if (avatarURL !== undefined) opts.avatarURL = avatarURL;\n\n sc.initUser(opts);\n }, [sc, code, name, emailAddress, readOnly, avatarURL]);\n\n return null;\n}\n","import { useEffect } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Sends SDK configuration to the Stromcom widget.\n * Should be placed once inside <StromcomProvider>, before threads or the home component.\n * Re-sends conf whenever any option prop changes.\n *\n * Options that accept functions (notificationRenderer, onNotification, etc.) take\n * actual JS functions here — no need for the string-wrapping used in the PHP library.\n *\n * @param {object} [props.notificationRenderer] - Function(notification) for custom notification rendering\n * @param {object} [props.onNotification] - Function(count) called when unread count changes\n * @param {(string|string[])} [props.appFrameCSSPath] - CSS URL(s) for the widget frame — the shadow root holding the container, the backdrop and the iframe\n * @param {(string|string[])} [props.appCSSPath] - CSS URL(s) for the app itself (inside the iframe), on top of the CSS from the project settings\n * @param {Function} [props.notificationElementTargetElement] - Function/Promise/Element returning the icon mount target\n * @param {boolean} [props.notificationElementShowAlways] - Show icon even with zero notifications\n * @param {number} [props.notificationElementPosition] - Icon position: 1=top-left 2=top-right 3=bottom-right 4=bottom-left\n * @param {object} [props.notificationElementStyles] - Inline styles object for the default notification button\n * @param {string} [props.notificationElementCSSPath] - CSS URL for the default notification renderer\n * @param {boolean} [props.notificationElementNoShadowRoot]- Disable shadow-root for notifications\n * @param {Function} [props.homeBeforeRender] - Callback before notification center opens\n * @param {Function} [props.notificationElementBeforeRender]- Callback before notification icon renders\n * @param {Function} [props.notificationElementAfterRender] - Callback after notification icon renders\n * @param {(string|null)} [props.language] - UI language. Any code is accepted; one without a translation falls\n * back to the browser language, then English. null follows the browser. Overrides the `language`\n * prop on StromcomProvider, which is only the initial value; this one\n * can be changed at runtime.\n * @param {('stromcom-light'|'stromcom-dark'|null)} [props.theme] - Theme. null follows browser preference.\n * @param {Function} [props.entityResolve] - async ({type, id}) => detail; resolves business-entity detail for the message editor's chip hover-card\n *\n * @example\n * <StromcomConf\n * notificationElementPosition={4}\n * onNotification={(count) => setBadge(count)}\n * />\n */\nexport function StromcomConf({\n notificationRenderer,\n onNotification,\n appFrameCSSPath,\n appCSSPath,\n notificationElementTargetElement,\n notificationElementShowAlways,\n notificationElementPosition,\n notificationElementStyles,\n notificationElementCSSPath,\n notificationElementNoShadowRoot,\n homeBeforeRender,\n notificationElementBeforeRender,\n notificationElementAfterRender,\n language,\n theme,\n entityResolve,\n}) {\n const sc = useStromcom();\n\n useEffect(() => {\n if (!sc) return;\n\n const opts = {};\n\n if (notificationRenderer !== undefined) opts.notificationRenderer = notificationRenderer;\n if (onNotification !== undefined) opts.onNotification = onNotification;\n if (appFrameCSSPath !== undefined) opts.appFrameCSSPath = appFrameCSSPath;\n if (appCSSPath !== undefined) opts.appCSSPath = appCSSPath;\n if (notificationElementTargetElement !== undefined)\n opts.notificationElementTargetElement = notificationElementTargetElement;\n if (notificationElementShowAlways !== undefined)\n opts.notificationElementShowAlways = notificationElementShowAlways;\n if (notificationElementPosition !== undefined)\n opts.notificationElementPosition = notificationElementPosition;\n if (notificationElementStyles !== undefined)\n opts.notificationElementStyles = notificationElementStyles;\n if (notificationElementCSSPath !== undefined)\n opts.notificationElementCSSPath = notificationElementCSSPath;\n if (notificationElementNoShadowRoot !== undefined)\n opts.notificationElementNoShadowRoot = notificationElementNoShadowRoot;\n if (homeBeforeRender !== undefined) opts.homeBeforeRender = homeBeforeRender;\n if (notificationElementBeforeRender !== undefined)\n opts.notificationElementBeforeRender = notificationElementBeforeRender;\n if (notificationElementAfterRender !== undefined)\n opts.notificationElementAfterRender = notificationElementAfterRender;\n if (language !== undefined) opts.language = language;\n if (theme !== undefined) opts.theme = theme;\n if (entityResolve !== undefined) opts.entityResolve = entityResolve;\n\n sc.conf(opts);\n }, [\n sc,\n notificationRenderer,\n onNotification,\n appFrameCSSPath,\n appCSSPath,\n notificationElementTargetElement,\n notificationElementShowAlways,\n notificationElementPosition,\n notificationElementStyles,\n notificationElementCSSPath,\n notificationElementNoShadowRoot,\n homeBeforeRender,\n notificationElementBeforeRender,\n notificationElementAfterRender,\n language,\n theme,\n entityResolve,\n ]);\n\n return null;\n}\n","import { useEffect, useRef } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Embeds a Stromcom conversation thread into the rendered div.\n * The thread is initialized once on mount.\n *\n * IMPORTANT: The `code` should already be hashed server-side (HMAC + base-62).\n *\n * @param {object} props\n * @param {string} props.code - Hashed unique thread identifier (max 100 chars, [a-zA-Z0-9-_])\n * @param {string} [props.name] - Display name shown in the thread header\n * @param {string} [props.url] - Canonical URL of the page (shown as link in header)\n * @param {boolean} [props.userHint] - Enable @mention suggestions (default: true)\n * @param {string} [props.className] - CSS class for the container div\n * @param {object} [props.style] - Inline styles for the container div\n *\n * @example\n * <StromcomThread\n * code={order.stromcomHash}\n * name={`Order #${order.id}`}\n * url={window.location.href}\n * style={{ height: '400px' }}\n * />\n */\nexport function StromcomThread({ code, name, url, userHint, className, style, ...rest }) {\n const sc = useStromcom();\n const containerRef = useRef(null);\n const initializedRef = useRef(false);\n\n useEffect(() => {\n if (!sc || !containerRef.current || initializedRef.current) return;\n\n const opts = { code };\n if (name !== undefined) opts.name = name;\n if (url !== undefined) opts.url = url;\n if (userHint !== undefined) opts.userHint = userHint;\n\n sc.thread(containerRef.current, opts);\n initializedRef.current = true;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally omit option deps, thread cannot be re-initialized\n }, [sc]);\n\n return <div ref={containerRef} className={className} style={style} {...rest} />;\n}\n","import { useEffect, useRef } from 'react';\nimport { useStromcom } from './StromcomProvider.jsx';\n\n/**\n * Embeds the Stromcom notification center into the rendered div.\n * Initialized once on mount.\n *\n * @param {object} [props.className] - CSS class for the container div\n * @param {object} [props.style] - Inline styles for the container div\n *\n * @example\n * <StromcomHome style={{ position: 'fixed', bottom: 24, right: 24 }} />\n */\nexport function StromcomHome({ className, style, ...rest }) {\n const sc = useStromcom();\n const containerRef = useRef(null);\n const initializedRef = useRef(false);\n\n useEffect(() => {\n if (!sc || !containerRef.current || initializedRef.current) return;\n sc.home(containerRef.current);\n initializedRef.current = true;\n }, [sc]);\n\n return <div ref={containerRef} className={className} style={style} {...rest} />;\n}\n"],"mappings":"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","names":["useEffect","useEffect","useEffect","useEffect","useEffect","jsx","useEffect","useEffect","useRef","jsx","useRef","useEffect"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stromcom/react-snippet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "React integration for the STROMCOM chat widget. Loads the SDK, identifies users, and embeds threads as plain React components.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"stromcom",
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
"LICENSE"
|
|
37
37
|
],
|
|
38
38
|
"sideEffects": false,
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
39
42
|
"scripts": {
|
|
40
43
|
"build": "tsup",
|
|
41
44
|
"dev": "tsup --watch",
|
package/src/StromcomConf.jsx
CHANGED
|
@@ -11,7 +11,8 @@ import { useStromcom } from './StromcomProvider.jsx';
|
|
|
11
11
|
*
|
|
12
12
|
* @param {object} [props.notificationRenderer] - Function(notification) for custom notification rendering
|
|
13
13
|
* @param {object} [props.onNotification] - Function(count) called when unread count changes
|
|
14
|
-
* @param {string}
|
|
14
|
+
* @param {(string|string[])} [props.appFrameCSSPath] - CSS URL(s) for the widget frame — the shadow root holding the container, the backdrop and the iframe
|
|
15
|
+
* @param {(string|string[])} [props.appCSSPath] - CSS URL(s) for the app itself (inside the iframe), on top of the CSS from the project settings
|
|
15
16
|
* @param {Function} [props.notificationElementTargetElement] - Function/Promise/Element returning the icon mount target
|
|
16
17
|
* @param {boolean} [props.notificationElementShowAlways] - Show icon even with zero notifications
|
|
17
18
|
* @param {number} [props.notificationElementPosition] - Icon position: 1=top-left 2=top-right 3=bottom-right 4=bottom-left
|
|
@@ -21,7 +22,12 @@ import { useStromcom } from './StromcomProvider.jsx';
|
|
|
21
22
|
* @param {Function} [props.homeBeforeRender] - Callback before notification center opens
|
|
22
23
|
* @param {Function} [props.notificationElementBeforeRender]- Callback before notification icon renders
|
|
23
24
|
* @param {Function} [props.notificationElementAfterRender] - Callback after notification icon renders
|
|
25
|
+
* @param {(string|null)} [props.language] - UI language. Any code is accepted; one without a translation falls
|
|
26
|
+
* back to the browser language, then English. null follows the browser. Overrides the `language`
|
|
27
|
+
* prop on StromcomProvider, which is only the initial value; this one
|
|
28
|
+
* can be changed at runtime.
|
|
24
29
|
* @param {('stromcom-light'|'stromcom-dark'|null)} [props.theme] - Theme. null follows browser preference.
|
|
30
|
+
* @param {Function} [props.entityResolve] - async ({type, id}) => detail; resolves business-entity detail for the message editor's chip hover-card
|
|
25
31
|
*
|
|
26
32
|
* @example
|
|
27
33
|
* <StromcomConf
|
|
@@ -32,7 +38,8 @@ import { useStromcom } from './StromcomProvider.jsx';
|
|
|
32
38
|
export function StromcomConf({
|
|
33
39
|
notificationRenderer,
|
|
34
40
|
onNotification,
|
|
35
|
-
|
|
41
|
+
appFrameCSSPath,
|
|
42
|
+
appCSSPath,
|
|
36
43
|
notificationElementTargetElement,
|
|
37
44
|
notificationElementShowAlways,
|
|
38
45
|
notificationElementPosition,
|
|
@@ -42,7 +49,9 @@ export function StromcomConf({
|
|
|
42
49
|
homeBeforeRender,
|
|
43
50
|
notificationElementBeforeRender,
|
|
44
51
|
notificationElementAfterRender,
|
|
52
|
+
language,
|
|
45
53
|
theme,
|
|
54
|
+
entityResolve,
|
|
46
55
|
}) {
|
|
47
56
|
const sc = useStromcom();
|
|
48
57
|
|
|
@@ -53,7 +62,8 @@ export function StromcomConf({
|
|
|
53
62
|
|
|
54
63
|
if (notificationRenderer !== undefined) opts.notificationRenderer = notificationRenderer;
|
|
55
64
|
if (onNotification !== undefined) opts.onNotification = onNotification;
|
|
56
|
-
if (
|
|
65
|
+
if (appFrameCSSPath !== undefined) opts.appFrameCSSPath = appFrameCSSPath;
|
|
66
|
+
if (appCSSPath !== undefined) opts.appCSSPath = appCSSPath;
|
|
57
67
|
if (notificationElementTargetElement !== undefined)
|
|
58
68
|
opts.notificationElementTargetElement = notificationElementTargetElement;
|
|
59
69
|
if (notificationElementShowAlways !== undefined)
|
|
@@ -71,14 +81,17 @@ export function StromcomConf({
|
|
|
71
81
|
opts.notificationElementBeforeRender = notificationElementBeforeRender;
|
|
72
82
|
if (notificationElementAfterRender !== undefined)
|
|
73
83
|
opts.notificationElementAfterRender = notificationElementAfterRender;
|
|
84
|
+
if (language !== undefined) opts.language = language;
|
|
74
85
|
if (theme !== undefined) opts.theme = theme;
|
|
86
|
+
if (entityResolve !== undefined) opts.entityResolve = entityResolve;
|
|
75
87
|
|
|
76
88
|
sc.conf(opts);
|
|
77
89
|
}, [
|
|
78
90
|
sc,
|
|
79
91
|
notificationRenderer,
|
|
80
92
|
onNotification,
|
|
81
|
-
|
|
93
|
+
appFrameCSSPath,
|
|
94
|
+
appCSSPath,
|
|
82
95
|
notificationElementTargetElement,
|
|
83
96
|
notificationElementShowAlways,
|
|
84
97
|
notificationElementPosition,
|
|
@@ -88,7 +101,9 @@ export function StromcomConf({
|
|
|
88
101
|
homeBeforeRender,
|
|
89
102
|
notificationElementBeforeRender,
|
|
90
103
|
notificationElementAfterRender,
|
|
104
|
+
language,
|
|
91
105
|
theme,
|
|
106
|
+
entityResolve,
|
|
92
107
|
]);
|
|
93
108
|
|
|
94
109
|
return null;
|
package/src/StromcomProvider.jsx
CHANGED
|
@@ -58,6 +58,8 @@ function setupLayer(dataLayer) {
|
|
|
58
58
|
* @param {string} props.clientSecret - Bearer token from the Stromcom dashboard
|
|
59
59
|
* @param {string} [props.dataLayer] - Global JS variable name (default: "stromCom")
|
|
60
60
|
* @param {string} [props.environment] - "production" | "staging" | full custom loader URL
|
|
61
|
+
* @param {string} [props.language] - Initial UI language (e.g. "cs", "en", "sk"). Omit to auto-detect from the
|
|
62
|
+
* browser. Use StromcomConf's `language` prop to change it at runtime.
|
|
61
63
|
* @param {React.ReactNode} props.children
|
|
62
64
|
*
|
|
63
65
|
* @example
|
|
@@ -70,6 +72,7 @@ export function StromcomProvider({
|
|
|
70
72
|
clientSecret,
|
|
71
73
|
dataLayer = 'stromCom',
|
|
72
74
|
environment = 'production',
|
|
75
|
+
language,
|
|
73
76
|
children,
|
|
74
77
|
}) {
|
|
75
78
|
// Set up stubs synchronously so children can queue calls immediately on render.
|
|
@@ -91,11 +94,12 @@ export function StromcomProvider({
|
|
|
91
94
|
script.dataset.dl = dl;
|
|
92
95
|
script.dataset.ck = clientKey;
|
|
93
96
|
script.dataset.cs = clientSecret;
|
|
97
|
+
if (language) script.dataset.lang = language;
|
|
94
98
|
script.src = `${base}?${clientKey}`;
|
|
95
99
|
document.head.appendChild(script);
|
|
96
100
|
|
|
97
101
|
return () => script.remove();
|
|
98
|
-
}, [clientKey, clientSecret, dataLayer, environment]);
|
|
102
|
+
}, [clientKey, clientSecret, dataLayer, environment, language]);
|
|
99
103
|
|
|
100
104
|
return <StromcomContext.Provider value={layer}>{children}</StromcomContext.Provider>;
|
|
101
105
|
}
|
|
@@ -30,6 +30,57 @@ describe('StromcomConf', () => {
|
|
|
30
30
|
expect(layer.conf).toHaveBeenCalledWith({ onNotification });
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
it('forwards entityResolve as-is (no string-wrapping)', () => {
|
|
34
|
+
const layer = { conf: vi.fn() };
|
|
35
|
+
const entityResolve = vi.fn();
|
|
36
|
+
renderWithLayer(layer, { entityResolve });
|
|
37
|
+
|
|
38
|
+
expect(layer.conf).toHaveBeenCalledWith({ entityResolve });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('sends a single CSS path', () => {
|
|
42
|
+
const layer = { conf: vi.fn() };
|
|
43
|
+
renderWithLayer(layer, { appFrameCSSPath: 'https://example.com/frame.css' });
|
|
44
|
+
|
|
45
|
+
expect(layer.conf).toHaveBeenCalledWith({ appFrameCSSPath: 'https://example.com/frame.css' });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('sends a list of CSS paths as an array', () => {
|
|
49
|
+
const layer = { conf: vi.fn() };
|
|
50
|
+
const appCSSPath = ['https://example.com/a.css', 'https://example.com/b.css'];
|
|
51
|
+
renderWithLayer(layer, { appCSSPath });
|
|
52
|
+
|
|
53
|
+
expect(layer.conf).toHaveBeenCalledWith({ appCSSPath });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('sends the language option', () => {
|
|
57
|
+
const layer = { conf: vi.fn() };
|
|
58
|
+
renderWithLayer(layer, { language: 'cs' });
|
|
59
|
+
|
|
60
|
+
expect(layer.conf).toHaveBeenCalledWith({ language: 'cs' });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('sends language null to follow the browser', () => {
|
|
64
|
+
const layer = { conf: vi.fn() };
|
|
65
|
+
renderWithLayer(layer, { language: null });
|
|
66
|
+
|
|
67
|
+
expect(layer.conf).toHaveBeenCalledWith({ language: null });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('re-sends conf when the language changes', () => {
|
|
71
|
+
const layer = { conf: vi.fn() };
|
|
72
|
+
const { rerender } = renderWithLayer(layer, { language: 'cs' });
|
|
73
|
+
|
|
74
|
+
rerender(
|
|
75
|
+
<StromcomContext.Provider value={layer}>
|
|
76
|
+
<StromcomConf language="sk" />
|
|
77
|
+
</StromcomContext.Provider>,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(layer.conf).toHaveBeenCalledTimes(2);
|
|
81
|
+
expect(layer.conf).toHaveBeenLastCalledWith({ language: 'sk' });
|
|
82
|
+
});
|
|
83
|
+
|
|
33
84
|
it('re-sends conf when an option changes', () => {
|
|
34
85
|
const layer = { conf: vi.fn() };
|
|
35
86
|
const { rerender } = renderWithLayer(layer, { theme: 'stromcom-light' });
|
|
@@ -92,6 +92,26 @@ describe('StromcomProvider', () => {
|
|
|
92
92
|
expect(scriptFor('ck_5')).toBeNull();
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
+
it('sets data-lang when language is provided', () => {
|
|
96
|
+
render(
|
|
97
|
+
<StromcomProvider clientKey="ck_lang" clientSecret="cs_lang" language="cs">
|
|
98
|
+
<div />
|
|
99
|
+
</StromcomProvider>,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(scriptFor('ck_lang').dataset.lang).toBe('cs');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('omits data-lang when language is not provided', () => {
|
|
106
|
+
render(
|
|
107
|
+
<StromcomProvider clientKey="ck_nolang" clientSecret="cs_nolang">
|
|
108
|
+
<div />
|
|
109
|
+
</StromcomProvider>,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
expect(scriptFor('ck_nolang').dataset.lang).toBeUndefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
95
115
|
it('respects a custom dataLayer name', () => {
|
|
96
116
|
render(
|
|
97
117
|
<StromcomProvider clientKey="ck_6" clientSecret="cs_6" dataLayer="customLayer">
|
package/src/index.d.ts
CHANGED
|
@@ -19,6 +19,12 @@ export interface StromcomProviderProps {
|
|
|
19
19
|
dataLayer?: string;
|
|
20
20
|
/** `"production"`, `"staging"`, or a full custom loader URL. Default `"production"`. */
|
|
21
21
|
environment?: StromcomEnvironment;
|
|
22
|
+
/**
|
|
23
|
+
* Initial UI language (e.g. `"cs"`, `"en"`, `"sk"`). Any code is accepted; one without a
|
|
24
|
+
* translation falls back to the browser language, then English. Omit to auto-detect from the browser.
|
|
25
|
+
* Use `StromcomConf`'s `language` prop to change it at runtime.
|
|
26
|
+
*/
|
|
27
|
+
language?: string;
|
|
22
28
|
children?: ReactNode;
|
|
23
29
|
}
|
|
24
30
|
|
|
@@ -66,7 +72,13 @@ export function StromcomHome(props: StromcomHomeProps): JSX.Element;
|
|
|
66
72
|
export interface StromcomConfOptions {
|
|
67
73
|
notificationRenderer?: (notification: unknown) => unknown;
|
|
68
74
|
onNotification?: (count: number) => void;
|
|
69
|
-
|
|
75
|
+
/** CSS for the widget frame — the shadow root holding the container, the backdrop and the iframe. */
|
|
76
|
+
appFrameCSSPath?: string | string[];
|
|
77
|
+
/**
|
|
78
|
+
* CSS for the app itself (inside the iframe), on top of the CSS from the project
|
|
79
|
+
* settings. Applied only on a plan that allows appearance customization.
|
|
80
|
+
*/
|
|
81
|
+
appCSSPath?: string | string[];
|
|
70
82
|
notificationElementTargetElement?:
|
|
71
83
|
(() => HTMLElement | Promise<HTMLElement>) | HTMLElement | Promise<HTMLElement>;
|
|
72
84
|
notificationElementShowAlways?: boolean;
|
|
@@ -78,8 +90,16 @@ export interface StromcomConfOptions {
|
|
|
78
90
|
homeBeforeRender?: () => void | Promise<void>;
|
|
79
91
|
notificationElementBeforeRender?: () => void | Promise<void>;
|
|
80
92
|
notificationElementAfterRender?: () => void;
|
|
93
|
+
/**
|
|
94
|
+
* UI language, or `null` to follow the browser. Any language code is accepted;
|
|
95
|
+
* one the widget has no translation for falls back to the browser language, then English.
|
|
96
|
+
* Overrides `StromcomProviderProps.language`, which is only the initial value.
|
|
97
|
+
*/
|
|
98
|
+
language?: string | null;
|
|
81
99
|
/** Theme: `"stromcom-light"`, `"stromcom-dark"`, or `null` to follow browser preference. */
|
|
82
100
|
theme?: 'stromcom-light' | 'stromcom-dark' | null;
|
|
101
|
+
/** Resolves business-entity detail (order, ticket…) for the message editor's chip hover-card. */
|
|
102
|
+
entityResolve?: (entity: { type: string; id: string }) => unknown | Promise<unknown>;
|
|
83
103
|
}
|
|
84
104
|
|
|
85
105
|
export function StromcomConf(props: StromcomConfOptions): null;
|