@multiplayer-app/session-recorder-node 2.0.17 → 2.0.18

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.
@@ -1,48 +0,0 @@
1
- {
2
- "name": "http-server-exmaple",
3
- "version": "1.0.0",
4
- "description": "",
5
- "main": "./src/index.ts",
6
- "engines": {
7
- "node": ">=18",
8
- "npm": ">=8"
9
- },
10
- "scripts": {
11
- "start": "node dist/index.js",
12
- "dev": "nodemon --watch \"src/**\" --ext \"ts,json\" --ignore \"src/**/*.spec.ts\" --exec \"ts-node src/index.ts\"",
13
- "build": "npm run clean && npm run compile",
14
- "clean": "rm -rf ./dist",
15
- "compile": "tsc --build --force",
16
- "lint": "eslint src/**/*.ts --config ../../.eslintrc"
17
- },
18
- "author": "Multiplayer",
19
- "dependencies": {
20
- "@multiplayer-app/session-recorder-node": "1.3.37",
21
- "@opentelemetry/api": "1.9.0",
22
- "@opentelemetry/api-logs": "0.203.0",
23
- "@opentelemetry/auto-instrumentations-node": "0.62.1",
24
- "@opentelemetry/core": "2.0.1",
25
- "@opentelemetry/exporter-trace-otlp-http": "0.203.0",
26
- "@opentelemetry/exporter-logs-otlp-http": "0.203.0",
27
- "@opentelemetry/resources": "2.0.1",
28
- "@opentelemetry/sdk-logs": "0.203.0",
29
- "@opentelemetry/sdk-trace-base": "2.0.1",
30
- "@opentelemetry/sdk-trace-node": "2.0.1",
31
- "@opentelemetry/semantic-conventions": "1.36.0",
32
- "body-parser": "1.20.2",
33
- "cors": "2.8.5",
34
- "dotenv": "16.3.1",
35
- "express": "4.18.2"
36
- },
37
- "devDependencies": {
38
- "@types/body-parser": "1.19.2",
39
- "@types/cors": "2.8.13",
40
- "@types/express": "4.17.17",
41
- "@types/jest": "29.5.4",
42
- "@types/node": "20.5.7",
43
- "eslint": "10.1.0",
44
- "nodemon": "3.1.9",
45
- "ts-node": "10.9.1",
46
- "typescript": "5.7.3"
47
- }
48
- }
@@ -1,20 +0,0 @@
1
- import express, {
2
- type Request,
3
- type Response,
4
- type NextFunction
5
- } from 'express'
6
-
7
- const { Router } = express
8
- const router = Router()
9
-
10
- const health = async (req: Request, res: Response, next: NextFunction) => {
11
- try {
12
- return res.status(200).json({})
13
- } catch (err) {
14
- return next(err)
15
- }
16
- }
17
-
18
- router.route('/').get(health)
19
-
20
- export default router
@@ -1,24 +0,0 @@
1
- export const NODE_ENV = process.env.NODE_ENV || 'development'
2
- export const isProduction = NODE_ENV === 'production'
3
-
4
- export const PORT = Number(process.env.PORT || 3000)
5
- export const API_PREFIX = process.env.API_PREFIX || '/v1/api'
6
-
7
- export const LOG_LEVEL = process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug')
8
-
9
- export const COMPONENT_NAME = process.env.npm_package_name?.split('/').pop() as string || process.env.SERVICE_NAME || 'timegate'
10
- export const COMPONENT_VERSION = process.env.npm_package_version || '0.0.1'
11
- export const ENVIRONMENT = process.env.ENVIRONMENT || 'staging'
12
-
13
- export const MULTIPLAYER_OTLP_KEY = process.env.MULTIPLAYER_OTLP_KEY as string
14
-
15
- if (!MULTIPLAYER_OTLP_KEY) {
16
- throw new Error('MULTIPLAYER_OTLP_KEY is not set')
17
- }
18
-
19
- export const OTLP_TRACES_ENDPOINT = process.env.OTLP_TRACES_ENDPOINT || 'https://api.multiplayer.app/v1/traces'
20
- export const OTLP_LOGS_ENDPOINT = process.env.OTLP_LOGS_ENDPOINT || 'https://api.multiplayer.app/v1/logs'
21
-
22
- export const MULTIPLAYER_OTLP_SPAN_RATIO = process.env.MULTIPLAYER_OTLP_SPAN_RATIO
23
- ? Number(process.env.MULTIPLAYER_OTLP_SPAN_RATIO)
24
- : 0.01
@@ -1,46 +0,0 @@
1
- import 'dotenv/config'
2
- import './opentelemetry'
3
- import http from 'http'
4
- import cors from 'cors'
5
- import bodyParser from 'body-parser'
6
- import express, {
7
- type Request,
8
- type Response,
9
- type NextFunction,
10
- } from 'express'
11
- import { PORT, API_PREFIX } from './config'
12
- import api from './api'
13
-
14
- const app = express()
15
-
16
- app.use(cors())
17
- app.use(bodyParser.urlencoded({ extended: true }))
18
- app.use(bodyParser.json())
19
-
20
- app.use(API_PREFIX, api)
21
-
22
- app.use((req: Request, res: Response, next: NextFunction) => {
23
- res.status(404).send('Not found')
24
- })
25
-
26
- const httpServer = http.createServer(app)
27
- const onReady = () => {
28
- console.log(`🚀 Server ready at http://localhost:${PORT}`)
29
- }
30
-
31
- httpServer.listen(PORT, onReady)
32
-
33
- const exitHandler = async (error: Error) => {
34
- if (error) {
35
- console.log('Server exited with error', error)
36
- }
37
- process.removeListener('exit', exitHandler)
38
- process.exit()
39
- }
40
-
41
- process.on('exit', exitHandler)
42
- process.on('SIGINT', exitHandler)
43
- process.on('SIGTERM', exitHandler)
44
- process.on('uncaughtException', (err) => {
45
- console.error('uncaughtException', err)
46
- })
@@ -1,140 +0,0 @@
1
- import { hostname } from 'os'
2
- import { node, NodeSDK } from '@opentelemetry/sdk-node'
3
- import {
4
- BatchSpanProcessor,
5
- ParentBasedSampler,
6
- } from '@opentelemetry/sdk-trace-base'
7
- import {
8
- getNodeAutoInstrumentations,
9
- getResourceDetectors,
10
- } from '@opentelemetry/auto-instrumentations-node'
11
- import {
12
- resourceFromAttributes,
13
- detectResources,
14
- } from '@opentelemetry/resources'
15
- import {
16
- ATTR_SERVICE_NAME,
17
- ATTR_SERVICE_VERSION,
18
- SEMRESATTRS_HOST_NAME,
19
- SEMRESATTRS_DEPLOYMENT_ENVIRONMENT,
20
- SEMRESATTRS_PROCESS_RUNTIME_VERSION,
21
- SEMRESATTRS_PROCESS_PID,
22
- } from '@opentelemetry/semantic-conventions'
23
- import api from '@opentelemetry/api'
24
- // import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"
25
- import { W3CTraceContextPropagator } from '@opentelemetry/core'
26
- import {
27
- SessionRecorderHttpInstrumentationHooksNode,
28
- SessionRecorderTraceIdRatioBasedSampler,
29
- SessionRecorderIdGenerator,
30
- SessionRecorderHttpTraceExporter,
31
- SessionRecorderHttpLogsExporter,
32
- } from '@multiplayer-app/session-recorder-node'
33
- import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs'
34
- import * as apiLogs from '@opentelemetry/api-logs'
35
- // import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'
36
-
37
- import {
38
- OTLP_TRACES_ENDPOINT,
39
- OTLP_LOGS_ENDPOINT,
40
- MULTIPLAYER_OTLP_KEY,
41
- MULTIPLAYER_OTLP_SPAN_RATIO,
42
- COMPONENT_NAME,
43
- COMPONENT_VERSION,
44
- ENVIRONMENT,
45
- } from './config'
46
-
47
- // NOTE: Update instrumentation configuration as needed
48
- // For more see: https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node
49
- const instrumentations = [
50
- getNodeAutoInstrumentations({
51
- '@opentelemetry/instrumentation-http': {
52
- requestHook: SessionRecorderHttpInstrumentationHooksNode.requestHook({
53
- maskHeadersList: ['X-Api-Key'],
54
- maxPayloadSizeBytes: 5000,
55
- schemifyDocSpanPayload: false,
56
- isMaskBodyEnabled: false,
57
- }),
58
- responseHook: SessionRecorderHttpInstrumentationHooksNode.responseHook({
59
- maskHeadersList: ['X-Api-Key'],
60
- maxPayloadSizeBytes: 5000,
61
- schemifyDocSpanPayload: false,
62
- isMaskBodyEnabled: false,
63
- }),
64
- },
65
- }),
66
- ]
67
-
68
- const getResource = () => {
69
- const resourceWithAttributes = resourceFromAttributes({
70
- [ATTR_SERVICE_NAME]: COMPONENT_NAME,
71
- [ATTR_SERVICE_VERSION]: COMPONENT_VERSION,
72
- [SEMRESATTRS_HOST_NAME]: hostname(),
73
- [SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: ENVIRONMENT,
74
- [SEMRESATTRS_PROCESS_RUNTIME_VERSION]: process.version,
75
- [SEMRESATTRS_PROCESS_PID]: process.pid,
76
- })
77
- const detectedResources = detectResources({ detectors: getResourceDetectors() })
78
- const resource = resourceWithAttributes.merge(detectedResources)
79
-
80
- return resource
81
- }
82
-
83
- export const idGenerator = new SessionRecorderIdGenerator()
84
-
85
- const opentelemetry = () => {
86
- // const traceExporter = new OTLPTraceExporter({
87
- // url: OTLP_TRACES_ENDPOINT,
88
- // headers: {
89
- // Authorization: MULTIPLAYER_OTLP_KEY
90
- // },
91
- // })
92
- const traceExporter = new SessionRecorderHttpTraceExporter({
93
- apiKey: MULTIPLAYER_OTLP_KEY,
94
- url: OTLP_TRACES_ENDPOINT,
95
- })
96
-
97
- const resource = getResource()
98
-
99
- const provider = new node.NodeTracerProvider({
100
- resource,
101
- spanProcessors: [
102
- new BatchSpanProcessor(traceExporter),
103
- ],
104
- sampler: new ParentBasedSampler({
105
- root: new SessionRecorderTraceIdRatioBasedSampler(MULTIPLAYER_OTLP_SPAN_RATIO),
106
- }),
107
- idGenerator,
108
- })
109
-
110
- // const logExporter = new OTLPLogExporter({
111
- // url: OTLP_LOGS_ENDPOINT,
112
- // headers: {
113
- // Authorization: MULTIPLAYER_OTLP_KEY
114
- // },
115
- // })
116
- const logExporter = new SessionRecorderHttpLogsExporter({
117
- apiKey: MULTIPLAYER_OTLP_KEY,
118
- url: OTLP_LOGS_ENDPOINT,
119
- })
120
- const logRecordProcessor = new BatchLogRecordProcessor(logExporter)
121
-
122
- const loggerProvider = new LoggerProvider({
123
- resource,
124
- processors: [logRecordProcessor],
125
- })
126
-
127
- apiLogs.logs.setGlobalLoggerProvider(loggerProvider)
128
-
129
- provider.register()
130
- api.trace.setGlobalTracerProvider(provider)
131
- api.propagation.setGlobalPropagator(new W3CTraceContextPropagator())
132
-
133
- const sdk = new NodeSDK({
134
- instrumentations,
135
- })
136
-
137
- sdk.start()
138
- }
139
-
140
- opentelemetry()
@@ -1,39 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "declaration": true,
4
- "declarationMap": true,
5
- "baseUrl": ".",
6
- "module": "commonjs",
7
- "noImplicitAny": false,
8
- "noUnusedParameters": false,
9
- "allowJs": true,
10
- "allowSyntheticDefaultImports": true,
11
- "esModuleInterop": true,
12
- "forceConsistentCasingInFileNames": true,
13
- "isolatedModules": true,
14
- "moduleResolution": "node",
15
- "noImplicitReturns": true,
16
- "noImplicitThis": true,
17
- "noUnusedLocals": false,
18
- "preserveConstEnums": true,
19
- "removeComments": false,
20
- "resolveJsonModule": true,
21
- "skipLibCheck": true,
22
- "sourceMap": true,
23
- "types": [
24
- "node"
25
- ],
26
- "target": "ES2018",
27
- "downlevelIteration": true,
28
- "strict": true,
29
- "composite": true,
30
- "outDir": "./dist/",
31
- "rootDir": "./src/",
32
- "preserveSymlinks": true
33
- },
34
- "exclude": ["dist", "coverage", "jest.config.js", "__tests__", "migration"],
35
- "references": [],
36
- "ts-node": {
37
- "files": true
38
- }
39
- }
package/src/config.ts DELETED
@@ -1,5 +0,0 @@
1
- import pkg from '../package.json'
2
-
3
- export const MULTIPLAYER_BASE_API_URL = process.env.MULTIPLAYER_BASE_API_URL || 'https://api.multiplayer.app'
4
-
5
- export const SESSION_RECORDER_VERSION = process.env.SESSION_RECORDER_VERSION || pkg.version
package/src/helper.ts DELETED
@@ -1,13 +0,0 @@
1
- export const getFormattedDate = (date, options?) => {
2
- return new Date(date).toLocaleDateString(
3
- 'en-US',
4
- options || {
5
- month: 'short',
6
- year: 'numeric',
7
- day: 'numeric',
8
- hour: 'numeric',
9
- minute: '2-digit',
10
- second: '2-digit',
11
- },
12
- )
13
- }
package/src/index.ts DELETED
@@ -1,6 +0,0 @@
1
- import { SessionRecorder } from './sessionRecorder'
2
-
3
- export const sessionRecorder = new SessionRecorder()
4
-
5
- export * from '@multiplayer-app/session-recorder-common'
6
- export * as Integrations from './integrations'
@@ -1,52 +0,0 @@
1
- import type * as http from 'node:http'
2
- import { SessionRecorderSdk } from '@multiplayer-app/session-recorder-common'
3
-
4
- type ExpressMiddleware = (req: http.IncomingMessage, res: http.ServerResponse, next: () => void) => void;
5
-
6
- type ExpressErrorMiddleware = (
7
- error: Error,
8
- req: http.IncomingMessage,
9
- res: http.ServerResponse,
10
- next: (error: Error) => void,
11
- ) => void;
12
-
13
- interface MiddlewareError extends Error {
14
- status?: number | string;
15
- statusCode?: number | string;
16
- status_code?: number | string;
17
- output?: {
18
- statusCode?: number | string;
19
- };
20
- }
21
-
22
- interface ExpressHandlerOptions {
23
- shouldHandleError?(this: void, error: Error): boolean;
24
- }
25
-
26
- function getStatusCodeFromResponse(error: MiddlewareError): number {
27
- const statusCode = error.status || error.statusCode || error.status_code || error.output?.statusCode
28
- return statusCode ? parseInt(statusCode as string, 10) : 500
29
- }
30
-
31
- /** Returns true if response code is internal server error */
32
- function defaultShouldHandleError(error: Error): boolean {
33
- const status = getStatusCodeFromResponse(error)
34
- return status >= 500
35
- }
36
-
37
- export function expressErrorHandler(options?: ExpressHandlerOptions): ExpressErrorMiddleware {
38
- return function multiplayerErrorMiddleware(
39
- error: Error,
40
- request: http.IncomingMessage,
41
- res: http.ServerResponse,
42
- next: (error: Error) => void,
43
- ): void {
44
- const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError
45
-
46
- if (shouldHandleError(error)) {
47
- SessionRecorderSdk.captureException(error)
48
- }
49
-
50
- next(error)
51
- }
52
- }
@@ -1 +0,0 @@
1
- export * as express from './express'
@@ -1,230 +0,0 @@
1
- import { MULTIPLAYER_BASE_API_URL } from '../config'
2
- import { ISession } from '../types'
3
-
4
- export interface ApiServiceConfig {
5
- apiKey?: string
6
- apiBaseUrl?: string
7
- continuousRecording?: boolean
8
- }
9
-
10
- export interface StartSessionRequest {
11
- name?: string
12
- resourceAttributes?: Record<string, any>
13
- sessionAttributes?: Record<string, any>
14
- tags?: {
15
- key?: string
16
- value: string
17
- }[]
18
- }
19
-
20
- export interface StopSessionRequest {
21
- sessionAttributes?: {
22
- email?: string
23
- comment?: string
24
- },
25
- }
26
-
27
- export class ApiService {
28
- private config: ApiServiceConfig
29
-
30
- constructor() {
31
- this.config = {
32
- apiBaseUrl: MULTIPLAYER_BASE_API_URL,
33
- }
34
- }
35
-
36
- /**
37
- * Initialize the API service
38
- * @param config - API service configuration
39
- * @param config.apiKey - API key for authentication
40
- * @param config.apiBaseUrl - Base URL for API endpoints (preferred)
41
- * @param config.continuousRecording - Whether continuous recording is enabled
42
- */
43
- public init(config: ApiServiceConfig) {
44
- const { apiBaseUrl: _apiBaseUrl, ...restConfig } = config
45
-
46
- const apiBaseUrl = _apiBaseUrl || MULTIPLAYER_BASE_API_URL
47
-
48
- this.config = {
49
- ...this.config,
50
- ...restConfig,
51
- apiBaseUrl,
52
- }
53
- }
54
-
55
- /**
56
- * Update the API service configuration
57
- * @param config - Partial configuration to update
58
- */
59
- public updateConfigs(config: Partial<ApiServiceConfig>) {
60
- const { apiBaseUrl: _apiBaseUrl, ...restConfig } = config
61
-
62
- const apiBaseUrl = _apiBaseUrl || MULTIPLAYER_BASE_API_URL
63
-
64
- this.config = {
65
- ...this.config,
66
- ...restConfig,
67
- apiBaseUrl,
68
- }
69
- }
70
-
71
- /**
72
- * Get the current API base URL
73
- * @returns The current API base URL
74
- */
75
- public getApiBaseUrl(): string {
76
- return this.config.apiBaseUrl || MULTIPLAYER_BASE_API_URL
77
- }
78
-
79
- /**
80
- * Start a new debug session
81
- * @param requestBody - Session start request data
82
- * @param signal - Optional AbortSignal for request cancellation
83
- */
84
- async startSession(
85
- requestBody: StartSessionRequest,
86
- signal?: AbortSignal,
87
- ): Promise<ISession> {
88
- return this.makeRequest(
89
- '/debug-sessions/start',
90
- 'POST',
91
- requestBody,
92
- signal,
93
- )
94
- }
95
-
96
- /**
97
- * Stop an active debug session
98
- * @param sessionId - ID of the session to stop
99
- * @param requestBody - Session stop request data
100
- */
101
- async stopSession(
102
- sessionId: string,
103
- requestBody: StopSessionRequest,
104
- ): Promise<any> {
105
- return this.makeRequest(
106
- `/debug-sessions/${sessionId}/stop`,
107
- 'PATCH',
108
- requestBody,
109
- )
110
- }
111
-
112
- /**
113
- * Cancel an active session
114
- * @param sessionId - ID of the session to cancel
115
- */
116
- async cancelSession(sessionId: string): Promise<any> {
117
- return this.makeRequest(
118
- `/debug-sessions/${sessionId}/cancel`,
119
- 'DELETE',
120
- )
121
- }
122
-
123
- /**
124
- * Start a new session
125
- * @param requestBody - Session start request data
126
- * @param signal - Optional AbortSignal for request cancellation
127
- */
128
- async startContinuousSession(
129
- requestBody: StartSessionRequest,
130
- signal?: AbortSignal,
131
- ): Promise<any> {
132
- return this.makeRequest(
133
- '/continuous-debug-sessions/start',
134
- 'POST',
135
- requestBody,
136
- signal,
137
- )
138
- }
139
-
140
- /**
141
- * Save a continuous session
142
- * @param sessionId - ID of the session to save
143
- * @param requestBody - Session save request data
144
- * @param signal - Optional AbortSignal for request cancellation
145
- */
146
- async saveContinuousSession(
147
- sessionId: string,
148
- requestBody: StartSessionRequest,
149
- signal?: AbortSignal,
150
- ): Promise<any> {
151
- return this.makeRequest(
152
- `/continuous-debug-sessions/${sessionId}/save`,
153
- 'POST',
154
- requestBody,
155
- signal,
156
- )
157
- }
158
-
159
- /**
160
- * Cancel an active debug session
161
- * @param sessionId - ID of the session to cancel
162
- */
163
- async stopContinuousSession(sessionId: string): Promise<any> {
164
- return this.makeRequest(
165
- `/continuous-debug-sessions/${sessionId}/cancel`,
166
- 'DELETE',
167
- )
168
- }
169
-
170
- /**
171
- * Check debug session should be started remotely
172
- */
173
- async checkRemoteSession(
174
- requestBody: StartSessionRequest,
175
- signal?: AbortSignal,
176
- ): Promise<{ state: 'START' | 'STOP' }> {
177
- return this.makeRequest(
178
- '/remote-debug-session/check',
179
- 'POST',
180
- requestBody,
181
- signal,
182
- )
183
- }
184
-
185
- /**
186
- * Make a request to the session API
187
- * @param path - API endpoint path (relative to the base URL)
188
- * @param method - HTTP method (GET, POST, PATCH, etc.)
189
- * @param body - request payload
190
- * @param signal - AbortSignal to set request's signal
191
- */
192
- private async makeRequest(
193
- path: string,
194
- method: string,
195
- body?: any,
196
- signal?: AbortSignal,
197
- ): Promise<any> {
198
- const url = `${this.config.apiBaseUrl}/v0/radar${path}`
199
- const params = {
200
- method,
201
- body: body ? JSON.stringify(body) : null,
202
- headers: {
203
- 'Content-Type': 'application/json',
204
- ...(this.config.apiKey && { 'X-Api-Key': this.config.apiKey }),
205
- },
206
- }
207
-
208
- try {
209
- const response = await fetch(url, {
210
- ...params,
211
- credentials: 'include',
212
- signal,
213
- })
214
-
215
- if (!response.ok) {
216
- throw new Error('Network response was not ok: ' + response.statusText)
217
- }
218
-
219
- if (response.status === 204) {
220
- return null
221
- }
222
-
223
- return await response.json()
224
- } catch (error: any) {
225
- if (error?.name === 'AbortError') {
226
- throw new Error('Request aborted')
227
- }
228
- }
229
- }
230
- }