chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Sim Telemetry - Client-side Instrumentation
|
||||
*/
|
||||
|
||||
import { randomFloat } from '@sim/utils/random'
|
||||
import { env } from './lib/core/config/env'
|
||||
import { sanitizeEventData } from './lib/core/security/redaction'
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const TELEMETRY_STATUS_KEY = 'simstudio-telemetry-status'
|
||||
const BATCH_INTERVAL_MS = 10000 // Send batches every 10 seconds
|
||||
const MAX_BATCH_SIZE = 50 // Max events per batch
|
||||
let telemetryEnabled = true
|
||||
const eventBatch: any[] = []
|
||||
let batchTimer: NodeJS.Timeout | null = null
|
||||
|
||||
try {
|
||||
if (env.NEXT_TELEMETRY_DISABLED === '1') {
|
||||
telemetryEnabled = false
|
||||
} else {
|
||||
const storedPreference = localStorage.getItem(TELEMETRY_STATUS_KEY)
|
||||
if (storedPreference) {
|
||||
const status = JSON.parse(storedPreference)
|
||||
telemetryEnabled = status.enabled
|
||||
}
|
||||
}
|
||||
} catch (_e) {
|
||||
telemetryEnabled = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event to batch and schedule flush
|
||||
*/
|
||||
function addToBatch(event: any): void {
|
||||
if (!telemetryEnabled) return
|
||||
|
||||
eventBatch.push(event)
|
||||
|
||||
if (eventBatch.length >= MAX_BATCH_SIZE) {
|
||||
flushBatch()
|
||||
} else if (!batchTimer) {
|
||||
batchTimer = setTimeout(flushBatch, BATCH_INTERVAL_MS)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush batch of events to server
|
||||
*/
|
||||
function flushBatch(): void {
|
||||
if (eventBatch.length === 0) return
|
||||
|
||||
const batch = eventBatch.splice(0, eventBatch.length)
|
||||
if (batchTimer) {
|
||||
clearTimeout(batchTimer)
|
||||
batchTimer = null
|
||||
}
|
||||
|
||||
const sanitizedBatch = batch.map(sanitizeEventData)
|
||||
|
||||
const payload = JSON.stringify({
|
||||
category: 'batch',
|
||||
action: 'client_events',
|
||||
events: sanitizedBatch,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
|
||||
const payloadSize = new Blob([payload]).size
|
||||
const MAX_BEACON_SIZE = 64 * 1024 // 64KB
|
||||
|
||||
if (navigator.sendBeacon && payloadSize < MAX_BEACON_SIZE) {
|
||||
const sent = navigator.sendBeacon('/api/telemetry', payload)
|
||||
|
||||
if (!sent) {
|
||||
// boundary-raw-fetch: pre-bootstrap instrumentation
|
||||
fetch('/api/telemetry', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: payload,
|
||||
keepalive: true,
|
||||
}).catch(() => {
|
||||
// Silently fail
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// boundary-raw-fetch: pre-bootstrap instrumentation
|
||||
fetch('/api/telemetry', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: payload,
|
||||
keepalive: true,
|
||||
}).catch(() => {
|
||||
// Silently fail
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', flushBatch)
|
||||
window.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
flushBatch()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Global event tracking function
|
||||
*/
|
||||
|
||||
;(window as any).__SIM_TELEMETRY_ENABLED = telemetryEnabled
|
||||
;(window as any).__SIM_TRACK_EVENT = (eventName: string, properties?: any) => {
|
||||
if (!telemetryEnabled) return
|
||||
|
||||
addToBatch({
|
||||
category: 'feature_usage',
|
||||
action: eventName,
|
||||
timestamp: Date.now(),
|
||||
...(properties || {}),
|
||||
})
|
||||
}
|
||||
|
||||
if (telemetryEnabled) {
|
||||
const shouldTrackVitals = randomFloat() < 0.1
|
||||
|
||||
if (shouldTrackVitals) {
|
||||
window.addEventListener(
|
||||
'load',
|
||||
() => {
|
||||
if (typeof PerformanceObserver !== 'undefined') {
|
||||
const lcpObserver = new PerformanceObserver((list) => {
|
||||
const entries = list.getEntries()
|
||||
const lastEntry = entries[entries.length - 1]
|
||||
|
||||
if (lastEntry) {
|
||||
addToBatch({
|
||||
category: 'performance',
|
||||
action: 'web_vital',
|
||||
label: 'LCP',
|
||||
value: (lastEntry as any).startTime || 0,
|
||||
entryType: 'largest-contentful-paint',
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
|
||||
lcpObserver.disconnect()
|
||||
})
|
||||
|
||||
let clsValue = 0
|
||||
const clsObserver = new PerformanceObserver((list) => {
|
||||
for (const entry of list.getEntries()) {
|
||||
if (!(entry as any).hadRecentInput) {
|
||||
clsValue += (entry as any).value || 0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const fidObserver = new PerformanceObserver((list) => {
|
||||
const entries = list.getEntries()
|
||||
|
||||
for (const entry of entries) {
|
||||
const fidValue =
|
||||
((entry as any).processingStart || 0) - ((entry as any).startTime || 0)
|
||||
|
||||
addToBatch({
|
||||
category: 'performance',
|
||||
action: 'web_vital',
|
||||
label: 'FID',
|
||||
value: fidValue,
|
||||
entryType: 'first-input',
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
|
||||
fidObserver.disconnect()
|
||||
})
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
if (clsValue > 0) {
|
||||
addToBatch({
|
||||
category: 'performance',
|
||||
action: 'web_vital',
|
||||
label: 'CLS',
|
||||
value: clsValue,
|
||||
entryType: 'layout-shift',
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
clsObserver.disconnect()
|
||||
})
|
||||
|
||||
lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true })
|
||||
clsObserver.observe({ type: 'layout-shift', buffered: true })
|
||||
fidObserver.observe({ type: 'first-input', buffered: true })
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
}
|
||||
|
||||
window.addEventListener('error', (event) => {
|
||||
if (telemetryEnabled && !event.defaultPrevented) {
|
||||
addToBatch({
|
||||
category: 'error',
|
||||
action: 'unhandled_error',
|
||||
message: event.error?.message || event.message || 'Unknown error',
|
||||
url: window.location.pathname,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
if (telemetryEnabled) {
|
||||
addToBatch({
|
||||
category: 'error',
|
||||
action: 'unhandled_rejection',
|
||||
message: event.reason?.message || String(event.reason) || 'Unhandled promise rejection',
|
||||
url: window.location.pathname,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user