core-outline 1.1.23 → 1.1.25

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
@@ -12,17 +12,6 @@ npm install core-outline
12
12
  yarn add core-outline
13
13
  ```
14
14
 
15
- The response contains your `data_source_id`, `data_source_secret`, and `warehouse_id`. **The secret is shown once — store it securely.**
16
-
17
- ```json
18
- {
19
- "data_source_id": "3f4a1c2d-...",
20
- "data_source_secret": "aB3xK9mZqR...",
21
- "warehouse_id": "cad1acc0-...",
22
- "name": "My Website"
23
- }
24
- ```
25
-
26
15
  ## Usage
27
16
 
28
17
  Wrap your app's root component with `<CoreOutline>`. All tracking is automatic from that point — no additional configuration is needed.
@@ -79,3 +68,37 @@ flag_item_clicked('sku-123');
79
68
  // Track a purchase (price is optional, maps to value_num in ClickHouse)
80
69
  flag_item_purchased('sku-123', 49.99);
81
70
  ```
71
+
72
+ ## Data Pipeline
73
+
74
+ Events flow through the following pipeline:
75
+
76
+ ```
77
+ Browser → POST /api/ingest/events → RabbitMQ ({warehouse_id}.analytics.events)
78
+ → Consumer worker → ClickHouse atlas_analytics
79
+ ```
80
+
81
+ The ClickHouse tables populated are:
82
+
83
+ | Table | Populated by |
84
+ |-------|-------------|
85
+ | `fact_event` | All events except session start/end |
86
+ | `fact_session` | `session_start` and `session_end` events |
87
+ | `fact_pageview` | `pageview` events |
88
+ | `stg_events_sdk_event` | All events (raw staging) |
89
+ | `stg_rrweb_event` | Session recording chunks |
90
+
91
+ These tables are queried by the [atlas-analytics](https://analytics.atlas.coreoutline.com) API under `/metrics/saas/traffic`, `/metrics/saas/engagement`, and related endpoints.
92
+
93
+ ## Anonymous vs Session Identity
94
+
95
+ - **`anonymous_id`** — a UUID stored in `localStorage`; persists across browser sessions, used to stitch cross-session user journeys.
96
+ - **`session_id`** — a UUID stored in `sessionStorage`; reset on each new tab or browser close, matching the standard session definition.
97
+
98
+ ## Service URLs
99
+
100
+ | Service | URL |
101
+ |---------|-----|
102
+ | Hermes API (credential management) | `https://atlas-orchestrator.atlas.coreoutline.com` |
103
+ | Maia (data engineering / pipelines) | `https://data-engineering.atlas.coreoutline.com` |
104
+ | Atlas Analytics API | `https://analytics.atlas.coreoutline.com` |
package/dist/index.es.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import React, { useState, useRef, useEffect } from 'react';
2
3
  import { v4 } from 'uuid';
3
4
 
@@ -4411,25 +4412,57 @@ function getPagePath() {
4411
4412
 
4412
4413
  var INGEST_BASE = 'https://atlas-orchestrator.atlas.coreoutline.com';
4413
4414
  var FLUSH_INTERVAL_MS = 5000;
4415
+ // Refresh the token this many milliseconds before it actually expires.
4416
+ var TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000; // 5 minutes
4417
+
4414
4418
  var _token = null;
4415
4419
  var _warehouseId = null;
4416
4420
  var _organizationId = null;
4417
4421
  var _dataSourceId = null;
4422
+ var _dataSourceSecret = null;
4418
4423
  var _buffer = [];
4419
4424
  var _flushInterval = null;
4420
- function initIngest(_x, _x2, _x3) {
4421
- return _initIngest.apply(this, arguments);
4422
- }
4423
- function _initIngest() {
4424
- _initIngest = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee(warehouseId, dataSourceId, dataSourceSecret) {
4425
- var res, data;
4425
+ var _refreshTimeout = null;
4426
+ function _scheduleTokenRefresh(expiresInSeconds) {
4427
+ if (_refreshTimeout) {
4428
+ clearTimeout(_refreshTimeout);
4429
+ _refreshTimeout = null;
4430
+ }
4431
+ var refreshInMs = expiresInSeconds * 1000 - TOKEN_REFRESH_BUFFER_MS;
4432
+ if (refreshInMs <= 0) {
4433
+ // Token already near expiry — refresh immediately on next action.
4434
+ return;
4435
+ }
4436
+ _refreshTimeout = setTimeout(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
4437
+ var ok;
4426
4438
  return _regeneratorRuntime().wrap(function _callee$(_context) {
4427
4439
  while (1) switch (_context.prev = _context.next) {
4428
4440
  case 0:
4429
- _warehouseId = warehouseId;
4430
- _dataSourceId = dataSourceId;
4431
- _context.prev = 2;
4432
- _context.next = 5;
4441
+ _context.next = 2;
4442
+ return _doAuthorize(_warehouseId, _dataSourceId, _dataSourceSecret);
4443
+ case 2:
4444
+ ok = _context.sent;
4445
+ if (!ok) {
4446
+ console.warn('[CoreOutline] Proactive token refresh failed; will retry on next flush.');
4447
+ }
4448
+ case 4:
4449
+ case "end":
4450
+ return _context.stop();
4451
+ }
4452
+ }, _callee);
4453
+ })), refreshInMs);
4454
+ }
4455
+ function _doAuthorize(_x, _x2, _x3) {
4456
+ return _doAuthorize2.apply(this, arguments);
4457
+ }
4458
+ function _doAuthorize2() {
4459
+ _doAuthorize2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(warehouseId, dataSourceId, dataSourceSecret) {
4460
+ var res, data, expiresIn;
4461
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
4462
+ while (1) switch (_context2.prev = _context2.next) {
4463
+ case 0:
4464
+ _context2.prev = 0;
4465
+ _context2.next = 3;
4433
4466
  return fetch("".concat(INGEST_BASE, "/api/ingest/authorize"), {
4434
4467
  method: 'POST',
4435
4468
  headers: {
@@ -4441,35 +4474,85 @@ function _initIngest() {
4441
4474
  data_source_secret: dataSourceSecret
4442
4475
  })
4443
4476
  });
4444
- case 5:
4445
- res = _context.sent;
4477
+ case 3:
4478
+ res = _context2.sent;
4446
4479
  if (res.ok) {
4447
- _context.next = 9;
4480
+ _context2.next = 7;
4448
4481
  break;
4449
4482
  }
4450
4483
  console.error('[CoreOutline] Authorization failed:', res.status);
4451
- return _context.abrupt("return", false);
4452
- case 9:
4453
- _context.next = 11;
4484
+ return _context2.abrupt("return", false);
4485
+ case 7:
4486
+ _context2.next = 9;
4454
4487
  return res.json();
4455
- case 11:
4456
- data = _context.sent;
4488
+ case 9:
4489
+ data = _context2.sent;
4457
4490
  _token = data.access_token;
4458
4491
  _organizationId = data.organization_id;
4459
- return _context.abrupt("return", true);
4492
+ expiresIn = data.expires_in || 24 * 3600;
4493
+ _scheduleTokenRefresh(expiresIn);
4494
+ return _context2.abrupt("return", true);
4460
4495
  case 17:
4461
- _context.prev = 17;
4462
- _context.t0 = _context["catch"](2);
4463
- console.error('[CoreOutline] Authorization error:', _context.t0);
4464
- return _context.abrupt("return", false);
4496
+ _context2.prev = 17;
4497
+ _context2.t0 = _context2["catch"](0);
4498
+ console.error('[CoreOutline] Authorization error:', _context2.t0);
4499
+ return _context2.abrupt("return", false);
4465
4500
  case 21:
4466
4501
  case "end":
4467
- return _context.stop();
4502
+ return _context2.stop();
4503
+ }
4504
+ }, _callee2, null, [[0, 17]]);
4505
+ }));
4506
+ return _doAuthorize2.apply(this, arguments);
4507
+ }
4508
+ function initIngest(_x4, _x5, _x6) {
4509
+ return _initIngest.apply(this, arguments);
4510
+ }
4511
+ function _initIngest() {
4512
+ _initIngest = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee3(warehouseId, dataSourceId, dataSourceSecret) {
4513
+ return _regeneratorRuntime().wrap(function _callee3$(_context3) {
4514
+ while (1) switch (_context3.prev = _context3.next) {
4515
+ case 0:
4516
+ _warehouseId = warehouseId;
4517
+ _dataSourceId = dataSourceId;
4518
+ _dataSourceSecret = dataSourceSecret;
4519
+ return _context3.abrupt("return", _doAuthorize(warehouseId, dataSourceId, dataSourceSecret));
4520
+ case 4:
4521
+ case "end":
4522
+ return _context3.stop();
4468
4523
  }
4469
- }, _callee, null, [[2, 17]]);
4524
+ }, _callee3);
4470
4525
  }));
