首页
/ Puter.js Apps API 完整开发指南:在 Puter 桌面操作系统中创建、管理与分发应用

Puter.js Apps API 完整开发指南:在 Puter 桌面操作系统中创建、管理与分发应用

2026-09-08 14:53:32作者:卓艾滢Kingsley

Apps API 是 Puter.js 为开发者提供的一套应用生命周期管理接口,用于在 Puter 桌面操作系统中创建、查询、更新、删除以及分发应用。无论你构建的是网页应用、Puter 原生 App、Node.js 脚本还是 Worker,都可以通过同一套 puter.apps.* 方法管理属于自己的应用清单。读完本文,你将掌握全部 6 个 API 方法的完整语法、每个可选参数的业务含义、分页与流式读取的正确姿势,并能基于仓库源码理解这些方法背后的调用链。

本文以 Puter 官方文档 Apps 及其各方法子页(createlistdeleteupdategetcheckName)为骨架撰写,并对照 Puter.js SDK 源码 逐层印证底层实现。

Apps API 能做什么

在 Puter 的生态中,一个"App"就是一条指向某个网页入口(index_url)的注册记录。Puter.js 的 puter.apps 模块开箱即用地支持以下操作(对应源码中的 AppsModule,见 modules/apps/index.js):

方法 作用 对应详细文档
puter.apps.create() 创建一个新应用 支持位置参数与 options 对象两种调用形式
puter.apps.list() 列出当前用户全部应用 内置分页(cursor/offset)、流式遍历与统计选项
puter.apps.get() 获取单个应用详情 可按需附带 stats_periodicon_size
puter.apps.update() 更新应用属性 可改名称、入口 URL、图标、文件关联等
puter.apps.delete() 删除一个应用 删除后 get() 将失败
puter.apps.checkName() 预检应用名是否可用 不创建任何实体,避免 create() 因重名被拒

关于方法实现的几个值得注意的实现事实:SDK 中每个方法都保留"未绑定函数 + 构造器内 bind"的双保险设计,因此既可以 puter.apps.create(...) 链式调用,也可以解构为 const { create } = puter.apps 后独立调用(见 index.js);所有方法的 JSDoc 声明是公开签名的唯一事实来源,类型声明由其生成,无需手工维护。客户端的参数校验失败会抛出形状为 { success: false, error: { code: 'invalid_request', message } } 的错误对象,同时在顶层暴露 message/code,兼容新旧两种捕获写法(见 lib/validate.js)。

快速接入与运行前提

所有示例都通过引入 Puter.js 开始。官方示例统一在 HTML 中加载:

<script src="https://js.puter.com/v2/"></script>

关于平台的适用范围,各方法文档的 front matter 均声明 platforms: [websites, apps, nodejs, workers]——即网页站点、Puter 应用本身、Node.js 服务端与 Worker 环境都能调用这些接口。对于自托管 Puter,你完全可以改为加载并初始化仓库中自托管的 puter-js SDK,接口语义保持一致。

此外,所有示例都会用到 puter.randName()(随机生成互不冲突的名字)与 puter.print()(向页面打印输出),它们是官方 Playground 示例的标准工具函数。

创建应用:puter.apps.create()

create() 用于在用户的 app 列表中创建一个新应用。创建出的应用默认不拥有任何权限,在被授予权限前无法访问任何用户数据。

语法

puter.apps.create(name, indexURL);
puter.apps.create(name, indexURL, title);
puter.apps.create(options);

参数说明

位置参数形式

  • name(String,必填):应用名称,必须在该用户的 app 列表中唯一;若已存在同名应用,Promise 会被 reject。
  • indexURL(String,必填):应用首页 URL,即应用启动时加载的页面,必须对用户可访问;缺失时 Promise 被拒绝。
  • title(String,可选):应用的可读标题,省略时默认使用 name 作为标题。

indexURL 有一个硬性约束——URL 必须以 http://https:// 开头,其余协议一律不允许:

  • https://example.com/app/index.html
  • http://localhost:3000/index.html
  • file:///path/to/index.html
  • ftp://example.com/index.html

options 对象形式

options 支持把位置参数展开为结构化属性,同时新增一批行为选项:

