Compare commits

..

500 Commits

Author SHA1 Message Date
CrescentLeaf
20986af1ba (WIP) 重构客户端 2025-12-07 18:31:42 +08:00
CrescentLeaf
34d46a85f1 fix: 蠢到家的一集之 favouritechat 成精成 recentchats 2025-12-07 15:44:17 +08:00
CrescentLeaf
f8f66f0e33 删除暂时用不上的客户端设定图标 2025-12-07 11:08:42 +08:00
CrescentLeaf
58f0427350 去你妈的不区分大小写 2025-12-07 00:47:40 +08:00
CrescentLeaf
e3db26323b 客户端路由不会同步到服务端路由 2025-12-07 00:47:15 +08:00
CrescentLeaf
4788434445 refactor(client): 侧边列表重构 2025-12-07 00:36:51 +08:00
CrescentLeaf
07bc4a6654 头像文字或源为空文本时fallback 2025-12-07 00:30:45 +08:00
CrescentLeaf
bd49edb586 fix: 自我头像无法愉悦 2025-12-07 00:29:57 +08:00
CrescentLeaf
f4a9cc9cda 允许以仅用于调用方法的模式进行部分对象的实例化 2025-12-07 00:29:17 +08:00
CrescentLeaf
8817663371 添加解密失败fallback逻辑 2025-12-07 00:28:08 +08:00
CrescentLeaf
19b8b92f49 修改注释, 添加换行, 删除不必要的代码 2025-12-07 00:07:21 +08:00
CrescentLeaf
f584b49cd4 删除不必要的依赖 2025-12-07 00:06:40 +08:00
CrescentLeaf
13eefdd50c 自动温热一下身份 2025-12-07 00:06:32 +08:00
CrescentLeaf
3cd9031eef 使用更标准的 aes 加密写法, 更换密钥的算法, 限制 data 对象暴露 2025-12-07 00:06:02 +08:00
CrescentLeaf
94c901a233 ignore_all_empty?: boolean
this.refresh_token = args.refresh_token
/**
     * 进行身份验证以接受客户端事件
     *
     * 使用验证方式优先级: 访问 > 刷新 > 账号密码
     *
     * 若传递了账号密码, 则同时缓存新的访问令牌和刷新令牌
     *
     * 如只传递两个令牌的其一, 按照优先级并在成功验证后赋值
     *
     * 多个验证方式不会逐一尝试
     */
2025-12-07 00:03:24 +08:00
CrescentLeaf
1819c31267 updated vite to 7.2.6 2025-12-06 22:51:35 +08:00
CrescentLeaf
00371b1dda fix(client): 移动端界面显示异常 2025-12-06 19:39:23 +08:00
CrescentLeaf
2d48d2f536 feat(client): 登录注册 2025-12-06 17:01:24 +08:00
CrescentLeaf
4214ed9e10 睡觉 2025-12-06 17:01:15 +08:00
CrescentLeaf
198493cac1 等待对话框 2025-12-06 17:01:07 +08:00
CrescentLeaf
f57347b834 共享上下文 2025-12-06 17:00:58 +08:00
CrescentLeaf
f9dff68339 fix: stupid forgetting sha256 2025-12-06 16:58:20 +08:00
CrescentLeaf
48bd884690 fix(cp): 错误的注册方法返回值
* 不是, 我用户 ID 呢
2025-12-06 16:53:35 +08:00
CrescentLeaf
b85b6833b6 添加 react-router, 使 CallackError 获得更多成员, 导出, (WIP) 图片查看器修改, 修复遗忘的 data.apply() 2025-12-06 15:45:43 +08:00
CrescentLeaf
29ea0c5b84 Update 2025-12-06 13:37:25 +08:00
CrescentLeaf
508218a1c5 引入 mdui 类型定义 2025-12-06 13:37:16 +08:00
CrescentLeaf
98774036cd replace icon 2025-12-06 13:36:58 +08:00
CrescentLeaf
e15e1aa4c8 update dockerfile 2025-12-06 13:36:14 +08:00
CrescentLeaf
1c6c0eaf84 移除了无法工作的控制台快捷命令 2025-12-06 11:08:52 +08:00
CrescentLeaf
02b0708426 修改项目配置 2025-12-06 11:08:39 +08:00
CrescentLeaf
d433ceb4a9 抽取 randomUUID, crypto-browserify 2025-12-06 11:08:24 +08:00
CrescentLeaf
d76abcf512 正式回归 Node.js! 2025-12-06 10:17:27 +08:00
CrescentLeaf
6ca9946499 和deno斗争 2025-12-06 01:39:47 +08:00
CrescentLeaf
a549773eb2 TODO: 推翻整个项目重新建立根基 2025-12-06 00:18:10 +08:00
CrescentLeaf
faf594b2f6 export All beans from client protocol main 2025-12-05 22:22:11 +08:00
CrescentLeaf
185f5480fa feat: BlockQoute display in client 2025-12-05 21:27:05 +08:00
CrescentLeaf
b4a60bcbe2 ui: 以正确方式编写 chat-file 的自定义元素代码 2025-12-05 20:49:40 +08:00
CrescentLeaf
d57b023769 feat(unstable): 断开连接时储存事件并重发 2025-11-30 01:45:42 +08:00
CrescentLeaf
4b5f0bcdd6 刷新按钮同样重置页数 2025-11-30 01:45:16 +08:00
CrescentLeaf
3f9ce06ed6 ui: 貌似修了发送消息失败但提示仍在的问题但不知道有没有修好 2025-11-30 01:35:08 +08:00
CrescentLeaf
3def4d7449 fix: 遗漏的 User.getMyAllChats 2025-11-30 00:40:27 +08:00
CrescentLeaf
4b9d78d0d5 ui: 三个侧边列表的搜索框边距修缮 2025-11-30 00:36:15 +08:00
CrescentLeaf
1f6f8a768f ui: 时间显示修缮: 小时和分钟补齐一个 0 2025-11-30 00:32:27 +08:00
CrescentLeaf
a7c61d9306 ui: 不再为每个消息显示发送用户
* 合并显示, 但不完全
2025-11-30 00:32:08 +08:00
CrescentLeaf
0247eaeda9 ui: 对话页面的 Tab 栏项目过多时可以左右滑动, 并保持原有的形态 2025-11-30 00:30:10 +08:00
CrescentLeaf
f9dfa466f0 ui: 加载动画 2025-11-29 13:04:45 +08:00
CrescentLeaf
c2f99f5c62 ui: 列表搜索框扩充微调 2025-11-29 12:52:00 +08:00
CrescentLeaf
6f6dd3bfac ui: 修复了刷新按钮的边距问题 2025-11-29 12:43:33 +08:00
CrescentLeaf
e7f0af8e6e idk 2025-11-29 11:23:27 +08:00
CrescentLeaf
3bd0d79fdc wtf fix int wrong stupid 2025-11-29 01:21:56 +08:00
CrescentLeaf
1e213ddbc4 feat get mimetype 2025-11-29 01:20:48 +08:00
CrescentLeaf
839fb4c4b7 fix: 消息解析不要携带一些有的没有的 2025-11-29 01:05:24 +08:00
CrescentLeaf
35afcf03bb fix: wrong Message instance 2025-11-29 00:56:28 +08:00
CrescentLeaf
5864108f99 fix(cp): marked import 2025-11-29 00:56:08 +08:00
CrescentLeaf
d486c9df79 (client-protocol): 补全解析得到的 附件 和 提及 缺失的文本字段 2025-11-29 00:05:05 +08:00
CrescentLeaf
31e627ce20 fix(cp): 重新断定 parseWithTransformers 返回类型 2025-11-28 23:49:12 +08:00
CrescentLeaf
ca565e3c3e refactor(cp): 客户端事件支持数据对应事件类型 2025-11-28 23:36:47 +08:00
CrescentLeaf
12861b80a1 chore(client-protocol): 抽取获取基 http url 的方法 2025-11-28 23:10:50 +08:00
CrescentLeaf
02b1d28a6b feat(client-protocol): 可以解析消息啦
* 为客户端重构奠定库基础
* 对外提供了比较方便的获取消息附件及提及的方法
2025-11-28 23:10:20 +08:00
CrescentLeaf
f3850a6e2f fix: wrong file_hash get 2025-11-24 23:27:52 +08:00
CrescentLeaf
a9b4a71c0b fix: wrong url join 2025-11-24 22:13:35 +08:00
CrescentLeaf
8df803b3d8 Merge branch 'main' of ssh://codeberg.org/CrescentLeaf/LingChair 2025-11-24 22:12:00 +08:00
CrescentLeaf
2db2bc4c66 fix: wrong panduan 2025-11-24 22:11:52 +08:00
CrescentLeaf
ae837b71aa 修复移动端页面一吸入器页面 2025-11-23 10:17:16 +01:00
CrescentLeaf
fd3684c436 fix 2025-11-23 16:52:48 +08:00
CrescentLeaf
7fcf4ce50b idk 2025-11-23 16:35:18 +08:00
CrescentLeaf
4199335ef8 导出 2025-11-23 14:52:18 +08:00
CrescentLeaf
37281232c0 feat: 自动重新验证 2025-11-23 14:37:05 +08:00
CrescentLeaf
8b3022bed0 1919810 2025-11-23 14:24:50 +08:00
CrescentLeaf
8acf72c7bf 114514 2025-11-23 14:23:49 +08:00
CrescentLeaf
f097a491ae 可以在服务端配置部分客户端行为 目前只作了标题 2025-11-23 14:16:48 +08:00
CrescentLeaf
e90e1911e8 断章取义 2025-11-23 14:16:26 +08:00
CrescentLeaf
d6f1cae7b7 修复了一些配置错误 2025-11-23 13:32:47 +08:00
CrescentLeaf
0754b4128f 抽取公共部分 2025-11-23 13:27:43 +08:00
CrescentLeaf
1cb8ac3fff 移动目录 2025-11-23 13:27:15 +08:00
CrescentLeaf
f13623f4fc 列表不会带动搜索框转 2025-11-23 12:34:18 +08:00
CrescentLeaf
2cf9a20910 snackbar 略改 2025-11-23 12:33:26 +08:00
CrescentLeaf
59191cc42e feat: 查看自己所有的对话 2025-11-23 12:32:59 +08:00
CrescentLeaf
98132eb67c fix: stupid TIMEOUT & INTERVAL mix up 2025-11-23 12:19:32 +08:00
CrescentLeaf
204748699e 对话框默认可以外部点击关闭 2025-11-23 12:05:39 +08:00
CrescentLeaf
7d90d4b0f0 修改了消息原始数据的显示方式 2025-11-23 12:02:59 +08:00
CrescentLeaf
744f02677d Tab 栏样式修改之移除 ::after 2025-11-23 11:15:08 +08:00
CrescentLeaf
8fd9f21c78 todo: Tab 栏样式修改之移除 ::after 2025-11-23 01:14:16 +08:00
CrescentLeaf
5dfcf7a621 资料卡显示对话类型 2025-11-23 00:52:26 +08:00
CrescentLeaf
65602f09f2 资料卡展示对话或用户 id 2025-11-23 00:42:12 +08:00
CrescentLeaf
02d6ee4102 移除 UserProfileDialog 并入 ChatInfoDialog 2025-11-23 00:14:52 +08:00
CrescentLeaf
c9fffbeb12 修复由于没有语法检查导致的一系列符号丢失问题, 支持打开群成员的资料卡 2025-11-22 23:51:36 +08:00
CrescentLeaf
ce692bb763 只有群管理才能显示入群请求页面 2025-11-22 23:35:21 +08:00
CrescentLeaf
1e7e175389 feat: 删除群成员组 2025-11-22 21:50:08 +08:00
CrescentLeaf
c9d9dd8144 修缮了 ChatFragment 可能存在的性能问题 2025-11-22 11:26:19 +08:00
CrescentLeaf
03f8facde0 fix: 非 json 格式错误无法展示 2025-11-22 02:07:34 +08:00
CrescentLeaf
da4325475c 为数据库创建索引 2025-11-22 01:29:49 +08:00
CrescentLeaf
4cb7522251 feat: 群组成员列表 2025-11-22 01:25:29 +08:00
CrescentLeaf
578b3507fd 更新接口定义 2025-11-21 23:44:52 +08:00
CrescentLeaf
b976fed8e7 为 用户-对话 关联表添加索引 2025-11-21 23:15:11 +08:00
CrescentLeaf
48382c4592 修复了对话详情的快捷收藏对话无法正常工作的问题 2025-11-21 23:14:52 +08:00
CrescentLeaf
095b454539 todo: textfield, 但是不是 textarea 而是自定义的输入框 2025-11-21 22:34:27 +08:00
CrescentLeaf
cbdccfb5a7 修缮 snack 2025-11-21 21:52:17 +08:00
CrescentLeaf
32719b45ea 默认自动识别 Android / iOS 设备 2025-11-21 21:46:21 +08:00
CrescentLeaf
b32f60d94d 阻止提及文本点击事件冒泡 2025-11-21 21:38:15 +08:00
CrescentLeaf
d524304b29 ui: 提及 使用 a 而不是 span 2025-11-21 21:28:21 +08:00
CrescentLeaf
7689ec590a fix: 多数据类型消息元素之间的混合显示问题 2025-11-17 00:07:38 +08:00
CrescentLeaf
6517b04215 fix: 移除 chat-mention 换行支持 2025-11-17 00:07:15 +08:00
CrescentLeaf
51fbdc0f71 chore: 修改消息无效附加数据的提示文本 2025-11-17 00:05:10 +08:00
CrescentLeaf
4bf55749bb fix: 避免不同的消息类型之间的换行符导致显示异常 2025-11-17 00:04:45 +08:00
CrescentLeaf
9e8c9bc508 fix: chat-mention 被消去 2025-11-16 22:06:39 +08:00
CrescentLeaf
e1039703d1 修改控制台提示 2025-11-16 21:59:52 +08:00
CrescentLeaf
ace3f8c4f9 feat: 提及某个对话或用户
* 暂时不支持提醒某个在对话内的用户
2025-11-16 21:59:39 +08:00
CrescentLeaf
30c09d0613 fix: 文件文字文件消息, 但是文字(trim)为空导致的显示问题 2025-11-16 21:58:59 +08:00
CrescentLeaf
dec9068cc8 导出 openUserInfoDialog openChatInfoDialog 到 window
* 无奈之举
2025-11-16 19:31:16 +08:00
CrescentLeaf
19cfd84e7d fix: 错误的 openUserInfoDialog 参数类型判断 2025-11-16 19:30:36 +08:00
CrescentLeaf
d00dfab898 die 2025-11-15 00:36:03 +08:00
CrescentLeaf
be27894f95 todo: sendingFileSnackbar died 2025-11-14 23:59:55 +08:00
CrescentLeaf
9b0d91a615 无意义 2025-11-14 22:35:00 +08:00
CrescentLeaf
ad2fd93e02 chore: remove test file 2025-11-09 17:04:19 +08:00
CrescentLeaf
5b425260c9 消除了两个空指针错误 2025-11-09 16:52:29 +08:00
CrescentLeaf
31133f5704 rename: docker-update 2025-11-09 16:43:14 +08:00
CrescentLeaf
93dad0b896 client: 自动进行重新验证 2025-11-09 16:42:44 +08:00
CrescentLeaf
8969fb7cb6 ui: 笼统的错误提示 2025-11-09 16:42:44 +08:00
CrescentLeaf
82c7c3772e ui: 当未登录时不会提示部分数据拉取错误 2025-11-09 16:42:43 +08:00
CrescentLeaf
df217b167e 加载消息和初次打开加载消息的页面置底优化? 2025-11-09 16:42:43 +08:00
CrescentLeaf
2f85aef136 chore: 删除调试代码 2025-11-09 16:42:43 +08:00
Tianpao
b4d63a709b fix: where is my "o" 2025-11-09 16:18:31 +08:00
Tianpao
f64349d802 feat: update.sh 2025-11-09 16:16:23 +08:00
CrescentLeaf
86ace28066 富文本消息显示大重构!!!
* 将所有的 custom element 以正确的方式重新编写
* 可以正确解析 Markdown 文本, 图片, 斜体文本元素且不会杂糅了
* 通过 DOM 操作使得所有的文本聚合在一起, 并且取消了消息自带的填充边距, 删除了原本消息内无法正常工作的 "无边框显示模式"
* 添加新的 custom-element: chat-text 和 chat-text-container
2025-11-09 16:06:24 +08:00
CrescentLeaf
b46449a6e4 fix: chat-video 没有更新 2025-11-09 16:03:29 +08:00
CrescentLeaf
19b2fce904 util: escapeHtml 2025-11-09 16:01:53 +08:00
CrescentLeaf
a7df2c689a ui: 移除对媒体文件的显示圆角, 并修正大小 (块级元素) 2025-11-09 16:01:38 +08:00
CrescentLeaf
6ce8acdb2e build: 取消丢弃 console 2025-11-09 12:46:38 +08:00
CrescentLeaf
149f003175 fix: typo 2025-11-09 10:39:06 +08:00
CrescentLeaf
f0ca0fbbd4 feat: 全新的客户端协议库! 2025-11-09 01:00:01 +08:00
CrescentLeaf
3e5fc722e6 fix: typo 2025-11-09 00:38:43 +08:00
CrescentLeaf
a646d7908a 添加两个客户端协议的类型文件 2025-11-09 00:33:01 +08:00
CrescentLeaf
cfe8df43d1 为 MessageBean 添加 chat_id 字段?
* 不知道有没有用, 有可能会被移除
* 有可能是史山
2025-11-09 00:32:05 +08:00
CrescentLeaf
743ccd1172 chore: 提取公共类 2025-11-08 23:50:43 +08:00
CrescentLeaf
3c5bd187b7 chore: 修缮非 throw 方法返回值 2025-11-08 23:49:58 +08:00
CrescentLeaf
27035eb2ca fix: 更新头像 file_hash 忘记检测 target 存在与否 2025-11-08 23:06:19 +08:00
CrescentLeaf
7a308e2261 fix: 客户端协议修改用户资料后没有在原对象 Bean 同步 2025-11-08 22:55:15 +08:00
CrescentLeaf
3cc60986ab fix: add missing import 2025-11-08 22:54:41 +08:00
CrescentLeaf
13edd23245 feat(protocol): register, refactor: authOrThrow 2025-11-08 18:03:19 +08:00
CrescentLeaf
bc386908f7 fix: typo 2025-11-08 18:03:19 +08:00
Tianpao
c1074d8a2c opti: 更小的前端打包体积 2025-11-08 17:50:20 +08:00
CrescentLeaf
6ee209f9f6 feat(wip): 新的客户端协议库 2025-11-08 16:17:58 +08:00
CrescentLeaf
230cc08182 fix&rename: 重命名中间件, 上传文件中间件没能执行下一个函数 2025-11-01 19:56:49 +08:00
CrescentLeaf
d60a11995e Merge branch 'main' of ssh://codeberg.org/CrescentLeaf/LingChair 2025-11-01 10:22:59 +08:00
CrescentLeaf
68886573a8 feat: 在对话信息页面收藏/取消收藏 2025-11-01 10:22:31 +08:00
CrescentLeaf
fabd325976 feat: 查看对话/用户的头像 2025-11-01 10:06:35 +08:00
Tianpao
3a56415968 fix: install error 2025-11-01 09:28:26 +08:00
Tianpao
ed5e962370 refactor: middleware 2025-11-01 04:19:17 +08:00
Tianpao
dd39c3e63c chore:docker yaml 2025-11-01 03:27:28 +08:00
CrescentLeaf
02485de52c ui: 调整消息媒体的大小 2025-11-01 01:20:30 +08:00
CrescentLeaf
8891cd23af fix: 文件最后使用时间 2025-11-01 01:13:33 +08:00
CrescentLeaf
661cebdb24 chore: 补充所需要的方法 2025-11-01 01:13:17 +08:00
CrescentLeaf
8b3b32422f refactor: 使用表单进行文件上传!
* 可以上传大文件啦
* 最大限制 2GB
* 后端方法重置
2025-11-01 01:12:50 +08:00
CrescentLeaf
dffa773acc feat: 可点击通知跳转对话 2025-11-01 01:11:44 +08:00
CrescentLeaf
51c6d1f0a6 feat(wip): serviceworker
* 用于缓存等
2025-11-01 01:08:52 +08:00
CrescentLeaf
bd35f5c3eb chore: 注释 2025-11-01 00:06:11 +08:00
CrescentLeaf
7409427ce5 chore: 修改 Socket.io Server 初始化参数 2025-10-31 21:56:51 +08:00
CrescentLeaf
7bc843d440 feat: 通过设置 token 到 Headers 获取文件 2025-10-31 21:55:54 +08:00
CrescentLeaf
6c1dd703bc chore: debug -> build-and-run-server, 修改 Docketfile 启动命令以自动构建前端 2025-10-31 21:34:12 +08:00
Tianpao
046831b4e5 chore:Dockerfile and yaml 2025-10-31 21:26:58 +08:00
CrescentLeaf
937af27698 feat: 删除收藏的对话 2025-10-26 23:04:12 +08:00
CrescentLeaf
5469ff6826 若factor: addContact -> s 2025-10-26 23:02:56 +08:00
CrescentLeaf
f8e6fcac46 chore: 客户端的 deno.jsonc: patch -> links
* 与此同时, 会导致旧版本 Deno 不认
2025-10-26 22:07:49 +08:00
CrescentLeaf
ab96ef889d fix: 当用户名没有被修改时, 忽略修改操作 2025-10-26 21:20:11 +08:00
CrescentLeaf
b1e618e07c feat: 修改密码 (UI) 2025-10-26 21:18:31 +08:00
CrescentLeaf
62ee2ef01f feat(wip): 帐号设定 2025-10-26 15:16:29 +08:00
CrescentLeaf
04125a1495 feat(wip): 重设密码 2025-10-26 15:16:20 +08:00
CrescentLeaf
110a90ed7a feat: 重连服务器提示 2025-10-26 15:13:40 +08:00
CrescentLeaf
bfc14777be feat: 检验是否能请求加入对话 2025-10-26 14:24:26 +08:00
CrescentLeaf
4e34e70a11 feat: 手动刷新对话页面 2025-10-26 14:24:06 +08:00
CrescentLeaf
2d2bc7be83 ui: 请求加入对话的组件状态跟随群组设定 2025-10-26 14:23:56 +08:00
CrescentLeaf
5d6c4d6660 fix: 错误的使用 admin 表名, 应为 getJoinRequestsTableName 2025-10-26 14:19:05 +08:00
CrescentLeaf
ab8895b008 fix: 退出登录失败 2025-10-26 14:04:07 +08:00
CrescentLeaf
d5e349ee88 feat: 通知 2025-10-25 01:23:41 +08:00
CrescentLeaf
760e5a118a refactor: 抽离出广播方法 2025-10-25 00:48:24 +08:00
CrescentLeaf
2d78e39ca1 fix: 添加了新的字段代替 chat id
* 谁又能想到 chat id 的可变性和依赖性恰恰埋下了祸患呢
2025-10-24 22:21:28 +08:00
CrescentLeaf
afd9193dea ui: 暂时隐藏未制作的功能 2025-10-24 22:01:17 +08:00
CrescentLeaf
bc7b932c5c feat: 修改对话 ID 对话名称 对话头像
* 仅群组
2025-10-24 22:00:22 +08:00
CrescentLeaf
4807038619 fix: Chat 中的列命名错误
*  avatar avatar_file_hash
2025-10-24 21:55:06 +08:00
CrescentLeaf
e18024b851 chore: 移除调试代码 2025-10-24 21:24:36 +08:00
CrescentLeaf
1dfe702c58 refactor: 对对话文件的真实地址获取重构
* 顺带引入了 tws://file?hash= 协议, 以后会填坑
2025-10-24 21:22:41 +08:00
CrescentLeaf
04a63ced87 ui: 修改 chat-file 的卡片样式 2025-10-24 21:21:33 +08:00
CrescentLeaf
50e3e21634 ui: 对富文本的纯文件消息进行显示优化
* 唉太难弄了, 那边距可太恐怖了
2025-10-24 21:21:12 +08:00
CrescentLeaf
5e5436b02c chore: make lint happy 2025-10-24 20:31:49 +08:00
CrescentLeaf
72016c5da1 refactor: avatar_file_hash instead of avatar 2025-10-24 20:29:51 +08:00
CrescentLeaf
bef6e88bf7 chore: make lint happy 2025-10-24 20:23:05 +08:00
CrescentLeaf
3789e476f7 chore: make lint happy 2025-10-24 20:22:18 +08:00
CrescentLeaf
ba71d66db8 feat: 加入对话请求 2025-10-19 18:23:46 +08:00
CrescentLeaf
af55143292 只有不是对话成员时才不会加载消息呢 2025-10-19 15:18:16 +08:00
CrescentLeaf
b824186c37 移除无用代码 2025-10-19 15:12:01 +08:00
CrescentLeaf
5034eb1da5 添加是否为成员和是否为管理员的字段 2025-10-19 15:11:40 +08:00
CrescentLeaf
5e44a273fc 添加初始化对话默认字段 2025-10-19 15:10:35 +08:00
CrescentLeaf
484381c6e5 local const 得到的 chatInfo 而不是使用旧的 state 2025-10-19 15:10:23 +08:00
CrescentLeaf
349e0933c3 移除 caused_by 史山 2025-10-19 15:09:41 +08:00
CrescentLeaf
08556c9d40 非对话管理员不得更改设定 2025-10-19 15:09:12 +08:00
CrescentLeaf
687bc7a9aa 加长 timeout 时间 2025-10-19 14:52:45 +08:00
CrescentLeaf
5a34054024 fix: 限制用户访问任意私聊 2025-10-19 11:55:57 +08:00
CrescentLeaf
306bfa2b82 refactor: ChatAdmins stored in Chat.db 2025-10-19 11:27:54 +08:00
CrescentLeaf
506790aefa remove: caused_by 字段 2025-10-19 11:27:24 +08:00
CrescentLeaf
ab1ef2c30b 修改配置失败时配置回退 2025-10-08 15:14:22 +08:00
CrescentLeaf
61bc1a265c fix: 由于系统消息没有发送者导致的 NOT NULL 错误 2025-10-08 15:13:56 +08:00
CrescentLeaf
9c45f3e13e 用户可以在不成为成员的情况下查看对话详情 2025-10-08 15:13:30 +08:00
CrescentLeaf
23ad29fb2d feat: 添加 caused_by 字段以便客户端知道是什么情况 2025-10-08 15:13:11 +08:00
CrescentLeaf
5b64c6adcf feat(wip): 入群需要审批 2025-10-08 15:12:46 +08:00
CrescentLeaf
dd42f5e54e fix: 不能正常显示系统消息 2025-10-08 15:00:31 +08:00
CrescentLeaf
2d7b7818d7 chore: 建群时添加一条系统消息 2025-10-08 14:56:51 +08:00
CrescentLeaf
c27eb37852 fix: wrong status code (400 -> 403) 2025-10-08 14:52:01 +08:00
CrescentLeaf
bc48cf801b fix: 创建群组时, 没有任何管理员 2025-10-08 14:49:42 +08:00
CrescentLeaf
e46661ba15 feat(wip): Chat admin 2025-10-08 14:47:27 +08:00
CrescentLeaf
9cb71af85b feat: get old value of preference when is was updated 2025-10-08 14:41:45 +08:00
CrescentLeaf
241ff714b8 fix: 对话页面 2025-10-08 12:25:33 +08:00
CrescentLeaf
db43de19c4 fix: 配置组件没有正确同步状态
* 问题出在我应该根据 State 决定组件状态而不是组件状态决定 State
* 踩坑了, 浪费我时间, 唉
2025-10-08 12:24:33 +08:00
CrescentLeaf
38c28c3fb6 我累了 2025-10-08 02:52:02 +08:00
CrescentLeaf
0df1149618 FEAT(灵车 WIP): CHAT SETTINGS 2025-10-08 02:51:58 +08:00
CrescentLeaf
aeafcb5b97 feat(WIP): 对话管理员 2025-10-08 02:51:25 +08:00
CrescentLeaf
324962b0fc refactor: 配置存储类泛型化 2025-10-08 02:50:58 +08:00
CrescentLeaf
f5f3774daf chore: 移除了 babel 编译流程
* 加快调试速度
* 旧版本浏览器本来也没办法支持了...
2025-10-08 02:50:31 +08:00
CrescentLeaf
e666dc573a chore: 移除分号 2025-10-08 01:12:05 +08:00
CrescentLeaf
11362a5689 chore: make lint happy 2025-10-08 00:55:09 +08:00
CrescentLeaf
7c7e641d1f fix: 遗漏的 return 2025-10-08 00:54:46 +08:00
CrescentLeaf
fabdd192dd rename: default(Value -> State)
* 我没有想到这是已有的属性定义
2025-10-07 23:07:27 +08:00
CrescentLeaf
8d7ddd46be chore: make Preferences' lint happy 2025-10-07 23:05:34 +08:00
CrescentLeaf
4b91bc9dbb feat(wip): 群组设定 2025-10-07 22:32:16 +08:00
CrescentLeaf
80c6f0b7a7 chore: 移除调试代码 2025-10-07 22:32:11 +08:00
CrescentLeaf
4eff829a30 feat: 配置页面组件 2025-10-07 22:31:34 +08:00
CrescentLeaf
96ca578c70 ui: 调整 mdui-menu 圆角大小 2025-10-07 14:53:01 +08:00
CrescentLeaf
7a0110180d ui: 加大 mdui-menu 圆角大小 2025-10-07 14:23:29 +08:00
CrescentLeaf
b36fe7a67e fix: 接口调用自动刷新访问令牌制造的的灵车
* 是什么我忘了, 但是这就是灵车😇
2025-10-07 13:07:11 +08:00
CrescentLeaf
6e73662860 docs: readme
唉, 我 width 怎么无效了 (
2025-10-06 12:22:22 +02:00
CrescentLeaf
318f75a7cc docs: readme 2025-10-06 18:20:39 +08:00
CrescentLeaf
bc5ed9e602 docs: readme 2025-10-06 18:19:33 +08:00
CrescentLeaf
8c8d17a1c7 docs: readme 2025-10-06 12:05:12 +02:00
CrescentLeaf
71dee043a3 docs: readme 2025-10-06 17:48:57 +08:00
CrescentLeaf
059078ea8f refactor: 忽略刷新访问令牌重试的请求 方法重写
* 将原有的 CallableMethodBeforeAuth 纳入
2025-10-06 17:31:35 +08:00
CrescentLeaf
674fe000f4 fix: 无限进行刷新访问令牌
* 由于 Client.ts 中的 invoke 没有对请求方法做判断, 导致不该被 retry 的请求被自动重试
2025-10-06 17:27:03 +08:00
CrescentLeaf
85477fe46e feat: 添加刷新令牌支持
* 服务端: 添加对应的接口, 对原有令牌系统稍有修改, 添加了令牌类型
* 客户端: 自动刷新访问令牌, 登录时顺带获取刷新令牌
2025-10-06 17:13:23 +08:00
CrescentLeaf
dced175d7a chore: 统一为简体中文 2025-10-06 15:36:12 +08:00
CrescentLeaf
bd857b840b chore: 修改网页标题 2025-10-06 14:52:48 +08:00
CrescentLeaf
5d1c395340 docs: readme 2025-10-06 14:46:13 +08:00
CrescentLeaf
0e17b37156 chore: add icon for this project 2025-10-06 14:42:38 +08:00
CrescentLeaf
fb48c44655 ui: 移除 添加对话 输入框边距 2025-10-06 02:13:25 +08:00
CrescentLeaf
7378024235 feat: 添加任意对话, chore: 使用 User.create (createWithUserNameChecked 已移除) 2025-10-06 02:11:41 +08:00
CrescentLeaf
1c985f28a2 feat: 自动检验用户名和对话 ID 是否已经存在 2025-10-06 02:10:51 +08:00
CrescentLeaf
449c0a8806 feat: 创建群组, impl - 获取群组信息 2025-10-06 02:09:30 +08:00
CrescentLeaf
e1e42ea188 feat: 添加任意对话, 不局限于用户 2025-10-06 02:09:03 +08:00
CrescentLeaf
823eef76b0 feat: 所有对话列表中, 创建群组 2025-10-06 02:08:14 +08:00
CrescentLeaf
3b0b5ff032 feat: 创建群组对话框 2025-10-06 02:07:25 +08:00
CrescentLeaf
6112b4b207 fix: https cannot load pem 2025-10-06 00:56:42 +08:00
CrescentLeaf
9e8e967eb9 chore: remove useless code 2025-10-04 22:18:58 +08:00
CrescentLeaf
697082193f fix: missing contact type of contactsList 2025-10-04 22:18:58 +08:00
CrescentLeaf
86d68fd5e5 feat(wip): 群组 2025-10-04 22:13:13 +08:00
CrescentLeaf
ffa8ac73de ui: 微调 RecentsListItem 文字位置 2025-10-04 21:29:41 +08:00
CrescentLeaf
f01f3b02f4 ui: 移除延迟设置应用视图大小 2025-10-04 16:02:43 +08:00
CrescentLeaf
ad4e873d2f ui: 用户资料中进入对话, 连带上层对话框关闭 2025-10-04 15:52:22 +08:00
CrescentLeaf
a77e22a3ea feat: 从对话详情打开用户详情 2025-10-04 15:49:19 +08:00
CrescentLeaf
1fa91279e2 fix: 阻止用户头像点击事件传播 2025-10-04 15:35:16 +08:00
CrescentLeaf
debdb93935 feat: 对话中打开用户的资料 2025-10-04 15:32:54 +08:00
CrescentLeaf
81cdb4afd9 feat: Chat.getIdForPrivate 2025-10-04 15:32:29 +08:00
CrescentLeaf
bc08cd3c8c feat: 直接和对方私聊 2025-10-04 15:32:11 +08:00
CrescentLeaf
c24078b29d fix: stupid myId instead of targetUserId 2025-10-04 15:31:22 +08:00
CrescentLeaf
f04748aa5c feat: 在对话中打开对话详情对话框 2025-10-04 14:56:00 +08:00
CrescentLeaf
5ce97283f1 refactor: 抽离 openChatInfoDialog 2025-10-04 14:55:24 +08:00
CrescentLeaf
d6f794a094 fix: Chat.getInfo should return id 2025-10-04 14:55:06 +08:00
CrescentLeaf
47bbf12176 ui: 移动端设备上, 最近列表不会呈现激活状态 2025-10-04 14:40:30 +08:00
CrescentLeaf
2cee988ada feat: 自动更新最近对话
* 接收新消息
* 定时 15s
2025-10-04 14:35:19 +08:00
CrescentLeaf
04989762d9 feat: 最近对话 2025-10-04 14:32:22 +08:00
CrescentLeaf
89db6591a0 feat(api): User.getMyRecentChats 2025-10-04 14:13:06 +08:00
CrescentLeaf
d173fb7842 向消息接收者添加最近对话 2025-10-04 14:12:51 +08:00
CrescentLeaf
4133c13cf8 feat: RecentChats in User & Bean 2025-10-04 14:12:07 +08:00
CrescentLeaf
a1eddf813d chore: declare User.getMyRecentChats 2025-10-04 14:11:35 +08:00
CrescentLeaf
39b4a6d8a6 chore: 添加 Map 序列化相关辅助类 2025-10-04 14:07:21 +08:00
CrescentLeaf
e4cf9d6a68 chore: add reference 2025-10-04 13:02:29 +08:00
CrescentLeaf
f29538762b chore: 添加 Map 序列化相关辅助类 2025-10-04 13:02:13 +08:00
CrescentLeaf
7616a49ff8 chore: Chat (client) 同步伺服器端 Bean 定義 2025-10-04 12:10:32 +08:00
CrescentLeaf
42aefdd2f1 chore: deno ignore ./client/mdui_patched 2025-10-04 12:07:28 +08:00
CrescentLeaf
5fadb76a20 fix: 用戶現在無法任意訪問其他不屬於他的對話 2025-10-04 11:52:34 +08:00
CrescentLeaf
5474eac554 chore: 复制文字 先 trim 一遍 2025-10-04 11:42:03 +08:00
CrescentLeaf
a12a8830d4 ui: 改善 复制到剪贴薄 的用户体验
* 在 Via 浏览器上, writeText 本质上被重写了, 逻辑还是 execCommand copy
* 更换 copy 为 cut
2025-10-04 11:34:56 +08:00
CrescentLeaf
6c5f3aac85 ui: 修正由于 Menu 关闭没有同步状态导致的开关不正常 2025-10-04 11:07:55 +08:00
CrescentLeaf
6e164cbdfb fix: 本地 patch MDUI 以解决 tabindex = 0 导致的一系列玄学问题 2025-10-04 11:07:03 +08:00
CrescentLeaf
af694f6f6c ui: fallback to window.inner*
* Android 上, avail* 还是屏幕的
2025-10-03 23:58:16 +08:00
CrescentLeaf
c5ce13b13c chore: 測試兼容更舊的瀏覽器内核
* 效果不咋地, Edge 84 因爲 Marked.js 炸了
2025-10-03 23:33:06 +08:00
CrescentLeaf
0026cae639 ui: 繼續修繕 onResize 邏輯
* Edge 84, 但是廢了
* 實際上, avail* 不准, 但是並不知道什麽情況下才會
2025-10-03 23:07:21 +08:00
CrescentLeaf
376177d78e rename: (User -> My)ProfileDialog 2025-10-03 12:49:28 +08:00
CrescentLeaf
6c9ee005fd fix: 橫豎屏切換 resize 時的大小不當
* 橫屏時, 測試 Via 瀏覽器時 可能是因為全屏不當, 大小不正確, 也因此需要手動縮小, 繼續切豎屏正常
2025-10-03 12:47:19 +08:00
CrescentLeaf
82c5aeaaa0 ui: 修正 文件卡片 文字折行失敗
* https://commandnotfound.cn/css-layout/101/644/CSS-控制长文本自动折行
* 也許是 width 相關導致word-wrap 沒效果?
* 玄學
2025-10-03 01:08:57 +08:00
CrescentLeaf
14c279cc80 ui: 點擊聊天文件不會再跳轉的同時並下載了, 只會進行下載 2025-10-02 23:38:43 +08:00
CrescentLeaf
67c6f11892 ui: 微調 置底 延遲時間 2025-10-02 23:35:08 +08:00
CrescentLeaf
edf35b7dd0 ui: 阻止 附件 點擊導致消息菜單彈出 2025-10-02 21:28:11 +08:00
CrescentLeaf
de886dcfcc ui: 修正 chat-file 為 超鏈接 2025-10-02 21:27:52 +08:00
CrescentLeaf
67f019713a ui: 修正 消息右鍵菜單
* 修正打開狀態
* 避免不必要的狀態變更
2025-10-02 20:54:39 +08:00
CrescentLeaf
2af396a2b8 chore: 修正一個不合理的 uri-list 轉換成功判斷 2025-10-02 20:51:07 +08:00
CrescentLeaf
20f12c97c1 ui: 微調 時間顯示 文字 2025-10-02 20:49:00 +08:00
CrescentLeaf
15c4bcd48e fix: 消息發送失敗時, 沒有取消 加載狀態 2025-10-02 18:49:19 +08:00
CrescentLeaf
e429bbbcdb feat: 瀏覽器下載文件時, 支持設定爲原始文件名
* 基於 Content-Disposition
2025-10-02 18:42:29 +08:00
CrescentLeaf
020fd63c97 feat: 聊天文件 2025-10-02 18:37:25 +08:00
CrescentLeaf
2771503b6f fix: typo 2025-10-02 18:30:58 +08:00
CrescentLeaf
65458cf491 chore: make lint happy 2025-10-02 18:18:29 +08:00
CrescentLeaf
c23fdbf310 feat: 聊天視頻 (初始化) 2025-10-02 11:14:39 +08:00
CrescentLeaf
dfeed305e1 feat: 支持對話視頻, wip: 文件 2025-10-02 11:14:07 +08:00
CrescentLeaf
bc5485d622 移除 剪貼薄 fallback 元素 2025-10-02 11:12:07 +08:00
CrescentLeaf
d3b2949ff7 剪貼薄 2025-10-02 10:49:36 +08:00
CrescentLeaf
f376de2b48 ui: 微調消息菜單
* 仿照電報邏輯
* 添加 JsonView
2025-10-01 11:51:28 +08:00
CrescentLeaf
459fca064c depend: add react-json-view@1.21.3 2025-10-01 11:23:09 +08:00
CrescentLeaf
f436f84696 feat(wip): 支持視頻和文件 2025-10-01 01:08:52 +08:00
CrescentLeaf
73e795e29f fix: Marked 不解析 \n, 手動解析以使換行符正常使用 2025-10-01 01:03:39 +08:00
CrescentLeaf
a709ac7ee0 ui: 修繕圖片顯示, 不再為下方文字説明占位 (aka inline -> block) 2025-10-01 01:02:49 +08:00
CrescentLeaf
beab35a25e chore: make lint happy 2025-10-01 00:57:34 +08:00
CrescentLeaf
aa0d0e86a5 docs: readme 2025-10-01 00:36:41 +08:00
CrescentLeaf
f8a043e59f docs: readme 2025-10-01 00:20:16 +08:00
CrescentLeaf
441fb5b5be docs: license 2025-10-01 00:20:11 +08:00
CrescentLeaf
29ea6f9142 chore: 迁移工具方法 isMobileUI 2025-10-01 00:02:20 +08:00
CrescentLeaf
cd22f62d60 fix(wip): 复制文字到剪贴板 2025-10-01 00:01:52 +08:00
CrescentLeaf
b3ffdf8469 feat: 消息菜单 2025-10-01 00:01:39 +08:00
CrescentLeaf
6c17a0e4eb feat: 显示消息时间 2025-10-01 00:01:27 +08:00
CrescentLeaf
8163100559 feat: 上报消息时间戳 2025-09-30 23:47:49 +08:00
CrescentLeaf
1fec2bba06 feat(wip): 顯示消息的時間 2025-09-30 21:56:18 +08:00
CrescentLeaf
706a340407 fix: 在沒有消息時, 發送消息並拉取導致的消息重複 2025-09-30 21:54:25 +08:00
CrescentLeaf
7e81484932 refactor: 獨立 openImageViewer 2025-09-30 21:37:31 +08:00
CrescentLeaf
d7d8351dc9 ui: 添加細節: 添加聯絡人可直接回車, 可直接點擊清空 2025-09-30 21:35:58 +08:00
CrescentLeaf
19657fd150 ui: 微調: 可以點擊外部關閉對話框 2025-09-25 17:26:57 +08:00
CrescentLeaf
a6cef76ecf feat: 手動刷新聯絡人列表 2025-09-25 17:26:44 +08:00
CrescentLeaf
ee8e0e531e fix: 對話中的成員 無法收到更新信息 2025-09-25 17:16:20 +08:00
CrescentLeaf
151dc31f2c chore: 規範化 client event listener 寫法 2025-09-25 17:14:37 +08:00
CrescentLeaf
0b1a4a53a5 chore: make lint happy 2025-09-25 17:14:09 +08:00
CrescentLeaf
02efac9a8e fix: 用戶添加自己為對話或重複導致的重複 2025-09-25 16:52:51 +08:00
CrescentLeaf
8d739dd863 chore: 添加 EventBus
* 並讓對話列表先用上了
2025-09-25 16:52:23 +08:00
CrescentLeaf
c0c6c6ed1c feat: 添加對話 2025-09-25 16:51:43 +08:00
CrescentLeaf
d26c67f06d fix: 無法正常在 private chat 獲取到對方 User 2025-09-25 16:48:06 +08:00
CrescentLeaf
35d60642c0 chore: 生成的 Private chat id 人類可讀 2025-09-25 16:40:30 +08:00
CrescentLeaf
a928577f2a fix: 打開不同對話時, 使用了同一個 ChatFragment
* 並修復了使用 key 時, 因爲卸載組件后 ref 丟失導致的錯誤
2025-09-25 16:26:46 +08:00
CrescentLeaf
4b93e5fd67 chore: deno task server 不再自動編譯前端
* 請使用 debug 或 手動 build
2025-09-25 15:04:50 +08:00
CrescentLeaf
d6454f51c8 feat: find user by account (aka userName or userId) 2025-09-25 14:53:53 +08:00
CrescentLeaf
efc0f49b66 feat: 文件權限檢驗
* 基於讀取 Cookie 中的驗證信息
* 因為 ServiceWorker 需要安全的上下文, 而我想要到處可用, 因此暫時折中使用這個辦法
2025-09-25 14:19:45 +08:00
CrescentLeaf
a860da96a0 depend: add cookie-parser 1.4.7 2025-09-25 14:19:18 +08:00
CrescentLeaf
692eb3d2a3 chore: 將令牌檢測函數移動到 TokenManager
* 這樣才叫 TokenManager 嘛X
2025-09-25 14:18:50 +08:00
CrescentLeaf
b6be09ef7c chore: 客戶端自動添加 token 和 device_id 到 Cookie 裡, 以便 HTTP 請求 2025-09-25 14:17:29 +08:00
CrescentLeaf
8e15c8126f chore(wip): 聯絡人 -> 對話
* 這是設計時留下的問題, 現在逐步改正
2025-09-25 13:02:37 +08:00
CrescentLeaf
80a42d5d86 feat(wip): 添加對話 對話框 2025-09-25 13:02:02 +08:00
CrescentLeaf
b8f3886a1b chore: fuck lint and make it happy 2025-09-25 12:57:08 +08:00
CrescentLeaf
5a80041ec3 ui: 微調對話框選項距離 2025-09-25 12:54:42 +08:00
CrescentLeaf
d76e7e2bf5 chore: make lint happy & fix typo 2025-09-25 12:53:07 +08:00
CrescentLeaf
4fa3e16ab7 fix: 令牌驗證額外添加是否為有效令牌
* 如果解密無效, 直接返回一個無效的令牌, 並加以判斷
2025-09-25 12:12:12 +08:00
CrescentLeaf
9cc3a2149e feat: 退出登錄 2025-09-25 12:12:04 +08:00
CrescentLeaf
fdf52c0548 feat(wip): 複製到剪貼薄 2025-09-25 11:36:03 +08:00
CrescentLeaf
a6ee231ad5 feat: 客戶端查看自己的用戶 ID 2025-09-25 11:35:35 +08:00
CrescentLeaf
4bcc6e4347 feat: 手動選擇文件 2025-09-25 00:42:31 +08:00
CrescentLeaf
9395104c20 ui: 在加載歷史消息時,自動回到加載前的消息位置
* 使用奇技淫巧
2025-09-25 00:31:40 +08:00
CrescentLeaf
f063c4d165 ui: 修復錯誤添加在 最近對話 搜索框 的 paddingRight 2025-09-24 23:43:24 +08:00
CrescentLeaf
b3b077fa9d ui: 移動端支持修改個人資料, 修繕移動端 UI 的諸多潛在問題 2025-09-24 23:41:11 +08:00
CrescentLeaf
88123e1edb ui: 修正 最近對話 列表的 paddingLeft 2025-09-24 23:39:53 +08:00
CrescentLeaf
0106311a2a chore: make lint happy 2025-09-24 23:09:55 +08:00
CrescentLeaf
f5f2d5743f fix: typo 2025-09-24 23:09:20 +08:00
CrescentLeaf
4e38ad8e20 fix: 移動端未打開對話但提示對話不存在 2025-09-24 23:01:07 +08:00
CrescentLeaf
41362a591c feat: 粘貼文件, 多個同名文件共存發送 2025-09-24 22:57:12 +08:00
CrescentLeaf
1b36a45252 ui: 修繕圖片縮放對話框: 圖片原始位置
* 我盡力了, 這玩意設置位置太靈車了
2025-09-24 22:32:15 +08:00
CrescentLeaf
38db2e1310 fix: 多個同 DeviceId 不同 Session 的客戶端無法同時收到消息 2025-09-24 22:03:23 +08:00
CrescentLeaf
9a3e87d89c chore: 客戶端發送非驗證性請求前, 必須先等待驗證 2025-09-24 21:52:53 +08:00
CrescentLeaf
954b5d3430 ui: 細節優化: 發送消息時, 轉圈 2025-09-24 21:44:52 +08:00
CrescentLeaf
6dfe59c5a8 chore(ui): 圖片加載失敗使用 snackbar 提示 2025-09-24 21:34:04 +08:00
CrescentLeaf
b741cbf9ba chore: 進一步解除傳輸最大限制 2025-09-24 21:33:30 +08:00
CrescentLeaf
d5fbc490ea feat: 支持發送文件
* 目前還只能拖拽到輸入框
2025-09-24 21:33:16 +08:00
CrescentLeaf
276ce5cae8 fix: 控制臺不解析 buffer 2025-09-24 21:32:09 +08:00
CrescentLeaf
3a9312654e chore: 控制臺不解析 buffer
* 額外作用: 加快傳輸效率
2025-09-24 21:19:42 +08:00
CrescentLeaf
0a10009613 chore: localify pinch-zoom 2025-09-24 18:59:25 +08:00
CrescentLeaf
8759b660f5 refactor: randomUUID with fallback 2025-09-24 18:15:41 +08:00
CrescentLeaf
ae3b9c8226 ui: 修正 Tab 指示標顯示不正常 2025-09-24 18:15:04 +08:00
CrescentLeaf
faec599822 feat(wip): 富文本消息 (aka Markdown + 自定義解析) 2025-09-24 16:43:28 +08:00
CrescentLeaf
da1c7cd8cf fix: Markdown 沒有被渲染 2025-09-24 10:57:21 +08:00
CrescentLeaf
a0bf323ac9 feat(wip): Markdown 2025-09-24 09:23:31 +08:00
CrescentLeaf
4a2014e10d feat(wip): 上傳文件 2025-09-23 23:29:20 +08:00
CrescentLeaf
a01a64116f feat(wip): markdown 解析 2025-09-23 23:28:54 +08:00
CrescentLeaf
f6f2590532 chore: make lint happy 2025-09-23 23:10:04 +08:00
CrescentLeaf
20f5484e90 feat: 支持異步接口調用方法體 2025-09-23 23:08:50 +08:00
CrescentLeaf
14f5bbfec9 feat: 適配移動端界面 2025-09-23 17:44:10 +08:00
CrescentLeaf
5d5b04ba05 refactor: 重構 對話 成員的儲存邏輯
* 使用關聯資料庫, 鏈接 user_id 和 chat_id
2025-09-23 09:20:30 +08:00
CrescentLeaf
0ef2859291 feat(wip): 上傳圖片等多媒體文件 2025-09-23 00:27:57 +08:00
CrescentLeaf
b82d32cad7 chore: 添加 Chat 類型的常量定義 2025-09-22 23:08:41 +08:00
CrescentLeaf
10da3b8e77 refactor: 重寫 Chat 成員邏輯
* 不再區分 user_a/b, 直接使用 members_list 雙成員模式
* 爲以後群聊打下基礎
2025-09-22 23:08:19 +08:00
CrescentLeaf
184a80436d feat: 自動在重連時進行身份驗證 2025-09-22 23:06:24 +08:00
CrescentLeaf
2de4d3548d feat: 視情況 自動滾動到最新消息 2025-09-22 23:06:01 +08:00
CrescentLeaf
fc197ea41a feat(ui): 拉到最頂部加載更多消息 2025-09-22 23:05:21 +08:00
CrescentLeaf
43385780f8 fix: 打開對話后不會自己滑動到底部 2025-09-22 19:39:53 +08:00
CrescentLeaf
791102c034 fix: MessageManager 建表失敗 2025-09-21 16:13:48 +08:00
CrescentLeaf
8bcb3e74b6 feat: 服務端可以獲取每個客戶端的連接 2025-09-21 16:13:31 +08:00
CrescentLeaf
e4c26a07cf feat: 緩存資料, 獲取任意用戶的資料 2025-09-21 16:13:01 +08:00
CrescentLeaf
f118c6b6f5 chore: 修繕客戶端請求允許等待連接 2025-09-21 16:12:31 +08:00
CrescentLeaf
cb947429fb feat: 收發消息 2025-09-21 16:11:58 +08:00
CrescentLeaf
b3d620a329 refactor: 重寫消息顯示相關邏輯 2025-09-21 16:11:13 +08:00
CrescentLeaf
28ffd134df feat: 服務端 Api 可以持有 client socket 2025-09-21 14:12:06 +08:00
CrescentLeaf
f600245d3b feat: BaseApi 有條件獲取更多的數據 2025-09-21 14:06:36 +08:00
CrescentLeaf
706d811087 feat(wip): 事件緩存以備離綫重連重發 2025-09-21 14:06:08 +08:00
CrescentLeaf
e5dd3ade51 feat: 檢驗用戶的 設備 ID 2025-09-21 12:28:44 +08:00
CrescentLeaf
83719f5f44 fix(ui): 兩個列表沒有吃滿寬度 2025-09-21 11:06:21 +08:00
CrescentLeaf
082817d6cd feat(wip): 收發消息 2025-09-21 02:18:15 +08:00
CrescentLeaf
ee79e3eefa chore: RecentChat -> extends <- Chat 2025-09-21 02:18:05 +08:00
CrescentLeaf
6a1084eeca fix: Chat 創建失敗, 並修正了 ChatPrivate 獲取對方的邏輯 2025-09-21 02:17:44 +08:00
CrescentLeaf
71e6d24d6e fix: Chat 獲取 avatar 邏輯錯誤 2025-09-21 02:16:48 +08:00
CrescentLeaf
7686a9b7d1 chore: 使 Client.invoke 支持等待后請求, 解耦 updateCachedProfile 2025-09-21 02:16:01 +08:00
CrescentLeaf
4837c17c2e fix: Chat (客戶端側) title 設置為非空 2025-09-21 02:15:27 +08:00
CrescentLeaf
3d367711cc feat(wip): 聯絡人/群組對話框, 並打開對應的對話 2025-09-21 02:14:39 +08:00
CrescentLeaf
6f006f38a4 fix: app.use -> get 2025-09-21 02:13:55 +08:00
CrescentLeaf
cb4aeaed21 fix(ui): Avatar 不顯示文字 2025-09-21 02:13:28 +08:00
CrescentLeaf
791baf474c feat: 修復並正式支持聯絡人
* wip(ui): 增刪
2025-09-21 02:13:16 +08:00
CrescentLeaf
468de4f439 feat(ui): 編輯個人檔案對話框 2025-09-21 02:11:47 +08:00
CrescentLeaf
2ec4f634ae feat(wip): remove contact 2025-09-20 21:17:43 +08:00
CrescentLeaf
8f7e61dfd2 feat: Chat (instance) getAnotherUserForPrivate 2025-09-20 20:59:12 +08:00
CrescentLeaf
212c2fa5dc chore: 重命名易混淆的 ChatPrivate findFor 方法 2025-09-20 20:58:44 +08:00
CrescentLeaf
dd88e8d1b8 chore: 添加 ChatApi 注釋 2025-09-20 20:58:20 +08:00
CrescentLeaf
eaf0f98058 update 2025-09-20 20:32:26 +08:00
CrescentLeaf
1acc73c7b4 chore: make lint happy 2025-09-20 20:14:47 +08:00
CrescentLeaf
23df74ddac ui: 微調 資料卡 昵稱字體 2025-09-20 20:13:20 +08:00
CrescentLeaf
70478584b7 chore: 精簡類型注解 2025-09-20 20:12:57 +08:00
CrescentLeaf
90295f0d38 fix: useAsyncEffect loops 2025-09-20 19:52:04 +08:00
CrescentLeaf
5ff726d834 fix(ui): 右側的面板沒有吃滿寬度 2025-09-20 19:51:41 +08:00
CrescentLeaf
ab1bc844ab fix: WTF Where is my React 2025-09-20 18:41:46 +08:00
CrescentLeaf
167b157134 refactor: 封裝 useAsyncEffect 2025-09-20 18:26:08 +08:00
CrescentLeaf
3b98fc4de3 feat(wip): 多選聯絡人 2025-09-20 18:14:52 +08:00
CrescentLeaf
4a32fd216b feat: search for recentschat 2025-09-20 18:00:12 +08:00
CrescentLeaf
af9b0d7cf2 fix: 由於未知原因導致的 輸入框 逃竄到 Tab 的 change 事件, 造成 Tab Panel 顯示異常 2025-09-20 17:35:12 +08:00
CrescentLeaf
c82d718fa7 feat: search contact by nickname/id/username 2025-09-20 17:29:12 +08:00
CrescentLeaf
fc3df592bc chore: make code looks happy 2025-09-20 17:01:05 +08:00
CrescentLeaf
5ce42bf651 updated
浪費了半天時間, 索性移除了聯絡人分組的支援
2025-09-20 16:57:17 +08:00
CrescentLeaf
6a8acd4717 ui: remember split sizes state 2025-09-20 08:18:28 +08:00
CrescentLeaf
03f6f2743f chore: add "not impl" for not exists func 2025-09-20 00:33:22 +08:00
CrescentLeaf
13c42ddf38 chore: add UserBean for client 2025-09-20 00:32:55 +08:00
CrescentLeaf
c13913f08a feat(wip): 聯絡人 2025-09-20 00:32:37 +08:00
CrescentLeaf
b7ce12ff5e ui: 添加打開對話提示 2025-09-20 00:32:21 +08:00
CrescentLeaf
dd7c578534 fix: auth not check user is exists 2025-09-20 00:32:00 +08:00
CrescentLeaf
d473ff81bd feat(wip): 對話 2025-09-20 00:31:36 +08:00
CrescentLeaf
c6bfca0482 fix(typo): p->a<-ivate 2025-09-19 22:50:55 +08:00
CrescentLeaf
b1e7f3e485 fix(ui): 侧边联络人列表显示溢出 2025-09-19 20:04:56 +08:00
CrescentLeaf
a85ea56bb7 feat(wip): MessagesManager 2025-09-14 14:33:16 +08:00
CrescentLeaf
ee670f86b6 refactor: 解耦側邊列表 2025-09-14 14:33:04 +08:00
CrescentLeaf
85b48475de chore: remove useless code 2025-09-14 14:32:37 +08:00
CrescentLeaf
0af3e7a449 feat(wip): 實現 ChatPrivate 2025-09-14 14:32:24 +08:00
CrescentLeaf
2b54a7a13a chore: 統一 可選成員 寫法 2025-09-14 14:31:53 +08:00
CrescentLeaf
4cc4866db1 CHORE: FIX DENO LANGUAGE SERVER OUT OF MEMORY CAUSED BY COMPILED FRONTEND 2025-09-14 13:55:25 +08:00
CrescentLeaf
a3d5e93240 feat(wip): Chat impl 2025-09-14 00:37:03 +08:00
CrescentLeaf
ed494413fd feat(wip): Chat.getInfo 2025-09-14 00:36:51 +08:00
CrescentLeaf
557234841d ui: ChatFragment 使用分面板的樣式 2025-09-14 00:18:56 +08:00
CrescentLeaf
ea17ab2ddd chore: rename ChatFragment. js -> ts 2025-09-14 00:12:50 +08:00
CrescentLeaf
20ef8a8514 chore: make lint happy 2025-09-14 00:11:13 +08:00
CrescentLeaf
124879f11f ui: AppMobile 界面長寬修正 2025-09-13 23:50:38 +08:00
CrescentLeaf
125938b8be feat(ui): (wip)移動端界面! 2025-09-13 22:14:35 +08:00
CrescentLeaf
2208a2d292 ui: 調整修改頭像 snackbar 位置 2025-09-13 13:02:50 +08:00
CrescentLeaf
1deec533ad fix: Android 上強制使用 @rollup/wasm-node 2025-09-13 12:28:02 +08:00
CrescentLeaf
633cfed87b feat: setNickName setUserName getMyInfo 2025-09-13 00:40:32 +08:00
CrescentLeaf
c51a6508e4 feat: access myUserProfile through Client 2025-09-13 00:40:09 +08:00
CrescentLeaf
12c2e13505 feat(wip): user profile dialog 2025-09-13 00:39:58 +08:00
CrescentLeaf
372e71bc1c chore: make User.ts declare better 2025-09-13 00:39:25 +08:00
CrescentLeaf
5fee5dd363 chore: useless change 2025-09-13 00:39:03 +08:00
CrescentLeaf
2ee73416e0 chore: change vite config: sourcemap: inline -> true 2025-09-13 00:38:51 +08:00
CrescentLeaf
73a1536df7 chore: add new Api declaretion 2025-09-13 00:38:17 +08:00
CrescentLeaf
8ebad65140 chore: import Avatar.jsx -> .tsx 2025-09-13 00:37:56 +08:00
CrescentLeaf
6896a1f8af refactor: Avatar.jsx -> .tsx 2025-09-13 00:37:25 +08:00
CrescentLeaf
b30035d5a8 feat: access uploaded files through http 2025-09-13 00:37:08 +08:00
CrescentLeaf
6b0e781fdf fix: file upload failed by folder not created 2025-09-13 00:36:48 +08:00
CrescentLeaf
fd6ceb82df chore: remove useless & add getAvatarFileHash 2025-09-13 00:36:12 +08:00
CrescentLeaf
546f04dc0e chore: declare new Api 2025-09-08 23:18:26 +08:00
CrescentLeaf
bc11034892 feat(wip): declare Message 2025-09-08 23:18:13 +08:00
CrescentLeaf
dfe8b27a12 feat(wip): 聊天頁面的消息列表, 自己索引消息 2025-09-08 23:17:59 +08:00
CrescentLeaf
5eb7e0018a feat(untestes): setAvatar 2025-09-08 23:17:28 +08:00
CrescentLeaf
b3015084a6 feat(wip): sendMessage getMessageHistory 2025-09-08 23:17:05 +08:00
CrescentLeaf
316fd140bc feat: BaseApi 兩個 Token 檢查方法 2025-09-08 23:16:41 +08:00
CrescentLeaf
3cb9bcc148 chore: Token.ts 單獨成類 2025-09-08 23:16:17 +08:00
CrescentLeaf
4ca3bd44da fix: missing File.getHash 2025-09-08 22:46:31 +08:00
CrescentLeaf
39c1473c57 chore: fuck lint and make it happy 2025-09-08 22:45:46 +08:00
CrescentLeaf
3c3beebfc5 fix: wrong Crypto->E<-S 2025-09-08 22:44:53 +08:00
CrescentLeaf
9b3a24e37a chore: make lint unhappy 2025-09-08 21:33:38 +08:00
CrescentLeaf
182236964b chore: 更加豐富的顔色! 2025-09-08 21:31:17 +08:00
CrescentLeaf
a3920f9084 fix: 訪問令牌失效判定邏輯錯誤 2025-09-08 21:31:01 +08:00
CrescentLeaf
45aef8204a fix: CryptoES -> CryptoJS 2025-09-08 21:26:09 +08:00
CrescentLeaf
e2c385b559 fix: token not stored after login 2025-09-08 21:22:22 +08:00
CrescentLeaf
4a942f1e77 chore: remove unused window exportion 2025-09-08 21:20:53 +08:00
CrescentLeaf
fb541849b4 fix: LocalDataStorage 2025-09-08 21:18:58 +08:00
CrescentLeaf
9e92fad8fa chore: colorful console.log :) 2025-09-08 20:19:36 +08:00
CrescentLeaf
3617292409 chore: add salt&key in config 2025-09-08 03:10:57 +08:00
CrescentLeaf
a3fc61494e feat: token 2025-09-08 03:10:45 +08:00
CrescentLeaf
fa62180667 feat: login & register 2025-09-08 03:10:36 +08:00
CrescentLeaf
e60c1cf1c4 feat: user password 2025-09-08 03:10:26 +08:00
CrescentLeaf
7e60e4a4be chore: add checkArgsEmpty 2025-09-08 03:09:56 +08:00
CrescentLeaf
f3a9cb8641 chore: add DataWrongError 2025-09-08 03:09:41 +08:00
CrescentLeaf
c577797e57 chore: add DataWrongError 2025-09-08 03:09:21 +08:00
CrescentLeaf
3a7e4970d4 ui: login & register 2025-09-08 03:09:05 +08:00
CrescentLeaf
0e14bb9a45 dep: add socket.io-client 2025-09-08 03:08:11 +08:00
CrescentLeaf
2869a77abd chore: add mising "id" 2025-09-07 22:30:45 +08:00
CrescentLeaf
913d1f395f chore: make lint UNHAPPY 2025-09-07 22:30:31 +08:00
CrescentLeaf
abf06c71af chore: useEventListener allow Ref<null> 2025-09-07 21:53:53 +08:00
CrescentLeaf
afeab61468 chore: make lint unhappy 2025-09-07 21:53:26 +08:00
CrescentLeaf
f06e93ef06 ui: add snackbar util 2025-09-07 20:07:06 +08:00
CrescentLeaf
71b368a5ac feat: 在服務端重新編譯前端 2025-09-07 18:21:49 +08:00
CrescentLeaf
1a69b521e6 chore: 2025-09-07 13:08:55 +08:00
CrescentLeaf
47233fbe58 chore: useEventListener -> TS 2025-09-07 13:04:13 +08:00
CrescentLeaf
5b5845db14 chore: ReactDOM <- react-dom/client 2025-09-07 13:00:45 +08:00
CrescentLeaf
c752f13d22 chore: make lint happy 2025-09-07 12:59:07 +08:00
CrescentLeaf
427393a747 chore: Debug with --watch 2025-09-07 12:53:08 +08:00
CrescentLeaf
d587b32a0a chore: 修改 VSCode 調試配置 2025-09-07 12:49:42 +08:00
CrescentLeaf
25320fe521 refactor: 推翻舊架構, 進入 Vite 盛世!
* 所有的 CDN 依賴已全部 npm 化
* Webpack? 一邊去! Vite 太好用啦!
* 將 Imports.ts 剔除
* 移除了大量的靜態文件
* 將 index.html 的部分代碼分離
* 修改 deno task
* 移除了動態編譯頁面的支持
* ./static 引用全部變更為 npm 包引用
2025-09-07 12:49:09 +08:00
656 changed files with 96724 additions and 33810 deletions