4471
4526
  return _initIngest.apply(this, arguments);
4472
4527
  }
4528
+ function _ensureFreshToken() {
4529
+ return _ensureFreshToken2.apply(this, arguments);
4530
+ }
4531
+ function _ensureFreshToken2() {
4532
+ _ensureFreshToken2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee4() {
4533
+ return _regeneratorRuntime().wrap(function _callee4$(_context4) {
4534
+ while (1) switch (_context4.prev = _context4.next) {
4535
+ case 0:
4536
+ if (!_token) {
4537
+ _context4.next = 2;
4538
+ break;
4539
+ }
4540
+ return _context4.abrupt("return");
4541
+ case 2:
4542
+ if (!(_warehouseId && _dataSourceId && _dataSourceSecret)) {
4543
+ _context4.next = 5;
4544
+ break;
4545
+ }
4546
+ _context4.next = 5;
4547
+ return _doAuthorize(_warehouseId, _dataSourceId, _dataSourceSecret);
4548
+ case 5:
4549
+ case "end":
4550
+ return _context4.stop();
4551
+ }
4552
+ }, _callee4);
4553
+ }));
4554
+ return _ensureFreshToken2.apply(this, arguments);
4555
+ }
4473
4556
  function trackEvent(eventType, payload) {
4474
4557
  if (!_token) return;
4475
4558
  var event = _objectSpread2({
@@ -4483,49 +4566,75 @@ function trackEvent(eventType, payload) {
4483
4566
  }
4484
4567
  }
4485
4568
  function flush() {
4486
- if (!_token || _buffer.length === 0) return;
4487
- var events = _buffer.splice(0, _buffer.length);
4488
- var body = JSON.stringify({
4489
- warehouse_id: _warehouseId,
4490
- organization_id: _organizationId,
4491
- events: events
4492
- });
4493
- var headers = {
4494
- 'Content-Type': 'application/json',
4495
- Authorization: "Bearer ".concat(_token)
4496
- };
4497
- if (navigator.sendBeacon) {
4498
- var blob = new Blob([body], {
4499
- type: 'application/json'
4500
- });
4501
- navigator.sendBeacon("".concat(INGEST_BASE, "/api/ingest/events"), blob);
4502
- } else {
4503
- fetch("".concat(INGEST_BASE, "/api/ingest/events"), {
4504
- method: 'POST',
4505
- headers: headers,
4506
- body: body,
4507
- keepalive: true
4508
- })["catch"](function (err) {
4509
- return console.error('[CoreOutline] Flush error:', err);
4510
- });
4511
- }
4569
+ return _flush.apply(this, arguments);
4570
+ }
4571
+ function _flush() {
4572
+ _flush = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee5() {
4573
+ var events, body, headers, blob;
4574
+ return _regeneratorRuntime().wrap(function _callee5$(_context5) {
4575
+ while (1) switch (_context5.prev = _context5.next) {
4576
+ case 0:
4577
+ _context5.next = 2;
4578
+ return _ensureFreshToken();
4579
+ case 2:
4580
+ if (!(!_token || _buffer.length === 0)) {
4581
+ _context5.next = 4;
4582
+ break;
4583
+ }
4584
+ return _context5.abrupt("return");
4585
+ case 4:
4586
+ events = _buffer.splice(0, _buffer.length);
4587
+ body = JSON.stringify({
4588
+ warehouse_id: _warehouseId,
4589
+ organization_id: _organizationId,
4590
+ events: events
4591
+ });
4592
+ headers = {
4593
+ 'Content-Type': 'application/json',
4594
+ Authorization: "Bearer ".concat(_token)
4595
+ };
4596
+ if (navigator.sendBeacon) {
4597
+ blob = new Blob([body], {
4598
+ type: 'application/json'
4599
+ });
4600
+ navigator.sendBeacon("".concat(INGEST_BASE, "/api/ingest/events"), blob);
4601
+ } else {
4602
+ fetch("".concat(INGEST_BASE, "/api/ingest/events"), {
4603
+ method: 'POST',
4604
+ headers: headers,
4605
+ body: body,
4606
+ keepalive: true
4607
+ })["catch"](function (err) {
4608
+ return console.error('[CoreOutline] Flush error:', err);
4609
+ });
4610
+ }
4611
+ case 8:
4612
+ case "end":
4613
+ return _context5.stop();
4614
+ }
4615
+ }, _callee5);
4616
+ }));
4617
+ return _flush.apply(this, arguments);
4512
4618
  }
