生成されたファイルダウンロードリンクの使用方法に関する重要なお知らせ

Tripoユーザーの皆様へ

この度、当社のプラットフォームからのリソースへのアクセスおよび利用方法に影響を与える可能性のある変更についてお知らせいたします。

変更点について

生成されたすべての成果物URLにCORSポリシーを適用いたします。これは、当社のプラットフォームでホストされているすべてのリソースが、クロスオリジンリクエストに対する適切な処理を必要とすることを意味します。言い換えれば、TripoのAPIが返すリンクを使用して生成されたモデルファイルを直接提供することはできなくなります

なぜこの変更が実施されるのか?

この変更は、セキュリティの向上と、異なるオリジン間のリソースへの不正アクセス防止に対する当社の取り組みの一環です。CORS(Cross-Origin Resource Sharing)は、どの外部ドメインがリソースにアクセスできるかをサーバーが指定するためのメカニズムです。
CORSの詳細については、MDNのガイドを参照してください。

ワークフローへの影響

現在、成果物URLにはCORSヘッダーを提供していません。このため、ブラウザや外部ドメインからこれらのリソースを直接使用またはリンクしている場合、問題が発生する可能性があります。
これらのアセットへの継続的なアクセスを確保するためには、以下のいずれかの措置を講じる必要があります。

  1. 成果物をダウンロードしてローカルに再保存する: 成果物をダウンロードし、バックエンドに保存することができます。ローカルに保存すれば、CORS制限なしにサーバーからこれらのアセットを提供できます。
  2. エッジプロキシを使用する(例:Next.js API Route、Cloudflare Workers): エッジプロキシを使用して成果物をフェッチし、必要なCORSヘッダーを応答に追加して提供することができます。例えば、以下の通りです。
    • Next.js API Route: APIルートを設定し、当社のプラットフォームからリソースをフェッチし、適切なCORSヘッダーを付けて転送します。
    • Cloudflare Workers: Cloudflare Workersを使用して、当社のサーバーからアセットを提供する際にCORSヘッダーを追加します。

準備方法:

  • 統合の確認: システムが成果物URLのCORSを処理できることを確認してください。
  • 成果物のダウンロードと保存: エッジプロキシを使用しない場合は、成果物をダウンロードしてバックエンドに保存することを検討してください。
  • エッジプロキシのセットアップ: Next.jsやCloudflare Workersなどのソリューションを使用している場合は、アセットが適切なCORSヘッダーで提供されるように設定を更新してください。
  • システムのテスト: 当社のプラットフォームへのすべてのクロスオリジンリクエストが期待通りに動作するかどうかを確認してください。

具体的なコード例が必要ですか? 開始方法がわからない場合や、実装が難しいと感じる場合は、役立つ実用的なコードスニペットを提供しています。詳細については、付録を参照してください。

よくある質問

  • 生成されたファイルは失われますか?

    **いいえ。**いつでもAPIから新しいURLを取得できます。

  • 貴社のURLを直接返していませんが、後処理を行っています。心配する必要がありますか?

    当社のURLを直接ユーザーに返していない場合は、この変更をスキップできます。ただし、特にシステムでエラーの急増や異常な動作に気づいた場合は、使用状況を監視することをお勧めします。

サポートが必要な場合

この変更にはワークフローの更新が必要となる場合があることを理解しております。エッジプロキシのセットアップに関するご支援や、この移行に関するご質問がございましたら、当社のサポートチーム(support@tripo3d.ai)までご連絡ください。
プラットフォームのセキュリティ強化にご協力いただきありがとうございます!安全で生産的な作業をお続けください!
敬具
Tripoチーム

付録A:バックエンド再保存アプローチ

FastAPI(他のフレームワークや言語への移植や変換が容易です)を使用している場合、CORSを処理し、成果物をローカルに保存するために既存のコードを調整する方法を以下に示します。

現在のコード:

@app.post("/")
def generate(prompt: str) -> str:
    resp = ...  # API works
    return resp.json()['url']

以下のように簡単に変更できます。

import httpx
import uuid

from fastapi.responses import FileResponse
from fastapi import HTTPException

@app.post("/")
def generate(prompt: str) -> str:
    resp = ...  # same as above

    # Add the code below
    file_id = str(uuid.uuid4())
    with httpx.Client() as client:
        # Download the artifact
        response = client.get(url)

        # Check if the request was successful
        if response.status_code == 200:
            # Then resave it
            with open(f"./downloaded/{file_id}.glb", "wb") as file:
                file.write(response.content)

    return file_id

@app.get("/artifact/{file_id}")
def download(file_id: str) -> FileResponse:
    if os.path.exists(f"./downloaded/{file_id}.glb"):
      return FileResponse(f"./downloaded/{file_id}.glb")
    raise HTTPException(status_code=404, detail="Item not found")

ファイルをダウンロードしてローカルに保存した後、新しいURLをユーザーに提供する必要があります。たとえば、アプリケーションが https://app.example.com でホストされている場合、提供するURLは次のようになります。

