From 26722d73ae6af455de04a414158f05b75c13c21e Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:03:14 -0700 Subject: [PATCH 01/19] Init package files --- packages/launchdarkly/.eslintignore | 2 + packages/launchdarkly/.eslintrc.js | 39 +++ packages/launchdarkly/.gitignore | 4 + packages/launchdarkly/LICENSE | 21 ++ packages/launchdarkly/README.md | 25 ++ packages/launchdarkly/package.json | 72 +++++ .../launchdarkly/rollup.bundle.config.mjs | 12 + packages/launchdarkly/rollup.npm.config.mjs | 19 ++ .../scripts/shim-preact-export.js | 75 ++++++ packages/launchdarkly/src/core/integration.ts | 0 packages/launchdarkly/src/index.ts | 9 + packages/launchdarkly/test.setup.ts | 255 ++++++++++++++++++ packages/launchdarkly/tsconfig.json | 8 + packages/launchdarkly/tsconfig.test.json | 15 ++ packages/launchdarkly/tsconfig.types.json | 10 + packages/launchdarkly/vitest.config.ts | 12 + .../replay-internal/src/types/launchdarkly.ts | 4 + 17 files changed, 582 insertions(+) create mode 100644 packages/launchdarkly/.eslintignore create mode 100644 packages/launchdarkly/.eslintrc.js create mode 100644 packages/launchdarkly/.gitignore create mode 100644 packages/launchdarkly/LICENSE create mode 100644 packages/launchdarkly/README.md create mode 100644 packages/launchdarkly/package.json create mode 100644 packages/launchdarkly/rollup.bundle.config.mjs create mode 100644 packages/launchdarkly/rollup.npm.config.mjs create mode 100644 packages/launchdarkly/scripts/shim-preact-export.js create mode 100644 packages/launchdarkly/src/core/integration.ts create mode 100644 packages/launchdarkly/src/index.ts create mode 100644 packages/launchdarkly/test.setup.ts create mode 100644 packages/launchdarkly/tsconfig.json create mode 100644 packages/launchdarkly/tsconfig.test.json create mode 100644 packages/launchdarkly/tsconfig.types.json create mode 100644 packages/launchdarkly/vitest.config.ts create mode 100644 packages/replay-internal/src/types/launchdarkly.ts diff --git a/packages/launchdarkly/.eslintignore b/packages/launchdarkly/.eslintignore new file mode 100644 index 000000000000..b38db2f296ff --- /dev/null +++ b/packages/launchdarkly/.eslintignore @@ -0,0 +1,2 @@ +node_modules/ +build/ diff --git a/packages/launchdarkly/.eslintrc.js b/packages/launchdarkly/.eslintrc.js new file mode 100644 index 000000000000..dc39a10b354b --- /dev/null +++ b/packages/launchdarkly/.eslintrc.js @@ -0,0 +1,39 @@ +// Note: All paths are relative to the directory in which eslint is being run, rather than the directory where this file +// lives + +// ESLint config docs: https://eslint.org/docs/user-guide/configuring/ + +module.exports = { + extends: ['../../.eslintrc.js'], + overrides: [ + { + files: ['src/**/*.ts'], + }, + { + files: ['test.setup.ts', 'vitest.config.ts'], + parserOptions: { + project: ['tsconfig.test.json'], + }, + rules: { + 'no-console': 'off', + }, + }, + { + files: ['test/**/*.ts'], + + rules: { + // most of these errors come from `new Promise(process.nextTick)` + '@typescript-eslint/unbound-method': 'off', + // TODO: decide if we want to enable this again after the migration + // We can take the freedom to be a bit more lenient with tests + '@typescript-eslint/no-floating-promises': 'off', + }, + }, + { + files: ['src/types/deprecated.ts'], + rules: { + '@typescript-eslint/naming-convention': 'off', + }, + }, + ], +}; diff --git a/packages/launchdarkly/.gitignore b/packages/launchdarkly/.gitignore new file mode 100644 index 000000000000..363d3467c6fa --- /dev/null +++ b/packages/launchdarkly/.gitignore @@ -0,0 +1,4 @@ +node_modules +/*.tgz +.eslintcache +build diff --git a/packages/launchdarkly/LICENSE b/packages/launchdarkly/LICENSE new file mode 100644 index 000000000000..6bfafc44539c --- /dev/null +++ b/packages/launchdarkly/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/launchdarkly/README.md b/packages/launchdarkly/README.md new file mode 100644 index 000000000000..336e74da6593 --- /dev/null +++ b/packages/launchdarkly/README.md @@ -0,0 +1,25 @@ +

+ + Sentry + +

