core-outline 1.1.24 → 1.1.26

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,28 +12,6 @@ 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
-
26
- The response contains your `data_source_id`, `data_source_secret`, and `warehouse_id`. **The secret is shown once — store it securely.**
27
-
28
- ```json
29
- {
30
- "data_source_id": "3f4a1c2d-...",
31
- "data_source_secret": "aB3xK9mZqR...",
32
- "warehouse_id": "cad1acc0-...",
33
- "name": "My Website"
34
- }
35
- ```
36
-
37
15
  ## Usage
38
16
 
39
17
  Wrap your app's root component with `<CoreOutline>`. All tracking is automatic from that point — no additional configuration is needed.
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
 
@@ -4532,19 +4533,13 @@ function _ensureFreshToken2() {
4532
4533
  return _regeneratorRuntime().wrap(function _callee4$(_context4) {
4533
4534
  while (1) switch (_context4.prev = _context4.next) {
4534
4535
  case 0:
4535
- if (!_token) {
4536
- _context4.next = 2;
4537
- break;
4538
- }
4539
- return _context4.abrupt("return");
4540
- case 2:
4541
4536
  if (!(_warehouseId && _dataSourceId && _dataSourceSecret)) {
4542
- _context4.next = 5;
4537
+ _context4.next = 3;
4543
4538
  break;
4544
4539
  }
4545
- _context4.next = 5;
4540
+ _context4.next = 3;
4546
4541
  return _doAuthorize(_warehouseId, _dataSourceId, _dataSourceSecret);
4547
- case 5:
4542
+ case 3:
4548
4543
  case "end":
4549
4544
  return _context4.stop();
4550
4545
  }
@@ -4553,7 +4548,8 @@ function _ensureFreshToken2() {
4553
4548
  return _ensureFreshToken2.apply(this, arguments);
4554
4549
  }
4555
4550
  function trackEvent(eventType, payload) {
4556
- if (!_token) return;
4551
+ // if (!_token) return;
4552
+
4557
4553
  var event = _objectSpread2({
4558
4554
  event_id: v4(),
4559
4555
  event_type: eventType,
@@ -4576,12 +4572,7 @@ function _flush() {
4576
4572
  _context5.next = 2;
4577
4573
  return _ensureFreshToken();
4578
4574
  case 2:
4579
- if (!(!_token || _buffer.length === 0)) {
4580
- _context5.next = 4;
4581
- break;
4582
- }
4583
- return _context5.abrupt("return");
4584
- case 4:
4575
+ // if (!_token || _buffer.length === 0) return;
4585
4576
  events = _buffer.splice(0, _buffer.length);
4586
4577
  body = JSON.stringify({
4587
4578
  warehouse_id: _warehouseId,
@@ -4607,7 +4598,7 @@ function _flush() {
4607
4598
  return console.error('[CoreOutline] Flush error:', err);
4608
4599
  });
4609
4600
  }
4610
- case 8:
4601
+ case 6:
4611
4602
  case "end":
4612
4603
  return _context5.stop();
4613
4604
  }
@@ -4626,14 +4617,8 @@ function _sendRrwebBatch() {
4626
4617
  _context6.next = 2;
4627
4618
  return _ensureFreshToken();
4628
4619
  case 2:
4629
- if (!(!_token || !chunks || chunks.length === 0)) {
4630
- _context6.next = 4;
4631
- break;
4632
- }
4633
- return _context6.abrupt("return");
4634
- case 4:
4635
- _context6.prev = 4;
4636
- _context6.next = 7;
4620
+ _context6.prev = 2;
4621
+ _context6.next = 5;
4637
4622
  return fetch("".concat(INGEST_BASE, "/api/ingest/rrweb"), {
4638
4623
  method: 'POST',
4639
4624
  headers: {
@@ -4649,18 +4634,18 @@ function _sendRrwebBatch() {
4649
4634
  }),
4650
4635
  keepalive: true
4651
4636
  });
4652
- case 7:
4653
- _context6.next = 12;
4637
+ case 5:
4638
+ _context6.next = 10;
4654
4639
  break;
4655
- case 9:
4656
- _context6.prev = 9;
4657
- _context6.t0 = _context6["catch"](4);
4640
+ case 7:
4641
+ _context6.prev = 7;
4642
+ _context6.t0 = _context6["catch"](2);
4658
4643
  console.error('[CoreOutline] rrweb upload error:', _context6.t0);
4659
- case 12:
4644
+ case 10:
4660
4645
  case "end":
4661
4646
  return _context6.stop();
4662
4647
  }
4663
- }, _callee6, null, [[4, 9]]);
4648
+ }, _callee6, null, [[2, 7]]);
4664
4649
  }));
