惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
Forbes - Security
Forbes - Security
The Last Watchdog
The Last Watchdog
TaoSecurity Blog
TaoSecurity Blog
Schneier on Security
Schneier on Security
SecWiki News
SecWiki News
V
Vulnerabilities – Threatpost
Project Zero
Project Zero
O
OpenAI News
W
WeLiveSecurity
Security Archives - TechRepublic
Security Archives - TechRepublic
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
H
Hacker News: Front Page
Cisco Talos Blog
Cisco Talos Blog
Spread Privacy
Spread Privacy
Help Net Security
Help Net Security
P
Privacy & Cybersecurity Law Blog
K
Kaspersky official blog
S
Security @ Cisco Blogs
Latest news
Latest news
AWS News Blog
AWS News Blog
U
Unit 42
Martin Fowler
Martin Fowler
阮一峰的网络日志
阮一峰的网络日志
S
Secure Thoughts
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Know Your Adversary
Know Your Adversary
Scott Helme
Scott Helme
博客园 - 司徒正美
B
Blog RSS Feed
C
Check Point Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
D
Docker
Google Online Security Blog
Google Online Security Blog
Jina AI
Jina AI
aimingoo的专栏
aimingoo的专栏
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Last Week in AI
Last Week in AI
月光博客
月光博客
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
SegmentFault 最新的问题
NISL@THU
NISL@THU
T
The Blog of Author Tim Ferriss
C
Cisco Blogs
Attack and Defense Labs
Attack and Defense Labs
小众软件
小众软件

Deno

