Last active
January 30, 2023 10:36
-
-
Save ItsWendell/45ebbb7d2ecc7e35f0a87b2f0cf62476 to your computer and use it in GitHub Desktop.
Segment Analytics client for Lambda with Fire & Forget support.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { merge } from "lodash"; | |
import { request } from "https"; | |
import { v4 as uuidv4 } from "uuid"; | |
export const name = "analytics-lamdba"; | |
export const version = "0.1.2"; | |
export type SegmentMessageTypes = | |
| "identify" | |
| "group" | |
| "page" | |
| "track" | |
| "screen" | |
| "alias"; | |
export interface SegmentMessage { | |
userId?: string; | |
anonymousId?: string; | |
timestamp?: string; | |
context?: SegmentContext; | |
traits?: SegmentTraits; | |
[x: string]: any; | |
} | |
export interface SegmentContext { | |
active?: boolean; | |
app?: { | |
name?: string; | |
version?: string | number; | |
build?: string | number; | |
}; | |
campaign?: Record<string, any>; | |
device?: Record<string, any>; | |
ip?: string; | |
library?: { | |
name?: string; | |
version?: string | number; | |
}; | |
locale?: string; | |
location?: { | |
city?: string; | |
country?: string; | |
latitude?: string; | |
longitude?: string; | |
region?: string; | |
speed?: number | string; | |
}; | |
network?: string; | |
os?: string; | |
page?: string; | |
referrer?: string; | |
screen?: string; | |
timezone?: string; | |
groupId?: string; | |
traits?: SegmentTraits; | |
userAgent?: string; | |
[x: string]: any; | |
} | |
export interface SegmentTraits { | |
avatar?: string; // URL to an avatar image for the user | |
birthday?: Date; // User’s birthday | |
company?: Record<string, any>; // Company the user represents, optionally containing?: name (a String), id (a String or Number), industry (a String), employee_count (a Number) or plan (a String) | |
createdAt?: Date; // Date the user’s account was first created. Segment recommends using ISO-8601 date strings. | |
description?: string; // Description of the user | |
email?: string; // Email address of a user | |
firstName?: string; // First name of a user | |
gender?: string; // Gender of a user | |
id?: string; // Unique ID in your database for a user | |
lastName?: string; // Last name of a user | |
name?: string; // Full name of a user. If you only pass a first and last name Segment automatically fills in the full name for you. | |
phone?: string; // Phone number of a user | |
title?: string; // Title of a user, usually related to their position at a specific company. Example?: “VP of Engineering” | |
username?: string; // User’s username. This should be unique to each user, like the usernames of Twitter or GitHub. | |
website?: string; // Website of a user | |
[x: string]: any; | |
} | |
export interface SegmentClientOptions { | |
host?: string; | |
enabled?: boolean; | |
debug?: boolean; | |
await?: boolean; // Should we await the actual response, or is fire and forget enough? | |
} | |
export interface SegmentUser { | |
userId?: string | null; | |
anonymousId?: string | null; | |
traits?: SegmentTraits; | |
context?: SegmentContext; | |
} | |
/** | |
* Segment Lambda Client | |
*/ | |
class SegmentClient { | |
/** Segment User */ | |
user: SegmentUser = { | |
anonymousId: uuidv4(), | |
}; | |
/** Segment Authorization Token */ | |
token = ""; | |
/** Client Options */ | |
options: SegmentClientOptions = {}; | |
/** Base URL */ | |
baseURL = "https://api.segment.io/v1"; | |
/** Segment Write Key */ | |
writeKey: string | undefined; | |
constructor(writeKey?: string, options?: SegmentClientOptions) { | |
if (!writeKey) { | |
console.warn( | |
"[Segment Analytics] No writeKey passed for segment analytics." | |
); | |
return; | |
} | |
this.setWriteKey(writeKey); | |
this.options = options ? options : {}; | |
this.options.enabled = Boolean(writeKey); | |
} | |
private setWriteKey = (writeKey: string) => { | |
this.token = `Basic ${Buffer.from(`${writeKey}:`).toString("base64")}`; | |
}; | |
identify = async ( | |
userId?: string, | |
traits?: SegmentTraits, | |
config?: { | |
send?: boolean; | |
context?: SegmentContext; | |
} | |
) => { | |
// If userId's are equal, merge traits / context. | |
if (userId && this.user?.userId === userId) { | |
this.user = merge(this.user, { | |
traits, | |
context: config?.context, | |
}); | |
} else { | |
// If not, set userId or anonymousId, traits and context. | |
this.user = { | |
userId: userId, | |
anonymousId: !userId ? uuidv4() : undefined, | |
traits, | |
context: config?.context, | |
}; | |
} | |
// Only send this out when it's actually nessesary | |
if (config?.send) { | |
return await this.send("identify", { | |
traits, | |
context: config?.context, | |
}); | |
} | |
}; | |
track = ( | |
event: string, | |
properties?: SegmentMessage, | |
context?: SegmentContext | |
) => { | |
return this.send("track", { | |
event, | |
properties, | |
context, | |
}); | |
}; | |
post = (type: SegmentMessageTypes, data: SegmentMessage) => { | |
const content = JSON.stringify(data); | |
const options = { | |
host: "api.segment.io", | |
path: `/v1/${type}`, | |
method: "POST", | |
headers: { | |
Authorization: `${this.token}`, | |
"Content-Type": `application/json`, | |
"Content-Length": Buffer.byteLength(content), | |
"User-Agent": `${name}/${version}`, | |
}, | |
}; | |
return new Promise((resolve, reject) => { | |
const req = request( | |
options, | |
this.options.await | |
? () => { | |
resolve(true); | |
} | |
: undefined | |
); | |
req.on("error", (e) => { | |
reject(e); | |
}); | |
req.write(content); | |
if (!this.options.await) { | |
req.end(() => { | |
resolve(true); | |
}); | |
} | |
}); | |
}; | |
send = async (type: SegmentMessageTypes, data: SegmentMessage) => { | |
const message: SegmentMessage = data; | |
message.anonymousId = !data?.userId | |
? message?.anonymousId || this.user.anonymousId || undefined | |
: undefined; | |
message.userId = message?.userId || this.user.userId || undefined; | |
message.context = message.context || {}; | |
if (type !== "identify") { | |
message.context = merge(message.context, { | |
...(this?.user?.context || {}), | |
traits: this.user?.traits, | |
}); | |
} else { | |
message.context = merge(message.context, { | |
...(this?.user?.context || {}), | |
}); | |
} | |
if (!message.timestamp) { | |
message.timestamp = new Date().toISOString(); | |
} | |
if (this.options?.enabled) { | |
try { | |
await this.post(type, message); | |
} catch (e) { | |
console.error("[Segment Analytics]", e); | |
return null; | |
} | |
} | |
return null; | |
}; | |
} | |
export const analytics = new SegmentClient( | |
process.env.SEGMENT_WRITE_KEY || "", | |
{ | |
debug: process.env.SEGMENT_DEBUG === "true", | |
} | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment