Skip to content

Commit

Permalink
address feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
Molly Lloyd committed Oct 23, 2018
1 parent 6e09979 commit 1394a7d
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 72 deletions.
136 changes: 72 additions & 64 deletions src/util/mapbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import config from './config';
import browser from './browser';
import window from './window';
import { version } from '../../package.json';
import { uuid, validateUuid, storageAvailable, warnOnce } from './util';
import { uuid, validateUuid, storageAvailable, warnOnce, extend } from './util';
import { postData } from './ajax';

import type { RequestParameters } from './ajax';
Expand Down Expand Up @@ -133,20 +133,27 @@ function formatUrl(obj: UrlObject): string {
return `${obj.protocol}://${obj.authority}${obj.path}${params}`;
}

type TelemetryEventType = 'appUserTurnstile' | 'map.load';

class TelemetryEvent {
eventData: { anonId: ?string, lastSuccess: ?number, accessToken: ?string};
eventData: { lastSuccess: ?number, accessToken: ?string};
anonId: ?string;
queue: Array<any>;
type: TelemetryEventType;
pendingRequest: ?Cancelable;

constructor() {
this.eventData = { anonId: null, lastSuccess: null, accessToken: config.ACCESS_TOKEN};
constructor(type: TelemetryEventType) {
this.type = type;
this.anonId = null;
this.eventData = {lastSuccess: null, accessToken: config.ACCESS_TOKEN};
this.queue = [];
this.pendingRequest = null;
}

fetchEventData() {
const isLocalStorageAvailable = storageAvailable('localStorage');
const storageKey = `${telemEventKey}:${config.ACCESS_TOKEN || ''}`;
const uuidKey = `${telemEventKey}.uuid:${config.ACCESS_TOKEN || ''}`;

if (isLocalStorageAvailable) {
//Retrieve cached data
Expand All @@ -155,6 +162,9 @@ class TelemetryEvent {
if (data) {
this.eventData = JSON.parse(data);
}

const uuid = window.localStorage.getItem(uuidKey);
if (uuid) this.anonId = uuid;
} catch (e) {
warnOnce('Unable to read from LocalStorage');
}
Expand All @@ -164,10 +174,13 @@ class TelemetryEvent {
saveEventData() {
const isLocalStorageAvailable = storageAvailable('localStorage');
const storageKey = `${telemEventKey}:${config.ACCESS_TOKEN || ''}`;

const uuidKey = `${telemEventKey}.uuid:${config.ACCESS_TOKEN || ''}`;
if (isLocalStorageAvailable) {
try {
window.localStorage.setItem(storageKey, JSON.stringify(this.eventData));
window.localStorage.setItem(uuidKey, this.anonId);
if (this.eventData.lastSuccess) {
window.localStorage.setItem(storageKey, JSON.stringify(this.eventData));
}
} catch (e) {
warnOnce('Unable to write to LocalStorage');
}
Expand All @@ -177,20 +190,50 @@ class TelemetryEvent {

processRequests() {}

queueRequest(date: number | {id: number, timestamp: number}) {
this.queue.push(date);
/*
* If any event data should be persisted after the POST request, the callback should modify eventData`
* to the values that should be saved. For this reason, the callback should be invoked prior to the call
* to TelemetryEvent#saveData
*/
postEvent(timestamp: number, additionalPayload: {[string]: any}, callback: (err: ?Error) => void) {
const eventsUrlObject: UrlObject = parseUrl(config.EVENTS_URL);
eventsUrlObject.params.push(`access_token=${config.ACCESS_TOKEN || ''}`);
const payload: Object = {
event: this.type,
created: new Date(timestamp).toISOString(),
sdkIdentifier: 'mapbox-gl-js',
sdkVersion: version,
userId: this.anonId
};

const finalPayload = additionalPayload ? extend(payload, additionalPayload) : payload;
const request: RequestParameters = {
url: formatUrl(eventsUrlObject),
headers: {
'Content-Type': 'text/plain' //Skip the pre-flight OPTIONS request
},
body: JSON.stringify([finalPayload])
};

this.pendingRequest = postData(request, (error) => {
this.pendingRequest = null;
callback(error);
this.saveEventData();
this.processRequests();
});
}

queueRequest(event: number | {id: number, timestamp: number}) {
this.queue.push(event);
this.processRequests();
}
}

export class MapLoadEvent extends TelemetryEvent {
eventData: { anonId: ?string, lastSuccess: ?number, accessToken: ?string};
queue: Array<{ id: number, timestamp: number}>;
pendingRequest: ?Cancelable;
+success: {[number]: boolean};

constructor() {
super();
super('map.load');
this.success = {};
}

Expand All @@ -211,43 +254,27 @@ export class MapLoadEvent extends TelemetryEvent {
// Only one load event should fire per map
if (id && this.success[id]) return;

if (!this.eventData.anonId) {
if (!this.anonId) {
this.fetchEventData();
}

if (!validateUuid(this.eventData.anonId)) {
this.eventData.anonId = uuid();
if (!validateUuid(this.anonId)) {
this.anonId = uuid();
}

const eventsUrlObject: UrlObject = parseUrl(config.EVENTS_URL);
eventsUrlObject.params.push(`access_token=${config.ACCESS_TOKEN || ''}`);
const request: RequestParameters = {
url: formatUrl(eventsUrlObject),
headers: {
'Content-Type': 'text/plain' //Skip the pre-flight OPTIONS request
},
body: JSON.stringify([{
event: 'map.load',
created: new Date(timestamp).toISOString(),
sdkIdentifier: 'mapbox-gl-js',
sdkVersion: version,
userId: this.eventData.anonId
}])
};

this.pendingRequest = postData(request, (error) => {
this.pendingRequest = null;
if (!error) {
this.postEvent(timestamp, {}, (err) => {
if (!err) {
if (id) this.success[id] = true;
this.saveEventData();
this.processRequests();
}
});
}
}


export class TurnstileEvent extends TelemetryEvent {
constructor() {
super('appUserTurnstile');
}

postTurnstileEvent(tileUrls: Array<string>) {
//Enabled only when Mapbox Access Token is set and a source uses
Expand All @@ -268,56 +295,37 @@ export class TurnstileEvent extends TelemetryEvent {
let dueForEvent = this.eventData.accessToken ? (this.eventData.accessToken !== config.ACCESS_TOKEN) : false;
//Reset event data cache if the access token changed.
if (dueForEvent) {
this.eventData.anonId = this.eventData.lastSuccess = null;
this.anonId = this.eventData.lastSuccess = null;
}
if (!this.eventData.anonId || !this.eventData.lastSuccess) {
if (!this.anonId || !this.eventData.lastSuccess) {
//Retrieve cached data
this.fetchEventData();
}

if (!validateUuid(this.eventData.anonId)) {
this.eventData.anonId = uuid();
if (!validateUuid(this.anonId)) {
this.anonId = uuid();
dueForEvent = true;
}
const nextUpdate = this.queue.shift();

const nextUpdate = this.queue.shift();
// Record turnstile event once per calendar day.
if (this.eventData.lastSuccess) {
const lastUpdate = new Date(this.eventData.lastSuccess);
const nextDate = new Date(nextUpdate);
const daysElapsed = (nextUpdate - this.eventData.lastSuccess) / (24 * 60 * 60 * 1000);
dueForEvent = dueForEvent || daysElapsed >= 1 || daysElapsed < -1 || lastUpdate.getDate() !== nextDate.getDate();
} else {
dueForEvent = true;
}

if (!dueForEvent) {
return this.processRequests();
}

const eventsUrlObject: UrlObject = parseUrl(config.EVENTS_URL);
eventsUrlObject.params.push(`access_token=${config.ACCESS_TOKEN || ''}`);

const request: RequestParameters = {
url: formatUrl(eventsUrlObject),
headers: {
'Content-Type': 'text/plain' // Skip the pre-flight OPTIONS request
},
body: JSON.stringify([{
event: 'appUserTurnstile',
created: (new Date(nextUpdate)).toISOString(),
sdkIdentifier: 'mapbox-gl-js',
sdkVersion: version,
'enabled.telemetry': false,
userId: this.eventData.anonId
}])
};

this.pendingRequest = postData(request, (error: ?Error) => {
this.pendingRequest = null;
if (!error) {
this.postEvent(nextUpdate, {"enabled.telemetry": false}, (err)=> {
if (!err) {
this.eventData.lastSuccess = nextUpdate;
this.eventData.accessToken = config.ACCESS_TOKEN;
this.saveEventData();
this.processRequests();
}
});
}
Expand Down
38 changes: 30 additions & 8 deletions test/unit/util/mapbox.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,9 +381,8 @@ test("mapbox", (t) => {

t.test('does not POST event when previously stored data is on the same day', (t) => {
const now = +Date.now();

window.localStorage.setItem(`mapbox.eventData.uuid:${config.ACCESS_TOKEN}`, uuid());
window.localStorage.setItem(`mapbox.eventData:${config.ACCESS_TOKEN}`, JSON.stringify({
anonId: uuid(),
lastSuccess: now
}));

Expand All @@ -396,8 +395,8 @@ test("mapbox", (t) => {
t.test('POSTs event when previously stored anonId is not a valid uuid', (t) => {
const now = +Date.now();

window.localStorage.setItem(`mapbox.turnstileEventData:${config.ACCESS_TOKEN}`, JSON.stringify({
anonId: 'anonymous',
window.localStorage.setItem(`mapbox.eventData.uuid:${config.ACCESS_TOKEN}`, 'anonymous');
window.localStorage.setItem(`mapbox.eventData:${config.ACCESS_TOKEN}`, JSON.stringify({
lastSuccess: now
}));

Expand All @@ -414,8 +413,8 @@ test("mapbox", (t) => {
t.test('POSTs event when previously stored timestamp is more than 24 hours in the future', (t) => {
const now = +Date.now();

window.localStorage.setItem(`mapbox.turnstileEventData:${config.ACCESS_TOKEN}`, JSON.stringify({
anonId: uuid(),
window.localStorage.setItem(`mapbox.eventData.uuid:${config.ACCESS_TOKEN}`, uuid());
window.localStorage.setItem(`mapbox.eventData:${config.ACCESS_TOKEN}`, JSON.stringify({
lastSuccess: now + ms25Hours // 24-hours later
}));

Expand Down Expand Up @@ -617,9 +616,11 @@ test("mapbox", (t) => {
});
t.test('MapLoadEvent', (t) => {
let event;
let turnstileEvent;
t.beforeEach((callback) => {
window.useFakeXMLHttpRequest();
event = new mapbox.MapLoadEvent();
turnstileEvent = new mapbox.TurnstileEvent();
callback();
});

Expand Down Expand Up @@ -686,8 +687,8 @@ test("mapbox", (t) => {
callback();
});

t.test('POSTs event when previously stored anonId is not a valid uuid', (t) => {
window.localStorage.setItem(`mapbox.turnstileEventData:${config.ACCESS_TOKEN}`, JSON.stringify({
t.test('generates new uuid when previously stored anonId is not a valid uuid', (t) => {
window.localStorage.setItem(`mapbox.eventData.uuid:${config.ACCESS_TOKEN}`, JSON.stringify({
anonId: 'anonymous'
}));

Expand Down Expand Up @@ -753,6 +754,27 @@ test("mapbox", (t) => {
t.end();
});

t.test('uses the same uuid as TurnstileEvent', (t) => {
const anonId = uuid();
window.localStorage.setItem(`mapbox.eventData.uuid:${config.ACCESS_TOKEN}`, anonId);
turnstileEvent.postTurnstileEvent(mapboxTileURLs);
event.postMapLoadEvent(mapboxTileURLs, 1);


const turnstileReq = window.server.requests[0];
turnstileReq.respond(200);
const mapLoadReq = window.server.requests[1];
mapLoadReq.respond(200);
const turnstileBody = JSON.parse(turnstileReq.requestBody)[0];
const loadBody = JSON.parse(mapLoadReq.requestBody)[0];

t.equal(turnstileBody.userId, loadBody.userId);
t.equal(turnstileBody.userId, anonId);
const turnstileEventData = JSON.parse(window.localStorage.getItem(`mapbox.eventData:${config.ACCESS_TOKEN}`));
t.ok(turnstileEventData.lastSuccess);
t.end();
});

t.end();
});

Expand Down

0 comments on commit 1394a7d

Please sign in to comment.