https://app.example.com/artifact/<file_id>

このコードスニペットは、統合を調整する方法を示す単なるデモンストレーションです。ただし、考慮すべき重要な点がいくつかあります。

  • 適切な認証の追加: ダウンロードリンクが適切な認証方法によって保護されていることを確認し、不正アクセスを防ぎます。
  • 信頼性の高いストレージの使用: ファイルは、専用サーバーやS3バケットなどのクラウドストレージオプションなど、より信頼性の高いストレージソリューションに保存することをお勧めします。
  • メタデータの保存: 将来の使用(追跡や監査など)のために、成果物のメタデータをデータベースまたは別のストレージシステムに記録しておきます。

付録B:エッジプロキシアプローチ

シングルページアプリケーション (SPA) または バックエンド・フォー・フロントエンド (BFF) サーバーを開発しており、サーバー上でファイルのダウンロードと保存を直接処理したくない場合は、エッジプロキシアプローチを使用できます。これにより、当社のサーバーからファイルをフェッチし、必要なCORSヘッダーを適用してから、ユーザーに提供することができます。

ソリューション1:エッジ関数を使用したファイルのダウンロードと再送信

以下は、Cloudflare Workersを使用してこのアプローチを実装する方法の例です。このソリューションは、ファイルをダウンロードし、お客様のドメインで再送信することでCORSの問題を処理します。

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  // Parse the request URL
  const url = new URL(request.url);

  // Get the target URL from the 'url' query parameter
  const targetUrl = url.searchParams.get('url');

  // If no URL is provided, return an error
  if (!targetUrl) {
    return new Response('Please provide a URL parameter', {
      status: 400,
      headers: {
        'Content-Type': 'text/plain',
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type'
      }
    });
  }

  // Handle preflight OPTIONS request
  if (request.method === 'OPTIONS') {
    return handleCORS();
  }

  try {
    // Fetch the file from the target URL
    const response = await fetch(targetUrl);

    // If the fetch failed, return the error
    if (!response.ok) {
      return new Response(`Failed to fetch from target URL: ${response.statusText}`, {
        status: response.status,
        headers: corsHeaders()
      });
    }

    // Get the content type from the response or default to octet-stream
    const contentType = response.headers.get('Content-Type') || 'application/octet-stream';

    // Get the content disposition or create one from the URL
    let contentDisposition = response.headers.get('Content-Disposition');
    if (!contentDisposition) {
      // Extract filename from the URL
      const fileName = targetUrl.split('/').pop().split('?')[0] || 'file';
      contentDisposition = `attachment; filename="${fileName}"`;
    }

    // Create a new response with CORS headers
    const newResponse = new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: {
        'Content-Type': contentType,
        'Content-Disposition': contentDisposition,
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type',
        'Cache-Control': 'public, max-age=3600' // Cache for 1 hour
      }
    });

    return newResponse;
  } catch (error) {
    return new Response(`Error fetching the file: ${error.message}`, {
      status: 500,
      headers: corsHeaders()
    });
  }
}

// Handle CORS preflight requests
function handleCORS() {
  return new Response(null, {
    status: 204, // No content
    headers: corsHeaders()
  });
}

// Create CORS headers object
function corsHeaders() {
  return {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type',
    'Access-Control-Max-Age': '86400' // 24 hours
  };
}

Cloudflare Workerがセットアップされたら、以下のJavaScriptコードを使用してファイルをフェッチすることで、SPAに統合できます。

fetch('https://your-worker-url.workers.dev/?url=<target_url>')
  .then(response => response.blob())
  .then(blob => {
    // Create a download link
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'downloaded-file.pdf';
    document.body.appendChild(a);
    a.click();
    a.remove();
  })
  .catch(error => console.error('Error:', error));

このソリューションは、CORS制限をバイパスしながらファイルをエンドユーザーにダウンロードして提供する必要があるSPAやBFFサーバーに最適です。ファイルはターゲットURLからフェッチされ、エッジプロキシを介して渡され、必要なCORSヘッダーとともに提供されます。

ソリューション2:CORSヘッダープロキシのセットアップ

シンプルなCORSプロキシをセットアップしたい場合は、Cloudflareが提供するこの例を参照してください:Cloudflare Workers - CORS Header Proxy
これにより、CORSポリシーに準拠しながら、クロスオリジンリクエストをより簡単に処理できるようになります。

重要な考慮事項:

  • 認証: ファイルへの不正アクセスを防ぐために、エッジ関数を認証方法(APIキー、OAuthなど)で適切に保護してください。
  • ストレージ: このアプローチは機能しますが、大規模なダウンロードを管理したり、リソースへの信頼性の高いアクセスを確保する必要がある場合は、将来の使用のために成果物をサーバーに保存することを引き続きお勧めします。

Advancing 3D generation to new heights

moving at the speed of creativity, achieving the depths of imagination.