+ +# Sentry Integration for Feedback + +This SDK is **considered experimental and in a beta state**. It may experience breaking changes, and may be discontinued +at any time. Please reach out on [GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have +any feedback/concerns. + +To view Feedback in Sentry, your +[Sentry organization must be an early adopter](https://docs.sentry.io/product/accounts/early-adopter-features/). + +## Installation + +Please read the [offical integration documentation](https://docs.sentry.io/platforms/javascript/user-feedback/) for +installation instructions. + +## Configuration + +The Feedback integration is highly customizable, please read the +[official integration documentation](https://docs.sentry.io/platforms/javascript/user-feedback/configuration/) for the +most up-to-date configuration options. diff --git a/packages/launchdarkly/package.json b/packages/launchdarkly/package.json new file mode 100644 index 000000000000..d09f6f977406 --- /dev/null +++ b/packages/launchdarkly/package.json @@ -0,0 +1,72 @@ +{ + "name": "@sentry-internal/launchdarkly", + "version": "8.35.0", + "description": "Sentry SDK integration for user launchdarkly", + "repository": "git://github.com/getsentry/sentry-javascript.git", + "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/launchdarkly", + "author": "Sentry", + "license": "MIT", + "engines": { + "node": ">=14.18" + }, + "files": [ + "/build/npm" + ], + "main": "build/npm/cjs/index.js", + "module": "build/npm/esm/index.js", + "types": "build/npm/types/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "import": { + "types": "./build/npm/types/index.d.ts", + "default": "./build/npm/esm/index.js" + }, + "require": { + "types": "./build/npm/types/index.d.ts", + "default": "./build/npm/cjs/index.js" + } + } + }, + "typesVersions": { + "<4.9": { + "build/npm/types/index.d.ts": [ + "build/npm/types-ts3.8/index.d.ts" + ] + } + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sentry/core": "8.35.0", + "@sentry/types": "8.35.0", + "@sentry/utils": "8.35.0" + }, + "scripts": { + "build": "run-p build:transpile build:types build:bundle", + "build:transpile": "rollup -c rollup.npm.config.mjs", + "build:bundle": "rollup -c rollup.bundle.config.mjs", + "build:dev": "run-p build:transpile build:types", + "build:types": "run-s build:types:core build:types:downlevel", + "build:types:core": "tsc -p tsconfig.types.json", + "build:types:downlevel": "yarn downlevel-dts build/npm/types build/npm/types-ts3.8 --to ts3.8 && yarn node ./scripts/shim-preact-export.js", + "build:watch": "run-p build:transpile:watch build:bundle:watch build:types:watch", + "build:dev:watch": "run-p build:transpile:watch build:types:watch", + "build:transpile:watch": "yarn build:transpile --watch", + "build:bundle:watch": "yarn build:bundle --watch", + "build:types:watch": "tsc -p tsconfig.types.json --watch", + "build:tarball": "npm pack", + "circularDepCheck": "madge --circular src/index.ts", + "clean": "rimraf build sentry-internal-launchdarkly-*.tgz", + "fix": "eslint . --format stylish --fix", + "lint": "eslint . --format stylish", + "test": "jest", + "test:watch": "jest --watch", + "yalc:publish": "yalc publish --push --sig" + }, + "volta": { + "extends": "../../package.json" + }, + "sideEffects": false +} diff --git a/packages/launchdarkly/rollup.bundle.config.mjs b/packages/launchdarkly/rollup.bundle.config.mjs new file mode 100644 index 000000000000..cc29fadbc093 --- /dev/null +++ b/packages/launchdarkly/rollup.bundle.config.mjs @@ -0,0 +1,12 @@ +import { makeBaseBundleConfig, makeBundleConfigVariants } from '@sentry-internal/rollup-utils'; + +const baseBundleConfig = makeBaseBundleConfig({ + bundleType: 'addon', + entrypoints: ['src/index.ts'], + licenseTitle: '@sentry-internal/launchdarkly', + outputFileBase: () => 'bundles/launchdarkly', +}); + +const builds = makeBundleConfigVariants(baseBundleConfig); + +export default builds; diff --git a/packages/launchdarkly/rollup.npm.config.mjs b/packages/launchdarkly/rollup.npm.config.mjs new file mode 100644 index 000000000000..3b4431fa6829 --- /dev/null +++ b/packages/launchdarkly/rollup.npm.config.mjs @@ -0,0 +1,19 @@ +import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; + +export default makeNPMConfigVariants( + makeBaseNPMConfig({ + hasBundles: true, + packageSpecificConfig: { + output: { + // set exports to 'named' or 'auto' so that rollup doesn't warn + exports: 'named', + // set preserveModules to false because for Replay we actually want + // to bundle everything into one file. + preserveModules: + process.env.SENTRY_BUILD_PRESERVE_MODULES === undefined + ? false + : Boolean(process.env.SENTRY_BUILD_PRESERVE_MODULES), + }, + }, + }), +); diff --git a/packages/launchdarkly/scripts/shim-preact-export.js b/packages/launchdarkly/scripts/shim-preact-export.js new file mode 100644 index 000000000000..bd74e4da0a05 --- /dev/null +++ b/packages/launchdarkly/scripts/shim-preact-export.js @@ -0,0 +1,75 @@ +// preact does not support more modern TypeScript versions, which breaks our users that depend on older +// TypeScript versions. To fix this, we shim the types from preact to be any and remove the dependency on preact +// for types directly. This script is meant to be run after the build/npm/types-ts3.8 directory is created. + +// Path: build/npm/types-ts3.8/global.d.ts + +const fs = require('fs'); +const path = require('path'); + +/** + * This regex looks for preact imports we can replace and shim out. + * + * Example: + * import { ComponentChildren, VNode } from 'preact'; + */ +const preactImportRegex = /import\s*{\s*([\w\s,]+)\s*}\s*from\s*'preact'\s*;?/; + +function walk(dir) { + const files = fs.readdirSync(dir); + files.forEach(file => { + const filePath = path.join(dir, file); + const stat = fs.lstatSync(filePath); + if (stat.isDirectory()) { + walk(filePath); + } else { + if (filePath.endsWith('.d.ts')) { + const content = fs.readFileSync(filePath, 'utf8'); + const capture = preactImportRegex.exec(content); + if (capture) { + const groups = capture[1].split(',').map(s => s.trim()); + + // This generates a shim snippet to replace the type imports from preact + // It generates a snippet based on the capture groups of preactImportRegex. + // + // Example: + // + // import type { ComponentChildren, VNode } from 'preact'; + // becomes + // type ComponentChildren: any; + // type VNode: any; + const snippet = groups.reduce((acc, curr) => { + const searchableValue = curr.includes(' as ') ? curr.split(' as ')[1] : curr; + + // look to see if imported as value, then we have to use declare const + if (content.includes(`typeof ${searchableValue}`)) { + return `${acc}declare const ${searchableValue}: any;\n`; + } + + // look to see if generic type like Foo + if (content.includes(`${searchableValue}<`)) { + return `${acc}type ${searchableValue} = any;\n`; + } + + // otherwise we can just leave as type + return `${acc}type ${searchableValue} = any;\n`; + }, ''); + + // we then can remove the import from preact + const newContent = content.replace(preactImportRegex, '// replaced import from preact'); + + // and write the new content to the file + fs.writeFileSync(filePath, snippet + newContent, 'utf8'); + } + } + } + }); +} + +function run() { + // recurse through build/npm/types-ts3.8 directory + const dir = path.join('build', 'npm', 'types-ts3.8'); + walk(dir); +} + +run(); diff --git a/packages/launchdarkly/src/core/integration.ts b/packages/launchdarkly/src/core/integration.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/launchdarkly/src/index.ts b/packages/launchdarkly/src/index.ts new file mode 100644 index 000000000000..9397a3be9cc3 --- /dev/null +++ b/packages/launchdarkly/src/index.ts @@ -0,0 +1,9 @@ +// This file is used as entry point to generate the npm package and CDN bundles. + +// export { sendFeedback } from './core/sendFeedback'; +// export { buildFeedbackIntegration } from './core/integration'; +// export { getFeedback } from './core/getFeedback'; +// export { feedbackModalIntegration } from './modal/integration'; +// export { feedbackScreenshotIntegration } from './screenshot/integration'; + +export { buildLaunchDarklyIntegration } from './core/integration'; diff --git a/packages/launchdarkly/test.setup.ts b/packages/launchdarkly/test.setup.ts new file mode 100644 index 000000000000..05a762e60d50 --- /dev/null +++ b/packages/launchdarkly/test.setup.ts @@ -0,0 +1,255 @@ +import { printDiffOrStringify } from 'jest-matcher-utils'; +import { vi } from 'vitest'; +import type { Mocked, MockedFunction } from 'vitest'; + +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { getClient } from '@sentry/core'; +import type { ReplayRecordingData, Transport } from '@sentry/types'; +import * as SentryUtils from '@sentry/utils'; + +import type { ReplayContainer, Session } from './src/types'; + +type MockTransport = MockedFunction; + +vi.spyOn(SentryUtils, 'isBrowser').mockImplementation(() => true); + +type EnvelopeHeader = { + event_id: string; + sent_at: string; + sdk: { + name: string; + version?: string; + }; +}; + +type ReplayEventHeader = { type: 'replay_event' }; +type ReplayEventPayload = Record; +type RecordingHeader = { type: 'replay_recording'; length: number }; +type RecordingPayloadHeader = Record; +type SentReplayExpected = { + envelopeHeader?: EnvelopeHeader; + replayEventHeader?: ReplayEventHeader; + replayEventPayload?: ReplayEventPayload; + recordingHeader?: RecordingHeader; + recordingPayloadHeader?: RecordingPayloadHeader; + recordingData?: ReplayRecordingData; +}; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const toHaveSameSession = function (received: Mocked, expected: undefined | Session) { + const pass = this.equals(received.session?.id, expected?.id) as boolean; + + const options = { + isNot: this.isNot, + promise: this.promise, + }; + + return { + pass, + message: () => + `${this.utils.matcherHint('toHaveSameSession', undefined, undefined, options)}\n\n${printDiffOrStringify( + expected, + received.session, + 'Expected', + 'Received', + )}`, + }; +}; + +type Result = { + passed: boolean; + key: string; + expectedVal: SentReplayExpected[keyof SentReplayExpected]; + actualVal: SentReplayExpected[keyof SentReplayExpected]; +}; +type Call = [ + EnvelopeHeader, + [ + [ReplayEventHeader | undefined, ReplayEventPayload | undefined], + [RecordingHeader | undefined, RecordingPayloadHeader | undefined], + ], +]; +type CheckCallForSentReplayResult = { pass: boolean; call: Call | undefined; results: Result[] }; + +function checkCallForSentReplay( + call: Call | undefined, + expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, +): CheckCallForSentReplayResult { + const envelopeHeader = call?.[0]; + const envelopeItems = call?.[1] || [[], []]; + const [[replayEventHeader, replayEventPayload], [recordingHeader, recordingPayload] = []] = envelopeItems; + + // @ts-expect-error recordingPayload is always a string in our tests + const [recordingPayloadHeader, recordingData] = recordingPayload?.split('\n') || []; + + const actualObj: Required = { + // @ts-expect-error Custom envelope + envelopeHeader: envelopeHeader, + // @ts-expect-error Custom envelope + replayEventHeader: replayEventHeader, + // @ts-expect-error Custom envelope + replayEventPayload: replayEventPayload, + // @ts-expect-error Custom envelope + recordingHeader: recordingHeader, + recordingPayloadHeader: recordingPayloadHeader && JSON.parse(recordingPayloadHeader), + recordingData, + }; + + const isObjectContaining = expected && 'sample' in expected && 'inverse' in expected; + const expectedObj = isObjectContaining + ? (expected as { sample: SentReplayExpected }).sample + : (expected as SentReplayExpected); + + if (isObjectContaining) { + // eslint-disable-next-line no-console + console.warn('`expect.objectContaining` is unnecessary when using the `toHaveSentReplay` matcher'); + } + + const results = expected + ? Object.keys(expectedObj) + .map(key => { + const actualVal = actualObj[key as keyof SentReplayExpected]; + const expectedVal = expectedObj[key as keyof SentReplayExpected]; + const passed = !expectedVal || this.equals(actualVal, expectedVal); + + return { passed, key, expectedVal, actualVal }; + }) + .filter(({ passed }) => !passed) + : []; + + const pass = Boolean(call && (!expected || results.length === 0)); + + return { + pass, + call, + results, + }; +} + +/** + * Only want calls that send replay events, i.e. ignore error events + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function getReplayCalls(calls: any[][][]): any[][][] { + return calls + .map(call => { + const arg = call[0]; + if (arg.length !== 2) { + return []; + } + + if (!arg[1][0].find(({ type }: { type: string }) => ['replay_event', 'replay_recording'].includes(type))) { + return []; + } + + return [arg]; + }) + .filter(Boolean); +} + +/** + * Checks all calls to `fetch` and ensures a replay was uploaded by + * checking the `fetch()` request's body. + */ +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const toHaveSentReplay = function ( + _received: Mocked, + expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, +) { + const { calls } = (getClient()?.getTransport()?.send as MockTransport).mock; + + let result: CheckCallForSentReplayResult; + + const expectedKeysLength = expected + ? ('sample' in expected ? Object.keys(expected.sample) : Object.keys(expected)).length + : 0; + + const replayCalls = getReplayCalls(calls); + + for (const currentCall of replayCalls) { + result = checkCallForSentReplay.call(this, currentCall[0], expected); + if (result.pass) { + break; + } + + // stop on the first call where any of the expected obj passes + if (result.results.length < expectedKeysLength) { + break; + } + } + + // @ts-expect-error use before assigned + const { results, call, pass } = result; + + const options = { + isNot: this.isNot, + promise: this.promise, + }; + + return { + pass, + message: () => + !call + ? pass + ? 'Expected Replay to not have been sent, but a request was attempted' + : 'Expected Replay to have been sent, but a request was not attempted' + : `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results + .map(({ key, expectedVal, actualVal }: Result) => + printDiffOrStringify(expectedVal, actualVal, `Expected (key: ${key})`, `Received (key: ${key})`), + ) + .join('\n')}`, + }; +}; + +/** + * Checks the last call to `fetch` and ensures a replay was uploaded by + * checking the `fetch()` request's body. + */ +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const toHaveLastSentReplay = function ( + _received: Mocked, + expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, +) { + const { calls } = (getClient()?.getTransport()?.send as MockTransport).mock; + const replayCalls = getReplayCalls(calls); + + const lastCall = replayCalls[calls.length - 1]?.[0]; + + const { results, call, pass } = checkCallForSentReplay.call(this, lastCall, expected); + + const options = { + isNot: this.isNot, + promise: this.promise, + }; + + return { + pass, + message: () => + !call + ? pass + ? 'Expected Replay to not have been sent, but a request was attempted' + : 'Expected Replay to have last been sent, but a request was not attempted' + : `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results + .map(({ key, expectedVal, actualVal }: Result) => + printDiffOrStringify(expectedVal, actualVal, `Expected (key: ${key})`, `Received (key: ${key})`), + ) + .join('\n')}`, + }; +}; + +expect.extend({ + toHaveSameSession, + toHaveSentReplay, + toHaveLastSentReplay, +}); + +interface CustomMatchers { + toHaveSentReplay(expected?: SentReplayExpected): R; + toHaveLastSentReplay(expected?: SentReplayExpected): R; + toHaveSameSession(expected: undefined | Session): R; +} + +declare module 'vitest' { + type Assertion = CustomMatchers; + type AsymmetricMatchersContaining = CustomMatchers; +} diff --git a/packages/launchdarkly/tsconfig.json b/packages/launchdarkly/tsconfig.json new file mode 100644 index 000000000000..cd1b8207ea06 --- /dev/null +++ b/packages/launchdarkly/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "lib": ["DOM", "ES2018"], + "module": "esnext" + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/launchdarkly/tsconfig.test.json b/packages/launchdarkly/tsconfig.test.json new file mode 100644 index 000000000000..bb7130d948c0 --- /dev/null +++ b/packages/launchdarkly/tsconfig.test.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + + "include": ["test/**/*.ts", "vitest.config.ts", "test.setup.ts"], + + "compilerOptions": { + "types": ["node"], + "esModuleInterop": true, + "allowJs": true, + "noImplicitAny": true, + "noImplicitThis": false, + "strictNullChecks": true, + "strictPropertyInitialization": false + } +} diff --git a/packages/launchdarkly/tsconfig.types.json b/packages/launchdarkly/tsconfig.types.json new file mode 100644 index 000000000000..374fd9bc9364 --- /dev/null +++ b/packages/launchdarkly/tsconfig.types.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "build/npm/types" + } +} diff --git a/packages/launchdarkly/vitest.config.ts b/packages/launchdarkly/vitest.config.ts new file mode 100644 index 000000000000..976d9c37074d --- /dev/null +++ b/packages/launchdarkly/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config'; + +import baseConfig from '../../vite/vite.config'; + +export default defineConfig({ + ...baseConfig, + test: { + ...baseConfig.test, + setupFiles: ['./test.setup.ts'], + reporters: ['default'], + }, +}); diff --git a/packages/replay-internal/src/types/launchdarkly.ts b/packages/replay-internal/src/types/launchdarkly.ts new file mode 100644 index 000000000000..b06ecc734266 --- /dev/null +++ b/packages/replay-internal/src/types/launchdarkly.ts @@ -0,0 +1,4 @@ +// Integration options + +// Integration interface? + From 419cd83eaeb762a7d803f8b288c359004d9bd3c9 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:11:03 -0700 Subject: [PATCH 02/19] Revert changelog and move types file --- CHANGELOG.md | 50 ------------------- .../src/types/launchdarkly.ts | 0 2 files changed, 50 deletions(-) rename packages/{replay-internal => launchdarkly}/src/types/launchdarkly.ts (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index d584d65cff65..4bb9478862cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,56 +10,6 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -## 8.36.0 - -### Important Changes - -- **feat(nuxt): Add Sentry Pinia plugin ([#14047](https://github.com/getsentry/sentry-javascript/pull/14047))** - -The Nuxt SDK now allows you to track Pinia state for captured errors. To enable the Pinia plugin, set the `trackPinia` option to `true` in your client config: - -```ts -// sentry.client.config.ts - -Sentry.init({ - trackPinia: true, -}); -``` - -Read more about the Pinia plugin in the [Sentry Pinia Documentation](https://docs.sentry.io/platforms/javascript/guides/nuxt/features/pinia/). - -- **feat(nextjs/vercel-edge/cloudflare): Switch to OTEL for performance monitoring ([#13889](https://github.com/getsentry/sentry-javascript/pull/13889))** - -With this release, the Sentry Next.js, and Cloudflare SDKs will now capture performance data based on OpenTelemetry. -Some exceptions apply in cases where Next.js captures inaccurate data itself. - -NOTE: You may experience minor differences in transaction names in Sentry. -Most importantly transactions for serverside pages router invocations will now be named `GET /[param]/my/route` instead of `/[param]/my/route`. -This means that those transactions are now better aligned with the OpenTelemetry semantic conventions. - -### Other Changes - -- deps: Bump bundler plugins and CLI to 2.22.6 and 2.37.0 respectively ([#14050](https://github.com/getsentry/sentry-javascript/pull/14050)) -- feat(deps): bump @opentelemetry/instrumentation-aws-sdk from 0.44.0 to 0.45.0 ([#14099](https://github.com/getsentry/sentry-javascript/pull/14099)) -- feat(deps): bump @opentelemetry/instrumentation-connect from 0.39.0 to 0.40.0 ([#14101](https://github.com/getsentry/sentry-javascript/pull/14101)) -- feat(deps): bump @opentelemetry/instrumentation-express from 0.43.0 to 0.44.0 ([#14102](https://github.com/getsentry/sentry-javascript/pull/14102)) -- feat(deps): bump @opentelemetry/instrumentation-fs from 0.15.0 to 0.16.0 ([#14098](https://github.com/getsentry/sentry-javascript/pull/14098)) -- feat(deps): bump @opentelemetry/instrumentation-kafkajs from 0.3.0 to 0.4.0 ([#14100](https://github.com/getsentry/sentry-javascript/pull/14100)) -- feat(nextjs): Add method and url to route handler request data ([#14084](https://github.com/getsentry/sentry-javascript/pull/14084)) -- feat(node): Add breadcrumbs for `child_process` and `worker_thread` ([#13896](https://github.com/getsentry/sentry-javascript/pull/13896)) -- fix(core): Ensure standalone spans are not sent if SDK is disabled ([#14088](https://github.com/getsentry/sentry-javascript/pull/14088)) -- fix(nextjs): Await flush in api handlers ([#14023](https://github.com/getsentry/sentry-javascript/pull/14023)) -- fix(nextjs): Don't leak webpack types into exports ([#14116](https://github.com/getsentry/sentry-javascript/pull/14116)) -- fix(nextjs): Fix matching logic for file convention type for root level components ([#14038](https://github.com/getsentry/sentry-javascript/pull/14038)) -- fix(nextjs): Respect directives in value injection loader ([#14083](https://github.com/getsentry/sentry-javascript/pull/14083)) -- fix(nuxt): Only wrap `.mjs` entry files in rollup ([#14060](https://github.com/getsentry/sentry-javascript/pull/14060)) -- fix(nuxt): Re-export all exported bindings ([#14086](https://github.com/getsentry/sentry-javascript/pull/14086)) -- fix(nuxt): Server-side setup in readme ([#14049](https://github.com/getsentry/sentry-javascript/pull/14049)) -- fix(profiling-node): Always warn when running on incompatible major version of Node.js ([#14043](https://github.com/getsentry/sentry-javascript/pull/14043)) -- fix(replay): Fix `onError` callback ([#14002](https://github.com/getsentry/sentry-javascript/pull/14002)) -- perf(otel): Only calculate current timestamp once ([#14094](https://github.com/getsentry/sentry-javascript/pull/14094)) -- test(browser-integration): Add sentry DSN route handler by default ([#14095](https://github.com/getsentry/sentry-javascript/pull/14095)) - ## 8.35.0 ### Beta release of the official Nuxt Sentry SDK diff --git a/packages/replay-internal/src/types/launchdarkly.ts b/packages/launchdarkly/src/types/launchdarkly.ts similarity index 100% rename from packages/replay-internal/src/types/launchdarkly.ts rename to packages/launchdarkly/src/types/launchdarkly.ts From 610da4ded0774726333e0c54e583a5b8d5acc3b7 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:05:36 -0700 Subject: [PATCH 03/19] Add ld to dependencies and skeleton code. Get rid of core/ --- packages/launchdarkly/package.json | 3 +- packages/launchdarkly/src/core/integration.ts | 0 packages/launchdarkly/src/index.ts | 9 ++-- packages/launchdarkly/src/integration.ts | 43 +++++++++++++++++++ .../launchdarkly/src/types/launchdarkly.ts | 7 ++- 5 files changed, 53 insertions(+), 9 deletions(-) delete mode 100644 packages/launchdarkly/src/core/integration.ts create mode 100644 packages/launchdarkly/src/integration.ts diff --git a/packages/launchdarkly/package.json b/packages/launchdarkly/package.json index d09f6f977406..6d2a04d3ce19 100644 --- a/packages/launchdarkly/package.json +++ b/packages/launchdarkly/package.json @@ -41,7 +41,8 @@ "dependencies": { "@sentry/core": "8.35.0", "@sentry/types": "8.35.0", - "@sentry/utils": "8.35.0" + "@sentry/utils": "8.35.0", + "launchdarkly-js-client-sdk": "^3.5.0" }, "scripts": { "build": "run-p build:transpile build:types build:bundle", diff --git a/packages/launchdarkly/src/core/integration.ts b/packages/launchdarkly/src/core/integration.ts deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/packages/launchdarkly/src/index.ts b/packages/launchdarkly/src/index.ts index 9397a3be9cc3..194cee3677c0 100644 --- a/packages/launchdarkly/src/index.ts +++ b/packages/launchdarkly/src/index.ts @@ -1,9 +1,6 @@ // This file is used as entry point to generate the npm package and CDN bundles. -// export { sendFeedback } from './core/sendFeedback'; -// export { buildFeedbackIntegration } from './core/integration'; -// export { getFeedback } from './core/getFeedback'; -// export { feedbackModalIntegration } from './modal/integration'; -// export { feedbackScreenshotIntegration } from './screenshot/integration'; +export { launchDarklyIntegration } from './integration'; -export { buildLaunchDarklyIntegration } from './core/integration'; +// export type { +// } from './types'; diff --git a/packages/launchdarkly/src/integration.ts b/packages/launchdarkly/src/integration.ts new file mode 100644 index 000000000000..5e9f22fb00d7 --- /dev/null +++ b/packages/launchdarkly/src/integration.ts @@ -0,0 +1,43 @@ +import type { IntegrationFn } from '@sentry/types'; +import type { LDInspectionFlagUsedHandler } from 'launchdarkly-js-client-sdk'; +import type { LaunchDarklyOptions } from './types'; + +/** + * Sentry integration for capturing feature flags from LaunchDarkly. + * + * See the [feature flag documentation](TODO:) for more information. + * + * @example + * + * ``` + * Sentry.init({ + * dsn: '__DSN__', + * integrations: [Sentry.replayIntegration()], + * }); + * ``` + */ +export const launchDarklyIntegration = ((options?: LaunchDarklyOptions) => { + const { ldClient } = options; + + return { + name: 'launchdarkly', + + setup(client) { + // type is Sentry SDK client + + // pseudo-code + ldClient.addHandler(FlagUsedHandler()); + }, + }; +}) satisfies IntegrationFn; + +// https://launchdarkly.github.io/js-client-sdk/interfaces/LDInspectionFlagUsedHandler.html //TODO: rm this link +class FlagUsedHandler implements LDInspectionFlagUsedHandler { + public name = 'sentry-feature-flag-monitor'; // eslint-disable-line @sentry-internal/sdk/no-class-field-initializers + public synchronous?: boolean; + public type = 'flag-used' as const; // eslint-disable-line @sentry-internal/sdk/no-class-field-initializers + public method(flagKey, flagDetail, context) { + //TODO: + return; + } +} diff --git a/packages/launchdarkly/src/types/launchdarkly.ts b/packages/launchdarkly/src/types/launchdarkly.ts index b06ecc734266..efe89c01e2e0 100644 --- a/packages/launchdarkly/src/types/launchdarkly.ts +++ b/packages/launchdarkly/src/types/launchdarkly.ts @@ -1,4 +1,7 @@ -// Integration options +// TODO: could we just put everything in a types.ts file? -// Integration interface? +import type { LDClient } from 'launchdarkly-js-client-sdk'; +export type LaunchDarklyOptions = { + ldClient: LDClient; +}; From 5b2e5ec7e6c326aeefe434a822b874efd0a2b0c3 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:15:53 -0700 Subject: [PATCH 04/19] Fix readme, rename types file, bring back core/ --- packages/launchdarkly/README.md | 10 ---------- packages/launchdarkly/src/{ => core}/integration.ts | 0 packages/launchdarkly/src/index.ts | 2 +- .../src/types/{launchdarkly.ts => integration.ts} | 2 -- 4 files changed, 1 insertion(+), 13 deletions(-) rename packages/launchdarkly/src/{ => core}/integration.ts (100%) rename packages/launchdarkly/src/types/{launchdarkly.ts => integration.ts} (67%) diff --git a/packages/launchdarkly/README.md b/packages/launchdarkly/README.md index 336e74da6593..eb1e28a91017 100644 --- a/packages/launchdarkly/README.md +++ b/packages/launchdarkly/README.md @@ -10,16 +10,6 @@ This SDK is **considered experimental and in a beta state**. It may experience b at any time. Please reach out on [GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have any feedback/concerns. -To view Feedback in Sentry, your -[Sentry organization must be an early adopter](https://docs.sentry.io/product/accounts/early-adopter-features/). - ## Installation -Please read the [offical integration documentation](https://docs.sentry.io/platforms/javascript/user-feedback/) for -installation instructions. - ## Configuration - -The Feedback integration is highly customizable, please read the -[official integration documentation](https://docs.sentry.io/platforms/javascript/user-feedback/configuration/) for the -most up-to-date configuration options. diff --git a/packages/launchdarkly/src/integration.ts b/packages/launchdarkly/src/core/integration.ts similarity index 100% rename from packages/launchdarkly/src/integration.ts rename to packages/launchdarkly/src/core/integration.ts diff --git a/packages/launchdarkly/src/index.ts b/packages/launchdarkly/src/index.ts index 194cee3677c0..8952a4bc1264 100644 --- a/packages/launchdarkly/src/index.ts +++ b/packages/launchdarkly/src/index.ts @@ -1,6 +1,6 @@ // This file is used as entry point to generate the npm package and CDN bundles. -export { launchDarklyIntegration } from './integration'; +export { launchDarklyIntegration } from './core/integration'; // export type { // } from './types'; diff --git a/packages/launchdarkly/src/types/launchdarkly.ts b/packages/launchdarkly/src/types/integration.ts similarity index 67% rename from packages/launchdarkly/src/types/launchdarkly.ts rename to packages/launchdarkly/src/types/integration.ts index efe89c01e2e0..ef2f57c869cc 100644 --- a/packages/launchdarkly/src/types/launchdarkly.ts +++ b/packages/launchdarkly/src/types/integration.ts @@ -1,5 +1,3 @@ -// TODO: could we just put everything in a types.ts file? - import type { LDClient } from 'launchdarkly-js-client-sdk'; export type LaunchDarklyOptions = { From 66b1253d70aaf0318391902d2785fdbeb27bf100 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:42:54 -0700 Subject: [PATCH 05/19] Fix readme 2 --- packages/launchdarkly/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/launchdarkly/README.md b/packages/launchdarkly/README.md index eb1e28a91017..d35afcc2fce5 100644 --- a/packages/launchdarkly/README.md +++ b/packages/launchdarkly/README.md @@ -4,7 +4,7 @@

