Skip to main content

JavaScript Integration

RemixWarp supports powerful extension APIs and embedding workflows. Arbitrary “JavaScript blocks�?are not part of RemixWarp; instead, use unsandboxed extensions for advanced behavior or the Packager to include custom JavaScript in standalone builds.

Understanding JavaScript in RemixWarp​

What is supported?​

  • Unsandboxed extensions: Write extensions that run in the editor context and interact with the VM, renderer, and runtime.
  • Packager custom JS: When exporting with the Packager, include custom JavaScript in the output for standalone deployments.
  • Embedding: Communicate with embedded projects via postMessage from the host page.

Security Model​

  • Sandboxed extensions: Limited access; blocks may incur a per-frame delay.
  • Unsandboxed extensions: Full access to internal APIs; must remain stable and not block the main thread.
  • Packager JS: Runs in your packaged app’s context; follow CSP best practices.

Enabling Advanced Behavior​

Unsandboxed Extensions​

Load your extension via URL or register directly in an IIFE:

https://your-server.com

Packager​

Use the Packager to inject custom JS into the exported app.

Security Notice

Unsandboxed extensions and Packager JS run with elevated privileges. Only load code you trust.

Unsandboxed Extension Patterns​

Interacting with Scratch​

Accessing Variables​

Read and modify Scratch variables from an unsandboxed extension:

// Get variable value
const score = vm.runtime.getTargetForStage().variables[variableId].value;

// Set variable value
vm.runtime.getTargetForStage().variables[variableId].value = 100;

// Get variable by name
const scoreVar = vm.runtime.getTargetForStage().lookupVariableByNameAndType('score', '');
scoreVar.value = 200;

Controlling Sprites​

Manipulate sprites programmatically:

// Get current sprite
const sprite = vm.runtime.getEditingTarget();

// Change position
sprite.setXY(100, 50);

// Change costume
sprite.setCostume(0);

// Change size
sprite.setSize(150);

// Set rotation
sprite.setDirection(90);

Broadcasting Events​

Trigger Scratch broadcasts:

// Simple broadcast
vm.runtime.startHats('event_whenbroadcastreceived', {
BROADCAST_OPTION: 'my_event'
});

// Broadcast with data
vm.runtime.startHats('event_whenbroadcastreceived', {
BROADCAST_OPTION: 'data_received',
data: { score: 100, level: 5 }
});

Web API Integration (unsandboxed extensions)​

Fetch API​

Make HTTP requests:

// GET request
const response = await fetch('https://your-server.com');
const data = await response.json();
console.log(data);

// POST request
const response = await fetch('https://your-server.com', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice', score: 100 })
});

Local Storage​

Persist data across sessions:

// Save data
localStorage.setItem('gameData', JSON.stringify({
highScore: 1000,
playerName: 'Alice',
level: 5
}));

// Load data
const gameData = JSON.parse(localStorage.getItem('gameData') || '{}');
const highScore = gameData.highScore || 0;

Geolocation​

Access user location:

navigator.geolocation.getCurrentPosition(
(position) => {
const lat = position.coords.latitude;
const lon = position.coords.longitude;
console.log(`Location: ${lat}, ${lon}`);
},
(error) => {
console.error('Location access denied:', error);
}
);

Camera and Microphone​

Access media devices:

// Get camera stream
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const video = document.createElement('video');
video.srcObject = stream;
video.play();

// Get microphone stream
const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });

External Libraries (Packager or host page)​

Including Libraries​

Add external JavaScript libraries:

// Load library dynamically
const script = document.createElement('script');
script.src = 'https://your-server.com';
document.head.appendChild(script);

// Wait for library to load
script.onload = () => {
// Library is now available
const chart = new Chart(ctx, config);
};

Chart.js for Data Visualization​

// Create a chart
const ctx = document.createElement('canvas').getContext('2d');
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow'],
datasets: [{
data: [12, 19, 3],
backgroundColor: ['red', 'blue', 'yellow']
}]
}
});

Three.js for 3D Graphics​

// Create 3D scene
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();

// Add to page
document.body.appendChild(renderer.domElement);

Socket.io for Real-time Communication​

// Connect to WebSocket server
const socket = io('https://your-server.com');

socket.on('connect', () => {
console.log('Connected to server');
});

