core-outline 1.1.23 → 1.1.24

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,6 +12,17 @@ npm install core-outline
12
12
  yarn add core-outline
13
13
  ```
14
14
 
15
+ ## Prerequisites
16
+
17
+ Before adding the component to your app, create an SDK data source through the Hermes API to obtain your credentials. This requires your Firebase authentication token.
18
+
19
+ ```sh
20
+ curl -X POST https://atlas-orchestrator.atlas.coreoutline.com/api/ingest/sdk-sources \
21
+ -H "Authorization: Bearer <your-firebase-token>" \
22
+ -H "Content-Type: application/json" \
23
+ -d '{"name": "My Website"}'
24
+ ```
25
+
15
26
  The response contains your `data_source_id`, `data_source_secret`, and `warehouse_id`. **The secret is shown once — store it securely.**
16
27
 
17
28
  ```json
@@ -79,3 +90,37 @@ flag_item_clicked('sku-123');
79
90
  // Track a purchase (price is optional, maps to value_num in ClickHouse)
80
91
  flag_item_purchased('sku-123', 49.99);
81
92
  ```
93
+
94
+ ## Data Pipeline
95
+
96
+ Events flow through the following pipeline:
97
+
98
+ ```
99
+ Browser → POST /api/ingest/events → RabbitMQ ({warehouse_id}.analytics.events)
100
+ → Consumer worker → ClickHouse atlas_analytics
101
+ ```
102
+
103
+ The ClickHouse tables populated are:
104
+
105
+ | Table | Populated by |
106
+ |-------|-------------|
107
+ | `fact_event` | All events except session start/end |
108
+ | `fact_session` | `session_start` and `session_end` events |
109
+ | `fact_pageview` | `pageview` events |
110
+ | `stg_events_sdk_event` | All events (raw staging) |
111
+ | `stg_rrweb_event` | Session recording chunks |
112
+
113
+ These tables are queried by the [atlas-analytics](https://analytics.atlas.coreoutline.com) API under `/metrics/saas/traffic`, `/metrics/saas/engagement`, and related endpoints.
114
+
115
+ ## Anonymous vs Session Identity
116
+
117
+ - **`anonymous_id`** — a UUID stored in `localStorage`; persists across browser sessions, used to stitch cross-session user journeys.
118
+ - **`session_id`** — a UUID stored in `sessionStorage`; reset on each new tab or browser close, matching the standard session definition.
119
+
120
+ ## Service URLs
121
+
122
+ | Service | URL |
123
+ |---------|-----|
124
+ | Hermes API (credential management) | `https://atlas-orchestrator.atlas.coreoutline.com` |
125
+ | Maia (data engineering / pipelines) | `https://data-engineering.atlas.coreoutline.com` |
126
+ | Atlas Analytics API | `https://analytics.atlas.coreoutline.com` |
package/dist/index.es.js CHANGED
@@ -4411,25 +4411,57 @@ function getPagePath() {
4411
4411
 
4412
4412
  var INGEST_BASE = 'https://atlas-orchestrator.atlas.coreoutline.com';
4413
4413
  var FLUSH_INTERVAL_MS = 5000;
4414
+ // Refresh the token this many milliseconds before it actually expires.
4415
+ var TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000; // 5 minutes
4416
+
4414
4417
  var _token = null;
4415
4418
  var _warehouseId = null;
4416
4419
  var _organizationId = null;
4417
4420
  var _dataSourceId = null;
4421
+ var _dataSourceSecret = null;
4418
4422
  var _buffer = [];
4419
4423
  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;
4424
+ var _refreshTimeout = null;
4425
+ function _scheduleTokenRefresh(expiresInSeconds) {
4426
+ if (_refreshTimeout) {
4427
+ clearTimeout(_refreshTimeout);
4428
+ _refreshTimeout = null;
4429
+ }
4430
+ var refreshInMs = expiresInSeconds * 1000 - TOKEN_REFRESH_BUFFER_MS;
4431
+ if (refreshInMs <= 0) {
4432
+ // Token already near expiry — refresh immediately on next action.
4433
+ return;
4434
+ }
4435
+ _refreshTimeout = setTimeout(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
4436
+ var ok;
4426
4437
  return _regeneratorRuntime().wrap(function _callee$(_context) {
4427
4438
  while (1) switch (_context.prev = _context.next) {
4428
4439
  case 0:
4429
- _warehouseId = warehouseId;
4430
- _dataSourceId = dataSourceId;
4431
- _context.prev = 2;
4432
- _context.next = 5;
4440
+ _context.next = 2;
4441
+ return _doAuthorize(_warehouseId, _dataSourceId, _dataSourceSecret);
4442
+ case 2:
4443
+ ok = _context.sent;
4444
+ if (!ok) {
4445
+ console.warn('[CoreOutline] Proactive token refresh failed; will retry on next flush.');
4446
+ }
4447
+ case 4:
4448
+ case "end":
4449
+ return _context.stop();
4450
+ }
4451
+ }, _callee);
4452
+ })), refreshInMs);
4453
+ }
4454
+ function _doAuthorize(_x, _x2, _x3) {
4455
+ return _doAuthorize2.apply(this, arguments);
4456
+ }
4457
+ function _doAuthorize2() {
4458
+ _doAuthorize2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(warehouseId, dataSourceId, dataSourceSecret) {
4459
+ var res, data, expiresIn;
4460
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
4461
+ while (1) switch (_context2.prev = _context2.next) {
4462
+ case 0:
4463
+ _context2.prev = 0;
4464
+ _context2.next = 3;
4433
4465
  return fetch("".concat(INGEST_BASE, "/api/ingest/authorize"), {
4434
4466
  method: 'POST',
4435
4467
  headers: {
@@ -4441,35 +4473,85 @@ function _initIngest() {
4441
4473
  data_source_secret: dataSourceSecret
4442
4474
  })
4443
4475
  });
4444
- case 5:
4445
- res = _context.sent;
4476
+ case 3:
4477
+ res = _context2.sent;
4446
4478
  if (res.ok) {
4447
- _context.next = 9;
4479
+ _context2.next = 7;
4448
4480
  break;
4449
4481
  }
4450
4482
  console.error('[CoreOutline] Authorization failed:', res.status);
4451
- return _context.abrupt("return", false);
4452
- case 9:
4453
- _context.next = 11;
4483
+ return _context2.abrupt("return", false);
4484
+ case 7:
4485
+ _context2.next = 9;
4454
4486
  return res.json();
4455
- case 11:
4456
- data = _context.sent;
4487
+ case 9:
4488
+ data = _context2.sent;
4457
4489
  _token = data.access_token;
4458
4490
  _organizationId = data.organization_id;
4459
- return _context.abrupt("return", true);
4491
+ expiresIn = data.expires_in || 24 * 3600;
4492
+ _scheduleTokenRefresh(expiresIn);
4493
+ return _context2.abrupt("return", true);
4460
4494
  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);
4495
+ _context2.prev = 17;
4496
+ _context2.t0 = _context2["catch"](0);
4497
+ console.error('[CoreOutline] Authorization error:', _context2.t0);
4498
+ return _context2.abrupt("return", false);
4465
4499
  case 21:
4466
4500
  case "end":
4467
- return _context.stop();
4501
+ return _context2.stop();
4502
+ }
4503
+ }, _callee2, null, [[0, 17]]);
4504
+ }));
4505
+ return _doAuthorize2.apply(this, arguments);
4506
+ }
4507
+ function initIngest(_x4, _x5, _x6) {
4508
+ return _initIngest.apply(this, arguments);
4509
+ }
4510
+ function _initIngest() {
4511
+ _initIngest = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee3(warehouseId, dataSourceId, dataSourceSecret) {
4512
+ return _regeneratorRuntime().wrap(function _callee3$(_context3) {
4513
+ while (1) switch (_context3.prev = _context3.next) {
4514
+ case 0:
4515
+ _warehouseId = warehouseId;
4516
+ _dataSourceId = dataSourceId;
4517
+ _dataSourceSecret = dataSourceSecret;
4518
+ return _context3.abrupt("return", _doAuthorize(warehouseId, dataSourceId, dataSourceSecret));
4519
+ case 4:
4520
+ case "end":
4521
+ return _context3.stop();
4468
4522
  }
4469
- }, _callee, null, [[2, 17]]);
4523
+ }, _callee3);
4470
4524
  }));
