Skip to content

Commit

Permalink
Demo + misc. fixes and improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniGuardiola committed Dec 22, 2023
1 parent 55e070b commit fefe796
Show file tree
Hide file tree
Showing 21 changed files with 903 additions and 36 deletions.
5 changes: 5 additions & 0 deletions .changeset/cold-turtles-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rpc-anywhere": patch
---

Better naming for low-level message types.
5 changes: 5 additions & 0 deletions .changeset/lucky-gorillas-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rpc-anywhere": minor
---

Fixed message port transport.
5 changes: 5 additions & 0 deletions .changeset/ninety-clocks-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rpc-anywhere": minor
---

Added debug hooks for logging and debugging.
5 changes: 5 additions & 0 deletions .changeset/odd-cobras-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rpc-anywhere": patch
---

Reduced chance of colision for the transport id key.
5 changes: 5 additions & 0 deletions .changeset/sour-laws-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rpc-anywhere": minor
---

Added a cool demo!
Binary file modified bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions demo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.js
style.css
114 changes: 114 additions & 0 deletions demo/iframe.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<!doctype html>
<html class="overflow-hidden">
<head>
<title>RPC Anywhere demo - iframe</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/style.css" />
</head>
<body class="bg-blue-200">
<div id="sizer" class="p-4 space-y-4">
<input
id="synced-input"
type="text"
class="w-full rounded px-2 py-1 ring-2 ring-slate-900/20 focus:outline-none focus:ring-blue-500"
placeholder="Synced input"
value="This input is synced!"
/>
<button
id="colored-button"
data-color="red"
class="px-4 py-2 bg-red-500 text-white font-bold rounded"
>
Click to change the color of this button!
</button>
<p><span class="font-bold">Story time!</span> Fill in the blanks:</p>

<div class="flex gap-4">
<input
id="story-village-name-input"
type="text"
class="w-full rounded px-2 py-1 ring-2 ring-slate-900/20 focus:outline-none focus:ring-blue-500"
placeholder="Village name"
value="Whispering Meadows"
/>
<input
id="story-animal-input"
type="text"
class="w-full rounded px-2 py-1 ring-2 ring-slate-900/20 focus:outline-none focus:ring-blue-500"
placeholder="Animal"
value="curious rabbit"
/>
</div>

<div class="flex gap-4">
<input
id="story-name-input"
type="text"
class="w-full rounded px-2 py-1 ring-2 ring-slate-900/20 focus:outline-none focus:ring-blue-500"
placeholder="Name"
value="Benny"
/>
<input
id="story-activity-input"
type="text"
class="w-full rounded px-2 py-1 ring-2 ring-slate-900/20 focus:outline-none focus:ring-blue-500"
placeholder="Activity"
value="explore"
/>
</div>

<div class="flex gap-4">
<input
id="story-landmark-input"
type="text"
class="w-full rounded px-2 py-1 ring-2 ring-slate-900/20 focus:outline-none focus:ring-blue-500"
placeholder="Landmark"
value="Old Oak Tree"
/>
<input
id="story-object-input"
type="text"
class="w-full rounded px-2 py-1 ring-2 ring-slate-900/20 focus:outline-none focus:ring-blue-500"
placeholder="Object"
value="enchanted compass"
/>
</div>

<div class="flex gap-4">
<input
id="story-superpower-input"
type="text"
class="w-full rounded px-2 py-1 ring-2 ring-slate-900/20 focus:outline-none focus:ring-blue-500"
placeholder="Superpower"
value="time travel"
/>
<input
id="story-new-title-input"
type="text"
class="w-full rounded px-2 py-1 ring-2 ring-slate-900/20 focus:outline-none focus:ring-blue-500"
placeholder="New heroic title"
value="Guardian of the Meadows"
/>
</div>

<button
id="story-button"
class="px-4 py-2 bg-blue-500 text-white font-bold rounded"
>
Tell me a story!
</button>
<p id="story-result" style="display: none">
The story "<span id="story-title" class="font-bold"></span>" has been
created!
</p>
</div>
<script type="module" src="iframe.js"></script>
</body>
</html>
147 changes: 147 additions & 0 deletions demo/iframe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import {
type _RPCPacket,
createRPC,
createRPCRequestHandler,
createTransportFromMessagePort,
type RPCSchema,
} from "../src/index.js"; // "rpc-anywhere"
// import the parent (remote) schema
import { type ParentSchema } from "./parent.js";