属性 类型 必填 说明
name String 应用名,须对当前用户唯一
indexURL String 首页 URL,必须可访问且为 http/https
title String 人类可读标题,省略时回退为 name
description String 面向终端用户的应用描述
icon String 应用新图标
maximizeOnStart Boolean 启动时是否最大化,默认 false
filetypeAssociations Array<String> 应用可打开的文件夹联,支持文件扩展名与 MIME 类型,默认 []。例如 [".txt", ".md", "application/pdf"] 允许应用打开 .txt.md 与 PDF 文件
dedupeName Boolean 名称已存在时是否自动去重,默认 false
background Boolean 应用是否后台运行,默认 false
feedbackEnabled Boolean 用户能否通过 puter.ui.showFeedbackDialog() 向你发送反馈,默认 false
metadata Object 自定义元数据对象,可存储任意键值对

返回值

Promise,resolve 为 CreateAppResult 对象。

底层实现要点

对照 SDK 的 create.js 可以看到:

  • 当第一个参数是字符串时,SDK 内部构造 { object: { name, index_url, title } };当传入对象时,先经 toAppObject()indexURL/maximizeOnStart 等驼峰属性映射为后端使用的 index_url/maximize_on_start 下划线字段,title 缺省则用 name 填充;
  • dedupeName 会被转换为下划线形式的 dedupe_name 放进 options 透传;
  • nameindex_url 缺失时,客户端直接抛出 invalid_request 错误,根本不会发出网络请求;
  • 网络调用经由统一的 makeDriverMethod({ iface: 'puter-apps', driver: 'es:app', method: 'create', argNames: ['object'] }) 驱动方法完成,即请求会路由到 puter-apps 接口下的 es:app 驱动器。后端侧对应实现在 backend/controllers/apps/AppController.js 及其测试 AppController.test.ts

列出应用:puter.apps.list()

list() 返回一个数组,包含属于当前用户且当前应用有权访问的全部应用;用户没有任何应用时返回空数组。

语法

puter.apps.list();
puter.apps.list(options);

参数说明

options(可选对象)支持下列属性:

  • stats_period(String,可选):统计周期,用于获取指定时间段内的打开次数与用户数。可取值为 todayyesterday7d30dthis_monthlast_monththis_yearlast_yearmonth_to_dateyear_to_datelast_12_months,默认 all(全部时间)。
  • icon_size(Integer,可选):返回图标尺寸,可取 null163264128256512,默认 null(原始尺寸)。
  • limit(Number,可选):单次调用返回的最大应用数。
  • offset(Number,可选):跳过指定数量的应用。大型列表分页时优先使用 cursor
  • cursor(String,可选):接入分页模式。首页传 null,随后把每页返回的 cursor 传给下一次调用以获取下一页。
  • includeTotal(Boolean,可选):为 true 时,分页结果会附带当前用户的应用总数 total
  • stream(Boolean,可选):为 true 时不再返回 Promise,而是返回分页对象的异步迭代器,配合 for await ... of 使用。可与 limit(控制页大小)或 cursor(从中断页继续)组合,不能与 offset 组合;启用 includeTotal 时只有第一页携带 total

返回值

  • 不带任何分页参数:Promise resolve 为当前用户全部 App 对象构成的数组;
  • cursor(含 null)、offsetincludeTotal 任一参数:Promise resolve 为分页对象:
    • items(Array):本页的 App 对象;
    • cursor(String,可选):还有更多页时出现,回传给下一次调用;
    • total(Number,可选):应用总数,仅在设置了 includeTotal 时出现。

因此,不带分页参数的存量代码完全不受影响——SDK 只是改为在底层自动逐页抓取,最后仍拼成普通数组返回。

stream: true 的典型用法:

for await (const page of puter.apps.list({ stream: true })) {
    for (const app of page.items) {
        console.log(app.name);
    }
}

底层实现要点

list.js 的实现非常清晰地揭示了返回值的形态切换逻辑:

  1. 调用的驱动方法实际是 method: 'select',并使用 predicate: ['user-can-edit'] 限定为"当前用户可编辑"的应用集合;
  2. stats_periodicon_size 等非分页参数被解构出来后透传为 params
  3. stream === true 时走 iteratePages 生成异步生成器,若同时传了 offset 会直接抛出 invalid_request(提示改用 cursor 续页);
  4. 任何分页参数存在时,走单请求路径并返回页信封对象;
  5. 一个分页参数都没有时,走 fetchAllPages 在底层逐页取完全部数据,再以旧式的扁平数组形态返回,保证向后兼容。