4471
4525
  return _initIngest.apply(this, arguments);
4472
4526
  }
4527
+ function _ensureFreshToken() {
4528
+ return _ensureFreshToken2.apply(this, arguments);
4529
+ }
4530
+ function _ensureFreshToken2() {
4531
+ _ensureFreshToken2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee4() {
4532
+ return _regeneratorRuntime().wrap(function _callee4$(_context4) {
4533
+ while (1) switch (_context4.prev = _context4.next) {
4534
+ case 0:
4535
+ if (!_token) {
4536
+ _context4.next = 2;
4537
+ break;
4538
+ }
4539
+ return _context4.abrupt("return");
4540
+ case 2:
4541
+ if (!(_warehouseId && _dataSourceId && _dataSourceSecret)) {
4542
+ _context4.next = 5;
4543
+ break;
4544
+ }
4545
+ _context4.next = 5;
4546
+ return _doAuthorize(_warehouseId, _dataSourceId, _dataSourceSecret);
4547
+ case 5:
4548
+ case "end":
4549
+ return _context4.stop();
4550
+ }
4551
+ }, _callee4);
4552
+ }));
4553
+ return _ensureFreshToken2.apply(this, arguments);
4554
+ }
4473
4555
  function trackEvent(eventType, payload) {
4474
4556
  if (!_token) return;
4475
4557
  var event = _objectSpread2({
@@ -4483,49 +4565,75 @@ function trackEvent(eventType, payload) {
4483
4565
  }
4484
4566
  }
4485
4567
  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
- }
4568
+ return _flush.apply(this, arguments);
4569
+ }
4570
+ function _flush() {
4571
+ _flush = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee5() {
4572
+ var events, body, headers, blob;
4573
+ return _regeneratorRuntime().wrap(function _callee5$(_context5) {
4574
+ while (1) switch (_context5.prev = _context5.next) {
4575
+ case 0:
4576
+ _context5.next = 2;
4577
+ return _ensureFreshToken();
4578
+ case 2:
4579
+ if (!(!_token || _buffer.length === 0)) {
4580
+ _context5.next = 4;
4581
+ break;
4582
+ }
4583
+ return _context5.abrupt("return");
4584
+ case 4:
4585
+ events = _buffer.splice(0, _buffer.length);
4586
+ body = JSON.stringify({
4587
+ warehouse_id: _warehouseId,
4588
+ organization_id: _organizationId,
4589
+ events: events
4590
+ });
4591
+ headers = {
4592
+ 'Content-Type': 'application/json',
4593
+ Authorization: "Bearer ".concat(_token)
4594
+ };
4595
+ if (navigator.sendBeacon) {
4596
+ blob = new Blob([body], {
4597
+ type: 'application/json'
4598
+ });
4599
+ navigator.sendBeacon("".concat(INGEST_BASE, "/api/ingest/events"), blob);
4600
+ } else {
4601
+ fetch("".concat(INGEST_BASE, "/api/ingest/events"), {
4602
+ method: 'POST',
4603
+ headers: headers,
4604
+ body: body,
4605
+ keepalive: true
4606
+ })["catch"](function (err) {
4607
+ return console.error('[CoreOutline] Flush error:', err);
4608
+ });
4609
+ }
4610
+ case 8:
4611
+ case "end":
4612
+ return _context5.stop();
4613
+ }
4614
+ }, _callee5);
4615
+ }));
4616
+ return _flush.apply(this, arguments);
4512
4617
  }