Deno 2.8 | Deno Claw Patrol: an open-source security firewall for agents | Deno Fresh 2.3: Zero JS by default, View Transitions, and Temporal support | Deno Deno 2.7: Temporal API, Windows ARM, and npm overrides | Deno Build a dinosaur runner game with Deno, pt. 6 | Deno Build a dinosaur runner game with Deno, pt. 5 | Deno Deno Deploy is Generally Available | Deno Introducing Deno Sandbox | Deno Build a dinosaur runner game with Deno, pt. 4 | Deno Build a dinosaur runner game with Deno, pt. 3 | Deno Build a dinosaur runner game with Deno, pt. 2 | Deno React / Next.js Denial-of-Service Vulnerability: Deno Deploy users protected | Deno Deno 2.6: dx is the new npx | Deno Build a dinosaur runner game with Deno, pt. 1 | Deno React Server Functions / Next.js Vulnerability: Deno Deploy users protected | Deno My highlights from the new Deno Deploy | Deno Deno's Other Open Source Projects | Deno How Deno protects against npm exploits | Deno Help Us Raise $200k to Free JavaScript from Oracle | Deno Deno 2.5: Permissions in the config file | Deno Fresh 2.0 Graduates to Beta, Adds Vite Support | Deno Deno 2.4: deno bundle is back | Deno JavaScript™ Trademark Update | Deno What's coming to JavaScript | Deno A brief history of JavaScript | Deno Reports of Deno's Demise Have Been Greatly Exaggerated | Deno An Update on Fresh | Deno How Plaid migrated 100 services to a new database platform 5x faster with Deno | Deno Deno 2.3: Improved deno compile, local npm packages, and more | Deno Add JSR packages with pnpm and Yarn | Deno Zero-config Debugging with Deno and OpenTelemetry | Deno Exploring Art with TypeScript, Jupyter, Polars, and Observable Plot | Deno Deno v Oracle Update 3: Fighting the JavaScript Trademark | Deno Build a custom RAG AI agent in TypeScript and Jupyter | Deno How to get deep traces in your Node.js backend with OTel and Deno | Deno toranoana.deno #20 登録受付中(2025年3月14日) | Deno Node just added TypeScript support. What does that mean for Deno? | Deno The Dino 🦕, the Llama 🦙, and the Whale 🐋 | Deno Publish a lint rule, get a prize | Deno Deno 2.2: OpenTelemetry, Lint Plugins, node:sqlite | Deno If you're not using npm specifiers, you're doing it wrong | Deno How Deno's documentation is evolving | Deno Oracle justified its JavaScript trademark with Node.js—now it wants that ignored | Deno Introducing the JSR open governance board | Deno Intro to Wasm in Deno | Deno Announcing OpenAI on JSR | Deno Deno in 2024 | Deno Goodbye WinterCG, welcome WinterTC | Deno Build a SolidJS app with Deno | Deno Run your Next.js SSR app on Deno Deploy | Deno Solve Advent of Code 2024 with Deno and Win Prizes! | Deno Deno v. Oracle: Canceling the JavaScript Trademark | Deno Deno 2.1: Wasm Imports and other enhancements | Deno Build a Typesafe API with tRPC and Deno | Deno Self-contained Executable Programs with Deno Compile | Deno Build a Database App with Drizzle ORM and Deno | Deno Introducing your new JavaScript package manager: Deno | Deno Announcing Growthbook on JSR | Deno Build an Astro site with Deno | Deno How to convert CommonJS to ESM | Deno Announcing Deno 2 | Deno The Final Touches: What’s New In v2.0.0-rc.10 | Deno Announcing Stable V8 Bindings for Rust | Deno Deno 2.0 Release Candidate | Deno Secure, efficient private npm registries with Cloudsmith and Deno | Deno Painting the Plane as We Fly It: Designing JSR | Deno Introducing Web Cache API support on Deno Deploy | Deno Deno 1.46: The Last 1.x Release | Deno Protect your cloud spend with new Deno Deploy spend limits | Deno What we got wrong about HTTP imports | Deno Benchmarking AWS Lambda Cold Starts Across JavaScript Runtimes | Deno Announcing Supabase on JSR | Deno Deno 1.45: Workspace and Monorepo Support | Deno Introducing KV Backup for Deno Subhosting | Deno A Gentle Intro to TypeScript | Deno Announcing Hono on JSR | Deno How We Made the Deno Language Server Ten Times Faster | Deno How the Guardian uses Deno to audit accessibility and performance across their 2.7 million articles | Deno Introducing More Flexible Domain Association for Deno Subhosting | Deno The stabilization process of the Standard Library has begun | Deno Deno 1.44: Private npm registries, improved Node.js compat, and performance boosts | Deno How we built a secure, performant, multi-tenant cloud platform to run untrusted code | Deno The Deno Standard Library is now available on JSR | Deno How to document your JavaScript package | Deno Your Low Code Solution Needs an Escape Hatch | Deno Deno 1.43: Improved Language Server performance | Deno How Slack used Deno to save months of engineering effort in launching their new platform | Deno JSR Is Not Another Package Manager | Deno Announcing the Hookdeck SDK on JSR | Deno Announcing the Neon Serverless Driver on JSR | Deno An intro to TSConfig for JavaScript Developers | Deno How we built JSR | Deno How Netlify used Deno Subhosting to build a successful edge functions product | Deno Introducing Simpler Project Creation in Deno Deploy | Deno Deno 1.42: Better dependency management with JSR | Deno Introducing deployctl, the command line interface for Deno Deploy | Deno Introducing JSR - the JavaScript Registry | Deno How to add Monaco to a Next.js app and securely run untrusted user code | Deno Survey Results and Roadmap | Deno Deno 1.41: smaller deno compile binaries | Deno
Build Your Own Cloud IDE with the Subhosting API | Deno
2024-01-16 · via Deno

More and more SaaS platforms are allowing their users to customize their product with code, such as creating bespoke workflows or via an app/integrations marketplace. And a popular approach to enabling code-level customization with minimal friction is through a cloud IDE directly in their product.

A demo of the starter ide template.

In this post, we’ll show you how to build your own cloud IDE using the Deno Subhosting API, which allows you to programmatically deploy and run code within seconds on Deno Deploy’s global v8 isolate cloud. We’ll walk through our Subhosting IDE Starter template, built on Hono, Ace Editor, and Deno.

Or view the accompanying video tutorial on YouTube.

Setting up your project

Before we get started, we’ll need the following things:

Create the following project structure in a new folder:

subhosting_starter_ide/
├── .env
├── App.tsx
├── deno.json
└── main.tsx

Next, you have to create the following environmental variables:

  • a DEPLOY_ACCESS_TOKEN, which you can generate in your Deno Deploy account
  • a DEPLOY_ORG_ID, which you can grab from your organization page:

Where to find your organization id

Once you have these values, add them to your .env file:

DEPLOY_ACCESS_TOKEN = ddp_xxxxxx;
DEPLOY_ORG_ID = ed63948c - xxx - xxx - xxx - xxxxxx;

