@strata-ds/core 2.1.20 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -259,6 +259,9 @@ Wrap your application's root component with `StrataProvider`. This provider mana
259
259
  | `userDesignSystem` | `DesignSystemData` | Override design system data — useful for storybook, testing, or static mode. Takes precedence over `initialData`. |
260
260
  | `onSyncError` | `(error: { code: number; message: string; source: string }) => void` | Callback invoked when a sync cycle fails. `code` is the HTTP status (0 for non-HTTP errors), `message` is a human-readable description, and `source` is `"cdn"` or `"sync"`. |
261
261
  | `onSyncSuccess` | `(data: DesignSystemData) => void` | Callback invoked whenever a sync cycle succeeds with new data. |
262
+ | `reportUsage` | `boolean` | Whether to send non-sensitive usage telemetry to power "See It in the Wild" in the dashboard. Defaults to `true` in production. Set to `false` to opt out. |
263
+ | `appName` | `string` | Optional human-readable display name for your app in the Strata dashboard (e.g. `"Storefront Web"`). |
264
+ | `environment` | `string` | Override automatic environment detection (`"production"`, `"staging"`, `"development"`). Heartbeat is paused when set to `"development"`. |
262
265
 
263
266
  ```tsx
264
267
  // app/layout.tsx (Next.js App Router)
@@ -307,6 +310,55 @@ Projects with `syncToken` set are only readable by clients that present the corr
307
310
  </StrataProvider>
308
311
  ```
309
312
 
313
+ ### Connected Apps Telemetry ("See It in the Wild")
314
+
315
+ By default in production environments, `<StrataProvider>` sends a lightweight telemetry heartbeat every 5 minutes to report active design system surface integrations.
316
+
317
+ #### What is "Connected Apps"?
318
+ "Connected Apps" (featured as "See It in the Wild" on your public project page and Settings dashboard) provides real-time visibility into which live applications are consuming your design system, their active SDK version, environment, and health status (`Live`, `Stale`, or `Dormant`).
319
+
320
+ #### Data Transmitted
321
+ The telemetry heartbeat contains **only** non-sensitive metadata:
322
+ - Application origin / hostname (e.g. `https://app.example.com`)
323
+ - Strata SDK version (e.g. `2.2.0`)
324
+ - Sync mode (`cdn`, `legacy`, `static`) and polling interval
325
+ - User Agent string (browser family)
326
+ - Optional `appName` prop value
327
+
328
+ #### How to Opt Out
329
+ To disable telemetry heartbeats entirely, set `reportUsage={false}`:
330
+
331
+ ```tsx
332
+ <StrataProvider
333
+ syncEnabled={true}
334
+ projectId="YOUR_PROJECT_ID"
335
+ snapshotCdnBase="https://cdn.strata.charisol.io"
336
+ reportUsage={false}
337
+ >
338
+ {children}
339
+ </StrataProvider>
340
+ ```
341
+
342
+ #### CLI Setup & Build-Time Registration
343
+ You can also initialize project configuration using `npx strata init`:
344
+
345
+ ```bash
346
+ npx strata init
347
+ ```
348
+
349
+ This generates a `strata.json` configuration file:
350
+
351
+ ```json
352
+ {
353
+ "projectId": "YOUR_PROJECT_ID",
354
+ "snapshotCdnBase": "https://cdn.strata.charisol.io",
355
+ "appName": "Storefront Web",
356
+ "reportUsage": true
357
+ }
358
+ ```
359
+
360
+ When `strata.json` is present, the `@strata-ds/core` `postinstall` script automatically registers build-time presence for your application.
361
+
310
362
  ## Using Design System Components
311
363
 
312
364
  `@strata-ds/core` provides dynamic access to your components defined in Strata.
@@ -168,6 +168,37 @@ async function run() {
168
168
  variablesOnly: variablesOnly !== false,
169
169
  });
170
170
  console.log('[Strata] Postinstall snapshot retrieval complete.');
171
+ // Register build-time presence (non-blocking, best-effort)
172
+ try {
173
+ const os = await import('os');
174
+ const apiBase = config.apiBase || 'https://api-production-charisol-design-system-be.aws.charisol.io';
175
+ const headers = { 'Content-Type': 'application/json' };
176
+ if (syncToken)
177
+ headers['Authorization'] = `Bearer ${syncToken}`;
178
+ let sdkVersion = '2.2.0';
179
+ try {
180
+ const pkgPath = path.resolve(__dirname, '../../package.json');
181
+ if (fs.existsSync(pkgPath)) {
182
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
183
+ if (pkg.version)
184
+ sdkVersion = pkg.version;
185
+ }
186
+ }
187
+ catch (err) { }
188
+ await fetch(`${apiBase}/v1/connect/${projectId}/register`, {
189
+ method: 'POST',
190
+ headers,
191
+ body: JSON.stringify({
192
+ hostname: os.hostname(),
193
+ sdkVersion,
194
+ environment: 'build',
195
+ }),
196
+ });
197
+ console.log('[Strata] Build-time registration complete.');
198
+ }
199
+ catch (e) {
200
+ // Registration failure is non-fatal
201
+ }
171
202
  }
172
203
  catch (error) {
173
204
  // Graceful degradation: log warning but do not throw error so install succeeds
@@ -173,13 +173,20 @@ async function runInit() {
173
173
  const outputCss = (await askQuestion('Enter path to write CSS fallback file (default: ./src/styles/strata-fallbacks.css): ')) || './src/styles/strata-fallbacks.css';
174
174
  const varsOnlyInput = await askQuestion('Only output variables in snapshot JSON? (Y/n): ');
175
175
  const variablesOnly = varsOnlyInput.toLowerCase() !== 'n';
176
+ const appNameInput = await askQuestion('Enter Application Name (optional, e.g. "Storefront Web"): ');
177
+ const reportUsageInput = await askQuestion('Report usage telemetry to Strata Dashboard? (Y/n): ');
178
+ const reportUsage = reportUsageInput.toLowerCase() !== 'n';
176
179
  const config = {
177
180
  projectId,
178
181
  snapshotCdnBase: cdnBase,
179
182
  output,
180
183
  outputCss,
181
184
  variablesOnly,
185
+ reportUsage,
182
186
  };
187
+ if (appNameInput) {
188
+ config.appName = appNameInput;
189
+ }
183
190
  const strataJsonPath = path.join(process.cwd(), 'strata.json');
184
191
  fs.writeFileSync(strataJsonPath, JSON.stringify(config, null, 2), 'utf8');
185
192
  console.log(`\nConfig file written successfully to: ${strataJsonPath}`);
package/dist/index.d.ts CHANGED
@@ -146,6 +146,12 @@ interface StrataProviderProps {
146
146
  * Defaults to true.
147
147
  */
148
148
  injectCssVars?: boolean;
149
+ /** Whether to report usage telemetry (heartbeat). Defaults to true in production. */
150
+ reportUsage?: boolean;
151
+ /** Human-readable app name shown in the Strata dashboard. */
152
+ appName?: string;
153
+ /** Override automatic environment detection. */
154
+ environment?: string;
149
155
  }
150
156
  declare const StrataProvider: React__default.FC<StrataProviderProps>;
151
157
 
@@ -862,5 +868,24 @@ declare const useAvailableComponents: () => {
862
868
  allComponents: string[];
863
869
  };
864
870
 
865
- export { AutoForm, Card, DefaultCard, DynamicComponent, StrataComponentFactory, StrataDebug, StrataErrorBoundary, StrataProvider, strataRegistry, useAvailableComponents, useCardData, useComponent, useComponentData, useComponents, useCustomComponent, useDynamicComponents, useStrata };
871
+ interface HeartbeatConfig {
872
+ projectId: string;
873
+ syncToken?: string;
874
+ syncMode: "cdn" | "legacy" | "static";
875
+ syncInterval: number;
876
+ appName?: string;
877
+ environment?: string;
878
+ apiBase?: string;
879
+ }
880
+ declare class HeartbeatReporter {
881
+ private intervalId;
882
+ private config;
883
+ private disabled;
884
+ constructor(config: HeartbeatConfig);
885
+ start(): Promise<void>;
886
+ stop(): void;
887
+ private sendHeartbeat;
888
+ }
889
+
890
+ export { AutoForm, Card, DefaultCard, DynamicComponent, HeartbeatReporter, StrataComponentFactory, StrataDebug, StrataErrorBoundary, StrataProvider, strataRegistry, useAvailableComponents, useCardData, useComponent, useComponentData, useComponents, useCustomComponent, useDynamicComponents, useStrata };
866
891
  export type { CardComponent, CardProps, ComponentNode, ComponentProperty, ComponentVariant, CustomDesignSystemData, DesignSystemData, DesignToken, FilterType, LinkedElement, SelectedItem, SelectedType, TokenType };
package/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import React, { useState, useRef, useEffect, useMemo, useCallback, createContext, useContext } from 'react';
2
+ import React, { useRef, useEffect, useState, useMemo, useCallback, createContext, useContext } from 'react';
3
3
  import { extendTheme, ChakraProvider, Box, Flex, Text, Image, Button, VStack, HStack } from '@chakra-ui/react';
4
4
 
5
5
  function _arrayLikeToArray(r, a) {
@@ -1089,6 +1089,141 @@ if (shouldRunPascalCaseTest) {
1089
1089
  }, 1000);
1090
1090
  }
1091
1091
 
1092
+ var DEFAULT_HEARTBEAT_INTERVAL = 5 * 60 * 1000; // 5 minutes
1093
+ var DEFAULT_API_BASE = "https://api-production-charisol-design-system-be.aws.charisol.io";
1094
+ var HeartbeatReporter = /*#__PURE__*/function () {
1095
+ function HeartbeatReporter(config) {
1096
+ _classCallCheck(this, HeartbeatReporter);
1097
+ this.intervalId = null;
1098
+ this.disabled = false;
1099
+ this.config = config;
1100
+ }
1101
+ return _createClass(HeartbeatReporter, [{
1102
+ key: "start",
1103
+ value: function () {
1104
+ var _start = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
1105
+ var _this = this;
1106
+ return _regenerator().w(function (_context) {
1107
+ while (1) switch (_context.n) {
1108
+ case 0:
1109
+ if (!(typeof window === "undefined")) {
1110
+ _context.n = 1;
1111
+ break;
1112
+ }
1113
+ return _context.a(2);
1114
+ case 1:
1115
+ if (!(this.config.environment === "development")) {
1116
+ _context.n = 2;
1117
+ break;
1118
+ }
1119
+ return _context.a(2);
1120
+ case 2:
1121
+ _context.n = 3;
1122
+ return this.sendHeartbeat();
1123
+ case 3:
1124
+ // Schedule recurring heartbeats
1125
+ this.intervalId = setInterval(function () {
1126
+ return _this.sendHeartbeat();
1127
+ }, DEFAULT_HEARTBEAT_INTERVAL);
1128
+ case 4:
1129
+ return _context.a(2);
1130
+ }
1131
+ }, _callee, this);
1132
+ }));
1133
+ function start() {
1134
+ return _start.apply(this, arguments);
1135
+ }
1136
+ return start;
1137
+ }()
1138
+ }, {
1139
+ key: "stop",
1140
+ value: function stop() {
1141
+ if (this.intervalId) {
1142
+ clearInterval(this.intervalId);
1143
+ this.intervalId = null;
1144
+ }
1145
+ }
1146
+ }, {
1147
+ key: "sendHeartbeat",
1148
+ value: function () {
1149
+ var _sendHeartbeat = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
1150
+ var apiBase, url, headers, version, body, res;
1151
+ return _regenerator().w(function (_context2) {
1152
+ while (1) switch (_context2.p = _context2.n) {
1153
+ case 0:
1154
+ if (!this.disabled) {
1155
+ _context2.n = 1;
1156
+ break;
1157
+ }
1158
+ return _context2.a(2);
1159
+ case 1:
1160
+ _context2.p = 1;
1161
+ apiBase = this.config.apiBase || DEFAULT_API_BASE;
1162
+ url = "".concat(apiBase, "/v1/connect/").concat(this.config.projectId, "/heartbeat");
1163
+ headers = {
1164
+ "Content-Type": "application/json"
1165
+ };
1166
+ if (this.config.syncToken) {
1167
+ headers["Authorization"] = "Bearer ".concat(this.config.syncToken);
1168
+ }
1169
+ version = "2.2.1" ;
1170
+ body = {
1171
+ origin: window.location.origin,
1172
+ sdkVersion: version,
1173
+ syncMode: this.config.syncMode,
1174
+ syncInterval: this.config.syncInterval,
1175
+ userAgent: navigator.userAgent,
1176
+ appName: this.config.appName,
1177
+ metadata: {
1178
+ pageUrl: window.location.href,
1179
+ referrer: document.referrer,
1180
+ framework: detectFramework()
1181
+ }
1182
+ };
1183
+ _context2.n = 2;
1184
+ return fetch(url, {
1185
+ method: "POST",
1186
+ headers: headers,
1187
+ body: JSON.stringify(body),
1188
+ // Don't let heartbeat affect page performance
1189
+ keepalive: true
1190
+ });
1191
+ case 2:
1192
+ res = _context2.v;
1193
+ // If we get 401, disable heartbeat for this session
1194
+ if (res.status === 401) {
1195
+ console.warn("[strata] Heartbeat disabled: invalid sync token");
1196
+ this.disabled = true;
1197
+ this.stop();
1198
+ }
1199
+ _context2.n = 4;
1200
+ break;
1201
+ case 3:
1202
+ _context2.p = 3;
1203
+ _context2.v;
1204
+ case 4:
1205
+ return _context2.a(2);
1206
+ }
1207
+ }, _callee2, this, [[1, 3]]);
1208
+ }));
1209
+ function sendHeartbeat() {
1210
+ return _sendHeartbeat.apply(this, arguments);
1211
+ }
1212
+ return sendHeartbeat;
1213
+ }()
1214
+ }]);
1215
+ }();
1216
+ function detectFramework() {
1217
+ if (typeof window === "undefined") return "unknown";
1218
+ // @ts-ignore
1219
+ if (window.__NEXT_DATA__) return "nextjs";
1220
+ // @ts-ignore
1221
+ if (window.__REMIX_DEV_TOOLS) return "remix";
1222
+ // @ts-ignore
1223
+ if (window.__GATSBY) return "gatsby";
1224
+ return "react";
1225
+ }
1226
+
1092
1227
  var StrataContext = /*#__PURE__*/createContext(undefined);