4513
- function sendRrwebBatch(_x4, _x5) {
4618
+ function sendRrwebBatch(_x7, _x8) {
4514
4619
  return _sendRrwebBatch.apply(this, arguments);
4515
4620
  }
4516
4621
  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) {
4622
+ _sendRrwebBatch = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee6(chunks, sessionId) {
4623
+ return _regeneratorRuntime().wrap(function _callee6$(_context6) {
4624
+ while (1) switch (_context6.prev = _context6.next) {
4520
4625
  case 0:
4626
+ _context6.next = 2;
4627
+ return _ensureFreshToken();
4628
+ case 2:
4521
4629
  if (!(!_token || !chunks || chunks.length === 0)) {
4522
- _context2.next = 2;
4630
+ _context6.next = 4;
4523
4631
  break;
4524
4632
  }
4525
- return _context2.abrupt("return");
4526
- case 2:
4527
- _context2.prev = 2;
4528
- _context2.next = 5;
4633
+ return _context6.abrupt("return");
4634
+ case 4:
4635
+ _context6.prev = 4;
4636
+ _context6.next = 7;
4529
4637
  return fetch("".concat(INGEST_BASE, "/api/ingest/rrweb"), {
4530
4638
  method: 'POST',
4531
4639
  headers: {
@@ -4541,18 +4649,18 @@ function _sendRrwebBatch() {
4541
4649
  }),
4542
4650
  keepalive: true
4543
4651
  });
4544
- case 5:
4545
- _context2.next = 10;
4546
- break;
4547
4652
  case 7:
4548
- _context2.prev = 7;
4549
- _context2.t0 = _context2["catch"](2);
4550
- console.error('[CoreOutline] rrweb upload error:', _context2.t0);
4551
- case 10:
4653
+ _context6.next = 12;
4654
+ break;
4655
+ case 9:
4656
+ _context6.prev = 9;
4657
+ _context6.t0 = _context6["catch"](4);
4658
+ console.error('[CoreOutline] rrweb upload error:', _context6.t0);
4659
+ case 12:
4552
4660
  case "end":
4553
- return _context2.stop();
4661
+ return _context6.stop();
4554
4662
  }
4555
- }, _callee2, null, [[2, 7]]);
4663
+ }, _callee6, null, [[4, 9]]);
4556
4664
  }));
