@happyvertical/analytics 0.74.8

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,"file":"matomo-Ds_oRmZ6.js","sources":["../../src/shared/providers/matomo-admin.ts","../../src/shared/providers/matomo.ts"],"sourcesContent":["/**\n * Matomo admin client.\n *\n * Implements `AnalyticsAdminInterface` against Matomo's Reporting API. Matomo\n * exposes everything we need on a single endpoint (`POST {baseUrl}/index.php`)\n * with the action selected via `module=API` + `method=...`.\n *\n * Auth is `token_auth` — passed in the POST body, never the URL, so it does\n * not appear in proxy/access logs.\n */\n\nimport {\n type AnalyticsAccessRole,\n type AnalyticsAccessVerificationResult,\n type AnalyticsAdminInterface,\n AnalyticsError,\n type AnalyticsHealthResult,\n type AnalyticsSite,\n type AnalyticsUser,\n type AnalyticsUserToken,\n AuthenticationError,\n type CreateAnalyticsSiteOptions,\n type CreateAnalyticsUserOptions,\n type MintUserTokenOptions,\n type SetUserAccessOptions,\n type UpdateAnalyticsSiteOptions,\n type VerifyTokenSiteAccessOptions,\n type VerifyUserSiteAccessOptions,\n} from '../types.js';\n\nconst PROVIDER = 'matomo';\n\nexport interface MatomoAdminTransportOptions {\n baseUrl: string;\n tokenAuth: string;\n timeout?: number;\n}\n\ntype JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\n\ntype JsonRecord = { [key: string]: JsonValue };\n\nfunction stripTrailingSlash(value: string): string {\n return value.endsWith('/') ? value.slice(0, -1) : value;\n}\n\nfunction stripIndexPhpSuffix(value: string): string {\n return value.endsWith('/index.php')\n ? value.slice(0, -'/index.php'.length)\n : value;\n}\n\n/**\n * Normalize a Matomo base URL: trim whitespace, strip trailing slashes, and\n * remove a trailing `/index.php` if the caller provided one. Returns the host\n * root so callers can append `/index.php` themselves.\n */\nexport function normalizeMatomoBaseUrl(value: string): string {\n const trimmed = value.trim();\n if (!trimmed) {\n throw new AnalyticsError(\n 'Matomo baseUrl is required',\n 'MATOMO_BASE_URL_REQUIRED',\n PROVIDER,\n );\n }\n return stripIndexPhpSuffix(stripTrailingSlash(trimmed));\n}\n\nfunction isJsonRecord(value: unknown): value is JsonRecord {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction readString(record: JsonRecord, key: string): string | undefined {\n const value = record[key];\n if (typeof value === 'string' && value.length > 0) {\n return value;\n }\n if (typeof value === 'number') {\n return String(value);\n }\n return undefined;\n}\n\nfunction readBoolean(record: JsonRecord, key: string): boolean | undefined {\n const value = record[key];\n if (typeof value === 'boolean') return value;\n if (typeof value === 'string') {\n if (value === '1' || value === 'true') return true;\n if (value === '0' || value === 'false') return false;\n }\n if (typeof value === 'number') return value !== 0;\n return undefined;\n}\n\n/**\n * Matomo signals errors with a JSON body of the shape\n * `{ \"result\": \"error\", \"message\": \"...\" }` and HTTP 200. Detect that.\n */\nfunction readMatomoError(body: unknown): string | undefined {\n if (!isJsonRecord(body)) return undefined;\n if (body.result !== 'error') return undefined;\n return readString(body, 'message') ?? 'Matomo returned an error';\n}\n\n/**\n * POST a `module=API&method=...` form to Matomo and return the parsed JSON.\n *\n * Matomo accepts `application/x-www-form-urlencoded` — we use `URLSearchParams`\n * so array values like `idSites[]=1&idSites[]=2` are handled by the runtime.\n */\nexport class MatomoAdminTransport {\n private readonly baseUrl: string;\n private readonly tokenAuth: string;\n private readonly timeout?: number;\n\n constructor(options: MatomoAdminTransportOptions) {\n this.baseUrl = normalizeMatomoBaseUrl(options.baseUrl);\n if (!options.tokenAuth) {\n throw new AnalyticsError(\n 'Matomo tokenAuth is required',\n 'MATOMO_TOKEN_REQUIRED',\n PROVIDER,\n );\n }\n this.tokenAuth = options.tokenAuth;\n this.timeout = options.timeout;\n }\n\n /**\n * Call a Matomo API method by name (e.g. `SitesManager.addSite`).\n *\n * `params` becomes the request body. Array values are encoded as\n * `key[]=...&key[]=...`. Undefined and null values are dropped. The\n * reserved keys `module`, `method`, `format`, and `token_auth` are\n * controlled by the transport — any caller-supplied value for those\n * keys is silently ignored so they cannot override the dispatch or\n * the response format.\n */\n async call<T = unknown>(\n method: string,\n params: Record<\n string,\n string | number | boolean | undefined | string[] | number[]\n >,\n ): Promise<T> {\n const body = new URLSearchParams();\n\n for (const [key, value] of Object.entries(params)) {\n if (RESERVED_PARAM_KEYS.has(key)) continue;\n if (value === undefined || value === null) continue;\n if (Array.isArray(value)) {\n for (const item of value) {\n body.append(`${key}[]`, String(item));\n }\n continue;\n }\n body.set(key, String(value));\n }\n\n body.set('module', 'API');\n body.set('method', method);\n body.set('format', 'json');\n body.set('token_auth', this.tokenAuth);\n\n const controller =\n typeof AbortController === 'undefined'\n ? undefined\n : new AbortController();\n const timeoutHandle =\n controller && this.timeout\n ? setTimeout(() => controller.abort(), this.timeout)\n : undefined;\n\n try {\n const response = await fetch(`${this.baseUrl}/index.php`, {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body,\n signal: controller?.signal,\n });\n\n const text = await response.text();\n\n if (!response.ok) {\n // Read the body purely to enrich the error message; parse failures\n // here are not fatal because the HTTP error already explains the\n // shape of what came back.\n throw mapHttpError(response.status, tryParseJson(text), text);\n }\n\n const parsed = text.length > 0 ? parseJsonOrThrow(text) : undefined;\n\n const errorMessage = readMatomoError(parsed);\n if (errorMessage) {\n throw mapApplicationError(errorMessage);\n }\n\n return parsed as T;\n } catch (error) {\n if (error instanceof AnalyticsError) throw error;\n throw new AnalyticsError(\n `Matomo admin request failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n 'MATOMO_REQUEST_FAILED',\n PROVIDER,\n );\n } finally {\n if (timeoutHandle) clearTimeout(timeoutHandle);\n }\n }\n}\n\nconst RESERVED_PARAM_KEYS = new Set([\n 'module',\n 'method',\n 'format',\n 'token_auth',\n]);\n\n/**\n * Parse a 2xx response body as JSON. Matomo always answers `format=json`\n * with JSON; if it doesn't, something has gone wrong upstream (usually a\n * misconfigured reverse proxy or PHP fatal error returning HTML), and we\n * surface that as a typed error rather than silently coercing.\n */\nfunction parseJsonOrThrow(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch (error) {\n const preview = text.slice(0, 200);\n throw new AnalyticsError(\n `Matomo returned an invalid JSON response${\n error instanceof Error ? `: ${error.message}` : ''\n }${preview ? ` (response preview: ${preview})` : ''}`,\n 'MATOMO_INVALID_RESPONSE',\n PROVIDER,\n );\n }\n}\n\n/**\n * Best-effort parse for non-2xx response bodies — returns `undefined` on\n * parse failure so the HTTP-error code path can still surface a useful\n * status-based message.\n */\nfunction tryParseJson(text: string): unknown {\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\nfunction mapHttpError(\n status: number,\n body: unknown,\n text: string,\n): AnalyticsError {\n if (status === 401 || status === 403) {\n return new AuthenticationError(PROVIDER);\n }\n const message =\n (isJsonRecord(body) && readString(body, 'message')) ||\n text ||\n `Matomo HTTP ${status}`;\n return new AnalyticsError(message, `MATOMO_HTTP_${status}`, PROVIDER);\n}\n\nfunction mapApplicationError(message: string): AnalyticsError {\n // Matomo sentinel: anonymous (or insufficient-scope) tokens hit this branch\n // with HTTP 200 and a `result=error` payload.\n if (\n message.includes('requires view access') ||\n message.includes('requires admin access') ||\n message.includes('requires Super User access') ||\n message.includes(\"doesn't have access\")\n ) {\n return new AuthenticationError(PROVIDER, message, 'MATOMO_ACCESS_DENIED');\n }\n return new AnalyticsError(message, 'MATOMO_API_ERROR', PROVIDER);\n}\n\nfunction siteFromRow(row: JsonRecord): AnalyticsSite {\n const id = readString(row, 'idsite') ?? readString(row, 'idSite');\n if (!id) {\n throw new AnalyticsError(\n 'Matomo did not return a site id',\n 'MATOMO_INVALID_RESPONSE',\n PROVIDER,\n );\n }\n return {\n id,\n name: readString(row, 'name') ?? '',\n url: readString(row, 'main_url'),\n timezone: readString(row, 'timezone'),\n currency: readString(row, 'currency'),\n provider: PROVIDER,\n raw: row,\n };\n}\n\nfunction userFromRow(row: JsonRecord): AnalyticsUser {\n const login = readString(row, 'login');\n if (!login) {\n throw new AnalyticsError(\n 'Matomo did not return a user login',\n 'MATOMO_INVALID_RESPONSE',\n PROVIDER,\n );\n }\n return {\n login,\n email: readString(row, 'email'),\n isSuperUser: readBoolean(row, 'superuser_access'),\n provider: PROVIDER,\n raw: row,\n };\n}\n\nexport class MatomoAdmin implements AnalyticsAdminInterface {\n private readonly baseUrl: string;\n private readonly timeout?: number;\n private readonly transport: MatomoAdminTransport;\n\n constructor(options: MatomoAdminTransportOptions) {\n this.baseUrl = normalizeMatomoBaseUrl(options.baseUrl);\n this.timeout = options.timeout;\n this.transport = new MatomoAdminTransport({\n ...options,\n baseUrl: this.baseUrl,\n });\n\n // `AnalyticsAdminInterface` marks capability methods optional and tells\n // callers to feature-check them (`typeof admin.mintUserToken ===\n // 'function'`). The idiomatic TypeScript narrowing for that is to pull\n // the method into a local before calling it, which detaches `this`.\n // Bind every interface method so detached calls still hit this instance\n // instead of crashing with `this === undefined` (surfaced in production\n // as \"Cannot read properties of undefined (reading\n // 'cloneTransportWithToken')\" during minted-token verification —\n // https://github.com/happyvertical/sdk/issues/1043).\n this.createSite = this.createSite.bind(this);\n this.listSites = this.listSites.bind(this);\n this.getSite = this.getSite.bind(this);\n this.updateSite = this.updateSite.bind(this);\n this.deleteSite = this.deleteSite.bind(this);\n this.createUser = this.createUser.bind(this);\n this.getUser = this.getUser.bind(this);\n this.deleteUser = this.deleteUser.bind(this);\n this.setUserAccess = this.setUserAccess.bind(this);\n this.verifyUserSiteAccess = this.verifyUserSiteAccess.bind(this);\n this.verifyTokenSiteAccess = this.verifyTokenSiteAccess.bind(this);\n this.mintUserToken = this.mintUserToken.bind(this);\n this.health = this.health.bind(this);\n }\n\n private cloneTransportWithToken(tokenAuth: string): MatomoAdminTransport {\n return new MatomoAdminTransport({\n baseUrl: this.baseUrl,\n tokenAuth,\n timeout: this.timeout,\n });\n }\n\n // ---------------------------------------------------------------------------\n // Sites\n // ---------------------------------------------------------------------------\n\n async createSite(\n options: CreateAnalyticsSiteOptions,\n ): Promise<AnalyticsSite> {\n if (!options.urls || options.urls.length === 0) {\n throw new AnalyticsError(\n 'Matomo createSite requires at least one URL',\n 'MATOMO_URLS_REQUIRED',\n PROVIDER,\n );\n }\n\n const response = await this.transport.call<unknown>(\n 'SitesManager.addSite',\n {\n siteName: options.name,\n urls: options.urls,\n timezone: options.timezone,\n currency: options.currency,\n ...(options.raw ?? {}),\n },\n );\n\n // Matomo returns the new idSite as a bare integer or `{ value: N }`.\n const id = coerceSiteIdResponse(response);\n if (!id) {\n throw new AnalyticsError(\n 'Matomo did not return a site id',\n 'MATOMO_INVALID_RESPONSE',\n PROVIDER,\n );\n }\n\n const site = await this.getSite(id);\n if (site) return { ...site, tenantId: options.tenantId };\n\n return {\n id,\n name: options.name,\n url: options.urls[0],\n timezone: options.timezone,\n currency: options.currency,\n tenantId: options.tenantId,\n provider: PROVIDER,\n raw: response,\n };\n }\n\n async listSites(): Promise<AnalyticsSite[]> {\n const response = await this.transport.call<unknown>(\n 'SitesManager.getAllSites',\n {},\n );\n if (!Array.isArray(response)) return [];\n return response.filter(isJsonRecord).map((row) => siteFromRow(row));\n }\n\n async getSite(siteId: string): Promise<AnalyticsSite | undefined> {\n try {\n const response = await this.transport.call<unknown>(\n 'SitesManager.getSiteFromId',\n { idSite: siteId },\n );\n if (isJsonRecord(response)) {\n return siteFromRow(response);\n }\n // Some Matomo versions return `[]` for a non-existent site.\n return undefined;\n } catch (error) {\n if (error instanceof AnalyticsError && isNotFoundSiteError(error)) {\n return undefined;\n }\n throw error;\n }\n }\n\n async updateSite(\n options: UpdateAnalyticsSiteOptions,\n ): Promise<AnalyticsSite> {\n // Distinguish \"not provided\" (leave the provider's URLs untouched) from an\n // explicit empty list, which Matomo would otherwise silently ignore.\n if (options.urls && options.urls.length === 0) {\n throw new AnalyticsError(\n 'Matomo updateSite requires at least one URL when urls is provided',\n 'MATOMO_URLS_REQUIRED',\n PROVIDER,\n );\n }\n\n // `SitesManager.updateSite` is a partial update on the Matomo side too —\n // omitted params keep their current values. The transport drops undefined\n // params, so only the supplied fields are sent. A non-existent idSite\n // surfaces as a `result=error` payload and propagates as AnalyticsError.\n await this.transport.call<unknown>('SitesManager.updateSite', {\n idSite: options.siteId,\n siteName: options.name,\n urls: options.urls,\n timezone: options.timezone,\n currency: options.currency,\n ...(options.raw ?? {}),\n });\n\n // Read the site back so callers get the provider-normalized state, same\n // as createSite.\n const site = await this.getSite(options.siteId);\n if (!site) {\n throw new AnalyticsError(\n `Matomo site ${options.siteId} was not found after update`,\n 'MATOMO_SITE_NOT_FOUND',\n PROVIDER,\n );\n }\n return { ...site, tenantId: options.tenantId };\n }\n\n async deleteSite(siteId: string): Promise<void> {\n await this.transport.call<unknown>('SitesManager.deleteSite', {\n idSite: siteId,\n });\n }\n\n // ---------------------------------------------------------------------------\n // Users\n // ---------------------------------------------------------------------------\n\n async createUser(\n options: CreateAnalyticsUserOptions,\n ): Promise<AnalyticsUser> {\n if (!options.password) {\n throw new AnalyticsError(\n 'Matomo createUser requires a password',\n 'MATOMO_PASSWORD_REQUIRED',\n PROVIDER,\n );\n }\n\n await this.transport.call<unknown>('UsersManager.addUser', {\n userLogin: options.login,\n password: options.password,\n email: options.email,\n ...(options.raw ?? {}),\n });\n\n const user = await this.getUser(options.login);\n return (\n user ?? {\n login: options.login,\n email: options.email,\n tenantId: options.tenantId,\n provider: PROVIDER,\n }\n );\n }\n\n async getUser(login: string): Promise<AnalyticsUser | undefined> {\n try {\n const response = await this.transport.call<unknown>(\n 'UsersManager.getUser',\n { userLogin: login },\n );\n\n if (isJsonRecord(response)) return userFromRow(response);\n\n // Some Matomo versions return `[]` for a non-existent user.\n if (Array.isArray(response) && response.length === 0) return undefined;\n\n return undefined;\n } catch (error) {\n // Matomo 5.x raises an application error rather than returning an\n // empty array when the login is unknown.\n if (\n error instanceof AnalyticsError &&\n error.code === 'MATOMO_API_ERROR' &&\n /doesn'?t exist|does not exist|unknown/i.test(error.message)\n ) {\n return undefined;\n }\n throw error;\n }\n }\n\n async deleteUser(login: string): Promise<void> {\n await this.transport.call<unknown>('UsersManager.deleteUser', {\n userLogin: login,\n });\n }\n\n async setUserAccess(options: SetUserAccessOptions): Promise<void> {\n if (!isAccessRole(options.access)) {\n throw new AnalyticsError(\n `Invalid Matomo access role: ${options.access}`,\n 'MATOMO_INVALID_ROLE',\n PROVIDER,\n );\n }\n if (!options.siteIds || options.siteIds.length === 0) {\n throw new AnalyticsError(\n 'Matomo setUserAccess requires at least one site id',\n 'MATOMO_SITE_IDS_REQUIRED',\n PROVIDER,\n );\n }\n await this.transport.call<unknown>('UsersManager.setUserAccess', {\n userLogin: options.login,\n access: options.access,\n idSites: options.siteIds,\n });\n }\n\n async verifyUserSiteAccess(\n options: VerifyUserSiteAccessOptions,\n ): Promise<AnalyticsAccessVerificationResult> {\n const requiredAccess = options.minimumAccess ?? 'view';\n if (!isAccessRole(requiredAccess)) {\n throw new AnalyticsError(\n `Invalid Matomo access role: ${requiredAccess}`,\n 'MATOMO_INVALID_ROLE',\n PROVIDER,\n );\n }\n\n try {\n const response = await this.transport.call<unknown>(\n 'UsersManager.getSitesAccessFromUser',\n { userLogin: options.login },\n );\n const entries = normalizeSiteAccessEntries(response);\n const match = entries.find((entry) => entry.siteId === options.siteId);\n let access = match?.access ?? 'noaccess';\n if (access === 'noaccess') {\n const user = await this.getUser(options.login);\n if (user?.isSuperUser) {\n access = 'admin';\n }\n }\n const ok = hasMinimumAccess(access, requiredAccess);\n\n return {\n ok,\n provider: PROVIDER,\n login: options.login,\n siteId: options.siteId,\n access,\n requiredAccess,\n errorCode: ok ? undefined : 'MATOMO_ACCESS_DENIED',\n error: ok\n ? undefined\n : `User \"${options.login}\" has ${access} access to site ${options.siteId}; ${requiredAccess} is required.`,\n raw: response,\n };\n } catch (error) {\n return {\n ok: false,\n provider: PROVIDER,\n login: options.login,\n siteId: options.siteId,\n access: 'noaccess',\n requiredAccess,\n error: error instanceof Error ? error.message : String(error),\n errorCode: error instanceof AnalyticsError ? error.code : undefined,\n };\n }\n }\n\n async verifyTokenSiteAccess(\n options: VerifyTokenSiteAccessOptions,\n ): Promise<AnalyticsAccessVerificationResult> {\n try {\n const transport = this.cloneTransportWithToken(options.tokenAuth);\n const response = await transport.call<unknown>(\n 'SitesManager.getSiteFromId',\n { idSite: options.siteId },\n );\n if (Array.isArray(response) && response.length === 0) {\n return {\n ok: false,\n provider: PROVIDER,\n siteId: options.siteId,\n access: 'noaccess',\n requiredAccess: 'view',\n errorCode: 'MATOMO_SITE_NOT_FOUND',\n error: `Site ${options.siteId} was not found.`,\n raw: response,\n };\n }\n const siteVisible = isJsonRecord(response);\n if (siteVisible) {\n // Validate the response shape; siteFromRow throws on malformed rows.\n siteFromRow(response);\n }\n return {\n ok: siteVisible,\n provider: PROVIDER,\n siteId: options.siteId,\n access: siteVisible ? 'view' : 'noaccess',\n requiredAccess: 'view',\n errorCode: siteVisible ? undefined : 'MATOMO_ACCESS_DENIED',\n error: siteVisible\n ? undefined\n : `Token cannot read site ${options.siteId}.`,\n raw: response,\n };\n } catch (error) {\n const errorCode =\n error instanceof AnalyticsError ? error.code : undefined;\n return {\n ok: false,\n provider: PROVIDER,\n siteId: options.siteId,\n access: 'noaccess',\n requiredAccess: 'view',\n error:\n errorCode === 'MATOMO_ACCESS_DENIED'\n ? `Token cannot read site ${options.siteId} (no view grant).`\n : error instanceof Error\n ? error.message\n : String(error),\n errorCode,\n };\n }\n }\n\n async mintUserToken(\n options: MintUserTokenOptions,\n ): Promise<AnalyticsUserToken> {\n if (!options.passwordConfirmation) {\n throw new AnalyticsError(\n \"Matomo's createAppSpecificTokenAuth requires the target user's \" +\n 'password as `passwordConfirmation`. (It confirms the user being ' +\n 'minted for — not the caller — so a stolen super-user token alone ' +\n \"can't mint tokens for arbitrary users.)\",\n 'MATOMO_PASSWORD_CONFIRMATION_REQUIRED',\n PROVIDER,\n );\n }\n const effectiveDescription =\n options.description ?? `analytics-token ${options.login}`;\n const response = await this.transport.call<unknown>(\n 'UsersManager.createAppSpecificTokenAuth',\n {\n userLogin: options.login,\n passwordConfirmation: options.passwordConfirmation,\n description: effectiveDescription,\n expireHours: 0,\n },\n );\n\n const token =\n (isJsonRecord(response) && readString(response, 'value')) ||\n (typeof response === 'string' ? response : undefined);\n\n if (!token) {\n throw new AnalyticsError(\n 'Matomo did not return a token value',\n 'MATOMO_INVALID_RESPONSE',\n PROVIDER,\n );\n }\n\n return {\n token,\n login: options.login,\n description: effectiveDescription,\n provider: PROVIDER,\n raw: response,\n };\n }\n\n // ---------------------------------------------------------------------------\n // Health\n // ---------------------------------------------------------------------------\n\n async health(): Promise<AnalyticsHealthResult> {\n try {\n const response = await this.transport.call<unknown>(\n 'API.getMatomoVersion',\n {},\n );\n const version =\n (isJsonRecord(response) && readString(response, 'value')) ||\n (typeof response === 'string' ? response : undefined);\n return { ok: true, version };\n } catch (error) {\n return {\n ok: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n}\n\nfunction coerceSiteIdResponse(response: unknown): string | undefined {\n if (typeof response === 'number' && Number.isFinite(response)) {\n return String(response);\n }\n if (typeof response === 'string' && response.length > 0) {\n return response;\n }\n if (isJsonRecord(response)) {\n return (\n readString(response, 'value') ??\n readString(response, 'idsite') ??\n readString(response, 'idSite')\n );\n }\n return undefined;\n}\n\nfunction isAccessRole(value: string): value is AnalyticsAccessRole {\n return (\n value === 'noaccess' ||\n value === 'view' ||\n value === 'write' ||\n value === 'admin'\n );\n}\n\ninterface SiteAccessEntry {\n siteId: string;\n access: AnalyticsAccessRole;\n}\n\nfunction normalizeSiteAccessEntries(response: unknown): SiteAccessEntry[] {\n if (Array.isArray(response)) {\n return response\n .filter(isJsonRecord)\n .map((row) => {\n const siteId =\n readString(row, 'site') ??\n readString(row, 'idsite') ??\n readString(row, 'idSite');\n const access = readString(row, 'access');\n return siteId && access && isAccessRole(access)\n ? { siteId, access }\n : undefined;\n })\n .filter((entry): entry is SiteAccessEntry => !!entry);\n }\n\n if (isJsonRecord(response)) {\n const entries: SiteAccessEntry[] = [];\n for (const [siteId, access] of Object.entries(response)) {\n if (typeof access === 'string' && isAccessRole(access)) {\n entries.push({ siteId, access });\n }\n }\n return entries;\n }\n\n return [];\n}\n\nconst ACCESS_RANK: Record<AnalyticsAccessRole, number> = {\n noaccess: 0,\n view: 1,\n write: 2,\n admin: 3,\n};\n\nfunction hasMinimumAccess(\n access: AnalyticsAccessRole,\n minimumAccess: AnalyticsAccessRole,\n): boolean {\n return ACCESS_RANK[access] >= ACCESS_RANK[minimumAccess];\n}\n\n/**\n * Recognise the various ways Matomo signals \"this site id doesn't exist (or\n * isn't visible to the caller)\". Matomo has at least three error sentinels for\n * this case across versions and contexts:\n * - \"Site not found\"\n * - \"doesn't exist\" / \"does not exist\"\n * - \"An unexpected website was found in the request: website id was set to '...'\"\n */\nfunction isNotFoundSiteError(error: AnalyticsError): boolean {\n if (error.code !== 'MATOMO_API_ERROR') return false;\n return (\n /not.*found|doesn'?t exist|does not exist|unknown/i.test(error.message) ||\n /website was found in the request|website id was set/i.test(error.message)\n );\n}\n","/**\n * Matomo Analytics provider implementation.\n *\n * This first cut implements:\n * - The full `AnalyticsAdminInterface` (sites, users, access, tokens, health)\n * via {@link MatomoAdmin}. This is the API the tenant doctor uses.\n * - The `AnalyticsInterface` property-management surface, by delegating to the\n * admin (a Matomo \"site\" maps 1:1 to a GA4-style \"property\").\n * - Capabilities, tracking-snippet generation, and a focused reporting surface\n * for Matomo's VisitsSummary, Actions, Referrers, and Live APIs.\n *\n * GA4-specific concepts that don't translate cleanly (data streams, custom\n * dimensions/metrics, key events, server-side tracking) throw\n * `NotSupportedError`. Matomo has its own analogues (goals, custom dimensions\n * plugin, Tracking HTTP API) — they need a different shape and don't belong on\n * the GA4-shaped methods.\n */\n\nimport {\n type AnalyticsCapabilities,\n type AnalyticsInterface,\n type ConfigOptions,\n type CreateDataStreamOptions,\n type CreatePropertyOptions,\n type CustomDimension,\n type CustomDimensionOptions,\n type CustomMetric,\n type CustomMetricOptions,\n type DataStream,\n type DimensionMetadata,\n type KeyEvent,\n type KeyEventOptions,\n type ListPropertiesOptions,\n type MatomoOptions,\n type MetricMetadata,\n NotSupportedError,\n type PageviewEvent,\n type Property,\n PropertyNotFoundError,\n type RealtimeReportOptions,\n type ReportOptions,\n type ReportResult,\n type SnippetOptions,\n type TrackEvent,\n type TrackingSnippet,\n type UpdatePropertyOptions,\n} from '../types.js';\nimport {\n MatomoAdmin,\n MatomoAdminTransport,\n normalizeMatomoBaseUrl,\n} from './matomo-admin.js';\n\nconst PROVIDER = 'matomo';\n\nexport class MatomoProvider implements AnalyticsInterface {\n private readonly baseUrl: string;\n private readonly reportingTransport: MatomoAdminTransport;\n public readonly admin: MatomoAdmin;\n\n constructor(options: MatomoOptions) {\n this.baseUrl = normalizeMatomoBaseUrl(options.baseUrl);\n this.reportingTransport = new MatomoAdminTransport({\n baseUrl: this.baseUrl,\n tokenAuth: options.tokenAuth,\n timeout: options.timeout,\n });\n this.admin = new MatomoAdmin({\n baseUrl: this.baseUrl,\n tokenAuth: options.tokenAuth,\n timeout: options.timeout,\n });\n }\n\n // ---------------------------------------------------------------------------\n // Property management — delegated to admin (site === property in Matomo)\n // ---------------------------------------------------------------------------\n\n createProperty(_options: CreatePropertyOptions): Promise<Property> {\n // GA4's CreatePropertyOptions doesn't carry a website URL. Matomo\n // mandates one on every site, so use `admin.createSite({ urls: [...] })`\n // directly rather than overloading this method.\n throw new NotSupportedError(\n 'createProperty (use admin.createSite instead)',\n PROVIDER,\n );\n }\n\n async listProperties(_options?: ListPropertiesOptions): Promise<Property[]> {\n const sites = await this.admin.listSites();\n return sites.map((site) => propertyFromSite(site));\n }\n\n async getProperty(propertyId: string): Promise<Property> {\n const site = await this.admin.getSite(propertyId);\n if (!site) {\n throw new PropertyNotFoundError(propertyId, PROVIDER);\n }\n return propertyFromSite(site);\n }\n\n async updateProperty(\n _propertyId: string,\n _data: UpdatePropertyOptions,\n ): Promise<Property> {\n // SitesManager.updateSite exists; landing it requires shaping the\n // GA4-style fields onto Matomo's params (siteName/urls/timezone/...).\n // Deferred to the reporting follow-up where we round-trip these fields.\n throw new NotSupportedError('updateProperty', PROVIDER);\n }\n\n async deleteProperty(propertyId: string): Promise<void> {\n await this.admin.deleteSite(propertyId);\n }\n\n // ---------------------------------------------------------------------------\n // Capabilities\n // ---------------------------------------------------------------------------\n\n async getCapabilities(): Promise<AnalyticsCapabilities> {\n return {\n propertyManagement: true,\n dataStreams: false,\n customDimensions: false,\n customMetrics: false,\n keyEvents: false,\n reporting: true,\n realtimeReporting: true,\n serverSideTracking: false,\n clientSideSnippet: true,\n userIdentification: false,\n batchTracking: false,\n };\n }\n\n // ---------------------------------------------------------------------------\n // Client-side snippet\n // ---------------------------------------------------------------------------\n\n /**\n * Generate a Matomo `_paq` tracking snippet.\n *\n * Note on `SnippetOptions.anonymizeIp`: Matomo handles IP anonymization\n * **server-side** via the PrivacyManager plugin (Matomo admin → Privacy →\n * Anonymize visitors' IP addresses). The JS tracker has no equivalent\n * client-side directive — `setDoNotTrack` respects the browser's DNT\n * signal but does not anonymize IPs, so we deliberately do not emit it\n * here. The flag is preserved on the returned `config` object as a\n * caller-visible signal, but its enforcement is the operator's\n * responsibility on the Matomo install.\n */\n generateTrackingSnippet(\n propertyId: string,\n options: SnippetOptions = {},\n ): TrackingSnippet {\n const trackerUrl = `${this.baseUrl}/matomo.php`;\n const scriptUrl = `${this.baseUrl}/matomo.js`;\n const sendPageView = options.sendPageView ?? true;\n\n const lines: string[] = ['var _paq = window._paq = window._paq || [];'];\n if (sendPageView) {\n lines.push(\"_paq.push(['trackPageView']);\");\n }\n lines.push(\"_paq.push(['enableLinkTracking']);\");\n if (options.customConfig) {\n for (const [key, value] of Object.entries(options.customConfig)) {\n lines.push(\n `_paq.push([${JSON.stringify(key)}, ${JSON.stringify(value)}]);`,\n );\n }\n }\n lines.push(\n `(function() { var u=${JSON.stringify(`${this.baseUrl}/`)};` +\n ` _paq.push(['setTrackerUrl', u+'matomo.php']);` +\n ` _paq.push(['setSiteId', ${JSON.stringify(propertyId)}]);` +\n ` var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];` +\n ` g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); })();`,\n );\n\n return {\n html: `<script>\\n${lines.join('\\n')}\\n</script>`,\n config: {\n trackerUrl,\n scriptUrl,\n siteId: propertyId,\n anonymizeIp: !!options.anonymizeIp,\n sendPageView,\n },\n scripts: [scriptUrl],\n };\n }\n\n generateConfig(\n propertyId: string,\n options: ConfigOptions = {},\n ): Record<string, unknown> {\n return {\n trackerUrl: `${this.baseUrl}/matomo.php`,\n scriptUrl: `${this.baseUrl}/matomo.js`,\n siteId: propertyId,\n anonymizeIp: !!options.anonymizeIp,\n sendPageView: options.sendPageView ?? true,\n userId: options.userId,\n customDimensions: options.customDimensions,\n };\n }\n\n // ---------------------------------------------------------------------------\n // Methods deferred to a follow-up PR — they have meaningful Matomo analogues\n // but warrant their own tests and a wider review surface.\n // ---------------------------------------------------------------------------\n\n getDataStreams(_propertyId: string): Promise<DataStream[]> {\n throw new NotSupportedError('dataStreams', PROVIDER);\n }\n createDataStream(\n _propertyId: string,\n _options: CreateDataStreamOptions,\n ): Promise<DataStream> {\n throw new NotSupportedError('dataStreams', PROVIDER);\n }\n deleteDataStream(_propertyId: string, _streamId: string): Promise<void> {\n throw new NotSupportedError('dataStreams', PROVIDER);\n }\n getCustomDimensions(_propertyId: string): Promise<CustomDimension[]> {\n throw new NotSupportedError('customDimensions', PROVIDER);\n }\n createCustomDimension(\n _propertyId: string,\n _options: CustomDimensionOptions,\n ): Promise<CustomDimension> {\n throw new NotSupportedError('customDimensions', PROVIDER);\n }\n archiveCustomDimension(\n _propertyId: string,\n _dimensionId: string,\n ): Promise<void> {\n throw new NotSupportedError('customDimensions', PROVIDER);\n }\n getCustomMetrics(_propertyId: string): Promise<CustomMetric[]> {\n throw new NotSupportedError('customMetrics', PROVIDER);\n }\n createCustomMetric(\n _propertyId: string,\n _options: CustomMetricOptions,\n ): Promise<CustomMetric> {\n throw new NotSupportedError('customMetrics', PROVIDER);\n }\n archiveCustomMetric(_propertyId: string, _metricId: string): Promise<void> {\n throw new NotSupportedError('customMetrics', PROVIDER);\n }\n getKeyEvents(_propertyId: string): Promise<KeyEvent[]> {\n throw new NotSupportedError('keyEvents', PROVIDER);\n }\n createKeyEvent(\n _propertyId: string,\n _options: KeyEventOptions,\n ): Promise<KeyEvent> {\n throw new NotSupportedError('keyEvents', PROVIDER);\n }\n deleteKeyEvent(_propertyId: string, _eventId: string): Promise<void> {\n throw new NotSupportedError('keyEvents', PROVIDER);\n }\n async runReport(\n propertyId: string,\n options: ReportOptions,\n ): Promise<ReportResult> {\n const dimensions = options.dimensions ?? [];\n const metrics = options.metrics;\n const dateRange = options.dateRanges[0] ?? {\n startDate: '7daysAgo',\n endDate: 'today',\n };\n const query = matomoDateQuery(dateRange);\n const dimensionNames = dimensions.map((dimension) => dimension.name);\n const method = reportMethodForDimensions(dimensionNames);\n const response = await this.reportingTransport.call<unknown>(method, {\n idSite: propertyId,\n period: method === 'VisitsSummary.get' ? 'day' : query.period,\n date: query.date,\n filter_limit: options.limit,\n filter_offset: options.offset,\n flat: dimensionNames.some(isPageDimension) ? 1 : undefined,\n });\n\n const sourceRows = normalizeMatomoRows(response);\n const rows = sourceRows.map((row) => ({\n dimensionValues: dimensions.map((dimension) => ({\n value: dimensionValue(row, dimension.name),\n })),\n metricValues: metrics.map((metric) => ({\n value: metricValue(row, metric.name),\n })),\n }));\n\n return {\n dimensionHeaders: dimensions.map((dimension) => ({\n name: dimension.name,\n })),\n metricHeaders: metrics.map((metric) => ({\n name: metric.name,\n type: metricType(metric.name),\n })),\n rows,\n rowCount: rows.length,\n };\n }\n async runRealtimeReport(\n propertyId: string,\n options: RealtimeReportOptions = {},\n ): Promise<ReportResult> {\n const dimensions = options.dimensions ?? [];\n const metrics = options.metrics ?? [{ name: 'activeUsers' }];\n const limit = options.limit ?? 10;\n const lastMinutes = options.minuteRanges?.[0]?.startMinutesAgo ?? 30;\n\n if (dimensions.some(isPageDimension)) {\n const response = await this.reportingTransport.call<unknown>(\n 'Live.getLastVisitsDetails',\n {\n idSite: propertyId,\n period: 'day',\n date: 'today',\n filter_limit: limit,\n lastMinutes,\n },\n );\n const rows = realtimePageRows(response, dimensions, metrics, limit);\n return {\n dimensionHeaders: dimensions.map((dimension) => ({\n name: dimension.name,\n })),\n metricHeaders: metrics.map((metric) => ({\n name: metric.name,\n type: metricType(metric.name),\n })),\n rows,\n rowCount: rows.length,\n };\n }\n\n const response = await this.reportingTransport.call<unknown>(\n 'Live.getCounters',\n {\n idSite: propertyId,\n lastMinutes,\n },\n );\n const counters = firstRecord(response);\n const row = {\n dimensionValues: dimensions.map((dimension) => ({\n value: dimensionValue(counters, dimension.name),\n })),\n metricValues: metrics.map((metric) => ({\n value: realtimeMetricValue(counters, metric.name),\n })),\n };\n\n return {\n dimensionHeaders: dimensions.map((dimension) => ({\n name: dimension.name,\n })),\n metricHeaders: metrics.map((metric) => ({\n name: metric.name,\n type: metricType(metric.name),\n })),\n rows: [row],\n rowCount: 1,\n };\n }\n getMetrics(_propertyId: string): Promise<MetricMetadata[]> {\n throw new NotSupportedError('getMetrics', PROVIDER);\n }\n getDimensions(_propertyId: string): Promise<DimensionMetadata[]> {\n throw new NotSupportedError('getDimensions', PROVIDER);\n }\n track(_event: TrackEvent): Promise<void> {\n throw new NotSupportedError('track', PROVIDER);\n }\n trackPageview(_pageview: PageviewEvent): Promise<void> {\n throw new NotSupportedError('trackPageview', PROVIDER);\n }\n trackBatch(_events: TrackEvent[]): Promise<void> {\n throw new NotSupportedError('trackBatch', PROVIDER);\n }\n identify(_userId: string, _traits?: Record<string, unknown>): Promise<void> {\n throw new NotSupportedError('identify', PROVIDER);\n }\n}\n\nfunction propertyFromSite(site: {\n id: string;\n name: string;\n url?: string;\n timezone?: string;\n currency?: string;\n}): Property {\n return {\n id: site.id,\n name: `sites/${site.id}`,\n displayName: site.name,\n createTime: '',\n timeZone: site.timezone,\n currencyCode: site.currency,\n };\n}\n\ntype MatomoRow = Record<string, unknown>;\n\nfunction isRecord(value: unknown): value is MatomoRow {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction firstRecord(value: unknown): MatomoRow {\n if (Array.isArray(value)) {\n return value.find(isRecord) ?? {};\n }\n return isRecord(value) ? value : {};\n}\n\nfunction normalizeMatomoRows(value: unknown): MatomoRow[] {\n if (Array.isArray(value)) {\n return value.filter(isRecord);\n }\n if (!isRecord(value)) {\n return [];\n }\n const entries = Object.entries(value);\n if (entries.length > 0 && entries.every(([, row]) => isRecord(row))) {\n return entries.map(([key, row]) => ({ ...(row as MatomoRow), date: key }));\n }\n return [value];\n}\n\nfunction matomoDateQuery(range: { startDate: string; endDate: string }): {\n period: string;\n date: string;\n} {\n if (range.endDate === 'today') {\n const daysAgo = /^(\\d+)daysAgo$/.exec(range.startDate);\n if (daysAgo) {\n return { period: 'range', date: `last${daysAgo[1]}` };\n }\n }\n if (range.startDate === range.endDate) {\n return { period: 'day', date: range.startDate };\n }\n return { period: 'range', date: `${range.startDate},${range.endDate}` };\n}\n\nfunction reportMethodForDimensions(dimensions: string[]): string {\n if (dimensions.length === 0) {\n return 'VisitsSummary.get';\n }\n if (dimensions.every((dimension) => dimension === 'date')) {\n return 'VisitsSummary.get';\n }\n if (dimensions.every(isPageDimension)) {\n return 'Actions.getPageUrls';\n }\n if (\n dimensions.every(\n (dimension) =>\n dimension === 'sessionSource' || dimension === 'sessionMedium',\n )\n ) {\n return 'Referrers.getAll';\n }\n throw new NotSupportedError(\n `Matomo report dimensions (${dimensions.join(', ')})`,\n PROVIDER,\n );\n}\n\nfunction isPageDimension(dimension: { name: string } | string): boolean {\n const name = typeof dimension === 'string' ? dimension : dimension.name;\n return [\n 'pagePath',\n 'pageTitle',\n 'unifiedScreenName',\n 'unifiedPagePathScreen',\n ].includes(name);\n}\n\nfunction dimensionValue(row: MatomoRow, name: string): string {\n switch (name) {\n case 'date':\n return readString(row.date) ?? '';\n case 'pagePath':\n case 'unifiedPagePathScreen':\n return readString(row.url) ?? readString(row.label) ?? '';\n case 'pageTitle':\n case 'unifiedScreenName':\n return (\n readString(row.pageTitle) ??\n readString(row.title) ??\n readString(row.label) ??\n ''\n );\n case 'sessionSource':\n return (\n readString(row.label) ??\n readString(row.referer_name) ??\n readString(row.refererName) ??\n ''\n );\n case 'sessionMedium':\n return (\n readString(row.referer_type) ??\n readString(row.refererType) ??\n readString(row.type) ??\n ''\n );\n default:\n return readString(row[name]) ?? '';\n }\n}\n\nfunction metricValue(row: MatomoRow, name: string): string {\n switch (name) {\n case 'activeUsers':\n return readNumericString(row.nb_uniq_visitors ?? row.nb_users);\n case 'sessions':\n return readNumericString(row.nb_visits);\n case 'bounceRate':\n return readNumericString(row.bounce_rate);\n case 'averageSessionDuration':\n return readNumericString(row.avg_time_on_site);\n case 'screenPageViews':\n return readNumericString(row.nb_hits ?? row.nb_pageviews);\n default:\n return readNumericString(row[name]);\n }\n}\n\nfunction realtimeMetricValue(row: MatomoRow, name: string): string {\n switch (name) {\n case 'activeUsers':\n return readNumericString(row.visitors ?? row.nb_uniq_visitors);\n case 'sessions':\n return readNumericString(row.visits ?? row.nb_visits);\n case 'screenPageViews':\n return readNumericString(row.actions ?? row.nb_hits ?? row.nb_pageviews);\n default:\n return metricValue(row, name);\n }\n}\n\nfunction metricType(name: string): string {\n return name === 'bounceRate' ? 'TYPE_FLOAT' : 'TYPE_INTEGER';\n}\n\nfunction readString(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (typeof value === 'number') return String(value);\n return undefined;\n}\n\nfunction readNumericString(value: unknown): string {\n if (typeof value === 'number') return String(value);\n if (typeof value !== 'string') return '0';\n\n const trimmed = value.trim();\n if (!trimmed) return '0';\n if (trimmed.includes(':')) {\n return String(parseDurationSeconds(trimmed));\n }\n\n const numeric = Number(trimmed.replace('%', ''));\n return Number.isFinite(numeric) ? String(numeric) : '0';\n}\n\nfunction parseDurationSeconds(value: string): number {\n const parts = value.split(':').map((part) => Number(part));\n if (parts.some((part) => !Number.isFinite(part))) return 0;\n return parts.reduce((total, part) => total * 60 + part, 0);\n}\n\nfunction realtimePageRows(\n response: unknown,\n dimensions: { name: string }[],\n metrics: { name: string }[],\n limit: number,\n): {\n dimensionValues: { value: string }[];\n metricValues: { value: string }[];\n}[] {\n const pageCounts = new Map<string, MatomoRow & { visitors: Set<string> }>();\n const visits = normalizeMatomoRows(response);\n\n for (const visit of visits) {\n const visitorId =\n readString(visit.visitorId) ?? readString(visit.idVisit) ?? '';\n const actions = Array.isArray(visit.actionDetails)\n ? visit.actionDetails.filter(isRecord)\n : [];\n\n for (const action of actions) {\n const title =\n readString(action.pageTitle) ??\n readString(action.title) ??\n readString(action.url) ??\n '';\n const url = readString(action.url) ?? title;\n const key = `${title}\\u0000${url}`;\n const existing = pageCounts.get(key) ?? {\n pageTitle: title,\n url,\n visitors: new Set<string>(),\n actions: 0,\n };\n existing.actions = Number(existing.actions ?? 0) + 1;\n if (visitorId) existing.visitors.add(visitorId);\n pageCounts.set(key, existing);\n }\n }\n\n return [...pageCounts.values()]\n .sort((a, b) => Number(b.actions ?? 0) - Number(a.actions ?? 0))\n .slice(0, limit)\n .map((row) => ({\n dimensionValues: dimensions.map((dimension) => ({\n value: dimensionValue(row, dimension.name),\n })),\n metricValues: metrics.map((metric) => ({\n value:\n metric.name === 'activeUsers'\n ? String(row.visitors.size)\n : realtimeMetricValue(row, metric.name),\n })),\n }));\n}\n"],"names":["PROVIDER","readString","response"],"mappings":"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"}
@@ -0,0 +1,479 @@
1
+ import { AuthenticationError, PropertyNotFoundError, RateLimitError, AnalyticsError, NotSupportedError } from "../index.js";
2
+ const DEFAULT_BASE_URL = "https://plausible.io";
3
+ class PlausibleProvider {
4
+ options;
5
+ baseUrl;
6
+ constructor(options) {
7
+ this.options = {
8
+ timeout: 3e4,
9
+ maxRetries: 3,
10
+ baseUrl: DEFAULT_BASE_URL,
11
+ ...options
12
+ };
13
+ this.baseUrl = this.options.baseUrl || DEFAULT_BASE_URL;
14
+ }
15
+ /**
16
+ * Make an authenticated API request
17
+ */
18
+ async request(endpoint, options = {}) {
19
+ const url = `${this.baseUrl}${endpoint}`;
20
+ const method = options.method || "GET";
21
+ try {
22
+ const response = await fetch(url, {
23
+ method,
24
+ headers: {
25
+ Authorization: `Bearer ${this.options.apiKey}`,
26
+ "Content-Type": "application/json",
27
+ ...options.headers
28
+ },
29
+ body: options.body ? JSON.stringify(options.body) : void 0
30
+ });
31
+ if (!response.ok) {
32
+ if (response.status === 401) {
33
+ throw new AuthenticationError("plausible");
34
+ }
35
+ if (response.status === 404) {
36
+ throw new PropertyNotFoundError(endpoint, "plausible");
37
+ }
38
+ if (response.status === 429) {
39
+ const retryAfter = response.headers.get("Retry-After");
40
+ throw new RateLimitError(
41
+ "plausible",
42
+ retryAfter ? Number.parseInt(retryAfter, 10) : void 0
43
+ );
44
+ }
45
+ const errorText = await response.text();
46
+ throw new AnalyticsError(
47
+ `Plausible API error: ${response.status} - ${errorText}`,
48
+ "API_ERROR",
49
+ "plausible"
50
+ );
51
+ }
52
+ const text = await response.text();
53
+ if (!text) {
54
+ return {};
55
+ }
56
+ return JSON.parse(text);
57
+ } catch (error) {
58
+ if (error instanceof AnalyticsError) {
59
+ throw error;
60
+ }
61
+ throw new AnalyticsError(
62
+ `Network error: ${error.message}`,
63
+ "NETWORK_ERROR",
64
+ "plausible"
65
+ );
66
+ }
67
+ }
68
+ // ===========================================================================
69
+ // Property Management (Sites API)
70
+ // ===========================================================================
71
+ async createProperty(options) {
72
+ const domain = options.displayName;
73
+ const site = await this.request("/api/v1/sites", {
74
+ method: "POST",
75
+ body: {
76
+ domain,
77
+ timezone: options.timeZone || "Etc/UTC"
78
+ }
79
+ });
80
+ return {
81
+ id: site.domain,
82
+ name: site.domain,
83
+ displayName: site.domain,
84
+ createTime: (/* @__PURE__ */ new Date()).toISOString(),
85
+ timeZone: site.timezone
86
+ };
87
+ }
88
+ async listProperties(_options) {
89
+ throw new NotSupportedError("listProperties", "plausible");
90
+ }
91
+ async getProperty(propertyId) {
92
+ return {
93
+ id: propertyId,
94
+ name: propertyId,
95
+ displayName: propertyId,
96
+ createTime: ""
97
+ };
98
+ }
99
+ async updateProperty(_propertyId, _data) {
100
+ throw new NotSupportedError("updateProperty", "plausible");
101
+ }
102
+ async deleteProperty(propertyId) {
103
+ await this.request(`/api/v1/sites/${propertyId}`, {
104
+ method: "DELETE"
105
+ });
106
+ }
107
+ // ===========================================================================
108
+ // Data Streams (Not supported by Plausible)
109
+ // ===========================================================================
110
+ async getDataStreams(_propertyId) {
111
+ throw new NotSupportedError("getDataStreams", "plausible");
112
+ }
113
+ async createDataStream(_propertyId, _options) {
114
+ throw new NotSupportedError("createDataStream", "plausible");
115
+ }
116
+ async deleteDataStream(_propertyId, _streamId) {
117
+ throw new NotSupportedError("deleteDataStream", "plausible");
118
+ }
119
+ // ===========================================================================
120
+ // Custom Definitions (Not supported by Plausible via API)
121
+ // ===========================================================================
122
+ async getCustomDimensions(_propertyId) {
123
+ throw new NotSupportedError("getCustomDimensions", "plausible");
124
+ }
125
+ async createCustomDimension(_propertyId, _options) {
126
+ throw new NotSupportedError("createCustomDimension", "plausible");
127
+ }
128
+ async archiveCustomDimension(_propertyId, _dimensionId) {
129
+ throw new NotSupportedError("archiveCustomDimension", "plausible");
130
+ }
131
+ async getCustomMetrics(_propertyId) {
132
+ throw new NotSupportedError("getCustomMetrics", "plausible");
133
+ }
134
+ async createCustomMetric(_propertyId, _options) {
135
+ throw new NotSupportedError("createCustomMetric", "plausible");
136
+ }
137
+ async archiveCustomMetric(_propertyId, _metricId) {
138
+ throw new NotSupportedError("archiveCustomMetric", "plausible");
139
+ }
140
+ // ===========================================================================
141
+ // Key Events (Goals in Plausible - limited API support)
142
+ // ===========================================================================
143
+ async getKeyEvents(_propertyId) {
144
+ throw new NotSupportedError("getKeyEvents", "plausible");
145
+ }
146
+ async createKeyEvent(propertyId, options) {
147
+ const goal = await this.request(
148
+ `/api/v1/sites/${propertyId}/goals`,
149
+ {
150
+ method: "PUT",
151
+ body: {
152
+ goal_type: "event",
153
+ event_name: options.eventName
154
+ }
155
+ }
156
+ );
157
+ return {
158
+ id: String(goal.id),
159
+ name: goal.display_name,
160
+ eventName: options.eventName,
161
+ createTime: (/* @__PURE__ */ new Date()).toISOString()
162
+ };
163
+ }
164
+ async deleteKeyEvent(propertyId, eventId) {
165
+ await this.request(`/api/v1/sites/${propertyId}/goals/${eventId}`, {
166
+ method: "DELETE"
167
+ });
168
+ }
169
+ // ===========================================================================
170
+ // Reporting (Stats API v2)
171
+ // ===========================================================================
172
+ async runReport(propertyId, options) {
173
+ const siteId = propertyId;
174
+ const dateRange = options.dateRanges[0];
175
+ const queryBody = {
176
+ site_id: siteId,
177
+ metrics: options.metrics.map((m) => this.mapMetricName(m.name)),
178
+ date_range: [dateRange.startDate, dateRange.endDate]
179
+ };
180
+ if (options.dimensions) {
181
+ queryBody.dimensions = options.dimensions.map(
182
+ (d) => this.mapDimensionName(d.name)
183
+ );
184
+ }
185
+ if (options.orderBys) {
186
+ queryBody.order_by = options.orderBys.map((o) => [
187
+ o.metric?.metricName || o.dimension?.dimensionName || "",
188
+ o.desc ? "desc" : "asc"
189
+ ]);
190
+ }
191
+ if (options.limit || options.offset) {
192
+ queryBody.pagination = {
193
+ limit: options.limit,
194
+ offset: options.offset
195
+ };
196
+ }
197
+ const response = await this.request(
198
+ "/api/v2/query",
199
+ {
200
+ method: "POST",
201
+ body: queryBody
202
+ }
203
+ );
204
+ return {
205
+ dimensionHeaders: (options.dimensions || []).map((d) => ({
206
+ name: d.name
207
+ })),
208
+ metricHeaders: options.metrics.map((m) => ({
209
+ name: m.name,
210
+ type: "TYPE_INTEGER"
211
+ })),
212
+ rows: response.results.map((r) => ({
213
+ dimensionValues: r.dimensions.map((v) => ({ value: String(v) })),
214
+ metricValues: r.metrics.map((v) => ({ value: String(v) }))
215
+ })),
216
+ rowCount: response.meta.total_rows
217
+ };
218
+ }
219
+ async runRealtimeReport(propertyId, _options) {
220
+ const response = await this.request(
221
+ `/api/v1/stats/realtime/visitors?site_id=${propertyId}`
222
+ );
223
+ return {
224
+ dimensionHeaders: [],
225
+ metricHeaders: [{ name: "activeUsers", type: "TYPE_INTEGER" }],
226
+ rows: [
227
+ {
228
+ dimensionValues: [],
229
+ metricValues: [{ value: String(response.visitors) }]
230
+ }
231
+ ]
232
+ };
233
+ }
234
+ async getMetrics(_propertyId) {
235
+ return [
236
+ {
237
+ apiName: "visitors",
238
+ uiName: "Visitors",
239
+ description: "Unique visitors",
240
+ type: "TYPE_INTEGER"
241
+ },
242
+ {
243
+ apiName: "pageviews",
244
+ uiName: "Pageviews",
245
+ description: "Total page views",
246
+ type: "TYPE_INTEGER"
247
+ },
248
+ {
249
+ apiName: "bounce_rate",
250
+ uiName: "Bounce Rate",
251
+ description: "Percentage of single-page sessions",
252
+ type: "TYPE_FLOAT"
253
+ },
254
+ {
255
+ apiName: "visit_duration",
256
+ uiName: "Visit Duration",
257
+ description: "Average visit duration in seconds",
258
+ type: "TYPE_SECONDS"
259
+ },
260
+ {
261
+ apiName: "events",
262
+ uiName: "Events",
263
+ description: "Total events",
264
+ type: "TYPE_INTEGER"
265
+ },
266
+ {
267
+ apiName: "visits",
268
+ uiName: "Visits",
269
+ description: "Total visits (sessions)",
270
+ type: "TYPE_INTEGER"
271
+ },
272
+ {
273
+ apiName: "views_per_visit",
274
+ uiName: "Views per Visit",
275
+ description: "Average page views per visit",
276
+ type: "TYPE_FLOAT"
277
+ }
278
+ ];
279
+ }
280
+ async getDimensions(_propertyId) {
281
+ return [
282
+ {
283
+ apiName: "event:page",
284
+ uiName: "Page",
285
+ description: "Page path"
286
+ },
287
+ {
288
+ apiName: "visit:source",
289
+ uiName: "Source",
290
+ description: "Traffic source"
291
+ },
292
+ {
293
+ apiName: "visit:referrer",
294
+ uiName: "Referrer",
295
+ description: "Referrer URL"
296
+ },
297
+ {
298
+ apiName: "visit:utm_source",
299
+ uiName: "UTM Source",
300
+ description: "UTM source parameter"
301
+ },
302
+ {
303
+ apiName: "visit:utm_medium",
304
+ uiName: "UTM Medium",
305
+ description: "UTM medium parameter"
306
+ },
307
+ {
308
+ apiName: "visit:utm_campaign",
309
+ uiName: "UTM Campaign",
310
+ description: "UTM campaign parameter"
311
+ },
312
+ {
313
+ apiName: "visit:device",
314
+ uiName: "Device",
315
+ description: "Device type"
316
+ },
317
+ {
318
+ apiName: "visit:browser",
319
+ uiName: "Browser",
320
+ description: "Browser name"
321
+ },
322
+ {
323
+ apiName: "visit:os",
324
+ uiName: "OS",
325
+ description: "Operating system"
326
+ },
327
+ {
328
+ apiName: "visit:country",
329
+ uiName: "Country",
330
+ description: "Country name"
331
+ },
332
+ {
333
+ apiName: "visit:city",
334
+ uiName: "City",
335
+ description: "City name"
336
+ }
337
+ ];
338
+ }
339
+ /**
340
+ * Map GA4-style metric names to Plausible format
341
+ */
342
+ mapMetricName(name) {
343
+ const mapping = {
344
+ activeUsers: "visitors",
345
+ users: "visitors",
346
+ sessions: "visits",
347
+ pageviews: "pageviews",
348
+ bounceRate: "bounce_rate",
349
+ avgSessionDuration: "visit_duration",
350
+ events: "events"
351
+ };
352
+ return mapping[name] || name;
353
+ }
354
+ /**
355
+ * Map GA4-style dimension names to Plausible format
356
+ */
357
+ mapDimensionName(name) {
358
+ const mapping = {
359
+ pagePath: "event:page",
360
+ page: "event:page",
361
+ source: "visit:source",
362
+ medium: "visit:utm_medium",
363
+ campaign: "visit:utm_campaign",
364
+ country: "visit:country",
365
+ city: "visit:city",
366
+ deviceCategory: "visit:device",
367
+ browser: "visit:browser",
368
+ operatingSystem: "visit:os"
369
+ };
370
+ return mapping[name] || name;
371
+ }
372
+ // ===========================================================================
373
+ // Event Tracking (Events API)
374
+ // ===========================================================================
375
+ async track(event) {
376
+ const siteId = this.options.defaultSiteId;
377
+ if (!siteId) {
378
+ throw new AnalyticsError(
379
+ "Default site ID is required for server-side tracking",
380
+ "MISSING_SITE_ID",
381
+ "plausible"
382
+ );
383
+ }
384
+ await this.request("/api/event", {
385
+ method: "POST",
386
+ headers: {
387
+ "User-Agent": "Mozilla/5.0 (compatible; PlausibleBot/1.0)",
388
+ "X-Forwarded-For": "127.0.0.1"
389
+ // Will be overwritten by actual IP in production
390
+ },
391
+ body: {
392
+ name: event.name,
393
+ url: `https://${siteId}`,
394
+ domain: siteId,
395
+ props: event.params
396
+ }
397
+ });
398
+ }
399
+ async trackPageview(pageview) {
400
+ const siteId = this.options.defaultSiteId;
401
+ if (!siteId) {
402
+ throw new AnalyticsError(
403
+ "Default site ID is required for server-side tracking",
404
+ "MISSING_SITE_ID",
405
+ "plausible"
406
+ );
407
+ }
408
+ await this.request("/api/event", {
409
+ method: "POST",
410
+ headers: {
411
+ "User-Agent": "Mozilla/5.0 (compatible; PlausibleBot/1.0)",
412
+ "X-Forwarded-For": "127.0.0.1"
413
+ },
414
+ body: {
415
+ name: "pageview",
416
+ url: pageview.pageLocation || `https://${siteId}${pageview.pagePath}`,
417
+ domain: siteId,
418
+ props: pageview.params
419
+ }
420
+ });
421
+ }
422
+ async trackBatch(events) {
423
+ for (const event of events) {
424
+ await this.track(event);
425
+ }
426
+ }
427
+ async identify(_userId, _traits) {
428
+ throw new NotSupportedError("identify", "plausible");
429
+ }
430
+ // ===========================================================================
431
+ // Client-Side Helpers
432
+ // ===========================================================================
433
+ generateTrackingSnippet(propertyId, _options) {
434
+ const domain = propertyId;
435
+ const scriptUrl = `${this.baseUrl}/js/script.js`;
436
+ const html = `<script defer data-domain="${domain}" src="${scriptUrl}"><\/script>`;
437
+ return {
438
+ html,
439
+ config: {
440
+ domain,
441
+ scriptUrl
442
+ },
443
+ scripts: [scriptUrl]
444
+ };
445
+ }
446
+ generateConfig(propertyId, _options) {
447
+ return {
448
+ domain: propertyId,
449
+ apiHost: this.baseUrl
450
+ };
451
+ }
452
+ // ===========================================================================
453
+ // Provider Info
454
+ // ===========================================================================
455
+ async getCapabilities() {
456
+ return {
457
+ propertyManagement: true,
458
+ // Limited - create and delete only
459
+ dataStreams: false,
460
+ customDimensions: false,
461
+ // Via UI only
462
+ customMetrics: false,
463
+ keyEvents: true,
464
+ // Goals
465
+ reporting: true,
466
+ realtimeReporting: true,
467
+ serverSideTracking: true,
468
+ clientSideSnippet: true,
469
+ userIdentification: false,
470
+ // Privacy-focused
471
+ batchTracking: false
472
+ // No batch API
473
+ };
474
+ }
475
+ }
476
+ export {
477
+ PlausibleProvider
478
+ };
479
+ //# sourceMappingURL=plausible-BxpNa6qF.js.map