Frontend-Scratch Communication (iframe-rpc)
Relevant source files
The following files were used as context for generating this wiki page:
- frontend/src/pages/class/[classId]/session/[sessionId]/task/[taskId]/solve.tsx
- frontend/src/pages/task/[taskId]/detail.tsx
- frontend/src/components/dashboard/CodeView.tsx
- apps/scratch/src/pages/tests/Solve.spec.tsx
- apps/scratch/src/pages/tests/Edit.spec.tsx
- frontend/package.json
- apps/scratch/package.json
- frontend/src/components/task/TaskForm.tsx
The Collimator platform integrates the Scratch programming environment by embedding it within an <iframe> in the main frontend application. This architecture necessitates a robust communication mechanism for the parent frontend to interact with the child scratch application. This is achieved through a custom Remote Procedure Call (RPC) system built on top of the standard window.postMessage API.
This communication is abstracted by the iframe-rpc and iframe-rpc-react libraries, which are used across the frontend and apps/scratch packages. This system allows the main application to send commands to the Scratch instance—such as loading tasks, retrieving student submissions, and setting the locale—and receive data in response, enabling a seamless user experience across the two separate applications.
Sources: frontend/package.json:60, apps/scratch/package.json:48, frontend/src/pages/class/[classId]/session/[sessionId]/task/[taskId]/solve.tsx:4
Architecture Overview
The communication architecture consists of the main frontend application acting as a host for the scratch application, which runs in an isolated <iframe>. The EmbeddedApp.tsx component in the frontend serves as the primary bridge, managing the <iframe> and exposing methods to send requests to the Scratch application.
This diagram illustrates the high-level component relationship.
Frontend Application Pages*: These are Next.js pages that host the EmbeddedApp component. They are responsible for fetching task/solution data and invoking RPC methods on the embedded Scratch application. Examples include pages for solving a task, viewing a task's details, or viewing a past solution.
Sources: frontend/src/pages/class/[classId]/session/[sessionId]/task/[taskId]/solve.tsx, frontend/src/pages/task/[taskId]/detail.tsx, frontend/src/components/dashboard/CodeView.tsx
- EmbeddedApp.tsx: A wrapper component in the
frontendthat renders the<iframe>and sets up theiframe-rpc-reactclient. It exposes asendRequestmethod via a Reactref, which allows parent components to communicate with the iframe. - Scratch Application: A standalone React application (
apps/scratch) that is loaded into the iframe. It has specific pages (/solve,/edit,/show) that initialize an RPC server to listen for and respond to messages from the parent window.
Sources: apps/scratch/src/pages/tests/Solve.spec.tsx:21-28, apps/scratch/src/pages/tests/Edit.spec.tsx:21-28
Communication Flow
The interaction between the frontend and the Scratch app follows a request-response pattern managed by the iframe-rpc library. The frontend initiates a request, and the Scratch app processes it and sends back a result.
The following sequence diagram shows a typical flow for loading a task into the Scratch editor.
This flow is initiated by the onAppAvailable callback in the frontend, which is called once the embedded application is ready to communicate. This ensures that requests are only sent after the RPC connection has been established.
Sources: frontend/src/pages/task/[taskId]/detail.tsx:50-59, frontend/src/pages/class/[classId]/session/[sessionId]/task/[taskId]/solve.tsx:202-225, apps/scratch/src/pages/tests/Solve.spec.tsx:90-99
RPC Methods
The communication interface is defined by a set of RPC methods. The frontend calls these methods to control the Scratch application's state and retrieve data.
| Method | Parameters | Description | Source Files |
|---|---|---|---|
loadTask | { task: Blob, language: string } | Loads a task file (e.g., .sb3) into the Scratch editor. | detail.tsx:52, solve.tsx:220 |
loadSubmission | { task: Blob, submission: Blob, language: string, subTaskId?: string } | Loads a task file and a student's submission file. | solve.tsx:213, CodeView.tsx:78 |
getSubmission | undefined | Requests the current project state as a submission, including test results. | solve.tsx:169 |
getTask | undefined | Requests the current project state, including initial solution and configuration. | solve.tsx:300, Edit.spec.tsx:61 |
setLocale | string | Sets the display language of the Scratch interface. | solve.tsx:191 |
getHeight | undefined | Retrieves the current height of the content within the iframe. | Edit.spec.tsx:47 |
Sources: frontend/src/pages/class/[classId]/session/[sessionId]/task/[taskId]/solve.tsx, frontend/src/pages/task/[taskId]/detail.tsx, frontend/src/components/dashboard/CodeView.tsx, apps/scratch/src/pages/tests/Edit.spec.tsx
Implementation Details
Frontend Integration
The frontend uses the EmbeddedApp component to abstract away the iframe and postMessage logic.
-
URL Construction: The
srcof the iframe is determined based on the task type and the desired mode (solve,show,edit). AscratchAppHostNameconstant is used to build the final URL.
Sources: frontend/src/pages/task/[taskId]/detail.tsx:29-35, frontend/src/components/dashboard/CodeView.tsx:40-46 -
Request Handling: Parent components use a
refto call thesendRequestmethod on theEmbeddedAppinstance. This returns a promise that resolves with the result from the Scratch application.
Sources: frontend/src/pages/class/[classId]/session/[sessionId]/task/[taskId]/solve.tsx:168-172 -
Initialization: The
onAppAvailableprop is a critical part of the lifecycle. It's a callback function thatEmbeddedAppinvokes when the child iframe is ready to receive RPC calls. This is used to perform initial setup, like loading the task file.
Sources: frontend/src/pages/task/[taskId]/detail.tsx:50-59
Scratch Application (Child)
The apps/scratch project is configured to handle incoming messages from the parent window.
-
Event Listener: The application sets up a global
messageevent listener on thewindowobject. This listener parses incoming JSON-RPC 2.0 messages and routes them to the appropriate handler function.
Sources: apps/scratch/src/pages/tests/Solve.spec.tsx:21-28, apps/scratch/src/pages/tests/Edit.spec.tsx:21-28 -
Responding to Parent: After processing a request, the Scratch app uses
window.parent.postMessageto send the result (or an error) back to thefrontendapplication. The testing files demonstrate this by mockingwindow.parent.postMessageand asserting on the messages sent.
Sources: apps/scratch/src/pages/tests/Solve.spec.tsx:32-44, apps/scratch/src/pages/tests/Edit.spec.tsx:46-58
Key Data Structures
Communication relies on well-defined data structures passed as payloads.
Submission
When a user's work is requested via getSubmission or submitted, the Submission object is used. It is defined in iframe-rpc-react/src and contains:
file: ABlobrepresenting the.sb3project file.passedTests: An array ofTestobjects that passed.failedTests: An array ofTestobjects that failed.
Sources: frontend/src/pages/class/[classId]/session/[sessionId]/task/[taskId]/solve.tsx:128-138, frontend/src/components/task/TaskForm.tsx:214-217
Test
Each Test object represents an assertion from the Scratch project.
identifier: A unique ID for the test block.name: The user-defined name of the assertion.contextName: The name of the sprite the assertion belongs to.passed: A boolean indicating the test result.
Sources: frontend/src/pages/class/[classId]/session/[sessionId]/task/[taskId]/solve.tsx:128-138
Summary
The iframe-rpc system is a vital piece of the Collimator architecture, providing a decoupled, message-based communication layer between the main frontend and the embedded Scratch application. It abstracts the complexities of cross-origin iframe communication into a simple request-response API. This enables the platform to seamlessly integrate Scratch for various educational activities, from task creation and editing to student problem-solving and solution review, all while maintaining a clear separation of concerns between the two applications.