@byline/host-tanstack-start 4.15.0 → 4.16.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.
- package/dist/admin-shell/chrome/admin-menu-drawer.d.ts +2 -1
- package/dist/admin-shell/chrome/admin-menu-drawer.js +14 -2
- package/dist/integrations/analytics-agent.d.ts +20 -0
- package/dist/integrations/analytics-agent.js +57 -0
- package/dist/integrations/analytics-events.d.ts +51 -0
- package/dist/integrations/analytics-events.js +129 -0
- package/dist/integrations/serve-analytics-agent.d.ts +8 -0
- package/dist/integrations/serve-analytics-agent.js +11 -0
- package/dist/routes/analytics-range.d.ts +12 -0
- package/dist/routes/analytics-range.js +35 -0
- package/dist/routes/create-admin-analytics-route.d.ts +8 -0
- package/dist/routes/create-admin-analytics-route.js +108 -0
- package/dist/routes/create-admin-layout-route.js +10 -4
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.js +1 -0
- package/dist/server-fns/analytics/index.d.ts +9 -0
- package/dist/server-fns/analytics/index.js +2 -0
- package/dist/server-fns/analytics/maintenance.d.ts +22 -0
- package/dist/server-fns/analytics/maintenance.js +42 -0
- package/dist/server-fns/analytics/queries.d.ts +25 -0
- package/dist/server-fns/analytics/queries.js +54 -0
- package/dist/vite/analytics-dev.d.ts +22 -0
- package/dist/vite/analytics-dev.js +28 -0
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.js +1 -0
- package/package.json +14 -8
- package/src/admin-shell/chrome/admin-menu-drawer.tsx +14 -0
- package/src/integrations/analytics-agent.tsx +91 -0
- package/src/integrations/analytics-events.test.node.ts +245 -0
- package/src/integrations/analytics-events.ts +224 -0
- package/src/integrations/serve-analytics-agent.test.node.ts +20 -0
- package/src/integrations/serve-analytics-agent.ts +19 -0
- package/src/routes/analytics-range.ts +55 -0
- package/src/routes/create-admin-analytics-route.test.node.ts +46 -0
- package/src/routes/create-admin-analytics-route.tsx +109 -0
- package/src/routes/create-admin-layout-route.tsx +15 -8
- package/src/routes/index.ts +1 -0
- package/src/server-fns/analytics/index.ts +23 -0
- package/src/server-fns/analytics/maintenance.ts +54 -0
- package/src/server-fns/analytics/queries.ts +86 -0
- package/src/vite/analytics-dev.test.node.ts +126 -0
- package/src/vite/analytics-dev.ts +56 -0
- package/src/vite/index.ts +1 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
10
|
+
|
|
11
|
+
import { assertAdminActor, requireAdminActor } from '@byline/admin'
|
|
12
|
+
import { ANALYTICS_ABILITIES } from '@byline/admin/analytics'
|
|
13
|
+
import type {
|
|
14
|
+
AnalyticsCountryTotal,
|
|
15
|
+
AnalyticsDateRange,
|
|
16
|
+
AnalyticsEventKind,
|
|
17
|
+
AnalyticsPathTotal,
|
|
18
|
+
AnalyticsRankedTotals,
|
|
19
|
+
AnalyticsReferrerTotal,
|
|
20
|
+
AnalyticsReportCoverage,
|
|
21
|
+
AnalyticsSummary,
|
|
22
|
+
} from '@byline/analytics'
|
|
23
|
+
import { getAdminRequestContext } from '@byline/client/server'
|
|
24
|
+
|
|
25
|
+
export interface AnalyticsTopInput extends AnalyticsDateRange {
|
|
26
|
+
kind: AnalyticsEventKind
|
|
27
|
+
limit?: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AnalyticsRuntimeState {
|
|
31
|
+
enabled: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const getAnalyticsRuntime = createServerFn({ method: 'GET' }).handler(
|
|
35
|
+
async (): Promise<AnalyticsRuntimeState> => {
|
|
36
|
+
const context = await getAdminRequestContext()
|
|
37
|
+
requireAdminActor(context, 'reading analytics runtime state')
|
|
38
|
+
const { isAnalyticsRegistered } = await import('@byline/analytics')
|
|
39
|
+
return { enabled: isAnalyticsRegistered() }
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
export const getAnalyticsSummary = createServerFn({ method: 'GET' })
|
|
44
|
+
.validator((input: AnalyticsDateRange) => input)
|
|
45
|
+
.handler(async ({ data }): Promise<AnalyticsSummary> => {
|
|
46
|
+
await assertAnalyticsRead()
|
|
47
|
+
const { getAnalytics } = await import('@byline/analytics')
|
|
48
|
+
return getAnalytics().getSummary(data)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export const getAnalyticsReportCoverage = createServerFn({ method: 'GET' }).handler(
|
|
52
|
+
async (): Promise<AnalyticsReportCoverage> => {
|
|
53
|
+
await assertAnalyticsRead()
|
|
54
|
+
const { getAnalytics } = await import('@byline/analytics')
|
|
55
|
+
return getAnalytics().getReportCoverage()
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
export const getAnalyticsTop = createServerFn({ method: 'GET' })
|
|
60
|
+
.validator((input: AnalyticsTopInput) => input)
|
|
61
|
+
.handler(async ({ data }): Promise<AnalyticsRankedTotals<AnalyticsPathTotal>> => {
|
|
62
|
+
await assertAnalyticsRead()
|
|
63
|
+
const { getAnalytics } = await import('@byline/analytics')
|
|
64
|
+
return getAnalytics().getTopPaths(data)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
export const getAnalyticsReferrers = createServerFn({ method: 'GET' })
|
|
68
|
+
.validator((input: AnalyticsDateRange & { limit?: number }) => input)
|
|
69
|
+
.handler(async ({ data }): Promise<AnalyticsRankedTotals<AnalyticsReferrerTotal>> => {
|
|
70
|
+
await assertAnalyticsRead()
|
|
71
|
+
const { getAnalytics } = await import('@byline/analytics')
|
|
72
|
+
return getAnalytics().getReferrers(data)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
export const getAnalyticsCountries = createServerFn({ method: 'GET' })
|
|
76
|
+
.validator((input: AnalyticsDateRange) => input)
|
|
77
|
+
.handler(async ({ data }): Promise<AnalyticsCountryTotal[]> => {
|
|
78
|
+
await assertAnalyticsRead()
|
|
79
|
+
const { getAnalytics } = await import('@byline/analytics')
|
|
80
|
+
return getAnalytics().getCountries(data)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
async function assertAnalyticsRead(): Promise<void> {
|
|
84
|
+
const context = await getAdminRequestContext()
|
|
85
|
+
assertAdminActor(context, ANALYTICS_ABILITIES.read)
|
|
86
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
10
|
+
|
|
11
|
+
import { ANALYTICS_AGENT_SOURCE } from '@byline/analytics-agent/source'
|
|
12
|
+
import type { Plugin } from 'vite'
|
|
13
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
14
|
+
|
|
15
|
+
import { bylineAnalyticsDev } from './analytics-dev.js'
|
|
16
|
+
|
|
17
|
+
type Middleware = (
|
|
18
|
+
request: IncomingMessage,
|
|
19
|
+
response: ServerResponse,
|
|
20
|
+
next: (error?: unknown) => void
|
|
21
|
+
) => void | Promise<void>
|
|
22
|
+
|
|
23
|
+
describe('bylineAnalyticsDev', () => {
|
|
24
|
+
it('directly serves browser script requests before Vite can transform them', async () => {
|
|
25
|
+
const handler = captureMiddleware(bylineAnalyticsDev())
|
|
26
|
+
const request = requestStub('GET', '/b.js?version=current', {
|
|
27
|
+
accept: '*/*',
|
|
28
|
+
'sec-fetch-dest': 'script',
|
|
29
|
+
})
|
|
30
|
+
const { response, headers, end } = responseStub()
|
|
31
|
+
const next = vi.fn()
|
|
32
|
+
|
|
33
|
+
await handler(request, response, next)
|
|
34
|
+
|
|
35
|
+
expect(response.statusCode).toBe(200)
|
|
36
|
+
expect(headers).toEqual({
|
|
37
|
+
'cache-control': 'public, max-age=3600, must-revalidate',
|
|
38
|
+
'content-type': 'text/javascript; charset=utf-8',
|
|
39
|
+
'x-content-type-options': 'nosniff',
|
|
40
|
+
})
|
|
41
|
+
expect(end).toHaveBeenCalledWith(ANALYTICS_AGENT_SOURCE)
|
|
42
|
+
expect(next).not.toHaveBeenCalled()
|
|
43
|
+
expect(request.headers).toEqual({
|
|
44
|
+
accept: '*/*',
|
|
45
|
+
'sec-fetch-dest': 'script',
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('continues unrelated requests through the Vite middleware chain', async () => {
|
|
50
|
+
const handler = captureMiddleware(bylineAnalyticsDev())
|
|
51
|
+
const { response, end } = responseStub()
|
|
52
|
+
const next = vi.fn()
|
|
53
|
+
|
|
54
|
+
await handler(requestStub('GET', '/about'), response, next)
|
|
55
|
+
await handler(requestStub('POST', '/api/events'), response, next)
|
|
56
|
+
|
|
57
|
+
expect(next).toHaveBeenCalledTimes(2)
|
|
58
|
+
expect(end).not.toHaveBeenCalled()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('supports a custom public agent path', async () => {
|
|
62
|
+
const handler = captureMiddleware(bylineAnalyticsDev({ agentPath: '/first-party.js' }))
|
|
63
|
+
const defaultResponse = responseStub()
|
|
64
|
+
const customResponse = responseStub()
|
|
65
|
+
const next = vi.fn()
|
|
66
|
+
|
|
67
|
+
await handler(requestStub('GET', '/b.js'), defaultResponse.response, next)
|
|
68
|
+
await handler(requestStub('GET', '/first-party.js'), customResponse.response, next)
|
|
69
|
+
|
|
70
|
+
expect(next).toHaveBeenCalledOnce()
|
|
71
|
+
expect(defaultResponse.end).not.toHaveBeenCalled()
|
|
72
|
+
expect(customResponse.end).toHaveBeenCalledWith(ANALYTICS_AGENT_SOURCE)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
function captureMiddleware(plugin: Plugin): Middleware {
|
|
77
|
+
const handlers: Middleware[] = []
|
|
78
|
+
const configureServer = plugin.configureServer
|
|
79
|
+
|
|
80
|
+
expect(typeof configureServer).toBe('function')
|
|
81
|
+
if (typeof configureServer !== 'function') {
|
|
82
|
+
throw new Error('Expected a Vite configureServer hook')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
configureServer.call(
|
|
86
|
+
{} as never,
|
|
87
|
+
{
|
|
88
|
+
middlewares: {
|
|
89
|
+
use(handler: Middleware) {
|
|
90
|
+
handlers.push(handler)
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
} as never
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const handler = handlers[0]
|
|
97
|
+
expect(handler).toBeDefined()
|
|
98
|
+
if (handler == null) throw new Error('Expected a Vite middleware')
|
|
99
|
+
return handler
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function requestStub(
|
|
103
|
+
method: string,
|
|
104
|
+
url: string,
|
|
105
|
+
headers: IncomingMessage['headers'] = {}
|
|
106
|
+
): IncomingMessage {
|
|
107
|
+
return { method, url, headers } as IncomingMessage
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function responseStub(): {
|
|
111
|
+
response: ServerResponse
|
|
112
|
+
headers: Record<string, string | number | readonly string[]>
|
|
113
|
+
end: ReturnType<typeof vi.fn>
|
|
114
|
+
} {
|
|
115
|
+
const headers: Record<string, string | number | readonly string[]> = {}
|
|
116
|
+
const end = vi.fn()
|
|
117
|
+
const response = {
|
|
118
|
+
statusCode: 0,
|
|
119
|
+
setHeader(name: string, value: string | number | readonly string[]) {
|
|
120
|
+
headers[name] = value
|
|
121
|
+
return response
|
|
122
|
+
},
|
|
123
|
+
end,
|
|
124
|
+
} as unknown as ServerResponse
|
|
125
|
+
return { response, headers, end }
|
|
126
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Plugin } from 'vite'
|
|
10
|
+
|
|
11
|
+
import { serveAnalyticsAgent } from '../integrations/serve-analytics-agent.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Complete the first-party analytics transport during Vite development.
|
|
15
|
+
*
|
|
16
|
+
* Browser script requests carry `Sec-Fetch-Dest: script`, which Vite routes
|
|
17
|
+
* into its module pipeline before TanStack Start can serve the agent route.
|
|
18
|
+
* This plugin serves the agent directly. The application event route remains
|
|
19
|
+
* responsible for its development identity because POST server routes can
|
|
20
|
+
* bypass Vite's ordinary middleware chain. Production continues to use the
|
|
21
|
+
* application-owned agent route.
|
|
22
|
+
*/
|
|
23
|
+
export function bylineAnalyticsDev(
|
|
24
|
+
options: {
|
|
25
|
+
/** Public path that serves the standalone agent. */
|
|
26
|
+
agentPath?: string
|
|
27
|
+
} = {}
|
|
28
|
+
): Plugin {
|
|
29
|
+
const agentPath = options.agentPath ?? '/b.js'
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
name: 'byline:analytics-dev',
|
|
33
|
+
apply: 'serve',
|
|
34
|
+
configureServer(server) {
|
|
35
|
+
server.middlewares.use(async (request, response, next) => {
|
|
36
|
+
const pathname = request.url?.split('?', 1)[0]
|
|
37
|
+
|
|
38
|
+
if (request.method === 'GET' && pathname === agentPath) {
|
|
39
|
+
try {
|
|
40
|
+
const agentResponse = serveAnalyticsAgent()
|
|
41
|
+
response.statusCode = agentResponse.status
|
|
42
|
+
agentResponse.headers.forEach((value, name) => {
|
|
43
|
+
response.setHeader(name, value)
|
|
44
|
+
})
|
|
45
|
+
response.end(await agentResponse.text())
|
|
46
|
+
} catch (error) {
|
|
47
|
+
next(error)
|
|
48
|
+
}
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
next()
|
|
53
|
+
})
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}
|