socket.on('gameUpdate', (data) => {
// Handle real-time game updates
updateGameState(data);
});

Advanced Patterns​

Custom Extensions​

Create project-specific extensions:

class MyCustomExtension {
getInfo() {
return {
id: 'myextension',
name: 'My Custom Extension',
blocks: [
{
opcode: 'customBlock',
blockType: 'reporter',
text: 'custom calculation [NUM]',
arguments: {
NUM: { type: 'number', defaultValue: 10 }
}
}
]
};
}

customBlock(args) {
return Math.pow(args.NUM, 2) + 1;
}
}

// Register extension (unsandboxed)
Scratch.extensions.register(new MyCustomExtension());

Event System​

Create custom event systems:

class EventManager {
constructor() {
this.listeners = {};
}

on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}

emit(event, data) {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => callback(data));
}
}
}

const events = new EventManager();

// Usage in Scratch
events.on('scoreUpdate', (score) => {
vm.runtime.getTargetForStage().lookupVariableByNameAndType('score', '').value = score;
});

Performance Monitoring​

Monitor project performance:

class PerformanceMonitor {
constructor() {
this.startTime = performance.now();
this.frameCount = 0;
}

measureFrame() {
this.frameCount++;
const currentTime = performance.now();
const elapsed = currentTime - this.startTime;

if (elapsed >= 1000) {
const fps = this.frameCount / (elapsed / 1000);
console.log(`FPS: ${fps.toFixed(1)}`);

this.frameCount = 0;
this.startTime = currentTime;
}
}
}

const monitor = new PerformanceMonitor();
vm.runtime.on('PROJECT_RUN_START', () => {
monitor.measureFrame();
});

Integration Patterns​

Block-JavaScript Bridge​

Create seamless integration between blocks and JavaScript:

// Create bridge object
window.ScratchBridge = {
// Call from blocks: (call js function [bridge.calculate] with [10])
calculate: (input) => {
return Math.complex.calculation(input);
},

// Call from blocks: (call js function [bridge.saveData] with [data])
saveData: (data) => {
localStorage.setItem('projectData', JSON.stringify(data));
return 'saved';
},

// Call from blocks: (call js function [bridge.loadData])
loadData: () => {
return JSON.parse(localStorage.getItem('projectData') || '{}');
}
};

State Synchronization​

Keep JavaScript and Scratch state synchronized:

class StateSynchronizer {
constructor(vm) {
this.vm = vm;
this.jsState = {};
this.setupWatchers();
}

setupWatchers() {
// Watch Scratch variables
this.vm.runtime.on('VARIABLE_CHANGED', (variable) => {
this.jsState[variable.name] = variable.value;
this.onStateChange(variable.name, variable.value);
});
}

updateScratchVariable(name, value) {
const variable = this.vm.runtime.getTargetForStage()
.lookupVariableByNameAndType(name, '');
if (variable) {
variable.value = value;
}
}

onStateChange(name, value) {
// Custom logic when state changes
console.log(`${name} changed to ${value}`);
}
}

Security Best Practices​

Input Validation​

Always validate data from external sources:

function validateInput(input) {
// Check type
if (typeof input !== 'string') return false;

// Check length
if (input.length > 1000) return false;

// Check for dangerous patterns
if (/<script|javascript:|data:/i.test(input)) return false;

return true;
}

Sanitization​

Sanitize data before using it:

function sanitizeHTML(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}

Error Handling​

Implement robust error handling:

try {
const result = riskyOperation();
return result;
} catch (error) {
console.error('Operation failed:', error);
return 'Error occurred';
}

Debugging JavaScript​

Console Logging​

Use console methods for debugging:

console.log('Debug info:', data);
console.warn('Warning message');
console.error('Error occurred');
console.table(arrayData);

Browser Developer Tools​

  • F12: Open developer tools
  • Console Tab: View logs and execute code
  • Sources Tab: Set breakpoints and debug
  • Network Tab: Monitor API calls

Performance Profiling​

Profile JavaScript performance:

console.time('operation');
// Your code here
console.timeEnd('operation');

JavaScript integration in RemixWarp opens up unlimited possibilities for creating sophisticated, interactive projects. Use these features responsibly and always consider security implications when working with custom code!