4557
4665
  return _sendRrwebBatch.apply(this, arguments);
4558
4666
  }
package/dist/index.js CHANGED
@@ -4419,25 +4419,57 @@ function getPagePath() {
4419
4419
 
4420
4420
  var INGEST_BASE = 'https://atlas-orchestrator.atlas.coreoutline.com';
4421
4421
  var FLUSH_INTERVAL_MS = 5000;
4422
+ // Refresh the token this many milliseconds before it actually expires.
4423
+ var TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000; // 5 minutes
4424
+
4422
4425
  var _token = null;
4423
4426
  var _warehouseId = null;
4424
4427
  var _organizationId = null;
4425
4428
  var _dataSourceId = null;
4429
+ var _dataSourceSecret = null;
4426
4430
  var _buffer = [];
4427
4431
  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;
4432
+ var _refreshTimeout = null;
4433
+ function _scheduleTokenRefresh(expiresInSeconds) {
4434
+ if (_refreshTimeout) {
4435
+ clearTimeout(_refreshTimeout);
4436
+ _refreshTimeout = null;
4437
+ }
4438
+ var refreshInMs = expiresInSeconds * 1000 - TOKEN_REFRESH_BUFFER_MS;
4439
+ if (refreshInMs <= 0) {
4440
+ // Token already near expiry — refresh immediately on next action.
4441
+ return;
4442
+ }
4443
+ _refreshTimeout = setTimeout(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
4444
+ var ok;
4434
4445
  return _regeneratorRuntime().wrap(function _callee$(_context) {
4435
4446
  while (1) switch (_context.prev = _context.next) {
4436
4447
  case 0:
4437
- _warehouseId = warehouseId;
4438
- _dataSourceId = dataSourceId;
4439
- _context.prev = 2;
4440
- _context.next = 5;
4448
+ _context.next = 2;
4449
+ return _doAuthorize(_warehouseId, _dataSourceId, _dataSourceSecret);
4450
+ case 2:
4451
+ ok = _context.sent;
4452
+ if (!ok) {
4453
+ console.warn('[CoreOutline] Proactive token refresh failed; will retry on next flush.');
4454
+ }
4455
+ case 4:
4456
+ case "end":
4457
+ return _context.stop();
4458
+ }
4459
+ }, _callee);
4460
+ })), refreshInMs);
4461
+ }
4462
+ function _doAuthorize(_x, _x2, _x3) {
4463
+ return _doAuthorize2.apply(this, arguments);
4464
+ }
4465
+ function _doAuthorize2() {
4466
+ _doAuthorize2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(warehouseId, dataSourceId, dataSourceSecret) {
4467
+ var res, data, expiresIn;
4468
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
4469
+ while (1) switch (_context2.prev = _context2.next) {
4470
+ case 0:
4471
+ _context2.prev = 0;
4472
+ _context2.next = 3;
4441
4473
  return fetch("".concat(INGEST_BASE, "/api/ingest/authorize"), {
4442
4474
  method: 'POST',
4443
4475
  headers: {
@@ -4449,35 +4481,85 @@ function _initIngest() {
4449
4481
  data_source_secret: dataSourceSecret
4450
4482
  })
4451
4483
  });
4452
- case 5:
4453
- res = _context.sent;
4484
+ case 3:
4485
+ res = _context2.sent;
4454
4486
  if (res.ok) {
4455
- _context.next = 9;
4487
+ _context2.next = 7;
4456
4488
  break;
4457
4489
  }
4458
4490
  console.error('[CoreOutline] Authorization failed:', res.status);
4459
- return _context.abrupt("return", false);
4460
- case 9:
4461
- _context.next = 11;
4491
+ return _context2.abrupt("return", false);
4492
+ case 7:
4493
+ _context2.next = 9;
4462
4494
  return res.json();
4463
- case 11:
4464
- data = _context.sent;
4495
+ case 9:
4496
+ data = _context2.sent;
4465
4497
  _token = data.access_token;
4466
4498
  _organizationId = data.organization_id;
4467
- return _context.abrupt("return", true);
4499
+ expiresIn = data.expires_in || 24 * 3600;
4500
+ _scheduleTokenRefresh(expiresIn);
4501
+ return _context2.abrupt("return", true);
4468
4502
  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);
4503
+ _context2.prev = 17;
4504
+ _context2.t0 = _context2["catch"](0);
4505
+ console.error('[CoreOutline] Authorization error:', _context2.t0);
4506
+ return _context2.abrupt("return", false);
4473
4507
  case 21:
4474
4508
  case "end":
4475
- return _context.stop();
4509
+ return _context2.stop();
4510
+ }
4511
+ }, _callee2, null, [[0, 17]]);
4512
+ }));
4513
+ return _doAuthorize2.apply(this, arguments);
4514
+ }
4515
+ function initIngest(_x4, _x5, _x6) {
4516
+ return _initIngest.apply(this, arguments);
4517
+ }
4518
+ function _initIngest() {
4519
+ _initIngest = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee3(warehouseId, dataSourceId, dataSourceSecret) {
4520
+ return _regeneratorRuntime().wrap(function _callee3$(_context3) {
4521
+ while (1) switch (_context3.prev = _context3.next) {
4522
+ case 0:
4523
+ _warehouseId = warehouseId;
4524
+ _dataSourceId = dataSourceId;
4525
+ _dataSourceSecret = dataSourceSecret;
4526
+ return _context3.abrupt("return", _doAuthorize(warehouseId, dataSourceId, dataSourceSecret));
4527
+ case 4:
4528
+ case "end":
4529
+ return _context3.stop();
4476
4530
  }
4477
- }, _callee, null, [[2, 17]]);
4531
+ }, _callee3);
4478
4532
  }));
