Skip to content
This repository has been archived by the owner on Mar 31, 2021. It is now read-only.

Commit

Permalink
Switch from 'request' to 'got' (close #61)
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Boocock authored and paulboocock committed Sep 4, 2020
1 parent b95c6e7 commit 1f3a4a8
Show file tree
Hide file tree
Showing 9 changed files with 800 additions and 518 deletions.
515 changes: 320 additions & 195 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,12 @@
],
"license": "Apache-2.0",
"dependencies": {
"request": "^2.88.2",
"got": "^11.5.2",
"snowplow-tracker-core": "^0.9.1",
"tslib": "^2.0.1"
},
"devDependencies": {
"@types/node": "^14.6.0",
"@types/request": "^2.48.5",
"@types/sinon": "^9.0.5",
"@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^3.10.1",
Expand Down
144 changes: 22 additions & 122 deletions src/emitter.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,3 @@
/*
* Node.js tracker for Snowplow: emitter.js
*
* Copyright (c) 2014-2017 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/

import request from 'request';
import http from 'http';
import https from 'https';
import { PayloadDictionary } from 'snowplow-tracker-core';

export interface Emitter {
Expand All @@ -24,120 +6,38 @@ export interface Emitter {
}

export enum HttpProtocol {
HTTP = "http",
HTTPS = "https"
HTTP = 'http',
HTTPS = 'https',
}

export enum HttpMethod {
GET = "get",
POST = "post"
GET = 'get',
POST = 'post',
}

/**
* Create an emitter object which will send events to a collector
* Convert all fields in a payload dictionary to strings
*
* @param endpoint The collector to which events will be sent
* @param protocol "http" or "https"
* @param port The port for requests to use
* @param method "get" or "post"
* @param bufferSize Number of events which can be queued before flush is called
* @param callback Callback passed to the request function
* @param agentOptions configuration for http.Agent class
* @param payload Payload on which the new dictionary is based
*/
export function emitter(
endpoint: string,
protocol: HttpProtocol,
port?: number,
method?: HttpMethod,
bufferSize?: number,
callback?: request.RequestCallback,
agentOptions?: http.AgentOptions | https.AgentOptions
): Emitter {
const maxBufferLength = bufferSize ?? (method === HttpMethod.GET ? 0 : 10);
const path = method === HttpMethod.GET ? '/i' : '/com.snowplowanalytics.snowplow/tp2';
const targetUrl = protocol + '://' + endpoint + (port ? ':' + port : '') + path;
export const preparePayload = (payload: PayloadDictionary): Record<string, string> => {
const stringifiedPayload: Record<string, string> = {};

let buffer: Array<PayloadDictionary> = [];
const finalPayload = addDeviceSentTimestamp(payload);

/**
* Convert all fields in a payload dictionary to strings
*
* @param payload Payload on which the new dictionary is based
*/
const preparePayload = (payload: PayloadDictionary): Record<string, string> => {
const stringifiedPayload: Record<string, string> = {};

const finalPayload = addDeviceSentTimestamp(payload);

for (const key in finalPayload) {
if (Object.prototype.hasOwnProperty.call(finalPayload, key)) {
stringifiedPayload[key] = String(finalPayload[key]);
}
for (const key in finalPayload) {
if (Object.prototype.hasOwnProperty.call(finalPayload, key)) {
stringifiedPayload[key] = String(finalPayload[key]);
}
return stringifiedPayload;
};

const addDeviceSentTimestamp = (payload: PayloadDictionary): PayloadDictionary => {
payload['stm'] = new Date().getTime().toString();
return payload;
}
return stringifiedPayload;
};

/**
* Flushes all events currently stored in buffer
*/
const flush = (): void => {
const bufferCopy = buffer;
buffer = [];
if (bufferCopy.length === 0) {
return;
}

if (method === HttpMethod.POST) {
const postJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/payload_data/jsonschema/1-0-4',
data: bufferCopy.map(preparePayload),
};
request.post(
{
url: targetUrl,
json: postJson,
agentOptions: agentOptions,
headers: {
'content-type': 'application/json; charset=utf-8',
},
},
callback
);
} else {
for (let i = 0; i < bufferCopy.length; i++) {
request.get(
{
url: targetUrl,
agentOptions: agentOptions,
qs: addDeviceSentTimestamp(bufferCopy[i]),
},
callback
);
}
}
};

/**
* Adds a payload to the internal buffer and sends if buffer >= bufferSize
* @param payload Payload to add to buffer
*/
const input = (payload: PayloadDictionary): void => {
buffer.push(payload);
if (buffer.length >= maxBufferLength) {
flush();
}
};

return {
/**
* Send all events queued in the buffer to the collector
*/
flush,
input,
};
}
/**
* Adds the 'stm' paramater with the current time to the payload
* @param payload The payload which will be mutated
*/
const addDeviceSentTimestamp = (payload: PayloadDictionary): PayloadDictionary => {
payload['stm'] = new Date().getTime().toString();
return payload;
};
142 changes: 142 additions & 0 deletions src/got_emitter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Node.js tracker for Snowplow: emitter.js
*
* Copyright (c) 2014-2017 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/

import got, { Response, RequestError, Agents, RequiredRetryOptions, ToughCookieJar, PromiseCookieJar } from 'got';
import { PayloadDictionary } from 'snowplow-tracker-core';

import { Emitter, HttpProtocol, HttpMethod, preparePayload } from './emitter';
import { version } from './version';

/**
* Create an emitter object, which uses the `got` library, that will send events to a collector
*
* @param endpoint The collector to which events will be sent
* @param protocol http or https
* @param port The port for requests to use
* @param method get or post
* @param bufferSize Number of events which can be queued before flush is called
* @param retry Configure the retry policy for `got` - https://github.com/sindresorhus/got/blob/v11.5.2/readme.md#retry
* @param cookieJar Add a cookieJar to `got` - https://github.com/sindresorhus/got/blob/v11.5.2/readme.md#cookiejar
* @param callback Callback called after a `got` request following retries - called with ErrorRequest (https://github.com/sindresorhus/got/blob/v11.5.2/readme.md#errors) and Response (https://github.com/sindresorhus/got/blob/v11.5.2/readme.md#response)
* @param agents Set new http.Agent and https.Agent objects on `got` requests - https://github.com/sindresorhus/got/blob/v11.5.2/readme.md#agent
*/
export function gotEmitter(
endpoint: string,
protocol: HttpProtocol,
port?: number,
method?: HttpMethod,
bufferSize?: number,
retry?: number | Partial<RequiredRetryOptions>,
cookieJar?: PromiseCookieJar | ToughCookieJar,
callback?: (error?: RequestError, response?: Response<string>) => void,
agents?: Agents
): Emitter {
const maxBufferLength = bufferSize ?? (method === HttpMethod.GET ? 0 : 10);
const path = method === HttpMethod.GET ? '/i' : '/com.snowplowanalytics.snowplow/tp2';
const targetUrl = protocol + '://' + endpoint + (port ? ':' + port : '') + path;

let buffer: Array<PayloadDictionary> = [];

/**
* Handles the callback on a successful response if the callback is present
* @param response The got response object
*/
const handleSuccess = (response: Response<string>) => {
if (callback) {
try {
callback(undefined, response);
} catch (e) {
console.error('Error in callback after success', e);
}
}
};

/**
* Handles the callback on a failed request if the callback is present
* @param error The got error object
*/
const handleFailure = (error: RequestError) => {
if (callback) {
try {
callback(error);
} catch (e) {
console.error('Error in callback after failure', e);
}
}
};

/**
* Flushes all events currently stored in buffer
*/
const flush = (): void => {
const bufferCopy = buffer;
buffer = [];
if (bufferCopy.length === 0) {
return;
}

if (method === HttpMethod.POST) {
const postJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/payload_data/jsonschema/1-0-4',
data: bufferCopy.map(preparePayload),
};
got
.post(targetUrl, {
json: postJson,
headers: {
'content-type': 'application/json; charset=utf-8',
'user-agent': `snowplow-nodejs-tracker/${version}`,
},
agent: agents,
retry: retry,
cookieJar: cookieJar,
})
.then(handleSuccess, handleFailure);
} else {
for (let i = 0; i < bufferCopy.length; i++) {
got
.get(targetUrl, {
searchParams: preparePayload(bufferCopy[i]),
headers: {
'user-agent': `snowplow-nodejs-tracker/${version}`,
},
agent: agents,
retry: retry,
cookieJar: cookieJar,
})
.then(handleSuccess, handleFailure);
}
}
};

/**
* Adds a payload to the internal buffer and sends if buffer >= bufferSize
* @param payload Payload to add to buffer
*/
const input = (payload: PayloadDictionary): void => {
buffer.push(payload);
if (buffer.length >= maxBufferLength) {
flush();
}
};

return {
/**
* Send all events queued in the buffer to the collector
*/
flush,
input,
};
}
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/

export { Emitter, HttpMethod, HttpProtocol } from './emitter';
export { tracker, Tracker, EcommerceTransactionItem } from './tracker';
export { emitter, Emitter, HttpMethod, HttpProtocol } from './emitter';
export { gotEmitter } from './got_emitter';
export { version } from './version';
7 changes: 4 additions & 3 deletions src/tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/

import { version } from './version';
import { trackerCore, PayloadData, SelfDescribingJson, Timestamp, Core } from 'snowplow-tracker-core';

import { version } from './version';
import { Emitter } from './emitter';

export interface EcommerceTransactionItem {
Expand Down Expand Up @@ -175,11 +176,11 @@ export function tracker(

const setDomainUserId = function (userId: string) {
domainUserId = userId;
}
};

const setNetworkUserId = function (userId: string) {
networkUserId = userId;
}
};

return {
trackEcommerceTransactionWithItems,
Expand Down
Loading

0 comments on commit 1f3a4a8

Please sign in to comment.