@codaco/analytics 9.0.0 → 11.0.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.ts","../src/config.ts"],"sourcesContent":["// Helper function that ensures that a value is an Error\nexport function ensureError(value: unknown): Error {\n\tif (!value) return new Error(\"No value was thrown\");\n\n\tif (value instanceof Error) return value;\n\n\t// Test if value inherits from Error\n\tif (Object.prototype.isPrototypeOf.call(value, Error)) return value as Error & typeof value;\n\n\tlet stringified = \"[Unable to stringify the thrown value]\";\n\ttry {\n\t\tstringified = JSON.stringify(value);\n\t} catch (e) {\n\t\t// biome-ignore lint/suspicious/noConsole: logging\n\t\tconsole.error(e);\n\t}\n\n\tconst error = new Error(`This value was thrown as is, not through an Error: ${stringified}`);\n\treturn error;\n}\n","import type { AnalyticsConfig } from \"./types\";\n\n/**\n * Hardcoded PostHog API host - always uses the Cloudflare Worker reverse proxy\n * Authentication is handled by the worker at this endpoint\n */\nconst POSTHOG_PROXY_HOST = \"https://ph-relay.networkcanvas.com\";\n\n/**\n * Dummy API key used for proxy mode\n * PostHog JS library requires an API key for initialization, but when using\n * the reverse proxy, authentication is handled by the Cloudflare Worker.\n * This placeholder key is used for client-side initialization only.\n */\nconst PROXY_MODE_DUMMY_KEY = \"phc_proxy_mode_placeholder\";\n\n/**\n * Check if analytics is disabled via environment variables\n */\nexport function isDisabledByEnv(): boolean {\n\tif (typeof process === \"undefined\") {\n\t\treturn false;\n\t}\n\n\treturn process.env.DISABLE_ANALYTICS === \"true\" || process.env.NEXT_PUBLIC_DISABLE_ANALYTICS === \"true\";\n}\n\n/**\n * Default configuration for analytics\n * API host and key are hardcoded, but disabled flag can be set via environment variables\n */\nexport const defaultConfig: Partial<AnalyticsConfig> = {\n\t// Always use the Cloudflare Worker reverse proxy\n\tapiHost: POSTHOG_PROXY_HOST,\n\n\t// Analytics enabled by default (can be disabled via env var or config option)\n\tdisabled: false,\n\n\t// Debug mode disabled by default\n\tdebug: false,\n\n\t// Default PostHog options\n\tposthogOptions: {\n\t\t// Disable session recording by default (can be enabled per-app)\n\t\tdisable_session_recording: true,\n\n\t\t// Disable autocapture to keep events clean and intentional\n\t\tautocapture: false,\n\n\t\t// Disable automatic pageview capture (apps can enable if needed)\n\t\tcapture_pageview: false,\n\n\t\t// Disable pageleave events\n\t\tcapture_pageleave: false,\n\n\t\t// Don't use cross-subdomain cookies\n\t\tcross_subdomain_cookie: false,\n\n\t\t// Enable feature flags by default\n\t\tadvanced_disable_feature_flags: false,\n\n\t\t// Send feature flag events\n\t\tadvanced_disable_feature_flags_on_first_load: false,\n\n\t\t// Enable persistence for feature flags\n\t\tpersistence: \"localStorage+cookie\",\n\t},\n};\n\n/**\n * Merge user config with defaults\n *\n * Note: This package is designed to work exclusively with the Cloudflare Worker\n * reverse proxy (ph-relay.networkcanvas.com). Authentication is handled by the\n * worker, so the API key is optional and defaults to a placeholder value.\n *\n * The only environment variable checked is DISABLE_ANALYTICS / NEXT_PUBLIC_DISABLE_ANALYTICS\n * for disabling tracking. All other configuration is hardcoded or passed explicitly.\n */\nexport function mergeConfig(userConfig: AnalyticsConfig): Required<AnalyticsConfig> {\n\treturn {\n\t\tapp: userConfig.app,\n\t\tapiHost: userConfig.apiHost ?? defaultConfig.apiHost ?? POSTHOG_PROXY_HOST,\n\t\tapiKey: userConfig.apiKey ?? PROXY_MODE_DUMMY_KEY,\n\t\tinstallationId: userConfig.installationId,\n\t\tdisabled: userConfig.disabled ?? isDisabledByEnv() ?? defaultConfig.disabled ?? false,\n\t\tdebug: userConfig.debug ?? defaultConfig.debug ?? false,\n\t\tposthogOptions: {\n\t\t\t...defaultConfig.posthogOptions,\n\t\t\t...userConfig.posthogOptions,\n\t\t},\n\t};\n}\n"],"mappings":";AACO,SAAS,YAAY,OAAuB;AAClD,MAAI,CAAC,MAAO,QAAO,IAAI,MAAM,qBAAqB;AAElD,MAAI,iBAAiB,MAAO,QAAO;AAGnC,MAAI,OAAO,UAAU,cAAc,KAAK,OAAO,KAAK,EAAG,QAAO;AAE9D,MAAI,cAAc;AAClB,MAAI;AACH,kBAAc,KAAK,UAAU,KAAK;AAAA,EACnC,SAAS,GAAG;AAEX,YAAQ,MAAM,CAAC;AAAA,EAChB;AAEA,QAAM,QAAQ,IAAI,MAAM,sDAAsD,WAAW,EAAE;AAC3F,SAAO;AACR;;;ACbA,IAAM,qBAAqB;AAQ3B,IAAM,uBAAuB;AAKtB,SAAS,kBAA2B;AAC1C,MAAI,OAAO,YAAY,aAAa;AACnC,WAAO;AAAA,EACR;AAEA,SAAO,QAAQ,IAAI,sBAAsB,UAAU,QAAQ,IAAI,kCAAkC;AAClG;AAMO,IAAM,gBAA0C;AAAA;AAAA,EAEtD,SAAS;AAAA;AAAA,EAGT,UAAU;AAAA;AAAA,EAGV,OAAO;AAAA;AAAA,EAGP,gBAAgB;AAAA;AAAA,IAEf,2BAA2B;AAAA;AAAA,IAG3B,aAAa;AAAA;AAAA,IAGb,kBAAkB;AAAA;AAAA,IAGlB,mBAAmB;AAAA;AAAA,IAGnB,wBAAwB;AAAA;AAAA,IAGxB,gCAAgC;AAAA;AAAA,IAGhC,8CAA8C;AAAA;AAAA,IAG9C,aAAa;AAAA,EACd;AACD;AAYO,SAAS,YAAY,YAAwD;AACnF,SAAO;AAAA,IACN,KAAK,WAAW;AAAA,IAChB,SAAS,WAAW,WAAW,cAAc,WAAW;AAAA,IACxD,QAAQ,WAAW,UAAU;AAAA,IAC7B,gBAAgB,WAAW;AAAA,IAC3B,UAAU,WAAW,YAAY,gBAAgB,KAAK,cAAc,YAAY;AAAA,IAChF,OAAO,WAAW,SAAS,cAAc,SAAS;AAAA,IAClD,gBAAgB;AAAA,MACf,GAAG,cAAc;AAAA,MACjB,GAAG,WAAW;AAAA,IACf;AAAA,EACD;AACD;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,15 @@
1
- import { A as AnalyticsConfig, a as Analytics } from './types-Ymgjicqi.js';
2
- export { E as ErrorProperties, b as EventProperties, c as EventType, e as eventTypes, l as legacyEventTypeMap } from './types-Ymgjicqi.js';
1
+ import { A as AnalyticsConfig, a as Analytics } from './types-BxfgCtu7.js';
2
+ export { b as AppName, E as ErrorProperties, c as EventProperties, d as EventType, e as appNames, f as eventTypes, l as legacyEventTypeMap } from './types-BxfgCtu7.js';
3
3
  import * as react from 'react';
4
4
  import { ReactNode } from 'react';
5
5
  import 'zod';
6
6
 
7
+ /**
8
+ * Create a client-side analytics instance
9
+ * This wraps PostHog with Network Canvas-specific functionality
10
+ */
11
+ declare function createAnalytics(config: Required<AnalyticsConfig>): Analytics;
12
+
7
13
  /**
8
14
  * Check if analytics is disabled via environment variables
9
15
  */
@@ -86,10 +92,10 @@ declare function useFeatureFlagValue(flagKey: string): string | boolean | undefi
86
92
  /**
87
93
  * Props for the AnalyticsProvider
88
94
  */
