tldraw 许可模式解析:LICENSE.md 条款与 SDK 内嵌 License Key 技术执行机制
本文以 tldraw 仓库根目录的 LICENSE.md 为主体,完整梳理该自定义许可协议对开发环境/生产环境的划分、权限边界、试用与商业授权、终止条款等核心内容,并结合 @tldraw/editor 中的 LicenseManager、LicenseProvider 等源码,说明许可条款是如何通过 License Key 校验、域名绑定、水印与合规追踪等技术手段在 SDK 内部落地的。读完后,你应当能清楚判断自己在何种环境下可以免费使用 tldraw、生产部署前需要做什么,并理解 SDK 中许可证执行代码的调用链与设计取舍。
许可性质:不是通用开源协议,而是自定义的商业许可
首先需要明确一点:tldraw 根目录的 LICENSE.md 不是 MIT/Apache 这类通用开源协议,而是 tldraw, Inc. 自行起草的自定义许可("This License from tldraw, Inc. … governs your use of the accompanying Software")。它的基本姿态是:免费但不开放生产使用——允许你自由地开发、修改、打包提交贡献,但把"面向终端用户的正式部署"留给试用授权或商业授权。
这一点可以从仓库各包的 package.json 得到印证:绝大多数 SDK 包(packages/editor、packages/tldraw、packages/commenting、packages/collaboration、packages/assets、packages/driver 等)声明的均为 "license": "SEE LICENSE IN LICENSE.md",即全部指向这份自定义协议;而少数纯基础设施包,例如 packages/state/package.json,则单独采用 "license": "MIT"。也就是说,"SEE LICENSE IN LICENSE.md" 的许可边界以本文件全文为准,下面逐节展开。
核心定义:Production Environment 与 Development Environment 的分界线
协议在 "Definitions" 一节给出了三个关键定义,这组定义是整个许可模型的骨架:
- Production Environment(生产环境):任何将软件部署在服务器、云平台、Web 应用上,或用于向最终用户、客户、公众提供功能的正式部署;明确排除内部开发用途("Production Environment excludes internal development")。
- Development Environment(开发环境):由你的组织内部托管、仅用于开发/测试/预发布(staging)目的、且不可被最终用户、客户或公众访问的环境。
- License Key(许可密钥):一个程序化生成的密钥,用于控制软件的功能与使用限制。
从源码结构看,这条分界线在 SDK 中是真实可执行的。LicenseManager.ts 中的 getIsDevelopment() 会依据运行时 URL 判定当前是否处于开发环境:
http:协议一律视为开发环境;https:协议下仅当主机名为回环地址(localhost、::1、127.x.x.x)时才视为开发环境;- 主机名以
.localhost结尾(如 Tauri 的tauri://localhost、http://tauri.localhost)时,取决于NODE_ENV; - 其余情况(
NODE_ENV === 'production'且非本地回环)则落入生产判定。
这与协议定义一一对应:本地 http://localhost:3000 跑一个基于 tldraw 的应用属于 "Development Environment",可自由使用;而把它部署到 https://your-app.com 供用户访问,就属于 "Production Environment",需要持有对应授权。
许可授予的权限(Permissions)
在满足下述条件的前提下,协议明确授予以下权限:
- 在 Development Environment 中使用软件;
- 为满足自身需求修改软件;
- 将软件打包进你自己的项目(bundle with your own projects);
- 向 tldraw 提交你对软件的修改。
这四项权限意味着:内网工具、公司内部系统、个人学习项目、原型验证等场景下,你不受限制地 fork、魔改、二次分发(作为自己应用的一部分)。这是该协议区别于纯商业闭源 SDK 的地方——它对"开发侧"是开放的,这也是仓库中大量示例(apps/examples)与模板(templates)可以被直接运行和复制的原因。
使用条件(Conditions):六条对价义务
协议规定,获得上述权限需要遵守以下条件,这也是生产化之前最需要逐条核对的清单:
- 不得在生产环境中使用软件(除非持有下文所述的试用或商业授权);
- 不得禁用、修改或干扰软件的 License Key 执行机制——这一条直接对应源码中
LicenseManager的签名校验与状态机逻辑(见后文"技术执行"一节),绕过它既违反协议也会触发执行机制本身; - 不得移除软件中的任何版权声明或其他通知;
- 不得以覆盖或否定本协议效力的许可来发布软件(例如不得把 tldraw 包重新声明为 MIT 后单独分发);
- 不得将软件或其修改版作为独立产品分发,只能作为另一个应用的组成部分("only as part of another application");
- 分发软件时必须随附本协议的逐字副本,并遵守 tldraw 的商标政策(仓库根目录有对应的 TRADEMARKS.md 可一并查阅)。
条件 5 值得注意:它约束的是"单独售卖/分发 tldraw 本体",而不是禁止你做包含 tldraw 的应用;条件 4 则保证了这套许可链条不会被下游协议稀释。
试用授权(Trial license)与商业授权(Commercial license)
协议为进入生产环境预留了两条正式通道,两者都以 License Key 的签发日期 为起算点:
| 授权类型 | 触发方式 | 生产环境使用范围 | 关键限制 |
|---|---|---|---|
| 试用授权(Trial) | tldraw 向你提供试用性质的软件 | 在自 License Key 签发之日起的指定期限内,可用于生产环境 | 每个业务单元仅限一次试用期,除非 tldraw 书面另行批准 |
| 商业授权(Commercial) | 双方签署单独的商业协议 | 在自商业协议 License Key 签发之日起的指定期限内,可用于生产环境 | 以商业协议约定的期限与条款为准 |
对应到源码层面,LicenseManager 通过 License Key 中的位标志区分这两类授权:ANNUAL_LICENSE(按年付费,到期后进入 30 天宽限期,见 GRACE_PERIOD_DAYS = 30)与 PERPETUAL_LICENSE(永久授权,日历意义上的到期日仅限制未来 major/minor 版本,patch 版本持续可用),另有 EVALUATION_LICENSE 标志用于评估/试用型密钥——评估授权没有宽限期,到期立即失效(isEvaluationLicenseExpired 直接按到期日比较),这与协议中"试用期是指定期限"的表述相吻合。完整的标志定义见 LicenseManager.ts 中的 FLAGS 表。
终止条款(Termination)
协议规定两种自动终止情形:
- 你违反本协议任何条款时,许可自动终止;
- 你对 tldraw、其关联公司、或任何软件使用者(包括对你修改后的版本)发起版权、商业秘密或专利主张时,许可同样自动终止。
这与许多开源协议中的"专利报复/放弃"条款思路不同——这里是双向的:一旦走向知识产权诉讼,使用许可即刻失效。
"技术执行"条款:协议中的 Enforcement 到底指什么
协议 "Technical enforcement" 一节写明:
软件内嵌技术措施,用于验证 License Key 有效性、检测部署环境、按许可类型执行使用限制,并确保水印正确显示;软件可能收集并传输使用数据给 tldraw 以用于许可合规。
这一段并非空话,它在仓库中有完整的实现对应。下面按调用链说明(均为实现事实,可对照源码验证)。
License Key 的格式与签名校验
LicenseManager.extractLicenseKey()(LicenseManager.ts)揭示了密钥的 wire 格式:
tldraw-<base64(JSON 数据)>.<base64(签名)>
- 以
.分隔数据段与签名段;数据段必须以tldraw-前缀开头; - 数据段是 base64 编码的 JSON 数组,按固定下标承载
PROPERTIES定义的四个字段:ID(0)、HOSTS(1)、FLAGS(2)、EXPIRY_DATE(3)——即许可 ID、允许的域名列表、能力位标志、到期日; - 签名段使用浏览器原生 WebCrypto 的
crypto.subtle.verify,以 ECDSA + SHA-256 对数据段做验签(见 licensing.ts 中的importPublicKey:公钥为 P-256 曲线、SPKI 格式、仅授予verify用途); - 验签失败即抛出
Invalid signature,密钥判定为不可解析。
验签公钥以 base64 形式硬编码在 LicenseManager 构造函数中。从源码结构看,这是典型的非对称分发设计:只有 tldraw 持有私钥能签发/更新密钥,而客户端只带验签公钥,无法伪造合法密钥——这正是条件 2(不得干扰 License Key 执行)的技术基础。此外,代码还会清洗从 PDF 等来源复制时混入的零宽字符与换行(注释中说明借鉴了 AG Grid 的思路),并对"密钥含未知属性/未知标志"的情况给出"请升级 tldraw 包版本"的提示,保证了新密钥对旧 SDK 的向后兼容。
域名绑定:HOSTS 字段的匹配规则
isDomainValid()(LicenseManager.ts)实现了协议中"License Key 控制使用限制"的域名维度,匹配规则包括:
- 精确匹配,并自动兼容
example.com与www.example.com的互认; - 通配域
*:允许所有域名; - 通配 glob
*.somedomain.com:目前支持该形态的子域通配; - 原生应用协议匹配:对带
NATIVE_LICENSE标志的密钥,hosts 列表中实际存放的是协议名(如app-bundle:),以正则匹配当前页面 URL——服务于 Tauri 等桌面场景; - VS Code WebView 支持:当页面协议为
vscode-webview:时,以 URL 参数extensionId作为匹配主体——对应仓库中的apps/vscode编辑器扩展。
域名不匹配且非开发环境时,许可状态直接判定为 unlicensed-production,并输出 "License key is not valid for this domain" 提示。
许可状态机与过期策略
LicenseManager 维护一个响应式状态(state atom),取值集合为(见 LicenseManager.ts):
| 状态 | 含义 |
|---|---|
pending |
许可校验进行中 |
licensed |
许可有效(含 30 天宽限期内的已过期常规许可,不显示水印) |
licensed-with-watermark |
许可有效但显示水印(评估/带水印授权) |
unlicensed |
开发环境下的无许可状态 |
unlicensed-production |
生产环境缺少许可、密钥无效或域名不匹配 |
expired |
常规许可过期超过 30 天,或评估许可到期 |
过期策略与协议条款的对应关系:
- 常规许可(annual/perpetual)有 30 天宽限期(
GRACE_PERIOD_DAYS = 30,getExpirationDateWithGracePeriod计算):宽限期内仍返回licensed并打印"License expired N days ago, 请续期"的红色控制台提示;超过宽限期转为expired; - 到期日按 UTC 日期 语义处理(
getExpirationDateWithoutGracePeriod的注释明确:到期日当天全天可用,截止点是 UTC 次日零点),保证所有时区用户看到一致的失效时刻; - 永久许可的日历到期日只门控未来的 major/minor 发布,与当前 SDK 版本的发布时间(
publishDates.major/minor)比较,patch 版本不受影响。
执行动作:5 秒后卸载编辑器与水印追踪
许可状态最终通过 LicenseProvider.tsx 转换为 UI 行为:
- 当状态为
expired或unlicensed-production时,编辑器先展示 5 秒(LICENSE_TIMEOUT = 5000),随后停止渲染画布,仅保留一个data-testid="tl-license-expired"的隐藏节点(LicenseGate),用于 e2e 测试探测; - 在
unlicensed-production(生产环境无有效密钥)或with_watermark、evaluation场景下,maybeTrack()会向watermarks/watermark-track.svg对应的 CDN 端点发起一次fetch,携带version、license_type、license_id、sku(annual/perpetual/evaluation/unknown)、url、environment等参数——对应协议中"确保水印正确显示"与"收集并传输使用数据用于许可合规"两条;水印资源本身位于 assets/watermarks/; - 开发环境(
isDevelopment)不产生追踪请求,且所有功能标志默认全开(getEnabledFeatures对开发环境返回ALL_FEATURES),保证本地开发体验不受校验窗口影响;生产环境则在验签完成前默认关闭所有受控功能(fail-closed)。
目前受 License Key 门控的功能是 collaboration(协作伞标志,FEAT_COLLABORATION)及其子功能 commenting(FEAT_COMMENTING);评估授权会打开全部功能,便于客户在试用期内体验完整产品。相关行为有大量单测覆盖,可参考 LicenseManager.test.ts。
如何向 SDK 提供 License Key
LicenseProvider.tsx 的 getLicenseKeyFromEnv() 按固定优先级从构建期环境变量读取密钥(注释解释了为什么必须写成 process.env.X 全字面量而非动态访问——主流打包器只做静态查找替换)。支持的环境变量依次为:
TLDRAW_LICENSE_KEYNEXT_PUBLIC_TLDRAW_LICENSE_KEY(Next.js)REACT_APP_TLDRAW_LICENSE_KEY(CRA)GATSBY_TLDRAW_LICENSE_KEY(Gatsby)VITE_TLDRAW_LICENSE_KEY(Vite)PUBLIC_TLDRAW_LICENSE_KEY
以上六个名称在 process.env 与 import.meta.env 两种运行时命名空间下都会被依次尝试。因此在一个 Vite 应用中,配置 VITE_TLDRAW_LICENSE_KEY 即可让 <Tldraw /> 组件自动取到密钥,无需显式传入。
其他法律条款:知识产权、免责与准据法
协议其余条款要点如下,建议在商用前完整阅读 LICENSE.md 原文:
- 知识产权归属:tldraw 保留软件的全部权利、所有权与知识产权;本协议仅授予文中明确列出的有限权利,未明示授予的权利一律保留。
- 免责声明:软件按 "AS IS" 提供,不作任何明示或默示担保(包括但不限于适销性、特定用途适用性、不侵权);分发软件或其衍生作品时必须原样传递该免责条款。
- 责任限制:在法律允许的最大范围内,tldraw 对与软件或本协议相关的任何直接、间接、特殊或附带损害概不负责;分发时须同样向下游传递此限制。
- 准据法:协议适用特拉华州(Delaware)法律,双方同意特拉华州法院专属管辖,并放弃对缺乏属人管辖权及"非便利法院"(forum non-conveniens)的抗辩。
- 完整协议与转让:本协议构成双方完整协议,取代此前一切书面或口头约定;tldraw 可不经你同意将本协议转让给第三方。
小结:许可模型一图速览
把协议条款与源码行为对齐后,tldraw 的使用边界可以概括为:
| 场景 | 协议依据 | 源码行为 |
|---|---|---|
| 本地/内网开发、测试、staging,不对外部用户开放 | Permissions 第 1 条 | getIsDevelopment() 判定为开发环境,功能默认全开,无追踪请求,无需密钥 |
| 修改、打包进自有应用、向 tldraw 提 PR | Permissions 第 2–4 条 + Conditions 第 5 条 | 允许,但只能"作为另一个应用的一部分"分发 |
| 生产环境短期体验 | Trial license 条款 | 评估型密钥(EVALUATION_LICENSE),全功能、无水印,到期立即失效,无宽限期 |
| 生产环境正式商用 | Commercial license 条款 | annual/perpetual 密钥,域名绑定校验,年付有 30 天宽限,失效后编辑器 5 秒内被 LicenseGate 卸载 |
| 绕过/干扰密钥校验 | Conditions 第 2 条 | ECDSA 验签 + 状态机 fail-closed,绕过即触发协议 Termination |
对开发者而言,实际操作路径是:本地开发零门槛;确定产品化之前,按协议通过 tldraw 官方渠道获取试用或商业授权密钥,把密钥配置到构建环境变量中并确认 HOSTS 覆盖你的生产域名(含 www 变体或 *.domain 通配),即可在遵守 Conditions 的前提下把 tldraw 集成进正式产品。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00