Skip to content
This repository has been archived by the owner on Sep 2, 2022. It is now read-only.

Commit

Permalink
Merge pull request #15 from apilytics/user-agent
Browse files Browse the repository at this point in the history
  • Loading branch information
ruohola authored Feb 2, 2022
2 parents 747feed + 50be5cb commit 5079fba
Show file tree
Hide file tree
Showing 11 changed files with 332 additions and 1,196 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Send `User-Agent` information with metrics.

## [1.2.0] - 2022-02-01

### Added
Expand Down
1 change: 1 addition & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const myApilyticsMiddleware = async (req, handler) => {
statusCode: res.statusCode,
requestSize: req.bodyBytes.length,
responseSize: res.bodyBytes.length,
userAgent: req.headers['user-agent'],
timeMillis: timer(),
});
return res;
Expand Down
1 change: 1 addition & 0 deletions packages/core/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ describe('sendApilyticsMetrics()', () => {
statusCode: null,
requestSize: undefined,
responseSize: undefined,
userAgent: '',
apilyticsIntegration: undefined,
integratedLibrary: undefined,
});
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface Params {
statusCode?: number | null;
requestSize?: number;
responseSize?: number;
userAgent?: string;
apilyticsIntegration?: string;
integratedLibrary?: string;
}
Expand All @@ -33,6 +34,8 @@ interface Params {
* for the response. E.g. if the inner request handling threw an exception.
* @param params.requestSize - Size of the user's HTTP request's body in bytes.
* @param params.responseSize - Size of the sent HTTP response's body in bytes.
* @param params.userAgent - Value of the `User-Agent` header from the user's
* HTTP request.
* @param params.apilyticsIntegration - Name of the Apilytics integration that's
* calling this, e.g. 'apilytics-node-express'.
* No need to pass this when calling from user code.
Expand All @@ -52,6 +55,7 @@ interface Params {
* statusCode: res.statusCode,
* requestSize: req.bodyBytes.length,
* responseSize: res.bodyBytes.length,
* userAgent: req.headers['user-agent'],
* timeMillis: timer(),
* });
*/
Expand All @@ -64,6 +68,7 @@ export const sendApilyticsMetrics = ({
statusCode,
requestSize,
responseSize,
userAgent,
apilyticsIntegration,
integratedLibrary,
}: Params): void => {
Expand All @@ -74,6 +79,7 @@ export const sendApilyticsMetrics = ({
statusCode: statusCode ?? undefined,
requestSize,
responseSize,
userAgent: userAgent || undefined,
timeMillis,
});
let apilyticsVersion = `${
Expand Down
38 changes: 37 additions & 1 deletion packages/express/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,14 @@ describe('apilyticsMiddleware()', () => {

const createAgent = ({
apiKey,
middleware = apilyticsMiddleware,
}: {
apiKey: string | undefined;
middleware?: typeof apilyticsMiddleware;
}): request.SuperAgentTest => {
const app = express();

app.use(apilyticsMiddleware(apiKey));
app.use(middleware(apiKey));

app.all('*', testHandler);

Expand Down Expand Up @@ -141,6 +143,16 @@ describe('apilyticsMiddleware()', () => {
});
});

it('should send User-Agent', async () => {
const agent = createAgent({ apiKey });
const response = await agent.get('/dummy').set('User-Agent', 'some agent');
expect(response.status).toEqual(200);

expect(requestSpy).toHaveBeenCalledTimes(1);
const data = JSON.parse(clientRequestMock.write.mock.calls[0]);
expect(data.userAgent).toEqual('some agent');
});

it('should handle zero request and response sizes', async () => {
const agent = createAgent({ apiKey });
const response = await agent.post('/empty');
Expand Down Expand Up @@ -199,4 +211,28 @@ describe('apilyticsMiddleware()', () => {
expect(response.status).toEqual(200);
expect(requestSpy).toHaveBeenCalledTimes(1);
});

it('should handle `express` not being installed', async () => {
let agent: request.SuperAgentTest;
jest.isolateModules(() => {
jest.mock('express/package.json', () => {
throw new Error();
});
const { apilyticsMiddleware } = require('../src');
agent = createAgent({ apiKey, middleware: apilyticsMiddleware });
});

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const response = await agent!.get('/dummy');
expect(response.status).toEqual(200);
expect(requestSpy).toHaveBeenCalledTimes(1);

expect(requestSpy).toHaveBeenLastCalledWith(
expect.objectContaining({
headers: expect.objectContaining({
'Apilytics-Version': `apilytics-node-express/${APILYTICS_VERSION};node/${process.versions.node}`,
}),
}),
);
});
});
7 changes: 5 additions & 2 deletions packages/express/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@
"clean": "rm -rf dist/ tsconfig.tsbuildinfo"
},
"dependencies": {
"@apilytics/core": "1.2.0",
"@apilytics/core": "1.2.0"
},
"peerDependencies": {
"express": "*"
},
"devDependencies": {
"@types/express": "*"
"@types/express": "*",
"express": "*"
},
"engines": {
"node": ">=12.0.0"
Expand Down
12 changes: 10 additions & 2 deletions packages/express/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { URL } from 'url';
import { milliSecondTimer, sendApilyticsMetrics } from '@apilytics/core';
import type { NextFunction, Request, RequestHandler, Response } from 'express';

const EXPRESS_VERSION = require('express/package.json').version;
let EXPRESS_VERSION: string | undefined;
try {
EXPRESS_VERSION = require('express/package.json').version;
} catch {
// `express` peer dependency not installed (for some reason).
}

/**
* Express middleware that sends API analytics data to Apilytics (https://apilytics.io).
Expand Down Expand Up @@ -55,9 +60,12 @@ export const apilyticsMiddleware = (
statusCode: res.statusCode,
requestSize,
responseSize,
userAgent: req.headers['user-agent'],
timeMillis: timer(),
apilyticsIntegration: 'apilytics-node-express',
integratedLibrary: `express/${EXPRESS_VERSION}`,
integratedLibrary: EXPRESS_VERSION
? `express/${EXPRESS_VERSION}`
: undefined,
});
});
next();
Expand Down
38 changes: 37 additions & 1 deletion packages/next/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ describe('withApilytics()', () => {

const createAgent = ({
apiKey,
middleware = withApilytics,
}: {
apiKey: string | undefined;
middleware?: typeof withApilytics;
}): request.SuperAgentTest => {
const requestListener = (
req: http.IncomingMessage,
Expand All @@ -72,7 +74,7 @@ describe('withApilytics()', () => {
req,
res,
undefined,
withApilytics(testHandler, apiKey),
middleware(testHandler, apiKey),
{
previewModeId: 'id',
previewModeEncryptionKey: 'key',
Expand Down Expand Up @@ -161,6 +163,16 @@ describe('withApilytics()', () => {
});
});

it('should send User-Agent', async () => {
const agent = createAgent({ apiKey });
const response = await agent.get('/dummy').set('User-Agent', 'some agent');
expect(response.status).toEqual(200);

expect(requestSpy).toHaveBeenCalledTimes(1);
const data = JSON.parse(clientRequestMock.write.mock.calls[0]);
expect(data.userAgent).toEqual('some agent');
});

it('should handle zero request and response sizes', async () => {
const agent = createAgent({ apiKey });
const response = await agent.post('/empty');
Expand Down Expand Up @@ -234,4 +246,28 @@ describe('withApilytics()', () => {
expect(response.status).toEqual(200);
expect(requestSpy).toHaveBeenCalledTimes(1);
});

it('should handle `next` not being installed', async () => {
let agent: request.SuperAgentTest;
jest.isolateModules(() => {
jest.mock('next/package.json', () => {
throw new Error();
});
const { withApilytics } = require('../src');
agent = createAgent({ apiKey, middleware: withApilytics });
});

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const response = await agent!.get('/dummy');
expect(response.status).toEqual(200);
expect(requestSpy).toHaveBeenCalledTimes(1);

expect(requestSpy).toHaveBeenLastCalledWith(
expect.objectContaining({
headers: expect.objectContaining({
'Apilytics-Version': `apilytics-node-next/${APILYTICS_VERSION};node/${process.versions.node}`,
}),
}),
);
});
});
5 changes: 4 additions & 1 deletion packages/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@
"clean": "rm -rf dist/ tsconfig.tsbuildinfo"
},
"dependencies": {
"@apilytics/core": "1.2.0",
"@apilytics/core": "1.2.0"
},
"peerDependencies": {
"next": "*"
},
"devDependencies": {
"next": "*",
"react": "17.0.2"
},
"engines": {
Expand Down
10 changes: 8 additions & 2 deletions packages/next/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { URL } from 'url';
import { milliSecondTimer, sendApilyticsMetrics } from '@apilytics/core';
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next';

const NEXT_VERSION = require('next/package.json').version;
let NEXT_VERSION: string | undefined;
try {
NEXT_VERSION = require('next/package.json').version;
} catch {
// `next` peer dependency not installed (for some reason).
}

/**
* Next.js middleware that sends API analytics data to Apilytics (https://apilytics.io).
Expand Down Expand Up @@ -70,9 +75,10 @@ export const withApilytics = <T>(
statusCode,
requestSize,
responseSize,
userAgent: req.headers['user-agent'],
timeMillis: timer(),
apilyticsIntegration: 'apilytics-node-next',
integratedLibrary: `next/${NEXT_VERSION}`,
integratedLibrary: NEXT_VERSION ? `next/${NEXT_VERSION}` : undefined,
});
}
};
Expand Down
Loading

0 comments on commit 5079fba

Please sign in to comment.