23 lines
2.3 KiB
TypeScript
23 lines
2.3 KiB
TypeScript
import { ref, onMounted, onUnmounted } from 'vue'
|
|
import type { KbxApiOperationId, KbxExperimentAssignmentsResponse, KbxExperimentId } from '@kbx/contracts'
|
|
import { kbxApi } from '../http/generated/kbxApiClient'
|
|
import { kbxTelemetry } from '../telemetry/kbxTelemetryClient'
|
|
import { createExperimentRolloutConnection } from './createExperimentRolloutConnection'
|
|
|
|
const screenCache=new Map<string,Promise<KbxExperimentAssignmentsResponse>>()
|
|
const listeners=new Map<string,Set<()=>void>>()
|
|
let realtimeStarted=false
|
|
const realtime=createExperimentRolloutConnection()
|
|
realtime.onChanged(event=>{screenCache.delete(event.screenId);for(const refresh of listeners.get(event.screenId)??[])void refresh()})
|
|
async function ensureRealtime(){if(realtimeStarted)return;realtimeStarted=true;try{await realtime.start()}catch{realtimeStarted=false}}
|
|
function assignments(screenId:string,force=false){if(force)screenCache.delete(screenId);if(!screenCache.has(screenId))screenCache.set(screenId,kbxApi.request<KbxExperimentAssignmentsResponse>('common.experiments.assignments' as KbxApiOperationId,{query:{screenId}}));return screenCache.get(screenId)!}
|
|
export function useKbxExperiment(screenId:string,screenVersion:string,experimentId:KbxExperimentId,surface:string){
|
|
const variant=ref('control'),enrolled=ref(false);let lastExposure=''
|
|
const refresh=async()=>{
|
|
try{const result=await assignments(screenId,true);const assignment=result.assignments.find(x=>x.experimentId===experimentId);if(!assignment||!assignment.enrolled){enrolled.value=false;variant.value='control';lastExposure='';kbxTelemetry.clearExperiment(screenId);return}enrolled.value=true;variant.value=assignment.variant;kbxTelemetry.bindExperiment(screenId,assignment.experimentId,assignment.variant);const exposure=`${assignment.experimentId}:${assignment.variant}`;if(lastExposure!==exposure){lastExposure=exposure;kbxTelemetry.track('experiment.exposed',{screenId,screenVersion,attributes:{surface}})}}catch{/* experiment infrastructure must not block business UI */}
|
|
}
|
|
onMounted(async()=>{const set=listeners.get(screenId)??new Set<()=>void>();set.add(refresh);listeners.set(screenId,set);await ensureRealtime();await refresh()})
|
|
onUnmounted(()=>{listeners.get(screenId)?.delete(refresh);kbxTelemetry.clearExperiment(screenId)})
|
|
return {variant,enrolled,is:(value:string)=>variant.value===value,refresh}
|
|
}
|