Skip to content
93 changes: 93 additions & 0 deletions AISKU/Tests/CloudFlareWorkerRepro/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# ApplicationInsights with Angular SSR in Cloudflare Workers - Issue Reproduction

This repository contains scripts and tools to reproduce and analyze the issue reported in [ApplicationInsights-JS issue #2523](https://github.com/microsoft/ApplicationInsights-JS/issues/2523), where the ApplicationInsights SDK breaks Angular Server-Side Rendering in Cloudflare Workers.

## Setup and Reproduction

This reproducer includes several scripts to help diagnose the issue:

### 1. Basic Setup (`setup.sh`)

This script creates a simple Angular application with Cloudflare Worker integration and ApplicationInsights. It:

- Creates a new Angular project
- Adds Angular Universal for SSR
- Installs ApplicationInsights SDK
- Configures server routes for SSR
- Sets up AppComponent with ApplicationInsights initialization
- Creates a Wrangler configuration for Cloudflare Workers
- Adds a bundle analysis script

Usage:
```bash
chmod +x setup.sh
./setup.sh
```

### 2. Testing Workarounds (`test-workarounds.sh`)

This script implements and tests the workarounds mentioned in the issue:

- Using esbuild with `preserveNames=false`
- Using dynamic imports to load ApplicationInsights only in client-side context

Usage:
```bash
chmod +x test-workarounds.sh
./test-workarounds.sh
```

### 3. Analyzing the ApplicationInsights SDK (`analyze-appinsights.js`)

This script scans the ApplicationInsights SDK code to identify patterns that might cause issues with esbuild in Cloudflare Workers:

- Looks for `Object.defineProperty` calls
- Checks for `.name` property access or assignment
- Identifies use of `__name` metadata
- Scans for dynamic function creation and property redefinition

Usage:
```bash
node analyze-appinsights.js
```

### 4. Testing esbuild Configurations (`test-esbuild.js`)

This script tests different esbuild configurations to understand how they process function names and properties:

- Creates test code with various function types
- Builds with different esbuild configurations
- Analyzes the output for `__name` helper functions and property definitions

Usage:
```bash
node test-esbuild.js
```

## Expected Results

After running these scripts, you should be able to:

1. Reproduce the "Cannot redefine property: name" error in Cloudflare Workers
2. Understand how esbuild processes function names and properties
3. Identify patterns in the ApplicationInsights SDK that trigger esbuild to add property redefinition code
4. Test workarounds to see if they resolve the issue

## Key Findings

(This section will be updated after running the reproduction scripts and analyzing the results)

## Recommendations

Based on the reproduction and analysis, potential solutions might include:

1. Modifying the esbuild configuration when bundling for Cloudflare Workers
2. Adding specific detection for Cloudflare Workers environments in the SDK
3. Providing guidance to users on how to properly initialize the SDK in SSR contexts
4. Restructuring specific parts of the SDK to avoid triggering esbuild's property redefinition

## References

- [ApplicationInsights-JS Issue #2523](https://github.com/microsoft/ApplicationInsights-JS/issues/2523)
- [Cloudflare Worker documentation](https://developers.cloudflare.com/workers/)
- [Angular SSR with Cloudflare](https://developers.cloudflare.com/workers/frameworks/framework-guides/angular/)
124 changes: 124 additions & 0 deletions AISKU/Tests/CloudFlareWorkerRepro/analyze-appinsights.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* This script analyzes the ApplicationInsights SDK to understand how it might
* interact with esbuild and cause property redefinition issues in Cloudflare Workers.
*/

const fs = require('fs');
const path = require('path');

// Directory where node_modules are located
const nodeModulesDir = path.join(__dirname, 'angular-cloudflare-repro', 'node_modules');
const appInsightsDir = path.join(nodeModulesDir, '@microsoft', 'applicationinsights-web');

// Function to scan a file for potential issues
function analyzeFile(filePath) {
try {
const content = fs.readFileSync(filePath, 'utf8');

// Look for patterns that might cause issues with esbuild
const result = {
path: filePath,
definePropertyCount: (content.match(/Object\.defineProperty/g) || []).length,
namePropertyAccess: (content.match(/\.name\s*=/g) || []).length,
hasNameMetadata: content.includes('__name'),
functionNameUsage: (content.match(/\.name\s*\)/g) || []).length,
dynamicFunctionCreation: content.includes('new Function('),
propertyRedefinition: content.includes('Object.defineProperties'),
reflectionUsage: content.includes('Reflect.')
};

// Look for specific problematic patterns
if (result.definePropertyCount > 0 || result.namePropertyAccess > 0 || result.hasNameMetadata) {
return result;
}

return null;
} catch (error) {
return { path: filePath, error: error.message };
}
}

// Function to recursively scan directories
async function scanDirectory(dir, fileExtensions = ['.js', '.ts']) {
const results = [];

try {
const files = fs.readdirSync(dir);

for (const file of files) {
const filePath = path.join(dir, file);
const stats = fs.statSync(filePath);

if (stats.isDirectory()) {
results.push(...await scanDirectory(filePath, fileExtensions));
} else if (fileExtensions.includes(path.extname(filePath))) {
const result = analyzeFile(filePath);
if (result) {
results.push(result);
}
}
}
} catch (error) {
console.error(`Error scanning directory ${dir}:`, error);
}

return results;
}

// Main function
async function main() {
console.log('Analyzing ApplicationInsights SDK...');

if (!fs.existsSync(appInsightsDir)) {
console.error('ApplicationInsights SDK not found. Please run setup.sh first.');
return;
}

const results = await scanDirectory(appInsightsDir);

// Filter for the most suspicious files
const suspiciousFiles = results
.filter(r => r.definePropertyCount > 0 || r.namePropertyAccess > 0 || r.hasNameMetadata)
.sort((a, b) => (b.definePropertyCount + b.namePropertyAccess) - (a.definePropertyCount + a.namePropertyAccess));

console.log(`\nFound ${suspiciousFiles.length} suspicious files:`);
suspiciousFiles.forEach((file, i) => {
console.log(`\n${i + 1}. ${path.relative(appInsightsDir, file.path)}`);
console.log(` - Object.defineProperty calls: ${file.definePropertyCount}`);
console.log(` - .name property assignments: ${file.namePropertyAccess}`);
console.log(` - Has __name metadata: ${file.hasNameMetadata}`);
console.log(` - Function name usage: ${file.functionNameUsage}`);
if (file.dynamicFunctionCreation) console.log(' - Uses dynamic function creation (new Function())');
if (file.propertyRedefinition) console.log(' - Uses Object.defineProperties');
if (file.reflectionUsage) console.log(' - Uses Reflection API');
});

// Check for use of DynamicProto-JS as it was mentioned in the issue comments
const dynamicProtoDir = path.join(nodeModulesDir, '@microsoft', 'dynamicproto-js');
if (fs.existsSync(dynamicProtoDir)) {
console.log('\nAnalyzing DynamicProto-JS package...');
const dynamicProtoResults = await scanDirectory(dynamicProtoDir);

const suspiciousDynamicProtoFiles = dynamicProtoResults
.filter(r => r.definePropertyCount > 0 || r.namePropertyAccess > 0 || r.hasNameMetadata)
.sort((a, b) => (b.definePropertyCount + b.namePropertyAccess) - (a.definePropertyCount + a.namePropertyAccess));

if (suspiciousDynamicProtoFiles.length > 0) {
console.log(`\nFound ${suspiciousDynamicProtoFiles.length} suspicious files in DynamicProto-JS:`);
suspiciousDynamicProtoFiles.forEach((file, i) => {
console.log(`\n${i + 1}. ${path.relative(dynamicProtoDir, file.path)}`);
console.log(` - Object.defineProperty calls: ${file.definePropertyCount}`);
console.log(` - .name property assignments: ${file.namePropertyAccess}`);
console.log(` - Has __name metadata: ${file.hasNameMetadata}`);
});
} else {
console.log('\nNo suspicious patterns found in DynamicProto-JS');
}
} else {
console.log('\nDynamicProto-JS package not found');
}

console.log('\nAnalysis complete.');
}

main().catch(console.error);
153 changes: 153 additions & 0 deletions AISKU/Tests/CloudFlareWorkerRepro/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/bin/bash

# Create a new directory for the reproduction
echo "Creating directory for the reproduction..."
mkdir -p angular-cloudflare-repro
cd angular-cloudflare-repro

# Create a package.json file to install dependencies
echo "Creating package.json..."
cat > package.json << 'EOF'
{
"name": "angular-cloudflare-repro",
"version": "0.0.1",
"description": "Reproduction of ApplicationInsights issue with Angular and Cloudflare Worker",
"scripts": {
"start": "npx wrangler dev",
"build": "npm run build:client && npm run build:server",
"build:client": "ng build",
"build:server": "ng run angular-cloudflare-repro:server:production"
}
}
EOF

# Install Angular CLI
echo "Installing Angular CLI..."
npm install -g @angular/cli@latest

# Create a new Angular project
echo "Creating Angular project..."
ng new --skip-git --skip-tests --style=css --routing=true angular-cloudflare-repro

# Navigate to the project directory
cd angular-cloudflare-repro

# Install ApplicationInsights SDK
echo "Installing ApplicationInsights SDK..."
npm install @microsoft/applicationinsights-web

# Install Cloudflare worker dependencies
echo "Installing Cloudflare worker dependencies..."
npm install @cloudflare/workers-types wrangler

# Add Angular Universal SSR
echo "Adding Angular Universal SSR support..."
ng add @nguniversal/express-engine

# Create the server routes file for Angular SSR
echo "Creating server routes file..."
mkdir -p src/app/server
cat > src/app/server/app.route.server.ts << 'EOF'
import { Routes } from '@angular/router';

export const serverRoutes: Routes = [
{
path: '**',
component: null,
data: {
renderMode: 'server'
}
}
];
EOF

# Update AppComponent to include ApplicationInsights
echo "Updating AppComponent with ApplicationInsights..."
cat > src/app/app.component.ts << 'EOF'
import { Component, OnInit } from '@angular/core';
import { ApplicationInsights } from '@microsoft/applicationinsights-web';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'angular-cloudflare-repro';
private _appInsights: ApplicationInsights;

constructor() {
// This initialization alone will cause the issues
this._appInsights = new ApplicationInsights({
config: {
connectionString: 'PLACEHOLDER_CONNECTION_STRING',
enableAutoRouteTracking: true,
enableCorsCorrelation: false,
enableRequestHeaderTracking: true,
enableResponseHeaderTracking: true,
}
});

// Even commenting out loadAppInsights still produces the error
// this._appInsights.loadAppInsights();
}

ngOnInit() {
// Empty for now
}
}
EOF

# Create a Cloudflare Worker configuration
echo "Creating Cloudflare Worker configuration..."
cat > wrangler.toml << 'EOF'
name = "angular-cloudflare-repro"
main = "dist/server/main.js"
compatibility_date = "2023-06-28"
workers_dev = true

[build]
command = "npm run build"
[build.upload]
format = "modules"
EOF

# Create a simple test script to analyze bundle output
echo "Creating bundle analysis script..."
cat > analyze-bundle.js << 'EOF'
const fs = require('fs');
const path = require('path');

// Read the server bundle
const serverBundlePath = path.join(__dirname, 'dist', 'server', 'main.js');
try {
const bundle = fs.readFileSync(serverBundlePath, 'utf8');

// Look for defineProperty and __name occurrences
const definePropertyMatches = bundle.match(/defineProperty/g) || [];
const nameMatches = bundle.match(/__name/g) || [];

console.log(`Found ${definePropertyMatches.length} occurrences of defineProperty`);
console.log(`Found ${nameMatches.length} occurrences of __name`);

// Look for lines that might be redefining the name property
const lines = bundle.split('\n');
const suspiciousLines = lines.filter(line =>
line.includes('defineProperty') && line.includes('name')
);

console.log('\nSuspicious lines that might redefine name property:');
suspiciousLines.forEach((line, i) => {
console.log(`${i + 1}. ${line.trim()}`);
});

} catch (error) {
console.error('Failed to read server bundle:', error);
}
EOF

echo "Setup completed! Next steps:"
echo "1. Run 'cd angular-cloudflare-repro'"
echo "2. Run 'npm run build' to build the application"
echo "3. Run 'node analyze-bundle.js' to analyze the server bundle"
echo "4. Run 'npm start' to start the application locally using Wrangler"
Loading