获取单个应用:puter.apps.get()

返回指定名称的应用;若该应用不存在,Promise 会被 reject

语法

puter.apps.get(name);
puter.apps.get(name, options);

参数说明

  • name(String,必填):要获取的应用名称。
  • options(Object,可选):
    • stats_period(可选):与 list() 相同的周期取值,用于获取统计口径内的 open_count / user_count
    • icon_size(可选):与 list() 相同的图标尺寸取值。

返回值

Promise resolve 为指定名称对应的 App 对象。

关于 App 对象:它包含 uid(创建时由 Puter 生成的唯一标识)、nameicon(base64 Data URL)、descriptiontitlemaximize_on_start(默认 false)、index_urlcreated_at(格式 YYYY-MM-DDTHH:MM:SSZ)、background(默认 false)、filetype_associationsopen_countuser_countmetadata 等属性;当 stats_period 被设置时,open_count/user_count 会按周期口径返回。

App 对象还内建了用户遍历方法:app.users(pageSize)(异步迭代器,默认每页 100 个,逐项 { username, user_uuid },当用户通过 puter.perms.request() 授予了 user:<uuid>:email:read 权限时会额外携带 user_email)以及 app.getUsers({ limit, offset })(基于 limit/offset 的一页式拉取)。

更新应用:puter.apps.update()

按名称更新应用的属性。

语法

puter.apps.update(name, attributes);

参数说明

  • name(String,必填):要更新的应用名。
  • attributes(Object,必填):需要更新的属性集合,可包含:
    • name(可选):新应用名,必须对当前用户唯一,若被占用 Promise 会被拒绝;
    • indexURL(可选):新的首页 URL,须对用户可访问;
    • title(可选):新标题;
    • description(可选):面向终端用户的新描述;
    • icon(可选):新图标;
    • maximizeOnStart(可选):启动时是否最大化,默认 false
    • background(可选):是否后台运行,默认 false
    • feedbackEnabled(可选):是否允许用户通过 puter.ui.showFeedbackDialog() 反馈;省略该字段则保持当前值不变(区别于 create() 的默认 false);
    • filetypeAssociations(可选):可打开的文件类型数组,语义同 create()
    • metadata(可选):关联的自定义元数据键值对象。

返回值

Promise resolve 为更新后的 App 对象。

删除应用:puter.apps.delete()

删除指定名称的应用。删除操作属于不可逆的清理动作,官方所有示例都会在演示完创建/更新后立即删除应用以回收名称。

语法

puter.apps.delete(name);

参数说明与返回值

  • name(String,必填):要删除的应用名称。
  • 返回值:Promise resolve 为 { success: true, uid: <app uid> }uid 为被删除应用的标识,可用于确认删除目标。

名称预检:puter.apps.checkName()

在不创建任何东西的前提下,检查一个应用名对你是否可用。它最适合放在 puter.apps.create() 之前调用,因为 create() 遇到重名会直接 reject,而预检可以提前让 UI 给出友好提示。

语法

puter.apps.checkName(name);

参数说明与返回值

  • name(String,必填):要检查的应用名;缺失或为空字符串时 Promise 会以 invalid_request 错误被拒绝。
  • 返回值:Promise resolve 为描述该名称可用性的对象(官方文档未固定其字段形状,使用前建议以实际响应为准;前端校验由 lib/validate.js 保证)。

官方示例:从创建到清理的完整工作流

以下 5 个代码块是官方文档在 Playground 中的完整可运行示例(同名源码存放在 playground/examples 目录,对应 app-create.htmlapp-list.htmlapp-delete.htmlapp-update.htmlapp-get.html),这里原样保留并配以步骤注释。

(1)创建指向 example.com 的应用

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            // (1) Generate a random app name
            let appName = puter.randName();

            // (2) Create the app and prints its UID to the page
            let app = await puter.apps.create(appName, "https://example.com");
            puter.print(`Created app "${app.name}". UID: ${app.uid}`);

            // (3) Delete the app (cleanup)
            await puter.apps.delete(appName);
        })();
    </script>
</body>
</html>

