Skip to content

Instantly share code, notes, and snippets.

@ntotten
Created February 13, 2023 16:29
Show Gist options
  • Save ntotten/2d6ed938cc024b2026a3016d38ac27f7 to your computer and use it in GitHub Desktop.
Save ntotten/2d6ed938cc024b2026a3016d38ac27f7 to your computer and use it in GitHub Desktop.
import { ZuploRequest, ZuploContext, ResponseFactory, environment } from "@zuplo/runtime";
import { SegmentClient } from "./segment";
export async function postLogin(
request: ZuploRequest,
context: ZuploContext
): Promise<Response> {
if (!environment.SEGMENT_WRITE_KEY) {
throw new Error("SEGMENT_WRITE_KEY environment variable not set")
}
const client = new SegmentClient(environment.SEGMENT_WRITE_KEY);
const { user, stats, geoip, language, user_agent, ip } = await request.json() as Auth0LoginEvent;
await client.identity({
userId: user.user_id,
traits: {
email: user.email,
emailVerified: user.email_verified,
username: user.username,
lastName: user.family_name,
firstName: user.given_name,
logins: stats?.logins_count,
createdAt: user.created_at,
phone: user.phone_number,
phoneVerified: user.phone_verified,
avatar: user.picture,
},
context: {
active: true,
ip: ip,
location: {
longitude: geoip?.longitude,
latitude: geoip?.latitude,
city: geoip?.cityName,
country: geoip?.countryName,
},
timezone: geoip?.timeZone,
locale: language,
userAgent: user_agent
}
})
return new Response(null, { status: 200 });
}
const API_URL = "https://api.segment.io/v1";
export class SegmentClient {
private authHeader: string;
constructor(writeKey: string) {
this.authHeader = "Basic " + btoa(`${writeKey}:`);
}
identity(data: SegmentIdentity): Promise<void> {
return this.post("/identify", data);
}
track(data: SegmentTrack): Promise<void> {
return this.post("/track", data);
}
page(data: SegmentPage): Promise<void> {
return this.post("/page", data);
}
screen(data: SegmentScreen): Promise<void> {
return this.post("/screen", data);
}
group(data: SegmentGroup): Promise<void> {
return this.post("/group", data);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private async post(pathname: string, data: any): Promise<void> {
const response = await fetch(`${API_URL}${pathname}`, {
method: "POST",
headers: {
Authorization: this.authHeader,
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
// Segment always returns a 200, unless the request is in an invalid
// format or it is too large (32KB).
if (response.status !== 200) {
throw new Error("Error posting to segment API");
}
}
}
export interface SegmentBaseEvent {
userId?: string;
anonymousId?: string;
context?: Partial<SegmentContext>;
properties?: Partial<SegmentProperties>;
/**
* Timestamp when the message itself took place,
* defaulted to the current time by the Segment
* Tracking API, as a ISO-8601 format date string.
* If the event just happened, leave it out and
* we’ll use the server’s time. If you’re importing
* data from the past, make sure you to provide a
* timestamp.
*
* See the Timestamps fields docs for more detail.
*/
timestamp?: string;
}
export interface SegmentPage extends SegmentBaseEvent {
/**
* Name of the page For example, most sites have a
* “Signup” page that can be useful to tag,
* so you can see users as they move through your funnel.
*/
name: string;
}
export interface SegmentScreen extends SegmentBaseEvent {
/**
* Name of the screen See the Name field docs for more details.
*/
name: string;
}
export interface SegmentTrack extends SegmentBaseEvent {
event: string;
}
export interface SegmentGroup extends SegmentIdentity {
groupId: string;
}
export interface SegmentIdentity {
userId?: string;
anonymousId?: string;
traits?: Partial<SegmentTraits>;
context?: Partial<SegmentContext>;
/**
* Timestamp when the message itself took place,
* defaulted to the current time by the Segment
* Tracking API, as a ISO-8601 format date string.
* If the event just happened, leave it out and
* we’ll use the server’s time. If you’re importing
* data from the past, make sure you to provide a
* timestamp.
*
* See the Timestamps fields docs for more detail.
*/
timestamp?: string;
}
export interface SegmentContext {
/**
* Whether a user is active
*
* This is usually used to flag an .identify() call to just update the traits but not “last seen.”
*/
active: boolean;
/**
* Information about the current application.
*/
app: Partial<{
name: string;
version: string;
build: string;
}>;
/**
* Dictionary of information about the campaign that resulted
* in the API call, containing name, source, medium, term,
* content, and any other custom UTM parameter.
*
* This maps directly to the common UTM campaign parameters.
*/
campaign: Partial<{
[key: string]: string | number;
name: string;
source: string;
medium: string;
term: string;
content: string;
}>;
device: Partial<{
id: string;
advertisingId: string;
manufacturer: string;
model: string;
name: string;
type: string;
version: string;
}>;
ip: string;
library: Partial<{
name: string;
version: string;
}>;
locale: string;
location: Partial<{
city: string;
country: string;
latitude: string;
longitude: string;
region: string;
speed: string;
}>;
network: Partial<{
bluetooth: string;
carrier: string;
cellular: string;
wifi: string;
}>;
os: Partial<{
name: string;
version: string;
}>;
page: Partial<{
path: string;
referrer: string;
search: string;
title: string;
url: string;
}>;
referrer: Partial<{
type: string;
name: string;
url: string;
link: string;
}>;
screen: Partial<{
density: number;
height: number;
width: number;
}>;
/**
* Timezones are sent as tzdata strings to add user
* timezone information which might be stripped
* from the timestamp, for example America/New_York
*/
timezone: string;
/**
* Group / Account ID.
* This is useful in B2B use cases where you need to
* attribute your non-group calls to a company or
* account. It is relied on by several Customer
* Success and CRM tools.
*/
groupId: string;
traits: Partial<SegmentTraits>;
userAgent: string;
}
export interface SegmentTraits {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
address: Partial<{
city: string;
country: string;
postalCode: string;
state: string;
street: string;
}>;
age: number;
/**
* URL to an avatar image for the user
*/
avatar: string;
/**
* ISO-8601 date string
*/
birthday: string;
company: Partial<{
name: string;
id: string | number;
industry: string;
employeeCount: number;
plan: string;
}>;
createdAt: string;
description: string;
email: string;
firstName: string;
lastName: string;
gender: string;
/**
* Unique ID in your database for a user
*/
id: string;
/**
* Full name of a user. If you only pass a first and last name Segment automatically fills in the full name for you.
*/
name: string;
phone: string;
/**
* Title of a user, usually related to their position at a specific company. Example: “VP of Engineering”
*/
title: string;
/**
* User’s username. This should be unique to each user, like the usernames of Twitter or GitHub.
*/
username: string;
website: string;
}
export interface SegmentProperties {
[key: string]: string | number | boolean;
revenue: number;
currency: string;
value: number;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment