Skip to content

Mixpanel types #116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions jsdoc.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
plugins: [
'jsdoc-plugin-intersection'
],
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"dox": "^0.9.0",
"indent-string": "^4.0.0",
"jsdoc": "^3.6.3",
"jsdoc-plugin-intersection": "^1.0.4",
"lerna": "^3.10.7",
"markdown-magic": "^0.1.25",
"mkdirp": "^0.5.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/analytics-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"test:watch": "ava -v --watch",
"clean": "rimraf lib dist && mkdirp lib dist",
"prebuild": "npm run clean && npm run types",
"types": "../../node_modules/.bin/jsdoc -t ../../node_modules/tsd-jsdoc/dist -r ./src/ -d temp-types && node scripts/types.js",
"types": "../../node_modules/.bin/jsdoc -c ../../jsdoc.config.js -t ../../node_modules/tsd-jsdoc/dist -r ./src/ -d temp-types && node scripts/types.js",
"build": "node ../../scripts/build/index.js",
"postbuild": "npm run minify-dist && node ./scripts/postbuild.js",
"watch": "node ../../scripts/build/_watch.js",
Expand Down
46 changes: 42 additions & 4 deletions packages/analytics-core/scripts/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,55 @@ const content = fs.readFileSync(TYPES_PATH, 'utf-8')
const typesFromJsDocs = content
// Remove declares
.replace(/^declare\s/gm, '')
// Export analytics instance
.replace(/type AnalyticsInstance =/gm, 'export type AnalyticsInstance =')
// Export user-facing types (and where possible, convert objects to interface so they can be extended)
.replace(/type AnalyticsInstance =/gm, 'export interface AnalyticsInstance ')
.replace(/type AnalyticsInstanceConfig =/gm, 'export interface AnalyticsInstanceConfig ')
.replace(/type (\w+)Payload =/gm, match => `export ${match}`)
// Exported so user doesn't have to do ReturnType to get them
.replace(/type DetachListeners =/gm, 'export type DetachListeners =')

// Convert following types to generics to be able to pass payload type throught them
// Export hooks and convert them to generics accepting payload type
.replace(
/type (\w+)Hook =(.*)Context/gm,
(_, typeName, argsDefStart) => `export type ${typeName}Hook<T = ${typeName}Payload> =${argsDefStart}Context<T>`,
)
// Convert contexts to generics accepting payload type
.replace(
/type (\w+)Context =(.*)ContextProps(\W)/gm,
(_, typeName, typeDefStart, typeDefEnd) => `type ${typeName}Context<T = ${typeName}Payload> =${typeDefStart}ContextProps<T>${typeDefEnd}`,
)
// Convert context props types to generics accepting payload type
.replace(
/type (\w+)ContextProps =((.|\s)*?)payload: \w+/gm,
(_, typeName, typeDefStart) => `type ${typeName}ContextProps<T = ${typeName}Payload> =${typeDefStart}payload: T`,
)

// Rename following types so we can set generics in their place
.replace(/type AnalyticsPlugin = /gm, 'interface AnalyticsPluginBase ')
.replace(/type PageData = /gm, 'interface PageDataBase ')
// Make promises return void
.replace(/\@returns \{Promise\}/gm, '@returns {Promise<void>}')
.replace(/=> Promise;/gm, '=> Promise<any>;')
// Fix plugin interface
.replace(/plugins\?: object\[\]/gm, 'plugins?: Array<AnalyticsPlugin>')
// Convert unions ('|') to joins ('&').
// Joins are used for modular JSDOC typedefs that support intellisense in VS Code.
// 'jsdoc' cannot parse joins, so they are temporarily transpiled to unions by 'jsdoc-plugin-intersection'.
.replace(/ \| /gm, ' & ')

// Make types extensible
const typeExtensions = `

export type PageData<T extends string = string> = PageDataBase & Record<T, unknown>;
export type AnalyticsPlugin<T extends string = string> = AnalyticsPluginBase & string extends T
? Record<string, unknown>
: Record<T, Hook> & Record<string, unknown>;
`;