5
.gitignore vendored
View File

@@ -2,5 +2,6 @@
thewhitesilk_config.json thewhitesilk_config.json
# **默认**数据目录 # **默认**数据目录
thewhitesilk_data/ thewhitesilk_data/
# Node.js
deno.lock package-lock.json
node_modules/

8
.vscode/launch.json vendored
View File

@@ -5,10 +5,10 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"command": "deno task debug", "command": "npm run debug",
"name": "Run deno task debug", "name": "Debug",
"request": "launch", "request": "launch",
"type": "node-terminal", "type": "node-terminal"
}, }
] ]
} }

22
Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
# 使用官方 Deno 镜像
FROM denoland/deno:latest
# 设置镜像名称
LABEL image.name="lingchair"
# 设置工作目录
WORKDIR /app
# 复制源代码
COPY --exclude=.git --exclude=.gitignore --exclude=Dockerfile --exclude=readme.md --exclude=thewhitesilk_config.json --exclude=thewhitesilk_data . .
# 缓存依赖并构建项目
RUN npm run install-dependencies
RUN npm run build-client
# 暴露应用端口(根据你的应用调整端口号)
EXPOSE 3601
# 启动服务
CMD ["npm", "run", "server"]

View File

@@ -0,0 +1,3 @@
import { ApiCallbackMessage } from 'lingchair-internal-shared'
export type { ApiCallbackMessage as default }

View File

@@ -0,0 +1,11 @@
export * from 'lingchair-internal-shared'
import { ClientEvent } from "lingchair-internal-shared"
import Message from "./Message.ts"
export type ClientEventData<T extends ClientEvent> =
T extends "Client.onMessage" ? { message: Message } :
never
export type ClientEventCallback<T extends ClientEvent> = (data: ClientEventData<T>) => void

View File

@@ -0,0 +1,8 @@
import LingChairClient from "./LingChairClient.ts"
export default class BaseClientObject {
declare client: LingChairClient
constructor(client: LingChairClient) {
this.client = client
}
}

View File

@@ -0,0 +1,11 @@
import ApiCallbackMessage from "./ApiCallbackMessage.ts"
export default class CallbackError extends Error {
declare code: number
declare data?: object
constructor(re: ApiCallbackMessage) {
super(`[${re.code}] ${re.msg}${re.data ? ` (data: ${JSON.stringify(re.data)})` : ''}`)
this.code = re.code
this.data = re.data
}
}

234
client-protocol/Chat.ts Normal file
View File

@@ -0,0 +1,234 @@
import BaseClientObject from "./BaseClientObject.ts"
import BaseChatSettingsBean from "./bean/BaseChatSettingsBean.ts"
import ChatBean from "./bean/ChatBean.ts"
import JoinRequestBean from "./bean/JoinRequestBean.ts"
import MessageBean from "./bean/MessageBean.ts"
import CallbackError from "./CallbackError.ts"
import JoinRequest from "./JoinRequest.ts"
import LingChairClient from "./LingChairClient.ts"
import Message from "./Message.ts"
export default class Chat extends BaseClientObject {
declare bean: ChatBean
constructor(client: LingChairClient, bean: ChatBean) {
super(client)
this.bean = bean
}
/*
* ================================================
* 实例化方法
* ================================================
*/
static getForInvokeOnlyById(client: LingChairClient, id: string) {
return new Chat(client, {
id
} as ChatBean)
}
static async getById(client: LingChairClient, id: string) {
try {
return await this.getByIdOrThrow(client, id)
} catch (_) {
return null
}
}
static async getByIdOrThrow(client: LingChairClient, id: string) {
const re = await client.invoke("Chat.getInfo", {
token: client.access_token,
target: id,
})
if (re.code == 200)
return new Chat(client, re.data as unknown as ChatBean)
throw new CallbackError(re)
}
/**
* ================================================
* 创建对话 (另类实例化方法)
* ================================================
*/
static async getOrCreatePrivateChat(client: LingChairClient, user_id: string) {
try {
return await this.getOrCreatePrivateChatOrThrow(client, user_id)
} catch (_) {
return null
}
}
static async getOrCreatePrivateChatOrThrow(client: LingChairClient, user_id: string) {
const re = await client.invoke("Chat.getIdForPrivate", {
token: client.access_token,
target: user_id,
})
if (re.code != 200) throw new CallbackError(re)
return new Chat(client, re.data as unknown as ChatBean)
}
static async createGroup(client: LingChairClient, title: string, name?: string) {
try {
return await this.createGroupOrThrow(client, title, name)
} catch (_) {
return null
}
}
static async createGroupOrThrow(client: LingChairClient, title: string, name?: string) {
const re = await client.invoke("Chat.createGroup", {
token: client.access_token,
title,
name,
})
if (re.code != 200) throw new CallbackError(re)
return new Chat(client, re.data as unknown as ChatBean)
}
/**
* ================================================
* 对话消息
* ================================================
*/
async getMessages(page: number = 0) {
return (await this.getMessageBeans(page)).map((v) => new Message(this.client, v))
}
async getMessagesOrThrow(page: number = 0) {
return (await this.getMessageBeansOrThrow(page)).map((v) => new Message(this.client, v))
}
async getMessageBeans(page: number = 0) {
try {
return await this.getMessageBeansOrThrow(page)
} catch (_) {
return []
}
}
async getMessageBeansOrThrow(page: number = 0) {
const re = await this.client.invoke("Chat.getMessageHistory", {
token: this.client.access_token,
page,
target: this.bean.id,
})
if (re.code == 200) return re.data!.messages as MessageBean[]
throw new CallbackError(re)
}
async sendMessage(text: string) {
try {
return await this.sendMessageOrThrow(text)
} catch (_) {
return null
}
}
async sendMessageOrThrow(text: string) {
const re = await this.client.invoke("Chat.sendMessage", {
token: this.client.access_token,
text,
target: this.bean.id,
})
if (re.code == 200)
return new Message(this.client, re.data!.message as MessageBean)
throw new CallbackError(re)
}
/**
* ================================================
* 加入对话申请
* ================================================
*/
async getJoinRequests() {
try {
return await this.getJoinRequestsOrThrow()
} catch (_) {
return []
}
}
async getJoinRequestsOrThrow() {
const join_requests = await this.getJoinRequestBeansOrThrow()
return join_requests.map((jr) => new JoinRequest(this.client, jr, this.bean.id))
}
async getJoinRequestBeans() {
try {
return await this.getJoinRequestBeansOrThrow()
} catch (_) {
return []
}
}
async getJoinRequestBeansOrThrow() {
const re = await this.client.invoke("Chat.getJoinRequests", {
token: this.client.access_token
})
if (re.code == 200)
return re.data!.join_requests as JoinRequestBean[]
throw new CallbackError(re)
}
/**
* ================================================
* 对话信息
* ================================================
*/
async setAvatarFileHash(file_hash: string) {
try {
await this.setAvatarFileHashOrThrow(file_hash)
return true
} catch (_) {
return false
}
}
async setAvatarFileHashOrThrow(file_hash: string) {
const re = await this.client.invoke("Chat.setAvatar", {
token: this.client.access_token,
file_hash,
target: this.bean.id,
})
if (re.code != 200) throw new CallbackError(re)
this.bean.avatar_file_hash = file_hash
}
async updateSettings(args: BaseChatSettingsBean) {
try {
await this.updateSettingsOrThrow(args)
return true
} catch (_) {
return false
}
}
async updateSettingsOrThrow(args: BaseChatSettingsBean) {
const re = await this.client.invoke("Chat.updateSettings", {
token: this.client.access_token,
target: this.bean.id,
settings: args
})
if (re.code != 200) throw new CallbackError(re)
this.bean.settings = args
}
async getTheOtherUserId() {
try {
return await this.getTheOtherUserIdOrThrow()
} catch (_) {
return null
}
}
async getTheOtherUserIdOrThrow() {
const re = await this.client.invoke("Chat.updateSettings", {
token: this.client.access_token,
target: this.bean.id,
})
if (re.code != 200) throw new CallbackError(re)
return re.data!.user_id as string
}
/*
* ================================================
* 基本 Bean
* ================================================
*/
getId() {
return this.bean.id
}
getTitle() {
return this.bean.title
}
getType() {
return this.bean.type
}
isMember() {
return this.bean.is_member
}
isAdmin() {
return this.bean.is_admin
}
getAvatarFileHash() {
return this.bean.avatar_file_hash
}
getSettings() {
return this.bean.settings
}
}

View File

@@ -0,0 +1,66 @@
import BaseClientObject from "./BaseClientObject.ts"
import JoinRequestBean from "./bean/JoinRequestBean.ts"
import CallbackError from "./CallbackError.ts"
import LingChairClient from "./LingChairClient.ts"
import JoinRequestAction from "./type/JoinRequestAction.ts"
export default class JoinRequest extends BaseClientObject {
declare bean: JoinRequestBean
declare chat_id: string
constructor(client: LingChairClient, bean: JoinRequestBean, chat_id: string) {
super(client)
this.bean = bean
this.chat_id = chat_id
}
/*
* ================================================
* 操作
* ================================================
*/
async accept() {
return await this.process('accept')
}
async acceptOrThrow() {
return await this.processOrThrow('accept')
}
async remove() {
return await this.process('remove')
}
async removOrThrow() {
return await this.processOrThrow('remove')
}
async process(action: JoinRequestAction) {
try {
await this.processOrThrow(action)
return true
} catch (_) {
return false
}
}
async processOrThrow(action: JoinRequestAction) {
const re = await this.client.invoke("Chat.processJoinRequest", {
token: this.client.access_token,
chat_id: this.chat_id,
user_id: this.bean.user_id,
action,
})
if (re.code != 200) throw new CallbackError(re)
}
/*
* ================================================
* 基本 Bean
* ================================================
*/
getAvatarFileHash() {
return this.bean.avatar_file_hash
}
getUserId() {
return this.bean.user_id
}
getNickName() {
return this.bean.title
}
getReason() {
return this.bean.reason
}
}

View File