4665
4650
  return _sendRrwebBatch.apply(this, arguments);
4666
4651
  }
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 });
@@ -4540,19 +4541,13 @@ function _ensureFreshToken2() {
4540
4541
  return _regeneratorRuntime().wrap(function _callee4$(_context4) {
4541
4542
  while (1) switch (_context4.prev = _context4.next) {
4542
4543
  case 0:
4543
- if (!_token) {
4544
- _context4.next = 2;
4545
- break;
4546
- }
4547
- return _context4.abrupt("return");
4548
- case 2:
4549
4544
  if (!(_warehouseId && _dataSourceId && _dataSourceSecret)) {
4550
- _context4.next = 5;
4545
+ _context4.next = 3;
4551
4546
  break;
4552
4547
  }
4553
- _context4.next = 5;
4548
+ _context4.next = 3;
4554
4549
  return _doAuthorize(_warehouseId, _dataSourceId, _dataSourceSecret);
4555
- case 5:
4550
+ case 3:
4556
4551
  case "end":
4557
4552
  return _context4.stop();
4558
4553
  }
@@ -4561,7 +4556,8 @@ function _ensureFreshToken2() {
4561
4556
  return _ensureFreshToken2.apply(this, arguments);
4562
4557
  }
4563
4558
  function trackEvent(eventType, payload) {
4564
- if (!_token) return;
4559
+ // if (!_token) return;
4560
+
4565
4561
  var event = _objectSpread2({
4566
4562
  event_id: uuid.v4(),
4567
4563
  event_type: eventType,
@@ -4584,12 +4580,7 @@ function _flush() {
4584
4580
  _context5.next = 2;
4585
4581
  return _ensureFreshToken();
4586
4582
  case 2:
4587
- if (!(!_token || _buffer.length === 0)) {
4588
- _context5.next = 4;
4589
- break;
4590
- }
4591
- return _context5.abrupt("return");
4592
- case 4:
4583
+ // if (!_token || _buffer.length === 0) return;
4593
4584
  events = _buffer.splice(0, _buffer.length);
4594
4585
  body = JSON.stringify({
4595
4586
  warehouse_id: _warehouseId,
@@ -4615,7 +4606,7 @@ function _flush() {
4615
4606
  return console.error('[CoreOutline] Flush error:', err);
4616
4607
  });
4617
4608
  }
4618
- case 8:
4609
+ case 6:
4619
4610
  case "end":
4620
4611
  return _context5.stop();
4621
4612
  }
@@ -4634,14 +4625,8 @@ function _sendRrwebBatch() {
4634
4625
  _context6.next = 2;
4635
4626
  return _ensureFreshToken();
4636
4627
  case 2:
4637
- if (!(!_token || !chunks || chunks.length === 0)) {
4638
- _context6.next = 4;
4639
- break;
4640
- }
4641
- return _context6.abrupt("return");
4642
- case 4:
4643
- _context6.prev = 4;
4644
- _context6.next = 7;
4628
+ _context6.prev = 2;
4629
+ _context6.next = 5;
4645
4630
  return fetch("".concat(INGEST_BASE, "/api/ingest/rrweb"), {
4646
4631
  method: 'POST',
4647
4632
  headers: {
@@ -4657,18 +4642,18 @@ function _sendRrwebBatch() {
4657
4642
  }),
4658
4643
  keepalive: true
4659
4644
  });
4660
- case 7:
4661
- _context6.next = 12;
4645
+ case 5:
4646
+ _context6.next = 10;
4662
4647
  break;
4663
- case 9:
4664
- _context6.prev = 9;
4665
- _context6.t0 = _context6["catch"](4);
4648
+ case 7:
4649
+ _context6.prev = 7;
4650
+ _context6.t0 = _context6["catch"](2);
4666
4651
  console.error('[CoreOutline] rrweb upload error:', _context6.t0);
4667
- case 12:
4652
+ case 10:
4668
4653
  case "end":
4669
4654
  return _context6.stop();
4670
4655
  }
4671
- }, _callee6, null, [[4, 9]]);
4656
+ }, _callee6, null, [[2, 7]]);
4672
4657
  }));