4513
- function sendRrwebBatch(_x4, _x5) {
4619
+ function sendRrwebBatch(_x7, _x8) {
4514
4620
  return _sendRrwebBatch.apply(this, arguments);
4515
4621
  }
4516
4622
  function _sendRrwebBatch() {
4517
- _sendRrwebBatch = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(chunks, sessionId) {
4518
- return _regeneratorRuntime().wrap(function _callee2$(_context2) {
4519
- while (1) switch (_context2.prev = _context2.next) {
4623
+ _sendRrwebBatch = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee6(chunks, sessionId) {
4624
+ return _regeneratorRuntime().wrap(function _callee6$(_context6) {
4625
+ while (1) switch (_context6.prev = _context6.next) {
4520
4626
  case 0:
4627
+ _context6.next = 2;
4628
+ return _ensureFreshToken();
4629
+ case 2:
4521
4630
  if (!(!_token || !chunks || chunks.length === 0)) {
4522
- _context2.next = 2;
4631
+ _context6.next = 4;
4523
4632
  break;
4524
4633
  }
4525
- return _context2.abrupt("return");
4526
- case 2:
4527
- _context2.prev = 2;
4528
- _context2.next = 5;
4634
+ return _context6.abrupt("return");
4635
+ case 4:
4636
+ _context6.prev = 4;
4637
+ _context6.next = 7;
4529
4638
  return fetch("".concat(INGEST_BASE, "/api/ingest/rrweb"), {
4530
4639
  method: 'POST',
4531
4640
  headers: {
@@ -4541,18 +4650,18 @@ function _sendRrwebBatch() {
4541
4650
  }),
4542
4651
  keepalive: true
4543
4652
  });
4544
- case 5:
4545
- _context2.next = 10;
4546
- break;
4547
4653
  case 7:
4548
- _context2.prev = 7;
4549
- _context2.t0 = _context2["catch"](2);
4550
- console.error('[CoreOutline] rrweb upload error:', _context2.t0);
4551
- case 10:
4654
+ _context6.next = 12;
4655
+ break;
4656
+ case 9:
4657
+ _context6.prev = 9;
4658
+ _context6.t0 = _context6["catch"](4);
4659
+ console.error('[CoreOutline] rrweb upload error:', _context6.t0);
4660
+ case 12:
4552
4661
  case "end":
4553
- return _context2.stop();
4662
+ return _context6.stop();
4554
4663
  }
4555
- }, _callee2, null, [[2, 7]]);
4664
+ }, _callee6, null, [[4, 9]]);
4556
4665
  }));
4557
4666
  return _sendRrwebBatch.apply(this, arguments);
4558
4667
  }
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  'use strict';
2
3
 
3
4
  Object.defineProperty(exports, '__esModule', { value: true });
@@ -4419,25 +4420,57 @@ function getPagePath() {
4419
4420
 
4420
4421
  var INGEST_BASE = 'https://atlas-orchestrator.atlas.coreoutline.com';
4421
4422
  var FLUSH_INTERVAL_MS = 5000;
4423
+ // Refresh the token this many milliseconds before it actually expires.
4424
+ var TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000; // 5 minutes
4425
+
4422
4426
  var _token = null;
4423
4427
  var _warehouseId = null;
4424
4428
  var _organizationId = null;
4425
4429
  var _dataSourceId = null;
4430
+ var _dataSourceSecret = null;
4426
4431
  var _buffer = [];
4427
4432
  var _flushInterval = null;
4428
- function initIngest(_x, _x2, _x3) {
4429
- return _initIngest.apply(this, arguments);
4430
- }
4431
- function _initIngest() {
4432
- _initIngest = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee(warehouseId, dataSourceId, dataSourceSecret) {
4433
- var res, data;
4433
+ var _refreshTimeout = null;
4434
+ function _scheduleTokenRefresh(expiresInSeconds) {
4435
+ if (_refreshTimeout) {
4436
+ clearTimeout(_refreshTimeout);
4437
+ _refreshTimeout = null;
4438
+ }
4439
+ var refreshInMs = expiresInSeconds * 1000 - TOKEN_REFRESH_BUFFER_MS;
4440
+ if (refreshInMs <= 0) {
4441
+ // Token already near expiry — refresh immediately on next action.
4442
+ return;
4443
+ }
4444
+ _refreshTimeout = setTimeout(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
4445
+ var ok;
4434
4446
  return _regeneratorRuntime().wrap(function _callee$(_context) {
4435
4447
  while (1) switch (_context.prev = _context.next) {
4436
4448
  case 0:
4437
- _warehouseId = warehouseId;
4438
- _dataSourceId = dataSourceId;
4439
- _context.prev = 2;
4440
- _context.next = 5;
4449
+ _context.next = 2;
4450
+ return _doAuthorize(_warehouseId, _dataSourceId, _dataSourceSecret);
4451
+ case 2:
4452
+ ok = _context.sent;
4453
+ if (!ok) {
4454
+ console.warn('[CoreOutline] Proactive token refresh failed; will retry on next flush.');
4455
+ }
4456
+ case 4:
4457
+ case "end":
4458
+ return _context.stop();
4459
+ }
4460
+ }, _callee);
4461
+ })), refreshInMs);
4462
+ }
4463
+ function _doAuthorize(_x, _x2, _x3) {
4464
+ return _doAuthorize2.apply(this, arguments);
4465
+ }
4466
+ function _doAuthorize2() {
4467
+ _doAuthorize2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(warehouseId, dataSourceId, dataSourceSecret) {
4468
+ var res, data, expiresIn;
4469
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
4470
+ while (1) switch (_context2.prev = _context2.next) {
4471
+ case 0:
4472
+ _context2.prev = 0;
4473
+ _context2.next = 3;
4441
4474
  return fetch("".concat(INGEST_BASE, "/api/ingest/authorize"), {
4442
4475
  method: 'POST',
4443
4476
  headers: {
@@ -4449,35 +4482,85 @@ function _initIngest() {
4449
4482
  data_source_secret: dataSourceSecret
4450
4483
  })
4451
4484
  });
4452
- case 5:
4453
- res = _context.sent;
4485
+ case 3:
4486
+ res = _context2.sent;
4454
4487
  if (res.ok) {
4455
- _context.next = 9;
4488
+ _context2.next = 7;
4456
4489
  break;
4457
4490
  }
4458
4491
  console.error('[CoreOutline] Authorization failed:', res.status);
4459
- return _context.abrupt("return", false);
4460
- case 9:
4461
- _context.next = 11;
4492
+ return _context2.abrupt("return", false);
4493
+ case 7:
4494
+ _context2.next = 9;
4462
4495
  return res.json();
4463
- case 11:
4464
- data = _context.sent;
4496
+ case 9:
4497
+ data = _context2.sent;
4465
4498
  _token = data.access_token;
4466
4499
  _organizationId = data.organization_id;
4467
- return _context.abrupt("return", true);
4500
+ expiresIn = data.expires_in || 24 * 3600;
4501
+ _scheduleTokenRefresh(expiresIn);
4502
+ return _context2.abrupt("return", true);
4468
4503
  case 17:
4469
- _context.prev = 17;
4470
- _context.t0 = _context["catch"](2);
4471
- console.error('[CoreOutline] Authorization error:', _context.t0);
4472
- return _context.abrupt("return", false);
4504
+ _context2.prev = 17;
4505
+ _context2.t0 = _context2["catch"](0);
4506
+ console.error('[CoreOutline] Authorization error:', _context2.t0);
4507
+ return _context2.abrupt("return", false);
4473
4508
  case 21:
4474
4509
  case "end":
4475
- return _context.stop();
4510
+ return _context2.stop();
4511
+ }
4512
+ }, _callee2, null, [[0, 17]]);
4513
+ }));
4514
+ return _doAuthorize2.apply(this, arguments);
4515
+ }
4516
+ function initIngest(_x4, _x5, _x6) {
4517
+ return _initIngest.apply(this, arguments);
4518
+ }
4519
+ function _initIngest() {
4520
+ _initIngest = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee3(warehouseId, dataSourceId, dataSourceSecret) {
4521
+ return _regeneratorRuntime().wrap(function _callee3$(_context3) {
4522
+ while (1) switch (_context3.prev = _context3.next) {
4523
+ case 0:
4524
+ _warehouseId = warehouseId;
4525
+ _dataSourceId = dataSourceId;
4526
+ _dataSourceSecret = dataSourceSecret;
4527
+ return _context3.abrupt("return", _doAuthorize(warehouseId, dataSourceId, dataSourceSecret));
4528
+ case 4:
4529
+ case "end":
4530
+ return _context3.stop();
4476
4531
  }
4477
- }, _callee, null, [[2, 17]]);
4532
+ }, _callee3);
4478
4533
  }));