@@ -0,0 +1,269 @@
// deno-lint-ignore-file no-explicit-any
import { io, ManagerOptions, Socket, SocketOptions } from 'socket.io-client'
import crypto from 'node:crypto'
import { CallMethod, ClientEvent, ClientEventCallback } from './ApiDeclare.ts'
import ApiCallbackMessage from './ApiCallbackMessage.ts'
import { CallableMethodBeforeAuth, randomUUID } from "lingchair-internal-shared"
import CallbackError from "./CallbackError.ts"
import Message from "./Message.ts"
export default class LingChairClient {
declare client: Socket
declare access_token: string
declare server_url: string
declare device_id: string
declare refresh_token?: string
declare auto_fresh_token: boolean
declare auth_cache: {
refresh_token?: string,
access_token?: string,
account?: string,
password?: string,
}
constructor(args: {
server_url: string
device_id: string,
io?: Partial<ManagerOptions & SocketOptions>
auto_fresh_token?: boolean
}) {
this.server_url = args.server_url
this.auto_fresh_token = args.auto_fresh_token || false
this.device_id = args.device_id
this.client = io(args.server_url, {
transports: ["polling", "websocket", "webtransport"],
...args.io,
auth: {
...args.io?.auth,
device_id: this.device_id,
session_id: randomUUID(),
},
})
this.client.on("The_White_Silk", (name: ClientEvent, data: any, _callback: (ret: unknown) => void) => {
try {
if (name == null || data == null) return
for (const v of (this.events[name] || []))
v(({
"Client.onMessage": {
message: new Message(this, data.msg)
}
})[name])
} catch (e) {
console.error(e)
}
})
}
events: { [K in ClientEvent]?: ClientEventCallback<K>[] } = {}
on<K extends ClientEvent>(eventName: K, func: ClientEventCallback<K>) {
if (this.events[eventName] == null)
this.events[eventName] = []
if (this.events[eventName].indexOf(func) == -1)
this.events[eventName].push(func)
}
off<K extends ClientEvent>(eventName: K, func: ClientEventCallback<K>) {
if (this.events[eventName] == null)
this.events[eventName] = []
const index = this.events[eventName].indexOf(func)
if (index != -1)
this.events[eventName].splice(index, 1)
}
connect() {
this.client.connect()
}
disconnect() {
this.client.disconnect()
}
reconnect() {
this.disconnect()
this.connect()
}
invoke(method: CallMethod, args: object = {}, timeout: number = 10000): Promise<ApiCallbackMessage> {
return new Promise((resolve) => {
this.client!.timeout(timeout).emit("The_White_Silk", method, args, (err: Error, res: ApiCallbackMessage) => {
// 错误处理
if (err) return resolve({
code: -1,
msg: err.message,
})
if (CallableMethodBeforeAuth.indexOf(method) == -1 && res.code == 401 && this.auto_fresh_token) {
if (this.auth_cache)
this.auth(this.auth_cache).then((re) => {
if (!re) resolve(res)
this.invoke(method, args, timeout).then((re) => resolve(re))
})
else
resolve(res)
} else
resolve(res)
})
})
}
/**
* 建议在 auth 返回 true 时调用
*/
getCachedAccessToken() {
return this.access_token
}
/**
* 建议在 auth 返回 true 时调用
*/
getCachedRefreshToken() {
return this.refresh_token
}
/**
* 客户端上线
*
* 使用验证方式优先级: 访问 > 刷新 > 账号密码
*
* 不会逐一尝试
*/
async auth(args: {
refresh_token?: string,
access_token?: string,
account?: string,
password?: string,
}) {
try {
await this.authOrThrow(args)
return true
} catch (_) {
return false
}
}
/**
* 进行身份验证以接受客户端事件
*
* 使用验证方式优先级: 访问 > 刷新 > 账号密码
*
* 若传递了账号密码, 则同时缓存新的访问令牌和刷新令牌
*
* 如只传递两个令牌的其一, 按照优先级并在成功验证后赋值
*
* 多个验证方式不会逐一尝试
*/
async authOrThrow(args: {
refresh_token?: string
access_token?: string
account?: string
password?: string
ignore_all_empty?: boolean
}) {
if ((!args.access_token && !args.refresh_token) && (!args.account && !args.password) && !args.ignore_all_empty)
throw new Error('Access/Refresh token or account & password required, or ignore_all_empty=true')
this.auth_cache = args
let access_token = args.access_token
if (!access_token && args.refresh_token) {
const re = await this.invoke('User.refreshAccessToken', {
refresh_token: args.refresh_token,
})
if (re.code == 200) {
access_token = re.data!.access_token as string | undefined
this.refresh_token = args.refresh_token
} else
throw new CallbackError(re)
}
if (!access_token && (args.account && args.password)) {
const re = await this.invoke('User.login', {
account: args.account,
password: crypto.createHash('sha256').update(args.password).digest('hex'),
})
if (re.code == 200) {
access_token = re.data!.access_token as string | undefined
this.refresh_token = re.data!.refresh_token as string
} else
throw new CallbackError(re)
}
const re = await this.invoke('User.auth', {
access_token: access_token
})
if (re.code == 200)
this.access_token = access_token as string
else
throw new CallbackError(re)
}
getBaseHttpUrl() {
const url = new URL(this.server_url)
return (({
'ws:': 'http:',
'wss:': 'https:',
'http:': 'http:',
'https:': 'https:',
})[url.protocol] || 'http:') + '//' + url.host
}
getUrlForFileByHash(file_hash?: string, defaultUrl?: string) {
return file_hash ? (this.getBaseHttpUrl() + '/uploaded_files/' + file_hash) : defaultUrl
}
async register(args: {
nickname: string,
username?: string,
password: string,
}) {
try {
return await this.registerOrThrow(args)
} catch (_) {
return null
}
}
async registerOrThrow({
nickname,
username,
password,
}: {
nickname: string,
username?: string,
password: string,
}) {
const re = await this.invoke('User.register', {
nickname,
username,
password: crypto.createHash('sha256').update(password).digest('hex'),
})
if (re.code != 200)
throw new CallbackError(re)
return re.data!.user_id as string
}
async uploadFile({
chatId,
fileData,
fileName,
}: { fileName: string, fileData: ArrayBuffer | Blob | Response, chatId?: string }) {
const form = new FormData()
form.append("file",
fileData instanceof ArrayBuffer
? new File([fileData], fileName, { type: 'application/octet-stream' })
: (
fileData instanceof Blob ? fileData :
new File([await fileData.arrayBuffer()], fileName, { type: 'application/octet-stream' })
)
)
form.append('file_name', fileName)
chatId && form.append('chat_id', chatId)
const re = await fetch(this.getBaseHttpUrl() + '/upload_file', {
method: 'POST',
headers: {
"Token": this.access_token,
"Device-Id": this.device_id,
} as HeadersInit,
body: form,
credentials: 'omit',
})
const text = await (await re.blob()).text()
let json
try {
json = JSON.parse(text)
// deno-lint-ignore no-empty
} catch (_) { }
if (!re.ok) throw new CallbackError({
...(json == null ? {
msg: text
} : json),
code: re.status,
} as ApiCallbackMessage)
return json.data.file_hash as string
}
}

240
client-protocol/Message.ts Normal file
View File

@@ -0,0 +1,240 @@
import BaseClientObject from "./BaseClientObject.ts"
import MessageBean from "./bean/MessageBean.ts"
import LingChairClient from "./LingChairClient.ts"
import Chat from "./Chat.ts"
import User from "./User.ts"
import CallbackError from "./CallbackError.ts"
import ApiCallbackMessage from "./ApiCallbackMessage.ts"
import * as marked from 'marked'
import { text } from "node:stream/consumers";
class ChatMention extends BaseClientObject {
declare chat_id?: string
declare user_id?: string
declare text?: string
constructor(client: LingChairClient, {
user_id,
chat_id,
text,
}: {
user_id?: string,
chat_id?: string,
text: string,
}) {
super(client)
this.user_id = user_id
this.chat_id = chat_id
this.text = text
}
async getChat() {
return await Chat.getById(this.client, this.chat_id as string)
}
async getUser() {
return await User.getById(this.client, this.user_id as string)
}
getText() {
return this.text
}
}
type FileType = 'Video' | 'Image' | 'File'
type MentionType = 'ChatMention' | 'UserMention'
class ChatAttachment extends BaseClientObject {
declare file_hash: string
declare file_name: string
constructor(client: LingChairClient, {
file_hash,
file_name
}: {
file_hash: string,
file_name: string
}) {
super(client)
this.file_name = file_name
this.file_hash = file_hash
}
async blob() {
try {
return await this.blobOrThrow()
} catch (_) {
return null
}
}
fetch(init?: RequestInit) {
const url = this.client.getUrlForFileByHash(this.file_hash)
return fetch(url!, init)
}
async blobOrThrow() {
const re = await this.fetch()
const blob = await re.blob()
if (!re.ok) throw new CallbackError({
msg: await blob.text(),
code: re.status,
} as ApiCallbackMessage)
return blob
}
async getMimeType() {
try {
return await this.getMimeTypeOrThrow()
} catch (_) {
return null
}
}
async getMimeTypeOrThrow() {
const re = await this.fetch({
method: 'HEAD'
})
if (re.ok) {
const t = re.headers.get('content-type')
if (t)
return t
throw new Error("Unable to get Content-Type")
}
throw new CallbackError({
msg: await re.text(),
code: re.status,
} as ApiCallbackMessage)
}
async getLength() {
try {
return await this.getLengthOrThrow()
} catch (_) {
return null
}
}
async getLengthOrThrow() {
const re = await this.fetch({
method: 'HEAD'
})
if (re.ok) {
const contentLength = re.headers.get('content-length')
if (contentLength)
return parseInt(contentLength)
throw new Error("Unable to get Content-Length")
}
throw new CallbackError({
msg: await re.text(),
code: re.status,
} as ApiCallbackMessage)
}
getFileHash() {
return this.file_hash
}
getFileName() {
return this.file_name
}
}
export default class Message extends BaseClientObject {
declare bean: MessageBean
constructor(client: LingChairClient, bean: MessageBean) {
super(client)
this.bean = bean
}
/*
* ================================================
* 基本 Bean
* ================================================
*/
getId() {
return this.bean.id
}
getChatId() {
return this.bean.chat_id
}
async getChat() {
return await Chat.getById(this.client, this.bean.chat_id as string)
}
getText() {
return this.bean.text
}
parseWithTransformers({
attachment,
mention,
}: {
attachment?: ({ text, fileType, attachment }: { text: string, fileType: FileType, attachment: ChatAttachment }) => string,
mention?: ({ text, mentionType, mention }: { text: string, mentionType: MentionType, mention: ChatMention }) => string,
}) {
return new marked.Marked({
async: false,
extensions: [
{
name: 'text',
renderer: ({ text }) => text,
},
{
name: 'heading',
renderer({ tokens }) {
return this.parser.parseInline(tokens!)
},
},
{
name: 'paragraph',
renderer({ tokens }) {
return this.parser.parseInline(tokens!)
},
},
{
name: 'image',
renderer: ({ text, href }) => {
const mentionType = /^(UserMention|ChatMention)=.*/.exec(text)?.[1] as MentionType
const fileType = (/^(Video|File|Image)=.*/.exec(text)?.[1] || 'Image') as FileType
if (fileType != null && /tws:\/\/file\?hash=[A-Za-z0-9]+$/.test(href)) {
const file_hash = /^tws:\/\/file\?hash=(.*)/.exec(href)?.[1]!
let file_name: string = /^(Video|File|Image)=(.*)/.exec(text)?.[2] || text
file_name.trim() == '' && (file_name = 'Unnamed_File')
return attachment ? attachment({ text: text, attachment: new ChatAttachment(this.client, { file_hash, file_name }), fileType: fileType, }) : text
}
if (mentionType != null && /^tws:\/\/chat\?id=[A-Za-z0-9]+/.test(href)) {
const id = /^tws:\/\/chat\?id=(.*)/.exec(href)?.[1]!
const label = /^(User|Chat)Mention=(.*)/.exec(text)?.[2] || ''
return mention ? mention({
text: text,
mention: new ChatMention(this.client, {
[({
ChatMention: 'chat_id',
UserMention: 'user_id',
})[mentionType]]: id,
text: label,
}),
mentionType: mentionType,
}) : text
}
},
}
]
}).parse(this.getText()) as string
}
getAttachments() {
const attachments: ChatAttachment[] = []
this.parseWithTransformers({
attachment({ attachment }) {
attachments.push(attachment)
return ''
}
})
return attachments
}
getMentions() {
const mentions: ChatMention[] = []
this.parseWithTransformers({
mention({ mention }) {
mentions.push(mention)
return ''
}
})
return mentions
}
getUserId() {
return this.bean.user_id
}
async getUser() {
return await User.getById(this.client, this.bean.user_id as string)
}
getTime() {
return this.bean.time
}
}

View File

@@ -0,0 +1,13 @@
import RecentChatBean from "./bean/RecentChatBean.ts"
import Chat from "./Chat.ts"
import LingChairClient from "./LingChairClient.ts"
export default class RecentChat extends Chat {
declare bean: RecentChatBean
constructor(client: LingChairClient, bean: RecentChatBean) {
super(client, bean)
}
getContent() {
return this.bean.content
}
}

55
client-protocol/User.ts Normal file
View File

@@ -0,0 +1,55 @@
import BaseClientObject from "./BaseClientObject.ts"
import UserBean from "./bean/UserBean.ts"
import CallbackError from "./CallbackError.ts"
import LingChairClient from "./LingChairClient.ts"
export default class User extends BaseClientObject {
declare bean: UserBean
constructor(client: LingChairClient, bean: UserBean) {
super(client)
this.bean = bean
}
/*
* ================================================
* 实例化方法
* ================================================
*/
static getForInvokeOnlyById(client: LingChairClient, id: string) {
return new User(client, {
id
} as UserBean)
}
static async getById(client: LingChairClient, id: string) {
try {
return await this.getByIdOrThrow(client, id)
} catch (_) {
return null
}
}
static async getByIdOrThrow(client: LingChairClient, id: string) {
const re = await client.invoke("User.getInfo", {
token: client.access_token,
target: id,
})
if (re.code == 200)
return new User(client, re.data as unknown as UserBean)
throw new CallbackError(re)
}
/*
* ================================================
* 基本 Bean
* ================================================
*/
getId() {
return this.bean.id
}
getUserName() {
return this.bean.username
}
getNickName() {
return this.bean.nickname
}
getAvatarFileHash() {
return this.bean.avatar_file_hash
}
}

View File

@@ -0,0 +1,232 @@
import CallbackError from "./CallbackError.ts"
import Chat from "./Chat.ts"
import LingChairClient from "./LingChairClient.ts"
import RecentChat from "./RecentChat.ts"
import User from "./User.ts"
import ChatBean from "./bean/ChatBean.ts"
import RecentChatBean from "./bean/RecentChatBean.ts"
import UserBean from "./bean/UserBean.ts"
export default class UserMySelf extends User {
/*
* ================================================
* 实例化方法
* ================================================
*/
static async getMySelf(client: LingChairClient) {
try {
return await this.getMySelfOrThrow(client)
} catch (_) {
return null
}
}
static async getMySelfOrThrow(client: LingChairClient) {
const re = await client.invoke("User.getMyInfo", {
token: client.access_token,
})
if (re.code == 200)
return new UserMySelf(client, re.data as unknown as UserBean)
throw new CallbackError(re)
}
/*
* ================================================
* 账号相关
* ================================================
*/
async resetPassword(old_password: string, new_password: string) {
try {
await this.resetPasswordOrThrow(old_password, new_password)
return true
} catch (_) {
return false
}
}
async resetPasswordOrThrow(old_password: string, new_password: string) {
const re = await this.client.invoke("User.resetPassword", {
token: this.client.access_token,
old_password,
new_password,
})
if (re.code != 200) throw new CallbackError(re)
}
/*
* ================================================
* 个人资料
* ================================================
*/
async setAvatarFileHash(file_hash: string) {
try {
await this.setAvatarFileHashOrThrow(file_hash)
return true
} catch (_) {
return false
}
}
async setAvatarFileHashOrThrow(file_hash: string) {
const re = await this.client.invoke("User.setAvatar", {
token: this.client.access_token,
file_hash,
})
if (re.code != 200) throw new CallbackError(re)
this.bean.avatar_file_hash = file_hash
}
async setUserName(user_name: string) {
return await this.updateProfile({ username: user_name })
}
async setUserNameOrThrow(user_name: string) {
await this.updateProfileOrThrow({ username: user_name })
}
async setNickName(nick_name: string) {
return await this.updateProfile({ nickname: nick_name })
}
async setNickNameOrThrow(nick_name: string) {
await this.updateProfileOrThrow({ nickname: nick_name })
}
async updateProfile(args: {
username?: string,
nickname?: string
}) {
try {
await this.updateProfileOrThrow(args)
return true
} catch (_) {
return false
}
}
async updateProfileOrThrow({
username,
nickname
}: {
username?: string,
nickname?: string
}) {
const re = await this.client.invoke("User.updateProfile", {
token: this.client.access_token,
nickname,
username,
})
if (re.code != 200) throw new CallbackError(re)
nickname && (this.bean.nickname = nickname)
username && (this.bean.username = username)
}
/*
* ================================================
* 收藏对话
* ================================================
*/
async addFavouriteChats(chat_ids: string[]) {
try {
await this.addFavouriteChatsOrThrow(chat_ids)
return true
} catch (_) {
return false
}
}
async addFavouriteChatsOrThrow(chat_ids: string[]) {
const re = await this.client.invoke("User.addContacts", {
token: this.client.access_token,
targets: chat_ids,
})
if (re.code != 200) throw new CallbackError(re)
}
async removeFavouriteChats(chat_ids: string[]) {
try {
await this.removeFavouriteChatsOrThrow(chat_ids)
return true
} catch (_) {
return false
}
}
async removeFavouriteChatsOrThrow(chat_ids: string[]) {
const re = await this.client.invoke("User.removeContacts", {
token: this.client.access_token,
targets: chat_ids,
})
if (re.code != 200) throw new CallbackError(re)
}
async getMyFavouriteChatBeans() {
try {
return await this.getMyFavouriteChatBeansOrThrow()
} catch (_) {
return []
}
}
async getMyFavouriteChatBeansOrThrow() {
const re = await this.client.invoke("User.getMyContacts", {
token: this.client.access_token
})
if (re.code == 200)
return (re.data!.favourite_chats || re.data!.contacts_list) as ChatBean[]
throw new CallbackError(re)
}
async getMyFavouriteChats() {
try {
return await this.getMyFavouriteChatsOrThrow()
} catch (_) {
return []
}
}
async getMyFavouriteChatsOrThrow() {
return (await this.getMyFavouriteChatBeansOrThrow()).map((bean) => new Chat(this.client, bean))
}
/*
* ================================================
* 最近对话
* ================================================
*/
async getMyRecentChatBeans() {
try {
return await this.getMyRecentChatBeansOrThrow()
} catch (_) {
return []
}
}
async getMyRecentChatBeansOrThrow() {
const re = await this.client.invoke("User.getMyRecentChats", {
token: this.client.access_token
})
if (re.code == 200)
return re.data!.recent_chats as RecentChatBean[]
throw new CallbackError(re)
}
async getMyRecentChats() {
try {
return await this.getMyRecentChatsOrThrow()
} catch (_) {
return []
}
}
async getMyRecentChatsOrThrow() {
return (await this.getMyRecentChatBeansOrThrow()).map((bean) => new RecentChat(this.client, bean))
}
/*
* ================================================
* 所有对话
* ================================================
*/
async getMyAllChatBeans() {
try {
return await this.getMyAllChatBeansOrThrow()
} catch (_) {
return []
}
}
async getMyAllChatBeansOrThrow() {
const re = await this.client.invoke("User.getMyAllChats", {
token: this.client.access_token
})
if (re.code == 200)
return re.data!.all_chats as ChatBean[]
throw new CallbackError(re)
}
async getMyAllChats() {
try {
return await this.getMyAllChatsOrThrow()
} catch (_) {
return []
}
}
async getMyAllChatsOrThrow() {
return (await this.getMyAllChatBeansOrThrow()).map((bean) => new Chat(this.client, bean))
}
}

View File

@@ -0,0 +1,5 @@
interface BaseChatSettings {
[key: string]: unknown
}
export default BaseChatSettings

View File

@@ -0,0 +1,15 @@
import BaseChatSettingsBean from "./BaseChatSettingsBean.ts"
import ChatType from "../type/ChatType.ts"
export default class ChatBean {
declare type: ChatType
declare id: string
declare title: string
declare avatar_file_hash?: string
declare settings?: BaseChatSettingsBean
declare is_member: boolean
declare is_admin: boolean
[key: string]: unknown
}

View File

@@ -0,0 +1,14 @@
import BaseChatSettings from "./BaseChatSettingsBean.ts"
interface GroupSettingsBean extends BaseChatSettings {
allow_new_member_join?: boolean
allow_new_member_from_invitation?: boolean
new_member_join_method?: 'disabled' | 'allowed_by_admin' | 'answered_and_allowed_by_admin'
answered_and_allowed_by_admin_question?: string
// 下面两个比较特殊, 由服务端给予
group_title: string
group_name: string
}
export default GroupSettingsBean

View File

@@ -0,0 +1,8 @@
export default class JoinRequestBean {
declare user_id: string
declare nickname: string
declare avatar_file_hash?: string
declare reason?: string
[key: string]: unknown
}

View File

@@ -0,0 +1,7 @@
export default class MessageBean {
declare id: number
declare text: string
declare user_id?: string
declare chat_id?: string
declare time: string
}

View File

@@ -0,0 +1,5 @@
import ChatBean from "./ChatBean.ts"
export default class RecentChatBean extends ChatBean {
declare content: string
}

View File

@@ -0,0 +1,6 @@
export default class UserBean {
declare id: string
declare username?: string
declare nickname: string
declare avatar_file_hash?: string
}

28
client-protocol/main.ts Normal file
View File

@@ -0,0 +1,28 @@
import Chat from "./Chat.ts"
import User from "./User.ts"
import UserMySelf from "./UserMySelf.ts"
import UserBean from "./bean/UserBean.ts"
import ChatBean from "./bean/ChatBean.ts"
import GroupSettingsBean from "./bean/GroupSettingsBean.ts"
import JoinRequestBean from "./bean/JoinRequestBean.ts"
import MessageBean from "./bean/MessageBean.ts"
import RecentChatBean from "./bean/RecentChatBean.ts"
import LingChairClient from "./LingChairClient.ts"
import CallbackError from "./CallbackError.ts"
export {
LingChairClient,
CallbackError,
Chat,
User,
UserMySelf,
UserBean,
ChatBean,
MessageBean,
RecentChatBean,
JoinRequestBean,
}
export type { GroupSettingsBean }

View File

@@ -0,0 +1,11 @@
{
"name": "lingchair-client-protocol",
"type": "module",
"main": "./main.ts",
"dependencies": {
"lingchair-internal-shared": "*",
"marked": "16.3.0",
"socket.io-client": "4.8.1",
"crypto-browserify": "3.12.1"
}
}

View File

@@ -0,0 +1,3 @@
type ChatType = 'private' | 'group'
export default ChatType

View File

@@ -0,0 +1,3 @@
type JoinRequestAction = 'accept' | 'remove'
export default JoinRequestAction

View File

@@ -0,0 +1,8 @@
import MessageBean from '../bean/MessageBean.ts'
interface OnMessageData {
chat: string
msg: MessageBean
}
export default OnMessageData

23
client/ClientCache.ts Normal file
View File

@@ -0,0 +1,23 @@
import { Chat, User } from "lingchair-client-protocol"
import getClient from "./getClient"
type CouldCached = User | Chat | null
export default class ClientCache {
static caches: { [key: string]: CouldCached } = {}
static async getUser(id: string) {
const k = 'user_' + id
if (this.caches[k] != null)
return this.caches[k] as User | null
this.caches[k] = await User.getById(getClient(), id)
return this.caches[k]
}
static async getChat(id: string) {
const k = 'chat_' + id
if (this.caches[k] != null)
return this.caches[k] as Chat | null
this.caches[k] = await Chat.getById(getClient(), id)
return this.caches[k]
}
}

View File

@@ -1,45 +0,0 @@
import { CryptoES } from './Imports.ts'
const dataIsEmpty = !localStorage.tws_data || localStorage.tws_data == ''
const aes = {
enc: (m: string, k: string) => CryptoES.AES.encrypt(m, k).toString(CryptoES.Utf8),
dec: (m: string, k: string) => CryptoES.AES.decrypt(m, k).toString(CryptoES.Utf8),
}
const key = location.host + '_TWS_姐姐'
if (dataIsEmpty) localStorage.tws_data = aes.enc('{}', key)
let _dec = aes.dec(localStorage.tws_data, key)
if (_dec == '') _dec = '{}'
const _data_cached = JSON.parse(_dec)
// 類型定義
declare global {
interface Window {
data: {
apply: () => void
access_token?: string
}
}
}
// deno-lint-ignore no-window
(window.data == null) && (window.data = new Proxy({
apply() {
localStorage.tws_data = aes.enc(JSON.stringify(_data_cached), key)
}
}, {
get(_obj, k) {
return _data_cached[k]
},
set(_obj, k, v) {
_data_cached[k] = v
return true
},
}))
// deno-lint-ignore no-window
export default window.data

View File

@@ -1,30 +0,0 @@
// @ts-types="./typedef/react@18.3.18.d.ts"
import * as React from './static/react-esm-18.3.1.static.js'
import * as ReactDOM from './static/react-dom-esm-18.3.1.static.js'
import * as CryptoES from './static/crypto-es-3.1.0.static.mjs'
import type { Dialog } from 'https://unpkg.com/mdui@2.1.4/components/dialog/index.d.ts'
import type { TextField } from 'https://unpkg.com/mdui@2.1.4/components/text-field/index.d.ts'
import type { Button } from 'https://unpkg.com/mdui@2.1.4/components/button/index.d.ts'
import type { NavigationRail } from 'https://unpkg.com/mdui@2.1.4/components/navigation-rail/navigation-rail.d.ts'
declare global {
namespace React {
namespace JSX {
interface IntrinsicAttributes {
id?: string
}
}
}
}
export {
React,
ReactDOM,
CryptoES,
Dialog as MduiDialog,
TextField as MduiTextField,
Button as MduiButton,
NavigationRail as MduiNavigationRail,
}

View File

@@ -1,7 +0,0 @@
export type CallMethod =
"User.auth" |
"User.register" |
"User.login"
export type ClientEvent =
"Client.onMessage"

View File

@@ -1,41 +0,0 @@
import { io, Socket } from 'https://unpkg.com/socket.io-client@4.8.1/dist/socket.io.esm.min.js'
import { CallMethod, ClientEvent } from './ApiDeclare.ts'
import ApiCallbackMessage from './ApiCallbackMessage.ts'
type UnknownObject = { [key: string]: unknown }
class Client {
static socket?: Socket
static events: { [key: string]: (data: UnknownObject) => UnknownObject } = {}
static connect() {
this.socket?.disconnect()
this.socket && delete this.socket
this.socket = io()
this.socket!.on("The_White_Silk", (name: string, data: UnknownObject, callback: (ret: UnknownObject) => void) => {
try {
if (name == null || data == null) return
const re = this.events[name]?.(data)
re && callback(re)
} catch (e) {
console.error(e)
}
})
}
static invoke(method: CallMethod, args: UnknownObject = {}, timeout: number = 5000): Promise<ApiCallbackMessage> {
if (this.socket == null) throw new Error("客戶端未與伺服器端建立連接!")
return new Promise((resolve, reject) => {
this.socket!.timeout(timeout).emit("The_White_Silk", method, args, (err: string, res: ApiCallbackMessage) => {
if (err) return reject(err)
resolve(res)
})
})
}
static on(eventName: ClientEvent, func: (data: UnknownObject) => UnknownObject) {
this.events[eventName] = func
}
static off(eventName: ClientEvent){
delete this.events[eventName]
}
}
export default Client

View File

@@ -1,6 +0,0 @@
export default class RecentChat {
declare id: string
declare title: string
declare avatar: string | null
declare content: string
}

View File

@@ -1,7 +0,0 @@
export default class User {
declare id: string
declare count: number
declare username: string | null
declare nickname: string
declare avatar: string | null
}

71
client/data.ts Normal file
View File

@@ -0,0 +1,71 @@
import crypto from 'node:crypto'
const dataIsEmpty = !localStorage.tws_data || localStorage.tws_data == ''
class Aes {
static randomIv() {
return crypto.randomBytes(12)
}
static normalizeKey(key: string, keyLength = 32) {
const hash = crypto.createHash('sha256')
hash.update(key)
const keyBuffer = hash.digest()
return keyLength ? keyBuffer.subarray(0, keyLength) : keyBuffer
}
static encrypt(data: string, key: string) {
const iv = this.randomIv()
return Buffer.concat([iv, crypto.createCipheriv("aes-256-gcm", this.normalizeKey(key), iv).update(data)]).toString('hex')
}
static decrypt(data: string, key: string) {
const buffer = Buffer.from(data, 'hex')
const iv = buffer.subarray(0, 12)
return crypto.createDecipheriv("aes-256-gcm", this.normalizeKey(key), iv).update(buffer.subarray(12)).toString()
}
}
// 尽可能防止被窃取, 虽然理论上还是会被窃取
const key = crypto.createHash('sha256').update(location.host + '_TWS_姐姐_' + navigator.userAgent).digest().toString('base64')
if (dataIsEmpty) localStorage.tws_data = Aes.encrypt('{}', key)
let _data_cached
try {
_data_cached = JSON.parse(Aes.decrypt(localStorage.tws_data, key))
} catch (e) {
console.warn("数据解密失败, 使用空数据...", e)
_data_cached = {}
}
type IData = {
refresh_token?: string
split_sizes: number[]
apply(): void
access_token?: string
device_id: string
}
declare global {
interface Window {
data?: IData
}
}
const data = new Proxy({} as IData, {
get(_obj, k) {
if (k == '_cached') return _data_cached
if (k == 'apply') return () => localStorage.tws_data = Aes.encrypt(JSON.stringify(_data_cached), key)
return _data_cached[k]
},
set(_obj, k, v) {
if (k == '_cached') return false
_data_cached[k] = v
return true
}
})
if (new URL(location.href).searchParams.get('export_data') == 'true') {
window.data = data
console.warn("警告: 将 data 暴露到 window 有可能会导致令牌泄露!")
}
export default data

8
client/env.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
/// <reference types="mdui/jsx.zh-cn.d.ts" />
/// <reference types="vite/client" />
declare const __APP_VERSION__: string
declare const __GIT_HASH__: string
declare const __GIT_HASH_FULL__: string
declare const __GIT_BRANCH__: string
declare const __BUILD_TIME__: string

19
client/getClient.ts Normal file
View File

@@ -0,0 +1,19 @@
import { LingChairClient } from 'lingchair-client-protocol'
import data from "./data.ts"
import { UAParser } from 'ua-parser-js'
import { randomUUID } from 'lingchair-internal-shared'
import performAuth from './performAuth.ts'
if (!data.device_id) {
const ua = new UAParser(navigator.userAgent)
data.device_id = `LingChair_Web_${ua.getOS() || 'unknown-os'}-${ua.getDevice().type || 'unknown_device'}-${randomUUID()}`
}
const client = new LingChairClient({
server_url: '',
device_id: data.device_id,
auto_fresh_token: true,
})
export default function getClient() {
return client
}

BIN
client/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -7,124 +7,18 @@
<meta name="renderer" content="webkit" /> <meta name="renderer" content="webkit" />
<!-- UI --> <!-- UI -->
<script src="./static/mdui-2.1.4.global.min.js">
</script>
<link rel="icon" href="icon.ico" /> <link rel="icon" href="icon.ico" />
<link rel="stylesheet" href="./static/mdui-2.1.4.css" />
<link rel="stylesheet" href="./static/material_icons.css" /> <link rel="stylesheet" href="./static/material_icons.css" />
<script src="./static/jquery-3.7.1.min.js"></script> <title>LingChair</title>
<script src="./static/splitjs-1.6.5.min.js"></script>
<title>TheWhiteSilk</title> <link rel="stylesheet" href="./style.css" />
<style>
/* 滑条*/
.no-scroll-bar::-webkit-scrollbar {
width: 0px !important;
}
/* https://blog.csdn.net/qq_39347364/article/details/111996581*/
*::-webkit-scrollbar {
width: 7px;
height: 10px;
}
*::-webkit-scrollbar-track {
width: 6px;
background: rgba(#101F1C, 0.1);
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
}
*::-webkit-scrollbar-thumb {
background-color: rgba(144, 147, 153, .5);
background-clip: padding-box;
min-height: 28px;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
transition: background-color .3s;
cursor: pointer;
}
*::-webkit-scrollbar-thumb:hover {
background-color: rgba(144, 147, 153, .3);
}
/* 使用系统字体 在部分系统表现很好*/
/* 我们至今仍未能知道桌面端浏览器字体的秘密*/
*:not(.material-icons, .mdui-icon, mdui-icon, .fa, .google-symbols) {
font-family: -apple-system, system-ui, -webkit-system-font !important;
}
body {
display: flex;
margin: 0 0 0 0;
height: 100%;
}
html {
margin: 0 0 0 0;
height: 100%;
}
/* 防止小尺寸图片模糊*/
* {
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
-ms-interpolation-mode: nearest-neighbor;
}
.gutter {
background-color: rgb(var(--mdui-color-surface-variant));;
background-repeat: no-repeat;
background-position: 50%;
}
.gutter.gutter-horizontal {
cursor: col-resize;
}
</style>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<mdui-snackbar close-on-outside-click id="public_snackbar"></mdui-snackbar>
<mdui-dialog close-on-overlay-click id="ErrorDialog"> <script type="module" src="./init.ts"></script>
<span slot="headline">错误</span>
<span slot="description" id="ErrorDialog_Message"></span>
</mdui-dialog>
<script nomodule>
alert('很抱歉, 此应用无法在较旧的浏览器运行, 请使用基于 Chromium 89+ 的浏览器(内核)使用 :(')
</script>
<script>
new URL(location.href).searchParams.get('debug') == 'true' && window.addEventListener('error', ({ message, filename, lineno, colno, error }) => {
const m = $("#ErrorDialog_Message")
const d = $("#ErrorDialog").get(0)
const s = d.open
d.open = true
m.html((s ? `${m.html()}<br/><br/>` : '') + `${message} (${filename || 'unknown'}:${lineno}:${colno})`)
})
</script>
<script type="module">
import App from './ui/App.tsx'
import { React, ReactDOM } from './Imports.ts'
ReactDOM.createRoot(document.getElementById('app')).render(React.createElement(App, null))
let onResize = () => {
document.body.style.setProperty('--whitesilk-widget-message-maxwidth', mdui.breakpoint().down('md') ? "80%" : "70%")
document.body.style.setProperty('--whitesilk-window-width', window.innerWidth + 'px')
document.body.style.setProperty('--whitesilk-window-height', window.innerHeight + 'px')
}
window.addEventListener('resize', onResize)
onResize()
</script>
</body> </body>
</html> </html>

41
client/init.ts Normal file
View File

@@ -0,0 +1,41 @@
import 'mdui/mdui.css'
import 'mdui'
import { breakpoint } from "mdui"
import './env.d.ts'
import * as React from 'react'
import ReactDOM from 'react-dom/client'
import './ui/chat-elements/chat-image.ts'
import './ui/chat-elements/chat-video.ts'
import './ui/chat-elements/chat-file.ts'
import './ui/chat-elements/chat-text.ts'
import './ui/chat-elements/chat-mention.ts'
import './ui/chat-elements/chat-text-container.ts'
import './ui/chat-elements/chat-quote.ts'
import Main from "./ui/Main.tsx"
import performAuth from './performAuth.ts'
try {
await performAuth({})
} catch (e) {
console.log("验证失败", e)
}
ReactDOM.createRoot(document.getElementById('app') as HTMLElement).render(React.createElement(Main))
const onResize = () => {
document.body.style.setProperty('--whitesilk-widget-message-maxwidth', breakpoint().down('md') ? "80%" : "70%")
// deno-lint-ignore no-window
document.body.style.setProperty('--whitesilk-window-width', window.innerWidth + 'px')
// deno-lint-ignore no-window
document.body.style.setProperty('--whitesilk-window-height', window.innerHeight + 'px')
}
// deno-lint-ignore no-window no-window-prefix
window.addEventListener('resize', onResize)
onResize()
const config = await fetch('config.json').then((re) => re.json())
config.title && (document.title = config.title)

32
client/package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "lingchair-client",
"type": "module",
"version": "0.1.0-alpha",
"scripts": {
"build": "npx vite build",
"build-watch": "npx vite --watch build"
},
"dependencies": {
"dompurify": "3.2.7",
"lingchair-internal-shared": "*",
"marked": "16.3.0",
"mdui": "2.1.4",
"pinch-zoom-element": "1.1.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router": "7.10.1",
"socket.io-client": "4.8.1",
"split.js": "1.3.2",
"ua-parser-js": "2.0.6",
"use-context-selector": "2.0.0"
},
"devDependencies": {
"@rollup/wasm-node": "4.48.0",
"@types/react": "18.3.1",
"@types/react-dom": "18.3.1",
"@vitejs/plugin-react": "4.7.0",
"chalk": "5.4.1",
"vite": "7.2.6",
"vite-plugin-node-polyfills": "^0.24.0"
}
}

31
client/performAuth.ts Normal file
View File

@@ -0,0 +1,31 @@
import data from "./data.ts"
import getClient from "./getClient.ts"
/**
* 进行身份验证以接受客户端事件
*
* 使用验证方式优先级: 访问 > 刷新 > 账号密码
*
* 若传递了账号密码, 则同时缓存新的访问令牌和刷新令牌
*
* 如只传递两个令牌的其一, 按照优先级并在成功验证后赋值
*
* 多个验证方式不会逐一尝试
*/
export default async function performAuth(args: {
refresh_token?: string
account?: string
password?: string
}) {
if (args.account && args.password)
await getClient().authOrThrow({
account: args.account,
password: args.password,
})
else {
await getClient().authOrThrow({ refresh_token: args.refresh_token ? args.refresh_token : data.refresh_token, ignore_all_empty: true })
}
data.refresh_token = getClient().getCachedRefreshToken()
data.access_token = getClient().getCachedAccessToken()
data.apply()
}

View File