89
- interface AnalyticsProviderProps {
95
+ type AnalyticsProviderProps = {
90
96
  children: ReactNode;
91
97
  config: AnalyticsConfig;
92
- }
98
+ };
93
99
  /**
94
100
  * Provider component that initializes PostHog and provides analytics context
95
101
  *
@@ -116,4 +122,4 @@ declare function AnalyticsProvider({ children, config }: AnalyticsProviderProps)
116
122
 
117
123
  declare function ensureError(value: unknown): Error;
118
124
 
119
- export { Analytics, AnalyticsConfig, AnalyticsProvider, type AnalyticsProviderProps, defaultConfig, ensureError, isDisabledByEnv, mergeConfig, useAnalytics, useFeatureFlag, useFeatureFlagValue };
125
+ export { Analytics, AnalyticsConfig, AnalyticsProvider, type AnalyticsProviderProps, createAnalytics, defaultConfig, ensureError, isDisabledByEnv, mergeConfig, useAnalytics, useFeatureFlag, useFeatureFlagValue };
package/dist/index.js CHANGED
@@ -3,18 +3,12 @@ import {
3
3
  ensureError,
4
4
  isDisabledByEnv,
5
5
  mergeConfig
6
- } from "./chunk-3NEQVIC4.js";
7
-
8
- // src/hooks.ts
9
- import { useContext } from "react";
10
-
11
- // src/provider.tsx
12
- import { createContext, useEffect, useRef } from "react";
6
+ } from "./chunk-TPZBZWEN.js";
13
7
 
14
8
  // src/client.ts
15
9
  import posthog from "posthog-js";
16
10
  function createAnalytics(config) {
17
- const { apiHost, apiKey, installationId, disabled, debug, posthogOptions } = config;
11
+ const { app, apiHost, apiKey, installationId, disabled, debug, posthogOptions } = config;
18
12
  if (disabled) {
19
13
  return createNoOpAnalytics(installationId);
20
14
  }
@@ -22,6 +16,7 @@ function createAnalytics(config) {
22
16
  api_host: apiHost,
23
17
  loaded: (posthogInstance) => {
24
18
  posthogInstance.register({
19
+ app,
25
20
  installation_id: installationId
26
21
  });
27
22
  if (debug) {
@@ -135,7 +130,11 @@ function createNoOpAnalytics(installationId) {
135
130
  };
136
131
  }
137
132
 
133
+ // src/hooks.ts
134
+ import { useContext } from "react";
135
+
138
136
  // src/provider.tsx
137
+ import { createContext, useEffect, useRef } from "react";
139
138
  var AnalyticsContext = createContext(null);
140
139
  function AnalyticsProvider({ children, config }) {
141
140
  const analyticsRef = useRef(null);
@@ -169,7 +168,7 @@ function useFeatureFlagValue(flagKey) {
169
168
  }
170
169
 
171
170
  // src/types.ts
172
- import z from "zod";
171
+ import { z } from "zod";
173
172
  var eventTypes = [
174
173
  "app_setup",
175
174
  "protocol_installed",
@@ -186,6 +185,16 @@ var legacyEventTypeMap = {
186
185
  DataExported: "data_exported",
187
186
  Error: "error"
188
187
  };
188
+ var appNames = [
189
+ "Fresco",
190
+ "Studio",
191
+ "Architect",
192
+ "Interviewer",
193
+ "ArchitectWeb",
194
+ "CommunityForum",
195
+ "ProjectWebsite",
196
+ "Documentation"
197
+ ];
189
198
  var EventPropertiesSchema = z.object({
190
199
  metadata: z.record(z.string(), z.unknown()).optional()
191
200
  });
@@ -197,6 +206,8 @@ var ErrorPropertiesSchema = EventPropertiesSchema.extend({
197
206
  });
198
207
  export {
199
208
  AnalyticsProvider,
209
+ appNames,
210
+ createAnalytics,
200
211
  defaultConfig,
201
212
  ensureError,
202
213
  eventTypes,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks.ts","../src/provider.tsx","../src/client.ts","../src/types.ts"],"sourcesContent":["\"use client\";\n\nimport { useContext } from \"react\";\nimport { AnalyticsContext } from \"./provider\";\nimport type { Analytics } from \"./types\";\n\n/**\n * Hook to access analytics functionality in React components\n *\n * @example\n * ```tsx\n * import { useAnalytics } from '@codaco/analytics';\n *\n * function MyComponent() {\n * const { trackEvent, trackError } = useAnalytics();\n *\n * const handleAction = () => {\n * trackEvent('protocol_installed', {\n * metadata: { protocolName: 'My Protocol' }\n * });\n * };\n *\n * return <button onClick={handleAction}>Install Protocol</button>;\n * }\n * ```\n */\nexport function useAnalytics(): Analytics {\n\tconst analytics = useContext(AnalyticsContext);\n\n\tif (!analytics) {\n\t\tthrow new Error(\"useAnalytics must be used within an AnalyticsProvider\");\n\t}\n\n\treturn analytics;\n}\n\n/**\n * Hook to access feature flags\n *\n * @example\n * ```tsx\n * import { useFeatureFlag } from '@codaco/analytics';\n *\n * function MyComponent() {\n * const isNewFeatureEnabled = useFeatureFlag('new-feature');\n *\n * if (isNewFeatureEnabled) {\n * return <NewFeature />;\n * }\n *\n * return <OldFeature />;\n * }\n * ```\n */\nexport function useFeatureFlag(flagKey: string): boolean {\n\tconst analytics = useAnalytics();\n\treturn analytics.isFeatureEnabled(flagKey) ?? false;\n}\n\n/**\n * Hook to access feature flag values (for multivariate flags)\n *\n * @example\n * ```tsx\n * import { useFeatureFlagValue } from '@codaco/analytics';\n *\n * function MyComponent() {\n * const theme = useFeatureFlagValue('theme-variant');\n *\n * return <div className={theme === 'dark' ? 'dark-theme' : 'light-theme'}>\n * Content\n * </div>;\n * }\n * ```\n */\nexport function useFeatureFlagValue(flagKey: string): string | boolean | undefined {\n\tconst analytics = useAnalytics();\n\treturn analytics.getFeatureFlag(flagKey);\n}\n","\"use client\";\n\nimport { createContext, type ReactNode, useEffect, useRef } from \"react\";\nimport { createAnalytics } from \"./client\";\nimport { mergeConfig } from \"./config\";\nimport type { Analytics, AnalyticsConfig } from \"./types\";\n\n/**\n * React Context for analytics\n */\nexport const AnalyticsContext = createContext<Analytics | null>(null);\n\n/**\n * Props for the AnalyticsProvider\n */\nexport interface AnalyticsProviderProps {\n\tchildren: ReactNode;\n\tconfig: AnalyticsConfig;\n}\n\n/**\n * Provider component that initializes PostHog and provides analytics context\n *\n * @example\n * ```tsx\n * import { AnalyticsProvider } from '@codaco/analytics';\n *\n * function App({ children }) {\n * return (\n * <AnalyticsProvider\n * config={{\n * installationId: 'your-installation-id',\n * apiKey: 'phc_your_api_key', // optional if set via env\n * apiHost: 'https://ph-relay.networkcanvas.com', // optional\n * }}\n * >\n * {children}\n * </AnalyticsProvider>\n * );\n * }\n * ```\n */\nexport function AnalyticsProvider({ children, config }: AnalyticsProviderProps) {\n\tconst analyticsRef = useRef<Analytics | null>(null);\n\n\t// Initialize analytics only once\n\tuseEffect(() => {\n\t\tif (!analyticsRef.current) {\n\t\t\tconst mergedConfig = mergeConfig(config);\n\t\t\tanalyticsRef.current = createAnalytics(mergedConfig);\n\t\t}\n\t}, []); // Empty deps - only initialize once\n\n\t// Don't render children until analytics is initialized\n\tif (!analyticsRef.current) {\n\t\treturn null;\n\t}\n\n\treturn <AnalyticsContext.Provider value={analyticsRef.current}>{children}</AnalyticsContext.Provider>;\n}\n","import posthog from \"posthog-js\";\nimport type { Analytics, AnalyticsConfig, ErrorProperties, EventProperties, EventType } from \"./types\";\nimport { ensureError } from \"./utils\";\n\n/**\n * Create a client-side analytics instance\n * This wraps PostHog with Network Canvas-specific functionality\n */\nexport function createAnalytics(config: Required<AnalyticsConfig>): Analytics {\n\tconst { apiHost, apiKey, installationId, disabled, debug, posthogOptions } = config;\n\n\t// If analytics is disabled, return a no-op implementation\n\tif (disabled) {\n\t\treturn createNoOpAnalytics(installationId);\n\t}\n\n\t// Initialize PostHog\n\tposthog.init(apiKey, {\n\t\tapi_host: apiHost,\n\t\tloaded: (posthogInstance) => {\n\t\t\t// Set installation ID as a super property (included with every event)\n\t\t\tposthogInstance.register({\n\t\t\t\tinstallation_id: installationId,\n\t\t\t});\n\n\t\t\tif (debug) {\n\t\t\t\tposthogInstance.debug();\n\t\t\t}\n\t\t},\n\t\t...posthogOptions,\n\t});\n\n\treturn {\n\t\ttrackEvent: (eventType: EventType | string, properties?: EventProperties) => {\n\t\t\tif (disabled) return;\n\n\t\t\ttry {\n\t\t\t\tposthog.capture(eventType, {\n\t\t\t\t\t...properties,\n\t\t\t\t\t// Flatten metadata into properties for better PostHog integration\n\t\t\t\t\t...(properties?.metadata ?? {}),\n\t\t\t\t});\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\ttrackError: (error: Error, additionalProperties?: EventProperties) => {\n\t\t\tif (disabled) return;\n\n\t\t\ttry {\n\t\t\t\tconst errorObj = ensureError(error);\n\t\t\t\tconst errorProperties: ErrorProperties = {\n\t\t\t\t\tmessage: errorObj.message,\n\t\t\t\t\tname: errorObj.name,\n\t\t\t\t\tstack: errorObj.stack,\n\t\t\t\t\tcause: errorObj.cause ? String(errorObj.cause) : undefined,\n\t\t\t\t\t...additionalProperties,\n\t\t\t\t};\n\n\t\t\t\tposthog.capture(\"error\", {\n\t\t\t\t\t...errorProperties,\n\t\t\t\t\t// Flatten metadata\n\t\t\t\t\t...(additionalProperties?.metadata ?? {}),\n\t\t\t\t});\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tisFeatureEnabled: (flagKey: string) => {\n\t\t\tif (disabled) return false;\n\n\t\t\ttry {\n\t\t\t\treturn posthog.isFeatureEnabled(flagKey);\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t},\n\n\t\tgetFeatureFlag: (flagKey: string) => {\n\t\t\tif (disabled) return undefined;\n\n\t\t\ttry {\n\t\t\t\treturn posthog.getFeatureFlag(flagKey);\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t},\n\n\t\treloadFeatureFlags: () => {\n\t\t\tif (disabled) return;\n\n\t\t\ttry {\n\t\t\t\tposthog.reloadFeatureFlags();\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tidentify: (distinctId: string, properties?: Record<string, unknown>) => {\n\t\t\tif (disabled) return;\n\n\t\t\ttry {\n\t\t\t\tposthog.identify(distinctId, properties);\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\treset: () => {\n\t\t\tif (disabled) return;\n\n\t\t\ttry {\n\t\t\t\tposthog.reset();\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tisEnabled: () => !disabled,\n\n\t\tgetInstallationId: () => installationId,\n\t};\n}\n\n/**\n * Create a no-op analytics instance when analytics is disabled\n */\nfunction createNoOpAnalytics(installationId: string): Analytics {\n\treturn {\n\t\ttrackEvent: () => {},\n\t\ttrackError: () => {},\n\t\tisFeatureEnabled: () => false,\n\t\tgetFeatureFlag: () => undefined,\n\t\treloadFeatureFlags: () => {},\n\t\tidentify: () => {},\n\t\treset: () => {},\n\t\tisEnabled: () => false,\n\t\tgetInstallationId: () => installationId,\n\t};\n}\n","import z from \"zod\";\n\n/**\n * Event types supported by the analytics system.\n * These are converted to snake_case for PostHog.\n */\nexport const eventTypes = [\n\t\"app_setup\",\n\t\"protocol_installed\",\n\t\"interview_started\",\n\t\"interview_completed\",\n\t\"data_exported\",\n\t\"error\",\n] as const;\n\nexport type EventType = (typeof eventTypes)[number];\n\n/**\n * Legacy event type mapping for backward compatibility\n */\nexport const legacyEventTypeMap: Record<string, EventType> = {\n\tAppSetup: \"app_setup\",\n\tProtocolInstalled: \"protocol_installed\",\n\tInterviewStarted: \"interview_started\",\n\tInterviewCompleted: \"interview_completed\",\n\tDataExported: \"data_exported\",\n\tError: \"error\",\n};\n\n/**\n * Standard event properties that can be sent with any event\n */\nexport const EventPropertiesSchema = z.object({\n\tmetadata: z.record(z.string(), z.unknown()).optional(),\n});\n\nexport type EventProperties = z.infer<typeof EventPropertiesSchema>;\n\n/**\n * Error-specific properties for error tracking\n */\nexport const ErrorPropertiesSchema = EventPropertiesSchema.extend({\n\tmessage: z.string(),\n\tname: z.string(),\n\tstack: z.string().optional(),\n\tcause: z.string().optional(),\n});\n\nexport type ErrorProperties = z.infer<typeof ErrorPropertiesSchema>;\n\n/**\n * Analytics configuration options\n *\n * This package is designed to work exclusively with the Cloudflare Worker\n * reverse proxy at ph-relay.networkcanvas.com. All authentication is handled\n * by the worker, so the API key is optional.\n */\nexport interface AnalyticsConfig {\n\t/**\n\t * PostHog API host - should point to the Cloudflare Worker reverse proxy\n\t * Defaults to \"https://ph-relay.networkcanvas.com\"\n\t */\n\tapiHost?: string;\n\n\t/**\n\t * PostHog project API key (optional)\n\t *\n\t * When using the reverse proxy (default), authentication is handled by the\n\t * Cloudflare Worker. A placeholder key will be used for client-side PostHog\n\t * initialization if not provided.\n\t *\n\t * Only set this if you need to override the default behavior.\n\t */\n\tapiKey?: string;\n\n\t/**\n\t * Unique identifier for this installation/deployment\n\t * This is included with every event as a super property\n\t */\n\tinstallationId: string;\n\n\t/**\n\t * Disable all analytics tracking\n\t * Can be set via DISABLE_ANALYTICS or NEXT_PUBLIC_DISABLE_ANALYTICS env var\n\t */\n\tdisabled?: boolean;\n\n\t/**\n\t * Enable debug mode for PostHog\n\t */\n\tdebug?: boolean;\n\n\t/**\n\t * Additional options to pass to PostHog initialization\n\t */\n\tposthogOptions?: {\n\t\t/**\n\t\t * Disable session recording\n\t\t */\n\t\tdisable_session_recording?: boolean;\n\n\t\t/**\n\t\t * Autocapture settings\n\t\t */\n\t\tautocapture?: boolean;\n\n\t\t/**\n\t\t * Capture pageviews automatically\n\t\t */\n\t\tcapture_pageview?: boolean;\n\n\t\t/**\n\t\t * Capture pageleave events\n\t\t */\n\t\tcapture_pageleave?: boolean;\n\n\t\t/**\n\t\t * Cross-subdomain cookie\n\t\t */\n\t\tcross_subdomain_cookie?: boolean;\n\n\t\t/**\n\t\t * Advanced feature flags support\n\t\t */\n\t\tadvanced_disable_feature_flags?: boolean;\n\n\t\t/**\n\t\t * Other PostHog options\n\t\t */\n\t\t[key: string]: unknown;\n\t};\n}\n\n/**\n * Analytics instance interface\n */\nexport interface Analytics {\n\t/**\n\t * Track a custom event\n\t */\n\ttrackEvent: (eventType: EventType | string, properties?: EventProperties) => void;\n\n\t/**\n\t * Track an error with full stack trace\n\t */\n\ttrackError: (error: Error, additionalProperties?: EventProperties) => void;\n\n\t/**\n\t * Check if a feature flag is enabled\n\t */\n\tisFeatureEnabled: (flagKey: string) => boolean | undefined;\n\n\t/**\n\t * Get the value of a feature flag\n\t */\n\tgetFeatureFlag: (flagKey: string) => string | boolean | undefined;\n\n\t/**\n\t * Reload feature flags from PostHog\n\t */\n\treloadFeatureFlags: () => void;\n\n\t/**\n\t * Identify a user (optional - for advanced use cases)\n\t * Note: By default we only track installations, not users\n\t */\n\tidentify: (distinctId: string, properties?: Record<string, unknown>) => void;\n\n\t/**\n\t * Reset the user identity\n\t */\n\treset: () => void;\n\n\t/**\n\t * Check if analytics is enabled\n\t */\n\tisEnabled: () => boolean;\n\n\t/**\n\t * Get the installation ID\n\t */\n\tgetInstallationId: () => string;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/client.ts","../src/hooks.ts","../src/provider.tsx","../src/types.ts"],"sourcesContent":["import posthog from \"posthog-js\";\nimport type { Analytics, AnalyticsConfig, ErrorProperties, EventProperties, EventType } from \"./types\";\nimport { ensureError } from \"./utils\";\n\n/**\n * Create a client-side analytics instance\n * This wraps PostHog with Network Canvas-specific functionality\n */\nexport function createAnalytics(config: Required<AnalyticsConfig>): Analytics {\n\tconst { app, apiHost, apiKey, installationId, disabled, debug, posthogOptions } = config;\n\n\t// If analytics is disabled, return a no-op implementation\n\tif (disabled) {\n\t\treturn createNoOpAnalytics(installationId);\n\t}\n\n\t// Initialize PostHog\n\tposthog.init(apiKey, {\n\t\tapi_host: apiHost,\n\t\tloaded: (posthogInstance) => {\n\t\t\t// Set installation ID as a super property (included with every event)\n\t\t\tposthogInstance.register({\n\t\t\t\tapp,\n\t\t\t\tinstallation_id: installationId,\n\t\t\t});\n\n\t\t\tif (debug) {\n\t\t\t\tposthogInstance.debug();\n\t\t\t}\n\t\t},\n\t\t...posthogOptions,\n\t});\n\n\treturn {\n\t\ttrackEvent: (eventType: EventType | string, properties?: EventProperties) => {\n\t\t\tif (disabled) return;\n\n\t\t\ttry {\n\t\t\t\tposthog.capture(eventType, {\n\t\t\t\t\t...properties,\n\t\t\t\t\t// Flatten metadata into properties for better PostHog integration\n\t\t\t\t\t...(properties?.metadata ?? {}),\n\t\t\t\t});\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\ttrackError: (error: Error, additionalProperties?: EventProperties) => {\n\t\t\tif (disabled) return;\n\n\t\t\ttry {\n\t\t\t\tconst errorObj = ensureError(error);\n\t\t\t\tconst errorProperties: ErrorProperties = {\n\t\t\t\t\tmessage: errorObj.message,\n\t\t\t\t\tname: errorObj.name,\n\t\t\t\t\tstack: errorObj.stack,\n\t\t\t\t\tcause: errorObj.cause ? String(errorObj.cause) : undefined,\n\t\t\t\t\t...additionalProperties,\n\t\t\t\t};\n\n\t\t\t\tposthog.capture(\"error\", {\n\t\t\t\t\t...errorProperties,\n\t\t\t\t\t// Flatten metadata\n\t\t\t\t\t...(additionalProperties?.metadata ?? {}),\n\t\t\t\t});\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tisFeatureEnabled: (flagKey: string) => {\n\t\t\tif (disabled) return false;\n\n\t\t\ttry {\n\t\t\t\treturn posthog.isFeatureEnabled(flagKey);\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t},\n\n\t\tgetFeatureFlag: (flagKey: string) => {\n\t\t\tif (disabled) return undefined;\n\n\t\t\ttry {\n\t\t\t\treturn posthog.getFeatureFlag(flagKey);\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t},\n\n\t\treloadFeatureFlags: () => {\n\t\t\tif (disabled) return;\n\n\t\t\ttry {\n\t\t\t\tposthog.reloadFeatureFlags();\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tidentify: (distinctId: string, properties?: Record<string, unknown>) => {\n\t\t\tif (disabled) return;\n\n\t\t\ttry {\n\t\t\t\tposthog.identify(distinctId, properties);\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\treset: () => {\n\t\t\tif (disabled) return;\n\n\t\t\ttry {\n\t\t\t\tposthog.reset();\n\t\t\t} catch (_e) {\n\t\t\t\tif (debug) {\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tisEnabled: () => !disabled,\n\n\t\tgetInstallationId: () => installationId,\n\t};\n}\n\n/**\n * Create a no-op analytics instance when analytics is disabled\n */\nfunction createNoOpAnalytics(installationId: string): Analytics {\n\treturn {\n\t\ttrackEvent: () => {},\n\t\ttrackError: () => {},\n\t\tisFeatureEnabled: () => false,\n\t\tgetFeatureFlag: () => undefined,\n\t\treloadFeatureFlags: () => {},\n\t\tidentify: () => {},\n\t\treset: () => {},\n\t\tisEnabled: () => false,\n\t\tgetInstallationId: () => installationId,\n\t};\n}\n","\"use client\";\n\nimport { useContext } from \"react\";\nimport { AnalyticsContext } from \"./provider\";\nimport type { Analytics } from \"./types\";\n\n/**\n * Hook to access analytics functionality in React components\n *\n * @example\n * ```tsx\n * import { useAnalytics } from '@codaco/analytics';\n *\n * function MyComponent() {\n * const { trackEvent, trackError } = useAnalytics();\n *\n * const handleAction = () => {\n * trackEvent('protocol_installed', {\n * metadata: { protocolName: 'My Protocol' }\n * });\n * };\n *\n * return <button onClick={handleAction}>Install Protocol</button>;\n * }\n * ```\n */\nexport function useAnalytics(): Analytics {\n\tconst analytics = useContext(AnalyticsContext);\n\n\tif (!analytics) {\n\t\tthrow new Error(\"useAnalytics must be used within an AnalyticsProvider\");\n\t}\n\n\treturn analytics;\n}\n\n/**\n * Hook to access feature flags\n *\n * @example\n * ```tsx\n * import { useFeatureFlag } from '@codaco/analytics';\n *\n * function MyComponent() {\n * const isNewFeatureEnabled = useFeatureFlag('new-feature');\n *\n * if (isNewFeatureEnabled) {\n * return <NewFeature />;\n * }\n *\n * return <OldFeature />;\n * }\n * ```\n */\nexport function useFeatureFlag(flagKey: string): boolean {\n\tconst analytics = useAnalytics();\n\treturn analytics.isFeatureEnabled(flagKey) ?? false;\n}\n\n/**\n * Hook to access feature flag values (for multivariate flags)\n *\n * @example\n * ```tsx\n * import { useFeatureFlagValue } from '@codaco/analytics';\n *\n * function MyComponent() {\n * const theme = useFeatureFlagValue('theme-variant');\n *\n * return <div className={theme === 'dark' ? 'dark-theme' : 'light-theme'}>\n * Content\n * </div>;\n * }\n * ```\n */\nexport function useFeatureFlagValue(flagKey: string): string | boolean | undefined {\n\tconst analytics = useAnalytics();\n\treturn analytics.getFeatureFlag(flagKey);\n}\n","\"use client\";\n\nimport { createContext, type ReactNode, useEffect, useRef } from \"react\";\nimport { createAnalytics } from \"./client\";\nimport { mergeConfig } from \"./config\";\nimport type { Analytics, AnalyticsConfig } from \"./types\";\n\n/**\n * React Context for analytics\n */\nexport const AnalyticsContext = createContext<Analytics | null>(null);\n\n/**\n * Props for the AnalyticsProvider\n */\nexport type AnalyticsProviderProps = {\n\tchildren: ReactNode;\n\tconfig: AnalyticsConfig;\n};\n\n/**\n * Provider component that initializes PostHog and provides analytics context\n *\n * @example\n * ```tsx\n * import { AnalyticsProvider } from '@codaco/analytics';\n *\n * function App({ children }) {\n * return (\n * <AnalyticsProvider\n * config={{\n * installationId: 'your-installation-id',\n * apiKey: 'phc_your_api_key', // optional if set via env\n * apiHost: 'https://ph-relay.networkcanvas.com', // optional\n * }}\n * >\n * {children}\n * </AnalyticsProvider>\n * );\n * }\n * ```\n */\nexport function AnalyticsProvider({ children, config }: AnalyticsProviderProps) {\n\tconst analyticsRef = useRef<Analytics | null>(null);\n\n\t// Initialize analytics only once\n\tuseEffect(() => {\n\t\tif (!analyticsRef.current) {\n\t\t\tconst mergedConfig = mergeConfig(config);\n\t\t\tanalyticsRef.current = createAnalytics(mergedConfig);\n\t\t}\n\t}, []); // Empty deps - only initialize once\n\n\t// Don't render children until analytics is initialized\n\tif (!analyticsRef.current) {\n\t\treturn null;\n\t}\n\n\treturn <AnalyticsContext.Provider value={analyticsRef.current}>{children}</AnalyticsContext.Provider>;\n}\n","import { z } from \"zod\";\n\n/**\n * Common event types supported by the analytics system.\n * This list is non-exhaustive — trackEvent accepts any string.\n * These are provided for discoverability and autocomplete.\n */\nexport const eventTypes = [\n\t\"app_setup\",\n\t\"protocol_installed\",\n\t\"interview_started\",\n\t\"interview_completed\",\n\t\"data_exported\",\n\t\"error\",\n] as const;\n\nexport type EventType = (typeof eventTypes)[number];\n\n/**\n * Legacy event type mapping for backward compatibility\n */\nexport const legacyEventTypeMap: Record<string, EventType> = {\n\tAppSetup: \"app_setup\",\n\tProtocolInstalled: \"protocol_installed\",\n\tInterviewStarted: \"interview_started\",\n\tInterviewCompleted: \"interview_completed\",\n\tDataExported: \"data_exported\",\n\tError: \"error\",\n};\n\n/**\n * Supported application identifiers.\n * Used to segment events within a single PostHog project.\n */\nexport const appNames = [\n\t\"Fresco\",\n\t\"Studio\",\n\t\"Architect\",\n\t\"Interviewer\",\n\t\"ArchitectWeb\",\n\t\"CommunityForum\",\n\t\"ProjectWebsite\",\n\t\"Documentation\",\n] as const;\n\nexport type AppName = (typeof appNames)[number];\n\n/**\n * Standard event properties that can be sent with any event\n */\nexport const EventPropertiesSchema = z.object({\n\tmetadata: z.record(z.string(), z.unknown()).optional(),\n});\n\nexport type EventProperties = z.infer<typeof EventPropertiesSchema>;\n\n/**\n * Error-specific properties for error tracking\n */\nexport const ErrorPropertiesSchema = EventPropertiesSchema.extend({\n\tmessage: z.string(),\n\tname: z.string(),\n\tstack: z.string().optional(),\n\tcause: z.string().optional(),\n});\n\nexport type ErrorProperties = z.infer<typeof ErrorPropertiesSchema>;\n\n/**\n * Analytics configuration options\n *\n * This package is designed to work exclusively with the Cloudflare Worker\n * reverse proxy at ph-relay.networkcanvas.com. All authentication is handled\n * by the worker, so the API key is optional.\n */\nexport type AnalyticsConfig = {\n\t/**\n\t * Application identifier. Included with every event as a super property\n\t * to segment analytics within a single PostHog project.\n\t */\n\tapp: AppName;\n\n\t/**\n\t * PostHog API host - should point to the Cloudflare Worker reverse proxy\n\t * Defaults to \"https://ph-relay.networkcanvas.com\"\n\t */\n\tapiHost?: string;\n\n\t/**\n\t * PostHog project API key (optional)\n\t *\n\t * When using the reverse proxy (default), authentication is handled by the\n\t * Cloudflare Worker. A placeholder key will be used for client-side PostHog\n\t * initialization if not provided.\n\t *\n\t * Only set this if you need to override the default behavior.\n\t */\n\tapiKey?: string;\n\n\t/**\n\t * Unique identifier for this installation/deployment\n\t * This is included with every event as a super property\n\t */\n\tinstallationId: string;\n\n\t/**\n\t * Disable all analytics tracking\n\t * Can be set via DISABLE_ANALYTICS or NEXT_PUBLIC_DISABLE_ANALYTICS env var\n\t */\n\tdisabled?: boolean;\n\n\t/**\n\t * Enable debug mode for PostHog\n\t */\n\tdebug?: boolean;\n\n\t/**\n\t * Additional options to pass to PostHog initialization\n\t */\n\tposthogOptions?: {\n\t\t/**\n\t\t * Disable session recording\n\t\t */\n\t\tdisable_session_recording?: boolean;\n\n\t\t/**\n\t\t * Autocapture settings\n\t\t */\n\t\tautocapture?: boolean;\n\n\t\t/**\n\t\t * Capture pageviews automatically\n\t\t */\n\t\tcapture_pageview?: boolean;\n\n\t\t/**\n\t\t * Capture pageleave events\n\t\t */\n\t\tcapture_pageleave?: boolean;\n\n\t\t/**\n\t\t * Cross-subdomain cookie\n\t\t */\n\t\tcross_subdomain_cookie?: boolean;\n\n\t\t/**\n\t\t * Advanced feature flags support\n\t\t */\n\t\tadvanced_disable_feature_flags?: boolean;\n\n\t\t/**\n\t\t * Other PostHog options\n\t\t */\n\t\t[key: string]: unknown;\n\t};\n};\n\n/**\n * Analytics instance interface\n */\nexport type Analytics = {\n\t/**\n\t * Track a custom event\n\t */\n\ttrackEvent: (eventType: EventType | string, properties?: EventProperties) => void;\n\n\t/**\n\t * Track an error with full stack trace\n\t */\n\ttrackError: (error: Error, additionalProperties?: EventProperties) => void;\n\n\t/**\n\t * Check if a feature flag is enabled\n\t */\n\tisFeatureEnabled: (flagKey: string) => boolean | undefined;\n\n\t/**\n\t * Get the value of a feature flag\n\t */\n\tgetFeatureFlag: (flagKey: string) => string | boolean | undefined;\n\n\t/**\n\t * Reload feature flags from PostHog\n\t */\n\treloadFeatureFlags: () => void;\n\n\t/**\n\t * Identify a user (optional - for advanced use cases)\n\t * Note: By default we only track installations, not users\n\t */\n\tidentify: (distinctId: string, properties?: Record<string, unknown>) => void;\n\n\t/**\n\t * Reset the user identity\n\t */\n\treset: () => void;\n\n\t/**\n\t * Check if analytics is enabled\n\t */\n\tisEnabled: () => boolean;\n\n\t/**\n\t * Get the installation ID\n\t */\n\tgetInstallationId: () => string;\n};\n"],"mappings":"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","names":[]}
package/dist/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as AnalyticsConfig, a as Analytics } from './types-Ymgjicqi.js';
1
+ import { A as AnalyticsConfig, a as Analytics } from './types-BxfgCtu7.js';
2
2
  import 'zod';