4479
4534
  return _initIngest.apply(this, arguments);
4480
4535
  }
4536
+ function _ensureFreshToken() {
4537
+ return _ensureFreshToken2.apply(this, arguments);
4538
+ }
4539
+ function _ensureFreshToken2() {
4540
+ _ensureFreshToken2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee4() {
4541
+ return _regeneratorRuntime().wrap(function _callee4$(_context4) {
4542
+ while (1) switch (_context4.prev = _context4.next) {
4543
+ case 0:
4544
+ if (!_token) {
4545
+ _context4.next = 2;
4546
+ break;
4547
+ }
4548
+ return _context4.abrupt("return");
4549
+ case 2:
4550
+ if (!(_warehouseId && _dataSourceId && _dataSourceSecret)) {
4551
+ _context4.next = 5;
4552
+ break;
4553
+ }
4554
+ _context4.next = 5;
4555
+ return _doAuthorize(_warehouseId, _dataSourceId, _dataSourceSecret);
4556
+ case 5:
4557
+ case "end":
4558
+ return _context4.stop();
4559
+ }
4560
+ }, _callee4);
4561
+ }));
4562
+ return _ensureFreshToken2.apply(this, arguments);
4563
+ }
4481
4564
  function trackEvent(eventType, payload) {
4482
4565
  if (!_token) return;
4483
4566
  var event = _objectSpread2({
@@ -4491,49 +4574,75 @@ function trackEvent(eventType, payload) {
4491
4574
  }
4492
4575
  }
4493
4576
  function flush() {
4494
- if (!_token || _buffer.length === 0) return;
4495
- var events = _buffer.splice(0, _buffer.length);
4496
- var body = JSON.stringify({
4497
- warehouse_id: _warehouseId,
4498
- organization_id: _organizationId,
4499
- events: events
4500
- });
4501
- var headers = {
4502
- 'Content-Type': 'application/json',
4503
- Authorization: "Bearer ".concat(_token)
4504
- };
4505
- if (navigator.sendBeacon) {
4506
- var blob = new Blob([body], {
4507
- type: 'application/json'
4508
- });
4509
- navigator.sendBeacon("".concat(INGEST_BASE, "/api/ingest/events"), blob);
4510
- } else {
4511
- fetch("".concat(INGEST_BASE, "/api/ingest/events"), {
4512
- method: 'POST',
4513
- headers: headers,
4514
- body: body,
4515
- keepalive: true
4516
- })["catch"](function (err) {
4517
- return console.error('[CoreOutline] Flush error:', err);
4518
- });
4519
- }
4577
+ return _flush.apply(this, arguments);
4578
+ }
4579
+ function _flush() {
4580
+ _flush = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee5() {
4581
+ var events, body, headers, blob;
4582
+ return _regeneratorRuntime().wrap(function _callee5$(_context5) {
4583
+ while (1) switch (_context5.prev = _context5.next) {
4584
+ case 0:
4585
+ _context5.next = 2;
4586
+ return _ensureFreshToken();
4587
+ case 2:
4588
+ if (!(!_token || _buffer.length === 0)) {
4589
+ _context5.next = 4;
4590
+ break;
4591
+ }
4592
+ return _context5.abrupt("return");
4593
+ case 4:
4594
+ events = _buffer.splice(0, _buffer.length);
4595
+ body = JSON.stringify({
4596
+ warehouse_id: _warehouseId,
4597
+ organization_id: _organizationId,
4598
+ events: events
4599
+ });
4600
+ headers = {
4601
+ 'Content-Type': 'application/json',
4602
+ Authorization: "Bearer ".concat(_token)
4603
+ };
4604
+ if (navigator.sendBeacon) {
4605
+ blob = new Blob([body], {
4606
+ type: 'application/json'
4607
+ });
4608
+ navigator.sendBeacon("".concat(INGEST_BASE, "/api/ingest/events"), blob);
4609
+ } else {
4610
+ fetch("".concat(INGEST_BASE, "/api/ingest/events"), {
4611
+ method: 'POST',
4612
+ headers: headers,
4613
+ body: body,
4614
+ keepalive: true
4615
+ })["catch"](function (err) {
4616
+ return console.error('[CoreOutline] Flush error:', err);
4617
+ });
4618
+ }
4619
+ case 8:
4620
+ case "end":
4621
+ return _context5.stop();
4622
+ }
4623
+ }, _callee5);
4624
+ }));
4625
+ return _flush.apply(this, arguments);
4520
4626
  }
