├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── lerna.json ├── package-lock.json ├── package.json └── packages └── sidekick-agent-nodejs ├── README.md ├── __tests__ ├── config │ ├── data │ │ ├── captureframe │ │ │ └── data.js │ │ ├── capturelog │ │ │ └── data.js │ │ ├── event │ │ │ └── data.js │ │ ├── requestresponse │ │ │ ├── breakpoint-method.js │ │ │ ├── config │ │ │ │ └── data.js │ │ │ ├── error-method.js │ │ │ ├── logpoint │ │ │ │ └── data.js │ │ │ └── tracepoint │ │ │ │ └── data.js │ │ └── scriptstore │ │ │ └── ts │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── index.js.map │ │ │ └── index.ts │ └── utils │ │ └── port-utils.js ├── integration │ ├── config │ │ └── request.response.integration.test.js │ ├── error │ │ └── event.integration.test.js │ ├── generalevent │ │ └── general.event.integration.test.js │ ├── logpoint │ │ ├── event.integration.test.js │ │ └── request.response.integration.test.js │ ├── tags │ │ └── request.response.integration.test.js │ └── tracepoint │ │ ├── event.integration.test.js │ │ └── request.response.integration.test.js └── unit │ ├── script.store.test.js │ └── tracepoint │ └── capture.frame.converter.test.js ├── bootstrap └── index.js ├── jest.config.js ├── package-lock.json ├── package.json ├── scripts └── build.js ├── src ├── api │ ├── Api.ts │ ├── external │ │ ├── communication │ │ │ ├── BufferedCommunicationApi.ts │ │ │ ├── CommunicationApi.ts │ │ │ ├── CommunicationManager.ts │ │ │ └── broker │ │ │ │ ├── DebugBrokerApi.ts │ │ │ │ └── handler │ │ │ │ ├── BrokerHandler.ts │ │ │ │ ├── BrokerHandlerContainer.ts │ │ │ │ ├── event │ │ │ │ └── EventHandlerContainer.ts │ │ │ │ ├── request │ │ │ │ ├── RequestHandlerContainer.ts │ │ │ │ ├── config │ │ │ │ │ ├── AttachRequestHandler.ts │ │ │ │ │ ├── ConfigRequestHandler.ts │ │ │ │ │ ├── DetachRequestHandler.ts │ │ │ │ │ └── UpdateConfigRequestHandler.ts │ │ │ │ ├── logpoint │ │ │ │ │ ├── DisableLogPointRequestHandler.ts │ │ │ │ │ ├── EnableLogPointRequestHandler.ts │ │ │ │ │ ├── LogpointRequestHandler.ts │ │ │ │ │ ├── PutLogPointRequestHandler.ts │ │ │ │ │ ├── RemoveBatchLogPointRequestHandler.ts │ │ │ │ │ ├── RemoveLogPointRequestHandler.ts │ │ │ │ │ └── UpdateLogPointRequestHandler.ts │ │ │ │ ├── tag │ │ │ │ │ ├── DisableProbeTagRequestHandler.ts │ │ │ │ │ ├── EnableProbeTagRequestHandler.ts │ │ │ │ │ ├── RemoveProbeTagRequestHandler.ts │ │ │ │ │ └── TagRequestHandler.ts │ │ │ │ └── tracepoint │ │ │ │ │ ├── DisableTracePointRequestHandler.ts │ │ │ │ │ ├── EnableTracePointRequestHandler.ts │ │ │ │ │ ├── PutTracePointRequestHandler.ts │ │ │ │ │ ├── RemoveBatchTracePointRequestHandler.ts │ │ │ │ │ ├── RemoveTracePointRequestHandler.ts │ │ │ │ │ ├── TracepointRequestHandler.ts │ │ │ │ │ └── UpdateTracePointRequestHandler.ts │ │ │ │ └── response │ │ │ │ ├── ResponseHandlerContainer.ts │ │ │ │ ├── config │ │ │ │ ├── ConfigResponseHandler.ts │ │ │ │ └── GetConfigResponseHandler.ts │ │ │ │ ├── logpoint │ │ │ │ ├── FilterLogPointsResponseHandler.ts │ │ │ │ └── LogPointsResponseHandler.ts │ │ │ │ └── tracepoint │ │ │ │ ├── FilterTracePointsResponseHandler.ts │ │ │ │ └── TracePointsResponseHandler.ts │ │ ├── manager │ │ │ ├── ConfigManager.ts │ │ │ ├── LogpointManager.ts │ │ │ ├── TagManager.ts │ │ │ └── TracepointManager.ts │ │ └── source │ │ │ └── SourceMapResolverApi.ts │ ├── internal │ │ ├── debug │ │ │ ├── DebugApi.ts │ │ │ ├── action │ │ │ │ ├── ConditionAwareProbeAction.ts │ │ │ │ ├── ErrorRateLimitedProbeAction.ts │ │ │ │ ├── ExpiringProbeAction.ts │ │ │ │ ├── ProbeActionFactory.ts │ │ │ │ └── RateLimitedProbeAction.ts │ │ │ ├── converter │ │ │ │ └── CaptureFrameConverter.ts │ │ │ ├── error │ │ │ │ ├── ErrorStackAction.ts │ │ │ │ └── ErrorStackContext.ts │ │ │ ├── logpoint │ │ │ │ ├── LogPointAction.ts │ │ │ │ └── LogPointContext.ts │ │ │ ├── probe │ │ │ │ ├── CaptureProbeAction.ts │ │ │ │ ├── ProbeAction.ts │ │ │ │ └── ProbeContext.ts │ │ │ └── tracepoint │ │ │ │ ├── TracePointAction.ts │ │ │ │ └── TracePointContext.ts │ │ └── v8 │ │ │ └── V8inspectorApi.ts │ └── status │ │ └── ApiStatus.ts ├── application │ ├── Application.ts │ ├── ApplicationInfo.ts │ ├── ApplicationInfoProvider.ts │ ├── ConfigAwareApplicationInfoProvider.ts │ └── status │ │ ├── ApplicationStatusProvider.ts │ │ ├── logpoint │ │ └── ApplicationStatusLogPointProvider.ts │ │ └── tracepoint │ │ └── ApplicationStatusTracePointProvider.ts ├── config │ ├── ConfigMetadata.ts │ ├── ConfigNames.ts │ └── ConfigProvider.ts ├── constants │ └── index.ts ├── error │ ├── CodedError.ts │ ├── ConfigValidationError.ts │ ├── ProbeErrorCodes.ts │ ├── ProbeErrors.ts │ ├── logpoint │ │ └── LogPointErrorCodes.ts │ └── tracepoint │ │ └── TracePointErrorCodes.ts ├── handler │ ├── Handler.ts │ └── HandlerContainer.ts ├── index.ts ├── limit │ └── rate │ │ └── RateLimiter.ts ├── listener │ ├── Listener.ts │ ├── apistatus │ │ └── ApiStatusListener.ts │ ├── communication │ │ └── CommunicationApiListener.ts │ └── debug │ │ └── DebugApiListener.ts ├── logger │ └── index.ts ├── manager │ └── SidekickManager.ts ├── scheduler │ ├── Scheduler.ts │ └── task │ │ ├── DisabledErrorCollectionEnableTask.ts │ │ ├── ExpiredProbeCleanTask.ts │ │ ├── GetConfigTask.ts │ │ ├── SendStatusTask.ts │ │ └── Task.ts ├── store │ ├── probe │ │ └── ProbeStore.ts │ ├── queue │ │ └── TaskExecutorQueue.ts │ └── script │ │ └── ScriptStore.ts ├── trace │ ├── TraceInfoResolver.ts │ ├── TraceInfoSupport.ts │ ├── opentelemetry │ │ └── OpenTelemetryTraceInfoResolver.ts │ └── thundra │ │ └── ThundraTraceInfoResolver.ts ├── types │ └── index.ts └── utils │ ├── AstValidator.ts │ ├── CaptureUtils.ts │ ├── CommunicationUtils.ts │ ├── ConfigUtils.ts │ ├── CryptoUtils.ts │ ├── FileUtils.ts │ ├── MustacheExpressionUtils.ts │ ├── OsUtils.ts │ ├── PathUtils.ts │ ├── ProbeUtils.ts │ ├── PropertyAccessClassificationUtils.ts │ ├── ScriptUtils.ts │ ├── TypeCastUtils.ts │ ├── UrlUtils.ts │ ├── UuidUtils.ts │ └── VersionUtils.ts └── tsconfig.json /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version_scale: 7 | type: choice 8 | description: Release Scale 9 | options: 10 | - patch 11 | - minor 12 | - major 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | token: ${{ secrets.SIDEKICK_DEVOPS_GITHUB_ACCESS_TOKEN }} 21 | - name: Configure Git User 22 | run: | 23 | git config --global user.email "action@github.com" 24 | git config --global user.name "GitHub Action" 25 | 26 | - name: Use Node.js 14.x 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: "14.x" 30 | registry-url: https://registry.npmjs.org 31 | 32 | - name: NPM Install 33 | run: npm install 34 | 35 | - name: NPM Build 36 | run: npm run clean-build:all 37 | 38 | - name: NPM Publish 39 | run: npm run release 40 | env: 41 | RELEASE_SCALE: ${{ github.event.inputs.version_scale }} 42 | NODE_AUTH_TOKEN: ${{ secrets.SIDEKICK_NPM_API_KEY }} 43 | GITHUB_TOKEN: ${{ secrets.SIDEKICK_DEVOPS_GITHUB_ACCESS_TOKEN }} 44 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Collect Workflow Telemetry 13 | uses: runforesight/foresight-workflow-kit-action@v1 14 | with: 15 | api_key: ${{ secrets.FORESIGHT_API_KEY }} 16 | - uses: actions/checkout@v3 17 | - name: Use Node.js 14.x 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: "14.x" 21 | - name: NPM Install 22 | run: npm install 23 | - name: NPM Test 24 | run: npm run test 25 | env: 26 | JEST_JUNIT_OUTPUT_DIR: "./report" 27 | - name: Analyze Test and/or Coverage Results 28 | uses: runforesight/foresight-test-kit-action@v1 29 | if: always() 30 | with: 31 | api_key: ${{ secrets.FORESIGHT_API_KEY }} 32 | test_format: JUNIT 33 | test_framework: JEST 34 | test_path: packages/sidekick-agent-nodejs/report/junit.xml 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | report 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ./packages/sidekick-agent-nodejs/README.md -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "independent" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@runsidekick/sidekick-agent-nodejs-root", 3 | "version": "1.0.0", 4 | "description": "Sidekick node.js agent", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "lerna exec npm run test", 8 | "postinstall": "lerna bootstrap --hoist --ignore-scripts", 9 | "clean:all": "lerna run --parallel clean", 10 | "build:all": "lerna exec npm run build", 11 | "clean-build:all": "npm-run-all -p clean:all -s build:all", 12 | "prerelease": "lerna version ${RELEASE_SCALE} --no-push --exact --yes", 13 | "release": "lerna publish from-package --ignore-scripts --no-verify-access --yes", 14 | "postrelease": "git push --follow-tags origin" 15 | }, 16 | "devDependencies": { 17 | "lerna": "^4.0.0", 18 | "npm-run-all": "^4.1.5", 19 | "rimraf": "^3.0.1" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/runsidekick/sidekick-agent-nodejs.git" 24 | }, 25 | "keywords": [], 26 | "author": "Sidekick Team", 27 | "license": "AGPL", 28 | "bugs": { 29 | "url": "https://github.com/runsidekick/sidekick-agent-nodejs/issues" 30 | }, 31 | "publishConfig": { 32 | "access": "public", 33 | "registry": "https://registry.npmjs.org" 34 | }, 35 | "homepage": "https://github.com/runsidekick/sidekick-agent-nodejs#readme" 36 | } 37 | -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/config/data/event/data.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | 3 | const ApplicationStatusEventSchema = Joi.object({ 4 | id: Joi.string(), 5 | name: Joi.string().valid('ApplicationStatusEvent'), 6 | sendAck: Joi.boolean(), 7 | type: Joi.string().valid('Event'), 8 | client: Joi.string().optional(), 9 | application: Joi.object({ 10 | name: Joi.string(), 11 | hostName: Joi.string(), 12 | instanceId: '2336:d711b464-b09f-4c8d-91bd-4dbd956a891d@batuhanvm', 13 | runtime: Joi.string().valid('nodejs'), 14 | stage: Joi.string(), 15 | customTags: Joi.array(), 16 | version: Joi.string(), 17 | tracePoints: Joi.object(), 18 | }), 19 | time: Joi.number(), 20 | hostName: Joi.string(), 21 | applicationName: Joi.string(), 22 | applicationInstanceId: Joi.string(), 23 | }) 24 | 25 | module.exports = { 26 | ApplicationStatusEventSchema, 27 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/config/data/requestresponse/breakpoint-method.js: -------------------------------------------------------------------------------- 1 | const BreakpointMethod = (param) => { 2 | const field1 = param; 3 | const filed2 = 6; 4 | const field3 = { 5 | field4: 'dada' 6 | } 7 | 8 | return field1; 9 | } 10 | 11 | const CallerBreakpointMethod = (param) => { 12 | const callerField = 'CallerBreakpointMethod'; 13 | return BreakpointMethod(param); 14 | } 15 | 16 | module.exports = { 17 | BreakpointMethod, 18 | CallerBreakpointMethod, 19 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/config/data/requestresponse/config/data.js: -------------------------------------------------------------------------------- 1 | const GetConfigResponse = { 2 | id: '1', 3 | name: 'GetConfigResponse', 4 | type: 'Response', 5 | client: 'test', 6 | config: { 7 | maxProperties: 11, 8 | } 9 | } 10 | 11 | const UpdateConfigRequest = { 12 | id: '2', 13 | name: 'UpdateConfigRequest', 14 | type: 'Request', 15 | client: 'test', 16 | config: { 17 | maxProperties: 20, 18 | } 19 | } 20 | 21 | const AttachRequest = { 22 | id: '3', 23 | name: 'AttachRequest', 24 | type: 'Request', 25 | client: 'test', 26 | } 27 | 28 | const DetachRequest = { 29 | id: '4', 30 | name: 'DetachRequest', 31 | type: 'Request', 32 | client: 'test', 33 | } 34 | 35 | module.exports = { 36 | GetConfigResponse, 37 | UpdateConfigRequest, 38 | AttachRequest, 39 | DetachRequest, 40 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/config/data/requestresponse/error-method.js: -------------------------------------------------------------------------------- 1 | const ErrorMethod = (param) => { 2 | const field1 = param; 3 | throw new Error('Error from test.') 4 | } 5 | 6 | module.exports = { 7 | ErrorMethod, 8 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/config/data/requestresponse/logpoint/data.js: -------------------------------------------------------------------------------- 1 | const PutLogPointRequest = { 2 | id: '1', 3 | name: 'PutLogPointRequest', 4 | type: 'Request', 5 | client: 'test', 6 | logPointId: '18', 7 | fileName: 'breakpoint-method.js', 8 | lineNo: 4, 9 | fileHash: 'hash', 10 | expireCount: 5, 11 | action: 'Logpoint', 12 | logExpression: "Hello {{field1.item1}}", 13 | logLevel: 'INFO', 14 | stdoutEnabled: true, 15 | }; 16 | 17 | const EnableLogPointRequest = { 18 | id: '2', 19 | name: 'EnableLogPointRequest', 20 | type: 'Request', 21 | client: 'test', 22 | logPointId: '18', 23 | }; 24 | 25 | const DisableLogPointRequest = { 26 | id: '3', 27 | name: 'DisableLogPointRequest', 28 | type: 'Request', 29 | client: 'test', 30 | logPointId: '18', 31 | }; 32 | 33 | const UpdateLogPointRequest = { 34 | id: '4', 35 | name: 'UpdateLogPointRequest', 36 | type: 'Request', 37 | client: 'test', 38 | logPointId: '18', 39 | conditionExpression: 'a == "a"', 40 | expireSecs: 10000, 41 | expireCount: 3, 42 | }; 43 | 44 | const EnableProbeTagRequest = { 45 | id: '5', 46 | name: 'EnableProbeTagRequest', 47 | type: 'Request', 48 | tag: 'tag1' 49 | } 50 | 51 | const DisableProbeTagRequest = { 52 | id: '6', 53 | name: 'DisableProbeTagRequest', 54 | type: 'Request', 55 | tag: 'tag1' 56 | } 57 | 58 | module.exports = { 59 | PutLogPointRequest, 60 | EnableLogPointRequest, 61 | DisableLogPointRequest, 62 | UpdateLogPointRequest, 63 | EnableProbeTagRequest, 64 | DisableProbeTagRequest, 65 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/config/data/requestresponse/tracepoint/data.js: -------------------------------------------------------------------------------- 1 | const PutTracePointRequest = { 2 | id: '1', 3 | name: 'PutTracePointRequest', 4 | type: 'Request', 5 | client: 'test', 6 | tracePointId: '18', 7 | fileName:'breakpoint-method.js', 8 | lineNo: 4, 9 | fileHash: 'hash', 10 | expireCount: 3, 11 | action: 'Tracepoint', 12 | }; 13 | 14 | const EnableTracePointRequest = { 15 | id: '2', 16 | name: 'EnableTracePointRequest', 17 | type: 'Request', 18 | client: 'test', 19 | tracePointId: '18', 20 | }; 21 | 22 | const DisableTracePointRequest = { 23 | id: '3', 24 | name: 'DisableTracePointRequest', 25 | type: 'Request', 26 | client: 'test', 27 | tracePointId: '18', 28 | }; 29 | 30 | const UpdateTracePointRequest = { 31 | id: '4', 32 | name: 'UpdateTracePointRequest', 33 | type: 'Request', 34 | client: 'test', 35 | tracePointId: '18', 36 | conditionExpression: 'a == "a"', 37 | expireSecs: 10000, 38 | expireCount: 3, 39 | }; 40 | 41 | const EnableProbeTagRequest = { 42 | id: '5', 43 | name: 'EnableProbeTagRequest', 44 | type: 'Request', 45 | tag: 'tag1' 46 | } 47 | 48 | const DisableProbeTagRequest = { 49 | id: '6', 50 | name: 'DisableProbeTagRequest', 51 | type: 'Request', 52 | tag: 'tag1' 53 | } 54 | 55 | module.exports = { 56 | PutTracePointRequest, 57 | EnableTracePointRequest, 58 | DisableTracePointRequest, 59 | UpdateTracePointRequest, 60 | EnableProbeTagRequest, 61 | DisableProbeTagRequest, 62 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/config/data/scriptstore/ts/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare type FunctionAParam = { 2 | a: number; 3 | b: number; 4 | }; 5 | export declare type FunctionAResult = { 6 | result: number; 7 | }; 8 | export declare const functionA: (param: FunctionAParam) => FunctionAResult; 9 | -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/config/data/scriptstore/ts/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.functionA = void 0; 4 | const functionA = (param) => { 5 | const a = param.a; 6 | const b = param.b; 7 | const result = a * b; 8 | return { 9 | result, 10 | }; 11 | }; 12 | exports.functionA = functionA; 13 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/config/data/scriptstore/ts/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../ts/index.ts"],"names":[],"mappings":";;;AASO,MAAM,SAAS,GAAG,CAAC,KAAqB,EAAmB,EAAE;IAChE,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IAClB,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IAElB,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC;IACrB,OAAO;QACH,MAAM;KACU,CAAC;AACzB,CAAC,CAAA;AARY,QAAA,SAAS,aAQrB"} -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/config/data/scriptstore/ts/index.ts: -------------------------------------------------------------------------------- 1 | export type FunctionAParam = { 2 | a: number; 3 | b: number; 4 | }; 5 | 6 | export type FunctionAResult = { 7 | result: number; 8 | }; 9 | 10 | export const functionA = (param: FunctionAParam): FunctionAResult => { 11 | const a = param.a; 12 | const b = param.b; 13 | 14 | const result = a * b; 15 | return { 16 | result, 17 | } as FunctionAResult; 18 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/config/utils/port-utils.js: -------------------------------------------------------------------------------- 1 | const portfinder = require('portfinder'); 2 | 3 | const maximum = 100; 4 | const minimum = 10; 5 | 6 | const getRandomPort = async () => { 7 | const randomNumber = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum; 8 | const port = await portfinder.getPortPromise(); 9 | 10 | return port + randomNumber; 11 | } 12 | 13 | module.exports = { 14 | getRandomPort 15 | } 16 | -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/integration/error/event.integration.test.js: -------------------------------------------------------------------------------- 1 | const { WebSocketServer } = require('ws'); 2 | const { getRandomPort } = require('../../config/utils/port-utils'); 3 | 4 | const { 5 | start, 6 | stop, 7 | } = require('../../../dist'); 8 | 9 | const { ErrorMethod } = require('../../config/data/requestresponse/error-method'); 10 | 11 | describe('Error Event Test', function () { 12 | jest.setTimeout(30000) 13 | 14 | let wss; 15 | let wsClient; 16 | let sidekick; 17 | 18 | beforeAll(async () => { 19 | const port = await getRandomPort(); 20 | wss = new WebSocketServer({ port }); 21 | wss.on('connection', function connection(ws) { 22 | wsClient = ws; 23 | }); 24 | 25 | sidekick = await start({ 26 | apiKey: 'foo', 27 | brokerHost: 'ws://localhost', 28 | brokerPort: port, 29 | inMinute: 1, 30 | errorCollectionEnable: true, 31 | errorCollectionEnableCaptureFrame: true, 32 | }); 33 | }); 34 | 35 | afterAll(async () => { 36 | if (wsClient) { 37 | wsClient = null; 38 | } 39 | 40 | stop(); 41 | if (wss) { 42 | wss.close(); 43 | } 44 | }); 45 | 46 | it('Check Error Event', (done) => { 47 | const wsClientMessageHandler = (data) => { 48 | const validateErrorEvent = (message) => { 49 | wsClient.removeListener('message', wsClientMessageHandler); 50 | expect(message.error).toBeTruthy(); 51 | expect(message.error.message).toBe('Error: Error from test.'); 52 | expect(message.frames).toBeTruthy(); 53 | expect(message.frames.length).toBe(2); 54 | done(); 55 | } 56 | 57 | try { 58 | const message = JSON.parse(data.toString()); 59 | if (message.name === 'FilterLogPointsRequest') { 60 | try { 61 | ErrorMethod(); 62 | } catch (error) { } 63 | } 64 | 65 | if (message.name === 'ErrorStackSnapshotEvent') { 66 | validateErrorEvent(message); 67 | } 68 | } catch (error) { 69 | done(error); 70 | } 71 | } 72 | 73 | wsClient.on('message', wsClientMessageHandler); 74 | }); 75 | }); -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/integration/generalevent/general.event.integration.test.js: -------------------------------------------------------------------------------- 1 | const { WebSocketServer } = require('ws'); 2 | const { getRandomPort } = require('../../config/utils/port-utils'); 3 | 4 | const { 5 | start, 6 | stop, 7 | } = require('../../../dist'); 8 | 9 | const { BreakpointMethod } = require('../../config/data/requestresponse/breakpoint-method'); 10 | const { PutTracePointRequest } = require('../../config/data/requestresponse/tracepoint/data'); 11 | 12 | const { ApplicationStatusEventSchema } = require('../../config/data/event/data'); 13 | 14 | const ProbeUtils = require('../../../dist/utils/ProbeUtils').default; 15 | 16 | describe('General Event Test', function () { 17 | jest.setTimeout(30000) 18 | 19 | let wss; 20 | let wsClient; 21 | let sidekick; 22 | 23 | beforeAll(async () => { 24 | const port = await getRandomPort(); 25 | wss = new WebSocketServer({ port }); 26 | wss.on('connection', function connection(ws) { 27 | wsClient = ws; 28 | }); 29 | 30 | sidekick = await start({ 31 | apiKey: 'foo', 32 | brokerHost: 'ws://localhost', 33 | brokerPort: port, 34 | hashCheckDisable: true, 35 | inMinute: 1 36 | }); 37 | 38 | tracePointId = ProbeUtils.getProbeId({ 39 | ...PutTracePointRequest, 40 | id: PutTracePointRequest.tracePointId, 41 | type: 'Tracepoint', 42 | }); 43 | }); 44 | 45 | afterAll(async () => { 46 | if (wsClient) { 47 | wsClient = null; 48 | } 49 | 50 | stop(); 51 | if (wss) { 52 | wss.close(); 53 | } 54 | }); 55 | 56 | it('Check Application Status Event', (done) => { 57 | const validateApplicationStatusEvent = (message) => { 58 | const breakpoint = sidekick.debugApi.get(tracePointId); 59 | ApplicationStatusEventSchema.validate(message); 60 | wsClient.removeListener('message', wsClientMessageHandler); 61 | sidekick.debugApi.delete(breakpoint); 62 | done(); 63 | } 64 | 65 | const wsClientMessageHandler = (data) => { 66 | try { 67 | const message = JSON.parse(data.toString()); 68 | if (message.name === 'ApplicationStatusEvent') { 69 | validateApplicationStatusEvent(message); 70 | } 71 | } catch (error) { 72 | done(error); 73 | } 74 | } 75 | 76 | wsClient.on('message', wsClientMessageHandler); 77 | wsClient.send(JSON.stringify(PutTracePointRequest)); 78 | }); 79 | 80 | it('Check Rate Limit Status Event', (done) => { 81 | const validateRateLimitEvent = (message) => { 82 | const breakpoint = sidekick.debugApi.get(tracePointId); 83 | ApplicationStatusEventSchema.validate(message); 84 | wsClient.removeListener('message', wsClientMessageHandler); 85 | sidekick.debugApi.delete(breakpoint); 86 | done(); 87 | } 88 | 89 | const wsClientMessageHandler = (data) => { 90 | try { 91 | const message = JSON.parse(data.toString()); 92 | if (message.name === 'PutTracePointResponse') { 93 | BreakpointMethod(); 94 | } 95 | 96 | if (message.name === 'ProbeRateLimitEvent') { 97 | validateRateLimitEvent(message); 98 | } 99 | } catch (error) { 100 | done(error); 101 | } 102 | } 103 | 104 | wsClient.on('message', wsClientMessageHandler); 105 | wsClient.send(JSON.stringify(PutTracePointRequest)); 106 | }); 107 | }); -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/integration/logpoint/event.integration.test.js: -------------------------------------------------------------------------------- 1 | const { WebSocketServer } = require('ws'); 2 | const { getRandomPort } = require('../../config/utils/port-utils'); 3 | 4 | const { 5 | start, 6 | stop, 7 | } = require('../../../dist'); 8 | 9 | const { BreakpointMethod } = require('../../config/data/requestresponse/breakpoint-method'); 10 | const { PutLogPointRequest } = require('../../config/data/requestresponse/logpoint/data'); 11 | 12 | const ProbeUtils = require('../../../dist/utils/ProbeUtils').default; 13 | 14 | describe('Logpoint Event Test', function () { 15 | jest.setTimeout(30000) 16 | 17 | let wss; 18 | let wsClient; 19 | let sidekick; 20 | let logPointId; 21 | 22 | beforeAll(async () => { 23 | const port = await getRandomPort(); 24 | wss = new WebSocketServer({ port }); 25 | wss.on('connection', function connection(ws) { 26 | wsClient = ws; 27 | }); 28 | 29 | sidekick = await start({ 30 | apiKey: 'foo', 31 | brokerHost: 'ws://localhost', 32 | brokerPort: port, 33 | hashCheckDisable: true, 34 | inMinute: 1 35 | }); 36 | 37 | logPointId = ProbeUtils.getProbeId({ 38 | ...PutLogPointRequest, 39 | id: PutLogPointRequest.logPointId, 40 | type: 'Logpoint', 41 | }); 42 | }); 43 | 44 | afterAll(async () => { 45 | if (wsClient) { 46 | wsClient = null; 47 | } 48 | 49 | stop(); 50 | if (wss) { 51 | wss.close(); 52 | } 53 | }); 54 | 55 | 56 | it('Check logpoint Event', (done) => { 57 | const validateLogpointEvent = (message) => { 58 | const breakpoint = sidekick.debugApi.get(logPointId); 59 | wsClient.removeListener('message', wsClientMessageHandler); 60 | expect(message.logPointId).toBe(logPointId.replace('Logpoint:', '')); 61 | expect(message.fileName).toBe(PutLogPointRequest.fileName); 62 | expect(message.lineNo).toBe(PutLogPointRequest.lineNo); 63 | expect(message.methodName).toBe('BreakpointMethod'); 64 | expect(message.logMessage).toBe('Hello Thundra!'); 65 | sidekick.debugApi.delete(breakpoint); 66 | done(); 67 | 68 | } 69 | 70 | const wsClientMessageHandler = (data) => { 71 | try { 72 | const message = JSON.parse(data.toString()); 73 | if (message.name === 'PutLogPointResponse') { 74 | BreakpointMethod({ 75 | item1: 'Thundra!' 76 | }); 77 | } 78 | 79 | if (message.name === 'LogPointEvent') { 80 | validateLogpointEvent(message); 81 | } 82 | } catch (error) { 83 | done(error); 84 | } 85 | } 86 | 87 | wsClient.on('message', wsClientMessageHandler); 88 | wsClient.send(JSON.stringify(PutLogPointRequest)); 89 | }); 90 | }); -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/integration/tracepoint/event.integration.test.js: -------------------------------------------------------------------------------- 1 | const { WebSocketServer } = require('ws'); 2 | const { getRandomPort } = require('../../config/utils/port-utils'); 3 | 4 | const { 5 | start, 6 | stop, 7 | } = require('../../../dist'); 8 | 9 | const { BreakpointMethod } = require('../../config/data/requestresponse/breakpoint-method'); 10 | 11 | const { PutTracePointRequest } = require('../../config/data/requestresponse/tracepoint/data'); 12 | 13 | const ProbeUtils = require('../../../dist/utils/ProbeUtils').default; 14 | 15 | describe('Tracepoint Snapshot Event Test', function () { 16 | jest.setTimeout(30000) 17 | 18 | let wss; 19 | let wsClient; 20 | let sidekick; 21 | 22 | beforeAll(async () => { 23 | const port = await getRandomPort(); 24 | wss = new WebSocketServer({ port }); 25 | wss.on('connection', function connection(ws) { 26 | wsClient = ws; 27 | }); 28 | 29 | sidekick = await start({ 30 | apiKey: 'foo', 31 | brokerHost: 'ws://localhost', 32 | brokerPort: port, 33 | hashCheckDisable: true, 34 | inMinute: 1 35 | }); 36 | 37 | tracePointId = ProbeUtils.getProbeId({ 38 | ...PutTracePointRequest, 39 | id: PutTracePointRequest.tracePointId, 40 | type: 'Tracepoint', 41 | }); 42 | }); 43 | 44 | afterAll(async () => { 45 | if (wsClient) { 46 | wsClient = null; 47 | } 48 | 49 | stop(); 50 | if (wss) { 51 | wss.close(); 52 | } 53 | }); 54 | 55 | it('Check Snapshot Event', (done) => { 56 | const validateSnapshotEvent = (message) => { 57 | const breakpoint = sidekick.debugApi.get(tracePointId); 58 | wsClient.removeListener('message', wsClientMessageHandler); 59 | expect(message.tracePointId).toBe(tracePointId.replace('Tracepoint:', '')); 60 | expect(message.fileName).toBe(PutTracePointRequest.fileName); 61 | expect(message.lineNo).toBe(PutTracePointRequest.lineNo); 62 | expect(message.methodName).toBe('BreakpointMethod'); 63 | expect(message.frames).toBeTruthy(); 64 | sidekick.debugApi.delete(breakpoint); 65 | done(); 66 | } 67 | 68 | const wsClientMessageHandler = (data) => { 69 | try { 70 | const message = JSON.parse(data.toString()); 71 | if (message.name === 'PutTracePointResponse') { 72 | BreakpointMethod(); 73 | } 74 | 75 | if (message.name === 'TracePointSnapshotEvent') { 76 | validateSnapshotEvent(message); 77 | } 78 | } catch (error) { 79 | done(error); 80 | } 81 | } 82 | 83 | wsClient.on('message', wsClientMessageHandler); 84 | wsClient.send(JSON.stringify(PutTracePointRequest)); 85 | }); 86 | }); -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/unit/script.store.test.js: -------------------------------------------------------------------------------- 1 | const ScriptStore = require('../../dist/store/script/ScriptStore').default; 2 | const ProbeUtils = require('../../dist/utils/ProbeUtils').default; 3 | const CryptoUtils = require('../../dist/utils/CryptoUtils').default; 4 | const ConfigProvider = require('../../dist/config/ConfigProvider').default; 5 | const ConfigMetadata = require('../../dist/config/ConfigMetadata').default; 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | 9 | describe('Script Store Test', function () { 10 | let scriptStore; 11 | const basefile = '__tests__/config/data/scriptstore/ts/index'; 12 | const sourceFile = `${basefile}.ts` 13 | const generatedFile = `${basefile}.js` 14 | const filenamePrefix = 'https://api.github.com/repos/abc/aaa/contents' 15 | const sourceFilePath = path.join(__dirname, `../../${generatedFile}`); 16 | const scriptId = 1; 17 | const scriptUrl = `file://${sourceFilePath}`; 18 | const filename = `${filenamePrefix}/${sourceFile}?ref=f889d36daf57d5f96f76eed1ad2bb2ec0ecd71a7`; 19 | let generatedFileContent; 20 | 21 | beforeAll(async () => { 22 | ConfigProvider.init({ 23 | config: { 24 | apiKey: 'foo', 25 | }, 26 | configMetaData: ConfigMetadata 27 | }); 28 | 29 | scriptStore = new ScriptStore(); 30 | }); 31 | 32 | beforeEach(async () => { 33 | generatedFileContent = await fs.promises.readFile(sourceFilePath); 34 | scriptStore.set({ 35 | scriptParams: { 36 | scriptId, 37 | url: sourceFilePath, 38 | }, 39 | scriptRawUrl: scriptUrl, 40 | scriptSource: generatedFileContent.toString(), 41 | }); 42 | }); 43 | 44 | it('Check Script Url', async () => { 45 | const _scriptUrl = scriptStore.getScriptRawFilename(scriptId); 46 | expect(_scriptUrl).toBe(scriptUrl); 47 | }); 48 | 49 | it('Check Script Wrapper', async () => { 50 | const _filename = ProbeUtils.extractFileNameFrom(filename, 'contents') 51 | const scriptWraper = scriptStore.getByScriptUrl(_filename); 52 | expect(scriptWraper).not.toBeNull(); 53 | }); 54 | 55 | it('Check Generated Position', async () => { 56 | const _filename = ProbeUtils.extractFileNameFrom(filename, 'contents') 57 | const scriptWraper = scriptStore.getByScriptUrl(_filename); 58 | const generatedPosition = scriptWraper.generatedPositionFor(_filename, 14); 59 | expect(generatedPosition.line).toBe(7); 60 | }); 61 | 62 | it('Check Hash', async () => { 63 | const _filename = ProbeUtils.extractFileNameFrom(filename, 'contents') 64 | const scriptWraper = scriptStore.getByScriptUrl(_filename); 65 | const result = scriptWraper.isCompatableWithFile(CryptoUtils.generateSHA256(generatedFileContent)); 66 | expect(result).toBe(true); 67 | }) 68 | }); -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/__tests__/unit/tracepoint/capture.frame.converter.test.js: -------------------------------------------------------------------------------- 1 | const ConfigProvider = require('../../../dist/config/ConfigProvider').default; 2 | const ConfigMetadata = require('../../../dist/config/ConfigMetadata').default; 3 | const CaptureFrameConverter = require('../../../dist/api/internal/debug/converter/CaptureFrameConverter').default; 4 | const { 5 | Frames, 6 | CovertedFramesSchema, 7 | } = require('../../config/data/captureframe/data'); 8 | 9 | describe('Capture Frame Converter Test', function () { 10 | let captureFrameConverter; 11 | 12 | beforeAll(async () => { 13 | captureFrameConverter = new CaptureFrameConverter({ 14 | maxExpandFrames: 1, 15 | maxFrames: 3, 16 | minified: false, 17 | maxParseDepth: 3, 18 | maxProperties: 20, 19 | propertyAccessClassification: 'ENUMERABLE-OWN' 20 | }); 21 | 22 | ConfigProvider.init({ 23 | config: { 24 | apiKey: 'foo', 25 | }, 26 | configMetaData: ConfigMetadata 27 | }); 28 | }); 29 | 30 | it('Check Converted Json Schema', async () => { 31 | const convertedFrames = captureFrameConverter.convert(Frames); 32 | 33 | const result = CovertedFramesSchema.validate(convertedFrames); 34 | expect(result.error).toBeUndefined(); 35 | }); 36 | }); -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/bootstrap/index.js: -------------------------------------------------------------------------------- 1 | const sidekick = require('@runsidekick/sidekick-agent-nodejs'); 2 | 3 | try { 4 | sidekick.start().then(() => { 5 | }).catch((error) => { 6 | console.info('An error occured while Sidekick auto start.', error); 7 | }) 8 | } catch (error) { 9 | console.error('An error occured while Sidekick auto start.', error); 10 | } 11 | -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/jest.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | testPathIgnorePatterns: ['./__tests__/config'], 3 | name: 'sidekick-agent-nodej', 4 | verbose: true, 5 | }; 6 | 7 | module.exports = config; 8 | -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@runsidekick/sidekick-agent-nodejs", 3 | "version": "0.0.19", 4 | "description": "Sidekick node.js agent", 5 | "author": "Sidekick Team", 6 | "homepage": "https://github.com/runsidekick/sidekick-agent-nodejs/tree/main/packages/sidekick-agent-nodejs#readme", 7 | "license": "AGPL", 8 | "main": "dist/index.js", 9 | "files": [ 10 | "dist/" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/runsidekick/sidekick-agent-nodejs.git" 15 | }, 16 | "scripts": { 17 | "test": "jest --reporters=default --reporters=jest-junit --forceExit", 18 | "pretest": "npm run build:tsc", 19 | "lint": "eslint src --ext .ts", 20 | "clean": "rimraf dist coverage tsconfig.tsbuildinfo", 21 | "prebuild": "npm run build:types", 22 | "build": "node scripts/build.js", 23 | "postbuild": "npm run copy:static-files", 24 | "build:tsc": "tsc", 25 | "build:types": "tsc --emitDeclarationOnly --declaration", 26 | "copy:static-files": "copyfiles -u 1 \"bootstrap/*\" dist/bootstrap" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/runsidekick/sidekick-agent-nodejs/issues" 30 | }, 31 | "devDependencies": { 32 | "@types/acorn": "^4.0.6", 33 | "@types/estree": "^0.0.50", 34 | "@types/node": "^18.11.18", 35 | "@types/uuid": "^8.3.4", 36 | "@types/ws": "^8.2.2", 37 | "@typescript-eslint/eslint-plugin": "^4.27.0", 38 | "@typescript-eslint/parser": "^4.27.0", 39 | "copyfiles": "^2.4.1", 40 | "esbuild": "^0.14.23", 41 | "eslint": "^7.29.0", 42 | "jest": "^27.5.1", 43 | "jest-junit": "^14.0.1", 44 | "joi": "^17.6.0", 45 | "portfinder": "^1.0.28", 46 | "typescript": "^4.4.4" 47 | }, 48 | "dependencies": { 49 | "acorn": "^8.7.0", 50 | "app-root-path": "^3.0.0", 51 | "atob": "^2.1.2", 52 | "decode-uri-component": "^0.2.0", 53 | "lru-cache": "^7.7.1", 54 | "mustache": "^4.2.0", 55 | "npmlog": "^6.0.1", 56 | "p-queue": "^6.6.2", 57 | "source-map": "^0.6.1", 58 | "uuid": "^8.3.2", 59 | "ws": "^8.5.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/scripts/build.js: -------------------------------------------------------------------------------- 1 | const { build } = require("esbuild"); 2 | 3 | const makeAllPackagesExternalPlugin = { 4 | name: 'make-all-packages-external', 5 | setup(build) { 6 | let filter = /^[^.\/]|^\.[^.\/]|^\.\.[^\/]/ // Must not start with "/" or "./" or "../" 7 | build.onResolve({ filter }, args => ({ path: args.path, external: true })) 8 | }, 9 | } 10 | 11 | build({ 12 | bundle: true, 13 | minify: true, 14 | target: "node14", 15 | platform: "node", 16 | entryPoints: ["./src/index.ts"], 17 | outfile: "./dist/index.js", 18 | plugins: [ 19 | makeAllPackagesExternalPlugin, 20 | ], 21 | }); -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/Api.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "events"; 2 | 3 | export interface Api {} 4 | 5 | export interface Listenable extends EventEmitter, Api { } 6 | 7 | export const ConnectableApiEventNames = { 8 | OPEN: 'OPEN', 9 | ERROR: 'ERROR', 10 | CLOSE: 'CLOSE', 11 | } 12 | 13 | export interface ConnectableApi extends Listenable { 14 | connect(): void; 15 | reconnect(): void; 16 | close(): void; 17 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/BufferedCommunicationApi.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "events"; 2 | import { CommunicationApiData } from "../../../types"; 3 | import CommunicationApi, { CommunicationApiEventNames } from './CommunicationApi'; 4 | import TaskExecutorQueue, { TaskExecutorPQueue } from '../../../store/queue/TaskExecutorQueue'; 5 | import Logger from '../../../logger'; 6 | import ConfigProvider from "../../../config/ConfigProvider"; 7 | import { ConfigNames } from "../../../config/ConfigNames"; 8 | 9 | export default interface BufferedCommunicationApi extends CommunicationApi { 10 | silent(): void; 11 | unsilent(): void; 12 | } 13 | 14 | export class DefaultBufferedCommunicationApi extends EventEmitter implements BufferedCommunicationApi { 15 | protected taskExecutorQueue: TaskExecutorQueue; 16 | protected communicationApi: CommunicationApi; 17 | protected connected: boolean; 18 | 19 | constructor(communicationApi: CommunicationApi) { 20 | super(); 21 | 22 | this.communicationApi = communicationApi; 23 | this.taskExecutorQueue = new TaskExecutorPQueue(ConfigProvider.get(ConfigNames.taskExecutionQueue.concurrency)); 24 | 25 | this.communicationApi.on(CommunicationApiEventNames.OPEN, (data) => this._emit(CommunicationApiEventNames.OPEN, data)); 26 | this.communicationApi.on(CommunicationApiEventNames.ERROR, (data) => this._emit(CommunicationApiEventNames.ERROR, data)); 27 | this.communicationApi.on(CommunicationApiEventNames.CLOSE, (data) => this._emit(CommunicationApiEventNames.CLOSE, data)); 28 | this.communicationApi.on(CommunicationApiEventNames.MESSAGE, (data) => this.onMessage(data)); 29 | 30 | this.unsilent(); 31 | } 32 | 33 | connect(): void { 34 | this.communicationApi.connect(); 35 | this.connected = true; 36 | } 37 | 38 | reconnect(): void { 39 | this.connected = false; 40 | this.communicationApi.reconnect(); 41 | this.connected = true; 42 | } 43 | 44 | close(): void { 45 | this.connected = false; 46 | 47 | this.communicationApi.removeAllListeners(CommunicationApiEventNames.OPEN); 48 | this.communicationApi.removeAllListeners(CommunicationApiEventNames.ERROR); 49 | this.communicationApi.removeAllListeners(CommunicationApiEventNames.CLOSE); 50 | this.communicationApi.removeAllListeners(CommunicationApiEventNames.MESSAGE); 51 | 52 | this.communicationApi.close(); 53 | } 54 | 55 | send(data: CommunicationApiData) { 56 | if (this.taskExecutorQueue) { 57 | this.taskExecutorQueue.execute(() => { 58 | try { 59 | this.communicationApi.send(data); 60 | } catch (error) { 61 | Logger.debug(` An error occured while sending task. ${error.message}`); 62 | } 63 | }); 64 | } 65 | } 66 | 67 | silent(): void { 68 | this.taskExecutorQueue.pause(); 69 | } 70 | 71 | unsilent(): void { 72 | this.taskExecutorQueue.start(); 73 | } 74 | 75 | protected onMessage(data: any): void { 76 | if (this.taskExecutorQueue) { 77 | this.taskExecutorQueue.execute(() => { 78 | try { 79 | this._emit(CommunicationApiEventNames.MESSAGE, data); 80 | } catch (error) { 81 | Logger.debug(` An error occured while emiting new message. ${error.message}`); 82 | } 83 | }); 84 | } 85 | } 86 | 87 | private _emit(event: string, data: any) { 88 | if (this.connected) { 89 | this.emit(event, data); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/CommunicationApi.ts: -------------------------------------------------------------------------------- 1 | import { CommunicationApiData } from "../../../types"; 2 | import { ConnectableApi, ConnectableApiEventNames } from "../../Api"; 3 | 4 | export const CommunicationApiEventNames = { 5 | ...ConnectableApiEventNames, 6 | MESSAGE: 'MESSAGE', 7 | } 8 | 9 | export default interface CommunicationApi extends ConnectableApi { 10 | send(data: CommunicationApiData): any; 11 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/CommunicationManager.ts: -------------------------------------------------------------------------------- 1 | import CommunicationUtils from "../../../utils/CommunicationUtils"; 2 | import { ApplicationStatus, ApplicationStatusEvent, CommunicationApiData } from "../../../types"; 3 | import CommunicationApi from "./CommunicationApi"; 4 | import Logger from '../../../logger'; 5 | import ApplicationStatusProvider from "../../../application/status/ApplicationStatusProvider"; 6 | import UuidUtils from "../../../utils/UuidUtils"; 7 | import Application from "../../../application/Application"; 8 | 9 | export default class CommunicationManager { 10 | private static comminicationApi: CommunicationApi; 11 | private static applicationStatusProviderList: ApplicationStatusProvider[]; 12 | 13 | private constructor(){} 14 | 15 | static setCommunicationApi(comminicationApi: CommunicationApi) { 16 | CommunicationManager.comminicationApi = comminicationApi; 17 | } 18 | 19 | static setProviderList(applicationStatusProviderList: ApplicationStatusProvider[]) { 20 | CommunicationManager.applicationStatusProviderList = applicationStatusProviderList; 21 | } 22 | 23 | static send(data: any) { 24 | if (CommunicationManager.comminicationApi) { 25 | Logger.debug(' Sending data to communication api.'); 26 | 27 | CommunicationManager.comminicationApi.send(data); 28 | } 29 | } 30 | 31 | static sendRequest(data: CommunicationApiData) { 32 | CommunicationManager.send(data); 33 | } 34 | 35 | static sendResponse(data: CommunicationApiData) { 36 | CommunicationUtils.appendResponseProperties(data); 37 | CommunicationManager.send(data); 38 | } 39 | 40 | static sendEvent(data: CommunicationApiData) { 41 | CommunicationUtils.appendEventProperties(data); 42 | CommunicationManager.send(data); 43 | } 44 | 45 | static sendApplicationStatusEvent(client?: string) { 46 | const applicationInfo = Application.getApplicationInfo(); 47 | const applicationStatus = { 48 | name: applicationInfo.applicationName, 49 | hostName: applicationInfo.hostname, 50 | instanceId: applicationInfo.applicationInstanceId, 51 | runtime: applicationInfo.applicationRuntime, 52 | stage: applicationInfo.applicationStage, 53 | customTags: applicationInfo.applicationTags 54 | ? Object.keys(applicationInfo.applicationTags).map(key => { 55 | return { 56 | tagName: key, 57 | tagValue: applicationInfo.applicationTags[key], 58 | }; 59 | }) 60 | : [], 61 | version: applicationInfo.applicationVersion, 62 | } as ApplicationStatus; 63 | 64 | CommunicationManager.applicationStatusProviderList.forEach(provider => { 65 | provider.provide(applicationStatus, client); 66 | }); 67 | 68 | CommunicationManager.sendEvent(new ApplicationStatusEvent( 69 | UuidUtils.generateId(), 70 | applicationStatus, 71 | client 72 | )); 73 | } 74 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/DebugBrokerApi.ts: -------------------------------------------------------------------------------- 1 | import CommunicationApi, { CommunicationApiEventNames } from "../CommunicationApi"; 2 | 3 | import { ClientOptions, WebSocket } from 'ws'; 4 | import EventEmitter from "events"; 5 | import { ClientRequestArgs } from "http"; 6 | import ConfigProvider from '../../../../config/ConfigProvider'; 7 | import { ConfigNames } from '../../../../config/ConfigNames'; 8 | import Application from '../../../../application/Application'; 9 | import { CommunicationApiData } from "../../../../types"; 10 | import Logger from '../../../../logger'; 11 | 12 | export default class DebugBrokerApi extends EventEmitter implements CommunicationApi { 13 | private static API_KEY_HEADER_NAME = "x-sidekick-api-key"; 14 | private static APP_INSTANCE_ID_HEADER_NAME = "x-sidekick-app-instance-id"; 15 | private static APP_NAME_HEADER_NAME = "x-sidekick-app-name"; 16 | private static APP_STAGE_HEADER_NAME = "x-sidekick-app-stage"; 17 | private static APP_VERSION_HEADER_NAME = "x-sidekick-app-version"; 18 | private static APP_HOSTNAME_HEADER_NAME = "x-sidekick-app-hostname"; 19 | private static APP_RUNTIME_HEADER_NAME = "x-sidekick-app-runtime"; 20 | private static APP_TAG_HEADER_NAME_PREFIX = "x-sidekick-app-tag-"; 21 | 22 | protected address?: string | URL; 23 | protected options?: ClientOptions | ClientRequestArgs; 24 | protected ws: WebSocket; 25 | protected connected = true; 26 | 27 | constructor(address: string | URL, options?: ClientOptions | ClientRequestArgs) { 28 | super(); 29 | 30 | this.address = address; 31 | this.options = options; 32 | } 33 | 34 | static generateBrokerUrl(host: string, port: number): string { 35 | if (host.startsWith("ws://") || host.startsWith("wss://")) { 36 | return host + ":" + port; 37 | } else { 38 | return "wss://" + host + ":" + port; 39 | } 40 | } 41 | 42 | static generateBrokerHeaders(): { [key: string]: string } { 43 | const headers: { [key: string]: string } = {}; 44 | 45 | const applicationInfo = Application.getApplicationInfo(); 46 | 47 | headers[DebugBrokerApi.API_KEY_HEADER_NAME] = ConfigProvider.get(ConfigNames.agent.apiKey); 48 | headers[DebugBrokerApi.APP_INSTANCE_ID_HEADER_NAME] = applicationInfo.applicationInstanceId; 49 | headers[DebugBrokerApi.APP_NAME_HEADER_NAME] = applicationInfo.applicationName; 50 | headers[DebugBrokerApi.APP_STAGE_HEADER_NAME] = applicationInfo.applicationStage; 51 | headers[DebugBrokerApi.APP_VERSION_HEADER_NAME] = applicationInfo.applicationVersion; 52 | headers[DebugBrokerApi.APP_HOSTNAME_HEADER_NAME] = applicationInfo.hostname; 53 | headers[DebugBrokerApi.APP_RUNTIME_HEADER_NAME] = applicationInfo.applicationRuntime; 54 | 55 | if (applicationInfo.applicationTags) { 56 | Object.keys(applicationInfo.applicationTags).forEach(key => { 57 | const headerKey = `${DebugBrokerApi.APP_TAG_HEADER_NAME_PREFIX}${key}`; 58 | if (!headers[headerKey]) { 59 | headers[headerKey] = applicationInfo.applicationTags[key]; 60 | } 61 | }) 62 | } 63 | 64 | return headers; 65 | } 66 | 67 | connect(): void { 68 | this.tryConnect(); 69 | } 70 | 71 | reconnect(): void { 72 | this.tryConnect(true); 73 | } 74 | 75 | close(): void { 76 | this.connected = false; 77 | if (this.ws.readyState == 1) { 78 | this.ws.close(); 79 | this.ws = null; 80 | } 81 | } 82 | 83 | send(data: CommunicationApiData): Promise { 84 | return new Promise((resolve, reject) => { 85 | if (this.ws.readyState == 1) { 86 | this.ws.send(JSON.stringify(data), (err) => { 87 | if (err) { 88 | return reject(err) 89 | } 90 | 91 | resolve(''); 92 | }); 93 | } else { 94 | resolve(''); 95 | } 96 | }) 97 | } 98 | 99 | private tryConnect(reload?: boolean) { 100 | this.ws = new WebSocket(`${this.address}/app`, this.options); 101 | this.ws.once('open', () => { 102 | Logger.debug(' Broker connection established') 103 | this.connected = true; 104 | this._emit(CommunicationApiEventNames.OPEN, this); 105 | }); 106 | 107 | this.ws.on('message', (data) => { 108 | const message = JSON.parse(data.toString()); 109 | this._emit(CommunicationApiEventNames.MESSAGE, message); 110 | }); 111 | 112 | this.ws.on('error', (err) => { 113 | this.connected = true; 114 | Logger.debug(` An error occured on broker connection. ${err.message}`) 115 | this._emit(CommunicationApiEventNames.ERROR, { reload, message: err.message }); 116 | }); 117 | 118 | this.ws.once('close', (code, reason) => { 119 | Logger.debug(` Connection closed with broker. Code: ${code} Reason: ${reason}`); 120 | this._emit(CommunicationApiEventNames.CLOSE, { code, reason }); 121 | this.connected = false; 122 | }); 123 | } 124 | 125 | private _emit(event: string, data: any) { 126 | if (this.connected) { 127 | this.emit(event, data); 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/BrokerHandler.ts: -------------------------------------------------------------------------------- 1 | import TracepointManager from "../../../manager/TracepointManager"; 2 | import LogpointManager from "../../../manager/LogpointManager"; 3 | import TagManager from "../../../manager/TagManager"; 4 | import ConfigManager from "../../../manager/ConfigManager"; 5 | import Handler from "../../../../../handler/Handler"; 6 | import { CommunicationApiData } from "../../../../../types"; 7 | import * as ProbeErrors from '../../../../../error/ProbeErrors'; 8 | import ProbeUtils from "../../../../../utils/ProbeUtils"; 9 | 10 | export default abstract class BrokerHandler implements Handler { 11 | abstract handle(data: CommunicationApiData): any | undefined; 12 | 13 | protected validateLineNo(lineNo: number) { 14 | if (lineNo <= 0) { 15 | throw new ProbeErrors.LineNumberIsMandatory(); 16 | } 17 | } 18 | 19 | protected validateAndGetFileName(filename: string) { 20 | if (!filename) { 21 | throw new ProbeErrors.FileNameIsMandatory(); 22 | } 23 | 24 | return filename; 25 | } 26 | 27 | protected extractFileName(filename: string) { 28 | return ProbeUtils.extractFileNameFrom(filename, 'contents'); 29 | } 30 | } 31 | 32 | export abstract class BrokerTracePointHandler extends BrokerHandler { 33 | protected tracepointManager: TracepointManager; 34 | 35 | constructor(tracepointManager: TracepointManager) { 36 | super(); 37 | 38 | this.tracepointManager = tracepointManager; 39 | } 40 | 41 | abstract handle(data: CommunicationApiData): any | undefined; 42 | } 43 | 44 | export abstract class BrokerLogPointHandler extends BrokerHandler { 45 | protected logpointManager: LogpointManager; 46 | 47 | constructor(logpointManager: LogpointManager) { 48 | super(); 49 | 50 | this.logpointManager = logpointManager; 51 | } 52 | 53 | abstract handle(data: CommunicationApiData): any | undefined; 54 | } 55 | 56 | export abstract class BrokerTagHandler extends BrokerHandler { 57 | protected tagManager: TagManager; 58 | 59 | constructor(tagManager: TagManager) { 60 | super(); 61 | 62 | this.tagManager = tagManager; 63 | } 64 | 65 | abstract handle(data: CommunicationApiData): any | undefined; 66 | } 67 | 68 | export abstract class BrokerConfigHandler extends BrokerHandler { 69 | protected configManager: ConfigManager; 70 | 71 | constructor(configManager: ConfigManager) { 72 | super(); 73 | 74 | this.configManager = configManager; 75 | } 76 | 77 | abstract handle(data: CommunicationApiData): any | undefined; 78 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/BrokerHandlerContainer.ts: -------------------------------------------------------------------------------- 1 | import { CommunicationApiData, CommunicationApiDataType } from "../../../../../types"; 2 | import RequestHandlerContainer from "./request/RequestHandlerContainer"; 3 | import Handler from "../../../../../handler/Handler"; 4 | import EventHandlerContainer from "./event/EventHandlerContainer"; 5 | import HandlerContainer from "../../../../../handler/HandlerContainer"; 6 | import ResponseHandlerContainer from "./response/ResponseHandlerContainer"; 7 | import TracepointManager from "../../../manager/TracepointManager"; 8 | import LogpointManager from "../../../manager/LogpointManager"; 9 | import TagManager from "../../../manager/TagManager"; 10 | import ConfigManager from "../../../manager/ConfigManager"; 11 | 12 | export default class BrokerHandlerContainer implements HandlerContainer { 13 | private handlerMap: Map 14 | 15 | constructor( 16 | tracepointManager: TracepointManager, 17 | logpointManager: LogpointManager, 18 | tagManager: TagManager, 19 | configManager: ConfigManager) { 20 | this.handlerMap = new Map([ 21 | ['Request', new RequestHandlerContainer(tracepointManager, logpointManager, tagManager, configManager)], 22 | ['Response', new ResponseHandlerContainer(tracepointManager, logpointManager, configManager)], 23 | ['Event', new EventHandlerContainer()], 24 | ]); 25 | } 26 | 27 | getHandler(communicationApiData: CommunicationApiData): Handler | undefined { 28 | const handlerContainer = this.handlerMap.get(communicationApiData.type); 29 | if (!handlerContainer) { 30 | return; 31 | } 32 | 33 | return handlerContainer.getHandler(communicationApiData); 34 | } 35 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/event/EventHandlerContainer.ts: -------------------------------------------------------------------------------- 1 | import Handler from "../../../../../../handler/Handler"; 2 | import HandlerContainer from "../../../../../../handler/HandlerContainer"; 3 | import { Message } from "../../../../../../types"; 4 | 5 | export default class EventHandlerContainer implements HandlerContainer { 6 | private eventHandlerMap: Map; 7 | 8 | constructor() { 9 | this.eventHandlerMap = new Map([]); 10 | } 11 | 12 | getHandler(message: Message): Handler { 13 | return this.eventHandlerMap.get(message.name); 14 | } 15 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/RequestHandlerContainer.ts: -------------------------------------------------------------------------------- 1 | import Handler from "../../../../../../handler/Handler"; 2 | import { BrokerRequest, RequestName } from "../../../../../../types"; 3 | import HandlerContainer from "../../../../../../handler/HandlerContainer"; 4 | import PutTracePointMessageHandler from "./tracepoint/PutTracePointRequestHandler"; 5 | import EnableTracePointRequestHandler from "./tracepoint/EnableTracePointRequestHandler"; 6 | import DisableTracePointRequestHandler from "./tracepoint/DisableTracePointRequestHandler"; 7 | import RemoveTracePointRequestHandler from "./tracepoint/RemoveTracePointRequestHandler"; 8 | import RemoveBatchTracePointRequestHandler from "./tracepoint/RemoveBatchTracePointRequestHandler"; 9 | import UpdateTracePointRequestHandler from "./tracepoint/UpdateTracePointRequestHandler"; 10 | import PutLogPointRequestHandler from "./logpoint/PutLogPointRequestHandler"; 11 | import EnableLogPointRequestHandler from "./logpoint/EnableLogPointRequestHandler"; 12 | import DisableLogPointRequestHandler from "./logpoint/DisableLogPointRequestHandler"; 13 | import RemoveLogPointRequestHandler from "./logpoint/RemoveLogPointRequestHandler"; 14 | import RemoveBatchLogPointRequestHandler from "./logpoint/RemoveBatchLogPointRequestHandler"; 15 | import UpdateLogPointRequestHandler from "./logpoint/UpdateLogPointRequestHandler"; 16 | import EnableProbeTagRequestHandler from "./tag/EnableProbeTagRequestHandler"; 17 | import DisableProbeTagRequestHandler from "./tag/DisableProbeTagRequestHandler"; 18 | import RemoveProbeTagRequestHandler from "./tag/RemoveProbeTagRequestHandler"; 19 | import UpdateConfigRequestHandler from "./config/UpdateConfigRequestHandler"; 20 | import AttachRequestHandler from "./config/AttachRequestHandler"; 21 | import DetachRequestHandler from "./config/DetachRequestHandler"; 22 | import TracepointManager from "../../../../manager/TracepointManager"; 23 | import LogpointManager from "../../../../manager/LogpointManager"; 24 | import TagManager from "../../../../manager/TagManager"; 25 | import ConfigManager from "../../../../manager/ConfigManager"; 26 | 27 | export default class RequestHandlerContainer implements HandlerContainer { 28 | private requestHandlerMap: Map; 29 | 30 | constructor( 31 | tracepointManager: TracepointManager, 32 | logpointManager: LogpointManager, 33 | tagManager: TagManager, 34 | configManager: ConfigManager) { 35 | this.requestHandlerMap = new Map([ 36 | ['PutTracePointRequest', new PutTracePointMessageHandler(tracepointManager)], 37 | ['EnableTracePointRequest', new EnableTracePointRequestHandler(tracepointManager)], 38 | ['DisableTracePointRequest', new DisableTracePointRequestHandler(tracepointManager)], 39 | ['RemoveTracePointRequest', new RemoveTracePointRequestHandler(tracepointManager)], 40 | ['RemoveBatchTracePointRequest', new RemoveBatchTracePointRequestHandler(tracepointManager)], 41 | ['UpdateTracePointRequest', new UpdateTracePointRequestHandler(tracepointManager)], 42 | ['PutLogPointRequest', new PutLogPointRequestHandler(logpointManager)], 43 | ['EnableLogPointRequest', new EnableLogPointRequestHandler(logpointManager)], 44 | ['DisableLogPointRequest', new DisableLogPointRequestHandler(logpointManager)], 45 | ['RemoveLogPointRequest', new RemoveLogPointRequestHandler(logpointManager)], 46 | ['RemoveBatchLogPointRequest', new RemoveBatchLogPointRequestHandler(logpointManager)], 47 | ['UpdateLogPointRequest', new UpdateLogPointRequestHandler(logpointManager)], 48 | ['EnableProbeTagRequest', new EnableProbeTagRequestHandler(tagManager)], 49 | ['DisableProbeTagRequest', new DisableProbeTagRequestHandler(tagManager)], 50 | ['RemoveProbeTagRequest', new RemoveProbeTagRequestHandler(tagManager)], 51 | ['UpdateConfigRequest', new UpdateConfigRequestHandler(configManager)], 52 | ['AttachRequest', new AttachRequestHandler(configManager)], 53 | ['DetachRequest', new DetachRequestHandler(configManager)], 54 | ]); 55 | } 56 | 57 | getHandler(brokerRequest: BrokerRequest): Handler { 58 | return this.requestHandlerMap.get(brokerRequest.name); 59 | } 60 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/config/AttachRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import ConfigManager from '../../../../../manager/ConfigManager'; 2 | import { AttachRequest, AttachResponse, BrokerResponse } from '../../../../../../../types'; 3 | import ConfigRequestHandler from './ConfigRequestHandler'; 4 | import CommunicationManager from '../../../../CommunicationManager'; 5 | import Logger from '../../../../../../../logger'; 6 | 7 | export default class AttachRequestHandler extends ConfigRequestHandler { 8 | constructor(configManager: ConfigManager) { 9 | super(configManager); 10 | } 11 | 12 | handle(attachRequest: AttachRequest): BrokerResponse { 13 | Logger.debug(' Handling attach reqeust message.'); 14 | 15 | const response = new AttachResponse(attachRequest.id, attachRequest.client); 16 | try { 17 | this.configManager.makeUnsilent(); 18 | 19 | CommunicationManager.sendApplicationStatusEvent(); 20 | if (attachRequest.client) { 21 | CommunicationManager.sendApplicationStatusEvent(attachRequest.client) 22 | } 23 | } catch (error) { 24 | Logger.debug(` An error occured while handling attach reqeust message. ${error.message}`); 25 | response.setError(error); 26 | } finally { 27 | return response; 28 | } 29 | }; 30 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/config/ConfigRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import ConfigManager from '../../../../../manager/ConfigManager'; 2 | import { BrokerConfigHandler } from "../../BrokerHandler"; 3 | 4 | export default abstract class ConfigRequestHandler extends BrokerConfigHandler { 5 | constructor(configManager: ConfigManager) { 6 | super(configManager); 7 | } 8 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/config/DetachRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import ConfigManager from '../../../../../manager/ConfigManager'; 2 | import { DetachRequest, DetachResponse, BrokerResponse } from '../../../../../../../types'; 3 | import ConfigRequestHandler from './ConfigRequestHandler'; 4 | import CommunicationManager from '../../../../CommunicationManager'; 5 | import Logger from '../../../../../../../logger'; 6 | 7 | export default class DetachRequestHandler extends ConfigRequestHandler { 8 | constructor(configManager: ConfigManager) { 9 | super(configManager); 10 | } 11 | 12 | handle(detachRequest: DetachRequest): BrokerResponse { 13 | Logger.debug(' Handling deattach reqeust message.'); 14 | 15 | const response = new DetachResponse(detachRequest.id, detachRequest.client); 16 | try { 17 | this.configManager.makeSilent(); 18 | 19 | CommunicationManager.sendApplicationStatusEvent(); 20 | if (detachRequest.client) { 21 | CommunicationManager.sendApplicationStatusEvent(detachRequest.client) 22 | } 23 | } catch (error) { 24 | Logger.debug(` An error occured while handling deattach reqeust message. ${error.message}`); 25 | response.setError(error); 26 | } finally { 27 | return response; 28 | } 29 | }; 30 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/config/UpdateConfigRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import ConfigManager from '../../../../../manager/ConfigManager'; 2 | import { UpdateConfigRequest, UpdateConfigResponse, BrokerResponse } from '../../../../../../../types'; 3 | import ConfigRequestHandler from './ConfigRequestHandler'; 4 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 5 | import Logger from '../../../../../../../logger'; 6 | 7 | export default class UpdateConfigRequestHandler extends ConfigRequestHandler { 8 | constructor(configManager: ConfigManager) { 9 | super(configManager); 10 | } 11 | 12 | handle(updateConfigRequest: UpdateConfigRequest): BrokerResponse { 13 | Logger.debug(' Handling update config reqeust message.'); 14 | 15 | const response = new UpdateConfigResponse(updateConfigRequest.id, updateConfigRequest.client); 16 | try { 17 | this.configManager.updateConfig(updateConfigRequest.config); 18 | 19 | CommunicationManager.sendApplicationStatusEvent(); 20 | if (updateConfigRequest.client) { 21 | CommunicationManager.sendApplicationStatusEvent(updateConfigRequest.client) 22 | } 23 | } catch (error) { 24 | Logger.debug(` An error occured while handling update config reqeust message. ${error.message}`); 25 | response.setError(error); 26 | } finally { 27 | return response; 28 | } 29 | }; 30 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/logpoint/DisableLogPointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | 2 | import LogpointManager from '../../../../../manager/LogpointManager'; 3 | import { BrokerResponse, DisableLogPointRequest, DisableLogPointResponse } from '../../../../../../../types'; 4 | import LogpointRequestHandler from './LogpointRequestHandler'; 5 | import Logger from '../../../../../../../logger'; 6 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 7 | 8 | export default class DisableLogPointRequestHandler extends LogpointRequestHandler { 9 | constructor(logpointManager: LogpointManager) { 10 | super(logpointManager); 11 | } 12 | 13 | handle(disableLogPointRequest: DisableLogPointRequest): BrokerResponse { 14 | Logger.debug(` Handling disable logpoint message. ${disableLogPointRequest.logPointId}`); 15 | 16 | const response = new DisableLogPointResponse(disableLogPointRequest.id, disableLogPointRequest.client); 17 | try { 18 | this.logpointManager.disableLogPoint( 19 | disableLogPointRequest.logPointId, 20 | disableLogPointRequest.client); 21 | 22 | CommunicationManager.sendApplicationStatusEvent(); 23 | if (disableLogPointRequest.client) { 24 | CommunicationManager.sendApplicationStatusEvent(disableLogPointRequest.client) 25 | } 26 | } catch (error) { 27 | Logger.debug(` An error occured while handling disable message. ${error.message}`); 28 | response.setError(error); 29 | } finally { 30 | return response; 31 | } 32 | }; 33 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/logpoint/EnableLogPointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | 2 | import LogpointManager from '../../../../../manager/LogpointManager'; 3 | import { BrokerResponse, EnableLogPointRequest, EnableLogPointResponse } from '../../../../../../../types'; 4 | import LogpointRequestHandler from './LogpointRequestHandler'; 5 | import Logger from '../../../../../../../logger'; 6 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 7 | 8 | export default class EnableLogPointRequestHandler extends LogpointRequestHandler { 9 | constructor(logpointManager: LogpointManager) { 10 | super(logpointManager); 11 | } 12 | 13 | handle(enableLogPointRequest: EnableLogPointRequest): BrokerResponse { 14 | Logger.debug(` Handling enable logpoint message. ${enableLogPointRequest.logPointId}`); 15 | 16 | const response = new EnableLogPointResponse(enableLogPointRequest.id, enableLogPointRequest.client); 17 | try { 18 | this.logpointManager.enableLogPoint( 19 | enableLogPointRequest.logPointId, 20 | enableLogPointRequest.client); 21 | 22 | CommunicationManager.sendApplicationStatusEvent(); 23 | if (enableLogPointRequest.client) { 24 | CommunicationManager.sendApplicationStatusEvent(enableLogPointRequest.client) 25 | } 26 | } catch (error) { 27 | Logger.debug(` An error occured while handling enable message. ${error.message}`); 28 | response.setError(error); 29 | } finally { 30 | return response; 31 | } 32 | }; 33 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/logpoint/LogpointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import LogpointManager from "../../../../../manager/LogpointManager"; 2 | import { BrokerLogPointHandler } from "../../BrokerHandler"; 3 | 4 | export default abstract class LogpointRequestHandler extends BrokerLogPointHandler { 5 | constructor(logpointManager: LogpointManager) { 6 | super(logpointManager); 7 | } 8 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/logpoint/PutLogPointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import LogpointManager from '../../../../../manager/LogpointManager'; 2 | import { BrokerResponse, PutLogPointRequest, PutLogPointResponse, Logpoint } from '../../../../../../../types'; 3 | import LogpointRequestHandler from './LogpointRequestHandler'; 4 | import Logger from '../../../../../../../logger'; 5 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 6 | 7 | export default class PutLogPointRequestHandler extends LogpointRequestHandler { 8 | constructor(logpointManager: LogpointManager) { 9 | super(logpointManager); 10 | } 11 | 12 | handle(putLogPointRequest: PutLogPointRequest): BrokerResponse { 13 | Logger.debug(` Handling put logpoint message. 14 | ${putLogPointRequest.fileName} ${putLogPointRequest.logPointId}`); 15 | 16 | const response = new PutLogPointResponse(putLogPointRequest.id, putLogPointRequest.client); 17 | try { 18 | putLogPointRequest.fileName = 19 | this.validateAndGetFileName(putLogPointRequest.fileName); 20 | this.validateLineNo(putLogPointRequest.lineNo); 21 | 22 | this.logpointManager.putLogpoint({ 23 | ...putLogPointRequest, 24 | id: putLogPointRequest.logPointId, 25 | } as Logpoint); 26 | 27 | CommunicationManager.sendApplicationStatusEvent(); 28 | if (putLogPointRequest.client) { 29 | CommunicationManager.sendApplicationStatusEvent(putLogPointRequest.client) 30 | } 31 | 32 | } catch (error) { 33 | Logger.debug(` An error occured while handling put logpoint message. ${error.message}`); 34 | response.setError(error); 35 | } finally { 36 | return response; 37 | } 38 | }; 39 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/logpoint/RemoveBatchLogPointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | 2 | import LogpointManager from '../../../../../manager/LogpointManager'; 3 | import { BrokerResponse, RemoveBatchLogPointRequest, RemoveBatchLogPointResponse } from '../../../../../../../types'; 4 | import LogpointRequestHandler from './LogpointRequestHandler'; 5 | import Logger from '../../../../../../../logger'; 6 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 7 | 8 | export default class RemoveBatchLogPointRequestHandler extends LogpointRequestHandler { 9 | constructor(logpointManager: LogpointManager) { 10 | super(logpointManager); 11 | } 12 | 13 | handle(removeBatchLogPointRequest: RemoveBatchLogPointRequest): BrokerResponse { 14 | Logger.debug(' Handling remove batch logpoint message'); 15 | 16 | const response = new RemoveBatchLogPointResponse(removeBatchLogPointRequest.id, removeBatchLogPointRequest.client); 17 | try { 18 | for (const logpointId in removeBatchLogPointRequest.logPointIds) { 19 | try { 20 | this.logpointManager.removeLogPoint(logpointId, removeBatchLogPointRequest.client); 21 | response.removedLogPointIds.push(logpointId); 22 | } catch (error) { 23 | response.unRemovedLogPointIds[logpointId] = error.message; 24 | } 25 | } 26 | 27 | CommunicationManager.sendApplicationStatusEvent(); 28 | if (removeBatchLogPointRequest.client) { 29 | CommunicationManager.sendApplicationStatusEvent(removeBatchLogPointRequest.client) 30 | } 31 | } catch (error) { 32 | Logger.debug(` An error occured while handling remove batch message. ${error.message}`); 33 | } finally { 34 | return response; 35 | } 36 | }; 37 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/logpoint/RemoveLogPointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | 2 | import LogpointManager from '../../../../../manager/LogpointManager'; 3 | import { BrokerResponse, RemoveLogPointRequest, RemoveLogPointResponse } from '../../../../../../../types'; 4 | import LogpointRequestHandler from './LogpointRequestHandler'; 5 | import Logger from '../../../../../../../logger'; 6 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 7 | 8 | export default class RemoveLogPointRequestHandler extends LogpointRequestHandler { 9 | constructor(logpointManager: LogpointManager) { 10 | super(logpointManager); 11 | } 12 | 13 | handle(removeLogPointRequest: RemoveLogPointRequest): BrokerResponse { 14 | Logger.debug(` Handling remove logpoint message. ${removeLogPointRequest.logPointId}`); 15 | 16 | const response = new RemoveLogPointResponse(removeLogPointRequest.id, removeLogPointRequest.client); 17 | try { 18 | this.logpointManager.removeLogPoint( 19 | removeLogPointRequest.logPointId, 20 | removeLogPointRequest.client); 21 | 22 | CommunicationManager.sendApplicationStatusEvent(); 23 | if (removeLogPointRequest.client) { 24 | CommunicationManager.sendApplicationStatusEvent(removeLogPointRequest.client) 25 | } 26 | } catch (error) { 27 | Logger.debug(` An error occured while handling remove message. ${error.message}`); 28 | response.setError(error); 29 | } finally { 30 | return response; 31 | } 32 | }; 33 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/logpoint/UpdateLogPointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | 2 | import LogpointManager from '../../../../../manager/LogpointManager'; 3 | import { BrokerResponse, UpdateLogPointResponse, UpdateLogPointRequest, Logpoint } from '../../../../../../../types'; 4 | import LogpointRequestHandler from './LogpointRequestHandler'; 5 | import Logger from '../../../../../../../logger'; 6 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 7 | 8 | export default class UpdateLogPointRequestHandler extends LogpointRequestHandler { 9 | constructor(logpointManager: LogpointManager) { 10 | super(logpointManager); 11 | } 12 | 13 | handle(updateLogPointRequest: UpdateLogPointRequest): BrokerResponse { 14 | Logger.debug(` Handling update logpoint message. ${updateLogPointRequest.logPointId}`); 15 | 16 | const response = new UpdateLogPointResponse(updateLogPointRequest.id, updateLogPointRequest.client); 17 | try { 18 | const logpoint = { 19 | ...updateLogPointRequest, 20 | id: updateLogPointRequest.logPointId, 21 | stdoutEnabled: updateLogPointRequest.stdoutEnabled, 22 | } as Logpoint; 23 | 24 | this.logpointManager.updateLogPoint(logpoint); 25 | 26 | CommunicationManager.sendApplicationStatusEvent(); 27 | if (updateLogPointRequest.client) { 28 | CommunicationManager.sendApplicationStatusEvent(updateLogPointRequest.client) 29 | } 30 | } catch (error) { 31 | Logger.debug(` An error occured while handling update message. ${error.message}`); 32 | response.setError(error); 33 | } finally { 34 | return response; 35 | } 36 | }; 37 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/tag/DisableProbeTagRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import TagManager from '../../../../../manager/TagManager'; 2 | import { 3 | BrokerResponse, 4 | DisableProbeTagRequest, 5 | DisableProbeTagResponse, 6 | } from '../../../../../../../types'; 7 | import TagRequestHandler from './TagRequestHandler'; 8 | import Logger from '../../../../../../../logger'; 9 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 10 | 11 | export default class DisableProbeTagRequestHandler extends TagRequestHandler { 12 | constructor(tagManager: TagManager) { 13 | super(tagManager); 14 | } 15 | 16 | handle(disableProbeTagRequest: DisableProbeTagRequest): BrokerResponse { 17 | Logger.debug(` Handling disable tag message. 18 | ${disableProbeTagRequest.tag}`); 19 | 20 | const response = new DisableProbeTagResponse(disableProbeTagRequest.id, disableProbeTagRequest.client); 21 | try { 22 | this.tagManager.disableTag(disableProbeTagRequest.tag); 23 | 24 | CommunicationManager.sendApplicationStatusEvent(); 25 | if (disableProbeTagRequest.client) { 26 | CommunicationManager.sendApplicationStatusEvent(disableProbeTagRequest.client) 27 | } 28 | 29 | } catch (error) { 30 | Logger.debug(` An error occured while handling disable tag message. ${error.message}`); 31 | response.setError(error); 32 | } finally { 33 | return response; 34 | } 35 | }; 36 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/tag/EnableProbeTagRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import TagManager from '../../../../../manager/TagManager'; 2 | import { 3 | BrokerResponse, 4 | EnableProbeTagRequest, 5 | EnableProbeTagResponse, 6 | } from '../../../../../../../types'; 7 | import TagRequestHandler from './TagRequestHandler'; 8 | import Logger from '../../../../../../../logger'; 9 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 10 | 11 | export default class EnableProbeTagRequestHandler extends TagRequestHandler { 12 | constructor(tagManager: TagManager) { 13 | super(tagManager); 14 | } 15 | 16 | handle(enableProbeTagRequest: EnableProbeTagRequest): BrokerResponse { 17 | Logger.debug(` Handling enable tag message. 18 | ${enableProbeTagRequest.tag}`); 19 | 20 | const response = new EnableProbeTagResponse(enableProbeTagRequest.id, enableProbeTagRequest.client); 21 | try { 22 | this.tagManager.enableTag(enableProbeTagRequest.tag); 23 | 24 | CommunicationManager.sendApplicationStatusEvent(); 25 | if (enableProbeTagRequest.client) { 26 | CommunicationManager.sendApplicationStatusEvent(enableProbeTagRequest.client) 27 | } 28 | 29 | } catch (error) { 30 | Logger.debug(` An error occured while handling enable tag message. ${error.message}`); 31 | response.setError(error); 32 | } finally { 33 | return response; 34 | } 35 | }; 36 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/tag/RemoveProbeTagRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import TagManager from '../../../../../manager/TagManager'; 2 | import { 3 | BrokerResponse, 4 | RemoveProbeTagRequest, 5 | RemoveProbeTagResponse, 6 | } from '../../../../../../../types'; 7 | import TagRequestHandler from './TagRequestHandler'; 8 | import Logger from '../../../../../../../logger'; 9 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 10 | 11 | export default class RemoveProbeTagRequestHandler extends TagRequestHandler { 12 | constructor(tagManager: TagManager) { 13 | super(tagManager); 14 | } 15 | 16 | handle(removeProbeTagRequest: RemoveProbeTagRequest): BrokerResponse { 17 | Logger.debug(` Handling Remove tag message. 18 | ${removeProbeTagRequest.tag}`); 19 | 20 | const response = new RemoveProbeTagResponse(removeProbeTagRequest.id, removeProbeTagRequest.client); 21 | try { 22 | this.tagManager.removeTag(removeProbeTagRequest.tag); 23 | 24 | CommunicationManager.sendApplicationStatusEvent(); 25 | if (removeProbeTagRequest.client) { 26 | CommunicationManager.sendApplicationStatusEvent(removeProbeTagRequest.client) 27 | } 28 | 29 | } catch (error) { 30 | Logger.debug(` An error occured while handling Remove tag message. ${error.message}`); 31 | response.setError(error); 32 | } finally { 33 | return response; 34 | } 35 | }; 36 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/tag/TagRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import TagManager from '../../../../../manager/TagManager'; 2 | import { BrokerTagHandler } from "../../BrokerHandler"; 3 | 4 | export default abstract class TagRequestHandler extends BrokerTagHandler { 5 | constructor(tagManager: TagManager) { 6 | super(tagManager); 7 | } 8 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/tracepoint/DisableTracePointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import TracepointManager from '../../../../../manager/TracepointManager'; 2 | import { BrokerResponse, DisableTracePointRequest, DisableTracePointResponse } from '../../../../../../../types'; 3 | import TracepointRequestHandler from './TracepointRequestHandler'; 4 | import Logger from '../../../../../../../logger'; 5 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 6 | 7 | export default class DisableTracePointRequestHandler extends TracepointRequestHandler { 8 | constructor(tracepointManager: TracepointManager) { 9 | super(tracepointManager); 10 | } 11 | 12 | handle(disableTracePointRequest: DisableTracePointRequest): BrokerResponse { 13 | Logger.debug(` Handling disable tracepoint message. ${disableTracePointRequest.tracePointId}`); 14 | 15 | const response = new DisableTracePointResponse(disableTracePointRequest.id, disableTracePointRequest.client); 16 | try { 17 | this.tracepointManager.disableTracePoint( 18 | disableTracePointRequest.tracePointId, 19 | disableTracePointRequest.client); 20 | 21 | CommunicationManager.sendApplicationStatusEvent(); 22 | if (disableTracePointRequest.client) { 23 | CommunicationManager.sendApplicationStatusEvent(disableTracePointRequest.client) 24 | } 25 | } catch (error) { 26 | Logger.debug(` An error occured while handling disable message. ${error.message}`); 27 | response.setError(error); 28 | } finally { 29 | return response; 30 | } 31 | }; 32 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/tracepoint/EnableTracePointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | 2 | import TracepointManager from '../../../../../manager/TracepointManager'; 3 | import { BrokerResponse, EnableTracePointRequest, EnableTracePointResponse } from '../../../../../../../types'; 4 | import TracepointRequestHandler from './TracepointRequestHandler'; 5 | import Logger from '../../../../../../../logger'; 6 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 7 | 8 | export default class EnableTracePointRequestHandler extends TracepointRequestHandler { 9 | constructor(tracepointManager: TracepointManager) { 10 | super(tracepointManager); 11 | } 12 | 13 | handle(enableTracePointRequest: EnableTracePointRequest): BrokerResponse { 14 | Logger.debug(` Handling enable tracepoint message. ${enableTracePointRequest.tracePointId}`); 15 | 16 | const response = new EnableTracePointResponse(enableTracePointRequest.id, enableTracePointRequest.client); 17 | try { 18 | this.tracepointManager.enableTracePoint( 19 | enableTracePointRequest.tracePointId, 20 | enableTracePointRequest.client); 21 | 22 | CommunicationManager.sendApplicationStatusEvent(); 23 | if (enableTracePointRequest.client) { 24 | CommunicationManager.sendApplicationStatusEvent(enableTracePointRequest.client) 25 | } 26 | } catch (error) { 27 | Logger.debug(` An error occured while handling enable message. ${error.message}`); 28 | response.setError(error); 29 | } finally { 30 | return response; 31 | } 32 | }; 33 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/tracepoint/PutTracePointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import TracepointManager from '../../../../../manager/TracepointManager'; 2 | import { BrokerResponse, PutTracePointRequest, PutTracePointResponse, Tracepoint } from '../../../../../../../types'; 3 | import TracepointRequestHandler from './TracepointRequestHandler'; 4 | import Logger from '../../../../../../../logger'; 5 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 6 | 7 | export default class PutTracePointRequestHandler extends TracepointRequestHandler { 8 | constructor(tracepointManager: TracepointManager) { 9 | super(tracepointManager); 10 | } 11 | 12 | handle(putTracePointRequest: PutTracePointRequest): BrokerResponse { 13 | Logger.debug(` Handling put tracepoint message. 14 | ${putTracePointRequest.fileName} ${putTracePointRequest.tracePointId}`); 15 | 16 | const response = new PutTracePointResponse(putTracePointRequest.id, putTracePointRequest.client); 17 | try { 18 | putTracePointRequest.fileName = 19 | this.validateAndGetFileName(putTracePointRequest.fileName); 20 | this.validateLineNo(putTracePointRequest.lineNo); 21 | 22 | this.tracepointManager.putTracepoint({ 23 | ...putTracePointRequest, 24 | id: putTracePointRequest.tracePointId, 25 | tracingEnabled: putTracePointRequest.enableTracing, 26 | } as Tracepoint); 27 | 28 | CommunicationManager.sendApplicationStatusEvent(); 29 | if (putTracePointRequest.client) { 30 | CommunicationManager.sendApplicationStatusEvent(putTracePointRequest.client) 31 | } 32 | 33 | } catch (error) { 34 | Logger.debug(` An error occured while handling put tracepoint message. ${error.message}`); 35 | response.setError(error); 36 | } finally { 37 | return response; 38 | } 39 | }; 40 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/tracepoint/RemoveBatchTracePointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | 2 | import TracepointManager from '../../../../../manager/TracepointManager'; 3 | import { BrokerResponse, RemoveBatchTracePointRequest, RemoveBatchTracePointResponse } from '../../../../../../../types'; 4 | import TracepointRequestHandler from './TracepointRequestHandler'; 5 | import Logger from '../../../../../../../logger'; 6 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 7 | 8 | export default class RemoveBatchTracePointRequestHandler extends TracepointRequestHandler { 9 | constructor(tracepointManager: TracepointManager) { 10 | super(tracepointManager); 11 | } 12 | 13 | handle(removeBatchTracePointRequest: RemoveBatchTracePointRequest): BrokerResponse { 14 | Logger.debug(' Handling remove batch tracepoint message'); 15 | 16 | const response = new RemoveBatchTracePointResponse(removeBatchTracePointRequest.id, removeBatchTracePointRequest.client); 17 | try { 18 | for (const tracepointId in removeBatchTracePointRequest.tracePointIds) { 19 | try { 20 | this.tracepointManager.removeTracePoint(tracepointId, removeBatchTracePointRequest.client); 21 | response.removedTracePointIds.push(tracepointId); 22 | } catch (error) { 23 | response.unRemovedTracePointIds[tracepointId] = error.message; 24 | } 25 | } 26 | 27 | CommunicationManager.sendApplicationStatusEvent(); 28 | if (removeBatchTracePointRequest.client) { 29 | CommunicationManager.sendApplicationStatusEvent(removeBatchTracePointRequest.client) 30 | } 31 | } catch (error) { 32 | Logger.debug(` An error occured while handling remove batch message. ${error.message}`); 33 | } finally { 34 | return response; 35 | } 36 | }; 37 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/tracepoint/RemoveTracePointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | 2 | import TracepointManager from "../../../../../manager/TracepointManager"; 3 | import { BrokerResponse, RemoveTracePointRequest, RemoveTracePointResponse } from "../../../../../../../types"; 4 | import TracepointRequestHandler from "./TracepointRequestHandler"; 5 | import Logger from '../../../../../../../logger'; 6 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 7 | 8 | export default class RemoveTracePointRequestHandler extends TracepointRequestHandler { 9 | constructor(tracepointManager: TracepointManager) { 10 | super(tracepointManager); 11 | } 12 | 13 | handle(removeTracePointRequest: RemoveTracePointRequest): BrokerResponse { 14 | Logger.debug(` Handling remove tracepoint message. ${removeTracePointRequest.tracePointId}`); 15 | 16 | const response = new RemoveTracePointResponse(removeTracePointRequest.id, removeTracePointRequest.client); 17 | try { 18 | this.tracepointManager.removeTracePoint( 19 | removeTracePointRequest.tracePointId, 20 | removeTracePointRequest.client); 21 | 22 | CommunicationManager.sendApplicationStatusEvent(); 23 | if (removeTracePointRequest.client) { 24 | CommunicationManager.sendApplicationStatusEvent(removeTracePointRequest.client) 25 | } 26 | } catch (error) { 27 | Logger.debug(` An error occured while handling remove message. ${error.message}`); 28 | response.setError(error); 29 | } finally { 30 | return response; 31 | } 32 | }; 33 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/tracepoint/TracepointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | import TracepointManager from '../../../../../manager/TracepointManager'; 2 | import { BrokerTracePointHandler } from "../../BrokerHandler"; 3 | 4 | export default abstract class TracepointRequestHandler extends BrokerTracePointHandler { 5 | constructor(tracepointManager: TracepointManager) { 6 | super(tracepointManager); 7 | } 8 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/request/tracepoint/UpdateTracePointRequestHandler.ts: -------------------------------------------------------------------------------- 1 | 2 | import TracepointManager from '../../../../../manager/TracepointManager'; 3 | import { BrokerResponse, UpdateTracePointResponse, UpdateTracePointRequest, Tracepoint } from '../../../../../../../types'; 4 | import TracepointRequestHandler from './TracepointRequestHandler'; 5 | import Logger from '../../../../../../../logger'; 6 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 7 | 8 | export default class UpdateTracePointRequestHandler extends TracepointRequestHandler { 9 | constructor(tracepointManager: TracepointManager) { 10 | super(tracepointManager); 11 | } 12 | 13 | handle(updateTracePointRequest: UpdateTracePointRequest): BrokerResponse { 14 | Logger.debug(` Handling update tracepoint message. ${updateTracePointRequest.tracePointId}`); 15 | 16 | const response = new UpdateTracePointResponse(updateTracePointRequest.id, updateTracePointRequest.client); 17 | try { 18 | const tracepoint = { 19 | ...updateTracePointRequest, 20 | id: updateTracePointRequest.tracePointId, 21 | tracingEnabled: updateTracePointRequest.enableTracing, 22 | } as Tracepoint; 23 | 24 | this.tracepointManager.updateTracePoint(tracepoint); 25 | 26 | CommunicationManager.sendApplicationStatusEvent(); 27 | if (updateTracePointRequest.client) { 28 | CommunicationManager.sendApplicationStatusEvent(updateTracePointRequest.client) 29 | } 30 | } catch (error) { 31 | Logger.debug(` An error occured while handling update message. ${error.message}`); 32 | response.setError(error); 33 | } finally { 34 | return response; 35 | } 36 | }; 37 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/response/ResponseHandlerContainer.ts: -------------------------------------------------------------------------------- 1 | import Handler from "../../../../../../handler/Handler"; 2 | import { BrokerResponse, ResponseName } from "../../../../../../types"; 3 | import HandlerContainer from "../../../../../../handler/HandlerContainer"; 4 | import FilterTracePointsResponseHandler from "./tracepoint/FilterTracePointsResponseHandler"; 5 | import FilterLogPointsResponseHandler from "./logpoint/FilterLogPointsResponseHandler"; 6 | import GetConfigResponseHandler from './config/GetConfigResponseHandler'; 7 | import TracepointManager from "../../../../manager/TracepointManager"; 8 | import LogpointManager from "../../../../manager/LogpointManager"; 9 | import ConfigManager from "../../../../manager/ConfigManager"; 10 | 11 | export default class ResponseHandlerContainer implements HandlerContainer { 12 | private responseHandlerMap: Map; 13 | 14 | constructor( 15 | tracepointManager: TracepointManager, 16 | logpointManager: LogpointManager, 17 | configManager: ConfigManager) { 18 | this.responseHandlerMap = new Map([ 19 | ['FilterTracePointsResponse', new FilterTracePointsResponseHandler(tracepointManager)], 20 | ['FilterLogPointsResponse', new FilterLogPointsResponseHandler(logpointManager)], 21 | ['GetConfigResponse', new GetConfigResponseHandler(configManager)], 22 | ]); 23 | } 24 | 25 | getHandler(brokerResponse: BrokerResponse): Handler { 26 | return this.responseHandlerMap.get(brokerResponse.name); 27 | } 28 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/response/config/ConfigResponseHandler.ts: -------------------------------------------------------------------------------- 1 | import ConfigManager from '../../../../../manager/ConfigManager'; 2 | import { BrokerConfigHandler } from "../../BrokerHandler"; 3 | 4 | export default abstract class ConfigResponseHandler extends BrokerConfigHandler { 5 | constructor(configManager: ConfigManager) { 6 | super(configManager); 7 | } 8 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/response/config/GetConfigResponseHandler.ts: -------------------------------------------------------------------------------- 1 | import ConfigManager from '../../../../../manager/ConfigManager'; 2 | import { GetConfigResponse } from '../../../../../../../types'; 3 | import ConfigResponseHandler from './ConfigResponseHandler'; 4 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 5 | import Logger from '../../../../../../../logger'; 6 | 7 | export default class GetConfigResponseHandler extends ConfigResponseHandler { 8 | constructor(configManager: ConfigManager) { 9 | super(configManager); 10 | } 11 | 12 | handle(getConfigResponse: GetConfigResponse): void { 13 | Logger.debug(` Handling get config response message. ${getConfigResponse.requestId}`); 14 | 15 | try { 16 | this.configManager.updateConfig(getConfigResponse.config); 17 | 18 | CommunicationManager.sendApplicationStatusEvent(); 19 | if (getConfigResponse.client) { 20 | CommunicationManager.sendApplicationStatusEvent(getConfigResponse.client) 21 | } 22 | } catch (error) { 23 | Logger.debug(` An error occured while handling get config response message. ${error.message}`); 24 | } 25 | }; 26 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/response/logpoint/FilterLogPointsResponseHandler.ts: -------------------------------------------------------------------------------- 1 | 2 | import LogpointManager from '../../../../../manager/LogpointManager'; 3 | import { FilterLogPointsResponse, Logpoint } from '../../../../../../../types'; 4 | import LogPointsResponseHandler from './LogPointsResponseHandler'; 5 | import Logger from '../../../../../../../logger'; 6 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 7 | 8 | export default class FilterLogPointsResponseHandler extends LogPointsResponseHandler { 9 | constructor(logpointManager: LogpointManager) { 10 | super(logpointManager); 11 | } 12 | 13 | handle(filterLogPointsResponse: FilterLogPointsResponse): void { 14 | Logger.debug(` Handling filter logpoint message. ${filterLogPointsResponse.requestId}`); 15 | 16 | try { 17 | filterLogPointsResponse.logPoints.forEach(logpoint => { 18 | this.logpointManager.putLogpoint({ 19 | ...logpoint, 20 | id: logpoint.id, 21 | fileName: this.extractFileName(logpoint.fileName), 22 | lineNo: logpoint.lineNo, 23 | } as Logpoint); 24 | }); 25 | 26 | CommunicationManager.sendApplicationStatusEvent(); 27 | if (filterLogPointsResponse.client) { 28 | CommunicationManager.sendApplicationStatusEvent(filterLogPointsResponse.client) 29 | } 30 | } catch (error) { 31 | Logger.debug(` An error occured while handling filter message. ${error.message}`); 32 | } 33 | }; 34 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/response/logpoint/LogPointsResponseHandler.ts: -------------------------------------------------------------------------------- 1 | import LogpointManager from "../../../../../manager/LogpointManager"; 2 | import { BrokerLogPointHandler } from "../../BrokerHandler"; 3 | 4 | export default abstract class LogPointsResponseHandler extends BrokerLogPointHandler { 5 | constructor(logpointManager: LogpointManager) { 6 | super(logpointManager); 7 | } 8 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/response/tracepoint/FilterTracePointsResponseHandler.ts: -------------------------------------------------------------------------------- 1 | 2 | import TracepointManager from '../../../../../manager/TracepointManager'; 3 | import { FilterTracePointsResponse, Tracepoint } from '../../../../../../../types'; 4 | import TracePointsResponseHandler from './TracePointsResponseHandler'; 5 | import Logger from '../../../../../../../logger'; 6 | import CommunicationManager from '../../../../../communication/CommunicationManager'; 7 | 8 | export default class FilterTracePointsResponseHandler extends TracePointsResponseHandler { 9 | constructor(tracepointManager: TracepointManager) { 10 | super(tracepointManager); 11 | } 12 | 13 | handle(filterTracePointsResponse: FilterTracePointsResponse): void { 14 | Logger.debug(` Handling filter tracepoint message. ${filterTracePointsResponse.requestId}`); 15 | 16 | try { 17 | filterTracePointsResponse.tracePoints.forEach(tracepoint => { 18 | this.tracepointManager.putTracepoint({ 19 | ...tracepoint, 20 | id: tracepoint.id, 21 | fileName: this.extractFileName(tracepoint.fileName), 22 | lineNo: tracepoint.lineNo, 23 | } as Tracepoint); 24 | }); 25 | 26 | CommunicationManager.sendApplicationStatusEvent(); 27 | if (filterTracePointsResponse.client) { 28 | CommunicationManager.sendApplicationStatusEvent(filterTracePointsResponse.client) 29 | } 30 | } catch (error) { 31 | Logger.debug(` An error occured while handling filter message. ${error.message}`); 32 | } 33 | }; 34 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/communication/broker/handler/response/tracepoint/TracePointsResponseHandler.ts: -------------------------------------------------------------------------------- 1 | import TracepointManager from "../../../../../manager/TracepointManager"; 2 | import { BrokerTracePointHandler } from "../../BrokerHandler"; 3 | 4 | export default abstract class TracePointsResponseHandler extends BrokerTracePointHandler { 5 | constructor(tracepointManager: TracepointManager) { 6 | super(tracepointManager); 7 | } 8 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/manager/ConfigManager.ts: -------------------------------------------------------------------------------- 1 | import DebugApi from "src/api/internal/debug/DebugApi"; 2 | import ConfigProvider from "../../../config/ConfigProvider"; 3 | import CommunicationManager from "../communication/CommunicationManager"; 4 | import CommunicationUtils from "../../../utils/CommunicationUtils"; 5 | 6 | export default interface ConfigManager { 7 | makeSilent(): void; 8 | makeUnsilent(): void; 9 | updateConfig(candidateConfig: { [key: string]: any }): void; 10 | } 11 | 12 | export class DefaultConfigManager implements ConfigManager { 13 | protected debugApi: DebugApi; 14 | 15 | constructor(debugApi: DebugApi) { 16 | this.debugApi = debugApi; 17 | } 18 | 19 | makeSilent(): void { 20 | ConfigProvider.update({ 'silent': true }); 21 | this.debugApi.close(); 22 | } 23 | 24 | makeUnsilent(): void { 25 | ConfigProvider.update({ 'silent': false }); 26 | this.debugApi.connect(); 27 | CommunicationManager.sendRequest(CommunicationUtils.createTracepointFilterRequest()); 28 | CommunicationManager.sendRequest(CommunicationUtils.createLogpointFilterRequest()); 29 | CommunicationManager.sendRequest(CommunicationUtils.createGetConfigRequest()); 30 | } 31 | 32 | updateConfig(candidateConfig: { [key: string]: any }) { 33 | ConfigProvider.update(candidateConfig); 34 | const errorCollectionEnable = candidateConfig.errorCollectionEnable; 35 | if (errorCollectionEnable != undefined) { 36 | errorCollectionEnable ? this.debugApi.activateErrorCollection() : this.debugApi.deactivateErrorCollection(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/external/manager/TagManager.ts: -------------------------------------------------------------------------------- 1 | import DebugApi from "../../../api/internal/debug/DebugApi"; 2 | 3 | export default interface TagManager { 4 | enableTag(tag: string): void; 5 | disableTag(tag: string): void; 6 | removeTag(tag: string): void; 7 | } 8 | 9 | export class DefaultTagManager implements TagManager { 10 | protected debugApi: DebugApi; 11 | 12 | constructor(debugApi: DebugApi){ 13 | this.debugApi = debugApi; 14 | } 15 | 16 | enableTag(tag: string): void { 17 | const probeIds = this.debugApi.getByTag(tag); 18 | (probeIds || []).forEach(probeId => { 19 | const probe = this.debugApi.get(probeId); 20 | this.debugApi.enable(probe); 21 | }); 22 | } 23 | 24 | disableTag(tag: string): void { 25 | const probeIds = this.debugApi.getByTag(tag); 26 | (probeIds || []).forEach(probeId => { 27 | const probe = this.debugApi.get(probeId); 28 | this.debugApi.disable(probe); 29 | }); 30 | } 31 | 32 | removeTag(tag: string): void { 33 | this.debugApi.removeTag(tag); 34 | } 35 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/debug/action/ConditionAwareProbeAction.ts: -------------------------------------------------------------------------------- 1 | import * as inspector from 'inspector'; 2 | import V8InspectorApi from "../../v8/V8inspectorApi"; 3 | import ProbeAction, { DelegatedProbeAction } from "../probe/ProbeAction"; 4 | import ProbeContext from "../probe/ProbeContext"; 5 | import Logger from '../../../../logger'; 6 | 7 | export default class ConditionAwareProbeAction extends DelegatedProbeAction { 8 | protected v8InspectorApi?: V8InspectorApi; 9 | 10 | constructor(action: ProbeAction, v8InspectorApi?: V8InspectorApi) { 11 | super(action); 12 | 13 | this.v8InspectorApi = v8InspectorApi; 14 | } 15 | 16 | onProbe(message: inspector.InspectorNotification) { 17 | if (!this.checkCondition(message.params.callFrames[0])) { 18 | Logger.debug(` Condition did not valid.`); 19 | return; 20 | } 21 | 22 | super.onProbe(message); 23 | } 24 | 25 | private checkCondition(callFrame: inspector.Debugger.CallFrame): boolean { 26 | const context: C = this.getContext(); 27 | 28 | let checkResult = true; 29 | if (context != null) { 30 | const condition = context.getCondition(); 31 | if (condition) { 32 | try { 33 | const result = this.v8InspectorApi.evaluateOnCallFrame({ 34 | callFrameId: callFrame.callFrameId, 35 | expression: condition, 36 | returnByValue: true, 37 | throwOnSideEffect: true, 38 | }); 39 | 40 | if (result && result.response && result.response.result && 41 | result.response.result.subtype !== 'error' && result.response.result.value == false) { 42 | checkResult = false; 43 | } 44 | } catch (error) { 45 | Logger.debug(` An error occured while evaluating condtion.`); 46 | checkResult = false; 47 | } 48 | } 49 | } 50 | 51 | return checkResult; 52 | } 53 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/debug/action/ErrorRateLimitedProbeAction.ts: -------------------------------------------------------------------------------- 1 | import * as inspector from 'inspector'; 2 | import ProbeAction, { DelegatedProbeAction } from "../probe/ProbeAction"; 3 | import ProbeContext from "../probe/ProbeContext"; 4 | import Logger from '../../../../logger'; 5 | import RateLimiter, { DefaultRateLimiter } from '../../../../limit/rate/RateLimiter'; 6 | import { ConfigNames } from "../../../../config/ConfigNames"; 7 | import ConfigProvider from "../../../../config/ConfigProvider"; 8 | 9 | import LRU from 'lru-cache'; 10 | 11 | export default class ErrorRateLimitedProbeAction extends DelegatedProbeAction { 12 | protected rateLimiterLruCache: LRU; 13 | protected inMinute: number; 14 | 15 | constructor(action: ProbeAction, inMinute?: number) { 16 | super(action); 17 | 18 | this.rateLimiterLruCache = new LRU({ 19 | max: 100, 20 | maxSize: 100, 21 | ttl: 1000 * 60 * 5, 22 | allowStale: false, 23 | updateAgeOnGet: true, 24 | updateAgeOnHas: true, 25 | sizeCalculation: (value: RateLimiter, key: string) => { 26 | // return an positive integer which is the size of the item, 27 | // if a positive integer is not returned, will use 0 as the size. 28 | return 1; 29 | }, 30 | }); 31 | 32 | this.inMinute = inMinute || ConfigProvider.get(ConfigNames.errorCollection.rateLimit.pointInMinute); 33 | } 34 | 35 | onProbe(message: inspector.InspectorNotification) { 36 | if (this.checkRateLimited(message)) { 37 | Logger.debug(` Rate limit exceeded.`); 38 | return; 39 | } 40 | 41 | super.onProbe(message); 42 | } 43 | 44 | private checkRateLimited(message: inspector.InspectorNotification): boolean { 45 | const firstFrame = message.params.callFrames[0]; 46 | const scriptLineIdentifier = `${firstFrame.location.scriptId}:${firstFrame.location.lineNumber}`; 47 | let rateLimiter = this.rateLimiterLruCache.get(scriptLineIdentifier); 48 | if (!rateLimiter) { 49 | rateLimiter = new DefaultRateLimiter(this.inMinute); 50 | this.rateLimiterLruCache.set(scriptLineIdentifier, rateLimiter); 51 | } 52 | 53 | return rateLimiter.checkRateLimit(new Date().getTime()) === 'EXCEEDED'; 54 | } 55 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/debug/action/ExpiringProbeAction.ts: -------------------------------------------------------------------------------- 1 | import * as inspector from 'inspector'; 2 | import ProbeAction, { DelegatedProbeAction } from "../probe/ProbeAction"; 3 | import ProbeContext from "../probe/ProbeContext"; 4 | import Logger from '../../../../logger'; 5 | 6 | export default class ExpiringProbeAction extends DelegatedProbeAction { 7 | constructor(action: ProbeAction ) { 8 | super(action); 9 | } 10 | 11 | isExpired(): boolean { 12 | const context: C = this.getContext(); 13 | return context.isExpired(); 14 | } 15 | 16 | onProbe(message: inspector.InspectorNotification) { 17 | if (this.isExpired()) { 18 | Logger.debug(` Probe expired.`); 19 | return; 20 | } 21 | 22 | super.onProbe(message); 23 | } 24 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/debug/action/ProbeActionFactory.ts: -------------------------------------------------------------------------------- 1 | import ScriptStore from "../../../../store/script/ScriptStore"; 2 | import V8InspectorApi from "../../v8/V8inspectorApi"; 3 | import { ProbeInfo } from "../../../../types"; 4 | import ConditionAwareProbeAction from "./ConditionAwareProbeAction"; 5 | import TracePointAction from "../tracepoint/TracePointAction"; 6 | import TracePointContext from "../tracepoint/TracePointContext"; 7 | import ProbeAction from "../probe/ProbeAction"; 8 | import ProbeContext from "../probe/ProbeContext"; 9 | import ExpiringProbeAction from "./ExpiringProbeAction"; 10 | import RateLimitedProbeAction from "./RateLimitedProbeAction"; 11 | import LogPointAction from "../logpoint/LogPointAction"; 12 | import LogPointContext from "../logpoint/LogPointContext"; 13 | import ErrorStackAction from "../error/ErrorStackAction"; 14 | import ErrorStackContext from "../error/ErrorStackContext"; 15 | import { ConfigNames } from "../../../../config/ConfigNames"; 16 | import ConfigProvider from "../../../../config/ConfigProvider"; 17 | import { DefaultRateLimiter } from '../../../../limit/rate/RateLimiter'; 18 | import ErrorRateLimitedProbeAction from "./ErrorRateLimitedProbeAction"; 19 | import { ProbeActionType } from '../../../../types'; 20 | 21 | const actions: Record(delegatedAction: ProbeAction) => ProbeAction> = { 22 | 'ConditionAwareProbeAction': (delegatedAction) => { 23 | return new ConditionAwareProbeAction(delegatedAction); 24 | }, 25 | 'RateLimitedProbeAction': (delegatedAction) => { 26 | return new RateLimitedProbeAction( 27 | delegatedAction, 28 | new DefaultRateLimiter(ConfigProvider.get(ConfigNames.rateLimit.inMinute)) 29 | ); 30 | }, 31 | 'ErrorRateLimitedProbeAction': (delegatedAction) => { 32 | return new ErrorRateLimitedProbeAction(delegatedAction); 33 | }, 34 | 'ExpiringProbeAction': (delegatedAction) => { 35 | return new ExpiringProbeAction(delegatedAction); 36 | }, 37 | } 38 | 39 | export default class ProbeActionFactory { 40 | static getAction( 41 | probeInfo: ProbeInfo, 42 | scriptStore: ScriptStore, 43 | v8InspectorApi: V8InspectorApi 44 | ): ProbeAction { 45 | let probeAction: ProbeAction; 46 | switch(probeInfo.probe.type) { 47 | case 'Tracepoint': 48 | probeAction = new TracePointAction( 49 | new TracePointContext( 50 | probeInfo.v8BreakpointId, 51 | probeInfo.probe, 52 | probeInfo.generatedPosition, 53 | ), 54 | scriptStore, 55 | v8InspectorApi); 56 | 57 | break; 58 | case 'Logpoint': 59 | probeAction = new LogPointAction( 60 | new LogPointContext( 61 | probeInfo.v8BreakpointId, 62 | probeInfo.probe, 63 | probeInfo.generatedPosition, 64 | ), 65 | scriptStore, 66 | v8InspectorApi); 67 | 68 | break; 69 | case 'ErrorStack': 70 | probeAction = new ErrorStackAction( 71 | new ErrorStackContext( 72 | probeInfo.v8BreakpointId, 73 | probeInfo.probe, 74 | ), 75 | scriptStore, 76 | v8InspectorApi); 77 | 78 | break; 79 | default: 80 | return; 81 | } 82 | 83 | probeInfo.probe.actions.forEach(action => { 84 | const actionWrapper = actions[action]; 85 | if (actionWrapper) { 86 | probeAction = actionWrapper(probeAction); 87 | } 88 | }); 89 | 90 | return probeAction; 91 | } 92 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/debug/action/RateLimitedProbeAction.ts: -------------------------------------------------------------------------------- 1 | import * as inspector from 'inspector'; 2 | import CommunicationManager from '../../../external/communication/CommunicationManager'; 3 | import RateLimiter from '../../../../limit/rate/RateLimiter'; 4 | import ProbeAction, { DelegatedProbeAction } from "../probe/ProbeAction"; 5 | import ProbeContext from "../probe/ProbeContext"; 6 | import { ProbeRateLimitEvent } from '../../../../types'; 7 | import UuidUtils from '../../../../utils/UuidUtils'; 8 | import Logger from '../../../../logger'; 9 | 10 | export default class RateLimitedProbeAction extends DelegatedProbeAction { 11 | protected rateLimiter: RateLimiter; 12 | 13 | constructor(action: ProbeAction, rateLimiter: RateLimiter) { 14 | super(action); 15 | 16 | this.rateLimiter = rateLimiter; 17 | } 18 | 19 | onProbe(message: inspector.InspectorNotification) { 20 | if (this.checkRateLimited()) { 21 | Logger.debug(` Rate limit exceeded.`); 22 | return; 23 | } 24 | 25 | super.onProbe(message); 26 | } 27 | 28 | private checkRateLimited(): boolean { 29 | const rateLimitResult = this.rateLimiter.checkRateLimit(new Date().getTime()); 30 | if (rateLimitResult === 'HIT') { 31 | Logger.debug(` Rate limit hitted.`); 32 | 33 | const context: C = this.getContext(); 34 | if (context) { 35 | CommunicationManager.sendEvent( 36 | new ProbeRateLimitEvent( 37 | UuidUtils.generateId(), 38 | context.getFileName(), 39 | context.getLineNo(), 40 | context.getClient(), 41 | )); 42 | } 43 | 44 | } 45 | 46 | return rateLimitResult === 'EXCEEDED'; 47 | } 48 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/debug/converter/CaptureFrameConverter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CaptureConfig, 3 | CaptureFrame, 4 | Frame, 5 | PropertyAccessClassification, 6 | } from '../../../../types'; 7 | import TypeCastUtils from '../../../../utils/TypeCastUtils'; 8 | import PropertyAccessClassificationUtils from '../../../../utils/PropertyAccessClassificationUtils'; 9 | import ConfigProvider from '../../../../config/ConfigProvider'; 10 | import { ConfigNames } from '../../../../config/ConfigNames'; 11 | import Logger from '../../../../logger'; 12 | 13 | export default class CaptureFrameConverter { 14 | convert(captureFrames: CaptureFrame[] = []): Frame[] { 15 | const frames: Frame[] = []; 16 | 17 | captureFrames.forEach(captureFrame => { 18 | const { 19 | lineNo, 20 | methodName, 21 | className, 22 | locals, 23 | } = captureFrame; 24 | 25 | let frame = { 26 | lineNo, 27 | methodName, 28 | className, 29 | variables: {}, 30 | } as Frame; 31 | 32 | frames.push(frame); 33 | if (locals) { 34 | const resolveVariable = this.findResolvedVariable( 35 | locals, 36 | { 37 | maxProperties: ConfigProvider.get(ConfigNames.capture.maxProperties), 38 | maxParseDepth: ConfigProvider.get(ConfigNames.capture.maxParseDepth), 39 | } as CaptureConfig); 40 | if (resolveVariable && !Array.isArray(resolveVariable)) { 41 | frame.variables = resolveVariable; 42 | } 43 | } 44 | }) 45 | 46 | return frames; 47 | } 48 | 49 | private findResolvedVariable( 50 | locals: { [key: string]: any } | any[], 51 | config: CaptureConfig, 52 | iteration = 1, 53 | ) { 54 | const arrayParser = (members: any[]) => { 55 | const _members = members.slice(0, config.maxProperties); 56 | const parsedArr = []; 57 | const mLenght = _members.length; 58 | for (let i = 0; i < mLenght; i++) { 59 | let value = _members[i]; 60 | const type = !ConfigProvider.get(ConfigNames.sourceCode.minified) && value && value.constructor 61 | ? value.constructor.name || typeof value 62 | : typeof value; 63 | if (TypeCastUtils.isObject(value)) { 64 | if (Object.getOwnPropertyNames(value).length == 0) { 65 | try { 66 | value = value + ''; 67 | } catch (error) { 68 | Logger.debug(` An error occured while parsing 69 | with type ${type}: ${error.message}`); 70 | value = ''; 71 | } 72 | } else { 73 | value = iteration > config.maxParseDepth 74 | ? '...' 75 | : this.findResolvedVariable(value, config, iteration + 1); 76 | } 77 | } 78 | 79 | const arrayFlag = Array.isArray(value); 80 | parsedArr.push({ 81 | '@type': arrayFlag ? 'array': type, 82 | '@value': value, 83 | ...( arrayFlag ? { '@array': true } : undefined ) 84 | }) 85 | } 86 | 87 | return parsedArr; 88 | } 89 | 90 | const objectParser = (members: { [key: string]: any }) => { 91 | const parsedVariable: { [key: string]: any } = {}; 92 | const properties = PropertyAccessClassificationUtils.getProperties( 93 | members, 94 | ConfigProvider.get(ConfigNames.capture.propertyAccessClassification)); 95 | 96 | const oLenght = Math.min(properties.length, config.maxProperties); 97 | for (let i = 0; i < oLenght; i++) { 98 | const member = properties[i]; 99 | const name = member; 100 | let value = members[member]; 101 | const type = !ConfigProvider.get(ConfigNames.sourceCode.minified) && value && value.constructor 102 | ? value.constructor.name || typeof value 103 | : typeof value; 104 | if (!parsedVariable[name]) { 105 | if (TypeCastUtils.isObject(value)) { 106 | if (Object.getOwnPropertyNames(value).length == 0) { 107 | try { 108 | value = value + ''; 109 | } catch (error) { 110 | Logger.debug(` An error occured while parsing 111 | field ${name} with type ${type}: ${error.message}`); 112 | value = ''; 113 | } 114 | } else { 115 | value = iteration > config.maxParseDepth 116 | ? '...' 117 | : this.findResolvedVariable(value, config, iteration + 1); 118 | } 119 | } 120 | 121 | const arrayFlag = Array.isArray(value); 122 | parsedVariable[name] = { 123 | '@type': arrayFlag ? 'array' : type, 124 | '@value': value, 125 | ...( arrayFlag ? { '@array': true } : undefined ) 126 | } 127 | } 128 | } 129 | 130 | return parsedVariable; 131 | } 132 | 133 | return Array.isArray(locals) ? arrayParser(locals) : objectParser(locals); 134 | } 135 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/debug/error/ErrorStackAction.ts: -------------------------------------------------------------------------------- 1 | import * as inspector from 'inspector'; 2 | import { 3 | CaptureFrame, 4 | ErrorStackSnapshotEvent, 5 | ProbeType 6 | } from '../../../../types'; 7 | import ScriptStore from '../../../../store/script/ScriptStore'; 8 | import V8InspectorApi from '../../v8/V8inspectorApi'; 9 | import CaptureProbeAction from '../probe/CaptureProbeAction'; 10 | import ErrorStackContext from './ErrorStackContext'; 11 | import Logger from '../../../../logger'; 12 | import { ConfigNames } from '../../../../config/ConfigNames'; 13 | import ConfigProvider from '../../../../config/ConfigProvider'; 14 | import CommunicationManager from '../../../external/communication/CommunicationManager'; 15 | 16 | export default class ErrorStackAction extends CaptureProbeAction { 17 | protected captureFrame: boolean; 18 | 19 | constructor( 20 | context: ErrorStackContext, 21 | scriptStore: ScriptStore, 22 | v8InspectorApi: V8InspectorApi 23 | ) { 24 | super(context, scriptStore, v8InspectorApi); 25 | 26 | this.captureFrame = ConfigProvider.get(ConfigNames.errorCollection.captureFrame); 27 | } 28 | 29 | getType(): ProbeType { 30 | return 'ErrorStack'; 31 | } 32 | 33 | onProbe(message: inspector.InspectorNotification): void { 34 | Logger.debug(' Error stack probe action working ...'); 35 | 36 | try { 37 | this.handleErrorStack( 38 | message, 39 | this.captureFrame ? this.resolveFrames(message.params.callFrames): undefined); 40 | } catch (error) { 41 | Logger.debug(` An error occured while resolving frames ${error.message}`); 42 | } 43 | } 44 | 45 | protected handleErrorStack( 46 | message: inspector.InspectorNotification, 47 | frames: CaptureFrame[]) { 48 | setImmediate(() => { 49 | try { 50 | let fileName; 51 | let lineNo; 52 | let methodName; 53 | let convertedFrames; 54 | const { params } = message; 55 | const callFrame = params.callFrames[0]; 56 | const currentDate = new Date().toISOString(); 57 | if (frames) { 58 | const userFrames = this.prepareFrames(frames); 59 | if (!userFrames) { 60 | return; 61 | } 62 | 63 | convertedFrames = this.captureFrameConverter.convert(userFrames); 64 | ({ className: fileName, lineNo, methodName} = convertedFrames[0]); 65 | } else { 66 | ({ path: fileName, line: lineNo } = this.resolveLocation(callFrame)); 67 | methodName = this.resolveFunctionName(callFrame); 68 | } 69 | 70 | let error = {} as Error; 71 | if (params.data) { 72 | const errorInfo = params.data as any; 73 | if (errorInfo) { 74 | error.name = errorInfo['className']; 75 | error.stack = errorInfo['description']; 76 | if (error.stack) { 77 | error.message = error.stack.substring(0, error.stack.indexOf("\n")) 78 | } 79 | } 80 | } 81 | 82 | const snapshotEvent = new ErrorStackSnapshotEvent( 83 | `${fileName}:${lineNo}:${currentDate}`, 84 | fileName, 85 | methodName, 86 | error, 87 | convertedFrames, 88 | fileName, 89 | lineNo, 90 | ); 91 | 92 | CommunicationManager.sendEvent(snapshotEvent); 93 | } catch (error) { 94 | Logger.debug(` An error occured while handling error stack ${error.message}`); 95 | } 96 | }); 97 | } 98 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/debug/error/ErrorStackContext.ts: -------------------------------------------------------------------------------- 1 | import * as inspector from 'inspector'; 2 | import { 3 | Probe, 4 | ProbeType 5 | } from "../../../../types"; 6 | import { DefaultProbeContext } from "../probe/ProbeContext"; 7 | 8 | export default class ErrorPointContext extends DefaultProbeContext { 9 | constructor( 10 | v8BreakpointId: inspector.Debugger.BreakpointId, 11 | rawProbe: Probe, 12 | ) { 13 | super(v8BreakpointId, rawProbe); 14 | } 15 | 16 | getProbeAction(): ProbeType { 17 | return 'ErrorStack'; 18 | } 19 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/debug/logpoint/LogPointContext.ts: -------------------------------------------------------------------------------- 1 | import * as inspector from 'inspector'; 2 | import { 3 | Probe, 4 | ProbeType, 5 | SourceLocation 6 | } from "../../../../types"; 7 | import { DefaultProbeContext } from "../probe/ProbeContext"; 8 | 9 | export default class LogPointContext extends DefaultProbeContext { 10 | constructor( 11 | v8BreakpointId: inspector.Debugger.BreakpointId, 12 | rawProbe: Probe, 13 | generatedPosition?: SourceLocation, 14 | ) { 15 | super(v8BreakpointId, rawProbe, generatedPosition); 16 | } 17 | 18 | getProbeAction(): ProbeType { 19 | return 'Logpoint'; 20 | } 21 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/debug/probe/ProbeAction.ts: -------------------------------------------------------------------------------- 1 | import * as inspector from 'inspector'; 2 | import { 3 | Probe, 4 | ProbeType 5 | } from '../../../../types'; 6 | import ProbeContext from "./ProbeContext"; 7 | import ScriptStore from "../../../../store/script/ScriptStore"; 8 | import V8InspectorApi from "../../v8/V8inspectorApi"; 9 | 10 | export default interface ProbeAction { 11 | getId(): string; 12 | getV8BreakpointId(): string; 13 | getLocationId(): string; 14 | getProbe(): Probe; 15 | updateProbe(probe: Probe): void; 16 | getType(): ProbeType; 17 | getClient(): string; 18 | getContext(): C; 19 | enable(): void; 20 | disable(): void; 21 | isDisabled(): boolean; 22 | isExpired(): boolean; 23 | onProbe(message: inspector.InspectorNotification): void; 24 | } 25 | 26 | export abstract class DefaultProbeAction implements ProbeAction { 27 | protected context: C; 28 | protected scriptStore: ScriptStore; 29 | protected v8InspectorApi: V8InspectorApi; 30 | 31 | constructor( 32 | context: C, 33 | scriptStore: ScriptStore, 34 | v8InspectorApi: V8InspectorApi 35 | ) { 36 | this.context = context; 37 | this.scriptStore = scriptStore; 38 | this.v8InspectorApi = v8InspectorApi; 39 | } 40 | 41 | getId(): string { 42 | return this.context.getProbe().id; 43 | } 44 | 45 | getV8BreakpointId(): string { 46 | return this.context.getV8BreakpointId(); 47 | } 48 | 49 | getProbe(): Probe { 50 | return this.context.getProbe(); 51 | } 52 | 53 | getLocationId(): string { 54 | return this.context.getLocationId(); 55 | } 56 | 57 | getClient(): string { 58 | return this.context.getProbe().client; 59 | } 60 | 61 | getContext(): C { 62 | return this.context; 63 | } 64 | 65 | enable(): void { 66 | this.context.getProbe().enabled = true; 67 | } 68 | 69 | disable(): void { 70 | this.context.getProbe().enabled = false; 71 | } 72 | 73 | isDisabled(): boolean { 74 | return this.context.getProbe().enabled == false; 75 | } 76 | 77 | isExpired(): boolean { 78 | return this.context.isExpired(); 79 | } 80 | 81 | updateProbe(probe: Probe): void { 82 | const rawProbe = this.context.getProbe(); 83 | rawProbe.tracingEnabled = probe.tracingEnabled; 84 | if (probe.condition) { 85 | rawProbe.condition = probe.condition; 86 | } else if(rawProbe.condition) { 87 | rawProbe.condition = null; 88 | } 89 | 90 | if (probe.expireSecs) { 91 | rawProbe.expireSecs = probe.expireSecs; 92 | } 93 | 94 | if (probe.expireCount) { 95 | rawProbe.expireCount = probe.expireCount; 96 | } 97 | 98 | if (probe.enabled != null) { 99 | rawProbe.enabled = probe.enabled; 100 | } 101 | } 102 | 103 | abstract onProbe(message: inspector.InspectorNotification): void; 104 | abstract getType(): ProbeType; 105 | } 106 | 107 | export abstract class DelegatedProbeAction implements ProbeAction { 108 | private action: ProbeAction; 109 | 110 | constructor(action: ProbeAction) { 111 | this.action = action; 112 | } 113 | 114 | getId(): string { 115 | return this.action.getId(); 116 | } 117 | 118 | getV8BreakpointId(): string { 119 | return this.action.getV8BreakpointId(); 120 | } 121 | 122 | getLocationId(): string { 123 | return this.action.getLocationId(); 124 | } 125 | 126 | getProbe(): Probe { 127 | return this.action.getProbe(); 128 | } 129 | 130 | updateProbe(probe: Probe): void { 131 | this.action.updateProbe(probe); 132 | } 133 | 134 | getType(): ProbeType { 135 | return this.action.getType(); 136 | } 137 | 138 | getClient(): string { 139 | return this.action.getClient(); 140 | } 141 | 142 | getContext(): C { 143 | return this.action.getContext(); 144 | } 145 | 146 | enable(): void { 147 | return this.action.enable(); 148 | } 149 | 150 | disable(): void { 151 | return this.action.disable(); 152 | } 153 | 154 | isDisabled(): boolean { 155 | return this.action.isDisabled(); 156 | } 157 | 158 | isExpired(): boolean { 159 | return this.action.isExpired(); 160 | } 161 | 162 | onProbe(message: inspector.InspectorNotification): void { 163 | this.action.onProbe(message); 164 | } 165 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/debug/probe/ProbeContext.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Probe, 3 | ProbeType, 4 | SourceLocation, 5 | } from "../../../../types"; 6 | import * as inspector from 'inspector'; 7 | import ProbeUtils from '../../../../utils/ProbeUtils'; 8 | 9 | export class Backchannel { 10 | private store: Map; 11 | private index: number; 12 | 13 | constructor() { 14 | this.store = new Map(); 15 | this.index = 0; 16 | } 17 | 18 | add() { 19 | this.index++; 20 | 21 | this.store.set(this.index, {}); 22 | 23 | return this.index; 24 | } 25 | 26 | get(index: number) { 27 | return this.store.get(index); 28 | } 29 | 30 | delete(index: number) { 31 | this.store.delete(index); 32 | } 33 | 34 | clear() { 35 | this.store.clear(); 36 | } 37 | } 38 | 39 | export const extractor = `(...arguments) => global.__sidekick_copy(arguments)`; 40 | 41 | (process as any).__sidekick_backchannel = new Backchannel(); 42 | 43 | (global as any).__sidekick_copy = function copy([index, _this, ...scopes]: [index: number, _this: any, scopes: []]) { 44 | const backChannel = (process as any).__sidekick_backchannel.get(index); 45 | 46 | backChannel.this = _this; 47 | backChannel.scopes = scopes; 48 | }; 49 | 50 | export default interface ProbeContext { 51 | getV8BreakpointId(): string; 52 | getLocationId(): string; 53 | getClient(): string; 54 | getFileName(): string; 55 | getLineNo(): number; 56 | getCondition(): string; 57 | getProbe(): Probe; 58 | getTags(): string[]; 59 | getGeneratedPosition(): SourceLocation; 60 | getHitCount(): number; 61 | isExpired(): boolean; 62 | hit(): void; 63 | } 64 | 65 | export abstract class DefaultProbeContext implements ProbeContext { 66 | readonly probeId: string; 67 | readonly v8BreakpointId: inspector.Debugger.BreakpointId; 68 | readonly rawProbe: Probe; 69 | readonly locationId: string; 70 | readonly generatedPosition?: SourceLocation; 71 | protected createdAt: number; 72 | protected hitCount: number; 73 | protected expiredAt: number; 74 | protected expirable: boolean; 75 | 76 | constructor( 77 | v8BreakpointId: inspector.Debugger.BreakpointId, 78 | rawProbe: Probe, 79 | generatedPosition?: SourceLocation, 80 | ) { 81 | this.probeId = rawProbe.id; 82 | this.v8BreakpointId = v8BreakpointId; 83 | this.rawProbe = rawProbe; 84 | this.locationId = ProbeUtils.generateLocationId(rawProbe); 85 | this.generatedPosition = generatedPosition; 86 | this.createdAt = new Date().getTime(); 87 | this.hitCount = 0; 88 | 89 | if (this.rawProbe.expireSecs) { 90 | this.expiredAt = this.createdAt + (this.rawProbe.expireSecs * 1000); 91 | } 92 | 93 | this.expirable = !(this.rawProbe.tags && this.rawProbe.tags.length); 94 | } 95 | 96 | getV8BreakpointId(): string { 97 | return this.v8BreakpointId; 98 | } 99 | 100 | getLocationId(): string { 101 | return this.locationId; 102 | } 103 | 104 | getClient(): string { 105 | return this.rawProbe.client; 106 | } 107 | 108 | getLineNo(): number { 109 | return this.generatedPosition ? this.generatedPosition.line : this.rawProbe.lineNo; 110 | } 111 | 112 | getFileName(): string { 113 | return this.generatedPosition ? this.generatedPosition.path : this.rawProbe.fileName; 114 | } 115 | 116 | getHitCount(): number { 117 | return this.hitCount; 118 | } 119 | 120 | hit(): void { 121 | this.hitCount++; 122 | } 123 | 124 | isExpired(): boolean { 125 | let result = false; 126 | if (this.expirable) { 127 | if (this.rawProbe.expireCount) { 128 | result = this.hitCount > this.rawProbe.expireCount 129 | } 130 | 131 | if (!result && this.expiredAt) { 132 | result = new Date().getTime() >= this.expiredAt; 133 | } 134 | } 135 | 136 | return result; 137 | } 138 | 139 | getGeneratedPosition(): SourceLocation { 140 | return this.generatedPosition; 141 | } 142 | 143 | getCondition(): string { 144 | return this.rawProbe.condition; 145 | } 146 | 147 | getProbe(): Probe { 148 | return this.rawProbe; 149 | } 150 | 151 | getTags(): string[] { 152 | return this.rawProbe.tags; 153 | } 154 | 155 | abstract getProbeAction(): ProbeType; 156 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/debug/tracepoint/TracePointAction.ts: -------------------------------------------------------------------------------- 1 | import * as inspector from 'inspector'; 2 | import { 3 | ProbeType, 4 | TracePointSnapshotEvent, 5 | TracePointSnapshotFailedEvent, 6 | CaptureFrame 7 | } from '../../../../types'; 8 | import ScriptStore from '../../../../store/script/ScriptStore'; 9 | import V8InspectorApi from '../../v8/V8inspectorApi'; 10 | import CaptureProbeAction from '../probe/CaptureProbeAction'; 11 | import TracePointContext from "./TracePointContext"; 12 | import CommunicationManager from '../../../external/communication/CommunicationManager'; 13 | import UuidUtils from '../../../../utils/UuidUtils'; 14 | import ProbeUtils from '../../../../utils/ProbeUtils'; 15 | import Logger from '../../../../logger'; 16 | import * as TraceInfoSupport from '../../../../trace/TraceInfoSupport'; 17 | import { TraceInfo } from '../../../../trace/TraceInfoResolver'; 18 | 19 | export default class TracePointAction extends CaptureProbeAction { 20 | constructor( 21 | context: TracePointContext, 22 | scriptStore: ScriptStore, 23 | v8InspectorApi: V8InspectorApi 24 | ) { 25 | super(context, scriptStore, v8InspectorApi); 26 | } 27 | 28 | getType(): ProbeType { 29 | return 'Tracepoint'; 30 | } 31 | 32 | onProbe(message: inspector.InspectorNotification): void { 33 | Logger.debug(' Tracepoint probe action working ...'); 34 | 35 | try { 36 | this.handleTracepoint( 37 | this.resolveFrames(message.params.callFrames), 38 | this.context.getProbe().tracingEnabled ? TraceInfoSupport.getTraceInfo(): undefined); 39 | } catch (error) { 40 | Logger.debug(` An error occured while resolving frames ${error.message}`); 41 | this.sendTracepointFailedEvent(error); 42 | } 43 | } 44 | 45 | protected handleTracepoint(frames: CaptureFrame[], traceInfo?: TraceInfo) { 46 | setImmediate(() => { 47 | try { 48 | const userFrames = this.prepareFrames(frames); 49 | if (!userFrames) { 50 | return; 51 | } 52 | 53 | const convertedFrames = this.captureFrameConverter.convert(userFrames); 54 | const snapshotEvent = new TracePointSnapshotEvent( 55 | this.context.rawProbe.id.replace(`${this.context.rawProbe.type}:`, ''), 56 | this.context.rawProbe.client, 57 | this.context.rawProbe.remoteFilename || this.context.rawProbe.fileName, 58 | convertedFrames && convertedFrames[0] ? convertedFrames[0].methodName : '', 59 | convertedFrames, 60 | this.context.rawProbe.fileName, 61 | this.context.rawProbe.lineNo, 62 | ); 63 | 64 | if (traceInfo) { 65 | Object.assign(snapshotEvent, traceInfo); 66 | } 67 | 68 | CommunicationManager.sendEvent(snapshotEvent); 69 | } catch (error) { 70 | Logger.debug(` An error occured while resolving frames ${error.message}`); 71 | this.sendTracepointFailedEvent(error); 72 | } 73 | }); 74 | } 75 | 76 | protected sendTracepointFailedEvent(error: any) { 77 | setImmediate(() => { 78 | try { 79 | const snapshotFailedEvent = new TracePointSnapshotFailedEvent( 80 | UuidUtils.generateId(), 81 | this.context.rawProbe.client, 82 | this.context.rawProbe.remoteFilename || this.context.rawProbe.fileName, 83 | this.context.rawProbe.lineNo 84 | ) 85 | 86 | let codedError = ProbeUtils.getCodedError(error, this.context.rawProbe); 87 | snapshotFailedEvent.setError(codedError || error) 88 | CommunicationManager.sendEvent(snapshotFailedEvent) 89 | } catch (error) { 90 | Logger.debug(` An error occured while sending tracepoint snapshot failed event ${error.message}`); 91 | } 92 | }); 93 | } 94 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/debug/tracepoint/TracePointContext.ts: -------------------------------------------------------------------------------- 1 | import * as inspector from 'inspector'; 2 | import { 3 | Probe, 4 | ProbeType, 5 | SourceLocation 6 | } from "../../../../types"; 7 | import { DefaultProbeContext } from "../probe/ProbeContext"; 8 | 9 | export default class TracePointContext extends DefaultProbeContext { 10 | constructor( 11 | v8BreakpointId: inspector.Debugger.BreakpointId, 12 | rawProbe: Probe, 13 | generatedPosition?: SourceLocation, 14 | ) { 15 | super(v8BreakpointId, rawProbe, generatedPosition); 16 | } 17 | 18 | getProbeAction(): ProbeType { 19 | return 'Tracepoint'; 20 | } 21 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/internal/v8/V8inspectorApi.ts: -------------------------------------------------------------------------------- 1 | import * as inspector from 'inspector'; 2 | import { Api } from '../../Api'; 3 | 4 | export default interface V8InspectorApi extends Api { 5 | setSession(session: inspector.Session): void; 6 | post(messageId: any, params: any, cb?: any): void; 7 | setBreakpointByUrl(options: inspector.Debugger.SetBreakpointByUrlParameterType) 8 | : { error?: Error, response?: inspector.Debugger.SetBreakpointByUrlReturnType }; 9 | removeBreakpoint(breakpointId: string): { error?: Error }; 10 | updateBreakpointByUrl(breakpointId: string, options: inspector.Debugger.SetBreakpointByUrlParameterType) 11 | : { error?: Error, response?: inspector.Debugger.SetBreakpointByUrlReturnType } 12 | | { error?: Error }; 13 | evaluateOnCallFrame( 14 | options: inspector.Debugger.EvaluateOnCallFrameParameterType 15 | ): any; 16 | getProperties(options: inspector.Runtime.GetPropertiesParameterType) 17 | : { error?: Error, response?: inspector.Runtime.GetPropertiesReturnType }; 18 | } 19 | 20 | export class DefaultV8InspectorApi implements V8InspectorApi { 21 | session: inspector.Session; 22 | constructor(session: inspector.Session) { 23 | this.session = session; 24 | } 25 | 26 | setSession(session: inspector.Session): void { 27 | this.session = session; 28 | } 29 | 30 | post(messageId: any, params: any, cb?: any) { 31 | const myCb = (...args: any[]) => { 32 | if (cb !== undefined) { 33 | cb(...args); 34 | } 35 | }; 36 | 37 | if (this.session === null) { 38 | if (cb !== undefined) { 39 | cb(new Error("No debug session"), null); 40 | } 41 | 42 | return; 43 | } 44 | 45 | this.session.post(messageId, params, myCb); 46 | } 47 | 48 | setBreakpointByUrl( 49 | options: inspector.Debugger.SetBreakpointByUrlParameterType 50 | ) { 51 | const result: { 52 | error?: Error; 53 | response?: inspector.Debugger.SetBreakpointByUrlReturnType; 54 | } = {}; 55 | 56 | this.post( 57 | 'Debugger.setBreakpointByUrl', 58 | options, 59 | ( 60 | error: Error | null, 61 | response: inspector.Debugger.SetBreakpointByUrlReturnType 62 | ) => { 63 | if (error) result.error = error; 64 | result.response = response; 65 | } 66 | ); 67 | 68 | return result; 69 | } 70 | 71 | removeBreakpoint(breakpointId: string) { 72 | const result: {error?: Error} = {}; 73 | this.post( 74 | 'Debugger.removeBreakpoint', 75 | {breakpointId}, 76 | (error: Error | null) => { 77 | if (error) result.error = error; 78 | } 79 | ); 80 | return result; 81 | } 82 | 83 | updateBreakpointByUrl( 84 | breakpointId: string, 85 | options: inspector.Debugger.SetBreakpointByUrlParameterType 86 | ) { 87 | let result = this.removeBreakpoint(breakpointId); 88 | if (result.error) { 89 | return result; 90 | } 91 | 92 | return this.setBreakpointByUrl(options); 93 | } 94 | 95 | evaluateOnCallFrame( 96 | options: inspector.Debugger.EvaluateOnCallFrameParameterType 97 | ) { 98 | const result: { 99 | error?: Error; 100 | response?: inspector.Debugger.EvaluateOnCallFrameReturnType; 101 | } = {}; 102 | this.post( 103 | 'Debugger.evaluateOnCallFrame', 104 | options, 105 | ( 106 | error: Error | null, 107 | response: inspector.Debugger.EvaluateOnCallFrameReturnType 108 | ) => { 109 | if (error) result.error = error; 110 | result.response = response; 111 | } 112 | ); 113 | return result; 114 | } 115 | 116 | getProperties(options: inspector.Runtime.GetPropertiesParameterType) { 117 | const result: { 118 | error?: Error; 119 | response?: inspector.Runtime.GetPropertiesReturnType; 120 | } = {}; 121 | 122 | this.post( 123 | 'Runtime.getProperties', 124 | options, 125 | ( 126 | error: Error | null, 127 | response: inspector.Runtime.GetPropertiesReturnType 128 | ) => { 129 | if (error) result.error = error; 130 | result.response = response; 131 | } 132 | ); 133 | 134 | return result; 135 | } 136 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/api/status/ApiStatus.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "events"; 2 | import { Listenable } from "../Api"; 3 | 4 | export const ApiStatusEventNames = { 5 | STATUSCHANGED: 'STATUSCHANGED', 6 | } 7 | 8 | export default class ApiStatus extends EventEmitter implements Listenable { 9 | private statusMap: Map; 10 | 11 | constructor(statusMap?: Map) { 12 | super(); 13 | this.statusMap = statusMap || new Map(); 14 | } 15 | 16 | getStatus(key: string) { 17 | return this.statusMap.get(key); 18 | } 19 | 20 | setStatus(key: string, status: boolean) { 21 | const currentStatus = this.statusMap.get(key); 22 | if (currentStatus !== undefined && currentStatus == status) { 23 | return; 24 | } 25 | 26 | this.statusMap.set(key, status); 27 | this.emit(ApiStatusEventNames.STATUSCHANGED, { 28 | type: 'ApiStatus', 29 | name: 'StatusChanged', 30 | data: { 31 | [key]: status 32 | } 33 | }) 34 | } 35 | 36 | disableStatuses() { 37 | this.statusMap.forEach((status, key) => { 38 | this.statusMap.set(key, false); 39 | }) 40 | } 41 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/application/Application.ts: -------------------------------------------------------------------------------- 1 | import ApplicationInfo from "./ApplicationInfo"; 2 | import ApplicationInfoProvider from "./ApplicationInfoProvider"; 3 | 4 | export default class Application { 5 | static applicationInfoProvider: ApplicationInfoProvider; 6 | 7 | private Application() { 8 | } 9 | 10 | static getApplicationInfoProvider(): ApplicationInfoProvider { 11 | return Application.applicationInfoProvider; 12 | } 13 | 14 | static setApplicationInfoProvider(applicationInfoProvider: ApplicationInfoProvider): void { 15 | Application.applicationInfoProvider = applicationInfoProvider; 16 | } 17 | 18 | static getApplicationInfo(): ApplicationInfo { 19 | if (!Application.applicationInfoProvider) { 20 | return; 21 | } 22 | 23 | return Application.applicationInfoProvider.getApplicationInfo(); 24 | } 25 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/application/ApplicationInfo.ts: -------------------------------------------------------------------------------- 1 | export default class ApplicationInfo { 2 | applicationId: string; 3 | applicationInstanceId: string; 4 | applicationName: string; 5 | applicationVersion: string; 6 | applicationStage: string; 7 | applicationRuntime: string; 8 | hostname: string; 9 | applicationRuntimeVersion: string; 10 | applicationTags: { [key: string]: any }; 11 | 12 | constructor( 13 | applicationId: string, 14 | applicationInstanceId: string, 15 | applicationName: string, 16 | applicationVersion: string, 17 | applicationStage: string, 18 | applicationRuntime: string, 19 | hostname: string, 20 | applicationRuntimeVersion?: string, 21 | applicationTags?: { [key: string]: any }, 22 | ) { 23 | this.applicationId = applicationId; 24 | this.applicationInstanceId = applicationInstanceId; 25 | this.applicationName = applicationName; 26 | this.applicationVersion = applicationVersion; 27 | this.applicationStage = applicationStage; 28 | this.applicationRuntime = applicationRuntime; 29 | this.hostname = hostname; 30 | this.applicationRuntimeVersion = applicationRuntimeVersion; 31 | this.applicationTags = applicationTags; 32 | } 33 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/application/ApplicationInfoProvider.ts: -------------------------------------------------------------------------------- 1 | import ApplicationInfo from "./ApplicationInfo"; 2 | 3 | export default abstract class ApplicationInfoProvider { 4 | static APPLICATION_RUNTIME = 'nodejs'; 5 | 6 | abstract getApplicationInfo(): ApplicationInfo; 7 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/application/ConfigAwareApplicationInfoProvider.ts: -------------------------------------------------------------------------------- 1 | import ApplicationInfo from "./ApplicationInfo"; 2 | import ApplicationInfoProvider from "./ApplicationInfoProvider"; 3 | import ConfigProvider from '../config/ConfigProvider'; 4 | import { ConfigNames } from '../config/ConfigNames'; 5 | import UuidUtils from '../utils/UuidUtils'; 6 | const os = require("os"); 7 | 8 | export default class ConfigAwareApplicationInfoProvider extends ApplicationInfoProvider { 9 | private applicationInfo : ApplicationInfo; 10 | 11 | constructor() { 12 | super() 13 | 14 | const hostname = os.hostname(); 15 | const applicationName = ConfigProvider.get(ConfigNames.application.name, hostname); 16 | this.applicationInfo = new ApplicationInfo( 17 | ConfigProvider.get(ConfigNames.application.id, `nodejs:${applicationName}`), 18 | ConfigProvider.get(ConfigNames.application.instanceId, `${process.pid}:${UuidUtils.generateId()}@${hostname}`), 19 | applicationName, 20 | ConfigProvider.get(ConfigNames.application.version, ''), 21 | ConfigProvider.get(ConfigNames.application.stage, ''), 22 | 'nodejs', 23 | hostname, 24 | process.version, 25 | ConfigProvider.get<{[key: string]: any}>(ConfigNames.application.tag, {}), 26 | ) 27 | } 28 | 29 | getApplicationInfo(): ApplicationInfo { 30 | return this.applicationInfo; 31 | } 32 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/application/status/ApplicationStatusProvider.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationStatus } from "../../types"; 2 | 3 | export default interface ApplicationStatusProvider { 4 | provide(applicationStatus: ApplicationStatus, client: string): void; 5 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/application/status/logpoint/ApplicationStatusLogPointProvider.ts: -------------------------------------------------------------------------------- 1 | import LogpointManager from "../../../api/external/manager/LogpointManager"; 2 | import { ApplicationStatus } from "../../../types"; 3 | import ApplicationStatusProvider from "../ApplicationStatusProvider"; 4 | 5 | export default class ApplicationStatusLogPointProvider implements ApplicationStatusProvider { 6 | protected logpointManager: LogpointManager; 7 | 8 | constructor(logpointManager: LogpointManager) { 9 | this.logpointManager = logpointManager; 10 | } 11 | 12 | provide(applicationStatus: ApplicationStatus, client?: string) { 13 | applicationStatus.logPoints = this.logpointManager.getAll(client); 14 | } 15 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/application/status/tracepoint/ApplicationStatusTracePointProvider.ts: -------------------------------------------------------------------------------- 1 | import TracepointManager from "../../../api/external/manager/TracepointManager"; 2 | import { ApplicationStatus } from "../../../types"; 3 | import ApplicationStatusProvider from "../ApplicationStatusProvider"; 4 | 5 | export default class ApplicationStatusTracePointProvider implements ApplicationStatusProvider { 6 | protected tracepointManager: TracepointManager; 7 | 8 | constructor(tracepointManager: TracepointManager) { 9 | this.tracepointManager = tracepointManager; 10 | } 11 | 12 | provide(applicationStatus: ApplicationStatus, client?: string) { 13 | applicationStatus.tracePoints = this.tracepointManager.getAll(client); 14 | } 15 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | import { ConfigNames } from "../config/ConfigNames"; 2 | 3 | export const AGENT_UUID_CONST = '3cda958c-e704-56ff-b519-ab2e3dc3ccb4'; 4 | export const SCHEDULAR_SENSITIVITY = 30; // second 5 | export const SCHEDULAR_MAX_SECOND = 60 * 60 * 24; // one day 6 | export const SCHEDULAR_MAX_TICK = SCHEDULAR_MAX_SECOND / SCHEDULAR_SENSITIVITY; 7 | 8 | export const PROBE_DEFAULT_EXPIRY_SECS = 60 * 30 // 30 minutes 9 | export const PROBE_DEFAULT_EXPIRY_COUNT = 50; 10 | export const PROBE_MAX_EXPIRY_SECS = 60 * 60 * 24; // 1 day 11 | export const PROBE_MAX_EXPIRY_COUNT = 1000; 12 | 13 | export const ERROR_PROBE_STORE_ID = 'ERROR_PROBE_STORE_ID'; 14 | 15 | export const ERROR_COLLECTION_DENY_FILE_NAMES = [ 16 | '@runsidekick' 17 | ] 18 | 19 | export const CONFIG_CONSTANT = { 20 | [ConfigNames.broker.host]: { 21 | default: 'wss://broker.service.runsidekick.com' 22 | }, 23 | [ConfigNames.broker.port]: { 24 | default: 443 25 | }, 26 | [ConfigNames.agent.logLevel]: { 27 | default: 'info', 28 | }, 29 | [ConfigNames.agent.disable]: { 30 | default: false, 31 | }, 32 | [ConfigNames.agent.silent]: { 33 | default: false, 34 | }, 35 | [ConfigNames.debugApi.resetV8Debugger]: { 36 | default: true, 37 | }, 38 | [ConfigNames.debugApi.resetV8DebuggerThreshold]: { 39 | default: 100, 40 | }, 41 | [ConfigNames.debugApi.enableAsyncCallStack]: { 42 | default: false, 43 | }, 44 | [ConfigNames.debugApi.cleanupAsyncCallStackInterval]: { 45 | default: 30 * 1000, 46 | }, 47 | [ConfigNames.scriptStore.disablePositionCache]: { 48 | default: false, 49 | }, 50 | [ConfigNames.scriptStore.hashCheckEnable]: { 51 | default: false, 52 | }, 53 | [ConfigNames.rateLimit.inMinute]: { 54 | default: 200, 55 | }, 56 | [ConfigNames.capture.maxFrames]: { 57 | default: 10, 58 | min: 1, 59 | max: 20 60 | }, 61 | [ConfigNames.capture.maxExpandFrames]: { 62 | default: 1, 63 | min: 1, 64 | max: 5 65 | }, 66 | [ConfigNames.capture.maxProperties]: { 67 | default: 10, 68 | min: 1, 69 | max: 50 70 | }, 71 | [ConfigNames.capture.maxParseDepth]: { 72 | default: 3, 73 | min: 1, 74 | max: 6 75 | }, 76 | [ConfigNames.capture.propertyAccessClassification]: { 77 | default: 'ENUMERABLE-OWN', 78 | }, 79 | [ConfigNames.sourceCode.minified]: { 80 | default: false, 81 | }, 82 | [ConfigNames.broker.client]: { 83 | default: 'default', 84 | }, 85 | [ConfigNames.taskExecutionQueue.concurrency]: { 86 | default: 5, 87 | }, 88 | [ConfigNames.taskExecutionQueue.maxSize]: { 89 | default: 10, 90 | }, 91 | [ConfigNames.agent.rejectOnStartup]: { 92 | default: false, 93 | }, 94 | [ConfigNames.dataReduction.captureFrame]: { 95 | canEnv: false, 96 | }, 97 | [ConfigNames.dataReduction.logMessage]: { 98 | canEnv: false, 99 | }, 100 | [ConfigNames.errorCollection.enable]: { 101 | default: false, 102 | }, 103 | [ConfigNames.errorCollection.captureFrame]: { 104 | default: false, 105 | }, 106 | [ConfigNames.errorCollection.rateLimit.pointInMinute]: { 107 | default: 10, 108 | }, 109 | [ConfigNames.errorCollection.rateLimit.totalInMinute]: { 110 | default: 100, 111 | } 112 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/error/CodedError.ts: -------------------------------------------------------------------------------- 1 | export default class CodedError extends Error { 2 | code: number; 3 | 4 | constructor(code: number, message: string) { 5 | super(message); 6 | this.code = code; 7 | } 8 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/error/ConfigValidationError.ts: -------------------------------------------------------------------------------- 1 | export default class ConfigValidationError extends Error { 2 | constructor(message: string) { 3 | super(message) 4 | } 5 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/error/ProbeErrorCodes.ts: -------------------------------------------------------------------------------- 1 | import LogPointErrors from './logpoint/LogPointErrorCodes'; 2 | import TracePointErrors from './tracepoint/TracePointErrorCodes'; 3 | 4 | export default { 5 | ...LogPointErrors, 6 | ...TracePointErrors 7 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/error/ProbeErrors.ts: -------------------------------------------------------------------------------- 1 | export class ProbeAlreadyExistError extends Error { 2 | constructor(message: string = '') { 3 | super(message) 4 | } 5 | }; 6 | export class NoProbeExistError extends Error { 7 | constructor(message: string = '') { 8 | super(message) 9 | } 10 | }; 11 | export class FileNameIsMandatory extends Error { 12 | constructor(message: string = '') { 13 | super(message) 14 | } 15 | }; 16 | export class LineNumberIsMandatory extends Error { 17 | constructor(message: string = '') { 18 | super(message) 19 | } 20 | }; 21 | export class NoProbeExistWithId extends Error { 22 | constructor(message: string = '') { 23 | super(message) 24 | } 25 | }; 26 | export class ClientHasNoAccessToProbe extends Error { 27 | constructor(message: string = '') { 28 | super(message) 29 | } 30 | }; 31 | export class PutProbeFailed extends Error { 32 | constructor(message: string = '') { 33 | super(message) 34 | } 35 | }; 36 | export class SourceCodeMisMatchDetected extends Error { 37 | constructor(message: string = '') { 38 | super(message) 39 | } 40 | }; 41 | export class UpdateProbeFailed extends Error { 42 | constructor(message: string = '') { 43 | super(message) 44 | } 45 | }; 46 | export class UpdateProbeWithIdFailed extends Error { 47 | constructor(message: string = '') { 48 | super(message) 49 | } 50 | }; 51 | export class RemoveProbeFailed extends Error { 52 | constructor(message: string = '') { 53 | super(message) 54 | } 55 | }; 56 | export class RemoveProbeWithIdFailed extends Error { 57 | constructor(message: string = '') { 58 | super(message) 59 | } 60 | }; 61 | export class EnableProbeFailed extends Error { 62 | constructor(message: string = '') { 63 | super(message) 64 | } 65 | }; 66 | export class EnableProbeWithIdFailed extends Error { 67 | constructor(message: string = '') { 68 | super(message) 69 | } 70 | }; 71 | export class DisableProbeFailed extends Error { 72 | constructor(message: string = '') { 73 | super(message) 74 | } 75 | }; 76 | export class DisableProbeWithIdFailed extends Error { 77 | constructor(message: string = '') { 78 | super(message) 79 | } 80 | }; 81 | export class ScriptNotFound extends Error { 82 | constructor(message: string = '') { 83 | super(message) 84 | } 85 | }; 86 | export class ConditionNotValid extends Error { 87 | constructor(message: string = '') { 88 | super(message) 89 | } 90 | }; 91 | -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/error/logpoint/LogPointErrorCodes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ProbeAlreadyExistError, 3 | NoProbeExistError, 4 | FileNameIsMandatory, 5 | ClientHasNoAccessToProbe, 6 | DisableProbeFailed, 7 | DisableProbeWithIdFailed, 8 | EnableProbeFailed, 9 | EnableProbeWithIdFailed, 10 | LineNumberIsMandatory, 11 | NoProbeExistWithId, 12 | PutProbeFailed, 13 | RemoveProbeFailed, 14 | RemoveProbeWithIdFailed, 15 | SourceCodeMisMatchDetected, 16 | UpdateProbeFailed, 17 | UpdateProbeWithIdFailed, 18 | } from "../ProbeErrors"; 19 | 20 | const LogPointErrors = { 21 | [ProbeAlreadyExistError.name]: { 22 | code: 3000, 23 | message: 'Logpoint has been already added in file {} on line {} from client {}' 24 | }, 25 | [NoProbeExistError.name]: { 26 | code: 3001, 27 | message: 'No logpoint could be found in file {} on line {} from client {}' 28 | }, 29 | [FileNameIsMandatory.name]: { 30 | code: 3002, 31 | message: 'File name is mandatory' 32 | }, 33 | [LineNumberIsMandatory.name]: { 34 | code: 3003, 35 | message: 'Line number is mandatory' 36 | }, 37 | [NoProbeExistWithId.name]: { 38 | code: 3004, 39 | message: 'No logpoint could be found with id {} from client {}' 40 | }, 41 | [ClientHasNoAccessToProbe.name]: { 42 | code: 3005, 43 | message: 'Client {} has no access to logpoint with id {}' 44 | }, 45 | [PutProbeFailed.name]: { 46 | code: 3050, 47 | message: 'Error occurred while putting logpoint to file {} on line {} from client {}: {}' 48 | }, 49 | [SourceCodeMisMatchDetected.name]: { 50 | code: 3051, 51 | message: 'Source code mismatch detected while putting logpoint to file {fileName} on line {lineNo} from client {client} {reason}' 52 | }, 53 | [UpdateProbeFailed.name]: { 54 | code: 3100, 55 | message: 'Error occurred while updating logpoint to file {} on line {} from client {}: {}' 56 | }, 57 | [UpdateProbeWithIdFailed.name]: { 58 | code: 3101, 59 | message: 'Error occurred while updating logpoint with id {} from client {}: {}' 60 | }, 61 | [RemoveProbeFailed.name]: { 62 | code: 3150, 63 | message: 'Error occurred while removing logpoint from file {} on line {} from client {}: {}' 64 | }, 65 | [RemoveProbeWithIdFailed.name]: { 66 | code: 3151, 67 | message: 'Error occurred while removing logpoint with id {} from client {}: {}' 68 | }, 69 | [EnableProbeFailed.name]: { 70 | code: 3200, 71 | message: 'Error occurred while enabling logpoint to file {} on line {} from client {}: {' 72 | }, 73 | [EnableProbeWithIdFailed.name]: { 74 | code: 3201, 75 | message: 'Error occurred while enabling logpoint with id {} from client {}: {}' 76 | }, 77 | [DisableProbeFailed.name]: { 78 | code: 3250, 79 | message: 'Error occurred while disabling logpoint to file {} on line {} from client {}: {}' 80 | }, 81 | [DisableProbeWithIdFailed.name]: { 82 | code: 3251, 83 | message: 'Error occurred while disabling logpoint with id {} from client {}: {}' 84 | }, 85 | } 86 | 87 | export default LogPointErrors; -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/error/tracepoint/TracePointErrorCodes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ProbeAlreadyExistError, 3 | NoProbeExistError, 4 | FileNameIsMandatory, 5 | ClientHasNoAccessToProbe, 6 | DisableProbeFailed, 7 | DisableProbeWithIdFailed, 8 | EnableProbeFailed, 9 | EnableProbeWithIdFailed, 10 | LineNumberIsMandatory, 11 | NoProbeExistWithId, 12 | PutProbeFailed, 13 | RemoveProbeFailed, 14 | RemoveProbeWithIdFailed, 15 | SourceCodeMisMatchDetected, 16 | UpdateProbeFailed, 17 | UpdateProbeWithIdFailed, 18 | } from "../ProbeErrors"; 19 | 20 | const TracePointErrors = { 21 | [ProbeAlreadyExistError.name]: { 22 | code: 2000, 23 | message: 'Tracepoint has been already added in file {fileName} on line {lineNo} from client {client}' 24 | }, 25 | [NoProbeExistError.name]: { 26 | code: 2001, 27 | message: 'No tracepoint could be found in file {fileName} on line {lineNo} from client {client}' 28 | }, 29 | [FileNameIsMandatory.name]: { 30 | code: 2002, 31 | message: 'File name is mandatory' 32 | }, 33 | [LineNumberIsMandatory.name]: { 34 | code: 2003, 35 | message: 'Line number is mandatory' 36 | }, 37 | [NoProbeExistWithId.name]: { 38 | code: 2004, 39 | message: 'No tracepoint could be found with id {id} from client {client}' 40 | }, 41 | [ClientHasNoAccessToProbe.name]: { 42 | code: 2005, 43 | message: 'Client {client} has no access to tracepoint with id {id}' 44 | }, 45 | [PutProbeFailed.name]: { 46 | code: 2050, 47 | message: 'Error occurred while putting tracepoint to file {fileName} on line {lineNo} from client {client}' 48 | }, 49 | [SourceCodeMisMatchDetected.name]: { 50 | code: 2051, 51 | message: 'Source code mismatch detected while putting tracepoint to file {fileName} on line {lineNo} from client {client} {reason}' 52 | }, 53 | [UpdateProbeFailed.name]: { 54 | code: 2100, 55 | message: 'Error occurred while updating tracepoint at file {fileName} on line {lineNo} from client {client} {reason}' 56 | }, 57 | [UpdateProbeWithIdFailed.name]: { 58 | code: 2101, 59 | message: 'Error occurred while updating tracepoint with id {id} from client client {client} {reason}' 60 | }, 61 | [RemoveProbeFailed.name]: { 62 | code: 2150, 63 | message: 'Error occurred while removing tracepoint from file {fileName} on line {lineNo} from client {client} {reason}' 64 | }, 65 | [RemoveProbeWithIdFailed.name]: { 66 | code: 2151, 67 | message: 'Error occurred while removing tracepoint with id {id} from client client {client} {reason}' 68 | }, 69 | [EnableProbeFailed.name]: { 70 | code: 2200, 71 | message: 'Error occurred while enabling tracepoint at file {fileName} on line {lineNo} from client {client} {reason}' 72 | }, 73 | [EnableProbeWithIdFailed.name]: { 74 | code: 2201, 75 | message: 'Error occurred while enabling tracepoint with {id} from client client {client} {reason}' 76 | }, 77 | [DisableProbeFailed.name]: { 78 | code: 2250, 79 | message: 'Error occurred while disabling tracepoint at file {fileName} on line {lineNo} from client {client} {reason}' 80 | }, 81 | [DisableProbeWithIdFailed.name]: { 82 | code: 2251, 83 | message: 'Error occurred while disabling tracepoint with {id} from client client {client} {reason}' 84 | }, 85 | } 86 | 87 | export default TracePointErrors; -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/handler/Handler.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "../types"; 2 | 3 | export default interface Handler { 4 | handle(message: Message): any | undefined; 5 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/handler/HandlerContainer.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "../types"; 2 | import Handler from "./Handler"; 3 | 4 | export default interface HandlerContainer { 5 | getHandler(message: Message): Handler | undefined; 6 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/index.ts: -------------------------------------------------------------------------------- 1 | import { SidekickConfig, ConfigNames } from "./config/ConfigNames"; 2 | import SidekickManager from "./manager/SidekickManager" 3 | import ConfigMetadata from './config/ConfigMetadata'; 4 | import ConfigProvider from './config/ConfigProvider'; 5 | import Logger, { LogLevel } from './logger'; 6 | import Application from './application/Application'; 7 | import ConfigAwareApplicationInfoProvider from "./application/ConfigAwareApplicationInfoProvider"; 8 | import FileUtils from './utils/FileUtils'; 9 | import * as path from 'path'; 10 | 11 | let sidekickManager: SidekickManager; 12 | 13 | /** 14 | * @param {SidekickConfig} options? 15 | * @returns Promise 16 | */ 17 | export const start = (options?: SidekickConfig): Promise | undefined => { 18 | if (sidekickManager) { 19 | Logger.error(' Debug Agent has already been started.'); 20 | return; 21 | } 22 | 23 | try { 24 | FileUtils.statSync(path.join(process.cwd(), 'package.json')) 25 | } catch (error) { 26 | Logger.error(' No package.json located in working directory.', error); 27 | return; 28 | } 29 | 30 | try { 31 | ConfigProvider.init({ 32 | config: options || {} as SidekickConfig, 33 | configMetaData: ConfigMetadata 34 | }); 35 | } catch (error) { 36 | Logger.error(' An error occured while parsing config.', error); 37 | return; 38 | } 39 | 40 | if (ConfigProvider.get(ConfigNames.agent.disable)) { 41 | Logger.info(` Agent disabled.`); 42 | return; 43 | } 44 | 45 | Application.setApplicationInfoProvider(new ConfigAwareApplicationInfoProvider()); 46 | 47 | const logLevel = ConfigProvider.get(ConfigNames.agent.logLevel); 48 | Logger.setLogLevel(logLevel); 49 | 50 | Logger.info(' Agent starting ...'); 51 | 52 | sidekickManager = new SidekickManager(); 53 | const startPromise = sidekickManager.start(); 54 | /** 55 | * encapsulate rejection of start promise 56 | */ 57 | if (!ConfigProvider.get(ConfigNames.agent.rejectOnStartup)) { 58 | startPromise.catch(() => { /** no need to log, already logged */}); 59 | } 60 | 61 | return startPromise; 62 | } 63 | 64 | /** 65 | * @returns Sidekick 66 | */ 67 | export const get = (): SidekickManager | undefined => { 68 | return sidekickManager; 69 | } 70 | 71 | /** 72 | * Stop Sideckick 73 | */ 74 | export const stop = () => { 75 | if (sidekickManager) { 76 | sidekickManager.stop(); 77 | sidekickManager = null; 78 | } 79 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/limit/rate/RateLimiter.ts: -------------------------------------------------------------------------------- 1 | import { RateLimitResult } from "../../types"; 2 | 3 | export default interface RateLimiter { 4 | checkRateLimit(criteria: any): RateLimitResult; 5 | } 6 | 7 | export class DefaultRateLimiter implements RateLimiter { 8 | protected milisecondsInMinute = 1000 * 60; 9 | protected window = 4; 10 | protected inMinute: number; 11 | protected idxMask: number; 12 | protected rateLimitInfos: RateLimitInfo[] = []; 13 | 14 | constructor(inMinute = 200) { 15 | this.inMinute = inMinute; 16 | this.idxMask = this.window - 1; 17 | } 18 | 19 | checkRateLimit(currentTime: number): RateLimitResult { 20 | const currentMinute = Math.floor(currentTime / this.milisecondsInMinute); 21 | const rateLimitInfoIdx = (currentMinute & this.idxMask); 22 | let rateLimitInfo = this.rateLimitInfos[rateLimitInfoIdx]; 23 | if (!rateLimitInfo) { 24 | rateLimitInfo = this.setRateLimitInfo(rateLimitInfoIdx, null, currentMinute); 25 | } else { 26 | if (rateLimitInfo.minute < currentMinute) { 27 | rateLimitInfo = this.setRateLimitInfo(rateLimitInfoIdx, rateLimitInfo, currentMinute); 28 | } else if (rateLimitInfo.minute > currentMinute) { 29 | // Normally this case should not happen, as there is enough window to prevent overlapping 30 | return 'OK'; 31 | } 32 | } 33 | 34 | if (!rateLimitInfo) { 35 | return 'OK'; 36 | } 37 | 38 | rateLimitInfo.increaseCounter(); 39 | const count = rateLimitInfo.counter; 40 | if (count < this.inMinute) { 41 | return 'OK'; 42 | } else if (count == this.inMinute) { 43 | return 'HIT'; 44 | } else { 45 | return 'EXCEEDED'; 46 | } 47 | } 48 | 49 | setRateLimitInfo(idx: number, existingRateLimitInfo: RateLimitInfo, currentMinute: number): any { 50 | const newRateLimitInfo = new RateLimitInfo(currentMinute); 51 | if (!existingRateLimitInfo) { 52 | this.rateLimitInfos[idx] = newRateLimitInfo; 53 | } else { 54 | if (existingRateLimitInfo == this.rateLimitInfos[idx]) { 55 | this.rateLimitInfos[idx] = newRateLimitInfo; 56 | } else { 57 | return this.rateLimitInfos[idx]; 58 | } 59 | } 60 | 61 | return newRateLimitInfo; 62 | } 63 | } 64 | 65 | class RateLimitInfo { 66 | minute: number; 67 | counter: number 68 | 69 | constructor(minute: number) { 70 | this.minute = minute; 71 | this.counter = 0; 72 | } 73 | 74 | increaseCounter() { 75 | this.counter = this.counter + 1; 76 | } 77 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/listener/Listener.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "events"; 2 | import { Message } from "../types"; 3 | import ApiStatus from "../api/status/ApiStatus"; 4 | import { SilentModeAcceptedRequestName, SilentRequestName } from '../types'; 5 | import ConfigProvider from "../config/ConfigProvider"; 6 | import { ConfigNames } from "../config/ConfigNames"; 7 | import Logger from "../logger"; 8 | 9 | export default interface Listener extends EventEmitter { 10 | listen(): Promise; 11 | unlisten(): void; 12 | } 13 | 14 | export abstract class SimpleListener extends EventEmitter implements Listener { 15 | abstract listen(): Promise; 16 | abstract unlisten(): void; 17 | } 18 | 19 | export abstract class SimpleApiListener extends SimpleListener { 20 | protected apiStatus: ApiStatus; 21 | 22 | constructor(apiStatus: ApiStatus) { 23 | super(); 24 | 25 | this.apiStatus = apiStatus; 26 | } 27 | 28 | abstract listen(): Promise; 29 | abstract unlisten(): void; 30 | } 31 | 32 | export abstract class MessageHandlerListener extends SimpleApiListener { 33 | protected onMessage: any; 34 | 35 | constructor(apiStatus: ApiStatus) { 36 | super(apiStatus); 37 | this.onMessage = (message: Message) => setImmediate(() => this._handleMessage(message)); 38 | } 39 | 40 | abstract listen(): Promise; 41 | abstract unlisten(): void; 42 | protected abstract handleMessage(message: Message): void; 43 | private _handleMessage(message: Message) { 44 | if (ConfigProvider.get(ConfigNames.agent.silent) 45 | && !SilentModeAcceptedRequestName.includes(message.name as SilentRequestName)) { 46 | Logger.debug(` Message name: ${message.name} skipped. Agent on silent mode.`) 47 | return; 48 | } 49 | 50 | this.handleMessage(message); 51 | } 52 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/listener/apistatus/ApiStatusListener.ts: -------------------------------------------------------------------------------- 1 | import ApiStatus, { ApiStatusEventNames } from "../../api/status/ApiStatus"; 2 | import { MessageHandlerListener } from "../Listener"; 3 | import { InternalMessage } from "../../types"; 4 | import BufferedCommunicationApi from "../../api/external/communication/BufferedCommunicationApi"; 5 | import Logger from '../../logger'; 6 | 7 | export default class ApiStatusListener extends MessageHandlerListener { 8 | protected bufferedCommunicationApi: BufferedCommunicationApi 9 | 10 | constructor(bufferedCommunicationApi: BufferedCommunicationApi, apiStatus: ApiStatus) { 11 | super(apiStatus); 12 | this.bufferedCommunicationApi = bufferedCommunicationApi; 13 | } 14 | 15 | listen(): Promise { 16 | return new Promise((resolve, reject) => { 17 | try { 18 | Logger.debug(` Trying to listen api status ...`); 19 | this.apiStatus.on(ApiStatusEventNames.STATUSCHANGED, this.onMessage); 20 | resolve(); 21 | } catch (error) { 22 | Logger.error(' An error occured while to listen api status.'); 23 | reject(error) 24 | } 25 | }); 26 | } 27 | 28 | unlisten(): void { 29 | Logger.debug(' Unlisten api status.'); 30 | this.apiStatus.removeListener(ApiStatusEventNames.STATUSCHANGED, this.onMessage); 31 | } 32 | 33 | protected handleMessage(message: InternalMessage): void { 34 | if (message.data) { 35 | let flag = true; 36 | Object.keys(message.data).forEach(key => { 37 | if (flag && !message.data[key]) { 38 | flag = false; 39 | } 40 | }) 41 | 42 | Logger.debug(` Api status will set to ${flag}.`); 43 | flag ? this.bufferedCommunicationApi.unsilent() : this.bufferedCommunicationApi.silent(); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/listener/debug/DebugApiListener.ts: -------------------------------------------------------------------------------- 1 | import DebugApi, { DebugApiEventNames } from "../../api/internal/debug/DebugApi"; 2 | import { SimpleApiListener } from "../Listener"; 3 | import ApiStatus from '../../api/status/ApiStatus'; 4 | import Logger from '../../logger'; 5 | 6 | export default class DebugApiListener extends SimpleApiListener { 7 | private debugApi: DebugApi; 8 | 9 | constructor(debugApi: DebugApi, apiStatus: ApiStatus) { 10 | super(apiStatus); 11 | this.debugApi = debugApi; 12 | } 13 | 14 | listen(): Promise { 15 | return new Promise((resolve, reject) => { 16 | try { 17 | Logger.debug(' Trying to listen debug api ...'); 18 | const open = () => { 19 | Logger.debug(' Debug api listening.'); 20 | this.apiStatus.setStatus(this.debugApi.constructor.name, true); 21 | resolve(); 22 | }; 23 | 24 | const close = () => { 25 | Logger.debug(' Comminication api connection closed.'); 26 | this.apiStatus.setStatus(this.debugApi.constructor.name, false); 27 | } 28 | 29 | this.debugApi.on(DebugApiEventNames.OPEN, open); 30 | this.debugApi.on(DebugApiEventNames.CLOSE, close); 31 | 32 | this.debugApi.connect(); 33 | } catch (error) { 34 | reject(); 35 | } 36 | }); 37 | } 38 | 39 | unlisten(): void { 40 | Logger.debug(' Unlisten debug api.'); 41 | this.debugApi.close(); 42 | } 43 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/logger/index.ts: -------------------------------------------------------------------------------- 1 | import { ConfigNames } from '../config/ConfigNames'; 2 | 3 | const logger = require('npmlog'); 4 | 5 | logger.addLevel('debug', 1600, { fg: 'green' }); 6 | logger.level = process.env[ConfigNames.agent.logLevel] ? 7 | process.env[ConfigNames.agent.logLevel].toLowerCase() : 'info'; 8 | logger.disableColor(); 9 | 10 | export type LogLevel = 11 | | 'debug' 12 | | 'info' 13 | | 'warn' 14 | | 'error' 15 | 16 | export default { 17 | setLogLevel: (logLevel: LogLevel) => { 18 | logger.level = logLevel; 19 | }, 20 | info: (message: string): void => { 21 | logger.info('', message); 22 | }, 23 | warn:(message: string): void => { 24 | logger.warn('', message); 25 | }, 26 | debug: (message: string): void => { 27 | logger.debug('', message); 28 | }, 29 | error: (message: string, err?: Error): void => { 30 | if (err) { 31 | logger.error('', message, err); 32 | } else { 33 | logger.error('', message); 34 | } 35 | } 36 | }; -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/scheduler/Scheduler.ts: -------------------------------------------------------------------------------- 1 | import { setInterval } from "timers"; 2 | import Task from "./task/Task"; 3 | import { 4 | SCHEDULAR_SENSITIVITY, 5 | SCHEDULAR_MAX_TICK, 6 | } from '../constants'; 7 | import Logger from '../logger'; 8 | 9 | export default interface Scheduler { 10 | start(): void; 11 | add(period: number, task: Task): boolean; 12 | stop(): void; 13 | } 14 | 15 | export class TaskScheduler implements Scheduler { 16 | private interval: any; 17 | private tasks: { tickCount: number, task: Task }[] = []; 18 | private schedulerTickCount = 0; 19 | 20 | constructor(tasks?: { period: number, task: Task }[]) { 21 | (tasks || []).forEach(task => { 22 | this.add(task.period, task.task); 23 | }); 24 | } 25 | 26 | start() { 27 | this.interval = setInterval(() => { 28 | try { 29 | if (this.schedulerTickCount >= SCHEDULAR_MAX_TICK) { 30 | this.schedulerTickCount = 0; 31 | } 32 | 33 | this.schedulerTickCount++; 34 | let tasksWillBeExecuted: any[] = []; 35 | this.tasks.forEach(task => { 36 | if (this.schedulerTickCount % task.tickCount == 0) { 37 | tasksWillBeExecuted.push(task.task.execute()) 38 | } 39 | }); 40 | 41 | Promise.all(tasksWillBeExecuted) 42 | .then() 43 | .catch(err => { 44 | Logger.debug(` An error occured while executing scheduled task. ${err.message}`); 45 | }) 46 | 47 | } catch (error) { 48 | Logger.debug(` An error occured while task interval. ${this.schedulerTickCount} ${error.message}`); 49 | } 50 | }, SCHEDULAR_SENSITIVITY * 1000); 51 | 52 | this.interval.unref(); 53 | } 54 | 55 | add(period: number, task: Task): boolean { 56 | const tickCount = this.getTickCount(period); 57 | if (tickCount < 1) { 58 | return false; 59 | } 60 | 61 | this.tasks.push({ 62 | tickCount, 63 | task, 64 | }) 65 | 66 | return true; 67 | } 68 | 69 | stop() { 70 | clearInterval(this.interval); 71 | this.tasks = []; 72 | } 73 | 74 | private getTickCount(period: number): number { 75 | const tickCount = Math.round(period / SCHEDULAR_SENSITIVITY); 76 | return (tickCount <= SCHEDULAR_MAX_TICK && tickCount >= 1) ? tickCount : -1; 77 | } 78 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/scheduler/task/DisabledErrorCollectionEnableTask.ts: -------------------------------------------------------------------------------- 1 | import { ConfigNames } from "../../config/ConfigNames"; 2 | import ConfigProvider from "../../config/ConfigProvider"; 3 | import DebugApi from "../../api/internal/debug/DebugApi"; 4 | import Task from "./Task"; 5 | 6 | export default class DisabledErrorCollectionActivateTask implements Task { 7 | private debugApi: DebugApi 8 | 9 | constructor(debugApi: DebugApi) { 10 | this.debugApi = debugApi; 11 | } 12 | 13 | execute() { 14 | if (!ConfigProvider.get(ConfigNames.agent.silent) && 15 | ConfigProvider.get(ConfigNames.errorCollection.enable)) { 16 | this.debugApi.enableErrorCollect(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/scheduler/task/ExpiredProbeCleanTask.ts: -------------------------------------------------------------------------------- 1 | import { ConfigNames } from "../../config/ConfigNames"; 2 | import ConfigProvider from "../../config/ConfigProvider"; 3 | import ProbeStore from "../../store/probe/ProbeStore"; 4 | import Task from "./Task"; 5 | 6 | export default class ExpiredProbeCleanTask implements Task { 7 | private probeStore: ProbeStore 8 | 9 | constructor(probeStore: ProbeStore) { 10 | this.probeStore = probeStore; 11 | } 12 | 13 | execute() { 14 | if (!ConfigProvider.get(ConfigNames.agent.silent)) { 15 | this.probeStore.deleteExpiredProbes(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/scheduler/task/GetConfigTask.ts: -------------------------------------------------------------------------------- 1 | import CommunicationUtils from "../../utils/CommunicationUtils"; 2 | import CommunicationManager from "../../api/external/communication/CommunicationManager"; 3 | import { ConfigNames } from "../../config/ConfigNames"; 4 | import ConfigProvider from "../../config/ConfigProvider"; 5 | import Task from "./Task"; 6 | 7 | export default class GetConfigTask implements Task { 8 | execute() { 9 | if (!ConfigProvider.get(ConfigNames.agent.silent)) { 10 | CommunicationManager.sendRequest(CommunicationUtils.createGetConfigRequest()); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/scheduler/task/SendStatusTask.ts: -------------------------------------------------------------------------------- 1 | import CommunicationManager from "../../api/external/communication/CommunicationManager"; 2 | import Task from "./Task"; 3 | 4 | export default class SendStatusTask implements Task { 5 | execute() { 6 | CommunicationManager.sendApplicationStatusEvent(); 7 | } 8 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/scheduler/task/Task.ts: -------------------------------------------------------------------------------- 1 | export default interface Task { 2 | execute(): any; 3 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/store/probe/ProbeStore.ts: -------------------------------------------------------------------------------- 1 | import ProbeAction from '../../api/internal/debug/probe/ProbeAction'; 2 | import ProbeContext from '../../api/internal/debug/probe/ProbeContext'; 3 | import { Probe } from '../../types'; 4 | import ProbeUtils from '../../utils/ProbeUtils'; 5 | import { ERROR_PROBE_STORE_ID } from '../../constants'; 6 | 7 | export default class ProbeStore { 8 | protected locationMap: Map; 9 | protected breakpointProbeMap: Map>; 10 | protected probeMap: Map>; 11 | protected tagProbeMap: Map>; 12 | 13 | constructor() { 14 | this.locationMap = new Map(); 15 | this.breakpointProbeMap = new Map(); 16 | this.probeMap = new Map(); 17 | this.tagProbeMap = new Map(); 18 | } 19 | 20 | getAllProbeContexts(): ProbeContext[] { 21 | const contexts: ProbeContext[] = []; 22 | for(const [key, value] of this.probeMap) { 23 | if (key === ERROR_PROBE_STORE_ID) { 24 | continue; 25 | } 26 | 27 | contexts.push(value.getContext()); 28 | } 29 | 30 | return contexts; 31 | } 32 | 33 | getAllProbes(): Probe[] { 34 | const probes: Probe[] = []; 35 | for(const [key, value] of this.probeMap) { 36 | if (key === ERROR_PROBE_STORE_ID) { 37 | continue; 38 | } 39 | 40 | probes.push(value.getProbe()); 41 | } 42 | 43 | return probes; 44 | } 45 | 46 | get(probeId: string): ProbeAction { 47 | return this.probeMap.get(probeId); 48 | } 49 | 50 | getProbeIds(v8BreakpointId: string): Set { 51 | return this.breakpointProbeMap.get(v8BreakpointId); 52 | } 53 | 54 | getProbeIdsByLocationId(locationId: string) { 55 | const v8BreakpointId = this.locationMap.get(locationId); 56 | if (!v8BreakpointId) { 57 | return; 58 | } 59 | 60 | return this.breakpointProbeMap.get(v8BreakpointId); 61 | } 62 | 63 | getProbeByTag(tag: string): Set { 64 | return this.tagProbeMap.get(tag); 65 | } 66 | 67 | removeProbeTag(tag: string): void { 68 | this.tagProbeMap.delete(tag); 69 | } 70 | 71 | set(v8BreakpointId: string, action: ProbeAction): boolean { 72 | const probeId = action.getId(); 73 | const locationId = action.getLocationId(); 74 | 75 | if (!this.breakpointProbeMap.has(v8BreakpointId)) { 76 | this.breakpointProbeMap.set(v8BreakpointId, new Set()); 77 | this.locationMap.set(locationId, v8BreakpointId); 78 | } 79 | 80 | this.breakpointProbeMap.get(v8BreakpointId).add(probeId); 81 | this.probeMap.set(probeId, action); 82 | 83 | (action.getContext().getTags() || []).forEach(tag => { 84 | if (!this.tagProbeMap.has(tag)) { 85 | this.tagProbeMap.set(tag, new Set()); 86 | } 87 | 88 | this.tagProbeMap.get(tag).add(probeId); 89 | }); 90 | 91 | return true; 92 | } 93 | 94 | delete(probeId: string): void { 95 | if (this.probeMap.has(probeId)) { 96 | const probeAction = this.probeMap.get(probeId); 97 | const v8BreakpointId = probeAction.getV8BreakpointId(); 98 | const probeRefs = this.breakpointProbeMap.get(v8BreakpointId); 99 | if (probeRefs) { 100 | probeRefs.delete(probeId); 101 | 102 | if (probeRefs.size == 0) { 103 | this.breakpointProbeMap.delete(v8BreakpointId); 104 | this.locationMap.delete(probeAction.getLocationId()); 105 | } 106 | } 107 | 108 | (probeAction.getContext().getTags() || []).forEach(tag => { 109 | const probeTagRefs = this.tagProbeMap.get(tag); 110 | if (probeTagRefs) { 111 | probeTagRefs.delete(probeId); 112 | 113 | if (probeTagRefs.size == 0) { 114 | this.tagProbeMap.delete(tag); 115 | 116 | } 117 | } 118 | }); 119 | 120 | this.probeMap.delete(probeId); 121 | } 122 | } 123 | 124 | deleteExpiredProbes() { 125 | this.probeMap.forEach((probeAction: ProbeAction) => { 126 | if (probeAction.getContext().isExpired()) { 127 | this.delete(probeAction.getId()); 128 | } 129 | }) 130 | } 131 | 132 | clear(): void { 133 | this.breakpointProbeMap.clear(); 134 | this.locationMap.clear(); 135 | this.probeMap.clear(); 136 | } 137 | 138 | isV8BreakpointExistOnLocation(probe: Probe): boolean { 139 | const locationId = ProbeUtils.generateLocationId(probe); 140 | return this.locationMap.has(locationId); 141 | } 142 | 143 | isV8BreakpointExist(v8BreakpointId: string) { 144 | return this.breakpointProbeMap.has(v8BreakpointId); 145 | } 146 | 147 | findV8Breakpoint(probe: Probe): string { 148 | const locationId = ProbeUtils.generateLocationId(probe); 149 | return this.locationMap.get(locationId); 150 | } 151 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/store/queue/TaskExecutorQueue.ts: -------------------------------------------------------------------------------- 1 | import PQueue, { QueueAddOptions } from 'p-queue'; 2 | import Logger from '../../logger'; 3 | import ConfigProvider from "../../config/ConfigProvider"; 4 | import { ConfigNames } from "../../config/ConfigNames"; 5 | 6 | class MaxSizeQueue { 7 | maxSize: number; 8 | queue: any[]; 9 | 10 | constructor() { 11 | this.maxSize = ConfigProvider.get(ConfigNames.taskExecutionQueue.maxSize); 12 | this.queue = []; 13 | } 14 | 15 | enqueue(run: any, options: any) { 16 | if (this.size == this.maxSize - 1) { 17 | this.dequeue; 18 | } 19 | 20 | this.queue.push(run); 21 | } 22 | 23 | dequeue() { 24 | return this.queue.shift(); 25 | } 26 | 27 | get size() { 28 | return this.queue.length; 29 | } 30 | 31 | filter(options: any) { 32 | return this.queue; 33 | } 34 | } 35 | 36 | export default interface TaskExecutorQueue { 37 | start(): void; 38 | pause(): void; 39 | execute(task: any): Promise; 40 | } 41 | 42 | export class TaskExecutorPQueue implements TaskExecutorQueue { 43 | protected queue: PQueue; 44 | 45 | constructor(concurrency: number = 5) { 46 | this.queue = new PQueue({ concurrency, queueClass: MaxSizeQueue }); 47 | } 48 | 49 | start() { 50 | if (this.queue.isPaused) { 51 | Logger.debug(' Task executor service started.'); 52 | this.queue.start(); 53 | } 54 | } 55 | 56 | pause() { 57 | if (!this.queue.isPaused) { 58 | Logger.debug(' Task executor service paused.'); 59 | this.queue.pause(); 60 | } 61 | } 62 | 63 | execute(task: any) { 64 | return this.queue.add(task); 65 | } 66 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/trace/TraceInfoResolver.ts: -------------------------------------------------------------------------------- 1 | export default interface TraceInfoResolver { 2 | get(): TraceInfo; 3 | } 4 | 5 | export abstract class DefaultTraceInfoResolver implements TraceInfoResolver { 6 | protected state: ResolverState; 7 | constructor() { 8 | this.resolve(); 9 | } 10 | 11 | protected abstract resolve(): void; 12 | abstract get(): TraceInfo; 13 | } 14 | 15 | export type ResolverState = 16 | | 'INITIATED' 17 | | 'RESOLVED' 18 | | 'NOTRESOLVED' 19 | 20 | export type TraceInfo = { 21 | traceId?: string; 22 | transactionId?: string; 23 | spanId?: string; 24 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/trace/TraceInfoSupport.ts: -------------------------------------------------------------------------------- 1 | import ThundraTraceInfoResolver from "./thundra/ThundraTraceInfoResolver"; 2 | import OpenTelemetryTraceInfoResolver from "./opentelemetry/OpenTelemetryTraceInfoResolver"; 3 | import TraceInfoResolver, { TraceInfo } from "./TraceInfoResolver"; 4 | import Logger from '../logger'; 5 | 6 | const Libs: { [key: string]: TraceInfoResolver } = { 7 | Thundra: new ThundraTraceInfoResolver(), 8 | OpenTelemetry: new OpenTelemetryTraceInfoResolver() 9 | } 10 | 11 | let loadedTraceInfo: TraceInfo; 12 | 13 | export const loadTraceInfo = () => { 14 | for (const traceInfoResolver of Object.values(Libs)) { 15 | const traceInfo = traceInfoResolver.get(); 16 | if (traceInfo) { 17 | Logger.debug(' Trace info loaded.'); 18 | loadedTraceInfo = traceInfo; 19 | break; 20 | } 21 | } 22 | } 23 | 24 | export const getTraceInfo = (): TraceInfo => { 25 | return loadedTraceInfo; 26 | } 27 | 28 | (global as any).__sidekick_loadTraceInfo = (): boolean => { 29 | loadTraceInfo(); 30 | return true; 31 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/trace/opentelemetry/OpenTelemetryTraceInfoResolver.ts: -------------------------------------------------------------------------------- 1 | import { DefaultTraceInfoResolver, TraceInfo } from "../TraceInfoResolver"; 2 | import Logger from '../../logger'; 3 | 4 | let otel: any; 5 | export default class OpenTelemetryTraceInfoResolver extends DefaultTraceInfoResolver { 6 | private static readonly OTEL_LIB = '@opentelemetry/api'; 7 | 8 | protected resolve(): void { 9 | try { 10 | if (this.state !== 'NOTRESOLVED') { 11 | otel = require(OpenTelemetryTraceInfoResolver.OTEL_LIB); 12 | this.state = 'RESOLVED'; 13 | Logger.debug(' @opentelemetry/api resolved.'); 14 | } 15 | } catch (error) { 16 | this.state = this.state == undefined ? 'INITIATED' : 'NOTRESOLVED'; 17 | Logger.debug(' @opentelemetry/api did not resolved.'); 18 | } 19 | } 20 | 21 | get(): TraceInfo { 22 | try { 23 | if (this.state === 'NOTRESOLVED') { 24 | return; 25 | } else if (this.state === 'INITIATED') { 26 | if (require.resolve(OpenTelemetryTraceInfoResolver.OTEL_LIB)) { 27 | this.resolve(); 28 | } else { 29 | this.state = 'NOTRESOLVED'; 30 | return; 31 | } 32 | } 33 | 34 | if (this.state === 'RESOLVED') { 35 | const tracer = otel.trace; 36 | if (tracer && (otel.context.active)) { 37 | let activeSpan = tracer.getSpan(otel.context.active()); 38 | if (activeSpan && activeSpan.spanContext) { 39 | return { 40 | traceId: activeSpan.spanContext().traceId, 41 | spanId: activeSpan.spanContext().spanId, 42 | transactionId: activeSpan.spanContext().traceId, 43 | } 44 | } 45 | } 46 | } 47 | } catch (err) { 48 | Logger.debug(` An error occured while obtaining opentelemetry trace info. ${err.message}`); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/trace/thundra/ThundraTraceInfoResolver.ts: -------------------------------------------------------------------------------- 1 | import { DefaultTraceInfoResolver, TraceInfo } from "../TraceInfoResolver"; 2 | import Logger from '../../logger'; 3 | 4 | let thundra: any; 5 | export default class ThundraTraceInfoResolver extends DefaultTraceInfoResolver { 6 | private static readonly THUNDRA_LIB = '@thundra/core'; 7 | 8 | protected resolve(): void { 9 | try { 10 | if (this.state != 'NOTRESOLVED') { 11 | thundra = require(ThundraTraceInfoResolver.THUNDRA_LIB); 12 | this.state = 'RESOLVED'; 13 | Logger.debug(' @thundra/core resolved.'); 14 | } 15 | } catch (error) { 16 | this.state = this.state == undefined ? 'INITIATED' : 'NOTRESOLVED'; 17 | Logger.debug(' @thundra/core did not resolved.'); 18 | } 19 | } 20 | 21 | get(): TraceInfo { 22 | try { 23 | if (this.state == 'NOTRESOLVED') { 24 | return; 25 | } else if (this.state == 'INITIATED') { 26 | if (require.resolve(ThundraTraceInfoResolver.THUNDRA_LIB)) { 27 | this.resolve(); 28 | } else { 29 | this.state = 'NOTRESOLVED'; 30 | return; 31 | } 32 | } 33 | 34 | if (this.state == 'RESOLVED') { 35 | const tracer = thundra.tracer(); 36 | if (tracer && tracer.getActiveSpan) { 37 | const activeSpan = tracer.getActiveSpan(); 38 | if (activeSpan && activeSpan.spanContext) { 39 | return { 40 | traceId: activeSpan.spanContext.traceId, 41 | spanId: activeSpan.spanContext.spanId, 42 | transactionId: activeSpan.transactionId, 43 | } 44 | } 45 | } 46 | } 47 | } catch (err) { 48 | Logger.debug(` An error occured while obtaining thundra trace info. ${err.message}`); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/AstValidator.ts: -------------------------------------------------------------------------------- 1 | import * as estree from 'estree'; 2 | 3 | /** 4 | * Validates if the AST represented by the node has obvious side-effects. 5 | * It catches the most common cases such as assignments, method calls, and 6 | * control flow. It doesn't (presently) catch property access that may end 7 | * up calling accessors. 8 | * 9 | * @param {Object} node AST Node (as per the Mozilla Parser API) 10 | * @return {boolean} if the exper 11 | */ 12 | 13 | export default class AstValidator { 14 | static isValid(node: estree.Node | null): boolean { 15 | // Empty expression is allowed 16 | if (node === null) { 17 | return true; 18 | } 19 | 20 | switch (node.type) { 21 | case 'Program': 22 | return node.body.every(AstValidator.isValid); 23 | 24 | // 25 | // S T A T E M E N T S 26 | // 27 | case 'EmptyStatement': 28 | return true; 29 | case 'ExpressionStatement': 30 | return AstValidator.isValid(node.expression); 31 | case 'BlockStatement': 32 | return node.body.every(AstValidator.isValid); 33 | case 'LabeledStatement': 34 | return AstValidator.isValid(node.body); 35 | 36 | // 37 | // E X P R E S S I O N S 38 | // 39 | case 'AssignmentExpression': 40 | case 'CallExpression': 41 | case 'FunctionExpression': 42 | case 'NewExpression': 43 | case 'UpdateExpression': 44 | return false; 45 | 46 | case 'Identifier': 47 | case 'Literal': 48 | case 'ThisExpression': 49 | return true; 50 | 51 | case 'ArrayExpression': 52 | return node.elements.every(AstValidator.isValid); 53 | 54 | case 'BinaryExpression': 55 | case 'LogicalExpression': 56 | return AstValidator.isValid(node.left) && AstValidator.isValid(node.right); 57 | 58 | case 'ConditionalExpression': 59 | return ( 60 | AstValidator.isValid(node.test) && 61 | AstValidator.isValid(node.alternate) && 62 | AstValidator.isValid(node.consequent) 63 | ); 64 | 65 | case 'MemberExpression': 66 | return AstValidator.isValid(node.object) && AstValidator.isValid(node.property); 67 | 68 | case 'ObjectExpression': 69 | // every property is a valid expression 70 | return node.properties.every(prop => { 71 | return AstValidator.isValid((prop as {value: estree.Node}).value); 72 | }); 73 | 74 | case 'SequenceExpression': 75 | return node.expressions.every(AstValidator.isValid); 76 | 77 | case 'UnaryExpression': 78 | return AstValidator.isValid(node.argument); 79 | 80 | case 'SpreadElement': 81 | return AstValidator.isValid(node.argument); 82 | 83 | case 'TemplateLiteral': 84 | return node.quasis.every(AstValidator.isValid) && node.expressions.every(AstValidator.isValid); 85 | case 'TaggedTemplateExpression': 86 | return AstValidator.isValid(node.tag) && AstValidator.isValid(node.quasi); 87 | case 'TemplateElement': 88 | return true; 89 | 90 | default: 91 | return false; 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/CaptureUtils.ts: -------------------------------------------------------------------------------- 1 | import PathUtils from './PathUtils'; 2 | 3 | const resolvableFrameFullPathResults: Map = new Map(); 4 | 5 | export default class CaptureUtils { 6 | static formatValue(type: string, value: any) { 7 | if (value === undefined) { 8 | return ''; 9 | } 10 | 11 | switch(type) { 12 | case 'number': 13 | case 'boolean': 14 | return value; 15 | default: 16 | return String(value); 17 | } 18 | } 19 | 20 | static shouldFramePathBeResolved(frameFullPath: string): boolean { 21 | if (resolvableFrameFullPathResults.has(frameFullPath)) { 22 | return resolvableFrameFullPathResults.get(frameFullPath); 23 | } 24 | 25 | let result = true; 26 | // Only capture data from the frames for which we can link the data back 27 | // to the source files. 28 | if (!PathUtils.isPathInCurrentWorkingDirectory(frameFullPath)) { 29 | result = false; 30 | } 31 | 32 | if (result && PathUtils.isPathInNodeModulesDirectory(PathUtils.resolveRelativePath(frameFullPath))) { 33 | result = false; 34 | } 35 | 36 | if (resolvableFrameFullPathResults.size > 10000) { 37 | resolvableFrameFullPathResults.clear(); 38 | } 39 | 40 | resolvableFrameFullPathResults.set(frameFullPath, result); 41 | return result; 42 | } 43 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/CommunicationUtils.ts: -------------------------------------------------------------------------------- 1 | import Application from "../application/Application"; 2 | import ApplicationInfo from "../application/ApplicationInfo"; 3 | import { 4 | BrokerEvent, 5 | BrokerResponse, 6 | CommunicationApiData, 7 | FilterTracePointsRequest, 8 | FilterLogPointsRequest, 9 | GetConfigRequest, 10 | } from "../types"; 11 | import UuidUtils from "./UuidUtils"; 12 | 13 | export default class CommunicationUtils { 14 | static createTracepointFilterRequest() { 15 | const applicationInfo = Application.getApplicationInfo(); 16 | if (!applicationInfo) { 17 | return; 18 | } 19 | 20 | return new FilterTracePointsRequest({ 21 | name: applicationInfo.applicationName, 22 | stage: applicationInfo.applicationStage, 23 | version: applicationInfo.applicationVersion, 24 | customTags: applicationInfo.applicationTags 25 | }, 26 | UuidUtils.generateId()) 27 | } 28 | 29 | static createLogpointFilterRequest() { 30 | const applicationInfo = Application.getApplicationInfo(); 31 | if (!applicationInfo) { 32 | return; 33 | } 34 | 35 | return new FilterLogPointsRequest({ 36 | name: applicationInfo.applicationName, 37 | stage: applicationInfo.applicationStage, 38 | version: applicationInfo.applicationVersion, 39 | customTags: applicationInfo.applicationTags 40 | }, 41 | UuidUtils.generateId()) 42 | } 43 | 44 | static createGetConfigRequest() { 45 | const applicationInfo = Application.getApplicationInfo(); 46 | if (!applicationInfo) { 47 | return; 48 | } 49 | 50 | return new GetConfigRequest(UuidUtils.generateId()) 51 | } 52 | 53 | static appendResponseProperties(data: CommunicationApiData) { 54 | const response = data as BrokerResponse; 55 | if (response) { 56 | const applicationInfo: ApplicationInfo = Application.getApplicationInfo(); 57 | if (applicationInfo) { 58 | if (!response.applicationName) { 59 | response.applicationName = applicationInfo.applicationName; 60 | } 61 | 62 | if (!response.applicationInstanceId) { 63 | response.applicationInstanceId = applicationInfo.applicationInstanceId; 64 | } 65 | } 66 | } 67 | } 68 | 69 | static appendEventProperties(data: CommunicationApiData) { 70 | const event = data as BrokerEvent; 71 | if (event) { 72 | if (!event.id) { 73 | event.id = UuidUtils.generateId(); 74 | } 75 | 76 | if (!event.time) { 77 | event.time = new Date().getTime(); 78 | } 79 | 80 | const applicationInfo: ApplicationInfo = Application.getApplicationInfo(); 81 | if (applicationInfo) { 82 | if (!event.hostName) { 83 | event.hostName = applicationInfo.hostname; 84 | } 85 | 86 | if (!event.applicationName) { 87 | event.applicationName = applicationInfo.applicationName; 88 | } 89 | 90 | if (!event.applicationInstanceId) { 91 | event.applicationInstanceId = applicationInfo.applicationInstanceId; 92 | } 93 | } 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/ConfigUtils.ts: -------------------------------------------------------------------------------- 1 | import { CONFIG_CONSTANT } from "../constants" 2 | 3 | export default class ConfigUtils { 4 | static getAvailableNumberValue = (value: number, property: string) => { 5 | return value < CONFIG_CONSTANT[property].min 6 | ? CONFIG_CONSTANT[property].default 7 | : value > CONFIG_CONSTANT[property].max 8 | ? CONFIG_CONSTANT[property].max 9 | : value; 10 | } 11 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/CryptoUtils.ts: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | 3 | export default class CryptoUtils { 4 | static generateSHA256(source: string) { 5 | return crypto 6 | .createHash('sha256') 7 | .update(source) 8 | .digest('hex'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/FileUtils.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import PathUtils from './PathUtils'; 3 | 4 | export default class FileUtils { 5 | static isFileTypescript(filename: string) { 6 | return PathUtils.getFileExtention(filename) === '.ts' 7 | } 8 | 9 | static statSync(filename: string) { 10 | return fs.statSync(filename); 11 | } 12 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/MustacheExpressionUtils.ts: -------------------------------------------------------------------------------- 1 | const Mustache = require('mustache'); 2 | 3 | export default class MustacheUtils { 4 | static render(expression: string, model: any) { 5 | return Mustache.render(expression, model); 6 | } 7 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/OsUtils.ts: -------------------------------------------------------------------------------- 1 | export default class OsUtils { 2 | static isWindows(): boolean { 3 | return process.platform === 'win32'; 4 | }; 5 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/PathUtils.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | const PROCESS_CWD = process.cwd(); 4 | const PROCESS_CWD_LENGTH = PROCESS_CWD.length + 1; 5 | 6 | const FILE_PROTOCOL = 'file://'; 7 | // on windows on Node 11+ the file protocol needs to have three slashes 8 | const WINDOWS_FILE_PROTOCOL = 'file:///'; 9 | 10 | // Used to match paths like file:///C:/... on windows 11 | // but do not match paths like file:///home/... on linux 12 | const WINDOWS_URL_REGEX = RegExp(`^${WINDOWS_FILE_PROTOCOL}[a-zA-Z]+:`); 13 | 14 | export default class PathUtils { 15 | static getAbsolutePath(candidatePath: string): string { 16 | return path.isAbsolute(candidatePath) ? candidatePath : path.join(PROCESS_CWD, candidatePath); 17 | }; 18 | 19 | static getFileExtention(filename: string) { 20 | return path.extname(filename); 21 | } 22 | 23 | static getRoutePath(filename: string) { 24 | return filename.replace(PROCESS_CWD, ''); 25 | } 26 | 27 | static canonizeFileName(filename: string) { 28 | return path.normalize(filename.replace(/[\\\/]/g, '/')); 29 | } 30 | 31 | static stripFileProtocol(path: string): string { 32 | const lowerPath = path.toLowerCase(); 33 | if (WINDOWS_URL_REGEX.test(lowerPath)) { 34 | return path.substr(WINDOWS_FILE_PROTOCOL.length); 35 | } 36 | 37 | if (lowerPath.startsWith(FILE_PROTOCOL)) { 38 | return path.substr(FILE_PROTOCOL.length); 39 | } 40 | 41 | return path; 42 | } 43 | 44 | static stripCurrentWorkingDirectory(path: string): string { 45 | // Strip 1 extra character to remove the slash. 46 | return PathUtils.stripFileProtocol(path).substr(PROCESS_CWD_LENGTH); 47 | } 48 | 49 | static resolveRelativePath(fullPath: string): string { 50 | return PathUtils.stripCurrentWorkingDirectory(fullPath); 51 | } 52 | 53 | static isPathInCurrentWorkingDirectory(path: string): boolean { 54 | return (PathUtils.stripFileProtocol(path).indexOf(PROCESS_CWD) === 0); 55 | } 56 | 57 | static isPathInNodeModulesDirectory(path: string): boolean { 58 | return PathUtils.stripFileProtocol(path).indexOf('node_modules') === 0; 59 | } 60 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/ProbeUtils.ts: -------------------------------------------------------------------------------- 1 | import { Probe } from "../types"; 2 | import UuidUtils from "./UuidUtils"; 3 | import * as acorn from 'acorn'; 4 | import * as ProbeErrors from '../error/ProbeErrors'; 5 | import ProbeErrorCodes from "../error/ProbeErrorCodes"; 6 | import CodedError from "../error/CodedError"; 7 | import { 8 | PROBE_DEFAULT_EXPIRY_SECS, 9 | PROBE_DEFAULT_EXPIRY_COUNT, 10 | PROBE_MAX_EXPIRY_SECS, 11 | PROBE_MAX_EXPIRY_COUNT, 12 | } from '../constants'; 13 | import ConfigProvider from "../config/ConfigProvider"; 14 | import { ConfigNames } from "../config/ConfigNames"; 15 | 16 | const AstValidator = require('./AstValidator').default; 17 | 18 | export default class ProbeUtils { 19 | static getProbeId(probe: Probe) { 20 | return `${probe.type}:${probe.id}`; 21 | } 22 | 23 | static extractFileNameFrom(filename: string, extractText: string) { 24 | const idx = filename.indexOf(extractText); 25 | if (idx >= 0) { 26 | filename = filename.substring(idx + extractText.length).split('?')[0]; 27 | } 28 | 29 | return filename; 30 | } 31 | 32 | static formatErrorMesage(rawMessage: string, data: { [key: string]: any }, reason: string = ''): string { 33 | try { 34 | let result = rawMessage; 35 | (Object.keys(data) || []).forEach(key => { 36 | const regexp = new RegExp('\\{' + key + '\\}', 'gi'); 37 | result = result.replace(regexp, data[key]); 38 | }) 39 | 40 | if (reason) { 41 | result = result.replace('{reason}', reason); 42 | } 43 | 44 | return result; 45 | } catch (error) { 46 | return reason || rawMessage; 47 | } 48 | } 49 | 50 | static generateLocationId(probe: Probe) { 51 | return UuidUtils.generareIdFrom(`${probe.fileName}${probe.lineNo}${probe.client}`); 52 | } 53 | 54 | static validateCondition(probe: Probe) { 55 | if (probe.condition) { 56 | const ast: acorn.Node | null = acorn.parse(probe.condition, { 57 | sourceType: 'script', 58 | ecmaVersion: 6, 59 | }); 60 | 61 | if (!AstValidator.isValid(ast)) { 62 | throw new ProbeErrors.ConditionNotValid(`Condition '${probe.condition}' is not valid`); 63 | } 64 | } 65 | } 66 | 67 | static getCodedError(error: Error, data: any) { 68 | return ProbeUtils.getCodedErrorByName(error.constructor.name, data, error.message); 69 | } 70 | 71 | static getCodedErrorByName(errorName: string, data: any, reason?: string) { 72 | const knownError = ProbeErrorCodes[errorName]; 73 | if (!knownError) { 74 | return; 75 | } 76 | 77 | return new CodedError( 78 | knownError.code, 79 | ProbeUtils.formatErrorMesage( 80 | knownError.message, 81 | data, 82 | reason, 83 | )); 84 | } 85 | 86 | static extractFileName(filename: string) { 87 | return ProbeUtils.extractFileNameFrom(filename, 'contents'); 88 | } 89 | 90 | static fillProbeId(probe: Probe) { 91 | probe.id = ProbeUtils.getProbeId(probe); 92 | } 93 | 94 | static fillClientDefault(probe: Probe) { 95 | if (!probe.client) { 96 | probe.client = ConfigProvider.get(ConfigNames.broker.client); 97 | } 98 | } 99 | 100 | static fillExpireSecsDefault(probe: Probe) { 101 | if (probe.expireSecs > 0) { 102 | probe.expireSecs = Math.min(probe.expireSecs, PROBE_MAX_EXPIRY_SECS); 103 | } else { 104 | probe.expireSecs = PROBE_DEFAULT_EXPIRY_SECS; 105 | } 106 | } 107 | 108 | static fillExpireCountDefault(probe: Probe) { 109 | if (probe.expireCount > 0) { 110 | probe.expireCount = Math.min(probe.expireCount, PROBE_MAX_EXPIRY_COUNT); 111 | } else { 112 | probe.expireCount = PROBE_DEFAULT_EXPIRY_COUNT; 113 | } 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/PropertyAccessClassificationUtils.ts: -------------------------------------------------------------------------------- 1 | import { PropertyAccessClassification } from "../types"; 2 | 3 | const PROPERTY_ACCESS_CLASSIFICATION = { 4 | ['ENUMERABLE-OWN']: (locals: { [key: string]: any }): string[] => { 5 | return Object.keys((locals || {})); 6 | }, 7 | ['ENUMERABLE-OWN-AND-ENUMERABLE-PARENT']: (locals: { [key: string]: any }): string[] => { 8 | let names = new Set(); 9 | for (const local in locals) { 10 | names.add(local) 11 | } 12 | 13 | return Array.from(names); 14 | }, 15 | ['ENUMERABLE-OWN-AND-NON-ENUMERABLE-OWN']: (locals: { [key: string]: any }): string[] => { 16 | return Object.getOwnPropertyNames((locals || {})); 17 | }, 18 | ['ENUMERABLE-OWN-AND-NON-ENUMERABLE-OWN-ENUMERABLE-PARENT']: (locals: { [key: string]: any }): string[] => { 19 | let names = new Set(); 20 | for (const local in locals) { 21 | names.add(local) 22 | } 23 | 24 | Object.getOwnPropertyNames((locals || {})).forEach(local => names.add(local)); 25 | return Array.from(names); 26 | }, 27 | } 28 | 29 | export default class PropertyAccessClassificationUtils { 30 | static getProperties( 31 | locals: { [key: string]: any }, 32 | classification: PropertyAccessClassification = 'ENUMERABLE-OWN'): string[] { 33 | if (!locals) { 34 | return []; 35 | } 36 | 37 | return PROPERTY_ACCESS_CLASSIFICATION[classification](locals); 38 | } 39 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/TypeCastUtils.ts: -------------------------------------------------------------------------------- 1 | export default class TypeCastUtils { 2 | static isPrimitive(type: string): boolean { 3 | return ( 4 | type === 'undefined' || 5 | type === 'boolean' || 6 | type === 'number' || 7 | type === 'string' || 8 | type === 'symbol' 9 | ); 10 | } 11 | 12 | static isObject(value: any): boolean { 13 | return value && typeof value === 'object'; 14 | } 15 | 16 | static isArray(value: any): boolean { 17 | return Array.isArray(value); 18 | } 19 | 20 | static isFunction(type: string): boolean { 21 | return type === 'function'; 22 | } 23 | 24 | static castToType(value: any, type: string) { 25 | if (!TypeCastUtils.isPrimitive(type) || value == '') { 26 | return value; 27 | } 28 | 29 | let result = value; 30 | try { 31 | switch(type) { 32 | case 'number': 33 | result = typeof value == 'string' ? Number(value): value; 34 | break; 35 | case 'boolean': 36 | result = typeof value == 'string' ? value === 'true': value; 37 | break; 38 | } 39 | } finally { 40 | return result; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/UrlUtils.ts: -------------------------------------------------------------------------------- 1 | import * as url from 'url' 2 | 3 | export default class UrlUtils { 4 | static getFilePath(filename: string) { 5 | return url.fileURLToPath(filename); 6 | } 7 | 8 | static isEncoded(uri: string) { 9 | uri = uri || ''; 10 | return uri !== decodeURIComponent(uri); 11 | } 12 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/UuidUtils.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | import { v5 as uuidv5 } from 'uuid'; 3 | import { AGENT_UUID_CONST } from '../constants'; 4 | 5 | export default class UuidUtils { 6 | static generateId(): string { 7 | return uuidv4(); 8 | }; 9 | 10 | static generareIdFrom(value: string): string { 11 | return uuidv5(value, AGENT_UUID_CONST); 12 | }; 13 | 14 | static generateShortId(): string { 15 | return UuidUtils.generateId().substring(0, 8); 16 | }; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/src/utils/VersionUtils.ts: -------------------------------------------------------------------------------- 1 | export default class VersionUtils { 2 | static validateVersion(major: number, minor = 0, patch = 0) { 3 | let [runtimeMajor, runtimeMinor, runtimePatch] = process.version.substr(1).split('.'); 4 | const parsedMajor = parseInt(runtimeMajor); 5 | const parsedMinor = parseInt(runtimeMinor); 6 | const parsedPatch = parseInt(runtimePatch); 7 | return parsedMajor > major || parsedMajor === major 8 | && (parsedMinor > minor || parsedMinor === minor && parsedPatch >= patch); 9 | } 10 | } -------------------------------------------------------------------------------- /packages/sidekick-agent-nodejs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "declaration": true, 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitAny": true, 8 | "target": "ES2018", 9 | "rootDir": "src", 10 | "outDir": "./dist", 11 | "preserveConstEnums": true, 12 | "removeComments": false, 13 | "strictNullChecks": false, 14 | "sourceMap": true, 15 | "baseUrl": "./", 16 | }, 17 | "include": [ 18 | "src" 19 | ] 20 | } --------------------------------------------------------------------------------