diff --git a/src/__tests__/extensions/surveys/action-matcher.test.ts b/src/__tests__/extensions/surveys/action-matcher.test.ts index c7ef574f2..021088470 100644 --- a/src/__tests__/extensions/surveys/action-matcher.test.ts +++ b/src/__tests__/extensions/surveys/action-matcher.test.ts @@ -1,6 +1,6 @@ /// -import { ActionType, ActionStepStringMatching } from '../../../posthog-surveys-types' +import { SurveyActionType, ActionStepStringMatching } from '../../../posthog-surveys-types' import { PostHogPersistence } from '../../../posthog-persistence' import { PostHog } from '../../../posthog-core' import { CaptureResult, PostHogConfig } from '../../../types' @@ -45,7 +45,7 @@ describe('action-matcher', () => { eventName: string, currentUrl?: string, urlMatch?: ActionStepStringMatching - ): ActionType => { + ): SurveyActionType => { return { id: id, name: `${eventName || 'user defined '} action`, @@ -68,7 +68,7 @@ describe('action-matcher', () => { } it('can match action on event name', () => { - const pageViewAction = createAction(3, '$mypageview') as unknown as ActionType + const pageViewAction = createAction(3, '$mypageview') as unknown as SurveyActionType const actionMatcher = new ActionMatcher(instance) actionMatcher.register([pageViewAction]) let pageViewActionMatched = false diff --git a/src/__tests__/utils/survey-event-receiver.test.ts b/src/__tests__/utils/survey-event-receiver.test.ts index d36ff33e4..4ee51ed51 100644 --- a/src/__tests__/utils/survey-event-receiver.test.ts +++ b/src/__tests__/utils/survey-event-receiver.test.ts @@ -4,7 +4,7 @@ import { SurveyType, SurveyQuestionType, Survey, - ActionType, + SurveyActionType, ActionStepStringMatching, } from '../../posthog-surveys-types' import { PostHogPersistence } from '../../posthog-persistence' @@ -209,7 +209,7 @@ describe('survey-event-receiver', () => { eventName: string, currentUrl?: string, urlMatch?: ActionStepStringMatching - ): ActionType => { + ): SurveyActionType => { return { id: id, name: `${eventName || 'user defined '} action`, @@ -238,7 +238,7 @@ describe('survey-event-receiver', () => { type: SurveyType.Popover, questions: [{ type: SurveyQuestionType.Open, question: 'what is a bokoblin?' }], conditions: { - actions: [createAction(2, '$autocapture') as unknown as ActionType], + actions: [createAction(2, '$autocapture') as unknown as SurveyActionType], }, } as unknown as Survey @@ -249,7 +249,7 @@ describe('survey-event-receiver', () => { type: SurveyType.Popover, questions: [{ type: SurveyQuestionType.Open, question: 'what is a bokoblin?' }], conditions: { - actions: [createAction(3, '$pageview') as unknown as ActionType], + actions: [createAction(3, '$pageview') as unknown as SurveyActionType], }, } as unknown as Survey @@ -262,7 +262,7 @@ describe('survey-event-receiver', () => { questions: [{ type: SurveyQuestionType.Open, question: 'what is a bokoblin?' }], conditions: { actions: { - values: [createAction(3, '$mypageview') as unknown as ActionType], + values: [createAction(3, '$mypageview') as unknown as SurveyActionType], }, }, } as unknown as Survey diff --git a/src/extensions/surveys.tsx b/src/extensions/surveys.tsx index b1fec5638..f4bbffa31 100644 --- a/src/extensions/surveys.tsx +++ b/src/extensions/surveys.tsx @@ -211,7 +211,7 @@ export class SurveyManager { ) } - public callSurveysAndEvaluateDisplayLogic = (forceReload: boolean = false): void => { + public callSurveysAndEvaluateDisplayLogic = (): void => { this.posthog?.getActiveMatchingSurveys((surveys) => { const nonAPISurveys = surveys.filter((survey) => survey.type !== 'api') @@ -240,7 +240,7 @@ export class SurveyManager { this.handlePopoverSurvey(survey) } }) - }, forceReload) + }) } private addSurveyToFocus = (id: string): void => { @@ -350,11 +350,11 @@ export function generateSurveys(posthog: PostHog) { } const surveyManager = new SurveyManager(posthog) - surveyManager.callSurveysAndEvaluateDisplayLogic(true) + surveyManager.callSurveysAndEvaluateDisplayLogic() // recalculate surveys every second to check if URL or selectors have changed setInterval(() => { - surveyManager.callSurveysAndEvaluateDisplayLogic(false) + surveyManager.callSurveysAndEvaluateDisplayLogic() }, 1000) return surveyManager } diff --git a/src/extensions/surveys/action-matcher.ts b/src/extensions/surveys/action-matcher.ts index a6d4ceb0a..82bb125a0 100644 --- a/src/extensions/surveys/action-matcher.ts +++ b/src/extensions/surveys/action-matcher.ts @@ -1,5 +1,5 @@ import { PostHog } from '../../posthog-core' -import { ActionStepStringMatching, ActionStepType, ActionType, SurveyElement } from '../../posthog-surveys-types' +import { ActionStepStringMatching, ActionStepType, SurveyActionType, SurveyElement } from '../../posthog-surveys-types' import { SimpleEventEmitter } from '../../utils/simple-event-emitter' import { CaptureResult } from '../../types' import { isUndefined } from '../../utils/type-utils' @@ -7,7 +7,7 @@ import { window } from '../../utils/globals' import { isUrlMatchingRegex } from '../../utils/request-utils' export class ActionMatcher { - private readonly actionRegistry?: Set + private readonly actionRegistry?: Set private readonly instance?: PostHog private readonly actionEvents: Set private _debugEventEmitter = new SimpleEventEmitter() @@ -15,7 +15,7 @@ export class ActionMatcher { constructor(instance?: PostHog) { this.instance = instance this.actionEvents = new Set() - this.actionRegistry = new Set() + this.actionRegistry = new Set() } init() { @@ -27,7 +27,7 @@ export class ActionMatcher { } } - register(actions: ActionType[]): void { + register(actions: SurveyActionType[]): void { if (isUndefined(this.instance?._addCaptureHook)) { return } @@ -74,7 +74,7 @@ export class ActionMatcher { this.onAction('actionCaptured', (data) => callback(data)) } - private checkAction(event?: CaptureResult, action?: ActionType): boolean { + private checkAction(event?: CaptureResult, action?: SurveyActionType): boolean { if (action?.steps == null) { return false } diff --git a/src/posthog-surveys.ts b/src/posthog-surveys.ts index f33fe4f4a..723119a9c 100644 --- a/src/posthog-surveys.ts +++ b/src/posthog-surveys.ts @@ -12,7 +12,7 @@ import { SurveyEventReceiver } from './utils/survey-event-receiver' import { assignableWindow, document, window } from './utils/globals' import { RemoteConfig } from './types' import { createLogger } from './utils/logger' -import { isNullish } from './utils/type-utils' +import { isArray, isNullish } from './utils/type-utils' import { getSurveySeenStorageKeys } from './extensions/surveys/surveys-utils' const logger = createLogger('[Surveys]') @@ -59,10 +59,12 @@ function getRatingBucketForResponseValue(responseValue: number, scale: number) { } export class PostHogSurveys { - private _decideServerResponse?: boolean + private _surveysRemoteEnabled?: boolean public _surveyEventReceiver: SurveyEventReceiver | null private _surveyManager: any + private surveys?: Survey[] + constructor(private readonly instance: PostHog) { // we set this to undefined here because we need the persistence storage for this type // but that's not initialized until loadIfEnabled is called. @@ -70,7 +72,13 @@ export class PostHogSurveys { } onRemoteConfig(response: RemoteConfig) { - this._decideServerResponse = !!response['surveys'] + if (isArray(response['surveys'])) { + this.surveys = response['surveys'] + this._surveysRemoteEnabled = this.surveys.length > 0 + } else { + this._surveysRemoteEnabled = !!response['surveys'] + } + this.loadIfEnabled() } @@ -83,7 +91,7 @@ export class PostHogSurveys { loadIfEnabled() { const surveysGenerator = assignableWindow?.__PosthogExtensions__?.generateSurveys - if (!this.instance.config.disable_surveys && this._decideServerResponse && !surveysGenerator) { + if (!this.instance.config.disable_surveys && this._surveysRemoteEnabled && !surveysGenerator) { if (this._surveyEventReceiver == null) { this._surveyEventReceiver = new SurveyEventReceiver(this.instance) } @@ -98,6 +106,38 @@ export class PostHogSurveys { } } + reloadSurveys(callback: (surveys: Survey[]) => void) { + // TODO: If we are using RemoteConfig - load it from there instead of API + this.instance._send_request({ + url: this.instance.requestRouter.endpointFor('api', `/api/surveys/?token=${this.instance.config.token}`), + method: 'GET', + transport: 'XHR', + callback: (response) => { + if (response.statusCode !== 200 || !response.json) { + return callback([]) + } + const surveys = response.json.surveys || [] + + const eventOrActionBasedSurveys = surveys.filter( + (survey: Survey) => + (survey.conditions?.events && + survey.conditions?.events?.values && + survey.conditions?.events?.values?.length > 0) || + (survey.conditions?.actions && + survey.conditions?.actions?.values && + survey.conditions?.actions?.values?.length > 0) + ) + + if (eventOrActionBasedSurveys.length > 0) { + this._surveyEventReceiver?.register(eventOrActionBasedSurveys) + } + + this.instance.persistence?.register({ [SURVEYS]: surveys }) + return callback(surveys) + }, + }) + } + getSurveys(callback: SurveyCallback, forceReload = false) { // In case we manage to load the surveys script, but config says not to load surveys // then we shouldn't return survey data @@ -109,40 +149,16 @@ export class PostHogSurveys { this._surveyEventReceiver = new SurveyEventReceiver(this.instance) } - const existingSurveys = this.instance.get_property(SURVEYS) + const existingSurveys = this.surveys ?? this.instance.get_property(SURVEYS) - if (!existingSurveys || forceReload) { - this.instance._send_request({ - url: this.instance.requestRouter.endpointFor( - 'api', - `/api/surveys/?token=${this.instance.config.token}` - ), - method: 'GET', - transport: 'XHR', - callback: (response) => { - if (response.statusCode !== 200 || !response.json) { - return callback([]) - } - const surveys = response.json.surveys || [] - - const eventOrActionBasedSurveys = surveys.filter( - (survey: Survey) => - (survey.conditions?.events && - survey.conditions?.events?.values && - survey.conditions?.events?.values?.length > 0) || - (survey.conditions?.actions && - survey.conditions?.actions?.values && - survey.conditions?.actions?.values?.length > 0) - ) - - if (eventOrActionBasedSurveys.length > 0) { - this._surveyEventReceiver?.register(eventOrActionBasedSurveys) - } + console.log('existingSurveys', existingSurveys, forceReload) - this.instance.persistence?.register({ [SURVEYS]: surveys }) - return callback(surveys) - }, - }) + if (forceReload) { + throw new Error('forceReload') + } + + if (!existingSurveys || forceReload) { + this.reloadSurveys(callback) } else { return callback(existingSurveys) } diff --git a/src/types.ts b/src/types.ts index 303b501b1..8041763ef 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,7 @@ import { PostHog } from './posthog-core' import type { SegmentAnalytics } from './extensions/segment-integration' import { recordOptions } from './extensions/replay/sessionrecording-utils' +import { Survey, SurveyType } from './posthog-surveys-types' export type Property = any export type Properties = Record @@ -522,7 +523,8 @@ export interface RemoteConfig { urlBlocklist?: SessionRecordingUrlTrigger[] eventTriggers?: string[] } - surveys?: boolean + surveys?: boolean | Survey[] + survey_config?: Record // TODO: Type this better toolbarParams: ToolbarParams editorParams?: ToolbarParams /** @deprecated, renamed to toolbarParams, still present on older API responses */ toolbarVersion: 'toolbar' /** @deprecated, moved to toolbarParams */