Skip to content

Commit

Permalink
feat: Initial commit
Browse files Browse the repository at this point in the history
Exported code from monorepo
  • Loading branch information
franky47 committed Mar 13, 2020
1 parent e22c3b9 commit 6ddd76f
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 31 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# @chiffre/template-library
# @chiffre/analytics-core

[![NPM](https://img.shields.io/npm/v/@chiffre/template-library?color=red)](https://www.npmjs.com/package/@chiffre/template-library)
[![MIT License](https://img.shields.io/github/license/chiffre-io/template-library.svg?color=blue)](https://github.com/chiffre-io/template-library/blob/next/LICENSE)
[![Continuous Integration](https://github.com/chiffre-io/template-library/workflows/Continuous%20Integration/badge.svg?branch=next)](https://github.com/chiffre-io/template-library/actions)
[![Coverage Status](https://coveralls.io/repos/github/chiffre-io/template-library/badge.svg?branch=next)](https://coveralls.io/github/chiffre-io/template-library?branch=next)
[![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=chiffre-io/template-library)](https://dependabot.com)
[![NPM](https://img.shields.io/npm/v/@chiffre/analytics-core?color=red)](https://www.npmjs.com/package/@chiffre/analytics-core)
[![MIT License](https://img.shields.io/github/license/chiffre-io/analytics-core.svg?color=blue)](https://github.com/chiffre-io/analytics-core/blob/next/LICENSE)
[![Continuous Integration](https://github.com/chiffre-io/analytics-core/workflows/Continuous%20Integration/badge.svg?branch=next)](https://github.com/chiffre-io/analytics-core/actions)
[![Coverage Status](https://coveralls.io/repos/github/chiffre-io/analytics-core/badge.svg?branch=next)](https://coveralls.io/github/chiffre-io/analytics-core?branch=next)
[![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=chiffre-io/analytics-core)](https://dependabot.com)

Template for Chiffre libraries
Types & definitions for Chiffre Analytics

## License

[MIT](https://github.com/chiffre-io/template-library/blob/next/LICENSE) - Made with ❤️ by [François Best](https://francoisbest.com).
[MIT](https://github.com/chiffre-io/analytics-core/blob/next/LICENSE) - Made with ❤️ by [François Best](https://francoisbest.com).
17 changes: 9 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@chiffre/template-library",
"name": "@chiffre/analytics-core",
"version": "0.0.0-semantically-released",
"description": "Template for Chiffre libraries",
"description": "Types & definitions for Chiffre Analytics",
"main": "dist/index.js",
"license": "MIT",
"author": {
Expand All @@ -11,11 +11,11 @@
},
"repository": {
"type": "git",
"url": "https://github.com/chiffre-io/template-library"
"url": "https://github.com/chiffre-io/analytics-core"
},
"keywords": [
"chiffre",
"template"
"analytics"
],
"publishConfig": {
"access": "public"
Expand All @@ -28,14 +28,15 @@
"build": "run-s build:clean build:ts",
"ci": "run-s build test"
},
"dependencies": {},
"dependencies": {
"nanoid": "^2.1.11"
},
"devDependencies": {
"@commitlint/config-conventional": "^8.3.4",
"@types/faker": "^4.1.10",
"@types/jest": "^25.1.4",
"@types/nanoid": "^2.1.0",
"@types/node": "^13.9.1",
"commitlint": "^8.3.5",
"faker": "^4.1.0",
"husky": "^4.2.3",
"jest": "^25.1.0",
"npm-run-all": "^4.1.5",
Expand All @@ -46,7 +47,7 @@
"jest": {
"verbose": true,
"preset": "ts-jest/presets/js-with-ts",
"testEnvironment": "node"
"testEnvironment": "jsdom"
},
"husky": {
"hooks": {
Expand Down
132 changes: 132 additions & 0 deletions src/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { SessionData } from './session'
import { PageVisitData } from './navigation'

export interface Event<T, K extends keyof T> {
type: K
time: number
data?: T[K]
}

// ---

export interface GenericDataPoint<T, Meta = any> {
name: string
value: T
meta?: Meta
}

export type GenericEvents = {
'generic:number': GenericDataPoint<number>
'generic:numbers': GenericDataPoint<number>[]
'generic:string': GenericDataPoint<string>
'generic:strings': GenericDataPoint<string>[]
}
export type GenericEvent = Event<GenericEvents, keyof GenericEvents>

export function isGenericEvent(event: AllEvents): event is GenericEvent {
return (
isGenericNumberEvent(event) ||
isGenericNumbersEvent(event) ||
isGenericStringEvent(event) ||
isGenericStringsEvent(event)
)
}
export function isGenericNumberEvent(
event: AllEvents
): event is Event<GenericEvents, 'generic:number'> {
return event.type === 'generic:number'
}
export function isGenericNumbersEvent(
event: AllEvents
): event is Event<GenericEvents, 'generic:numbers'> {
return event.type === 'generic:numbers'
}
export function isGenericStringEvent(
event: AllEvents
): event is Event<GenericEvents, 'generic:string'> {
return event.type === 'generic:string'
}
export function isGenericStringsEvent(
event: AllEvents
): event is Event<GenericEvents, 'generic:strings'> {
return event.type === 'generic:strings'
}

// --

export interface BrowserEventData {
sid: string
path: string
}

export type BrowserDataPoint<T = {}> = BrowserEventData & T

export type BrowserEvents = {
'session:start': BrowserDataPoint<SessionData>
'session:dnt': never
'session:end': BrowserDataPoint
'page:visit': BrowserDataPoint<PageVisitData>
'page:hide': BrowserDataPoint
'page:show': BrowserDataPoint
}
export type BrowserEvent = Event<BrowserEvents, keyof BrowserEvents>

export function isBrowserEvent(event: AllEvents): event is BrowserEvent {
return (
isSessionStartEvent(event) ||
isSessionEndEvent(event) ||
isPageVisitEvent(event) ||
isPageHideEvent(event) ||
isPageShowEvent(event)
)
}
export function isSessionStartEvent(
event: AllEvents
): event is Event<BrowserEvents, 'session:start'> {
return event.type === 'session:start'
}
export function isSessionDNTEvent(
event: AllEvents
): event is Event<BrowserEvents, 'session:dnt'> {
return event.type === 'session:dnt'
}
export function isSessionEndEvent(
event: AllEvents
): event is Event<BrowserEvents, 'session:end'> {
return event.type === 'session:end'
}
export function isPageVisitEvent(
event: AllEvents
): event is Event<BrowserEvents, 'page:visit'> {
return event.type === 'page:visit'
}
export function isPageHideEvent(
event: AllEvents
): event is Event<BrowserEvents, 'page:hide'> {
return event.type === 'page:hide'
}
export function isPageShowEvent(
event: AllEvents
): event is Event<BrowserEvents, 'page:show'> {
return event.type === 'page:show'
}

function eventFactory<Events>() {
return function createEvent<K extends keyof Events>(
type: K,
data?: Events[K]
): Event<Events, K> {
return {
type,
time: Date.now(),
data
}
}
}

export const createGenericEvent = eventFactory<GenericEvents>()
export const createBrowserEvent = eventFactory<BrowserEvents>()

export type EventSender = <T>(event: Event<T, keyof T>) => void

export type AllEvents = GenericEvent | BrowserEvent
37 changes: 34 additions & 3 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
import hello from './index'
import { sessionStart, sessionEnd, pageHide, pageShow } from './session'
import {
isBrowserEvent,
isSessionStartEvent,
isSessionEndEvent,
isPageHideEvent,
isPageShowEvent
} from './events'

test('testing works', () => {
expect(hello('World')).toEqual('Hello, World !')
test('sessionStart', () => {
const event = sessionStart()
expect(event.type).toEqual('session:start')
expect(isBrowserEvent(event)).toEqual(true)
expect(isSessionStartEvent(event)).toEqual(true)
})

test('sessionEnd', () => {
const event = sessionEnd()
expect(event.type).toEqual('session:end')
expect(isBrowserEvent(event)).toEqual(true)
expect(isSessionEndEvent(event)).toEqual(true)
})

test('pageHide', () => {
const event = pageHide()
expect(event.type).toEqual('page:hide')
expect(isBrowserEvent(event)).toEqual(true)
expect(isPageHideEvent(event)).toEqual(true)
})

test('pageShow', () => {
const event = pageShow()
expect(event.type).toEqual('page:show')
expect(isBrowserEvent(event)).toEqual(true)
expect(isPageShowEvent(event)).toEqual(true)
})
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export default (name: string) => `Hello, ${name} !`
export * from './events'
export * from './session'
export * from './navigation'
24 changes: 24 additions & 0 deletions src/navigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createBrowserEvent, EventSender } from './events'
import { sessionID } from './session'

export interface PageVisitData {
from: string
}

export const setupPageVisitListeners = (send: EventSender) => {
let oldPath = window.location.pathname

setInterval(() => {
const newPath = window.location.pathname
if (oldPath === newPath) {
return
}
const event = createBrowserEvent(`page:visit`, {
sid: sessionID,
from: oldPath,
path: newPath
})
oldPath = newPath
send(event)
}, 500)
}
72 changes: 72 additions & 0 deletions src/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import nanoid from 'nanoid'
import { createBrowserEvent, EventSender } from './events'

export const sessionID = nanoid()

export interface SessionData {
ua: string // user-agent
os: string // operating system
ref: string // referer
lang: string // language
tzo: number // timezone offset from UTC in minutes
vp: {
// viewport dimensions
w: number
h: number
}
lvd?: string // last visit date, ISO-8601
}

export const sessionStart = () => {
const event = createBrowserEvent('session:start', {
ua: navigator.userAgent,
os: navigator.platform,
ref: document.referrer,
lang: navigator.language,
tzo: new Date().getTimezoneOffset(),
vp: {
w: window.innerWidth,
h: window.innerHeight
},
lvd: window.localStorage.getItem('chiffre:last-visit-date') || undefined,
sid: sessionID,
path: window.location.pathname
})
window.localStorage.setItem(
'chiffre:last-visit-date',
new Date().toISOString().slice(0, 10) // YYYY-MM-DD
)
return event
}

export const sessionEnd = () => {
return createBrowserEvent('session:end', {
sid: sessionID,
path: window.location.pathname
})
}

export const pageHide = () => {
return createBrowserEvent('page:hide', {
sid: sessionID,
path: window.location.pathname
})
}

export const pageShow = () => {
return createBrowserEvent('page:show', {
sid: sessionID,
path: window.location.pathname
})
}

export const setupSessionListeners = (send: EventSender) => {
const startEvent = sessionStart()
window.addEventListener('beforeunload', () => {
send(sessionEnd())
})
window.addEventListener('visibilitychange', () => {
send(document.hidden ? pageHide() : pageShow())
})
send(startEvent)
}
Loading

0 comments on commit 6ddd76f

Please sign in to comment.