Let’s setup our deno.json to include a command to run the server and an import map to import Hono:

{
  "tasks": {
    "dev": "deno run -A --watch --env main.tsx"
  },
  "imports": {
    "$hono/": "https://deno.land/x/hono@v3.12.0/"
  }
}

Building the server in main.tsx

The main logic of our cloud IDE will be in main.tsx, which creates the server and the following routes for our application:

  • GET /: list all projects
  • GET /deployments: list all deployments for the given project
  • POST /deployment: create a new deployment for the given project
  • POST /project: create a new project for the given org

And we need to be able to serve static assets from the ./static folder, which contains client-side JavaScript and CSS necessary such as Ace.js:


import { Hono } from "$hono/mod.ts";
import { jsx } from "$hono/jsx/index.ts";
import { serveStatic } from "$hono/middleware.ts";
import App from "./App.tsx";

const app = new Hono();

app.get("/", async (c) => {
});


app.get("/deployments", async (c) => {
});


app.post("/deployment", async (c) => {
});


app.post("/project", async (c) => {
});

app.use("/*", serveStatic({ root: "./static" }));

Deno.serve(app.fetch);

Next, let’s fill in the logic for each route handler. To simplify this for now, we’ll import a wrapper library around the Subhosting API that we’ll create later:

import Client from "./subhosting.ts";

const shc = new Client();

Using our shc wrapper library, we can add the logic for each route handler:

app.get("/", async (c) => {
  const projects = await (await shc.listProjects()).json();
  return c.html(<App projects={projects} />);
});


app.get("/deployments", async (c) => {
  const projectId = c.req.query("projectId") || "";
  const dr = await shc.listDeployments(projectId, {
    order: "desc",
  });
  const deployments = await dr.json();
  return c.json(deployments);
});


app.post("/deployment", async (c) => {
  const body = await c.req.json();

  const dr = await shc.createDeployment(body.projectId, {
    entryPointUrl: "main.ts", 
    assets: {
      "main.ts": {
        "kind": "file",
        "content": body.code,
        "encoding": "utf-8",
      },
    },
    envVars: {}, 
  });
  const deploymentResponse = await dr.json();

  return c.json(deploymentResponse);
});


app.post("/project", async (c) => {
  const body = await c.req.parseBody();

  const pr = await shc.createProject(body.name as string);
  const projectResponse = await pr.json();
  console.log(projectResponse);

  return c.redirect("/");
});

Before we move on, let’s dig into the payload we’re sending to create a deployment:

entryPointUrl: "main.ts", 
assets: {
  "main.ts": {
    "kind": "file",
    "content": body.code,
    "encoding": "utf-8",
  },
},
envVars: {}, 
  • entryPointUrl: this string is the filename that serves as the entrypoint to deploy. Note that this value must map to a key under assets
  • assets: this is a JSON object of files, scripts, whatever the deployment needs to run. Our example is very simple, so it’s a single file (main.ts ), but for more complex deployments, can become very large with many files.
  • envVars: you can specify env vars here, which will be present when the code is executed. This is useful in case you want your code to have access to API credentials or other configuration level information to work properly.

To learn more about creating a deployment with the Subhosting API, check out our documentation.

Next, let’s create our Subhosting client in subhosting.ts.

Creating the Subhosting API wrapper library

Let’s create a new subhosting.ts file to the root of the project, which will serve as a wrapper around the Subhosting API. In this file, we’ll define an interface for ClientOptions, as well as a Client class, which will have these fields accessToken, orgId, and clientOptions, in addition to a constructor that initializes the instance variables for the class with simple error handling:

export interface ClientOptions {
  endpoint?: string;
}

export default class Client {
  accessToken: string;
  orgId: string;
  clientOptions: ClientOptions;

  constructor(accessToken?: string, orgId?: string, options?: ClientOptions) {
    const at = accessToken ?? Deno.env.get("DEPLOY_ACCESS_TOKEN");
    if (!at) {
      throw new Error(
        "A Deno Deploy access token is required (or set DEPLOY_ACCESS_TOKEN env variable).",
      );
    }

    const org = orgId ?? Deno.env.get("DEPLOY_ORG_ID");
    if (!org) {
      throw new Error(
        "Deno Subhosting org ID is required (or set DEPLOY_ORG_ID env variable).",
      );
    }

    this.accessToken = at;
    this.orgId = org;
    this.clientOptions = Object.assign({
      endpoint: "https://api.deno.com/v1",
    }, options);
  }
}

