Skip to content

Feat: card popup window #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions apps/extension/components/TokenScriptIframe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,36 @@ export function TokenScriptIframe(props: {
}

if (event.data?.source === "TLINK_API_REQUEST") {
iframeRef.current?.contentWindow?.postMessage(
{
try {
iframeRef.current?.contentWindow?.postMessage(
{
type: "TLINK_API_RESPONSE",
source: "TLINK_API_RESPONSE",
data: {
uid: event.data.data.uid,
method: event.data.data.method,
response: await handleTlinkApiRequest(
event.data.data.method,
event.data.data.payload
)
}
},
"*"
)
} catch (e) {
console.error("TLink API request failed: ", e);
iframeRef.current?.contentWindow?.postMessage({
type: "TLINK_API_RESPONSE",
source: "TLINK_API_RESPONSE",
data: {
uid: event.data.data.uid,
method: event.data.data.method,
response: handleTlinkApiRequest(
event.data.data.method,
event.data.data.payload
)
error: e.message
}
},
"*"
)
}, "*");
}

return;
}

if (event.data?.source === "tlink") {
Expand Down
3 changes: 2 additions & 1 deletion apps/extension/entrypoints/background.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

export default defineBackground(() => {
// never mark the function here async
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
Expand Down Expand Up @@ -37,7 +38,7 @@ export default defineBackground(() => {
}
})
.catch((err) => {
console.error("error handling message", err)
console.error("error handling message", err, msg)
})

return true
Expand Down
22 changes: 22 additions & 0 deletions apps/extension/entrypoints/options/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1.0" />
<title>TLink Options</title>
<meta name="manifest.open_in_tab" content="false" />
<meta name="manifest.browser_style" content="true" />
</head>
<body>
<label>
<input type="checkbox" id="use_popup_window" />
Use a popup window to show Tapp action cards
</label>
<br/><br/>
<div id="status"></div>
<br/>
<button id="save">Save</button>
<script type="module" src="options.js"></script>
</body>
</html>
30 changes: 30 additions & 0 deletions apps/extension/entrypoints/options/options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Saves options to chrome.storage
const saveOptions = () => {
const use_popup_window = document.getElementById('use_popup_window').checked;

chrome.storage.sync.set(
{ use_popup_window },
() => {
// Update status to let user know options were saved.
const status = document.getElementById('status');
status.textContent = 'Options saved.';
setTimeout(() => {
status.textContent = '';
}, 750);
}
);
};

// Restores select box and checkbox state using the preferences
// stored in chrome.storage.
const restoreOptions = () => {
chrome.storage.sync.get(
{ use_popup_window: false },
(items) => {
document.getElementById('use_popup_window').checked = items.use_popup_window;
}
);
};

document.addEventListener('DOMContentLoaded', restoreOptions);
document.getElementById('save').addEventListener('click', saveOptions);
3 changes: 2 additions & 1 deletion apps/extension/entrypoints/sandbox/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals"></iframe>
</div>
<script>

;(function () {
const iframe = document.getElementById("tlink-iframe")

function handleMessage(event) {
async function handleMessage(event) {
// We only proxy messages that originate from the child iframe or parent window
// This prevents postMessage listeners of other tlink iframes on the same page causing duplicate requests to the background service
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "@repo/tlinks/index.css"
import { useEffect, useState } from "react"
import "~/assets/style.css"
import {AbstractActionComponent, TokenscriptCardMetadata} from "@repo/tlinks";
import {openTsPopupWindow} from "@/lib/open-ts-popup-window";

export const TwitterObserver = () => {
const [dAppUrl, setDAppUrl] = useState("")
Expand All @@ -18,11 +19,19 @@ export const TwitterObserver = () => {
connect: () => chrome.runtime.sendMessage({ type: "connect" }),
getConnectedAccount: () =>
chrome.runtime.sendMessage({ type: "getConnectedAccount" }),
interceptHandlePost: (component: AbstractActionComponent) => {
interceptHandlePost: async (component: AbstractActionComponent) => {
if (isTokenScriptViewerUrl(component.href)) {
setTsMetadata(component.tsMetadata as TokenscriptCardMetadata)
setDAppUrl(component.href)
iframePopupRef.current?.setOpen(true)

const options = await chrome.storage.sync.get({ use_popup_window: false });

if (options.use_popup_window){
openTsPopupWindow(component.href, component.tsMetadata);
} else {
setTsMetadata(component.tsMetadata as TokenscriptCardMetadata)
setDAppUrl(component.href)
iframePopupRef.current?.setOpen(true)
}

return true
} else {
return false
Expand Down
55 changes: 54 additions & 1 deletion apps/extension/lib/handle-tlink-api.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,67 @@
import { getTwitterHandle } from "@/lib/get-twitter-handle"
import {TS_VIEWER_URL} from "@repo/tlinks/utils";

export function handleTlinkApiRequest(method: string, payload: any) {
export async function handleTlinkApiRequest(method: string, payload: any) {
switch (method) {
case "getTlinkContext":
return {
handle: getTwitterHandle(),
API_KEY:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9qZWN0IjoibXVsdGktY2hhbm5lbC1yZW5kZXJpbmctdGxpbmsiLCJpYXQiOjE3MjcxNzE1MDl9.9864en0XJbVgzACE_gHrQ00mr6fgctNRXWZ0Nex3DcQ"
}
case "getTurnstileToken":
case "getRecaptchaToken":
return await handleTlinkApiViaTSViewerWindow(method, payload);
}

return null
}

async function handleTlinkApiViaTSViewerWindow(method: string, payload: any){

return new Promise(async (resolve, reject) => {

let popup: WindowProxy|null;
let closedTimer;

const requestUrl = `${TS_VIEWER_URL}?viewType=tlink-api&method=${method}&payload=${encodeURIComponent(JSON.stringify(payload))}`

function handleMessage(event: MessageEvent){

if (event.source !== popup || event.data.type !== "TLINK_API_RESPONSE") {
return
}

if (closedTimer)
clearInterval(closedTimer)

if (event.data.response){
resolve(event.data.response);
} else {
reject(new Error(event.data.error));
}

popup?.close();
}

window.addEventListener("message", handleMessage);

popup = popupCenter(requestUrl, "TLink Request", 400, 400);

if (!popup)
reject(new Error("Failed to open the popup window"));

closedTimer = setInterval(() => {
if (!popup || popup.closed){
reject(new Error("Popup closed"));
}
}, 1000)

});
}

export function popupCenter(url: string, title: string, w: number, h: number) {
const left = (screen.width/2)-(w/2);
const top = (screen.height/2)-(h/2);
return window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left);
}
72 changes: 72 additions & 0 deletions apps/extension/lib/open-ts-popup-window.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {handleTlinkApiRequest, popupCenter} from "@/lib/handle-tlink-api.ts";
import {TokenscriptCardMetadata} from "@repo/tlinks/src";

export function openTsPopupWindow(dAppUrl: string, tsMetadata: TokenscriptCardMetadata){

let popup;

const handleMessage = async (event: MessageEvent) => {

// We only proxy messages that originate from the child iframe
if (!popup || event.source !== popup) {
return
}

console.log("Processing message from popup", event, popup);

if (event.data?.type === "TLINK_API_REQUEST") {
popup.postMessage(
{
type: "TLINK_API_RESPONSE",
source: "TLINK_API_RESPONSE",
data: {
uid: event.data.data.uid,
method: event.data.data.method,
response: await handleTlinkApiRequest(
event.data.data.method,
event.data.data.payload
)
}
},
"*"
)
}

if (event.data?.jsonrpc) {
const resp = await chrome.runtime.sendMessage({
type: "rpc",
data: event.data
})

sendResponse(event.data, resp)
}

function sendResponse(
messageData: MessageEvent["data"],
response: any | null
) {
const data = messageData

if (response?.error) {
data.error = response.error
} else {
data.result = response.result
}

popup!!.postMessage(
data,
"*"
)
}
}

const width = tsMetadata.fullScreen ? Math.round(screen.width * 0.9) : 550;
const height = tsMetadata.fullScreen ? Math.round(screen.height * 0.9) : 800;

popup = popupCenter(dAppUrl, "", width, height);

window.addEventListener("message", handleMessage);

if (!popup)
throw new Error("Failed to open the popup window");
}
4 changes: 2 additions & 2 deletions packages/tlinks/src/api/ActionConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface ActionAdapter {
| { signature: string }
| { error: string | { code: number; message: string } }
>;
interceptHandlePost?: (href: AbstractActionComponent) => boolean;
interceptHandlePost?: (href: AbstractActionComponent) => boolean|Promise<boolean>;
tsIframeRenderer?: (props: { websiteUrl: string }) => JSX.Element | null;
}

Expand Down Expand Up @@ -74,7 +74,7 @@ export class ActionConfig implements ActionAdapter {
}
}

interceptHandlePost(component: AbstractActionComponent) {
async interceptHandlePost(component: AbstractActionComponent) {
return this.adapter.interceptHandlePost?.(component) || false;
}

Expand Down
4 changes: 3 additions & 1 deletion packages/tlinks/src/utils/build-ts-ifram-url.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {TS_VIEWER_URL} from "./constants.ts";

export const buildTsIframeUrl = ({
chainId,
contract,
Expand Down Expand Up @@ -26,7 +28,7 @@ export const buildTsIframeUrl = ({
if (tokenId) dAppUrlParams.append('tokenId', tokenId);
if (scriptId) dAppUrlParams.append('scriptId', scriptId);

return `https://viewer-staging.tokenscript.org/?${dAppUrlParams.toString()}${
return `${TS_VIEWER_URL}?${dAppUrlParams.toString()}${
(hash || '').length > 1 ? hash : ''
}`;
};
4 changes: 3 additions & 1 deletion packages/tlinks/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,6 @@ export class SimpleActionComponent extends AbstractActionComponent {
account,
};
}
}
}

export const TS_VIEWER_URL = "https://viewer.tokenscript.org/";
1 change: 1 addition & 0 deletions packages/tlinks/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { buildTsIframeUrl } from './build-ts-ifram-url.ts';
export { fetchTlinkData, fetchTsData } from './fetch-ts-data.ts';
export type { TokenscriptCardMetadata } from './fetch-ts-data.ts';
export { setProxyUrl } from './proxify.ts';
export { TS_VIEWER_URL } from './constants.ts';