4521
- function sendRrwebBatch(_x4, _x5) {
4627
+ function sendRrwebBatch(_x7, _x8) {
4522
4628
  return _sendRrwebBatch.apply(this, arguments);
4523
4629
  }
4524
4630
  function _sendRrwebBatch() {
4525
- _sendRrwebBatch = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(chunks, sessionId) {
4526
- return _regeneratorRuntime().wrap(function _callee2$(_context2) {
4527
- while (1) switch (_context2.prev = _context2.next) {
4631
+ _sendRrwebBatch = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee6(chunks, sessionId) {
4632
+ return _regeneratorRuntime().wrap(function _callee6$(_context6) {
4633
+ while (1) switch (_context6.prev = _context6.next) {
4528
4634
  case 0:
4635
+ _context6.next = 2;
4636
+ return _ensureFreshToken();
4637
+ case 2:
4529
4638
  if (!(!_token || !chunks || chunks.length === 0)) {
4530
- _context2.next = 2;
4639
+ _context6.next = 4;
4531
4640
  break;
4532
4641
  }
4533
- return _context2.abrupt("return");
4534
- case 2:
4535
- _context2.prev = 2;
4536
- _context2.next = 5;
4642
+ return _context6.abrupt("return");
4643
+ case 4:
4644
+ _context6.prev = 4;
4645
+ _context6.next = 7;
4537
4646
  return fetch("".concat(INGEST_BASE, "/api/ingest/rrweb"), {
4538
4647
  method: 'POST',
4539
4648
  headers: {
@@ -4549,18 +4658,18 @@ function _sendRrwebBatch() {
4549
4658
  }),
4550
4659
  keepalive: true
4551
4660
  });
4552
- case 5:
4553
- _context2.next = 10;
4554
- break;
4555
4661
  case 7:
4556
- _context2.prev = 7;
4557
- _context2.t0 = _context2["catch"](2);
4558
- console.error('[CoreOutline] rrweb upload error:', _context2.t0);
4559
- case 10:
4662
+ _context6.next = 12;
4663
+ break;
4664
+ case 9:
4665
+ _context6.prev = 9;
4666
+ _context6.t0 = _context6["catch"](4);
4667
+ console.error('[CoreOutline] rrweb upload error:', _context6.t0);
4668
+ case 12:
4560
4669
  case "end":
4561
- return _context2.stop();
4670
+ return _context6.stop();
4562
4671
  }
4563
- }, _callee2, null, [[2, 7]]);
4672
+ }, _callee6, null, [[4, 9]]);
4564
4673
  }));
4565
4674
  return _sendRrwebBatch.apply(this, arguments);
4566
4675
  }
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "core-outline",
3
- "version": "1.1.23",
3
+ "version": "1.1.25",
4
4
  "description": "A React component for Core&Outline",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.es.js",
7
+ "files": [
8
+ "dist"
9
+ ],
7
10
  "scripts": {
8
11
  "test": "echo \"Error: no test specified\" && exit 1",
9
12
  "storybook": "storybook dev -p 6006",
package/.babelrc DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "presets": ["@babel/preset-env", "@babel/preset-react"]
3
- }
package/.eslintignore DELETED
@@ -1,2 +0,0 @@
1
- node_modules
2
- dist
package/.eslintrc.json DELETED
@@ -1,23 +0,0 @@
1
- {
2
- "env": {
3
- "browser": true,
4
- "es2021": true
5
- },
6
- "extends": ["eslint:recommended", "plugin:react/recommended", "prettier"],
7
- "parserOptions": {
8
- "ecmaFeatures": {
9
- "jsx": true
10
- },
11
- "ecmaVersion": 12,
12
- "sourceType": "module"
13
- },
14
- "plugins": ["react"],
15
- "rules": {
16
- "react/react-in-jsx-scope": "off"
17
- },
18
- "settings": {
19
- "react": {
20
- "version": "detect"
21
- }
22
- }
23
- }
package/.prettierignore DELETED
@@ -1,2 +0,0 @@
1
- node_modules
2
- dist
package/.prettierrc DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "singleQuote": true,
3
- "trailingComma": "es5",
4
- "printWidth": 80,
5
- "tabWidth": 2,
6
- "semi": true
7
- }
@@ -1,16 +0,0 @@
1
- /** @type { import('@storybook/react-webpack5').StorybookConfig } */
2
- const config = {
3
- stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
4
- addons: [
5
- '@storybook/addon-webpack5-compiler-swc',
6
- '@storybook/addon-onboarding',
7
- '@storybook/addon-essentials',
8
- '@chromatic-com/storybook',
9
- '@storybook/addon-interactions',
10
- ],
11
- framework: {
12
- name: '@storybook/react-webpack5',
13
- options: {},
14
- },
15
- };
16
- export default config;
@@ -1,13 +0,0 @@
1
- /** @type { import('@storybook/react').Preview } */
2
- const preview = {
3
- parameters: {
4
- controls: {
5
- matchers: {
6
- color: /(background|color)$/i,
7
- date: /Date$/i,
8
- },
9
- },
10
- },
11
- };
12
-
13
- export default preview;
@@ -1,44 +0,0 @@
1
- # Tracking Pipeline — react-component Changes
2
- Date: 2026-06-27
3
-
4
- ## Summary
5
- Replaced Socket.IO with direct HTTP calls to the Hermes ingest API. Added warehouse_id-based routing, improved event schema, and removed dev artifacts.
6
-
7
- ## Breaking Changes
8
- - `<CoreOutline>` now requires a `warehouse_id` prop (in addition to existing `data_source_id` + `data_source_secret`)
9
- - The `data_source_secret` is now validated against Hermes's Vault store; the old `api.coreoutline.com/data-source/authorize` endpoint is no longer used
10
-
11
- ## New Files
12
- - `src/components/CoreOutline/helpers.js` — browser utilities: `getAnonymousId`, `getSessionId`, `getUtmParams`, `detectDeviceType`, `detectOS`, `getBrowserName`, `getPagePath`
13
- - `src/components/CoreOutline/ingest.js` — HTTP ingest client: `initIngest`, `trackEvent`, `flush`, `sendRrwebBatch`, `stopFlushInterval`
14
-
15
- ## Modified Files
16
-
17
- ### `src/components/CoreOutline/CoreOutline.js`
18
- - Added `warehouse_id` prop
19
- - Replaced all `socket.send()` calls with `trackEvent()` from `ingest.js`
20
- - Events now include: UTM params, device type, OS, page_url, page_path, anonymous_id (persistent cross-session), session_id (per-tab via sessionStorage)
21
- - Removed `getLocation()` / geolocation (replaced by server-side IP geolocation)
22
- - Removed dev-only `<button>Save Events Locally</button>` from rendered output
23
- - `flag_item_clicked(item_id)` and `flag_item_purchased(item_id, price)` rewritten to use `trackEvent()`; `flag_item_purchased` now accepts an optional `price` parameter mapped to `value_num`
24
- - Events auto-flush every 5 seconds and on page unload/visibility change
25
- - rrweb recordings uploaded via `sendRrwebBatch` at session end
26
-
27
- ### `package.json`
28
- - Removed: `socket.io-client` (~100KB bundle savings)
29
- - Added: `uuid` as explicit dependency (was transitive via rrweb)
30
-
31
- ## Event Types Emitted
32
- | event_type | event_category | Destination table |
33
- |---|---|---|
34
- | `session_start` | `session` | `fact_session` |
35
- | `session_end` | `session` | `fact_session` (update via ReplacingMergeTree) |
36
- | `pageview` | `navigation` | `fact_pageview` + `fact_event` |
37
- | `item_clicked` | `interaction` | `fact_event` |
38
- | `product_clicked` | `commerce` | `fact_event` |
39
- | `product_purchased` | `commerce` | `fact_event` (value_num = price) |
40
-
41
- ## Data Flow
42
- react-component → `POST https://api.coreoutline.com/api/ingest/authorize` (get JWT)
43
- → `POST https://api.coreoutline.com/api/ingest/events` (batch, every 5s + on unload)
44
- → `POST https://api.coreoutline.com/api/ingest/rrweb` (session end)
package/rollup.config.js DELETED
@@ -1,35 +0,0 @@
1
- import babel from 'rollup-plugin-babel';
2
- import resolve from '@rollup/plugin-node-resolve';
3
- import external from 'rollup-plugin-peer-deps-external';
4
- import { terser } from 'rollup-plugin-terser';
5
- import postcss from 'rollup-plugin-postcss';
6
-
7
- export default [
8
- {
9
- input: './src/components/CoreOutline/CoreOutline.js',
10
- output: [
11
- {
12
- file: 'dist/index.js',
13
- format: 'cjs',
14
- },
15
- {
16
- file: 'dist/index.es.js',
17
- format: 'es',
18
- exports: 'named',
19
- },
20
- ],
21
- plugins: [
22
- postcss({
23
- plugins: [],
24
- minimize: true,
25
- }),
26
- babel({
27
- exclude: 'node_modules/**',
28
- presets: ['@babel/preset-react'],
29
- }),
30
- external(),
31
- resolve(),
32
- // terser(),
33
- ],
34
- },
35
- ];
@@ -1,223 +0,0 @@
1
- import React, { useEffect, useRef, useState } from 'react';
2
- import { record } from 'rrweb';
3
- import {
4
- EventType,
5
- IncrementalSource,
6
- MouseInteractions,
7
- } from 'rrweb';
8
- import {
9
- getAnonymousId,
10
- getSessionId,
11
- getUtmParams,
12
- detectDeviceType,
13
- detectOS,
14
- getBrowserName,
15
- getPagePath,
16
- } from './helpers';
17
- import {
18
- initIngest,
19
- trackEvent,
20
- flush,
21
- sendRrwebBatch,
22
- stopFlushInterval,
23
- } from './ingest';
24
-
25
- const CoreOutline = ({ children, data_source_id, data_source_secret, warehouse_id }) => {
26
- const [replayEvents, setReplayEvents] = useState([]);
27
- const recorderActive = useRef(false);
28
- const [currentPage, setCurrentPage] = useState(window.location.href);
29
- const pageviewCountRef = useRef(0);
30
- const eventCountRef = useRef(0);
31
- const sessionStartRef = useRef(Date.now());
32
-
33
- const sessionId = getSessionId();
34
- const anonymousId = getAnonymousId();
35
-
36
- useEffect(() => {
37
- const utmParams = getUtmParams();
38
- const deviceType = detectDeviceType();
39
- const os = detectOS();
40
- const browser = getBrowserName();
41
- const referrer = document.referrer || 'direct';
42
-
43
- const basePayload = {
44
- session_id: sessionId,
45
- anonymous_id: anonymousId,
46
- account_id: data_source_id,
47
- platform: 'web',
48
- device_type: deviceType,
49
- os,
50
- browser,
51
- referrer,
52
- page_url: window.location.href,
53
- page_path: getPagePath(),
54
- ...utmParams,
55
- };
56
-
57
- let initialized = false;
58
-
59
- const init = async () => {
60
- initialized = await initIngest(warehouse_id, data_source_id, data_source_secret);
61
- if (!initialized) return;
62
-
63
- pageviewCountRef.current += 1;
64
- trackEvent('pageview', { ...basePayload, event_name: 'pageview', event_category: 'navigation' });
65
- trackEvent('session_start', {
66
- ...basePayload,
67
- event_name: 'session_start',
68
- event_category: 'session',
69
- session_start_ts: new Date().toISOString(),
70
- });
71
- };
72
-
73
- init();
74
-
75
- const handleSessionEnd = () => {
76
- if (!initialized) return;
77
- const durationMs = Date.now() - sessionStartRef.current;
78
- trackEvent('session_end', {
79
- ...basePayload,
80
- event_name: 'session_end',
81
- event_category: 'session',
82
- session_end_ts: new Date().toISOString(),
83
- pageviews: pageviewCountRef.current,
84
- events_count: eventCountRef.current,
85
- duration_ms: durationMs,
86
- });
87
- flush();
88
- stopFlushInterval();
89
- sendRrwebBatch(replayEvents, sessionId);
90
- };
91
-
92
- const handleVisibilityChange = () => {
93
- if (document.visibilityState === 'hidden') handleSessionEnd();
94
- };
95
-
96
- window.addEventListener('beforeunload', handleSessionEnd);
97
- document.addEventListener('visibilitychange', handleVisibilityChange);
98
-
99
- return () => {
100
- handleSessionEnd();
101
- window.removeEventListener('beforeunload', handleSessionEnd);
102
- document.removeEventListener('visibilitychange', handleVisibilityChange);
103
- };
104
- }, []);
105
-
106
- useEffect(() => {
107
- const utmParams = getUtmParams();
108
-
109
- const handleNavigation = () => {
110
- setCurrentPage(window.location.href);
111
- pageviewCountRef.current += 1;
112
- trackEvent('pageview', {
113
- session_id: sessionId,
114
- anonymous_id: anonymousId,
115
- account_id: data_source_id,
116
- platform: 'web',
117
- page_url: window.location.href,
118
- page_path: getPagePath(),
119
- referrer: document.referrer || 'direct',
120
- event_name: 'pageview',
121
- event_category: 'navigation',
122
- ...utmParams,
123
- });
124
- };
125
-
126
- window.addEventListener('popstate', handleNavigation);
127
- const originalPushState = window.history.pushState;
128
- window.history.pushState = function (...args) {
129
- originalPushState.apply(this, args);
130
- handleNavigation();
131
- };
132
-
133
- return () => {
134
- window.removeEventListener('popstate', handleNavigation);
135
- window.history.pushState = originalPushState;
136
- };
137
- }, [currentPage]);
138
-
139
- useEffect(() => {
140
- let stopFn;
141
- if (!recorderActive.current) {
142
- stopFn = record({
143
- emit(event) {
144
- setReplayEvents((prev) => [...prev, event]);
145
- },
146
- maskAllInputs: false,
147
- maskTextSelector: null,
148
- });
149
- recorderActive.current = true;
150
- }
151
-
152
- return () => {
153
- if (stopFn) {
154
- stopFn();
155
- recorderActive.current = false;
156
- }
157
- };
158
- }, []);
159
-
160
- useEffect(() => {
161
- const handleClick = (event) => {
162
- const clickedElement = document.elementFromPoint(event.clientX, event.clientY);
163
- const metadata = clickedElement
164
- ? {
165
- label: clickedElement.getAttribute('data-label') || clickedElement.innerText || null,
166
- value: clickedElement.getAttribute('value') || clickedElement.innerText || null,
167
- id: clickedElement.getAttribute('id') || null,
168
- name: clickedElement.getAttribute('name') || null,
169
- class: clickedElement.getAttribute('class') || null,
170
- tag: clickedElement.tagName,
171
- inner_text: event.target.innerText || null,
172
- }
173
- : {};
174
-
175
- eventCountRef.current += 1;
176
- trackEvent('item_clicked', {
177
- session_id: sessionId,
178
- anonymous_id: anonymousId,
179
- account_id: data_source_id,
180
- platform: 'web',
181
- page_url: window.location.href,
182
- page_path: getPagePath(),
183
- event_name: 'item_clicked',
184
- event_category: 'interaction',
185
- properties_json: JSON.stringify(metadata),
186
- });
187
- };
188
-
189
- document.addEventListener('click', handleClick);
190
- return () => document.removeEventListener('click', handleClick);
191
- }, []);
192
-
193
- return <>{children}</>;
194
- };
195
-
196
- export const flag_item_clicked = (item_id) => {
197
- const sessionId = sessionStorage.getItem('co_session_id') || '';
198
- const anonymousId = localStorage.getItem('co_anon_id') || '';
199
- trackEvent('product_clicked', {
200
- session_id: sessionId,
201
- anonymous_id: anonymousId,
202
- event_name: 'product_clicked',
203
- event_category: 'commerce',
204
- feature_key: item_id,
205
- });
206
- return { status: 'success', message: 'Item click flagged successfully.' };
207
- };
208
-
209
- export const flag_item_purchased = (item_id, price = null) => {
210
- const sessionId = sessionStorage.getItem('co_session_id') || '';
211
- const anonymousId = localStorage.getItem('co_anon_id') || '';
212
- trackEvent('product_purchased', {
213
- session_id: sessionId,
214
- anonymous_id: anonymousId,
215
- event_name: 'product_purchased',
216
- event_category: 'commerce',
217
- feature_key: item_id,
218
- value_num: price != null ? Number(price) : null,
219
- });
220
- return { status: 'success', message: 'Item purchase flagged successfully.' };
221
- };
222
-
223
- export default CoreOutline;
@@ -1,26 +0,0 @@
1
- import { useEffect, useRef } from 'react';
2
- import { record } from 'rrweb';
3
-
4
- const useRRWebRecorder = () => {
5
- const eventsRef = useRef([]);
6
-
7
- useEffect(() => {
8
- const stopRecording = record({
9
- emit(event) {
10
- if (event) {
11
- eventsRef.current.push(event);
12
- }
13
- },
14
- });
15
-
16
- return () => {
17
- if (stopRecording) {
18
- stopRecording();
19
- }
20
- };
21
- }, []);
22
-
23
- return eventsRef;
24
- };
25
-
26
- export default useRRWebRecorder;
@@ -1,66 +0,0 @@
1
- import { v4 as uuidv4 } from 'uuid';
2
-
3
- const ANON_ID_KEY = 'co_anon_id';
4
- const SESSION_ID_KEY = 'co_session_id';
5
-
6
- export function getAnonymousId() {
7
- let id = localStorage.getItem(ANON_ID_KEY);
8
- if (!id) {
9
- id = uuidv4();
10
- localStorage.setItem(ANON_ID_KEY, id);
11
- }
12
- return id;
13
- }
14
-
15
- export function getSessionId() {
16
- let id = sessionStorage.getItem(SESSION_ID_KEY);
17
- if (!id) {
18
- id = uuidv4();
19
- sessionStorage.setItem(SESSION_ID_KEY, id);
20
- }
21
- return id;
22
- }
23
-
24
- export function getUtmParams() {
25
- const params = new URLSearchParams(window.location.search);
26
- return {
27
- utm_source: params.get('utm_source') || undefined,
28
- utm_medium: params.get('utm_medium') || undefined,
29
- utm_campaign: params.get('utm_campaign') || undefined,
30
- utm_term: params.get('utm_term') || undefined,
31
- utm_content: params.get('utm_content') || undefined,
32
- };
33
- }
34
-
35
- export function detectDeviceType() {
36
- const ua = navigator.userAgent;
37
- if (/Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua)) {
38
- if (/iPad|Tablet/i.test(ua)) return 'tablet';
39
- return 'mobile';
40
- }
41
- return 'desktop';
42
- }
43
-
44
- export function detectOS() {
45
- const ua = navigator.userAgent;
46
- if (/iPhone|iPad|iPod/i.test(ua)) return 'iOS';
47
- if (/Android/i.test(ua)) return 'Android';
48
- if (/Win/i.test(ua)) return 'Windows';
49
- if (/Mac/i.test(ua)) return 'macOS';
50
- if (/Linux/i.test(ua)) return 'Linux';
51
- return 'Other';
52
- }
53
-
54
- export function getBrowserName() {
55
- const ua = navigator.userAgent;
56
- if (ua.indexOf('Firefox') > -1) return 'Firefox';
57
- if (ua.indexOf('Opera') > -1 || ua.indexOf('OPR') > -1) return 'Opera';
58
- if (ua.indexOf('Chrome') > -1) return 'Chrome';
59
- if (ua.indexOf('Safari') > -1) return 'Safari';
60
- if (ua.indexOf('MSIE') > -1 || ua.indexOf('Trident/') > -1) return 'IE';
61
- return 'Unknown';
62
- }
63
-
64
- export function getPagePath() {
65
- return window.location.pathname;
66
- }
@@ -1 +0,0 @@
1
- export * from './CoreOutline';
@@ -1,116 +0,0 @@
1
- import { v4 as uuidv4 } from 'uuid';
2
-
3
- const INGEST_BASE = 'https://atlas-orchestrator.atlas.coreoutline.com';
4
- const FLUSH_INTERVAL_MS = 5000;
5
-
6
- let _token = null;
7
- let _warehouseId = null;
8
- let _organizationId = null;
9
- let _dataSourceId = null;
10
- let _buffer = [];
11
- let _flushInterval = null;
12
-
13
- export async function initIngest(warehouseId, dataSourceId, dataSourceSecret) {
14
- _warehouseId = warehouseId;
15
- _dataSourceId = dataSourceId;
16
-
17
- try {
18
- const res = await fetch(`${INGEST_BASE}/api/ingest/authorize`, {
19
- method: 'POST',
20
- headers: { 'Content-Type': 'application/json' },
21
- body: JSON.stringify({
22
- warehouse_id: warehouseId,
23
- data_source_id: dataSourceId,
24
- data_source_secret: dataSourceSecret,
25
- }),
26
- });
27
-
28
- if (!res.ok) {
29
- console.error('[CoreOutline] Authorization failed:', res.status);
30
- return false;
31
- }
32
-
33
- const data = await res.json();
34
- _token = data.access_token;
35
- _organizationId = data.organization_id;
36
- return true;
37
- } catch (err) {
38
- console.error('[CoreOutline] Authorization error:', err);
39
- return false;
40
- }
41
- }
42
-
43
- export function trackEvent(eventType, payload) {
44
- if (!_token) return;
45
-
46
- const event = {
47
- event_id: uuidv4(),
48
- event_type: eventType,
49
- event_ts: new Date().toISOString(),
50
- ...payload,
51
- };
52
- _buffer.push(event);
53
-
54
- if (!_flushInterval) {
55
- _flushInterval = setInterval(flush, FLUSH_INTERVAL_MS);
56
- }
57
- }
58
-
59
- export function flush() {
60
- if (!_token || _buffer.length === 0) return;
61
-
62
- const events = _buffer.splice(0, _buffer.length);
63
- const body = JSON.stringify({
64
- warehouse_id: _warehouseId,
65
- organization_id: _organizationId,
66
- events,
67
- });
68
-
69
- const headers = {
70
- 'Content-Type': 'application/json',
71
- Authorization: `Bearer ${_token}`,
72
- };
73
-
74
- if (navigator.sendBeacon) {
75
- const blob = new Blob([body], { type: 'application/json' });
76
- navigator.sendBeacon(`${INGEST_BASE}/api/ingest/events`, blob);
77
- } else {
78
- fetch(`${INGEST_BASE}/api/ingest/events`, {
79
- method: 'POST',
80
- headers,
81
- body,
82
- keepalive: true,
83
- }).catch((err) => console.error('[CoreOutline] Flush error:', err));
84
- }
85
- }
86
-
87
- export async function sendRrwebBatch(chunks, sessionId) {
88
- if (!_token || !chunks || chunks.length === 0) return;
89
-
90
- try {
91
- await fetch(`${INGEST_BASE}/api/ingest/rrweb`, {
92
- method: 'POST',
93
- headers: {
94
- 'Content-Type': 'application/json',
95
- Authorization: `Bearer ${_token}`,
96
- },
97
- body: JSON.stringify({
98
- warehouse_id: _warehouseId,
99
- organization_id: _organizationId,
100
- session_id: sessionId,
101
- data_source_id: _dataSourceId,
102
- chunks,
103
- }),
104
- keepalive: true,
105
- });
106
- } catch (err) {
107
- console.error('[CoreOutline] rrweb upload error:', err);
108
- }
109
- }
110
-
111
- export function stopFlushInterval() {
112
- if (_flushInterval) {
113
- clearInterval(_flushInterval);
114
- _flushInterval = null;
115
- }
116
- }
@@ -1,3 +0,0 @@
1
- import { io } from 'socket.io-client';
2
- const socket = io('http://streams.coreoutline.com');
3
- export default socket;
package/src/index.js DELETED
@@ -1 +0,0 @@
1
- export * from './components/CoreOutline';