@runhuman/sensor 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../../shared/src/routes/route-builder.ts","../../shared/src/routes/web-routes.ts","../../shared/src/routes/api-routes.ts","../src/api-client.ts","../src/event-buffer.ts","../src/interceptors/network.interceptor.ts","../src/interceptors/console.interceptor.ts","../src/interceptors/error.interceptor.ts","../src/interceptors/index.ts","../src/session-manager.ts","../src/deep-link-handler.ts","../src/runhuman.ts"],"sourcesContent":["/**\r\n * @runhuman/sensor — Public API\r\n *\r\n * Usage:\r\n * import { Runhuman } from '@runhuman/sensor';\r\n * Runhuman.init({ apiKey: 'rh_your_api_key' });\r\n */\r\n\r\nexport { Runhuman } from './runhuman.js';\r\nexport type { RunhumanConfig } from './runhuman.js';\r\nexport type { TelemetryPlatform } from '@runhuman/shared';\r\n","/**\r\n * Type-safe route builder system\r\n *\r\n * Provides compile-time type checking for route parameters.\r\n * Usage:\r\n * const route = defineRoute('/dashboard/:projectId/jobs/:jobId?');\r\n * route.build({ projectId: 'abc' }); // '/dashboard/abc/jobs'\r\n * route.build({ projectId: 'abc', jobId: '1' }); // '/dashboard/abc/jobs/1'\r\n */\r\n\r\n/**\r\n * API prefix for backend routes.\r\n * Frontend apiClient adds this prefix automatically.\r\n * Backend should use withApiPrefix() to add this to route patterns.\r\n */\r\nexport const API_PREFIX = '/api' as const;\r\n\r\n/**\r\n * Prepends the API prefix to a route pattern.\r\n * Used by backend to ensure routes match what frontend expects.\r\n *\r\n * @example\r\n * withApiPrefix('/organizations') // => '/api/organizations'\r\n * withApiPrefix(apiRoutes.organizations.pattern) // => '/api/organizations'\r\n */\r\nexport function withApiPrefix<T extends string>(pattern: T): `/api${T}` {\r\n return `${API_PREFIX}${pattern}` as `/api${T}`;\r\n}\r\n\r\n// Helper type to simplify intersected object types into a single object type\r\ntype Simplify<T> = { [K in keyof T]: T[K] } & {};\r\n\r\n/**\r\n * Extracts route parameters from a route pattern string.\r\n * - `:param` becomes required: { param: string }\r\n * - `:param?` becomes optional: { param?: string }\r\n *\r\n * Examples:\r\n * - '/dashboard/:projectId' → { projectId: string }\r\n * - '/dashboard/:projectId/jobs/:jobId?' → { projectId: string; jobId?: string }\r\n * - '/dashboard' → {}\r\n */\r\nexport type ExtractRouteParams<T extends string> =\r\n // Optional param followed by more path: :param?/rest\r\n T extends `${string}:${infer Param}?/${infer Rest}`\r\n ? Simplify<{ [K in Param]?: string } & ExtractRouteParams<`/${Rest}`>>\r\n : // Required param followed by more path: :param/rest\r\n T extends `${string}:${infer Param}/${infer Rest}`\r\n ? Simplify<{ [K in Param]: string } & ExtractRouteParams<`/${Rest}`>>\r\n : // Optional param at end: :param?\r\n T extends `${string}:${infer Param}?`\r\n ? { [K in Param]?: string }\r\n : // Required param at end: :param\r\n T extends `${string}:${infer Param}`\r\n ? { [K in Param]: string }\r\n : // No params\r\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\r\n {};\r\n\r\n/**\r\n * Check if a type is an empty object (no params)\r\n */\r\ntype IsEmptyObject<T> = keyof T extends never ? true : false;\r\n\r\n/**\r\n * Route object with pattern and type-safe build function\r\n */\r\nexport interface Route<TPattern extends string> {\r\n /** The raw route pattern with :param placeholders */\r\n pattern: TPattern;\r\n\r\n /**\r\n * Build a URL from the route pattern and params.\r\n * - If route has no params: build() takes no arguments\r\n * - If route has params: build(params) requires matching params object\r\n */\r\n build: IsEmptyObject<ExtractRouteParams<TPattern>> extends true\r\n ? () => string\r\n : (params: ExtractRouteParams<TPattern>) => string;\r\n}\r\n\r\n/**\r\n * Creates a type-safe route builder.\r\n *\r\n * @param pattern Route pattern with :param placeholders (e.g., '/dashboard/:projectId/jobs')\r\n * @returns Route object with pattern and build function\r\n *\r\n * @example\r\n * const route = defineRoute('/dashboard/:projectId/jobs/:jobId?');\r\n * route.pattern; // '/dashboard/:projectId/jobs/:jobId?'\r\n * route.build({ projectId: 'abc' }); // '/dashboard/abc/jobs'\r\n * route.build({ projectId: 'abc', jobId: '123' }); // '/dashboard/abc/jobs/123'\r\n */\r\nexport function defineRoute<TPattern extends string>(\r\n pattern: TPattern\r\n): Route<TPattern> {\r\n const build = (params?: Record<string, string | undefined>): string => {\r\n if (!params) return pattern;\r\n\r\n let result = pattern as string;\r\n\r\n // Replace all :param and :param? placeholders\r\n for (const [key, value] of Object.entries(params)) {\r\n if (value !== undefined) {\r\n // Replace both :param and :param? variants\r\n result = result.replace(`:${key}?`, value);\r\n result = result.replace(`:${key}`, value);\r\n }\r\n }\r\n\r\n // Remove any remaining optional params that weren't provided\r\n result = result.replace(/\\/:[^/]+\\?/g, '');\r\n\r\n // Clean up any double slashes that might result from removal\r\n result = result.replace(/\\/+/g, '/');\r\n\r\n // Remove trailing slash unless it's the root\r\n if (result.length > 1 && result.endsWith('/')) {\r\n result = result.slice(0, -1);\r\n }\r\n\r\n return result;\r\n };\r\n\r\n return {\r\n pattern,\r\n build: build as Route<TPattern>['build'],\r\n };\r\n}\r\n","/**\r\n * Frontend route definitions for the web dashboard\r\n */\r\nimport { defineRoute } from './route-builder.js';\r\n\r\n/**\r\n * All frontend routes for the web application.\r\n *\r\n * Usage:\r\n * import { webRoutes } from '@runhuman/shared';\r\n * navigate(webRoutes.playground.build({ projectId: 'abc' }));\r\n * // => '/dashboard/abc/playground'\r\n */\r\nexport const webRoutes = {\r\n // ============================================\r\n // Non-project routes (no projectId required)\r\n // ============================================\r\n\r\n /** Dashboard home page */\r\n dashboard: defineRoute('/dashboard'),\r\n\r\n /** Subscription management page */\r\n managePlan: defineRoute('/dashboard/manage-plan'),\r\n\r\n /** Plan selection / upgrade page */\r\n plans: defineRoute('/dashboard/plans'),\r\n\r\n /** Simple job view (redirects to full URL with projectId) */\r\n jobSimple: defineRoute('/dashboard/jobs/:jobId'),\r\n\r\n // ============================================\r\n // Admin routes (@volter.ai only)\r\n // ============================================\r\n\r\n /** Admin panel */\r\n admin: defineRoute('/dashboard/admin'),\r\n\r\n // ============================================\r\n // Organizations routes\r\n // ============================================\r\n\r\n /** Organizations list page */\r\n organizations: defineRoute('/dashboard/organizations'),\r\n\r\n /** Organization dashboard */\r\n organization: defineRoute('/dashboard/organizations/:organizationId'),\r\n\r\n /** Organization members page */\r\n organizationMembers: defineRoute('/dashboard/organizations/:organizationId/members'),\r\n\r\n /** Organization settings page */\r\n organizationSettings: defineRoute('/dashboard/organizations/:organizationId/settings'),\r\n\r\n /** Organization usage/billing page */\r\n organizationUsage: defineRoute('/dashboard/organizations/:organizationId/usage'),\r\n\r\n /** Organization projects page */\r\n organizationProjects: defineRoute('/dashboard/organizations/:organizationId/projects'),\r\n\r\n /** Organization jobs page (jobs across all projects) */\r\n organizationJobs: defineRoute('/dashboard/organizations/:organizationId/jobs'),\r\n\r\n /** Organization API keys page */\r\n organizationApiKeys: defineRoute('/dashboard/organizations/:organizationId/api-keys'),\r\n\r\n /** Organization integrations page */\r\n organizationIntegrations: defineRoute('/dashboard/organizations/:organizationId/integrations'),\r\n\r\n /** Organization GitHub integrations page */\r\n organizationGitHub: defineRoute('/dashboard/organizations/:organizationId/github'),\r\n\r\n /** Organization SSO/SAML configuration page */\r\n organizationSso: defineRoute('/dashboard/organizations/:organizationId/sso'),\r\n\r\n /** Import from GitHub page */\r\n organizationImportGitHub: defineRoute('/dashboard/organizations/:organizationId/import'),\r\n\r\n // ============================================\r\n // User settings routes\r\n // ============================================\r\n\r\n /** Settings root (redirects to account) */\r\n settings: defineRoute('/dashboard/settings'),\r\n\r\n /** Account settings page */\r\n settingsAccount: defineRoute('/dashboard/settings/account'),\r\n\r\n // ============================================\r\n // Tester portal routes\r\n // ============================================\r\n\r\n /** Tester dashboard/portal home */\r\n tester: defineRoute('/tester'),\r\n\r\n /** Tester available jobs list */\r\n testerJobs: defineRoute('/tester/jobs'),\r\n\r\n /** Tester settings page */\r\n testerSettings: defineRoute('/tester/settings'),\r\n\r\n /** Tester active test session */\r\n testerTest: defineRoute('/tester/test/:jobId'),\r\n\r\n // ============================================\r\n // Project routes (require projectId)\r\n // ============================================\r\n\r\n /** Project root (redirects to playground) */\r\n project: defineRoute('/dashboard/:projectId'),\r\n\r\n /** Playground page for testing */\r\n playground: defineRoute('/dashboard/:projectId/playground'),\r\n\r\n /** Jobs list page */\r\n jobs: defineRoute('/dashboard/:projectId/jobs'),\r\n\r\n /** Single job detail page */\r\n job: defineRoute('/dashboard/:projectId/jobs/:jobId'),\r\n\r\n /** Templates page */\r\n templates: defineRoute('/dashboard/:projectId/templates'),\r\n\r\n /** Single template detail (modal) */\r\n template: defineRoute('/dashboard/:projectId/templates/:templateId'),\r\n\r\n /** GitHub issues page */\r\n issues: defineRoute('/dashboard/:projectId/issues'),\r\n\r\n /** Single issue detail (modal) */\r\n issue: defineRoute('/dashboard/:projectId/issues/:issueNumber'),\r\n\r\n /** Issue test sessions page */\r\n issueSessions: defineRoute('/dashboard/:projectId/issue-sessions'),\r\n\r\n /** Single test session detail (modal) */\r\n issueSession: defineRoute('/dashboard/:projectId/issue-sessions/:sessionId'),\r\n\r\n /** Project settings page */\r\n projectSettings: defineRoute('/dashboard/:projectId/settings'),\r\n\r\n /** Flow charts page */\r\n flowCharts: defineRoute('/dashboard/:projectId/flowcharts'),\r\n\r\n /** Single flow chart detail (embedded in dashboard - deprecated, use flowChartView) */\r\n flowChart: defineRoute('/dashboard/:projectId/flowcharts/:flowchartId'),\r\n\r\n /** Full-page flow chart viewer (outside dashboard layout) */\r\n flowChartView: defineRoute('/view/flowchart/:projectId/:flowchartId'),\r\n\r\n // ============================================\r\n // External/public routes\r\n // ============================================\r\n\r\n /** Organization invite acceptance page */\r\n invite: defineRoute('/invite/:token'),\r\n\r\n /** Quick start flow for new users (pre-auth) */\r\n quickStart: defineRoute('/start'),\r\n\r\n /** Public job view with token-secured access */\r\n publicJob: defineRoute('/j/:jobId/:token'),\r\n\r\n /** Public system status page */\r\n statuspage: defineRoute('/statuspage'),\r\n\r\n /** Documentation */\r\n docs: defineRoute('/docs/setup'),\r\n\r\n /** Pricing page */\r\n pricing: defineRoute('/pricing'),\r\n\r\n /** Home page */\r\n home: defineRoute('/'),\r\n\r\n // ============================================\r\n // Learn Platform routes\r\n // ============================================\r\n\r\n /** Learn catalogue (default product) */\r\n learn: defineRoute('/learn'),\r\n\r\n /** Single lesson view */\r\n learnLesson: defineRoute('/learn/:lessonSlug'),\r\n} as const;\r\n\r\n/**\r\n * Type for all web route names\r\n */\r\nexport type WebRouteName = keyof typeof webRoutes;\r\n","/**\r\n * API endpoint definitions\r\n *\r\n * These routes are used by the web frontend to call the API.\r\n * Note: These are relative paths (no /api prefix for the client,\r\n * which adds /api automatically via API_BASE).\r\n */\r\nimport { defineRoute } from './route-builder.js';\r\n\r\n/**\r\n * All API endpoints.\r\n *\r\n * Usage:\r\n * import { apiRoutes } from '@runhuman/shared';\r\n * apiClient(apiRoutes.jobs.build());\r\n * // => '/jobs'\r\n */\r\nexport const apiRoutes = {\r\n // ============================================\r\n // Jobs endpoints\r\n // ============================================\r\n\r\n /** List jobs (GET) or create job (POST) */\r\n jobs: defineRoute('/jobs'),\r\n\r\n /** Get/update/delete specific job */\r\n job: defineRoute('/jobs/:jobId'),\r\n\r\n /** Get job status (for polling) */\r\n jobStatus: defineRoute('/jobs/:jobId/status'),\r\n\r\n /** Update job feedback and rating */\r\n jobFeedback: defineRoute('/jobs/:jobId/feedback'),\r\n\r\n /** Enable/disable public sharing for a job (POST = enable, DELETE = disable) */\r\n jobShare: defineRoute('/jobs/:jobId/share'),\r\n\r\n /** Claim a job (for testers) */\r\n jobClaim: defineRoute('/jobs/:jobId/claim'),\r\n\r\n /** Synchronous job execution */\r\n run: defineRoute('/run'),\r\n\r\n /** Get public job (no auth required, token-secured) */\r\n publicJob: defineRoute('/public/jobs/:jobId/:token'),\r\n\r\n // ============================================\r\n // Tester App Job endpoints (secured by testerToken)\r\n // ============================================\r\n\r\n /** Get/update job for tester app */\r\n testerJob: defineRoute('/tester/jobs/:jobId'),\r\n\r\n /** Get presigned upload URLs for artifacts */\r\n testerJobUploadUrls: defineRoute('/tester/jobs/:jobId/upload-urls'),\r\n\r\n /** Process test results with AI */\r\n testerJobProcessResults: defineRoute('/tester/jobs/:jobId/process-results'),\r\n\r\n /** Get processing status */\r\n testerJobProcessingStatus: defineRoute('/tester/jobs/:jobId/processing/:processingJobId'),\r\n\r\n /** Extract frames from video */\r\n testerJobExtractFrames: defineRoute('/tester/jobs/:jobId/extract-frames'),\r\n\r\n /** Abort job due to platform issue */\r\n testerJobAbort: defineRoute('/tester/jobs/:jobId/abort'),\r\n\r\n /** Mark job as invalid due to bad instructions */\r\n testerJobInvalid: defineRoute('/tester/jobs/:jobId/invalid'),\r\n\r\n /** Notify phase transition from tester app */\r\n testerJobPhase: defineRoute('/tester/jobs/:jobId/phase'),\r\n\r\n // ============================================\r\n // LiveKit endpoints (screen sharing & recording)\r\n // ============================================\r\n\r\n /** Start LiveKit session + get tester token (testerToken auth) */\r\n livekitTesterToken: defineRoute('/tester/jobs/:jobId/livekit-token'),\r\n\r\n /** End LiveKit session + stop egress (testerToken auth) */\r\n livekitEndSession: defineRoute('/tester/jobs/:jobId/livekit-end'),\r\n\r\n /** Save transcript entries from Web Speech API (testerToken auth) */\r\n livekitTranscript: defineRoute('/tester/jobs/:jobId/livekit-transcript'),\r\n\r\n /** Get viewer token for live watching (Clerk JWT auth) */\r\n livekitViewerToken: defineRoute('/jobs/:jobId/livekit-viewer-token'),\r\n\r\n /** Check if LiveKit session is active (Clerk JWT or testerToken auth) */\r\n livekitStatus: defineRoute('/jobs/:jobId/livekit-status'),\r\n\r\n /** Get presigned recording URL (Clerk JWT auth) */\r\n livekitRecording: defineRoute('/jobs/:jobId/livekit-recording'),\r\n\r\n // ============================================\r\n // API Keys endpoints\r\n // ============================================\r\n\r\n /** List API keys (GET) or create key (POST) */\r\n keys: defineRoute('/keys'),\r\n\r\n /** Get/update/delete specific key */\r\n key: defineRoute('/keys/:keyId'),\r\n\r\n /** Revoke an API key */\r\n keyRevoke: defineRoute('/keys/:keyId/revoke'),\r\n\r\n /** Get API key info from header (for authenticated requests) */\r\n keyInfo: defineRoute('/key-info'),\r\n\r\n // ============================================\r\n // Personal Access Tokens (PATs) endpoints\r\n // ============================================\r\n\r\n /** List PATs (GET) or create PAT (POST) */\r\n pats: defineRoute('/pats'),\r\n\r\n /** Get/delete specific PAT */\r\n pat: defineRoute('/pats/:patId'),\r\n\r\n /** Revoke a PAT */\r\n patRevoke: defineRoute('/pats/:patId/revoke'),\r\n\r\n /** Get PAT info from header (for authenticated requests) */\r\n patInfo: defineRoute('/pat-info'),\r\n\r\n // ============================================\r\n // Projects endpoints\r\n // ============================================\r\n\r\n /** List projects (GET) or create project (POST) */\r\n projects: defineRoute('/projects'),\r\n\r\n /** Get/update/delete specific project */\r\n project: defineRoute('/projects/:projectId'),\r\n\r\n /** Get jobs for a project */\r\n projectJobs: defineRoute('/projects/:projectId/jobs'),\r\n\r\n /** Get API keys for a project */\r\n projectApiKeys: defineRoute('/projects/:projectId/api-keys'),\r\n\r\n /** Transfer a project to another user or organization */\r\n projectTransfer: defineRoute('/projects/:projectId/transfer'),\r\n\r\n /** List templates for a project (GET) or create template (POST) */\r\n projectTemplates: defineRoute('/projects/:projectId/templates'),\r\n\r\n /** Get/update/delete specific template */\r\n projectTemplate: defineRoute('/projects/:projectId/templates/:templateId'),\r\n\r\n /** List flow charts for a project (GET) or upload flow chart (POST) */\r\n projectFlowCharts: defineRoute('/projects/:projectId/flowcharts'),\r\n\r\n /** Upload flow chart to a project */\r\n projectFlowChartsUpload: defineRoute('/projects/:projectId/flowcharts/upload'),\r\n\r\n /** Get/delete specific flow chart */\r\n projectFlowChart: defineRoute('/projects/:projectId/flowcharts/:flowchartId'),\r\n\r\n /** Get flow chart data */\r\n projectFlowChartData: defineRoute('/projects/:projectId/flowcharts/:flowchartId/data'),\r\n\r\n /** Chat with AI about a flow chart */\r\n projectFlowChartChat: defineRoute('/projects/:projectId/flowcharts/:flowchartId/chat'),\r\n\r\n /** Enable/disable public sharing for a flow chart */\r\n projectFlowChartShare: defineRoute('/projects/:projectId/flowcharts/:flowchartId/share'),\r\n\r\n /** Bulk create projects from GitHub repos */\r\n bulkCreateProjects: defineRoute('/projects/bulk'),\r\n\r\n // ============================================\r\n // GitHub endpoints\r\n // ============================================\r\n\r\n /** GitHub OAuth authorize */\r\n githubOAuthAuthorize: defineRoute('/github/oauth/authorize'),\r\n\r\n /** GitHub OAuth callback */\r\n githubCallback: defineRoute('/github/oauth/callback'),\r\n\r\n /** Link GitHub account (get GitHub username via OAuth) */\r\n githubLink: defineRoute('/github/link'),\r\n\r\n /** List issues for a repo (by owner/repo) */\r\n githubIssuesByRepo: defineRoute('/github/issues/:owner/:repo'),\r\n\r\n /** List issues (by projectId query param) */\r\n githubIssues: defineRoute('/github/issues'),\r\n\r\n /** Get a single issue */\r\n githubIssue: defineRoute('/github/issues/:issueNumber'),\r\n\r\n /** Get comments for an issue */\r\n githubIssueComments: defineRoute('/github/issues/:issueNumber/comments'),\r\n\r\n /** Get labels for a repo */\r\n githubIssueLabels: defineRoute('/github/issues/labels'),\r\n\r\n /** Get assignees for a repo */\r\n githubIssueAssignees: defineRoute('/github/issues/assignees'),\r\n\r\n /** Test a single issue */\r\n githubIssueTest: defineRoute('/github/issues/test'),\r\n\r\n /** Bulk test multiple issues */\r\n githubIssuesBulkTest: defineRoute('/github/issues/bulk-test'),\r\n\r\n /** List test sessions */\r\n githubTestSessions: defineRoute('/github/issues/test-sessions'),\r\n\r\n /** Get/delete a test session */\r\n githubTestSession: defineRoute('/github/issues/test-sessions/:sessionId'),\r\n\r\n /** Mark a test session as seen */\r\n githubTestSessionSeen: defineRoute('/github/issues/test-sessions/:sessionId/seen'),\r\n\r\n /** Get counts of running and unseen test sessions */\r\n githubTestSessionsCounts: defineRoute('/github/issues/test-sessions/counts'),\r\n\r\n /** Bulk test GitHub issues (legacy route) */\r\n githubBulkTest: defineRoute('/github/bulk-test'),\r\n\r\n /** GitHub App webhooks (receives issue_comment events etc.) */\r\n githubWebhooks: defineRoute('/github/webhooks'),\r\n\r\n // ============================================\r\n // Auth endpoints (Clerk-based)\r\n // ============================================\r\n\r\n /** Sync user data from Clerk after sign-in */\r\n authSync: defineRoute('/auth/sync'),\r\n\r\n /** Get current user info */\r\n authMe: defineRoute('/auth/me'),\r\n\r\n /** Mark user as startup (bonus tokens) */\r\n authStartup: defineRoute('/auth/startup'),\r\n\r\n /** Delete user account and all associated data */\r\n authDeleteAccount: defineRoute('/auth/account'),\r\n\r\n /** Preview account deletion impact (owned orgs, members, projects) */\r\n authDeletionPreview: defineRoute('/auth/account/deletion-preview'),\r\n\r\n // ============================================\r\n // Billing endpoints (Polar-based credits)\r\n // ============================================\r\n\r\n /** Get user's credit balance from Polar */\r\n billingBalance: defineRoute('/billing/balance'),\r\n\r\n /** Check if user has available credits */\r\n billingHasCredits: defineRoute('/billing/has-credits'),\r\n\r\n /** Get a customer portal URL for managing billing */\r\n billingPortal: defineRoute('/billing/portal'),\r\n\r\n /** Create a checkout session for purchasing credits */\r\n billingCheckout: defineRoute('/billing/checkout'),\r\n\r\n /** Get active subscription details (tier, cycle, etc.) */\r\n billingSubscription: defineRoute('/billing/subscription'),\r\n\r\n /** Update subscription plan (change tier/cycle) */\r\n billingChangePlan: defineRoute('/billing/change-plan'),\r\n\r\n // ============================================\r\n // Other endpoints\r\n // ============================================\r\n\r\n /** Health check */\r\n health: defineRoute('/health'),\r\n\r\n /** Detailed system status */\r\n status: defineRoute('/status'),\r\n\r\n /** List templates */\r\n templates: defineRoute('/templates'),\r\n\r\n /** Issue analyzer */\r\n issueAnalyzer: defineRoute('/issue-analyzer'),\r\n\r\n /** PR analyzer */\r\n prAnalyzer: defineRoute('/pr-analyzer'),\r\n\r\n /** Logs endpoint */\r\n logs: defineRoute('/logs'),\r\n\r\n // ============================================\r\n // Relevant Issues endpoints\r\n // ============================================\r\n\r\n /** Run relevant issues discovery for user */\r\n relevantIssuesDiscover: defineRoute('/relevant-issues/discover'),\r\n\r\n /** Get latest cached relevant issues result */\r\n relevantIssuesLatest: defineRoute('/relevant-issues/latest'),\r\n\r\n // ============================================\r\n // Onboarding endpoints\r\n // ============================================\r\n\r\n /** Get/update user's onboarding state */\r\n onboarding: defineRoute('/onboarding'),\r\n\r\n /** Mark onboarding as complete */\r\n onboardingComplete: defineRoute('/onboarding/complete'),\r\n\r\n /** Check if user needs onboarding */\r\n onboardingCheck: defineRoute('/onboarding/check'),\r\n\r\n // ============================================\r\n // Search endpoints\r\n // ============================================\r\n\r\n /** Global search across jobs, projects, issues, sessions */\r\n search: defineRoute('/search'),\r\n\r\n // ============================================\r\n // Tester Profile endpoints\r\n // ============================================\r\n\r\n /** Apply to become a tester */\r\n testerApply: defineRoute('/tester/apply'),\r\n\r\n /** Get/update own tester profile */\r\n testerProfile: defineRoute('/tester/profile'),\r\n\r\n /** Get public tester profile */\r\n testerPublicProfile: defineRoute('/testers/:testerId/profile'),\r\n\r\n /** Download tester app */\r\n testerDownloadApp: defineRoute('/tester/download-app'),\r\n\r\n /** Download browser extension */\r\n testerDownloadExtension: defineRoute('/tester/download-extension'),\r\n\r\n /** Download mobile tester app (Android APK) */\r\n testerDownloadMobileApp: defineRoute('/tester/download-mobile-app'),\r\n\r\n /** List available (unclaimed) jobs for testers */\r\n testerAvailableJobs: defineRoute('/tester/jobs/available'),\r\n\r\n /** Get presigned URL for avatar upload */\r\n testerAvatarUploadUrl: defineRoute('/tester/profile/avatar-upload-url'),\r\n\r\n /** Get tester app version */\r\n testerAppVersion: defineRoute('/tester/app-version'),\r\n\r\n /** Register/unregister Expo push token for mobile notifications */\r\n testerPushToken: defineRoute('/tester/push-token'),\r\n\r\n // ============================================\r\n // Extension endpoints (browser extension)\r\n // ============================================\r\n\r\n /** List extension tokens (GET) or create token (POST) */\r\n extensionTokens: defineRoute('/tester/extension-tokens'),\r\n\r\n /** Revoke (DELETE) a specific extension token */\r\n extensionToken: defineRoute('/tester/extension-tokens/:tokenId'),\r\n\r\n /** WebSocket endpoint for extension data streaming */\r\n extensionStream: defineRoute('/extension/stream'),\r\n\r\n /** Correlate extension data to a test job's time window */\r\n testerJobCorrelate: defineRoute('/tester/jobs/:jobId/correlate'),\r\n\r\n /** Check if extension data is flowing for this tester (polling endpoint) */\r\n testerJobDataStatus: defineRoute('/tester/jobs/:jobId/data-status'),\r\n\r\n /** Create extension token for auto-configuring the extension from the test page */\r\n testerJobExtensionToken: defineRoute('/tester/jobs/:jobId/extension-token'),\r\n\r\n // ============================================\r\n // Repo Templates endpoints\r\n // ============================================\r\n\r\n /** List templates from a GitHub repo's .runhuman/templates/ directory */\r\n repoTemplates: defineRoute('/repos/:owner/:repo/templates'),\r\n\r\n /** Get a single template from a GitHub repo */\r\n repoTemplate: defineRoute('/repos/:owner/:repo/templates/:templateName'),\r\n\r\n // ============================================\r\n // Organizations endpoints\r\n // ============================================\r\n\r\n /** List organizations (GET) or create organization (POST) */\r\n organizations: defineRoute('/organizations'),\r\n\r\n /** Get/update/delete specific organization */\r\n organization: defineRoute('/organizations/:organizationId'),\r\n\r\n /** List organization members */\r\n organizationMembers: defineRoute('/organizations/:organizationId/members'),\r\n\r\n /** Invite member to organization */\r\n organizationInvite: defineRoute('/organizations/:organizationId/invite'),\r\n\r\n /** Remove member from organization */\r\n organizationMember: defineRoute('/organizations/:organizationId/members/:userId'),\r\n\r\n /** List organization projects */\r\n organizationProjects: defineRoute('/organizations/:organizationId/projects'),\r\n\r\n /** Get organization jobs (jobs across all projects in the organization) */\r\n organizationJobs: defineRoute('/organizations/:organizationId/jobs'),\r\n\r\n /** Transfer organization ownership */\r\n organizationTransferOwnership: defineRoute('/organizations/:organizationId/transfer-ownership'),\r\n\r\n /** Create organization API key */\r\n organizationApiKeys: defineRoute('/organizations/:organizationId/api-keys'),\r\n\r\n /** Get GitHub installations for an organization */\r\n organizationGitHubInstallations: defineRoute('/organizations/:organizationId/github/installations'),\r\n\r\n /** Get/delete specific GitHub installation for an organization */\r\n organizationGitHubInstallation: defineRoute('/organizations/:organizationId/github/installations/:installationId'),\r\n\r\n /** List repos accessible to an organization via its GitHub installations */\r\n organizationGitHubRepos: defineRoute('/organizations/:organizationId/github/repos'),\r\n\r\n /** Refresh repos for an organization's GitHub installation */\r\n organizationGitHubInstallationRefresh: defineRoute('/organizations/:organizationId/github/installations/:installationId/refresh'),\r\n\r\n /** Check if org has access to a specific repo via its GitHub installations */\r\n organizationGitHubRepoCheckAccess: defineRoute('/organizations/:organizationId/github/repos/check-access'),\r\n\r\n /** Find deployed URL for a repo via org's GitHub installations */\r\n organizationGitHubRepoFindUrl: defineRoute('/organizations/:organizationId/github/repos/find-url'),\r\n\r\n /** Get organization billing balance (Polar) */\r\n organizationBilling: defineRoute('/organizations/:organizationId/billing'),\r\n\r\n // ============================================\r\n // Transfers endpoints\r\n // ============================================\r\n\r\n /** Get pending incoming transfers for current user */\r\n transfersPending: defineRoute('/transfers/pending'),\r\n\r\n /** Get pending outgoing transfers (initiated by current user) */\r\n transfersOutgoing: defineRoute('/transfers/outgoing'),\r\n\r\n /** Accept a transfer */\r\n transferAccept: defineRoute('/transfers/:transferId/accept'),\r\n\r\n /** Reject a transfer */\r\n transferReject: defineRoute('/transfers/:transferId/reject'),\r\n\r\n /** Cancel a transfer */\r\n transferCancel: defineRoute('/transfers/:transferId/cancel'),\r\n\r\n // ============================================\r\n // Invites endpoints (public)\r\n // ============================================\r\n\r\n /** Get invite details by token (public) */\r\n invite: defineRoute('/invites/:token'),\r\n\r\n /** Redeem an invite (authenticated) */\r\n inviteRedeem: defineRoute('/invites/:token/redeem'),\r\n\r\n // ============================================\r\n // User Agreements endpoints\r\n // ============================================\r\n\r\n /** Record user agreement acceptance (POST) or get user's agreements (GET) */\r\n agreements: defineRoute('/agreements'),\r\n\r\n /** Check if user has accepted a specific agreement */\r\n agreementCheck: defineRoute('/agreements/check'),\r\n\r\n // ============================================\r\n // Organization SSO endpoints\r\n // ============================================\r\n\r\n /** List SSO connections for an organization (GET) or create one (POST) */\r\n organizationSsoConnections: defineRoute('/organizations/:organizationId/sso/connections'),\r\n\r\n /** Get/delete a specific SSO connection */\r\n organizationSsoConnection: defineRoute('/organizations/:organizationId/sso/connections/:connectionId'),\r\n\r\n // ============================================\r\n // Enterprise endpoints\r\n // ============================================\r\n\r\n /** Submit enterprise contact inquiry (POST) */\r\n enterpriseInquiry: defineRoute('/enterprise/inquiry'),\r\n\r\n // ============================================\r\n // Changelog endpoints\r\n // ============================================\r\n\r\n /** List changelog entries (public) */\r\n changelog: defineRoute('/changelog'),\r\n\r\n /** Get unread changelog count (authenticated) */\r\n changelogUnreadCount: defineRoute('/changelog/unread-count'),\r\n\r\n /** Mark changelog as read (authenticated) */\r\n changelogMarkRead: defineRoute('/changelog/mark-read'),\r\n\r\n // ============================================\r\n // Telemetry SDK endpoints\r\n // ============================================\r\n\r\n /** Create telemetry session (POST) — SDK calls this when a test session starts */\r\n telemetrySessions: defineRoute('/telemetry/sessions'),\r\n\r\n /** End telemetry session (POST) — SDK calls this when testing completes */\r\n telemetrySessionEnd: defineRoute('/telemetry/sessions/:sessionId/end'),\r\n\r\n /** Submit telemetry event batch (POST) — SDK periodically flushes captured events */\r\n telemetryBatch: defineRoute('/telemetry/sessions/:sessionId/events'),\r\n\r\n /** Check session status for a job (GET) — SDK polls to know when to activate */\r\n telemetrySessionStatus: defineRoute('/telemetry/jobs/:jobId/status'),\r\n} as const;\r\n\r\n/**\r\n * Type for all API route names\r\n */\r\nexport type ApiRouteName = keyof typeof apiRoutes;\r\n","/**\r\n * Telemetry API Client\r\n *\r\n * Thin HTTP client wrapping the 4 Phase 0 telemetry endpoints.\r\n * Captures a reference to fetch at construction time — before the\r\n * network interceptor patches globalThis.fetch — to avoid recursion.\r\n */\r\n\r\nimport { apiRoutes, API_PREFIX } from '@runhuman/shared';\r\nimport type {\r\n CreateTelemetrySessionRequest,\r\n CreateTelemetrySessionResponse,\r\n TelemetryBatchRequest,\r\n TelemetryBatchResponse,\r\n EndTelemetrySessionResponse,\r\n TelemetrySessionStatusResponse,\r\n} from '@runhuman/shared';\r\n\r\nexport class ApiClient {\r\n private readonly baseUrl: string;\r\n private readonly apiKey: string;\r\n // Captured at construction time, before interceptors patch globalThis.fetch\r\n private readonly fetchFn: typeof globalThis.fetch;\r\n\r\n constructor(baseUrl: string, apiKey: string) {\r\n this.baseUrl = baseUrl.replace(/\\/$/, '');\r\n this.apiKey = apiKey;\r\n this.fetchFn = globalThis.fetch.bind(globalThis);\r\n }\r\n\r\n async createSession(\r\n req: CreateTelemetrySessionRequest,\r\n ): Promise<CreateTelemetrySessionResponse> {\r\n return this.post(\r\n apiRoutes.telemetrySessions.build(),\r\n req,\r\n );\r\n }\r\n\r\n async submitBatch(\r\n sessionId: string,\r\n req: TelemetryBatchRequest,\r\n ): Promise<TelemetryBatchResponse> {\r\n return this.post(\r\n apiRoutes.telemetryBatch.build({ sessionId }),\r\n req,\r\n );\r\n }\r\n\r\n async endSession(sessionId: string): Promise<EndTelemetrySessionResponse> {\r\n return this.post(\r\n apiRoutes.telemetrySessionEnd.build({ sessionId }),\r\n undefined,\r\n );\r\n }\r\n\r\n async getSessionStatus(jobId: string): Promise<TelemetrySessionStatusResponse> {\r\n return this.get(apiRoutes.telemetrySessionStatus.build({ jobId }));\r\n }\r\n\r\n private url(routePath: string): string {\r\n return `${this.baseUrl}${API_PREFIX}${routePath}`;\r\n }\r\n\r\n private async post<T>(routePath: string, body: unknown): Promise<T> {\r\n const response = await this.fetchFn(this.url(routePath), {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n Authorization: `Bearer ${this.apiKey}`,\r\n },\r\n body: body !== undefined ? JSON.stringify(body) : undefined,\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text();\r\n throw new Error(`Runhuman API error ${response.status}: ${text}`);\r\n }\r\n\r\n return response.json() as Promise<T>;\r\n }\r\n\r\n private async get<T>(routePath: string): Promise<T> {\r\n const response = await this.fetchFn(this.url(routePath), {\r\n method: 'GET',\r\n headers: {\r\n Authorization: `Bearer ${this.apiKey}`,\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text();\r\n throw new Error(`Runhuman API error ${response.status}: ${text}`);\r\n }\r\n\r\n return response.json() as Promise<T>;\r\n }\r\n}\r\n","/**\r\n * Event Buffer\r\n *\r\n * In-memory ring buffer for telemetry events.\r\n * Events are pushed by interceptors and drained by the flush cycle.\r\n * FIFO eviction when at max capacity.\r\n */\r\n\r\nimport type { TelemetryBatchEvent } from '@runhuman/shared';\r\n\r\nexport class EventBuffer {\r\n private buffer: TelemetryBatchEvent[] = [];\r\n private readonly maxCapacity: number;\r\n\r\n constructor(maxCapacity = 1000) {\r\n this.maxCapacity = maxCapacity;\r\n }\r\n\r\n get size(): number {\r\n return this.buffer.length;\r\n }\r\n\r\n push(event: TelemetryBatchEvent): void {\r\n if (this.buffer.length >= this.maxCapacity) {\r\n this.buffer.shift();\r\n }\r\n this.buffer.push(event);\r\n }\r\n\r\n drain(): TelemetryBatchEvent[] {\r\n const events = this.buffer;\r\n this.buffer = [];\r\n return events;\r\n }\r\n\r\n clear(): void {\r\n this.buffer = [];\r\n }\r\n}\r\n","/**\r\n * Network Interceptor\r\n *\r\n * Patches globalThis.fetch to capture HTTP requests.\r\n * Privacy: strips Authorization, Cookie, Set-Cookie headers.\r\n * No request/response bodies are captured.\r\n */\r\n\r\nimport type { TelemetryBatchEvent } from '@runhuman/shared';\r\nimport type { EventCallback, Interceptor } from './types.js';\r\n\r\nconst STRIPPED_HEADERS = new Set([\r\n 'authorization',\r\n 'cookie',\r\n 'set-cookie',\r\n 'x-api-key',\r\n 'proxy-authorization',\r\n]);\r\n\r\nfunction scrubHeaders(headers: Headers): Record<string, string> {\r\n const result: Record<string, string> = {};\r\n headers.forEach((value, key) => {\r\n result[key] = STRIPPED_HEADERS.has(key.toLowerCase()) ? '[REDACTED]' : value;\r\n });\r\n return result;\r\n}\r\n\r\nexport class NetworkInterceptor implements Interceptor {\r\n private originalFetch: typeof globalThis.fetch | null = null;\r\n private sessionStartTime = 0;\r\n\r\n install(onEvent: EventCallback): void {\r\n this.originalFetch = globalThis.fetch;\r\n this.sessionStartTime = Date.now();\r\n\r\n const captured = this.originalFetch;\r\n const startTime = this.sessionStartTime;\r\n\r\n globalThis.fetch = async function interceptedFetch(\r\n input: RequestInfo | URL,\r\n init?: RequestInit,\r\n ): Promise<Response> {\r\n const requestStart = Date.now();\r\n const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;\r\n const method = init?.method ?? 'GET';\r\n\r\n const response = await captured.call(globalThis, input, init);\r\n\r\n const event: TelemetryBatchEvent = {\r\n type: 'network',\r\n relativeTimestampMs: requestStart - startTime,\r\n clientTimestamp: new Date(requestStart).toISOString(),\r\n data: {\r\n url,\r\n method: method.toUpperCase(),\r\n status: response.status,\r\n statusText: response.statusText,\r\n durationMs: Date.now() - requestStart,\r\n responseHeaders: scrubHeaders(response.headers),\r\n },\r\n };\r\n\r\n onEvent(event);\r\n\r\n return response;\r\n };\r\n }\r\n\r\n uninstall(): void {\r\n if (this.originalFetch) {\r\n globalThis.fetch = this.originalFetch;\r\n this.originalFetch = null;\r\n }\r\n }\r\n\r\n /** Update the session start time (called when a new session begins) */\r\n setSessionStartTime(startTime: number): void {\r\n this.sessionStartTime = startTime;\r\n }\r\n}\r\n","/**\r\n * Console Interceptor\r\n *\r\n * Patches console.log/warn/error/info/debug to capture log output.\r\n * Arguments are stringified to avoid capturing sensitive objects.\r\n */\r\n\r\nimport type { TelemetryBatchEvent } from '@runhuman/shared';\r\nimport type { EventCallback, Interceptor } from './types.js';\r\n\r\nconst CONSOLE_METHODS = ['log', 'warn', 'error', 'info', 'debug'] as const;\r\ntype ConsoleMethod = typeof CONSOLE_METHODS[number];\r\n\r\nfunction stringify(args: unknown[]): string {\r\n return args\r\n .map((arg) => {\r\n if (typeof arg === 'string') return arg;\r\n try {\r\n return JSON.stringify(arg);\r\n } catch {\r\n return String(arg);\r\n }\r\n })\r\n .join(' ');\r\n}\r\n\r\nexport class ConsoleInterceptor implements Interceptor {\r\n private originals: Partial<Record<ConsoleMethod, (...args: unknown[]) => void>> = {};\r\n private sessionStartTime = 0;\r\n\r\n install(onEvent: EventCallback): void {\r\n this.sessionStartTime = Date.now();\r\n const startTime = this.sessionStartTime;\r\n\r\n for (const method of CONSOLE_METHODS) {\r\n this.originals[method] = console[method].bind(console);\r\n\r\n console[method] = (...args: unknown[]) => {\r\n const now = Date.now();\r\n\r\n const event: TelemetryBatchEvent = {\r\n type: 'console',\r\n relativeTimestampMs: now - startTime,\r\n clientTimestamp: new Date(now).toISOString(),\r\n data: {\r\n level: method,\r\n message: stringify(args),\r\n },\r\n };\r\n\r\n onEvent(event);\r\n\r\n // Call the original so console output still appears\r\n this.originals[method]!(...args);\r\n };\r\n }\r\n }\r\n\r\n uninstall(): void {\r\n for (const method of CONSOLE_METHODS) {\r\n if (this.originals[method]) {\r\n console[method] = this.originals[method]!;\r\n }\r\n }\r\n this.originals = {};\r\n }\r\n\r\n setSessionStartTime(startTime: number): void {\r\n this.sessionStartTime = startTime;\r\n }\r\n}\r\n","/**\r\n * Error Interceptor\r\n *\r\n * Captures unhandled errors via:\r\n * - React Native's ErrorUtils.setGlobalHandler (when available)\r\n * - globalThis.onerror fallback (web/Node)\r\n *\r\n * Fatal errors emit 'crash', non-fatal emit 'error'.\r\n */\r\n\r\nimport type { TelemetryBatchEvent } from '@runhuman/shared';\r\nimport type { EventCallback, Interceptor } from './types.js';\r\n\r\n// React Native exposes ErrorUtils globally\r\ninterface ErrorUtilsType {\r\n getGlobalHandler(): (error: Error, isFatal: boolean) => void;\r\n setGlobalHandler(handler: (error: Error, isFatal: boolean) => void): void;\r\n}\r\n\r\ndeclare const ErrorUtils: ErrorUtilsType | undefined;\r\n\r\nexport class ErrorInterceptor implements Interceptor {\r\n private previousHandler: ((error: Error, isFatal: boolean) => void) | null = null;\r\n private previousOnError: typeof globalThis.onerror | null = null;\r\n private sessionStartTime = 0;\r\n\r\n install(onEvent: EventCallback): void {\r\n this.sessionStartTime = Date.now();\r\n const startTime = this.sessionStartTime;\r\n\r\n // React Native path\r\n if (typeof ErrorUtils !== 'undefined') {\r\n this.previousHandler = ErrorUtils.getGlobalHandler();\r\n const prev = this.previousHandler;\r\n\r\n ErrorUtils.setGlobalHandler((error: Error, isFatal: boolean) => {\r\n const now = Date.now();\r\n\r\n const event: TelemetryBatchEvent = {\r\n type: isFatal ? 'crash' : 'error',\r\n relativeTimestampMs: now - startTime,\r\n clientTimestamp: new Date(now).toISOString(),\r\n data: {\r\n message: error.message,\r\n stack: error.stack,\r\n name: error.name,\r\n isFatal,\r\n },\r\n };\r\n\r\n onEvent(event);\r\n\r\n // Forward to previous handler\r\n prev(error, isFatal);\r\n });\r\n return;\r\n }\r\n\r\n // Web/Node fallback\r\n this.previousOnError = globalThis.onerror;\r\n\r\n globalThis.onerror = (\r\n message: Event | string,\r\n source?: string,\r\n lineno?: number,\r\n colno?: number,\r\n error?: Error,\r\n ) => {\r\n const now = Date.now();\r\n\r\n const event: TelemetryBatchEvent = {\r\n type: 'error',\r\n relativeTimestampMs: now - startTime,\r\n clientTimestamp: new Date(now).toISOString(),\r\n data: {\r\n message: typeof message === 'string' ? message : message.type,\r\n stack: error?.stack,\r\n name: error?.name,\r\n source,\r\n lineno,\r\n colno,\r\n },\r\n };\r\n\r\n onEvent(event);\r\n\r\n if (typeof this.previousOnError === 'function') {\r\n return (this.previousOnError as (...args: unknown[]) => unknown).call(globalThis, message, source, lineno, colno, error);\r\n }\r\n };\r\n }\r\n\r\n uninstall(): void {\r\n if (typeof ErrorUtils !== 'undefined' && this.previousHandler) {\r\n ErrorUtils.setGlobalHandler(this.previousHandler);\r\n this.previousHandler = null;\r\n }\r\n\r\n if (this.previousOnError !== null) {\r\n globalThis.onerror = this.previousOnError;\r\n this.previousOnError = null;\r\n }\r\n }\r\n\r\n setSessionStartTime(startTime: number): void {\r\n this.sessionStartTime = startTime;\r\n }\r\n}\r\n","/**\r\n * Interceptor Manager\r\n *\r\n * Aggregates all interceptors into a single install/uninstall surface.\r\n */\r\n\r\nimport type { EventCallback, Interceptor } from './types.js';\r\nimport { NetworkInterceptor } from './network.interceptor.js';\r\nimport { ConsoleInterceptor } from './console.interceptor.js';\r\nimport { ErrorInterceptor } from './error.interceptor.js';\r\n\r\nexport class InterceptorManager implements Interceptor {\r\n private readonly interceptors: Interceptor[];\r\n\r\n readonly network: NetworkInterceptor;\r\n readonly console: ConsoleInterceptor;\r\n readonly error: ErrorInterceptor;\r\n\r\n constructor() {\r\n this.network = new NetworkInterceptor();\r\n this.console = new ConsoleInterceptor();\r\n this.error = new ErrorInterceptor();\r\n this.interceptors = [this.network, this.console, this.error];\r\n }\r\n\r\n install(onEvent: EventCallback): void {\r\n for (const interceptor of this.interceptors) {\r\n interceptor.install(onEvent);\r\n }\r\n }\r\n\r\n uninstall(): void {\r\n // Uninstall in reverse order\r\n for (let i = this.interceptors.length - 1; i >= 0; i--) {\r\n this.interceptors[i].uninstall();\r\n }\r\n }\r\n\r\n setSessionStartTime(startTime: number): void {\r\n this.network.setSessionStartTime(startTime);\r\n this.console.setSessionStartTime(startTime);\r\n this.error.setSessionStartTime(startTime);\r\n }\r\n}\r\n\r\nexport type { Interceptor, EventCallback } from './types.js';\r\n","/**\r\n * Session Manager\r\n *\r\n * Orchestrates the sensor lifecycle:\r\n * idle → polling → active → ending → idle\r\n *\r\n * - Polls the status endpoint to detect when a tester starts a job\r\n * - Creates a telemetry session and installs interceptors\r\n * - Periodically flushes buffered events to the API\r\n * - Ends the session when the job is no longer active\r\n */\r\n\r\nimport type { TelemetryPlatform } from '@runhuman/shared';\r\nimport type { ApiClient } from './api-client.js';\r\nimport { EventBuffer } from './event-buffer.js';\r\nimport { InterceptorManager } from './interceptors/index.js';\r\n\r\nexport type SessionState = 'idle' | 'polling' | 'active' | 'ending';\r\n\r\nexport interface SessionManagerConfig {\r\n apiClient: ApiClient;\r\n platform: TelemetryPlatform;\r\n sdkVersion: string;\r\n flushIntervalMs: number;\r\n pollIntervalMs: number;\r\n maxBufferSize: number;\r\n debug: boolean;\r\n}\r\n\r\nexport class SessionManager {\r\n private state: SessionState = 'idle';\r\n private sessionId: string | null = null;\r\n private activeJobId: string | null = null;\r\n private pollTimer: ReturnType<typeof setInterval> | null = null;\r\n private flushTimer: ReturnType<typeof setInterval> | null = null;\r\n private readonly buffer: EventBuffer;\r\n private readonly interceptors: InterceptorManager;\r\n private readonly config: SessionManagerConfig;\r\n\r\n constructor(config: SessionManagerConfig) {\r\n this.config = config;\r\n this.buffer = new EventBuffer(config.maxBufferSize);\r\n this.interceptors = new InterceptorManager();\r\n }\r\n\r\n getState(): SessionState {\r\n return this.state;\r\n }\r\n\r\n getSessionId(): string | null {\r\n return this.sessionId;\r\n }\r\n\r\n /**\r\n * Start polling for job activation.\r\n * The sensor calls this on init — polling continues until a job is detected\r\n * or stopPolling() is called.\r\n */\r\n startPolling(jobId: string): void {\r\n if (this.state !== 'idle') return;\r\n\r\n this.activeJobId = jobId;\r\n this.state = 'polling';\r\n this.log('Polling started', { jobId });\r\n\r\n this.pollTimer = setInterval(() => this.pollOnce(), this.config.pollIntervalMs);\r\n // Also poll immediately\r\n this.pollOnce();\r\n }\r\n\r\n stopPolling(): void {\r\n if (this.pollTimer) {\r\n clearInterval(this.pollTimer);\r\n this.pollTimer = null;\r\n }\r\n if (this.state === 'polling') {\r\n this.state = 'idle';\r\n this.log('Polling stopped');\r\n }\r\n }\r\n\r\n /**\r\n * Activate a session immediately (e.g., from a deep link).\r\n * Skips polling — goes straight to session creation.\r\n */\r\n async activate(jobId: string): Promise<void> {\r\n if (this.state === 'active') {\r\n this.log('Already active, ignoring activate()', { jobId });\r\n return;\r\n }\r\n\r\n this.stopPolling();\r\n this.activeJobId = jobId;\r\n await this.startSession();\r\n }\r\n\r\n /**\r\n * End the current session and return to idle.\r\n */\r\n async deactivate(): Promise<void> {\r\n if (this.state !== 'active') return;\r\n await this.endSession();\r\n }\r\n\r\n /**\r\n * Full teardown — stop everything and clean up.\r\n */\r\n async destroy(): Promise<void> {\r\n this.stopPolling();\r\n if (this.state === 'active') {\r\n await this.endSession();\r\n }\r\n }\r\n\r\n private async pollOnce(): Promise<void> {\r\n if (this.state !== 'polling' || !this.activeJobId) return;\r\n\r\n try {\r\n const status = await this.config.apiClient.getSessionStatus(this.activeJobId);\r\n\r\n if (status.jobActive && status.sdkEnabled && status.activeSessionId) {\r\n this.log('Job has active session, starting capture', {\r\n jobId: this.activeJobId,\r\n activeSessionId: status.activeSessionId,\r\n });\r\n this.stopPolling();\r\n await this.startSession();\r\n }\r\n } catch (error) {\r\n this.log('Poll failed', { error });\r\n }\r\n }\r\n\r\n private async startSession(): Promise<void> {\r\n this.state = 'active';\r\n const now = new Date();\r\n\r\n const response = await this.config.apiClient.createSession({\r\n jobId: this.activeJobId!,\r\n platform: this.config.platform,\r\n sdkVersion: this.config.sdkVersion,\r\n clientStartTime: now.toISOString(),\r\n });\r\n\r\n this.sessionId = response.sessionId;\r\n const sessionStartTime = now.getTime();\r\n\r\n this.log('Session started', {\r\n sessionId: this.sessionId,\r\n clockOffsetMs: response.clockOffsetMs,\r\n });\r\n\r\n // Set the session start time on interceptors so relativeTimestampMs is correct\r\n this.interceptors.setSessionStartTime(sessionStartTime);\r\n\r\n // Install interceptors — events flow into the buffer\r\n this.interceptors.install((event) => this.buffer.push(event));\r\n\r\n // Start flush timer\r\n this.flushTimer = setInterval(() => this.flush(), this.config.flushIntervalMs);\r\n }\r\n\r\n private async endSession(): Promise<void> {\r\n this.state = 'ending';\r\n\r\n // Stop flush timer and uninstall interceptors\r\n if (this.flushTimer) {\r\n clearInterval(this.flushTimer);\r\n this.flushTimer = null;\r\n }\r\n this.interceptors.uninstall();\r\n\r\n // Final flush\r\n await this.flush();\r\n\r\n // End the session on the server\r\n if (this.sessionId) {\r\n try {\r\n const result = await this.config.apiClient.endSession(this.sessionId);\r\n this.log('Session ended', {\r\n sessionId: this.sessionId,\r\n eventCount: result.eventCount,\r\n correlated: result.correlated,\r\n });\r\n } catch (error) {\r\n this.log('Failed to end session', { sessionId: this.sessionId, error });\r\n }\r\n }\r\n\r\n this.sessionId = null;\r\n this.activeJobId = null;\r\n this.buffer.clear();\r\n this.state = 'idle';\r\n }\r\n\r\n private async flush(): Promise<void> {\r\n if (!this.sessionId) return;\r\n\r\n const events = this.buffer.drain();\r\n if (events.length === 0) return;\r\n\r\n try {\r\n const result = await this.config.apiClient.submitBatch(this.sessionId, { events });\r\n this.log('Flushed events', {\r\n accepted: result.accepted,\r\n sessionEventCount: result.sessionEventCount,\r\n });\r\n } catch (error) {\r\n this.log('Flush failed, events lost', { count: events.length, error });\r\n }\r\n }\r\n\r\n private log(message: string, data?: Record<string, unknown>): void {\r\n if (this.config.debug) {\r\n console.debug(`[Runhuman] ${message}`, data);\r\n }\r\n }\r\n}\r\n","/**\r\n * Deep Link Handler\r\n *\r\n * Listens for React Native deep links matching the pattern:\r\n * <scheme>://runhuman?jobId=<id>\r\n *\r\n * When matched, triggers immediate session activation via SessionManager.\r\n * Falls back gracefully when react-native Linking is unavailable.\r\n */\r\n\r\nimport type { SessionManager } from './session-manager.js';\r\n\r\n// React Native Linking API shape (avoid importing react-native at module level)\r\ninterface LinkingModule {\r\n addEventListener(type: string, handler: (event: { url: string }) => void): { remove(): void };\r\n getInitialURL(): Promise<string | null>;\r\n}\r\n\r\nexport class DeepLinkHandler {\r\n private subscription: { remove(): void } | null = null;\r\n private readonly sessionManager: SessionManager;\r\n private readonly debug: boolean;\r\n\r\n constructor(sessionManager: SessionManager, debug: boolean) {\r\n this.sessionManager = sessionManager;\r\n this.debug = debug;\r\n }\r\n\r\n async install(): Promise<void> {\r\n const Linking = this.getLinking();\r\n if (!Linking) {\r\n this.log('react-native Linking not available, deep links disabled');\r\n return;\r\n }\r\n\r\n // Handle deep links received while app is running\r\n this.subscription = Linking.addEventListener('url', ({ url }) => {\r\n this.handleUrl(url);\r\n });\r\n\r\n // Handle deep link that launched the app (cold start)\r\n const initialUrl = await Linking.getInitialURL();\r\n if (initialUrl) {\r\n this.handleUrl(initialUrl);\r\n }\r\n }\r\n\r\n uninstall(): void {\r\n if (this.subscription) {\r\n this.subscription.remove();\r\n this.subscription = null;\r\n }\r\n }\r\n\r\n private handleUrl(url: string): void {\r\n const jobId = this.extractJobId(url);\r\n if (!jobId) return;\r\n\r\n this.log('Deep link received', { url, jobId });\r\n this.sessionManager.activate(jobId);\r\n }\r\n\r\n /**\r\n * Extract jobId from a URL matching: <scheme>://runhuman?jobId=<id>\r\n * Returns null if the URL doesn't match the expected pattern.\r\n */\r\n private extractJobId(url: string): string | null {\r\n try {\r\n const parsed = new URL(url);\r\n // Match hostname OR pathname containing \"runhuman\"\r\n const isRunhuman =\r\n parsed.hostname === 'runhuman' ||\r\n parsed.pathname.includes('runhuman');\r\n\r\n if (!isRunhuman) return null;\r\n\r\n return parsed.searchParams.get('jobId');\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n private getLinking(): LinkingModule | null {\r\n try {\r\n // Dynamic require to avoid hard dependency on react-native\r\n const { Linking } = require('react-native');\r\n return Linking as LinkingModule;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n private log(message: string, data?: Record<string, unknown>): void {\r\n if (this.debug) {\r\n console.debug(`[Runhuman] ${message}`, data);\r\n }\r\n }\r\n}\r\n","/**\r\n * Runhuman Sensor — Public API\r\n *\r\n * Singleton facade. Customers interact with this class only:\r\n *\r\n * Runhuman.init({ apiKey: 'rh_...' })\r\n *\r\n * When a Runhuman tester starts a job on an SDK-enabled app,\r\n * the sensor activates, captures events, and sends them to the backend.\r\n * Zero overhead when no test is running.\r\n */\r\n\r\nimport type { TelemetryPlatform } from '@runhuman/shared';\r\nimport { ApiClient } from './api-client.js';\r\nimport { SessionManager } from './session-manager.js';\r\nimport { DeepLinkHandler } from './deep-link-handler.js';\r\n\r\nconst PRODUCTION_BASE_URL = 'https://qa-experiment.fly.dev';\r\nconst SDK_VERSION = '0.1.0';\r\n\r\nexport interface RunhumanConfig {\r\n /** API key for authentication (e.g., 'rh_...') */\r\n apiKey: string;\r\n /** Base URL of the Runhuman API (defaults to production) */\r\n baseUrl?: string;\r\n /** Job ID if known upfront (e.g., from deep link params at app launch) */\r\n jobId?: string;\r\n /** Target platform (defaults to 'react-native') */\r\n platform?: TelemetryPlatform;\r\n /** How often to flush buffered events in ms (default: 5000) */\r\n flushIntervalMs?: number;\r\n /** How often to poll for job status in ms (default: 10000) */\r\n pollIntervalMs?: number;\r\n /** Max events in the ring buffer before FIFO eviction (default: 1000) */\r\n maxBufferSize?: number;\r\n /** Enable deep link activation (default: true) */\r\n enableDeepLinks?: boolean;\r\n /** Log debug info to console (default: false) */\r\n debug?: boolean;\r\n}\r\n\r\nexport class Runhuman {\r\n private static instance: Runhuman | null = null;\r\n\r\n private readonly sessionManager: SessionManager;\r\n private readonly deepLinkHandler: DeepLinkHandler | null;\r\n private readonly debug: boolean;\r\n\r\n private constructor(config: RunhumanConfig) {\r\n this.debug = config.debug === true;\r\n\r\n const apiClient = new ApiClient(\r\n config.baseUrl ?? PRODUCTION_BASE_URL,\r\n config.apiKey,\r\n );\r\n\r\n this.sessionManager = new SessionManager({\r\n apiClient,\r\n platform: config.platform ?? 'react-native',\r\n sdkVersion: SDK_VERSION,\r\n flushIntervalMs: config.flushIntervalMs ?? 5000,\r\n pollIntervalMs: config.pollIntervalMs ?? 10000,\r\n maxBufferSize: config.maxBufferSize ?? 1000,\r\n debug: this.debug,\r\n });\r\n\r\n // Deep link handler (optional)\r\n const enableDeepLinks = config.enableDeepLinks !== false;\r\n if (enableDeepLinks) {\r\n this.deepLinkHandler = new DeepLinkHandler(this.sessionManager, this.debug);\r\n this.deepLinkHandler.install();\r\n } else {\r\n this.deepLinkHandler = null;\r\n }\r\n\r\n this.log('Initialized');\r\n\r\n // If a jobId was provided, start polling immediately\r\n if (config.jobId) {\r\n this.sessionManager.startPolling(config.jobId);\r\n }\r\n }\r\n\r\n /**\r\n * Initialize the Runhuman sensor. Call once at app startup.\r\n *\r\n * @example\r\n * ```ts\r\n * Runhuman.init({ apiKey: 'rh_your_api_key' });\r\n * ```\r\n */\r\n static init(config: RunhumanConfig): Runhuman {\r\n if (Runhuman.instance) {\r\n throw new Error('Runhuman.init() called twice — use Runhuman.getInstance() or call destroy() first');\r\n }\r\n\r\n Runhuman.instance = new Runhuman(config);\r\n return Runhuman.instance;\r\n }\r\n\r\n /**\r\n * Get the existing Runhuman instance.\r\n * Throws if init() hasn't been called.\r\n */\r\n static getInstance(): Runhuman {\r\n if (!Runhuman.instance) {\r\n throw new Error('Runhuman not initialized — call Runhuman.init() first');\r\n }\r\n return Runhuman.instance;\r\n }\r\n\r\n /**\r\n * Manually activate a telemetry session for a specific job.\r\n * Use this when you receive a deep link or know the job ID.\r\n */\r\n activate(jobId: string): void {\r\n this.sessionManager.activate(jobId);\r\n }\r\n\r\n /**\r\n * Manually deactivate the current session.\r\n * Flushes remaining events and ends the session.\r\n */\r\n async deactivate(): Promise<void> {\r\n await this.sessionManager.deactivate();\r\n }\r\n\r\n /**\r\n * Full teardown — stops polling, ends any active session, cleans up listeners.\r\n * After calling destroy(), you can call init() again.\r\n */\r\n async destroy(): Promise<void> {\r\n await this.sessionManager.destroy();\r\n\r\n if (this.deepLinkHandler) {\r\n this.deepLinkHandler.uninstall();\r\n }\r\n\r\n Runhuman.instance = null;\r\n this.log('Destroyed');\r\n }\r\n\r\n private log(message: string): void {\r\n if (this.debug) {\r\n console.debug(`[Runhuman] ${message}`);\r\n }\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../../shared/src/routes/route-builder.ts","../../shared/src/routes/web-routes.ts","../../shared/src/routes/api-routes.ts","../src/api-client.ts","../src/event-buffer.ts","../src/interceptors/network.interceptor.ts","../src/interceptors/console.interceptor.ts","../src/interceptors/error.interceptor.ts","../src/interceptors/index.ts","../src/session-manager.ts","../src/deep-link-handler.ts","../src/runhuman.ts"],"sourcesContent":["/**\r\n * @runhuman/sensor — Public API\r\n *\r\n * Usage:\r\n * import { Runhuman } from '@runhuman/sensor';\r\n * Runhuman.init({ apiKey: 'rh_your_api_key' });\r\n */\r\n\r\nexport { Runhuman } from './runhuman.js';\r\nexport type { RunhumanConfig } from './runhuman.js';\r\nexport type { SessionState } from './session-manager.js';\r\nexport type { ApiClient } from './api-client.js';\r\nexport type { TelemetryPlatform } from '@runhuman/shared';\r\n","/**\r\n * Type-safe route builder system\r\n *\r\n * Provides compile-time type checking for route parameters.\r\n * Usage:\r\n * const route = defineRoute('/dashboard/:projectId/jobs/:jobId?');\r\n * route.build({ projectId: 'abc' }); // '/dashboard/abc/jobs'\r\n * route.build({ projectId: 'abc', jobId: '1' }); // '/dashboard/abc/jobs/1'\r\n */\r\n\r\n/**\r\n * API prefix for backend routes.\r\n * Frontend apiClient adds this prefix automatically.\r\n * Backend should use withApiPrefix() to add this to route patterns.\r\n */\r\nexport const API_PREFIX = '/api' as const;\r\n\r\n/**\r\n * Prepends the API prefix to a route pattern.\r\n * Used by backend to ensure routes match what frontend expects.\r\n *\r\n * @example\r\n * withApiPrefix('/organizations') // => '/api/organizations'\r\n * withApiPrefix(apiRoutes.organizations.pattern) // => '/api/organizations'\r\n */\r\nexport function withApiPrefix<T extends string>(pattern: T): `/api${T}` {\r\n return `${API_PREFIX}${pattern}` as `/api${T}`;\r\n}\r\n\r\n// Helper type to simplify intersected object types into a single object type\r\ntype Simplify<T> = { [K in keyof T]: T[K] } & {};\r\n\r\n/**\r\n * Extracts route parameters from a route pattern string.\r\n * - `:param` becomes required: { param: string }\r\n * - `:param?` becomes optional: { param?: string }\r\n *\r\n * Examples:\r\n * - '/dashboard/:projectId' → { projectId: string }\r\n * - '/dashboard/:projectId/jobs/:jobId?' → { projectId: string; jobId?: string }\r\n * - '/dashboard' → {}\r\n */\r\nexport type ExtractRouteParams<T extends string> =\r\n // Optional param followed by more path: :param?/rest\r\n T extends `${string}:${infer Param}?/${infer Rest}`\r\n ? Simplify<{ [K in Param]?: string } & ExtractRouteParams<`/${Rest}`>>\r\n : // Required param followed by more path: :param/rest\r\n T extends `${string}:${infer Param}/${infer Rest}`\r\n ? Simplify<{ [K in Param]: string } & ExtractRouteParams<`/${Rest}`>>\r\n : // Optional param at end: :param?\r\n T extends `${string}:${infer Param}?`\r\n ? { [K in Param]?: string }\r\n : // Required param at end: :param\r\n T extends `${string}:${infer Param}`\r\n ? { [K in Param]: string }\r\n : // No params\r\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\r\n {};\r\n\r\n/**\r\n * Check if a type is an empty object (no params)\r\n */\r\ntype IsEmptyObject<T> = keyof T extends never ? true : false;\r\n\r\n/**\r\n * Route object with pattern and type-safe build function\r\n */\r\nexport interface Route<TPattern extends string> {\r\n /** The raw route pattern with :param placeholders */\r\n pattern: TPattern;\r\n\r\n /**\r\n * Build a URL from the route pattern and params.\r\n * - If route has no params: build() takes no arguments\r\n * - If route has params: build(params) requires matching params object\r\n */\r\n build: IsEmptyObject<ExtractRouteParams<TPattern>> extends true\r\n ? () => string\r\n : (params: ExtractRouteParams<TPattern>) => string;\r\n}\r\n\r\n/**\r\n * Creates a type-safe route builder.\r\n *\r\n * @param pattern Route pattern with :param placeholders (e.g., '/dashboard/:projectId/jobs')\r\n * @returns Route object with pattern and build function\r\n *\r\n * @example\r\n * const route = defineRoute('/dashboard/:projectId/jobs/:jobId?');\r\n * route.pattern; // '/dashboard/:projectId/jobs/:jobId?'\r\n * route.build({ projectId: 'abc' }); // '/dashboard/abc/jobs'\r\n * route.build({ projectId: 'abc', jobId: '123' }); // '/dashboard/abc/jobs/123'\r\n */\r\nexport function defineRoute<TPattern extends string>(\r\n pattern: TPattern\r\n): Route<TPattern> {\r\n const build = (params?: Record<string, string | undefined>): string => {\r\n if (!params) return pattern;\r\n\r\n let result = pattern as string;\r\n\r\n // Replace all :param and :param? placeholders\r\n for (const [key, value] of Object.entries(params)) {\r\n if (value !== undefined) {\r\n // Replace both :param and :param? variants\r\n result = result.replace(`:${key}?`, value);\r\n result = result.replace(`:${key}`, value);\r\n }\r\n }\r\n\r\n // Remove any remaining optional params that weren't provided\r\n result = result.replace(/\\/:[^/]+\\?/g, '');\r\n\r\n // Clean up any double slashes that might result from removal\r\n result = result.replace(/\\/+/g, '/');\r\n\r\n // Remove trailing slash unless it's the root\r\n if (result.length > 1 && result.endsWith('/')) {\r\n result = result.slice(0, -1);\r\n }\r\n\r\n return result;\r\n };\r\n\r\n return {\r\n pattern,\r\n build: build as Route<TPattern>['build'],\r\n };\r\n}\r\n","/**\r\n * Frontend route definitions for the web dashboard\r\n */\r\nimport { defineRoute } from './route-builder.js';\r\n\r\n/**\r\n * All frontend routes for the web application.\r\n *\r\n * Usage:\r\n * import { webRoutes } from '@runhuman/shared';\r\n * navigate(webRoutes.playground.build({ projectId: 'abc' }));\r\n * // => '/dashboard/abc/playground'\r\n */\r\nexport const webRoutes = {\r\n // ============================================\r\n // Non-project routes (no projectId required)\r\n // ============================================\r\n\r\n /** Dashboard home page */\r\n dashboard: defineRoute('/dashboard'),\r\n\r\n /** Subscription management page */\r\n managePlan: defineRoute('/dashboard/manage-plan'),\r\n\r\n /** Plan selection / upgrade page */\r\n plans: defineRoute('/dashboard/plans'),\r\n\r\n /** Simple job view (redirects to full URL with projectId) */\r\n jobSimple: defineRoute('/dashboard/jobs/:jobId'),\r\n\r\n // ============================================\r\n // Admin routes (@volter.ai only)\r\n // ============================================\r\n\r\n /** Admin panel */\r\n admin: defineRoute('/dashboard/admin'),\r\n\r\n // ============================================\r\n // Organizations routes\r\n // ============================================\r\n\r\n /** Organizations list page */\r\n organizations: defineRoute('/dashboard/organizations'),\r\n\r\n /** Organization dashboard */\r\n organization: defineRoute('/dashboard/organizations/:organizationId'),\r\n\r\n /** Organization members page */\r\n organizationMembers: defineRoute('/dashboard/organizations/:organizationId/members'),\r\n\r\n /** Organization settings page */\r\n organizationSettings: defineRoute('/dashboard/organizations/:organizationId/settings'),\r\n\r\n /** Organization usage/billing page */\r\n organizationUsage: defineRoute('/dashboard/organizations/:organizationId/usage'),\r\n\r\n /** Organization projects page */\r\n organizationProjects: defineRoute('/dashboard/organizations/:organizationId/projects'),\r\n\r\n /** Organization jobs page (jobs across all projects) */\r\n organizationJobs: defineRoute('/dashboard/organizations/:organizationId/jobs'),\r\n\r\n /** Organization API keys page */\r\n organizationApiKeys: defineRoute('/dashboard/organizations/:organizationId/api-keys'),\r\n\r\n /** Organization integrations page */\r\n organizationIntegrations: defineRoute('/dashboard/organizations/:organizationId/integrations'),\r\n\r\n /** Organization GitHub integrations page */\r\n organizationGitHub: defineRoute('/dashboard/organizations/:organizationId/github'),\r\n\r\n /** Organization SSO/SAML configuration page */\r\n organizationSso: defineRoute('/dashboard/organizations/:organizationId/sso'),\r\n\r\n /** Organization schedules overview */\r\n organizationSchedules: defineRoute('/dashboard/organizations/:organizationId/schedules'),\r\n\r\n /** Import from GitHub page */\r\n organizationImportGitHub: defineRoute('/dashboard/organizations/:organizationId/import'),\r\n\r\n // ============================================\r\n // User settings routes\r\n // ============================================\r\n\r\n /** Settings root (redirects to account) */\r\n settings: defineRoute('/dashboard/settings'),\r\n\r\n /** Account settings page */\r\n settingsAccount: defineRoute('/dashboard/settings/account'),\r\n\r\n // ============================================\r\n // Tester portal routes\r\n // ============================================\r\n\r\n /** Tester dashboard/portal home */\r\n tester: defineRoute('/tester'),\r\n\r\n /** Tester available jobs list */\r\n testerJobs: defineRoute('/tester/jobs'),\r\n\r\n /** Tester settings page */\r\n testerSettings: defineRoute('/tester/settings'),\r\n\r\n /** Tester active test session */\r\n testerTest: defineRoute('/tester/test/:jobId'),\r\n\r\n // ============================================\r\n // Project routes (require projectId)\r\n // ============================================\r\n\r\n /** Project root (redirects to playground) */\r\n project: defineRoute('/dashboard/:projectId'),\r\n\r\n /** Playground page for testing */\r\n playground: defineRoute('/dashboard/:projectId/playground'),\r\n\r\n /** Jobs list page */\r\n jobs: defineRoute('/dashboard/:projectId/jobs'),\r\n\r\n /** Single job detail page */\r\n job: defineRoute('/dashboard/:projectId/jobs/:jobId'),\r\n\r\n /** Templates page */\r\n templates: defineRoute('/dashboard/:projectId/templates'),\r\n\r\n /** Single template detail (modal) */\r\n template: defineRoute('/dashboard/:projectId/templates/:templateId'),\r\n\r\n /** GitHub issues page */\r\n issues: defineRoute('/dashboard/:projectId/issues'),\r\n\r\n /** Single issue detail (modal) */\r\n issue: defineRoute('/dashboard/:projectId/issues/:issueNumber'),\r\n\r\n /** Issue test sessions page */\r\n issueSessions: defineRoute('/dashboard/:projectId/issue-sessions'),\r\n\r\n /** Single test session detail (modal) */\r\n issueSession: defineRoute('/dashboard/:projectId/issue-sessions/:sessionId'),\r\n\r\n /** Project settings page */\r\n projectSettings: defineRoute('/dashboard/:projectId/settings'),\r\n\r\n /** Schedules page */\r\n schedules: defineRoute('/dashboard/:projectId/schedules'),\r\n\r\n /** Flow charts page */\r\n flowCharts: defineRoute('/dashboard/:projectId/flowcharts'),\r\n\r\n /** Single flow chart detail (embedded in dashboard - deprecated, use flowChartView) */\r\n flowChart: defineRoute('/dashboard/:projectId/flowcharts/:flowchartId'),\r\n\r\n /** Full-page flow chart viewer (outside dashboard layout) */\r\n flowChartView: defineRoute('/view/flowchart/:projectId/:flowchartId'),\r\n\r\n // ============================================\r\n // External/public routes\r\n // ============================================\r\n\r\n /** Organization invite acceptance page */\r\n invite: defineRoute('/invite/:token'),\r\n\r\n /** Quick start flow for new users (pre-auth) */\r\n quickStart: defineRoute('/start'),\r\n\r\n /** Public job view with token-secured access */\r\n publicJob: defineRoute('/j/:jobId/:token'),\r\n\r\n /** Public system status page */\r\n statuspage: defineRoute('/statuspage'),\r\n\r\n /** Documentation */\r\n docs: defineRoute('/docs/setup'),\r\n\r\n /** Pricing page */\r\n pricing: defineRoute('/pricing'),\r\n\r\n /** Home page */\r\n home: defineRoute('/'),\r\n\r\n // ============================================\r\n // Learn Platform routes\r\n // ============================================\r\n\r\n /** Learn catalogue (default product) */\r\n learn: defineRoute('/learn'),\r\n\r\n /** Single lesson view */\r\n learnLesson: defineRoute('/learn/:lessonSlug'),\r\n} as const;\r\n\r\n/**\r\n * Type for all web route names\r\n */\r\nexport type WebRouteName = keyof typeof webRoutes;\r\n","/**\r\n * API endpoint definitions\r\n *\r\n * These routes are used by the web frontend to call the API.\r\n * Note: These are relative paths (no /api prefix for the client,\r\n * which adds /api automatically via API_BASE).\r\n */\r\nimport { defineRoute } from './route-builder.js';\r\n\r\n/**\r\n * All API endpoints.\r\n *\r\n * Usage:\r\n * import { apiRoutes } from '@runhuman/shared';\r\n * apiClient(apiRoutes.jobs.build());\r\n * // => '/jobs'\r\n */\r\nexport const apiRoutes = {\r\n // ============================================\r\n // Jobs endpoints\r\n // ============================================\r\n\r\n /** List jobs (GET) or create job (POST) */\r\n jobs: defineRoute('/jobs'),\r\n\r\n /** Get/update/delete specific job */\r\n job: defineRoute('/jobs/:jobId'),\r\n\r\n /** Get job status (for polling) */\r\n jobStatus: defineRoute('/jobs/:jobId/status'),\r\n\r\n /** Get individual job artifact by type */\r\n jobArtifact: defineRoute('/jobs/:jobId/artifacts/:artifactType'),\r\n\r\n /** Update job feedback and rating */\r\n jobFeedback: defineRoute('/jobs/:jobId/feedback'),\r\n\r\n /** Enable/disable public sharing for a job (POST = enable, DELETE = disable) */\r\n jobShare: defineRoute('/jobs/:jobId/share'),\r\n\r\n /** Claim a job (for testers) */\r\n jobClaim: defineRoute('/jobs/:jobId/claim'),\r\n\r\n /** Synchronous job execution */\r\n run: defineRoute('/run'),\r\n\r\n /** Get public job (no auth required, token-secured) */\r\n publicJob: defineRoute('/public/jobs/:jobId/:token'),\r\n\r\n // ============================================\r\n // Tester App Job endpoints (secured by testerToken)\r\n // ============================================\r\n\r\n /** Get/update job for tester app */\r\n testerJob: defineRoute('/tester/jobs/:jobId'),\r\n\r\n /** Get presigned upload URLs for artifacts */\r\n testerJobUploadUrls: defineRoute('/tester/jobs/:jobId/upload-urls'),\r\n\r\n /** Process test results with AI */\r\n testerJobProcessResults: defineRoute('/tester/jobs/:jobId/process-results'),\r\n\r\n /** Get processing status */\r\n testerJobProcessingStatus: defineRoute('/tester/jobs/:jobId/processing/:processingJobId'),\r\n\r\n /** Extract frames from video */\r\n testerJobExtractFrames: defineRoute('/tester/jobs/:jobId/extract-frames'),\r\n\r\n /** Abort job due to platform issue */\r\n testerJobAbort: defineRoute('/tester/jobs/:jobId/abort'),\r\n\r\n /** Mark job as invalid due to bad instructions */\r\n testerJobInvalid: defineRoute('/tester/jobs/:jobId/invalid'),\r\n\r\n /** Notify phase transition from tester app */\r\n testerJobPhase: defineRoute('/tester/jobs/:jobId/phase'),\r\n\r\n // ============================================\r\n // LiveKit endpoints (screen sharing & recording)\r\n // ============================================\r\n\r\n /** Start LiveKit session + get tester token (testerToken auth) */\r\n livekitTesterToken: defineRoute('/tester/jobs/:jobId/livekit-token'),\r\n\r\n /** End LiveKit session + stop egress (testerToken auth) */\r\n livekitEndSession: defineRoute('/tester/jobs/:jobId/livekit-end'),\r\n\r\n /** Save transcript entries from Web Speech API (testerToken auth) */\r\n livekitTranscript: defineRoute('/tester/jobs/:jobId/livekit-transcript'),\r\n\r\n /** Get viewer token for live watching (Clerk JWT auth) */\r\n livekitViewerToken: defineRoute('/jobs/:jobId/livekit-viewer-token'),\r\n\r\n /** Check if LiveKit session is active (Clerk JWT or testerToken auth) */\r\n livekitStatus: defineRoute('/jobs/:jobId/livekit-status'),\r\n\r\n /** Get presigned recording URL (Clerk JWT auth) */\r\n livekitRecording: defineRoute('/jobs/:jobId/livekit-recording'),\r\n\r\n // ============================================\r\n // API Keys endpoints\r\n // ============================================\r\n\r\n /** List API keys (GET) or create key (POST) */\r\n keys: defineRoute('/keys'),\r\n\r\n /** Get/update/delete specific key */\r\n key: defineRoute('/keys/:keyId'),\r\n\r\n /** Revoke an API key */\r\n keyRevoke: defineRoute('/keys/:keyId/revoke'),\r\n\r\n /** Get API key info from header (for authenticated requests) */\r\n keyInfo: defineRoute('/key-info'),\r\n\r\n // ============================================\r\n // Personal Access Tokens (PATs) endpoints\r\n // ============================================\r\n\r\n /** List PATs (GET) or create PAT (POST) */\r\n pats: defineRoute('/pats'),\r\n\r\n /** Get/delete specific PAT */\r\n pat: defineRoute('/pats/:patId'),\r\n\r\n /** Revoke a PAT */\r\n patRevoke: defineRoute('/pats/:patId/revoke'),\r\n\r\n /** Get PAT info from header (for authenticated requests) */\r\n patInfo: defineRoute('/pat-info'),\r\n\r\n // ============================================\r\n // Projects endpoints\r\n // ============================================\r\n\r\n /** List projects (GET) or create project (POST) */\r\n projects: defineRoute('/projects'),\r\n\r\n /** Get/update/delete specific project */\r\n project: defineRoute('/projects/:projectId'),\r\n\r\n /** Get jobs for a project */\r\n projectJobs: defineRoute('/projects/:projectId/jobs'),\r\n\r\n /** Get API keys for a project */\r\n projectApiKeys: defineRoute('/projects/:projectId/api-keys'),\r\n\r\n /** Transfer a project to another user or organization */\r\n projectTransfer: defineRoute('/projects/:projectId/transfer'),\r\n\r\n /** List templates for a project (GET) or create template (POST) */\r\n projectTemplates: defineRoute('/projects/:projectId/templates'),\r\n\r\n /** Get/update/delete specific template */\r\n projectTemplate: defineRoute('/projects/:projectId/templates/:templateId'),\r\n\r\n /** List flow charts for a project (GET) or upload flow chart (POST) */\r\n projectFlowCharts: defineRoute('/projects/:projectId/flowcharts'),\r\n\r\n /** Upload flow chart to a project */\r\n projectFlowChartsUpload: defineRoute('/projects/:projectId/flowcharts/upload'),\r\n\r\n /** Get/delete specific flow chart */\r\n projectFlowChart: defineRoute('/projects/:projectId/flowcharts/:flowchartId'),\r\n\r\n /** Get flow chart data */\r\n projectFlowChartData: defineRoute('/projects/:projectId/flowcharts/:flowchartId/data'),\r\n\r\n /** Chat with AI about a flow chart */\r\n projectFlowChartChat: defineRoute('/projects/:projectId/flowcharts/:flowchartId/chat'),\r\n\r\n /** Enable/disable public sharing for a flow chart */\r\n projectFlowChartShare: defineRoute('/projects/:projectId/flowcharts/:flowchartId/share'),\r\n\r\n /** List schedules for a project (GET) or create schedule (POST) */\r\n projectSchedules: defineRoute('/projects/:projectId/schedules'),\r\n\r\n /** Get/update/delete specific schedule */\r\n projectSchedule: defineRoute('/projects/:projectId/schedules/:scheduleId'),\r\n\r\n /** List execution history for a schedule */\r\n projectScheduleExecutions: defineRoute('/projects/:projectId/schedules/:scheduleId/executions'),\r\n\r\n /** Bulk create projects from GitHub repos */\r\n bulkCreateProjects: defineRoute('/projects/bulk'),\r\n\r\n // ============================================\r\n // GitHub endpoints\r\n // ============================================\r\n\r\n /** GitHub OAuth authorize */\r\n githubOAuthAuthorize: defineRoute('/github/oauth/authorize'),\r\n\r\n /** GitHub OAuth callback */\r\n githubCallback: defineRoute('/github/oauth/callback'),\r\n\r\n /** Link GitHub account (get GitHub username via OAuth) */\r\n githubLink: defineRoute('/github/link'),\r\n\r\n /** List issues for a repo (by owner/repo) */\r\n githubIssuesByRepo: defineRoute('/github/issues/:owner/:repo'),\r\n\r\n /** List issues (by projectId query param) */\r\n githubIssues: defineRoute('/github/issues'),\r\n\r\n /** Get a single issue */\r\n githubIssue: defineRoute('/github/issues/:issueNumber'),\r\n\r\n /** Get comments for an issue */\r\n githubIssueComments: defineRoute('/github/issues/:issueNumber/comments'),\r\n\r\n /** Get labels for a repo */\r\n githubIssueLabels: defineRoute('/github/issues/labels'),\r\n\r\n /** Get assignees for a repo */\r\n githubIssueAssignees: defineRoute('/github/issues/assignees'),\r\n\r\n /** Test a single issue */\r\n githubIssueTest: defineRoute('/github/issues/test'),\r\n\r\n /** Bulk test multiple issues */\r\n githubIssuesBulkTest: defineRoute('/github/issues/bulk-test'),\r\n\r\n /** List test sessions */\r\n githubTestSessions: defineRoute('/github/issues/test-sessions'),\r\n\r\n /** Get/delete a test session */\r\n githubTestSession: defineRoute('/github/issues/test-sessions/:sessionId'),\r\n\r\n /** Mark a test session as seen */\r\n githubTestSessionSeen: defineRoute('/github/issues/test-sessions/:sessionId/seen'),\r\n\r\n /** Get counts of running and unseen test sessions */\r\n githubTestSessionsCounts: defineRoute('/github/issues/test-sessions/counts'),\r\n\r\n /** Bulk test GitHub issues (legacy route) */\r\n githubBulkTest: defineRoute('/github/bulk-test'),\r\n\r\n /** GitHub App webhooks (receives issue_comment events etc.) */\r\n githubWebhooks: defineRoute('/github/webhooks'),\r\n\r\n // ============================================\r\n // Auth endpoints (Clerk-based)\r\n // ============================================\r\n\r\n /** Sync user data from Clerk after sign-in */\r\n authSync: defineRoute('/auth/sync'),\r\n\r\n /** Get current user info */\r\n authMe: defineRoute('/auth/me'),\r\n\r\n /** Mark user as startup (bonus tokens) */\r\n authStartup: defineRoute('/auth/startup'),\r\n\r\n /** Delete user account and all associated data */\r\n authDeleteAccount: defineRoute('/auth/account'),\r\n\r\n /** Preview account deletion impact (owned orgs, members, projects) */\r\n authDeletionPreview: defineRoute('/auth/account/deletion-preview'),\r\n\r\n // ============================================\r\n // Billing endpoints (Polar-based credits)\r\n // ============================================\r\n\r\n /** Get user's credit balance from Polar */\r\n billingBalance: defineRoute('/billing/balance'),\r\n\r\n /** Check if user has available credits */\r\n billingHasCredits: defineRoute('/billing/has-credits'),\r\n\r\n /** Get a customer portal URL for managing billing */\r\n billingPortal: defineRoute('/billing/portal'),\r\n\r\n /** Create a checkout session for purchasing credits */\r\n billingCheckout: defineRoute('/billing/checkout'),\r\n\r\n /** Get active subscription details (tier, cycle, etc.) */\r\n billingSubscription: defineRoute('/billing/subscription'),\r\n\r\n /** Update subscription plan (change tier/cycle) */\r\n billingChangePlan: defineRoute('/billing/change-plan'),\r\n\r\n // ============================================\r\n // Other endpoints\r\n // ============================================\r\n\r\n /** Health check */\r\n health: defineRoute('/health'),\r\n\r\n /** Detailed system status */\r\n status: defineRoute('/status'),\r\n\r\n /** List templates */\r\n templates: defineRoute('/templates'),\r\n\r\n /** Issue analyzer */\r\n issueAnalyzer: defineRoute('/issue-analyzer'),\r\n\r\n /** PR analyzer */\r\n prAnalyzer: defineRoute('/pr-analyzer'),\r\n\r\n /** Logs endpoint */\r\n logs: defineRoute('/logs'),\r\n\r\n // ============================================\r\n // Relevant Issues endpoints\r\n // ============================================\r\n\r\n /** Run relevant issues discovery for user */\r\n relevantIssuesDiscover: defineRoute('/relevant-issues/discover'),\r\n\r\n /** Get latest cached relevant issues result */\r\n relevantIssuesLatest: defineRoute('/relevant-issues/latest'),\r\n\r\n // ============================================\r\n // Onboarding endpoints\r\n // ============================================\r\n\r\n /** Get/update user's onboarding state */\r\n onboarding: defineRoute('/onboarding'),\r\n\r\n /** Mark onboarding as complete */\r\n onboardingComplete: defineRoute('/onboarding/complete'),\r\n\r\n /** Check if user needs onboarding */\r\n onboardingCheck: defineRoute('/onboarding/check'),\r\n\r\n // ============================================\r\n // Search endpoints\r\n // ============================================\r\n\r\n /** Global search across jobs, projects, issues, sessions */\r\n search: defineRoute('/search'),\r\n\r\n // ============================================\r\n // Tester Profile endpoints\r\n // ============================================\r\n\r\n /** Apply to become a tester */\r\n testerApply: defineRoute('/tester/apply'),\r\n\r\n /** Get/update own tester profile */\r\n testerProfile: defineRoute('/tester/profile'),\r\n\r\n /** Get public tester profile */\r\n testerPublicProfile: defineRoute('/testers/:testerId/profile'),\r\n\r\n /** Download tester app */\r\n testerDownloadApp: defineRoute('/tester/download-app'),\r\n\r\n /** Download browser extension */\r\n testerDownloadExtension: defineRoute('/tester/download-extension'),\r\n\r\n /** Download mobile tester app (Android APK) */\r\n testerDownloadMobileApp: defineRoute('/tester/download-mobile-app'),\r\n\r\n /** List available (unclaimed) jobs for testers */\r\n testerAvailableJobs: defineRoute('/tester/jobs/available'),\r\n\r\n /** Get presigned URL for avatar upload */\r\n testerAvatarUploadUrl: defineRoute('/tester/profile/avatar-upload-url'),\r\n\r\n /** Get tester app version */\r\n testerAppVersion: defineRoute('/tester/app-version'),\r\n\r\n /** Register/unregister Expo push token for mobile notifications */\r\n testerPushToken: defineRoute('/tester/push-token'),\r\n\r\n // ============================================\r\n // Extension endpoints (browser extension)\r\n // ============================================\r\n\r\n /** List extension tokens (GET) or create token (POST) */\r\n extensionTokens: defineRoute('/tester/extension-tokens'),\r\n\r\n /** Revoke (DELETE) a specific extension token */\r\n extensionToken: defineRoute('/tester/extension-tokens/:tokenId'),\r\n\r\n /** WebSocket endpoint for extension data streaming */\r\n extensionStream: defineRoute('/extension/stream'),\r\n\r\n /** Correlate extension data to a test job's time window */\r\n testerJobCorrelate: defineRoute('/tester/jobs/:jobId/correlate'),\r\n\r\n /** Check if extension data is flowing for this tester (polling endpoint) */\r\n testerJobDataStatus: defineRoute('/tester/jobs/:jobId/data-status'),\r\n\r\n /** Create extension token for auto-configuring the extension from the test page */\r\n testerJobExtensionToken: defineRoute('/tester/jobs/:jobId/extension-token'),\r\n\r\n // ============================================\r\n // Repo Templates endpoints\r\n // ============================================\r\n\r\n /** List templates from a GitHub repo's .runhuman/templates/ directory */\r\n repoTemplates: defineRoute('/repos/:owner/:repo/templates'),\r\n\r\n /** Get a single template from a GitHub repo */\r\n repoTemplate: defineRoute('/repos/:owner/:repo/templates/:templateName'),\r\n\r\n // ============================================\r\n // Organizations endpoints\r\n // ============================================\r\n\r\n /** List organizations (GET) or create organization (POST) */\r\n organizations: defineRoute('/organizations'),\r\n\r\n /** Get/update/delete specific organization */\r\n organization: defineRoute('/organizations/:organizationId'),\r\n\r\n /** List organization members */\r\n organizationMembers: defineRoute('/organizations/:organizationId/members'),\r\n\r\n /** Invite member to organization */\r\n organizationInvite: defineRoute('/organizations/:organizationId/invite'),\r\n\r\n /** Remove member from organization */\r\n organizationMember: defineRoute('/organizations/:organizationId/members/:userId'),\r\n\r\n /** List organization projects */\r\n organizationProjects: defineRoute('/organizations/:organizationId/projects'),\r\n\r\n /** Get organization jobs (jobs across all projects in the organization) */\r\n organizationJobs: defineRoute('/organizations/:organizationId/jobs'),\r\n\r\n /** Transfer organization ownership */\r\n organizationTransferOwnership: defineRoute('/organizations/:organizationId/transfer-ownership'),\r\n\r\n /** Create organization API key */\r\n organizationApiKeys: defineRoute('/organizations/:organizationId/api-keys'),\r\n\r\n /** Get GitHub installations for an organization */\r\n organizationGitHubInstallations: defineRoute('/organizations/:organizationId/github/installations'),\r\n\r\n /** Get/delete specific GitHub installation for an organization */\r\n organizationGitHubInstallation: defineRoute('/organizations/:organizationId/github/installations/:installationId'),\r\n\r\n /** List repos accessible to an organization via its GitHub installations */\r\n organizationGitHubRepos: defineRoute('/organizations/:organizationId/github/repos'),\r\n\r\n /** Refresh repos for an organization's GitHub installation */\r\n organizationGitHubInstallationRefresh: defineRoute('/organizations/:organizationId/github/installations/:installationId/refresh'),\r\n\r\n /** Check if org has access to a specific repo via its GitHub installations */\r\n organizationGitHubRepoCheckAccess: defineRoute('/organizations/:organizationId/github/repos/check-access'),\r\n\r\n /** Find deployed URL for a repo via org's GitHub installations */\r\n organizationGitHubRepoFindUrl: defineRoute('/organizations/:organizationId/github/repos/find-url'),\r\n\r\n /** Get all schedules across projects in the organization */\r\n organizationSchedules: defineRoute('/organizations/:organizationId/schedules'),\r\n\r\n /** Get organization billing balance (Polar) */\r\n organizationBilling: defineRoute('/organizations/:organizationId/billing'),\r\n\r\n // ============================================\r\n // Transfers endpoints\r\n // ============================================\r\n\r\n /** Get pending incoming transfers for current user */\r\n transfersPending: defineRoute('/transfers/pending'),\r\n\r\n /** Get pending outgoing transfers (initiated by current user) */\r\n transfersOutgoing: defineRoute('/transfers/outgoing'),\r\n\r\n /** Accept a transfer */\r\n transferAccept: defineRoute('/transfers/:transferId/accept'),\r\n\r\n /** Reject a transfer */\r\n transferReject: defineRoute('/transfers/:transferId/reject'),\r\n\r\n /** Cancel a transfer */\r\n transferCancel: defineRoute('/transfers/:transferId/cancel'),\r\n\r\n // ============================================\r\n // Invites endpoints (public)\r\n // ============================================\r\n\r\n /** Get invite details by token (public) */\r\n invite: defineRoute('/invites/:token'),\r\n\r\n /** Redeem an invite (authenticated) */\r\n inviteRedeem: defineRoute('/invites/:token/redeem'),\r\n\r\n // ============================================\r\n // User Agreements endpoints\r\n // ============================================\r\n\r\n /** Record user agreement acceptance (POST) or get user's agreements (GET) */\r\n agreements: defineRoute('/agreements'),\r\n\r\n /** Check if user has accepted a specific agreement */\r\n agreementCheck: defineRoute('/agreements/check'),\r\n\r\n // ============================================\r\n // Organization SSO endpoints\r\n // ============================================\r\n\r\n /** List SSO connections for an organization (GET) or create one (POST) */\r\n organizationSsoConnections: defineRoute('/organizations/:organizationId/sso/connections'),\r\n\r\n /** Get/delete a specific SSO connection */\r\n organizationSsoConnection: defineRoute('/organizations/:organizationId/sso/connections/:connectionId'),\r\n\r\n // ============================================\r\n // Enterprise endpoints\r\n // ============================================\r\n\r\n /** Submit enterprise contact inquiry (POST) */\r\n enterpriseInquiry: defineRoute('/enterprise/inquiry'),\r\n\r\n // ============================================\r\n // Changelog endpoints\r\n // ============================================\r\n\r\n /** List changelog entries (public) */\r\n changelog: defineRoute('/changelog'),\r\n\r\n /** Get unread changelog count (authenticated) */\r\n changelogUnreadCount: defineRoute('/changelog/unread-count'),\r\n\r\n /** Mark changelog as read (authenticated) */\r\n changelogMarkRead: defineRoute('/changelog/mark-read'),\r\n\r\n // ============================================\r\n // Telemetry SDK endpoints\r\n // ============================================\r\n\r\n /** Create telemetry session (POST) — SDK calls this when a test session starts */\r\n telemetrySessions: defineRoute('/telemetry/sessions'),\r\n\r\n /** End telemetry session (POST) — SDK calls this when testing completes */\r\n telemetrySessionEnd: defineRoute('/telemetry/sessions/:sessionId/end'),\r\n\r\n /** Submit telemetry event batch (POST) — SDK periodically flushes captured events */\r\n telemetryBatch: defineRoute('/telemetry/sessions/:sessionId/events'),\r\n\r\n /** Check session status for a job (GET) — SDK polls to know when to activate */\r\n telemetrySessionStatus: defineRoute('/telemetry/jobs/:jobId/status'),\r\n\r\n /** Resolve a sensor short code to a jobId (GET) — SDK calls this when tester enters a code */\r\n telemetryShortCodeResolve: defineRoute('/telemetry/short-codes/:code'),\r\n} as const;\r\n\r\n/**\r\n * Type for all API route names\r\n */\r\nexport type ApiRouteName = keyof typeof apiRoutes;\r\n","/**\r\n * Telemetry API Client\r\n *\r\n * Thin HTTP client wrapping the 4 Phase 0 telemetry endpoints.\r\n * Captures a reference to fetch at construction time — before the\r\n * network interceptor patches globalThis.fetch — to avoid recursion.\r\n */\r\n\r\nimport { apiRoutes, API_PREFIX } from '@runhuman/shared';\r\nimport type {\r\n CreateTelemetrySessionRequest,\r\n CreateTelemetrySessionResponse,\r\n TelemetryBatchRequest,\r\n TelemetryBatchResponse,\r\n EndTelemetrySessionResponse,\r\n TelemetrySessionStatusResponse,\r\n} from '@runhuman/shared';\r\n\r\nexport class ApiClient {\r\n private readonly baseUrl: string;\r\n private readonly apiKey: string;\r\n // Captured at construction time, before interceptors patch globalThis.fetch\r\n private readonly fetchFn: typeof globalThis.fetch;\r\n\r\n constructor(baseUrl: string, apiKey: string) {\r\n this.baseUrl = baseUrl.replace(/\\/$/, '');\r\n this.apiKey = apiKey;\r\n this.fetchFn = globalThis.fetch.bind(globalThis);\r\n }\r\n\r\n async createSession(\r\n req: CreateTelemetrySessionRequest,\r\n ): Promise<CreateTelemetrySessionResponse> {\r\n return this.post(\r\n apiRoutes.telemetrySessions.build(),\r\n req,\r\n );\r\n }\r\n\r\n async submitBatch(\r\n sessionId: string,\r\n req: TelemetryBatchRequest,\r\n ): Promise<TelemetryBatchResponse> {\r\n return this.post(\r\n apiRoutes.telemetryBatch.build({ sessionId }),\r\n req,\r\n );\r\n }\r\n\r\n async endSession(sessionId: string): Promise<EndTelemetrySessionResponse> {\r\n return this.post(\r\n apiRoutes.telemetrySessionEnd.build({ sessionId }),\r\n undefined,\r\n );\r\n }\r\n\r\n async getSessionStatus(jobId: string): Promise<TelemetrySessionStatusResponse> {\r\n return this.get(apiRoutes.telemetrySessionStatus.build({ jobId }));\r\n }\r\n\r\n async resolveShortCode(code: string): Promise<{ jobId: string } | null> {\r\n const normalized = code.replace(/^RH-/i, '').toUpperCase();\r\n try {\r\n return await this.get<{ jobId: string }>(\r\n apiRoutes.telemetryShortCodeResolve.build({ code: normalized }),\r\n );\r\n } catch (error) {\r\n if (error instanceof Error && error.message.includes('404')) return null;\r\n throw error;\r\n }\r\n }\r\n\r\n private url(routePath: string): string {\r\n return `${this.baseUrl}${API_PREFIX}${routePath}`;\r\n }\r\n\r\n private async post<T>(routePath: string, body: unknown): Promise<T> {\r\n const response = await this.fetchFn(this.url(routePath), {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n Authorization: `Bearer ${this.apiKey}`,\r\n },\r\n body: body !== undefined ? JSON.stringify(body) : undefined,\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text();\r\n throw new Error(`Runhuman API error ${response.status}: ${text}`);\r\n }\r\n\r\n return response.json() as Promise<T>;\r\n }\r\n\r\n private async get<T>(routePath: string): Promise<T> {\r\n const response = await this.fetchFn(this.url(routePath), {\r\n method: 'GET',\r\n headers: {\r\n Authorization: `Bearer ${this.apiKey}`,\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text();\r\n throw new Error(`Runhuman API error ${response.status}: ${text}`);\r\n }\r\n\r\n return response.json() as Promise<T>;\r\n }\r\n}\r\n","/**\r\n * Event Buffer\r\n *\r\n * In-memory ring buffer for telemetry events.\r\n * Events are pushed by interceptors and drained by the flush cycle.\r\n * FIFO eviction when at max capacity.\r\n */\r\n\r\nimport type { TelemetryBatchEvent } from '@runhuman/shared';\r\n\r\nexport class EventBuffer {\r\n private buffer: TelemetryBatchEvent[] = [];\r\n private readonly maxCapacity: number;\r\n\r\n constructor(maxCapacity = 1000) {\r\n this.maxCapacity = maxCapacity;\r\n }\r\n\r\n get size(): number {\r\n return this.buffer.length;\r\n }\r\n\r\n push(event: TelemetryBatchEvent): void {\r\n if (this.buffer.length >= this.maxCapacity) {\r\n this.buffer.shift();\r\n }\r\n this.buffer.push(event);\r\n }\r\n\r\n drain(): TelemetryBatchEvent[] {\r\n const events = this.buffer;\r\n this.buffer = [];\r\n return events;\r\n }\r\n\r\n clear(): void {\r\n this.buffer = [];\r\n }\r\n}\r\n","/**\r\n * Network Interceptor\r\n *\r\n * Patches globalThis.fetch to capture HTTP requests.\r\n * Privacy: strips Authorization, Cookie, Set-Cookie headers.\r\n * No request/response bodies are captured.\r\n */\r\n\r\nimport type { TelemetryBatchEvent } from '@runhuman/shared';\r\nimport type { EventCallback, Interceptor } from './types.js';\r\n\r\nconst STRIPPED_HEADERS = new Set([\r\n 'authorization',\r\n 'cookie',\r\n 'set-cookie',\r\n 'x-api-key',\r\n 'proxy-authorization',\r\n]);\r\n\r\nfunction scrubHeaders(headers: Headers): Record<string, string> {\r\n const result: Record<string, string> = {};\r\n headers.forEach((value, key) => {\r\n result[key] = STRIPPED_HEADERS.has(key.toLowerCase()) ? '[REDACTED]' : value;\r\n });\r\n return result;\r\n}\r\n\r\nexport class NetworkInterceptor implements Interceptor {\r\n private originalFetch: typeof globalThis.fetch | null = null;\r\n private sessionStartTime = 0;\r\n\r\n install(onEvent: EventCallback): void {\r\n this.originalFetch = globalThis.fetch;\r\n this.sessionStartTime = Date.now();\r\n\r\n const captured = this.originalFetch;\r\n const startTime = this.sessionStartTime;\r\n\r\n globalThis.fetch = async function interceptedFetch(\r\n input: RequestInfo | URL,\r\n init?: RequestInit,\r\n ): Promise<Response> {\r\n const requestStart = Date.now();\r\n const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;\r\n const method = init?.method ?? 'GET';\r\n\r\n const response = await captured.call(globalThis, input, init);\r\n\r\n const event: TelemetryBatchEvent = {\r\n type: 'network',\r\n relativeTimestampMs: requestStart - startTime,\r\n clientTimestamp: new Date(requestStart).toISOString(),\r\n data: {\r\n url,\r\n method: method.toUpperCase(),\r\n status: response.status,\r\n statusText: response.statusText,\r\n durationMs: Date.now() - requestStart,\r\n responseHeaders: scrubHeaders(response.headers),\r\n },\r\n };\r\n\r\n onEvent(event);\r\n\r\n return response;\r\n };\r\n }\r\n\r\n uninstall(): void {\r\n if (this.originalFetch) {\r\n globalThis.fetch = this.originalFetch;\r\n this.originalFetch = null;\r\n }\r\n }\r\n\r\n /** Update the session start time (called when a new session begins) */\r\n setSessionStartTime(startTime: number): void {\r\n this.sessionStartTime = startTime;\r\n }\r\n}\r\n","/**\r\n * Console Interceptor\r\n *\r\n * Patches console.log/warn/error/info/debug to capture log output.\r\n * Arguments are stringified to avoid capturing sensitive objects.\r\n */\r\n\r\nimport type { TelemetryBatchEvent } from '@runhuman/shared';\r\nimport type { EventCallback, Interceptor } from './types.js';\r\n\r\nconst CONSOLE_METHODS = ['log', 'warn', 'error', 'info', 'debug'] as const;\r\ntype ConsoleMethod = typeof CONSOLE_METHODS[number];\r\n\r\nfunction stringify(args: unknown[]): string {\r\n return args\r\n .map((arg) => {\r\n if (typeof arg === 'string') return arg;\r\n try {\r\n return JSON.stringify(arg);\r\n } catch {\r\n return String(arg);\r\n }\r\n })\r\n .join(' ');\r\n}\r\n\r\nexport class ConsoleInterceptor implements Interceptor {\r\n private originals: Partial<Record<ConsoleMethod, (...args: unknown[]) => void>> = {};\r\n private sessionStartTime = 0;\r\n\r\n install(onEvent: EventCallback): void {\r\n this.sessionStartTime = Date.now();\r\n const startTime = this.sessionStartTime;\r\n\r\n for (const method of CONSOLE_METHODS) {\r\n this.originals[method] = console[method].bind(console);\r\n\r\n console[method] = (...args: unknown[]) => {\r\n const now = Date.now();\r\n\r\n const event: TelemetryBatchEvent = {\r\n type: 'console',\r\n relativeTimestampMs: now - startTime,\r\n clientTimestamp: new Date(now).toISOString(),\r\n data: {\r\n level: method,\r\n message: stringify(args),\r\n },\r\n };\r\n\r\n onEvent(event);\r\n\r\n // Call the original so console output still appears\r\n this.originals[method]!(...args);\r\n };\r\n }\r\n }\r\n\r\n uninstall(): void {\r\n for (const method of CONSOLE_METHODS) {\r\n if (this.originals[method]) {\r\n console[method] = this.originals[method]!;\r\n }\r\n }\r\n this.originals = {};\r\n }\r\n\r\n setSessionStartTime(startTime: number): void {\r\n this.sessionStartTime = startTime;\r\n }\r\n}\r\n","/**\r\n * Error Interceptor\r\n *\r\n * Captures unhandled errors via:\r\n * - React Native's ErrorUtils.setGlobalHandler (when available)\r\n * - globalThis.onerror fallback (web/Node)\r\n *\r\n * Fatal errors emit 'crash', non-fatal emit 'error'.\r\n */\r\n\r\nimport type { TelemetryBatchEvent } from '@runhuman/shared';\r\nimport type { EventCallback, Interceptor } from './types.js';\r\n\r\n// React Native exposes ErrorUtils globally\r\ninterface ErrorUtilsType {\r\n getGlobalHandler(): (error: Error, isFatal: boolean) => void;\r\n setGlobalHandler(handler: (error: Error, isFatal: boolean) => void): void;\r\n}\r\n\r\ndeclare const ErrorUtils: ErrorUtilsType | undefined;\r\n\r\nexport class ErrorInterceptor implements Interceptor {\r\n private previousHandler: ((error: Error, isFatal: boolean) => void) | null = null;\r\n private previousOnError: typeof globalThis.onerror | null = null;\r\n private sessionStartTime = 0;\r\n\r\n install(onEvent: EventCallback): void {\r\n this.sessionStartTime = Date.now();\r\n const startTime = this.sessionStartTime;\r\n\r\n // React Native path\r\n if (typeof ErrorUtils !== 'undefined') {\r\n this.previousHandler = ErrorUtils.getGlobalHandler();\r\n const prev = this.previousHandler;\r\n\r\n ErrorUtils.setGlobalHandler((error: Error, isFatal: boolean) => {\r\n const now = Date.now();\r\n\r\n const event: TelemetryBatchEvent = {\r\n type: isFatal ? 'crash' : 'error',\r\n relativeTimestampMs: now - startTime,\r\n clientTimestamp: new Date(now).toISOString(),\r\n data: {\r\n message: error.message,\r\n stack: error.stack,\r\n name: error.name,\r\n isFatal,\r\n },\r\n };\r\n\r\n onEvent(event);\r\n\r\n // Forward to previous handler\r\n prev(error, isFatal);\r\n });\r\n return;\r\n }\r\n\r\n // Web/Node fallback\r\n this.previousOnError = globalThis.onerror;\r\n\r\n globalThis.onerror = (\r\n message: Event | string,\r\n source?: string,\r\n lineno?: number,\r\n colno?: number,\r\n error?: Error,\r\n ) => {\r\n const now = Date.now();\r\n\r\n const event: TelemetryBatchEvent = {\r\n type: 'error',\r\n relativeTimestampMs: now - startTime,\r\n clientTimestamp: new Date(now).toISOString(),\r\n data: {\r\n message: typeof message === 'string' ? message : message.type,\r\n stack: error?.stack,\r\n name: error?.name,\r\n source,\r\n lineno,\r\n colno,\r\n },\r\n };\r\n\r\n onEvent(event);\r\n\r\n if (typeof this.previousOnError === 'function') {\r\n return (this.previousOnError as (...args: unknown[]) => unknown).call(globalThis, message, source, lineno, colno, error);\r\n }\r\n };\r\n }\r\n\r\n uninstall(): void {\r\n if (typeof ErrorUtils !== 'undefined' && this.previousHandler) {\r\n ErrorUtils.setGlobalHandler(this.previousHandler);\r\n this.previousHandler = null;\r\n }\r\n\r\n if (this.previousOnError !== null) {\r\n globalThis.onerror = this.previousOnError;\r\n this.previousOnError = null;\r\n }\r\n }\r\n\r\n setSessionStartTime(startTime: number): void {\r\n this.sessionStartTime = startTime;\r\n }\r\n}\r\n","/**\r\n * Interceptor Manager\r\n *\r\n * Aggregates all interceptors into a single install/uninstall surface.\r\n */\r\n\r\nimport type { EventCallback, Interceptor } from './types.js';\r\nimport { NetworkInterceptor } from './network.interceptor.js';\r\nimport { ConsoleInterceptor } from './console.interceptor.js';\r\nimport { ErrorInterceptor } from './error.interceptor.js';\r\n\r\nexport class InterceptorManager implements Interceptor {\r\n private readonly interceptors: Interceptor[];\r\n\r\n readonly network: NetworkInterceptor;\r\n readonly console: ConsoleInterceptor;\r\n readonly error: ErrorInterceptor;\r\n\r\n constructor() {\r\n this.network = new NetworkInterceptor();\r\n this.console = new ConsoleInterceptor();\r\n this.error = new ErrorInterceptor();\r\n this.interceptors = [this.network, this.console, this.error];\r\n }\r\n\r\n install(onEvent: EventCallback): void {\r\n for (const interceptor of this.interceptors) {\r\n interceptor.install(onEvent);\r\n }\r\n }\r\n\r\n uninstall(): void {\r\n // Uninstall in reverse order\r\n for (let i = this.interceptors.length - 1; i >= 0; i--) {\r\n this.interceptors[i].uninstall();\r\n }\r\n }\r\n\r\n setSessionStartTime(startTime: number): void {\r\n this.network.setSessionStartTime(startTime);\r\n this.console.setSessionStartTime(startTime);\r\n this.error.setSessionStartTime(startTime);\r\n }\r\n}\r\n\r\nexport type { Interceptor, EventCallback } from './types.js';\r\n","/**\r\n * Session Manager\r\n *\r\n * Orchestrates the sensor lifecycle:\r\n * idle → polling → active → ending → idle\r\n *\r\n * - Polls the status endpoint to detect when a tester starts a job\r\n * - Creates a telemetry session and installs interceptors\r\n * - Periodically flushes buffered events to the API\r\n * - Ends the session when the job is no longer active\r\n */\r\n\r\nimport type { TelemetryPlatform } from '@runhuman/shared';\r\nimport type { ApiClient } from './api-client.js';\r\nimport { EventBuffer } from './event-buffer.js';\r\nimport { InterceptorManager } from './interceptors/index.js';\r\n\r\nexport type SessionState = 'idle' | 'polling' | 'active' | 'ending';\r\n\r\nexport interface SessionManagerConfig {\r\n apiClient: ApiClient;\r\n platform: TelemetryPlatform;\r\n sdkVersion: string;\r\n flushIntervalMs: number;\r\n pollIntervalMs: number;\r\n maxBufferSize: number;\r\n debug: boolean;\r\n}\r\n\r\nexport class SessionManager {\r\n private state: SessionState = 'idle';\r\n private sessionId: string | null = null;\r\n private activeJobId: string | null = null;\r\n private pollTimer: ReturnType<typeof setInterval> | null = null;\r\n private flushTimer: ReturnType<typeof setInterval> | null = null;\r\n private readonly buffer: EventBuffer;\r\n private readonly interceptors: InterceptorManager;\r\n private readonly config: SessionManagerConfig;\r\n private readonly listeners = new Set<() => void>();\r\n\r\n constructor(config: SessionManagerConfig) {\r\n this.config = config;\r\n this.buffer = new EventBuffer(config.maxBufferSize);\r\n this.interceptors = new InterceptorManager();\r\n }\r\n\r\n getState(): SessionState {\r\n return this.state;\r\n }\r\n\r\n /** Alias for getState() — named for React's useSyncExternalStore convention */\r\n getSnapshot(): SessionState {\r\n return this.state;\r\n }\r\n\r\n getSessionId(): string | null {\r\n return this.sessionId;\r\n }\r\n\r\n getActiveJobId(): string | null {\r\n return this.activeJobId;\r\n }\r\n\r\n /**\r\n * Subscribe to state changes. Returns an unsubscribe function.\r\n * Compatible with React's useSyncExternalStore.\r\n */\r\n subscribe(listener: () => void): () => void {\r\n this.listeners.add(listener);\r\n return () => this.listeners.delete(listener);\r\n }\r\n\r\n private setState(newState: SessionState): void {\r\n this.state = newState;\r\n for (const listener of this.listeners) {\r\n listener();\r\n }\r\n }\r\n\r\n /**\r\n * Start polling for job activation.\r\n * The sensor calls this on init — polling continues until a job is detected\r\n * or stopPolling() is called.\r\n */\r\n startPolling(jobId: string): void {\r\n if (this.state !== 'idle') return;\r\n\r\n this.activeJobId = jobId;\r\n this.setState('polling');\r\n this.log('Polling started', { jobId });\r\n\r\n this.pollTimer = setInterval(() => this.pollOnce(), this.config.pollIntervalMs);\r\n // Also poll immediately\r\n this.pollOnce();\r\n }\r\n\r\n stopPolling(): void {\r\n if (this.pollTimer) {\r\n clearInterval(this.pollTimer);\r\n this.pollTimer = null;\r\n }\r\n if (this.state === 'polling') {\r\n this.setState('idle');\r\n this.log('Polling stopped');\r\n }\r\n }\r\n\r\n /**\r\n * Activate a session immediately (e.g., from a deep link).\r\n * Skips polling — goes straight to session creation.\r\n */\r\n async activate(jobId: string): Promise<void> {\r\n if (this.state === 'active') {\r\n this.log('Already active, ignoring activate()', { jobId });\r\n return;\r\n }\r\n\r\n this.stopPolling();\r\n this.activeJobId = jobId;\r\n await this.startSession();\r\n }\r\n\r\n /**\r\n * End the current session and return to idle.\r\n */\r\n async deactivate(): Promise<void> {\r\n if (this.state !== 'active') return;\r\n await this.endSession();\r\n }\r\n\r\n /**\r\n * Full teardown — stop everything and clean up.\r\n */\r\n async destroy(): Promise<void> {\r\n this.stopPolling();\r\n if (this.state === 'active') {\r\n await this.endSession();\r\n }\r\n }\r\n\r\n private async pollOnce(): Promise<void> {\r\n if (this.state !== 'polling' || !this.activeJobId) return;\r\n\r\n try {\r\n const status = await this.config.apiClient.getSessionStatus(this.activeJobId);\r\n\r\n if (status.jobActive && !status.activeSessionId) {\r\n this.log('Job is active, creating session', {\r\n jobId: this.activeJobId,\r\n });\r\n this.stopPolling();\r\n await this.startSession();\r\n }\r\n } catch (error) {\r\n this.log('Poll failed', { error });\r\n }\r\n }\r\n\r\n private async startSession(): Promise<void> {\r\n this.setState('active');\r\n const now = new Date();\r\n\r\n const response = await this.config.apiClient.createSession({\r\n jobId: this.activeJobId!,\r\n platform: this.config.platform,\r\n sdkVersion: this.config.sdkVersion,\r\n clientStartTime: now.toISOString(),\r\n });\r\n\r\n this.sessionId = response.sessionId;\r\n const sessionStartTime = now.getTime();\r\n\r\n this.log('Session started', {\r\n sessionId: this.sessionId,\r\n clockOffsetMs: response.clockOffsetMs,\r\n });\r\n\r\n // Set the session start time on interceptors so relativeTimestampMs is correct\r\n this.interceptors.setSessionStartTime(sessionStartTime);\r\n\r\n // Install interceptors — events flow into the buffer\r\n this.interceptors.install((event) => this.buffer.push(event));\r\n\r\n // Start flush timer\r\n this.flushTimer = setInterval(() => this.flush(), this.config.flushIntervalMs);\r\n }\r\n\r\n private async endSession(): Promise<void> {\r\n this.setState('ending');\r\n\r\n // Stop flush timer and uninstall interceptors\r\n if (this.flushTimer) {\r\n clearInterval(this.flushTimer);\r\n this.flushTimer = null;\r\n }\r\n this.interceptors.uninstall();\r\n\r\n // Final flush\r\n await this.flush();\r\n\r\n // End the session on the server\r\n if (this.sessionId) {\r\n try {\r\n const result = await this.config.apiClient.endSession(this.sessionId);\r\n this.log('Session ended', {\r\n sessionId: this.sessionId,\r\n eventCount: result.eventCount,\r\n correlated: result.correlated,\r\n });\r\n } catch (error) {\r\n this.log('Failed to end session', { sessionId: this.sessionId, error });\r\n }\r\n }\r\n\r\n this.sessionId = null;\r\n this.activeJobId = null;\r\n this.buffer.clear();\r\n this.setState('idle');\r\n }\r\n\r\n private async flush(): Promise<void> {\r\n if (!this.sessionId) return;\r\n\r\n const events = this.buffer.drain();\r\n if (events.length === 0) return;\r\n\r\n try {\r\n const result = await this.config.apiClient.submitBatch(this.sessionId, { events });\r\n this.log('Flushed events', {\r\n accepted: result.accepted,\r\n sessionEventCount: result.sessionEventCount,\r\n });\r\n } catch (error) {\r\n this.log('Flush failed, events lost', { count: events.length, error });\r\n }\r\n }\r\n\r\n private log(message: string, data?: Record<string, unknown>): void {\r\n if (this.config.debug) {\r\n console.debug(`[Runhuman] ${message}`, data);\r\n }\r\n }\r\n}\r\n","/**\r\n * Deep Link Handler\r\n *\r\n * Listens for React Native deep links matching the pattern:\r\n * <scheme>://runhuman?jobId=<id>\r\n *\r\n * When matched, triggers immediate session activation via SessionManager.\r\n * Falls back gracefully when react-native Linking is unavailable.\r\n */\r\n\r\nimport type { SessionManager } from './session-manager.js';\r\n\r\n// React Native Linking API shape (avoid importing react-native at module level)\r\ninterface LinkingModule {\r\n addEventListener(type: string, handler: (event: { url: string }) => void): { remove(): void };\r\n getInitialURL(): Promise<string | null>;\r\n}\r\n\r\nexport class DeepLinkHandler {\r\n private subscription: { remove(): void } | null = null;\r\n private readonly sessionManager: SessionManager;\r\n private readonly debug: boolean;\r\n\r\n constructor(sessionManager: SessionManager, debug: boolean) {\r\n this.sessionManager = sessionManager;\r\n this.debug = debug;\r\n }\r\n\r\n async install(): Promise<void> {\r\n const Linking = this.getLinking();\r\n if (!Linking) {\r\n this.log('react-native Linking not available, deep links disabled');\r\n return;\r\n }\r\n\r\n // Handle deep links received while app is running\r\n this.subscription = Linking.addEventListener('url', ({ url }) => {\r\n this.handleUrl(url);\r\n });\r\n\r\n // Handle deep link that launched the app (cold start)\r\n const initialUrl = await Linking.getInitialURL();\r\n if (initialUrl) {\r\n this.handleUrl(initialUrl);\r\n }\r\n }\r\n\r\n uninstall(): void {\r\n if (this.subscription) {\r\n this.subscription.remove();\r\n this.subscription = null;\r\n }\r\n }\r\n\r\n private handleUrl(url: string): void {\r\n const jobId = this.extractJobId(url);\r\n if (!jobId) return;\r\n\r\n this.log('Deep link received', { url, jobId });\r\n this.sessionManager.activate(jobId);\r\n }\r\n\r\n /**\r\n * Extract jobId from a URL matching: <scheme>://runhuman?jobId=<id>\r\n * Returns null if the URL doesn't match the expected pattern.\r\n */\r\n private extractJobId(url: string): string | null {\r\n try {\r\n const parsed = new URL(url);\r\n // Match hostname OR pathname containing \"runhuman\"\r\n const isRunhuman =\r\n parsed.hostname === 'runhuman' ||\r\n parsed.pathname.includes('runhuman');\r\n\r\n if (!isRunhuman) return null;\r\n\r\n return parsed.searchParams.get('jobId');\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n private getLinking(): LinkingModule | null {\r\n try {\r\n // Dynamic require to avoid hard dependency on react-native\r\n const { Linking } = require('react-native');\r\n return Linking as LinkingModule;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n private log(message: string, data?: Record<string, unknown>): void {\r\n if (this.debug) {\r\n console.debug(`[Runhuman] ${message}`, data);\r\n }\r\n }\r\n}\r\n","/**\r\n * Runhuman Sensor — Public API\r\n *\r\n * Singleton facade. Customers interact with this class only:\r\n *\r\n * Runhuman.init({ apiKey: 'rh_...' })\r\n *\r\n * When a Runhuman tester starts a job on an SDK-enabled app,\r\n * the sensor activates, captures events, and sends them to the backend.\r\n * Zero overhead when no test is running.\r\n */\r\n\r\nimport type { TelemetryPlatform } from '@runhuman/shared';\r\nimport { ApiClient } from './api-client.js';\r\nimport { SessionManager } from './session-manager.js';\r\nimport { DeepLinkHandler } from './deep-link-handler.js';\r\n\r\nconst PRODUCTION_BASE_URL = 'https://qa-experiment.fly.dev';\r\nconst SDK_VERSION = '0.1.0';\r\n\r\nexport interface RunhumanConfig {\r\n /** API key for authentication (e.g., 'rh_...') */\r\n apiKey: string;\r\n /** Base URL of the Runhuman API (defaults to production) */\r\n baseUrl?: string;\r\n /** Job ID if known upfront (e.g., from deep link params at app launch) */\r\n jobId?: string;\r\n /** Target platform (defaults to 'react-native') */\r\n platform?: TelemetryPlatform;\r\n /** How often to flush buffered events in ms (default: 5000) */\r\n flushIntervalMs?: number;\r\n /** How often to poll for job status in ms (default: 10000) */\r\n pollIntervalMs?: number;\r\n /** Max events in the ring buffer before FIFO eviction (default: 1000) */\r\n maxBufferSize?: number;\r\n /** Enable deep link activation (default: true) */\r\n enableDeepLinks?: boolean;\r\n /** Log debug info to console (default: false) */\r\n debug?: boolean;\r\n}\r\n\r\nexport class Runhuman {\r\n private static instance: Runhuman | null = null;\r\n\r\n private readonly apiClient: ApiClient;\r\n private readonly sessionManager: SessionManager;\r\n private readonly deepLinkHandler: DeepLinkHandler | null;\r\n private readonly debug: boolean;\r\n\r\n private constructor(config: RunhumanConfig) {\r\n this.debug = config.debug === true;\r\n\r\n this.apiClient = new ApiClient(\r\n config.baseUrl ?? PRODUCTION_BASE_URL,\r\n config.apiKey,\r\n );\r\n\r\n this.sessionManager = new SessionManager({\r\n apiClient: this.apiClient,\r\n platform: config.platform ?? 'react-native',\r\n sdkVersion: SDK_VERSION,\r\n flushIntervalMs: config.flushIntervalMs ?? 5000,\r\n pollIntervalMs: config.pollIntervalMs ?? 10000,\r\n maxBufferSize: config.maxBufferSize ?? 1000,\r\n debug: this.debug,\r\n });\r\n\r\n // Deep link handler (optional)\r\n const enableDeepLinks = config.enableDeepLinks !== false;\r\n if (enableDeepLinks) {\r\n this.deepLinkHandler = new DeepLinkHandler(this.sessionManager, this.debug);\r\n this.deepLinkHandler.install();\r\n } else {\r\n this.deepLinkHandler = null;\r\n }\r\n\r\n this.log('Initialized');\r\n\r\n // If a jobId was provided, start polling immediately\r\n if (config.jobId) {\r\n this.sessionManager.startPolling(config.jobId);\r\n }\r\n }\r\n\r\n /**\r\n * Initialize the Runhuman sensor. Call once at app startup.\r\n *\r\n * @example\r\n * ```ts\r\n * Runhuman.init({ apiKey: 'rh_your_api_key' });\r\n * ```\r\n */\r\n static init(config: RunhumanConfig): Runhuman {\r\n if (Runhuman.instance) {\r\n throw new Error('Runhuman.init() called twice — use Runhuman.getInstance() or call destroy() first');\r\n }\r\n\r\n Runhuman.instance = new Runhuman(config);\r\n return Runhuman.instance;\r\n }\r\n\r\n /**\r\n * Get the existing Runhuman instance.\r\n * Throws if init() hasn't been called.\r\n */\r\n static getInstance(): Runhuman {\r\n if (!Runhuman.instance) {\r\n throw new Error('Runhuman not initialized — call Runhuman.init() first');\r\n }\r\n return Runhuman.instance;\r\n }\r\n\r\n /**\r\n * Manually activate a telemetry session for a specific job.\r\n * Use this when you receive a deep link or know the job ID.\r\n */\r\n activate(jobId: string): void {\r\n this.sessionManager.activate(jobId);\r\n }\r\n\r\n /**\r\n * Manually deactivate the current session.\r\n * Flushes remaining events and ends the session.\r\n */\r\n async deactivate(): Promise<void> {\r\n await this.sessionManager.deactivate();\r\n }\r\n\r\n /**\r\n * Full teardown — stops polling, ends any active session, cleans up listeners.\r\n * After calling destroy(), you can call init() again.\r\n */\r\n async destroy(): Promise<void> {\r\n await this.sessionManager.destroy();\r\n\r\n if (this.deepLinkHandler) {\r\n this.deepLinkHandler.uninstall();\r\n }\r\n\r\n Runhuman.instance = null;\r\n this.log('Destroyed');\r\n }\r\n\r\n /** Access the session manager (used by <RunhumanOverlay /> for reactive state) */\r\n getSessionManager(): SessionManager {\r\n return this.sessionManager;\r\n }\r\n\r\n /** Access the API client (used by <RunhumanOverlay /> for short code resolution) */\r\n getApiClient(): ApiClient {\r\n return this.apiClient;\r\n }\r\n\r\n private log(message: string): void {\r\n if (this.debug) {\r\n console.debug(`[Runhuman] ${message}`);\r\n }\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -74,6 +74,8 @@ var webRoutes = {
74
74
  organizationGitHub: defineRoute("/dashboard/organizations/:organizationId/github"),