(2)创建 3 个随机应用再统一列出

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            // (1) Generate 3 random app names
            let appName_1 = puter.randName();
            let appName_2 = puter.randName();
            let appName_3 = puter.randName();

            // (2) Create 3 apps
            await puter.apps.create(appName_1, 'https://example.com');
            await puter.apps.create(appName_2, 'https://example.com');
            await puter.apps.create(appName_3, 'https://example.com');

            // (3) Get all apps (list)
            let apps = await puter.apps.list();

            // (4) Display the names of the apps
            puter.print(JSON.stringify(apps.map(app => app.name)));

            // (5) Delete the 3 apps we created earlier (cleanup)
            await puter.apps.delete(appName_1);
            await puter.apps.delete(appName_2);
            await puter.apps.delete(appName_3);
        })();
    </script>
</body>
</html>

(3)创建随机应用后删除,并验证 get() 已失效

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            // (1) Generate a random app name to make sure it doesn't already exist
            let appName = puter.randName();

            // (2) Create the app
            await puter.apps.create(appName, "https://example.com");
            puter.print(`"${appName}" created<br>`);

            // (3) Delete the app
            await puter.apps.delete(appName);
            puter.print(`"${appName}" deleted<br>`);

            // (4) Try to retrieve the app (should fail)
            puter.print(`Trying to retrieve "${appName}"...<br>`);
            try {
                await puter.apps.get(appName);
            } catch (e) {
                puter.print(`"${appName}" could not be retrieved<br>`);
            }
        })();
    </script>
</body>
</html>

(4)创建随机应用后修改其标题

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            // (1) Create a random app
            let appName = puter.randName();
            await puter.apps.create(appName, "https://example.com")
            puter.print(`"${appName}" created<br>`);

            // (2) Update the app
            let updated_app = await puter.apps.update(appName, {title: "My Updated Test App!"})
            puter.print(`Changed title to "${updated_app.title}"<br>`);

            // (3) Delete the app (cleanup)
            await puter.apps.delete(appName)
        })();
    </script>
</body>
</html>

(5)创建随机应用后读取其信息

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            // (1) Generate a random app name to make sure it doesn't already exist
            let appName = puter.randName();

            // (2) Create the app
            await puter.apps.create(appName, "https://example.com");
            puter.print(`"${appName}" created<br>`);

            // (3) Retrieve the app using get()
            let app = await puter.apps.get(appName);
            puter.print(`"${appName}" retrieved using get(): id: ${app.uid}<br>`);

            // (4) Delete the app (cleanup)
            await puter.apps.delete(appName);
        })();
    </script>
</body>
</html>

checkName() 的组合示例(文档子页附带,最贴近真实注册/命名流程):

<html>
<body>
    <script src="https://js.puter.com/v2/"></script>
    <script>
        (async () => {
            const appName = puter.randName();

            // (1) A freshly generated name is available
            puter.print(JSON.stringify(await puter.apps.checkName(appName)) + '<br>');

            // (2) Create the app, which takes the name
            await puter.apps.create(appName, 'https://example.com');

            // (3) The same name now reports differently
            puter.print(JSON.stringify(await puter.apps.checkName(appName)) + '<br>');

            // (4) Delete the app (cleanup)
            await puter.apps.delete(appName);
        })();
    </script>
</body>
</html>

命名唯一性:贯穿全部写操作的核心约束

从上述 API 可以看出,Puter 的 app 体系以"名称(name)"作为操作句柄,而不是先查 uid 再传 uid——create/get/update/delete/checkName 全部直接以名称寻址。这带来几条实用结论:

  1. 名称是用户级唯一键create()update() 都要求新名称对该用户唯一,否则 Promise 被拒绝;删除重名之外的唯一途径。
  2. 重名场景有两个出口:要么先 checkName() 预检并引导用户换名;要么在 create() 中传 dedupeName: true 让系统自动生成去重后的名称。
  3. 写操作前先读update() 并不支持"部分字段未知也可全量覆盖"的语义,建议在修改前用 get(name) 拉取现状,只传需要变更的字段,尤其在保持 feedbackEnabled 等字段既有取值时(省略即不改变)。
  4. uid 仅用于记录与回溯create()delete() 的返回中会携带 uid,可以用于日志审计或后续跨模块引用。

深入阅读与可用资源

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391