@@ -1,35 +0,0 @@
import { Base, BufferedBlockAlgorithm, HMAC, Hasher, Hex, Latin1, Utf8, WordArray } from "./crypto-es@3.1.0/core.mjs";
import { X64Word, X64WordArray } from "./crypto-es@3.1.0/x64-core.mjs";
import { Base64 } from "./crypto-es@3.1.0/enc-base64.mjs";
import { HmacMD5, MD5, MD5Algo } from "./crypto-es@3.1.0/md5.mjs";
import { EvpKDF, EvpKDFAlgo } from "./crypto-es@3.1.0/evpkdf.mjs";
import { BlockCipher, BlockCipherMode, CBC, Cipher, CipherParams, OpenSSLFormatter, OpenSSLKdf, PasswordBasedCipher, Pkcs7, SerializableCipher, StreamCipher } from "./crypto-es@3.1.0/cipher-core.mjs";
import { Utf16, Utf16BE, Utf16LE } from "./crypto-es@3.1.0/enc-utf16.mjs";
import { Base64url } from "./crypto-es@3.1.0/enc-base64url.mjs";
import { HmacSHA1, SHA1, SHA1Algo } from "./crypto-es@3.1.0/sha1.mjs";
import { HmacSHA256, SHA256, SHA256Algo } from "./crypto-es@3.1.0/sha256.mjs";
import { HmacSHA224, SHA224, SHA224Algo } from "./crypto-es@3.1.0/sha224.mjs";
import { HmacSHA512, SHA512, SHA512Algo } from "./crypto-es@3.1.0/sha512.mjs";
import { HmacSHA384, SHA384, SHA384Algo } from "./crypto-es@3.1.0/sha384.mjs";
import { HmacSHA3, SHA3, SHA3Algo } from "./crypto-es@3.1.0/sha3.mjs";
import { HmacRIPEMD160, RIPEMD160, RIPEMD160Algo } from "./crypto-es@3.1.0/ripemd160.mjs";
import { PBKDF2, PBKDF2Algo } from "./crypto-es@3.1.0/pbkdf2.mjs";
import { AES, AESAlgo } from "./crypto-es@3.1.0/aes.mjs";
import { DES, DESAlgo, TripleDES, TripleDESAlgo } from "./crypto-es@3.1.0/tripledes.mjs";
import { Rabbit, RabbitAlgo } from "./crypto-es@3.1.0/rabbit.mjs";
import { RabbitLegacy, RabbitLegacyAlgo } from "./crypto-es@3.1.0/rabbit-legacy.mjs";
import { RC4, RC4Algo, RC4Drop, RC4DropAlgo } from "./crypto-es@3.1.0/rc4.mjs";
import { Blowfish, BlowfishAlgo } from "./crypto-es@3.1.0/blowfish.mjs";
import { CFB } from "./crypto-es@3.1.0/mode-cfb.mjs";
import { CTR } from "./crypto-es@3.1.0/mode-ctr.mjs";
import { CTRGladman } from "./crypto-es@3.1.0/mode-ctr-gladman.mjs";
import { ECB } from "./crypto-es@3.1.0/mode-ecb.mjs";
import { OFB } from "./crypto-es@3.1.0/mode-ofb.mjs";
import { AnsiX923 } from "./crypto-es@3.1.0/pad-ansix923.mjs";
import { Iso10126 } from "./crypto-es@3.1.0/pad-iso10126.mjs";
import { ZeroPadding } from "./crypto-es@3.1.0/pad-zeropadding.mjs";
import { Iso97971 } from "./crypto-es@3.1.0/pad-iso97971.mjs";
import { NoPadding } from "./crypto-es@3.1.0/pad-nopadding.mjs";
import { HexFormatter } from "./crypto-es@3.1.0/format-hex.mjs";
export { AES, AESAlgo, AnsiX923, Base, Base64, Base64url, BlockCipher, BlockCipherMode, Blowfish, BlowfishAlgo, BufferedBlockAlgorithm, CBC, CFB, CTR, CTRGladman, Cipher, CipherParams, DES, DESAlgo, ECB, EvpKDF, EvpKDFAlgo, HMAC, Hasher, Hex, HexFormatter, HmacMD5, HmacRIPEMD160, HmacSHA1, HmacSHA224, HmacSHA256, HmacSHA3, HmacSHA384, HmacSHA512, Iso10126, Iso97971, Latin1, MD5, MD5Algo, NoPadding, OFB, OpenSSLFormatter, OpenSSLKdf, PBKDF2, PBKDF2Algo, PasswordBasedCipher, Pkcs7, RC4, RC4Algo, RC4Drop, RC4DropAlgo, RIPEMD160, RIPEMD160Algo, Rabbit, RabbitAlgo, RabbitLegacy, RabbitLegacyAlgo, SHA1, SHA1Algo, SHA224, SHA224Algo, SHA256, SHA256Algo, SHA3, SHA384, SHA384Algo, SHA3Algo, SHA512, SHA512Algo, SerializableCipher, StreamCipher, TripleDES, TripleDESAlgo, Utf16, Utf16BE, Utf16LE, Utf8, WordArray, X64Word, X64WordArray, ZeroPadding };

View File

@@ -1,134 +0,0 @@
import { BlockCipher } from "./cipher-core.mjs";
//#region src/aes.ts
const _SBOX = [];
const INV_SBOX = [];
const _SUB_MIX_0 = [];
const _SUB_MIX_1 = [];
const _SUB_MIX_2 = [];
const _SUB_MIX_3 = [];
const INV_SUB_MIX_0 = [];
const INV_SUB_MIX_1 = [];
const INV_SUB_MIX_2 = [];
const INV_SUB_MIX_3 = [];
const RCON = [
0,
1,
2,
4,
8,
16,
32,
64,
128,
27,
54
];
/**
* AES block cipher algorithm.
*/
var AESAlgo = class extends BlockCipher {
/** Number of rounds for this key size */
_nRounds;
/** Previous key for optimization */
_keyPriorReset;
/** Key schedule for encryption */
_keySchedule;
/** Inverse key schedule for decryption */
_invKeySchedule;
/** Key size in 32-bit words */
static keySize = 256 / 32;
_doReset() {
let t;
if (this._nRounds && this._keyPriorReset === this._key) return;
this._keyPriorReset = this._key;
const key = this._keyPriorReset;
const keyWords = key.words;
const keySize = key.sigBytes / 4;
this._nRounds = keySize + 6;
const nRounds = this._nRounds;
const ksRows = (nRounds + 1) * 4;
this._keySchedule = [];
const keySchedule = this._keySchedule;
for (let ksRow = 0; ksRow < ksRows; ksRow += 1) if (ksRow < keySize) keySchedule[ksRow] = keyWords[ksRow];
else {
t = keySchedule[ksRow - 1];
if (!(ksRow % keySize)) {
t = t << 8 | t >>> 24;
t = _SBOX[t >>> 24] << 24 | _SBOX[t >>> 16 & 255] << 16 | _SBOX[t >>> 8 & 255] << 8 | _SBOX[t & 255];
t ^= RCON[ksRow / keySize | 0] << 24;
} else if (keySize > 6 && ksRow % keySize === 4) t = _SBOX[t >>> 24] << 24 | _SBOX[t >>> 16 & 255] << 16 | _SBOX[t >>> 8 & 255] << 8 | _SBOX[t & 255];
keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t;
}
this._invKeySchedule = [];
const invKeySchedule = this._invKeySchedule;
for (let invKsRow = 0; invKsRow < ksRows; invKsRow += 1) {
const ksRow = ksRows - invKsRow;
if (invKsRow % 4) t = keySchedule[ksRow];
else t = keySchedule[ksRow - 4];
if (invKsRow < 4 || ksRow <= 4) invKeySchedule[invKsRow] = t;
else invKeySchedule[invKsRow] = INV_SUB_MIX_0[_SBOX[t >>> 24]] ^ INV_SUB_MIX_1[_SBOX[t >>> 16 & 255]] ^ INV_SUB_MIX_2[_SBOX[t >>> 8 & 255]] ^ INV_SUB_MIX_3[_SBOX[t & 255]];
}
}
encryptBlock(M, offset) {
this._doCryptBlock(M, offset, this._keySchedule, _SUB_MIX_0, _SUB_MIX_1, _SUB_MIX_2, _SUB_MIX_3, _SBOX);
}
decryptBlock(M, offset) {
const _M = M;
let t = _M[offset + 1];
_M[offset + 1] = _M[offset + 3];
_M[offset + 3] = t;
this._doCryptBlock(_M, offset, this._invKeySchedule, INV_SUB_MIX_0, INV_SUB_MIX_1, INV_SUB_MIX_2, INV_SUB_MIX_3, INV_SBOX);
t = _M[offset + 1];
_M[offset + 1] = _M[offset + 3];
_M[offset + 3] = t;
}
_doCryptBlock(M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) {
const _M = M;
const nRounds = this._nRounds;
let s0 = _M[offset] ^ keySchedule[0];
let s1 = _M[offset + 1] ^ keySchedule[1];
let s2 = _M[offset + 2] ^ keySchedule[2];
let s3 = _M[offset + 3] ^ keySchedule[3];
let ksRow = 4;
for (let round = 1; round < nRounds; round += 1) {
const t0$1 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[s1 >>> 16 & 255] ^ SUB_MIX_2[s2 >>> 8 & 255] ^ SUB_MIX_3[s3 & 255] ^ keySchedule[ksRow];
ksRow += 1;
const t1$1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[s2 >>> 16 & 255] ^ SUB_MIX_2[s3 >>> 8 & 255] ^ SUB_MIX_3[s0 & 255] ^ keySchedule[ksRow];
ksRow += 1;
const t2$1 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[s3 >>> 16 & 255] ^ SUB_MIX_2[s0 >>> 8 & 255] ^ SUB_MIX_3[s1 & 255] ^ keySchedule[ksRow];
ksRow += 1;
const t3$1 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[s0 >>> 16 & 255] ^ SUB_MIX_2[s1 >>> 8 & 255] ^ SUB_MIX_3[s2 & 255] ^ keySchedule[ksRow];
ksRow += 1;
s0 = t0$1;
s1 = t1$1;
s2 = t2$1;
s3 = t3$1;
}
const t0 = (SBOX[s0 >>> 24] << 24 | SBOX[s1 >>> 16 & 255] << 16 | SBOX[s2 >>> 8 & 255] << 8 | SBOX[s3 & 255]) ^ keySchedule[ksRow];
ksRow += 1;
const t1 = (SBOX[s1 >>> 24] << 24 | SBOX[s2 >>> 16 & 255] << 16 | SBOX[s3 >>> 8 & 255] << 8 | SBOX[s0 & 255]) ^ keySchedule[ksRow];
ksRow += 1;
const t2 = (SBOX[s2 >>> 24] << 24 | SBOX[s3 >>> 16 & 255] << 16 | SBOX[s0 >>> 8 & 255] << 8 | SBOX[s1 & 255]) ^ keySchedule[ksRow];
ksRow += 1;
const t3 = (SBOX[s3 >>> 24] << 24 | SBOX[s0 >>> 16 & 255] << 16 | SBOX[s1 >>> 8 & 255] << 8 | SBOX[s2 & 255]) ^ keySchedule[ksRow];
ksRow += 1;
_M[offset] = t0;
_M[offset + 1] = t1;
_M[offset + 2] = t2;
_M[offset + 3] = t3;
}
};
/**
* Shortcut functions to the cipher's object interface.
*
* @example
*
* var ciphertext = AES.encrypt(message, key, cfg);
* var plaintext = AES.decrypt(ciphertext, key, cfg);
*/
const AES = BlockCipher._createHelper(AESAlgo);
//#endregion
export { AES, AESAlgo };
//# sourceMappingURL=aes.mjs.map

File diff suppressed because it is too large Load Diff

View File

@@ -1,636 +0,0 @@
import { Base, BufferedBlockAlgorithm, Hex, WordArray } from "./core.mjs";
import { Base64 } from "./enc-base64.mjs";
import { EvpKDFAlgo } from "./evpkdf.mjs";
//#region src/cipher-core.ts
/**
* Abstract base cipher template.
* Provides the foundation for all encryption and decryption algorithms.
*
* @property keySize - This cipher's key size in words (default: 4 = 128 bits)
* @property ivSize - This cipher's IV size in words (default: 4 = 128 bits)
* @property _ENC_XFORM_MODE - A constant representing encryption mode
* @property _DEC_XFORM_MODE - A constant representing decryption mode
*/
var Cipher = class Cipher extends BufferedBlockAlgorithm {
/** Encryption mode constant */
static _ENC_XFORM_MODE = 1;
/** Decryption mode constant */
static _DEC_XFORM_MODE = 2;
/** Default key size in words (128 bits) */
static keySize = 128 / 32;
/** Default IV size in words (128 bits) */
static ivSize = 128 / 32;
/** Configuration options */
cfg;
/** Transform mode (encryption or decryption) */
_xformMode;
/** The key */
_key;
/** Block size in words */
blockSize = 128 / 32;
/**
* Initializes a newly created cipher.
*
* @param xformMode - Either the encryption or decryption transformation mode constant
* @param key - The key
* @param cfg - Configuration options to use for this operation
* @example
* ```javascript
* const cipher = new AESAlgo(
* Cipher._ENC_XFORM_MODE, keyWordArray, { iv: ivWordArray }
* );
* ```
*/
constructor(xformMode, key, cfg) {
super();
this.cfg = Object.assign({}, cfg);
this._xformMode = xformMode;
this._key = key;
}
/**
* Creates this cipher in encryption mode.
*
* @param key - The key
* @param cfg - Configuration options to use for this operation
* @returns A cipher instance
* @static
* @example
* ```javascript
* const cipher = AESAlgo.createEncryptor(keyWordArray, { iv: ivWordArray });
* ```
*/
static createEncryptor(key, cfg) {
return this.create(Cipher._ENC_XFORM_MODE, key, cfg);
}
/**
* Creates this cipher in decryption mode.
*
* @param key - The key
* @param cfg - Configuration options to use for this operation
* @returns A cipher instance
* @static
* @example
* ```javascript
* const cipher = AESAlgo.createDecryptor(keyWordArray, { iv: ivWordArray });
* ```
*/
static createDecryptor(key, cfg) {
return this.create(Cipher._DEC_XFORM_MODE, key, cfg);
}
static create(...args) {
if (args.length >= 2 && typeof args[0] === "number") {
const [xformMode, key, cfg] = args;
const instance = new this(xformMode, key, cfg);
instance.reset();
return instance;
} else return new this(...args);
}
/**
* Creates shortcut functions to a cipher's object interface.
*
* @param SubCipher - The cipher to create a helper for
* @returns An object with encrypt and decrypt shortcut functions
* @static
* @example
* ```javascript
* const AES = Cipher._createHelper(AESAlgo);
* ```
*/
static _createHelper(SubCipher) {
const selectCipherStrategy = (key) => {
if (typeof key === "string") return PasswordBasedCipher;
return SerializableCipher;
};
return {
encrypt(message, key, cfg) {
return selectCipherStrategy(key).encrypt(SubCipher, message, key, cfg);
},
decrypt(ciphertext, key, cfg) {
return selectCipherStrategy(key).decrypt(SubCipher, ciphertext, key, cfg);
}
};
}
/**
* Resets this cipher to its initial state.
*
* @example
* ```javascript
* cipher.reset();
* ```
*/
reset() {
super.reset();
this._doReset();
}
/**
* Adds data to be encrypted or decrypted.
*
* @param dataUpdate - The data to encrypt or decrypt
* @returns The data after processing
* @example
* ```javascript
* const encrypted = cipher.process('data');
* const encrypted = cipher.process(wordArray);
* ```
*/
process(dataUpdate) {
this._append(dataUpdate);
return this._process();
}
/**
* Finalizes the encryption or decryption process.
* Note that the finalize operation is effectively a destructive, read-once operation.
*
* @param dataUpdate - The final data to encrypt or decrypt
* @returns The data after final processing
* @example
* ```javascript
* const encrypted = cipher.finalize();
* const encrypted = cipher.finalize('data');
* const encrypted = cipher.finalize(wordArray);
* ```
*/
finalize(dataUpdate) {
if (dataUpdate) this._append(dataUpdate);
const finalProcessedData = this._doFinalize();
return finalProcessedData;
}
};
/**
* Abstract base stream cipher template.
* Stream ciphers process data one unit at a time rather than in blocks.
*
* @property blockSize - The number of 32-bit words this cipher operates on (default: 1 = 32 bits)
*/
var StreamCipher = class extends Cipher {
blockSize = 1;
constructor(xformMode, key, cfg) {
super(xformMode, key, cfg);
this.blockSize = 1;
}
_doFinalize() {
const finalProcessedBlocks = this._process(true);
return finalProcessedBlocks;
}
};
/**
* Abstract base block cipher mode template.
* Defines how multiple blocks are processed together.
*/
var BlockCipherMode = class extends Base {
/** The cipher instance */
_cipher;
/** The initialization vector */
_iv;
/** The previous block (for chaining modes) */
_prevBlock;
/**
* Initializes a newly created mode.
*
* @param cipher - A block cipher instance
* @param iv - The IV words
* @example
* ```javascript
* const mode = new CBCMode(cipher, iv.words);
* ```
*/
constructor(cipher, iv) {
super();
this._cipher = cipher;
this._iv = iv;
}
/**
* Creates this mode for encryption.
*
* @param cipher - A block cipher instance
* @param iv - The IV words
* @returns The mode instance
* @static
* @example
* ```javascript
* const mode = CBC.createEncryptor(cipher, iv.words);
* ```
*/
static createEncryptor(cipher, iv) {
return this.Encryptor.create(cipher, iv);
}
/**
* Creates this mode for decryption.
*
* @param cipher - A block cipher instance
* @param iv - The IV words
* @returns The mode instance
* @static
* @example
* ```javascript
* const mode = CBC.createDecryptor(cipher, iv.words);
* ```
*/
static createDecryptor(cipher, iv) {
return this.Decryptor.create(cipher, iv);
}
/**
* Process a block of data
* Must be implemented by concrete modes
*/
processBlock(_words, _offset) {}
};
/**
* XOR blocks for cipher block chaining
* @private
*/
function xorBlock(words, offset, blockSize) {
const _words = words;
let block;
const iv = this._iv;
if (iv) {
block = iv;
this._iv = void 0;
} else block = this._prevBlock;
if (block) for (let i = 0; i < blockSize; i += 1) _words[offset + i] ^= block[i];
}
/**
* CBC Encryptor
*/
var CBCEncryptor = class extends BlockCipherMode {
/**
* Processes the data block at offset.
*
* @param words - The data words to operate on
* @param offset - The offset where the block starts
* @example
* ```javascript
* mode.processBlock(data.words, offset);
* ```
*/
processBlock(words, offset) {
const cipher = this._cipher;
const blockSize = cipher.blockSize;
xorBlock.call(this, words, offset, blockSize);
cipher.encryptBlock(words, offset);
this._prevBlock = words.slice(offset, offset + blockSize);
}
};
/**
* CBC Decryptor
*/
var CBCDecryptor = class extends BlockCipherMode {
/**
* Processes the data block at offset.
*
* @param words - The data words to operate on
* @param offset - The offset where the block starts
* @example
* ```javascript
* mode.processBlock(data.words, offset);
* ```
*/
processBlock(words, offset) {
const cipher = this._cipher;
const blockSize = cipher.blockSize;
const thisBlock = words.slice(offset, offset + blockSize);
cipher.decryptBlock(words, offset);
xorBlock.call(this, words, offset, blockSize);
this._prevBlock = thisBlock;
}
};
/**
* Cipher Block Chaining mode.
* Each block is XORed with the previous ciphertext block before encryption.
*/
var CBC = class extends BlockCipherMode {
/** CBC Encryptor */
static Encryptor = CBCEncryptor;
/** CBC Decryptor */
static Decryptor = CBCDecryptor;
};
/**
* PKCS #5/7 padding strategy.
* Pads data with bytes all of the same value as the count of padding bytes.
*/
const Pkcs7 = {
pad(data, blockSize) {
const blockSizeBytes = blockSize * 4;
const nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes;
const paddingWord = nPaddingBytes << 24 | nPaddingBytes << 16 | nPaddingBytes << 8 | nPaddingBytes;
const paddingWords = [];
for (let i = 0; i < nPaddingBytes; i += 4) paddingWords.push(paddingWord);
const padding = WordArray.create(paddingWords, nPaddingBytes);
data.concat(padding);
},
unpad(data) {
const nPaddingBytes = data.words[data.sigBytes - 1 >>> 2] & 255;
data.sigBytes -= nPaddingBytes;
}
};
/**
* Abstract base block cipher template.
* Block ciphers process data in fixed-size blocks.
*
* @property blockSize - The number of 32-bit words this cipher operates on (default: 4 = 128 bits)
*/
var BlockCipher = class extends Cipher {
/** Block mode instance */
_mode;
/**
* Initializes a newly created block cipher.
*
* @param xformMode - Transform mode
* @param key - The key
* @param cfg - Configuration options
*/
constructor(xformMode, key, cfg) {
super(xformMode, key, Object.assign({
mode: CBC,
padding: Pkcs7
}, cfg));
this.blockSize = 128 / 32;
}
reset() {
super.reset();
const { cfg } = this;
const { iv, mode } = cfg;
let modeCreator;
if (this._xformMode === this.constructor._ENC_XFORM_MODE) modeCreator = mode?.createEncryptor;
else {
modeCreator = mode?.createDecryptor;
this._minBufferSize = 1;
}
if (modeCreator && mode) {
this._mode = modeCreator.call(mode, this, iv?.words);
this._mode.__creator = modeCreator;
}
}
_doProcessBlock(words, offset) {
this._mode?.processBlock(words, offset);
}
_doFinalize() {
let finalProcessedBlocks;
const { padding } = this.cfg;
if (this._xformMode === this.constructor._ENC_XFORM_MODE) {
if (padding) padding.pad(this._data, this.blockSize);
finalProcessedBlocks = this._process(true);
} else {
finalProcessedBlocks = this._process(true);
if (padding) padding.unpad(finalProcessedBlocks);
}
return finalProcessedBlocks;
}
};
/**
* A collection of cipher parameters.
* Encapsulates all the parameters used in a cipher operation.
*
* @property ciphertext - The raw ciphertext
* @property key - The key to this ciphertext
* @property iv - The IV used in the ciphering operation
* @property salt - The salt used with a key derivation function
* @property algorithm - The cipher algorithm
* @property mode - The block mode used in the ciphering operation
* @property padding - The padding scheme used in the ciphering operation
* @property blockSize - The block size of the cipher
* @property formatter - The default formatting strategy
*/
var CipherParams = class CipherParams extends Base {
ciphertext;
key;
iv;
salt;
algorithm;
mode;
padding;
blockSize;
formatter;
/**
* Initializes a newly created cipher params object.
*
* @param cipherParams - An object with any of the possible cipher parameters
* @example
* ```javascript
* const cipherParams = new CipherParams({
* ciphertext: ciphertextWordArray,
* key: keyWordArray,
* iv: ivWordArray,
* salt: saltWordArray,
* algorithm: AESAlgo,
* mode: CBC,
* padding: Pkcs7,
* blockSize: 4,
* formatter: OpenSSLFormatter
* });
* ```
*/
constructor(cipherParams) {
super();
if (cipherParams) this.mixIn(cipherParams);
if (!this.formatter) this.formatter = OpenSSLFormatter;
}
static create(...args) {
const [cipherParams] = args;
return new CipherParams(cipherParams);
}
/**
* Converts this cipher params object to a string.
*
* @param formatter - The formatting strategy to use
* @returns The stringified cipher params
* @throws Error if neither the formatter nor the default formatter is set
* @example
* ```javascript
* const string = cipherParams.toString();
* const string = cipherParams.toString(OpenSSLFormatter);
* ```
*/
toString(formatter) {
const fmt = formatter || this.formatter;
if (!fmt) throw new Error("cipher params formatter required");
return fmt.stringify(this);
}
};
/**
* OpenSSL formatting strategy.
* Formats cipher params in OpenSSL-compatible format.
*/
const OpenSSLFormatter = {
stringify(cipherParams) {
let wordArray;
const { ciphertext, salt } = cipherParams;
if (salt && ciphertext) wordArray = WordArray.create([1398893684, 1701076831]).concat(salt).concat(ciphertext);
else if (ciphertext) wordArray = ciphertext;
else wordArray = new WordArray();
return wordArray.toString(Base64);
},
parse(openSSLStr) {
let salt;
const ciphertext = Base64.parse(openSSLStr);
const ciphertextWords = ciphertext.words;
if (ciphertextWords[0] === 1398893684 && ciphertextWords[1] === 1701076831) {
salt = WordArray.create(ciphertextWords.slice(2, 4));
ciphertextWords.splice(0, 4);
ciphertext.sigBytes -= 16;
}
return CipherParams.create({
ciphertext,
salt
});
}
};
/**
* A cipher wrapper that returns ciphertext as a serializable cipher params object.
* Handles the serialization and deserialization of cipher operations.
*/
var SerializableCipher = class extends Base {
/** Configuration options */
static cfg = { format: OpenSSLFormatter };
/**
* Encrypts a message.
*
* @param cipher - The cipher algorithm to use
* @param message - The message to encrypt
* @param key - The key
* @param cfg - Configuration options to use for this operation
* @returns A cipher params object
* @static
* @example
* ```javascript
* const ciphertextParams = SerializableCipher.encrypt(AESAlgo, message, key);
* const ciphertextParams = SerializableCipher.encrypt(AESAlgo, message, key, { iv: iv });
* ```
*/
static encrypt(cipher, message, key, cfg) {
const _cfg = Object.assign({}, this.cfg, cfg);
const encryptor = cipher.createEncryptor(key, _cfg);
const ciphertext = encryptor.finalize(message);
const cipherCfg = encryptor.cfg;
return CipherParams.create({
ciphertext,
key,
iv: cipherCfg.iv,
algorithm: cipher,
mode: cipherCfg.mode,
padding: cipherCfg.padding,
blockSize: encryptor.blockSize,
formatter: _cfg.format || OpenSSLFormatter
});
}
/**
* Decrypts serialized ciphertext.
*
* @param cipher - The cipher algorithm to use
* @param ciphertext - The ciphertext to decrypt
* @param key - The key
* @param cfg - Configuration options to use for this operation
* @returns The plaintext
* @static
* @example
* ```javascript
* const plaintext = SerializableCipher.decrypt(AESAlgo, formattedCiphertext, key, { iv: iv });
* const plaintext = SerializableCipher.decrypt(AESAlgo, ciphertextParams, key, { iv: iv });
* ```
*/
static decrypt(cipher, ciphertext, key, cfg) {
const _cfg = Object.assign({}, this.cfg, cfg);
const _ciphertext = this._parse(ciphertext, _cfg.format);
const plaintext = cipher.createDecryptor(key, _cfg).finalize(_ciphertext.ciphertext);
return plaintext;
}
/**
* Converts serialized ciphertext to CipherParams.
*
* @param ciphertext - The ciphertext
* @param format - The formatting strategy to use to parse serialized ciphertext
* @returns The unserialized ciphertext
* @static
* @private
*/
static _parse(ciphertext, format) {
if (typeof ciphertext === "string") {
if (!format) throw new Error("Format required to parse string");
return format.parse(ciphertext, this);
}
if (ciphertext instanceof CipherParams) return ciphertext;
return new CipherParams(ciphertext);
}
};
/**
* OpenSSL key derivation function.
* Derives a key and IV from a password using the OpenSSL method.
*/
const OpenSSLKdf = { execute(password, keySize, ivSize, salt, hasher) {
let _salt;
if (!salt) _salt = WordArray.random(64 / 8);
else if (typeof salt === "string") _salt = Hex.parse(salt);
else _salt = salt;
let key;
if (!hasher) key = EvpKDFAlgo.create({ keySize: keySize + ivSize }).compute(password, _salt);
else key = EvpKDFAlgo.create({
keySize: keySize + ivSize,
hasher
}).compute(password, _salt);
const iv = WordArray.create(key.words.slice(keySize), ivSize * 4);
key.sigBytes = keySize * 4;
return CipherParams.create({
key,
iv,
salt: _salt
});
} };
/**
* A serializable cipher wrapper that derives the key from a password.
* Returns ciphertext as a serializable cipher params object.
*/
var PasswordBasedCipher = class extends SerializableCipher {
/** Configuration options */
static cfg = Object.assign({}, SerializableCipher.cfg, { kdf: OpenSSLKdf });
/**
* Encrypts a message using a password.
*
* @param cipher - The cipher algorithm to use
* @param message - The message to encrypt
* @param password - The password
* @param cfg - Configuration options to use for this operation
* @returns A cipher params object
* @static
* @example
* ```javascript
* const ciphertextParams = PasswordBasedCipher.encrypt(AESAlgo, message, 'password');
* ```
*/
static encrypt(cipher, message, password, cfg) {
const _cfg = Object.assign({}, this.cfg, cfg);
if (!_cfg.kdf) throw new Error("KDF required for password-based encryption");
const derivedParams = _cfg.kdf.execute(password, cipher.keySize || cipher.keySize, cipher.ivSize || cipher.ivSize, _cfg.salt, _cfg.hasher);
_cfg.iv = derivedParams.iv;
const ciphertext = SerializableCipher.encrypt.call(this, cipher, message, derivedParams.key, _cfg);
ciphertext.salt = derivedParams.salt;
return ciphertext;
}
/**
* Decrypts serialized ciphertext using a password.
*
* @param cipher - The cipher algorithm to use
* @param ciphertext - The ciphertext to decrypt
* @param password - The password
* @param cfg - Configuration options to use for this operation
* @returns The plaintext
* @static
* @example
* ```javascript
* const plaintext = PasswordBasedCipher.decrypt(AESAlgo, formattedCiphertext, 'password');
* ```
*/
static decrypt(cipher, ciphertext, password, cfg) {
const _cfg = Object.assign({}, this.cfg, cfg);
const _ciphertext = this._parse(ciphertext, _cfg.format);
if (!_cfg.kdf) throw new Error("KDF required for password-based decryption");
const derivedParams = _cfg.kdf.execute(password, cipher.keySize || cipher.keySize, cipher.ivSize || cipher.ivSize, _ciphertext.salt, _cfg.hasher);
_cfg.iv = derivedParams.iv;
const plaintext = SerializableCipher.decrypt.call(this, cipher, _ciphertext, derivedParams.key, _cfg);
return plaintext;
}
};
//#endregion
export { BlockCipher, BlockCipherMode, CBC, Cipher, CipherParams, OpenSSLFormatter, OpenSSLKdf, PasswordBasedCipher, Pkcs7, SerializableCipher, StreamCipher };
//# sourceMappingURL=cipher-core.mjs.map

View File

@@ -1,584 +0,0 @@
//#region src/core.ts
const crypto = (typeof globalThis !== "undefined" ? globalThis : void 0)?.crypto || (typeof global !== "undefined" ? global : void 0)?.crypto || (typeof window !== "undefined" ? window : void 0)?.crypto || (typeof self !== "undefined" ? self : void 0)?.crypto || (typeof frames !== "undefined" ? frames : void 0)?.[0]?.crypto;
/**
* Random word array generator function
*/
let randomWordArray;
if (crypto) randomWordArray = (nBytes) => {
const words = [];
for (let i = 0; i < nBytes; i += 4) words.push(crypto.getRandomValues(new Uint32Array(1))[0]);
return new WordArray(words, nBytes);
};
else randomWordArray = (nBytes) => {
const words = [];
const r = (m_w) => {
let _m_w = m_w;
let _m_z = 987654321;
const mask = 4294967295;
return () => {
_m_z = 36969 * (_m_z & 65535) + (_m_z >> 16) & mask;
_m_w = 18e3 * (_m_w & 65535) + (_m_w >> 16) & mask;
let result = (_m_z << 16) + _m_w & mask;
result /= 4294967296;
result += .5;
return result * (Math.random() > .5 ? 1 : -1);
};
};
let rcache;
for (let i = 0; i < nBytes; i += 4) {
const _r = r((rcache || Math.random()) * 4294967296);
rcache = _r() * 987654071;
words.push(_r() * 4294967296 | 0);
}
return new WordArray(words, nBytes);
};
/**
* Base class for inheritance.
* Provides basic object-oriented programming utilities.
*/
var Base = class {
/**
* Creates a new instance of this class with the provided arguments.
* This is a factory method that provides an alternative to using 'new'.
*
* @param args - Arguments to pass to the constructor
* @returns A new instance of this class
* @static
* @example
* ```javascript
* const instance = MyType.create(arg1, arg2);
* ```
*/
static create(...args) {
return new this(...args);
}
/**
* Copies properties from the provided object into this instance.
* Performs a shallow merge of properties.
*
* @param properties - The properties to mix in
* @returns This instance for method chaining
* @example
* ```javascript
* instance.mixIn({ field: 'value', another: 123 });
* ```
*/
mixIn(properties) {
return Object.assign(this, properties);
}
/**
* Creates a deep copy of this object.
*
* @returns A clone of this instance
* @example
* ```javascript
* const clone = instance.clone();
* ```
*/
clone() {
const clone = new this.constructor();
Object.assign(clone, this);
return clone;
}
};
/**
* An array of 32-bit words.
* This is the core data structure used throughout the library for representing binary data.
*
* @property words - The array of 32-bit words
* @property sigBytes - The number of significant bytes in this word array
*/
var WordArray = class extends Base {
/** The array of 32-bit words */
words;
/** The number of significant bytes in this word array */
sigBytes;
/**
* Initializes a newly created word array.
* Can accept various input formats including regular arrays, typed arrays, and ArrayBuffers.
*
* @param words - An array of 32-bit words, typed array, or ArrayBuffer
* @param sigBytes - The number of significant bytes in the words (defaults to words.length * 4)
* @example
* ```javascript
* const wordArray = new WordArray();
* const wordArray = new WordArray([0x00010203, 0x04050607]);
* const wordArray = new WordArray([0x00010203, 0x04050607], 6);
* const wordArray = new WordArray(new Uint8Array([1, 2, 3, 4]));
* ```
*/
constructor(words = [], sigBytes) {
super();
if (words instanceof ArrayBuffer) {
const typedArray = new Uint8Array(words);
this._initFromUint8Array(typedArray);
return;
}
if (ArrayBuffer.isView(words)) {
let uint8Array;
if (words instanceof Uint8Array) uint8Array = words;
else uint8Array = new Uint8Array(words.buffer, words.byteOffset, words.byteLength);
this._initFromUint8Array(uint8Array);
return;
}
this.words = words;
this.sigBytes = sigBytes !== void 0 ? sigBytes : this.words.length * 4;
}
/**
* Initialize from Uint8Array
* @private
*/
_initFromUint8Array(typedArray) {
const typedArrayByteLength = typedArray.byteLength;
const words = [];
for (let i = 0; i < typedArrayByteLength; i += 1) words[i >>> 2] |= typedArray[i] << 24 - i % 4 * 8;
this.words = words;
this.sigBytes = typedArrayByteLength;
}
/**
* Creates a word array filled with cryptographically strong random bytes.
* Uses Web Crypto API if available, falls back to Math.random() if not.
*
* @param nBytes - The number of random bytes to generate
* @returns The random word array
* @static
* @example
* ```javascript
* const randomBytes = WordArray.random(16); // Generate 16 random bytes
* ```
*/
static random = randomWordArray;
/**
* Converts this word array to a string using the specified encoding.
*
* @param encoder - The encoding strategy to use (defaults to Hex)
* @returns The stringified word array
* @example
* ```javascript
* const hexString = wordArray.toString();
* const base64String = wordArray.toString(Base64);
* const utf8String = wordArray.toString(Utf8);
* ```
*/
toString(encoder = Hex) {
return encoder.stringify(this);
}
/**
* Concatenates a word array to this word array.
* Modifies this word array in place.
*
* @param wordArray - The word array to append
* @returns This word array for method chaining
* @example
* ```javascript
* wordArray1.concat(wordArray2);
* const combined = wordArray1.concat(wordArray2).concat(wordArray3);
* ```
*/
concat(wordArray) {
const thisWords = this.words;
const thatWords = wordArray.words;
const thisSigBytes = this.sigBytes;
const thatSigBytes = wordArray.sigBytes;
this.clamp();
if (thisSigBytes % 4) for (let i = 0; i < thatSigBytes; i += 1) {
const thatByte = thatWords[i >>> 2] >>> 24 - i % 4 * 8 & 255;
thisWords[thisSigBytes + i >>> 2] |= thatByte << 24 - (thisSigBytes + i) % 4 * 8;
}
else for (let i = 0; i < thatSigBytes; i += 4) thisWords[thisSigBytes + i >>> 2] = thatWords[i >>> 2];
this.sigBytes += thatSigBytes;
return this;
}
/**
* Removes insignificant bits from the end of the word array.
* This ensures the word array only contains the exact number of significant bytes.
*
* @example
* ```javascript
* wordArray.clamp();
* ```
*/
clamp() {
const { words, sigBytes } = this;
words[sigBytes >>> 2] &= 4294967295 << 32 - sigBytes % 4 * 8;
words.length = Math.ceil(sigBytes / 4);
}
/**
* Creates a copy of this word array.
*
* @returns The cloned word array
* @example
* ```javascript
* const clone = wordArray.clone();
* ```
*/
clone() {
const clone = super.clone();
clone.words = this.words.slice(0);
return clone;
}
};
/**
* Hex encoding strategy.
* Converts between word arrays and hexadecimal strings.
*/
const Hex = {
stringify(wordArray) {
const { words, sigBytes } = wordArray;
const hexChars = [];
for (let i = 0; i < sigBytes; i += 1) {
const bite = words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
hexChars.push((bite >>> 4).toString(16));
hexChars.push((bite & 15).toString(16));
}
return hexChars.join("");
},
parse(hexStr) {
const hexStrLength = hexStr.length;
const words = [];
for (let i = 0; i < hexStrLength; i += 2) words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << 24 - i % 8 * 4;
return new WordArray(words, hexStrLength / 2);
}
};
/**
* Latin1 encoding strategy.
* Converts between word arrays and Latin-1 (ISO-8859-1) strings.
*/
const Latin1 = {
stringify(wordArray) {
const { words, sigBytes } = wordArray;
const latin1Chars = [];
for (let i = 0; i < sigBytes; i += 1) {
const bite = words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
latin1Chars.push(String.fromCharCode(bite));
}
return latin1Chars.join("");
},
parse(latin1Str) {
const latin1StrLength = latin1Str.length;
const words = [];
for (let i = 0; i < latin1StrLength; i += 1) words[i >>> 2] |= (latin1Str.charCodeAt(i) & 255) << 24 - i % 4 * 8;
return new WordArray(words, latin1StrLength);
}
};
/**
* UTF-8 encoding strategy.
* Converts between word arrays and UTF-8 strings.
*/
const Utf8 = {
stringify(wordArray) {
try {
return decodeURIComponent(escape(Latin1.stringify(wordArray)));
} catch (e) {
throw new Error("Malformed UTF-8 data");
}
},
parse(utf8Str) {
return Latin1.parse(unescape(encodeURIComponent(utf8Str)));
}
};
/**
* Abstract buffered block algorithm template.
* Provides a base implementation for algorithms that process data in fixed-size blocks.
*
* @property _minBufferSize - The number of blocks that should be kept unprocessed in the buffer
*/
var BufferedBlockAlgorithm = class extends Base {
/** The number of blocks that should be kept unprocessed in the buffer */
_minBufferSize = 0;
/** The data buffer */
_data;
/** The number of bytes in the data buffer */
_nDataBytes;
constructor() {
super();
}
/**
* Resets this block algorithm's data buffer to its initial state.
*
* @example
* ```javascript
* bufferedBlockAlgorithm.reset();
* ```
*/
reset() {
this._data = new WordArray();
this._nDataBytes = 0;
}
/**
* Adds new data to this block algorithm's buffer.
*
* @param data - The data to append (strings are converted to WordArray using UTF-8)
* @example
* ```javascript
* bufferedBlockAlgorithm._append('data');
* bufferedBlockAlgorithm._append(wordArray);
* ```
*/
_append(data) {
let m_data;
if (typeof data === "string") m_data = Utf8.parse(data);
else m_data = data;
this._data.concat(m_data);
this._nDataBytes += m_data.sigBytes;
}
/**
* Processes available data blocks.
* This method invokes _doProcessBlock(dataWords, offset), which must be implemented by a concrete subtype.
*
* @param doFlush - Whether all blocks and partial blocks should be processed
* @returns The processed data
* @example
* ```javascript
* const processedData = bufferedBlockAlgorithm._process();
* const processedData = bufferedBlockAlgorithm._process(true); // Flush
* ```
*/
_process(doFlush) {
let processedWords;
const data = this._data;
const dataWords = data.words;
const dataSigBytes = data.sigBytes;
const blockSizeBytes = this.blockSize * 4;
let nBlocksReady = dataSigBytes / blockSizeBytes;
if (doFlush) nBlocksReady = Math.ceil(nBlocksReady);
else nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);
const nWordsReady = nBlocksReady * this.blockSize;
const nBytesReady = Math.min(nWordsReady * 4, dataSigBytes);
if (nWordsReady) {
for (let offset = 0; offset < nWordsReady; offset += this.blockSize) this._doProcessBlock(dataWords, offset);
processedWords = dataWords.splice(0, nWordsReady);
data.sigBytes -= nBytesReady;
}
return new WordArray(processedWords || [], nBytesReady);
}
/**
* Creates a copy of this object.
*
* @returns The clone
* @example
* ```javascript
* const clone = bufferedBlockAlgorithm.clone();
* ```
*/
clone() {
const clone = super.clone();
clone._data = this._data.clone();
return clone;
}
};
/**
* Abstract hasher template.
* Base class for all hash algorithm implementations.
*
* @property blockSize - The number of 32-bit words this hasher operates on (default: 16 = 512 bits)
*/
var Hasher = class extends BufferedBlockAlgorithm {
/** The number of 32-bit words this hasher operates on */
blockSize = 512 / 32;
/** Configuration options */
cfg;
/** The hash result */
_hash;
/**
* Initializes a newly created hasher.
*
* @param cfg - Configuration options
*/
constructor(cfg) {
super();
this.cfg = Object.assign({}, cfg);
this.reset();
}
/**
* Creates a shortcut function to a hasher's object interface.
*
* @param SubHasher - The hasher class to create a helper for
* @returns The shortcut function
* @static
* @example
* ```javascript
* const SHA256 = Hasher._createHelper(SHA256Algo);
* ```
*/
static _createHelper(SubHasher) {
return (message, cfg) => {
return new SubHasher(cfg).finalize(message);
};
}
/**
* Creates a shortcut function to the HMAC's object interface.
*
* @param SubHasher - The hasher class to use in this HMAC helper
* @returns The shortcut function
* @static
* @example
* ```javascript
* const HmacSHA256 = Hasher._createHmacHelper(SHA256Algo);
* ```
*/
static _createHmacHelper(SubHasher) {
return (message, key) => {
return new HMAC(SubHasher, key).finalize(message);
};
}
/**
* Resets this hasher to its initial state.
*
* @example
* ```javascript
* hasher.reset();
* ```
*/
reset() {
super.reset();
this._doReset();
}
/**
* Updates this hasher with a message.
*
* @param messageUpdate - The message to append
* @returns This hasher instance for method chaining
* @example
* ```javascript
* hasher.update('message');
* hasher.update(wordArray);
* ```
*/
update(messageUpdate) {
this._append(messageUpdate);
this._process();
return this;
}
/**
* Finalizes the hash computation.
* Note that the finalize operation is effectively a destructive, read-once operation.
*
* @param messageUpdate - An optional final message update
* @returns The computed hash
* @example
* ```javascript
* const hash = hasher.finalize();
* const hash = hasher.finalize('message');
* const hash = hasher.finalize(wordArray);
* ```
*/
finalize(messageUpdate) {
if (messageUpdate) this._append(messageUpdate);
const hash = this._doFinalize();
return hash;
}
};
/**
* Base class for 32-bit hash algorithms.
* Hash algorithms that operate on 32-bit words should extend this class.
*/
var Hasher32 = class extends Hasher {};
/**
* Base class for 64-bit hash algorithms.
* Hash algorithms that operate on 64-bit words should extend this class.
*/
var Hasher64 = class extends Hasher {};
/**
* HMAC (Hash-based Message Authentication Code) algorithm.
* Provides message authentication using a cryptographic hash function and a secret key.
*/
var HMAC = class HMAC extends Base {
/** The inner hasher instance */
_hasher;
/** The outer key */
_oKey;
/** The inner key */
_iKey;
/**
* Initializes a newly created HMAC.
*
* @param SubHasher - The hash algorithm class to use
* @param key - The secret key
* @example
* ```javascript
* const hmac = new HMAC(SHA256Algo, 'secret key');
* ```
*/
constructor(SubHasher, key) {
super();
const hasher = new SubHasher();
this._hasher = hasher;
let _key;
if (typeof key === "string") _key = Utf8.parse(key);
else _key = key;
const hasherBlockSize = hasher.blockSize;
const hasherBlockSizeBytes = hasherBlockSize * 4;
if (_key.sigBytes > hasherBlockSizeBytes) _key = hasher.finalize(_key);
_key.clamp();
const oKey = _key.clone();
this._oKey = oKey;
const iKey = _key.clone();
this._iKey = iKey;
const oKeyWords = oKey.words;
const iKeyWords = iKey.words;
for (let i = 0; i < hasherBlockSize; i += 1) {
oKeyWords[i] ^= 1549556828;
iKeyWords[i] ^= 909522486;
}
oKey.sigBytes = hasherBlockSizeBytes;
iKey.sigBytes = hasherBlockSizeBytes;
this.reset();
}
static create(...args) {
const [SubHasher, key] = args;
return new HMAC(SubHasher, key);
}
/**
* Resets this HMAC to its initial state.
*
* @example
* ```javascript
* hmac.reset();
* ```
*/
reset() {
const hasher = this._hasher;
hasher.reset();
hasher.update(this._iKey);
}
/**
* Updates this HMAC with a message.
*
* @param messageUpdate - The message to append
* @returns This HMAC instance for method chaining
* @example
* ```javascript
* hmac.update('message');
* hmac.update(wordArray);
* ```
*/
update(messageUpdate) {
this._hasher.update(messageUpdate);
return this;
}
/**
* Finalizes the HMAC computation.
* Note that the finalize operation is effectively a destructive, read-once operation.
*
* @param messageUpdate - An optional final message update
* @returns The computed HMAC
* @example
* ```javascript
* const hmacValue = hmac.finalize();
* const hmacValue = hmac.finalize('message');
* const hmacValue = hmac.finalize(wordArray);
* ```
*/
finalize(messageUpdate) {
const hasher = this._hasher;
const innerHash = hasher.finalize(messageUpdate);
hasher.reset();
const hmac = hasher.finalize(this._oKey.clone().concat(innerHash));
return hmac;
}
};
//#endregion
export { Base, BufferedBlockAlgorithm, HMAC, Hasher, Hasher32, Hasher64, Hex, Latin1, Utf8, WordArray };
//# sourceMappingURL=core.mjs.map