1093
1228
  var useStrata = function useStrata() {
1094
1229
  var context = useContext(StrataContext);
@@ -1117,7 +1252,31 @@ var StrataProvider = function StrataProvider(_ref) {
1117
1252
  _ref$persistCache = _ref.persistCache,
1118
1253
  persistCache = _ref$persistCache === void 0 ? true : _ref$persistCache,
1119
1254
  _ref$injectCssVars = _ref.injectCssVars,
1120
- injectCssVars = _ref$injectCssVars === void 0 ? true : _ref$injectCssVars;
1255
+ injectCssVars = _ref$injectCssVars === void 0 ? true : _ref$injectCssVars,
1256
+ _ref$reportUsage = _ref.reportUsage,
1257
+ reportUsage = _ref$reportUsage === void 0 ? true : _ref$reportUsage,
1258
+ appName = _ref.appName,
1259
+ environment = _ref.environment;
1260
+ // Heartbeat reporter — ref to avoid re-creation on each render
1261
+ var heartbeatRef = useRef(null);
1262
+ useEffect(function () {
1263
+ if (!reportUsage || !syncEnabled || !projectId) return;
1264
+ var syncMode = snapshotCdnBase ? "cdn" : syncUrl ? "legacy" : "static";
1265
+ var reporter = new HeartbeatReporter({
1266
+ projectId: projectId,
1267
+ syncToken: syncToken,
1268
+ syncMode: syncMode,
1269
+ syncInterval: syncInterval,
1270
+ appName: appName,
1271
+ environment: environment
1272
+ });
1273
+ heartbeatRef.current = reporter;
1274
+ reporter.start();
1275
+ return function () {
1276
+ reporter.stop();
1277
+ heartbeatRef.current = null;
1278
+ };
1279
+ }, [reportUsage, syncEnabled, projectId, syncToken, snapshotCdnBase, syncUrl, syncInterval, appName, environment]);
1121
1280
  var _useState = useState(function () {
1122
1281
  if (userDesignSystem) return "initial";
1123
1282
  if (initialData) return "initial";
@@ -3409,4 +3568,4 @@ var AutoForm = function AutoForm(_ref2) {
3409
3568
  return /*#__PURE__*/React.createElement(FormComponent, props, children);
3410
3569
  };
3411
3570
 
3412
- export { AutoForm, Card, DefaultCard, DynamicComponent, StrataComponentFactory, StrataDebug, StrataErrorBoundary, StrataProvider, strataRegistry, useAvailableComponents, useCardData, useComponent, useComponentData, useComponents, useCustomComponent, useDynamicComponents, useStrata };
3571
+ export { AutoForm, Card, DefaultCard, DynamicComponent, HeartbeatReporter, StrataComponentFactory, StrataDebug, StrataErrorBoundary, StrataProvider, strataRegistry, useAvailableComponents, useCardData, useComponent, useComponentData, useComponents, useCustomComponent, useDynamicComponents, useStrata };
package/dist/index.js CHANGED
@@ -1091,6 +1091,141 @@ if (shouldRunPascalCaseTest) {
1091
1091
  }, 1000);
1092
1092
  }
1093
1093
 
1094
+ var DEFAULT_HEARTBEAT_INTERVAL = 5 * 60 * 1000; // 5 minutes
1095
+ var DEFAULT_API_BASE = "https://api-production-charisol-design-system-be.aws.charisol.io";
1096
+ var HeartbeatReporter = /*#__PURE__*/function () {
1097
+ function HeartbeatReporter(config) {
1098
+ _classCallCheck(this, HeartbeatReporter);
1099
+ this.intervalId = null;
1100
+ this.disabled = false;
1101
+ this.config = config;
1102
+ }
1103
+ return _createClass(HeartbeatReporter, [{
1104
+ key: "start",
1105
+ value: function () {
1106
+ var _start = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
1107
+ var _this = this;
1108
+ return _regenerator().w(function (_context) {
1109
+ while (1) switch (_context.n) {
1110
+ case 0:
1111
+ if (!(typeof window === "undefined")) {
1112
+ _context.n = 1;
1113
+ break;
1114
+ }
1115
+ return _context.a(2);
1116
+ case 1:
1117
+ if (!(this.config.environment === "development")) {
1118
+ _context.n = 2;
1119
+ break;
1120
+ }
1121
+ return _context.a(2);
1122
+ case 2:
1123
+ _context.n = 3;
1124
+ return this.sendHeartbeat();
1125
+ case 3:
1126
+ // Schedule recurring heartbeats
1127
+ this.intervalId = setInterval(function () {
1128
+ return _this.sendHeartbeat();
1129
+ }, DEFAULT_HEARTBEAT_INTERVAL);
1130
+ case 4:
1131
+ return _context.a(2);
1132
+ }
1133
+ }, _callee, this);
1134
+ }));
1135
+ function start() {
1136
+ return _start.apply(this, arguments);
1137
+ }
1138
+ return start;
1139
+ }()
1140
+ }, {
1141
+ key: "stop",
1142
+ value: function stop() {
1143
+ if (this.intervalId) {
1144
+ clearInterval(this.intervalId);
1145
+ this.intervalId = null;
1146
+ }
1147
+ }
1148
+ }, {
1149
+ key: "sendHeartbeat",
1150
+ value: function () {
1151
+ var _sendHeartbeat = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
1152
+ var apiBase, url, headers, version, body, res;
1153
+ return _regenerator().w(function (_context2) {
1154
+ while (1) switch (_context2.p = _context2.n) {
1155
+ case 0:
1156
+ if (!this.disabled) {
1157
+ _context2.n = 1;
1158
+ break;
1159
+ }
1160
+ return _context2.a(2);
1161
+ case 1:
1162
+ _context2.p = 1;
1163
+ apiBase = this.config.apiBase || DEFAULT_API_BASE;
1164
+ url = "".concat(apiBase, "/v1/connect/").concat(this.config.projectId, "/heartbeat");
1165
+ headers = {
1166
+ "Content-Type": "application/json"
1167
+ };
1168
+ if (this.config.syncToken) {
1169
+ headers["Authorization"] = "Bearer ".concat(this.config.syncToken);
1170
+ }
1171
+ version = "2.2.1" ;
1172
+ body = {
1173
+ origin: window.location.origin,
1174
+ sdkVersion: version,
1175
+ syncMode: this.config.syncMode,
1176
+ syncInterval: this.config.syncInterval,
1177
+ userAgent: navigator.userAgent,
1178
+ appName: this.config.appName,
1179
+ metadata: {
1180
+ pageUrl: window.location.href,
1181
+ referrer: document.referrer,
1182
+ framework: detectFramework()
1183
+ }
1184
+ };
1185
+ _context2.n = 2;
1186
+ return fetch(url, {
1187
+ method: "POST",
1188
+ headers: headers,
1189
+ body: JSON.stringify(body),
1190
+ // Don't let heartbeat affect page performance
1191
+ keepalive: true
1192
+ });
1193
+ case 2:
1194
+ res = _context2.v;
1195
+ // If we get 401, disable heartbeat for this session
1196
+ if (res.status === 401) {
1197
+ console.warn("[strata] Heartbeat disabled: invalid sync token");
1198
+ this.disabled = true;
1199
+ this.stop();
1200
+ }
1201
+ _context2.n = 4;
1202
+ break;
1203
+ case 3:
1204
+ _context2.p = 3;
1205
+ _context2.v;
1206
+ case 4:
1207
+ return _context2.a(2);
1208
+ }
1209
+ }, _callee2, this, [[1, 3]]);
1210
+ }));
1211
+ function sendHeartbeat() {
1212
+ return _sendHeartbeat.apply(this, arguments);
1213
+ }
1214
+ return sendHeartbeat;
1215
+ }()
1216
+ }]);
1217
+ }();
1218
+ function detectFramework() {
1219
+ if (typeof window === "undefined") return "unknown";
1220
+ // @ts-ignore
1221
+ if (window.__NEXT_DATA__) return "nextjs";
1222
+ // @ts-ignore
1223
+ if (window.__REMIX_DEV_TOOLS) return "remix";
1224
+ // @ts-ignore
1225
+ if (window.__GATSBY) return "gatsby";
1226
+ return "react";
1227
+ }
1228
+
1094
1229
  var StrataContext = /*#__PURE__*/React.createContext(undefined);