4673
4658
  return _sendRrwebBatch.apply(this, arguments);
4674
4659
  }
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "core-outline",
3
- "version": "1.1.24",
3
+ "version": "1.1.26",
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,157 +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
- // Refresh the token this many milliseconds before it actually expires.
6
- const TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000; // 5 minutes
7
-
8
- let _token = null;
9
- let _warehouseId = null;
10
- let _organizationId = null;
11
- let _dataSourceId = null;
12
- let _dataSourceSecret = null;
13
- let _buffer = [];
14
- let _flushInterval = null;
15
- let _refreshTimeout = null;
16
-
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
- }
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) {
38
- try {
39
- const res = await fetch(`${INGEST_BASE}/api/ingest/authorize`, {
40
- method: 'POST',
41
- headers: { 'Content-Type': 'application/json' },
42
- body: JSON.stringify({
43
- warehouse_id: warehouseId,
44
- data_source_id: dataSourceId,
45
- data_source_secret: dataSourceSecret,
46
- }),
47
- });
48
-
49
- if (!res.ok) {
50
- console.error('[CoreOutline] Authorization failed:', res.status);
51
- return false;
52
- }
53
-
54
- const data = await res.json();
55
- _token = data.access_token;
56
- _organizationId = data.organization_id;
57
-
58
- const expiresIn = data.expires_in || 24 * 3600;
59
- _scheduleTokenRefresh(expiresIn);
60
- return true;
61
- } catch (err) {
62
- console.error('[CoreOutline] Authorization error:', err);
63
- return false;
64
- }
65
- }
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
-
82
- export function trackEvent(eventType, payload) {
83
- if (!_token) return;
84
-
85
- const event = {
86
- event_id: uuidv4(),
87
- event_type: eventType,
88
- event_ts: new Date().toISOString(),
89
- ...payload,
90
- };
91
- _buffer.push(event);
92
-
93
- if (!_flushInterval) {
94
- _flushInterval = setInterval(flush, FLUSH_INTERVAL_MS);
95
- }
96
- }
97
-
98
- export async function flush() {
99
- await _ensureFreshToken();
100
- if (!_token || _buffer.length === 0) return;
101
-
102
- const events = _buffer.splice(0, _buffer.length);
103
- const body = JSON.stringify({
104
- warehouse_id: _warehouseId,
105
- organization_id: _organizationId,
106
- events,
107
- });
108
-
109
- const headers = {
110
- 'Content-Type': 'application/json',
111
- Authorization: `Bearer ${_token}`,
112
- };
113
-
114
- if (navigator.sendBeacon) {
115
- const blob = new Blob([body], { type: 'application/json' });
116
- navigator.sendBeacon(`${INGEST_BASE}/api/ingest/events`, blob);
117
- } else {
118
- fetch(`${INGEST_BASE}/api/ingest/events`, {
119
- method: 'POST',
120
- headers,
121
- body,
122
- keepalive: true,
123
- }).catch((err) => console.error('[CoreOutline] Flush error:', err));
124
- }
125
- }
126
-
127
- export async function sendRrwebBatch(chunks, sessionId) {
128
- await _ensureFreshToken();
129
- if (!_token || !chunks || chunks.length === 0) return;
130
-
131
- try {
132
- await fetch(`${INGEST_BASE}/api/ingest/rrweb`, {
133
- method: 'POST',
134
- headers: {
135
- 'Content-Type': 'application/json',
136
- Authorization: `Bearer ${_token}`,
137
- },
138
- body: JSON.stringify({
139
- warehouse_id: _warehouseId,
140
- organization_id: _organizationId,
141
- session_id: sessionId,
142
- data_source_id: _dataSourceId,
143
- chunks,
144
- }),
145
- keepalive: true,
146
- });
147
- } catch (err) {
148
- console.error('[CoreOutline] rrweb upload error:', err);
149
- }
150
- }
151
-
152
- export function stopFlushInterval() {
153
- if (_flushInterval) {
154
- clearInterval(_flushInterval);
155
- _flushInterval = null;
156
- }
157
- }
@@ -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';