-# Sentry Integration for Feedback +# Sentry Integration for LaunchDarkly This SDK is **considered experimental and in a beta state**. It may experience breaking changes, and may be discontinued at any time. Please reach out on [GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have From 85acc6d5b5a09afdbdf4c63cd025878a85c8d352 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:33:08 -0700 Subject: [PATCH 06/19] Finish implementing, minus scope.flags --- packages/launchdarkly/src/core/integration.ts | 64 ++++++++++++++----- .../launchdarkly/src/types/integration.ts | 6 +- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/packages/launchdarkly/src/core/integration.ts b/packages/launchdarkly/src/core/integration.ts index 5e9f22fb00d7..c12130a04448 100644 --- a/packages/launchdarkly/src/core/integration.ts +++ b/packages/launchdarkly/src/core/integration.ts @@ -1,7 +1,13 @@ -import type { IntegrationFn } from '@sentry/types'; -import type { LDInspectionFlagUsedHandler } from 'launchdarkly-js-client-sdk'; +/* eslint-disable @sentry-internal/sdk/no-class-field-initializers */ + +import * as Sentry from '@sentry/browser'; +import type { Client as SentryClient, Event, EventHint, IntegrationFn } from '@sentry/types'; +import type { LDContext, LDEvaluationDetail, LDInspectionFlagUsedHandler } from 'launchdarkly-js-client-sdk'; import type { LaunchDarklyOptions } from './types'; +// import type { Client } from '/client'; +// import type { Event, EventHint } from './event'; + /** * Sentry integration for capturing feature flags from LaunchDarkly. * @@ -16,28 +22,54 @@ import type { LaunchDarklyOptions } from './types'; * }); * ``` */ -export const launchDarklyIntegration = ((options?: LaunchDarklyOptions) => { - const { ldClient } = options; +export const launchDarklyIntegration = ((_options?: LaunchDarklyOptions) => { + // const { _ldClient } = options; + // const ldClient = _ldClient as LDClient; // for type hint return { name: 'launchdarkly', - setup(client) { - // type is Sentry SDK client - - // pseudo-code - ldClient.addHandler(FlagUsedHandler()); + processEvent(event: Event, hint: EventHint, client: SentryClient): Event | null | PromiseLike { + const scope = Sentry.getCurrentScope(); // client doesn't have getCurrentScope + const flagData = { values: scope.flags.get() }; + if (event.contexts) { + event.contexts.flags = flagData; + } else { + event.contexts = { flags: flagData }; + } }, }; }) satisfies IntegrationFn; -// https://launchdarkly.github.io/js-client-sdk/interfaces/LDInspectionFlagUsedHandler.html //TODO: rm this link -class FlagUsedHandler implements LDInspectionFlagUsedHandler { - public name = 'sentry-feature-flag-monitor'; // eslint-disable-line @sentry-internal/sdk/no-class-field-initializers - public synchronous?: boolean; - public type = 'flag-used' as const; // eslint-disable-line @sentry-internal/sdk/no-class-field-initializers - public method(flagKey, flagDetail, context) { - //TODO: +/** + * https://launchdarkly.github.io/js-client-sdk/interfaces/LDInspectionFlagUsedHandler.html //TODO: rm this link + * TODO: docstring + */ +export class SentryInspector implements LDInspectionFlagUsedHandler { + public name = 'sentry-feature-flag-monitor'; + + public synchronous = true; // TODO: T or F? + + public type = 'flag-used' as const; + + /** + * TODO: docstring + */ + public method(flagKey: string, flagDetail: LDEvaluationDetail, _context: LDContext): void { + if (typeof flagDetail.value === 'boolean') { + const flags = Sentry.getCurrentScope().flags; + flags.set(flagKey, flagDetail.value); + } return; } } + +/* + +import SentryInspector from @sentry/ld + +client = LDClient.init(..., SentryInspector) + +sentry.init(integrations: [LDIntegration]) + +*/ diff --git a/packages/launchdarkly/src/types/integration.ts b/packages/launchdarkly/src/types/integration.ts index ef2f57c869cc..1d9e763b8ad2 100644 --- a/packages/launchdarkly/src/types/integration.ts +++ b/packages/launchdarkly/src/types/integration.ts @@ -1,5 +1,3 @@ -import type { LDClient } from 'launchdarkly-js-client-sdk'; +// import type { LDClient } from 'launchdarkly-js-client-sdk'; -export type LaunchDarklyOptions = { - ldClient: LDClient; -}; +export type LaunchDarklyOptions = Record; From 3b3a767fb8bf90c8919fee0047f04ed8982c2a7b Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:05:51 -0700 Subject: [PATCH 07/19] Implement flag buffer in sentry scope --- packages/core/src/scope.ts | 40 +++++++++++++++++++ packages/launchdarkly/package.json | 1 + packages/launchdarkly/src/core/integration.ts | 18 ++++----- packages/types/src/flags.ts | 2 + packages/types/src/scope.ts | 11 +++++ 5 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 packages/types/src/flags.ts diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index ff89c0d593a9..3fba03feab94 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -11,6 +11,7 @@ import type { EventProcessor, Extra, Extras, + FeatureFlag, Primitive, PropagationContext, RequestSession, @@ -97,6 +98,12 @@ class ScopeClass implements ScopeInterface { /** Contains the last event id of a captured event. */ protected _lastEventId?: string; + /** LRU cache of flags last evaluated by a feature flag provider. Used by FF integrations. */ + protected _flagBuffer: FeatureFlag[]; + + /** Max size of the flagBuffer */ + protected _flagBufferSize: number; // TODO: make const? + // NOTE: Any field which gets added here should get added not only to the constructor but also to the `clone` method. public constructor() { @@ -111,6 +118,9 @@ class ScopeClass implements ScopeInterface { this._contexts = {}; this._sdkProcessingMetadata = {}; this._propagationContext = generatePropagationContext(); + + this._flagBuffer = []; + this._flagBufferSize = 100; } /** @@ -499,6 +509,36 @@ class ScopeClass implements ScopeInterface { return this._propagationContext; } + /** + * @inheritDoc + */ + public getFlags(): FeatureFlag[] { + return this._flagBuffer || []; + } + + /** + * @inheritDoc + */ + public insertFlag(name: string, value: boolean): void { + // Check if the flag is already in the buffer + const index = this._flagBuffer.findIndex(f => f.flag === name); + + if (index !== -1) { + // Delete flag if it is in the buffer + this._flagBuffer.splice(index, 1); + } else if (this._flagBuffer.length === this._flagBufferSize) { + // If at capacity, we need to remove the earliest flag (pop from front) + // This will only happen if not a duplicate flag + this._flagBuffer.shift(); + } + + // Push the flag to the end of the queue + this._flagBuffer.push({ + flag: name, + result: value, + }); + } + /** * @inheritDoc */ diff --git a/packages/launchdarkly/package.json b/packages/launchdarkly/package.json index 6d2a04d3ce19..8cd3530d3f1e 100644 --- a/packages/launchdarkly/package.json +++ b/packages/launchdarkly/package.json @@ -39,6 +39,7 @@ "access": "public" }, "dependencies": { + "@sentry/browser": "^8.35.0", "@sentry/core": "8.35.0", "@sentry/types": "8.35.0", "@sentry/utils": "8.35.0", diff --git a/packages/launchdarkly/src/core/integration.ts b/packages/launchdarkly/src/core/integration.ts index c12130a04448..e9321a70a6a2 100644 --- a/packages/launchdarkly/src/core/integration.ts +++ b/packages/launchdarkly/src/core/integration.ts @@ -5,9 +5,6 @@ import type { Client as SentryClient, Event, EventHint, IntegrationFn } from '@s import type { LDContext, LDEvaluationDetail, LDInspectionFlagUsedHandler } from 'launchdarkly-js-client-sdk'; import type { LaunchDarklyOptions } from './types'; -// import type { Client } from '/client'; -// import type { Event, EventHint } from './event'; - /** * Sentry integration for capturing feature flags from LaunchDarkly. * @@ -16,6 +13,7 @@ import type { LaunchDarklyOptions } from './types'; * @example * * ``` + * TODO: * Sentry.init({ * dsn: '__DSN__', * integrations: [Sentry.replayIntegration()], @@ -29,14 +27,15 @@ export const launchDarklyIntegration = ((_options?: LaunchDarklyOptions) => { return { name: 'launchdarkly', - processEvent(event: Event, hint: EventHint, client: SentryClient): Event | null | PromiseLike { + processEvent(event: Event, _hint: EventHint, _client: SentryClient): Event { const scope = Sentry.getCurrentScope(); // client doesn't have getCurrentScope - const flagData = { values: scope.flags.get() }; + const flagContext = { values: scope.getFlags() }; if (event.contexts) { - event.contexts.flags = flagData; + event.contexts.flags = flagContext; } else { - event.contexts = { flags: flagData }; + event.contexts = { flags: flagContext }; } + return event; }, }; }) satisfies IntegrationFn; @@ -46,7 +45,7 @@ export const launchDarklyIntegration = ((_options?: LaunchDarklyOptions) => { * TODO: docstring */ export class SentryInspector implements LDInspectionFlagUsedHandler { - public name = 'sentry-feature-flag-monitor'; + public name = 'sentry-flag-used-handler'; public synchronous = true; // TODO: T or F? @@ -57,8 +56,7 @@ export class SentryInspector implements LDInspectionFlagUsedHandler { */ public method(flagKey: string, flagDetail: LDEvaluationDetail, _context: LDContext): void { if (typeof flagDetail.value === 'boolean') { - const flags = Sentry.getCurrentScope().flags; - flags.set(flagKey, flagDetail.value); + Sentry.getCurrentScope().insertFlag(flagKey, flagDetail.value); } return; } diff --git a/packages/types/src/flags.ts b/packages/types/src/flags.ts new file mode 100644 index 000000000000..f7aa2a5ce672 --- /dev/null +++ b/packages/types/src/flags.ts @@ -0,0 +1,2 @@ +// Key names match the type used by Sentry frontend. +export type FeatureFlag = { flag: string; result: boolean }; diff --git a/packages/types/src/scope.ts b/packages/types/src/scope.ts index a4b91f4b5d96..ea3477eeab28 100644 --- a/packages/types/src/scope.ts +++ b/packages/types/src/scope.ts @@ -5,6 +5,7 @@ import type { Context, Contexts } from './context'; import type { Event, EventHint } from './event'; import type { EventProcessor } from './eventprocessor'; import type { Extra, Extras } from './extra'; +import type { FeatureFlag } from './flags'; import type { Primitive } from './misc'; import type { RequestSession, Session } from './session'; import type { SeverityLevel } from './severity'; @@ -231,6 +232,16 @@ export interface Scope { */ getPropagationContext(): PropagationContext; + /** + * TODO: michelle + */ + getFlags(): FeatureFlag[]; + + /** + * TODO: michelle + */ + insertFlag(name: string, value: boolean): void; + /** * Capture an exception for this scope. * From ab3181db8a73d9f2adeee2cb68e50815cfa1c4c3 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:07:03 -0700 Subject: [PATCH 08/19] Revert changelog --- CHANGELOG.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bb9478862cc..d584d65cff65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,56 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 8.36.0 + +### Important Changes + +- **feat(nuxt): Add Sentry Pinia plugin ([#14047](https://github.com/getsentry/sentry-javascript/pull/14047))** + +The Nuxt SDK now allows you to track Pinia state for captured errors. To enable the Pinia plugin, set the `trackPinia` option to `true` in your client config: + +```ts +// sentry.client.config.ts + +Sentry.init({ + trackPinia: true, +}); +``` + +Read more about the Pinia plugin in the [Sentry Pinia Documentation](https://docs.sentry.io/platforms/javascript/guides/nuxt/features/pinia/). + +- **feat(nextjs/vercel-edge/cloudflare): Switch to OTEL for performance monitoring ([#13889](https://github.com/getsentry/sentry-javascript/pull/13889))** + +With this release, the Sentry Next.js, and Cloudflare SDKs will now capture performance data based on OpenTelemetry. +Some exceptions apply in cases where Next.js captures inaccurate data itself. + +NOTE: You may experience minor differences in transaction names in Sentry. +Most importantly transactions for serverside pages router invocations will now be named `GET /[param]/my/route` instead of `/[param]/my/route`. +This means that those transactions are now better aligned with the OpenTelemetry semantic conventions. + +### Other Changes + +- deps: Bump bundler plugins and CLI to 2.22.6 and 2.37.0 respectively ([#14050](https://github.com/getsentry/sentry-javascript/pull/14050)) +- feat(deps): bump @opentelemetry/instrumentation-aws-sdk from 0.44.0 to 0.45.0 ([#14099](https://github.com/getsentry/sentry-javascript/pull/14099)) +- feat(deps): bump @opentelemetry/instrumentation-connect from 0.39.0 to 0.40.0 ([#14101](https://github.com/getsentry/sentry-javascript/pull/14101)) +- feat(deps): bump @opentelemetry/instrumentation-express from 0.43.0 to 0.44.0 ([#14102](https://github.com/getsentry/sentry-javascript/pull/14102)) +- feat(deps): bump @opentelemetry/instrumentation-fs from 0.15.0 to 0.16.0 ([#14098](https://github.com/getsentry/sentry-javascript/pull/14098)) +- feat(deps): bump @opentelemetry/instrumentation-kafkajs from 0.3.0 to 0.4.0 ([#14100](https://github.com/getsentry/sentry-javascript/pull/14100)) +- feat(nextjs): Add method and url to route handler request data ([#14084](https://github.com/getsentry/sentry-javascript/pull/14084)) +- feat(node): Add breadcrumbs for `child_process` and `worker_thread` ([#13896](https://github.com/getsentry/sentry-javascript/pull/13896)) +- fix(core): Ensure standalone spans are not sent if SDK is disabled ([#14088](https://github.com/getsentry/sentry-javascript/pull/14088)) +- fix(nextjs): Await flush in api handlers ([#14023](https://github.com/getsentry/sentry-javascript/pull/14023)) +- fix(nextjs): Don't leak webpack types into exports ([#14116](https://github.com/getsentry/sentry-javascript/pull/14116)) +- fix(nextjs): Fix matching logic for file convention type for root level components ([#14038](https://github.com/getsentry/sentry-javascript/pull/14038)) +- fix(nextjs): Respect directives in value injection loader ([#14083](https://github.com/getsentry/sentry-javascript/pull/14083)) +- fix(nuxt): Only wrap `.mjs` entry files in rollup ([#14060](https://github.com/getsentry/sentry-javascript/pull/14060)) +- fix(nuxt): Re-export all exported bindings ([#14086](https://github.com/getsentry/sentry-javascript/pull/14086)) +- fix(nuxt): Server-side setup in readme ([#14049](https://github.com/getsentry/sentry-javascript/pull/14049)) +- fix(profiling-node): Always warn when running on incompatible major version of Node.js ([#14043](https://github.com/getsentry/sentry-javascript/pull/14043)) +- fix(replay): Fix `onError` callback ([#14002](https://github.com/getsentry/sentry-javascript/pull/14002)) +- perf(otel): Only calculate current timestamp once ([#14094](https://github.com/getsentry/sentry-javascript/pull/14094)) +- test(browser-integration): Add sentry DSN route handler by default ([#14095](https://github.com/getsentry/sentry-javascript/pull/14095)) + ## 8.35.0 ### Beta release of the official Nuxt Sentry SDK From 22684ea4775145552652e995068e15c4d2b8323e Mon Sep 17 00:00:00 2001 From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:33:35 -0700 Subject: [PATCH 09/19] fix types --- packages/types/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index b100c1e9c26a..ab260d2be4db 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -173,3 +173,4 @@ export type { export type { ParameterizedString } from './parameterize'; export type { ContinuousProfiler, ProfilingIntegration, Profiler } from './profiling'; export type { ViewHierarchyData, ViewHierarchyWindow } from './view-hierarchy'; +export type { FeatureFlag } from './flags'; From 79e5b2478d366f7b073dc7639750cab19b916166 Mon Sep 17 00:00:00 2001 From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:46:24 -0700 Subject: [PATCH 10/19] docstring --- packages/types/src/scope.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/types/src/scope.ts b/packages/types/src/scope.ts index ea3477eeab28..11973afb4af0 100644 --- a/packages/types/src/scope.ts +++ b/packages/types/src/scope.ts @@ -1,3 +1,4 @@ + import type { Attachment } from './attachment'; import type { Breadcrumb } from './breadcrumb'; import type { Client } from './client'; @@ -233,12 +234,13 @@ export interface Scope { getPropagationContext(): PropagationContext; /** - * TODO: michelle + * Return the list of recently accessed feature flags. */ getFlags(): FeatureFlag[]; /** - * TODO: michelle + * When an integration sends data that a flag name and its value have been evaluated, + * add it to the list of recently accessed feature flags. */ insertFlag(name: string, value: boolean): void; From 63649c55764b1d9891567fe3e94854ce3f1394f3 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:06:46 -0700 Subject: [PATCH 11/19] Export FeatureFlag type in index.ts --- packages/types/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index b100c1e9c26a..bf187a15edb5 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -56,6 +56,7 @@ export type { Event, EventHint, EventType, ErrorEvent, TransactionEvent } from ' export type { EventProcessor } from './eventprocessor'; export type { Exception } from './exception'; export type { Extra, Extras } from './extra'; +export type { FeatureFlag } from './flags'; // eslint-disable-next-line deprecation/deprecation export type { Hub } from './hub'; export type { Integration, IntegrationClass, IntegrationFn } from './integration'; From bd0475550bc1b9f21d36e4256d1755a9c277f38e Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:48:53 -0700 Subject: [PATCH 12/19] Clean up comments --- packages/launchdarkly/src/core/integration.ts | 14 -------------- packages/launchdarkly/src/types/integration.ts | 4 +--- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/packages/launchdarkly/src/core/integration.ts b/packages/launchdarkly/src/core/integration.ts index e9321a70a6a2..90c9a9194b34 100644 --- a/packages/launchdarkly/src/core/integration.ts +++ b/packages/launchdarkly/src/core/integration.ts @@ -21,9 +21,6 @@ import type { LaunchDarklyOptions } from './types'; * ``` */ export const launchDarklyIntegration = ((_options?: LaunchDarklyOptions) => { - // const { _ldClient } = options; - // const ldClient = _ldClient as LDClient; // for type hint - return { name: 'launchdarkly', @@ -41,7 +38,6 @@ export const launchDarklyIntegration = ((_options?: LaunchDarklyOptions) => { }) satisfies IntegrationFn; /** - * https://launchdarkly.github.io/js-client-sdk/interfaces/LDInspectionFlagUsedHandler.html //TODO: rm this link * TODO: docstring */ export class SentryInspector implements LDInspectionFlagUsedHandler { @@ -61,13 +57,3 @@ export class SentryInspector implements LDInspectionFlagUsedHandler { return; } } - -/* - -import SentryInspector from @sentry/ld - -client = LDClient.init(..., SentryInspector) - -sentry.init(integrations: [LDIntegration]) - -*/ diff --git a/packages/launchdarkly/src/types/integration.ts b/packages/launchdarkly/src/types/integration.ts index 1d9e763b8ad2..6516bc7a8ff7 100644 --- a/packages/launchdarkly/src/types/integration.ts +++ b/packages/launchdarkly/src/types/integration.ts @@ -1,3 +1 @@ -// import type { LDClient } from 'launchdarkly-js-client-sdk'; - -export type LaunchDarklyOptions = Record; +export type LaunchDarklyOptions = Record; //TODO: From 91a4db9f39d3c3a97a981e09bc835f742e217ffc Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:07:34 -0700 Subject: [PATCH 13/19] Fix build (uses yalc for scope changes) and use LRUMap util --- package.json | 1 + packages/core/src/scope.ts | 37 +++++------ .../launchdarkly/.yalc/@sentry/types/LICENSE | 21 +++++++ .../.yalc/@sentry/types/README.md | 20 ++++++ .../.yalc/@sentry/types/package.json | 63 +++++++++++++++++++ .../launchdarkly/.yalc/@sentry/types/yalc.sig | 1 + packages/launchdarkly/package.json | 2 +- packages/launchdarkly/src/core/integration.ts | 2 +- .../src/{types/integration.ts => types.ts} | 0 packages/launchdarkly/yalc.lock | 10 +++ packages/types/src/index.ts | 1 - 11 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 packages/launchdarkly/.yalc/@sentry/types/LICENSE create mode 100644 packages/launchdarkly/.yalc/@sentry/types/README.md create mode 100644 packages/launchdarkly/.yalc/@sentry/types/package.json create mode 100644 packages/launchdarkly/.yalc/@sentry/types/yalc.sig rename packages/launchdarkly/src/{types/integration.ts => types.ts} (100%) create mode 100644 packages/launchdarkly/yalc.lock diff --git a/package.json b/package.json index bee335619d24..5348ebd8c687 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "packages/gatsby", "packages/google-cloud-serverless", "packages/integration-shims", + "packages/launchdarkly", "packages/nestjs", "packages/nextjs", "packages/node", diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index 3fba03feab94..d1bcb34441ae 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -22,7 +22,14 @@ import type { SeverityLevel, User, } from '@sentry/types'; -import { dateTimestampInSeconds, generatePropagationContext, isPlainObject, logger, uuid4 } from '@sentry/utils'; +import { + LRUMap, + dateTimestampInSeconds, + generatePropagationContext, + isPlainObject, + logger, + uuid4, +} from '@sentry/utils'; import { updateSession } from './session'; import { _getSpanForScope, _setSpanForScope } from './utils/spanOnScope'; @@ -99,7 +106,7 @@ class ScopeClass implements ScopeInterface { protected _lastEventId?: string; /** LRU cache of flags last evaluated by a feature flag provider. Used by FF integrations. */ - protected _flagBuffer: FeatureFlag[]; + protected _flagBuffer: LRUMap; /** Max size of the flagBuffer */ protected _flagBufferSize: number; // TODO: make const? @@ -119,8 +126,8 @@ class ScopeClass implements ScopeInterface { this._sdkProcessingMetadata = {}; this._propagationContext = generatePropagationContext(); - this._flagBuffer = []; this._flagBufferSize = 100; + this._flagBuffer = new LRUMap(this._flagBufferSize); } /** @@ -513,30 +520,18 @@ class ScopeClass implements ScopeInterface { * @inheritDoc */ public getFlags(): FeatureFlag[] { - return this._flagBuffer || []; + const flags: FeatureFlag[] = []; + this._flagBuffer.keys().forEach(key => { + flags.push({ flag: key, result: this._flagBuffer.get(key) as boolean }); + }); + return flags; } /** * @inheritDoc */ public insertFlag(name: string, value: boolean): void { - // Check if the flag is already in the buffer - const index = this._flagBuffer.findIndex(f => f.flag === name); - - if (index !== -1) { - // Delete flag if it is in the buffer - this._flagBuffer.splice(index, 1); - } else if (this._flagBuffer.length === this._flagBufferSize) { - // If at capacity, we need to remove the earliest flag (pop from front) - // This will only happen if not a duplicate flag - this._flagBuffer.shift(); - } - - // Push the flag to the end of the queue - this._flagBuffer.push({ - flag: name, - result: value, - }); + this._flagBuffer.set(name, value); } /** diff --git a/packages/launchdarkly/.yalc/@sentry/types/LICENSE b/packages/launchdarkly/.yalc/@sentry/types/LICENSE new file mode 100644 index 000000000000..d5b40b7c4219 --- /dev/null +++ b/packages/launchdarkly/.yalc/@sentry/types/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/launchdarkly/.yalc/@sentry/types/README.md b/packages/launchdarkly/.yalc/@sentry/types/README.md new file mode 100644 index 000000000000..4c0e2d9cbc34 --- /dev/null +++ b/packages/launchdarkly/.yalc/@sentry/types/README.md @@ -0,0 +1,20 @@ +