// Expose main API
const newContent = `declare module "analytics" {
${indentString(typesFromJsDocs, 2)}
${typeExtensions}

export const CONSTANTS: constants;

export const init: typeof analytics;
Expand Down
2 changes: 1 addition & 1 deletion packages/analytics-core/src/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export const coreEvents = [
*/
'identifyEnd',
/**
* `identifyAborted` - Fires if 'track' call is cancelled by a plugin
* `identifyAborted` - Fires if 'identify' call is cancelled by a plugin
*/
'identifyAborted',
/**
Expand Down
81 changes: 57 additions & 24 deletions packages/analytics-core/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,21 @@ import heartBeat from './utils/heartbeat'

const { setItem, removeItem } = middleware

/**
* Core Analytic constants. These are exposed for third party plugins & listeners
* @typedef {Object} AnalyticsInstanceConfig
* @property {string} [app] - Name of site / app
* @property {string} [version] - Version of your app
* @property {boolean} [debug] - Should analytics run in debug mode
* @property {Array.<AnalyticsPlugin>} [plugins] - Array of analytics plugins
*/

/**
* Analytics library configuration
*
* After the library is initialized with config, the core API is exposed & ready for use in the application.
*
* @param {object} config - analytics core config
* @param {string} [config.app] - Name of site / app
* @param {string} [config.version] - Version of your app
* @param {boolean} [config.debug] - Should analytics run in debug mode
* @param {Array.<Object>} [config.plugins] - Array of analytics plugins
* @param {AnalyticsInstanceConfig} [config] - Analytics core config
* @return {AnalyticsInstance} Analytics Instance
* @example
*
Expand Down Expand Up @@ -193,7 +198,9 @@ function analytics(config = {}) {
* analytics.plugins.enable(['google', 'segment'])
*/
enable: (plugins, callback) => {
store.dispatch(enablePlugin(plugins, callback))
/** @type {EnablePluginPayload} */
const enablePluginPayload = enablePlugin(plugins, callback);
store.dispatch(enablePluginPayload);
},
/**
* Disable analytics plugin
Expand All @@ -207,7 +214,9 @@ function analytics(config = {}) {
* analytics.plugins.disable(['google', 'segment'])
*/
disable: (name, callback) => {
store.dispatch(disablePlugin(name, callback))
/** @type {EnablePluginPayload} */
const disablePluginPayload = disablePlugin(name, callback);
store.dispatch(disablePluginPayload);
},
/*
* Load registered analytic providers.
Expand All @@ -217,11 +226,13 @@ function analytics(config = {}) {
* analytics.plugins.load('segment')
*/
load: (plugins) => {
store.dispatch({
/** @type {EnablePluginPayload} */
const loadPluginPayload = {
type: EVENTS.loadPlugin,
// Todo handle multiple plugins via array
plugins: (plugins) ? [plugins] : Object.keys(getPlugins()),
})
};
store.dispatch(loadPluginPayload);
},
/* @TODO if it stays, state loaded needs to be set. Re PLUGIN_INIT above
add: (newPlugin) => {
Expand Down Expand Up @@ -312,7 +323,8 @@ function analytics(config = {}) {
const resolvedId = id || data.userId || getUserProp(ID, instance, data)

return new Promise((resolve, reject) => {
store.dispatch({
/** @type {IdentifyPayload} */
const identifyPayload = {
type: EVENTS.identifyStart,
userId: resolvedId,
traits: data || {},
Expand All @@ -324,7 +336,8 @@ function analytics(config = {}) {
timestamp: timestamp(),
callback: resolvePromise(resolve, getCallback(traits, options, callback))
},
})
};
store.dispatch(identifyPayload);
})
},
/**
Expand Down Expand Up @@ -384,7 +397,8 @@ function analytics(config = {}) {
const opts = isObject(options) ? options : {}

return new Promise((resolve, reject) => {
store.dispatch({
/** @type {TrackPayload} */
const trackPayload = {
type: EVENTS.trackStart,
event: name,
properties: data,
Expand All @@ -395,7 +409,8 @@ function analytics(config = {}) {
timestamp: timestamp(),
callback: resolvePromise(resolve, getCallback(payload, options, callback))
},
})
};
store.dispatch(trackPayload);
})
},
/**
Expand Down Expand Up @@ -444,7 +459,8 @@ function analytics(config = {}) {
const opts = isObject(options) ? options : {}

return new Promise((resolve, reject) => {
store.dispatch({
/** @type {PagePayload} */
const pagePayload = {
type: EVENTS.pageStart,
properties: getPageData(d),
options: opts,
Expand All @@ -454,7 +470,8 @@ function analytics(config = {}) {
timestamp: timestamp(),
callback: resolvePromise(resolve, getCallback(data, options, callback))
},
})
};
store.dispatch(pagePayload);
})
},
/**
Expand Down Expand Up @@ -497,11 +514,13 @@ function analytics(config = {}) {
*/
reset: (callback) => {
return new Promise((resolve, reject) => {
store.dispatch({
/** @type {ResetPayload} */
const resetPayload = {
type: EVENTS.resetStart,
timestamp: timestamp(),
callback: resolvePromise(resolve, callback)
})
};
store.dispatch(resetPayload);
})
},
/**
Expand Down Expand Up @@ -876,15 +895,25 @@ function analytics(config = {}) {
})

/* Register analytic plugins */
store.dispatch({
/** @type {RegisterPluginsPayload} */
const registerPluginsPayload = {
type: EVENTS.registerPlugins,
plugins: pluginKeys,
})
};
store.dispatch(registerPluginsPayload);

parsedOptions.pluginsArray.map((plugin, i) => { // eslint-disable-line
const { bootstrap, config } = plugin
if (bootstrap && isFunction(bootstrap)) {
bootstrap({ instance, config, payload: plugin })
/** @type {BootstrapContext} */
const bootstrapContext = {
hello: plugin.name,
instance,
config,
payload: plugin,
};
// Call EVENTS.bootstrap
bootstrap(bootstrapContext);
}
const lastCall = parsedOptions.pluginsArray.length === (i + 1)
/* Register plugins */
Expand All @@ -896,19 +925,23 @@ function analytics(config = {}) {

/* All plugins registered initialize */
if (lastCall) {
store.dispatch({
/** @type {InitializePayload} */
const initializeStartPayload = {
type: EVENTS.initializeStart,
plugins: pluginKeys
})
}
store.dispatch(initializeStartPayload);
}
})

if (process.browser) {
/* Watch for network events */
watch(offline => {
store.dispatch({
/** @type {EmptyPayload} */
const networkPayload = {
type: (offline) ? EVENTS.offline : EVENTS.online,
})
};
store.dispatch(networkPayload);
})

/* Tick heartbeat for queued events */
Expand Down
6 changes: 4 additions & 2 deletions packages/analytics-core/src/middleware/identify.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export default function identifyMiddleware(instance) {
const currentTraits = getItem(USER_TRAITS) || {}

if (currentId && (currentId !== userId)) {
store.dispatch({
/** @type {UserIdChangedPayload} */
const userIdChangedPayload = {
type: EVENTS.userIdChanged,
old: {
userId: currentId,
Expand All @@ -40,7 +41,8 @@ export default function identifyMiddleware(instance) {
traits
},
options: options,
})
};
store.dispatch(userIdChangedPayload);
}

/* Save user id */
Expand Down
24 changes: 15 additions & 9 deletions packages/analytics-core/src/middleware/initialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function initializeMiddleware(instance) {
const paramsArray = Object.keys(action.params)
if (paramsArray.length) {
const { an_uid, an_event } = params
const groupedParams = paramsArray.reduce((acc, key) => {
const { campaign, props, traits } = paramsArray.reduce((acc, key) => {
// match utm params & dclid (display) & gclid (cpc)
if (key.match(utmRegex) || key.match(/^(d|g)clid/)) {
const cleanName = key.replace(utmRegex, '')
Expand All @@ -41,31 +41,37 @@ export default function initializeMiddleware(instance) {
traits: {}
})

store.dispatch({
/** @type {ParamsPayload} */
const paramsPayload = {
type: EVENTS.params,
raw: params,
...groupedParams,
campaign,
props,
traits,
...(an_uid ? { userId: an_uid } : {}),
})
};
store.dispatch(paramsPayload);

/* If userId set, call identify */
if (an_uid) {
// timeout to debounce and make sure integration is registered. Todo refactor
setTimeout(() => instance.identify(an_uid, groupedParams.traits), 0)
setTimeout(() => instance.identify(an_uid, traits), 0)
}

/* If tracking event set, call track */
if (an_event) {
// timeout to debounce and make sure integration is registered. Todo refactor
setTimeout(() => instance.track(an_event, groupedParams.props), 0)
setTimeout(() => instance.track(an_event, props), 0)
}

// if url has utm params
if (Object.keys(groupedParams.campaign).length) {
store.dispatch({
/** @type {CampaignPayload} */
const campaignPayload = {
type: EVENTS.campaign,
campaign: groupedParams.campaign
})
campaign,
};
store.dispatch(campaignPayload);
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions packages/analytics-core/src/middleware/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default function pluginMiddleware(instance, getPlugins, systemEvents) {
acc[curr] = allPlugins[curr]
return acc
}, {})
/** @type {InitializePayload} */
const initializeAction = {
type: EVENTS.initializeStart,
plugins: action.plugins
Expand Down Expand Up @@ -82,11 +83,13 @@ export default function pluginMiddleware(instance, getPlugins, systemEvents) {
// setTimeout to ensure runs after 'page'
setTimeout(() => {
if (pluginsArray.length === allCalls.length) {
store.dispatch({
/** @type {ReadyPayload} */
const readyPayload = {
type: EVENTS.ready,
plugins: completed,
failed: failed
})
};
store.dispatch(readyPayload);
}
}, 0)
})
Expand Down
Loading