View File

@@ -1,104 +0,0 @@
import { WordArray } from "./core.mjs";
//#region src/enc-base64.ts
/**
* Parses a Base64 string to a WordArray.
* Helper function for Base64 decoding.
*
* @param base64Str - The Base64 string to parse
* @param base64StrLength - The length of the Base64 string
* @param reverseMap - The reverse character map for decoding
* @returns The decoded WordArray
*/
const parseLoop = (base64Str, base64StrLength, reverseMap) => {
const words = [];
let nBytes = 0;
for (let i = 0; i < base64StrLength; i += 1) if (i % 4) {
const bits1 = reverseMap[base64Str.charCodeAt(i - 1)] << i % 4 * 2;
const bits2 = reverseMap[base64Str.charCodeAt(i)] >>> 6 - i % 4 * 2;
const bitsCombined = bits1 | bits2;
words[nBytes >>> 2] |= bitsCombined << 24 - nBytes % 4 * 8;
nBytes += 1;
}
return WordArray.create(words, nBytes);
};
/**
* Base64 encoding strategy implementation.
* @private
*/
var Base64Impl = class {
/** The Base64 character map */
_map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
/** The reverse character map for decoding */
_reverseMap;
/**
* Converts a word array to a Base64 string.
*
* @param wordArray - The word array to convert
* @returns The Base64 string representation
* @example
* ```javascript
* const base64String = Base64.stringify(wordArray);
* ```
*/
stringify(wordArray) {
const { words, sigBytes } = wordArray;
const map = this._map;
wordArray.clamp();
const base64Chars = [];
for (let i = 0; i < sigBytes; i += 3) {
const byte1 = words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
const byte2 = words[i + 1 >>> 2] >>> 24 - (i + 1) % 4 * 8 & 255;
const byte3 = words[i + 2 >>> 2] >>> 24 - (i + 2) % 4 * 8 & 255;
const triplet = byte1 << 16 | byte2 << 8 | byte3;
for (let j = 0; j < 4 && i + j * .75 < sigBytes; j += 1) base64Chars.push(map.charAt(triplet >>> 6 * (3 - j) & 63));
}
const paddingChar = map.charAt(64);
if (paddingChar) while (base64Chars.length % 4) base64Chars.push(paddingChar);
return base64Chars.join("");
}
/**
* Converts a Base64 string to a word array.
*
* @param base64Str - The Base64 string to parse
* @returns The word array representation
* @example
* ```javascript
* const wordArray = Base64.parse(base64String);
* ```
*/
parse(base64Str) {
let base64StrLength = base64Str.length;
const map = this._map;
let reverseMap = this._reverseMap;
if (!reverseMap) {
this._reverseMap = [];
reverseMap = this._reverseMap;
for (let j = 0; j < map.length; j += 1) reverseMap[map.charCodeAt(j)] = j;
}
const paddingChar = map.charAt(64);
if (paddingChar) {
const paddingIndex = base64Str.indexOf(paddingChar);
if (paddingIndex !== -1) base64StrLength = paddingIndex;
}
return parseLoop(base64Str, base64StrLength, reverseMap);
}
};
/**
* Base64 encoding strategy.
* Converts between WordArrays and Base64 strings.
*
* @example
* ```javascript
* // Encoding
* const base64String = Base64.stringify(wordArray);
*
* // Decoding
* const wordArray = Base64.parse(base64String);
* ```
*/
const Base64 = new Base64Impl();
//#endregion
export { Base64, parseLoop };
//# sourceMappingURL=enc-base64.mjs.map

View File

@@ -1,102 +0,0 @@
import { parseLoop } from "./enc-base64.mjs";
//#region src/enc-base64url.ts
/**
* Base64url encoding strategy implementation.
* @private
*/
var Base64urlImpl = class {
/** Standard Base64 character map */
_map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
/** URL-safe Base64 character map (no padding) */
_safeMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
/** Reverse character map for decoding */
_reverseMap;
/**
* Converts a word array to a Base64url string.
*
* @param wordArray - The word array to convert
* @param urlSafe - Whether to use URL-safe encoding (default: true)
* @returns The Base64url string representation
* @example
* ```javascript
* // URL-safe encoding (default)
* const base64urlString = Base64url.stringify(wordArray);
*
* // Standard Base64 encoding
* const base64String = Base64url.stringify(wordArray, false);
* ```
*/
stringify(wordArray, urlSafe = true) {
const { words, sigBytes } = wordArray;
const map = urlSafe ? this._safeMap : this._map;
wordArray.clamp();
const base64Chars = [];
for (let i = 0; i < sigBytes; i += 3) {
const byte1 = words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
const byte2 = words[i + 1 >>> 2] >>> 24 - (i + 1) % 4 * 8 & 255;
const byte3 = words[i + 2 >>> 2] >>> 24 - (i + 2) % 4 * 8 & 255;
const triplet = byte1 << 16 | byte2 << 8 | byte3;
for (let j = 0; j < 4 && i + j * .75 < sigBytes; j += 1) base64Chars.push(map.charAt(triplet >>> 6 * (3 - j) & 63));
}
const paddingChar = map.charAt(64);
if (paddingChar) while (base64Chars.length % 4) base64Chars.push(paddingChar);
return base64Chars.join("");
}
/**
* Converts a Base64url string to a word array.
*
* @param base64Str - The Base64url string to parse
* @param urlSafe - Whether to use URL-safe decoding (default: true)
* @returns The word array representation
* @example
* ```javascript
* // URL-safe decoding (default)
* const wordArray = Base64url.parse(base64urlString);
*
* // Standard Base64 decoding
* const wordArray = Base64url.parse(base64String, false);
* ```
*/
parse(base64Str, urlSafe = true) {
let base64StrLength = base64Str.length;
const map = urlSafe ? this._safeMap : this._map;
let reverseMap = this._reverseMap;
if (!reverseMap) {
this._reverseMap = [];
reverseMap = this._reverseMap;
for (let j = 0; j < map.length; j += 1) reverseMap[map.charCodeAt(j)] = j;
}
const paddingChar = map.charAt(64);
if (paddingChar) {
const paddingIndex = base64Str.indexOf(paddingChar);
if (paddingIndex !== -1) base64StrLength = paddingIndex;
}
return parseLoop(base64Str, base64StrLength, reverseMap);
}
};
/**
* Base64url encoding strategy.
* Provides URL-safe Base64 encoding/decoding that can be used in URLs without escaping.
*
* The URL-safe variant:
* - Uses '-' instead of '+'
* - Uses '_' instead of '/'
* - Omits padding '=' characters
*
* @example
* ```javascript
* // URL-safe encoding (default)
* const urlSafeString = Base64url.stringify(wordArray);
* const wordArray = Base64url.parse(urlSafeString);
*
* // Standard Base64 encoding
* const base64String = Base64url.stringify(wordArray, false);
* const wordArray = Base64url.parse(base64String, false);
* ```
*/
const Base64url = new Base64urlImpl();
//#endregion
export { Base64url };
//# sourceMappingURL=enc-base64url.mjs.map

View File

@@ -1,57 +0,0 @@
import { WordArray } from "./core.mjs";
//#region src/enc-utf16.ts
/**
* Swaps endian of a word
* @param word - The word to swap
* @returns The word with swapped endian
*/
const swapEndian = (word) => word << 8 & 4278255360 | word >>> 8 & 16711935;
/**
* UTF-16 BE encoding strategy.
*/
const Utf16BE = {
stringify(wordArray) {
const { words, sigBytes } = wordArray;
const utf16Chars = [];
for (let i = 0; i < sigBytes; i += 2) {
const codePoint = words[i >>> 2] >>> 16 - i % 4 * 8 & 65535;
utf16Chars.push(String.fromCharCode(codePoint));
}
return utf16Chars.join("");
},
parse(utf16Str) {
const utf16StrLength = utf16Str.length;
const words = [];
for (let i = 0; i < utf16StrLength; i += 1) words[i >>> 1] |= utf16Str.charCodeAt(i) << 16 - i % 2 * 16;
return WordArray.create(words, utf16StrLength * 2);
}
};
/**
* UTF-16 encoding strategy (defaults to UTF-16 BE).
*/
const Utf16 = Utf16BE;
/**
* UTF-16 LE encoding strategy.
*/
const Utf16LE = {
stringify(wordArray) {
const { words, sigBytes } = wordArray;
const utf16Chars = [];
for (let i = 0; i < sigBytes; i += 2) {
const codePoint = swapEndian(words[i >>> 2] >>> 16 - i % 4 * 8 & 65535);
utf16Chars.push(String.fromCharCode(codePoint));
}
return utf16Chars.join("");
},
parse(utf16Str) {
const utf16StrLength = utf16Str.length;
const words = [];
for (let i = 0; i < utf16StrLength; i += 1) words[i >>> 1] |= swapEndian(utf16Str.charCodeAt(i) << 16 - i % 2 * 16);
return WordArray.create(words, utf16StrLength * 2);
}
};
//#endregion
export { Utf16, Utf16BE, Utf16LE };
//# sourceMappingURL=enc-utf16.mjs.map

View File

@@ -1,91 +0,0 @@
import { Base, WordArray } from "./core.mjs";
import { MD5Algo } from "./md5.mjs";
//#region src/evpkdf.ts
/**
* This key derivation function is meant to conform with EVP_BytesToKey.
* www.openssl.org/docs/crypto/EVP_BytesToKey.html
*/
var EvpKDFAlgo = class extends Base {
cfg;
/**
* Initializes a newly created key derivation function.
*
* @param {Object} cfg (Optional) The configuration options to use for the derivation.
*
* @example
*
* const kdf = new EvpKDFAlgo();
* const kdf = new EvpKDFAlgo({ keySize: 8 });
* const kdf = new EvpKDFAlgo({ keySize: 8, iterations: 1000 });
*/
constructor(cfg) {
super();
/**
* Configuration options.
*
* @property {number} keySize The key size in words to generate. Default: 4 (128 bits)
* @property {Hasher} hasher The hash algorithm to use. Default: MD5
* @property {number} iterations The number of iterations to perform. Default: 1
*/
this.cfg = Object.assign({}, {
keySize: 128 / 32,
hasher: MD5Algo,
iterations: 1
}, cfg);
}
/**
* Derives a key from a password.
*
* @param {WordArray|string} password The password.
* @param {WordArray|string} salt A salt.
*
* @return {WordArray} The derived key.
*
* @example
*
* const key = kdf.compute(password, salt);
*/
compute(password, salt) {
let block;
const { cfg } = this;
const hasher = new cfg.hasher();
const derivedKey = WordArray.create();
const derivedKeyWords = derivedKey.words;
const { keySize, iterations } = cfg;
while (derivedKeyWords.length < keySize) {
if (block) hasher.update(block);
block = hasher.update(password).finalize(salt);
hasher.reset();
for (let i = 1; i < iterations; i += 1) {
block = hasher.finalize(block);
hasher.reset();
}
derivedKey.concat(block);
}
derivedKey.sigBytes = keySize * 4;
return derivedKey;
}
};
/**
* Derives a key from a password.
*
* @param {WordArray|string} password The password.
* @param {WordArray|string} salt A salt.
* @param {Object} cfg (Optional) The configuration options to use for this computation.
*
* @return {WordArray} The derived key.
*
* @static
*
* @example
*
* var key = EvpKDF(password, salt);
* var key = EvpKDF(password, salt, { keySize: 8 });
* var key = EvpKDF(password, salt, { keySize: 8, iterations: 1000 });
*/
const EvpKDF = (password, salt, cfg) => new EvpKDFAlgo(cfg).compute(password, salt);
//#endregion
export { EvpKDF, EvpKDFAlgo };
//# sourceMappingURL=evpkdf.mjs.map

View File

@@ -1,22 +0,0 @@
import { Hex } from "./core.mjs";
import { CipherParams } from "./cipher-core.mjs";
//#region src/format-hex.ts
/**
* Hex formatter for cipher params.
* Converts cipher params to/from hexadecimal strings.
*/
const HexFormatter = {
stringify(cipherParams) {
if (!cipherParams.ciphertext) throw new Error("Ciphertext is required");
return cipherParams.ciphertext.toString(Hex);
},
parse(input) {
const ciphertext = Hex.parse(input);
return CipherParams.create({ ciphertext });
}
};
//#endregion
export { HexFormatter };
//# sourceMappingURL=format-hex.mjs.map

View File

@@ -1,201 +0,0 @@
import { Hasher, Hasher32, WordArray } from "./core.mjs";
//#region src/md5.ts
const T = /* @__PURE__ */ (() => {
const a = [];
for (let i = 0; i < 64; i += 1) a[i] = Math.abs(Math.sin(i + 1)) * 4294967296 | 0;
return a;
})();
/**
* MD5 round function F
*/
const FF = (a, b, c, d, x, s, t) => {
const n = a + (b & c | ~b & d) + x + t;
return (n << s | n >>> 32 - s) + b;
};
/**
* MD5 round function G
*/
const GG = (a, b, c, d, x, s, t) => {
const n = a + (b & d | c & ~d) + x + t;
return (n << s | n >>> 32 - s) + b;
};
/**
* MD5 round function H
*/
const HH = (a, b, c, d, x, s, t) => {
const n = a + (b ^ c ^ d) + x + t;
return (n << s | n >>> 32 - s) + b;
};
/**
* MD5 round function I
*/
const II = (a, b, c, d, x, s, t) => {
const n = a + (c ^ (b | ~d)) + x + t;
return (n << s | n >>> 32 - s) + b;
};
/**
* MD5 hash algorithm.
*/
var MD5Algo = class extends Hasher32 {
_doReset() {
this._hash = new WordArray([
1732584193,
4023233417,
2562383102,
271733878
]);
}
_doProcessBlock(M, offset) {
const _M = M;
for (let i = 0; i < 16; i += 1) {
const offset_i = offset + i;
const M_offset_i = M[offset_i];
_M[offset_i] = (M_offset_i << 8 | M_offset_i >>> 24) & 16711935 | (M_offset_i << 24 | M_offset_i >>> 8) & 4278255360;
}
const H = this._hash.words;
const M_offset_0 = _M[offset + 0];
const M_offset_1 = _M[offset + 1];
const M_offset_2 = _M[offset + 2];
const M_offset_3 = _M[offset + 3];
const M_offset_4 = _M[offset + 4];
const M_offset_5 = _M[offset + 5];
const M_offset_6 = _M[offset + 6];
const M_offset_7 = _M[offset + 7];
const M_offset_8 = _M[offset + 8];
const M_offset_9 = _M[offset + 9];
const M_offset_10 = _M[offset + 10];
const M_offset_11 = _M[offset + 11];
const M_offset_12 = _M[offset + 12];
const M_offset_13 = _M[offset + 13];
const M_offset_14 = _M[offset + 14];
const M_offset_15 = _M[offset + 15];
let a = H[0];
let b = H[1];
let c = H[2];
let d = H[3];
a = FF(a, b, c, d, M_offset_0, 7, T[0]);
d = FF(d, a, b, c, M_offset_1, 12, T[1]);
c = FF(c, d, a, b, M_offset_2, 17, T[2]);
b = FF(b, c, d, a, M_offset_3, 22, T[3]);
a = FF(a, b, c, d, M_offset_4, 7, T[4]);
d = FF(d, a, b, c, M_offset_5, 12, T[5]);
c = FF(c, d, a, b, M_offset_6, 17, T[6]);
b = FF(b, c, d, a, M_offset_7, 22, T[7]);
a = FF(a, b, c, d, M_offset_8, 7, T[8]);
d = FF(d, a, b, c, M_offset_9, 12, T[9]);
c = FF(c, d, a, b, M_offset_10, 17, T[10]);
b = FF(b, c, d, a, M_offset_11, 22, T[11]);
a = FF(a, b, c, d, M_offset_12, 7, T[12]);
d = FF(d, a, b, c, M_offset_13, 12, T[13]);
c = FF(c, d, a, b, M_offset_14, 17, T[14]);
b = FF(b, c, d, a, M_offset_15, 22, T[15]);
a = GG(a, b, c, d, M_offset_1, 5, T[16]);
d = GG(d, a, b, c, M_offset_6, 9, T[17]);
c = GG(c, d, a, b, M_offset_11, 14, T[18]);
b = GG(b, c, d, a, M_offset_0, 20, T[19]);
a = GG(a, b, c, d, M_offset_5, 5, T[20]);
d = GG(d, a, b, c, M_offset_10, 9, T[21]);
c = GG(c, d, a, b, M_offset_15, 14, T[22]);
b = GG(b, c, d, a, M_offset_4, 20, T[23]);
a = GG(a, b, c, d, M_offset_9, 5, T[24]);
d = GG(d, a, b, c, M_offset_14, 9, T[25]);
c = GG(c, d, a, b, M_offset_3, 14, T[26]);
b = GG(b, c, d, a, M_offset_8, 20, T[27]);
a = GG(a, b, c, d, M_offset_13, 5, T[28]);
d = GG(d, a, b, c, M_offset_2, 9, T[29]);
c = GG(c, d, a, b, M_offset_7, 14, T[30]);
b = GG(b, c, d, a, M_offset_12, 20, T[31]);
a = HH(a, b, c, d, M_offset_5, 4, T[32]);
d = HH(d, a, b, c, M_offset_8, 11, T[33]);
c = HH(c, d, a, b, M_offset_11, 16, T[34]);
b = HH(b, c, d, a, M_offset_14, 23, T[35]);
a = HH(a, b, c, d, M_offset_1, 4, T[36]);
d = HH(d, a, b, c, M_offset_4, 11, T[37]);
c = HH(c, d, a, b, M_offset_7, 16, T[38]);
b = HH(b, c, d, a, M_offset_10, 23, T[39]);
a = HH(a, b, c, d, M_offset_13, 4, T[40]);
d = HH(d, a, b, c, M_offset_0, 11, T[41]);
c = HH(c, d, a, b, M_offset_3, 16, T[42]);
b = HH(b, c, d, a, M_offset_6, 23, T[43]);
a = HH(a, b, c, d, M_offset_9, 4, T[44]);
d = HH(d, a, b, c, M_offset_12, 11, T[45]);
c = HH(c, d, a, b, M_offset_15, 16, T[46]);
b = HH(b, c, d, a, M_offset_2, 23, T[47]);
a = II(a, b, c, d, M_offset_0, 6, T[48]);
d = II(d, a, b, c, M_offset_7, 10, T[49]);
c = II(c, d, a, b, M_offset_14, 15, T[50]);
b = II(b, c, d, a, M_offset_5, 21, T[51]);
a = II(a, b, c, d, M_offset_12, 6, T[52]);
d = II(d, a, b, c, M_offset_3, 10, T[53]);
c = II(c, d, a, b, M_offset_10, 15, T[54]);
b = II(b, c, d, a, M_offset_1, 21, T[55]);
a = II(a, b, c, d, M_offset_8, 6, T[56]);
d = II(d, a, b, c, M_offset_15, 10, T[57]);
c = II(c, d, a, b, M_offset_6, 15, T[58]);
b = II(b, c, d, a, M_offset_13, 21, T[59]);
a = II(a, b, c, d, M_offset_4, 6, T[60]);
d = II(d, a, b, c, M_offset_11, 10, T[61]);
c = II(c, d, a, b, M_offset_2, 15, T[62]);
b = II(b, c, d, a, M_offset_9, 21, T[63]);
H[0] = H[0] + a | 0;
H[1] = H[1] + b | 0;
H[2] = H[2] + c | 0;
H[3] = H[3] + d | 0;
}
_doFinalize() {
const data = this._data;
const dataWords = data.words;
const nBitsTotal = this._nDataBytes * 8;
const nBitsLeft = data.sigBytes * 8;
dataWords[nBitsLeft >>> 5] |= 128 << 24 - nBitsLeft % 32;
const nBitsTotalH = Math.floor(nBitsTotal / 4294967296);
const nBitsTotalL = nBitsTotal;
dataWords[(nBitsLeft + 64 >>> 9 << 4) + 15] = (nBitsTotalH << 8 | nBitsTotalH >>> 24) & 16711935 | (nBitsTotalH << 24 | nBitsTotalH >>> 8) & 4278255360;
dataWords[(nBitsLeft + 64 >>> 9 << 4) + 14] = (nBitsTotalL << 8 | nBitsTotalL >>> 24) & 16711935 | (nBitsTotalL << 24 | nBitsTotalL >>> 8) & 4278255360;
data.sigBytes = (dataWords.length + 1) * 4;
this._process();
const hash = this._hash;
const H = hash.words;
for (let i = 0; i < 4; i += 1) {
const H_i = H[i];
H[i] = (H_i << 8 | H_i >>> 24) & 16711935 | (H_i << 24 | H_i >>> 8) & 4278255360;
}
return hash;
}
clone() {
const clone = super.clone.call(this);
clone._hash = this._hash.clone();
return clone;
}
};
/**
* Shortcut function to the hasher's object interface.
*
* @param message - The message to hash.
* @returns The hash.
*
* @example
* ```js
* const hash = MD5('message');
* const hash = MD5(wordArray);
* ```
*/
const MD5 = Hasher._createHelper(MD5Algo);
/**
* Shortcut function to the HMAC's object interface.
*
* @param message - The message to hash.
* @param key - The secret key.
* @returns The HMAC.
*
* @example
* ```js
* const hmac = HmacMD5(message, key);
* ```
*/
const HmacMD5 = Hasher._createHmacHelper(MD5Algo);
//#endregion
export { HmacMD5, MD5, MD5Algo };
//# sourceMappingURL=md5.mjs.map

View File

@@ -1,48 +0,0 @@
import { BlockCipherMode } from "./cipher-core.mjs";
//#region src/mode-cfb.ts
function generateKeystreamAndEncrypt(words, offset, blockSize, cipher) {
const _words = words;
let keystream;
const iv = this._iv;
if (iv) {
keystream = iv.slice(0);
this._iv = void 0;
} else keystream = this._prevBlock;
cipher.encryptBlock(keystream, 0);
for (let i = 0; i < blockSize; i += 1) _words[offset + i] ^= keystream[i];
}
/**
* CFB Encryptor
*/
var CFBEncryptor = class extends BlockCipherMode {
processBlock(words, offset) {
const cipher = this._cipher;
const blockSize = cipher.blockSize;
generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher);
this._prevBlock = words.slice(offset, offset + blockSize);
}
};
/**
* CFB Decryptor
*/
var CFBDecryptor = class extends BlockCipherMode {
processBlock(words, offset) {
const cipher = this._cipher;
const blockSize = cipher.blockSize;
const thisBlock = words.slice(offset, offset + blockSize);
generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher);
this._prevBlock = thisBlock;
}
};
/**
* Cipher Feedback block mode.
*/
var CFB = class extends BlockCipherMode {
static Encryptor = CFBEncryptor;
static Decryptor = CFBDecryptor;
};
//#endregion
export { CFB };
//# sourceMappingURL=mode-cfb.mjs.map

View File

@@ -1,68 +0,0 @@
import { BlockCipherMode } from "./cipher-core.mjs";
//#region src/mode-ctr-gladman.ts
const incWord = (word) => {
let _word = word;
if ((word >> 24 & 255) === 255) {
let b1 = word >> 16 & 255;
let b2 = word >> 8 & 255;
let b3 = word & 255;
if (b1 === 255) {
b1 = 0;
if (b2 === 255) {
b2 = 0;
if (b3 === 255) b3 = 0;
else b3 += 1;
} else b2 += 1;
} else b1 += 1;
_word = 0;
_word += b1 << 16;
_word += b2 << 8;
_word += b3;
} else _word += 1 << 24;
return _word;
};
const incCounter = (counter) => {
const _counter = counter;
_counter[0] = incWord(_counter[0]);
if (_counter[0] === 0) _counter[1] = incWord(_counter[1]);
return _counter;
};
/**
* CTRGladman Encryptor/Decryptor (same operation)
*/
var CTRGladmanMode = class extends BlockCipherMode {
/** Counter for CTR Gladman mode */
_counter;
processBlock(words, offset) {
const _words = words;
const cipher = this._cipher;
const blockSize = cipher.blockSize;
const iv = this._iv;
let counter = this._counter;
if (iv) {
this._counter = iv.slice(0);
counter = this._counter;
this._iv = void 0;
}
incCounter(counter);
const keystream = counter.slice(0);
cipher.encryptBlock(keystream, 0);
for (let i = 0; i < blockSize; i += 1) _words[offset + i] ^= keystream[i];
}
};
/** @preserve
* Counter block mode compatible with Dr Brian Gladman fileenc.c
* derived from CTR mode
* Jan Hruby jhruby.web@gmail.com
*/
var CTRGladman = class extends BlockCipherMode {
/** Counter for CTR Gladman mode */
_counter;
static Encryptor = CTRGladmanMode;
static Decryptor = CTRGladmanMode;
};
//#endregion
export { CTRGladman };
//# sourceMappingURL=mode-ctr-gladman.mjs.map

View File

@@ -1,39 +0,0 @@
import { BlockCipherMode } from "./cipher-core.mjs";
//#region src/mode-ctr.ts
/**
* CTR Encryptor/Decryptor (same operation)
*/
var CTRMode = class extends BlockCipherMode {
/** Counter for CTR mode */
_counter;
processBlock(words, offset) {
const _words = words;
const cipher = this._cipher;
const blockSize = cipher.blockSize;
const iv = this._iv;
let counter = this._counter;
if (iv) {
this._counter = iv.slice(0);
counter = this._counter;
this._iv = void 0;
}
const keystream = counter.slice(0);
cipher.encryptBlock(keystream, 0);
counter[blockSize - 1] = counter[blockSize - 1] + 1 | 0;
for (let i = 0; i < blockSize; i += 1) _words[offset + i] ^= keystream[i];
}
};
/**
* Counter block mode.
*/
var CTR = class extends BlockCipherMode {
/** Counter for CTR mode */
_counter;
static Encryptor = CTRMode;
static Decryptor = CTRMode;
};
//#endregion
export { CTR };
//# sourceMappingURL=mode-ctr.mjs.map

View File

@@ -1,30 +0,0 @@
import { BlockCipherMode } from "./cipher-core.mjs";
//#region src/mode-ecb.ts
/**
* ECB Encryptor
*/
var ECBEncryptor = class extends BlockCipherMode {
processBlock(words, offset) {
this._cipher.encryptBlock(words, offset);
}
};
/**
* ECB Decryptor
*/
var ECBDecryptor = class extends BlockCipherMode {
processBlock(words, offset) {
this._cipher.decryptBlock(words, offset);
}
};
/**
* Electronic Codebook block mode.
*/
var ECB = class extends BlockCipherMode {
static Encryptor = ECBEncryptor;
static Decryptor = ECBDecryptor;
};
//#endregion
export { ECB };
//# sourceMappingURL=mode-ecb.mjs.map

View File

@@ -1,40 +0,0 @@
import { BlockCipherMode } from "./cipher-core.mjs";
//#region src/mode-ofb.ts
/**
* OFB Encryptor/Decryptor (same operation)
*/
var OFBMode = class extends BlockCipherMode {
/** Keystream for OFB mode */
_keystream;
processBlock(words, offset) {
const _words = words;
const cipher = this._cipher;
const blockSize = cipher.blockSize;
const iv = this._iv;
let keystream = this._keystream;
if (iv) {
this._keystream = iv.slice(0);
keystream = this._keystream;
this._iv = void 0;
} else if (!keystream) {
this._keystream = new Array(blockSize).fill(0);
keystream = this._keystream;
}
cipher.encryptBlock(keystream, 0);
for (let i = 0; i < blockSize; i += 1) _words[offset + i] ^= keystream[i];
}
};
/**
* Output Feedback block mode.
*/
var OFB = class extends BlockCipherMode {
/** Keystream for OFB mode */
_keystream;
static Encryptor = OFBMode;
static Decryptor = OFBMode;
};
//#endregion
export { OFB };
//# sourceMappingURL=mode-ofb.mjs.map

View File

@@ -1,25 +0,0 @@
//#region src/pad-ansix923.ts
/**
* ANSI X.923 padding strategy.
*/
const AnsiX923 = {
pad(data, blockSize) {
const _data = data;
const dataSigBytes = _data.sigBytes;
const blockSizeBytes = blockSize * 4;
const nPaddingBytes = blockSizeBytes - dataSigBytes % blockSizeBytes;
const lastBytePos = dataSigBytes + nPaddingBytes - 1;
_data.clamp();
_data.words[lastBytePos >>> 2] |= nPaddingBytes << 24 - lastBytePos % 4 * 8;
_data.sigBytes += nPaddingBytes;
},
unpad(data) {
const _data = data;
const nPaddingBytes = _data.words[_data.sigBytes - 1 >>> 2] & 255;
_data.sigBytes -= nPaddingBytes;
}
};
//#endregion
export { AnsiX923 };
//# sourceMappingURL=pad-ansix923.mjs.map

View File

@@ -1,22 +0,0 @@
import { WordArray } from "./core.mjs";
//#region src/pad-iso10126.ts
/**
* ISO 10126 padding strategy.
*/
const Iso10126 = {
pad(data, blockSize) {
const blockSizeBytes = blockSize * 4;
const nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes;
data.concat(WordArray.random(nPaddingBytes - 1)).concat(WordArray.create([nPaddingBytes << 24], 1));
},
unpad(data) {
const _data = data;
const nPaddingBytes = _data.words[_data.sigBytes - 1 >>> 2] & 255;
_data.sigBytes -= nPaddingBytes;
}
};
//#endregion
export { Iso10126 };
//# sourceMappingURL=pad-iso10126.mjs.map

View File

@@ -1,22 +0,0 @@
import { WordArray } from "./core.mjs";
import { ZeroPadding } from "./pad-zeropadding.mjs";
//#region src/pad-iso97971.ts
/**
* ISO/IEC 9797-1 Padding Method 2.
*/
const Iso97971 = {
pad(data, blockSize) {
data.concat(WordArray.create([2147483648], 1));
ZeroPadding.pad(data, blockSize);
},
unpad(data) {
const _data = data;
ZeroPadding.unpad(_data);
_data.sigBytes -= 1;
}
};
//#endregion
export { Iso97971 };
//# sourceMappingURL=pad-iso97971.mjs.map

View File

@@ -1,12 +0,0 @@
//#region src/pad-nopadding.ts
/**
* A noop padding strategy.
*/
const NoPadding = {
pad(_data, _blockSize) {},
unpad(_data) {}
};
//#endregion
export { NoPadding };
//# sourceMappingURL=pad-nopadding.mjs.map