1095
1230
  var useStrata = function useStrata() {
1096
1231
  var context = React.useContext(StrataContext);
@@ -1119,7 +1254,31 @@ var StrataProvider = function StrataProvider(_ref) {
1119
1254
  _ref$persistCache = _ref.persistCache,
1120
1255
  persistCache = _ref$persistCache === void 0 ? true : _ref$persistCache,
1121
1256
  _ref$injectCssVars = _ref.injectCssVars,
1122
- injectCssVars = _ref$injectCssVars === void 0 ? true : _ref$injectCssVars;
1257
+ injectCssVars = _ref$injectCssVars === void 0 ? true : _ref$injectCssVars,
1258
+ _ref$reportUsage = _ref.reportUsage,
1259
+ reportUsage = _ref$reportUsage === void 0 ? true : _ref$reportUsage,
1260
+ appName = _ref.appName,
1261
+ environment = _ref.environment;
1262
+ // Heartbeat reporter — ref to avoid re-creation on each render
1263
+ var heartbeatRef = React.useRef(null);
1264
+ React.useEffect(function () {
1265
+ if (!reportUsage || !syncEnabled || !projectId) return;
1266
+ var syncMode = snapshotCdnBase ? "cdn" : syncUrl ? "legacy" : "static";
1267
+ var reporter = new HeartbeatReporter({
1268
+ projectId: projectId,
1269
+ syncToken: syncToken,
1270
+ syncMode: syncMode,
1271
+ syncInterval: syncInterval,
1272
+ appName: appName,
1273
+ environment: environment
1274
+ });
1275
+ heartbeatRef.current = reporter;
1276
+ reporter.start();
1277
+ return function () {
1278
+ reporter.stop();
1279
+ heartbeatRef.current = null;
1280
+ };
1281
+ }, [reportUsage, syncEnabled, projectId, syncToken, snapshotCdnBase, syncUrl, syncInterval, appName, environment]);
1123
1282
  var _useState = React.useState(function () {
1124
1283
  if (userDesignSystem) return "initial";
1125
1284
  if (initialData) return "initial";
@@ -3415,6 +3574,7 @@ exports.AutoForm = AutoForm;
3415
3574
  exports.Card = Card;
3416
3575
  exports.DefaultCard = DefaultCard;
3417
3576
  exports.DynamicComponent = DynamicComponent;
3577
+ exports.HeartbeatReporter = HeartbeatReporter;
3418
3578
  exports.StrataComponentFactory = StrataComponentFactory;
3419
3579
  exports.StrataDebug = StrataDebug;
3420
3580
  exports.StrataErrorBoundary = StrataErrorBoundary;
@@ -71,6 +71,12 @@ interface StrataProviderProps {
71
71
  * Defaults to true.
72
72
  */
73
73
  injectCssVars?: boolean;
74
+ /** Whether to report usage telemetry (heartbeat). Defaults to true in production. */
75
+ reportUsage?: boolean;
76
+ /** Human-readable app name shown in the Strata dashboard. */
77
+ appName?: string;
78
+ /** Override automatic environment detection. */
79
+ environment?: string;
74
80
  }
75
81
  export declare const StrataProvider: React.FC<StrataProviderProps>;
76
82
  export {};
@@ -0,0 +1,18 @@
1
+ export interface HeartbeatConfig {
2
+ projectId: string;
3
+ syncToken?: string;
4
+ syncMode: "cdn" | "legacy" | "static";
5
+ syncInterval: number;
6
+ appName?: string;
7
+ environment?: string;
8
+ apiBase?: string;
9
+ }
10
+ export declare class HeartbeatReporter {
11
+ private intervalId;
12
+ private config;
13
+ private disabled;
14
+ constructor(config: HeartbeatConfig);
15
+ start(): Promise<void>;
16
+ stop(): void;
17
+ private sendHeartbeat;
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strata-ds/core",
3
- "version": "2.1.20",
3
+ "version": "2.2.1",
4
4
  "type": "module",
5
5
  "description": "The runtime component library for Strata — design once, sync everywhere.",
6
6
  "homepage": "https://strata.charisol.io",
@@ -54,6 +54,7 @@
54
54
  "@rollup/plugin-babel": "^6.1.0",
55
55
  "@rollup/plugin-commonjs": "^28.0.9",
56
56
  "@rollup/plugin-node-resolve": "^16.0.3",
57
+ "@rollup/plugin-replace": "^6.0.3",
57
58
  "@rollup/plugin-typescript": "^12.3.0",
58
59
  "@types/node": "^25.0.0",
59
60
  "@types/react": "^19.2.2",