4479
4533
  return _initIngest.apply(this, arguments);
4480
4534
  }
4535
+ function _ensureFreshToken() {
4536
+ return _ensureFreshToken2.apply(this, arguments);
4537
+ }
4538
+ function _ensureFreshToken2() {
4539
+ _ensureFreshToken2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee4() {
4540
+ return _regeneratorRuntime().wrap(function _callee4$(_context4) {
4541
+ while (1) switch (_context4.prev = _context4.next) {
4542
+ case 0:
4543
+ if (!_token) {
4544
+ _context4.next = 2;
4545
+ break;
4546
+ }
4547
+ return _context4.abrupt("return");
4548
+ case 2:
4549
+ if (!(_warehouseId && _dataSourceId && _dataSourceSecret)) {
4550
+ _context4.next = 5;
4551
+ break;
4552
+ }
4553
+ _context4.next = 5;
4554
+ return _doAuthorize(_warehouseId, _dataSourceId, _dataSourceSecret);
4555
+ case 5:
4556
+ case "end":
4557
+ return _context4.stop();
4558
+ }
4559
+ }, _callee4);
4560
+ }));
4561
+ return _ensureFreshToken2.apply(this, arguments);
4562
+ }
4481
4563
  function trackEvent(eventType, payload) {
4482
4564
  if (!_token) return;
4483
4565
  var event = _objectSpread2({
@@ -4491,49 +4573,75 @@ function trackEvent(eventType, payload) {
4491
4573
  }
4492
4574
  }
4493
4575
  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
- }
4576
+ return _flush.apply(this, arguments);
4577
+ }
4578
+ function _flush() {
4579
+ _flush = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee5() {
4580
+ var events, body, headers, blob;
4581
+ return _regeneratorRuntime().wrap(function _callee5$(_context5) {
4582
+ while (1) switch (_context5.prev = _context5.next) {
4583
+ case 0:
4584
+ _context5.next = 2;
4585
+ return _ensureFreshToken();
4586
+ case 2:
4587
+ if (!(!_token || _buffer.length === 0)) {
4588
+ _context5.next = 4;
4589
+ break;
4590
+ }
4591
+ return _context5.abrupt("return");
4592
+ case 4:
4593
+ events = _buffer.splice(0, _buffer.length);
4594
+ body = JSON.stringify({
4595
+ warehouse_id: _warehouseId,
4596
+ organization_id: _organizationId,
4597
+ events: events
4598
+ });
4599
+ headers = {
4600
+ 'Content-Type': 'application/json',
4601
+ Authorization: "Bearer ".concat(_token)
4602
+ };
4603
+ if (navigator.sendBeacon) {
4604
+ blob = new Blob([body], {
4605
+ type: 'application/json'
4606
+ });
4607
+ navigator.sendBeacon("".concat(INGEST_BASE, "/api/ingest/events"), blob);
4608
+ } else {
4609
+ fetch("".concat(INGEST_BASE, "/api/ingest/events"), {
4610
+ method: 'POST',
4611
+ headers: headers,
4612
+ body: body,
4613
+ keepalive: true
4614
+ })["catch"](function (err) {
4615
+ return console.error('[CoreOutline] Flush error:', err);
4616
+ });
4617
+ }
4618
+ case 8:
4619
+ case "end":
4620
+ return _context5.stop();
4621
+ }
4622
+ }, _callee5);
4623
+ }));
4624
+ return _flush.apply(this, arguments);
4520
4625
  }
