├── .gitattributes ├── .gitignore ├── package.json ├── readme.md ├── splitbee-core ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── src │ ├── api.ts │ └── index.ts ├── tsconfig.json └── yarn.lock ├── splitbee-node ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── src │ └── index.ts ├── tsconfig.json └── yarn.lock ├── splitbee-react-native ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── src │ ├── device │ │ ├── expo.ts │ │ ├── index.ts │ │ └── native.ts │ ├── index.ts │ └── timer.ts ├── tsconfig.json └── yarn.lock ├── splitbee-web ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── example │ ├── .gitignore │ ├── favicon.svg │ ├── index.html │ ├── package.json │ ├── src │ │ ├── main.ts │ │ ├── style.css │ │ └── vite-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── package.json ├── src │ ├── index.ts │ └── types.ts ├── tsconfig.json └── yarn.lock └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": [ 4 | "splitbee-core", 5 | "splitbee-node" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Splitbee-JS 2 | 3 | Following packages are maintained in this repository. 4 | 5 | * `splitbee-core` – a simple API wrapper that is used by `splitbee-node` and `splitbee-react-native` 6 | * `splitbee-node` – The official Node.js library to send events to the Splitbee API. Fully typed. 7 | * `splitbee-react-native` – The library that tracks your React Native app. 8 | * `splitbee-web` – The SPA library (Next.js, Gatsby, Vue.js, Angular.JS) – No need to wait for the script to load! -------------------------------------------------------------------------------- /splitbee-core/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /splitbee-core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tobias Lins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /splitbee-core/README.md: -------------------------------------------------------------------------------- 1 | # @splitbee-core 2 | 3 | This package includes the basic communication layer with the internal [Splitbee](https://splitbee.com) API + typings. 4 | 5 | ## Related packages 6 | 7 | - [@splitbee/web](https://splitbee.io/docs/javascript-library) - For front-end JavaScript applications 8 | - [@splitbee/node](https://splitbee.io/docs/backend-analytics-nodejs) - For back-end use with Node.js 9 | -------------------------------------------------------------------------------- /splitbee-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@splitbee/core", 3 | "version": "0.3.0", 4 | "license": "MIT", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "files": [ 8 | "dist", 9 | "src" 10 | ], 11 | "engines": { 12 | "node": ">=10" 13 | }, 14 | "scripts": { 15 | "start": "tsdx watch", 16 | "build": "tsdx build", 17 | "lint": "tsdx lint", 18 | "prepare": "tsdx build" 19 | }, 20 | "peerDependencies": {}, 21 | "husky": { 22 | "hooks": { 23 | "pre-commit": "tsdx lint" 24 | } 25 | }, 26 | "prettier": { 27 | "printWidth": 80, 28 | "semi": true, 29 | "singleQuote": true, 30 | "trailingComma": "es5" 31 | }, 32 | "author": "Tobias Lins", 33 | "module": "dist/splitbee-core.esm.js", 34 | "devDependencies": { 35 | "husky": "^4.2.5", 36 | "tsdx": "^0.13.2", 37 | "tslib": "^2.0.0", 38 | "typescript": "^3.9.7" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /splitbee-core/src/api.ts: -------------------------------------------------------------------------------- 1 | export type Device = { 2 | os: { name: string; version: string }; 3 | client: { 4 | name: string; //'Opera'; 5 | type: string; //'browser'; 6 | // engine: 'Blink'; 7 | // engineVersion: ''; 8 | version: string; //'73.0'; 9 | }; 10 | device: { 11 | type: string; //'desktop'; 12 | brand: string; // 'Apple'; 13 | model: string; // ''; 14 | }; 15 | }; 16 | 17 | export interface RequestContext { 18 | projectId?: string; 19 | userId?: string; 20 | uid?: string; 21 | userAgent?: string; 22 | device?: Device; 23 | } 24 | interface GenericRequest { 25 | context?: RequestContext; 26 | } 27 | 28 | export type EventData = { 29 | [key: string]: string | number | boolean | undefined; 30 | }; 31 | 32 | /** 33 | * @deprecated Use EventData instead. 34 | */ 35 | export type JSONType = EventData; 36 | 37 | interface PageViewRequest extends GenericRequest { 38 | path: '/i'; 39 | body: { 40 | referrer?: string; 41 | requestId?: string; 42 | origin?: string; 43 | page?: string; 44 | options?: EventOptions; 45 | }; 46 | } 47 | 48 | export interface EventOptions { 49 | updateLastSeen?: boolean; 50 | __uid?: string; 51 | } 52 | export interface EventRequest extends GenericRequest { 53 | path: '/t'; 54 | body: { 55 | event: string; 56 | data?: EventData; 57 | options?: EventOptions; 58 | }; 59 | } 60 | 61 | interface IdentifyRequest extends GenericRequest { 62 | path: '/user'; 63 | body: EventData; 64 | } 65 | 66 | interface EndRequest extends GenericRequest { 67 | path: '/end'; 68 | body: { 69 | requestId: string; 70 | data: { 71 | duration: number; 72 | }; 73 | }; 74 | } 75 | 76 | type Requests = PageViewRequest | EventRequest | IdentifyRequest | EndRequest; 77 | 78 | export type Response = { 79 | uid: string; 80 | }; 81 | 82 | let ENDPOINT = `https://hive.splitbee.io`; 83 | 84 | export const setEndpoint = (endpoint: string) => (ENDPOINT = endpoint); 85 | 86 | export const splitbeeRequest = async ({ 87 | path, 88 | context, 89 | body, 90 | }: Requests): Promise => { 91 | try { 92 | const res = await fetch(ENDPOINT + path, { 93 | method: 'POST', 94 | headers: { 95 | ...(context?.userId && 96 | context.userId !== '' && { userId: context.userId }), 97 | ...(context?.uid && { uid: context.uid }), 98 | ...(context?.projectId && { sbp: context.projectId }), 99 | ...(context?.userAgent && { 'user-agent': context.userAgent }), 100 | ...(context?.device && { device: JSON.stringify(context.device) }), 101 | // ...(event.context.page && { origin: event.context.page.url }), 102 | }, 103 | body: JSON.stringify(body), 104 | }); 105 | 106 | if (typeof process.env !== undefined && res.status !== 200) { 107 | const body = await res.json(); 108 | console.log('Splitbee Error: ', body); 109 | } 110 | return { 111 | uid: res.headers.get('uid')!, 112 | }; 113 | } catch (err) { 114 | return undefined; 115 | } 116 | }; 117 | -------------------------------------------------------------------------------- /splitbee-core/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | splitbeeRequest, 3 | RequestContext, 4 | JSONType, 5 | EventData, 6 | EventOptions, 7 | setEndpoint, 8 | Response, 9 | } from './api'; 10 | 11 | export const analytics = { 12 | track: async ({ 13 | event, 14 | data, 15 | context, 16 | options, 17 | }: { 18 | event: string; 19 | data?: EventData; 20 | context: RequestContext; 21 | options?: EventOptions; 22 | }) => { 23 | return await splitbeeRequest({ 24 | path: '/t', 25 | context, 26 | body: { event, data, options }, 27 | }); 28 | }, 29 | 30 | page: async ({ 31 | page, 32 | data, 33 | context, 34 | options, 35 | }: { 36 | page: string; 37 | data?: { referrer?: string; requestId?: string }; 38 | context: RequestContext; 39 | options?: EventOptions; 40 | }) => { 41 | return await splitbeeRequest({ 42 | path: '/i', 43 | context, 44 | body: { 45 | page, 46 | ...(data?.referrer && { referrer: data.referrer }), 47 | ...(data?.requestId && { requestId: data.requestId }), 48 | options, 49 | }, 50 | }); 51 | }, 52 | 53 | identify: async ({ 54 | userData, 55 | context, 56 | }: { 57 | userData: EventData; 58 | context: RequestContext; 59 | }) => { 60 | return await splitbeeRequest({ 61 | path: '/user', 62 | context, 63 | body: userData, 64 | }); 65 | }, 66 | 67 | end: async ({ 68 | requestId, 69 | data, 70 | context, 71 | }: { 72 | requestId: string; 73 | data: { 74 | duration: number; 75 | destination?: string; 76 | }; 77 | context: RequestContext; 78 | }) => { 79 | return await splitbeeRequest({ 80 | path: '/end', 81 | context, 82 | body: { 83 | requestId, 84 | data, 85 | }, 86 | }); 87 | }, 88 | }; 89 | 90 | export { 91 | RequestContext, 92 | EventOptions, 93 | JSONType, 94 | EventData, 95 | Response, 96 | setEndpoint, 97 | }; 98 | -------------------------------------------------------------------------------- /splitbee-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "importHelpers": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "rootDir": "./src", 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "moduleResolution": "node", 16 | "baseUrl": "./", 17 | "paths": { 18 | "*": ["src/*", "node_modules/*"] 19 | }, 20 | "jsx": "react", 21 | "esModuleInterop": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /splitbee-node/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /splitbee-node/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tobias Lins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /splitbee-node/README.md: -------------------------------------------------------------------------------- 1 | # splitbee-node 2 | 3 | Used to track events for Splitbee using Node.JS 4 | 5 | ### Basic Usage 6 | 7 | ```js 8 | const { SplitbeeAnalytics } = require('@splitbee/node'); 9 | 10 | // Token can be found in dashboard settings 11 | const analytics = new SplitbeeAnalytics('PROJECT_TOKEN'); 12 | 13 | analytics.track({ 14 | userId: 'myunique@user.id', 15 | event: 'Payment confirmed', 16 | data: { 17 | paymentId: '1234567890', 18 | }, 19 | }); 20 | ``` 21 | 22 | Set data to user profile 23 | 24 | ```js 25 | analytics.user.set({ 26 | userId: 'myunique@user.id', 27 | userData: { 28 | username: 'Custom Name', 29 | isTrial: true, 30 | }, 31 | }); 32 | ``` 33 | 34 | The full reference can be found in our [documentation](https://splitbee.io/docs/backend-analytics-nodejs). 35 | -------------------------------------------------------------------------------- /splitbee-node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@splitbee/node", 3 | "version": "0.3.1", 4 | "license": "MIT", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "files": [ 8 | "dist", 9 | "src" 10 | ], 11 | "engines": { 12 | "node": ">=10" 13 | }, 14 | "scripts": { 15 | "start": "tsdx watch", 16 | "build": "tsdx build", 17 | "lint": "tsdx lint" 18 | }, 19 | "peerDependencies": {}, 20 | "husky": { 21 | "hooks": { 22 | "pre-commit": "tsdx lint" 23 | } 24 | }, 25 | "prettier": { 26 | "printWidth": 80, 27 | "semi": true, 28 | "singleQuote": true, 29 | "trailingComma": "es5" 30 | }, 31 | "author": "Tobias Lins", 32 | "module": "dist/splitbee-node.esm.js", 33 | "devDependencies": { 34 | "husky": "^4.2.5", 35 | "tsdx": "^0.13.2", 36 | "tslib": "^2.0.0", 37 | "typescript": "^3.9.7" 38 | }, 39 | "dependencies": { 40 | "@splitbee/core": "^0.3.0", 41 | "cross-fetch": "^3.0.5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /splitbee-node/src/index.ts: -------------------------------------------------------------------------------- 1 | import 'cross-fetch/polyfill'; 2 | import { analytics, EventData, EventOptions } from '@splitbee/core'; 3 | 4 | export class SplitbeeAnalytics { 5 | private projectId?: string; 6 | constructor(projectId: string) { 7 | this.projectId = projectId; 8 | } 9 | 10 | public track = async ( 11 | { 12 | userId, 13 | event, 14 | data, 15 | }: { 16 | userId: string; 17 | event: string; 18 | data?: EventData; 19 | }, 20 | options?: EventOptions 21 | ) => { 22 | await analytics.track({ 23 | event, 24 | data, 25 | options, 26 | context: { 27 | projectId: this.projectId, 28 | userId, 29 | uid: options?.__uid, 30 | }, 31 | }); 32 | }; 33 | 34 | public page = async ( 35 | { 36 | page, 37 | data, 38 | userId, 39 | }: { 40 | page: string; 41 | data?: { referrer?: string; requestId?: string }; 42 | userId: string; 43 | }, 44 | options?: EventOptions 45 | ) => { 46 | await analytics.page({ 47 | page, 48 | data, 49 | options, 50 | context: { 51 | projectId: this.projectId, 52 | userId, 53 | uid: options?.__uid, 54 | }, 55 | }); 56 | }; 57 | 58 | public user = { 59 | set: async ( 60 | { 61 | userData, 62 | userId, 63 | }: { 64 | userData: EventData; 65 | userId: string; 66 | }, 67 | options?: EventOptions 68 | ) => { 69 | await analytics.identify({ 70 | userData, 71 | context: { 72 | projectId: this.projectId, 73 | userId, 74 | uid: options?.__uid, 75 | }, 76 | }); 77 | }, 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /splitbee-node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "importHelpers": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "rootDir": "./src", 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "moduleResolution": "node", 16 | "baseUrl": "./", 17 | "paths": { 18 | "*": ["src/*", "node_modules/*"] 19 | }, 20 | "jsx": "react", 21 | "esModuleInterop": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /splitbee-react-native/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /splitbee-react-native/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tobias Lins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /splitbee-react-native/README.md: -------------------------------------------------------------------------------- 1 | # Splitbee Analytics for React Native Apps 2 | 3 | Used to track screen views & events of React Native & Expo Apps using Splitbee. 4 | 5 | ### Usage with Expo 6 | 7 | Install following expo dependencies: 8 | 9 | ```bash 10 | expo install @splitbee/react-native expo-device expo-constants @react-native-async-storage/async-storage 11 | ``` 12 | 13 | We need those for getting the device data & persisting the user on a device. 14 | 15 | ### Usage with React Native 16 | 17 | Install following dependencies: [@react-native-async-storage/async-storage](`https://react-native-async-storage.github.io/async-storage/docs/install`) & [react-native-device-info](https://github.com/react-native-device-info/react-native-device-info) 18 | 19 | ```bash 20 | yarn add @splitbee/react-native react-native-device-info @react-native-async-storage/async-storage 21 | npx pod-install 22 | ``` 23 | 24 | Please follow the official documentation of those libraries on how to link them correctly. 25 | 26 | ## Usage 27 | 28 | ### Initialize Splitbee 29 | 30 | First of all you need to initialize the Splitbee SDK. For that, run `splitbee.init` with your token. 31 | 32 | ```js 33 | import splitbee from '@splitbee/react-native'; 34 | 35 | splitbee.init('YOUR_TOKEN'); // Take the token from the project settings in the Splitbee dashboard 36 | ``` 37 | 38 | ## Screen Tracking 39 | 40 | ### Usage with [react-navigation](https://reactnavigation.org/docs/getting-started) 41 | 42 | In this example we are using react-navigation to screen views automatically. 43 | 44 | ```JSX 45 | import splitbee, { useTrackReactNavigation } from '@splitbee/react-native'; 46 | 47 | export default function Navigation() { 48 | const [{ onReady, onStateChange }, navigationRef] = useTrackReactNavigation(); 49 | 50 | return ( 51 | { 55 | onReady(); 56 | }} 57 | onStateChange={() => { 58 | onStateChange(); 59 | }} 60 | > 61 | 62 | 63 | ); 64 | } 65 | ``` 66 | 67 | If you already have a `ref` set for the `NavigationContainer`, just pass it to `useTrackReactNavigation` 68 | 69 | ```JSX 70 | const navigationRef = useRef(null) 71 | const [{ onReady, onStateChange }] = useTrackReactNavigation(navigationRef); 72 | ``` 73 | 74 | ### Usage with [react-native-navigation](https://wix.github.io/react-native-navigation) 75 | 76 | To setup automated screen tracking you need to add following code to your `index.js` 77 | 78 | ```js 79 | Navigation.events().registerComponentDidAppearListener( 80 | async ({ componentName, componentType }) => { 81 | if (componentType === 'Component') { 82 | await splitbee.screen(componentName); 83 | } 84 | } 85 | ); 86 | ``` 87 | -------------------------------------------------------------------------------- /splitbee-react-native/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@splitbee/react-native", 3 | "version": "0.2.2", 4 | "license": "MIT", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "files": [ 8 | "dist", 9 | "src" 10 | ], 11 | "engines": { 12 | "node": ">=10" 13 | }, 14 | "scripts": { 15 | "start": "tsc --watch watch", 16 | "build": "tsc", 17 | "prepare": "tsc" 18 | }, 19 | "peerDependencies": { 20 | "expo-constants": ">=9.0.1", 21 | "expo-device": ">=2.4.0", 22 | "react": ">=16.8.0", 23 | "react-native": ">=0.59.0", 24 | "react-native-device-info": ">=7.0.1" 25 | }, 26 | "husky": { 27 | "hooks": { 28 | "pre-commit": "tsdx lint" 29 | } 30 | }, 31 | "prettier": { 32 | "printWidth": 80, 33 | "semi": true, 34 | "singleQuote": true, 35 | "trailingComma": "es5" 36 | }, 37 | "author": "Tobias Lins", 38 | "devDependencies": { 39 | "@react-navigation/native": "^5.9.3", 40 | "@types/react": ">=16.8.0", 41 | "@types/react-native": ">=0.59.0", 42 | "expo-constants": "^10.0.1", 43 | "expo-device": "^3.1.1", 44 | "husky": "^4.2.5", 45 | "react-native-device-info": "^8.0.1", 46 | "tsdx": "^0.13.2", 47 | "typescript": "^3.9.7" 48 | }, 49 | "dependencies": { 50 | "@react-native-async-storage/async-storage": ">=1.13.0", 51 | "@splitbee/core": "^0.2.7", 52 | "cross-fetch": "^3.0.5", 53 | "tslib": "^2.1.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /splitbee-react-native/src/device/expo.ts: -------------------------------------------------------------------------------- 1 | import * as Device from 'expo-device'; 2 | import Constants from 'expo-constants'; 3 | 4 | const getDeviceType = (device: Device.DeviceType) => { 5 | switch (device) { 6 | case Device.DeviceType.DESKTOP: 7 | return 'desktop'; 8 | case Device.DeviceType.PHONE: 9 | return 'smartphone'; 10 | case Device.DeviceType.TABLET: 11 | return 'tablet'; 12 | case Device.DeviceType.TV: 13 | return 'tv'; 14 | case Device.DeviceType.UNKNOWN: 15 | return undefined; 16 | } 17 | }; 18 | 19 | export const getDeviceInfo = async () => { 20 | return { 21 | os: { 22 | name: Device.osName, 23 | version: Device.osVersion, 24 | }, 25 | client: { 26 | name: 'React Native', 27 | type: 'app', 28 | version: Constants.nativeAppVersion, 29 | build: Constants.nativeBuildVersion, 30 | }, 31 | device: { 32 | type: getDeviceType(await Device.getDeviceTypeAsync()), 33 | brand: Device.brand, 34 | model: Constants.deviceName || Device.modelName, 35 | deviceId: Device.modelId, 36 | }, 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /splitbee-react-native/src/device/index.ts: -------------------------------------------------------------------------------- 1 | let deviceInfo; 2 | 3 | try { 4 | require('expo-device'); 5 | const { getDeviceInfo } = require('./expo'); 6 | deviceInfo = getDeviceInfo; 7 | console.log('could load expo'); 8 | } catch (error) { 9 | console.log('no expo'); 10 | console.log(error); 11 | const { getDeviceInfo } = require('./native'); 12 | deviceInfo = getDeviceInfo; 13 | } 14 | 15 | export const getDeviceInfo = deviceInfo; 16 | -------------------------------------------------------------------------------- /splitbee-react-native/src/device/native.ts: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native'; 2 | import DeviceInfo from 'react-native-device-info'; 3 | 4 | const getOSName = () => { 5 | switch (Platform.OS) { 6 | case 'ios': 7 | return 'iOS'; 8 | case 'android': 9 | return 'Android'; 10 | case 'windows': 11 | return 'Windows'; 12 | case 'web': 13 | return 'Web'; 14 | case 'macos': 15 | return 'MacOS'; 16 | } 17 | }; 18 | 19 | export const getDeviceInfo = async () => { 20 | return { 21 | os: { 22 | name: getOSName(), 23 | version: Platform.Version, 24 | }, 25 | client: { 26 | name: 'React Native', 27 | type: 'app', 28 | version: await DeviceInfo.getVersion(), 29 | build: await DeviceInfo.getBuildNumber(), 30 | }, 31 | device: { 32 | type: (await DeviceInfo.isTablet()) ? 'tablet' : 'smartphone', 33 | brand: await DeviceInfo.getManufacturer(), 34 | model: await DeviceInfo.getModel(), 35 | deviceId: await DeviceInfo.getDeviceId(), 36 | }, 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /splitbee-react-native/src/index.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | import { AppState, AppStateStatus } from 'react-native'; 3 | import { analytics, JSONType, Response } from '@splitbee/core'; 4 | import AsyncStorage from '@react-native-async-storage/async-storage'; 5 | import { NavigationContainerRef } from '@react-navigation/native'; 6 | 7 | import { getActiveSeconds, resetTime } from './timer'; 8 | import { getDeviceInfo } from './device'; 9 | 10 | const UID_KEY = 'splitbee_uid'; 11 | const USERID_KEY = 'splitbee_userId'; 12 | 13 | let projectToken: string | undefined; 14 | let uid: string | undefined; 15 | let userId: string | undefined; 16 | let requestId: string | undefined; 17 | let lastPage: string | undefined; 18 | 19 | const generateUid = () => 20 | Math.random() 21 | .toString(36) 22 | .substring(7); 23 | 24 | const loadUid = async () => { 25 | uid = uid || (await AsyncStorage.getItem('splitbee_uid')) || undefined; 26 | userId = 27 | userId || (await AsyncStorage.getItem('splitbee_userId')) || undefined; 28 | }; 29 | 30 | export const useTrackReactNavigation = ( 31 | ref?: React.MutableRefObject 32 | ): [ 33 | { onReady: () => void; onStateChange: () => void }, 34 | React.MutableRefObject 35 | ] => { 36 | const navigationRef = useRef(null); 37 | const routeNameRef = useRef(null); 38 | 39 | const navRef = ref?.current || navigationRef.current; 40 | 41 | return [ 42 | { 43 | onReady: () => (routeNameRef.current = navRef?.getCurrentRoute()!.name!), 44 | onStateChange: async () => { 45 | const previousRouteName = routeNameRef.current; 46 | const currentRouteName = navRef?.getCurrentRoute()?.name; 47 | 48 | if (previousRouteName !== currentRouteName) { 49 | splitbee.screen(currentRouteName!); 50 | } 51 | routeNameRef.current = currentRouteName!; 52 | }, 53 | }, 54 | ref || navigationRef, 55 | ]; 56 | }; 57 | 58 | const sendEnd = async (closeApp?: boolean) => { 59 | if (requestId) { 60 | await analytics.end({ 61 | requestId, 62 | data: { 63 | duration: getActiveSeconds(), 64 | ...(closeApp && { destination: 'close' }), 65 | }, 66 | context: { projectId: projectToken, uid, userId }, 67 | }); 68 | } 69 | resetTime(); 70 | }; 71 | 72 | const onChange = async (state: AppStateStatus) => { 73 | if (state === 'background' && requestId) { 74 | await sendEnd(true); 75 | } else if (state === 'active') { 76 | if (lastPage) { 77 | splitbee.screen(lastPage); 78 | } 79 | } 80 | }; 81 | 82 | const processResponse = async (response: Response | undefined) => { 83 | if (response?.uid) { 84 | uid = response.uid; 85 | await AsyncStorage.setItem(UID_KEY, response.uid); 86 | } 87 | }; 88 | 89 | const getContext = async () => ({ 90 | projectId: projectToken, 91 | uid, 92 | userId, 93 | device: await getDeviceInfo(), 94 | }); 95 | 96 | const splitbee = { 97 | init: (token: string) => { 98 | projectToken = token; 99 | loadUid(); 100 | AppState.removeEventListener('change', onChange); 101 | AppState.addEventListener('change', onChange); 102 | }, 103 | setUserId: (id: string) => { 104 | userId = id; 105 | AsyncStorage.setItem(USERID_KEY, id) 106 | .then(() => {}) 107 | .catch(() => {}); 108 | }, 109 | screen: async (page: string) => { 110 | sendEnd(); 111 | requestId = generateUid(); 112 | 113 | if (projectToken) { 114 | lastPage = page; 115 | const response = await analytics.page({ 116 | page, 117 | data: { 118 | requestId, 119 | }, 120 | context: await getContext(), 121 | }); 122 | 123 | processResponse(response); 124 | } 125 | }, 126 | track: async (event: string, data?: any) => { 127 | if (projectToken) { 128 | const response = await analytics.track({ 129 | event, 130 | data, 131 | context: await getContext(), 132 | }); 133 | processResponse(response); 134 | } 135 | }, 136 | identify: async (userData: JSONType) => { 137 | if (projectToken) { 138 | const response = await analytics.identify({ 139 | userData, 140 | context: await getContext(), 141 | }); 142 | processResponse(response); 143 | } 144 | }, 145 | }; 146 | 147 | export default splitbee; 148 | -------------------------------------------------------------------------------- /splitbee-react-native/src/timer.ts: -------------------------------------------------------------------------------- 1 | let secondsActive = 0; 2 | 3 | let interval: NodeJS.Timeout; 4 | 5 | export const startInterval = () => { 6 | if (interval) { 7 | clearInterval(interval); 8 | } 9 | interval = setInterval(() => { 10 | secondsActive += 1; 11 | }, 1000); 12 | }; 13 | 14 | startInterval(); 15 | 16 | export const resetTime = () => { 17 | startInterval(); 18 | secondsActive = 0; 19 | }; 20 | 21 | export const getActiveSeconds = () => { 22 | return secondsActive; 23 | }; 24 | -------------------------------------------------------------------------------- /splitbee-react-native/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "allowSyntheticDefaultImports": true, 6 | "esModuleInterop": true, 7 | "isolatedModules": true, 8 | "jsx": "react", 9 | "lib": ["es6"], 10 | "moduleResolution": "node", 11 | "strict": true, 12 | "target": "esnext", 13 | "outDir": "dist", 14 | "declaration": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /splitbee-web/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /splitbee-web/.npmignore: -------------------------------------------------------------------------------- 1 | example -------------------------------------------------------------------------------- /splitbee-web/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tobias Lins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /splitbee-web/README.md: -------------------------------------------------------------------------------- 1 | # @splitbee/web 2 | 3 | Used to track page views and events using any web framework like Next.js, Gatsby.js, Nuxt.js and others. 4 | 5 | ### Basic usage 6 | 7 | ```js 8 | import splitbee from '@splitbee/web'; 9 | 10 | splitbee.init(); 11 | 12 | splitbee.track('My event', { some: 'data' }); 13 | ``` 14 | 15 | The full reference can be found in our [documentation](https://splitbee.io/docs/javascript-library). 16 | -------------------------------------------------------------------------------- /splitbee-web/example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /splitbee-web/example/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /splitbee-web/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Splitbee Test 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /splitbee-web/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "tsc && vite build", 7 | "serve": "vite preview" 8 | }, 9 | "devDependencies": { 10 | "typescript": "^4.3.2", 11 | "vite": "^2.5.4" 12 | }, 13 | "dependencies": { 14 | "@splitbee/web": "../" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /splitbee-web/example/src/main.ts: -------------------------------------------------------------------------------- 1 | import './style.css'; 2 | import splitbee from '@splitbee/web'; 3 | 4 | const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); 5 | 6 | const deleteAllCookies = () => { 7 | const cookies = document.cookie.split(';'); 8 | for (const cookie of cookies) { 9 | const eqPos = cookie.indexOf('='); 10 | const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; 11 | document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT'; 12 | } 13 | }; 14 | 15 | const run = async () => { 16 | deleteAllCookies(); 17 | console.log('Preload: ', document.cookie); 18 | splitbee.init({ 19 | disableCookie: true, 20 | token: 'NCOSXDG3NWJC', 21 | scriptUrl: 'https://deploy-preview-42--splitbee.netlify.app/sb.js', 22 | apiUrl: 'https://hive.splitbee.io', 23 | }); 24 | 25 | await sleep(1000); 26 | console.log('Loaded: ', document.cookie); 27 | splitbee.track('No Cookie'); 28 | await sleep(1000); 29 | splitbee.enableCookie(true); 30 | console.log('Enable Cookie', document.cookie); 31 | splitbee.track('New Cookie Reset'); 32 | await sleep(1000); 33 | splitbee.reset(); 34 | console.log('Reset Cookie', document.cookie); 35 | splitbee.track('Reset Cookie'); 36 | await sleep(1000); 37 | }; 38 | 39 | const app = document.querySelector('#app')!; 40 | 41 | (window as any).run = run; 42 | 43 | app.innerHTML = ` 44 |

Hello Splitbee!

45 | Documentation 46 | `; 47 | -------------------------------------------------------------------------------- /splitbee-web/example/src/style.css: -------------------------------------------------------------------------------- 1 | #app { 2 | font-family: Avenir, Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | text-align: center; 6 | color: #2c3e50; 7 | margin-top: 60px; 8 | } 9 | -------------------------------------------------------------------------------- /splitbee-web/example/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /splitbee-web/example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "noEmit": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true 16 | }, 17 | "include": ["./src"] 18 | } 19 | -------------------------------------------------------------------------------- /splitbee-web/example/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@splitbee/web@../": 6 | version "0.2.4" 7 | 8 | colorette@^1.2.2: 9 | version "1.4.0" 10 | resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" 11 | integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== 12 | 13 | esbuild@^0.12.17: 14 | version "0.12.27" 15 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.27.tgz#9bcfb837111c5e89b189188dde339515b213a724" 16 | integrity sha512-G42siADcTdRU1qRBxhiIiVLG4gcEMyWV4CWfLBdSii+olCueZJHFRHc7EqQRnRvNkSQq88i0k1Oufw/YVueUWQ== 17 | 18 | fsevents@~2.3.2: 19 | version "2.3.2" 20 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 21 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 22 | 23 | function-bind@^1.1.1: 24 | version "1.1.1" 25 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 26 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 27 | 28 | has@^1.0.3: 29 | version "1.0.3" 30 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 31 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 32 | dependencies: 33 | function-bind "^1.1.1" 34 | 35 | is-core-module@^2.2.0: 36 | version "2.6.0" 37 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.6.0.tgz#d7553b2526fe59b92ba3e40c8df757ec8a709e19" 38 | integrity sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ== 39 | dependencies: 40 | has "^1.0.3" 41 | 42 | nanoid@^3.1.23: 43 | version "3.1.25" 44 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" 45 | integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q== 46 | 47 | path-parse@^1.0.6: 48 | version "1.0.7" 49 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 50 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 51 | 52 | postcss@^8.3.6: 53 | version "8.3.6" 54 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea" 55 | integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A== 56 | dependencies: 57 | colorette "^1.2.2" 58 | nanoid "^3.1.23" 59 | source-map-js "^0.6.2" 60 | 61 | resolve@^1.20.0: 62 | version "1.20.0" 63 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" 64 | integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== 65 | dependencies: 66 | is-core-module "^2.2.0" 67 | path-parse "^1.0.6" 68 | 69 | rollup@^2.38.5: 70 | version "2.56.3" 71 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.56.3.tgz#b63edadd9851b0d618a6d0e6af8201955a77aeff" 72 | integrity sha512-Au92NuznFklgQCUcV96iXlxUbHuB1vQMaH76DHl5M11TotjOHwqk9CwcrT78+Tnv4FN9uTBxq6p4EJoYkpyekg== 73 | optionalDependencies: 74 | fsevents "~2.3.2" 75 | 76 | source-map-js@^0.6.2: 77 | version "0.6.2" 78 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" 79 | integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== 80 | 81 | typescript@^4.3.2: 82 | version "4.4.3" 83 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324" 84 | integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA== 85 | 86 | vite@^2.5.4: 87 | version "2.5.7" 88 | resolved "https://registry.yarnpkg.com/vite/-/vite-2.5.7.tgz#e495be9d8bcbf9d30c7141efdccacde746ee0125" 89 | integrity sha512-hyUoWmRPhjN1aI+ZSBqDINKdIq7aokHE2ZXiztOg4YlmtpeQtMwMeyxv6X9YxHZmvGzg/js/eATM9Z1nwyakxg== 90 | dependencies: 91 | esbuild "^0.12.17" 92 | postcss "^8.3.6" 93 | resolve "^1.20.0" 94 | rollup "^2.38.5" 95 | optionalDependencies: 96 | fsevents "~2.3.2" 97 | -------------------------------------------------------------------------------- /splitbee-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@splitbee/web", 3 | "version": "0.3.0", 4 | "license": "MIT", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "files": [ 8 | "dist", 9 | "src" 10 | ], 11 | "scripts": { 12 | "start": "tsdx watch", 13 | "build": "tsdx build", 14 | "lint": "tsdx lint", 15 | "prepare": "tsdx build" 16 | }, 17 | "husky": { 18 | "hooks": { 19 | "pre-commit": "tsdx lint" 20 | } 21 | }, 22 | "prettier": { 23 | "printWidth": 80, 24 | "semi": true, 25 | "singleQuote": true, 26 | "trailingComma": "es5" 27 | }, 28 | "author": "Tobias Lins", 29 | "module": "dist/web.esm.js", 30 | "devDependencies": { 31 | "husky": "^4.2.5", 32 | "tsdx": "^0.13.2", 33 | "tslib": "^2.0.0", 34 | "typescript": "^3.9.7" 35 | }, 36 | "dependencies": {}, 37 | "publishConfig": { 38 | "access": "public", 39 | "registry": "https://registry.npmjs.org" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /splitbee-web/src/index.ts: -------------------------------------------------------------------------------- 1 | import { QueueData, Splitbee, SplitbeeOptions } from './types'; 2 | 3 | const isBrowser = typeof window !== 'undefined'; 4 | const SCRIPT_URL = 'https://cdn.splitbee.io/sb.js'; 5 | let queue: Array = []; 6 | 7 | const handleLoad = () => { 8 | if (isBrowser && !window.splitbee) return; 9 | splitbee.track = window.splitbee.track; 10 | splitbee.user = window.splitbee.user; 11 | splitbee.enableCookie = window.splitbee.enableCookie; 12 | splitbee.reset = window.splitbee.reset; 13 | 14 | queue.forEach(ev => { 15 | if (ev.type === 'track') window.splitbee.track.apply(null, ev.payload); 16 | else if (ev.type === 'user') 17 | window.splitbee.user.set.apply(null, ev.payload); 18 | else if (ev.type === 'enableCookie') 19 | window.splitbee.enableCookie.apply(null, ev.payload); 20 | else if (ev.type === 'reset') window.splitbee.reset(); 21 | }); 22 | 23 | queue = []; 24 | }; 25 | 26 | const createAddToQueue = (type: QueueData['type']) => async (...args: any) => { 27 | queue.push({ type: type, payload: args }); 28 | if (isBrowser && window.splitbee) { 29 | handleLoad(); 30 | } 31 | }; 32 | 33 | const initSplitbee = (options?: SplitbeeOptions) => { 34 | if (!isBrowser || window.splitbee) return; 35 | 36 | const document = window.document; 37 | const scriptUrl = options?.scriptUrl ? options.scriptUrl : SCRIPT_URL; 38 | 39 | const injectedScript = document.querySelector( 40 | `script[src='${scriptUrl}']` 41 | ) as HTMLScriptElement | null; 42 | 43 | if (injectedScript) { 44 | injectedScript.onload = handleLoad; 45 | return; 46 | } 47 | 48 | const script = document.createElement('script'); 49 | script.src = scriptUrl; 50 | script.async = true; 51 | 52 | if (options) { 53 | if (options.apiUrl) script.dataset.api = options.apiUrl; 54 | if (options.token) script.dataset.token = options.token; 55 | if (options.disableCookie) script.dataset.noCookie = '1'; 56 | } 57 | 58 | script.onload = handleLoad; 59 | 60 | document.head.appendChild(script); 61 | }; 62 | 63 | const splitbee: Splitbee = { 64 | track: createAddToQueue('track'), 65 | user: { 66 | set: createAddToQueue('user'), 67 | }, 68 | init: initSplitbee, 69 | enableCookie: createAddToQueue('enableCookie'), 70 | reset: createAddToQueue('reset'), 71 | }; 72 | 73 | export default splitbee; 74 | -------------------------------------------------------------------------------- /splitbee-web/src/types.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | type Data = { [key: string]: string | number | boolean | undefined | null }; 4 | 5 | export type SplitbeeOptions = { 6 | disableCookie?: boolean; 7 | token?: string; 8 | apiUrl?: string; 9 | scriptUrl?: string; 10 | }; 11 | 12 | export type Splitbee = { 13 | track: (event: string, data?: Data) => Promise; 14 | user: { 15 | set: (data: Data) => Promise; 16 | }; 17 | init: (config?: SplitbeeOptions) => void; 18 | enableCookie: (forceNewSession?: boolean) => void; 19 | reset: () => void; 20 | }; 21 | 22 | declare global { 23 | interface Window { 24 | splitbee: Splitbee; 25 | } 26 | } 27 | 28 | export type QueueData = { 29 | type: 'user' | 'track' | 'enableCookie' | 'reset'; 30 | payload: any; 31 | }; 32 | -------------------------------------------------------------------------------- /splitbee-web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "importHelpers": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "rootDir": "./src", 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "moduleResolution": "node", 16 | "baseUrl": "./", 17 | "paths": { 18 | "*": ["src/*", "node_modules/*"] 19 | }, 20 | "jsx": "react", 21 | "esModuleInterop": true 22 | } 23 | } 24 | --------------------------------------------------------------------------------