View File

@@ -1,24 +0,0 @@
//#region src/pad-zeropadding.ts
/**
* Zero padding strategy.
*/
const ZeroPadding = {
pad(data, blockSize) {
const _data = data;
const blockSizeBytes = blockSize * 4;
_data.clamp();
_data.sigBytes += blockSizeBytes - (data.sigBytes % blockSizeBytes || blockSizeBytes);
},
unpad(data) {
const _data = data;
const dataWords = _data.words;
for (let i = _data.sigBytes - 1; i >= 0; i -= 1) if (dataWords[i >>> 2] >>> 24 - i % 4 * 8 & 255) {
_data.sigBytes = i + 1;
break;
}
}
};
//#endregion
export { ZeroPadding };
//# sourceMappingURL=pad-zeropadding.mjs.map

View File

@@ -1,99 +0,0 @@
import { Base, HMAC, WordArray } from "./core.mjs";
import { SHA256Algo } from "./sha256.mjs";
//#region src/pbkdf2.ts
/**
* Password-Based Key Derivation Function 2 algorithm.
*/
var PBKDF2Algo = class extends Base {
cfg;
/**
* Initializes a newly created key derivation function.
*
* @param {Object} cfg (Optional) The configuration options to use for the derivation.
*
* @example
*
* const kdf = new PBKDF2Algo();
* const kdf = new PBKDF2Algo({ keySize: 8 });
* const kdf = new PBKDF2Algo({ keySize: 8, iterations: 1000 });
*/
constructor(cfg) {
super();
/**
* Configuration options.
*
* The default `hasher` and `interations` is different from CryptoJs to enhance security:
* https://github.com/entronad/crypto-es/security/advisories/GHSA-mpj8-q39x-wq5h
*
* @property {number} keySize The key size in words to generate. Default: 4 (128 bits)
* @property {Hasher} hasher The hasher to use. Default: SHA256
* @property {number} iterations The number of iterations to perform. Default: 250000
*/
this.cfg = Object.assign({}, {
keySize: 128 / 32,
hasher: SHA256Algo,
iterations: 25e4
}, cfg);
}
/**
* Computes the Password-Based Key Derivation Function 2.
*
* @param {WordArray|string} password The password.
* @param {WordArray|string} salt A salt.
*
* @return {WordArray} The derived key.
*
* @example
*
* const key = kdf.compute(password, salt);
*/
compute(password, salt) {
const { cfg } = this;
const hmac = HMAC.create(cfg.hasher, password);
const derivedKey = WordArray.create();
const blockIndex = WordArray.create([1]);
const derivedKeyWords = derivedKey.words;
const blockIndexWords = blockIndex.words;
const { keySize, iterations } = cfg;
while (derivedKeyWords.length < keySize) {
const block = hmac.update(salt).finalize(blockIndex);
hmac.reset();
const blockWords = block.words;
const blockWordsLength = blockWords.length;
let intermediate = block;
for (let i = 1; i < iterations; i += 1) {
intermediate = hmac.finalize(intermediate);
hmac.reset();
const intermediateWords = intermediate.words;
for (let j = 0; j < blockWordsLength; j += 1) blockWords[j] ^= intermediateWords[j];
}
derivedKey.concat(block);
blockIndexWords[0] += 1;
}
derivedKey.sigBytes = keySize * 4;
return derivedKey;
}
};
/**
* Computes the Password-Based Key Derivation Function 2.
*
* @param {WordArray|string} password The password.
* @param {WordArray|string} salt A salt.
* @param {Object} cfg (Optional) The configuration options to use for this computation.
*
* @return {WordArray} The derived key.
*
* @static
*
* @example
*
* var key = PBKDF2(password, salt);
* var key = PBKDF2(password, salt, { keySize: 8 });
* var key = PBKDF2(password, salt, { keySize: 8, iterations: 1000 });
*/
const PBKDF2 = (password, salt, cfg) => new PBKDF2Algo(cfg).compute(password, salt);
//#endregion
export { PBKDF2, PBKDF2Algo };
//# sourceMappingURL=pbkdf2.mjs.map

View File

@@ -1,126 +0,0 @@
import { StreamCipher } from "./cipher-core.mjs";
//#region src/rabbit-legacy.ts
const S = [];
const C_ = [];
const G = [];
function nextState() {
const X = this._X;
const C = this._C;
for (let i = 0; i < 8; i += 1) C_[i] = C[i];
C[0] = C[0] + 1295307597 + this._b | 0;
C[1] = C[1] + 3545052371 + (C[0] >>> 0 < C_[0] >>> 0 ? 1 : 0) | 0;
C[2] = C[2] + 886263092 + (C[1] >>> 0 < C_[1] >>> 0 ? 1 : 0) | 0;
C[3] = C[3] + 1295307597 + (C[2] >>> 0 < C_[2] >>> 0 ? 1 : 0) | 0;
C[4] = C[4] + 3545052371 + (C[3] >>> 0 < C_[3] >>> 0 ? 1 : 0) | 0;
C[5] = C[5] + 886263092 + (C[4] >>> 0 < C_[4] >>> 0 ? 1 : 0) | 0;
C[6] = C[6] + 1295307597 + (C[5] >>> 0 < C_[5] >>> 0 ? 1 : 0) | 0;
C[7] = C[7] + 3545052371 + (C[6] >>> 0 < C_[6] >>> 0 ? 1 : 0) | 0;
this._b = C[7] >>> 0 < C_[7] >>> 0 ? 1 : 0;
for (let i = 0; i < 8; i += 1) {
const gx = X[i] + C[i];
const ga = gx & 65535;
const gb = gx >>> 16;
const gh = ((ga * ga >>> 17) + ga * gb >>> 15) + gb * gb;
const gl = ((gx & 4294901760) * gx | 0) + ((gx & 65535) * gx | 0);
G[i] = gh ^ gl;
}
X[0] = G[0] + (G[7] << 16 | G[7] >>> 16) + (G[6] << 16 | G[6] >>> 16) | 0;
X[1] = G[1] + (G[0] << 8 | G[0] >>> 24) + G[7] | 0;
X[2] = G[2] + (G[1] << 16 | G[1] >>> 16) + (G[0] << 16 | G[0] >>> 16) | 0;
X[3] = G[3] + (G[2] << 8 | G[2] >>> 24) + G[1] | 0;
X[4] = G[4] + (G[3] << 16 | G[3] >>> 16) + (G[2] << 16 | G[2] >>> 16) | 0;
X[5] = G[5] + (G[4] << 8 | G[4] >>> 24) + G[3] | 0;
X[6] = G[6] + (G[5] << 16 | G[5] >>> 16) + (G[4] << 16 | G[4] >>> 16) | 0;
X[7] = G[7] + (G[6] << 8 | G[6] >>> 24) + G[5] | 0;
}
/**
* Rabbit stream cipher algorithm.
*
* This is a legacy version that neglected to convert the key to little-endian.
* This error doesn't affect the cipher's security,
* but it does affect its compatibility with other implementations.
*/
var RabbitLegacyAlgo = class extends StreamCipher {
_X;
_C;
_b;
static ivSize = 64 / 32;
constructor(xformMode, key, cfg) {
super(xformMode, key, cfg);
this.blockSize = 128 / 32;
}
_doReset() {
const K = this._key.words;
const { iv } = this.cfg;
this._X = [
K[0],
K[3] << 16 | K[2] >>> 16,
K[1],
K[0] << 16 | K[3] >>> 16,
K[2],
K[1] << 16 | K[0] >>> 16,
K[3],
K[2] << 16 | K[1] >>> 16
];
const X = this._X;
this._C = [
K[2] << 16 | K[2] >>> 16,
K[0] & 4294901760 | K[1] & 65535,
K[3] << 16 | K[3] >>> 16,
K[1] & 4294901760 | K[2] & 65535,
K[0] << 16 | K[0] >>> 16,
K[2] & 4294901760 | K[3] & 65535,
K[1] << 16 | K[1] >>> 16,
K[3] & 4294901760 | K[0] & 65535
];
const C = this._C;
this._b = 0;
for (let i = 0; i < 4; i += 1) nextState.call(this);
for (let i = 0; i < 8; i += 1) C[i] ^= X[i + 4 & 7];
if (iv) {
const IV = iv.words;
const IV_0 = IV[0];
const IV_1 = IV[1];
const i0 = (IV_0 << 8 | IV_0 >>> 24) & 16711935 | (IV_0 << 24 | IV_0 >>> 8) & 4278255360;
const i2 = (IV_1 << 8 | IV_1 >>> 24) & 16711935 | (IV_1 << 24 | IV_1 >>> 8) & 4278255360;
const i1 = i0 >>> 16 | i2 & 4294901760;
const i3 = i2 << 16 | i0 & 65535;
C[0] ^= i0;
C[1] ^= i1;
C[2] ^= i2;
C[3] ^= i3;
C[4] ^= i0;
C[5] ^= i1;
C[6] ^= i2;
C[7] ^= i3;
for (let i = 0; i < 4; i += 1) nextState.call(this);
}
}
_doProcessBlock(M, offset) {
const _M = M;
const X = this._X;
nextState.call(this);
S[0] = X[0] ^ X[5] >>> 16 ^ X[3] << 16;
S[1] = X[2] ^ X[7] >>> 16 ^ X[5] << 16;
S[2] = X[4] ^ X[1] >>> 16 ^ X[7] << 16;
S[3] = X[6] ^ X[3] >>> 16 ^ X[1] << 16;
for (let i = 0; i < 4; i += 1) {
S[i] = (S[i] << 8 | S[i] >>> 24) & 16711935 | (S[i] << 24 | S[i] >>> 8) & 4278255360;
_M[offset + i] ^= S[i];
}
}
};
/**
* Shortcut functions to the cipher's object interface.
*
* @example
*
* var ciphertext = RabbitLegacy.encrypt(message, key, cfg);
* var plaintext = RabbitLegacy.decrypt(ciphertext, key, cfg);
*/
const RabbitLegacy = StreamCipher._createHelper(RabbitLegacyAlgo);
//#endregion
export { RabbitLegacy, RabbitLegacyAlgo };
//# sourceMappingURL=rabbit-legacy.mjs.map

View File

@@ -1,123 +0,0 @@
import { StreamCipher } from "./cipher-core.mjs";
//#region src/rabbit.ts
const S = [];
const C_ = [];
const G = [];
function nextState() {
const X = this._X;
const C = this._C;
for (let i = 0; i < 8; i += 1) C_[i] = C[i];
C[0] = C[0] + 1295307597 + this._b | 0;
C[1] = C[1] + 3545052371 + (C[0] >>> 0 < C_[0] >>> 0 ? 1 : 0) | 0;
C[2] = C[2] + 886263092 + (C[1] >>> 0 < C_[1] >>> 0 ? 1 : 0) | 0;
C[3] = C[3] + 1295307597 + (C[2] >>> 0 < C_[2] >>> 0 ? 1 : 0) | 0;
C[4] = C[4] + 3545052371 + (C[3] >>> 0 < C_[3] >>> 0 ? 1 : 0) | 0;
C[5] = C[5] + 886263092 + (C[4] >>> 0 < C_[4] >>> 0 ? 1 : 0) | 0;
C[6] = C[6] + 1295307597 + (C[5] >>> 0 < C_[5] >>> 0 ? 1 : 0) | 0;
C[7] = C[7] + 3545052371 + (C[6] >>> 0 < C_[6] >>> 0 ? 1 : 0) | 0;
this._b = C[7] >>> 0 < C_[7] >>> 0 ? 1 : 0;
for (let i = 0; i < 8; i += 1) {
const gx = X[i] + C[i];
const ga = gx & 65535;
const gb = gx >>> 16;
const gh = ((ga * ga >>> 17) + ga * gb >>> 15) + gb * gb;
const gl = ((gx & 4294901760) * gx | 0) + ((gx & 65535) * gx | 0);
G[i] = gh ^ gl;
}
X[0] = G[0] + (G[7] << 16 | G[7] >>> 16) + (G[6] << 16 | G[6] >>> 16) | 0;
X[1] = G[1] + (G[0] << 8 | G[0] >>> 24) + G[7] | 0;
X[2] = G[2] + (G[1] << 16 | G[1] >>> 16) + (G[0] << 16 | G[0] >>> 16) | 0;
X[3] = G[3] + (G[2] << 8 | G[2] >>> 24) + G[1] | 0;
X[4] = G[4] + (G[3] << 16 | G[3] >>> 16) + (G[2] << 16 | G[2] >>> 16) | 0;
X[5] = G[5] + (G[4] << 8 | G[4] >>> 24) + G[3] | 0;
X[6] = G[6] + (G[5] << 16 | G[5] >>> 16) + (G[4] << 16 | G[4] >>> 16) | 0;
X[7] = G[7] + (G[6] << 8 | G[6] >>> 24) + G[5] | 0;
}
/**
* Rabbit stream cipher algorithm
*/
var RabbitAlgo = class extends StreamCipher {
_X;
_C;
_b;
static ivSize = 64 / 32;
constructor(xformMode, key, cfg) {
super(xformMode, key, cfg);
this.blockSize = 128 / 32;
}
_doReset() {
const K = this._key.words;
const { iv } = this.cfg;
for (let i = 0; i < 4; i += 1) K[i] = (K[i] << 8 | K[i] >>> 24) & 16711935 | (K[i] << 24 | K[i] >>> 8) & 4278255360;
this._X = [
K[0],
K[3] << 16 | K[2] >>> 16,
K[1],
K[0] << 16 | K[3] >>> 16,
K[2],
K[1] << 16 | K[0] >>> 16,
K[3],
K[2] << 16 | K[1] >>> 16
];
const X = this._X;
this._C = [
K[2] << 16 | K[2] >>> 16,
K[0] & 4294901760 | K[1] & 65535,
K[3] << 16 | K[3] >>> 16,
K[1] & 4294901760 | K[2] & 65535,
K[0] << 16 | K[0] >>> 16,
K[2] & 4294901760 | K[3] & 65535,
K[1] << 16 | K[1] >>> 16,
K[3] & 4294901760 | K[0] & 65535
];
const C = this._C;
this._b = 0;
for (let i = 0; i < 4; i += 1) nextState.call(this);
for (let i = 0; i < 8; i += 1) C[i] ^= X[i + 4 & 7];
if (iv) {
const IV = iv.words;
const IV_0 = IV[0];
const IV_1 = IV[1];
const i0 = (IV_0 << 8 | IV_0 >>> 24) & 16711935 | (IV_0 << 24 | IV_0 >>> 8) & 4278255360;
const i2 = (IV_1 << 8 | IV_1 >>> 24) & 16711935 | (IV_1 << 24 | IV_1 >>> 8) & 4278255360;
const i1 = i0 >>> 16 | i2 & 4294901760;
const i3 = i2 << 16 | i0 & 65535;
C[0] ^= i0;
C[1] ^= i1;
C[2] ^= i2;
C[3] ^= i3;
C[4] ^= i0;
C[5] ^= i1;
C[6] ^= i2;
C[7] ^= i3;
for (let i = 0; i < 4; i += 1) nextState.call(this);
}
}
_doProcessBlock(M, offset) {
const _M = M;
const X = this._X;
nextState.call(this);
S[0] = X[0] ^ X[5] >>> 16 ^ X[3] << 16;
S[1] = X[2] ^ X[7] >>> 16 ^ X[5] << 16;
S[2] = X[4] ^ X[1] >>> 16 ^ X[7] << 16;
S[3] = X[6] ^ X[3] >>> 16 ^ X[1] << 16;
for (let i = 0; i < 4; i += 1) {
S[i] = (S[i] << 8 | S[i] >>> 24) & 16711935 | (S[i] << 24 | S[i] >>> 8) & 4278255360;
_M[offset + i] ^= S[i];
}
}
};
/**
* Shortcut functions to the cipher's object interface.
*
* @example
*
* var ciphertext = Rabbit.encrypt(message, key, cfg);
* var plaintext = Rabbit.decrypt(ciphertext, key, cfg);
*/
const Rabbit = StreamCipher._createHelper(RabbitAlgo);
//#endregion
export { Rabbit, RabbitAlgo };
//# sourceMappingURL=rabbit.mjs.map

View File

@@ -1,93 +0,0 @@
import { StreamCipher } from "./cipher-core.mjs";
//#region src/rc4.ts
/**
* RC4 stream cipher algorithm.
*/
var RC4Algo = class extends StreamCipher {
static keySize = 256 / 32;
static ivSize = 0;
_S;
_i;
_j;
generateKeystreamWord() {
const S = this._S;
let i = this._i;
let j = this._j;
let keystreamWord = 0;
for (let n = 0; n < 4; n += 1) {
i = (i + 1) % 256;
j = (j + S[i]) % 256;
const t = S[i];
S[i] = S[j];
S[j] = t;
keystreamWord |= S[(S[i] + S[j]) % 256] << 24 - n * 8;
}
this._i = i;
this._j = j;
return keystreamWord;
}
_doReset() {
const key = this._key;
const keyWords = key.words;
const keySigBytes = key.sigBytes;
this._S = [];
const S = this._S;
for (let i = 0; i < 256; i += 1) S[i] = i;
for (let i = 0, j = 0; i < 256; i += 1) {
const keyByteIndex = i % keySigBytes;
const keyByte = keyWords[keyByteIndex >>> 2] >>> 24 - keyByteIndex % 4 * 8 & 255;
j = (j + S[i] + keyByte) % 256;
const t = S[i];
S[i] = S[j];
S[j] = t;
}
this._j = 0;
this._i = this._j;
}
_doProcessBlock(M, offset) {
const _M = M;
_M[offset] ^= this.generateKeystreamWord();
}
};
/**
* Shortcut functions to the cipher's object interface.
*
* @example
*
* var ciphertext = RC4.encrypt(message, key, cfg);
* var plaintext = RC4.decrypt(ciphertext, key, cfg);
*/
const RC4 = StreamCipher._createHelper(RC4Algo);
/**
* Modified RC4 stream cipher algorithm.
*/
var RC4DropAlgo = class extends RC4Algo {
constructor(xformMode, key, cfg) {
super(xformMode, key, cfg);
/**
* Configuration options.
*
* @property {number} drop The number of keystream words to drop. Default 192
*/
if (this.cfg.drop === void 0) this.cfg.drop = 192;
}
_doReset() {
super._doReset();
const dropCount = this.cfg.drop || 192;
for (let i = dropCount; i > 0; i -= 1) this.generateKeystreamWord();
}
};
/**
* Shortcut functions to the cipher's object interface.
*
* @example
*
* var ciphertext = RC4Drop.encrypt(message, key, cfg);
* var plaintext = RC4Drop.decrypt(ciphertext, key, cfg);
*/
const RC4Drop = StreamCipher._createHelper(RC4DropAlgo);
//#endregion
export { RC4, RC4Algo, RC4Drop, RC4DropAlgo };
//# sourceMappingURL=rc4.mjs.map

View File

@@ -1,497 +0,0 @@
import { Hasher, Hasher32, WordArray } from "./core.mjs";
//#region src/ripemd160.ts
const _zl = WordArray.create([
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
7,
4,
13,
1,
10,
6,
15,
3,
12,
0,
9,
5,
2,
14,
11,
8,
3,
10,
14,
4,
9,
15,
8,
1,
2,
7,
0,
6,
13,
11,
5,
12,
1,
9,
11,
10,
0,
8,
12,
4,
13,
3,
7,
15,
14,
5,
6,
2,
4,
0,
5,
9,
7,
12,
2,
10,
14,
1,
3,
8,
11,
6,
15,
13
]);
const _zr = WordArray.create([
5,
14,
7,
0,
9,
2,
11,
4,
13,
6,
15,
8,
1,
10,
3,
12,
6,
11,
3,
7,
0,
13,
5,
10,
14,
15,
8,
12,
4,
9,
1,
2,
15,
5,
1,
3,
7,
14,
6,
9,
11,
8,
12,
2,
10,
0,
4,
13,
8,
6,
4,
1,
3,
11,
15,
0,
5,
12,
2,
13,
9,
7,
10,
14,
12,
15,
10,
4,
1,
5,
8,
7,
6,
2,
13,
14,
0,
3,
9,
11
]);
const _sl = WordArray.create([
11,
14,
15,
12,
5,
8,
7,
9,
11,
13,
14,
15,
6,
7,
9,
8,
7,
6,
8,
13,
11,
9,
7,
15,
7,
12,
15,
9,
11,
7,
13,
12,
11,
13,
6,
7,
14,
9,
13,
15,
14,
8,
13,
6,
5,
12,
7,
5,
11,
12,
14,
15,
14,
15,
9,
8,
9,
14,
5,
6,
8,
6,
5,
12,
9,
15,
5,
11,
6,
8,
13,
12,
5,
12,
13,
14,
11,
8,
5,
6
]);
const _sr = WordArray.create([
8,
9,
9,
11,
13,
15,
15,
5,
7,
7,
8,
11,
14,
14,
12,
6,
9,
13,
15,
7,
12,
8,
9,
11,
7,
7,
12,
7,
6,
15,
13,
11,
9,
7,
15,
11,
8,
6,
6,
14,
12,
13,
5,
14,
13,
13,
7,
5,
15,
5,
8,
11,
14,
14,
6,
14,
6,
9,
12,
9,
12,
5,
15,
8,
8,
5,
12,
9,
12,
5,
14,
6,
8,
13,
6,
5,
15,
13,
11,
11
]);
const _hl = WordArray.create([
0,
1518500249,
1859775393,
2400959708,
2840853838
]);
const _hr = WordArray.create([
1352829926,
1548603684,
1836072691,
2053994217,
0
]);
/**
* RIPEMD160 round function 1
*/
const f1 = (x, y, z) => x ^ y ^ z;
/**
* RIPEMD160 round function 2
*/
const f2 = (x, y, z) => x & y | ~x & z;
/**
* RIPEMD160 round function 3
*/
const f3 = (x, y, z) => (x | ~y) ^ z;
/**
* RIPEMD160 round function 4
*/
const f4 = (x, y, z) => x & z | y & ~z;
/**
* RIPEMD160 round function 5
*/
const f5 = (x, y, z) => x ^ (y | ~z);
/**
* Rotate left helper
*/
const rotl = (x, n) => x << n | x >>> 32 - n;
/**
* RIPEMD160 hash algorithm.
*/
var RIPEMD160Algo = class extends Hasher32 {
_doReset() {
this._hash = WordArray.create([
1732584193,
4023233417,
2562383102,
271733878,
3285377520
]);
}
_doProcessBlock(M, offset) {
const _M = M;
for (let i = 0; i < 16; i += 1) {
const offset_i = offset + i;
const M_offset_i = _M[offset_i];
_M[offset_i] = (M_offset_i << 8 | M_offset_i >>> 24) & 16711935 | (M_offset_i << 24 | M_offset_i >>> 8) & 4278255360;
}
const H = this._hash.words;
const hl = _hl.words;
const hr = _hr.words;
const zl = _zl.words;
const zr = _zr.words;
const sl = _sl.words;
const sr = _sr.words;
let al = H[0];
let bl = H[1];
let cl = H[2];
let dl = H[3];
let el = H[4];
let ar = H[0];
let br = H[1];
let cr = H[2];
let dr = H[3];
let er = H[4];
let t;
for (let i = 0; i < 80; i += 1) {
t = al + _M[offset + zl[i]] | 0;
if (i < 16) t += f1(bl, cl, dl) + hl[0];
else if (i < 32) t += f2(bl, cl, dl) + hl[1];
else if (i < 48) t += f3(bl, cl, dl) + hl[2];
else if (i < 64) t += f4(bl, cl, dl) + hl[3];
else t += f5(bl, cl, dl) + hl[4];
t |= 0;
t = rotl(t, sl[i]);
t = t + el | 0;
al = el;
el = dl;
dl = rotl(cl, 10);
cl = bl;
bl = t;
t = ar + _M[offset + zr[i]] | 0;
if (i < 16) t += f5(br, cr, dr) + hr[0];
else if (i < 32) t += f4(br, cr, dr) + hr[1];
else if (i < 48) t += f3(br, cr, dr) + hr[2];
else if (i < 64) t += f2(br, cr, dr) + hr[3];
else t += f1(br, cr, dr) + hr[4];
t |= 0;
t = rotl(t, sr[i]);
t = t + er | 0;
ar = er;
er = dr;
dr = rotl(cr, 10);
cr = br;
br = t;
}
t = H[1] + cl + dr | 0;
H[1] = H[2] + dl + er | 0;
H[2] = H[3] + el + ar | 0;
H[3] = H[4] + al + br | 0;
H[4] = H[0] + bl + cr | 0;
H[0] = t;
}
_doFinalize() {
const data = this._data;
const dataWords = data.words;
const nBitsTotal = this._nDataBytes * 8;
const nBitsLeft = data.sigBytes * 8;
dataWords[nBitsLeft >>> 5] |= 128 << 24 - nBitsLeft % 32;
dataWords[(nBitsLeft + 64 >>> 9 << 4) + 14] = (nBitsTotal << 8 | nBitsTotal >>> 24) & 16711935 | (nBitsTotal << 24 | nBitsTotal >>> 8) & 4278255360;
data.sigBytes = (dataWords.length + 1) * 4;
this._process();
const hash = this._hash;
const H = hash.words;
for (let i = 0; i < 5; i += 1) {
const H_i = H[i];
H[i] = (H_i << 8 | H_i >>> 24) & 16711935 | (H_i << 24 | H_i >>> 8) & 4278255360;
}
return hash;
}
clone() {
const clone = super.clone.call(this);
clone._hash = this._hash.clone();
return clone;
}
};
/**
* Shortcut function to the hasher's object interface.
*
* @param message - The message to hash.
* @returns The hash.
*
* @example
* ```js
* const hash = RIPEMD160('message');
* const hash = RIPEMD160(wordArray);
* ```
*/
const RIPEMD160 = Hasher._createHelper(RIPEMD160Algo);
/**
* Shortcut function to the HMAC's object interface.
*
* @param message - The message to hash.
* @param key - The secret key.
* @returns The HMAC.
*
* @example
* ```js
* const hmac = HmacRIPEMD160(message, key);
* ```
*/
const HmacRIPEMD160 = Hasher._createHmacHelper(RIPEMD160Algo);
//#endregion
export { HmacRIPEMD160, RIPEMD160, RIPEMD160Algo };
//# sourceMappingURL=ripemd160.mjs.map

View File

@@ -1,95 +0,0 @@
import { Hasher, Hasher32, WordArray } from "./core.mjs";
//#region src/sha1.ts
const W = [];
/**
* SHA-1 hash algorithm.
*/
var SHA1Algo = class extends Hasher32 {
_doReset() {
this._hash = new WordArray([
1732584193,
4023233417,
2562383102,
271733878,
3285377520
]);
}
_doProcessBlock(M, offset) {
const H = this._hash.words;
let a = H[0];
let b = H[1];
let c = H[2];
let d = H[3];
let e = H[4];
for (let i = 0; i < 80; i += 1) {
if (i < 16) W[i] = M[offset + i] | 0;
else {
const n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16];
W[i] = n << 1 | n >>> 31;
}
let t = (a << 5 | a >>> 27) + e + W[i];
if (i < 20) t += (b & c | ~b & d) + 1518500249;
else if (i < 40) t += (b ^ c ^ d) + 1859775393;
else if (i < 60) t += (b & c | b & d | c & d) - 1894007588;
else t += (b ^ c ^ d) - 899497514;
e = d;
d = c;
c = b << 30 | b >>> 2;
b = a;
a = t;
}
H[0] = H[0] + a | 0;
H[1] = H[1] + b | 0;
H[2] = H[2] + c | 0;
H[3] = H[3] + d | 0;
H[4] = H[4] + e | 0;
}
_doFinalize() {
const data = this._data;
const dataWords = data.words;
const nBitsTotal = this._nDataBytes * 8;
const nBitsLeft = data.sigBytes * 8;
dataWords[nBitsLeft >>> 5] |= 128 << 24 - nBitsLeft % 32;
dataWords[(nBitsLeft + 64 >>> 9 << 4) + 14] = Math.floor(nBitsTotal / 4294967296);
dataWords[(nBitsLeft + 64 >>> 9 << 4) + 15] = nBitsTotal;
data.sigBytes = dataWords.length * 4;
this._process();
return this._hash;
}
clone() {
const clone = super.clone.call(this);
clone._hash = this._hash.clone();
return clone;
}
};
/**
* Shortcut function to the hasher's object interface.
*
* @param message - The message to hash.
* @returns The hash.
*
* @example
* ```js
* const hash = SHA1('message');
* const hash = SHA1(wordArray);
* ```
*/
const SHA1 = Hasher._createHelper(SHA1Algo);
/**
* Shortcut function to the HMAC's object interface.
*
* @param message - The message to hash.
* @param key - The secret key.
* @returns The HMAC.
*
* @example
* ```js
* const hmac = HmacSHA1(message, key);
* ```
*/
const HmacSHA1 = Hasher._createHmacHelper(SHA1Algo);
//#endregion
export { HmacSHA1, SHA1, SHA1Algo };
//# sourceMappingURL=sha1.mjs.map

View File

@@ -1,60 +0,0 @@
import { WordArray } from "./core.mjs";
import { SHA256Algo } from "./sha256.mjs";
//#region src/sha224.ts
/**
* SHA-224 hash algorithm.
*/
var SHA224Algo = class extends SHA256Algo {
_doReset() {
this._hash = new WordArray([
3238371032,
914150663,
812702999,
4144912697,
4290775857,
1750603025,
1694076839,
3204075428
]);
}
_doFinalize() {
const hash = super._doFinalize.call(this);
hash.sigBytes -= 4;
return hash;
}
clone() {
const clone = super.clone.call(this);
return clone;
}
};
/**
* Shortcut function to the hasher's object interface.
*
* @param message - The message to hash.
* @returns The hash.
*
* @example
* ```js
* const hash = SHA224('message');
* const hash = SHA224(wordArray);
* ```
*/
const SHA224 = SHA256Algo._createHelper(SHA224Algo);
/**
* Shortcut function to the HMAC's object interface.
*
* @param message - The message to hash.
* @param key - The secret key.
* @returns The HMAC.
*
* @example
* ```js
* const hmac = HmacSHA224(message, key);
* ```
*/
const HmacSHA224 = SHA256Algo._createHmacHelper(SHA224Algo);
//#endregion
export { HmacSHA224, SHA224, SHA224Algo };
//# sourceMappingURL=sha224.mjs.map

View File

@@ -1,126 +0,0 @@
import { Hasher, Hasher32, WordArray } from "./core.mjs";
//#region src/sha256.ts
const { H, K } = /* @__PURE__ */ (() => {
const _H = [];
const _K = [];
const isPrime = (n$1) => {
const sqrtN = Math.sqrt(n$1);
for (let factor = 2; factor <= sqrtN; factor += 1) if (!(n$1 % factor)) return false;
return true;
};
const getFractionalBits = (n$1) => (n$1 - (n$1 | 0)) * 4294967296 | 0;
let n = 2;
let nPrime = 0;
while (nPrime < 64) {
if (isPrime(n)) {
if (nPrime < 8) _H[nPrime] = getFractionalBits(n ** (1 / 2));
_K[nPrime] = getFractionalBits(n ** (1 / 3));
nPrime += 1;
}
n += 1;
}
return {
H: _H,
K: _K
};
})();
const W = [];
/**
* SHA-256 hash algorithm.
*/
var SHA256Algo = class extends Hasher32 {
_doReset() {
this._hash = new WordArray(H.slice(0));
}
_doProcessBlock(M, offset) {
const _H = this._hash.words;
let a = _H[0];
let b = _H[1];
let c = _H[2];
let d = _H[3];
let e = _H[4];
let f = _H[5];
let g = _H[6];
let h = _H[7];
for (let i = 0; i < 64; i += 1) {
if (i < 16) W[i] = M[offset + i] | 0;
else {
const gamma0x = W[i - 15];
const gamma0 = (gamma0x << 25 | gamma0x >>> 7) ^ (gamma0x << 14 | gamma0x >>> 18) ^ gamma0x >>> 3;
const gamma1x = W[i - 2];
const gamma1 = (gamma1x << 15 | gamma1x >>> 17) ^ (gamma1x << 13 | gamma1x >>> 19) ^ gamma1x >>> 10;
W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16];
}
const ch = e & f ^ ~e & g;
const maj = a & b ^ a & c ^ b & c;
const sigma0 = (a << 30 | a >>> 2) ^ (a << 19 | a >>> 13) ^ (a << 10 | a >>> 22);
const sigma1 = (e << 26 | e >>> 6) ^ (e << 21 | e >>> 11) ^ (e << 7 | e >>> 25);
const t1 = h + sigma1 + ch + K[i] + W[i];
const t2 = sigma0 + maj;
h = g;
g = f;
f = e;
e = d + t1 | 0;
d = c;
c = b;
b = a;
a = t1 + t2 | 0;
}
_H[0] = _H[0] + a | 0;
_H[1] = _H[1] + b | 0;
_H[2] = _H[2] + c | 0;
_H[3] = _H[3] + d | 0;
_H[4] = _H[4] + e | 0;
_H[5] = _H[5] + f | 0;
_H[6] = _H[6] + g | 0;
_H[7] = _H[7] + h | 0;
}
_doFinalize() {
const data = this._data;
const dataWords = data.words;
const nBitsTotal = this._nDataBytes * 8;
const nBitsLeft = data.sigBytes * 8;
dataWords[nBitsLeft >>> 5] |= 128 << 24 - nBitsLeft % 32;
dataWords[(nBitsLeft + 64 >>> 9 << 4) + 14] = Math.floor(nBitsTotal / 4294967296);
dataWords[(nBitsLeft + 64 >>> 9 << 4) + 15] = nBitsTotal;
data.sigBytes = dataWords.length * 4;
this._process();
return this._hash;
}
clone() {
const clone = super.clone.call(this);
clone._hash = this._hash.clone();
return clone;
}
};
/**
* Shortcut function to the hasher's object interface.
*
* @param message - The message to hash.
* @returns The hash.
*
* @example
* ```js
* const hash = SHA256('message');
* const hash = SHA256(wordArray);
* ```
*/
const SHA256 = Hasher._createHelper(SHA256Algo);
/**
* Shortcut function to the HMAC's object interface.
*
* @param message - The message to hash.
* @param key - The secret key.
* @returns The HMAC.
*
* @example
* ```js
* const hmac = HmacSHA256(message, key);
* ```
*/
const HmacSHA256 = Hasher._createHmacHelper(SHA256Algo);
//#endregion
export { HmacSHA256, SHA256, SHA256Algo };
//# sourceMappingURL=sha256.mjs.map

View File

@@ -1,170 +0,0 @@
import { Hasher, Hasher32, WordArray } from "./core.mjs";
import { X64Word } from "./x64-core.mjs";
//#region src/sha3.ts
const RHO_OFFSETS = [];
const PI_INDEXES = [];
const ROUND_CONSTANTS = [];
const T = /* @__PURE__ */ (() => {
const a = [];
for (let i = 0; i < 25; i += 1) a[i] = X64Word.create();
return a;
})();
/**
* SHA-3 hash algorithm.
*/
var SHA3Algo = class extends Hasher32 {
_state = [];
/**
* Initializes a newly created hasher.
*
* @param cfg - Configuration options.
* @property {number} outputLength - The desired number of bits in the output hash.
* Only values permitted are: 224, 256, 384, 512.
* Default: 512
*/
constructor(cfg) {
super(Object.assign({ outputLength: 512 }, cfg));
}
_doReset() {
this._state = [];
for (let i = 0; i < 25; i += 1) this._state[i] = new X64Word();
this.blockSize = (1600 - 2 * this.cfg.outputLength) / 32;
}
_doProcessBlock(M, offset) {
if (this._state.length === 0) this._doReset();
const state = this._state;
const nBlockSizeLanes = this.blockSize / 2;
for (let i = 0; i < nBlockSizeLanes; i += 1) {
let M2i = M[offset + 2 * i];
let M2i1 = M[offset + 2 * i + 1];
M2i = (M2i << 8 | M2i >>> 24) & 16711935 | (M2i << 24 | M2i >>> 8) & 4278255360;
M2i1 = (M2i1 << 8 | M2i1 >>> 24) & 16711935 | (M2i1 << 24 | M2i1 >>> 8) & 4278255360;
const lane = state[i];
lane.high ^= M2i1;
lane.low ^= M2i;
}
for (let round = 0; round < 24; round += 1) {
for (let x = 0; x < 5; x += 1) {
let tMsw = 0;
let tLsw = 0;
for (let y = 0; y < 5; y += 1) {
const lane$1 = state[x + 5 * y];
tMsw ^= lane$1.high;
tLsw ^= lane$1.low;
}
const Tx = T[x];
Tx.high = tMsw;
Tx.low = tLsw;
}
for (let x = 0; x < 5; x += 1) {
const Tx4 = T[(x + 4) % 5];
const Tx1 = T[(x + 1) % 5];
const Tx1Msw = Tx1.high;
const Tx1Lsw = Tx1.low;
const tMsw = Tx4.high ^ (Tx1Msw << 1 | Tx1Lsw >>> 31);
const tLsw = Tx4.low ^ (Tx1Lsw << 1 | Tx1Msw >>> 31);
for (let y = 0; y < 5; y += 1) {
const lane$1 = state[x + 5 * y];
lane$1.high ^= tMsw;
lane$1.low ^= tLsw;
}
}
for (let laneIndex = 1; laneIndex < 25; laneIndex += 1) {
let tMsw;
let tLsw;
const lane$1 = state[laneIndex];
const laneMsw = lane$1.high;
const laneLsw = lane$1.low;
const rhoOffset = RHO_OFFSETS[laneIndex];
if (rhoOffset < 32) {
tMsw = laneMsw << rhoOffset | laneLsw >>> 32 - rhoOffset;
tLsw = laneLsw << rhoOffset | laneMsw >>> 32 - rhoOffset;
} else {
tMsw = laneLsw << rhoOffset - 32 | laneMsw >>> 64 - rhoOffset;
tLsw = laneMsw << rhoOffset - 32 | laneLsw >>> 64 - rhoOffset;
}
const TPiLane = T[PI_INDEXES[laneIndex]];
TPiLane.high = tMsw;
TPiLane.low = tLsw;
}
const T0 = T[0];
const state0 = state[0];
T0.high = state0.high;
T0.low = state0.low;
for (let x = 0; x < 5; x += 1) for (let y = 0; y < 5; y += 1) {
const laneIndex = x + 5 * y;
const lane$1 = state[laneIndex];
const TLane = T[laneIndex];
const Tx1Lane = T[(x + 1) % 5 + 5 * y];
const Tx2Lane = T[(x + 2) % 5 + 5 * y];
lane$1.high = TLane.high ^ ~Tx1Lane.high & Tx2Lane.high;
lane$1.low = TLane.low ^ ~Tx1Lane.low & Tx2Lane.low;
}
const lane = state[0];
const roundConstant = ROUND_CONSTANTS[round];
lane.high ^= roundConstant.high;
lane.low ^= roundConstant.low;
}
}
_doFinalize() {
const data = this._data;
const dataWords = data.words;
const nBitsLeft = data.sigBytes * 8;
const blockSizeBits = this.blockSize * 32;
dataWords[nBitsLeft >>> 5] |= 1 << 24 - nBitsLeft % 32;
dataWords[(Math.ceil((nBitsLeft + 1) / blockSizeBits) * blockSizeBits >>> 5) - 1] |= 128;
data.sigBytes = dataWords.length * 4;
this._process();
const state = this._state;
const outputLengthBytes = this.cfg.outputLength / 8;
const outputLengthLanes = outputLengthBytes / 8;
const hashWords = [];
for (let i = 0; i < outputLengthLanes; i += 1) {
const lane = state[i];
let laneMsw = lane.high;
let laneLsw = lane.low;
laneMsw = (laneMsw << 8 | laneMsw >>> 24) & 16711935 | (laneMsw << 24 | laneMsw >>> 8) & 4278255360;
laneLsw = (laneLsw << 8 | laneLsw >>> 24) & 16711935 | (laneLsw << 24 | laneLsw >>> 8) & 4278255360;
hashWords.push(laneLsw);
hashWords.push(laneMsw);
}
return new WordArray(hashWords, outputLengthBytes);
}
clone() {
const clone = super.clone.call(this);
clone._state = [];
for (let i = 0; i < this._state.length; i += 1) clone._state[i] = this._state[i].clone();
return clone;
}
};
/**
* Shortcut function to the hasher's object interface.
*
* @param message - The message to hash.
* @returns The hash.
*
* @example
* ```js
* const hash = SHA3('message');
* const hash = SHA3(wordArray);
* ```
*/
const SHA3 = Hasher._createHelper(SHA3Algo);
/**
* Shortcut function to the HMAC's object interface.
*
* @param message - The message to hash.
* @param key - The secret key.
* @returns The HMAC.
*
* @example
* ```js
* const hmac = HmacSHA3(message, key);
* ```
*/
const HmacSHA3 = Hasher._createHmacHelper(SHA3Algo);
//#endregion
export { HmacSHA3, SHA3, SHA3Algo };
//# sourceMappingURL=sha3.mjs.map

