사용자 위젯의 iframe 기능 권한
사용자 위젯의 iframe은 기본적으로 카메라와 팝업 같은 브라우저 기능을 제한합니다. 필요한 기능은 개발자가 구성에 요청하고 앱 제작자가 호스트에서 허용해야 합니다. 대부분의 위젯에는 추가 권한이 필요하지 않으므로 필요한 항목만 요청합니다.
카메라와 마이크 등은 최종 사용자도 브라우저 권한 요청을 명시적으로 허용해야 합니다. iframe 속성 허용은 접근을 요청할 수 있게 할 뿐 사용자 허용을 대신하지 않습니다.
요청할 수 있는 값
| iframe 속성 | 허용 요청값 |
|---|---|
| allow | camera, microphone, autoplay |
| sandbox | allow-downloads, allow-forms, allow-popups |
위젯 구성의 permissions에 요청합니다.
import { defineConfig } from "@osdk/widget.client";
export default defineConfig({
id: "widgetId",
name: "A custom widget",
description: "A widget description",
type: "workshop",
parameters: {},
events: {},
permissions: ["camera", "allow-popups"],
});
Workshop에서 허용
Widget setup의 Parameters와 Events 아래 Permissions에서 앱 제작자가 요청값을 허용합니다. 일부 항목은 사유 확인을 요구할 수 있습니다.
개발 중에는 playground나 VS Code 작업 공간에서 임시로 허용하여 확인할 수 있습니다. 이 개발 경로도 브라우저의 카메라/마이크 사용자 허용을 대신하지 않습니다.
카메라와 마이크
camera와 microphone을 요청하고 허용한 구성에서 사용자 동작에 연결하여 호출합니다.
const mediaStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
성공하면 MediaStream을 받습니다. 거부나 장치 사용 불가 등 실패는 앱에서 안내하고 처리해야 합니다. 사용이 끝난 입력은 track을 중지하여 해제합니다.
mediaStream.getTracks().forEach(track => track.stop());
지원되는 브라우저에서는 Permissions API로 granted, prompt 또는 denied 상태를 확인할 수 있습니다.
navigator.permissions.query({ name: "camera" });
navigator.permissions.query({ name: "microphone" });
iframe 허용 상태, 브라우저 기능 지원과 사용자 권한을 각각 구분합니다.
자동 재생
autoplay 속성을 허용한 구성에서는 audio나 video에 자동 재생을 요청할 수 있습니다. 다음은 실제 미디어 URL을 myVideoSrc로 받는 React 예입니다.
export const VideoPreview = ({ myVideoSrc }: { myVideoSrc: string }) => (
<video src={myVideoSrc} autoPlay controls />
);
자동 재생은 브라우저의 미디어 정책도 따릅니다. controls로 사용자 재생 동작을 제공할 수 있습니다.
다운로드
아래 다운로드에는 allow-downloads가 필요합니다. 다음 helper는 임시 링크로 요청을 시작합니다.
function triggerDownload(url: string, filename: string) {
const anchor = document.createElement("a");
anchor.download = filename;
anchor.href = url;
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
}
CSV Blob
export const CsvDownload = () => (
<button onClick={() => {
const csv = "a,b,c\n1,2,3";
const blob = new Blob([csv], { type: "text/csv" });
const url = URL.createObjectURL(blob);
triggerDownload(url, "data.csv");
URL.revokeObjectURL(url);
}}>
Download CSV
</button>
);
포함된 정적 자산의 직접 링크
예제의 img.png는 실제 위젯 프로젝트에 준비한 파일입니다. Vite로 URL을 import하여 download 속성에 연결할 수 있습니다.
import imgUrl from "./img.png";
export const ImageDownload = () => (
<a href={imgUrl} download="img.png" target="_blank" rel="noreferrer">Download</a>
);
public/img.png는 루트 절대경로 /img.png로 참조합니다.
export const PublicImageDownload = () => (
<a href="/img.png" download="img.png" target="_blank" rel="noreferrer">Download</a>
);
개발 모드에서는 자산이 localhost 같은 다른 origin에서 제공되어 다운로드 대신 새 탭으로 열릴 수 있습니다. 프로덕션 위젯의 포함 자산에서는 다운로드 동작을 사용합니다.
정적 자산을 Blob으로 받기
포함된 자산을 fetch하여 필요하면 가공한 뒤 Blob으로 다운로드할 수 있습니다. 이는 위젯에 포함한 정적 자산 경로이며 임의 외부 API 호출을 허용하는 것은 아닙니다.
Vite import에는 ?no-inline을 사용하여 data: URI로 바뀌면서 CSP에 막히는 것을 피합니다. 다음은 같은 다운로드 처리를 import URL과 public 경로에 사용하는 예입니다. 앞의 triggerDownload helper를 같은 모듈에서 사용합니다.
import imgUrl from "./img.png?no-inline";
import { useState } from "react";
async function downloadAsset(url: string) {
const response = await fetch(url);
if (!response.ok) throw new Error(`Asset request failed: ${response.status}`);
const blob = await response.blob();
const objectUrl = URL.createObjectURL(blob);
try {
triggerDownload(objectUrl, "img.png");
} finally {
URL.revokeObjectURL(objectUrl);
}
}
export const AssetDownloads = () => {
const [error, setError] = useState<string>();
const download = async (url: string) => {
setError(undefined);
try {
await downloadAsset(url);
} catch (cause) {
setError(cause instanceof Error ? cause.message : String(cause));
}
};
return (
<div>
<button onClick={() => void download(imgUrl)}>Download imported asset</button>
<button onClick={() => void download("/img.png")}>Download public asset</button>
{error && <p role="alert">{error}</p>}
</div>
);
};
새 탭과 창
Workshop에서는 호스트 Open URL 이벤트를 권장합니다. 추가 iframe 속성을 요청하지 않아도 이 경로로 이동할 수 있습니다.
위젯 안의 링크로 새 탭을 열려면 allow-popups를 허용해야 합니다.
<a href="https://palantir.com/docs" target="_blank" rel="noreferrer">Link</a>
이 방식으로 연 창은 위젯의 sandbox 제한을 상속합니다. 그 창에서 다운로드도 해야 한다면 위젯에 allow-downloads도 필요합니다.
배포 지원 범위는 Marketplace, 개발 버전 환경은 Node 관리, 응답성 문제는 문제 해결을 확인합니다.