4521
- function sendRrwebBatch(_x4, _x5) {
4626
+ function sendRrwebBatch(_x7, _x8) {
4522
4627
  return _sendRrwebBatch.apply(this, arguments);
4523
4628
  }
4524
4629
  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) {
4630
+ _sendRrwebBatch = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee6(chunks, sessionId) {
4631
+ return _regeneratorRuntime().wrap(function _callee6$(_context6) {
4632
+ while (1) switch (_context6.prev = _context6.next) {
4528
4633
  case 0:
4634
+ _context6.next = 2;
4635
+ return _ensureFreshToken();
4636
+ case 2:
4529
4637
  if (!(!_token || !chunks || chunks.length === 0)) {
4530
- _context2.next = 2;
4638
+ _context6.next = 4;
4531
4639
  break;
4532
4640
  }
4533
- return _context2.abrupt("return");
4534
- case 2:
4535
- _context2.prev = 2;
4536
- _context2.next = 5;
4641
+ return _context6.abrupt("return");
4642
+ case 4:
4643
+ _context6.prev = 4;
4644
+ _context6.next = 7;
4537
4645
  return fetch("".concat(INGEST_BASE, "/api/ingest/rrweb"), {
4538
4646
  method: 'POST',
4539
4647
  headers: {
@@ -4549,18 +4657,18 @@ function _sendRrwebBatch() {
4549
4657
  }),
4550
4658
  keepalive: true
4551
4659
  });
4552
- case 5:
4553
- _context2.next = 10;
4554
- break;
4555
4660
  case 7:
4556
- _context2.prev = 7;
4557
- _context2.t0 = _context2["catch"](2);
4558
- console.error('[CoreOutline] rrweb upload error:', _context2.t0);
4559
- case 10:
4661
+ _context6.next = 12;
4662
+ break;
4663
+ case 9:
4664
+ _context6.prev = 9;
4665
+ _context6.t0 = _context6["catch"](4);
4666
+ console.error('[CoreOutline] rrweb upload error:', _context6.t0);
4667
+ case 12:
4560
4668
  case "end":
4561
- return _context2.stop();
4669
+ return _context6.stop();
4562
4670
  }
4563
- }, _callee2, null, [[2, 7]]);
4671
+ }, _callee6, null, [[4, 9]]);
4564
4672
  }));
4565
4673
  return _sendRrwebBatch.apply(this, arguments);