+ + Sentry + +

+ +# Sentry JavaScript SDK Types + +[![npm version](https://img.shields.io/npm/v/@sentry/types.svg)](https://www.npmjs.com/package/@sentry/types) +[![npm dm](https://img.shields.io/npm/dm/@sentry/types.svg)](https://www.npmjs.com/package/@sentry/types) +[![npm dt](https://img.shields.io/npm/dt/@sentry/types.svg)](https://www.npmjs.com/package/@sentry/types) + +## Links + +- [Official SDK Docs](https://docs.sentry.io/quickstart/) +- [TypeDoc](http://getsentry.github.io/sentry-javascript/) + +## General + +Common types used by the Sentry JavaScript SDKs. diff --git a/packages/launchdarkly/.yalc/@sentry/types/package.json b/packages/launchdarkly/.yalc/@sentry/types/package.json new file mode 100644 index 000000000000..09555640efbf --- /dev/null +++ b/packages/launchdarkly/.yalc/@sentry/types/package.json @@ -0,0 +1,63 @@ +{ + "name": "@sentry/types", + "version": "8.35.0+76139869", + "description": "Types for all Sentry JavaScript SDKs", + "repository": "git://github.com/getsentry/sentry-javascript.git", + "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/types", + "author": "Sentry", + "license": "MIT", + "engines": { + "node": ">=14.18" + }, + "files": [ + "/build" + ], + "main": "build/cjs/index.js", + "module": "build/esm/index.js", + "types": "build/types/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "import": { + "types": "./build/types/index.d.ts", + "default": "./build/esm/index.js" + }, + "require": { + "types": "./build/types/index.d.ts", + "default": "./build/cjs/index.js" + } + } + }, + "typesVersions": { + "<4.9": { + "build/types/index.d.ts": [ + "build/types-ts3.8/index.d.ts" + ] + } + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "run-p build:transpile build:types", + "build:dev": "yarn build", + "build:transpile": "rollup -c rollup.npm.config.mjs", + "build:types": "run-s build:types:core build:types:downlevel", + "build:types:core": "tsc -p tsconfig.types.json", + "build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8", + "build:watch": "run-p build:transpile:watch build:types:watch", + "build:dev:watch": "yarn build:watch", + "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", + "build:types:watch": "tsc -p tsconfig.types.json --watch", + "build:tarball": "npm pack", + "clean": "rimraf build sentry-types-*.tgz", + "lint": "eslint . --format stylish", + "fix": "eslint . --format stylish --fix", + "yalc:publish": "yalc publish --push --sig" + }, + "volta": { + "extends": "../../package.json" + }, + "sideEffects": false, + "yalcSig": "761398697aa19d98ec1c2085192f95dc" +} diff --git a/packages/launchdarkly/.yalc/@sentry/types/yalc.sig b/packages/launchdarkly/.yalc/@sentry/types/yalc.sig new file mode 100644 index 000000000000..83ab3038045f --- /dev/null +++ b/packages/launchdarkly/.yalc/@sentry/types/yalc.sig @@ -0,0 +1 @@ +761398697aa19d98ec1c2085192f95dc \ No newline at end of file diff --git a/packages/launchdarkly/package.json b/packages/launchdarkly/package.json index 8cd3530d3f1e..c97f24b64981 100644 --- a/packages/launchdarkly/package.json +++ b/packages/launchdarkly/package.json @@ -41,7 +41,7 @@ "dependencies": { "@sentry/browser": "^8.35.0", "@sentry/core": "8.35.0", - "@sentry/types": "8.35.0", + "@sentry/types": "file:.yalc/@sentry/types", "@sentry/utils": "8.35.0", "launchdarkly-js-client-sdk": "^3.5.0" }, diff --git a/packages/launchdarkly/src/core/integration.ts b/packages/launchdarkly/src/core/integration.ts index 90c9a9194b34..9539490e025e 100644 --- a/packages/launchdarkly/src/core/integration.ts +++ b/packages/launchdarkly/src/core/integration.ts @@ -3,7 +3,7 @@ import * as Sentry from '@sentry/browser'; import type { Client as SentryClient, Event, EventHint, IntegrationFn } from '@sentry/types'; import type { LDContext, LDEvaluationDetail, LDInspectionFlagUsedHandler } from 'launchdarkly-js-client-sdk'; -import type { LaunchDarklyOptions } from './types'; +import type { LaunchDarklyOptions } from '../types'; /** * Sentry integration for capturing feature flags from LaunchDarkly. diff --git a/packages/launchdarkly/src/types/integration.ts b/packages/launchdarkly/src/types.ts similarity index 100% rename from packages/launchdarkly/src/types/integration.ts rename to packages/launchdarkly/src/types.ts diff --git a/packages/launchdarkly/yalc.lock b/packages/launchdarkly/yalc.lock new file mode 100644 index 000000000000..a6f21bb34c90 --- /dev/null +++ b/packages/launchdarkly/yalc.lock @@ -0,0 +1,10 @@ +{ + "version": "v1", + "packages": { + "@sentry/types": { + "signature": "761398697aa19d98ec1c2085192f95dc", + "file": true, + "replaced": "8.35.0" + } + } +} \ No newline at end of file diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 0e173b0986f5..bf187a15edb5 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -174,4 +174,3 @@ export type { export type { ParameterizedString } from './parameterize'; export type { ContinuousProfiler, ProfilingIntegration, Profiler } from './profiling'; export type { ViewHierarchyData, ViewHierarchyWindow } from './view-hierarchy'; -export type { FeatureFlag } from './flags'; From 1ca9d536ec6c07618eda6e97d99a8533b9b14f04 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:34:13 -0800 Subject: [PATCH 14/19] Tweak hook name and docs --- packages/launchdarkly/package.json | 2 +- packages/launchdarkly/src/core/integration.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/launchdarkly/package.json b/packages/launchdarkly/package.json index c97f24b64981..a69bc2e5ae27 100644 --- a/packages/launchdarkly/package.json +++ b/packages/launchdarkly/package.json @@ -1,7 +1,7 @@ { "name": "@sentry-internal/launchdarkly", "version": "8.35.0", - "description": "Sentry SDK integration for user launchdarkly", + "description": "Sentry SDK integration for Launch Darkly feature flagging", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/launchdarkly", "author": "Sentry", diff --git a/packages/launchdarkly/src/core/integration.ts b/packages/launchdarkly/src/core/integration.ts index 9539490e025e..8e4f45b82aef 100644 --- a/packages/launchdarkly/src/core/integration.ts +++ b/packages/launchdarkly/src/core/integration.ts @@ -38,10 +38,12 @@ export const launchDarklyIntegration = ((_options?: LaunchDarklyOptions) => { }) satisfies IntegrationFn; /** - * TODO: docstring + * LaunchDarkly hook that listens for flag evaluations and updates the + * flagBuffer in our current scope + * TODO: finalize docstring */ export class SentryInspector implements LDInspectionFlagUsedHandler { - public name = 'sentry-flag-used-handler'; + public name = 'sentry-flag-auditor'; public synchronous = true; // TODO: T or F? From 893cd62cb9526ef2c1ba56465721a966e3a6db4b Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:03:02 -0800 Subject: [PATCH 15/19] FlagBuffer class and interface, add to scope._contexts. Remove old flag field and methods --- packages/core/src/scope.ts | 29 -------- packages/launchdarkly/src/core/integration.ts | 5 +- packages/types/src/context.ts | 6 ++ packages/types/src/flags.ts | 33 +++++++++ packages/types/src/scope.ts | 13 ---- packages/utils/src/flagBuffer.ts | 72 +++++++++++++++++++ packages/utils/src/index.ts | 1 + 7 files changed, 115 insertions(+), 44 deletions(-) create mode 100644 packages/utils/src/flagBuffer.ts diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index d1bcb34441ae..a50d5b9ffa7e 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -11,7 +11,6 @@ import type { EventProcessor, Extra, Extras, - FeatureFlag, Primitive, PropagationContext, RequestSession, @@ -23,7 +22,6 @@ import type { User, } from '@sentry/types'; import { - LRUMap, dateTimestampInSeconds, generatePropagationContext, isPlainObject, @@ -105,12 +103,6 @@ class ScopeClass implements ScopeInterface { /** Contains the last event id of a captured event. */ protected _lastEventId?: string; - /** LRU cache of flags last evaluated by a feature flag provider. Used by FF integrations. */ - protected _flagBuffer: LRUMap; - - /** Max size of the flagBuffer */ - protected _flagBufferSize: number; // TODO: make const? - // NOTE: Any field which gets added here should get added not only to the constructor but also to the `clone` method. public constructor() { @@ -125,9 +117,6 @@ class ScopeClass implements ScopeInterface { this._contexts = {}; this._sdkProcessingMetadata = {}; this._propagationContext = generatePropagationContext(); - - this._flagBufferSize = 100; - this._flagBuffer = new LRUMap(this._flagBufferSize); } /** @@ -516,24 +505,6 @@ class ScopeClass implements ScopeInterface { return this._propagationContext; } - /** - * @inheritDoc - */ - public getFlags(): FeatureFlag[] { - const flags: FeatureFlag[] = []; - this._flagBuffer.keys().forEach(key => { - flags.push({ flag: key, result: this._flagBuffer.get(key) as boolean }); - }); - return flags; - } - - /** - * @inheritDoc - */ - public insertFlag(name: string, value: boolean): void { - this._flagBuffer.set(name, value); - } - /** * @inheritDoc */ diff --git a/packages/launchdarkly/src/core/integration.ts b/packages/launchdarkly/src/core/integration.ts index 8e4f45b82aef..bac24fe12bc8 100644 --- a/packages/launchdarkly/src/core/integration.ts +++ b/packages/launchdarkly/src/core/integration.ts @@ -45,10 +45,11 @@ export const launchDarklyIntegration = ((_options?: LaunchDarklyOptions) => { export class SentryInspector implements LDInspectionFlagUsedHandler { public name = 'sentry-flag-auditor'; - public synchronous = true; // TODO: T or F? - public type = 'flag-used' as const; + // We don't want the handler to impact the performance of the user's flag evaluations. + public synchronous = false; + /** * TODO: docstring */ diff --git a/packages/types/src/context.ts b/packages/types/src/context.ts index 10fc61420e25..39985ac62b42 100644 --- a/packages/types/src/context.ts +++ b/packages/types/src/context.ts @@ -1,5 +1,6 @@ import type { Primitive } from './misc'; import type { SpanOrigin } from './span'; +import type { FlagBufferInterface } from './flags' export type Context = Record; @@ -13,6 +14,7 @@ export interface Contexts extends Record { cloud_resource?: CloudResourceContext; state?: StateContext; profile?: ProfileContext; + flags?: FeatureFlagContext; } export interface StateContext extends Record { @@ -124,3 +126,7 @@ export interface MissingInstrumentationContext extends Record { package: string; ['javascript.is_cjs']?: boolean; } + +export interface FeatureFlagContext extends Record { + flag_buffer: FlagBufferInterface; +} diff --git a/packages/types/src/flags.ts b/packages/types/src/flags.ts index f7aa2a5ce672..534f6c333cd7 100644 --- a/packages/types/src/flags.ts +++ b/packages/types/src/flags.ts @@ -1,2 +1,35 @@ // Key names match the type used by Sentry frontend. export type FeatureFlag = { flag: string; result: boolean }; + +/** + * Ordered LRU cache for storing feature flags in the scope context. The name + * of each flag in the buffer is unique, and the output of getAll() is ordered + * from oldest to newest. + */ +export interface FlagBufferInterface { + readonly maxSize: number; + + /** + * Returns a deep copy of the current FlagBuffer. + */ + clone(): FlagBufferInterface; + + /** + * Returns an ordered array of the flags currently stored in the buffer. + * This is in the order of insertion (oldest to newest). + */ + getAll(): readonly FeatureFlag[]; + + /** + * Add a flag to the buffer. After inserting, the flag is guaranteed to be at + * the end of the buffer, with no other flags of the same name in it. + * + * @param flag + */ + insert(name: string, value: boolean): void; + + /** + * Clear the buffer. Returns the number of flags removed. + */ + clear(): number; +} diff --git a/packages/types/src/scope.ts b/packages/types/src/scope.ts index 0cc1b1d2eafd..2d5c72230aef 100644 --- a/packages/types/src/scope.ts +++ b/packages/types/src/scope.ts @@ -1,4 +1,3 @@ - import type { Attachment } from './attachment'; import type { Breadcrumb } from './breadcrumb'; import type { Client } from './client'; @@ -6,7 +5,6 @@ import type { Context, Contexts } from './context'; import type { Event, EventHint } from './event'; import type { EventProcessor } from './eventprocessor'; import type { Extra, Extras } from './extra'; -import type { FeatureFlag } from './flags'; import type { Primitive } from './misc'; import type { RequestSession, Session } from './session'; import type { SeverityLevel } from './severity'; @@ -233,17 +231,6 @@ export interface Scope { */ getPropagationContext(): PropagationContext; - /** - * Return the list of recently accessed feature flags. - */ - getFlags(): FeatureFlag[]; - - /** - * When an integration sends data that a flag name and its value have been evaluated, - * add it to the list of recently accessed feature flags. - */ - insertFlag(name: string, value: boolean): void; - /** * Capture an exception for this scope. * diff --git a/packages/utils/src/flagBuffer.ts b/packages/utils/src/flagBuffer.ts new file mode 100644 index 000000000000..d4c30c03fb0d --- /dev/null +++ b/packages/utils/src/flagBuffer.ts @@ -0,0 +1,72 @@ +import type { FeatureFlag } from '@sentry/types'; +import type { FlagBufferInterface } from '@sentry/types/build/types/flags'; + +export const DEFAULT_FLAG_BUFFER_SIZE = 100; + +/** + * Array implementation of types/FlagBufferInterface. + * + * Ordered LRU cache for storing feature flags in the scope context. The name + * of each flag in the buffer is unique, and the output of getAll() is ordered + * from oldest to newest. + */ +export class FlagBuffer implements FlagBufferInterface { + public readonly maxSize: number; + + private readonly _flags: FeatureFlag[]; + + public constructor(_maxSize: number = DEFAULT_FLAG_BUFFER_SIZE, _initialFlags: FeatureFlag[] = []) { + this.maxSize = _maxSize; + if (_initialFlags.length > _maxSize) { + throw Error(`_initialFlags param exceeds the maxSize of ${_maxSize}`); + } + this._flags = _initialFlags; + } + + /** + * @inheritdoc + */ + public clone(): FlagBuffer { + return new FlagBuffer(this.maxSize, this._flags); + } + + /** + * @inheritdoc + */ + public getAll(): readonly FeatureFlag[] { + return [...this._flags]; // shallow copy + } + + /** + * @inheritdoc + */ + public insert(name: string, value: boolean): void { + // Check if the flag is already in the buffer + const index = this._flags.findIndex(f => f.flag === name); + + if (index !== -1) { + // The flag was found, remove it from its current position - O(n) + this._flags.splice(index, 1); + } + + if (this._flags.length === this.maxSize) { + // If at capacity, pop the earliest flag - O(n) + this._flags.shift(); + } + + // Push the flag to the end - O(1) + this._flags.push({ + flag: name, + result: value, + }); + } + + /** + * @inheritdoc + */ + public clear(): number { + const length = this._flags.length; + this._flags.splice(0, length); // O(n) + return length; + } +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 4a2d68ca0d8b..c1a1cd3cd10e 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -40,3 +40,4 @@ export * from './buildPolyfills'; export * from './propagationContext'; export * from './vercelWaitUntil'; export * from './version'; +export * from './flagBuffer'; From f172683fbd1b6630c0196ef0533d158925bfe2db Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:09:21 -0800 Subject: [PATCH 16/19] Call clone in scope.clone() --- packages/core/src/scope.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index a50d5b9ffa7e..46a5e3c12ec2 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -128,6 +128,11 @@ class ScopeClass implements ScopeInterface { newScope._tags = { ...this._tags }; newScope._extra = { ...this._extra }; newScope._contexts = { ...this._contexts }; + if (this._contexts.flags) { + // The flags context requires a deep copy. + newScope._contexts.flags = {flag_buffer: this._contexts.flags.flag_buffer.clone()}; + } + newScope._user = this._user; newScope._level = this._level; newScope._session = this._session; From b96c17fe3a4a1413f00c011763610baec84560e4 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:04:47 -0800 Subject: [PATCH 17/19] Rewrite as a util fx instead of class. Use in LD integration --- packages/core/src/scope.ts | 6 +- .../launchdarkly/.yalc/@sentry/types/LICENSE | 21 ------ .../.yalc/@sentry/types/README.md | 20 ------ .../.yalc/@sentry/types/package.json | 63 ---------------- .../launchdarkly/.yalc/@sentry/types/yalc.sig | 1 - packages/launchdarkly/package.json | 4 +- .../launchdarkly/rollup.bundle.config.mjs | 2 +- packages/launchdarkly/src/core/integration.ts | 39 +++++----- packages/launchdarkly/yalc.lock | 10 --- packages/types/src/context.ts | 5 +- packages/types/src/flags.ts | 35 +-------- packages/utils/src/flagBuffer.ts | 72 ------------------- packages/utils/src/flags.ts | 38 ++++++++++ packages/utils/src/index.ts | 2 +- yarn.lock | 42 +++++++++++ 15 files changed, 115 insertions(+), 245 deletions(-) delete mode 100644 packages/launchdarkly/.yalc/@sentry/types/LICENSE delete mode 100644 packages/launchdarkly/.yalc/@sentry/types/README.md delete mode 100644 packages/launchdarkly/.yalc/@sentry/types/package.json delete mode 100644 packages/launchdarkly/.yalc/@sentry/types/yalc.sig delete mode 100644 packages/launchdarkly/yalc.lock delete mode 100644 packages/utils/src/flagBuffer.ts create mode 100644 packages/utils/src/flags.ts diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index 46a5e3c12ec2..65e5821a3d1f 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -129,8 +129,10 @@ class ScopeClass implements ScopeInterface { newScope._extra = { ...this._extra }; newScope._contexts = { ...this._contexts }; if (this._contexts.flags) { - // The flags context requires a deep copy. - newScope._contexts.flags = {flag_buffer: this._contexts.flags.flag_buffer.clone()}; + // The flags context needs a deep copy. + newScope._contexts.flags = { + values: [...this._contexts.flags.values] + } } newScope._user = this._user; diff --git a/packages/launchdarkly/.yalc/@sentry/types/LICENSE b/packages/launchdarkly/.yalc/@sentry/types/LICENSE deleted file mode 100644 index d5b40b7c4219..000000000000 --- a/packages/launchdarkly/.yalc/@sentry/types/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/launchdarkly/.yalc/@sentry/types/README.md b/packages/launchdarkly/.yalc/@sentry/types/README.md deleted file mode 100644 index 4c0e2d9cbc34..000000000000 --- a/packages/launchdarkly/.yalc/@sentry/types/README.md +++ /dev/null @@ -1,20 +0,0 @@ -

