Skip to content

Commit

Permalink
feat: add opt-in telemetry (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
paolodamico authored Jan 3, 2023
1 parent 4e13ce3 commit 081a5ef
Show file tree
Hide file tree
Showing 14 changed files with 105 additions and 25 deletions.
7 changes: 4 additions & 3 deletions example-nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "yarn build:idkit && next dev",
"build": "next build",
"start": "next start",
"build:idkit": "cd ../idkit && yarn build",
"lint": "next lint"
},
"dependencies": {
"@types/node": "18.11.9",
"@types/react": "18.0.25",
"@types/react-dom": "18.0.9",
"@worldcoin/idkit": "^0.0.1",
"eslint": "8.28.0",
"eslint-config-next": "13.0.5",
"next": "13.0.5",
"react": "18.2.0",
"react-dom": "18.2.0",
"typescript": "4.9.3",
"@worldcoin/idkit": "^0.0.1"
"typescript": "4.9.3"
}
}
9 changes: 8 additions & 1 deletion example-nextjs/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import Head from "next/head";
import dynamic from "next/dynamic";
import { useCallback } from "react";
import styles from "../styles/Home.module.css";
import { IDKitWidget, ISuccessResult } from "@worldcoin/idkit";
import type { ISuccessResult, WidgetProps } from "@worldcoin/idkit";

// FIXME: Temporary workaround because posthog-js-lite does not properly handle SSR (requires window to be defined),
// we need an upstream patch or implement our own capturing with simple XHR requests.
const IDKitWidget = dynamic<WidgetProps>(() => import("@worldcoin/idkit").then((mod) => mod.IDKitWidget), {
ssr: false,
});

