@stromcom/react-snippet 0.4.0 → 0.5.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 +96 -16
- package/dist/index.cjs +11 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +40 -7
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/StromcomConf.jsx +10 -6
- package/src/StromcomUser.jsx +12 -1
- package/src/__tests__/StromcomConf.test.jsx +9 -1
- package/src/__tests__/StromcomUser.test.jsx +27 -3
- package/src/index.d.ts +40 -7
package/README.md
CHANGED
|
@@ -103,9 +103,9 @@ Just render `<StromcomThread>` multiple times with different `code`s. Each div h
|
|
|
103
103
|
|
|
104
104
|
See [`examples/06-multiple-threads-per-page.jsx`](./examples/06-multiple-threads-per-page.jsx).
|
|
105
105
|
|
|
106
|
-
###
|
|
106
|
+
### Logged-in users only
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
STROMCOM is for communication between your logged-in users — there are no anonymous visitors. The widget renders **nothing** (no threads, no notification bubble) until `<StromcomUser>` has been mounted with both `code` **and** `name`. Render `<StromcomUser>` (and your threads) only for logged-in users, and mount it the moment a user logs in. See [`examples/04-anonymous-then-login.jsx`](./examples/04-anonymous-then-login.jsx).
|
|
109
109
|
|
|
110
110
|
## Hashing user & thread codes — IMPORTANT
|
|
111
111
|
|
|
@@ -147,12 +147,12 @@ Never pass raw internal IDs from the client.
|
|
|
147
147
|
|
|
148
148
|
### `<StromcomUser>`
|
|
149
149
|
|
|
150
|
-
Identifies the logged-in user. Re-calls `initUser` whenever any prop changes. Renders nothing.
|
|
150
|
+
Identifies the logged-in user. Re-calls `initUser` whenever any prop changes. Renders nothing. The widget stays hidden until both `code` and `name` are set; in development a missing `name` logs a `console.warn`.
|
|
151
151
|
|
|
152
152
|
| Prop | Type | Required | Description |
|
|
153
153
|
| -------------- | --------- | -------- | ------------------------------------------------------- |
|
|
154
154
|
| `code` | `string` | ✅ | Hashed user identifier. Max 100 chars, `[a-zA-Z0-9-_]`. |
|
|
155
|
-
| `name` | `string` |
|
|
155
|
+
| `name` | `string` | ✅ | Display name. Without it the widget renders nothing. |
|
|
156
156
|
| `emailAddress` | `string` | | Used for email notifications. |
|
|
157
157
|
| `readOnly` | `boolean` | | When `true`, user can read but not send messages. |
|
|
158
158
|
| `avatarURL` | `string` | | Full avatar URL. |
|
|
@@ -185,16 +185,17 @@ Sends SDK configuration. Place once inside the provider, before threads.
|
|
|
185
185
|
|
|
186
186
|
Notable options:
|
|
187
187
|
|
|
188
|
-
| Prop | Type
|
|
189
|
-
| ----------------------------- |
|
|
190
|
-
| `notificationRenderer` | `Function`
|
|
191
|
-
| `onNotification` | `Function`
|
|
192
|
-
| `
|
|
193
|
-
| `
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
188
|
+
| Prop | Type | Description |
|
|
189
|
+
| ----------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
190
|
+
| `notificationRenderer` | `Function` | Custom notification rendering function. |
|
|
191
|
+
| `onNotification` | `Function` | Called with the unread counts `{thread, message, notification, reaction}`. |
|
|
192
|
+
| `onLoad` | `Function` | Called once the widget is running. From then on `openHome()`, `closeHome()` and `toggleHome()` work — see [Notification center from your own button](#notification-center-from-your-own-button). |
|
|
193
|
+
| `appFrameCSSPath` | `string\|string[]` | CSS for the widget frame — the shadow root holding the container, the backdrop and the iframe. |
|
|
194
|
+
| `appCSSPath` | `string\|string[]` | CSS for the app itself (inside the iframe), on top of the CSS from the project settings. |
|
|
195
|
+
| `notificationElementPosition` | `1\|2\|3\|4\|null` | Icon position: 1=TL, 2=TR, 3=BR, 4=BL. `null` = no notification bubble. |
|
|
196
|
+
| `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>`. |
|
|
197
|
+
| `theme` | `"stromcom-default"\|"stromcom-dark"\|null` | Theme. `null` follows browser preference. |
|
|
198
|
+
| `entityResolve` | `Function` | `async ({type, id, groups, url}) => detail` — resolves business-entity detail (order, ticket…) for the message editor's chip hover-card. |
|
|
198
199
|
|
|
199
200
|
See [`src/StromcomConf.jsx`](./src/StromcomConf.jsx) for the full list.
|
|
200
201
|
|
|
@@ -226,10 +227,89 @@ import { useStromcom } from '@stromcom/react-snippet';
|
|
|
226
227
|
|
|
227
228
|
function SwitchUserButton() {
|
|
228
229
|
const sc = useStromcom();
|
|
229
|
-
return
|
|
230
|
+
return (
|
|
231
|
+
<button onClick={() => sc?.initUser({ code: 'newHashedCode', name: 'Jane' })}>
|
|
232
|
+
Switch user
|
|
233
|
+
</button>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Notification center from your own button
|
|
239
|
+
|
|
240
|
+
The default bubble opens the notification center over the whole page. To open it from an
|
|
241
|
+
element your app already has (a toolbar button, a menu item), leave
|
|
242
|
+
`notificationElementPosition` unset (the bubble renders only when it is set), keep the
|
|
243
|
+
count in your own badge and call the widget's commands through `useStromcom()`:
|
|
244
|
+
|
|
245
|
+
```jsx
|
|
246
|
+
import { useCallback, useState } from 'react';
|
|
247
|
+
import { StromcomConf, useStromcom } from '@stromcom/react-snippet';
|
|
248
|
+
|
|
249
|
+
export function MessagesButton() {
|
|
250
|
+
const sc = useStromcom();
|
|
251
|
+
const [ready, setReady] = useState(false);
|
|
252
|
+
const [count, setCount] = useState(0);
|
|
253
|
+
|
|
254
|
+
const onLoad = useCallback(() => setReady(true), []);
|
|
255
|
+
const onNotification = useCallback((counts) => setCount(counts.thread + counts.notification), []);
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<>
|
|
259
|
+
<StromcomConf onLoad={onLoad} onNotification={onNotification} />
|
|
260
|
+
<button disabled={!ready} onClick={() => sc?.toggleHome?.()}>
|
|
261
|
+
Messages {count > 0 && <span className="badge">{count}</span>}
|
|
262
|
+
</button>
|
|
263
|
+
</>
|
|
264
|
+
);
|
|
230
265
|
}
|
|
231
266
|
```
|
|
232
267
|
|
|
268
|
+
| Command | Effect |
|
|
269
|
+
| -------------- | ------------------------------------------------------------- |
|
|
270
|
+
| `openHome()` | Opens the notification center (creates it on the first call). |
|
|
271
|
+
| `closeHome()` | Hides it. A click on the backdrop does the same. |
|
|
272
|
+
| `toggleHome()` | Opens or hides it, like a click on the default bubble. |
|
|
273
|
+
|
|
274
|
+
The commands are installed by the widget itself once it starts — after the loader script
|
|
275
|
+
has run and the user is set. Until then they are `undefined`, so guard with `?.` and
|
|
276
|
+
enable the button in `onLoad`. `onLoad` fires once,
|
|
277
|
+
when the widget starts, so `<StromcomConf>` with it has to be rendered together with
|
|
278
|
+
`<StromcomUser>`, not later.
|
|
279
|
+
|
|
280
|
+
## Limitations
|
|
281
|
+
|
|
282
|
+
The components cover the common cases; a few SDK features are only reachable imperatively via `useStromcom()`:
|
|
283
|
+
|
|
284
|
+
- **`<StromcomThread>` doesn't forward `attributes` or `hash`**, and it is initialised once on mount. When the entity changes, pass a new React `key` to remount it.
|
|
285
|
+
- **`conf()` replaces the whole configuration.** Every `<StromcomConf>` render sends its full option set, so don't mix `<StromcomConf>` with imperative `sc.conf(...)` calls — whichever runs last wins and drops the other's options. Pick one.
|
|
286
|
+
- **Memoize callback and array props** (`onNotification`, `entityResolve`, `appCSSPath`, …) with `useCallback` / `useMemo` or module constants. A new identity on every render re-sends the configuration.
|
|
287
|
+
|
|
288
|
+
For a thread with `attributes`, call the SDK yourself:
|
|
289
|
+
|
|
290
|
+
```jsx
|
|
291
|
+
import { useEffect, useRef } from 'react';
|
|
292
|
+
import { useStromcom } from '@stromcom/react-snippet';
|
|
293
|
+
|
|
294
|
+
function OrderThread({ order }) {
|
|
295
|
+
const sc = useStromcom();
|
|
296
|
+
const ref = useRef(null);
|
|
297
|
+
|
|
298
|
+
useEffect(() => {
|
|
299
|
+
if (!sc || !ref.current) return;
|
|
300
|
+
sc.thread(ref.current, {
|
|
301
|
+
code: order.stromcomHash,
|
|
302
|
+
name: `Order #${order.id}`,
|
|
303
|
+
attributes: { status: order.status },
|
|
304
|
+
});
|
|
305
|
+
}, [sc, order.stromcomHash]);
|
|
306
|
+
|
|
307
|
+
return <div ref={ref} style={{ height: 500 }} />;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Remount on entity change: <OrderThread key={order.id} order={order} />
|
|
311
|
+
```
|
|
312
|
+
|
|
233
313
|
## Examples
|
|
234
314
|
|
|
235
315
|
The [`examples/`](./examples) folder has focused, copy-pasteable snippets for the common patterns:
|
|
@@ -239,7 +319,7 @@ The [`examples/`](./examples) folder has focused, copy-pasteable snippets for th
|
|
|
239
319
|
| [`01-basic-app.jsx`](./examples/01-basic-app.jsx) | Minimal setup — provider + user + one thread |
|
|
240
320
|
| [`02-nextjs-app-router/`](./examples/02-nextjs-app-router) | Next.js 13+ App Router with server-side hashing |
|
|
241
321
|
| [`03-multi-page-router.jsx`](./examples/03-multi-page-router.jsx) | React Router — provider in root, thread per route |
|
|
242
|
-
| [`04-anonymous-then-login.jsx`](./examples/04-anonymous-then-login.jsx) |
|
|
322
|
+
| [`04-anonymous-then-login.jsx`](./examples/04-anonymous-then-login.jsx) | User and threads mount only after login |
|
|
243
323
|
| [`05-conditional-thread.jsx`](./examples/05-conditional-thread.jsx) | Thread inside a modal / opens on demand |
|
|
244
324
|
| [`06-multiple-threads-per-page.jsx`](./examples/06-multiple-threads-per-page.jsx) | Several threads on one page (per-row, per-section) |
|
|
245
325
|
| [`07-imperative-api.jsx`](./examples/07-imperative-api.jsx) | `useStromcom()` for switch-user / logout flows |
|
package/dist/index.cjs
CHANGED
|
@@ -105,6 +105,14 @@ function useStromcom() {
|
|
|
105
105
|
var import_react2 = require("react");
|
|
106
106
|
function StromcomUser({ code, name, emailAddress, readOnly, avatarURL }) {
|
|
107
107
|
const sc = useStromcom();
|
|
108
|
+
(0, import_react2.useEffect)(() => {
|
|
109
|
+
var _a;
|
|
110
|
+
if (!name && typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.NODE_ENV) !== "production") {
|
|
111
|
+
console.warn(
|
|
112
|
+
"[@stromcom/react-snippet] <StromcomUser> is missing `name` \u2014 the widget renders nothing until both `code` and `name` are set."
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}, [name]);
|
|
108
116
|
(0, import_react2.useEffect)(() => {
|
|
109
117
|
if (!sc) return;
|
|
110
118
|
const opts = { code };
|
|
@@ -122,6 +130,7 @@ var import_react3 = require("react");
|
|
|
122
130
|
function StromcomConf({
|
|
123
131
|
notificationRenderer,
|
|
124
132
|
onNotification,
|
|
133
|
+
onLoad,
|
|
125
134
|
appFrameCSSPath,
|
|
126
135
|
appCSSPath,
|
|
127
136
|
notificationElementTargetElement,
|
|
@@ -143,6 +152,7 @@ function StromcomConf({
|
|
|
143
152
|
const opts = {};
|
|
144
153
|
if (notificationRenderer !== void 0) opts.notificationRenderer = notificationRenderer;
|
|
145
154
|
if (onNotification !== void 0) opts.onNotification = onNotification;
|
|
155
|
+
if (onLoad !== void 0) opts.onLoad = onLoad;
|
|
146
156
|
if (appFrameCSSPath !== void 0) opts.appFrameCSSPath = appFrameCSSPath;
|
|
147
157
|
if (appCSSPath !== void 0) opts.appCSSPath = appCSSPath;
|
|
148
158
|
if (notificationElementTargetElement !== void 0)
|
|
@@ -170,6 +180,7 @@ function StromcomConf({
|
|
|
170
180
|
sc,
|
|
171
181
|
notificationRenderer,
|
|
172
182
|
onNotification,
|
|
183
|
+
onLoad,
|
|
173
184
|
appFrameCSSPath,
|
|
174
185
|
appCSSPath,
|
|
175
186
|
notificationElementTargetElement,
|
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 {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"]}
|
|
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 * The widget renders nothing until both `code` and `name` are set, so render\n * this component only for logged-in users and always pass `name`.\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 (required)\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 (!name && typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n console.warn(\n '[@stromcom/react-snippet] <StromcomUser> is missing `name` — the widget renders nothing until both `code` and `name` are set.',\n );\n }\n }, [name]);\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({thread, message, notification, reaction}) called with the unread counts\n * @param {Function} [props.onLoad] - Called once the widget is running; from then on stromCom.openHome() / closeHome() / toggleHome() work\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; null = no bubble\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 the notification center opens for the first time\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-default'|'stromcom-dark'|null)} [props.theme] - Theme. null follows browser preference.\n * @param {Function} [props.entityResolve] - async ({type, id, groups, url}) => detail; resolves business-entity detail for the message editor's chip hover-card\n *\n * @example\n * <StromcomConf\n * notificationElementPosition={4}\n * onNotification={(counts) => setBadge(counts.thread + counts.notification)}\n * />\n */\nexport function StromcomConf({\n notificationRenderer,\n onNotification,\n onLoad,\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 (onLoad !== undefined) opts.onLoad = onLoad;\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 onLoad,\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
|
@@ -7,6 +7,16 @@ export interface StromcomLayer {
|
|
|
7
7
|
thread: (target: HTMLElement, options: StromcomThreadOptions) => void;
|
|
8
8
|
conf: (options: StromcomConfOptions) => void;
|
|
9
9
|
home: (target: HTMLElement) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Opens the notification center over the whole page (creates it on the first call).
|
|
12
|
+
* Installed by the widget itself once it starts (loader script run, user set) —
|
|
13
|
+
* `undefined` until then. Wait for `StromcomConf`'s `onLoad`.
|
|
14
|
+
*/
|
|
15
|
+
openHome?: () => Promise<void>;
|
|
16
|
+
/** Hides the notification center. See `openHome`. */
|
|
17
|
+
closeHome?: () => void;
|
|
18
|
+
/** Opens or hides the notification center, like a click on the default bubble. See `openHome`. */
|
|
19
|
+
toggleHome?: () => Promise<void>;
|
|
10
20
|
[key: string]: any;
|
|
11
21
|
}
|
|
12
22
|
|
|
@@ -36,7 +46,8 @@ export function useStromcom(): StromcomLayer | null;
|
|
|
36
46
|
export interface StromcomUserOptions {
|
|
37
47
|
/** Hashed unique user identifier (max 100 chars, [a-zA-Z0-9-_]). Hash server-side. */
|
|
38
48
|
code: string;
|
|
39
|
-
name
|
|
49
|
+
/** Display name. Required — the widget renders nothing until both `code` and `name` are set. */
|
|
50
|
+
name: string;
|
|
40
51
|
emailAddress?: string;
|
|
41
52
|
readOnly?: boolean;
|
|
42
53
|
avatarURL?: string;
|
|
@@ -69,9 +80,30 @@ export interface StromcomHomeProps {
|
|
|
69
80
|
|
|
70
81
|
export function StromcomHome(props: StromcomHomeProps): JSX.Element;
|
|
71
82
|
|
|
83
|
+
/** Business entity passed to `entityResolve`. */
|
|
84
|
+
export interface StromcomEntity {
|
|
85
|
+
type: string;
|
|
86
|
+
id: string;
|
|
87
|
+
groups: string[];
|
|
88
|
+
url: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Unread counts passed to `onNotification`. */
|
|
92
|
+
export interface StromcomNotificationCounts {
|
|
93
|
+
thread: number;
|
|
94
|
+
message: number;
|
|
95
|
+
notification: number;
|
|
96
|
+
reaction: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
72
99
|
export interface StromcomConfOptions {
|
|
73
100
|
notificationRenderer?: (notification: unknown) => unknown;
|
|
74
|
-
onNotification?: (
|
|
101
|
+
onNotification?: (counts: StromcomNotificationCounts) => void;
|
|
102
|
+
/**
|
|
103
|
+
* Called once the widget is running (user set, apps rendered). From then on
|
|
104
|
+
* `openHome()`, `closeHome()` and `toggleHome()` on `useStromcom()` work.
|
|
105
|
+
*/
|
|
106
|
+
onLoad?: (api: unknown) => void;
|
|
75
107
|
/** CSS for the widget frame — the shadow root holding the container, the backdrop and the iframe. */
|
|
76
108
|
appFrameCSSPath?: string | string[];
|
|
77
109
|
/**
|
|
@@ -82,11 +114,12 @@ export interface StromcomConfOptions {
|
|
|
82
114
|
notificationElementTargetElement?:
|
|
83
115
|
(() => HTMLElement | Promise<HTMLElement>) | HTMLElement | Promise<HTMLElement>;
|
|
84
116
|
notificationElementShowAlways?: boolean;
|
|
85
|
-
/** Icon position: 1=top-left, 2=top-right, 3=bottom-right, 4=bottom-left. */
|
|
86
|
-
notificationElementPosition?: 1 | 2 | 3 | 4;
|
|
117
|
+
/** Icon position: 1=top-left, 2=top-right, 3=bottom-right, 4=bottom-left; `null` = no bubble. */
|
|
118
|
+
notificationElementPosition?: 1 | 2 | 3 | 4 | null;
|
|
87
119
|
notificationElementStyles?: Record<string, string | number | null>;
|
|
88
120
|
notificationElementCSSPath?: string;
|
|
89
121
|
notificationElementNoShadowRoot?: boolean;
|
|
122
|
+
/** Called before the notification center opens for the first time (bubble click or `openHome()` / `toggleHome()`). */
|
|
90
123
|
homeBeforeRender?: () => void | Promise<void>;
|
|
91
124
|
notificationElementBeforeRender?: () => void | Promise<void>;
|
|
92
125
|
notificationElementAfterRender?: () => void;
|
|
@@ -96,10 +129,10 @@ export interface StromcomConfOptions {
|
|
|
96
129
|
* Overrides `StromcomProviderProps.language`, which is only the initial value.
|
|
97
130
|
*/
|
|
98
131
|
language?: string | null;
|
|
99
|
-
/** Theme: `"stromcom-
|
|
100
|
-
theme?: 'stromcom-
|
|
132
|
+
/** Theme: `"stromcom-default"`, `"stromcom-dark"`, or `null` to follow browser preference. */
|
|
133
|
+
theme?: 'stromcom-default' | 'stromcom-dark' | null;
|
|
101
134
|
/** Resolves business-entity detail (order, ticket…) for the message editor's chip hover-card. */
|
|
102
|
-
entityResolve?: (entity:
|
|
135
|
+
entityResolve?: (entity: StromcomEntity) => unknown | Promise<unknown>;
|
|
103
136
|
}
|
|
104
137
|
|
|
105
138
|
export function StromcomConf(props: StromcomConfOptions): null;
|
package/dist/index.js
CHANGED
|
@@ -75,6 +75,14 @@ function useStromcom() {
|
|
|
75
75
|
import { useEffect as useEffect2 } from "react";
|
|
76
76
|
function StromcomUser({ code, name, emailAddress, readOnly, avatarURL }) {
|
|
77
77
|
const sc = useStromcom();
|
|
78
|
+
useEffect2(() => {
|
|
79
|
+
var _a;
|
|
80
|
+
if (!name && typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.NODE_ENV) !== "production") {
|
|
81
|
+
console.warn(
|
|
82
|
+
"[@stromcom/react-snippet] <StromcomUser> is missing `name` \u2014 the widget renders nothing until both `code` and `name` are set."
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}, [name]);
|
|
78
86
|
useEffect2(() => {
|
|
79
87
|
if (!sc) return;
|
|
80
88
|
const opts = { code };
|
|
@@ -92,6 +100,7 @@ import { useEffect as useEffect3 } from "react";
|
|
|
92
100
|
function StromcomConf({
|
|
93
101
|
notificationRenderer,
|
|
94
102
|
onNotification,
|
|
103
|
+
onLoad,
|
|
95
104
|
appFrameCSSPath,
|
|
96
105
|
appCSSPath,
|
|
97
106
|
notificationElementTargetElement,
|
|
@@ -113,6 +122,7 @@ function StromcomConf({
|
|
|
113
122
|
const opts = {};
|
|
114
123
|
if (notificationRenderer !== void 0) opts.notificationRenderer = notificationRenderer;
|
|
115
124
|
if (onNotification !== void 0) opts.onNotification = onNotification;
|
|
125
|
+
if (onLoad !== void 0) opts.onLoad = onLoad;
|
|
116
126
|
if (appFrameCSSPath !== void 0) opts.appFrameCSSPath = appFrameCSSPath;
|
|
117
127
|
if (appCSSPath !== void 0) opts.appCSSPath = appCSSPath;
|
|
118
128
|
if (notificationElementTargetElement !== void 0)
|
|
@@ -140,6 +150,7 @@ function StromcomConf({
|
|
|
140
150
|
sc,
|
|
141
151
|
notificationRenderer,
|
|
142
152
|
onNotification,
|
|
153
|
+
onLoad,
|
|
143
154
|
appFrameCSSPath,
|
|
144
155
|
appCSSPath,
|
|
145
156
|
notificationElementTargetElement,
|
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 {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":";AAAA,SAAS,eAAe,YAAY,iBAAiB;AAuG5C;AArGF,IAAM,kBAAkB,cAAc,IAAI;AAEjD,IAAM,cAAc;AAAA,EAClB,YAAY;AAAA,EACZ,SAAS;AACX;AAOA,SAAS,WAAW,WAAW;AAd/B;AAeE,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO;AAAA,EACT;AAEA,OAAI,YAAO,SAAS,MAAhB,mBAAmB,YAAY;AACjC,WAAO,OAAO,SAAS;AAAA,EACzB;AAEA,QAAM,KAAK,YAAY;AACvB,SAAO,EAAE,IAAI,OAAO,EAAE,KAAK,CAAC;AAC5B,SAAO,SAAS,IAAI,OAAO,SAAS,KAAK,CAAC;AAC1C,QAAM,QAAQ,OAAO,SAAS;AAE9B,QAAM,OAAO,CAAC,QAAQ,OAAO,YAAY;AACvC,YAAQ,SAAS;AACjB,QAAI,SAAS;AACX,aAAO,EAAE,EAAE,KAAK,IAAI,OAAO,EAAE,EAAE,KAAK,KAAK,CAAC;AAAA,IAC5C;AAEA,UAAM,MAAM,IAAI,IAAI,SAAS;AAC3B,UAAI,SAAS;AACX,eAAO,EAAE,EAAE,KAAK,EAAE,KAAK,IAAI;AAAA,MAC7B,OAAO;AACL,eAAO,EAAE,EAAE,KAAK,IAAI;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAEA,OAAK,YAAY,MAAM;AACvB,OAAK,UAAU,WAAW,IAAI;AAC9B,OAAK,MAAM;AACX,OAAK,QAAQ,QAAQ,IAAI;AAEzB,QAAM,aAAa;AACnB,SAAO;AACT;AAmBO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,cAAc;AAAA,EACd;AAAA,EACA;AACF,GAAG;AAED,QAAM,QAAQ,WAAW,SAAS;AAElC,YAAU,MAAM;AAhFlB;AAiFI,QAAI,OAAO,WAAW,YAAa;AAGnC,QAAI,SAAS,cAAc,mBAAmB,SAAS,0BAA0B,EAAG;AAEpF,UAAM,QAAO,iBAAY,WAAW,MAAvB,YAA4B;AACzC,UAAM,KAAK,YAAY;AAEvB,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,QAAQ;AACf,WAAO,QAAQ,OAAO;AACtB,WAAO,QAAQ,IAAI;AACnB,WAAO,QAAQ,KAAK;AACpB,WAAO,QAAQ,KAAK;AACpB,WAAO,QAAQ,KAAK;AACpB,QAAI,SAAU,QAAO,QAAQ,OAAO;AACpC,WAAO,MAAM,GAAG,IAAI,IAAI,SAAS;AACjC,aAAS,KAAK,YAAY,MAAM;AAEhC,WAAO,MAAM,OAAO,OAAO;AAAA,EAC7B,GAAG,CAAC,WAAW,cAAc,WAAW,aAAa,QAAQ,CAAC;AAE9D,SAAO,oBAAC,gBAAgB,UAAhB,EAAyB,OAAO,OAAQ,UAAS;AAC3D;AAEO,SAAS,cAAc;AAC5B,SAAO,WAAW,eAAe;AACnC;;;AC5GA,SAAS,aAAAA,kBAAiB;AAwBnB,SAAS,aAAa,EAAE,MAAM,MAAM,cAAc,UAAU,UAAU,GAAG;AAC9E,QAAM,KAAK,YAAY;AAEvB,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,GAAI;AAET,UAAM,OAAO,EAAE,KAAK;AACpB,QAAI,SAAS,OAAW,MAAK,OAAO;AACpC,QAAI,iBAAiB,OAAW,MAAK,eAAe;AACpD,QAAI,aAAa,OAAW,MAAK,WAAW;AAC5C,QAAI,cAAc,OAAW,MAAK,YAAY;AAE9C,OAAG,SAAS,IAAI;AAAA,EAClB,GAAG,CAAC,IAAI,MAAM,MAAM,cAAc,UAAU,SAAS,CAAC;AAEtD,SAAO;AACT;;;ACxCA,SAAS,aAAAC,kBAAiB;AAqCnB,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAG;AACD,QAAM,KAAK,YAAY;AAEvB,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,GAAI;AAET,UAAM,OAAO,CAAC;AAEd,QAAI,yBAAyB,OAAW,MAAK,uBAAuB;AACpE,QAAI,mBAAmB,OAAW,MAAK,iBAAiB;AACxD,QAAI,oBAAoB,OAAW,MAAK,kBAAkB;AAC1D,QAAI,eAAe,OAAW,MAAK,aAAa;AAChD,QAAI,qCAAqC;AACvC,WAAK,mCAAmC;AAC1C,QAAI,kCAAkC;AACpC,WAAK,gCAAgC;AACvC,QAAI,gCAAgC;AAClC,WAAK,8BAA8B;AACrC,QAAI,8BAA8B;AAChC,WAAK,4BAA4B;AACnC,QAAI,+BAA+B;AACjC,WAAK,6BAA6B;AACpC,QAAI,oCAAoC;AACtC,WAAK,kCAAkC;AACzC,QAAI,qBAAqB,OAAW,MAAK,mBAAmB;AAC5D,QAAI,oCAAoC;AACtC,WAAK,kCAAkC;AACzC,QAAI,mCAAmC;AACrC,WAAK,iCAAiC;AACxC,QAAI,aAAa,OAAW,MAAK,WAAW;AAC5C,QAAI,UAAU,OAAW,MAAK,QAAQ;AACtC,QAAI,kBAAkB,OAAW,MAAK,gBAAgB;AAEtD,OAAG,KAAK,IAAI;AAAA,EACd,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,SAAO;AACT;;;AC7GA,SAAS,aAAAC,YAAW,cAAc;AA2CzB,gBAAAC,YAAA;AAlBF,SAAS,eAAe,EAAE,MAAM,MAAM,KAAK,UAAU,WAAW,OAAO,GAAG,KAAK,GAAG;AACvF,QAAM,KAAK,YAAY;AACvB,QAAM,eAAe,OAAO,IAAI;AAChC,QAAM,iBAAiB,OAAO,KAAK;AAEnC,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,MAAM,CAAC,aAAa,WAAW,eAAe,QAAS;AAE5D,UAAM,OAAO,EAAE,KAAK;AACpB,QAAI,SAAS,OAAW,MAAK,OAAO;AACpC,QAAI,QAAQ,OAAW,MAAK,MAAM;AAClC,QAAI,aAAa,OAAW,MAAK,WAAW;AAE5C,OAAG,OAAO,aAAa,SAAS,IAAI;AACpC,mBAAe,UAAU;AAAA,EAE3B,GAAG,CAAC,EAAE,CAAC;AAEP,SAAO,gBAAAD,KAAC,SAAI,KAAK,cAAc,WAAsB,OAAe,GAAG,MAAM;AAC/E;;;AC5CA,SAAS,aAAAE,YAAW,UAAAC,eAAc;AAwBzB,gBAAAC,YAAA;AAXF,SAAS,aAAa,EAAE,WAAW,OAAO,GAAG,KAAK,GAAG;AAC1D,QAAM,KAAK,YAAY;AACvB,QAAM,eAAeC,QAAO,IAAI;AAChC,QAAM,iBAAiBA,QAAO,KAAK;AAEnC,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,MAAM,CAAC,aAAa,WAAW,eAAe,QAAS;AAC5D,OAAG,KAAK,aAAa,OAAO;AAC5B,mBAAe,UAAU;AAAA,EAC3B,GAAG,CAAC,EAAE,CAAC;AAEP,SAAO,gBAAAF,KAAC,SAAI,KAAK,cAAc,WAAsB,OAAe,GAAG,MAAM;AAC/E;","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 * The widget renders nothing until both `code` and `name` are set, so render\n * this component only for logged-in users and always pass `name`.\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 (required)\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 (!name && typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n console.warn(\n '[@stromcom/react-snippet] <StromcomUser> is missing `name` — the widget renders nothing until both `code` and `name` are set.',\n );\n }\n }, [name]);\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({thread, message, notification, reaction}) called with the unread counts\n * @param {Function} [props.onLoad] - Called once the widget is running; from then on stromCom.openHome() / closeHome() / toggleHome() work\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; null = no bubble\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 the notification center opens for the first time\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-default'|'stromcom-dark'|null)} [props.theme] - Theme. null follows browser preference.\n * @param {Function} [props.entityResolve] - async ({type, id, groups, url}) => detail; resolves business-entity detail for the message editor's chip hover-card\n *\n * @example\n * <StromcomConf\n * notificationElementPosition={4}\n * onNotification={(counts) => setBadge(counts.thread + counts.notification)}\n * />\n */\nexport function StromcomConf({\n notificationRenderer,\n onNotification,\n onLoad,\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 (onLoad !== undefined) opts.onLoad = onLoad;\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 onLoad,\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
package/src/StromcomConf.jsx
CHANGED
|
@@ -10,34 +10,36 @@ import { useStromcom } from './StromcomProvider.jsx';
|
|
|
10
10
|
* actual JS functions here — no need for the string-wrapping used in the PHP library.
|
|
11
11
|
*
|
|
12
12
|
* @param {object} [props.notificationRenderer] - Function(notification) for custom notification rendering
|
|
13
|
-
* @param {object} [props.onNotification] - Function(
|
|
13
|
+
* @param {object} [props.onNotification] - Function({thread, message, notification, reaction}) called with the unread counts
|
|
14
|
+
* @param {Function} [props.onLoad] - Called once the widget is running; from then on stromCom.openHome() / closeHome() / toggleHome() work
|
|
14
15
|
* @param {(string|string[])} [props.appFrameCSSPath] - CSS URL(s) for the widget frame — the shadow root holding the container, the backdrop and the iframe
|
|
15
16
|
* @param {(string|string[])} [props.appCSSPath] - CSS URL(s) for the app itself (inside the iframe), on top of the CSS from the project settings
|
|
16
17
|
* @param {Function} [props.notificationElementTargetElement] - Function/Promise/Element returning the icon mount target
|
|
17
18
|
* @param {boolean} [props.notificationElementShowAlways] - Show icon even with zero notifications
|
|
18
|
-
* @param {number} [props.notificationElementPosition] - Icon position: 1=top-left 2=top-right 3=bottom-right 4=bottom-left
|
|
19
|
+
* @param {number} [props.notificationElementPosition] - Icon position: 1=top-left 2=top-right 3=bottom-right 4=bottom-left; null = no bubble
|
|
19
20
|
* @param {object} [props.notificationElementStyles] - Inline styles object for the default notification button
|
|
20
21
|
* @param {string} [props.notificationElementCSSPath] - CSS URL for the default notification renderer
|
|
21
22
|
* @param {boolean} [props.notificationElementNoShadowRoot]- Disable shadow-root for notifications
|
|
22
|
-
* @param {Function} [props.homeBeforeRender] - Callback before notification center opens
|
|
23
|
+
* @param {Function} [props.homeBeforeRender] - Callback before the notification center opens for the first time
|
|
23
24
|
* @param {Function} [props.notificationElementBeforeRender]- Callback before notification icon renders
|
|
24
25
|
* @param {Function} [props.notificationElementAfterRender] - Callback after notification icon renders
|
|
25
26
|
* @param {(string|null)} [props.language] - UI language. Any code is accepted; one without a translation falls
|
|
26
27
|
* back to the browser language, then English. null follows the browser. Overrides the `language`
|
|
27
28
|
* prop on StromcomProvider, which is only the initial value; this one
|
|
28
29
|
* can be changed at runtime.
|
|
29
|
-
* @param {('stromcom-
|
|
30
|
-
* @param {Function} [props.entityResolve] - async ({type, id}) => detail; resolves business-entity detail for the message editor's chip hover-card
|
|
30
|
+
* @param {('stromcom-default'|'stromcom-dark'|null)} [props.theme] - Theme. null follows browser preference.
|
|
31
|
+
* @param {Function} [props.entityResolve] - async ({type, id, groups, url}) => detail; resolves business-entity detail for the message editor's chip hover-card
|
|
31
32
|
*
|
|
32
33
|
* @example
|
|
33
34
|
* <StromcomConf
|
|
34
35
|
* notificationElementPosition={4}
|
|
35
|
-
* onNotification={(
|
|
36
|
+
* onNotification={(counts) => setBadge(counts.thread + counts.notification)}
|
|
36
37
|
* />
|
|
37
38
|
*/
|
|
38
39
|
export function StromcomConf({
|
|
39
40
|
notificationRenderer,
|
|
40
41
|
onNotification,
|
|
42
|
+
onLoad,
|
|
41
43
|
appFrameCSSPath,
|
|
42
44
|
appCSSPath,
|
|
43
45
|
notificationElementTargetElement,
|
|
@@ -62,6 +64,7 @@ export function StromcomConf({
|
|
|
62
64
|
|
|
63
65
|
if (notificationRenderer !== undefined) opts.notificationRenderer = notificationRenderer;
|
|
64
66
|
if (onNotification !== undefined) opts.onNotification = onNotification;
|
|
67
|
+
if (onLoad !== undefined) opts.onLoad = onLoad;
|
|
65
68
|
if (appFrameCSSPath !== undefined) opts.appFrameCSSPath = appFrameCSSPath;
|
|
66
69
|
if (appCSSPath !== undefined) opts.appCSSPath = appCSSPath;
|
|
67
70
|
if (notificationElementTargetElement !== undefined)
|
|
@@ -90,6 +93,7 @@ export function StromcomConf({
|
|
|
90
93
|
sc,
|
|
91
94
|
notificationRenderer,
|
|
92
95
|
onNotification,
|
|
96
|
+
onLoad,
|
|
93
97
|
appFrameCSSPath,
|
|
94
98
|
appCSSPath,
|
|
95
99
|
notificationElementTargetElement,
|
package/src/StromcomUser.jsx
CHANGED
|
@@ -5,12 +5,15 @@ import { useStromcom } from './StromcomProvider.jsx';
|
|
|
5
5
|
* Identifies the currently logged-in user.
|
|
6
6
|
* Re-calls initUser whenever any prop changes.
|
|
7
7
|
*
|
|
8
|
+
* The widget renders nothing until both `code` and `name` are set, so render
|
|
9
|
+
* this component only for logged-in users and always pass `name`.
|
|
10
|
+
*
|
|
8
11
|
* IMPORTANT: The `code` should already be hashed server-side (HMAC + base-62).
|
|
9
12
|
* Never pass your raw internal user ID directly from the client.
|
|
10
13
|
*
|
|
11
14
|
* @param {object} props
|
|
12
15
|
* @param {string} props.code - Hashed unique user identifier (max 100 chars, [a-zA-Z0-9-_])
|
|
13
|
-
* @param {string}
|
|
16
|
+
* @param {string} props.name - Display name (required)
|
|
14
17
|
* @param {string} [props.emailAddress]- Used for email notifications
|
|
15
18
|
* @param {boolean} [props.readOnly] - When true, user can read but not send messages
|
|
16
19
|
* @param {string} [props.avatarURL] - Full URL to avatar image
|
|
@@ -25,6 +28,14 @@ import { useStromcom } from './StromcomProvider.jsx';
|
|
|
25
28
|
export function StromcomUser({ code, name, emailAddress, readOnly, avatarURL }) {
|
|
26
29
|
const sc = useStromcom();
|
|
27
30
|
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!name && typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {
|
|
33
|
+
console.warn(
|
|
34
|
+
'[@stromcom/react-snippet] <StromcomUser> is missing `name` — the widget renders nothing until both `code` and `name` are set.',
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}, [name]);
|
|
38
|
+
|
|
28
39
|
useEffect(() => {
|
|
29
40
|
if (!sc) return;
|
|
30
41
|
|
|
@@ -30,6 +30,14 @@ describe('StromcomConf', () => {
|
|
|
30
30
|
expect(layer.conf).toHaveBeenCalledWith({ onNotification });
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
it('forwards onLoad as-is (no string-wrapping)', () => {
|
|
34
|
+
const layer = { conf: vi.fn() };
|
|
35
|
+
const onLoad = vi.fn();
|
|
36
|
+
renderWithLayer(layer, { onLoad });
|
|
37
|
+
|
|
38
|
+
expect(layer.conf).toHaveBeenCalledWith({ onLoad });
|
|
39
|
+
});
|
|
40
|
+
|
|
33
41
|
it('forwards entityResolve as-is (no string-wrapping)', () => {
|
|
34
42
|
const layer = { conf: vi.fn() };
|
|
35
43
|
const entityResolve = vi.fn();
|
|
@@ -83,7 +91,7 @@ describe('StromcomConf', () => {
|
|
|
83
91
|
|
|
84
92
|
it('re-sends conf when an option changes', () => {
|
|
85
93
|
const layer = { conf: vi.fn() };
|
|
86
|
-
const { rerender } = renderWithLayer(layer, { theme: 'stromcom-
|
|
94
|
+
const { rerender } = renderWithLayer(layer, { theme: 'stromcom-default' });
|
|
87
95
|
|
|
88
96
|
rerender(
|
|
89
97
|
<StromcomContext.Provider value={layer}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { render, cleanup } from '@testing-library/react';
|
|
3
3
|
import { StromcomContext } from '../StromcomProvider.jsx';
|
|
4
4
|
import { StromcomUser } from '../StromcomUser.jsx';
|
|
@@ -11,7 +11,16 @@ function renderWithLayer(layer, props) {
|
|
|
11
11
|
);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
let warn;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
cleanup();
|
|
22
|
+
warn.mockRestore();
|
|
23
|
+
});
|
|
15
24
|
|
|
16
25
|
describe('StromcomUser', () => {
|
|
17
26
|
it('calls initUser with only the code when no optional props are given', () => {
|
|
@@ -55,8 +64,23 @@ describe('StromcomUser', () => {
|
|
|
55
64
|
expect(layer.initUser).toHaveBeenLastCalledWith({ code: 'hashed-user-3', name: 'Janet' });
|
|
56
65
|
});
|
|
57
66
|
|
|
67
|
+
it('warns in development when name is missing', () => {
|
|
68
|
+
const layer = { initUser: vi.fn() };
|
|
69
|
+
renderWithLayer(layer, { code: 'hashed-user-5' });
|
|
70
|
+
|
|
71
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
72
|
+
expect(warn.mock.calls[0][0]).toContain('missing `name`');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('does not warn when name is given', () => {
|
|
76
|
+
const layer = { initUser: vi.fn() };
|
|
77
|
+
renderWithLayer(layer, { code: 'hashed-user-6', name: 'Jane' });
|
|
78
|
+
|
|
79
|
+
expect(warn).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
|
|
58
82
|
it('does not call initUser and renders nothing when there is no layer', () => {
|
|
59
|
-
const { container } = render(<StromcomUser code="hashed-user-4" />);
|
|
83
|
+
const { container } = render(<StromcomUser code="hashed-user-4" name="Jane" />);
|
|
60
84
|
|
|
61
85
|
expect(container.firstChild).toBeNull();
|
|
62
86
|
});
|
package/src/index.d.ts
CHANGED
|
@@ -7,6 +7,16 @@ export interface StromcomLayer {
|
|
|
7
7
|
thread: (target: HTMLElement, options: StromcomThreadOptions) => void;
|
|
8
8
|
conf: (options: StromcomConfOptions) => void;
|
|
9
9
|
home: (target: HTMLElement) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Opens the notification center over the whole page (creates it on the first call).
|
|
12
|
+
* Installed by the widget itself once it starts (loader script run, user set) —
|
|
13
|
+
* `undefined` until then. Wait for `StromcomConf`'s `onLoad`.
|
|
14
|
+
*/
|
|
15
|
+
openHome?: () => Promise<void>;
|
|
16
|
+
/** Hides the notification center. See `openHome`. */
|
|
17
|
+
closeHome?: () => void;
|
|
18
|
+
/** Opens or hides the notification center, like a click on the default bubble. See `openHome`. */
|
|
19
|
+
toggleHome?: () => Promise<void>;
|
|
10
20
|
[key: string]: any;
|
|
11
21
|
}
|
|
12
22
|
|
|
@@ -36,7 +46,8 @@ export function useStromcom(): StromcomLayer | null;
|
|
|
36
46
|
export interface StromcomUserOptions {
|
|
37
47
|
/** Hashed unique user identifier (max 100 chars, [a-zA-Z0-9-_]). Hash server-side. */
|
|
38
48
|
code: string;
|
|
39
|
-
name
|
|
49
|
+
/** Display name. Required — the widget renders nothing until both `code` and `name` are set. */
|
|
50
|
+
name: string;
|
|
40
51
|
emailAddress?: string;
|
|
41
52
|
readOnly?: boolean;
|
|
42
53
|
avatarURL?: string;
|
|
@@ -69,9 +80,30 @@ export interface StromcomHomeProps {
|
|
|
69
80
|
|
|
70
81
|
export function StromcomHome(props: StromcomHomeProps): JSX.Element;
|
|
71
82
|
|
|
83
|
+
/** Business entity passed to `entityResolve`. */
|
|
84
|
+
export interface StromcomEntity {
|
|
85
|
+
type: string;
|
|
86
|
+
id: string;
|
|
87
|
+
groups: string[];
|
|
88
|
+
url: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Unread counts passed to `onNotification`. */
|
|
92
|
+
export interface StromcomNotificationCounts {
|
|
93
|
+
thread: number;
|
|
94
|
+
message: number;
|
|
95
|
+
notification: number;
|
|
96
|
+
reaction: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
72
99
|
export interface StromcomConfOptions {
|
|
73
100
|
notificationRenderer?: (notification: unknown) => unknown;
|
|
74
|
-
onNotification?: (
|
|
101
|
+
onNotification?: (counts: StromcomNotificationCounts) => void;
|
|
102
|
+
/**
|
|
103
|
+
* Called once the widget is running (user set, apps rendered). From then on
|
|
104
|
+
* `openHome()`, `closeHome()` and `toggleHome()` on `useStromcom()` work.
|
|
105
|
+
*/
|
|
106
|
+
onLoad?: (api: unknown) => void;
|
|
75
107
|
/** CSS for the widget frame — the shadow root holding the container, the backdrop and the iframe. */
|
|
76
108
|
appFrameCSSPath?: string | string[];
|
|
77
109
|
/**
|
|
@@ -82,11 +114,12 @@ export interface StromcomConfOptions {
|
|
|
82
114
|
notificationElementTargetElement?:
|
|
83
115
|
(() => HTMLElement | Promise<HTMLElement>) | HTMLElement | Promise<HTMLElement>;
|
|
84
116
|
notificationElementShowAlways?: boolean;
|
|
85
|
-
/** Icon position: 1=top-left, 2=top-right, 3=bottom-right, 4=bottom-left. */
|
|
86
|
-
notificationElementPosition?: 1 | 2 | 3 | 4;
|
|
117
|
+
/** Icon position: 1=top-left, 2=top-right, 3=bottom-right, 4=bottom-left; `null` = no bubble. */
|
|
118
|
+
notificationElementPosition?: 1 | 2 | 3 | 4 | null;
|
|
87
119
|
notificationElementStyles?: Record<string, string | number | null>;
|
|
88
120
|
notificationElementCSSPath?: string;
|
|
89
121
|
notificationElementNoShadowRoot?: boolean;
|
|
122
|
+
/** Called before the notification center opens for the first time (bubble click or `openHome()` / `toggleHome()`). */
|
|
90
123
|
homeBeforeRender?: () => void | Promise<void>;
|
|
91
124
|
notificationElementBeforeRender?: () => void | Promise<void>;
|
|
92
125
|
notificationElementAfterRender?: () => void;
|
|
@@ -96,10 +129,10 @@ export interface StromcomConfOptions {
|
|
|
96
129
|
* Overrides `StromcomProviderProps.language`, which is only the initial value.
|
|
97
130
|
*/
|
|
98
131
|
language?: string | null;
|
|
99
|
-
/** Theme: `"stromcom-
|
|
100
|
-
theme?: 'stromcom-
|
|
132
|
+
/** Theme: `"stromcom-default"`, `"stromcom-dark"`, or `null` to follow browser preference. */
|
|
133
|
+
theme?: 'stromcom-default' | 'stromcom-dark' | null;
|
|
101
134
|
/** Resolves business-entity detail (order, ticket…) for the message editor's chip hover-card. */
|
|
102
|
-
entityResolve?: (entity:
|
|
135
|
+
entityResolve?: (entity: StromcomEntity) => unknown | Promise<unknown>;
|
|
103
136
|
}
|
|
104
137
|
|
|
105
138
|
export function StromcomConf(props: StromcomConfOptions): null;
|