View File

@@ -1,60 +0,0 @@
import { X64Word, X64WordArray } from "./x64-core.mjs";
import { SHA512Algo } from "./sha512.mjs";
//#region src/sha384.ts
/**
* SHA-384 hash algorithm.
*/
var SHA384Algo = class extends SHA512Algo {
_doReset() {
this._hash = new X64WordArray([
new X64Word(3418070365, 3238371032),
new X64Word(1654270250, 914150663),
new X64Word(2438529370, 812702999),
new X64Word(355462360, 4144912697),
new X64Word(1731405415, 4290775857),
new X64Word(2394180231, 1750603025),
new X64Word(3675008525, 1694076839),
new X64Word(1203062813, 3204075428)
]);
}
_doFinalize() {
const hash = super._doFinalize.call(this);
hash.sigBytes -= 16;
return hash;
}
clone() {
const clone = super.clone.call(this);
return clone;
}
};
/**
* Shortcut function to the hasher's object interface.
*
* @param message - The message to hash.
* @returns The hash.
*
* @example
* ```js
* const hash = SHA384('message');
* const hash = SHA384(wordArray);
* ```
*/
const SHA384 = SHA512Algo._createHelper(SHA384Algo);
/**
* Shortcut function to the HMAC's object interface.
*
* @param message - The message to hash.
* @param key - The secret key.
* @returns The HMAC.
*
* @example
* ```js
* const hmac = HmacSHA384(message, key);
* ```
*/
const HmacSHA384 = SHA512Algo._createHmacHelper(SHA384Algo);
//#endregion
export { HmacSHA384, SHA384, SHA384Algo };
//# sourceMappingURL=sha384.mjs.map

View File

@@ -1,300 +0,0 @@
import { Hasher, Hasher64 } from "./core.mjs";
import { X64Word, X64WordArray } from "./x64-core.mjs";
//#region src/sha512.ts
const K = [
new X64Word(1116352408, 3609767458),
new X64Word(1899447441, 602891725),
new X64Word(3049323471, 3964484399),
new X64Word(3921009573, 2173295548),
new X64Word(961987163, 4081628472),
new X64Word(1508970993, 3053834265),
new X64Word(2453635748, 2937671579),
new X64Word(2870763221, 3664609560),
new X64Word(3624381080, 2734883394),
new X64Word(310598401, 1164996542),
new X64Word(607225278, 1323610764),
new X64Word(1426881987, 3590304994),
new X64Word(1925078388, 4068182383),
new X64Word(2162078206, 991336113),
new X64Word(2614888103, 633803317),
new X64Word(3248222580, 3479774868),
new X64Word(3835390401, 2666613458),
new X64Word(4022224774, 944711139),
new X64Word(264347078, 2341262773),
new X64Word(604807628, 2007800933),
new X64Word(770255983, 1495990901),
new X64Word(1249150122, 1856431235),
new X64Word(1555081692, 3175218132),
new X64Word(1996064986, 2198950837),
new X64Word(2554220882, 3999719339),
new X64Word(2821834349, 766784016),
new X64Word(2952996808, 2566594879),
new X64Word(3210313671, 3203337956),
new X64Word(3336571891, 1034457026),
new X64Word(3584528711, 2466948901),
new X64Word(113926993, 3758326383),
new X64Word(338241895, 168717936),
new X64Word(666307205, 1188179964),
new X64Word(773529912, 1546045734),
new X64Word(1294757372, 1522805485),
new X64Word(1396182291, 2643833823),
new X64Word(1695183700, 2343527390),
new X64Word(1986661051, 1014477480),
new X64Word(2177026350, 1206759142),
new X64Word(2456956037, 344077627),
new X64Word(2730485921, 1290863460),
new X64Word(2820302411, 3158454273),
new X64Word(3259730800, 3505952657),
new X64Word(3345764771, 106217008),
new X64Word(3516065817, 3606008344),
new X64Word(3600352804, 1432725776),
new X64Word(4094571909, 1467031594),
new X64Word(275423344, 851169720),
new X64Word(430227734, 3100823752),
new X64Word(506948616, 1363258195),
new X64Word(659060556, 3750685593),
new X64Word(883997877, 3785050280),
new X64Word(958139571, 3318307427),
new X64Word(1322822218, 3812723403),
new X64Word(1537002063, 2003034995),
new X64Word(1747873779, 3602036899),
new X64Word(1955562222, 1575990012),
new X64Word(2024104815, 1125592928),
new X64Word(2227730452, 2716904306),
new X64Word(2361852424, 442776044),
new X64Word(2428436474, 593698344),
new X64Word(2756734187, 3733110249),
new X64Word(3204031479, 2999351573),
new X64Word(3329325298, 3815920427),
new X64Word(3391569614, 3928383900),
new X64Word(3515267271, 566280711),
new X64Word(3940187606, 3454069534),
new X64Word(4118630271, 4000239992),
new X64Word(116418474, 1914138554),
new X64Word(174292421, 2731055270),
new X64Word(289380356, 3203993006),
new X64Word(460393269, 320620315),
new X64Word(685471733, 587496836),
new X64Word(852142971, 1086792851),
new X64Word(1017036298, 365543100),
new X64Word(1126000580, 2618297676),
new X64Word(1288033470, 3409855158),
new X64Word(1501505948, 4234509866),
new X64Word(1607167915, 987167468),
new X64Word(1816402316, 1246189591)
];
const W = /* @__PURE__ */ (() => {
const a = [];
for (let i = 0; i < 80; i += 1) a[i] = new X64Word();
return a;
})();
/**
* SHA-512 hash algorithm.
*/
var SHA512Algo = class extends Hasher64 {
constructor(cfg) {
super(cfg);
this.blockSize = 1024 / 32;
}
_doReset() {
this._hash = new X64WordArray([
new X64Word(1779033703, 4089235720),
new X64Word(3144134277, 2227873595),
new X64Word(1013904242, 4271175723),
new X64Word(2773480762, 1595750129),
new X64Word(1359893119, 2917565137),
new X64Word(2600822924, 725511199),
new X64Word(528734635, 4215389547),
new X64Word(1541459225, 327033209)
]);
}
_doProcessBlock(M, offset) {
const H = this._hash.words;
const H0 = H[0];
const H1 = H[1];
const H2 = H[2];
const H3 = H[3];
const H4 = H[4];
const H5 = H[5];
const H6 = H[6];
const H7 = H[7];
const H0h = H0.high;
let H0l = H0.low;
const H1h = H1.high;
let H1l = H1.low;
const H2h = H2.high;
let H2l = H2.low;
const H3h = H3.high;
let H3l = H3.low;
const H4h = H4.high;
let H4l = H4.low;
const H5h = H5.high;
let H5l = H5.low;
const H6h = H6.high;
let H6l = H6.low;
const H7h = H7.high;
let H7l = H7.low;
let ah = H0h;
let al = H0l;
let bh = H1h;
let bl = H1l;
let ch = H2h;
let cl = H2l;
let dh = H3h;
let dl = H3l;
let eh = H4h;
let el = H4l;
let fh = H5h;
let fl = H5l;
let gh = H6h;
let gl = H6l;
let hh = H7h;
let hl = H7l;
for (let i = 0; i < 80; i += 1) {
let Wil;
let Wih;
const Wi = W[i];
if (i < 16) {
Wi.high = M[offset + i * 2] | 0;
Wih = Wi.high;
Wi.low = M[offset + i * 2 + 1] | 0;
Wil = Wi.low;
} else {
const gamma0x = W[i - 15];
const gamma0xh = gamma0x.high;
const gamma0xl = gamma0x.low;
const gamma0h = (gamma0xh >>> 1 | gamma0xl << 31) ^ (gamma0xh >>> 8 | gamma0xl << 24) ^ gamma0xh >>> 7;
const gamma0l = (gamma0xl >>> 1 | gamma0xh << 31) ^ (gamma0xl >>> 8 | gamma0xh << 24) ^ (gamma0xl >>> 7 | gamma0xh << 25);
const gamma1x = W[i - 2];
const gamma1xh = gamma1x.high;
const gamma1xl = gamma1x.low;
const gamma1h = (gamma1xh >>> 19 | gamma1xl << 13) ^ (gamma1xh << 3 | gamma1xl >>> 29) ^ gamma1xh >>> 6;
const gamma1l = (gamma1xl >>> 19 | gamma1xh << 13) ^ (gamma1xl << 3 | gamma1xh >>> 29) ^ (gamma1xl >>> 6 | gamma1xh << 26);
const Wi7 = W[i - 7];
const Wi7h = Wi7.high;
const Wi7l = Wi7.low;
const Wi16 = W[i - 16];
const Wi16h = Wi16.high;
const Wi16l = Wi16.low;
Wil = gamma0l + Wi7l;
Wih = gamma0h + Wi7h + (Wil >>> 0 < gamma0l >>> 0 ? 1 : 0);
Wil += gamma1l;
Wih = Wih + gamma1h + (Wil >>> 0 < gamma1l >>> 0 ? 1 : 0);
Wil += Wi16l;
Wih = Wih + Wi16h + (Wil >>> 0 < Wi16l >>> 0 ? 1 : 0);
Wi.high = Wih;
Wi.low = Wil;
}
const chh = eh & fh ^ ~eh & gh;
const chl = el & fl ^ ~el & gl;
const majh = ah & bh ^ ah & ch ^ bh & ch;
const majl = al & bl ^ al & cl ^ bl & cl;
const sigma0h = (ah >>> 28 | al << 4) ^ (ah << 30 | al >>> 2) ^ (ah << 25 | al >>> 7);
const sigma0l = (al >>> 28 | ah << 4) ^ (al << 30 | ah >>> 2) ^ (al << 25 | ah >>> 7);
const sigma1h = (eh >>> 14 | el << 18) ^ (eh >>> 18 | el << 14) ^ (eh << 23 | el >>> 9);
const sigma1l = (el >>> 14 | eh << 18) ^ (el >>> 18 | eh << 14) ^ (el << 23 | eh >>> 9);
const Ki = K[i];
const Kih = Ki.high;
const Kil = Ki.low;
let t1l = hl + sigma1l;
let t1h = hh + sigma1h + (t1l >>> 0 < hl >>> 0 ? 1 : 0);
t1l += chl;
t1h = t1h + chh + (t1l >>> 0 < chl >>> 0 ? 1 : 0);
t1l += Kil;
t1h = t1h + Kih + (t1l >>> 0 < Kil >>> 0 ? 1 : 0);
t1l += Wil;
t1h = t1h + Wih + (t1l >>> 0 < Wil >>> 0 ? 1 : 0);
const t2l = sigma0l + majl;
const t2h = sigma0h + majh + (t2l >>> 0 < sigma0l >>> 0 ? 1 : 0);
hh = gh;
hl = gl;
gh = fh;
gl = fl;
fh = eh;
fl = el;
el = dl + t1l | 0;
eh = dh + t1h + (el >>> 0 < dl >>> 0 ? 1 : 0) | 0;
dh = ch;
dl = cl;
ch = bh;
cl = bl;
bh = ah;
bl = al;
al = t1l + t2l | 0;
ah = t1h + t2h + (al >>> 0 < t1l >>> 0 ? 1 : 0) | 0;
}
H0.low = H0l + al;
H0l = H0.low;
H0.high = H0h + ah + (H0l >>> 0 < al >>> 0 ? 1 : 0);
H1.low = H1l + bl;
H1l = H1.low;
H1.high = H1h + bh + (H1l >>> 0 < bl >>> 0 ? 1 : 0);
H2.low = H2l + cl;
H2l = H2.low;
H2.high = H2h + ch + (H2l >>> 0 < cl >>> 0 ? 1 : 0);
H3.low = H3l + dl;
H3l = H3.low;
H3.high = H3h + dh + (H3l >>> 0 < dl >>> 0 ? 1 : 0);
H4.low = H4l + el;
H4l = H4.low;
H4.high = H4h + eh + (H4l >>> 0 < el >>> 0 ? 1 : 0);
H5.low = H5l + fl;
H5l = H5.low;
H5.high = H5h + fh + (H5l >>> 0 < fl >>> 0 ? 1 : 0);
H6.low = H6l + gl;
H6l = H6.low;
H6.high = H6h + gh + (H6l >>> 0 < gl >>> 0 ? 1 : 0);
H7.low = H7l + hl;
H7l = H7.low;
H7.high = H7h + hh + (H7l >>> 0 < hl >>> 0 ? 1 : 0);
}
_doFinalize() {
const data = this._data;
const dataWords = data.words;
const nBitsTotal = this._nDataBytes * 8;
const nBitsLeft = data.sigBytes * 8;
dataWords[nBitsLeft >>> 5] |= 128 << 24 - nBitsLeft % 32;
dataWords[(nBitsLeft + 128 >>> 10 << 5) + 30] = Math.floor(nBitsTotal / 4294967296);
dataWords[(nBitsLeft + 128 >>> 10 << 5) + 31] = nBitsTotal;
data.sigBytes = dataWords.length * 4;
this._process();
const hash = this._hash.toX32();
return hash;
}
clone() {
const clone = super.clone.call(this);
clone._hash = this._hash.clone();
return clone;
}
};
/**
* Shortcut function to the hasher's object interface.
*
* @param message - The message to hash.
* @returns The hash.
*
* @example
* ```js
* const hash = SHA512('message');
* const hash = SHA512(wordArray);
* ```
*/
const SHA512 = Hasher._createHelper(SHA512Algo);
/**
* Shortcut function to the HMAC's object interface.
*
* @param message - The message to hash.
* @param key - The secret key.
* @returns The HMAC.
*
* @example
* ```js
* const hmac = HmacSHA512(message, key);
* ```
*/
const HmacSHA512 = Hasher._createHmacHelper(SHA512Algo);
//#endregion
export { HmacSHA512, SHA512, SHA512Algo };
//# sourceMappingURL=sha512.mjs.map

View File

@@ -1,822 +0,0 @@
import { WordArray } from "./core.mjs";
import { BlockCipher } from "./cipher-core.mjs";
//#region src/tripledes.ts
const PC1 = [
57,
49,
41,
33,
25,
17,
9,
1,
58,
50,
42,
34,
26,
18,
10,
2,
59,
51,
43,
35,
27,
19,
11,
3,
60,
52,
44,
36,
63,
55,
47,
39,
31,
23,
15,
7,
62,
54,
46,
38,
30,
22,
14,
6,
61,
53,
45,
37,
29,
21,
13,
5,
28,
20,
12,
4
];
const PC2 = [
14,
17,
11,
24,
1,
5,
3,
28,
15,
6,
21,
10,
23,
19,
12,
4,
26,
8,
16,
7,
27,
20,
13,
2,
41,
52,
31,
37,
47,
55,
30,
40,
51,
45,
33,
48,
44,
49,
39,
56,
34,
53,
46,
42,
50,
36,
29,
32
];
const BIT_SHIFTS = [
1,
2,
4,
6,
8,
10,
12,
14,
15,
17,
19,
21,
23,
25,
27,
28
];
const SBOX_P = [
{
0: 8421888,
268435456: 32768,
536870912: 8421378,
805306368: 2,
1073741824: 512,
1342177280: 8421890,
1610612736: 8389122,
1879048192: 8388608,
2147483648: 514,
2415919104: 8389120,
2684354560: 33280,
2952790016: 8421376,
3221225472: 32770,
3489660928: 8388610,
3758096384: 0,
4026531840: 33282,
134217728: 0,
402653184: 8421890,
671088640: 33282,
939524096: 32768,
1207959552: 8421888,
1476395008: 512,
1744830464: 8421378,
2013265920: 2,
2281701376: 8389120,
2550136832: 33280,
2818572288: 8421376,
3087007744: 8389122,
3355443200: 8388610,
3623878656: 32770,
3892314112: 514,
4160749568: 8388608,
1: 32768,
268435457: 2,
536870913: 8421888,
805306369: 8388608,
1073741825: 8421378,
1342177281: 33280,
1610612737: 512,
1879048193: 8389122,
2147483649: 8421890,
2415919105: 8421376,
2684354561: 8388610,
2952790017: 33282,
3221225473: 514,
3489660929: 8389120,
3758096385: 32770,
4026531841: 0,
134217729: 8421890,
402653185: 8421376,
671088641: 8388608,
939524097: 512,
1207959553: 32768,
1476395009: 8388610,
1744830465: 2,
2013265921: 33282,
2281701377: 32770,
2550136833: 8389122,
2818572289: 514,
3087007745: 8421888,
3355443201: 8389120,
3623878657: 0,
3892314113: 33280,
4160749569: 8421378
},
{
0: 1074282512,
16777216: 16384,
33554432: 524288,
50331648: 1074266128,
67108864: 1073741840,
83886080: 1074282496,
100663296: 1073758208,
117440512: 16,
134217728: 540672,
150994944: 1073758224,
167772160: 1073741824,
184549376: 540688,
201326592: 524304,
218103808: 0,
234881024: 16400,
251658240: 1074266112,
8388608: 1073758208,
25165824: 540688,
41943040: 16,
58720256: 1073758224,
75497472: 1074282512,
92274688: 1073741824,
109051904: 524288,
125829120: 1074266128,
142606336: 524304,
159383552: 0,
176160768: 16384,
192937984: 1074266112,
209715200: 1073741840,
226492416: 540672,
243269632: 1074282496,
260046848: 16400,
268435456: 0,
285212672: 1074266128,
301989888: 1073758224,
318767104: 1074282496,
335544320: 1074266112,
352321536: 16,
369098752: 540688,
385875968: 16384,
402653184: 16400,
419430400: 524288,
436207616: 524304,
452984832: 1073741840,
469762048: 540672,
486539264: 1073758208,
503316480: 1073741824,
520093696: 1074282512,
276824064: 540688,
293601280: 524288,
310378496: 1074266112,
327155712: 16384,
343932928: 1073758208,
360710144: 1074282512,
377487360: 16,
394264576: 1073741824,
411041792: 1074282496,
427819008: 1073741840,
444596224: 1073758224,
461373440: 524304,
478150656: 0,
494927872: 16400,
511705088: 1074266128,
528482304: 540672
},
{
0: 260,
1048576: 0,
2097152: 67109120,
3145728: 65796,
4194304: 65540,
5242880: 67108868,
6291456: 67174660,
7340032: 67174400,
8388608: 67108864,
9437184: 67174656,
10485760: 65792,
11534336: 67174404,
12582912: 67109124,
13631488: 65536,
14680064: 4,
15728640: 256,
524288: 67174656,
1572864: 67174404,
2621440: 0,
3670016: 67109120,
4718592: 67108868,
5767168: 65536,
6815744: 65540,
7864320: 260,
8912896: 4,
9961472: 256,
11010048: 67174400,
12058624: 65796,
13107200: 65792,
14155776: 67109124,
15204352: 67174660,
16252928: 67108864,
16777216: 67174656,
17825792: 65540,
18874368: 65536,
19922944: 67109120,
20971520: 256,
22020096: 67174660,
23068672: 67108868,
24117248: 0,
25165824: 67109124,
26214400: 67108864,
27262976: 4,
28311552: 65792,
29360128: 67174400,
30408704: 260,
31457280: 65796,
32505856: 67174404,
17301504: 67108864,
18350080: 260,
19398656: 67174656,
20447232: 0,
21495808: 65540,
22544384: 67109120,
23592960: 256,
24641536: 67174404,
25690112: 65536,
26738688: 67174660,
27787264: 65796,
28835840: 67108868,
29884416: 67109124,
30932992: 67174400,
31981568: 4,
33030144: 65792
},
{
0: 2151682048,
65536: 2147487808,
131072: 4198464,
196608: 2151677952,
262144: 0,
327680: 4198400,
393216: 2147483712,
458752: 4194368,
524288: 2147483648,
589824: 4194304,
655360: 64,
720896: 2147487744,
786432: 2151678016,
851968: 4160,
917504: 4096,
983040: 2151682112,
32768: 2147487808,
98304: 64,
163840: 2151678016,
229376: 2147487744,
294912: 4198400,
360448: 2151682112,
425984: 0,
491520: 2151677952,
557056: 4096,
622592: 2151682048,
688128: 4194304,
753664: 4160,
819200: 2147483648,
884736: 4194368,
950272: 4198464,
1015808: 2147483712,
1048576: 4194368,
1114112: 4198400,
1179648: 2147483712,
1245184: 0,
1310720: 4160,
1376256: 2151678016,
1441792: 2151682048,
1507328: 2147487808,
1572864: 2151682112,
1638400: 2147483648,
1703936: 2151677952,
1769472: 4198464,
1835008: 2147487744,
1900544: 4194304,
1966080: 64,
2031616: 4096,
1081344: 2151677952,
1146880: 2151682112,
1212416: 0,
1277952: 4198400,
1343488: 4194368,
1409024: 2147483648,
1474560: 2147487808,
1540096: 64,
1605632: 2147483712,
1671168: 4096,
1736704: 2147487744,
1802240: 2151678016,
1867776: 4160,
1933312: 2151682048,
1998848: 4194304,
2064384: 4198464
},
{
0: 128,
4096: 17039360,
8192: 262144,
12288: 536870912,
16384: 537133184,
20480: 16777344,
24576: 553648256,
28672: 262272,
32768: 16777216,
36864: 537133056,
40960: 536871040,
45056: 553910400,
49152: 553910272,
53248: 0,
57344: 17039488,
61440: 553648128,
2048: 17039488,
6144: 553648256,
10240: 128,
14336: 17039360,
18432: 262144,
22528: 537133184,
26624: 553910272,
30720: 536870912,
34816: 537133056,
38912: 0,
43008: 553910400,
47104: 16777344,
51200: 536871040,
55296: 553648128,
59392: 16777216,
63488: 262272,
65536: 262144,
69632: 128,
73728: 536870912,
77824: 553648256,
81920: 16777344,
86016: 553910272,
90112: 537133184,
94208: 16777216,
98304: 553910400,
102400: 553648128,
106496: 17039360,
110592: 537133056,
114688: 262272,
118784: 536871040,
122880: 0,
126976: 17039488,
67584: 553648256,
71680: 16777216,
75776: 17039360,
79872: 537133184,
83968: 536870912,
88064: 17039488,
92160: 128,
96256: 553910272,
100352: 262272,
104448: 553910400,
108544: 0,
112640: 553648128,
116736: 16777344,
120832: 262144,
124928: 537133056,
129024: 536871040
},
{
0: 268435464,
256: 8192,
512: 270532608,
768: 270540808,
1024: 268443648,
1280: 2097152,
1536: 2097160,
1792: 268435456,
2048: 0,
2304: 268443656,
2560: 2105344,
2816: 8,
3072: 270532616,
3328: 2105352,
3584: 8200,
3840: 270540800,
128: 270532608,
384: 270540808,
640: 8,
896: 2097152,
1152: 2105352,
1408: 268435464,
1664: 268443648,
1920: 8200,
2176: 2097160,
2432: 8192,
2688: 268443656,
2944: 270532616,
3200: 0,
3456: 270540800,
3712: 2105344,
3968: 268435456,
4096: 268443648,
4352: 270532616,
4608: 270540808,
4864: 8200,
5120: 2097152,
5376: 268435456,
5632: 268435464,
5888: 2105344,
6144: 2105352,
6400: 0,
6656: 8,
6912: 270532608,
7168: 8192,
7424: 268443656,
7680: 270540800,
7936: 2097160,
4224: 8,
4480: 2105344,
4736: 2097152,
4992: 268435464,
5248: 268443648,
5504: 8200,
5760: 270540808,
6016: 270532608,
6272: 270540800,
6528: 270532616,
6784: 8192,
7040: 2105352,
7296: 2097160,
7552: 0,
7808: 268435456,
8064: 268443656
},
{
0: 1048576,
16: 33555457,
32: 1024,
48: 1049601,
64: 34604033,
80: 0,
96: 1,
112: 34603009,
128: 33555456,
144: 1048577,
160: 33554433,
176: 34604032,
192: 34603008,
208: 1025,
224: 1049600,
240: 33554432,
8: 34603009,
24: 0,
40: 33555457,
56: 34604032,
72: 1048576,
88: 33554433,
104: 33554432,
120: 1025,
136: 1049601,
152: 33555456,
168: 34603008,
184: 1048577,
200: 1024,
216: 34604033,
232: 1,
248: 1049600,
256: 33554432,
272: 1048576,
288: 33555457,
304: 34603009,
320: 1048577,
336: 33555456,
352: 34604032,
368: 1049601,
384: 1025,
400: 34604033,
416: 1049600,
432: 1,
448: 0,
464: 34603008,
480: 33554433,
496: 1024,
264: 1049600,
280: 33555457,
296: 34603009,
312: 1,
328: 33554432,
344: 1048576,
360: 1025,
376: 34604032,
392: 33554433,
408: 34603008,
424: 0,
440: 34604033,
456: 1049601,
472: 1024,
488: 33555456,
504: 1048577
},
{
0: 134219808,
1: 131072,
2: 134217728,
3: 32,
4: 131104,
5: 134350880,
6: 134350848,
7: 2048,
8: 134348800,
9: 134219776,
10: 133120,
11: 134348832,
12: 2080,
13: 0,
14: 134217760,
15: 133152,
2147483648: 2048,
2147483649: 134350880,
2147483650: 134219808,
2147483651: 134217728,
2147483652: 134348800,
2147483653: 133120,
2147483654: 133152,
2147483655: 32,
2147483656: 134217760,
2147483657: 2080,
2147483658: 131104,
2147483659: 134350848,
2147483660: 0,
2147483661: 134348832,
2147483662: 134219776,
2147483663: 131072,
16: 133152,
17: 134350848,
18: 32,
19: 2048,
20: 134219776,
21: 134217760,
22: 134348832,
23: 131072,
24: 0,
25: 131104,
26: 134348800,
27: 134219808,
28: 134350880,
29: 133120,
30: 2080,
31: 134217728,
2147483664: 131072,
2147483665: 2048,
2147483666: 134348832,
2147483667: 133152,
2147483668: 32,
2147483669: 134348800,
2147483670: 134217728,
2147483671: 134219808,
2147483672: 134350880,
2147483673: 134217760,
2147483674: 134219776,
2147483675: 0,
2147483676: 133120,
2147483677: 2080,
2147483678: 131104,
2147483679: 134350848
}
];
const SBOX_MASK = [
4160749569,
528482304,
33030144,
2064384,
129024,
8064,
504,
2147483679
];
function exchangeLR(offset, mask) {
if (this._lBlock === void 0 || this._rBlock === void 0) throw new Error("Block values not initialized");
const t = (this._lBlock >>> offset ^ this._rBlock) & mask;
this._rBlock ^= t;
this._lBlock ^= t << offset;
}
function exchangeRL(offset, mask) {
if (this._lBlock === void 0 || this._rBlock === void 0) throw new Error("Block values not initialized");
const t = (this._rBlock >>> offset ^ this._lBlock) & mask;
this._lBlock ^= t;
this._rBlock ^= t << offset;
}
/**
* DES block cipher algorithm.
*/
var DESAlgo = class extends BlockCipher {
/** Key size in 32-bit words */
static keySize = 64 / 32;
/** IV size in 32-bit words */
static ivSize = 64 / 32;
/** Subkeys for encryption */
_subKeys;
/** Inverse subkeys for decryption */
_invSubKeys;
/** Left block for processing */
_lBlock;
/** Right block for processing */
_rBlock;
constructor(xformMode, key, cfg) {
super(xformMode, key, cfg);
this.blockSize = 64 / 32;
}
_doReset() {
const key = this._key;
const keyWords = key.words;
const keyBits = [];
for (let i = 0; i < 56; i += 1) {
const keyBitPos = PC1[i] - 1;
keyBits[i] = keyWords[keyBitPos >>> 5] >>> 31 - keyBitPos % 32 & 1;
}
this._subKeys = [];
const subKeys = this._subKeys;
for (let nSubKey = 0; nSubKey < 16; nSubKey += 1) {
subKeys[nSubKey] = [];
const subKey = subKeys[nSubKey];
const bitShift = BIT_SHIFTS[nSubKey];
for (let i = 0; i < 24; i += 1) {
subKey[i / 6 | 0] |= keyBits[(PC2[i] - 1 + bitShift) % 28] << 31 - i % 6;
subKey[4 + (i / 6 | 0)] |= keyBits[28 + (PC2[i + 24] - 1 + bitShift) % 28] << 31 - i % 6;
}
subKey[0] = subKey[0] << 1 | subKey[0] >>> 31;
for (let i = 1; i < 7; i += 1) subKey[i] >>>= (i - 1) * 4 + 3;
subKey[7] = subKey[7] << 5 | subKey[7] >>> 27;
}
this._invSubKeys = [];
const invSubKeys = this._invSubKeys;
for (let i = 0; i < 16; i += 1) invSubKeys[i] = subKeys[15 - i];
}
encryptBlock(M, offset) {
this._doCryptBlock(M, offset, this._subKeys);
}
decryptBlock(M, offset) {
this._doCryptBlock(M, offset, this._invSubKeys);
}
_doCryptBlock(M, offset, subKeys) {
const _M = M;
this._lBlock = M[offset];
this._rBlock = M[offset + 1];
exchangeLR.call(this, 4, 252645135);
exchangeLR.call(this, 16, 65535);
exchangeRL.call(this, 2, 858993459);
exchangeRL.call(this, 8, 16711935);
exchangeLR.call(this, 1, 1431655765);
for (let round = 0; round < 16; round += 1) {
const subKey = subKeys[round];
const lBlock = this._lBlock;
const rBlock = this._rBlock;
let f = 0;
for (let i = 0; i < 8; i += 1) f |= SBOX_P[i][((rBlock ^ subKey[i]) & SBOX_MASK[i]) >>> 0];
this._lBlock = rBlock;
this._rBlock = lBlock ^ f;
}
const t = this._lBlock;
this._lBlock = this._rBlock;
this._rBlock = t;
exchangeLR.call(this, 1, 1431655765);
exchangeRL.call(this, 8, 16711935);
exchangeRL.call(this, 2, 858993459);
exchangeLR.call(this, 16, 65535);
exchangeLR.call(this, 4, 252645135);
_M[offset] = this._lBlock;
_M[offset + 1] = this._rBlock;
}
};
/**
* Shortcut functions to the cipher's object interface.
*
* @example
*
* var ciphertext = DES.encrypt(message, key, cfg);
* var plaintext = DES.decrypt(ciphertext, key, cfg);
*/
const DES = BlockCipher._createHelper(DESAlgo);
/**
* Triple-DES block cipher algorithm.
*/
var TripleDESAlgo = class extends BlockCipher {
/** Key size in 32-bit words */
static keySize = 192 / 32;
/** IV size in 32-bit words */
static ivSize = 64 / 32;
/** First DES instance */
_des1;
/** Second DES instance */
_des2;
/** Third DES instance */
_des3;
_doReset() {
const key = this._key;
const keyWords = key.words;
if (keyWords.length !== 2 && keyWords.length !== 4 && keyWords.length < 6) throw new Error("Invalid key length - 3DES requires the key length to be 64, 128, 192 or >192.");
const key1 = keyWords.slice(0, 2);
const key2 = keyWords.length < 4 ? keyWords.slice(0, 2) : keyWords.slice(2, 4);
const key3 = keyWords.length < 6 ? keyWords.slice(0, 2) : keyWords.slice(4, 6);
this._des1 = DESAlgo.createEncryptor(WordArray.create(key1));
this._des2 = DESAlgo.createEncryptor(WordArray.create(key2));
this._des3 = DESAlgo.createEncryptor(WordArray.create(key3));
}
encryptBlock(M, offset) {
this._des1.encryptBlock(M, offset);
this._des2.decryptBlock(M, offset);
this._des3.encryptBlock(M, offset);
}
decryptBlock(M, offset) {
this._des3.decryptBlock(M, offset);
this._des2.encryptBlock(M, offset);
this._des1.decryptBlock(M, offset);
}
};
/**
* Shortcut functions to the cipher's object interface.
*
* @example
*
* var ciphertext = TripleDES.encrypt(message, key, cfg);
* var plaintext = TripleDES.decrypt(ciphertext, key, cfg);
*/
const TripleDES = BlockCipher._createHelper(TripleDESAlgo);
//#endregion
export { DES, DESAlgo, TripleDES, TripleDESAlgo };
//# sourceMappingURL=tripledes.mjs.map

View File

@@ -1,132 +0,0 @@
import { Base, WordArray } from "./core.mjs";
//#region src/x64-core.ts
/**
* A 64-bit word representation.
* Stores a 64-bit value as two 32-bit words due to JavaScript's number limitations.
*
* @property high - The high 32 bits
* @property low - The low 32 bits
*/
var X64Word = class extends Base {
/** The high 32 bits of the 64-bit word */
high;
/** The low 32 bits of the 64-bit word */
low;
/**
* Initializes a newly created 64-bit word.
*
* @param high - The high 32 bits (default: 0)
* @param low - The low 32 bits (default: 0)
* @example
* ```javascript
* const x64Word = new X64Word(0x00010203, 0x04050607);
* const x64Word = X64Word.create(0x00010203, 0x04050607);
* ```
*/
constructor(high = 0, low = 0) {
super();
this.high = high;
this.low = low;
}
/**
* Creates a copy of this word.
*
* @returns The cloned 64-bit word
* @example
* ```javascript
* const clone = x64Word.clone();
* ```
*/
clone() {
const clone = super.clone();
clone.high = this.high;
clone.low = this.low;
return clone;
}
};
/**
* An array of 64-bit words.
* This is used for algorithms that operate on 64-bit words, such as SHA-512.
*
* @property words - The array of X64Word objects
* @property sigBytes - The number of significant bytes in this word array
*/
var X64WordArray = class X64WordArray extends Base {
/** The array of X64Word objects */
words;
/** The number of significant bytes in this word array */
sigBytes;
/**
* Initializes a newly created 64-bit word array.
*
* @param words - An array of X64Word objects
* @param sigBytes - The number of significant bytes in the words (defaults to words.length * 8)
* @example
* ```javascript
* const wordArray = new X64WordArray();
*
* const wordArray = new X64WordArray([
* new X64Word(0x00010203, 0x04050607),
* new X64Word(0x18191a1b, 0x1c1d1e1f)
* ]);
*
* const wordArray = new X64WordArray([
* new X64Word(0x00010203, 0x04050607),
* new X64Word(0x18191a1b, 0x1c1d1e1f)
* ], 10);
* ```
*/
constructor(words = [], sigBytes = words.length * 8) {
super();
this.words = words;
this.sigBytes = sigBytes;
}
static create(...args) {
const [words, sigBytes] = args;
return new X64WordArray(words, sigBytes);
}
/**
* Converts this 64-bit word array to a 32-bit word array.
* Each 64-bit word is split into two 32-bit words (high and low).
*
* @returns This word array's data as a 32-bit word array
* @example
* ```javascript
* const x32WordArray = x64WordArray.toX32();
* ```
*/
toX32() {
const x64Words = this.words;
const x64WordsLength = x64Words.length;
const x32Words = [];
for (let i = 0; i < x64WordsLength; i += 1) {
const x64Word = x64Words[i];
x32Words.push(x64Word.high);
x32Words.push(x64Word.low);
}
return WordArray.create(x32Words, this.sigBytes);
}
/**
* Creates a deep copy of this word array.
* Clones both the array and each X64Word object within it.
*
* @returns The cloned X64WordArray
* @example
* ```javascript
* const clone = x64WordArray.clone();
* ```
*/
clone() {
const clone = super.clone();
clone.words = this.words.slice(0);
const { words } = clone;
const wordsLength = words.length;
for (let i = 0; i < wordsLength; i += 1) words[i] = words[i].clone();
return clone;
}
};
//#endregion
export { X64Word, X64WordArray };
//# sourceMappingURL=x64-core.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +0,0 @@
/* esm.sh - react-dom@18.3.1 */
import "./react@18.3.1/react.mjs";
import "./scheduler@0.23.2/scheduler.mjs";
export * from "./react-dom@18.3.1/react-dom.mjs";
export { default } from "./react-dom@18.3.1/react-dom.mjs";

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +0,0 @@
/* esm.sh - react@18.3.1 */
export * from "./react@18.3.1/react.mjs";
export { default } from "./react@18.3.1/react.mjs";

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