75
75
  /** Organization SSO/SAML configuration page */
76
76
  organizationSso: defineRoute("/dashboard/organizations/:organizationId/sso"),
77
+ /** Organization schedules overview */
78
+ organizationSchedules: defineRoute("/dashboard/organizations/:organizationId/schedules"),
77
79
  /** Import from GitHub page */
78
80
  organizationImportGitHub: defineRoute("/dashboard/organizations/:organizationId/import"),
79
81
  // ============================================
@@ -119,6 +121,8 @@ var webRoutes = {
119
121
  issueSession: defineRoute("/dashboard/:projectId/issue-sessions/:sessionId"),
120
122
  /** Project settings page */
121
123
  projectSettings: defineRoute("/dashboard/:projectId/settings"),
124
+ /** Schedules page */
125
+ schedules: defineRoute("/dashboard/:projectId/schedules"),
122
126
  /** Flow charts page */
123
127
  flowCharts: defineRoute("/dashboard/:projectId/flowcharts"),
124
128
  /** Single flow chart detail (embedded in dashboard - deprecated, use flowChartView) */
@@ -162,6 +166,8 @@ var apiRoutes = {
162
166
  job: defineRoute("/jobs/:jobId"),
163
167
  /** Get job status (for polling) */
164
168
  jobStatus: defineRoute("/jobs/:jobId/status"),
169
+ /** Get individual job artifact by type */
170
+ jobArtifact: defineRoute("/jobs/:jobId/artifacts/:artifactType"),
165
171
  /** Update job feedback and rating */
166
172
  jobFeedback: defineRoute("/jobs/:jobId/feedback"),
167
173
  /** Enable/disable public sharing for a job (POST = enable, DELETE = disable) */
@@ -257,6 +263,12 @@ var apiRoutes = {
257
263
  projectFlowChartChat: defineRoute("/projects/:projectId/flowcharts/:flowchartId/chat"),
258
264
  /** Enable/disable public sharing for a flow chart */
259
265
  projectFlowChartShare: defineRoute("/projects/:projectId/flowcharts/:flowchartId/share"),
266
+ /** List schedules for a project (GET) or create schedule (POST) */
267
+ projectSchedules: defineRoute("/projects/:projectId/schedules"),
268
+ /** Get/update/delete specific schedule */
269
+ projectSchedule: defineRoute("/projects/:projectId/schedules/:scheduleId"),
270
+ /** List execution history for a schedule */
271
+ projectScheduleExecutions: defineRoute("/projects/:projectId/schedules/:scheduleId/executions"),
260
272
  /** Bulk create projects from GitHub repos */
261
273
  bulkCreateProjects: defineRoute("/projects/bulk"),
262
274
  // ============================================
@@ -438,6 +450,8 @@ var apiRoutes = {
438
450
  organizationGitHubRepoCheckAccess: defineRoute("/organizations/:organizationId/github/repos/check-access"),
439
451
  /** Find deployed URL for a repo via org's GitHub installations */
440
452
  organizationGitHubRepoFindUrl: defineRoute("/organizations/:organizationId/github/repos/find-url"),
453
+ /** Get all schedules across projects in the organization */
454
+ organizationSchedules: defineRoute("/organizations/:organizationId/schedules"),
441
455
  /** Get organization billing balance (Polar) */
442
456
  organizationBilling: defineRoute("/organizations/:organizationId/billing"),
443
457
  // ============================================
@@ -498,7 +512,9 @@ var apiRoutes = {
498
512
  /** Submit telemetry event batch (POST) — SDK periodically flushes captured events */
499
513
  telemetryBatch: defineRoute("/telemetry/sessions/:sessionId/events"),
500
514
  /** Check session status for a job (GET) — SDK polls to know when to activate */
501
- telemetrySessionStatus: defineRoute("/telemetry/jobs/:jobId/status")
515
+ telemetrySessionStatus: defineRoute("/telemetry/jobs/:jobId/status"),
516
+ /** Resolve a sensor short code to a jobId (GET) — SDK calls this when tester enters a code */
517
+ telemetryShortCodeResolve: defineRoute("/telemetry/short-codes/:code")
502
518
  };
503
519
 
504
520
  // src/api-client.ts
@@ -529,6 +545,17 @@ var ApiClient = class {
529
545
  async getSessionStatus(jobId) {
530
546
  return this.get(apiRoutes.telemetrySessionStatus.build({ jobId }));
531
547
  }
548
+ async resolveShortCode(code) {
549
+ const normalized = code.replace(/^RH-/i, "").toUpperCase();
550
+ try {
551
+ return await this.get(
552
+ apiRoutes.telemetryShortCodeResolve.build({ code: normalized })
553
+ );
554
+ } catch (error) {
555
+ if (error instanceof Error && error.message.includes("404")) return null;
556
+ throw error;
557
+ }
558
+ }
532
559
  url(routePath) {
533
560
  return `${this.baseUrl}${API_PREFIX}${routePath}`;
534
561
  }
@@ -798,6 +825,7 @@ var SessionManager = class {
798
825
  this.activeJobId = null;
799
826
  this.pollTimer = null;
800
827
  this.flushTimer = null;
828
+ this.listeners = /* @__PURE__ */ new Set();
801
829
  this.config = config;
802
830
  this.buffer = new EventBuffer(config.maxBufferSize);
803
831
  this.interceptors = new InterceptorManager();
@@ -805,9 +833,30 @@ var SessionManager = class {
805
833
  getState() {
806
834
  return this.state;
807
835
  }
836
+ /** Alias for getState() — named for React's useSyncExternalStore convention */
837
+ getSnapshot() {
838
+ return this.state;
839
+ }
808
840
  getSessionId() {
809
841
  return this.sessionId;
810
842
  }
843
+ getActiveJobId() {
844
+ return this.activeJobId;
845
+ }
846
+ /**
847
+ * Subscribe to state changes. Returns an unsubscribe function.
848
+ * Compatible with React's useSyncExternalStore.
849
+ */
850
+ subscribe(listener) {
851
+ this.listeners.add(listener);
852
+ return () => this.listeners.delete(listener);
853
+ }
854
+ setState(newState) {
855
+ this.state = newState;
856
+ for (const listener of this.listeners) {
857
+ listener();
858
+ }
859
+ }
811
860
  /**
812
861
  * Start polling for job activation.
813
862
  * The sensor calls this on init — polling continues until a job is detected
@@ -816,7 +865,7 @@ var SessionManager = class {
816
865
  startPolling(jobId) {
817
866
  if (this.state !== "idle") return;
818
867
  this.activeJobId = jobId;
819
- this.state = "polling";
868
+ this.setState("polling");
820
869
  this.log("Polling started", { jobId });
821
870
  this.pollTimer = setInterval(() => this.pollOnce(), this.config.pollIntervalMs);
822
871
  this.pollOnce();
@@ -827,7 +876,7 @@ var SessionManager = class {
827
876
  this.pollTimer = null;
828
877
  }
829
878
  if (this.state === "polling") {
830
- this.state = "idle";
879
+ this.setState("idle");
831
880
  this.log("Polling stopped");
832
881
  }
833
882
  }
@@ -864,10 +913,9 @@ var SessionManager = class {
864
913
  if (this.state !== "polling" || !this.activeJobId) return;
865
914
  try {
866
915
  const status = await this.config.apiClient.getSessionStatus(this.activeJobId);
867
- if (status.jobActive && status.sdkEnabled && status.activeSessionId) {
868
- this.log("Job has active session, starting capture", {
869
- jobId: this.activeJobId,
870
- activeSessionId: status.activeSessionId
916
+ if (status.jobActive && !status.activeSessionId) {
917
+ this.log("Job is active, creating session", {
918
+ jobId: this.activeJobId
871
919
  });
872
920
  this.stopPolling();
873
921
  await this.startSession();
@@ -877,7 +925,7 @@ var SessionManager = class {
877
925
  }
878
926
  }
879
927
  async startSession() {
880
- this.state = "active";
928
+ this.setState("active");
881
929
  const now = /* @__PURE__ */ new Date();
882
930
  const response = await this.config.apiClient.createSession({
883
931
  jobId: this.activeJobId,
@@ -896,7 +944,7 @@ var SessionManager = class {
896
944
  this.flushTimer = setInterval(() => this.flush(), this.config.flushIntervalMs);
897
945
  }
898
946
  async endSession() {
899
- this.state = "ending";
947
+ this.setState("ending");
900
948
  if (this.flushTimer) {
901
949
  clearInterval(this.flushTimer);
902
950
  this.flushTimer = null;
@@ -918,7 +966,7 @@ var SessionManager = class {
918
966
  this.sessionId = null;
919
967
  this.activeJobId = null;
920
968
  this.buffer.clear();
921
- this.state = "idle";
969
+ this.setState("idle");
922
970
  }
923
971
  async flush() {
924
972
  if (!this.sessionId) return;
@@ -1009,12 +1057,12 @@ var SDK_VERSION = "0.1.0";
1009
1057
  var _Runhuman = class _Runhuman {
1010
1058
  constructor(config) {
1011
1059
  this.debug = config.debug === true;
1012
- const apiClient = new ApiClient(
1060
+ this.apiClient = new ApiClient(
1013
1061
  config.baseUrl ?? PRODUCTION_BASE_URL,
1014
1062
  config.apiKey
1015
1063
  );
1016
1064
  this.sessionManager = new SessionManager({
1017
- apiClient,
1065
+ apiClient: this.apiClient,
1018
1066
  platform: config.platform ?? "react-native",
1019
1067
  sdkVersion: SDK_VERSION,
1020
1068
  flushIntervalMs: config.flushIntervalMs ?? 5e3,
@@ -1085,6 +1133,14 @@ var _Runhuman = class _Runhuman {
1085
1133
  _Runhuman.instance = null;
1086
1134
  this.log("Destroyed");
1087
1135
  }
1136
+ /** Access the session manager (used by <RunhumanOverlay /> for reactive state) */
1137
+ getSessionManager() {
1138
+ return this.sessionManager;
1139
+ }
1140
+ /** Access the API client (used by <RunhumanOverlay /> for short code resolution) */
1141
+ getApiClient() {
1142
+ return this.apiClient;
1143
+ }
1088
1144
  log(message) {
1089
1145
  if (this.debug) {
1090
1146
  console.debug(`[Runhuman] ${message}`);