Next, let’s create the functions that we’ve imported and are using in main.tsx. Before we do that, let’s import the following helper functions, urlJoin and normalize at the top of the file:

import { normalize, urlJoin } from "https://deno.land/x/url_join@1.0.0/mod.ts";

Note in our GitHub repo, we’ve inlined both functions since they were pretty simple.

Let’s define a convenience getter, orgUrl , which returns the org URL fragment:

export default class Client {
  

  get orgUrl() {
    return `/organizations/${this.orgId}`;
  }

  
}

Once we’ve done that, we can define the functions that we’ve imported and are using in main.tsx:

  • fetch
  • listProjects
  • createProject
  • listDeployments
  • listAppLogs
  • createDeployment

With the additional functions, your Client will look like this:

export default class Client {
  

  
  async fetch(url: string, options?: RequestInit): Promise<Response> {
    const finalUrl = urlJoin(this.clientOptions.endpoint, url);
    const finalHeaders = Object.assign({
      Authorization: `Bearer ${this.accessToken}`,
      "Content-Type": "application/json",
    }, options?.headers || {});
    const finalOptions = Object.assign({}, options, { headers: finalHeaders });

    return await fetch(finalUrl, finalOptions);
  }

  
  
  async listProjects(query?: any): Promise<Response> {
    const qs = new URLSearchParams(query).toString();
    return await this.fetch(`${this.orgUrl}/projects?${qs}`, { method: "GET" });
  }

  
  async createProject(name?: string): Promise<Response> {
    return await this.fetch(`${this.orgUrl}/projects`, {
      method: "POST",
      body: JSON.stringify({ name }),
    });
  }

  
  
  async listDeployments(projectId: string, query?: any): Promise<Response> {
    const qs = new URLSearchParams(query).toString();
    return await this.fetch(`/projects/${projectId}/deployments?${qs}`, {
      method: "GET",
    });
  }

  
  
  async listAppLogs(deploymentId: string, query?: any): Promise<Response> {
    const qs = new URLSearchParams(query).toString();
    return await this.fetch(`/deployments/${deploymentId}/app_logs?${qs}`, {
      method: "GET",
    });
  }

  
  async createDeployment(
    projectId: string,
    
    deploymentOptions: any,
  ): Promise<Response> {
    return await this.fetch(`/projects/${projectId}/deployments`, {
      method: "POST",
      body: JSON.stringify(deploymentOptions),
    });
  }

For the full subhosting.ts code, which includes TSDoc style comments, please refer to the GitHub repo. If you’re interested in digging into the Subhosting API endpoints, check out our API reference.

The logic for our server’s route handlers should finally be complete. The next step is to define our frontend components.

Building the frontend in App.tsx

Let’s create the App JSX component, which we import in main.tsx.

This is a simple server-side rendered JSX component. A few things to point out:

  1. There are two <script> tags that import:

    • /ace/ace.js, which is a full featured in-browser IDE library, and
    • app.js, some vanilla JavaScript for naive client-side interactions, which we’ll dive into later
  2. The only props passed into this component are projects, which is an array of objects representing your Subhosting projects. We’ll use map to return a list of <option> elements, which is added to a <select> element:

  3. Note that the <div id="deployments"> is the parent element of the list of deployments. We’ll use vanilla JavaScript in app.js to continuously set its the innerHTML .

Your App.tsx should look something like this:


import { jsx } from "$hono/jsx/index.ts";


export default function App({ projects }: { projects?: any }) {
  
  const projList = projects?.map((p: any) => {
    return <option value={p.id}>{p.name}</option>;
  });

  return (
    <html>
      <head>
        <title>Basic Browser IDE (Deno Subhosting)</title>
        <link rel="stylesheet" href="/styles.css" />
        <script src="/ace/ace.js"></script>
        <script src="/app.js"></script>
      </head>
      <body>
        <nav>
          <h1>
            Basic Browser IDE
          </h1>
          <div id="project-selector">
            <select id="project-list">
              {projList}
            </select>
            <form action="/project" method="POST">
              <button type="submit" id="new-project">
                Generate New Project
              </button>
            </form>
          </div>
        </nav>
        <main>
          <div style="position:relative;height:100%;width:100%;">
            <div id="editor-container">
              <div id="editor"></div>
            </div>
            <div id="deployments-container">
              <h3>Deployments</h3>
              <div id="deployments"></div>
            </div>
            <button id="deploy-button">Save & Deploy</button>
          </div>
        </main>
      </body>
    </html>
  );
}

Next, let’s create our client-side JavaScript.

Client-side JavaScript with Ace and app.js

Let’s create a new directory, static, in which we’ll add:

Let’s start with app.js. When the window loads, we’ll need to initialize the editor, bind event handlers to the #deploy-button and #project-list, and call pollData() (which we’ll define shortly) every five seconds to get a list of deployments for the current projectId:

let editor;

window.onload = function () {
  
  editor = ace.edit("editor");
  editor.session.setTabSize(2);
  editor.setTheme("ace/theme/chrome");
  editor.session.setMode("ace/mode/typescript");
  editor.setValue(
    `Deno.serve(() => {
  console.log("Responding hello...");
  return new Response("Hello, subhosting!");
});`,
    -1,
  );

  
  document.getElementById("deploy-button").addEventListener(
    "click",
    saveAndDeploy,
  );

  
  document.getElementById("project-list").addEventListener("change", pollData);

  
  setInterval(pollData, 5000);
  pollData();
};

Next, let’s define the following functions:

  • pollData: get a list of deployments from the /deployments endpoint given the current projectId and display them with setDeployments
  • saveAndDeploy: grab the projectId and code then create a deployment with a POST request to the /deployment endpoint
  • getProjectId: get the project ID from the <select id="project-list">
  • setDeployments: given an array of deployments, create the HTML needed to display deployment information, such as a link to the deployment URL, the deployment status, and when the deployment was created
async function pollData() {
  const projectId = getProjectId();

  try {
    
    const dr = await fetch(`/deployments?projectId=${projectId}`);
    const deployments = await dr.json();
    setDeployments(deployments);
  } catch (e) {
    console.error(e);
  }
}

async function saveAndDeploy(e) {
  const $t = document.getElementById("deployments");
  const currentHtml = $t.innerHTML;
  $t.innerHTML = "<p>Creating deployment...</p>" + currentHtml;

  const projectId = getProjectId();

  const dr = await fetch(`/deployment`, {
    method: "POST",
    body: JSON.stringify({
      projectId,
      code: editor.getValue(),
    }),
  });
  const deployResult = await dr.json();
}

function getProjectId() {
  const $project = document.getElementById("project-list");
  return $project.value;
}

function setDeployments(deployments) {
  const $t = document.getElementById("deployments");

  if (!deployments || deployments.length < 1) {
    $t.innerHTML = "<p>No deployments for this project.</p>";
  } else {
    let html = "";
    deployments.forEach((deployment) => {
      html += `<div class="deployment-line">
        <a href="https://${deployment.domains[0]}" target="_blank">
          ${deployment.domains[0] || "URL pending..."}
        </a>
        <span class="timestamp">
          <span class="status ${deployment.status}">${deployment.status}</span>
          ${deployment.updatedAt}
        </span>
      </div>`;
    });
    $t.innerHTML = html;
  }
}

With all of that, your app should be complete. To start the server, run the command deno task dev.

A note on deployment limitations

As of January 2024, active deployments are capped at 50 per day on the free Subhosting plan. This may cause problems during your testing, as each time you save your code in the browser, a new deployment is created. We are working on changes to our billing structure to avoid this problem, but if you run into any problems with this limit, please reach out to support@deno.com.

What’s next?

Cloud IDEs are becoming more common as a frictionless way to edit, write, and deploy code. They can improve the developer experience in situations where your developer would need to build and setup a server in their own workflow outside of your product. While you could build your own infrastructure to deploy and run third party code, you would also have to maintain and scale it, as well as consider the security implications of running untrusted code.

Building a cloud IDE to deploy and run third party code is made simple with Deno Subhosting, which is designed for maximum security and can initiate deployments programmatically via a REST API. We hope this tutorial and starter template is a good foundation to build your advanced cloud IDEs or integrate a cloud IDE into your product.