73
client/style.css Normal file
View File

@@ -0,0 +1,73 @@
/* 滑条*/
.no-scroll-bar::-webkit-scrollbar {
width: 0px !important;
}
/* https://blog.csdn.net/qq_39347364/article/details/111996581*/
*::-webkit-scrollbar {
width: 7px;
height: 10px;
}
*::-webkit-scrollbar-track {
width: 6px;
background: rgba(#101f1c, 0.1);
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
}
*::-webkit-scrollbar-thumb {
background-color: rgba(144, 147, 153, 0.5);
background-clip: padding-box;
min-height: 28px;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
transition: background-color 0.3s;
cursor: pointer;
}
*::-webkit-scrollbar-thumb:hover {
background-color: rgba(144, 147, 153, 0.3);
}
/* 使用系统字体 在部分系统表现很好*/
/* 我们至今仍未能知道桌面端浏览器字体的秘密*/
*:not(.material-icons, .mdui-icon, mdui-icon, .fa, .google-symbols) {
font-family: -apple-system, system-ui, -webkit-system-font !important;
}
body {
display: flex;
margin: 0 0 0 0;
height: 100%;
}
html {
margin: 0 0 0 0;
height: 100%;
}
/* 防止小尺寸图片模糊*/
* {
image-rendering: crisp-edges;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
}
.gutter {
background-color: rgb(var(--mdui-color-surface-variant));
background-repeat: no-repeat;
background-position: 50%;
}
.gutter.gutter-horizontal {
cursor: col-resize;
}
a {
color: rgb(var(--mdui-color-primary));
}

View File

@@ -1,40 +0,0 @@
<!doctype html>
<html lang="zh-CN" class="mdui-theme-auto">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no" />
<meta name="renderer" content="webkit" />
<link rel="stylesheet" href="https://unpkg.com/mdui@2/mdui.css">
<script src="https://unpkg.com/mdui@2/mdui.global.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
<title>TheWhiteSilk Debugger</title>
</head>
<body>
<mdui-button id="send">Send</mdui-button>
<mdui-text-field id="edittext" autosize></mdui-text-field>
<div id="out">
</div>
<script>
const socket = io()
$('#edittext').val(`{
"method": "",
"args": {
}
}`)
$('#send').click(() => {
socket.emit("the_white_silk", JSON.parse($('#edittext').val()), (response) => {
$('#out').text(JSON.stringify(response))
});
})
</script>
</body>
</html>

18
client/tsconfig.json Normal file
View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"noEmit": true
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,131 +0,0 @@
// eslint-disable-next-line @definitelytyped/export-just-namespace
export = PropTypes;
declare namespace PropTypes {
type ReactComponentLike =
| string
| ((props: any) => any)
| (new(props: any, context: any) => any);
interface ReactElementLike {
type: ReactComponentLike;
props: unknown;
key: string | null;
}
interface ReactNodeArray extends Iterable<ReactNodeLike> {}
/**
* @internal Use `Awaited<ReactNodeLike>` instead
*/
// Helper type to enable `Awaited<ReactNodeLike>`.
// Must be a copy of the non-thenables of `ReactNodeLike`.
type AwaitedReactNodeLike =
| ReactElementLike
| string
| number
| bigint
| ReactNodeArray
| boolean
| null
| undefined;
type ReactNodeLike =
| ReactElementLike
| ReactNodeArray
| string
| number
| bigint
| boolean
| null
| undefined
| Promise<AwaitedReactNodeLike>;
const nominalTypeHack: unique symbol;
type IsOptional<T> = undefined extends T ? true : false;
type RequiredKeys<V> = {
[K in keyof V]-?: Exclude<V[K], undefined> extends Validator<infer T> ? IsOptional<T> extends true ? never : K
: never;
}[keyof V];
type OptionalKeys<V> = Exclude<keyof V, RequiredKeys<V>>;
type InferPropsInner<V> = { [K in keyof V]-?: InferType<V[K]> };
interface Validator<T> {
(
props: { [key: string]: any },
propName: string,
componentName: string,
location: string,
propFullName: string,
): Error | null;
[nominalTypeHack]?: {
type: T;
} | undefined;
}
interface Requireable<T> extends Validator<T | undefined | null> {
isRequired: Validator<NonNullable<T>>;
}
type ValidationMap<T> = { [K in keyof T]?: Validator<T[K]> };
/**
* Like {@link ValidationMap} but treats `undefined`, `null` and optional properties the same.
* This type is only added as a migration path in React 19 where this type was removed from React.
* Runtime and compile time types would mismatch since you could see `undefined` at runtime when your types don't expect this type.
*/
type WeakValidationMap<T> = {
[K in keyof T]?: null extends T[K] ? Validator<T[K] | null | undefined>
: undefined extends T[K] ? Validator<T[K] | null | undefined>
: Validator<T[K]>;
};
type InferType<V> = V extends Validator<infer T> ? T : any;
type InferProps<V> =
& InferPropsInner<Pick<V, RequiredKeys<V>>>
& Partial<InferPropsInner<Pick<V, OptionalKeys<V>>>>;
const any: Requireable<any>;
const array: Requireable<any[]>;
const bool: Requireable<boolean>;
const func: Requireable<(...args: any[]) => any>;
const number: Requireable<number>;
const object: Requireable<object>;
const string: Requireable<string>;
const node: Requireable<ReactNodeLike>;
const element: Requireable<ReactElementLike>;
const symbol: Requireable<symbol>;
const elementType: Requireable<ReactComponentLike>;
function instanceOf<T>(expectedClass: new(...args: any[]) => T): Requireable<T>;
function oneOf<T>(types: readonly T[]): Requireable<T>;
function oneOfType<T extends Validator<any>>(types: T[]): Requireable<NonNullable<InferType<T>>>;
function arrayOf<T>(type: Validator<T>): Requireable<T[]>;
function objectOf<T>(type: Validator<T>): Requireable<{ [K in keyof any]: T }>;
function shape<P extends ValidationMap<any>>(type: P): Requireable<InferProps<P>>;
function exact<P extends ValidationMap<any>>(type: P): Requireable<Required<InferProps<P>>>;
/**
* Assert that the values match with the type specs.
* Error messages are memorized and will only be shown once.
*
* @param typeSpecs Map of name to a ReactPropType
* @param values Runtime values that need to be type-checked
* @param location e.g. "prop", "context", "child context"
* @param componentName Name of the component for error messages
* @param getStack Returns the component stack
*/
function checkPropTypes(
typeSpecs: any,
values: any,
location: string,
componentName: string,
getStack?: () => any,
): void;
/**
* Only available if NODE_ENV=production
*/
function resetWarningCache(): void;
}

View File

@@ -1,161 +0,0 @@
/*
React projects that don't include the DOM library need these interfaces to compile.
React Native applications use React, but there is no DOM available. The JavaScript runtime
is ES6/ES2015 only. These definitions allow such projects to compile with only `--lib ES6`.
Warning: all of these interfaces are empty. If you want type definitions for various properties
(such as HTMLInputElement.prototype.value), you need to add `--lib DOM` (via command line or tsconfig.json).
*/
interface Event {}
interface AnimationEvent extends Event {}
interface ClipboardEvent extends Event {}
interface CompositionEvent extends Event {}
interface DragEvent extends Event {}
interface FocusEvent extends Event {}
interface InputEvent extends Event {}
interface KeyboardEvent extends Event {}
interface MouseEvent extends Event {}
interface TouchEvent extends Event {}
interface PointerEvent extends Event {}
interface ToggleEvent extends Event {}
interface TransitionEvent extends Event {}
interface UIEvent extends Event {}
interface WheelEvent extends Event {}
interface EventTarget {}
interface Document {}
interface DataTransfer {}
interface StyleMedia {}
interface Element {}
interface DocumentFragment {}
interface HTMLElement extends Element {}
interface HTMLAnchorElement extends HTMLElement {}
interface HTMLAreaElement extends HTMLElement {}
interface HTMLAudioElement extends HTMLElement {}
interface HTMLBaseElement extends HTMLElement {}
interface HTMLBodyElement extends HTMLElement {}
interface HTMLBRElement extends HTMLElement {}
interface HTMLButtonElement extends HTMLElement {}
interface HTMLCanvasElement extends HTMLElement {}
interface HTMLDataElement extends HTMLElement {}
interface HTMLDataListElement extends HTMLElement {}
interface HTMLDetailsElement extends HTMLElement {}
interface HTMLDialogElement extends HTMLElement {}
interface HTMLDivElement extends HTMLElement {}
interface HTMLDListElement extends HTMLElement {}
interface HTMLEmbedElement extends HTMLElement {}
interface HTMLFieldSetElement extends HTMLElement {}
interface HTMLFormElement extends HTMLElement {}
interface HTMLHeadingElement extends HTMLElement {}
interface HTMLHeadElement extends HTMLElement {}
interface HTMLHRElement extends HTMLElement {}
interface HTMLHtmlElement extends HTMLElement {}
interface HTMLIFrameElement extends HTMLElement {}
interface HTMLImageElement extends HTMLElement {}
interface HTMLInputElement extends HTMLElement {}
interface HTMLModElement extends HTMLElement {}
interface HTMLLabelElement extends HTMLElement {}
interface HTMLLegendElement extends HTMLElement {}
interface HTMLLIElement extends HTMLElement {}
interface HTMLLinkElement extends HTMLElement {}
interface HTMLMapElement extends HTMLElement {}
interface HTMLMetaElement extends HTMLElement {}
interface HTMLMeterElement extends HTMLElement {}
interface HTMLObjectElement extends HTMLElement {}
interface HTMLOListElement extends HTMLElement {}
interface HTMLOptGroupElement extends HTMLElement {}
interface HTMLOptionElement extends HTMLElement {}
interface HTMLOutputElement extends HTMLElement {}
interface HTMLParagraphElement extends HTMLElement {}
interface HTMLParamElement extends HTMLElement {}
interface HTMLPreElement extends HTMLElement {}
interface HTMLProgressElement extends HTMLElement {}
interface HTMLQuoteElement extends HTMLElement {}
interface HTMLSlotElement extends HTMLElement {}
interface HTMLScriptElement extends HTMLElement {}
interface HTMLSelectElement extends HTMLElement {}
interface HTMLSourceElement extends HTMLElement {}
interface HTMLSpanElement extends HTMLElement {}
interface HTMLStyleElement extends HTMLElement {}
interface HTMLTableElement extends HTMLElement {}
interface HTMLTableColElement extends HTMLElement {}
interface HTMLTableDataCellElement extends HTMLElement {}
interface HTMLTableHeaderCellElement extends HTMLElement {}
interface HTMLTableRowElement extends HTMLElement {}
interface HTMLTableSectionElement extends HTMLElement {}
interface HTMLTemplateElement extends HTMLElement {}
interface HTMLTextAreaElement extends HTMLElement {}
interface HTMLTimeElement extends HTMLElement {}
interface HTMLTitleElement extends HTMLElement {}
interface HTMLTrackElement extends HTMLElement {}
interface HTMLUListElement extends HTMLElement {}
interface HTMLVideoElement extends HTMLElement {}
interface HTMLWebViewElement extends HTMLElement {}
interface SVGElement extends Element {}
interface SVGSVGElement extends SVGElement {}
interface SVGCircleElement extends SVGElement {}
interface SVGClipPathElement extends SVGElement {}
interface SVGDefsElement extends SVGElement {}
interface SVGDescElement extends SVGElement {}
interface SVGEllipseElement extends SVGElement {}
interface SVGFEBlendElement extends SVGElement {}
interface SVGFEColorMatrixElement extends SVGElement {}
interface SVGFEComponentTransferElement extends SVGElement {}
interface SVGFECompositeElement extends SVGElement {}
interface SVGFEConvolveMatrixElement extends SVGElement {}
interface SVGFEDiffuseLightingElement extends SVGElement {}
interface SVGFEDisplacementMapElement extends SVGElement {}
interface SVGFEDistantLightElement extends SVGElement {}
interface SVGFEDropShadowElement extends SVGElement {}
interface SVGFEFloodElement extends SVGElement {}
interface SVGFEFuncAElement extends SVGElement {}
interface SVGFEFuncBElement extends SVGElement {}
interface SVGFEFuncGElement extends SVGElement {}
interface SVGFEFuncRElement extends SVGElement {}
interface SVGFEGaussianBlurElement extends SVGElement {}
interface SVGFEImageElement extends SVGElement {}
interface SVGFEMergeElement extends SVGElement {}
interface SVGFEMergeNodeElement extends SVGElement {}
interface SVGFEMorphologyElement extends SVGElement {}
interface SVGFEOffsetElement extends SVGElement {}
interface SVGFEPointLightElement extends SVGElement {}
interface SVGFESpecularLightingElement extends SVGElement {}
interface SVGFESpotLightElement extends SVGElement {}
interface SVGFETileElement extends SVGElement {}
interface SVGFETurbulenceElement extends SVGElement {}
interface SVGFilterElement extends SVGElement {}
interface SVGForeignObjectElement extends SVGElement {}
interface SVGGElement extends SVGElement {}
interface SVGImageElement extends SVGElement {}
interface SVGLineElement extends SVGElement {}
interface SVGLinearGradientElement extends SVGElement {}
interface SVGMarkerElement extends SVGElement {}
interface SVGMaskElement extends SVGElement {}
interface SVGMetadataElement extends SVGElement {}
interface SVGPathElement extends SVGElement {}
interface SVGPatternElement extends SVGElement {}
interface SVGPolygonElement extends SVGElement {}
interface SVGPolylineElement extends SVGElement {}
interface SVGRadialGradientElement extends SVGElement {}
interface SVGRectElement extends SVGElement {}
interface SVGSetElement extends SVGElement {}
interface SVGStopElement extends SVGElement {}
interface SVGSwitchElement extends SVGElement {}
interface SVGSymbolElement extends SVGElement {}
interface SVGTextElement extends SVGElement {}
interface SVGTextPathElement extends SVGElement {}
interface SVGTSpanElement extends SVGElement {}
interface SVGUseElement extends SVGElement {}
interface SVGViewElement extends SVGElement {}
interface FormData {}
interface Text {}
interface TouchList {}
interface WebGLRenderingContext {}
interface WebGL2RenderingContext {}
interface TrustedHTML {}

File diff suppressed because it is too large Load Diff

View File

@@ -1,169 +0,0 @@
import Client from "../api/Client.ts"
import data from "../Data.ts"
import ChatFragment from "./chat/ChatFragment.jsx"
import LoginDialog from "./dialog/LoginDialog.tsx"
import ContactsListItem from "./main/ContactsListItem.jsx"
import RecentsListItem from "./main/RecentsListItem.jsx"
import snackbar from "./snackbar.js"
import useEventListener from './useEventListener.js'
import { MduiDialog, React, MduiNavigationRail, MduiTextField, MduiButton } from '../Imports.ts'
import User from "../api/client_data/User.ts"
import RecentChat from "../api/client_data/RecentChat.ts"
import '../typedef/mdui-jsx.d.ts'
declare function Split(r: unknown, s: unknown): {
setSizes?: undefined;
getSizes?: undefined;
collapse?: undefined;
destroy?: undefined;
parent?: undefined;
pairs?: undefined;
} | {
setSizes: (e: unknown) => void;
getSizes: () => unknown;
collapse: (e: unknown) => void;
destroy: (e: unknown, t: unknown) => void;
parent: unknown;
pairs: unknown[];
}
export default function App() {
const [recentsList, setRecentsList] = React.useState([
{
id: '0',
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
title: "麻油衣酱",
content: "成步堂君, 我又坐牢了("
},
{
id: '0',
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
title: "Maya Fey",
content: "我是绫里真宵, 是一名灵媒师~"
},
] as RecentChat[])
const [contactsMap, setContactsMap] = React.useState({
: [
{
id: '0',
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
nickname: "麻油衣酱",
},
{
id: '0',
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
nickname: "Maya Fey",
},
],
} as unknown as { [key: string]: User[] })
const [navigationItemSelected, setNavigationItemSelected] = React.useState('Recents')
const navigationRailRef = React.useRef(null)
useEventListener(navigationRailRef, 'change', (event) => {
setNavigationItemSelected((event.target as HTMLElement as MduiNavigationRail).value as string)
})
const loginDialogRef: React.MutableRefObject<MduiDialog | null> = React.useRef(null)
const inputAccountRef: React.MutableRefObject<MduiTextField | null> = React.useRef(null)
const inputPasswordRef: React.MutableRefObject<MduiTextField | null> = React.useRef(null)
const registerButtonRef: React.MutableRefObject<MduiButton | null> = React.useRef(null)
const loginButtonRef: React.MutableRefObject<MduiButton | null> = React.useRef(null)
React.useEffect(() => {
;(async () => {
Split(['#SideBar', '#ChatFragment'], {
sizes: [25, 75],
minSize: [200, 400],
gutterSize: 2,
})
Client.connect()
const re = await Client.invoke("User.auth", {
access_token: data.access_token,
})
if (re.code == 401)
loginDialogRef.current!.open = true
else if (re.code != 200)
snackbar("驗證失敗: " + re.msg)
})()
}, [])
return (
<div style={{
display: "flex",
position: 'relative',
width: 'calc(var(--whitesilk-window-width) - 80px)',
height: 'var(--whitesilk-window-height)',
}}>
<LoginDialog
loginDialogRef={loginDialogRef}
inputAccountRef={inputAccountRef}
inputPasswordRef={inputPasswordRef}
registerButtonRef={registerButtonRef}
loginButtonRef={loginButtonRef} />
<mdui-navigation-rail contained value="Recents" ref={navigationRailRef}>
<mdui-button-icon icon="menu" slot="top"></mdui-button-icon>
<mdui-navigation-rail-item icon="watch_later--outlined" value="Recents"></mdui-navigation-rail-item>
<mdui-navigation-rail-item icon="contacts--outlined" value="Contacts"></mdui-navigation-rail-item>
<mdui-button-icon icon="settings" slot="bottom"></mdui-button-icon>
</mdui-navigation-rail>
{
// 侧边列表
}
<div id="SideBar">
{
// 最近聊天
<mdui-list style={{
overflowY: 'auto',
paddingRight: '10px',
display: navigationItemSelected == "Recents" ? undefined : 'none'
}}>
{
recentsList.map((v) =>
<RecentsListItem
key={v.id}
nickName={v.title}
avatar={v.avatar}
content={v.content} />
)
}
</mdui-list>
}
{
// 联系人列表
<mdui-list style={{
overflowY: 'auto',
paddingRight: '10px',
display: navigationItemSelected == "Contacts" ? undefined : 'none'
}}>
<mdui-collapse accordion value={Object.keys(contactsMap)[0]}>
{
Object.keys(contactsMap).map((v) =>
<mdui-collapse-item key={v} value={v}>
<mdui-list-subheader slot="header">{v}</mdui-list-subheader>
{
contactsMap[v].map((v2) =>
<ContactsListItem
key={v2.id}
nickName={v2.nickname}
avatar={v2.avatar} />
)
}
</mdui-collapse-item>
)
}
</mdui-collapse>
</mdui-list>
}
</div>
{
// 聊天页面
}
<ChatFragment id="ChatFragment" />
</div>
)
}

View File

@@ -1,17 +0,0 @@
import { React } from '../Imports.ts'
export default function Avatar({ src, text, icon = 'person', ...props } = {}) {
return (
src ? <mdui-avatar {...props}>
<img src={src} alt={'(头像)' + text || ''} />
</mdui-avatar>
: (
text ? <mdui-avatar {...props}>
{
text.substring(0, 0)
}
</mdui-avatar>
: <mdui-avatar icon={icon} {...props} />
)
)
}

25
client/ui/Avatar.tsx Normal file
View File

@@ -0,0 +1,25 @@
interface Args extends React.HTMLAttributes<HTMLElement> {
src?: string
text?: string
icon?: string
avatarRef?: React.LegacyRef<HTMLElement>
}
export default function Avatar({
src,
text,
icon = 'person',
avatarRef,
...props
}: Args) {
if (src != null && src != '')
return <mdui-avatar ref={avatarRef} {...props} src={src} />
else if (text != null && text != '')
return <mdui-avatar ref={avatarRef} {...props}>
{
text.substring(0, 1)
}
</mdui-avatar>
else
return <mdui-avatar icon={icon} ref={avatarRef} {...props} />
}

View File

@@ -0,0 +1,34 @@
import { UserMySelf } from "lingchair-client-protocol"
import useAsyncEffect from "../utils/useAsyncEffect.ts"
import Avatar from "./Avatar.tsx"
import getClient from "../getClient.ts"
import React from "react"
import sleep from "../utils/sleep.ts"
interface Args extends React.HTMLAttributes<HTMLElement> {
avatarRef?: React.LegacyRef<HTMLElement>
}
export default function AvatarMySelf({
avatarRef,
...props
}: Args) {
if (!avatarRef) avatarRef = React.useRef<HTMLElement>(null)
const [args, setArgs] = React.useState<{
text: string,
src: string,
}>({
text: '',
src: '',
})
useAsyncEffect(async () => {
await sleep(200)
const mySelf = await UserMySelf.getMySelfOrThrow(getClient())
setArgs({
text: mySelf.getNickName(),
src: getClient().getUrlForFileByHash(mySelf.getAvatarFileHash(), '')!
})
})
return <Avatar avatarRef={avatarRef} {...props} text={args.text} src={args.src}></Avatar>
}

19
client/ui/ImageViewer.tsx Normal file
View File

@@ -0,0 +1,19 @@
import { Dialog } from 'mdui'
import 'pinch-zoom-element'
import React from "react"
export default function ImageViewer() {
const dialogRef = React.useRef<Dialog>()
return <mdui-dialog ref={dialogRef} fullscreen="fullscreen">
<mdui-button-icon icon="open_in_new"
onclick="window.open(document.querySelector('#image-viewer-dialog-inner > *').src, '_blank')">
</mdui-button-icon>
<mdui-button-icon icon="close" onClick={() => dialogRef.current!.open = false}>
</mdui-button-icon>
{
// @ts-ignore 注册了这个元素
<pinch-zoom id="image-viewer-dialog-inner" style="width: var(--whitesilk-window-width); height: var(--whitesilk-window-height);"></pinch-zoom>
}
</mdui-dialog>
}

231
client/ui/Main.tsx Normal file
View File

@@ -0,0 +1,231 @@
import isMobileUI from "../utils/isMobileUI.ts"
import useEventListener from "../utils/useEventListener.ts"
import AvatarMySelf from "./AvatarMySelf.tsx"
import MainSharedContext from './MainSharedContext.ts'
import * as React from 'react'
import { BrowserRouter, Link, Outlet, Route, Routes } from "react-router"
import LoginDialog from "./main-page/LoginDialog.tsx"
import useAsyncEffect from "../utils/useAsyncEffect.ts"
import performAuth from "../performAuth.ts"
import { CallbackError, Chat, UserMySelf } from "lingchair-client-protocol"
import showCircleProgressDialog from "./showCircleProgressDialog.ts"
import RegisterDialog from "./main-page/RegisterDialog.tsx"
import sleep from "../utils/sleep.ts"
import { $, NavigationDrawer } from "mdui"
import getClient from "../getClient.ts"
import showSnackbar from "../utils/showSnackbar.ts"
import AllChatsList from "./main-page/AllChatsList.tsx"
import FavouriteChatsList from "./main-page/FavouriteChatsList.tsx"
import AddFavourtieChatDialog from "./main-page/AddFavourtieChatDialog.tsx"
import RecentChatsList from "./main-page/RecentChatsList.tsx"
import ChatInfoDialog from "./routers/ChatInfoDialog.tsx"
export default function Main() {
const [myProfileCache, setMyProfileCache] = React.useState<UserMySelf>()
// 多页面切换
const navigationRef = React.useRef<HTMLElement>()
const [currentShowPage, setCurrentShowPage] = React.useState('Recents')
type HTMLElementWithValue = HTMLElement & { value: string }
useEventListener(navigationRef, 'change', (event) => {
setCurrentShowPage((event.target as HTMLElementWithValue).value)
})
const drawerRef = React.useRef<NavigationDrawer>()
React.useEffect(() => {
$(drawerRef.current!.shadowRoot).append(`
<style>
.panel {
width: 17.5rem !important;
display: flex !important;
flex-direction: column;
}
</style>
`)
}, [])
const [showLoginDialog, setShowLoginDialog] = React.useState(false)
const [showRegisterDialog, setShowRegisterDialog] = React.useState(false)
const [showAddFavourtieChatDialog, setShowAddFavourtieChatDialog] = React.useState(false)
const [currentSelectedChatId, setCurrentSelectedChatId] = React.useState('')
const [favouriteChats, setFavouriteChats] = React.useState<Chat[]>([])
const sharedContext = {
functions_lazy: React.useRef({
updateFavouriteChats: () => { },
updateRecentChats: () => { },
updateAllChats: () => { },
}),
favouriteChats,
setFavouriteChats,
setShowLoginDialog,
setShowRegisterDialog,
setShowAddFavourtieChatDialog,
currentSelectedChatId,
setCurrentSelectedChatId,
myProfileCache,
}
useAsyncEffect(async () => {
const waitingForAuth = showCircleProgressDialog("加载中...")
try {
await performAuth({})
try {
setMyProfileCache(await UserMySelf.getMySelfOrThrow(getClient()))
} catch (e) {
if (e instanceof CallbackError)
showSnackbar({
message: '获取资料失败: ' + e.message
})
}
} catch (e) {
if (e instanceof CallbackError)
if (e.code == 401 || e.code == 400)
setShowLoginDialog(true)
}
// 动画都没来得及, 稍微等一下 (
await sleep(100)
waitingForAuth.open = false
})
const subRoutes = <>
<Route path="/info">
<Route path="chat" element={<ChatInfoDialog />} />
<Route path="user" element={<ChatInfoDialog />} />
</Route>
</>
return (
<MainSharedContext.Provider value={sharedContext}>
<BrowserRouter>
<Routes>
<Route path="/" element={(
<div style={{
display: "flex",
position: 'relative',
flexDirection: isMobileUI() ? 'column' : 'row',
width: `calc(var(--whitesilk-window-width))${isMobileUI() ? '' : ' - 80px'}`,
height: 'var(--whitesilk-window-height)',
}}>
{
// 将子路由渲染到此处
<Outlet />
}
<LoginDialog open={showLoginDialog} />
<RegisterDialog open={showRegisterDialog} />
<AddFavourtieChatDialog open={showAddFavourtieChatDialog} />
<mdui-navigation-drawer ref={drawerRef} modal close-on-esc close-on-overlay-click>
<mdui-list style={{
padding: '10px',
}}>
<mdui-list-item rounded>
<span>{myProfileCache?.getNickName()}</span>
<AvatarMySelf slot="icon" />
</mdui-list-item>
<mdui-list-item rounded icon="manage_accounts"></mdui-list-item>
<mdui-divider style={{
margin: '10px',
}}></mdui-divider>
<mdui-list-item rounded icon="person_add"></mdui-list-item>
<mdui-list-item rounded icon="group_add"></mdui-list-item>
<Link to="/info/user?id=0960bd15-4527-4000-97a8-73110160296f"><mdui-list-item rounded icon="group_add"></mdui-list-item></Link>
<Link to="/info/chat?id=priv_0960bd15_4527_4000_97a8_73110160296f__0960bd15_4527_4000_97a8_73110160296f"><mdui-list-item rounded icon="group_add">2</mdui-list-item></Link>
</mdui-list>
<div style={{
flexGrow: 1,
}}></div>
<span style={{
padding: '10px',
fontSize: 'small',
}}>
LingChair Web v{__APP_VERSION__}<br />
Build: <a href={`https://codeberg.org/CrescentLeaf/LingChair/src/commit/${__GIT_HASH_FULL__}`}>{__GIT_HASH__}</a> ({__BUILD_TIME__})<br />
Codeberg <a href="https://codeberg.org/CrescentLeaf/LingChair"></a>
</span>
</mdui-navigation-drawer>
{
/**
* Default: 侧边列表提供列表切换
*/
!isMobileUI() ?
<mdui-navigation-rail ref={navigationRef} contained value="Recents">
<mdui-button-icon slot="top" icon="menu" onClick={() => drawerRef.current!.open = true}></mdui-button-icon>
<mdui-navigation-rail-item icon="watch_later--outlined" active-icon="watch_later--filled" value="Recents"></mdui-navigation-rail-item>
<mdui-navigation-rail-item icon="favorite_border" active-icon="favorite" value="Favourites"></mdui-navigation-rail-item>
<mdui-navigation-rail-item icon="chat--outlined" active-icon="chat--filled" value="AllChats"></mdui-navigation-rail-item>
</mdui-navigation-rail>
/**
* Mobile: 底部导航栏提供列表切换
*/
: <mdui-top-app-bar style={{
position: 'sticky',
marginTop: '3px',
marginRight: '6px',
marginLeft: '15px',
top: '0px',
}}>
<mdui-button-icon icon="menu" onClick={() => drawerRef.current!.open = true}></mdui-button-icon>
<mdui-top-app-bar-title>{
({
Recents: "最近对话",
Favourites: "收藏对话",
AllChats: "所有对话",
})[currentShowPage]
}</mdui-top-app-bar-title>
<div style={{
flexGrow: 1,
}}></div>
</mdui-top-app-bar>
}
{
/**
* Mobile: 指定高度的容器
* Default: 侧边列表
*/
<div style={isMobileUI() ? {
display: 'flex',
height: 'calc(100% - 80px - 67px)',
width: '100%',
} : {}} id="SideBar">
<RecentChatsList style={{
display: currentShowPage == 'Recents' ? undefined : 'none'
}} />
<FavouriteChatsList style={{
display: currentShowPage == 'Favourites' ? undefined : 'none'
}} />
<AllChatsList style={{
display: currentShowPage == 'AllChats' ? undefined : 'none'
}} />
</div>
}
{
/**
* Mobile: 底部导航栏提供列表切换
* Default: 侧边列表提供列表切换
*/
isMobileUI() && <mdui-navigation-bar ref={navigationRef} label-visibility="selected" value="Recents" style={{
position: 'sticky',
bottom: '0',
}}>
<mdui-navigation-bar-item icon="watch_later--outlined" active-icon="watch_later--filled" value="Recents"></mdui-navigation-bar-item>
<mdui-navigation-bar-item icon="favorite_border" active-icon="favorite" value="Favourites"></mdui-navigation-bar-item>
<mdui-navigation-bar-item icon="chat--outlined" active-icon="chat--filled" value="AllChats"></mdui-navigation-bar-item>
</mdui-navigation-bar>
}
</div>
)}>
{subRoutes}
</Route>
</Routes>
</BrowserRouter>
</MainSharedContext.Provider>
)
}

View File

@@ -0,0 +1,23 @@
import { Chat, UserMySelf } from "lingchair-client-protocol"
import { createContext } from "use-context-selector"
type Shared = {
functions_lazy: React.MutableRefObject<{
updateFavouriteChats: () => void
updateRecentChats: () => void
updateAllChats: () => void
}>
favouriteChats: Chat[]
setFavouriteChats: React.Dispatch<React.SetStateAction<Chat[]>>
setShowLoginDialog: React.Dispatch<React.SetStateAction<boolean>>
setShowRegisterDialog: React.Dispatch<React.SetStateAction<boolean>>
setShowAddFavourtieChatDialog: React.Dispatch<React.SetStateAction<boolean>>
setCurrentSelectedChatId: React.Dispatch<React.SetStateAction<string>>
myProfileCache?: UserMySelf
currentSelectedChatId: string
}
const MainSharedContext = createContext({} as Shared)
export default MainSharedContext
export type { Shared }

View File

@@ -0,0 +1,39 @@
import { $ } from 'mdui/jq'
customElements.define('chat-file', class extends HTMLElement {
static observedAttributes = ['href', 'name']
declare anchor: HTMLAnchorElement
declare span: HTMLSpanElement
constructor() {
super()
this.attachShadow({ mode: 'open' })
}
update() {
if (this.anchor == null) return
this.anchor.href = $(this).attr('href') as string
this.anchor.download = $(this).attr('href') as string
this.span.textContent = $(this).attr("name") as string
}
attributeChangedCallback(_name: string, _oldValue: unknown, _newValue: unknown) {
this.update()
}
connectedCallback() {
this.anchor = new DOMParser().parseFromString(`
<a style="width: 100%;height: 100%;">
<mdui-card clickable style="display: flex;align-items: center;box-shadow: inherit;border-radius: inherit;">
<mdui-icon name="insert_drive_file" style="margin: 13px;font-size: 34px;"></mdui-icon>
<span style="margin-right: 13px; word-wrap: break-word; word-break:break-all;white-space:normal; max-width :100%;"></span>
</mdui-card>
</a>`, 'text/html').body.firstChild as HTMLAnchorElement
this.span = $(this.anchor).find('span').get(0)
this.anchor.style.textDecoration = 'none'
this.anchor.style.color = 'inherit'
this.anchor.onclick = (e) => {
e.stopPropagation()
}
this.shadowRoot!.appendChild(this.anchor)
this.update()
}
})

View File

@@ -0,0 +1,58 @@
import openImageViewer from "../../utils/openImageViewer.ts"
import { $ } from 'mdui/jq'
customElements.define('chat-image', class extends HTMLElement {
static observedAttributes = ['src', 'show-error']
declare img: HTMLImageElement
declare error: HTMLElement
constructor() {
super()
this.attachShadow({ mode: 'open' })
}
update() {
if (this.img == null) return
this.img.src = $(this).attr('src') as string
const error = $(this).attr('show-error') == 'true'
this.img.style.display = error ? 'none' : 'block'
this.error.style.display = error ? '' : 'none'
}
attributeChangedCallback(_name: string, _oldValue: unknown, _newValue: unknown) {
this.update()
}
connectedCallback() {
this.img = new Image()
this.img.style.width = '100%'
this.img.style.maxHeight = "300px"
this.img.style.objectFit = 'cover'
// this.img.style.borderRadius = "var(--mdui-shape-corner-medium)"
this.shadowRoot!.appendChild(this.img)
this.error = new DOMParser().parseFromString(`<mdui-icon name="broken_image" style="font-size: 2rem;"></mdui-icon>`, 'text/html').body.firstChild as HTMLElement
this.shadowRoot!.appendChild(this.error)
this.img.addEventListener('error', () => {
$(this).attr('show-error', 'true')
})
this.error.addEventListener('click', (event) => {
event.stopPropagation()
const img = this.img
this.img = new Image()
this.img.style.width = '100%'
this.img.style.maxHeight = "300px"
this.img.style.objectFit = 'cover'
this.shadowRoot!.replaceChild(img, this.img)
$(this).attr('show-error', undefined)
})
this.img.addEventListener('click', (event) => {
event.stopPropagation()
openImageViewer($(this).attr('src') as string)
})
this.update()
}
})

View File

@@ -0,0 +1,60 @@
import { $ } from 'mdui'
import showSnackbar from "../../utils/showSnackbar.ts";
customElements.define('chat-mention', class extends HTMLElement {
declare link: HTMLAnchorElement
static observedAttributes = ['user-id']
constructor() {
super()
this.attachShadow({ mode: 'open' })
}
connectedCallback() {
const shadow = this.shadowRoot as ShadowRoot
this.link = document.createElement('a')
this.link.style.fontSynthesis = 'style weight'
this.link.style.color = 'rgb(var(--mdui-color-primary))'
this.link.href = 'javascript:void(0)'
shadow.appendChild(this.link)
this.update()
}
attributeChangedCallback(_name: string, _oldValue: unknown, _newValue: unknown) {
this.update()
}
async update() {
if (this.link == null) return
const userId = $(this).attr('user-id')
const chatId = $(this).attr('chat-id')
const text = $(this).attr('text')
this.link.style.fontStyle = ''
if (chatId) {
this.link.onclick = (e) => {
e.stopPropagation()
// deno-lint-ignore no-window
}
} else if (userId) {
this.link.onclick = (e) => {
e.stopPropagation()
// deno-lint-ignore no-window
}
}
text && (this.link.textContent = text)
if (!(userId || chatId)) {
this.link.textContent = "无效的提及"
this.link.style.fontStyle = 'italic'
this.link.onclick = (e) => {
e.stopPropagation()
showSnackbar({
message: "该提及没有指定用户或者对话!",
})
}
}
}
})

View File

@@ -0,0 +1,45 @@
import { $ } from 'mdui/jq'
customElements.define('chat-quote', class extends HTMLElement {
declare container: HTMLAnchorElement
declare span: HTMLSpanElement
declare ellipsis: boolean
constructor() {
super()
this.attachShadow({ mode: 'open' })
}
update() {
if (this.container == null) return
this.span.textContent = this.textContent
this.updateStyle()
}
updateStyle() {
this.span.style.whiteSpace = this.ellipsis ? 'nowrap' : 'pre-wrap'
this.span.style.overflow = this.ellipsis ? 'hidden' : ''
this.span.style.textOverflow = this.ellipsis ? 'ellipsis' : ''
}
attributeChangedCallback(_name: string, _oldValue: unknown, _newValue: unknown) {
this.update()
}
connectedCallback() {
this.container = new DOMParser().parseFromString(`
<a style="width: 100%;height: 100%; color: rgb(var(--mdui-color-primary));" href="javascript:void(0)">
<span style="display: block; word-wrap: break-word; word-break:break-all;white-space:normal; max-width :100%;"></span>
</a>`, 'text/html').body.firstChild as HTMLAnchorElement
this.span = $(this.container).find('span').get(0)
this.container.style.textDecoration = 'none'
this.span.style.fontSynthesis = 'style weight'
this.container.onclick = (e) => {
this.ellipsis = !this.ellipsis
this.updateStyle()
e.stopPropagation()
}
this.ellipsis = true
this.shadowRoot!.appendChild(this.container)
this.update()
}
})

Some files were not shown because too many files have changed in this diff Show More