4566
4674
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "core-outline",
3
- "version": "1.1.23",
3
+ "version": "1.1.24",
4
4
  "description": "A React component for Core&Outline",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.es.js",
@@ -2,18 +2,39 @@ import { v4 as uuidv4 } from 'uuid';
2
2
 
3
3
  const INGEST_BASE = 'https://atlas-orchestrator.atlas.coreoutline.com';
4
4
  const FLUSH_INTERVAL_MS = 5000;
5
+ // Refresh the token this many milliseconds before it actually expires.
6
+ const TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000; // 5 minutes
5
7
 
6
8
  let _token = null;
7
9
  let _warehouseId = null;
8
10
  let _organizationId = null;
9
11
  let _dataSourceId = null;
12
+ let _dataSourceSecret = null;
10
13
  let _buffer = [];
11
14
  let _flushInterval = null;
15
+ let _refreshTimeout = null;
12
16
 
13
- export async function initIngest(warehouseId, dataSourceId, dataSourceSecret) {
14
- _warehouseId = warehouseId;
15
- _dataSourceId = dataSourceId;
17
+ function _scheduleTokenRefresh(expiresInSeconds) {
18
+ if (_refreshTimeout) {
19
+ clearTimeout(_refreshTimeout);
20
+ _refreshTimeout = null;
21
+ }
22
+
23
+ const refreshInMs = expiresInSeconds * 1000 - TOKEN_REFRESH_BUFFER_MS;
24
+ if (refreshInMs <= 0) {
25
+ // Token already near expiry — refresh immediately on next action.
26
+ return;
27
+ }
16
28
 
29
+ _refreshTimeout = setTimeout(async () => {
30
+ const ok = await _doAuthorize(_warehouseId, _dataSourceId, _dataSourceSecret);
31
+ if (!ok) {
32
+ console.warn('[CoreOutline] Proactive token refresh failed; will retry on next flush.');
33
+ }
34
+ }, refreshInMs);
35
+ }
36
+
37
+ async function _doAuthorize(warehouseId, dataSourceId, dataSourceSecret) {
17
38
  try {
18
39
  const res = await fetch(`${INGEST_BASE}/api/ingest/authorize`, {
19
40
  method: 'POST',
@@ -33,6 +54,9 @@ export async function initIngest(warehouseId, dataSourceId, dataSourceSecret) {
33
54
  const data = await res.json();
34
55
  _token = data.access_token;
35
56
  _organizationId = data.organization_id;
57
+
58
+ const expiresIn = data.expires_in || 24 * 3600;
59
+ _scheduleTokenRefresh(expiresIn);
36
60
  return true;
37
61
  } catch (err) {
38
62
  console.error('[CoreOutline] Authorization error:', err);
@@ -40,6 +64,21 @@ export async function initIngest(warehouseId, dataSourceId, dataSourceSecret) {
40
64
  }
41
65
  }
42
66
 
67
+ export async function initIngest(warehouseId, dataSourceId, dataSourceSecret) {
68
+ _warehouseId = warehouseId;
69
+ _dataSourceId = dataSourceId;
70
+ _dataSourceSecret = dataSourceSecret;
71
+
72
+ return _doAuthorize(warehouseId, dataSourceId, dataSourceSecret);
73
+ }
74
+
75
+ async function _ensureFreshToken() {
76
+ if (_token) return;
77
+ if (_warehouseId && _dataSourceId && _dataSourceSecret) {
78
+ await _doAuthorize(_warehouseId, _dataSourceId, _dataSourceSecret);
79
+ }
80
+ }
81
+
43
82
  export function trackEvent(eventType, payload) {
44
83
  if (!_token) return;
45
84
 
@@ -56,7 +95,8 @@ export function trackEvent(eventType, payload) {
56
95
  }
57
96
  }
58
97
 
59
- export function flush() {
98
+ export async function flush() {
99
+ await _ensureFreshToken();
60
100
  if (!_token || _buffer.length === 0) return;
61
101
 
62
102
  const events = _buffer.splice(0, _buffer.length);
@@ -85,6 +125,7 @@ export function flush() {
85
125
  }
86
126
 
87
127
  export async function sendRrwebBatch(chunks, sessionId) {
128
+ await _ensureFreshToken();
88
129
  if (!_token || !chunks || chunks.length === 0) return;
89
130
 
90
131
  try {