// grab some elements and prepare some stuff
const syncedInputEl = el<HTMLInputElement>("synced-input");
const coloredButtonEl = el("colored-button");
const storyButtonEl = el("story-button");
const storyResultEl = el("story-result");
const storyTitleEl = el("story-title");
const storyVillageNameEl = el<HTMLInputElement>("story-village-name-input");
const storyAnimalEl = el<HTMLInputElement>("story-animal-input");
const storyNameEl = el<HTMLInputElement>("story-name-input");
const storyActivityEl = el<HTMLInputElement>("story-activity-input");
const storyLandmarkEl = el<HTMLInputElement>("story-landmark-input");
const storyObjectEl = el<HTMLInputElement>("story-object-input");
const storySuperpowerEl = el<HTMLInputElement>("story-superpower-input");
const storyNewTitleEl = el<HTMLInputElement>("story-new-title-input");
const colors = ["red", "green", "blue", "purple"] as const;
type Color = (typeof colors)[number];

// request handler
const requestHandler = createRPCRequestHandler({
/**
* Get the current color of the button.
*/
getColor: () => coloredButtonEl.dataset.color as Color,
});

// declare the iframe (local) schema
export type IframeSchema = RPCSchema<
{
messages: {
/**
* Sent when the iframe's RPC is ready.
*/
ready: void;
/**
* Sent when the iframe's input is updated.
*/
iframeInputUpdated: string;
};
},
// infer request types from the request handler
typeof requestHandler
>;

function waitForFrameParentLoad() {
if (window.parent.document.readyState === "complete")
return Promise.resolve();
return new Promise((resolve) =>
window.parent.addEventListener("load", resolve),
);
}

// wait for the parent window to load
waitForFrameParentLoad().then(() => {
console.log("[iframe] The parent has loaded!");

// create the iframe's RPC
const rpc = createRPC<IframeSchema, ParentSchema>({
// provide the transport
transport: createTransportFromMessagePort(window, window.parent, {
// provide a unique ID that matches the parent
transportId: "rpc-anywhere-demo",
}),
// provide the request handler
requestHandler,
// this is for demo purposes - you can ignore it
_debugHooks: { onSend: _debugOnSend, onReceive: _debugOnReceive },
});

// use the proxy as an alias ✨
const parent = rpc.proxy;

// send the ready message
parent.send.ready();

// synced input
syncedInputEl.addEventListener("input", () =>
parent.send.iframeInputUpdated(syncedInputEl.value),
);
rpc.addMessageListener(
"parentInputUpdated",
(value) => (syncedInputEl.value = value),
);

// story time
storyButtonEl.addEventListener("click", async () => {
const { title } = await parent.request.createStory({
villageName: storyVillageNameEl.value,
animal: storyAnimalEl.value,
name: storyNameEl.value,
activity: storyActivityEl.value,
landmark: storyLandmarkEl.value,
object: storyObjectEl.value,
superpower: storySuperpowerEl.value,
newTitle: storyNewTitleEl.value,
});
storyResultEl.style.removeProperty("display");
storyTitleEl.textContent = title;
});
});

// non-demo stuff - you can ignore this :)
// ---------------------------------------

const colorToClass = {
red: "bg-red-500",
green: "bg-green-500",
blue: "bg-blue-500",
purple: "bg-purple-500",
};
function updateButtonColor(color?: Color) {
const currentColor = coloredButtonEl.dataset.color;
const currentClass = colorToClass[currentColor as Color];
const nextColor =
color ??
colors[(colors.indexOf(currentColor as Color) + 1) % colors.length];
const nextClass = colorToClass[nextColor];
coloredButtonEl.dataset.color = nextColor;
coloredButtonEl.classList.remove(currentClass);
coloredButtonEl.classList.add(nextClass);
}
coloredButtonEl.addEventListener("click", () => updateButtonColor());

function el<Element extends HTMLElement>(id: string) {
const element = document.getElementById(id);
if (!element) throw new Error(`Element with id ${id} not found`);
return element as Element;
}

const iframeLogsEl = window.parent.document.querySelector("#iframe-logs")!;
function _debugOnSend(packet: _RPCPacket) {
console.log("[iframe] sent", packet);
(window.parent as any)._debugAppendMessage("send", iframeLogsEl, packet);
}
function _debugOnReceive(packet: _RPCPacket) {
console.log("[iframe] received", packet);
(window.parent as any)._debugAppendMessage("receive", iframeLogsEl, packet);
}
Loading

0 comments on commit fefe796

Please sign in to comment.