@posthog/core
Advanced tools
| import { SurveyResponses, SurveyResponseValue } from '../types'; | ||
| export declare const SURVEY_LANGUAGE_PROPERTY = "$survey_language"; | ||
| export declare function getSurveyResponseKey(questionId: string): string; | ||
| export declare function getSurveyOldResponseKey(originalQuestionIndex: number): string; | ||
| export declare function getSurveyResponseValue(responses: SurveyResponses, questionId?: string): SurveyResponseValue | undefined; | ||
| export declare function buildSurveyResponseProperties(responses: SurveyResponses | undefined, survey: SurveyForResponses): Record<string, unknown>; | ||
| export declare function surveyHasResponses(responses?: SurveyResponses): boolean; | ||
| export declare function getSurveyInteractionProperty(survey: SurveyWithIteration, action: string): string; | ||
| type SurveyQuestionForResponses = { | ||
| id?: string; | ||
| question: string; | ||
| originalQuestionIndex?: number; | ||
| }; | ||
| type SurveyForResponses = { | ||
| questions: SurveyQuestionForResponses[]; | ||
| }; | ||
| type SurveyWithIteration = { | ||
| id: string; | ||
| current_iteration?: number | null; | ||
| }; | ||
| export {}; | ||
| //# sourceMappingURL=events.d.ts.map |
| {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/surveys/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAG/D,eAAO,MAAM,wBAAwB,qBAAqB,CAAA;AAE1D,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,uBAAuB,CAAC,qBAAqB,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,eAAe,EAC1B,UAAU,CAAC,EAAE,MAAM,GAClB,mBAAmB,GAAG,SAAS,CASjC;AAED,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,eAAe,YAAK,EAC/B,MAAM,EAAE,kBAAkB,GACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsBzB;AAED,wBAAgB,kBAAkB,CAAC,SAAS,GAAE,eAAoB,GAAG,OAAO,CAE3E;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOhG;AACD,KAAK,0BAA0B,GAAG;IAChC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAC/B,CAAA;AAED,KAAK,kBAAkB,GAAG;IACxB,SAAS,EAAE,0BAA0B,EAAE,CAAA;CACxC,CAAA;AAED,KAAK,mBAAmB,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC,CAAA"} |
| "use strict"; | ||
| var __webpack_require__ = {}; | ||
| (()=>{ | ||
| __webpack_require__.d = (exports1, definition)=>{ | ||
| for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, { | ||
| enumerable: true, | ||
| get: definition[key] | ||
| }); | ||
| }; | ||
| })(); | ||
| (()=>{ | ||
| __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop); | ||
| })(); | ||
| (()=>{ | ||
| __webpack_require__.r = (exports1)=>{ | ||
| if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, { | ||
| value: 'Module' | ||
| }); | ||
| Object.defineProperty(exports1, '__esModule', { | ||
| value: true | ||
| }); | ||
| }; | ||
| })(); | ||
| var __webpack_exports__ = {}; | ||
| __webpack_require__.r(__webpack_exports__); | ||
| __webpack_require__.d(__webpack_exports__, { | ||
| getSurveyInteractionProperty: ()=>getSurveyInteractionProperty, | ||
| buildSurveyResponseProperties: ()=>buildSurveyResponseProperties, | ||
| SURVEY_LANGUAGE_PROPERTY: ()=>SURVEY_LANGUAGE_PROPERTY, | ||
| getSurveyOldResponseKey: ()=>getSurveyOldResponseKey, | ||
| getSurveyResponseKey: ()=>getSurveyResponseKey, | ||
| getSurveyResponseValue: ()=>getSurveyResponseValue, | ||
| surveyHasResponses: ()=>surveyHasResponses | ||
| }); | ||
| const index_js_namespaceObject = require("../utils/index.js"); | ||
| const SURVEY_LANGUAGE_PROPERTY = '$survey_language'; | ||
| function getSurveyResponseKey(questionId) { | ||
| return `$survey_response_${questionId}`; | ||
| } | ||
| function getSurveyOldResponseKey(originalQuestionIndex) { | ||
| return 0 === originalQuestionIndex ? '$survey_response' : `$survey_response_${originalQuestionIndex}`; | ||
| } | ||
| function getSurveyResponseValue(responses, questionId) { | ||
| if (!questionId) return null; | ||
| const response = responses[getSurveyResponseKey(questionId)]; | ||
| if ((0, index_js_namespaceObject.isArray)(response)) return [ | ||
| ...response | ||
| ]; | ||
| return response; | ||
| } | ||
| function buildSurveyResponseProperties(responses = {}, survey) { | ||
| const oldFormatResponses = {}; | ||
| survey.questions.forEach((question)=>{ | ||
| if ((0, index_js_namespaceObject.isUndefined)(question.originalQuestionIndex)) return; | ||
| const oldResponseKey = getSurveyOldResponseKey(question.originalQuestionIndex); | ||
| const response = getSurveyResponseValue(responses, question.id); | ||
| if (!(0, index_js_namespaceObject.isUndefined)(response)) oldFormatResponses[oldResponseKey] = response; | ||
| }); | ||
| return { | ||
| $survey_questions: survey.questions.map((question)=>({ | ||
| id: question.id, | ||
| question: question.question, | ||
| response: getSurveyResponseValue(responses, question.id) | ||
| })), | ||
| ...responses, | ||
| ...oldFormatResponses | ||
| }; | ||
| } | ||
| function surveyHasResponses(responses = {}) { | ||
| return Object.values(responses).some((response)=>!(0, index_js_namespaceObject.isNullish)(response)); | ||
| } | ||
| function getSurveyInteractionProperty(survey, action) { | ||
| let surveyProperty = `$survey_${action}/${survey.id}`; | ||
| if (survey.current_iteration && survey.current_iteration > 0) surveyProperty = `$survey_${action}/${survey.id}/${survey.current_iteration}`; | ||
| return surveyProperty; | ||
| } | ||
| exports.SURVEY_LANGUAGE_PROPERTY = __webpack_exports__.SURVEY_LANGUAGE_PROPERTY; | ||
| exports.buildSurveyResponseProperties = __webpack_exports__.buildSurveyResponseProperties; | ||
| exports.getSurveyInteractionProperty = __webpack_exports__.getSurveyInteractionProperty; | ||
| exports.getSurveyOldResponseKey = __webpack_exports__.getSurveyOldResponseKey; | ||
| exports.getSurveyResponseKey = __webpack_exports__.getSurveyResponseKey; | ||
| exports.getSurveyResponseValue = __webpack_exports__.getSurveyResponseValue; | ||
| exports.surveyHasResponses = __webpack_exports__.surveyHasResponses; | ||
| for(var __webpack_i__ in __webpack_exports__)if (-1 === [ | ||
| "SURVEY_LANGUAGE_PROPERTY", | ||
| "buildSurveyResponseProperties", | ||
| "getSurveyInteractionProperty", | ||
| "getSurveyOldResponseKey", | ||
| "getSurveyResponseKey", | ||
| "getSurveyResponseValue", | ||
| "surveyHasResponses" | ||
| ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__]; | ||
| Object.defineProperty(exports, '__esModule', { | ||
| value: true | ||
| }); |
| import { isArray, isNullish, isUndefined } from "../utils/index.mjs"; | ||
| const SURVEY_LANGUAGE_PROPERTY = '$survey_language'; | ||
| function getSurveyResponseKey(questionId) { | ||
| return `$survey_response_${questionId}`; | ||
| } | ||
| function getSurveyOldResponseKey(originalQuestionIndex) { | ||
| return 0 === originalQuestionIndex ? '$survey_response' : `$survey_response_${originalQuestionIndex}`; | ||
| } | ||
| function getSurveyResponseValue(responses, questionId) { | ||
| if (!questionId) return null; | ||
| const response = responses[getSurveyResponseKey(questionId)]; | ||
| if (isArray(response)) return [ | ||
| ...response | ||
| ]; | ||
| return response; | ||
| } | ||
| function buildSurveyResponseProperties(responses = {}, survey) { | ||
| const oldFormatResponses = {}; | ||
| survey.questions.forEach((question)=>{ | ||
| if (isUndefined(question.originalQuestionIndex)) return; | ||
| const oldResponseKey = getSurveyOldResponseKey(question.originalQuestionIndex); | ||
| const response = getSurveyResponseValue(responses, question.id); | ||
| if (!isUndefined(response)) oldFormatResponses[oldResponseKey] = response; | ||
| }); | ||
| return { | ||
| $survey_questions: survey.questions.map((question)=>({ | ||
| id: question.id, | ||
| question: question.question, | ||
| response: getSurveyResponseValue(responses, question.id) | ||
| })), | ||
| ...responses, | ||
| ...oldFormatResponses | ||
| }; | ||
| } | ||
| function surveyHasResponses(responses = {}) { | ||
| return Object.values(responses).some((response)=>!isNullish(response)); | ||
| } | ||
| function getSurveyInteractionProperty(survey, action) { | ||
| let surveyProperty = `$survey_${action}/${survey.id}`; | ||
| if (survey.current_iteration && survey.current_iteration > 0) surveyProperty = `$survey_${action}/${survey.id}/${survey.current_iteration}`; | ||
| return surveyProperty; | ||
| } | ||
| export { SURVEY_LANGUAGE_PROPERTY, buildSurveyResponseProperties, getSurveyInteractionProperty, getSurveyOldResponseKey, getSurveyResponseKey, getSurveyResponseValue, surveyHasResponses }; |
| export { getValidationError, getLengthFromRules, getRequirementsHint } from './validation'; | ||
| export { buildSurveyResponseProperties, getSurveyInteractionProperty, getSurveyOldResponseKey, getSurveyResponseKey, getSurveyResponseValue, SURVEY_LANGUAGE_PROPERTY, surveyHasResponses, } from './events'; | ||
| export { applySurveyTranslation, detectSurveyLanguage, findBestTranslationMatch, getBaseLanguage, getLanguageFromStoredPersonProperties, normalizeLanguageCode, } from './translations'; | ||
| //# sourceMappingURL=index.d.ts.map |
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/surveys/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAC1F,OAAO,EACL,6BAA6B,EAC7B,4BAA4B,EAC5B,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,UAAU,CAAA;AACjB,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACxB,eAAe,EACf,qCAAqC,EACrC,qBAAqB,GACtB,MAAM,gBAAgB,CAAA"} |
| "use strict"; | ||
| var __webpack_require__ = {}; | ||
| (()=>{ | ||
| __webpack_require__.d = (exports1, definition)=>{ | ||
| for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, { | ||
| enumerable: true, | ||
| get: definition[key] | ||
| }); | ||
| }; | ||
| })(); | ||
| (()=>{ | ||
| __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop); | ||
| })(); | ||
| (()=>{ | ||
| __webpack_require__.r = (exports1)=>{ | ||
| if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, { | ||
| value: 'Module' | ||
| }); | ||
| Object.defineProperty(exports1, '__esModule', { | ||
| value: true | ||
| }); | ||
| }; | ||
| })(); | ||
| var __webpack_exports__ = {}; | ||
| __webpack_require__.r(__webpack_exports__); | ||
| __webpack_require__.d(__webpack_exports__, { | ||
| getSurveyInteractionProperty: ()=>external_events_js_namespaceObject.getSurveyInteractionProperty, | ||
| getSurveyOldResponseKey: ()=>external_events_js_namespaceObject.getSurveyOldResponseKey, | ||
| getBaseLanguage: ()=>external_translations_js_namespaceObject.getBaseLanguage, | ||
| getSurveyResponseValue: ()=>external_events_js_namespaceObject.getSurveyResponseValue, | ||
| SURVEY_LANGUAGE_PROPERTY: ()=>external_events_js_namespaceObject.SURVEY_LANGUAGE_PROPERTY, | ||
| detectSurveyLanguage: ()=>external_translations_js_namespaceObject.detectSurveyLanguage, | ||
| applySurveyTranslation: ()=>external_translations_js_namespaceObject.applySurveyTranslation, | ||
| getValidationError: ()=>external_validation_js_namespaceObject.getValidationError, | ||
| getLanguageFromStoredPersonProperties: ()=>external_translations_js_namespaceObject.getLanguageFromStoredPersonProperties, | ||
| buildSurveyResponseProperties: ()=>external_events_js_namespaceObject.buildSurveyResponseProperties, | ||
| getRequirementsHint: ()=>external_validation_js_namespaceObject.getRequirementsHint, | ||
| findBestTranslationMatch: ()=>external_translations_js_namespaceObject.findBestTranslationMatch, | ||
| normalizeLanguageCode: ()=>external_translations_js_namespaceObject.normalizeLanguageCode, | ||
| getSurveyResponseKey: ()=>external_events_js_namespaceObject.getSurveyResponseKey, | ||
| getLengthFromRules: ()=>external_validation_js_namespaceObject.getLengthFromRules, | ||
| surveyHasResponses: ()=>external_events_js_namespaceObject.surveyHasResponses | ||
| }); | ||
| const external_validation_js_namespaceObject = require("./validation.js"); | ||
| const external_events_js_namespaceObject = require("./events.js"); | ||
| const external_translations_js_namespaceObject = require("./translations.js"); | ||
| exports.SURVEY_LANGUAGE_PROPERTY = __webpack_exports__.SURVEY_LANGUAGE_PROPERTY; | ||
| exports.applySurveyTranslation = __webpack_exports__.applySurveyTranslation; | ||
| exports.buildSurveyResponseProperties = __webpack_exports__.buildSurveyResponseProperties; | ||
| exports.detectSurveyLanguage = __webpack_exports__.detectSurveyLanguage; | ||
| exports.findBestTranslationMatch = __webpack_exports__.findBestTranslationMatch; | ||
| exports.getBaseLanguage = __webpack_exports__.getBaseLanguage; | ||
| exports.getLanguageFromStoredPersonProperties = __webpack_exports__.getLanguageFromStoredPersonProperties; | ||
| exports.getLengthFromRules = __webpack_exports__.getLengthFromRules; | ||
| exports.getRequirementsHint = __webpack_exports__.getRequirementsHint; | ||
| exports.getSurveyInteractionProperty = __webpack_exports__.getSurveyInteractionProperty; | ||
| exports.getSurveyOldResponseKey = __webpack_exports__.getSurveyOldResponseKey; | ||
| exports.getSurveyResponseKey = __webpack_exports__.getSurveyResponseKey; | ||
| exports.getSurveyResponseValue = __webpack_exports__.getSurveyResponseValue; | ||
| exports.getValidationError = __webpack_exports__.getValidationError; | ||
| exports.normalizeLanguageCode = __webpack_exports__.normalizeLanguageCode; | ||
| exports.surveyHasResponses = __webpack_exports__.surveyHasResponses; | ||
| for(var __webpack_i__ in __webpack_exports__)if (-1 === [ | ||
| "SURVEY_LANGUAGE_PROPERTY", | ||
| "applySurveyTranslation", | ||
| "buildSurveyResponseProperties", | ||
| "detectSurveyLanguage", | ||
| "findBestTranslationMatch", | ||
| "getBaseLanguage", | ||
| "getLanguageFromStoredPersonProperties", | ||
| "getLengthFromRules", | ||
| "getRequirementsHint", | ||
| "getSurveyInteractionProperty", | ||
| "getSurveyOldResponseKey", | ||
| "getSurveyResponseKey", | ||
| "getSurveyResponseValue", | ||
| "getValidationError", | ||
| "normalizeLanguageCode", | ||
| "surveyHasResponses" | ||
| ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__]; | ||
| Object.defineProperty(exports, '__esModule', { | ||
| value: true | ||
| }); |
| import { getLengthFromRules, getRequirementsHint, getValidationError } from "./validation.mjs"; | ||
| import { SURVEY_LANGUAGE_PROPERTY, buildSurveyResponseProperties, getSurveyInteractionProperty, getSurveyOldResponseKey, getSurveyResponseKey, getSurveyResponseValue, surveyHasResponses } from "./events.mjs"; | ||
| import { applySurveyTranslation, detectSurveyLanguage, findBestTranslationMatch, getBaseLanguage, getLanguageFromStoredPersonProperties, normalizeLanguageCode } from "./translations.mjs"; | ||
| export { SURVEY_LANGUAGE_PROPERTY, applySurveyTranslation, buildSurveyResponseProperties, detectSurveyLanguage, findBestTranslationMatch, getBaseLanguage, getLanguageFromStoredPersonProperties, getLengthFromRules, getRequirementsHint, getSurveyInteractionProperty, getSurveyOldResponseKey, getSurveyResponseKey, getSurveyResponseValue, getValidationError, normalizeLanguageCode, surveyHasResponses }; |
| import { SurveyQuestionTranslation, SurveyTranslation } from '../types'; | ||
| export type DetectSurveyLanguageOptions = { | ||
| overrideLanguage?: unknown; | ||
| storedPersonProperties?: unknown; | ||
| locale?: unknown; | ||
| }; | ||
| export declare function getLanguageFromStoredPersonProperties(storedPersonProperties: unknown): string | null; | ||
| export declare function detectSurveyLanguage({ overrideLanguage, storedPersonProperties, locale, }: DetectSurveyLanguageOptions): string | null; | ||
| export declare function normalizeLanguageCode(languageCode: string): string; | ||
| export declare function getBaseLanguage(languageCode: string): string; | ||
| export declare function findBestTranslationMatch(translations: Record<string, unknown> | undefined, targetLanguage: string): string | null; | ||
| type TranslatableSurveyAppearance = { | ||
| thankYouMessageHeader?: string; | ||
| thankYouMessageDescription?: string | null; | ||
| thankYouMessageCloseButtonText?: string; | ||
| }; | ||
| type TranslatableSurveyQuestion = { | ||
| question: string; | ||
| description?: string | null; | ||
| buttonText?: string; | ||
| link?: string | null; | ||
| lowerBoundLabel?: string; | ||
| upperBoundLabel?: string; | ||
| choices?: string[]; | ||
| translations?: Record<string, SurveyQuestionTranslation>; | ||
| }; | ||
| type TranslatableSurvey<TQuestion extends TranslatableSurveyQuestion = TranslatableSurveyQuestion> = { | ||
| name: string; | ||
| translations?: Record<string, SurveyTranslation>; | ||
| appearance?: TranslatableSurveyAppearance | null; | ||
| questions: TQuestion[]; | ||
| }; | ||
| export declare function applySurveyTranslation<TQuestion extends TranslatableSurveyQuestion, TSurvey extends TranslatableSurvey<TQuestion>>(survey: TSurvey, targetLanguage: string): { | ||
| survey: TSurvey; | ||
| matchedKey: string | null; | ||
| }; | ||
| export {}; | ||
| //# sourceMappingURL=translations.d.ts.map |
| {"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../src/surveys/translations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAKvE,MAAM,MAAM,2BAA2B,GAAG;IACxC,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAMD,wBAAgB,qCAAqC,CAAC,sBAAsB,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAUpG;AAED,wBAAgB,oBAAoB,CAAC,EACnC,gBAAgB,EAChB,sBAAsB,EACtB,MAAM,GACP,EAAE,2BAA2B,GAAG,MAAM,GAAG,IAAI,CAqB7C;AAED,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACjD,cAAc,EAAE,MAAM,GACrB,MAAM,GAAG,IAAI,CAuBf;AAgBD,KAAK,4BAA4B,GAAG;IAClC,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,0BAA0B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1C,8BAA8B,CAAC,EAAE,MAAM,CAAA;CACxC,CAAA;AAED,KAAK,0BAA0B,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAA;CACzD,CAAA;AAED,KAAK,kBAAkB,CAAC,SAAS,SAAS,0BAA0B,GAAG,0BAA0B,IAAI;IACnG,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;IAChD,UAAU,CAAC,EAAE,4BAA4B,GAAG,IAAI,CAAA;IAChD,SAAS,EAAE,SAAS,EAAE,CAAA;CACvB,CAAA;AA0DD,wBAAgB,sBAAsB,CACpC,SAAS,SAAS,0BAA0B,EAC5C,OAAO,SAAS,kBAAkB,CAAC,SAAS,CAAC,EAC7C,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAwDzF"} |
| "use strict"; | ||
| var __webpack_require__ = {}; | ||
| (()=>{ | ||
| __webpack_require__.d = (exports1, definition)=>{ | ||
| for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, { | ||
| enumerable: true, | ||
| get: definition[key] | ||
| }); | ||
| }; | ||
| })(); | ||
| (()=>{ | ||
| __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop); | ||
| })(); | ||
| (()=>{ | ||
| __webpack_require__.r = (exports1)=>{ | ||
| if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, { | ||
| value: 'Module' | ||
| }); | ||
| Object.defineProperty(exports1, '__esModule', { | ||
| value: true | ||
| }); | ||
| }; | ||
| })(); | ||
| var __webpack_exports__ = {}; | ||
| __webpack_require__.r(__webpack_exports__); | ||
| __webpack_require__.d(__webpack_exports__, { | ||
| normalizeLanguageCode: ()=>normalizeLanguageCode, | ||
| getBaseLanguage: ()=>getBaseLanguage, | ||
| detectSurveyLanguage: ()=>detectSurveyLanguage, | ||
| applySurveyTranslation: ()=>applySurveyTranslation, | ||
| findBestTranslationMatch: ()=>findBestTranslationMatch, | ||
| getLanguageFromStoredPersonProperties: ()=>getLanguageFromStoredPersonProperties | ||
| }); | ||
| const index_js_namespaceObject = require("../utils/index.js"); | ||
| const logger = (0, index_js_namespaceObject.createLogger)('[SurveyTranslations]'); | ||
| function getTrimmedLanguage(value) { | ||
| return 'string' == typeof value && value.trim() ? value.trim() : null; | ||
| } | ||
| function getLanguageFromStoredPersonProperties(storedPersonProperties) { | ||
| if (!storedPersonProperties || 'object' != typeof storedPersonProperties || !('language' in storedPersonProperties)) return null; | ||
| return getTrimmedLanguage(storedPersonProperties.language); | ||
| } | ||
| function detectSurveyLanguage({ overrideLanguage, storedPersonProperties, locale }) { | ||
| const explicitLanguage = getTrimmedLanguage(overrideLanguage); | ||
| if (explicitLanguage) { | ||
| logger.info(`Using override display language: ${explicitLanguage}`); | ||
| return explicitLanguage; | ||
| } | ||
| const personLanguage = getLanguageFromStoredPersonProperties(storedPersonProperties); | ||
| if (personLanguage) { | ||
| logger.info(`Using person property language: ${personLanguage}`); | ||
| return personLanguage; | ||
| } | ||
| const detectedLocale = getTrimmedLanguage(locale); | ||
| if (detectedLocale) { | ||
| logger.info(`Using detected locale: ${detectedLocale}`); | ||
| return detectedLocale; | ||
| } | ||
| logger.info('No user language detected'); | ||
| return null; | ||
| } | ||
| function normalizeLanguageCode(languageCode) { | ||
| return languageCode.toLowerCase(); | ||
| } | ||
| function getBaseLanguage(languageCode) { | ||
| return languageCode.split('-')[0]; | ||
| } | ||
| function findBestTranslationMatch(translations, targetLanguage) { | ||
| if (!translations || !targetLanguage) return null; | ||
| const normalizedTarget = normalizeLanguageCode(targetLanguage); | ||
| const exactMatch = Object.keys(translations).find((key)=>normalizeLanguageCode(key) === normalizedTarget); | ||
| if (exactMatch) { | ||
| logger.debug(`Found exact translation match: ${exactMatch}`); | ||
| return exactMatch; | ||
| } | ||
| if (normalizedTarget.includes('-')) { | ||
| const baseLanguage = getBaseLanguage(normalizedTarget); | ||
| const baseMatch = Object.keys(translations).find((key)=>normalizeLanguageCode(key) === baseLanguage); | ||
| if (baseMatch) { | ||
| logger.debug(`Found base language translation match: ${baseMatch} (from ${targetLanguage})`); | ||
| return baseMatch; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| function isTranslatedChoices(questionTranslation) { | ||
| return (0, index_js_namespaceObject.isArray)(questionTranslation.choices); | ||
| } | ||
| function hasThankYouTranslation(translation) { | ||
| return !(0, index_js_namespaceObject.isUndefined)(translation.thankYouMessageHeader) || !(0, index_js_namespaceObject.isUndefined)(translation.thankYouMessageDescription) || !(0, index_js_namespaceObject.isUndefined)(translation.thankYouMessageCloseButtonText); | ||
| } | ||
| function mergeQuestionTranslation(question, targetLanguage) { | ||
| const translationKey = findBestTranslationMatch(question.translations, targetLanguage); | ||
| if (!translationKey) return { | ||
| question, | ||
| matchedKey: null, | ||
| hasChanges: false | ||
| }; | ||
| const questionTranslation = question.translations?.[translationKey]; | ||
| if (!questionTranslation) return { | ||
| question, | ||
| matchedKey: null, | ||
| hasChanges: false | ||
| }; | ||
| const translated = { | ||
| ...question | ||
| }; | ||
| let hasChanges = false; | ||
| if (!(0, index_js_namespaceObject.isUndefined)(questionTranslation.question)) { | ||
| translated.question = questionTranslation.question; | ||
| hasChanges = true; | ||
| } | ||
| if (!(0, index_js_namespaceObject.isUndefined)(questionTranslation.description)) { | ||
| translated.description = questionTranslation.description; | ||
| hasChanges = true; | ||
| } | ||
| if (!(0, index_js_namespaceObject.isUndefined)(questionTranslation.buttonText)) { | ||
| translated.buttonText = questionTranslation.buttonText; | ||
| hasChanges = true; | ||
| } | ||
| if ('link' in translated && !(0, index_js_namespaceObject.isUndefined)(questionTranslation.link)) { | ||
| translated.link = questionTranslation.link; | ||
| hasChanges = true; | ||
| } | ||
| if ('lowerBoundLabel' in translated && !(0, index_js_namespaceObject.isUndefined)(questionTranslation.lowerBoundLabel)) { | ||
| translated.lowerBoundLabel = questionTranslation.lowerBoundLabel; | ||
| hasChanges = true; | ||
| } | ||
| if ('upperBoundLabel' in translated && !(0, index_js_namespaceObject.isUndefined)(questionTranslation.upperBoundLabel)) { | ||
| translated.upperBoundLabel = questionTranslation.upperBoundLabel; | ||
| hasChanges = true; | ||
| } | ||
| if ('choices' in translated && isTranslatedChoices(questionTranslation)) { | ||
| translated.choices = questionTranslation.choices; | ||
| hasChanges = true; | ||
| } | ||
| return { | ||
| question: hasChanges ? translated : question, | ||
| matchedKey: hasChanges ? translationKey : null, | ||
| hasChanges | ||
| }; | ||
| } | ||
| function applySurveyTranslation(survey, targetLanguage) { | ||
| const translationKey = findBestTranslationMatch(survey.translations, targetLanguage); | ||
| const translated = { | ||
| ...survey | ||
| }; | ||
| let hasTranslation = false; | ||
| if (translationKey) { | ||
| const translation = survey.translations?.[translationKey]; | ||
| if (translation) { | ||
| logger.info(`Applying survey-level translation for language: ${translationKey}`); | ||
| if (!(0, index_js_namespaceObject.isUndefined)(translation.name)) { | ||
| translated.name = translation.name; | ||
| hasTranslation = true; | ||
| } | ||
| if (translated.appearance) { | ||
| translated.appearance = { | ||
| ...translated.appearance | ||
| }; | ||
| if (!(0, index_js_namespaceObject.isUndefined)(translation.thankYouMessageHeader)) { | ||
| translated.appearance.thankYouMessageHeader = translation.thankYouMessageHeader; | ||
| hasTranslation = true; | ||
| } | ||
| if (!(0, index_js_namespaceObject.isUndefined)(translation.thankYouMessageDescription)) { | ||
| translated.appearance.thankYouMessageDescription = translation.thankYouMessageDescription; | ||
| hasTranslation = true; | ||
| } | ||
| if (!(0, index_js_namespaceObject.isUndefined)(translation.thankYouMessageCloseButtonText)) { | ||
| translated.appearance.thankYouMessageCloseButtonText = translation.thankYouMessageCloseButtonText; | ||
| hasTranslation = true; | ||
| } | ||
| } else if (hasThankYouTranslation(translation)) hasTranslation = true; | ||
| } | ||
| } | ||
| const translatedResults = survey.questions.map((question)=>mergeQuestionTranslation(question, targetLanguage)); | ||
| const translatedQuestions = translatedResults.map((result)=>result.question); | ||
| const anyQuestionTranslated = translatedResults.some((result)=>result.hasChanges); | ||
| let questionMatchedKey = null; | ||
| if (!translationKey) questionMatchedKey = translatedResults.find((result)=>result.matchedKey)?.matchedKey || null; | ||
| if (anyQuestionTranslated) { | ||
| translated.questions = translatedQuestions; | ||
| hasTranslation = true; | ||
| logger.info(`Applied question-level translations for language: ${targetLanguage}`); | ||
| } | ||
| return { | ||
| survey: translated, | ||
| matchedKey: hasTranslation ? translationKey || questionMatchedKey : null | ||
| }; | ||
| } | ||
| exports.applySurveyTranslation = __webpack_exports__.applySurveyTranslation; | ||
| exports.detectSurveyLanguage = __webpack_exports__.detectSurveyLanguage; | ||
| exports.findBestTranslationMatch = __webpack_exports__.findBestTranslationMatch; | ||
| exports.getBaseLanguage = __webpack_exports__.getBaseLanguage; | ||
| exports.getLanguageFromStoredPersonProperties = __webpack_exports__.getLanguageFromStoredPersonProperties; | ||
| exports.normalizeLanguageCode = __webpack_exports__.normalizeLanguageCode; | ||
| for(var __webpack_i__ in __webpack_exports__)if (-1 === [ | ||
| "applySurveyTranslation", | ||
| "detectSurveyLanguage", | ||
| "findBestTranslationMatch", | ||
| "getBaseLanguage", | ||
| "getLanguageFromStoredPersonProperties", | ||
| "normalizeLanguageCode" | ||
| ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__]; | ||
| Object.defineProperty(exports, '__esModule', { | ||
| value: true | ||
| }); |
| import { createLogger, isArray, isUndefined } from "../utils/index.mjs"; | ||
| const logger = createLogger('[SurveyTranslations]'); | ||
| function getTrimmedLanguage(value) { | ||
| return 'string' == typeof value && value.trim() ? value.trim() : null; | ||
| } | ||
| function getLanguageFromStoredPersonProperties(storedPersonProperties) { | ||
| if (!storedPersonProperties || 'object' != typeof storedPersonProperties || !('language' in storedPersonProperties)) return null; | ||
| return getTrimmedLanguage(storedPersonProperties.language); | ||
| } | ||
| function detectSurveyLanguage({ overrideLanguage, storedPersonProperties, locale }) { | ||
| const explicitLanguage = getTrimmedLanguage(overrideLanguage); | ||
| if (explicitLanguage) { | ||
| logger.info(`Using override display language: ${explicitLanguage}`); | ||
| return explicitLanguage; | ||
| } | ||
| const personLanguage = getLanguageFromStoredPersonProperties(storedPersonProperties); | ||
| if (personLanguage) { | ||
| logger.info(`Using person property language: ${personLanguage}`); | ||
| return personLanguage; | ||
| } | ||
| const detectedLocale = getTrimmedLanguage(locale); | ||
| if (detectedLocale) { | ||
| logger.info(`Using detected locale: ${detectedLocale}`); | ||
| return detectedLocale; | ||
| } | ||
| logger.info('No user language detected'); | ||
| return null; | ||
| } | ||
| function normalizeLanguageCode(languageCode) { | ||
| return languageCode.toLowerCase(); | ||
| } | ||
| function getBaseLanguage(languageCode) { | ||
| return languageCode.split('-')[0]; | ||
| } | ||
| function findBestTranslationMatch(translations, targetLanguage) { | ||
| if (!translations || !targetLanguage) return null; | ||
| const normalizedTarget = normalizeLanguageCode(targetLanguage); | ||
| const exactMatch = Object.keys(translations).find((key)=>normalizeLanguageCode(key) === normalizedTarget); | ||
| if (exactMatch) { | ||
| logger.debug(`Found exact translation match: ${exactMatch}`); | ||
| return exactMatch; | ||
| } | ||
| if (normalizedTarget.includes('-')) { | ||
| const baseLanguage = getBaseLanguage(normalizedTarget); | ||
| const baseMatch = Object.keys(translations).find((key)=>normalizeLanguageCode(key) === baseLanguage); | ||
| if (baseMatch) { | ||
| logger.debug(`Found base language translation match: ${baseMatch} (from ${targetLanguage})`); | ||
| return baseMatch; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| function isTranslatedChoices(questionTranslation) { | ||
| return isArray(questionTranslation.choices); | ||
| } | ||
| function hasThankYouTranslation(translation) { | ||
| return !isUndefined(translation.thankYouMessageHeader) || !isUndefined(translation.thankYouMessageDescription) || !isUndefined(translation.thankYouMessageCloseButtonText); | ||
| } | ||
| function mergeQuestionTranslation(question, targetLanguage) { | ||
| const translationKey = findBestTranslationMatch(question.translations, targetLanguage); | ||
| if (!translationKey) return { | ||
| question, | ||
| matchedKey: null, | ||
| hasChanges: false | ||
| }; | ||
| const questionTranslation = question.translations?.[translationKey]; | ||
| if (!questionTranslation) return { | ||
| question, | ||
| matchedKey: null, | ||
| hasChanges: false | ||
| }; | ||
| const translated = { | ||
| ...question | ||
| }; | ||
| let hasChanges = false; | ||
| if (!isUndefined(questionTranslation.question)) { | ||
| translated.question = questionTranslation.question; | ||
| hasChanges = true; | ||
| } | ||
| if (!isUndefined(questionTranslation.description)) { | ||
| translated.description = questionTranslation.description; | ||
| hasChanges = true; | ||
| } | ||
| if (!isUndefined(questionTranslation.buttonText)) { | ||
| translated.buttonText = questionTranslation.buttonText; | ||
| hasChanges = true; | ||
| } | ||
| if ('link' in translated && !isUndefined(questionTranslation.link)) { | ||
| translated.link = questionTranslation.link; | ||
| hasChanges = true; | ||
| } | ||
| if ('lowerBoundLabel' in translated && !isUndefined(questionTranslation.lowerBoundLabel)) { | ||
| translated.lowerBoundLabel = questionTranslation.lowerBoundLabel; | ||
| hasChanges = true; | ||
| } | ||
| if ('upperBoundLabel' in translated && !isUndefined(questionTranslation.upperBoundLabel)) { | ||
| translated.upperBoundLabel = questionTranslation.upperBoundLabel; | ||
| hasChanges = true; | ||
| } | ||
| if ('choices' in translated && isTranslatedChoices(questionTranslation)) { | ||
| translated.choices = questionTranslation.choices; | ||
| hasChanges = true; | ||
| } | ||
| return { | ||
| question: hasChanges ? translated : question, | ||
| matchedKey: hasChanges ? translationKey : null, | ||
| hasChanges | ||
| }; | ||
| } | ||
| function applySurveyTranslation(survey, targetLanguage) { | ||
| const translationKey = findBestTranslationMatch(survey.translations, targetLanguage); | ||
| const translated = { | ||
| ...survey | ||
| }; | ||
| let hasTranslation = false; | ||
| if (translationKey) { | ||
| const translation = survey.translations?.[translationKey]; | ||
| if (translation) { | ||
| logger.info(`Applying survey-level translation for language: ${translationKey}`); | ||
| if (!isUndefined(translation.name)) { | ||
| translated.name = translation.name; | ||
| hasTranslation = true; | ||
| } | ||
| if (translated.appearance) { | ||
| translated.appearance = { | ||
| ...translated.appearance | ||
| }; | ||
| if (!isUndefined(translation.thankYouMessageHeader)) { | ||
| translated.appearance.thankYouMessageHeader = translation.thankYouMessageHeader; | ||
| hasTranslation = true; | ||
| } | ||
| if (!isUndefined(translation.thankYouMessageDescription)) { | ||
| translated.appearance.thankYouMessageDescription = translation.thankYouMessageDescription; | ||
| hasTranslation = true; | ||
| } | ||
| if (!isUndefined(translation.thankYouMessageCloseButtonText)) { | ||
| translated.appearance.thankYouMessageCloseButtonText = translation.thankYouMessageCloseButtonText; | ||
| hasTranslation = true; | ||
| } | ||
| } else if (hasThankYouTranslation(translation)) hasTranslation = true; | ||
| } | ||
| } | ||
| const translatedResults = survey.questions.map((question)=>mergeQuestionTranslation(question, targetLanguage)); | ||
| const translatedQuestions = translatedResults.map((result)=>result.question); | ||
| const anyQuestionTranslated = translatedResults.some((result)=>result.hasChanges); | ||
| let questionMatchedKey = null; | ||
| if (!translationKey) questionMatchedKey = translatedResults.find((result)=>result.matchedKey)?.matchedKey || null; | ||
| if (anyQuestionTranslated) { | ||
| translated.questions = translatedQuestions; | ||
| hasTranslation = true; | ||
| logger.info(`Applied question-level translations for language: ${targetLanguage}`); | ||
| } | ||
| return { | ||
| survey: translated, | ||
| matchedKey: hasTranslation ? translationKey || questionMatchedKey : null | ||
| }; | ||
| } | ||
| export { applySurveyTranslation, detectSurveyLanguage, findBestTranslationMatch, getBaseLanguage, getLanguageFromStoredPersonProperties, normalizeLanguageCode }; |
| import { describe, expect, it } from '@jest/globals' | ||
| import { | ||
| buildSurveyResponseProperties, | ||
| getSurveyInteractionProperty, | ||
| getSurveyResponseKey, | ||
| getSurveyResponseValue, | ||
| surveyHasResponses, | ||
| } from './events' | ||
| describe('survey event helpers', () => { | ||
| const survey = { | ||
| id: 'survey-1', | ||
| current_iteration: 2, | ||
| questions: [ | ||
| { id: 'q1', question: 'Rate us', originalQuestionIndex: 0 }, | ||
| { id: 'q2', question: 'Anything else?', originalQuestionIndex: 1 }, | ||
| ], | ||
| } | ||
| it('builds response properties with current and legacy response keys', () => { | ||
| const responses = { | ||
| [getSurveyResponseKey('q1')]: 5, | ||
| [getSurveyResponseKey('q2')]: ['fast', 'clear'], | ||
| } | ||
| expect(buildSurveyResponseProperties(responses, survey)).toEqual({ | ||
| $survey_questions: [ | ||
| { id: 'q1', question: 'Rate us', response: 5 }, | ||
| { id: 'q2', question: 'Anything else?', response: ['fast', 'clear'] }, | ||
| ], | ||
| $survey_response_q1: 5, | ||
| $survey_response_q2: ['fast', 'clear'], | ||
| $survey_response: 5, | ||
| $survey_response_1: ['fast', 'clear'], | ||
| }) | ||
| }) | ||
| it('copies array response values before returning them', () => { | ||
| const responses = { [getSurveyResponseKey('q1')]: ['a'] } | ||
| const response = getSurveyResponseValue(responses, 'q1') | ||
| expect(response).toEqual(['a']) | ||
| expect(response).not.toBe(responses.$survey_response_q1) | ||
| }) | ||
| it('detects non-nullish responses and builds interaction property names', () => { | ||
| expect(surveyHasResponses({ $survey_response_q1: null })).toBe(false) | ||
| expect(surveyHasResponses({ $survey_response_q1: 0 })).toBe(true) | ||
| expect(getSurveyInteractionProperty(survey, 'responded')).toBe('$survey_responded/survey-1/2') | ||
| }) | ||
| }) |
| import { SurveyResponses, SurveyResponseValue } from '../types' | ||
| import { isArray, isNullish, isUndefined } from '../utils' | ||
| export const SURVEY_LANGUAGE_PROPERTY = '$survey_language' | ||
| export function getSurveyResponseKey(questionId: string): string { | ||
| return `$survey_response_${questionId}` | ||
| } | ||
| export function getSurveyOldResponseKey(originalQuestionIndex: number): string { | ||
| return originalQuestionIndex === 0 ? '$survey_response' : `$survey_response_${originalQuestionIndex}` | ||
| } | ||
| export function getSurveyResponseValue( | ||
| responses: SurveyResponses, | ||
| questionId?: string | ||
| ): SurveyResponseValue | undefined { | ||
| if (!questionId) { | ||
| return null | ||
| } | ||
| const response = responses[getSurveyResponseKey(questionId)] | ||
| if (isArray(response)) { | ||
| return [...response] | ||
| } | ||
| return response | ||
| } | ||
| export function buildSurveyResponseProperties( | ||
| responses: SurveyResponses = {}, | ||
| survey: SurveyForResponses | ||
| ): Record<string, unknown> { | ||
| const oldFormatResponses: SurveyResponses = {} | ||
| survey.questions.forEach((question: SurveyQuestionForResponses) => { | ||
| if (isUndefined(question.originalQuestionIndex)) { | ||
| return | ||
| } | ||
| const oldResponseKey = getSurveyOldResponseKey(question.originalQuestionIndex) | ||
| const response = getSurveyResponseValue(responses, question.id) | ||
| if (!isUndefined(response)) { | ||
| oldFormatResponses[oldResponseKey] = response | ||
| } | ||
| }) | ||
| return { | ||
| $survey_questions: survey.questions.map((question: SurveyQuestionForResponses) => ({ | ||
| id: question.id, | ||
| question: question.question, | ||
| response: getSurveyResponseValue(responses, question.id), | ||
| })), | ||
| ...responses, | ||
| ...oldFormatResponses, | ||
| } | ||
| } | ||
| export function surveyHasResponses(responses: SurveyResponses = {}): boolean { | ||
| return Object.values(responses).some((response) => !isNullish(response)) | ||
| } | ||
| export function getSurveyInteractionProperty(survey: SurveyWithIteration, action: string): string { | ||
| let surveyProperty = `$survey_${action}/${survey.id}` | ||
| if (survey.current_iteration && survey.current_iteration > 0) { | ||
| surveyProperty = `$survey_${action}/${survey.id}/${survey.current_iteration}` | ||
| } | ||
| return surveyProperty | ||
| } | ||
| type SurveyQuestionForResponses = { | ||
| id?: string | ||
| question: string | ||
| originalQuestionIndex?: number | ||
| } | ||
| type SurveyForResponses = { | ||
| questions: SurveyQuestionForResponses[] | ||
| } | ||
| type SurveyWithIteration = { | ||
| id: string | ||
| current_iteration?: number | null | ||
| } |
| export { getValidationError, getLengthFromRules, getRequirementsHint } from './validation' | ||
| export { | ||
| buildSurveyResponseProperties, | ||
| getSurveyInteractionProperty, | ||
| getSurveyOldResponseKey, | ||
| getSurveyResponseKey, | ||
| getSurveyResponseValue, | ||
| SURVEY_LANGUAGE_PROPERTY, | ||
| surveyHasResponses, | ||
| } from './events' | ||
| export { | ||
| applySurveyTranslation, | ||
| detectSurveyLanguage, | ||
| findBestTranslationMatch, | ||
| getBaseLanguage, | ||
| getLanguageFromStoredPersonProperties, | ||
| normalizeLanguageCode, | ||
| } from './translations' |
| import { describe, expect, it } from '@jest/globals' | ||
| import { | ||
| applySurveyTranslation, | ||
| detectSurveyLanguage, | ||
| findBestTranslationMatch, | ||
| getLanguageFromStoredPersonProperties, | ||
| } from './translations' | ||
| import { Survey, SurveyQuestionType, SurveyType } from '../types' | ||
| const createBaseSurvey = (): Survey => ({ | ||
| id: 'test-survey', | ||
| name: 'Test Survey', | ||
| description: 'Test Description', | ||
| type: SurveyType.Popover, | ||
| questions: [ | ||
| { | ||
| type: SurveyQuestionType.Open, | ||
| question: 'What do you think?', | ||
| id: 'q1', | ||
| originalQuestionIndex: 0, | ||
| }, | ||
| ], | ||
| appearance: { | ||
| thankYouMessageHeader: 'Thank you!', | ||
| thankYouMessageDescription: 'We appreciate your feedback', | ||
| thankYouMessageCloseButtonText: 'Close', | ||
| }, | ||
| }) | ||
| describe('survey translations', () => { | ||
| describe('detectSurveyLanguage', () => { | ||
| it.each([ | ||
| { | ||
| name: 'prioritizes override language', | ||
| input: { | ||
| overrideLanguage: 'de', | ||
| storedPersonProperties: { language: 'es' }, | ||
| locale: 'fr', | ||
| }, | ||
| expected: 'de', | ||
| }, | ||
| { | ||
| name: 'uses person language when override is missing', | ||
| input: { | ||
| storedPersonProperties: { language: 'es' }, | ||
| locale: 'fr', | ||
| }, | ||
| expected: 'es', | ||
| }, | ||
| { | ||
| name: 'falls back to locale', | ||
| input: { | ||
| storedPersonProperties: { some_other_property: 'value' }, | ||
| locale: 'fr-CA', | ||
| }, | ||
| expected: 'fr-CA', | ||
| }, | ||
| { | ||
| name: 'returns null when no sources exist', | ||
| input: { | ||
| storedPersonProperties: { some_other_property: 'value' }, | ||
| }, | ||
| expected: null, | ||
| }, | ||
| { | ||
| name: 'trims override language', | ||
| input: { | ||
| overrideLanguage: ' es ', | ||
| }, | ||
| expected: 'es', | ||
| }, | ||
| ])('$name', ({ input, expected }) => { | ||
| expect(detectSurveyLanguage(input)).toBe(expected) | ||
| }) | ||
| it('reads language from stored person properties', () => { | ||
| expect(getLanguageFromStoredPersonProperties({ language: 'it' })).toBe('it') | ||
| expect(getLanguageFromStoredPersonProperties({ language: ' ' })).toBeNull() | ||
| expect(getLanguageFromStoredPersonProperties({})).toBeNull() | ||
| }) | ||
| }) | ||
| describe('findBestTranslationMatch', () => { | ||
| it('supports exact and base-language matches', () => { | ||
| expect(findBestTranslationMatch({ fr: {}, 'fr-CA': {} }, 'FR-ca')).toBe('fr-CA') | ||
| expect(findBestTranslationMatch({ fr: {} }, 'fr-CA')).toBe('fr') | ||
| expect(findBestTranslationMatch({ es: {} }, 'de')).toBeNull() | ||
| }) | ||
| }) | ||
| describe('applySurveyTranslation', () => { | ||
| it('returns original survey when no translations exist', () => { | ||
| const survey = createBaseSurvey() | ||
| const result = applySurveyTranslation(survey, 'fr') | ||
| expect(result.survey).toEqual(survey) | ||
| expect(result.matchedKey).toBeNull() | ||
| }) | ||
| it('applies survey-level translations', () => { | ||
| const survey = createBaseSurvey() | ||
| survey.translations = { | ||
| fr: { | ||
| name: 'Enquete Test', | ||
| thankYouMessageHeader: 'Merci', | ||
| thankYouMessageDescription: 'Merci pour votre retour', | ||
| thankYouMessageCloseButtonText: 'Fermer', | ||
| }, | ||
| } | ||
| const result = applySurveyTranslation(survey, 'fr') | ||
| expect(result.survey.name).toBe('Enquete Test') | ||
| expect(result.survey.appearance?.thankYouMessageHeader).toBe('Merci') | ||
| expect(result.survey.appearance?.thankYouMessageDescription).toBe('Merci pour votre retour') | ||
| expect(result.survey.appearance?.thankYouMessageCloseButtonText).toBe('Fermer') | ||
| expect(result.matchedKey).toBe('fr') | ||
| }) | ||
| it('applies question-level translations', () => { | ||
| const survey = createBaseSurvey() | ||
| survey.questions = [ | ||
| { | ||
| type: SurveyQuestionType.Rating, | ||
| question: 'How was it?', | ||
| id: 'q1', | ||
| originalQuestionIndex: 0, | ||
| display: 'number', | ||
| scale: 5, | ||
| lowerBoundLabel: 'Bad', | ||
| upperBoundLabel: 'Great', | ||
| translations: { | ||
| pt: { | ||
| question: 'Como foi?', | ||
| lowerBoundLabel: 'Ruim', | ||
| upperBoundLabel: 'Otimo', | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| type: SurveyQuestionType.MultipleChoice, | ||
| question: 'Pick one', | ||
| id: 'q2', | ||
| originalQuestionIndex: 1, | ||
| choices: ['One', 'Other'], | ||
| hasOpenChoice: true, | ||
| translations: { | ||
| pt: { | ||
| choices: ['Um', 'Outro'], | ||
| }, | ||
| }, | ||
| }, | ||
| ] | ||
| const result = applySurveyTranslation(survey, 'pt-BR') | ||
| expect(result.survey.questions[0].question).toBe('Como foi?') | ||
| expect('lowerBoundLabel' in result.survey.questions[0] && result.survey.questions[0].lowerBoundLabel).toBe('Ruim') | ||
| expect('upperBoundLabel' in result.survey.questions[0] && result.survey.questions[0].upperBoundLabel).toBe( | ||
| 'Otimo' | ||
| ) | ||
| expect('choices' in result.survey.questions[1] && result.survey.questions[1].choices).toEqual(['Um', 'Outro']) | ||
| expect(result.matchedKey).toBe('pt') | ||
| }) | ||
| it('uses question-level match when there is no survey-level translation', () => { | ||
| const survey = createBaseSurvey() | ||
| survey.questions[0].translations = { | ||
| de: { | ||
| question: 'Was denkst du?', | ||
| }, | ||
| } | ||
| const result = applySurveyTranslation(survey, 'de') | ||
| expect(result.survey.questions[0].question).toBe('Was denkst du?') | ||
| expect(result.matchedKey).toBe('de') | ||
| }) | ||
| it('preserves custom survey and question fields for shared consumers', () => { | ||
| const survey = { | ||
| name: 'Custom Survey', | ||
| customSurveyField: true, | ||
| questions: [ | ||
| { | ||
| question: 'Pick one', | ||
| customQuestionField: 'native-renderer', | ||
| translations: { | ||
| fr: { | ||
| question: 'Choisissez une option', | ||
| }, | ||
| }, | ||
| }, | ||
| ], | ||
| } | ||
| const result = applySurveyTranslation(survey, 'fr') | ||
| expect(result.survey.customSurveyField).toBe(true) | ||
| expect(result.survey.questions[0].question).toBe('Choisissez une option') | ||
| expect(result.survey.questions[0].customQuestionField).toBe('native-renderer') | ||
| expect(result.matchedKey).toBe('fr') | ||
| }) | ||
| }) | ||
| }) |
| import { SurveyQuestionTranslation, SurveyTranslation } from '../types' | ||
| import { createLogger, isArray, isUndefined } from '../utils' | ||
| const logger = createLogger('[SurveyTranslations]') | ||
| export type DetectSurveyLanguageOptions = { | ||
| overrideLanguage?: unknown | ||
| storedPersonProperties?: unknown | ||
| locale?: unknown | ||
| } | ||
| function getTrimmedLanguage(value: unknown): string | null { | ||
| return typeof value === 'string' && value.trim() ? value.trim() : null | ||
| } | ||
| export function getLanguageFromStoredPersonProperties(storedPersonProperties: unknown): string | null { | ||
| if ( | ||
| !storedPersonProperties || | ||
| typeof storedPersonProperties !== 'object' || | ||
| !('language' in storedPersonProperties) | ||
| ) { | ||
| return null | ||
| } | ||
| return getTrimmedLanguage(storedPersonProperties.language) | ||
| } | ||
| export function detectSurveyLanguage({ | ||
| overrideLanguage, | ||
| storedPersonProperties, | ||
| locale, | ||
| }: DetectSurveyLanguageOptions): string | null { | ||
| const explicitLanguage = getTrimmedLanguage(overrideLanguage) | ||
| if (explicitLanguage) { | ||
| logger.info(`Using override display language: ${explicitLanguage}`) | ||
| return explicitLanguage | ||
| } | ||
| const personLanguage = getLanguageFromStoredPersonProperties(storedPersonProperties) | ||
| if (personLanguage) { | ||
| logger.info(`Using person property language: ${personLanguage}`) | ||
| return personLanguage | ||
| } | ||
| const detectedLocale = getTrimmedLanguage(locale) | ||
| if (detectedLocale) { | ||
| logger.info(`Using detected locale: ${detectedLocale}`) | ||
| return detectedLocale | ||
| } | ||
| logger.info('No user language detected') | ||
| return null | ||
| } | ||
| export function normalizeLanguageCode(languageCode: string): string { | ||
| return languageCode.toLowerCase() | ||
| } | ||
| export function getBaseLanguage(languageCode: string): string { | ||
| return languageCode.split('-')[0] | ||
| } | ||
| export function findBestTranslationMatch( | ||
| translations: Record<string, unknown> | undefined, | ||
| targetLanguage: string | ||
| ): string | null { | ||
| if (!translations || !targetLanguage) { | ||
| return null | ||
| } | ||
| const normalizedTarget = normalizeLanguageCode(targetLanguage) | ||
| const exactMatch = Object.keys(translations).find((key) => normalizeLanguageCode(key) === normalizedTarget) | ||
| if (exactMatch) { | ||
| logger.debug(`Found exact translation match: ${exactMatch}`) | ||
| return exactMatch | ||
| } | ||
| if (normalizedTarget.includes('-')) { | ||
| const baseLanguage = getBaseLanguage(normalizedTarget) | ||
| const baseMatch = Object.keys(translations).find((key) => normalizeLanguageCode(key) === baseLanguage) | ||
| if (baseMatch) { | ||
| logger.debug(`Found base language translation match: ${baseMatch} (from ${targetLanguage})`) | ||
| return baseMatch | ||
| } | ||
| } | ||
| return null | ||
| } | ||
| function isTranslatedChoices( | ||
| questionTranslation: SurveyQuestionTranslation | ||
| ): questionTranslation is SurveyQuestionTranslation & { choices: string[] } { | ||
| return isArray(questionTranslation.choices) | ||
| } | ||
| function hasThankYouTranslation(translation: SurveyTranslation): boolean { | ||
| return ( | ||
| !isUndefined(translation.thankYouMessageHeader) || | ||
| !isUndefined(translation.thankYouMessageDescription) || | ||
| !isUndefined(translation.thankYouMessageCloseButtonText) | ||
| ) | ||
| } | ||
| type TranslatableSurveyAppearance = { | ||
| thankYouMessageHeader?: string | ||
| thankYouMessageDescription?: string | null | ||
| thankYouMessageCloseButtonText?: string | ||
| } | ||
| type TranslatableSurveyQuestion = { | ||
| question: string | ||
| description?: string | null | ||
| buttonText?: string | ||
| link?: string | null | ||
| lowerBoundLabel?: string | ||
| upperBoundLabel?: string | ||
| choices?: string[] | ||
| translations?: Record<string, SurveyQuestionTranslation> | ||
| } | ||
| type TranslatableSurvey<TQuestion extends TranslatableSurveyQuestion = TranslatableSurveyQuestion> = { | ||
| name: string | ||
| translations?: Record<string, SurveyTranslation> | ||
| appearance?: TranslatableSurveyAppearance | null | ||
| questions: TQuestion[] | ||
| } | ||
| function mergeQuestionTranslation<TQuestion extends TranslatableSurveyQuestion>( | ||
| question: TQuestion, | ||
| targetLanguage: string | ||
| ): { question: TQuestion; matchedKey: string | null; hasChanges: boolean } { | ||
| const translationKey = findBestTranslationMatch(question.translations, targetLanguage) | ||
| if (!translationKey) { | ||
| return { question, matchedKey: null, hasChanges: false } | ||
| } | ||
| const questionTranslation = question.translations?.[translationKey] | ||
| if (!questionTranslation) { | ||
| return { question, matchedKey: null, hasChanges: false } | ||
| } | ||
| const translated: TQuestion = { ...question } | ||
| let hasChanges = false | ||
| if (!isUndefined(questionTranslation.question)) { | ||
| translated.question = questionTranslation.question | ||
| hasChanges = true | ||
| } | ||
| if (!isUndefined(questionTranslation.description)) { | ||
| translated.description = questionTranslation.description | ||
| hasChanges = true | ||
| } | ||
| if (!isUndefined(questionTranslation.buttonText)) { | ||
| translated.buttonText = questionTranslation.buttonText | ||
| hasChanges = true | ||
| } | ||
| if ('link' in translated && !isUndefined(questionTranslation.link)) { | ||
| translated.link = questionTranslation.link | ||
| hasChanges = true | ||
| } | ||
| if ('lowerBoundLabel' in translated && !isUndefined(questionTranslation.lowerBoundLabel)) { | ||
| translated.lowerBoundLabel = questionTranslation.lowerBoundLabel | ||
| hasChanges = true | ||
| } | ||
| if ('upperBoundLabel' in translated && !isUndefined(questionTranslation.upperBoundLabel)) { | ||
| translated.upperBoundLabel = questionTranslation.upperBoundLabel | ||
| hasChanges = true | ||
| } | ||
| if ('choices' in translated && isTranslatedChoices(questionTranslation)) { | ||
| translated.choices = questionTranslation.choices | ||
| hasChanges = true | ||
| } | ||
| return { | ||
| question: hasChanges ? translated : question, | ||
| matchedKey: hasChanges ? translationKey : null, | ||
| hasChanges, | ||
| } | ||
| } | ||
| export function applySurveyTranslation< | ||
| TQuestion extends TranslatableSurveyQuestion, | ||
| TSurvey extends TranslatableSurvey<TQuestion>, | ||
| >(survey: TSurvey, targetLanguage: string): { survey: TSurvey; matchedKey: string | null } { | ||
| const translationKey = findBestTranslationMatch(survey.translations, targetLanguage) | ||
| const translated: TSurvey = { ...survey } | ||
| let hasTranslation = false | ||
| if (translationKey) { | ||
| const translation = survey.translations?.[translationKey] | ||
| if (translation) { | ||
| logger.info(`Applying survey-level translation for language: ${translationKey}`) | ||
| if (!isUndefined(translation.name)) { | ||
| translated.name = translation.name | ||
| hasTranslation = true | ||
| } | ||
| if (translated.appearance) { | ||
| translated.appearance = { ...translated.appearance } | ||
| if (!isUndefined(translation.thankYouMessageHeader)) { | ||
| translated.appearance.thankYouMessageHeader = translation.thankYouMessageHeader | ||
| hasTranslation = true | ||
| } | ||
| if (!isUndefined(translation.thankYouMessageDescription)) { | ||
| translated.appearance.thankYouMessageDescription = translation.thankYouMessageDescription | ||
| hasTranslation = true | ||
| } | ||
| if (!isUndefined(translation.thankYouMessageCloseButtonText)) { | ||
| translated.appearance.thankYouMessageCloseButtonText = translation.thankYouMessageCloseButtonText | ||
| hasTranslation = true | ||
| } | ||
| } else if (hasThankYouTranslation(translation)) { | ||
| hasTranslation = true | ||
| } | ||
| } | ||
| } | ||
| const translatedResults = survey.questions.map((question) => mergeQuestionTranslation(question, targetLanguage)) | ||
| const translatedQuestions = translatedResults.map((result) => result.question) | ||
| const anyQuestionTranslated = translatedResults.some((result) => result.hasChanges) | ||
| let questionMatchedKey: string | null = null | ||
| if (!translationKey) { | ||
| questionMatchedKey = translatedResults.find((result) => result.matchedKey)?.matchedKey || null | ||
| } | ||
| if (anyQuestionTranslated) { | ||
| translated.questions = translatedQuestions | ||
| hasTranslation = true | ||
| logger.info(`Applied question-level translations for language: ${targetLanguage}`) | ||
| } | ||
| return { | ||
| survey: translated, | ||
| matchedKey: hasTranslation ? translationKey || questionMatchedKey : null, | ||
| } | ||
| } |
+2
-1
@@ -7,3 +7,4 @@ export { getFeatureFlagValue } from './featureFlagUtils'; | ||
| export { PostHogLogs } from './logs'; | ||
| export type { BufferedLogEntry, PostHogLogsConfig, ResolvedPostHogLogsConfig } from './logs/types'; | ||
| export type { BeforeSendLogFn, BufferedLogEntry, CaptureLogger, LogSdkContext, PostHogLogsConfig, ResolvedPostHogLogsConfig, } from './logs/types'; | ||
| export type { CaptureLogOptions, LogAttributeValue, LogAttributes, LogSeverityLevel } from './logs/types'; | ||
| export { uuidv7 } from './vendor/uuidv7'; | ||
@@ -10,0 +11,0 @@ export * from './posthog-core'; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,QAAQ,CAAA;AACjE,cAAc,SAAS,CAAA;AACvB,OAAO,KAAK,aAAa,MAAM,kBAAkB,CAAA;AACjD,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,GACnB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AACpC,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AAClG,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,0BAA0B,CAAA;AACxC,cAAc,mBAAmB,CAAA;AACjC,cAAc,SAAS,CAAA;AACvB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,QAAQ,CAAA;AACjE,cAAc,SAAS,CAAA;AACvB,OAAO,KAAK,aAAa,MAAM,kBAAkB,CAAA;AACjD,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,GACnB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AACpC,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,cAAc,CAAA;AAIrB,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACzG,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,0BAA0B,CAAA;AACxC,cAAc,mBAAmB,CAAA;AACjC,cAAc,SAAS,CAAA;AACvB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA"} |
+94
-4
@@ -1,5 +0,4 @@ | ||
| import type { LogSdkContext } from '@posthog/types'; | ||
| import { Logger } from '../types'; | ||
| import type { PostHogCoreStateless } from '../posthog-core-stateless'; | ||
| import type { CaptureLogOptions, ResolvedPostHogLogsConfig } from './types'; | ||
| import type { CaptureLogOptions, LogSdkContext, ResolvedPostHogLogsConfig } from './types'; | ||
| export declare class PostHogLogs { | ||
@@ -11,8 +10,99 @@ private readonly _instance; | ||
| private readonly _onReady; | ||
| private _localEnabled; | ||
| private readonly _waitForStoragePersist; | ||
| private _maxBufferSize; | ||
| constructor(_instance: PostHogCoreStateless, _config: ResolvedPostHogLogsConfig, _logger: Logger, _getContext: () => LogSdkContext, _onReady: (fn: () => void) => void); | ||
| private _flushIntervalMs; | ||
| private _maxBatchRecordsPerPost; | ||
| private _flushTimer?; | ||
| private _flushPromise; | ||
| private _rateCapWindowMs; | ||
| private _maxLogsPerInterval?; | ||
| private _intervalWindowStart; | ||
| private _intervalLogCount; | ||
| private _droppedWarned; | ||
| constructor(_instance: PostHogCoreStateless, _config: ResolvedPostHogLogsConfig, _logger: Logger, _getContext: () => LogSdkContext, _onReady: (fn: () => void) => void, _waitForStoragePersist?: () => Promise<void>); | ||
| captureLog(options: CaptureLogOptions): void; | ||
| /** | ||
| * Runs the configured `beforeSend` hook(s) on a capture record: | ||
| * - single fn OR array of fns (chain, left-to-right) | ||
| * - returning `null` drops the record (logged at info) | ||
| * - a thrown error is logged and the chain *continues* with the previous | ||
| * result — a buggy user filter must never crash the caller's | ||
| * `captureLog()` call | ||
| */ | ||
| private _runBeforeSend; | ||
| /** | ||
| * Returns `true` if this capture fits within the current rate-cap window, | ||
| * `false` if it should be dropped. | ||
| * | ||
| * Fixed (tumbling) window: the counter resets the first time `captureLog` | ||
| * fires after `rateCapWindowMs` has elapsed — no timer needed. | ||
| * `maxLogsPerInterval === undefined` means unbounded. | ||
| * | ||
| * Wall-clock safety: if `Date.now()` jumps backward (manual device-clock | ||
| * change, big NTP correction), `elapsed` goes negative. We treat that the | ||
| * same as "window expired" and reset — otherwise the rate cap would be | ||
| * stuck until the clock caught up to the old window start, potentially | ||
| * dropping logs for hours. | ||
| * | ||
| * Pre-init note: the counter increments here, before `_onReady` defers | ||
| * `_enqueue` to the init promise. If init resolves slowly and the user is | ||
| * later opted out, the counter has already consumed budget for records | ||
| * that won't enqueue. Cosmetic — no record is "lost" beyond what's | ||
| * already gated, and the window rolls on its own. | ||
| */ | ||
| private _checkRateLimit; | ||
| /** | ||
| * Drains `LogsQueue` in `maxBatchRecordsPerPost` slices, POSTing each as an | ||
| * OTLP payload. | ||
| * - Network error → keep items in queue, re-throw (caller retries later) | ||
| * - 413 → halve batch size, retry same records (do not advance) | ||
| * - Any other error → drop the batch (avoid infinite loop on malformed data), | ||
| * re-throw so callers can log/report | ||
| * Concurrent calls are serialized through `_flushPromise` so records at the | ||
| * head of the queue can't be sent twice. | ||
| */ | ||
| flush(): Promise<void>; | ||
| private _flushInner; | ||
| private _persistQueueAdvance; | ||
| /** | ||
| * OTLP resource attributes for every batch. | ||
| * | ||
| * Layout: user `resourceAttributes` spread first, then SDK-controlled | ||
| * keys layered on top so users cannot accidentally clobber them. Most logs | ||
| * backends index on `service.name` and `telemetry.sdk.*` for routing, | ||
| * SDK-version dashboards, and bug-correlation; letting a stray user key | ||
| * overwrite them silently breaks ingestion attribution. The dedicated | ||
| * `serviceName` / `environment` / `serviceVersion` config fields are the | ||
| * supported way to override `service.name` / `deployment.environment` / | ||
| * `service.version`. | ||
| */ | ||
| private _buildResourceAttributes; | ||
| private _enqueue; | ||
| /** | ||
| * Stops the timer-based flush and sends anything still in the queue. | ||
| * Intended for process-teardown paths (RN `_shutdown` override). Swallows | ||
| * errors so a failing final flush can't block the broader shutdown. | ||
| * | ||
| * If `timeoutMs` is provided, the final flush races against that budget so | ||
| * a slow network/storage can't hold up shutdown indefinitely. Without it, | ||
| * flush time is bounded only by `fetchRetryCount * (requestTimeout + | ||
| * fetchRetryDelay)`, which can exceed the caller's shutdown SLA. | ||
| */ | ||
| shutdown(timeoutMs?: number): Promise<void>; | ||
| /** | ||
| * Time-bounded flush for transient lifecycle events (e.g. RN | ||
| * foreground→background) that must complete inside an OS-imposed window. | ||
| * Unlike `shutdown`, this leaves the periodic flush timer in place so the | ||
| * pipeline keeps draining if the process is resumed instead of suspended. | ||
| * | ||
| * Errors propagate so the host SDK can route them through its standard | ||
| * lifecycle error handler (e.g. RN's `logFlushError`). If the timer wins | ||
| * the race, a late rejection from the in-flight flush is silenced via a | ||
| * no-op handler attached after the race settles, to avoid noisy | ||
| * unhandled-rejection logs — the next regular flush cycle will retry. | ||
| */ | ||
| flushWithTimeout(timeoutMs: number): Promise<void>; | ||
| private _flushInBackground; | ||
| private _clearFlushTimer; | ||
| } | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/logs/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,EAAE,MAAM,EAA4B,MAAM,UAAU,CAAA;AAC3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,KAAK,EAAoB,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAE7F,qBAAa,WAAW;IAKpB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR3B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAAQ;gBAGX,SAAS,EAAE,oBAAoB,EAC/B,OAAO,EAAE,yBAAyB,EAClC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,aAAa,EAChC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI;IAMrD,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAoB5C,OAAO,CAAC,QAAQ;CAgBjB"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/logs/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAA4B,MAAM,UAAU,CAAA;AAC3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAErE,OAAO,KAAK,EAGV,iBAAiB,EACjB,aAAa,EACb,yBAAyB,EAC1B,MAAM,SAAS,CAAA;AAEhB,qBAAa,WAAW;IAuBpB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAMzB,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IAhCzC,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,gBAAgB,CAAQ;IAIhC,OAAO,CAAC,uBAAuB,CAAQ;IACvC,OAAO,CAAC,WAAW,CAAC,CAAmC;IAGvD,OAAO,CAAC,aAAa,CAA6B;IAMlD,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,mBAAmB,CAAC,CAAQ;IACpC,OAAO,CAAC,oBAAoB,CAAI;IAChC,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,cAAc,CAAQ;gBAGX,SAAS,EAAE,oBAAoB,EAC/B,OAAO,EAAE,yBAAyB,EAClC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,aAAa,EAChC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,EAMlC,sBAAsB,GAAE,MAAM,OAAO,CAAC,IAAI,CAA2B;IASxF,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAgC5C;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAwBtB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,eAAe;IAwBvB;;;;;;;;;OASG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAUd,WAAW;YAiEX,oBAAoB;IAUlC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,wBAAwB;IAWhC,OAAO,CAAC,QAAQ;IAgChB;;;;;;;;;OASG;IACG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcjD;;;;;;;;;;;OAWG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBxD,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,gBAAgB;CAMzB"} |
+147
-4
@@ -31,4 +31,5 @@ "use strict"; | ||
| const external_types_js_namespaceObject = require("../types.js"); | ||
| const index_js_namespaceObject = require("../utils/index.js"); | ||
| class PostHogLogs { | ||
| constructor(_instance, _config, _logger, _getContext, _onReady){ | ||
| constructor(_instance, _config, _logger, _getContext, _onReady, _waitForStoragePersist = ()=>Promise.resolve()){ | ||
| this._instance = _instance; | ||
@@ -39,10 +40,21 @@ this._config = _config; | ||
| this._onReady = _onReady; | ||
| this._localEnabled = false !== _config.enabled; | ||
| this._waitForStoragePersist = _waitForStoragePersist; | ||
| this._flushPromise = null; | ||
| this._intervalWindowStart = 0; | ||
| this._intervalLogCount = 0; | ||
| this._droppedWarned = false; | ||
| this._maxBufferSize = _config.maxBufferSize; | ||
| this._flushIntervalMs = _config.flushIntervalMs; | ||
| this._maxBatchRecordsPerPost = _config.maxBatchRecordsPerPost; | ||
| this._rateCapWindowMs = _config.rateCapWindowMs; | ||
| this._maxLogsPerInterval = _config.maxLogsPerInterval; | ||
| } | ||
| captureLog(options) { | ||
| if (!this._localEnabled) return; | ||
| if (this._instance.optedOut) return; | ||
| if (!options?.body) return; | ||
| const record = (0, external_logs_utils_js_namespaceObject.buildOtlpLogRecord)(options, this._getContext()); | ||
| const filtered = this._runBeforeSend(options); | ||
| if (null === filtered) return; | ||
| if (!filtered.body) return; | ||
| if (!this._checkRateLimit()) return; | ||
| const record = (0, external_logs_utils_js_namespaceObject.buildOtlpLogRecord)(filtered, this._getContext()); | ||
| const entry = { | ||
@@ -53,2 +65,92 @@ record | ||
| } | ||
| _runBeforeSend(options) { | ||
| const beforeSend = this._config.beforeSend; | ||
| if (!beforeSend) return options; | ||
| const fns = (0, index_js_namespaceObject.isArray)(beforeSend) ? beforeSend : [ | ||
| beforeSend | ||
| ]; | ||
| let result = options; | ||
| for (const fn of fns)try { | ||
| const next = fn(result); | ||
| if (!next) { | ||
| this._logger.info("Log was rejected in beforeSend function"); | ||
| return null; | ||
| } | ||
| result = next; | ||
| } catch (e) { | ||
| this._logger.error("Error in beforeSend function for log:", e); | ||
| } | ||
| return result; | ||
| } | ||
| _checkRateLimit() { | ||
| if (void 0 === this._maxLogsPerInterval) return true; | ||
| const now = Date.now(); | ||
| const elapsed = now - this._intervalWindowStart; | ||
| if (elapsed >= this._rateCapWindowMs || elapsed < 0) { | ||
| this._intervalWindowStart = now; | ||
| this._intervalLogCount = 0; | ||
| this._droppedWarned = false; | ||
| } | ||
| if (this._intervalLogCount >= this._maxLogsPerInterval) { | ||
| if (!this._droppedWarned) { | ||
| this._logger.warn(`captureLog dropping logs: exceeded ${this._maxLogsPerInterval} logs per ${this._rateCapWindowMs}ms`); | ||
| this._droppedWarned = true; | ||
| } | ||
| return false; | ||
| } | ||
| this._intervalLogCount++; | ||
| return true; | ||
| } | ||
| async flush() { | ||
| if (this._flushPromise) return this._flushPromise; | ||
| this._flushPromise = this._flushInner().finally(()=>{ | ||
| this._flushPromise = null; | ||
| }); | ||
| return this._flushPromise; | ||
| } | ||
| async _flushInner() { | ||
| this._clearFlushTimer(); | ||
| let queue = this._instance.getPersistedProperty(external_types_js_namespaceObject.PostHogPersistedProperty.LogsQueue) ?? []; | ||
| if (0 === queue.length) return; | ||
| const originalQueueLength = queue.length; | ||
| let sentCount = 0; | ||
| while(queue.length > 0 && sentCount < originalQueueLength){ | ||
| const batchSize = Math.min(queue.length, this._maxBatchRecordsPerPost); | ||
| const batch = queue.slice(0, batchSize); | ||
| const records = batch.map((e)=>e.record); | ||
| const payload = (0, external_logs_utils_js_namespaceObject.buildOtlpLogsPayload)(records, this._buildResourceAttributes(), this._instance.getLibraryId(), this._instance.getLibraryVersion()); | ||
| const outcome = await this._instance._sendLogsBatch(payload); | ||
| if ('too-large' === outcome.kind && batch.length > 1) { | ||
| this._maxBatchRecordsPerPost = Math.max(1, Math.floor(batch.length / 2)); | ||
| this._logger.warn(`Received 413 when sending logs batch of size ${batch.length}, reducing batch size to ${this._maxBatchRecordsPerPost}`); | ||
| continue; | ||
| } | ||
| if ('retry-later' === outcome.kind) throw outcome.error; | ||
| if ('too-large' === outcome.kind) this._logger.warn("Dropping a single log record after 413 with batch size 1 \u2014 the record is larger than the server cap and cannot be split further."); | ||
| else if ('ok' === outcome.kind && this._maxBatchRecordsPerPost < this._config.maxBatchRecordsPerPost) this._maxBatchRecordsPerPost = Math.min(this._config.maxBatchRecordsPerPost, this._maxBatchRecordsPerPost + 1); | ||
| await this._persistQueueAdvance(batch.length); | ||
| queue = this._instance.getPersistedProperty(external_types_js_namespaceObject.PostHogPersistedProperty.LogsQueue) ?? []; | ||
| sentCount += batch.length; | ||
| if ('fatal' === outcome.kind) throw outcome.error; | ||
| } | ||
| } | ||
| async _persistQueueAdvance(consumed) { | ||
| const refreshed = this._instance.getPersistedProperty(external_types_js_namespaceObject.PostHogPersistedProperty.LogsQueue) ?? []; | ||
| this._instance.setPersistedProperty(external_types_js_namespaceObject.PostHogPersistedProperty.LogsQueue, refreshed.slice(consumed)); | ||
| await this._waitForStoragePersist(); | ||
| } | ||
| _buildResourceAttributes() { | ||
| return { | ||
| ...this._config.resourceAttributes, | ||
| 'service.name': this._config.serviceName || 'unknown_service', | ||
| ...this._config.environment && { | ||
| 'deployment.environment': this._config.environment | ||
| }, | ||
| ...this._config.serviceVersion && { | ||
| 'service.version': this._config.serviceVersion | ||
| }, | ||
| 'telemetry.sdk.name': this._instance.getLibraryId(), | ||
| 'telemetry.sdk.version': this._instance.getLibraryVersion() | ||
| }; | ||
| } | ||
| _enqueue(entry) { | ||
@@ -63,3 +165,44 @@ if (this._instance.optedOut) return; | ||
| this._instance.setPersistedProperty(external_types_js_namespaceObject.PostHogPersistedProperty.LogsQueue, queue); | ||
| if (queue.length >= this._maxBufferSize) return void this._flushInBackground(); | ||
| if (!this._flushTimer) this._flushTimer = (0, index_js_namespaceObject.safeSetTimeout)(()=>{ | ||
| this._flushTimer = void 0; | ||
| this._flushInBackground(); | ||
| }, this._flushIntervalMs); | ||
| } | ||
| async shutdown(timeoutMs) { | ||
| this._clearFlushTimer(); | ||
| const flushPromise = this.flush().catch(()=>{}); | ||
| if (void 0 === timeoutMs) return void await flushPromise; | ||
| await Promise.race([ | ||
| flushPromise, | ||
| new Promise((resolve)=>(0, index_js_namespaceObject.safeSetTimeout)(resolve, timeoutMs)) | ||
| ]); | ||
| } | ||
| async flushWithTimeout(timeoutMs) { | ||
| let timedOut = false; | ||
| const flushPromise = this.flush(); | ||
| const timerPromise = new Promise((resolve)=>(0, index_js_namespaceObject.safeSetTimeout)(()=>{ | ||
| timedOut = true; | ||
| resolve(); | ||
| }, timeoutMs)); | ||
| try { | ||
| await Promise.race([ | ||
| flushPromise, | ||
| timerPromise | ||
| ]); | ||
| } finally{ | ||
| if (timedOut) flushPromise.catch(()=>{}); | ||
| } | ||
| } | ||
| _flushInBackground() { | ||
| this.flush().catch((err)=>{ | ||
| this._logger.error('PostHog logs flush failed:', err); | ||
| }); | ||
| } | ||
| _clearFlushTimer() { | ||
| if (this._flushTimer) { | ||
| clearTimeout(this._flushTimer); | ||
| this._flushTimer = void 0; | ||
| } | ||
| } | ||
| } | ||
@@ -66,0 +209,0 @@ exports.PostHogLogs = __webpack_exports__.PostHogLogs; |
+148
-5
@@ -1,5 +0,6 @@ | ||
| import { buildOtlpLogRecord } from "./logs-utils.mjs"; | ||
| import { buildOtlpLogRecord, buildOtlpLogsPayload } from "./logs-utils.mjs"; | ||
| import { PostHogPersistedProperty } from "../types.mjs"; | ||
| import { isArray, safeSetTimeout } from "../utils/index.mjs"; | ||
| class PostHogLogs { | ||
| constructor(_instance, _config, _logger, _getContext, _onReady){ | ||
| constructor(_instance, _config, _logger, _getContext, _onReady, _waitForStoragePersist = ()=>Promise.resolve()){ | ||
| this._instance = _instance; | ||
@@ -10,10 +11,21 @@ this._config = _config; | ||
| this._onReady = _onReady; | ||
| this._localEnabled = false !== _config.enabled; | ||
| this._waitForStoragePersist = _waitForStoragePersist; | ||
| this._flushPromise = null; | ||
| this._intervalWindowStart = 0; | ||
| this._intervalLogCount = 0; | ||
| this._droppedWarned = false; | ||
| this._maxBufferSize = _config.maxBufferSize; | ||
| this._flushIntervalMs = _config.flushIntervalMs; | ||
| this._maxBatchRecordsPerPost = _config.maxBatchRecordsPerPost; | ||
| this._rateCapWindowMs = _config.rateCapWindowMs; | ||
| this._maxLogsPerInterval = _config.maxLogsPerInterval; | ||
| } | ||
| captureLog(options) { | ||
| if (!this._localEnabled) return; | ||
| if (this._instance.optedOut) return; | ||
| if (!options?.body) return; | ||
| const record = buildOtlpLogRecord(options, this._getContext()); | ||
| const filtered = this._runBeforeSend(options); | ||
| if (null === filtered) return; | ||
| if (!filtered.body) return; | ||
| if (!this._checkRateLimit()) return; | ||
| const record = buildOtlpLogRecord(filtered, this._getContext()); | ||
| const entry = { | ||
@@ -24,2 +36,92 @@ record | ||
| } | ||
| _runBeforeSend(options) { | ||
| const beforeSend = this._config.beforeSend; | ||
| if (!beforeSend) return options; | ||
| const fns = isArray(beforeSend) ? beforeSend : [ | ||
| beforeSend | ||
| ]; | ||
| let result = options; | ||
| for (const fn of fns)try { | ||
| const next = fn(result); | ||
| if (!next) { | ||
| this._logger.info("Log was rejected in beforeSend function"); | ||
| return null; | ||
| } | ||
| result = next; | ||
| } catch (e) { | ||
| this._logger.error("Error in beforeSend function for log:", e); | ||
| } | ||
| return result; | ||
| } | ||
| _checkRateLimit() { | ||
| if (void 0 === this._maxLogsPerInterval) return true; | ||
| const now = Date.now(); | ||
| const elapsed = now - this._intervalWindowStart; | ||
| if (elapsed >= this._rateCapWindowMs || elapsed < 0) { | ||
| this._intervalWindowStart = now; | ||
| this._intervalLogCount = 0; | ||
| this._droppedWarned = false; | ||
| } | ||
| if (this._intervalLogCount >= this._maxLogsPerInterval) { | ||
| if (!this._droppedWarned) { | ||
| this._logger.warn(`captureLog dropping logs: exceeded ${this._maxLogsPerInterval} logs per ${this._rateCapWindowMs}ms`); | ||
| this._droppedWarned = true; | ||
| } | ||
| return false; | ||
| } | ||
| this._intervalLogCount++; | ||
| return true; | ||
| } | ||
| async flush() { | ||
| if (this._flushPromise) return this._flushPromise; | ||
| this._flushPromise = this._flushInner().finally(()=>{ | ||
| this._flushPromise = null; | ||
| }); | ||
| return this._flushPromise; | ||
| } | ||
| async _flushInner() { | ||
| this._clearFlushTimer(); | ||
| let queue = this._instance.getPersistedProperty(PostHogPersistedProperty.LogsQueue) ?? []; | ||
| if (0 === queue.length) return; | ||
| const originalQueueLength = queue.length; | ||
| let sentCount = 0; | ||
| while(queue.length > 0 && sentCount < originalQueueLength){ | ||
| const batchSize = Math.min(queue.length, this._maxBatchRecordsPerPost); | ||
| const batch = queue.slice(0, batchSize); | ||
| const records = batch.map((e)=>e.record); | ||
| const payload = buildOtlpLogsPayload(records, this._buildResourceAttributes(), this._instance.getLibraryId(), this._instance.getLibraryVersion()); | ||
| const outcome = await this._instance._sendLogsBatch(payload); | ||
| if ('too-large' === outcome.kind && batch.length > 1) { | ||
| this._maxBatchRecordsPerPost = Math.max(1, Math.floor(batch.length / 2)); | ||
| this._logger.warn(`Received 413 when sending logs batch of size ${batch.length}, reducing batch size to ${this._maxBatchRecordsPerPost}`); | ||
| continue; | ||
| } | ||
| if ('retry-later' === outcome.kind) throw outcome.error; | ||
| if ('too-large' === outcome.kind) this._logger.warn("Dropping a single log record after 413 with batch size 1 \u2014 the record is larger than the server cap and cannot be split further."); | ||
| else if ('ok' === outcome.kind && this._maxBatchRecordsPerPost < this._config.maxBatchRecordsPerPost) this._maxBatchRecordsPerPost = Math.min(this._config.maxBatchRecordsPerPost, this._maxBatchRecordsPerPost + 1); | ||
| await this._persistQueueAdvance(batch.length); | ||
| queue = this._instance.getPersistedProperty(PostHogPersistedProperty.LogsQueue) ?? []; | ||
| sentCount += batch.length; | ||
| if ('fatal' === outcome.kind) throw outcome.error; | ||
| } | ||
| } | ||
| async _persistQueueAdvance(consumed) { | ||
| const refreshed = this._instance.getPersistedProperty(PostHogPersistedProperty.LogsQueue) ?? []; | ||
| this._instance.setPersistedProperty(PostHogPersistedProperty.LogsQueue, refreshed.slice(consumed)); | ||
| await this._waitForStoragePersist(); | ||
| } | ||
| _buildResourceAttributes() { | ||
| return { | ||
| ...this._config.resourceAttributes, | ||
| 'service.name': this._config.serviceName || 'unknown_service', | ||
| ...this._config.environment && { | ||
| 'deployment.environment': this._config.environment | ||
| }, | ||
| ...this._config.serviceVersion && { | ||
| 'service.version': this._config.serviceVersion | ||
| }, | ||
| 'telemetry.sdk.name': this._instance.getLibraryId(), | ||
| 'telemetry.sdk.version': this._instance.getLibraryVersion() | ||
| }; | ||
| } | ||
| _enqueue(entry) { | ||
@@ -34,4 +136,45 @@ if (this._instance.optedOut) return; | ||
| this._instance.setPersistedProperty(PostHogPersistedProperty.LogsQueue, queue); | ||
| if (queue.length >= this._maxBufferSize) return void this._flushInBackground(); | ||
| if (!this._flushTimer) this._flushTimer = safeSetTimeout(()=>{ | ||
| this._flushTimer = void 0; | ||
| this._flushInBackground(); | ||
| }, this._flushIntervalMs); | ||
| } | ||
| async shutdown(timeoutMs) { | ||
| this._clearFlushTimer(); | ||
| const flushPromise = this.flush().catch(()=>{}); | ||
| if (void 0 === timeoutMs) return void await flushPromise; | ||
| await Promise.race([ | ||
| flushPromise, | ||
| new Promise((resolve)=>safeSetTimeout(resolve, timeoutMs)) | ||
| ]); | ||
| } | ||
| async flushWithTimeout(timeoutMs) { | ||
| let timedOut = false; | ||
| const flushPromise = this.flush(); | ||
| const timerPromise = new Promise((resolve)=>safeSetTimeout(()=>{ | ||
| timedOut = true; | ||
| resolve(); | ||
| }, timeoutMs)); | ||
| try { | ||
| await Promise.race([ | ||
| flushPromise, | ||
| timerPromise | ||
| ]); | ||
| } finally{ | ||
| if (timedOut) flushPromise.catch(()=>{}); | ||
| } | ||
| } | ||
| _flushInBackground() { | ||
| this.flush().catch((err)=>{ | ||
| this._logger.error('PostHog logs flush failed:', err); | ||
| }); | ||
| } | ||
| _clearFlushTimer() { | ||
| if (this._flushTimer) { | ||
| clearTimeout(this._flushTimer); | ||
| this._flushTimer = void 0; | ||
| } | ||
| } | ||
| } | ||
| export { PostHogLogs }; |
@@ -1,2 +0,3 @@ | ||
| import type { CaptureLogOptions, LogAttributeValue, LogSdkContext, LogSeverityLevel, OtlpAnyValue, OtlpKeyValue, OtlpLogRecord, OtlpLogsPayload, OtlpSeverityText } from '@posthog/types'; | ||
| import type { CaptureLogOptions, LogAttributeValue, LogSeverityLevel, OtlpAnyValue, OtlpKeyValue, OtlpLogRecord, OtlpLogsPayload, OtlpSeverityText } from '@posthog/types'; | ||
| import type { LogSdkContext } from './types'; | ||
| export declare function getOtlpSeverityText(level: LogSeverityLevel): OtlpSeverityText; | ||
@@ -3,0 +4,0 @@ export declare function getOtlpSeverityNumber(level: LogSeverityLevel): number; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"logs-utils.d.ts","sourceRoot":"","sources":["../../src/logs/logs-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,eAAe,EAEf,gBAAgB,EACjB,MAAM,gBAAgB,CAAA;AAkBvB,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,CAE7E;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAErE;AAMD,wBAAgB,cAAc,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAiCrE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAAG,YAAY,EAAE,CAU3F;AAiBD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,aAAa,GAAG,aAAa,CAmDvG;AAMD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,aAAa,EAAE,EAC3B,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACrD,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,eAAe,CAcjB"} | ||
| {"version":3,"file":"logs-utils.d.ts","sourceRoot":"","sources":["../../src/logs/logs-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,eAAe,EAEf,gBAAgB,EACjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAkB5C,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,CAE7E;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAErE;AAMD,wBAAgB,cAAc,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAiCrE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAAG,YAAY,EAAE,CAU3F;AAiBD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,aAAa,GAAG,aAAa,CAmDvG;AAMD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,aAAa,EAAE,EAC3B,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACrD,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,eAAe,CAcjB"} |
+140
-8
@@ -1,2 +0,21 @@ | ||
| export type { LogSeverityLevel, OtlpSeverityText, OtlpSeverityEntry, LogAttributeValue, LogAttributes, CaptureLogOptions, OtlpAnyValue, OtlpKeyValue, OtlpLogRecord, OtlpLogsPayload, LogSdkContext, } from '@posthog/types'; | ||
| export type { LogSeverityLevel, OtlpSeverityText, OtlpSeverityEntry, LogAttributeValue, LogAttributes, CaptureLogOptions, OtlpAnyValue, OtlpKeyValue, OtlpLogRecord, OtlpLogsPayload, } from '@posthog/types'; | ||
| /** | ||
| * SDK-internal context the host SDK passes to `buildOtlpLogRecord` at capture | ||
| * time. Each SDK populates the fields that apply to it: browser fills | ||
| * `currentUrl`, mobile fills `screenName` / `appState`. Missing fields are | ||
| * omitted from the OTLP record (no stray attributes). | ||
| * | ||
| * Internal to `@posthog/core` — customers don't see this in autocomplete. | ||
| */ | ||
| export interface LogSdkContext { | ||
| distinctId?: string; | ||
| sessionId?: string; | ||
| /** Web-only — current page URL */ | ||
| currentUrl?: string; | ||
| /** Mobile-only — current screen / view name */ | ||
| screenName?: string; | ||
| /** Mobile-only — app foreground/background state at capture time */ | ||
| appState?: 'foreground' | 'background'; | ||
| activeFeatureFlags?: string[]; | ||
| } | ||
| import type { Logger as CaptureLoggerType } from '@posthog/types'; | ||
@@ -8,20 +27,133 @@ export type CaptureLogger = CaptureLoggerType; | ||
| } | ||
| /** | ||
| * Pre-send filter. Inspect, mutate, or drop a captured record before it | ||
| * enters the rate-cap or the queue. Return the (possibly transformed) record | ||
| * to keep it; return `null` to drop it. | ||
| * | ||
| * Configure as a single fn or an array. Arrays form a left-to-right chain: | ||
| * each fn receives the previous fn's return value. A `null` from any link | ||
| * short-circuits the chain and drops the record. | ||
| * | ||
| * Runs *before* the rate cap so dropped records don't consume the | ||
| * per-interval budget. Throwing fns are logged and skipped — the chain | ||
| * continues with the previous return value, so a buggy filter degrades to a | ||
| * no-op rather than crashing `captureLog()`. | ||
| * | ||
| * @example Redact secrets from log bodies | ||
| * ```ts | ||
| * logs: { | ||
| * beforeSend: (record) => ({ | ||
| * ...record, | ||
| * body: record.body.replace(/api_key=\S+/g, 'api_key=[REDACTED]'), | ||
| * }), | ||
| * } | ||
| * ``` | ||
| * | ||
| * @example Drop noisy debug logs in production | ||
| * ```ts | ||
| * logs: { | ||
| * beforeSend: (record) => (record.level === 'debug' ? null : record), | ||
| * } | ||
| * ``` | ||
| */ | ||
| export type BeforeSendLogFn = (record: CaptureLogOptions) => CaptureLogOptions | null; | ||
| /** | ||
| * Configuration for the logs feature on `new PostHog(key, { logs: ... })`. | ||
| * All fields are optional; per-SDK defaults apply (mobile vs browser tune | ||
| * differently for cellular cost vs tab-suspension behavior). | ||
| */ | ||
| export interface PostHogLogsConfig { | ||
| enabled?: boolean; | ||
| /** | ||
| * Service name attached to every record as the OTLP `service.name` | ||
| * resource attribute. Used by the Logs UI for filtering / grouping. | ||
| * Default: `'unknown_service'`. | ||
| */ | ||
| serviceName?: string; | ||
| /** | ||
| * Service version attached as OTLP `service.version`. Useful for | ||
| * correlating regressions to specific app releases. | ||
| */ | ||
| serviceVersion?: string; | ||
| /** | ||
| * Deployment environment attached as OTLP `deployment.environment` | ||
| * (e.g. `'production'`, `'staging'`, `'dev'`). | ||
| */ | ||
| environment?: string; | ||
| /** | ||
| * Extra OTLP resource attributes attached to every record. Spread first; | ||
| * SDK-controlled keys (`service.name`, `telemetry.sdk.*`, RN's `os.*`) | ||
| * are layered on top so users cannot accidentally clobber them. Use the | ||
| * dedicated `serviceName` / `environment` / `serviceVersion` fields to | ||
| * override those keys. | ||
| */ | ||
| resourceAttributes?: Record<string, LogAttributeValue>; | ||
| /** | ||
| * How often the periodic background flush fires (ms). Records also flush | ||
| * eagerly when the buffer fills, on AppState changes (RN), and on | ||
| * `shutdown()`. Lower values trade battery/bandwidth for fresher data. | ||
| * Default: 10000 (RN) / 3000 (browser). | ||
| */ | ||
| flushIntervalMs?: number; | ||
| rateCapWindowMs?: number; | ||
| /** | ||
| * Max records held in memory before the queue evicts the oldest on push | ||
| * (FIFO). Bounds memory footprint and on-disk-queue size. When the buffer | ||
| * hits this size, an immediate flush is triggered to reclaim space; if | ||
| * the flush hasn't completed before the next capture, the oldest record | ||
| * is shifted out. Default: 100. | ||
| */ | ||
| maxBufferSize?: number; | ||
| maxLogsPerInterval?: number; | ||
| /** | ||
| * Max records per outbound POST. Keeps each request under the server's | ||
| * 2 MB cap. On a 413 response, the SDK halves this value, retries the | ||
| * same records, then ramps back up by 1 per healthy send. A 413 on a | ||
| * single-record batch drops the record (it's larger than the server can | ||
| * accept regardless of batch size). Default: 50 (RN) / 100 (browser). | ||
| */ | ||
| maxBatchRecordsPerPost?: number; | ||
| backgroundFlushBudgetMs?: number; | ||
| terminationFlushBudgetMs?: number; | ||
| beforeSend?: (record: CaptureLogOptions) => CaptureLogOptions | null; | ||
| /** | ||
| * Tumbling-window rate cap. Bounds how many records can be captured | ||
| * within a sliding (technically tumbling) time window. Records exceeding | ||
| * the cap are dropped synchronously at `captureLog()` (they never enter | ||
| * the buffer or consume bandwidth). A single warn line is logged per | ||
| * window when the cap is hit. | ||
| * | ||
| * Defaults are per-SDK; on RN the default is `{ maxLogs: 500, windowMs: | ||
| * 10000 }` (≈50 logs/sec ceiling, tuned for cellular bandwidth). | ||
| * | ||
| * @example Allow brief bursts up to 1000/min | ||
| * ```ts | ||
| * logs: { rateCap: { maxLogs: 1000, windowMs: 60000 } } | ||
| * ``` | ||
| * | ||
| * @example Disable the cap entirely (unbounded) | ||
| * ```ts | ||
| * logs: { rateCap: { maxLogs: undefined } } | ||
| * ``` | ||
| */ | ||
| rateCap?: { | ||
| /** | ||
| * Max records accepted per `windowMs` window. `undefined` = unbounded. | ||
| */ | ||
| maxLogs?: number; | ||
| /** | ||
| * Window length in ms. Tumbling, not sliding — the counter resets the | ||
| * first time a capture fires after the window expires. | ||
| */ | ||
| windowMs?: number; | ||
| }; | ||
| /** | ||
| * Pre-send filter. See {@link BeforeSendLogFn} for shape and examples. | ||
| * Configure as a single function or a chain. | ||
| */ | ||
| beforeSend?: BeforeSendLogFn | BeforeSendLogFn[]; | ||
| } | ||
| export interface ResolvedPostHogLogsConfig extends PostHogLogsConfig { | ||
| export interface ResolvedPostHogLogsConfig extends Omit<PostHogLogsConfig, 'rateCap'> { | ||
| maxBufferSize: number; | ||
| flushIntervalMs: number; | ||
| maxBatchRecordsPerPost: number; | ||
| rateCapWindowMs: number; | ||
| maxLogsPerInterval?: number; | ||
| backgroundFlushBudgetMs: number; | ||
| terminationFlushBudgetMs: number; | ||
| } | ||
| //# sourceMappingURL=types.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/logs/types.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,eAAe,EACf,aAAa,GACd,MAAM,gBAAgB,CAAA;AAKvB,OAAO,KAAK,EAAE,MAAM,IAAI,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AACjE,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAA;AAE7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAKzF,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,aAAa,CAAA;CACtB;AAID,MAAM,WAAW,iBAAiB;IAEhC,OAAO,CAAC,EAAE,OAAO,CAAA;IAGjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;IAGtD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAI/B,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,wBAAwB,CAAC,EAAE,MAAM,CAAA;IAIjC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,iBAAiB,GAAG,IAAI,CAAA;CACrE;AAKD,MAAM,WAAW,yBAA0B,SAAQ,iBAAiB;IAClE,aAAa,EAAE,MAAM,CAAA;CACtB"} | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/logs/types.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,eAAe,GAChB,MAAM,gBAAgB,CAAA;AAEvB;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,+CAA+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,YAAY,GAAG,YAAY,CAAA;IACtC,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC9B;AAKD,OAAO,KAAK,EAAE,MAAM,IAAI,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AACjE,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAA;AAE7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEzF,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,aAAa,CAAA;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,iBAAiB,KAAK,iBAAiB,GAAG,IAAI,CAAA;AAErF;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;IAEtD;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAE/B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,EAAE;QACR;;WAEG;QACH,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB;;;WAGG;QACH,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;IAED;;;OAGG;IACH,UAAU,CAAC,EAAE,eAAe,GAAG,eAAe,EAAE,CAAA;CACjD;AAKD,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,iBAAiB,EAAE,SAAS,CAAC;IACnF,aAAa,EAAE,MAAM,CAAA;IACrB,eAAe,EAAE,MAAM,CAAA;IACvB,sBAAsB,EAAE,MAAM,CAAA;IAC9B,eAAe,EAAE,MAAM,CAAA;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,uBAAuB,EAAE,MAAM,CAAA;IAC/B,wBAAwB,EAAE,MAAM,CAAA;CACjC"} |
@@ -0,1 +1,2 @@ | ||
| import type { OtlpLogsPayload } from '@posthog/types'; | ||
| import { SimpleEventEmitter } from './eventemitter'; | ||
@@ -6,2 +7,24 @@ import { PostHogFlagsResponse, PostHogCoreOptions, PostHogEventProperties, PostHogCaptureOptions, JsonType, PostHogRemoteConfig, FeatureFlagValue, PostHogFeatureFlagDetails, FeatureFlagDetail, SurveyResponse, PostHogFetchResponse, PostHogFetchOptions, PostHogPersistedProperty, Logger, GetFlagsResult } from './types'; | ||
| export declare function logFlushError(err: any): Promise<void>; | ||
| /** | ||
| * Outcome of a logs batch send. Keeps HTTP error classification inside core | ||
| * (single source of truth — same policy events already use in `_flush()`) so | ||
| * PostHogLogs doesn't need to know about specific error types. | ||
| * | ||
| * - ok → records are accepted; drop them from the queue | ||
| * - too-large → 413; caller should halve batch size and retry same records | ||
| * - retry-later → network error; caller keeps records and retries next cycle | ||
| * - fatal → anything else (auth, malformed, etc.); caller drops the | ||
| * batch and surfaces the error | ||
| */ | ||
| export type SendLogsBatchOutcome = { | ||
| kind: 'ok'; | ||
| } | { | ||
| kind: 'too-large'; | ||
| } | { | ||
| kind: 'retry-later'; | ||
| error: unknown; | ||
| } | { | ||
| kind: 'fatal'; | ||
| error: unknown; | ||
| }; | ||
| export declare enum QuotaLimitedFeature { | ||
@@ -201,2 +224,13 @@ FeatureFlags = "feature_flags", | ||
| private _flush; | ||
| /** | ||
| * Sends a pre-built OTLP logs payload to `/i/v1/logs`. Returns a tagged | ||
| * outcome instead of throwing so PostHogLogs doesn't have to know about the | ||
| * core's error class hierarchy. Error classification lives here (single | ||
| * source of truth, same policy the events `_flush()` uses for its own | ||
| * 413 / network / fatal handling). | ||
| * | ||
| * 413 is passed through as `too-large` (not auto-retried) so the caller can | ||
| * shrink `maxBatchRecordsPerPost` and retry the same records. | ||
| */ | ||
| _sendLogsBatch(payload: OtlpLogsPayload): Promise<SendLogsBatchOutcome>; | ||
| private fetchWithRetry; | ||
@@ -203,0 +237,0 @@ _shutdown(shutdownTimeoutMs?: number): Promise<void>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"posthog-core-stateless.d.ts","sourceRoot":"","sources":["../src/posthog-core-stateless.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"posthog-core-stateless.d.ts","sourceRoot":"","sources":["../src/posthog-core-stateless.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAGnD,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,QAAQ,EACR,mBAAmB,EACnB,gBAAgB,EAGhB,yBAAyB,EACzB,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EAExB,MAAM,EACN,cAAc,EAEf,MAAM,SAAS,CAAA;AAChB,OAAO,EAML,gBAAgB,EAIjB,MAAM,SAAS,CAAA;AAqChB,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,EAAE,OAAO,QAAQ,GAAG,SAAS,KAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAC9C,CAAA;AAE7C,wBAAsB,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAY3D;AAUD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GACd;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAA;AAErC,oBAAY,mBAAmB;IAC7B,YAAY,kBAAkB;IAC9B,UAAU,eAAe;CAC1B;AAED,8BAAsB,oBAAoB;IAExC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAA;IACrC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,4BAA4B,CAAQ;IAC5C,OAAO,CAAC,4BAA4B,CAAQ;IAC5C,OAAO,CAAC,mBAAmB,CAAC,CAAY;IACxC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,kBAAkB,CAAC,CAAmB;IAC9C,SAAS,CAAC,QAAQ,UAAA;IAClB,SAAS,CAAC,kBAAkB,EAAE,OAAO,CAAA;IAErC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAmC;IAGvD,SAAS,CAAC,OAAO,qBAA2B;IAC5C,SAAS,CAAC,WAAW,CAAC,EAAE,GAAG,CAAA;IAC3B,SAAS,CAAC,aAAa,EAAE,gBAAgB,CAAA;IACzC,SAAS,CAAC,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,SAAS,CAAC,cAAc,EAAE,OAAO,CAAQ;IACzC,SAAS,CAAC,4BAA4B,CAAC,EAAE,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAA;IACjF,SAAS,CAAC,OAAO,EAAE,MAAM,CAAA;IAGzB,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IACxF,QAAQ,CAAC,YAAY,IAAI,MAAM;IAC/B,QAAQ,CAAC,iBAAiB,IAAI,MAAM;IACpC,QAAQ,CAAC,kBAAkB,IAAI,MAAM,GAAG,IAAI;IAG5C,QAAQ,CAAC,oBAAoB,CAAC,CAAC,EAAE,GAAG,EAAE,wBAAwB,GAAG,CAAC,GAAG,SAAS;IAC9E,QAAQ,CAAC,oBAAoB,CAAC,CAAC,EAAE,GAAG,EAAE,wBAAwB,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;gBAE1E,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB;IA6C5D,SAAS,CAAC,aAAa,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAM7C,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAcpC,SAAS,CAAC,wBAAwB,IAAI,sBAAsB;IAO5D,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAM7B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,MAAM,IAAI;IAI3D;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,KAAK,CAAC,OAAO,GAAE,OAAc,GAAG,IAAI;IAYpC,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,OAAO,CAAC,YAAY;IAepB;;OAEG;IACI,iBAAiB,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAI5D;;SAEK;IACL,SAAS,CAAC,iBAAiB,CACzB,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,sBAAsB,EACnC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI;cAiBS,0BAA0B,CACxC,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,sBAAsB,EACnC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC;IAYhB,SAAS,CAAC,gBAAgB,CACxB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,sBAAsB,EACnC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI;cAOS,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,sBAAsB,EACnC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC;IAKhB,SAAS,CAAC,cAAc,CACtB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,sBAAsB,EACnC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI;cAgBS,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,sBAAsB,EACnC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC;IAchB;;SAEK;IACL,SAAS,CAAC,sBAAsB,CAC9B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GAAG,MAAM,EACzB,eAAe,CAAC,EAAE,sBAAsB,EACxC,OAAO,CAAC,EAAE,qBAAqB,EAC/B,UAAU,CAAC,EAAE,MAAM,EACnB,eAAe,CAAC,EAAE,sBAAsB,GACvC,IAAI;cAiBS,eAAe,IAAI,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IA0B3E;;SAEK;cAEW,QAAQ,CACtB,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,EAC5C,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAC7C,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EAC5D,YAAY,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EACtC,WAAW,GAAE,OAAe,GAC3B,OAAO,CAAC,cAAc,CAAC;IA2C1B,OAAO,CAAC,sBAAsB;cAiBd,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACnC,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAC7C,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EAC5D,YAAY,CAAC,EAAE,OAAO,GACrB,OAAO,CAAC;QACT,QAAQ,EAAE,gBAAgB,GAAG,SAAS,CAAA;QACtC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;KAC9B,CAAC;cAkCc,6BAA6B,CAC3C,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACnC,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAC7C,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EAC5D,YAAY,CAAC,EAAE,OAAO,GACrB,OAAO,CACN;QACE,QAAQ,EAAE,iBAAiB,GAAG,SAAS,CAAA;QACvC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;QAC7B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;KAChC,GACD,SAAS,CACZ;cA2Be,8BAA8B,CAC5C,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACnC,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAC7C,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EAC5D,YAAY,CAAC,EAAE,OAAO,GACrB,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;cA0BhB,+BAA+B,CAC7C,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACnC,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAC7C,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EAC5D,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAC5B,OAAO,CAAC,oBAAoB,CAAC,qBAAqB,CAAC,GAAG,SAAS,CAAC;cAiBnD,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,EAC5C,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAC7C,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EAC5D,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAC5B,OAAO,CAAC;QACT,KAAK,EAAE,oBAAoB,CAAC,cAAc,CAAC,GAAG,SAAS,CAAA;QACvD,QAAQ,EAAE,oBAAoB,CAAC,qBAAqB,CAAC,GAAG,SAAS,CAAA;QACjE,SAAS,EAAE,oBAAoB,CAAC,WAAW,CAAC,GAAG,SAAS,CAAA;KACzD,CAAC;cAac,mCAAmC,CACjD,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,EAC5C,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAC7C,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EAC5D,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAC5B,OAAO,CAAC;QACT,KAAK,EAAE,oBAAoB,CAAC,cAAc,CAAC,GAAG,SAAS,CAAA;QACvD,QAAQ,EAAE,oBAAoB,CAAC,qBAAqB,CAAC,GAAG,SAAS,CAAA;QACjE,SAAS,EAAE,oBAAoB,CAAC,WAAW,CAAC,GAAG,SAAS,CAAA;KACzD,CAAC;cA2Bc,8BAA8B,CAC5C,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,EAC5C,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAC7C,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EAC5D,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAC5B,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC;IA2CjD;;SAEK;IAEQ,mBAAmB,IAAI,OAAO,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;IA2CtE;;SAEK;IACL,OAAO,CAAC,MAAM,CAAoC;IAElD,SAAS,KAAK,KAAK,IAAI,sBAAsB,CAK5C;IAED,SAAS,KAAK,KAAK,CAAC,GAAG,EAAE,sBAAsB,GAAG,SAAS,EAE1D;IAEK,QAAQ,CAAC,UAAU,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAU3D,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjD;;SAEK;IAEL;;;;;OAKG;IACH,SAAS,CAAC,oBAAoB,CAAC,OAAO,EAAE,sBAAsB,GAAG,sBAAsB,GAAG,IAAI;IAI9F;;;;;;;OAOG;cACa,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7C,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,IAAI;cAsCrE,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuD1G,SAAS,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,sBAAsB;IA0B9G,OAAO,CAAC,eAAe;IAOvB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAMvB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB5B,SAAS,CAAC,gBAAgB,IAAI;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE;YAazC,MAAM;IA4FpB;;;;;;;;;OASG;IACG,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,oBAAoB,CAAC;YAoC/D,cAAc;IAsDtB,SAAS,CAAC,iBAAiB,GAAE,MAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAsDjE;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACG,QAAQ,CAAC,iBAAiB,GAAE,MAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CAYjE"} |
@@ -639,2 +639,41 @@ "use strict"; | ||
| } | ||
| async _sendLogsBatch(payload) { | ||
| const serialized = JSON.stringify(payload); | ||
| const url = `${this.host}/i/v1/logs?token=${encodeURIComponent(this.apiKey)}`; | ||
| const gzippedPayload = this.disableCompression ? null : await (0, external_gzip_js_namespaceObject.gzipCompress)(serialized, this.isDebug); | ||
| const fetchOptions = { | ||
| method: 'POST', | ||
| headers: { | ||
| ...this.getCustomHeaders(), | ||
| 'Content-Type': 'application/json', | ||
| ...null !== gzippedPayload && { | ||
| 'Content-Encoding': 'gzip' | ||
| } | ||
| }, | ||
| body: gzippedPayload || serialized | ||
| }; | ||
| try { | ||
| await this.fetchWithRetry(url, fetchOptions, { | ||
| retryCheck: (err)=>{ | ||
| if (isPostHogFetchContentTooLargeError(err)) return false; | ||
| return isPostHogFetchError(err); | ||
| } | ||
| }); | ||
| return { | ||
| kind: 'ok' | ||
| }; | ||
| } catch (err) { | ||
| if (isPostHogFetchContentTooLargeError(err)) return { | ||
| kind: 'too-large' | ||
| }; | ||
| if (err instanceof PostHogFetchNetworkError) return { | ||
| kind: 'retry-later', | ||
| error: err | ||
| }; | ||
| return { | ||
| kind: 'fatal', | ||
| error: err | ||
| }; | ||
| } | ||
| } | ||
| async fetchWithRetry(url, options, retryOptions, requestTimeout) { | ||
@@ -641,0 +680,0 @@ const body = options.body ? options.body : ''; |
@@ -608,2 +608,41 @@ import { SimpleEventEmitter } from "./eventemitter.mjs"; | ||
| } | ||
| async _sendLogsBatch(payload) { | ||
| const serialized = JSON.stringify(payload); | ||
| const url = `${this.host}/i/v1/logs?token=${encodeURIComponent(this.apiKey)}`; | ||
| const gzippedPayload = this.disableCompression ? null : await gzipCompress(serialized, this.isDebug); | ||
| const fetchOptions = { | ||
| method: 'POST', | ||
| headers: { | ||
| ...this.getCustomHeaders(), | ||
| 'Content-Type': 'application/json', | ||
| ...null !== gzippedPayload && { | ||
| 'Content-Encoding': 'gzip' | ||
| } | ||
| }, | ||
| body: gzippedPayload || serialized | ||
| }; | ||
| try { | ||
| await this.fetchWithRetry(url, fetchOptions, { | ||
| retryCheck: (err)=>{ | ||
| if (isPostHogFetchContentTooLargeError(err)) return false; | ||
| return isPostHogFetchError(err); | ||
| } | ||
| }); | ||
| return { | ||
| kind: 'ok' | ||
| }; | ||
| } catch (err) { | ||
| if (isPostHogFetchContentTooLargeError(err)) return { | ||
| kind: 'too-large' | ||
| }; | ||
| if (err instanceof PostHogFetchNetworkError) return { | ||
| kind: 'retry-later', | ||
| error: err | ||
| }; | ||
| return { | ||
| kind: 'fatal', | ||
| error: err | ||
| }; | ||
| } | ||
| } | ||
| async fetchWithRetry(url, options, retryOptions, requestTimeout) { | ||
@@ -610,0 +649,0 @@ const body = options.body ? options.body : ''; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../src/testing/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAEhC,eAAO,MAAM,IAAI,GAAU,GAAG,MAAM,KAAG,OAAO,CAAC,IAAI,CAElD,CAAA;AAED,eAAO,MAAM,eAAe,QAAa,OAAO,CAAC,IAAI,CAQpD,CAAA;AAED,eAAO,MAAM,SAAS,GAAI,UAAU,GAAG,KAAG,GAIzC,CAAA;AAED,eAAO,MAAM,uBAAuB,GAAI,CAAC,OAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAM5E,CAAA;AAED,eAAO,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,OAAO,CAAC,IAAI,CAI9C,CAAA;AAED,eAAO,MAAM,gBAAgB,QAAO,MAQnC,CAAA"} | ||
| {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../src/testing/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAEhC,eAAO,MAAM,IAAI,GAAU,GAAG,MAAM,KAAG,OAAO,CAAC,IAAI,CAElD,CAAA;AAED,eAAO,MAAM,eAAe,QAAa,OAAO,CAAC,IAAI,CAQpD,CAAA;AAED,eAAO,MAAM,SAAS,GAAI,UAAU,GAAG,KAAG,GAIzC,CAAA;AAED,eAAO,MAAM,uBAAuB,GAAI,CAAC,OAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAM5E,CAAA;AAED,eAAO,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,OAAO,CAAC,IAAI,CAI9C,CAAA;AAED,eAAO,MAAM,gBAAgB,QAAO,MASnC,CAAA"} |
@@ -63,2 +63,3 @@ "use strict"; | ||
| const createMockLogger = ()=>({ | ||
| debug: jest.fn((...args)=>console.debug(...args)), | ||
| info: jest.fn((...args)=>console.log(...args)), | ||
@@ -65,0 +66,0 @@ warn: jest.fn((...args)=>console.warn(...args)), |
@@ -30,2 +30,3 @@ const wait = async (t)=>{ | ||
| const createMockLogger = ()=>({ | ||
| debug: jest.fn((...args)=>console.debug(...args)), | ||
| info: jest.fn((...args)=>console.log(...args)), | ||
@@ -32,0 +33,0 @@ warn: jest.fn((...args)=>console.warn(...args)), |
+31
-2
@@ -321,2 +321,11 @@ export type PostHogCoreOptions = { | ||
| }; | ||
| /** | ||
| * Logs feature remote config. When a map, `captureConsoleLogs` (boolean) | ||
| * is the local opt-in flag for `console.*` autocapture (read by the JS | ||
| * SDK's `PostHogLogs` extension to decide whether to load the autocapture | ||
| * bundle). | ||
| */ | ||
| logs?: boolean | { | ||
| [key: string]: JsonType; | ||
| }; | ||
| }; | ||
@@ -538,6 +547,21 @@ export type FeatureFlagValue = string | boolean; | ||
| } | ||
| export interface SurveyTranslation { | ||
| name?: string; | ||
| thankYouMessageHeader?: string; | ||
| thankYouMessageDescription?: string; | ||
| thankYouMessageCloseButtonText?: string; | ||
| } | ||
| export interface SurveyQuestionTranslation { | ||
| question?: string; | ||
| description?: string | null; | ||
| buttonText?: string; | ||
| link?: string | null; | ||
| lowerBoundLabel?: string; | ||
| upperBoundLabel?: string; | ||
| choices?: string[]; | ||
| } | ||
| type SurveyQuestionBase = { | ||
| question: string; | ||
| id: string; | ||
| description?: string; | ||
| description?: string | null; | ||
| descriptionContentType?: SurveyQuestionDescriptionContentType; | ||
@@ -549,2 +573,3 @@ optional?: boolean; | ||
| validation?: SurveyValidationRule[]; | ||
| translations?: Record<string, SurveyQuestionTranslation>; | ||
| }; | ||
@@ -556,3 +581,3 @@ export type BasicSurveyQuestion = SurveyQuestionBase & { | ||
| type: SurveyQuestionType.Link; | ||
| link?: string; | ||
| link?: string | null; | ||
| }; | ||
@@ -608,2 +633,4 @@ export type RatingSurveyQuestion = SurveyQuestionBase & { | ||
| }; | ||
| export type SurveyResponseValue = string | number | string[] | null; | ||
| export type SurveyResponses = Record<string, SurveyResponseValue>; | ||
| export type SurveyCallback = (surveys: Survey[]) => void; | ||
@@ -646,2 +673,3 @@ export declare enum SurveyMatchType { | ||
| type: SurveyType; | ||
| translations?: Record<string, SurveyTranslation>; | ||
| feature_flag_keys?: { | ||
@@ -705,2 +733,3 @@ key: string; | ||
| export type Logger = { | ||
| debug: (...args: any[]) => void; | ||
| info: (...args: any[]) => void; | ||
@@ -707,0 +736,0 @@ warn: (...args: any[]) => void; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} |
| import { Logger } from '../types'; | ||
| type ConsoleLike = { | ||
| debug: (...args: any[]) => void; | ||
| log: (...args: any[]) => void; | ||
| warn: (...args: any[]) => void; | ||
| error: (...args: any[]) => void; | ||
| debug: (...args: any[]) => void; | ||
| }; | ||
@@ -8,0 +8,0 @@ export declare const _createLogger: (prefix: string, maybeCall: (fn: () => void) => void, consoleLike: ConsoleLike) => Logger; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAGjC,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC7B,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC9B,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC/B,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;CAChC,CAAA;AAYD,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,WAAW,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,EACnC,aAAa,WAAW,KACvB,MA6BF,CAAA;AAID,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,GAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAkB,UAE7F"} | ||
| {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAGjC,KAAK,WAAW,GAAG;IACjB,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC/B,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC7B,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC9B,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;CAChC,CAAA;AAYD,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,WAAW,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,EACnC,aAAa,WAAW,KACvB,MAiCF,CAAA;AAID,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,GAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAkB,UAE7F"} |
@@ -47,2 +47,5 @@ "use strict"; | ||
| const logger = { | ||
| debug: (...args)=>{ | ||
| _log('debug', ...args); | ||
| }, | ||
| info: (...args)=>{ | ||
@@ -49,0 +52,0 @@ _log('log', ...args); |
@@ -18,2 +18,5 @@ function createConsole(consoleLike = console) { | ||
| const logger = { | ||
| debug: (...args)=>{ | ||
| _log('debug', ...args); | ||
| }, | ||
| info: (...args)=>{ | ||
@@ -20,0 +23,0 @@ _log('log', ...args); |
+26
-2
| { | ||
| "name": "@posthog/core", | ||
| "version": "1.27.9", | ||
| "version": "1.28.0", | ||
| "license": "MIT", | ||
@@ -8,2 +8,21 @@ "main": "dist/index.js", | ||
| "types": "dist/index.d.ts", | ||
| "typesVersions": { | ||
| "*": { | ||
| "error-tracking": [ | ||
| "dist/error-tracking/index.d.ts" | ||
| ], | ||
| "surveys": [ | ||
| "dist/surveys/index.d.ts" | ||
| ], | ||
| "testing": [ | ||
| "dist/testing/index.d.ts" | ||
| ], | ||
| "utils": [ | ||
| "dist/utils/index.d.ts" | ||
| ], | ||
| "vendor/*": [ | ||
| "dist/vendor/*.d.ts" | ||
| ] | ||
| } | ||
| }, | ||
| "repository": { | ||
@@ -40,2 +59,7 @@ "type": "git", | ||
| }, | ||
| "./surveys": { | ||
| "types": "./dist/surveys/index.d.ts", | ||
| "require": "./dist/surveys/index.js", | ||
| "import": "./dist/surveys/index.mjs" | ||
| }, | ||
| "./utils": { | ||
@@ -48,3 +72,3 @@ "types": "./dist/utils/index.d.ts", | ||
| "dependencies": { | ||
| "@posthog/types": "1.372.5" | ||
| "@posthog/types": "1.372.6" | ||
| }, | ||
@@ -51,0 +75,0 @@ "devDependencies": { |
+12
-1
@@ -14,3 +14,14 @@ export { getFeatureFlagValue } from './featureFlagUtils' | ||
| export { PostHogLogs } from './logs' | ||
| export type { BufferedLogEntry, PostHogLogsConfig, ResolvedPostHogLogsConfig } from './logs/types' | ||
| export type { | ||
| BeforeSendLogFn, | ||
| BufferedLogEntry, | ||
| CaptureLogger, | ||
| LogSdkContext, | ||
| PostHogLogsConfig, | ||
| ResolvedPostHogLogsConfig, | ||
| } from './logs/types' | ||
| // Re-export the user-facing OTLP log types straight from `@posthog/types` | ||
| // via the `logs/types` barrel so consumers don't have to import from two | ||
| // packages to type their `captureLog` calls. | ||
| export type { CaptureLogOptions, LogAttributeValue, LogAttributes, LogSeverityLevel } from './logs/types' | ||
| export { uuidv7 } from './vendor/uuidv7' | ||
@@ -17,0 +28,0 @@ export * from './posthog-core' |
+891
-17
| import { PostHogPersistedProperty } from '../types' | ||
| import type { Logger } from '../types' | ||
| import { PostHogLogs } from './index' | ||
| import type { BufferedLogEntry, PostHogLogsConfig, ResolvedPostHogLogsConfig } from './types' | ||
| import type { BufferedLogEntry, ResolvedPostHogLogsConfig } from './types' | ||
| // Default resolved config for tests — mirrors what each SDK would build by | ||
| // merging user config onto its own defaults. Test-only fixture; the real | ||
| // defaults live per-SDK. | ||
| // defaults live per-SDK. Takes the resolved (flat) shape directly so tests | ||
| // can override `maxLogsPerInterval` / `rateCapWindowMs` without going through | ||
| // the public `rateCap: { maxLogs, windowMs }` wrapper. | ||
| const DEFAULT_MAX_BUFFER_SIZE = 100 | ||
| const resolveForTest = (partial?: PostHogLogsConfig): ResolvedPostHogLogsConfig => ({ | ||
| const DEFAULT_FLUSH_INTERVAL_MS = 10000 | ||
| const DEFAULT_MAX_BATCH_RECORDS_PER_POST = 50 | ||
| const DEFAULT_RATE_CAP_WINDOW_MS = 10000 | ||
| const DEFAULT_BACKGROUND_FLUSH_BUDGET_MS = 25000 | ||
| const DEFAULT_TERMINATION_FLUSH_BUDGET_MS = 2000 | ||
| const resolveForTest = (partial?: Partial<ResolvedPostHogLogsConfig>): ResolvedPostHogLogsConfig => ({ | ||
| ...partial, | ||
| maxBufferSize: partial?.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE, | ||
| flushIntervalMs: partial?.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS, | ||
| maxBatchRecordsPerPost: partial?.maxBatchRecordsPerPost ?? DEFAULT_MAX_BATCH_RECORDS_PER_POST, | ||
| rateCapWindowMs: partial?.rateCapWindowMs ?? DEFAULT_RATE_CAP_WINDOW_MS, | ||
| backgroundFlushBudgetMs: DEFAULT_BACKGROUND_FLUSH_BUDGET_MS, | ||
| terminationFlushBudgetMs: DEFAULT_TERMINATION_FLUSH_BUDGET_MS, | ||
| // Uncapped by default so existing tests aren't affected. The rate-limit | ||
| // describe block opts in explicitly via { maxLogsPerInterval: N }. | ||
| maxLogsPerInterval: partial?.maxLogsPerInterval, | ||
| }) | ||
@@ -23,2 +38,4 @@ | ||
| getSessionId: jest.fn(() => 'sess-456'), | ||
| getLibraryId: jest.fn(() => 'posthog-core-tests'), | ||
| getLibraryVersion: jest.fn(() => '0.0.0-test'), | ||
| getPersistedProperty: jest.fn((key: string) => store[key]), | ||
@@ -32,2 +49,3 @@ setPersistedProperty: jest.fn((key: string, value: any) => { | ||
| }), | ||
| _sendLogsBatch: jest.fn(() => Promise.resolve({ kind: 'ok' })), | ||
| addPendingPromise: jest.fn(<T>(promise: Promise<T>) => promise), | ||
@@ -46,2 +64,3 @@ _store: store, | ||
| const logger: any = { | ||
| debug: jest.fn(), | ||
| info: jest.fn(), | ||
@@ -193,6 +212,6 @@ warn: jest.fn(), | ||
| it('is a no-op when config.enabled is false', () => { | ||
| it('captures unconditionally — only optedOut, missing body, and beforeSend can drop', () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ enabled: false }), | ||
| resolveForTest(), | ||
| logger, | ||
@@ -202,14 +221,2 @@ getContextFor(mockInstance), | ||
| ) | ||
| logs.captureLog({ body: 'should be dropped' }) | ||
| expect(readQueue(mockInstance)).toHaveLength(0) | ||
| }) | ||
| it('captures when config is provided with enabled undefined (defaults to true)', () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({}), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'kept' }) | ||
@@ -365,2 +372,869 @@ expect(readQueue(mockInstance)).toHaveLength(1) | ||
| }) | ||
| describe('flush', () => { | ||
| it('is a no-op when the queue is empty', async () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest(), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| await logs.flush() | ||
| expect(mockInstance._sendLogsBatch).not.toHaveBeenCalled() | ||
| }) | ||
| it('drains the queue and sends an OTLP payload with resource + scope attrs', async () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ serviceName: 'my-service', environment: 'prod', serviceVersion: '1.2.3' }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'one' }) | ||
| logs.captureLog({ body: 'two' }) | ||
| await logs.flush() | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1) | ||
| const payload = mockInstance._sendLogsBatch.mock.calls[0][0] | ||
| const resourceAttrs = Object.fromEntries( | ||
| payload.resourceLogs[0].resource.attributes.map((a: any) => [a.key, a.value]) | ||
| ) | ||
| expect(resourceAttrs['service.name']).toEqual({ stringValue: 'my-service' }) | ||
| expect(resourceAttrs['deployment.environment']).toEqual({ stringValue: 'prod' }) | ||
| expect(resourceAttrs['service.version']).toEqual({ stringValue: '1.2.3' }) | ||
| // OTLP-standard SDK identification — pulled from the instance's | ||
| // getLibraryId/Version so every SDK self-identifies. | ||
| expect(resourceAttrs['telemetry.sdk.name']).toEqual({ stringValue: 'posthog-core-tests' }) | ||
| expect(resourceAttrs['telemetry.sdk.version']).toEqual({ stringValue: '0.0.0-test' }) | ||
| const scope = payload.resourceLogs[0].scopeLogs[0].scope | ||
| expect(scope).toEqual({ name: 'posthog-core-tests', version: '0.0.0-test' }) | ||
| const bodies = payload.resourceLogs[0].scopeLogs[0].logRecords.map((r: any) => r.body.stringValue) | ||
| expect(bodies).toEqual(['one', 'two']) | ||
| expect(readQueue(mockInstance)).toHaveLength(0) | ||
| }) | ||
| it('defaults service.name to "unknown_service" when not configured', async () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest(), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'hi' }) | ||
| await logs.flush() | ||
| const attrs = Object.fromEntries( | ||
| mockInstance._sendLogsBatch.mock.calls[0][0].resourceLogs[0].resource.attributes.map((a: any) => [ | ||
| a.key, | ||
| a.value, | ||
| ]) | ||
| ) | ||
| expect(attrs['service.name']).toEqual({ stringValue: 'unknown_service' }) | ||
| }) | ||
| it('SDK-controlled telemetry.sdk.* and service.name win over user resourceAttributes', async () => { | ||
| // Most logs backends index on these keys for routing, SDK-version | ||
| // dashboards, and bug-correlation. Letting a stray user key clobber | ||
| // them silently breaks ingestion attribution, so the layout puts | ||
| // user attrs first and SDK identity attrs on top. | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ | ||
| resourceAttributes: { | ||
| 'telemetry.sdk.name': 'my-wrapper', | ||
| 'service.name': 'user-supplied-service', | ||
| // Non-protected user keys still pass through. | ||
| 'host.name': 'my-host', | ||
| }, | ||
| }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'hi' }) | ||
| await logs.flush() | ||
| const attrs = Object.fromEntries( | ||
| mockInstance._sendLogsBatch.mock.calls[0][0].resourceLogs[0].resource.attributes.map((a: any) => [ | ||
| a.key, | ||
| a.value, | ||
| ]) | ||
| ) | ||
| expect(attrs['telemetry.sdk.name']).toEqual({ stringValue: 'posthog-core-tests' }) | ||
| expect(attrs['telemetry.sdk.version']).toEqual({ stringValue: '0.0.0-test' }) | ||
| expect(attrs['service.name']).toEqual({ stringValue: 'unknown_service' }) | ||
| expect(attrs['host.name']).toEqual({ stringValue: 'my-host' }) | ||
| }) | ||
| it('splits a large queue into multiple batches of maxBatchRecordsPerPost and persists after each', async () => { | ||
| const sendOrder: number[] = [] | ||
| let persistCallsBeforeSecondSend = 0 | ||
| mockInstance._sendLogsBatch = jest.fn(async (payload: any) => { | ||
| // Record the persist count *at the start of* send #2. The first send | ||
| // must have already persisted its queue advance by then — otherwise a | ||
| // crash between sends could double-send the first batch. | ||
| if (sendOrder.length === 1) { | ||
| persistCallsBeforeSecondSend = mockInstance.setPersistedProperty.mock.calls.length | ||
| } | ||
| sendOrder.push(payload.resourceLogs[0].scopeLogs[0].logRecords.length) | ||
| return { kind: 'ok' } | ||
| }) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxBatchRecordsPerPost: 2, maxBufferSize: 10 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| for (let i = 0; i < 5; i++) { | ||
| logs.captureLog({ body: `msg-${i}` }) | ||
| } | ||
| await logs.flush() | ||
| // 2 + 2 + 1 = 5 records across 3 POSTs | ||
| expect(sendOrder).toEqual([2, 2, 1]) | ||
| // After the first send, the queue must have been persisted before the second send — | ||
| // otherwise a crash between sends could double-send the first batch. | ||
| expect(persistCallsBeforeSecondSend).toBeGreaterThan(5 /* enqueue writes */) | ||
| expect(readQueue(mockInstance)).toHaveLength(0) | ||
| }) | ||
| it('halves maxBatchRecordsPerPost and retries the same records on too-large outcome', async () => { | ||
| const sendSizes: number[] = [] | ||
| mockInstance._sendLogsBatch = jest.fn(async (payload: any) => { | ||
| const size = payload.resourceLogs[0].scopeLogs[0].logRecords.length | ||
| sendSizes.push(size) | ||
| if (sendSizes.length === 1) { | ||
| return { kind: 'too-large' } | ||
| } | ||
| return { kind: 'ok' } | ||
| }) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxBatchRecordsPerPost: 4, maxBufferSize: 10 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| for (let i = 0; i < 4; i++) { | ||
| logs.captureLog({ body: `msg-${i}` }) | ||
| } | ||
| await logs.flush() | ||
| // First POST: 4 records → too-large. Retry with halved cap = 2, so: 2 + 2. | ||
| expect(sendSizes).toEqual([4, 2, 2]) | ||
| expect(readQueue(mockInstance)).toHaveLength(0) | ||
| }) | ||
| it('ramps maxBatchRecordsPerPost back toward the configured cap after a healthy streak', async () => { | ||
| // Reproduces the Greptile P1 concern: a one-off oversized payload | ||
| // should not permanently degrade throughput. After a 413 halves the | ||
| // cap, each healthy send grows it back by 1 until the configured | ||
| // maximum is reached. | ||
| const sendSizes: number[] = [] | ||
| mockInstance._sendLogsBatch = jest.fn(async (payload: any) => { | ||
| const size = payload.resourceLogs[0].scopeLogs[0].logRecords.length | ||
| sendSizes.push(size) | ||
| // First POST is rejected as too-large; everything else succeeds. | ||
| if (sendSizes.length === 1) { | ||
| return { kind: 'too-large' } | ||
| } | ||
| return { kind: 'ok' } | ||
| }) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxBatchRecordsPerPost: 4, maxBufferSize: 100 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| // Enqueue plenty so the recovery has room to ramp. | ||
| for (let i = 0; i < 16; i++) { | ||
| logs.captureLog({ body: `msg-${i}` }) | ||
| } | ||
| await logs.flush() | ||
| // First POST: 4 records → too-large. Cap halves to 2. From there each | ||
| // healthy send grows the cap by 1 toward the configured 4: | ||
| // sizes: [4 (413), 2, 3, 4, 4, ...] (the trailing 3 drains the | ||
| // remainder of the 16-record queue). | ||
| expect(sendSizes).toEqual([4, 2, 3, 4, 4, 3]) | ||
| expect(readQueue(mockInstance)).toHaveLength(0) | ||
| }) | ||
| it('drops the only record when too-large arrives on a batch of size 1', async () => { | ||
| mockInstance._sendLogsBatch = jest.fn(() => Promise.resolve({ kind: 'too-large' })) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxBatchRecordsPerPost: 1 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'too-big' }) | ||
| await logs.flush() | ||
| // Batch of 1 that's rejected as too-large is permanent — drop it rather | ||
| // than spin on the same record forever. | ||
| expect(readQueue(mockInstance)).toHaveLength(0) | ||
| }) | ||
| it('warns explicitly when dropping a size-1 413 (visibility for the lost record)', async () => { | ||
| mockInstance._sendLogsBatch = jest.fn(() => Promise.resolve({ kind: 'too-large' })) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxBatchRecordsPerPost: 1 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'oversized' }) | ||
| await logs.flush() | ||
| expect(logger.warn).toHaveBeenCalledWith( | ||
| expect.stringContaining('Dropping a single log record after 413 with batch size 1') | ||
| ) | ||
| }) | ||
| it('keeps draining the queue after a size-1 413 drop (one bad record does not stall the pipeline)', async () => { | ||
| // First record returns too-large with size 1 (drops and warns), then | ||
| // the rest of the queue should continue flushing normally. | ||
| let callCount = 0 | ||
| mockInstance._sendLogsBatch = jest.fn(() => { | ||
| callCount++ | ||
| return Promise.resolve(callCount === 1 ? { kind: 'too-large' } : { kind: 'ok' }) | ||
| }) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxBatchRecordsPerPost: 1, maxBufferSize: 10 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'oversized' }) | ||
| logs.captureLog({ body: 'ok-1' }) | ||
| logs.captureLog({ body: 'ok-2' }) | ||
| await logs.flush() | ||
| // Three sends: oversized (dropped), ok-1, ok-2. Queue is empty. | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(3) | ||
| expect(readQueue(mockInstance)).toHaveLength(0) | ||
| }) | ||
| it('size-1 413 retry-shrink path: starts at maxBatchRecordsPerPost, halves to 1, drops at 1', async () => { | ||
| // Realistic flow: batch=N gets too-large, halves to N/2, halves to 1, | ||
| // then 413 on size 1 is the permanent drop. Verifies the cap actually | ||
| // shrinks all the way down before the size-1 drop fires. | ||
| const sendSizes: number[] = [] | ||
| mockInstance._sendLogsBatch = jest.fn(async (payload: any) => { | ||
| const size = payload.resourceLogs[0].scopeLogs[0].logRecords.length | ||
| sendSizes.push(size) | ||
| return { kind: 'too-large' } | ||
| }) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxBatchRecordsPerPost: 4, maxBufferSize: 10 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| // Single oversized record. With maxBatchRecordsPerPost=4 but only 1 record | ||
| // in the queue, the first send is size 1 — going straight to the drop path. | ||
| logs.captureLog({ body: 'huge' }) | ||
| await logs.flush() | ||
| // Single send of size 1, dropped immediately (no halving rounds because | ||
| // batch was already at 1). | ||
| expect(sendSizes).toEqual([1]) | ||
| expect(readQueue(mockInstance)).toHaveLength(0) | ||
| expect(logger.warn).toHaveBeenCalledWith( | ||
| expect.stringContaining('Dropping a single log record after 413 with batch size 1') | ||
| ) | ||
| }) | ||
| it('keeps records in the queue on retry-later outcome and re-throws the carried error', async () => { | ||
| const netErr = new Error('offline') | ||
| mockInstance._sendLogsBatch = jest.fn(() => Promise.resolve({ kind: 'retry-later', error: netErr })) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest(), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'queued' }) | ||
| await expect(logs.flush()).rejects.toBe(netErr) | ||
| expect(readQueue(mockInstance)).toHaveLength(1) | ||
| }) | ||
| it('drops the batch on fatal outcome and re-throws the carried error', async () => { | ||
| const bogus = new Error('malformed') | ||
| mockInstance._sendLogsBatch = jest.fn(() => Promise.resolve({ kind: 'fatal', error: bogus })) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest(), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'doomed' }) | ||
| await expect(logs.flush()).rejects.toBe(bogus) | ||
| expect(readQueue(mockInstance)).toHaveLength(0) | ||
| }) | ||
| it('awaits _waitForStoragePersist between batches so a crash can’t replay records', async () => { | ||
| const sequence: string[] = [] | ||
| mockInstance._sendLogsBatch = jest.fn(async (payload: any) => { | ||
| sequence.push(`send:${payload.resourceLogs[0].scopeLogs[0].logRecords.length}`) | ||
| return { kind: 'ok' } | ||
| }) | ||
| const waitForStoragePersist = jest.fn(async () => { | ||
| sequence.push('waitForPersist') | ||
| }) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxBatchRecordsPerPost: 2, maxBufferSize: 10 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady, | ||
| waitForStoragePersist | ||
| ) | ||
| for (let i = 0; i < 3; i++) { | ||
| logs.captureLog({ body: `msg-${i}` }) | ||
| } | ||
| await logs.flush() | ||
| // Send 2 → waitForPersist → send 1 → waitForPersist. If the wait | ||
| // landed out-of-order (e.g. before send), a crash mid-batch could | ||
| // replay records on the next startup. | ||
| expect(sequence).toEqual(['send:2', 'waitForPersist', 'send:1', 'waitForPersist']) | ||
| expect(waitForStoragePersist).toHaveBeenCalledTimes(2) | ||
| }) | ||
| it('serializes concurrent flush calls rather than racing them', async () => { | ||
| let resolveFirst: (v: any) => void = () => {} | ||
| mockInstance._sendLogsBatch = jest.fn( | ||
| () => | ||
| new Promise((r) => { | ||
| resolveFirst = r | ||
| }) | ||
| ) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest(), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'a' }) | ||
| const first = logs.flush() | ||
| const second = logs.flush() | ||
| // Both callers observe the same in-flight promise, so only one POST happens. | ||
| resolveFirst({ kind: 'ok' }) | ||
| await Promise.all([first, second]) | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1) | ||
| }) | ||
| }) | ||
| describe('flush triggers', () => { | ||
| beforeEach(() => jest.useFakeTimers()) | ||
| afterEach(() => jest.useRealTimers()) | ||
| it('fires a flush when the buffer hits maxBufferSize', () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxBufferSize: 3 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'a' }) | ||
| logs.captureLog({ body: 'b' }) | ||
| expect(mockInstance._sendLogsBatch).not.toHaveBeenCalled() | ||
| logs.captureLog({ body: 'c' }) | ||
| // Threshold trigger fires `flush()` fire-and-forget; the call happens | ||
| // synchronously on the hot path. | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1) | ||
| }) | ||
| it('schedules one timer per idle window and fires flush on expiry', () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ flushIntervalMs: 5000 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'first' }) | ||
| logs.captureLog({ body: 'second' }) | ||
| logs.captureLog({ body: 'third' }) | ||
| // Only one timer armed, not three — subsequent enqueues inside the | ||
| // window must not push the flush out. | ||
| expect(mockInstance._sendLogsBatch).not.toHaveBeenCalled() | ||
| jest.advanceTimersByTime(4999) | ||
| expect(mockInstance._sendLogsBatch).not.toHaveBeenCalled() | ||
| jest.advanceTimersByTime(1) | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1) | ||
| }) | ||
| it('does not schedule a timer for the threshold-triggered path', () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxBufferSize: 2, flushIntervalMs: 5000 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'a' }) | ||
| logs.captureLog({ body: 'b' }) | ||
| // Threshold path flushed already; advancing time must not trigger a second send. | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1) | ||
| jest.advanceTimersByTime(5000) | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1) | ||
| }) | ||
| }) | ||
| describe('shutdown', () => { | ||
| beforeEach(() => jest.useFakeTimers()) | ||
| afterEach(() => jest.useRealTimers()) | ||
| it('drains the queue and clears any armed flush timer', async () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ flushIntervalMs: 5000 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'a' }) | ||
| // A timer is now armed — shutdown must cancel it so the process can | ||
| // exit cleanly even if the final flush triggers a duplicate send. | ||
| await logs.shutdown() | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1) | ||
| // Advancing past the original interval must not produce a second flush. | ||
| jest.advanceTimersByTime(10000) | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1) | ||
| }) | ||
| it('swallows flush errors so shutdown can complete', async () => { | ||
| mockInstance._sendLogsBatch = jest.fn(() => Promise.resolve({ kind: 'fatal', error: new Error('boom') })) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest(), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'doomed' }) | ||
| await expect(logs.shutdown()).resolves.toBeUndefined() | ||
| }) | ||
| it('is a no-op when the queue is empty and no timer is armed', async () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest(), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| await logs.shutdown() | ||
| expect(mockInstance._sendLogsBatch).not.toHaveBeenCalled() | ||
| }) | ||
| it('called twice is idempotent (second call is a no-op once queue drains)', async () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest(), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'x' }) | ||
| await logs.shutdown() | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1) | ||
| // Queue is empty now — a second shutdown shouldn't re-send. | ||
| await logs.shutdown() | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1) | ||
| }) | ||
| it('while a flush is in flight, the shared promise coordinates a single drain', async () => { | ||
| let resolveFirst: (v: any) => void = () => {} | ||
| mockInstance._sendLogsBatch = jest.fn( | ||
| () => | ||
| new Promise((r) => { | ||
| resolveFirst = r | ||
| }) | ||
| ) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest(), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'a' }) | ||
| // Real timers only here — shutdown(timeoutMs) path uses safeSetTimeout, | ||
| // which is incompatible with the default `jest.useFakeTimers()`. | ||
| jest.useRealTimers() | ||
| const flushP = logs.flush() | ||
| const shutdownP = logs.shutdown() | ||
| resolveFirst({ kind: 'ok' }) | ||
| await Promise.all([flushP, shutdownP]) | ||
| // Both callers joined the same in-flight flush — no double-send. | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1) | ||
| }) | ||
| it('races the final flush against timeoutMs so a stalled send does not hang shutdown', async () => { | ||
| jest.useRealTimers() | ||
| // _sendLogsBatch never resolves — the budget must force shutdown to return. | ||
| mockInstance._sendLogsBatch = jest.fn(() => new Promise(() => {})) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest(), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'stuck' }) | ||
| const start = Date.now() | ||
| await logs.shutdown(30) | ||
| const elapsed = Date.now() - start | ||
| // Loose upper bound — just prove we didn't wait forever. | ||
| expect(elapsed).toBeLessThan(500) | ||
| }) | ||
| it('propagates a _waitForStoragePersist rejection out of flush (so callers can react)', async () => { | ||
| const persistErr = new Error('disk is gone') | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest(), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady, | ||
| // Persist fails AFTER the HTTP send succeeds — records were sent but | ||
| // the queue-advance didn't reach disk. Surface the error so the | ||
| // caller knows a retry on restart may re-send. | ||
| () => Promise.reject(persistErr) | ||
| ) | ||
| logs.captureLog({ body: 'sent-but-not-persisted' }) | ||
| await expect(logs.flush()).rejects.toBe(persistErr) | ||
| }) | ||
| }) | ||
| describe('beforeSend hook', () => { | ||
| // Helper that hides the constructor boilerplate so the table-driven | ||
| // cases below can be a single line of setup each. | ||
| const makeLogs = (beforeSend: PostHogLogsConfig['beforeSend']): PostHogLogs => | ||
| new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ beforeSend }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| // Cases that share a "captureLog → assert queue body" shape. Bespoke | ||
| // assertions (logger expectations, throw-doesn't-crash, post-chain | ||
| // continuation after throw) live in their own `it` blocks below — those | ||
| // were warping the table when forced into it. | ||
| type Case = { | ||
| name: string | ||
| beforeSend: PostHogLogsConfig['beforeSend'] | ||
| input: string | ||
| expectedQueueLen: number | ||
| expectedBody?: string | ||
| } | ||
| const cases: Case[] = [ | ||
| { | ||
| name: 'transforms body when fn returns mutated value', | ||
| beforeSend: (r) => ({ ...r, body: r.body.toUpperCase() }), | ||
| input: 'hello', | ||
| expectedQueueLen: 1, | ||
| expectedBody: 'HELLO', | ||
| }, | ||
| { | ||
| name: 'drops the record when fn returns null', | ||
| beforeSend: () => null, | ||
| input: 'silent', | ||
| expectedQueueLen: 0, | ||
| }, | ||
| { | ||
| name: 'chains an array left-to-right (each fn sees previous result)', | ||
| beforeSend: [ | ||
| (r) => ({ ...r, body: `${r.body}-1` }), | ||
| (r) => ({ ...r, body: `${r.body}-2` }), | ||
| (r) => ({ ...r, body: `${r.body}-3` }), | ||
| ], | ||
| input: 'x', | ||
| expectedQueueLen: 1, | ||
| expectedBody: 'x-1-2-3', | ||
| }, | ||
| { | ||
| name: 'short-circuits the chain when any fn returns null', | ||
| beforeSend: [(r) => r, () => null, (r) => r], | ||
| input: 'dropped', | ||
| expectedQueueLen: 0, | ||
| }, | ||
| { | ||
| name: 'treats an empty body returned by beforeSend as a drop', | ||
| beforeSend: (r) => ({ ...r, body: '' }), | ||
| input: 'will-be-emptied', | ||
| expectedQueueLen: 0, | ||
| }, | ||
| ] | ||
| it.each(cases)('$name', ({ beforeSend, input, expectedQueueLen, expectedBody }) => { | ||
| const logs = makeLogs(beforeSend) | ||
| logs.captureLog({ body: input }) | ||
| const queue = readQueue(mockInstance) | ||
| expect(queue).toHaveLength(expectedQueueLen) | ||
| if (expectedBody !== undefined) { | ||
| expect(queue[0].record.body.stringValue).toBe(expectedBody) | ||
| } | ||
| }) | ||
| it('logs an info line when a fn returns null', () => { | ||
| // Carved out because the table only asserts queue shape; this | ||
| // verifies the diagnostic path that warns the user a record was | ||
| // dropped by their filter (no other knob to surface that). | ||
| const logs = makeLogs(() => null) | ||
| logs.captureLog({ body: 'silent' }) | ||
| expect(logger.info).toHaveBeenCalledWith('Log was rejected in beforeSend function') | ||
| }) | ||
| it('never crashes the caller when a fn throws — the chain continues with the prior result', () => { | ||
| // Bespoke: needs to verify (a) no throw escapes captureLog, (b) the | ||
| // chain continues with the previous result so a buggy filter degrades | ||
| // to a no-op, and (c) the failure is logged. Doesn't fit the table. | ||
| const thrower = jest.fn(() => { | ||
| throw new Error('bad filter') | ||
| }) | ||
| const after = jest.fn((r: any) => ({ ...r, body: `${r.body}!` })) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ beforeSend: [thrower, after] }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| expect(() => logs.captureLog({ body: 'hi' })).not.toThrow() | ||
| expect(readQueue(mockInstance)[0].record.body.stringValue).toBe('hi!') | ||
| expect(logger.error).toHaveBeenCalledWith( | ||
| expect.stringContaining('Error in beforeSend function for log:'), | ||
| expect.any(Error) | ||
| ) | ||
| }) | ||
| }) | ||
| describe('rate limiting', () => { | ||
| beforeEach(() => jest.useFakeTimers({ now: 0 })) | ||
| afterEach(() => jest.useRealTimers()) | ||
| // Tabular form for the simple in-window cap cases. Bespoke ones | ||
| // (warn-once, window-roll reset, clock-jump backward, beforeSend | ||
| // accounting) keep their own `it` blocks because they assert | ||
| // multi-window or interleaving behavior. | ||
| type CapCase = { | ||
| name: string | ||
| maxLogsPerInterval: number | undefined | ||
| capturesInWindow: number | ||
| expectedQueueLen: number | ||
| } | ||
| const capCases: CapCase[] = [ | ||
| { | ||
| name: 'is uncapped when maxLogsPerInterval is undefined (default)', | ||
| maxLogsPerInterval: undefined, | ||
| capturesInWindow: 50, | ||
| expectedQueueLen: 50, | ||
| }, | ||
| { | ||
| name: 'drops captures beyond maxLogsPerInterval within the window', | ||
| maxLogsPerInterval: 3, | ||
| capturesInWindow: 5, | ||
| expectedQueueLen: 3, | ||
| }, | ||
| ] | ||
| it.each(capCases)('$name', ({ maxLogsPerInterval, capturesInWindow, expectedQueueLen }) => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxLogsPerInterval, rateCapWindowMs: 1000 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| for (let i = 0; i < capturesInWindow; i++) { | ||
| logs.captureLog({ body: `msg-${i}` }) | ||
| } | ||
| expect(readQueue(mockInstance)).toHaveLength(expectedQueueLen) | ||
| }) | ||
| it('warns exactly once per window when dropping, regardless of how many drops', () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxLogsPerInterval: 2, rateCapWindowMs: 1000 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| for (let i = 0; i < 10; i++) { | ||
| logs.captureLog({ body: `msg-${i}` }) | ||
| } | ||
| expect(logger.warn).toHaveBeenCalledTimes(1) | ||
| expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('captureLog dropping logs')) | ||
| }) | ||
| it('resets the counter when the window rolls (and warns again on next overflow)', () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxLogsPerInterval: 1, rateCapWindowMs: 1000 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'window-1-kept' }) | ||
| logs.captureLog({ body: 'window-1-dropped' }) | ||
| expect(readQueue(mockInstance)).toHaveLength(1) | ||
| expect(logger.warn).toHaveBeenCalledTimes(1) | ||
| jest.setSystemTime(1001) | ||
| logs.captureLog({ body: 'window-2-kept' }) | ||
| logs.captureLog({ body: 'window-2-dropped' }) | ||
| expect(readQueue(mockInstance)).toHaveLength(2) | ||
| expect(logger.warn).toHaveBeenCalledTimes(2) | ||
| }) | ||
| it('resets the window when the clock jumps backward (NTP correction / manual clock change)', () => { | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxLogsPerInterval: 2, rateCapWindowMs: 1000 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| // Seed the window at t=5000, fill the budget. | ||
| jest.setSystemTime(5000) | ||
| logs.captureLog({ body: 'a' }) | ||
| logs.captureLog({ body: 'b' }) | ||
| logs.captureLog({ body: 'dropped-pre-jump' }) | ||
| expect(readQueue(mockInstance)).toHaveLength(2) | ||
| // Clock jumps backward by 1 hour (e.g. user reset device time). | ||
| // Without the `elapsed < 0` guard, the rate cap would stay "stuck" | ||
| // until `now` exceeds the old window-start again — potentially | ||
| // dropping every log for the duration of the backward jump. | ||
| jest.setSystemTime(5000 - 60 * 60 * 1000) | ||
| logs.captureLog({ body: 'accepted-post-jump' }) | ||
| expect(readQueue(mockInstance)).toHaveLength(3) | ||
| expect(readQueue(mockInstance)[2].record.body.stringValue).toBe('accepted-post-jump') | ||
| }) | ||
| it('beforeSend-rejected records do not consume the per-interval budget', () => { | ||
| // beforeSend drops the first record; rate cap is 1 per window. The | ||
| // SECOND capture should still succeed — if beforeSend consumed the | ||
| // budget, it'd be dropped. | ||
| const beforeSend = jest | ||
| .fn() | ||
| .mockReturnValueOnce(null) | ||
| .mockImplementation((r: any) => r) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxLogsPerInterval: 1, rateCapWindowMs: 1000, beforeSend }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'pre-filtered-out' }) | ||
| logs.captureLog({ body: 'should-still-fit' }) | ||
| expect(readQueue(mockInstance)).toHaveLength(1) | ||
| expect(readQueue(mockInstance)[0].record.body.stringValue).toBe('should-still-fit') | ||
| }) | ||
| }) | ||
| describe('concurrent capture during flush', () => { | ||
| it('mid-flush captures land in the queue for the next cycle — not lost, not double-sent', async () => { | ||
| let resolveSend: (v: any) => void = () => {} | ||
| let captureDuringSend: (() => void) | null = null | ||
| mockInstance._sendLogsBatch = jest.fn( | ||
| () => | ||
| new Promise((r) => { | ||
| if (captureDuringSend) { | ||
| captureDuringSend() | ||
| captureDuringSend = null | ||
| } | ||
| resolveSend = (v) => r(v) | ||
| }) | ||
| ) | ||
| const logs = new PostHogLogs( | ||
| mockInstance, | ||
| resolveForTest({ maxBatchRecordsPerPost: 1, maxBufferSize: 10 }), | ||
| logger, | ||
| getContextFor(mockInstance), | ||
| immediateOnReady | ||
| ) | ||
| logs.captureLog({ body: 'first' }) | ||
| captureDuringSend = (): void => { | ||
| logs.captureLog({ body: 'mid-flight' }) | ||
| } | ||
| const flushP = logs.flush() | ||
| await new Promise((r) => setImmediate(r)) | ||
| resolveSend({ kind: 'ok' }) | ||
| await flushP | ||
| // flush() uses `originalQueueLength` at entry, so a mid-flight capture | ||
| // is intentionally left for the NEXT flush (matches events semantics). | ||
| // The invariant we care about: not lost, not double-sent. | ||
| expect(readQueue(mockInstance)).toHaveLength(1) | ||
| expect(readQueue(mockInstance)[0].record.body.stringValue).toBe('mid-flight') | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1) | ||
| // A subsequent flush picks it up — no data lost. | ||
| const flushP2 = logs.flush() | ||
| await new Promise((r) => setImmediate(r)) | ||
| resolveSend({ kind: 'ok' }) | ||
| await flushP2 | ||
| expect(readQueue(mockInstance)).toHaveLength(0) | ||
| expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2) | ||
| }) | ||
| }) | ||
| }) |
+337
-13
@@ -1,11 +0,36 @@ | ||
| import type { LogSdkContext } from '@posthog/types' | ||
| import { buildOtlpLogRecord } from './logs-utils' | ||
| import type { LogAttributeValue } from '@posthog/types' | ||
| import { buildOtlpLogRecord, buildOtlpLogsPayload } from './logs-utils' | ||
| import { Logger, PostHogPersistedProperty } from '../types' | ||
| import type { PostHogCoreStateless } from '../posthog-core-stateless' | ||
| import type { BufferedLogEntry, CaptureLogOptions, ResolvedPostHogLogsConfig } from './types' | ||
| import { isArray, safeSetTimeout } from '../utils' | ||
| import type { | ||
| BeforeSendLogFn, | ||
| BufferedLogEntry, | ||
| CaptureLogOptions, | ||
| LogSdkContext, | ||
| ResolvedPostHogLogsConfig, | ||
| } from './types' | ||
| export class PostHogLogs { | ||
| private _localEnabled: boolean | ||
| private _maxBufferSize: number | ||
| private _flushIntervalMs: number | ||
| // Mutable — halved on 413 to shrink the next POST, and ramped back up by | ||
| // one record after each successful send so a one-off oversized payload | ||
| // (e.g. a giant stack trace) doesn't permanently degrade throughput. | ||
| private _maxBatchRecordsPerPost: number | ||
| private _flushTimer?: ReturnType<typeof safeSetTimeout> | ||
| // Serializes concurrent flushes — the second caller awaits the first rather | ||
| // than racing it and double-sending the same head-of-queue records. | ||
| private _flushPromise: Promise<void> | null = null | ||
| // Fixed-window rate cap. Tumbling (not sliding) for cheap arithmetic on the | ||
| // hot path. Window rolls the first time `captureLog` fires after the window | ||
| // expires — no background timer needed. `_droppedWarned` keeps the log noise | ||
| // to one line per window regardless of how many records got dropped. | ||
| private _rateCapWindowMs: number | ||
| private _maxLogsPerInterval?: number | ||
| private _intervalWindowStart = 0 | ||
| private _intervalLogCount = 0 | ||
| private _droppedWarned = false | ||
| constructor( | ||
@@ -16,12 +41,18 @@ private readonly _instance: PostHogCoreStateless, | ||
| private readonly _getContext: () => LogSdkContext, | ||
| private readonly _onReady: (fn: () => void) => void | ||
| private readonly _onReady: (fn: () => void) => void, | ||
| // Waits for the logs-storage persist to hit disk. Called between batches | ||
| // so a crash after a successful HTTP send but before the queue-advance | ||
| // reaches disk can't cause duplicate records on next startup. SDKs with | ||
| // synchronous storage (or no async persist layer) can pass a no-op. RN | ||
| // wires this to its dedicated `_logsStorage.waitForPersist()`. | ||
| private readonly _waitForStoragePersist: () => Promise<void> = () => Promise.resolve() | ||
| ) { | ||
| this._localEnabled = _config.enabled !== false | ||
| this._maxBufferSize = _config.maxBufferSize | ||
| this._flushIntervalMs = _config.flushIntervalMs | ||
| this._maxBatchRecordsPerPost = _config.maxBatchRecordsPerPost | ||
| this._rateCapWindowMs = _config.rateCapWindowMs | ||
| this._maxLogsPerInterval = _config.maxLogsPerInterval | ||
| } | ||
| captureLog(options: CaptureLogOptions): void { | ||
| if (!this._localEnabled) { | ||
| return | ||
| } | ||
| if (this._instance.optedOut) { | ||
@@ -34,6 +65,21 @@ return | ||
| // Ordering: beforeSend → rate cap → OTLP build. beforeSend runs first so | ||
| // user-dropped records don't consume the per-interval budget. | ||
| const filtered = this._runBeforeSend(options) | ||
| if (filtered === null) { | ||
| return | ||
| } | ||
| // beforeSend could return a record with empty body — treat as drop. | ||
| if (!filtered.body) { | ||
| return | ||
| } | ||
| if (!this._checkRateLimit()) { | ||
| return | ||
| } | ||
| // Build before deferring so attributes reflect state at capture time, not | ||
| // at drain time (identity/session changes between capture and drain must | ||
| // not corrupt recorded attributes). | ||
| const record = buildOtlpLogRecord(options, this._getContext()) | ||
| const record = buildOtlpLogRecord(filtered, this._getContext()) | ||
| const entry: BufferedLogEntry = { record } | ||
@@ -44,6 +90,199 @@ | ||
| /** | ||
| * Runs the configured `beforeSend` hook(s) on a capture record: | ||
| * - single fn OR array of fns (chain, left-to-right) | ||
| * - returning `null` drops the record (logged at info) | ||
| * - a thrown error is logged and the chain *continues* with the previous | ||
| * result — a buggy user filter must never crash the caller's | ||
| * `captureLog()` call | ||
| */ | ||
| private _runBeforeSend(options: CaptureLogOptions): CaptureLogOptions | null { | ||
| const beforeSend = this._config.beforeSend | ||
| if (!beforeSend) { | ||
| return options | ||
| } | ||
| const fns = isArray(beforeSend) ? beforeSend : [beforeSend] | ||
| let result: CaptureLogOptions = options | ||
| for (const fn of fns) { | ||
| try { | ||
| const next = fn(result) | ||
| if (!next) { | ||
| this._logger.info(`Log was rejected in beforeSend function`) | ||
| return null | ||
| } | ||
| result = next | ||
| } catch (e) { | ||
| // Swallow the throw — the chain continues with `result` unchanged so | ||
| // a buggy filter degrades to a no-op rather than crashing the app. | ||
| this._logger.error(`Error in beforeSend function for log:`, e) | ||
| } | ||
| } | ||
| return result | ||
| } | ||
| /** | ||
| * Returns `true` if this capture fits within the current rate-cap window, | ||
| * `false` if it should be dropped. | ||
| * | ||
| * Fixed (tumbling) window: the counter resets the first time `captureLog` | ||
| * fires after `rateCapWindowMs` has elapsed — no timer needed. | ||
| * `maxLogsPerInterval === undefined` means unbounded. | ||
| * | ||
| * Wall-clock safety: if `Date.now()` jumps backward (manual device-clock | ||
| * change, big NTP correction), `elapsed` goes negative. We treat that the | ||
| * same as "window expired" and reset — otherwise the rate cap would be | ||
| * stuck until the clock caught up to the old window start, potentially | ||
| * dropping logs for hours. | ||
| * | ||
| * Pre-init note: the counter increments here, before `_onReady` defers | ||
| * `_enqueue` to the init promise. If init resolves slowly and the user is | ||
| * later opted out, the counter has already consumed budget for records | ||
| * that won't enqueue. Cosmetic — no record is "lost" beyond what's | ||
| * already gated, and the window rolls on its own. | ||
| */ | ||
| private _checkRateLimit(): boolean { | ||
| if (this._maxLogsPerInterval === undefined) { | ||
| return true | ||
| } | ||
| const now = Date.now() | ||
| const elapsed = now - this._intervalWindowStart | ||
| if (elapsed >= this._rateCapWindowMs || elapsed < 0) { | ||
| this._intervalWindowStart = now | ||
| this._intervalLogCount = 0 | ||
| this._droppedWarned = false | ||
| } | ||
| if (this._intervalLogCount >= this._maxLogsPerInterval) { | ||
| if (!this._droppedWarned) { | ||
| this._logger.warn( | ||
| `captureLog dropping logs: exceeded ${this._maxLogsPerInterval} logs per ${this._rateCapWindowMs}ms` | ||
| ) | ||
| this._droppedWarned = true | ||
| } | ||
| return false | ||
| } | ||
| this._intervalLogCount++ | ||
| return true | ||
| } | ||
| /** | ||
| * Drains `LogsQueue` in `maxBatchRecordsPerPost` slices, POSTing each as an | ||
| * OTLP payload. | ||
| * - Network error → keep items in queue, re-throw (caller retries later) | ||
| * - 413 → halve batch size, retry same records (do not advance) | ||
| * - Any other error → drop the batch (avoid infinite loop on malformed data), | ||
| * re-throw so callers can log/report | ||
| * Concurrent calls are serialized through `_flushPromise` so records at the | ||
| * head of the queue can't be sent twice. | ||
| */ | ||
| async flush(): Promise<void> { | ||
| if (this._flushPromise) { | ||
| return this._flushPromise | ||
| } | ||
| this._flushPromise = this._flushInner().finally(() => { | ||
| this._flushPromise = null | ||
| }) | ||
| return this._flushPromise | ||
| } | ||
| private async _flushInner(): Promise<void> { | ||
| this._clearFlushTimer() | ||
| let queue = this._instance.getPersistedProperty<BufferedLogEntry[]>(PostHogPersistedProperty.LogsQueue) ?? [] | ||
| if (queue.length === 0) { | ||
| return | ||
| } | ||
| const originalQueueLength = queue.length | ||
| let sentCount = 0 | ||
| while (queue.length > 0 && sentCount < originalQueueLength) { | ||
| const batchSize = Math.min(queue.length, this._maxBatchRecordsPerPost) | ||
| const batch = queue.slice(0, batchSize) | ||
| const records = batch.map((e) => e.record) | ||
| const payload = buildOtlpLogsPayload( | ||
| records, | ||
| this._buildResourceAttributes(), | ||
| this._instance.getLibraryId(), | ||
| this._instance.getLibraryVersion() | ||
| ) | ||
| const outcome = await this._instance._sendLogsBatch(payload) | ||
| if (outcome.kind === 'too-large' && batch.length > 1) { | ||
| this._maxBatchRecordsPerPost = Math.max(1, Math.floor(batch.length / 2)) | ||
| this._logger.warn( | ||
| `Received 413 when sending logs batch of size ${batch.length}, reducing batch size to ${this._maxBatchRecordsPerPost}` | ||
| ) | ||
| // Don't advance the queue — retry the same records with the smaller cap. | ||
| continue | ||
| } | ||
| if (outcome.kind === 'retry-later') { | ||
| // Network error: keep records in the queue for the next flush cycle | ||
| // and surface the error so the caller can log/react. | ||
| throw outcome.error | ||
| } | ||
| // ok | fatal | too-large-with-batch-of-1 → records are leaving the | ||
| // queue. 'fatal' and size-1 413s are dropped so we don't spin on the | ||
| // same record forever. Surface the size-1 413 explicitly so a single | ||
| // oversized record (e.g. a giant body field) is visible in logs | ||
| // instead of silently disappearing. | ||
| if (outcome.kind === 'too-large') { | ||
| this._logger.warn( | ||
| 'Dropping a single log record after 413 with batch size 1 — the record is larger than the server cap and cannot be split further.' | ||
| ) | ||
| } else if (outcome.kind === 'ok' && this._maxBatchRecordsPerPost < this._config.maxBatchRecordsPerPost) { | ||
| // Linear recovery: each healthy send pushes the cap back up by 1 | ||
| // toward the configured maximum. Linear (not doubling) so we don't | ||
| // immediately retrigger a 413 if the previous shrink was justified. | ||
| this._maxBatchRecordsPerPost = Math.min(this._config.maxBatchRecordsPerPost, this._maxBatchRecordsPerPost + 1) | ||
| } | ||
| await this._persistQueueAdvance(batch.length) | ||
| queue = this._instance.getPersistedProperty<BufferedLogEntry[]>(PostHogPersistedProperty.LogsQueue) ?? [] | ||
| sentCount += batch.length | ||
| if (outcome.kind === 'fatal') { | ||
| throw outcome.error | ||
| } | ||
| } | ||
| } | ||
| private async _persistQueueAdvance(consumed: number): Promise<void> { | ||
| // Re-read the queue in case captures landed mid-flush, then drop the head. | ||
| const refreshed = this._instance.getPersistedProperty<BufferedLogEntry[]>(PostHogPersistedProperty.LogsQueue) ?? [] | ||
| this._instance.setPersistedProperty(PostHogPersistedProperty.LogsQueue, refreshed.slice(consumed)) | ||
| // Wait for the advance to hit disk before the next batch sends, matching | ||
| // events' `flushStorage()` contract. Prevents duplicates if the app crashes | ||
| // after the HTTP success but before the queue-advance persists. | ||
| await this._waitForStoragePersist() | ||
| } | ||
| /** | ||
| * OTLP resource attributes for every batch. | ||
| * | ||
| * Layout: user `resourceAttributes` spread first, then SDK-controlled | ||
| * keys layered on top so users cannot accidentally clobber them. Most logs | ||
| * backends index on `service.name` and `telemetry.sdk.*` for routing, | ||
| * SDK-version dashboards, and bug-correlation; letting a stray user key | ||
| * overwrite them silently breaks ingestion attribution. The dedicated | ||
| * `serviceName` / `environment` / `serviceVersion` config fields are the | ||
| * supported way to override `service.name` / `deployment.environment` / | ||
| * `service.version`. | ||
| */ | ||
| private _buildResourceAttributes(): Record<string, LogAttributeValue> { | ||
| return { | ||
| ...this._config.resourceAttributes, | ||
| 'service.name': this._config.serviceName || 'unknown_service', | ||
| ...(this._config.environment && { 'deployment.environment': this._config.environment }), | ||
| ...(this._config.serviceVersion && { 'service.version': this._config.serviceVersion }), | ||
| 'telemetry.sdk.name': this._instance.getLibraryId(), | ||
| 'telemetry.sdk.version': this._instance.getLibraryVersion(), | ||
| } | ||
| } | ||
| private _enqueue(entry: BufferedLogEntry): void { | ||
| // Re-check: optedOut can flip between captureLog and here — preload may | ||
| // have hydrated the real persisted value, or optIn/optOut may have fired | ||
| // while this fn was deferred. | ||
| // Re-check optedOut: preload may have hydrated the real persisted value, | ||
| // or optIn/optOut may have fired while this fn was deferred. | ||
| if (this._instance.optedOut) { | ||
@@ -60,3 +299,88 @@ return | ||
| this._instance.setPersistedProperty(PostHogPersistedProperty.LogsQueue, queue) | ||
| // Threshold trigger: at-capacity means flushing now reclaims space before | ||
| // the next capture has to shift something out. | ||
| if (queue.length >= this._maxBufferSize) { | ||
| this._flushInBackground() | ||
| return | ||
| } | ||
| // Timer trigger: only arm one timer at a time. A subsequent enqueue within | ||
| // the window shouldn't reschedule — that would keep pushing the flush out. | ||
| if (!this._flushTimer) { | ||
| this._flushTimer = safeSetTimeout(() => { | ||
| this._flushTimer = undefined | ||
| this._flushInBackground() | ||
| }, this._flushIntervalMs) | ||
| } | ||
| } | ||
| /** | ||
| * Stops the timer-based flush and sends anything still in the queue. | ||
| * Intended for process-teardown paths (RN `_shutdown` override). Swallows | ||
| * errors so a failing final flush can't block the broader shutdown. | ||
| * | ||
| * If `timeoutMs` is provided, the final flush races against that budget so | ||
| * a slow network/storage can't hold up shutdown indefinitely. Without it, | ||
| * flush time is bounded only by `fetchRetryCount * (requestTimeout + | ||
| * fetchRetryDelay)`, which can exceed the caller's shutdown SLA. | ||
| */ | ||
| async shutdown(timeoutMs?: number): Promise<void> { | ||
| this._clearFlushTimer() | ||
| const flushPromise = this.flush().catch(() => { | ||
| // Best-effort: a logs-flush failure during shutdown is not actionable | ||
| // and must not prevent the rest of shutdown from running. Errors are | ||
| // still surfaced from the regular `flush()` path in normal operation. | ||
| }) | ||
| if (timeoutMs === undefined) { | ||
| await flushPromise | ||
| return | ||
| } | ||
| await Promise.race([flushPromise, new Promise<void>((resolve) => safeSetTimeout(resolve, timeoutMs))]) | ||
| } | ||
| /** | ||
| * Time-bounded flush for transient lifecycle events (e.g. RN | ||
| * foreground→background) that must complete inside an OS-imposed window. | ||
| * Unlike `shutdown`, this leaves the periodic flush timer in place so the | ||
| * pipeline keeps draining if the process is resumed instead of suspended. | ||
| * | ||
| * Errors propagate so the host SDK can route them through its standard | ||
| * lifecycle error handler (e.g. RN's `logFlushError`). If the timer wins | ||
| * the race, a late rejection from the in-flight flush is silenced via a | ||
| * no-op handler attached after the race settles, to avoid noisy | ||
| * unhandled-rejection logs — the next regular flush cycle will retry. | ||
| */ | ||
| async flushWithTimeout(timeoutMs: number): Promise<void> { | ||
| let timedOut = false | ||
| const flushPromise = this.flush() | ||
| const timerPromise = new Promise<void>((resolve) => | ||
| safeSetTimeout(() => { | ||
| timedOut = true | ||
| resolve() | ||
| }, timeoutMs) | ||
| ) | ||
| try { | ||
| await Promise.race([flushPromise, timerPromise]) | ||
| } finally { | ||
| if (timedOut) { | ||
| // Race lost — flush is still in flight. Attach a no-op rejection | ||
| // handler so a late failure isn't logged as unhandled. | ||
| void flushPromise.catch(() => {}) | ||
| } | ||
| } | ||
| } | ||
| private _flushInBackground(): void { | ||
| void this.flush().catch((err) => { | ||
| this._logger.error('PostHog logs flush failed:', err) | ||
| }) | ||
| } | ||
| private _clearFlushTimer(): void { | ||
| if (this._flushTimer) { | ||
| clearTimeout(this._flushTimer) | ||
| this._flushTimer = undefined | ||
| } | ||
| } | ||
| } |
@@ -1,2 +0,3 @@ | ||
| import type { CaptureLogOptions, LogSdkContext, LogSeverityLevel } from '@posthog/types' | ||
| import type { CaptureLogOptions, LogSeverityLevel } from '@posthog/types' | ||
| import type { LogSdkContext } from './types' | ||
| import { | ||
@@ -3,0 +4,0 @@ buildOtlpLogRecord, |
| import type { | ||
| CaptureLogOptions, | ||
| LogAttributeValue, | ||
| LogSdkContext, | ||
| LogSeverityLevel, | ||
@@ -13,2 +12,3 @@ OtlpAnyValue, | ||
| } from '@posthog/types' | ||
| import type { LogSdkContext } from './types' | ||
| import { isArray, isBoolean, isNull, isUndefined } from '../utils' | ||
@@ -15,0 +15,0 @@ |
+150
-25
@@ -14,5 +14,24 @@ // Re-export OTLP/log types from @posthog/types so the rest of the logs module can | ||
| OtlpLogsPayload, | ||
| LogSdkContext, | ||
| } from '@posthog/types' | ||
| /** | ||
| * SDK-internal context the host SDK passes to `buildOtlpLogRecord` at capture | ||
| * time. Each SDK populates the fields that apply to it: browser fills | ||
| * `currentUrl`, mobile fills `screenName` / `appState`. Missing fields are | ||
| * omitted from the OTLP record (no stray attributes). | ||
| * | ||
| * Internal to `@posthog/core` — customers don't see this in autocomplete. | ||
| */ | ||
| export interface LogSdkContext { | ||
| distinctId?: string | ||
| sessionId?: string | ||
| /** Web-only — current page URL */ | ||
| currentUrl?: string | ||
| /** Mobile-only — current screen / view name */ | ||
| screenName?: string | ||
| /** Mobile-only — app foreground/background state at capture time */ | ||
| appState?: 'foreground' | 'background' | ||
| activeFeatureFlags?: string[] | ||
| } | ||
| // The public capture-logger interface lives in @posthog/types as `Logger`. Core | ||
@@ -26,5 +45,2 @@ // also exports a `Logger` (the SDK's internal warn/info/error logger). Alias the | ||
| // Wrapper around OtlpLogRecord for queue entries. Parallels events' queue | ||
| // item shape (`{ message }`) — future additions like `retryCount` or | ||
| // `enqueuedAt` can be added without migrating the queue format. | ||
| export interface BufferedLogEntry { | ||
@@ -34,36 +50,145 @@ record: OtlpLogRecord | ||
| // Public configuration for the logs module. Per-SDK defaults diverge (mobile | ||
| // cellular radio cost, browser tab suspension, node process lifecycle). | ||
| /** | ||
| * Pre-send filter. Inspect, mutate, or drop a captured record before it | ||
| * enters the rate-cap or the queue. Return the (possibly transformed) record | ||
| * to keep it; return `null` to drop it. | ||
| * | ||
| * Configure as a single fn or an array. Arrays form a left-to-right chain: | ||
| * each fn receives the previous fn's return value. A `null` from any link | ||
| * short-circuits the chain and drops the record. | ||
| * | ||
| * Runs *before* the rate cap so dropped records don't consume the | ||
| * per-interval budget. Throwing fns are logged and skipped — the chain | ||
| * continues with the previous return value, so a buggy filter degrades to a | ||
| * no-op rather than crashing `captureLog()`. | ||
| * | ||
| * @example Redact secrets from log bodies | ||
| * ```ts | ||
| * logs: { | ||
| * beforeSend: (record) => ({ | ||
| * ...record, | ||
| * body: record.body.replace(/api_key=\S+/g, 'api_key=[REDACTED]'), | ||
| * }), | ||
| * } | ||
| * ``` | ||
| * | ||
| * @example Drop noisy debug logs in production | ||
| * ```ts | ||
| * logs: { | ||
| * beforeSend: (record) => (record.level === 'debug' ? null : record), | ||
| * } | ||
| * ``` | ||
| */ | ||
| export type BeforeSendLogFn = (record: CaptureLogOptions) => CaptureLogOptions | null | ||
| /** | ||
| * Configuration for the logs feature on `new PostHog(key, { logs: ... })`. | ||
| * All fields are optional; per-SDK defaults apply (mobile vs browser tune | ||
| * differently for cellular cost vs tab-suspension behavior). | ||
| */ | ||
| export interface PostHogLogsConfig { | ||
| // Master switch. Default: true when a config object is provided. | ||
| enabled?: boolean | ||
| /** | ||
| * Service name attached to every record as the OTLP `service.name` | ||
| * resource attribute. Used by the Logs UI for filtering / grouping. | ||
| * Default: `'unknown_service'`. | ||
| */ | ||
| serviceName?: string | ||
| // Resource attributes | ||
| serviceName?: string | ||
| /** | ||
| * Service version attached as OTLP `service.version`. Useful for | ||
| * correlating regressions to specific app releases. | ||
| */ | ||
| serviceVersion?: string | ||
| /** | ||
| * Deployment environment attached as OTLP `deployment.environment` | ||
| * (e.g. `'production'`, `'staging'`, `'dev'`). | ||
| */ | ||
| environment?: string | ||
| /** | ||
| * Extra OTLP resource attributes attached to every record. Spread first; | ||
| * SDK-controlled keys (`service.name`, `telemetry.sdk.*`, RN's `os.*`) | ||
| * are layered on top so users cannot accidentally clobber them. Use the | ||
| * dedicated `serviceName` / `environment` / `serviceVersion` fields to | ||
| * override those keys. | ||
| */ | ||
| resourceAttributes?: Record<string, LogAttributeValue> | ||
| // Buffering | ||
| /** | ||
| * How often the periodic background flush fires (ms). Records also flush | ||
| * eagerly when the buffer fills, on AppState changes (RN), and on | ||
| * `shutdown()`. Lower values trade battery/bandwidth for fresher data. | ||
| * Default: 10000 (RN) / 3000 (browser). | ||
| */ | ||
| flushIntervalMs?: number | ||
| rateCapWindowMs?: number // separate from flushIntervalMs so flush cadence does not move the rate-cap window | ||
| /** | ||
| * Max records held in memory before the queue evicts the oldest on push | ||
| * (FIFO). Bounds memory footprint and on-disk-queue size. When the buffer | ||
| * hits this size, an immediate flush is triggered to reclaim space; if | ||
| * the flush hasn't completed before the next capture, the oldest record | ||
| * is shifted out. Default: 100. | ||
| */ | ||
| maxBufferSize?: number | ||
| maxLogsPerInterval?: number | ||
| maxBatchRecordsPerPost?: number // keeps each POST under the 2 MB server cap | ||
| // Shutdown — separate budgets because foreground→background and app-terminate | ||
| // have different OS-imposed windows. | ||
| backgroundFlushBudgetMs?: number | ||
| terminationFlushBudgetMs?: number | ||
| /** | ||
| * Max records per outbound POST. Keeps each request under the server's | ||
| * 2 MB cap. On a 413 response, the SDK halves this value, retries the | ||
| * same records, then ramps back up by 1 per healthy send. A 413 on a | ||
| * single-record batch drops the record (it's larger than the server can | ||
| * accept regardless of batch size). Default: 50 (RN) / 100 (browser). | ||
| */ | ||
| maxBatchRecordsPerPost?: number | ||
| // Filtering. Runs synchronously before the rate cap so beforeSend-dropped | ||
| // records do not consume the per-interval budget. | ||
| beforeSend?: (record: CaptureLogOptions) => CaptureLogOptions | null | ||
| /** | ||
| * Tumbling-window rate cap. Bounds how many records can be captured | ||
| * within a sliding (technically tumbling) time window. Records exceeding | ||
| * the cap are dropped synchronously at `captureLog()` (they never enter | ||
| * the buffer or consume bandwidth). A single warn line is logged per | ||
| * window when the cap is hit. | ||
| * | ||
| * Defaults are per-SDK; on RN the default is `{ maxLogs: 500, windowMs: | ||
| * 10000 }` (≈50 logs/sec ceiling, tuned for cellular bandwidth). | ||
| * | ||
| * @example Allow brief bursts up to 1000/min | ||
| * ```ts | ||
| * logs: { rateCap: { maxLogs: 1000, windowMs: 60000 } } | ||
| * ``` | ||
| * | ||
| * @example Disable the cap entirely (unbounded) | ||
| * ```ts | ||
| * logs: { rateCap: { maxLogs: undefined } } | ||
| * ``` | ||
| */ | ||
| rateCap?: { | ||
| /** | ||
| * Max records accepted per `windowMs` window. `undefined` = unbounded. | ||
| */ | ||
| maxLogs?: number | ||
| /** | ||
| * Window length in ms. Tumbling, not sliding — the counter resets the | ||
| * first time a capture fires after the window expires. | ||
| */ | ||
| windowMs?: number | ||
| } | ||
| /** | ||
| * Pre-send filter. See {@link BeforeSendLogFn} for shape and examples. | ||
| * Configure as a single function or a chain. | ||
| */ | ||
| beforeSend?: BeforeSendLogFn | BeforeSendLogFn[] | ||
| } | ||
| // Fields PostHogLogs needs resolved at runtime. Each SDK supplies its own | ||
| // defaults (mobile, browser, node have different right answers) and hands the | ||
| // filled-in config to the PostHogLogs constructor. | ||
| export interface ResolvedPostHogLogsConfig extends PostHogLogsConfig { | ||
| // Fields PostHogLogs needs resolved at runtime. The host SDK fills in its | ||
| // defaults and hands the resolved config to the PostHogLogs constructor. | ||
| // Flat names internally — public API uses `rateCap: { maxLogs, windowMs }`. | ||
| export interface ResolvedPostHogLogsConfig extends Omit<PostHogLogsConfig, 'rateCap'> { | ||
| maxBufferSize: number | ||
| flushIntervalMs: number | ||
| maxBatchRecordsPerPost: number | ||
| rateCapWindowMs: number | ||
| maxLogsPerInterval?: number | ||
| backgroundFlushBudgetMs: number | ||
| terminationFlushBudgetMs: number | ||
| } |
@@ -0,1 +1,2 @@ | ||
| import type { OtlpLogsPayload } from '@posthog/types' | ||
| import { SimpleEventEmitter } from './eventemitter' | ||
@@ -97,2 +98,19 @@ import { getFeatureFlagValue, normalizeFlagsResponse } from './featureFlagUtils' | ||
| /** | ||
| * Outcome of a logs batch send. Keeps HTTP error classification inside core | ||
| * (single source of truth — same policy events already use in `_flush()`) so | ||
| * PostHogLogs doesn't need to know about specific error types. | ||
| * | ||
| * - ok → records are accepted; drop them from the queue | ||
| * - too-large → 413; caller should halve batch size and retry same records | ||
| * - retry-later → network error; caller keeps records and retries next cycle | ||
| * - fatal → anything else (auth, malformed, etc.); caller drops the | ||
| * batch and surfaces the error | ||
| */ | ||
| export type SendLogsBatchOutcome = | ||
| | { kind: 'ok' } | ||
| | { kind: 'too-large' } | ||
| | { kind: 'retry-later'; error: unknown } | ||
| | { kind: 'fatal'; error: unknown } | ||
| export enum QuotaLimitedFeature { | ||
@@ -1183,2 +1201,48 @@ FeatureFlags = 'feature_flags', | ||
| /** | ||
| * Sends a pre-built OTLP logs payload to `/i/v1/logs`. Returns a tagged | ||
| * outcome instead of throwing so PostHogLogs doesn't have to know about the | ||
| * core's error class hierarchy. Error classification lives here (single | ||
| * source of truth, same policy the events `_flush()` uses for its own | ||
| * 413 / network / fatal handling). | ||
| * | ||
| * 413 is passed through as `too-large` (not auto-retried) so the caller can | ||
| * shrink `maxBatchRecordsPerPost` and retry the same records. | ||
| */ | ||
| async _sendLogsBatch(payload: OtlpLogsPayload): Promise<SendLogsBatchOutcome> { | ||
| const serialized = JSON.stringify(payload) | ||
| const url = `${this.host}/i/v1/logs?token=${encodeURIComponent(this.apiKey)}` | ||
| const gzippedPayload = !this.disableCompression ? await gzipCompress(serialized, this.isDebug) : null | ||
| const fetchOptions: PostHogFetchOptions = { | ||
| method: 'POST', | ||
| headers: { | ||
| ...this.getCustomHeaders(), | ||
| 'Content-Type': 'application/json', | ||
| ...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }), | ||
| }, | ||
| body: gzippedPayload || serialized, | ||
| } | ||
| try { | ||
| await this.fetchWithRetry(url, fetchOptions, { | ||
| retryCheck: (err) => { | ||
| if (isPostHogFetchContentTooLargeError(err)) { | ||
| return false | ||
| } | ||
| return isPostHogFetchError(err) | ||
| }, | ||
| }) | ||
| return { kind: 'ok' } | ||
| } catch (err) { | ||
| if (isPostHogFetchContentTooLargeError(err)) { | ||
| return { kind: 'too-large' } | ||
| } | ||
| if (err instanceof PostHogFetchNetworkError) { | ||
| return { kind: 'retry-later', error: err } | ||
| } | ||
| return { kind: 'fatal', error: err } | ||
| } | ||
| } | ||
| private async fetchWithRetry( | ||
@@ -1185,0 +1249,0 @@ url: string, |
@@ -39,2 +39,3 @@ import { Logger } from '@/types' | ||
| return { | ||
| debug: jest.fn((...args) => console.debug(...args)), | ||
| info: jest.fn((...args) => console.log(...args)), | ||
@@ -41,0 +42,0 @@ warn: jest.fn((...args) => console.warn(...args)), |
+38
-2
@@ -347,2 +347,14 @@ export type PostHogCoreOptions = { | ||
| } | ||
| /** | ||
| * Logs feature remote config. When a map, `captureConsoleLogs` (boolean) | ||
| * is the local opt-in flag for `console.*` autocapture (read by the JS | ||
| * SDK's `PostHogLogs` extension to decide whether to load the autocapture | ||
| * bundle). | ||
| */ | ||
| logs?: | ||
| | boolean | ||
| | { | ||
| [key: string]: JsonType | ||
| } | ||
| } | ||
@@ -610,6 +622,23 @@ | ||
| export interface SurveyTranslation { | ||
| name?: string | ||
| thankYouMessageHeader?: string | ||
| thankYouMessageDescription?: string | ||
| thankYouMessageCloseButtonText?: string | ||
| } | ||
| export interface SurveyQuestionTranslation { | ||
| question?: string | ||
| description?: string | null | ||
| buttonText?: string | ||
| link?: string | null | ||
| lowerBoundLabel?: string | ||
| upperBoundLabel?: string | ||
| choices?: string[] | ||
| } | ||
| type SurveyQuestionBase = { | ||
| question: string | ||
| id: string | ||
| description?: string | ||
| description?: string | null | ||
| descriptionContentType?: SurveyQuestionDescriptionContentType | ||
@@ -621,2 +650,3 @@ optional?: boolean | ||
| validation?: SurveyValidationRule[] | ||
| translations?: Record<string, SurveyQuestionTranslation> | ||
| } | ||
@@ -630,3 +660,3 @@ | ||
| type: SurveyQuestionType.Link | ||
| link?: string | ||
| link?: string | null | ||
| } | ||
@@ -693,2 +723,6 @@ | ||
| export type SurveyResponseValue = string | number | string[] | null | ||
| export type SurveyResponses = Record<string, SurveyResponseValue> | ||
| export type SurveyCallback = (surveys: Survey[]) => void | ||
@@ -736,2 +770,3 @@ | ||
| type: SurveyType | ||
| translations?: Record<string, SurveyTranslation> | ||
| feature_flag_keys?: { | ||
@@ -799,2 +834,3 @@ key: string | ||
| export type Logger = { | ||
| debug: (...args: any[]) => void | ||
| info: (...args: any[]) => void | ||
@@ -801,0 +837,0 @@ warn: (...args: any[]) => void |
@@ -5,6 +5,6 @@ import { Logger } from '../types' | ||
| type ConsoleLike = { | ||
| debug: (...args: any[]) => void | ||
| log: (...args: any[]) => void | ||
| warn: (...args: any[]) => void | ||
| error: (...args: any[]) => void | ||
| debug: (...args: any[]) => void | ||
| } | ||
@@ -27,3 +27,3 @@ | ||
| ): Logger => { | ||
| function _log(level: 'log' | 'warn' | 'error', ...args: any[]) { | ||
| function _log(level: 'debug' | 'log' | 'warn' | 'error', ...args: any[]) { | ||
| maybeCall(() => { | ||
@@ -36,2 +36,6 @@ const consoleMethod = consoleLike[level] | ||
| const logger: Logger = { | ||
| debug: (...args: any[]) => { | ||
| _log('debug', ...args) | ||
| }, | ||
| info: (...args: any[]) => { | ||
@@ -38,0 +42,0 @@ _log('log', ...args) |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
1008836
16.15%278
6.51%23745
15.26%34
-2.86%+ Added
- Removed
Updated