- - Sentry - -

- -# Sentry JavaScript SDK Types - -[![npm version](https://img.shields.io/npm/v/@sentry/types.svg)](https://www.npmjs.com/package/@sentry/types) -[![npm dm](https://img.shields.io/npm/dm/@sentry/types.svg)](https://www.npmjs.com/package/@sentry/types) -[![npm dt](https://img.shields.io/npm/dt/@sentry/types.svg)](https://www.npmjs.com/package/@sentry/types) - -## Links - -- [Official SDK Docs](https://docs.sentry.io/quickstart/) -- [TypeDoc](http://getsentry.github.io/sentry-javascript/) - -## General - -Common types used by the Sentry JavaScript SDKs. diff --git a/packages/launchdarkly/.yalc/@sentry/types/package.json b/packages/launchdarkly/.yalc/@sentry/types/package.json deleted file mode 100644 index 09555640efbf..000000000000 --- a/packages/launchdarkly/.yalc/@sentry/types/package.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "name": "@sentry/types", - "version": "8.35.0+76139869", - "description": "Types for all Sentry JavaScript SDKs", - "repository": "git://github.com/getsentry/sentry-javascript.git", - "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/types", - "author": "Sentry", - "license": "MIT", - "engines": { - "node": ">=14.18" - }, - "files": [ - "/build" - ], - "main": "build/cjs/index.js", - "module": "build/esm/index.js", - "types": "build/types/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "import": { - "types": "./build/types/index.d.ts", - "default": "./build/esm/index.js" - }, - "require": { - "types": "./build/types/index.d.ts", - "default": "./build/cjs/index.js" - } - } - }, - "typesVersions": { - "<4.9": { - "build/types/index.d.ts": [ - "build/types-ts3.8/index.d.ts" - ] - } - }, - "publishConfig": { - "access": "public" - }, - "scripts": { - "build": "run-p build:transpile build:types", - "build:dev": "yarn build", - "build:transpile": "rollup -c rollup.npm.config.mjs", - "build:types": "run-s build:types:core build:types:downlevel", - "build:types:core": "tsc -p tsconfig.types.json", - "build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8", - "build:watch": "run-p build:transpile:watch build:types:watch", - "build:dev:watch": "yarn build:watch", - "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", - "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:tarball": "npm pack", - "clean": "rimraf build sentry-types-*.tgz", - "lint": "eslint . --format stylish", - "fix": "eslint . --format stylish --fix", - "yalc:publish": "yalc publish --push --sig" - }, - "volta": { - "extends": "../../package.json" - }, - "sideEffects": false, - "yalcSig": "761398697aa19d98ec1c2085192f95dc" -} diff --git a/packages/launchdarkly/.yalc/@sentry/types/yalc.sig b/packages/launchdarkly/.yalc/@sentry/types/yalc.sig deleted file mode 100644 index 83ab3038045f..000000000000 --- a/packages/launchdarkly/.yalc/@sentry/types/yalc.sig +++ /dev/null @@ -1 +0,0 @@ -761398697aa19d98ec1c2085192f95dc \ No newline at end of file diff --git a/packages/launchdarkly/package.json b/packages/launchdarkly/package.json index a69bc2e5ae27..7d4075bca4e7 100644 --- a/packages/launchdarkly/package.json +++ b/packages/launchdarkly/package.json @@ -1,5 +1,5 @@ { - "name": "@sentry-internal/launchdarkly", + "name": "@sentry/launchdarkly", "version": "8.35.0", "description": "Sentry SDK integration for Launch Darkly feature flagging", "repository": "git://github.com/getsentry/sentry-javascript.git", @@ -41,7 +41,7 @@ "dependencies": { "@sentry/browser": "^8.35.0", "@sentry/core": "8.35.0", - "@sentry/types": "file:.yalc/@sentry/types", + "@sentry/types": "8.35.0", "@sentry/utils": "8.35.0", "launchdarkly-js-client-sdk": "^3.5.0" }, diff --git a/packages/launchdarkly/rollup.bundle.config.mjs b/packages/launchdarkly/rollup.bundle.config.mjs index cc29fadbc093..f79e2cd63d7e 100644 --- a/packages/launchdarkly/rollup.bundle.config.mjs +++ b/packages/launchdarkly/rollup.bundle.config.mjs @@ -3,7 +3,7 @@ import { makeBaseBundleConfig, makeBundleConfigVariants } from '@sentry-internal const baseBundleConfig = makeBaseBundleConfig({ bundleType: 'addon', entrypoints: ['src/index.ts'], - licenseTitle: '@sentry-internal/launchdarkly', + licenseTitle: '@sentry/launchdarkly', outputFileBase: () => 'bundles/launchdarkly', }); diff --git a/packages/launchdarkly/src/core/integration.ts b/packages/launchdarkly/src/core/integration.ts index bac24fe12bc8..e9702251b048 100644 --- a/packages/launchdarkly/src/core/integration.ts +++ b/packages/launchdarkly/src/core/integration.ts @@ -4,6 +4,7 @@ import * as Sentry from '@sentry/browser'; import type { Client as SentryClient, Event, EventHint, IntegrationFn } from '@sentry/types'; import type { LDContext, LDEvaluationDetail, LDInspectionFlagUsedHandler } from 'launchdarkly-js-client-sdk'; import type { LaunchDarklyOptions } from '../types'; +import { insertToFlagBuffer } from '@sentry/utils'; /** * Sentry integration for capturing feature flags from LaunchDarkly. @@ -11,13 +12,12 @@ import type { LaunchDarklyOptions } from '../types'; * See the [feature flag documentation](TODO:) for more information. * * @example - * * ``` - * TODO: - * Sentry.init({ - * dsn: '__DSN__', - * integrations: [Sentry.replayIntegration()], - * }); + * import {SentryInspector, launchDarklyIntegration} from '@sentry/launchdarkly'; + * import {LDClient} from 'launchdarkly-js-client-sdk'; + * + * Sentry.init(..., integrations: [launchDarklyIntegration()]) + * const ldClient = LDClient.initialize(..., inspectors: [SentryInspector]); * ``` */ export const launchDarklyIntegration = ((_options?: LaunchDarklyOptions) => { @@ -25,13 +25,13 @@ export const launchDarklyIntegration = ((_options?: LaunchDarklyOptions) => { name: 'launchdarkly', processEvent(event: Event, _hint: EventHint, _client: SentryClient): Event { - const scope = Sentry.getCurrentScope(); // client doesn't have getCurrentScope - const flagContext = { values: scope.getFlags() }; - if (event.contexts) { - event.contexts.flags = flagContext; - } else { - event.contexts = { flags: flagContext }; + const scope = Sentry.getCurrentScope(); + const flagContext = scope.getScopeData().contexts.flags; + + if (event.contexts === undefined) { + event.contexts = {}; } + event.contexts.flags = flagContext; return event; }, }; @@ -39,8 +39,10 @@ export const launchDarklyIntegration = ((_options?: LaunchDarklyOptions) => { /** * LaunchDarkly hook that listens for flag evaluations and updates the - * flagBuffer in our current scope - * TODO: finalize docstring + * flagBuffer in our current scope. + * + * This needs to be registered separately in the LDClient, after initializing + * Sentry. */ export class SentryInspector implements LDInspectionFlagUsedHandler { public name = 'sentry-flag-auditor'; @@ -51,11 +53,16 @@ export class SentryInspector implements LDInspectionFlagUsedHandler { public synchronous = false; /** - * TODO: docstring + * Handle a flag evaluation by storing its name and value on the current scope. */ public method(flagKey: string, flagDetail: LDEvaluationDetail, _context: LDContext): void { if (typeof flagDetail.value === 'boolean') { - Sentry.getCurrentScope().insertFlag(flagKey, flagDetail.value); + const scopeContexts = Sentry.getCurrentScope().getScopeData().contexts; + if (!scopeContexts.flags) { + scopeContexts.flags = {values: []} + } + const flagBuffer = scopeContexts.flags.values; + insertToFlagBuffer(flagBuffer, flagKey, flagDetail.value); } return; } diff --git a/packages/launchdarkly/yalc.lock b/packages/launchdarkly/yalc.lock deleted file mode 100644 index a6f21bb34c90..000000000000 --- a/packages/launchdarkly/yalc.lock +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": "v1", - "packages": { - "@sentry/types": { - "signature": "761398697aa19d98ec1c2085192f95dc", - "file": true, - "replaced": "8.35.0" - } - } -} \ No newline at end of file diff --git a/packages/types/src/context.ts b/packages/types/src/context.ts index 39985ac62b42..3e757928923a 100644 --- a/packages/types/src/context.ts +++ b/packages/types/src/context.ts @@ -1,6 +1,6 @@ import type { Primitive } from './misc'; import type { SpanOrigin } from './span'; -import type { FlagBufferInterface } from './flags' +import type { FeatureFlag } from './flags' export type Context = Record; @@ -128,5 +128,6 @@ export interface MissingInstrumentationContext extends Record { } export interface FeatureFlagContext extends Record { - flag_buffer: FlagBufferInterface; + // This should only be modified by @sentry/util methods (insertToFlagBuffer). + readonly values: FeatureFlag[]; } diff --git a/packages/types/src/flags.ts b/packages/types/src/flags.ts index 534f6c333cd7..c117fbd0d686 100644 --- a/packages/types/src/flags.ts +++ b/packages/types/src/flags.ts @@ -1,35 +1,2 @@ // Key names match the type used by Sentry frontend. -export type FeatureFlag = { flag: string; result: boolean }; - -/** - * Ordered LRU cache for storing feature flags in the scope context. The name - * of each flag in the buffer is unique, and the output of getAll() is ordered - * from oldest to newest. - */ -export interface FlagBufferInterface { - readonly maxSize: number; - - /** - * Returns a deep copy of the current FlagBuffer. - */ - clone(): FlagBufferInterface; - - /** - * Returns an ordered array of the flags currently stored in the buffer. - * This is in the order of insertion (oldest to newest). - */ - getAll(): readonly FeatureFlag[]; - - /** - * Add a flag to the buffer. After inserting, the flag is guaranteed to be at - * the end of the buffer, with no other flags of the same name in it. - * - * @param flag - */ - insert(name: string, value: boolean): void; - - /** - * Clear the buffer. Returns the number of flags removed. - */ - clear(): number; -} +export type FeatureFlag = { readonly flag: string; readonly result: boolean }; diff --git a/packages/utils/src/flagBuffer.ts b/packages/utils/src/flagBuffer.ts deleted file mode 100644 index d4c30c03fb0d..000000000000 --- a/packages/utils/src/flagBuffer.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { FeatureFlag } from '@sentry/types'; -import type { FlagBufferInterface } from '@sentry/types/build/types/flags'; - -export const DEFAULT_FLAG_BUFFER_SIZE = 100; - -/** - * Array implementation of types/FlagBufferInterface. - * - * Ordered LRU cache for storing feature flags in the scope context. The name - * of each flag in the buffer is unique, and the output of getAll() is ordered - * from oldest to newest. - */ -export class FlagBuffer implements FlagBufferInterface { - public readonly maxSize: number; - - private readonly _flags: FeatureFlag[]; - - public constructor(_maxSize: number = DEFAULT_FLAG_BUFFER_SIZE, _initialFlags: FeatureFlag[] = []) { - this.maxSize = _maxSize; - if (_initialFlags.length > _maxSize) { - throw Error(`_initialFlags param exceeds the maxSize of ${_maxSize}`); - } - this._flags = _initialFlags; - } - - /** - * @inheritdoc - */ - public clone(): FlagBuffer { - return new FlagBuffer(this.maxSize, this._flags); - } - - /** - * @inheritdoc - */ - public getAll(): readonly FeatureFlag[] { - return [...this._flags]; // shallow copy - } - - /** - * @inheritdoc - */ - public insert(name: string, value: boolean): void { - // Check if the flag is already in the buffer - const index = this._flags.findIndex(f => f.flag === name); - - if (index !== -1) { - // The flag was found, remove it from its current position - O(n) - this._flags.splice(index, 1); - } - - if (this._flags.length === this.maxSize) { - // If at capacity, pop the earliest flag - O(n) - this._flags.shift(); - } - - // Push the flag to the end - O(1) - this._flags.push({ - flag: name, - result: value, - }); - } - - /** - * @inheritdoc - */ - public clear(): number { - const length = this._flags.length; - this._flags.splice(0, length); // O(n) - return length; - } -} diff --git a/packages/utils/src/flags.ts b/packages/utils/src/flags.ts new file mode 100644 index 000000000000..2cc02b989577 --- /dev/null +++ b/packages/utils/src/flags.ts @@ -0,0 +1,38 @@ +import type { FeatureFlag } from '@sentry/types'; + +/** + * Ordered LRU cache for storing feature flags in the scope context. The name + * of each flag in the buffer is unique, and the output of getAll() is ordered + * from oldest to newest. + */ + +export const FLAG_BUFFER_SIZE = 100; + +/** + * Insert into a FeatureFlag array while maintaining ordered LRU properties. + * After inserting: + * - The flag is guaranteed to be at the end of `flags`. + * - No other flags with the same name exist in `flags`. + * - The length of `flags` does not exceed FLAG_BUFFER_SIZE. If needed, the + * oldest inserted flag is evicted. + */ +export function insertToFlagBuffer(flags: FeatureFlag[], name: string, value: boolean): void { + // Check if the flag is already in the buffer + const index = flags.findIndex(f => f.flag === name); + + if (index !== -1) { + // The flag was found, remove it from its current position - O(n) + flags.splice(index, 1); + } + + if (flags.length === FLAG_BUFFER_SIZE) { + // If at capacity, pop the earliest flag - O(n) + flags.shift(); + } + + // Push the flag to the end - O(1) + flags.push({ + flag: name, + result: value, + }); +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index c1a1cd3cd10e..1f8a86960a03 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -40,4 +40,4 @@ export * from './buildPolyfills'; export * from './propagationContext'; export * from './vercelWaitUntil'; export * from './version'; -export * from './flagBuffer'; +export * from './flags'; diff --git a/yarn.lock b/yarn.lock index 652c5721f5b6..4e2971138219 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8390,6 +8390,14 @@ "@sentry/cli-win32-i686" "2.37.0" "@sentry/cli-win32-x64" "2.37.0" +"@sentry/core@8.35.0": + version "8.35.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.35.0.tgz#17090f4d2d3bb983d9d99ecd2d27f4e9e107e0b0" + integrity sha512-Ci0Nmtw5ETWLqQJGY4dyF+iWh7PWKy6k303fCEoEmqj2czDrKJCp7yHBNV0XYbo00prj2ZTbCr6I7albYiyONA== + dependencies: + "@sentry/types" "8.35.0" + "@sentry/utils" "8.35.0" + "@sentry/rollup-plugin@2.22.6": version "2.22.6" resolved "https://registry.yarnpkg.com/@sentry/rollup-plugin/-/rollup-plugin-2.22.6.tgz#74e9ab69729ee024a497b21b66be3b1992e786d5" @@ -8398,6 +8406,18 @@ "@sentry/bundler-plugin-core" "2.22.6" unplugin "1.0.1" +"@sentry/types@8.35.0": + version "8.35.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.35.0.tgz#535c807800f7e378f61416f30177c0ef81b95012" + integrity sha512-AVEZjb16MlYPifiDDvJ19dPQyDn0jlrtC1PHs6ZKO+Rzyz+2EX2BRdszvanqArldexPoU1p5Bn2w81XZNXThBA== + +"@sentry/utils@8.35.0": + version "8.35.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.35.0.tgz#1e099fcbc60040091c79f028a83226c145d588ee" + integrity sha512-MdMb6+uXjqND7qIPWhulubpSeHzia6HtxeJa8jYI09OCvIcmNGPydv/Gx/LZBwosfMHrLdTWcFH7Y7aCxrq7cg== + dependencies: + "@sentry/types" "8.35.0" + "@sentry/vite-plugin@2.22.6", "@sentry/vite-plugin@^2.22.6": version "2.22.6" resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-2.22.6.tgz#d08a1ede05f137636d5b3c61845d24c0114f0d76" @@ -18029,6 +18049,11 @@ fake-indexeddb@^4.0.1: dependencies: realistic-structured-clone "^3.0.0" +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -22272,6 +22297,23 @@ launch-editor@^2.9.1: picocolors "^1.0.0" shell-quote "^1.8.1" +launchdarkly-js-client-sdk@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/launchdarkly-js-client-sdk/-/launchdarkly-js-client-sdk-3.5.0.tgz#cb0e3d6fc21e56750aa86fcaf75733d7f510946f" + integrity sha512-3dgxC9S8K2ix6qjdArjZGOJPtAytgfQTuE+vWgjWJK7725rpYbuqbHghIFr5B0+WyWyVBYANldjWd1JdtYLwsw== + dependencies: + escape-string-regexp "^4.0.0" + launchdarkly-js-sdk-common "5.4.0" + +launchdarkly-js-sdk-common@5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/launchdarkly-js-sdk-common/-/launchdarkly-js-sdk-common-5.4.0.tgz#c9787daebe0b583b01d2334218524ea142c85001" + integrity sha512-Kb3SDcB6S0HUpFNBZgtEt0YUV/fVkyg+gODfaOCJQ0Y0ApxLKNmmJBZOrPE2qIdzw536u4BqEjtaJdqJWCEElg== + dependencies: + base64-js "^1.3.0" + fast-deep-equal "^2.0.1" + uuid "^8.0.0" + lazystream@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" From f9a659b8aa2f3d8ebf91ff2b28cc177d856d4bde Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:09:48 -0800 Subject: [PATCH 18/19] Delete LD pkg --- packages/launchdarkly/.eslintignore | 2 - packages/launchdarkly/.eslintrc.js | 39 --- packages/launchdarkly/.gitignore | 4 - packages/launchdarkly/LICENSE | 21 -- packages/launchdarkly/README.md | 15 -- packages/launchdarkly/package.json | 74 ----- .../launchdarkly/rollup.bundle.config.mjs | 12 - packages/launchdarkly/rollup.npm.config.mjs | 19 -- .../scripts/shim-preact-export.js | 75 ------ packages/launchdarkly/src/core/integration.ts | 69 ----- packages/launchdarkly/src/index.ts | 6 - packages/launchdarkly/src/types.ts | 1 - packages/launchdarkly/test.setup.ts | 255 ------------------ packages/launchdarkly/tsconfig.json | 8 - packages/launchdarkly/tsconfig.test.json | 15 -- packages/launchdarkly/tsconfig.types.json | 10 - packages/launchdarkly/vitest.config.ts | 12 - 17 files changed, 637 deletions(-) delete mode 100644 packages/launchdarkly/.eslintignore delete mode 100644 packages/launchdarkly/.eslintrc.js delete mode 100644 packages/launchdarkly/.gitignore delete mode 100644 packages/launchdarkly/LICENSE delete mode 100644 packages/launchdarkly/README.md delete mode 100644 packages/launchdarkly/package.json delete mode 100644 packages/launchdarkly/rollup.bundle.config.mjs delete mode 100644 packages/launchdarkly/rollup.npm.config.mjs delete mode 100644 packages/launchdarkly/scripts/shim-preact-export.js delete mode 100644 packages/launchdarkly/src/core/integration.ts delete mode 100644 packages/launchdarkly/src/index.ts delete mode 100644 packages/launchdarkly/src/types.ts delete mode 100644 packages/launchdarkly/test.setup.ts delete mode 100644 packages/launchdarkly/tsconfig.json delete mode 100644 packages/launchdarkly/tsconfig.test.json delete mode 100644 packages/launchdarkly/tsconfig.types.json delete mode 100644 packages/launchdarkly/vitest.config.ts diff --git a/packages/launchdarkly/.eslintignore b/packages/launchdarkly/.eslintignore deleted file mode 100644 index b38db2f296ff..000000000000 --- a/packages/launchdarkly/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -build/ diff --git a/packages/launchdarkly/.eslintrc.js b/packages/launchdarkly/.eslintrc.js deleted file mode 100644 index dc39a10b354b..000000000000 --- a/packages/launchdarkly/.eslintrc.js +++ /dev/null @@ -1,39 +0,0 @@ -// Note: All paths are relative to the directory in which eslint is being run, rather than the directory where this file -// lives - -// ESLint config docs: https://eslint.org/docs/user-guide/configuring/ - -module.exports = { - extends: ['../../.eslintrc.js'], - overrides: [ - { - files: ['src/**/*.ts'], - }, - { - files: ['test.setup.ts', 'vitest.config.ts'], - parserOptions: { - project: ['tsconfig.test.json'], - }, - rules: { - 'no-console': 'off', - }, - }, - { - files: ['test/**/*.ts'], - - rules: { - // most of these errors come from `new Promise(process.nextTick)` - '@typescript-eslint/unbound-method': 'off', - // TODO: decide if we want to enable this again after the migration - // We can take the freedom to be a bit more lenient with tests - '@typescript-eslint/no-floating-promises': 'off', - }, - }, - { - files: ['src/types/deprecated.ts'], - rules: { - '@typescript-eslint/naming-convention': 'off', - }, - }, - ], -}; diff --git a/packages/launchdarkly/.gitignore b/packages/launchdarkly/.gitignore deleted file mode 100644 index 363d3467c6fa..000000000000 --- a/packages/launchdarkly/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -/*.tgz -.eslintcache -build diff --git a/packages/launchdarkly/LICENSE b/packages/launchdarkly/LICENSE deleted file mode 100644 index 6bfafc44539c..000000000000 --- a/packages/launchdarkly/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/launchdarkly/README.md b/packages/launchdarkly/README.md deleted file mode 100644 index d35afcc2fce5..000000000000 --- a/packages/launchdarkly/README.md +++ /dev/null @@ -1,15 +0,0 @@ -

