Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 23 additions & 22 deletions src/EventRecorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,6 @@ export class EventRecorder {
private timer: NodeJS.Timer;
private readonly dispatch: Promise<void>;

set flushInterval(value: number) {
clearInterval(this.timer);
this.timer = setInterval(() => this.flush(), value);
}

get accessQueue(): IAccessEvent[] {
return this.sendAccessQueue;
}

get eventQueue(): (AccessEvent | CustomEvent | ClickEvent | PageViewEvent)[] {
return this.sendEventQueue;
}

constructor(
clientSdkKey: string,
eventsUrl: string,
Expand All @@ -39,6 +26,19 @@ export class EventRecorder {
this.dispatch = this.startDispatch();
}

set flushInterval(value: number) {
clearInterval(this.timer);
this.timer = setInterval(() => this.flush(), value);
}

get accessQueue(): IAccessEvent[] {
return this.sendAccessQueue;
}

get eventQueue(): (AccessEvent | CustomEvent | ClickEvent | PageViewEvent)[] {
return this.sendEventQueue;
}

public recordAccessEvent(accessEvent: IAccessEvent): void {
if (this.closed) {
console.warn("Trying to push access record to a closed EventProcessor, omitted");
Expand Down Expand Up @@ -158,27 +158,28 @@ class AsyncBlockingQueue<T> {
this.promises = [];
}

private add() {
this.promises.push(new Promise(resolve => {
this.resolvers.push(resolve);
}));
}

enqueue(t: T) {
public enqueue(t: T) {
if (!this.resolvers.length) {
this.add();
}
this.resolvers.shift()?.(t);
}

dequeue(): Promise<T> | undefined {
public dequeue(): Promise<T> | undefined {
if (!this.promises.length) {
this.add();
}
return this.promises.shift();
}

isEmpty() {
public isEmpty() {
return !this.promises.length;
}

private add() {
this.promises.push(new Promise(resolve => {
this.resolvers.push(resolve);
}));
}

}
68 changes: 37 additions & 31 deletions src/FeatureProbe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const STATUS = {
ERROR: "error",
};

const REFRESH_INTERVAL = 1000;
const TIMEOUT_INTERVAL = 10000;

/**
* You can obtainan a client of FeatureProbe,
* which provides access to all of the SDK's functionality.
Expand Down Expand Up @@ -54,8 +57,8 @@ class FeatureProbe extends TinyEmitter {
realtimePath,
clientSdkKey,
user,
refreshInterval = 1000,
timeoutInterval = 10000,
refreshInterval = REFRESH_INTERVAL,
timeoutInterval = TIMEOUT_INTERVAL,
enableAutoReporting = true,
}: FPConfig) {
super();
Expand Down Expand Up @@ -98,6 +101,29 @@ class FeatureProbe extends TinyEmitter {
}
}

public static newForTest(toggles: { [key: string]: boolean }): FeatureProbe {
const fp = new FeatureProbe({
remoteUrl: "http://127.0.0.1:4000",
clientSdkKey: "_",
user: new FPUser(),
timeoutInterval: 1000,
});
const _toggles: { [key: string]: FPDetail } = {};
for (const key in toggles) {
const value = toggles[key];
_toggles[key] = {
value: value,
ruleIndex: null,
variationIndex: null,
version: 0,
reason: "",
};
}
fp.toggles = _toggles;
fp.successInitialized();
return fp;
}

/**
* Start the FeatureProbe client.
*/
Expand Down Expand Up @@ -339,29 +365,6 @@ class FeatureProbe extends TinyEmitter {
});
}

static newForTest(toggles: { [key: string]: boolean }): FeatureProbe {
const fp = new FeatureProbe({
remoteUrl: "http://127.0.0.1:4000",
clientSdkKey: "_",
user: new FPUser(),
timeoutInterval: 1000,
});
const _toggles: { [key: string]: FPDetail } = {};
for (const key in toggles) {
const value = toggles[key];
_toggles[key] = {
value: value,
ruleIndex: null,
variationIndex: null,
version: 0,
reason: "",
};
}
fp.toggles = _toggles;
fp.successInitialized();
return fp;
}

private connectSocket() {
const socket = io(this.realtimeUrl, {
path: this.realtimePath,
Expand Down Expand Up @@ -395,13 +398,16 @@ class FeatureProbe extends TinyEmitter {
if (typeof v == valueType) {
const timestamp = Date.now();

const DEFAULT_VARIATION_INDEX = -1;
const DEFAULT_VERSION = 0;

this.eventRecorder?.recordAccessEvent({
time: timestamp,
key: key,
value: detail.value,
index: detail.variationIndex ?? -1,
version: detail.version ?? 0,
reason: detail.reason
index: detail.variationIndex ?? DEFAULT_VARIATION_INDEX,
version: detail.version ?? DEFAULT_VERSION,
reason: detail.reason,
});

if (detail.trackAccessEvents) {
Expand All @@ -411,9 +417,9 @@ class FeatureProbe extends TinyEmitter {
user: this.getUser().getKey(),
key: key,
value: detail.value,
variationIndex: detail.variationIndex ?? -1,
variationIndex: detail.variationIndex ?? DEFAULT_VARIATION_INDEX,
ruleIndex: detail.ruleIndex ?? null,
version: detail.version ?? 0,
version: detail.version ?? DEFAULT_VERSION,
});
}

Expand Down Expand Up @@ -494,7 +500,7 @@ class FeatureProbe extends TinyEmitter {
"Content-Type": "application/json",
UA: getPlatform()?.UA,
}, {
user: userParam
user: userParam,
}, (json: unknown) => {
if (this.status !== STATUS.ERROR) {
this.toggles = json as { [key: string]: FPDetail; } | undefined;
Expand Down
25 changes: 13 additions & 12 deletions src/autoReportEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import { FPUser } from ".";
import { EventRecorder } from "./EventRecorder";
import { ClickEvent, IEvent, IEventValue, PageViewEvent } from "./types";

const WATCH_URL_CHANGE_INTERVAL = 300;

// Reference: https://github.com/sindresorhus/escape-string-regexp
function escapeStringRegexp(string: string): string {
if (typeof string !== 'string') {
throw new TypeError('Expected a string');
}
if (typeof string !== 'string') {
throw new TypeError('Expected a string');
}

return string
.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
.replace(/-/g, '\\x2d');
return string
.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
.replace(/-/g, '\\x2d');
}

/**
Expand Down Expand Up @@ -54,7 +56,7 @@ export default function reportEvents(
clientSdkKey: string,
user: FPUser,
getEventsUrl: string,
eventRecorder: EventRecorder
eventRecorder: EventRecorder,
): void {
let previousUrl: string = window.location.href;
let currentUrl;
Expand Down Expand Up @@ -100,9 +102,8 @@ export default function reportEvents(
function getClickEvents(event: MouseEvent, clickEvents: IEventValue[]) {
const matchedEvents = [];

for (let i = 0; i < clickEvents.length; i++) {
for (const clickEvent of clickEvents) {
let target = event.target;
const clickEvent = clickEvents[i];
const selector = clickEvent.selector;

const elements = selector && document.querySelectorAll(selector);
Expand Down Expand Up @@ -152,8 +153,8 @@ export default function reportEvents(
if (clickEvents.length > 0) {
cb = function(event: MouseEvent) {
const result = getClickEvents(event, clickEvents);
for (let i = 0; i < result.length; i++) {
sendEvents('click', result[i]);
for (const event of result) {
sendEvents('click', event);
}
};

Expand Down Expand Up @@ -196,7 +197,7 @@ export default function reportEvents(
*/
setInterval(() => {
watchUrlChange();
}, 300);
}, WATCH_URL_CHANGE_INTERVAL);

/**
* Get events data from Server API
Expand Down
13 changes: 7 additions & 6 deletions test/autoReportEvents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FetchMock } from "jest-fetch-mock";
import * as data from "./fixtures/events.json";

const _fetch = fetch as FetchMock;
const FLUSH_INTERVAL = 10000;

beforeEach(() => {});

Expand All @@ -17,19 +18,19 @@ test("report events", (done) => {
_fetch.mockResponseOnce(JSON.stringify(data));
const clientSdkKey = 'clientSdkKey';
const eventsUrl = 'http://featureprobe.io/server/event';
const recorder = new EventRecorder(clientSdkKey, eventsUrl, 10000);
const recorder = new EventRecorder(clientSdkKey, eventsUrl, FLUSH_INTERVAL);
const user = new FPUser('11111').with("city", "2");
const DELAY = 100;

reportEvents(clientSdkKey, user, eventsUrl, recorder);


setTimeout(() => {
document.body.click();
expect(recorder.eventQueue.length).toBe(3);
expect(recorder.eventQueue[0].kind).toBe('pageview');
expect(recorder.eventQueue[1].kind).toBe('pageview');
expect(recorder.eventQueue[2].kind).toBe('click');
expect(recorder.eventQueue.shift()?.kind).toBe('pageview');
expect(recorder.eventQueue.shift()?.kind).toBe('pageview');
expect(recorder.eventQueue.shift()?.kind).toBe('click');
done();
}, 100);
}, DELAY);
});