export default function Home() {
const handleProof = useCallback(
Expand Down
5 changes: 3 additions & 2 deletions example-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
"web-vitals": "^2.1.4"
},
"scripts": {
"dev": "react-scripts start",
"dev": "yarn build:idkit && react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"build:idkit": "cd ../idkit && yarn build"
},
"eslintConfig": {
"extends": [
Expand Down
2 changes: 1 addition & 1 deletion idkit/esbuild/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default /** @type {import('esbuild').BuildOptions} */ ({
logLevel: 'info',
define: {
global: 'globalThis',
worldIdJSVersion: JSON.stringify(packageJson.version),
IDKitVersion: JSON.stringify(packageJson.version),
},
entryPoints: [require.resolve('../src/index.ts')],
globalName: 'IDKit',
Expand Down
1 change: 1 addition & 0 deletions idkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"framer-motion": "^7.6.7",
"js-sha3": "^0.8.0",
"phone": "^3.1.30",
"posthog-js-lite": "2.0.0-alpha5",
"qr-code-styling-new": "^1.6.1",
"react-countdown": "^2.3.4",
"react-country-flag": "^3.0.2",
Expand Down
8 changes: 2 additions & 6 deletions idkit/src/components/IDKitWidget/BaseWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import root from 'react-shadow'
import { IDKITStage } from '@/types'
import Styles from '@build/index.css'
import useIDKitStore from '@/store/idkit'
import type { Config } from '@/types/config'
import ErrorState from './States/ErrorState'
import AboutState from './States/AboutState'
import { ConfigSource } from '@/types/config'
Expand All @@ -14,6 +13,7 @@ import type { IDKitStore } from '@/store/idkit'
import SuccessState from './States/SuccessState'
import WorldIDState from './States/WorldIDState'
import * as Dialog from '@radix-ui/react-dialog'
import type { WidgetProps } from '@/types/config'
import { useEffect, useMemo, useState } from 'react'
import WorldIDWordmark from '../Icons/WorldIDWordmark'
import EnterPhoneState from './States/EnterPhoneState'
Expand All @@ -32,11 +32,7 @@ const getParams = ({ copy, open, processing, onOpenChange, stage, setStage, setO
onOpenChange,
})

type Props = Config & {
children?: ({ open }: { open: () => void }) => JSX.Element
}

const IDKitWidget: FC<Props> = ({ children, actionId, signal, onSuccess, autoClose, copy }) => {
const IDKitWidget: FC<WidgetProps> = ({ children, actionId, signal, onSuccess, autoClose, copy }) => {
const { isOpen, onOpenChange, processing, stage, setStage, setOptions, copy: _copy } = useIDKitStore(getParams)
const [isMobile, setIsMobile] = useState(false)

Expand Down
5 changes: 3 additions & 2 deletions idkit/src/components/IDKitWidget/States/EnterPhoneState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import PhoneInput from '@/components/PhoneInput'
import WorldIDIcon from '@/components/WorldIDIcon'
import { XMarkIcon } from '@heroicons/react/20/solid'
import { isRequestCodeError, requestCode } from '@/services/phone'
import { getTelemetryId, telemetryPhoneTyped } from '@/lib/telemetry'

const getParams = ({
processing,
Expand All @@ -30,8 +31,8 @@ const getParams = ({
try {
setProcessing(true)
setErrorState(null)
// FIXME: ph_distinct_id
await requestCode(phoneNumber, stringifiedActionId, '')
await requestCode(phoneNumber, stringifiedActionId, getTelemetryId())
telemetryPhoneTyped()
setProcessing(false)
setStage(IDKITStage.ENTER_CODE)
} catch (error) {
Expand Down
9 changes: 7 additions & 2 deletions idkit/src/components/IDKitWidget/States/VerifyCodeState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useMemo, useRef } from 'react'
import useIDKitStore from '@/store/idkit'
import { DEFAULT_COPY } from '@/types/config'
import type { IDKitStore } from '@/store/idkit'
import { getTelemetryId } from '@/lib/telemetry'
import WorldIDIcon from '@/components/WorldIDIcon'
import ResendButton from '@/components/ResendButton'
import SMSCodeInput from '@/components/SMSCodeInput'
Expand Down Expand Up @@ -34,8 +35,12 @@ const getParams = ({
try {
setErrorState(null)
setProcessing(true)
// FIXME: Add ph_distinct_id
const { nullifier_hash, ...proof_payload } = await verifyCode(phoneNumber, code, stringifiedActionId, '')
const { nullifier_hash, ...proof_payload } = await verifyCode(
phoneNumber,
code,
stringifiedActionId,
getTelemetryId()
)
onSuccess({ signal_type: SignalType.Phone, nullifier_hash, proof_payload })
} catch (error) {
setProcessing(false)
Expand Down
4 changes: 2 additions & 2 deletions idkit/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import useIDKit from './hooks/useIDKit'
import type { Config } from '@/types/config'
import IDKitWidget from '@/components/IDKitWidget'
import type { WidgetProps, Config } from '@/types/config'
import type { ISuccessResult, SignalType, PhoneSignalProof, OrbSignalProof } from '@/types'

export { IDKitWidget, useIDKit }
export type { ISuccessResult, Config, SignalType, PhoneSignalProof, OrbSignalProof }
export type { ISuccessResult, Config, SignalType, PhoneSignalProof, OrbSignalProof, WidgetProps }
55 changes: 55 additions & 0 deletions idkit/src/lib/telemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import PostHog from 'posthog-js-lite'

// Set at build time
declare const IDKitVersion: string

function factoryPostHogFetchError(error: unknown) {
return { name: 'telemetry-error', error }
}

if (typeof window !== 'undefined') {
window.onunhandledrejection = function (event) {
return (event.reason as Record<string, any>).name !== 'telemetry-error'
}
}

async function posthogFetch(input: RequestInfo, init?: RequestInit): Promise<Response> {
try {
return await fetch(input, init)
} catch (error) {
throw factoryPostHogFetchError(error)
}
}

let posthog: PostHog | null = null

if (typeof window !== 'undefined') {
posthog = new PostHog(
'phc_QttqgDbMQDYHX1EMH7FnT6ECBVzdp0kGUq92aQaVQ6I', // cspell:disable-line
{ persistence: 'memory' }
)
posthog.fetch = posthogFetch
}

// Attributes sent on all events
const SUPER_PROPS = { version: IDKitVersion, package: 'idkit-js' }

export const getTelemetryId = (): string => {
return posthog?.getDistinctId() ?? ''
}

export const initTelemetry = (enableTelemetry?: boolean): void => {
if (enableTelemetry) {
posthog?.capture('idkit loaded', SUPER_PROPS)
} else {
posthog?.optOut()
}
}

export const telemetryModalOpened = (): void => {
posthog?.capture('idkit opened', SUPER_PROPS)
}

export const telemetryPhoneTyped = (): void => {
posthog?.capture('idkit phone typed', SUPER_PROPS)
}
13 changes: 7 additions & 6 deletions idkit/src/store/idkit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import create from 'zustand'
import { IDKITStage } from '@/types'
import { worldIDHash } from '@/lib/hashing'
import { telemetryModalOpened } from '@/lib/telemetry'
import type { CallbackFn, ErrorState, ISuccessResult } from '@/types'
import type { Config, ConfigSource, StringOrAdvanced } from '@/types/config'

Expand All @@ -23,7 +24,6 @@ export type IDKitStore = {
setOpen: (open: boolean) => void
setStage: (stage: IDKITStage) => void
onOpenChange: (open: boolean) => void
setActionId: (actionId: StringOrAdvanced) => void
onSuccess: (result: ISuccessResult) => void
setProcessing: (processing: boolean) => void
setPhoneNumber: (phoneNumber: string) => void
Expand All @@ -49,10 +49,6 @@ const useIDKitStore = create<IDKitStore>()((set, get) => ({
setOpen: open => set({ open }),
setCode: code => set({ code }),
setStage: stage => set({ stage }),
setActionId: actionId => {
const stringifiedActionId = typeof actionId === 'string' ? actionId : worldIDHash(actionId).digest
set({ actionId, stringifiedActionId })
},
setErrorState: errorState => set({ errorState }),
setPhoneNumber: phoneNumber => set({ phoneNumber }),
setProcessing: (processing: boolean) => set({ processing }),
Expand All @@ -65,8 +61,10 @@ const useIDKitStore = create<IDKitStore>()((set, get) => ({
})
},
setOptions: ({ onSuccess, signal, actionId, autoClose, copy }: Config, source: ConfigSource) => {
const stringifiedActionId = typeof actionId === 'string' ? actionId : worldIDHash(actionId).digest
set(store => ({
actionId,
stringifiedActionId,
signal,
autoClose,
copy: { ...store.copy, ...copy },
Expand All @@ -81,7 +79,10 @@ const useIDKitStore = create<IDKitStore>()((set, get) => ({
if (get().autoClose) setTimeout(() => set({ open: false }), 1000)
},
onOpenChange: open => {
if (open) return set({ open })
if (open) {
telemetryModalOpened()
return set({ open })
}
set({ open, phoneNumber: '', code: '', processing: false, stage: IDKITStage.ENTER_PHONE })
},
}))
Expand Down
4 changes: 4 additions & 0 deletions idkit/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export type Config = {
}
}

export type WidgetProps = Config & {
children?: ({ open }: { open: () => void }) => JSX.Element
}

export const DEFAULT_COPY = {
title: 'World ID',
heading: 'Verify your phone number',
Expand Down
3 changes: 3 additions & 0 deletions idkit/src/vanilla.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Config } from './types/config'
import type { Root } from 'react-dom/client'
import { ConfigSource } from './types/config'
import { createRoot } from 'react-dom/client'
import { initTelemetry } from './lib/telemetry'
import IDKitWidget from './components/IDKitWidget'

let root: Root
Expand All @@ -25,6 +26,8 @@ export const init = (config: Config): void => {
root = createRoot(node)
root.render(<IDKitWidget {...config} />)

initTelemetry(config.enableTelemetry)

isInitialized = true
}
} catch (error) {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9987,6 +9987,11 @@ postcss@^8.3.5, postcss@^8.4.12, postcss@^8.4.18, postcss@^8.4.19, postcss@^8.4.
picocolors "^1.0.0"
source-map-js "^1.0.2"

[email protected]:
version "2.0.0-alpha5"
resolved "https://registry.yarnpkg.com/posthog-js-lite/-/posthog-js-lite-2.0.0-alpha5.tgz#60cff1b756ba2723ebb0222ca132fd0de8036210"
integrity sha512-tlkBdypJuvK/s00n4EiQjwYVfuuZv6vt8BF3g1ooIQa2Gz9Vz80p8q3qsPLZ0V5ErGRy6i3Q4fWC9TDzR7GNRQ==

prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
Expand Down

0 comments on commit 081a5ef

Please sign in to comment.