首页
/ tldraw 许可模式解析:LICENSE.md 条款与 SDK 内嵌 License Key 技术执行机制

tldraw 许可模式解析:LICENSE.md 条款与 SDK 内嵌 License Key 技术执行机制

2026-09-06 17:27:53作者:幸俭卉

本文以 tldraw 仓库根目录的 LICENSE.md 为主体,完整梳理该自定义许可协议对开发环境/生产环境的划分、权限边界、试用与商业授权、终止条款等核心内容,并结合 @tldraw/editor 中的 LicenseManagerLicenseProvider 等源码,说明许可条款是如何通过 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/editorpackages/tldrawpackages/commentingpackages/collaborationpackages/assetspackages/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::1127.x.x.x)时才视为开发环境;
  • 主机名以 .localhost 结尾(如 Tauri 的 tauri://localhosthttp://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):六条对价义务

协议规定,获得上述权限需要遵守以下条件,这也是生产化之前最需要逐条核对的清单:

  1. 不得在生产环境中使用软件(除非持有下文所述的试用或商业授权);
  2. 不得禁用、修改或干扰软件的 License Key 执行机制——这一条直接对应源码中 LicenseManager 的签名校验与状态机逻辑(见后文"技术执行"一节),绕过它既违反协议也会触发执行机制本身;
  3. 不得移除软件中的任何版权声明或其他通知
  4. 不得以覆盖或否定本协议效力的许可来发布软件(例如不得把 tldraw 包重新声明为 MIT 后单独分发);
  5. 不得将软件或其修改版作为独立产品分发,只能作为另一个应用的组成部分("only as part of another application");
  6. 分发软件时必须随附本协议的逐字副本,并遵守 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.comwww.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 = 30getExpirationDateWithGracePeriod 计算):宽限期内仍返回 licensed 并打印"License expired N days ago, 请续期"的红色控制台提示;超过宽限期转为 expired
  • 到期日按 UTC 日期 语义处理(getExpirationDateWithoutGracePeriod 的注释明确:到期日当天全天可用,截止点是 UTC 次日零点),保证所有时区用户看到一致的失效时刻;
  • 永久许可的日历到期日只门控未来的 major/minor 发布,与当前 SDK 版本的发布时间(publishDates.major/minor)比较,patch 版本不受影响。

执行动作:5 秒后卸载编辑器与水印追踪

许可状态最终通过 LicenseProvider.tsx 转换为 UI 行为:

  • 当状态为 expiredunlicensed-production 时,编辑器先展示 5 秒LICENSE_TIMEOUT = 5000),随后停止渲染画布,仅保留一个 data-testid="tl-license-expired" 的隐藏节点(LicenseGate),用于 e2e 测试探测;
  • unlicensed-production(生产环境无有效密钥)或 with_watermarkevaluation 场景下,maybeTrack() 会向 watermarks/watermark-track.svg 对应的 CDN 端点发起一次 fetch,携带 versionlicense_typelicense_idsku(annual/perpetual/evaluation/unknown)、urlenvironment 等参数——对应协议中"确保水印正确显示"与"收集并传输使用数据用于许可合规"两条;水印资源本身位于 assets/watermarks/
  • 开发环境(isDevelopment)不产生追踪请求,且所有功能标志默认全开(getEnabledFeatures 对开发环境返回 ALL_FEATURES),保证本地开发体验不受校验窗口影响;生产环境则在验签完成前默认关闭所有受控功能(fail-closed)。

目前受 License Key 门控的功能是 collaboration(协作伞标志,FEAT_COLLABORATION)及其子功能 commentingFEAT_COMMENTING);评估授权会打开全部功能,便于客户在试用期内体验完整产品。相关行为有大量单测覆盖,可参考 LicenseManager.test.ts

如何向 SDK 提供 License Key

LicenseProvider.tsxgetLicenseKeyFromEnv() 按固定优先级从构建期环境变量读取密钥(注释解释了为什么必须写成 process.env.X 全字面量而非动态访问——主流打包器只做静态查找替换)。支持的环境变量依次为:

  1. TLDRAW_LICENSE_KEY
  2. NEXT_PUBLIC_TLDRAW_LICENSE_KEY(Next.js)
  3. REACT_APP_TLDRAW_LICENSE_KEY(CRA)
  4. GATSBY_TLDRAW_LICENSE_KEY(Gatsby)
  5. VITE_TLDRAW_LICENSE_KEY(Vite)
  6. PUBLIC_TLDRAW_LICENSE_KEY

以上六个名称在 process.envimport.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 集成进正式产品。

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