- - Sentry - -

- -# Sentry Integration for LaunchDarkly - -This SDK is **considered experimental and in a beta state**. It may experience breaking changes, and may be discontinued -at any time. Please reach out on [GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have -any feedback/concerns. - -## Installation - -## Configuration diff --git a/packages/launchdarkly/package.json b/packages/launchdarkly/package.json deleted file mode 100644 index 7d4075bca4e7..000000000000 --- a/packages/launchdarkly/package.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "name": "@sentry/launchdarkly", - "version": "8.35.0", - "description": "Sentry SDK integration for Launch Darkly feature flagging", - "repository": "git://github.com/getsentry/sentry-javascript.git", - "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/launchdarkly", - "author": "Sentry", - "license": "MIT", - "engines": { - "node": ">=14.18" - }, - "files": [ - "/build/npm" - ], - "main": "build/npm/cjs/index.js", - "module": "build/npm/esm/index.js", - "types": "build/npm/types/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "import": { - "types": "./build/npm/types/index.d.ts", - "default": "./build/npm/esm/index.js" - }, - "require": { - "types": "./build/npm/types/index.d.ts", - "default": "./build/npm/cjs/index.js" - } - } - }, - "typesVersions": { - "<4.9": { - "build/npm/types/index.d.ts": [ - "build/npm/types-ts3.8/index.d.ts" - ] - } - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@sentry/browser": "^8.35.0", - "@sentry/core": "8.35.0", - "@sentry/types": "8.35.0", - "@sentry/utils": "8.35.0", - "launchdarkly-js-client-sdk": "^3.5.0" - }, - "scripts": { - "build": "run-p build:transpile build:types build:bundle", - "build:transpile": "rollup -c rollup.npm.config.mjs", - "build:bundle": "rollup -c rollup.bundle.config.mjs", - "build:dev": "run-p build:transpile build:types", - "build:types": "run-s build:types:core build:types:downlevel", - "build:types:core": "tsc -p tsconfig.types.json", - "build:types:downlevel": "yarn downlevel-dts build/npm/types build/npm/types-ts3.8 --to ts3.8 && yarn node ./scripts/shim-preact-export.js", - "build:watch": "run-p build:transpile:watch build:bundle:watch build:types:watch", - "build:dev:watch": "run-p build:transpile:watch build:types:watch", - "build:transpile:watch": "yarn build:transpile --watch", - "build:bundle:watch": "yarn build:bundle --watch", - "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:tarball": "npm pack", - "circularDepCheck": "madge --circular src/index.ts", - "clean": "rimraf build sentry-internal-launchdarkly-*.tgz", - "fix": "eslint . --format stylish --fix", - "lint": "eslint . --format stylish", - "test": "jest", - "test:watch": "jest --watch", - "yalc:publish": "yalc publish --push --sig" - }, - "volta": { - "extends": "../../package.json" - }, - "sideEffects": false -} diff --git a/packages/launchdarkly/rollup.bundle.config.mjs b/packages/launchdarkly/rollup.bundle.config.mjs deleted file mode 100644 index f79e2cd63d7e..000000000000 --- a/packages/launchdarkly/rollup.bundle.config.mjs +++ /dev/null @@ -1,12 +0,0 @@ -import { makeBaseBundleConfig, makeBundleConfigVariants } from '@sentry-internal/rollup-utils'; - -const baseBundleConfig = makeBaseBundleConfig({ - bundleType: 'addon', - entrypoints: ['src/index.ts'], - licenseTitle: '@sentry/launchdarkly', - outputFileBase: () => 'bundles/launchdarkly', -}); - -const builds = makeBundleConfigVariants(baseBundleConfig); - -export default builds; diff --git a/packages/launchdarkly/rollup.npm.config.mjs b/packages/launchdarkly/rollup.npm.config.mjs deleted file mode 100644 index 3b4431fa6829..000000000000 --- a/packages/launchdarkly/rollup.npm.config.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; - -export default makeNPMConfigVariants( - makeBaseNPMConfig({ - hasBundles: true, - packageSpecificConfig: { - output: { - // set exports to 'named' or 'auto' so that rollup doesn't warn - exports: 'named', - // set preserveModules to false because for Replay we actually want - // to bundle everything into one file. - preserveModules: - process.env.SENTRY_BUILD_PRESERVE_MODULES === undefined - ? false - : Boolean(process.env.SENTRY_BUILD_PRESERVE_MODULES), - }, - }, - }), -); diff --git a/packages/launchdarkly/scripts/shim-preact-export.js b/packages/launchdarkly/scripts/shim-preact-export.js deleted file mode 100644 index bd74e4da0a05..000000000000 --- a/packages/launchdarkly/scripts/shim-preact-export.js +++ /dev/null @@ -1,75 +0,0 @@ -// preact does not support more modern TypeScript versions, which breaks our users that depend on older -// TypeScript versions. To fix this, we shim the types from preact to be any and remove the dependency on preact -// for types directly. This script is meant to be run after the build/npm/types-ts3.8 directory is created. - -// Path: build/npm/types-ts3.8/global.d.ts - -const fs = require('fs'); -const path = require('path'); - -/** - * This regex looks for preact imports we can replace and shim out. - * - * Example: - * import { ComponentChildren, VNode } from 'preact'; - */ -const preactImportRegex = /import\s*{\s*([\w\s,]+)\s*}\s*from\s*'preact'\s*;?/; - -function walk(dir) { - const files = fs.readdirSync(dir); - files.forEach(file => { - const filePath = path.join(dir, file); - const stat = fs.lstatSync(filePath); - if (stat.isDirectory()) { - walk(filePath); - } else { - if (filePath.endsWith('.d.ts')) { - const content = fs.readFileSync(filePath, 'utf8'); - const capture = preactImportRegex.exec(content); - if (capture) { - const groups = capture[1].split(',').map(s => s.trim()); - - // This generates a shim snippet to replace the type imports from preact - // It generates a snippet based on the capture groups of preactImportRegex. - // - // Example: - // - // import type { ComponentChildren, VNode } from 'preact'; - // becomes - // type ComponentChildren: any; - // type VNode: any; - const snippet = groups.reduce((acc, curr) => { - const searchableValue = curr.includes(' as ') ? curr.split(' as ')[1] : curr; - - // look to see if imported as value, then we have to use declare const - if (content.includes(`typeof ${searchableValue}`)) { - return `${acc}declare const ${searchableValue}: any;\n`; - } - - // look to see if generic type like Foo - if (content.includes(`${searchableValue}<`)) { - return `${acc}type ${searchableValue} = any;\n`; - } - - // otherwise we can just leave as type - return `${acc}type ${searchableValue} = any;\n`; - }, ''); - - // we then can remove the import from preact - const newContent = content.replace(preactImportRegex, '// replaced import from preact'); - - // and write the new content to the file - fs.writeFileSync(filePath, snippet + newContent, 'utf8'); - } - } - } - }); -} - -function run() { - // recurse through build/npm/types-ts3.8 directory - const dir = path.join('build', 'npm', 'types-ts3.8'); - walk(dir); -} - -run(); diff --git a/packages/launchdarkly/src/core/integration.ts b/packages/launchdarkly/src/core/integration.ts deleted file mode 100644 index e9702251b048..000000000000 --- a/packages/launchdarkly/src/core/integration.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* eslint-disable @sentry-internal/sdk/no-class-field-initializers */ - -import * as Sentry from '@sentry/browser'; -import type { Client as SentryClient, Event, EventHint, IntegrationFn } from '@sentry/types'; -import type { LDContext, LDEvaluationDetail, LDInspectionFlagUsedHandler } from 'launchdarkly-js-client-sdk'; -import type { LaunchDarklyOptions } from '../types'; -import { insertToFlagBuffer } from '@sentry/utils'; - -/** - * Sentry integration for capturing feature flags from LaunchDarkly. - * - * See the [feature flag documentation](TODO:) for more information. - * - * @example - * ``` - * import {SentryInspector, launchDarklyIntegration} from '@sentry/launchdarkly'; - * import {LDClient} from 'launchdarkly-js-client-sdk'; - * - * Sentry.init(..., integrations: [launchDarklyIntegration()]) - * const ldClient = LDClient.initialize(..., inspectors: [SentryInspector]); - * ``` - */ -export const launchDarklyIntegration = ((_options?: LaunchDarklyOptions) => { - return { - name: 'launchdarkly', - - processEvent(event: Event, _hint: EventHint, _client: SentryClient): Event { - const scope = Sentry.getCurrentScope(); - const flagContext = scope.getScopeData().contexts.flags; - - if (event.contexts === undefined) { - event.contexts = {}; - } - event.contexts.flags = flagContext; - return event; - }, - }; -}) satisfies IntegrationFn; - -/** - * LaunchDarkly hook that listens for flag evaluations and updates the - * flagBuffer in our current scope. - * - * This needs to be registered separately in the LDClient, after initializing - * Sentry. - */ -export class SentryInspector implements LDInspectionFlagUsedHandler { - public name = 'sentry-flag-auditor'; - - public type = 'flag-used' as const; - - // We don't want the handler to impact the performance of the user's flag evaluations. - public synchronous = false; - - /** - * Handle a flag evaluation by storing its name and value on the current scope. - */ - public method(flagKey: string, flagDetail: LDEvaluationDetail, _context: LDContext): void { - if (typeof flagDetail.value === 'boolean') { - const scopeContexts = Sentry.getCurrentScope().getScopeData().contexts; - if (!scopeContexts.flags) { - scopeContexts.flags = {values: []} - } - const flagBuffer = scopeContexts.flags.values; - insertToFlagBuffer(flagBuffer, flagKey, flagDetail.value); - } - return; - } -} diff --git a/packages/launchdarkly/src/index.ts b/packages/launchdarkly/src/index.ts deleted file mode 100644 index 8952a4bc1264..000000000000 --- a/packages/launchdarkly/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// This file is used as entry point to generate the npm package and CDN bundles. - -export { launchDarklyIntegration } from './core/integration'; - -// export type { -// } from './types'; diff --git a/packages/launchdarkly/src/types.ts b/packages/launchdarkly/src/types.ts deleted file mode 100644 index 6516bc7a8ff7..000000000000 --- a/packages/launchdarkly/src/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type LaunchDarklyOptions = Record; //TODO: diff --git a/packages/launchdarkly/test.setup.ts b/packages/launchdarkly/test.setup.ts deleted file mode 100644 index 05a762e60d50..000000000000 --- a/packages/launchdarkly/test.setup.ts +++ /dev/null @@ -1,255 +0,0 @@ -import { printDiffOrStringify } from 'jest-matcher-utils'; -import { vi } from 'vitest'; -import type { Mocked, MockedFunction } from 'vitest'; - -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { getClient } from '@sentry/core'; -import type { ReplayRecordingData, Transport } from '@sentry/types'; -import * as SentryUtils from '@sentry/utils'; - -import type { ReplayContainer, Session } from './src/types'; - -type MockTransport = MockedFunction; - -vi.spyOn(SentryUtils, 'isBrowser').mockImplementation(() => true); - -type EnvelopeHeader = { - event_id: string; - sent_at: string; - sdk: { - name: string; - version?: string; - }; -}; - -type ReplayEventHeader = { type: 'replay_event' }; -type ReplayEventPayload = Record; -type RecordingHeader = { type: 'replay_recording'; length: number }; -type RecordingPayloadHeader = Record; -type SentReplayExpected = { - envelopeHeader?: EnvelopeHeader; - replayEventHeader?: ReplayEventHeader; - replayEventPayload?: ReplayEventPayload; - recordingHeader?: RecordingHeader; - recordingPayloadHeader?: RecordingPayloadHeader; - recordingData?: ReplayRecordingData; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const toHaveSameSession = function (received: Mocked, expected: undefined | Session) { - const pass = this.equals(received.session?.id, expected?.id) as boolean; - - const options = { - isNot: this.isNot, - promise: this.promise, - }; - - return { - pass, - message: () => - `${this.utils.matcherHint('toHaveSameSession', undefined, undefined, options)}\n\n${printDiffOrStringify( - expected, - received.session, - 'Expected', - 'Received', - )}`, - }; -}; - -type Result = { - passed: boolean; - key: string; - expectedVal: SentReplayExpected[keyof SentReplayExpected]; - actualVal: SentReplayExpected[keyof SentReplayExpected]; -}; -type Call = [ - EnvelopeHeader, - [ - [ReplayEventHeader | undefined, ReplayEventPayload | undefined], - [RecordingHeader | undefined, RecordingPayloadHeader | undefined], - ], -]; -type CheckCallForSentReplayResult = { pass: boolean; call: Call | undefined; results: Result[] }; - -function checkCallForSentReplay( - call: Call | undefined, - expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, -): CheckCallForSentReplayResult { - const envelopeHeader = call?.[0]; - const envelopeItems = call?.[1] || [[], []]; - const [[replayEventHeader, replayEventPayload], [recordingHeader, recordingPayload] = []] = envelopeItems; - - // @ts-expect-error recordingPayload is always a string in our tests - const [recordingPayloadHeader, recordingData] = recordingPayload?.split('\n') || []; - - const actualObj: Required = { - // @ts-expect-error Custom envelope - envelopeHeader: envelopeHeader, - // @ts-expect-error Custom envelope - replayEventHeader: replayEventHeader, - // @ts-expect-error Custom envelope - replayEventPayload: replayEventPayload, - // @ts-expect-error Custom envelope - recordingHeader: recordingHeader, - recordingPayloadHeader: recordingPayloadHeader && JSON.parse(recordingPayloadHeader), - recordingData, - }; - - const isObjectContaining = expected && 'sample' in expected && 'inverse' in expected; - const expectedObj = isObjectContaining - ? (expected as { sample: SentReplayExpected }).sample - : (expected as SentReplayExpected); - - if (isObjectContaining) { - // eslint-disable-next-line no-console - console.warn('`expect.objectContaining` is unnecessary when using the `toHaveSentReplay` matcher'); - } - - const results = expected - ? Object.keys(expectedObj) - .map(key => { - const actualVal = actualObj[key as keyof SentReplayExpected]; - const expectedVal = expectedObj[key as keyof SentReplayExpected]; - const passed = !expectedVal || this.equals(actualVal, expectedVal); - - return { passed, key, expectedVal, actualVal }; - }) - .filter(({ passed }) => !passed) - : []; - - const pass = Boolean(call && (!expected || results.length === 0)); - - return { - pass, - call, - results, - }; -} - -/** - * Only want calls that send replay events, i.e. ignore error events - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function getReplayCalls(calls: any[][][]): any[][][] { - return calls - .map(call => { - const arg = call[0]; - if (arg.length !== 2) { - return []; - } - - if (!arg[1][0].find(({ type }: { type: string }) => ['replay_event', 'replay_recording'].includes(type))) { - return []; - } - - return [arg]; - }) - .filter(Boolean); -} - -/** - * Checks all calls to `fetch` and ensures a replay was uploaded by - * checking the `fetch()` request's body. - */ -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const toHaveSentReplay = function ( - _received: Mocked, - expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, -) { - const { calls } = (getClient()?.getTransport()?.send as MockTransport).mock; - - let result: CheckCallForSentReplayResult; - - const expectedKeysLength = expected - ? ('sample' in expected ? Object.keys(expected.sample) : Object.keys(expected)).length - : 0; - - const replayCalls = getReplayCalls(calls); - - for (const currentCall of replayCalls) { - result = checkCallForSentReplay.call(this, currentCall[0], expected); - if (result.pass) { - break; - } - - // stop on the first call where any of the expected obj passes - if (result.results.length < expectedKeysLength) { - break; - } - } - - // @ts-expect-error use before assigned - const { results, call, pass } = result; - - const options = { - isNot: this.isNot, - promise: this.promise, - }; - - return { - pass, - message: () => - !call - ? pass - ? 'Expected Replay to not have been sent, but a request was attempted' - : 'Expected Replay to have been sent, but a request was not attempted' - : `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results - .map(({ key, expectedVal, actualVal }: Result) => - printDiffOrStringify(expectedVal, actualVal, `Expected (key: ${key})`, `Received (key: ${key})`), - ) - .join('\n')}`, - }; -}; - -/** - * Checks the last call to `fetch` and ensures a replay was uploaded by - * checking the `fetch()` request's body. - */ -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const toHaveLastSentReplay = function ( - _received: Mocked, - expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, -) { - const { calls } = (getClient()?.getTransport()?.send as MockTransport).mock; - const replayCalls = getReplayCalls(calls); - - const lastCall = replayCalls[calls.length - 1]?.[0]; - - const { results, call, pass } = checkCallForSentReplay.call(this, lastCall, expected); - - const options = { - isNot: this.isNot, - promise: this.promise, - }; - - return { - pass, - message: () => - !call - ? pass - ? 'Expected Replay to not have been sent, but a request was attempted' - : 'Expected Replay to have last been sent, but a request was not attempted' - : `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results - .map(({ key, expectedVal, actualVal }: Result) => - printDiffOrStringify(expectedVal, actualVal, `Expected (key: ${key})`, `Received (key: ${key})`), - ) - .join('\n')}`, - }; -}; - -expect.extend({ - toHaveSameSession, - toHaveSentReplay, - toHaveLastSentReplay, -}); - -interface CustomMatchers { - toHaveSentReplay(expected?: SentReplayExpected): R; - toHaveLastSentReplay(expected?: SentReplayExpected): R; - toHaveSameSession(expected: undefined | Session): R; -} - -declare module 'vitest' { - type Assertion = CustomMatchers; - type AsymmetricMatchersContaining = CustomMatchers; -} diff --git a/packages/launchdarkly/tsconfig.json b/packages/launchdarkly/tsconfig.json deleted file mode 100644 index cd1b8207ea06..000000000000 --- a/packages/launchdarkly/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "lib": ["DOM", "ES2018"], - "module": "esnext" - }, - "include": ["src/**/*.ts"] -} diff --git a/packages/launchdarkly/tsconfig.test.json b/packages/launchdarkly/tsconfig.test.json deleted file mode 100644 index bb7130d948c0..000000000000 --- a/packages/launchdarkly/tsconfig.test.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "./tsconfig.json", - - "include": ["test/**/*.ts", "vitest.config.ts", "test.setup.ts"], - - "compilerOptions": { - "types": ["node"], - "esModuleInterop": true, - "allowJs": true, - "noImplicitAny": true, - "noImplicitThis": false, - "strictNullChecks": true, - "strictPropertyInitialization": false - } -} diff --git a/packages/launchdarkly/tsconfig.types.json b/packages/launchdarkly/tsconfig.types.json deleted file mode 100644 index 374fd9bc9364..000000000000 --- a/packages/launchdarkly/tsconfig.types.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - - "compilerOptions": { - "declaration": true, - "declarationMap": true, - "emitDeclarationOnly": true, - "outDir": "build/npm/types" - } -} diff --git a/packages/launchdarkly/vitest.config.ts b/packages/launchdarkly/vitest.config.ts deleted file mode 100644 index 976d9c37074d..000000000000 --- a/packages/launchdarkly/vitest.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -import baseConfig from '../../vite/vite.config'; - -export default defineConfig({ - ...baseConfig, - test: { - ...baseConfig.test, - setupFiles: ['./test.setup.ts'], - reporters: ['default'], - }, -}); From d9c5860c2add9237ed4b0583e52deac43d1e4e81 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:16:19 -0800 Subject: [PATCH 19/19] Delete LD from package.json and update lock --- package.json | 1 - yarn.lock | 42 ------------------------------------------ 2 files changed, 43 deletions(-) diff --git a/package.json b/package.json index b776421b71b1..ce3f91932c64 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "packages/gatsby", "packages/google-cloud-serverless", "packages/integration-shims", - "packages/launchdarkly", "packages/nestjs", "packages/nextjs", "packages/node", diff --git a/yarn.lock b/yarn.lock index 4e2971138219..652c5721f5b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8390,14 +8390,6 @@ "@sentry/cli-win32-i686" "2.37.0" "@sentry/cli-win32-x64" "2.37.0" -"@sentry/core@8.35.0": - version "8.35.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.35.0.tgz#17090f4d2d3bb983d9d99ecd2d27f4e9e107e0b0" - integrity sha512-Ci0Nmtw5ETWLqQJGY4dyF+iWh7PWKy6k303fCEoEmqj2czDrKJCp7yHBNV0XYbo00prj2ZTbCr6I7albYiyONA== - dependencies: - "@sentry/types" "8.35.0" - "@sentry/utils" "8.35.0" - "@sentry/rollup-plugin@2.22.6": version "2.22.6" resolved "https://registry.yarnpkg.com/@sentry/rollup-plugin/-/rollup-plugin-2.22.6.tgz#74e9ab69729ee024a497b21b66be3b1992e786d5" @@ -8406,18 +8398,6 @@ "@sentry/bundler-plugin-core" "2.22.6" unplugin "1.0.1" -"@sentry/types@8.35.0": - version "8.35.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.35.0.tgz#535c807800f7e378f61416f30177c0ef81b95012" - integrity sha512-AVEZjb16MlYPifiDDvJ19dPQyDn0jlrtC1PHs6ZKO+Rzyz+2EX2BRdszvanqArldexPoU1p5Bn2w81XZNXThBA== - -"@sentry/utils@8.35.0": - version "8.35.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.35.0.tgz#1e099fcbc60040091c79f028a83226c145d588ee" - integrity sha512-MdMb6+uXjqND7qIPWhulubpSeHzia6HtxeJa8jYI09OCvIcmNGPydv/Gx/LZBwosfMHrLdTWcFH7Y7aCxrq7cg== - dependencies: - "@sentry/types" "8.35.0" - "@sentry/vite-plugin@2.22.6", "@sentry/vite-plugin@^2.22.6": version "2.22.6" resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-2.22.6.tgz#d08a1ede05f137636d5b3c61845d24c0114f0d76" @@ -18049,11 +18029,6 @@ fake-indexeddb@^4.0.1: dependencies: realistic-structured-clone "^3.0.0" -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w== - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -22297,23 +22272,6 @@ launch-editor@^2.9.1: picocolors "^1.0.0" shell-quote "^1.8.1" -launchdarkly-js-client-sdk@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/launchdarkly-js-client-sdk/-/launchdarkly-js-client-sdk-3.5.0.tgz#cb0e3d6fc21e56750aa86fcaf75733d7f510946f" - integrity sha512-3dgxC9S8K2ix6qjdArjZGOJPtAytgfQTuE+vWgjWJK7725rpYbuqbHghIFr5B0+WyWyVBYANldjWd1JdtYLwsw== - dependencies: - escape-string-regexp "^4.0.0" - launchdarkly-js-sdk-common "5.4.0" - -launchdarkly-js-sdk-common@5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/launchdarkly-js-sdk-common/-/launchdarkly-js-sdk-common-5.4.0.tgz#c9787daebe0b583b01d2334218524ea142c85001" - integrity sha512-Kb3SDcB6S0HUpFNBZgtEt0YUV/fVkyg+gODfaOCJQ0Y0ApxLKNmmJBZOrPE2qIdzw536u4BqEjtaJdqJWCEElg== - dependencies: - base64-js "^1.3.0" - fast-deep-equal "^2.0.1" - uuid "^8.0.0" - lazystream@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638"