Skip to content

Instruments that guard DOM access with typeof window cannot be uploaded, because the bundle shadows window with a throwing Proxy #1802

Open

Description

@joshunrau

Instrument code that guards its DOM access with typeof window !== 'undefined' still throws when the API evaluates the bundle, so the instrument cannot be uploaded or imported. Where the host has no DOM, createBundle's GLOBALS preamble shadows document, self and window with Proxy objects that throw on any property access. They exist to give a better error message, but a Proxy is an object, so typeof window is 'object' on the server. The standard guard then passes, and the guarded window.… access hits the throwing trap. The UMD-style typeof self !== 'undefined' ? self : this check found in many copied-in libraries fails the same way. InstrumentsService.create evaluates every bundle in a Node vm with no DOM, so such an instrument is rejected with Failed to interpret instrument bundle (422), or skipped during a repository import, even though it is correct browser code.

Where

packages/instrument-bundler/src/bundle.ts:19-35:

  const __createProxy = (name) => {
    // ...
    return new Proxy({ name }, {
      get(target, property) {
        throw new Error(formatErrorMessage('get', property.toString(), target.name))
      },
      // ...
  };
  const document = globalThis.document ?? __createProxy('document');
  const self = globalThis.self ?? __createProxy('self');
  const window = globalThis.window ?? __createProxy('window');

Reproduce

  1. Bundle a form whose index.ts has, at module scope, const lang = typeof window !== 'undefined' ? window.navigator.language : 'en'; and export default { kind: 'FORM', lang, … }.
  2. Evaluate the bundle the way the API does: vm.runInContext(bundle, vm.createContext({})), or upload it through POST /v1/instruments.

Actual: Error: Cannot get property 'navigator' of object 'window' in global scope of file 'index.ts' (verified on main with vm), and the upload fails with 422.
Expected: the guard sees no window on the server, the bundle evaluates, and the instrument is stored. Unguarded DOM access at module scope still fails with a message naming the file.

Tests

packages/instrument-bundler/src/__tests__/bundle.test.ts: it('should let a typeof window guard skip DOM access when evaluated without a DOM, so guarded instruments can be uploaded'). Evaluate the bundled string with vm.runInContext in an empty context.

e2e, in testing/src/specs/admin-instruments.spec.ts (or wherever the upload flow is covered): uploading an instrument with a guarded module-scope window access succeeds.

Suggested fix

Drop the shadowing proxies, so document, self and window are truly undeclared when the host has none. Keep the helpful message another way: wrap the evaluation in createBundle's IIFE in a try/catch that rethrows a ReferenceError for document, self or window with globalThis.__ODC_BUNDLER_ERROR_CONTEXT attached, so unguarded access still names the file.

Activity

  1. added
    BugType: existing behavior is wrong
    Priority: LowNice to have, cosmetic or speculative
    Difficulty: MediumStraightforward change, about a day or less
    Area: InstrumentsInstrument authoring API, bundling, runtime, rendering, measures and the built-in library
    on Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: InstrumentsInstrument authoring API, bundling, runtime, rendering, measures and the built-in libraryBugType: existing behavior is wrongDifficulty: MediumStraightforward change, about a day or lessPriority: LowNice to have, cosmetic or speculative

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions