DEV Community

ekko1500
ekko1500

Posted on

Problem: Scratch Extension Cannot Communicate with scratch-gui / Web Bluetooth Situation

I was developing a custom Scratch extension:

kankaung-extension-bt.js
inside a modified scratch-gui.

Goal:

Scratch Blocks

KanKaung Extension

Web Bluetooth

ESP32 BLE
The ESP32 BLE connection worked when testing directly in Chrome:

navigator.bluetooth.requestDevice()
but when running from Scratch blocks, it failed.

Problem 1: Web Bluetooth worked in console but not from Scratch block
Error
Permissions policy violation:
bluetooth is not allowed in this document
or:

SecurityError:
Failed to execute requestDevice()
Cause
Scratch extensions are not running in the main browser window.

The extension is loaded inside an iframe:

window === top
false
Meaning:

Browser Window

├── scratch-gui

└── Extension iframe
|
└── kankaung-extension-bt.js
The iframe has different permissions and cannot directly access Web Bluetooth.

Problem 2: Trying to access Scratch VM
I tried:

Scratch.vm
or:

Scratch.runtime
Result:

undefined
Cause
External extensions only receive the public Scratch API:

Scratch.BlockType
Scratch.ArgumentType
Scratch.extensions.register()
They do NOT receive:

vm
runtime
redux store
Wrong Approach
Trying:

window.dispatchEvent(
new CustomEvent("KANKAUNG_CONNECT")
)
and listening in:

scratch-gui
did not work.

Why?
Because events stay inside the iframe.

Extension iframe

window.dispatchEvent()
|
X
|
scratch-gui window
The two windows are separated.

Wrong Approach 2
Trying:

window.parent.dispatchEvent()
or:

window.top.dispatchEvent()
caused:

Blocked a frame with origin "null"
from accessing a cross-origin frame
Cause
Browser security prevents direct access between different origins.

Even localhost pages can become:

origin: null
when loaded through iframe sandboxing.

Final Solution: Use postMessage()
postMessage() is designed for iframe communication.

Architecture:

Scratch Extension iframe
|
|
| window.parent.postMessage()
|

scratch-gui
containers/gui.jsx
|
|
| Web Bluetooth API
|

ESP32 BLE
Extension → scratch-gui
Example:

window.parent.postMessage(
{
type:"KANKAUNG_SEND",
command:"LED_ON"
},
"*");
scratch-gui receives message
In:

src/containers/gui.jsx
listen:

window.addEventListener(
"message",
(event)=>{

if(event.data.type==="KANKAUNG_SEND")
{
    kankaungBluetooth.send(
        event.data.command
    );
}
Enter fullscreen mode Exit fullscreen mode

});
scratch-gui → Extension
Do NOT use:

dispatchEvent()
Use:

window.postMessage(
{
type:"KANKAUNG_STATUS",
status:true
},
"*");
Extension listens:

window.addEventListener(
"message",
(event)=>{

if(event.data.type==="KANKAUNG_STATUS")
{
    this.connected =
    event.data.status;
}
Enter fullscreen mode Exit fullscreen mode

});
Final Communication Flow
User clicks Scratch block

Enter fullscreen mode Exit fullscreen mode

kankaung-extension-bt.js

Enter fullscreen mode Exit fullscreen mode

postMessage()

Enter fullscreen mode Exit fullscreen mode

src/containers/gui.jsx

Enter fullscreen mode Exit fullscreen mode

kankaung-bluetooth.js

Enter fullscreen mode Exit fullscreen mode

navigator.bluetooth

Enter fullscreen mode Exit fullscreen mode

ESP32 BLE

Enter fullscreen mode Exit fullscreen mode

response

Enter fullscreen mode Exit fullscreen mode

postMessage()

Enter fullscreen mode Exit fullscreen mode

Scratch Extension
Key Lessons To Remember

  1. Scratch extension ≠ scratch-gui Extension:

iframe
GUI:

main application
They are separated.

  1. Never use:
    Scratch.vm
    Scratch.runtime
    window.dispatchEvent()
    window.parent.dispatchEvent()
    for communication between extension and scratch-gui.

  2. Use:
    window.parent.postMessage()
    Extension → GUI

and:

window.postMessage()
GUI → Extension

  1. For custom hardware extensions in scratch-gui: Recommended architecture:

Extension
|
| postMessage
|
scratch-gui container
|
| Hardware manager
|
ESP32 / Arduino / Raspberry Pi
This approach is similar to how professional Scratch forks and hardware extensions communicate. You solved the hardest part: getting a custom Scratch extension to talk to your own hardware layer without Scratch Link.

Top comments (0)