3
3
 
4
4
  /**
package/dist/server.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ensureError,
3
3
  mergeConfig
4
- } from "./chunk-3NEQVIC4.js";
4
+ } from "./chunk-TPZBZWEN.js";
5
5
 
6
6
  // src/server.ts
7
7
  var ServerAnalytics = class {
@@ -98,6 +98,7 @@ var ServerAnalytics = class {
98
98
  event,
99
99
  properties: {
100
100
  ...properties,
101
+ app: this.config.app,
101
102
  installation_id: this.config.installationId
102
103
  },
103
104
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { mergeConfig } from \"./config\";\nimport type { Analytics, AnalyticsConfig, ErrorProperties, EventProperties, EventType } from \"./types\";\nimport { ensureError } from \"./utils\";\n\n/**\n * Server-side analytics implementation\n * This uses PostHog's API directly for server-side tracking\n */\nclass ServerAnalytics implements Analytics {\n\tprivate config: Required<AnalyticsConfig>;\n\tprivate disabled: boolean;\n\n\tconstructor(config: AnalyticsConfig) {\n\t\tthis.config = mergeConfig(config);\n\t\tthis.disabled = this.config.disabled;\n\t}\n\n\t/**\n\t * Track an event on the server-side\n\t */\n\ttrackEvent(eventType: EventType | string, properties?: EventProperties): void {\n\t\tif (this.disabled) return;\n\n\t\t// Send event to PostHog using fetch\n\t\tthis.sendToPostHog(eventType, {\n\t\t\t...properties,\n\t\t\t...(properties?.metadata ?? {}),\n\t\t}).catch((_error) => {\n\t\t\tif (this.config.debug) {\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Track an error on the server-side\n\t */\n\ttrackError(error: Error, additionalProperties?: EventProperties): void {\n\t\tif (this.disabled) return;\n\n\t\tconst errorObj = ensureError(error);\n\t\tconst errorProperties: ErrorProperties = {\n\t\t\tmessage: errorObj.message,\n\t\t\tname: errorObj.name,\n\t\t\tstack: errorObj.stack,\n\t\t\tcause: errorObj.cause ? String(errorObj.cause) : undefined,\n\t\t\t...additionalProperties,\n\t\t};\n\n\t\tthis.sendToPostHog(\"error\", {\n\t\t\t...errorProperties,\n\t\t\t...(additionalProperties?.metadata ?? {}),\n\t\t}).catch((_error) => {\n\t\t\tif (this.config.debug) {\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Feature flags are not supported in server-side mode\n\t * Use client-side hooks or PostHog API directly for feature flags\n\t */\n\tisFeatureEnabled(_flagKey: string): boolean {\n\t\treturn false;\n\t}\n\n\t/**\n\t * Feature flags are not supported in server-side mode\n\t */\n\tgetFeatureFlag(_flagKey: string): string | boolean | undefined {\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Feature flags are not supported in server-side mode\n\t */\n\treloadFeatureFlags(): void {}\n\n\t/**\n\t * User identification on the server-side\n\t */\n\tidentify(distinctId: string, properties?: Record<string, unknown>): void {\n\t\tif (this.disabled) return;\n\n\t\tthis.sendToPostHog(\"$identify\", {\n\t\t\t$set: properties ?? {},\n\t\t\tdistinct_id: distinctId,\n\t\t}).catch((_error) => {\n\t\t\tif (this.config.debug) {\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Reset is not applicable on server-side\n\t */\n\treset(): void {}\n\n\tisEnabled(): boolean {\n\t\treturn !this.disabled;\n\t}\n\n\tgetInstallationId(): string {\n\t\treturn this.config.installationId;\n\t}\n\n\t/**\n\t * Send event to PostHog using fetch API\n\t * Note: API key authentication is handled by the Cloudflare Worker proxy,\n\t * so we don't include it in the payload.\n\t */\n\tprivate async sendToPostHog(event: string, properties: Record<string, unknown>): Promise<void> {\n\t\tif (this.disabled) return;\n\n\t\tconst payload = {\n\t\t\tevent,\n\t\t\tproperties: {\n\t\t\t\t...properties,\n\t\t\t\tinstallation_id: this.config.installationId,\n\t\t\t},\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t};\n\n\t\ttry {\n\t\t\tconst response = await fetch(`${this.config.apiHost}/capture`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t// Use keepalive for reliability\n\t\t\t\tkeepalive: true,\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`PostHog API returned ${response.status}: ${response.statusText}`);\n\t\t\t}\n\t\t} catch (_error) {\n\t\t\t// Silently fail - we don't want analytics errors to break the app\n\t\t\tif (this.config.debug) {\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Global server-side analytics instance\n */\nlet serverAnalyticsInstance: ServerAnalytics | null = null;\n\n/**\n * Initialize server-side analytics\n * Call this once in your app (e.g., in a layout or middleware)\n *\n * @example\n * ```ts\n * // In your Next.js layout or API route\n * import { initServerAnalytics } from '@codaco/analytics/server';\n *\n * initServerAnalytics({\n * installationId: 'your-unique-installation-id',\n * });\n * ```\n */\nexport function initServerAnalytics(config: AnalyticsConfig): void {\n\tif (!serverAnalyticsInstance) {\n\t\tserverAnalyticsInstance = new ServerAnalytics(config);\n\t}\n}\n\n/**\n * Get the server-side analytics instance\n * Use this in server components, API routes, and server actions\n *\n * @example\n * ```ts\n * import { getServerAnalytics } from '@codaco/analytics/server';\n *\n * export async function POST(request: Request) {\n * const analytics = getServerAnalytics();\n * analytics.trackEvent('data_exported', {\n * metadata: { format: 'csv' }\n * });\n *\n * // ... rest of your handler\n * }\n * ```\n */\nexport function getServerAnalytics(): Analytics {\n\tif (!serverAnalyticsInstance) {\n\t\tthrow new Error(\n\t\t\t\"Server analytics not initialized. Call initServerAnalytics() first (e.g., in your root layout or middleware).\",\n\t\t);\n\t}\n\n\treturn serverAnalyticsInstance;\n}\n\n/**\n * Convenience export for direct usage\n * Requires calling initServerAnalytics() first\n */\nexport const serverAnalytics = new Proxy({} as Analytics, {\n\tget(_target, prop) {\n\t\tif (!serverAnalyticsInstance) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Server analytics not initialized. Call initServerAnalytics({ installationId: '...' }) first \" +\n\t\t\t\t\t\"(e.g., in your root layout or middleware).\",\n\t\t\t);\n\t\t}\n\n\t\treturn serverAnalyticsInstance[prop as keyof Analytics];\n\t},\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { mergeConfig } from \"./config\";\nimport type { Analytics, AnalyticsConfig, ErrorProperties, EventProperties, EventType } from \"./types\";\nimport { ensureError } from \"./utils\";\n\n/**\n * Server-side analytics implementation\n * This uses PostHog's API directly for server-side tracking\n */\nclass ServerAnalytics implements Analytics {\n\tprivate config: Required<AnalyticsConfig>;\n\tprivate disabled: boolean;\n\n\tconstructor(config: AnalyticsConfig) {\n\t\tthis.config = mergeConfig(config);\n\t\tthis.disabled = this.config.disabled;\n\t}\n\n\t/**\n\t * Track an event on the server-side\n\t */\n\ttrackEvent(eventType: EventType | string, properties?: EventProperties): void {\n\t\tif (this.disabled) return;\n\n\t\t// Send event to PostHog using fetch\n\t\tthis.sendToPostHog(eventType, {\n\t\t\t...properties,\n\t\t\t...(properties?.metadata ?? {}),\n\t\t}).catch((_error) => {\n\t\t\tif (this.config.debug) {\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Track an error on the server-side\n\t */\n\ttrackError(error: Error, additionalProperties?: EventProperties): void {\n\t\tif (this.disabled) return;\n\n\t\tconst errorObj = ensureError(error);\n\t\tconst errorProperties: ErrorProperties = {\n\t\t\tmessage: errorObj.message,\n\t\t\tname: errorObj.name,\n\t\t\tstack: errorObj.stack,\n\t\t\tcause: errorObj.cause ? String(errorObj.cause) : undefined,\n\t\t\t...additionalProperties,\n\t\t};\n\n\t\tthis.sendToPostHog(\"error\", {\n\t\t\t...errorProperties,\n\t\t\t...(additionalProperties?.metadata ?? {}),\n\t\t}).catch((_error) => {\n\t\t\tif (this.config.debug) {\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Feature flags are not supported in server-side mode\n\t * Use client-side hooks or PostHog API directly for feature flags\n\t */\n\tisFeatureEnabled(_flagKey: string): boolean {\n\t\treturn false;\n\t}\n\n\t/**\n\t * Feature flags are not supported in server-side mode\n\t */\n\tgetFeatureFlag(_flagKey: string): string | boolean | undefined {\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Feature flags are not supported in server-side mode\n\t */\n\treloadFeatureFlags(): void {}\n\n\t/**\n\t * User identification on the server-side\n\t */\n\tidentify(distinctId: string, properties?: Record<string, unknown>): void {\n\t\tif (this.disabled) return;\n\n\t\tthis.sendToPostHog(\"$identify\", {\n\t\t\t$set: properties ?? {},\n\t\t\tdistinct_id: distinctId,\n\t\t}).catch((_error) => {\n\t\t\tif (this.config.debug) {\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Reset is not applicable on server-side\n\t */\n\treset(): void {}\n\n\tisEnabled(): boolean {\n\t\treturn !this.disabled;\n\t}\n\n\tgetInstallationId(): string {\n\t\treturn this.config.installationId;\n\t}\n\n\t/**\n\t * Send event to PostHog using fetch API\n\t * Note: API key authentication is handled by the Cloudflare Worker proxy,\n\t * so we don't include it in the payload.\n\t */\n\tprivate async sendToPostHog(event: string, properties: Record<string, unknown>): Promise<void> {\n\t\tif (this.disabled) return;\n\n\t\tconst payload = {\n\t\t\tevent,\n\t\t\tproperties: {\n\t\t\t\t...properties,\n\t\t\t\tapp: this.config.app,\n\t\t\t\tinstallation_id: this.config.installationId,\n\t\t\t},\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t};\n\n\t\ttry {\n\t\t\tconst response = await fetch(`${this.config.apiHost}/capture`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t// Use keepalive for reliability\n\t\t\t\tkeepalive: true,\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`PostHog API returned ${response.status}: ${response.statusText}`);\n\t\t\t}\n\t\t} catch (_error) {\n\t\t\t// Silently fail - we don't want analytics errors to break the app\n\t\t\tif (this.config.debug) {\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Global server-side analytics instance\n */\nlet serverAnalyticsInstance: ServerAnalytics | null = null;\n\n/**\n * Initialize server-side analytics\n * Call this once in your app (e.g., in a layout or middleware)\n *\n * @example\n * ```ts\n * // In your Next.js layout or API route\n * import { initServerAnalytics } from '@codaco/analytics/server';\n *\n * initServerAnalytics({\n * installationId: 'your-unique-installation-id',\n * });\n * ```\n */\nexport function initServerAnalytics(config: AnalyticsConfig): void {\n\tif (!serverAnalyticsInstance) {\n\t\tserverAnalyticsInstance = new ServerAnalytics(config);\n\t}\n}\n\n/**\n * Get the server-side analytics instance\n * Use this in server components, API routes, and server actions\n *\n * @example\n * ```ts\n * import { getServerAnalytics } from '@codaco/analytics/server';\n *\n * export async function POST(request: Request) {\n * const analytics = getServerAnalytics();\n * analytics.trackEvent('data_exported', {\n * metadata: { format: 'csv' }\n * });\n *\n * // ... rest of your handler\n * }\n * ```\n */\nexport function getServerAnalytics(): Analytics {\n\tif (!serverAnalyticsInstance) {\n\t\tthrow new Error(\n\t\t\t\"Server analytics not initialized. Call initServerAnalytics() first (e.g., in your root layout or middleware).\",\n\t\t);\n\t}\n\n\treturn serverAnalyticsInstance;\n}\n\n/**\n * Convenience export for direct usage\n * Requires calling initServerAnalytics() first\n */\nexport const serverAnalytics = new Proxy({} as Analytics, {\n\tget(_target, prop) {\n\t\tif (!serverAnalyticsInstance) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Server analytics not initialized. Call initServerAnalytics({ installationId: '...' }) first \" +\n\t\t\t\t\t\"(e.g., in your root layout or middleware).\",\n\t\t\t);\n\t\t}\n\n\t\treturn serverAnalyticsInstance[prop as keyof Analytics];\n\t},\n});\n"],"mappings":"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","names":[]}
@@ -1,8 +1,9 @@
1
- import z from 'zod';
1
+ import { z } from 'zod';
2
2
 
3
3
  /**
4
- * Event types supported by the analytics system.
5
- * These are converted to snake_case for PostHog.
4
+ * Common event types supported by the analytics system.
5
+ * This list is non-exhaustive trackEvent accepts any string.
6
+ * These are provided for discoverability and autocomplete.
6
7
  */
7
8
  declare const eventTypes: readonly ["app_setup", "protocol_installed", "interview_started", "interview_completed", "data_exported", "error"];
8
9
  type EventType = (typeof eventTypes)[number];
@@ -10,6 +11,12 @@ type EventType = (typeof eventTypes)[number];
10
11
  * Legacy event type mapping for backward compatibility
11
12
  */
12
13
  declare const legacyEventTypeMap: Record<string, EventType>;
14
+ /**
15
+ * Supported application identifiers.
16
+ * Used to segment events within a single PostHog project.
17
+ */
18
+ declare const appNames: readonly ["Fresco", "Studio", "Architect", "Interviewer", "ArchitectWeb", "CommunityForum", "ProjectWebsite", "Documentation"];
19
+ type AppName = (typeof appNames)[number];
13
20
  /**
14
21
  * Standard event properties that can be sent with any event
15
22
  */
@@ -35,7 +42,12 @@ type ErrorProperties = z.infer<typeof ErrorPropertiesSchema>;
35
42
  * reverse proxy at ph-relay.networkcanvas.com. All authentication is handled
36
43
  * by the worker, so the API key is optional.
37
44
  */
38
- interface AnalyticsConfig {
45
+ type AnalyticsConfig = {
46
+ /**
47
+ * Application identifier. Included with every event as a super property
48
+ * to segment analytics within a single PostHog project.
49
+ */
50
+ app: AppName;
39
51
  /**
40
52
  * PostHog API host - should point to the Cloudflare Worker reverse proxy
41
53
  * Defaults to "https://ph-relay.networkcanvas.com"
@@ -98,11 +110,11 @@ interface AnalyticsConfig {
98
110
  */
99
111
  [key: string]: unknown;
100
112
  };
101
- }
113
+ };
102
114
  /**
103
115
  * Analytics instance interface
104
116
  */
105
- interface Analytics {
117
+ type Analytics = {
106
118
  /**
107
119
  * Track a custom event
108
120
  */
@@ -140,6 +152,6 @@ interface Analytics {
140
152
  * Get the installation ID
141
153
  */
142
154
  getInstallationId: () => string;
143
- }
155
+ };
144
156
 
145
- export { type AnalyticsConfig as A, type ErrorProperties as E, type Analytics as a, type EventProperties as b, type EventType as c, eventTypes as e, legacyEventTypeMap as l };
157
+ export { type AnalyticsConfig as A, type ErrorProperties as E, type Analytics as a, type AppName as b, type EventProperties as c, type EventType as d, appNames as e, eventTypes as f, legacyEventTypeMap as l };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codaco/analytics",
3
- "version": "9.0.0",
3
+ "version": "11.0.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -20,20 +20,20 @@
20
20
  "tag": "alpha"
21
21
  },
22
22
  "peerDependencies": {
23
- "next": "^16.0.3",
24
- "react": "^19.2.0"
23
+ "next": "^16.1.1",
24
+ "react": "^19.2.3"
25
25
  },
26
26
  "devDependencies": {
27
- "@types/node": "^22.18.8",
28
- "@types/react": "^19.2.5",
27
+ "@types/node": "^24.10.1",
28
+ "@types/react": "^19.2.8",
29
29
  "tsup": "^8.5.1",
30
30
  "typescript": "^5.9.3",
31
- "vitest": "^4.0.9",
31
+ "vitest": "^4.0.17",
32
32
  "@codaco/tsconfig": "0.1.0"
33
33
  },
34
34
  "dependencies": {
35
- "posthog-js": "^1.214.4",
36
- "zod": "^4.1.12"
35
+ "posthog-js": "^1.328.0",
36
+ "zod": "^4.2.0"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "tsup src/index.ts src/server.ts --format esm --dts --clean --sourcemap",
@@ -26,6 +26,7 @@ describe("createAnalytics", () => {
26
26
  });
27
27
 
28
28
  const mockConfig: Required<AnalyticsConfig> = {
29
+ app: "Fresco",
29
30
  apiHost: "https://ph-relay.networkcanvas.com",
30
31
  apiKey: "phc_test",
31
32
  installationId: "test-install-123",
@@ -54,7 +55,7 @@ describe("createAnalytics", () => {
54
55
  expect(analytics.isEnabled()).toBe(false);
55
56
  });
56
57
 
57
- it("should register installation ID as super property", () => {
58
+ it("should register app and installation ID as super properties", () => {
58
59
  const mockPosthogInstance = {
59
60
  register: vi.fn(),
60
61
  debug: vi.fn(),
@@ -68,6 +69,7 @@ describe("createAnalytics", () => {
68
69
  createAnalytics(mockConfig);
69
70
 
70
71
  expect(mockPosthogInstance.register).toHaveBeenCalledWith({
72
+ app: "Fresco",
71
73
  installation_id: "test-install-123",
72
74
  });
73
75
  });
@@ -1,6 +1,13 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import { defaultConfig, isDisabledByEnv, mergeConfig } from "../config";
3
- import { ErrorPropertiesSchema, EventPropertiesSchema, type EventType, eventTypes, legacyEventTypeMap } from "../types";
3
+ import {
4
+ appNames,
5
+ ErrorPropertiesSchema,
6
+ EventPropertiesSchema,
7
+ type EventType,
8
+ eventTypes,
9
+ legacyEventTypeMap,
10
+ } from "../types";
4
11
 
5
12
  describe("Event Types", () => {
6
13
  it("should export all event types", () => {
@@ -120,6 +127,7 @@ describe("Configuration", () => {
120
127
  describe("mergeConfig", () => {
121
128
  it("should merge user config with defaults", () => {
122
129
  const userConfig = {
130
+ app: "Fresco" as const,
123
131
  installationId: "test-123",
124
132
  apiKey: "phc_test",
125
133
  };
@@ -134,6 +142,7 @@ describe("Configuration", () => {
134
142
 
135
143
  it("should allow overriding default values", () => {
136
144
  const userConfig = {
145
+ app: "Fresco" as const,
137
146
  installationId: "test-123",
138
147
  apiKey: "phc_test",
139
148
  apiHost: "https://custom.posthog.com",
@@ -150,6 +159,7 @@ describe("Configuration", () => {
150
159
 
151
160
  it("should use placeholder API key when none provided (proxy mode)", () => {
152
161
  const userConfig = {
162
+ app: "Fresco" as const,
153
163
  installationId: "test-123",
154
164
  };
155
165
 
@@ -162,6 +172,7 @@ describe("Configuration", () => {
162
172
 
163
173
  it("should merge PostHog options", () => {
164
174
  const userConfig = {
175
+ app: "Fresco" as const,
165
176
  installationId: "test-123",
166
177
  apiKey: "phc_test",
167
178
  posthogOptions: {
@@ -177,6 +188,16 @@ describe("Configuration", () => {
177
188
  // Should still have default values for other options
178
189
  expect(merged.posthogOptions.disable_session_recording).toBe(true);
179
190
  });
191
+
192
+ it("should pass through app field", () => {
193
+ const userConfig = {
194
+ app: "Studio" as const,
195
+ installationId: "test-123",
196
+ };
197
+
198
+ const merged = mergeConfig(userConfig);
199
+ expect(merged.app).toBe("Studio");
200
+ });
180
201
  });
181
202
  });
182
203
 
@@ -191,6 +212,21 @@ describe("Environment Variables", () => {
191
212
  });
192
213
  });
193
214
 
215
+ describe("App Names", () => {
216
+ it("should export all app names", () => {
217
+ expect(appNames).toEqual([
218
+ "Fresco",
219
+ "Studio",
220
+ "Architect",
221
+ "Interviewer",
222
+ "ArchitectWeb",
223
+ "CommunityForum",
224
+ "ProjectWebsite",
225
+ "Documentation",
226
+ ]);
227
+ });
228
+ });
229
+
194
230
  describe("Type Exports", () => {
195
231
  it("should export EventType correctly", () => {
196
232
  const validTypes: EventType[] = [
package/src/client.ts CHANGED
@@ -7,7 +7,7 @@ import { ensureError } from "./utils";
7
7
  * This wraps PostHog with Network Canvas-specific functionality
8
8
  */
9
9
  export function createAnalytics(config: Required<AnalyticsConfig>): Analytics {
10
- const { apiHost, apiKey, installationId, disabled, debug, posthogOptions } = config;
10
+ const { app, apiHost, apiKey, installationId, disabled, debug, posthogOptions } = config;
11
11
 
12
12
  // If analytics is disabled, return a no-op implementation
13
13
  if (disabled) {
@@ -20,6 +20,7 @@ export function createAnalytics(config: Required<AnalyticsConfig>): Analytics {
20
20
  loaded: (posthogInstance) => {
21
21
  // Set installation ID as a super property (included with every event)
22
22
  posthogInstance.register({
23
+ app,
23
24
  installation_id: installationId,
24
25
  });
25
26
 
package/src/config.ts CHANGED
@@ -79,6 +79,7 @@ export const defaultConfig: Partial<AnalyticsConfig> = {
79
79
  */
80
80
  export function mergeConfig(userConfig: AnalyticsConfig): Required<AnalyticsConfig> {
81
81
  return {
82
+ app: userConfig.app,
82
83
  apiHost: userConfig.apiHost ?? defaultConfig.apiHost ?? POSTHOG_PROXY_HOST,
83
84
  apiKey: userConfig.apiKey ?? PROXY_MODE_DUMMY_KEY,
84
85
  installationId: userConfig.installationId,
package/src/index.ts CHANGED
@@ -49,21 +49,24 @@
49
49
  * ```
50
50
  */
51
51
 
52
+ // Re-export client factory for direct initialization (e.g., instrumentation-client.ts)
53
+ export { createAnalytics } from "./client";
52
54
  // Re-export config helpers
53
55
  export { defaultConfig, isDisabledByEnv, mergeConfig } from "./config";
54
56
  export { useAnalytics, useFeatureFlag, useFeatureFlagValue } from "./hooks";
55
-
56
57
  // Re-export provider and hooks for client-side usage
57
58
  export { AnalyticsProvider, type AnalyticsProviderProps } from "./provider";
59
+
58
60
  // Re-export types
59
61
  export type {
60
62
  Analytics,
61
63
  AnalyticsConfig,
64
+ AppName,
62
65
  ErrorProperties,
63
66
  EventProperties,
64
67
  EventType,
65
68
  } from "./types";
66
- export { eventTypes, legacyEventTypeMap } from "./types";
69
+ export { appNames, eventTypes, legacyEventTypeMap } from "./types";
67
70
 
68
71
  // Re-export utilities
69
72
  export { ensureError } from "./utils";
package/src/provider.tsx CHANGED
@@ -13,10 +13,10 @@ export const AnalyticsContext = createContext<Analytics | null>(null);
13
13
  /**
14
14
  * Props for the AnalyticsProvider
15
15
  */
16
- export interface AnalyticsProviderProps {
16
+ export type AnalyticsProviderProps = {
17
17
  children: ReactNode;
18
18
  config: AnalyticsConfig;
19
- }
19
+ };
20
20
 
21
21
  /**
22
22
  * Provider component that initializes PostHog and provides analytics context
package/src/server.ts CHANGED
@@ -115,6 +115,7 @@ class ServerAnalytics implements Analytics {
115
115
  event,
116
116
  properties: {
117
117
  ...properties,
118
+ app: this.config.app,
118
119
  installation_id: this.config.installationId,
119
120
  },
120
121
  timestamp: new Date().toISOString(),
package/src/types.ts CHANGED
@@ -1,8 +1,9 @@
1
- import z from "zod";
1
+ import { z } from "zod";
2
2
 
3
3
  /**
4
- * Event types supported by the analytics system.
5
- * These are converted to snake_case for PostHog.
4
+ * Common event types supported by the analytics system.
5
+ * This list is non-exhaustive trackEvent accepts any string.
6
+ * These are provided for discoverability and autocomplete.
6
7
  */
7
8
  export const eventTypes = [
8
9
  "app_setup",
@@ -27,6 +28,23 @@ export const legacyEventTypeMap: Record<string, EventType> = {
27
28
  Error: "error",
28
29
  };
29
30
 
31
+ /**
32
+ * Supported application identifiers.
33
+ * Used to segment events within a single PostHog project.
34
+ */
35
+ export const appNames = [
36
+ "Fresco",
37
+ "Studio",
38
+ "Architect",
39
+ "Interviewer",
40
+ "ArchitectWeb",
41
+ "CommunityForum",
42
+ "ProjectWebsite",
43
+ "Documentation",
44
+ ] as const;
45
+
46
+ export type AppName = (typeof appNames)[number];
47
+
30
48
  /**
31
49
  * Standard event properties that can be sent with any event
32
50
  */
@@ -55,7 +73,13 @@ export type ErrorProperties = z.infer<typeof ErrorPropertiesSchema>;
55
73
  * reverse proxy at ph-relay.networkcanvas.com. All authentication is handled
56
74
  * by the worker, so the API key is optional.
57
75
  */
58
- export interface AnalyticsConfig {
76
+ export type AnalyticsConfig = {
77
+ /**
78
+ * Application identifier. Included with every event as a super property
79
+ * to segment analytics within a single PostHog project.
80
+ */
81
+ app: AppName;
82
+
59
83
  /**
60
84
  * PostHog API host - should point to the Cloudflare Worker reverse proxy
61
85
  * Defaults to "https://ph-relay.networkcanvas.com"
@@ -129,12 +153,12 @@ export interface AnalyticsConfig {
129
153
  */
130
154
  [key: string]: unknown;
131
155
  };
132
- }
156
+ };
133
157
 
134
158
  /**
135
159
  * Analytics instance interface
136
160
  */
137
- export interface Analytics {
161
+ export type Analytics = {
138
162
  /**
139
163
  * Track a custom event
140
164
  */
@@ -180,4 +204,4 @@ export interface Analytics {
180
204
  * Get the installation ID
181
205
  */
182
206
  getInstallationId: () => string;
183
- }
207
+ };
package/tsconfig.json CHANGED
@@ -4,6 +4,6 @@
4
4
  "baseUrl": ".",
5
5
  "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
6
6
  },
7
- "include": ["."],
7
+ "include": ["src"],
8
8
  "exclude": ["dist", "build", "node_modules"]
9
9
  }