Compare commits
35 commits
Author | SHA1 | Date | |
---|---|---|---|
6671562ab7 | |||
373eca7a19 | |||
70d5c713ca | |||
f3eeb711a0 | |||
80f788c38b | |||
1192cffa29 | |||
a3a6d2b5ba | |||
756c8b3ef4 | |||
c2029ed271 | |||
82c80a53a6 | |||
4b96e03f54 | |||
d591282f5e | |||
|
873ef89e42 | ||
d7a8660952 | |||
eec5ce1a99 | |||
f7cdb9df70 | |||
2b1c4b7245 | |||
e885beaab9 | |||
d25fa27c24 | |||
7a0067460b | |||
63a98f3b41 | |||
b29f49fefc | |||
|
8e508b921c | ||
9052a02598 | |||
57c4fef275 | |||
748685e53e | |||
8212c62663 | |||
8d48909e4f | |||
5587de26c7 | |||
|
9bb310e0d1 | ||
15e669d943 | |||
a72ca7dcf4 | |||
599c265530 | |||
a97b5921c9 | |||
a2517d3d03 |
41 changed files with 773 additions and 735 deletions
97
.github/ISSUE_TEMPLATE/01_bug-report.yml
vendored
97
.github/ISSUE_TEMPLATE/01_bug-report.yml
vendored
|
@ -1,97 +0,0 @@
|
||||||
name: 🐛 Bug Report
|
|
||||||
description: Create a report to help us improve
|
|
||||||
labels: ["⚠️bug?"]
|
|
||||||
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Thanks for reporting!
|
|
||||||
First, in order to avoid duplicate Issues, please search to see if the problem you found has already been reported.
|
|
||||||
Also, If you are NOT owner/admin of server, PLEASE DONT REPORT SERVER SPECIFIC ISSUES TO HERE! (e.g. feature XXX is not working in misskey.example) Please try with another misskey servers, and if your issue is only reproducible with specific server, contact your server's owner/admin first.
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 💡 Summary
|
|
||||||
description: Tell us what the bug is
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 🥰 Expected Behavior
|
|
||||||
description: Tell us what should happen
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 🤬 Actual Behavior
|
|
||||||
description: |
|
|
||||||
Tell us what happens instead of the expected behavior.
|
|
||||||
Please include errors from the developer console and/or server log files if you have access to them.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 📝 Steps to Reproduce
|
|
||||||
placeholder: |
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 💻 Frontend Environment
|
|
||||||
description: |
|
|
||||||
Tell us where on the platform it happens
|
|
||||||
DO NOT WRITE "latest". Please provide the specific version.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
* Model and OS of the device(s): MacBook Pro (14inch, 2021), macOS Ventura 13.4
|
|
||||||
* Browser: Chrome 113.0.5672.126
|
|
||||||
* Server URL: misskey.example.com
|
|
||||||
* Misskey: 2024.x.x
|
|
||||||
value: |
|
|
||||||
* Model and OS of the device(s):
|
|
||||||
* Browser:
|
|
||||||
* Server URL:
|
|
||||||
* Misskey:
|
|
||||||
render: markdown
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 🛰 Backend Environment (for server admin)
|
|
||||||
description: |
|
|
||||||
Tell us where on the platform it happens
|
|
||||||
DO NOT WRITE "latest". Please provide the specific version.
|
|
||||||
If you are using a managed service, put that after the version.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
* Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
|
|
||||||
* Misskey: 2024.x.x
|
|
||||||
* Node: 20.x.x
|
|
||||||
* PostgreSQL: 15.x.x
|
|
||||||
* Redis: 7.x.x
|
|
||||||
* OS and Architecture: Ubuntu 24.04.2 LTS aarch64
|
|
||||||
value: |
|
|
||||||
* Installation Method or Hosting Service:
|
|
||||||
* Misskey:
|
|
||||||
* Node:
|
|
||||||
* PostgreSQL:
|
|
||||||
* Redis:
|
|
||||||
* OS and Architecture:
|
|
||||||
render: markdown
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: Do you want to address this bug yourself?
|
|
||||||
options:
|
|
||||||
- label: Yes, I will patch the bug myself and send a pull request
|
|
22
.github/ISSUE_TEMPLATE/02_feature-request.yml
vendored
22
.github/ISSUE_TEMPLATE/02_feature-request.yml
vendored
|
@ -1,22 +0,0 @@
|
||||||
name: ✨ Feature Request
|
|
||||||
description: Suggest an idea for this project
|
|
||||||
labels: ["✨Feature"]
|
|
||||||
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Summary
|
|
||||||
description: Tell us what the suggestion is
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Purpose
|
|
||||||
description: Describe the specific problem or need you think this feature will solve, and who it will help.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: Do you want to implement this feature yourself?
|
|
||||||
options:
|
|
||||||
- label: Yes, I will implement this by myself and send a pull request
|
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
8
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,8 +0,0 @@
|
||||||
contact_links:
|
|
||||||
- name: 💬 Misskey official Discord
|
|
||||||
url: https://discord.gg/Wp8gVStHW3
|
|
||||||
about: Chat freely about Misskey
|
|
||||||
# 仮
|
|
||||||
- name: 💬 Start discussion
|
|
||||||
url: https://github.com/misskey-dev/misskey/discussions
|
|
||||||
about: The official forum to join conversation and ask question
|
|
23
.github/PULL_REQUEST_TEMPLATE/01_bug.md
vendored
23
.github/PULL_REQUEST_TEMPLATE/01_bug.md
vendored
|
@ -1,23 +0,0 @@
|
||||||
<!-- ℹ お読みください / README
|
|
||||||
PRありがとうございます! PRを作成する前に、コントリビューションガイドをご確認ください:
|
|
||||||
Thank you for your PR! Before creating a PR, please check the contribution guide:
|
|
||||||
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
|
|
||||||
-->
|
|
||||||
|
|
||||||
## What
|
|
||||||
<!-- このPRで何をしたのか? どう変わるのか? -->
|
|
||||||
<!-- What did you do with this PR? How will it change things? -->
|
|
||||||
|
|
||||||
## Why
|
|
||||||
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
|
|
||||||
<!-- Why do you do it? What are your intentions? What is the problem? -->
|
|
||||||
|
|
||||||
## Additional info (optional)
|
|
||||||
<!-- テスト観点など -->
|
|
||||||
<!-- Test perspective, etc -->
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
|
|
||||||
- [ ] Test working in a local environment
|
|
||||||
- [ ] (If needed) Update CHANGELOG.md
|
|
||||||
- [ ] (If possible) Add tests
|
|
23
.github/PULL_REQUEST_TEMPLATE/02_enhance.md
vendored
23
.github/PULL_REQUEST_TEMPLATE/02_enhance.md
vendored
|
@ -1,23 +0,0 @@
|
||||||
<!-- ℹ お読みください / README
|
|
||||||
PRありがとうございます! PRを作成する前に、コントリビューションガイドをご確認ください:
|
|
||||||
Thank you for your PR! Before creating a PR, please check the contribution guide:
|
|
||||||
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
|
|
||||||
-->
|
|
||||||
|
|
||||||
## What
|
|
||||||
<!-- このPRで何をしたのか? どう変わるのか? -->
|
|
||||||
<!-- What did you do with this PR? How will it change things? -->
|
|
||||||
|
|
||||||
## Why
|
|
||||||
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
|
|
||||||
<!-- Why do you do it? What are your intentions? What is the problem? -->
|
|
||||||
|
|
||||||
## Additional info (optional)
|
|
||||||
<!-- テスト観点など -->
|
|
||||||
<!-- Test perspective, etc -->
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
|
|
||||||
- [ ] Test working in a local environment
|
|
||||||
- [ ] (If needed) Update CHANGELOG.md
|
|
||||||
- [ ] (If possible) Add tests
|
|
20
.github/PULL_REQUEST_TEMPLATE/03_release.md
vendored
20
.github/PULL_REQUEST_TEMPLATE/03_release.md
vendored
|
@ -1,20 +0,0 @@
|
||||||
## Summary
|
|
||||||
This is a release PR.
|
|
||||||
|
|
||||||
For more information on the release instructions, please see:
|
|
||||||
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md#release
|
|
||||||
|
|
||||||
## For reviewers
|
|
||||||
- CHANGELOGに抜け漏れは無いか
|
|
||||||
- バージョンの上げ方は適切か
|
|
||||||
- 他にこのリリースに含めなければならない変更は無いか
|
|
||||||
- 全体的な変更内容を俯瞰し問題は無いか
|
|
||||||
- レビューされていないコミットがある場合は、それが問題ないか
|
|
||||||
- 最終的な動作確認を行い問題は無いか
|
|
||||||
|
|
||||||
などを確認し、リリースする準備が整っていると思われる場合は approve してください。
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
- [ ] package.jsonのバージョンが正しく更新されている
|
|
||||||
- [ ] CHANGELOGが過不足無く更新されている
|
|
||||||
- [ ] CIが全て通っている
|
|
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
|
@ -1,7 +1,7 @@
|
||||||
<!-- ℹ お読みください / README
|
<!-- ℹ お読みください / README
|
||||||
PRありがとうございます! PRを作成する前に、コントリビューションガイドをご確認ください:
|
PRありがとうございます! PRを作成する前に、コントリビューションガイドをご確認ください:
|
||||||
Thank you for your PR! Before creating a PR, please check the contribution guide:
|
Thank you for your PR! Before creating a PR, please check the contribution guide:
|
||||||
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
|
https://forge.yumechi.jp/yume/yumechi-no-kuni/src/branch/master/CONTRIBUTING.md
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## What
|
## What
|
||||||
|
@ -17,7 +17,7 @@ https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
|
||||||
<!-- Test perspective, etc -->
|
<!-- Test perspective, etc -->
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
|
- [ ] Read the [contribution guide](https://forge.yumechi.jp/yume/yumechi-no-kuni/src/branch/master/CONTRIBUTING.md)
|
||||||
- [ ] Test working in a local environment
|
- [ ] Test working in a local environment
|
||||||
- [ ] (If needed) Add story of storybook
|
- [ ] (If needed) Add story of storybook
|
||||||
- [ ] (If needed) Update CHANGELOG.md
|
- [ ] (If needed) Update CHANGELOG.md
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
build:
|
|
||||||
misskey:
|
|
||||||
args:
|
|
||||||
- NODE_ENV=development
|
|
||||||
deploy:
|
|
||||||
- helm upgrade --install misskey chart --set image=${OKTETO_BUILD_MISSKEY_IMAGE} --set url="https://misskey-$(kubectl config view --minify -o jsonpath='{..namespace}').cloud.okteto.net" --set environment=development
|
|
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -1,3 +1,23 @@
|
||||||
|
|
||||||
|
## 2024.11.0-yumechinokuni.8
|
||||||
|
|
||||||
|
- Frontend: SSRでユーザープロフィールが表示されない問題を修正
|
||||||
|
- Security: SSRプライバシー方面の改善
|
||||||
|
- Security: AP Payloadの検証を強化
|
||||||
|
|
||||||
|
## 2024.11.0-yumechinokuni.7
|
||||||
|
|
||||||
|
- Misskey Trademark内容をWebUIから削除
|
||||||
|
- Service Worker キャッシュが正しく動作しない問題を修正
|
||||||
|
|
||||||
|
## 2024.11.0-yumechinokuni.6
|
||||||
|
|
||||||
|
- Upstream: 2024.11.0-alpha.4 タッグをマージする
|
||||||
|
- Performance: EmojiのリクエストをProxyでキャッシュするように
|
||||||
|
- Performance: Service Workerのキャッシュを最適化
|
||||||
|
- Security: AP Payloadの検証を強化
|
||||||
|
- Security: Image/Video Processorはドライブ機能だけを使うように
|
||||||
|
|
||||||
## 2024.11.0-yumechinokuni.5
|
## 2024.11.0-yumechinokuni.5
|
||||||
|
|
||||||
- Upstream: 2024.11.0-alpha.2 タッグをマージする
|
- Upstream: 2024.11.0-alpha.2 タッグをマージする
|
||||||
|
|
62
README.md
62
README.md
|
@ -1,49 +1,31 @@
|
||||||
<div align="center">
|
# ゆめちのくに
|
||||||
<a href="https://misskey-hub.net">
|
|
||||||
<img src="./assets/title_float.svg" alt="Misskey logo" style="border-radius:50%" width="300"/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
**🌎 **Misskey** is an open source, federated social media platform that's free forever! 🚀**
|
YumechiNoKuni is a fork of Misskey, with a focus on security, observability and reliability.
|
||||||
|
|
||||||
[Learn more](https://misskey-hub.net/)
|
[mi.yumechi.jp](https://mi.yumechi.jp) is running this version.
|
||||||
|
|
||||||
---
|
[Learn more about Misskey](https://misskey-hub.net/)
|
||||||
|
|
||||||
<a href="https://misskey-hub.net/servers/">
|
## Main differences
|
||||||
<img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=misskey&labelColor=363B40" alt="find an instance"/></a>
|
|
||||||
|
|
||||||
<a href="https://misskey-hub.net/docs/for-admin/install/guides/">
|
### Unique features
|
||||||
<img src="https://custom-icon-badges.herokuapp.com/badge/create_an-instance-FBD53C?logoColor=FBD53C&style=for-the-badge&logo=server&labelColor=363B40" alt="create an instance"/></a>
|
|
||||||
|
|
||||||
<a href="./CONTRIBUTING.md">
|
- Strict ActivityPub sanitization by whitelisting properties and normalizing all referential properties.
|
||||||
<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-contributor-A371F7?logoColor=A371F7&style=for-the-badge&logo=git-merge&labelColor=363B40" alt="become a contributor"/></a>
|
- Strict Content Security Policy.
|
||||||
|
- Require TLSv1.2+ over port 443 for all ActivityPub requests.
|
||||||
|
- Strongly-typed inbox filtering in Rust.
|
||||||
|
- Reduce needless retries by marking more errors as permanent.
|
||||||
|
- Detailed prometheus metrics for slow requests, DB queries, AP processing, failed auths, etc.
|
||||||
|
- Disable unauthenticated media processing and use custom AppArmored media proxy.
|
||||||
|
- Enable active users in nodeinfo back.
|
||||||
|
- Advertise Git information over nodeinfo for better observability and easy tracking of the actual code running.
|
||||||
|
- Logical replication for the database over mTLS.
|
||||||
|
- More atomic operations in API handlers.
|
||||||
|
|
||||||
<a href="https://discord.gg/Wp8gVStHW3">
|
### Picked from github.com/paricafe/misskey
|
||||||
<img src="https://custom-icon-badges.herokuapp.com/badge/join_the-community-5865F2?logoColor=5865F2&style=for-the-badge&logo=discord&labelColor=363B40" alt="join the community"/></a>
|
|
||||||
|
|
||||||
<a href="https://www.patreon.com/syuilo">
|
- pgroonga full-text search (with modifications).
|
||||||
<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-patron-F96854?logoColor=F96854&style=for-the-badge&logo=patreon&labelColor=363B40" alt="become a patron"/></a>
|
- Better Service Worker caching.
|
||||||
|
- Better hashtag statistics.
|
||||||
|
- Better handling of deep recursive AP objects.
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Thanks
|
|
||||||
|
|
||||||
<a href="https://sentry.io/"><img src="https://github.com/misskey-dev/misskey/assets/4439005/98576556-222f-467a-94be-e98dbda1d852" height="30" alt="Sentry" /></a>
|
|
||||||
|
|
||||||
Thanks to [Sentry](https://sentry.io/) for providing the error tracking platform that helps us catch unexpected errors.
|
|
||||||
|
|
||||||
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" height="30" alt="Chromatic" /></a>
|
|
||||||
|
|
||||||
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
|
|
||||||
|
|
||||||
<a href="https://about.codecov.io/for/open-source/"><img src="https://about.codecov.io/wp-content/themes/codecov/assets/brand/sentry-cobranding/logos/codecov-by-sentry-logo.svg" height="30" alt="Codecov" /></a>
|
|
||||||
|
|
||||||
Thanks to [Codecov](https://about.codecov.io/for/open-source/) for providing the code coverage platform that helps us improve our test coverage.
|
|
||||||
|
|
||||||
<a href="https://crowdin.com/"><img src="https://user-images.githubusercontent.com/20679825/230709597-1299a011-171a-4294-a91e-355a9b37c672.svg" height="30" alt="Crowdin" /></a>
|
|
||||||
|
|
||||||
Thanks to [Crowdin](https://crowdin.com/) for providing the localization platform that helps us translate Misskey into many languages.
|
|
||||||
|
|
||||||
<a href="https://hub.docker.com/"><img src="https://user-images.githubusercontent.com/20679825/230148221-f8e73a32-a49b-47c3-9029-9a15c3824f92.png" height="30" alt="Docker" /></a>
|
|
||||||
|
|
||||||
Thanks to [Docker](https://hub.docker.com/) for providing the container platform that helps us run Misskey in production.
|
|
||||||
|
|
13
SECURITY.md
13
SECURITY.md
|
@ -1,15 +1,12 @@
|
||||||
# Reporting Security Issues
|
# Reporting Security Issues
|
||||||
|
|
||||||
If you discover a security issue in Misskey, please report it by **[this form](https://github.com/misskey-dev/misskey/security/advisories/new)**.
|
If you discover a security issue in this project, please use the `git blame` command to identify the source of the issue,
|
||||||
|
if it was introduced by this fork please contact me at secity<at>yumechi.jp.
|
||||||
|
|
||||||
This will allow us to assess the risk, and make a fix available before we add a
|
For upstream issues please report by **[this form](https://github.com/misskey-dev/misskey/security/advisories/new)**.
|
||||||
bug report to the GitHub repository.
|
|
||||||
|
|
||||||
Thanks for helping make Misskey safe for everyone.
|
Thanks for helping make YumechiNoKuni safe for everyone.
|
||||||
|
|
||||||
## When create a patch
|
## When create a patch
|
||||||
|
|
||||||
If you can also create a patch to fix the vulnerability, please create a PR on the private fork.
|
If you can also create a patch to fix the vulnerability, please send a diff file with the report.
|
||||||
|
|
||||||
> [!note]
|
|
||||||
> There is a GitHub bug that prevents merging if a PR not following the develop branch of upstream, so please keep follow the develop branch.
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2024.11.0-yumechinokuni.5",
|
"version": "2024.11.0-yumechinokuni.8",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -44,6 +44,14 @@ export class DownloadService {
|
||||||
const maxSize = this.config.maxFileSize;
|
const maxSize = this.config.maxFileSize;
|
||||||
|
|
||||||
const urlObj = new URL(url);
|
const urlObj = new URL(url);
|
||||||
|
if (urlObj.protocol && urlObj.protocol !== 'https:') {
|
||||||
|
throw new Error(`Unsupported protocol: ${urlObj.protocol}, only HTTPS is supported`);
|
||||||
|
}
|
||||||
|
urlObj.protocol = 'https:';
|
||||||
|
if (urlObj.port && urlObj.port !== '443') {
|
||||||
|
throw new Error(`Unsupported port: ${urlObj.port}, only 443 is supported`);
|
||||||
|
}
|
||||||
|
|
||||||
let filename = urlObj.pathname.split('/').pop() ?? 'untitled';
|
let filename = urlObj.pathname.split('/').pop() ?? 'untitled';
|
||||||
|
|
||||||
const req = got.stream(url, {
|
const req = got.stream(url, {
|
||||||
|
|
|
@ -171,9 +171,10 @@ export class HttpRequestService {
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public getAgentByUrl(url: URL, bypassProxy = false): https.Agent {
|
public getAgentByUrl(url: URL, bypassProxy = false): https.Agent {
|
||||||
if (url.protocol !== 'https:') {
|
if (url.protocol && url.protocol !== 'https:') {
|
||||||
throw new Error('Invalid protocol');
|
throw new Error('Invalid protocol');
|
||||||
}
|
}
|
||||||
|
url.protocol = 'https:';
|
||||||
if (url.port && url.port !== '443') {
|
if (url.port && url.port !== '443') {
|
||||||
throw new Error('Invalid port');
|
throw new Error('Invalid port');
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { setImmediate } from 'node:timers/promises';
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import { In, DataSource, IsNull, LessThan } from 'typeorm';
|
import { In, DataSource, IsNull, LessThan } from 'typeorm';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
|
import * as Bull from 'bullmq';
|
||||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||||
import { extractMentions } from '@/misc/extract-mentions.js';
|
import { extractMentions } from '@/misc/extract-mentions.js';
|
||||||
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
||||||
|
@ -293,7 +294,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
case 'followers':
|
case 'followers':
|
||||||
// 他人のfollowers noteはreject
|
// 他人のfollowers noteはreject
|
||||||
if (data.renote.userId !== user.id) {
|
if (data.renote.userId !== user.id) {
|
||||||
throw new Error('Renote target is not public or home');
|
throw new Bull.UnrecoverableError('Renote target is not public or home');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renote対象がfollowersならfollowersにする
|
// Renote対象がfollowersならfollowersにする
|
||||||
|
@ -301,7 +302,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
break;
|
break;
|
||||||
case 'specified':
|
case 'specified':
|
||||||
// specified / direct noteはreject
|
// specified / direct noteはreject
|
||||||
throw new Error('Renote target is not public or home');
|
throw new Bull.UnrecoverableError('Renote target is not public or home');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,7 +83,11 @@ export class WebAuthnService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async verifyRegistration(userId: MiUser['id'], response: RegistrationResponseJSON): Promise<{
|
public async verifyRegistration(
|
||||||
|
userId: MiUser['id'],
|
||||||
|
response: RegistrationResponseJSON,
|
||||||
|
twoFactorOnly: boolean = false,
|
||||||
|
): Promise<{
|
||||||
credentialID: string;
|
credentialID: string;
|
||||||
credentialPublicKey: Uint8Array;
|
credentialPublicKey: Uint8Array;
|
||||||
attestationObject: Uint8Array;
|
attestationObject: Uint8Array;
|
||||||
|
@ -111,7 +115,7 @@ export class WebAuthnService {
|
||||||
expectedChallenge: challenge,
|
expectedChallenge: challenge,
|
||||||
expectedOrigin: relyingParty.origin,
|
expectedOrigin: relyingParty.origin,
|
||||||
expectedRPID: relyingParty.rpId,
|
expectedRPID: relyingParty.rpId,
|
||||||
requireUserVerification: true,
|
requireUserVerification: !twoFactorOnly,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -245,7 +249,11 @@ export class WebAuthnService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async verifyAuthentication(userId: MiUser['id'], response: AuthenticationResponseJSON): Promise<boolean> {
|
public async verifyAuthentication(
|
||||||
|
userId: MiUser['id'],
|
||||||
|
response: AuthenticationResponseJSON,
|
||||||
|
twoFactorOnly: boolean = false,
|
||||||
|
): Promise<boolean> {
|
||||||
const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`);
|
const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`);
|
||||||
|
|
||||||
if (!challenge) {
|
if (!challenge) {
|
||||||
|
@ -302,7 +310,7 @@ export class WebAuthnService {
|
||||||
counter: key.counter,
|
counter: key.counter,
|
||||||
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
|
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
|
||||||
},
|
},
|
||||||
requireUserVerification: true,
|
requireUserVerification: !twoFactorOnly,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
|
@ -64,7 +64,7 @@ export class Resolver {
|
||||||
public async resolveCollection(value: string | IObject): Promise<ICollection | IOrderedCollection> {
|
public async resolveCollection(value: string | IObject): Promise<ICollection | IOrderedCollection> {
|
||||||
const collection = typeof value === 'string'
|
const collection = typeof value === 'string'
|
||||||
? await this.resolve(value)
|
? await this.resolve(value)
|
||||||
: value;
|
: yumeNormalizeObject(value);
|
||||||
|
|
||||||
if (isCollectionOrOrderedCollection(collection)) {
|
if (isCollectionOrOrderedCollection(collection)) {
|
||||||
return collection;
|
return collection;
|
||||||
|
@ -74,7 +74,7 @@ export class Resolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async resolveNotNormalized(value: string | IObject): Promise<IUnsanitizedObject> {
|
private async resolveNotNormalized(value: string | IObject): Promise<IUnsanitizedObject> {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -129,12 +129,8 @@ export class Resolver {
|
||||||
throw new Error('invalid AP object: missing id');
|
throw new Error('invalid AP object: missing id');
|
||||||
}
|
}
|
||||||
|
|
||||||
const idURL = yumeAssertAcceptableURL(object.id);
|
yumeAssertAcceptableURL(object.id);
|
||||||
const valueURL = yumeAssertAcceptableURL(value);
|
yumeAssertAcceptableURL(value);
|
||||||
|
|
||||||
if (toASCII(idURL.host) !== toASCII(valueURL.host)) {
|
|
||||||
throw new Bull.UnrecoverableError(`invalid AP object ${value}: id ${object.id} has different host`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as Bull from 'bullmq';
|
||||||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
@ -164,7 +165,7 @@ export class ApNoteService {
|
||||||
const noteUrl = yumeAssertAcceptableURL(note.id);
|
const noteUrl = yumeAssertAcceptableURL(note.id);
|
||||||
|
|
||||||
if (noteUrl.host !== actUrl.host) {
|
if (noteUrl.host !== actUrl.host) {
|
||||||
throw new Error(`note url & uri host mismatch: note url: ${url}, note uri: ${note.id}`);
|
throw new Bull.UnrecoverableError(`note url & uri host mismatch: note url: ${url}, note uri: ${note.id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,6 @@ export function markOutgoing<T, L extends 'question' | undefined>(object: T, _ba
|
||||||
|
|
||||||
export function yumeNormalizeURL(url: string): string {
|
export function yumeNormalizeURL(url: string): string {
|
||||||
const u = new URL(url);
|
const u = new URL(url);
|
||||||
u.hash = '';
|
|
||||||
u.host = toASCII(u.host);
|
u.host = toASCII(u.host);
|
||||||
if (u.protocol && u.protocol !== 'https:') {
|
if (u.protocol && u.protocol !== 'https:') {
|
||||||
throw new bull.UnrecoverableError('protocol is not https');
|
throw new bull.UnrecoverableError('protocol is not https');
|
||||||
|
@ -104,26 +103,33 @@ export function yumeNormalizeRecursive<O extends IUnsanitizedObject | string | (
|
||||||
if (object.length > 64) {
|
if (object.length > 64) {
|
||||||
throw new bull.UnrecoverableError('array length limit exceeded');
|
throw new bull.UnrecoverableError('array length limit exceeded');
|
||||||
}
|
}
|
||||||
return object.flatMap(yumeNormalizeRecursive);
|
return object.flatMap((x) => yumeNormalizeRecursive(x, depth + (object.length + 3 / 4)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return yumeNormalizeObject(object);
|
return yumeNormalizeObject(object, depth + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function yumeNormalizeObject(object: IUnsanitizedObject): IObject {
|
export function yumeNormalizeObject(object: IUnsanitizedObject, depth = 0): IObject {
|
||||||
if (object.cc) {
|
if (object.cc) {
|
||||||
object.cc = yumeNormalizeRecursive(object.cc);
|
object.cc = yumeNormalizeRecursive(object.cc, depth + 1);
|
||||||
}
|
}
|
||||||
if (object.id) {
|
if (object.id) {
|
||||||
object.id = yumeNormalizeURL(object.id);
|
object.id = yumeNormalizeURL(object.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object.url) {
|
if (object.url) {
|
||||||
object.url = yumeNormalizeRecursive(object.url);
|
object.url = yumeNormalizeRecursive(object.url, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object.replies) {
|
||||||
|
object.replies.first = object.replies.first ?
|
||||||
|
typeof object.replies.first === 'string' ? yumeNormalizeURL(object.replies.first) : yumeNormalizeObject(object.replies.first, depth + 1) : undefined;
|
||||||
|
object.replies.items = object.replies.items ?
|
||||||
|
typeof object.replies.items === 'string' ? yumeNormalizeURL(object.replies.items) : yumeNormalizeRecursive(object.replies.items, depth + 1) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object.inReplyTo) {
|
if (object.inReplyTo) {
|
||||||
object.inReplyTo = yumeNormalizeRecursive(object.inReplyTo);
|
object.inReplyTo = yumeNormalizeRecursive(object.inReplyTo, depth + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return object as IObject;
|
return object as IObject;
|
||||||
|
@ -196,6 +202,8 @@ export interface IActivity extends IObject {
|
||||||
|
|
||||||
export interface SafeList {
|
export interface SafeList {
|
||||||
id: string;
|
id: string;
|
||||||
|
content: string | null;
|
||||||
|
tag: IObject | IObject[];
|
||||||
published: string;
|
published: string;
|
||||||
visibility: string;
|
visibility: string;
|
||||||
mentionedUsers: any[];
|
mentionedUsers: any[];
|
||||||
|
@ -205,6 +213,8 @@ export interface SafeList {
|
||||||
function extractSafe(object: IObject): Partial<SafeList> {
|
function extractSafe(object: IObject): Partial<SafeList> {
|
||||||
return {
|
return {
|
||||||
id: object.id,
|
id: object.id,
|
||||||
|
content: object.content,
|
||||||
|
tag: object.tag,
|
||||||
published: object.published,
|
published: object.published,
|
||||||
visibility: object.visibility,
|
visibility: object.visibility,
|
||||||
mentionedUsers: object.mentionedUsers,
|
mentionedUsers: object.mentionedUsers,
|
||||||
|
@ -612,7 +622,7 @@ export function yumeDowncastRemove(object: IObject): IRemove | null {
|
||||||
export function yumeDowncastLike(object: IObject): ILike | null {
|
export function yumeDowncastLike(object: IObject): ILike | null {
|
||||||
if (getApType(object) !== 'Like') return null;
|
if (getApType(object) !== 'Like') return null;
|
||||||
const obj = object as ILike;
|
const obj = object as ILike;
|
||||||
if (!obj.actor || !obj.object || !obj.target) return null;
|
if (!obj.actor || !obj.object) return null;
|
||||||
return {
|
return {
|
||||||
...extractMisskeyVendorKeys(object),
|
...extractMisskeyVendorKeys(object),
|
||||||
...extractSafe(object),
|
...extractSafe(object),
|
||||||
|
|
|
@ -278,7 +278,17 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
// Other Security/Privacy Headers
|
// Other Security/Privacy Headers
|
||||||
fastify.addHook('onRequest', (_, reply, done) => {
|
fastify.addHook('onRequest', (_, reply, done) => {
|
||||||
reply.header('x-content-type-options', 'nosniff');
|
reply.header('x-content-type-options', 'nosniff');
|
||||||
reply.header('permissions-policy', 'interest-cohort=()'); // Disable FLoC
|
reply.header('permissions-policy',
|
||||||
|
[
|
||||||
|
'interest-cohort',
|
||||||
|
'encrypted-media',
|
||||||
|
'attribution-reporting',
|
||||||
|
'geolocation', 'microphone', 'camera',
|
||||||
|
'midi', 'payment', 'usb', 'serial',
|
||||||
|
'xr-spatial-tracking'
|
||||||
|
]
|
||||||
|
.map(feature => `${feature}=()`).join(', '));
|
||||||
|
|
||||||
if (this.config.browserSandboxing.strictOriginReferrer) {
|
if (this.config.browserSandboxing.strictOriginReferrer) {
|
||||||
reply.header('referrer-policy', 'strict-origin');
|
reply.header('referrer-policy', 'strict-origin');
|
||||||
}
|
}
|
||||||
|
|
|
@ -255,7 +255,7 @@ export class SigninApiService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const authorized = await this.webAuthnService.verifyAuthentication(user.id, body.credential);
|
const authorized = await this.webAuthnService.verifyAuthentication(user.id, body.credential, !profile.usePasswordLessLogin);
|
||||||
|
|
||||||
if (authorized) {
|
if (authorized) {
|
||||||
return this.signinService.signin(request, reply, user);
|
return this.signinService.signin(request, reply, user);
|
||||||
|
|
|
@ -95,7 +95,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
throw new ApiError(meta.errors.twoFactorNotEnabled);
|
throw new ApiError(meta.errors.twoFactorNotEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyInfo = await this.webAuthnService.verifyRegistration(me.id, ps.credential);
|
const keyInfo = await this.webAuthnService.verifyRegistration(me.id, ps.credential, !profile.usePasswordLessLogin);
|
||||||
const keyId = keyInfo.credentialID;
|
const keyId = keyInfo.credentialID;
|
||||||
|
|
||||||
await this.userSecurityKeysRepository.insert({
|
await this.userSecurityKeysRepository.insert({
|
||||||
|
|
|
@ -7,6 +7,6 @@ export const commonPugFilters = {
|
||||||
throw new Error('Invalid mimeType');
|
throw new Error('Invalid mimeType');
|
||||||
}
|
}
|
||||||
const dataURI = `data:${options.mimeType};base64,${Buffer.from(data).toString('base64')}`;
|
const dataURI = `data:${options.mimeType};base64,${Buffer.from(data).toString('base64')}`;
|
||||||
return `<${options.tagName} data="${dataURI}"></${options.tagName}>`;
|
return `<${options.tagName} src="${dataURI}"></${options.tagName}>`;
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -248,16 +248,6 @@ export class ClientServerService {
|
||||||
fastify.addHook('onRequest', makeHstsHook(host, preload));
|
fastify.addHook('onRequest', makeHstsHook(host, preload));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other Security/Privacy Headers
|
|
||||||
fastify.addHook('onRequest', (_, reply, done) => {
|
|
||||||
reply.header('x-content-type-options', 'nosniff');
|
|
||||||
reply.header('permissions-policy', 'interest-cohort=()'); // Disable FLoC
|
|
||||||
if (this.config.browserSandboxing.strictOriginReferrer ?? true) {
|
|
||||||
reply.header('referrer-policy', 'strict-origin');
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
// CSP
|
// CSP
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
console.debug('cspPrerenderedContent', this.config.cspPrerenderedContent);
|
console.debug('cspPrerenderedContent', this.config.cspPrerenderedContent);
|
||||||
|
@ -503,6 +493,7 @@ export class ClientServerService {
|
||||||
|
|
||||||
// ServiceWorker
|
// ServiceWorker
|
||||||
fastify.get('/sw.js', async (request, reply) => {
|
fastify.get('/sw.js', async (request, reply) => {
|
||||||
|
reply.header('content-security-policy', `default-src \'self'; connect-src \'self\'${ this.config.mediaProxy ? ` ${new URL(this.config.mediaProxy).origin}` : '' }`);
|
||||||
return await reply.sendFile('/sw.js', swAssets, {
|
return await reply.sendFile('/sw.js', swAssets, {
|
||||||
maxAge: ms('10 minutes'),
|
maxAge: ms('10 minutes'),
|
||||||
});
|
});
|
||||||
|
@ -561,6 +552,7 @@ export class ClientServerService {
|
||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: host ?? IsNull(),
|
host: host ?? IsNull(),
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
|
requireSigninToViewContents: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return user && await this.feedService.packFeed(user);
|
return user && await this.feedService.packFeed(user);
|
||||||
|
@ -615,12 +607,21 @@ export class ClientServerService {
|
||||||
// User
|
// User
|
||||||
fastify.get<{ Params: { user: string; sub?: string; } }>('/@:user/:sub?', async (request, reply) => {
|
fastify.get<{ Params: { user: string; sub?: string; } }>('/@:user/:sub?', async (request, reply) => {
|
||||||
const { username, host } = Acct.parse(request.params.user);
|
const { username, host } = Acct.parse(request.params.user);
|
||||||
|
|
||||||
|
if (host) {
|
||||||
|
return await renderBase(reply); // リモートユーザーのページはSSRしない (プライバシーの観点から)
|
||||||
|
}
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
const user = await this.usersRepository.findOneBy({
|
||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: host ?? IsNull(),
|
host: host ?? IsNull(),
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (user?.requireSigninToViewContents) {
|
||||||
|
return await renderBase(reply);
|
||||||
|
}
|
||||||
|
|
||||||
vary(reply.raw, 'Accept');
|
vary(reply.raw, 'Accept');
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
@ -637,7 +638,9 @@ export class ClientServerService {
|
||||||
reply.header('X-Robots-Tag', 'noai');
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
}
|
}
|
||||||
|
|
||||||
const _user = await this.userEntityService.pack(user);
|
const _user = await this.userEntityService.pack(user, null, {
|
||||||
|
schema: host ? 'UserLite' : 'UserDetailedNotMe' // リモートユーザーの場合は詳細情報を返さない
|
||||||
|
});
|
||||||
|
|
||||||
return await reply.view('user', {
|
return await reply.view('user', {
|
||||||
user, profile, me,
|
user, profile, me,
|
||||||
|
@ -660,6 +663,7 @@ export class ClientServerService {
|
||||||
id: request.params.user,
|
id: request.params.user,
|
||||||
host: IsNull(),
|
host: IsNull(),
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
|
requireSigninToViewContents: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -711,9 +715,14 @@ export class ClientServerService {
|
||||||
// Page
|
// Page
|
||||||
fastify.get<{ Params: { user: string; page: string; } }>('/@:user/pages/:page', async (request, reply) => {
|
fastify.get<{ Params: { user: string; page: string; } }>('/@:user/pages/:page', async (request, reply) => {
|
||||||
const { username, host } = Acct.parse(request.params.user);
|
const { username, host } = Acct.parse(request.params.user);
|
||||||
|
|
||||||
|
if (host) {
|
||||||
|
return await renderBase(reply); // リモートユーザーのページはSSRしない
|
||||||
|
}
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
const user = await this.usersRepository.findOneBy({
|
||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: host ?? IsNull(),
|
host: IsNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
|
|
|
@ -5,6 +5,136 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
class Systemd {
|
||||||
|
constructor(version, cmdline) {
|
||||||
|
this.tty_dom = document.querySelector('#tty');
|
||||||
|
const welcome = document.createElement('div');
|
||||||
|
welcome.className = 'tty-line';
|
||||||
|
welcome.innerText = `YumechiNoKuni ${version} running in Web mode. (+mproxy, +metrics, +csp) cmdline: ${cmdline}`;
|
||||||
|
this.tty_dom.appendChild(welcome);
|
||||||
|
}
|
||||||
|
async start(id, promise) {
|
||||||
|
let state = { state: 'running' };
|
||||||
|
let persistentDom = null;
|
||||||
|
const started = Date.now();
|
||||||
|
const formatRunning = () => {
|
||||||
|
const shiftArray = (arr, n) => {
|
||||||
|
return arr.slice(n).concat(arr.slice(0, n));
|
||||||
|
};
|
||||||
|
const elapsed_secs = Math.floor((Date.now() - started) / 1000);
|
||||||
|
const stars = shiftArray([' ', '*', '*', '*', ' ', ' '], elapsed_secs % 6);
|
||||||
|
const spanStatus = document.createElement('span');
|
||||||
|
spanStatus.innerText = stars.join('');
|
||||||
|
spanStatus.className = 'tty-status-running';
|
||||||
|
const spanMessage = document.createElement('span');
|
||||||
|
spanMessage.innerText = `A start job is running for ${id} (${elapsed_secs}s / no limit)`;
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'tty-line';
|
||||||
|
div.innerHTML = '[';
|
||||||
|
div.appendChild(spanStatus);
|
||||||
|
div.innerHTML += '] ';
|
||||||
|
div.appendChild(spanMessage);
|
||||||
|
return div;
|
||||||
|
};
|
||||||
|
const formatDone = () => {
|
||||||
|
const elapsed_secs = (Date.now() - started) / 1000;
|
||||||
|
const spanStatus = document.createElement('span');
|
||||||
|
spanStatus.innerText = ' OK ';
|
||||||
|
spanStatus.className = 'tty-status-ok';
|
||||||
|
const spanMessage = document.createElement('span');
|
||||||
|
spanMessage.innerText = `Finished ${id} in ${elapsed_secs.toFixed(3)}s`;
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'tty-line';
|
||||||
|
div.innerHTML = '[';
|
||||||
|
div.appendChild(spanStatus);
|
||||||
|
div.innerHTML += '] ';
|
||||||
|
div.appendChild(spanMessage);
|
||||||
|
return div;
|
||||||
|
};
|
||||||
|
const formatFailed = (message) => {
|
||||||
|
const elapsed_secs = (Date.now() - started) / 1000;
|
||||||
|
const spanStatus = document.createElement('span');
|
||||||
|
spanStatus.innerText = 'FAILED';
|
||||||
|
spanStatus.className = 'tty-status-failed';
|
||||||
|
const spanMessage = document.createElement('span');
|
||||||
|
spanMessage.innerText = `Failed ${id} in ${elapsed_secs.toFixed(3)}s: ${message}`;
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'tty-line';
|
||||||
|
div.innerHTML = '[';
|
||||||
|
div.appendChild(spanStatus);
|
||||||
|
div.innerHTML += '] ';
|
||||||
|
div.appendChild(spanMessage);
|
||||||
|
return div;
|
||||||
|
};
|
||||||
|
const render = () => {
|
||||||
|
switch (state.state) {
|
||||||
|
case 'running':
|
||||||
|
if (persistentDom === null) {
|
||||||
|
persistentDom = formatRunning();
|
||||||
|
this.tty_dom.appendChild(persistentDom);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
persistentDom.innerHTML = formatRunning().innerHTML;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'done':
|
||||||
|
if (persistentDom === null) {
|
||||||
|
persistentDom = formatDone();
|
||||||
|
this.tty_dom.appendChild(persistentDom);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
persistentDom.innerHTML = formatDone().innerHTML;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'failed':
|
||||||
|
if (persistentDom === null) {
|
||||||
|
persistentDom = formatFailed(state.message);
|
||||||
|
this.tty_dom.appendChild(persistentDom);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
persistentDom.innerHTML = formatFailed(state.message).innerHTML;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
render();
|
||||||
|
const interval = setInterval(render, 500);
|
||||||
|
try {
|
||||||
|
let res = await promise;
|
||||||
|
state = { state: 'done' };
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
state = { state: 'failed', message: e.message };
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = { state: 'failed', message: 'Unknown error' };
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
clearInterval(interval);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async startSync(id, func) {
|
||||||
|
return this.start(id, (async () => {
|
||||||
|
return func();
|
||||||
|
})());
|
||||||
|
}
|
||||||
|
emergency_mode(code, details) {``
|
||||||
|
const divPrev = document.createElement('div');
|
||||||
|
divPrev.className = 'tty-line';
|
||||||
|
divPrev.innerText = 'Critical error occurred [' + code + '] : ' + details.message ? details.message : details;
|
||||||
|
this.tty_dom.appendChild(divPrev);
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'tty-line';
|
||||||
|
div.innerText = 'You are in emergency mode. Type Ctrl-Shift-I to view system logs. Clearing local storage by going to /flush and browser settings may help.';
|
||||||
|
this.tty_dom.appendChild(div);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
|
// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
|
||||||
(async () => {
|
(async () => {
|
||||||
window.onerror = (e) => {
|
window.onerror = (e) => {
|
||||||
|
@ -16,10 +146,24 @@
|
||||||
renderError('SOMETHING_HAPPENED_IN_PROMISE', e);
|
renderError('SOMETHING_HAPPENED_IN_PROMISE', e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cmdline = new URLSearchParams(location.search).get('cmdline') || '';
|
||||||
|
const cmdlineArray = cmdline.split(',').map(x => x.trim());
|
||||||
|
if (cmdlineArray.includes('nosplash')) {
|
||||||
|
document.querySelector('#splashIcon').classList.add('hidden');
|
||||||
|
document.querySelector('#splashSpinner').classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
const systemd = new Systemd(VERSION, cmdline);
|
||||||
|
|
||||||
|
if (cmdlineArray.includes('leak')) {
|
||||||
|
await systemd.start('Promise Leak Service', new Promise(() => { }));
|
||||||
|
}
|
||||||
|
|
||||||
let forceError = localStorage.getItem('forceError');
|
let forceError = localStorage.getItem('forceError');
|
||||||
if (forceError != null) {
|
if (forceError != null) {
|
||||||
renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.');
|
await systemd.startSync('Force Error Service', () => {
|
||||||
return;
|
throw new Error('This error is forced by having forceError in local storage.');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region Detect language & fetch translations
|
//#region Detect language & fetch translations
|
||||||
|
@ -37,7 +181,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const metaRes = await window.fetch('/api/meta', {
|
const metaRes = await systemd.start('Fetch /api/meta',window.fetch('/api/meta', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({}),
|
body: JSON.stringify({}),
|
||||||
credentials: 'omit',
|
credentials: 'omit',
|
||||||
|
@ -45,12 +189,12 @@
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
if (metaRes.status !== 200) {
|
if (metaRes.status !== 200) {
|
||||||
renderError('META_FETCH');
|
renderError('META_FETCH');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const meta = await metaRes.json();
|
const meta = await systemd.start('Parse /api/meta', metaRes.json());
|
||||||
const v = meta.version;
|
const v = meta.version;
|
||||||
if (v == null) {
|
if (v == null) {
|
||||||
renderError('META_FETCH_V');
|
renderError('META_FETCH_V');
|
||||||
|
@ -63,7 +207,7 @@
|
||||||
lang = 'en-US';
|
lang = 'en-US';
|
||||||
}
|
}
|
||||||
|
|
||||||
const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`);
|
const localRes = await systemd.start('Fetch Locale files', window.fetch(`/assets/locales/${lang}.${v}.json`));
|
||||||
if (localRes.status === 200) {
|
if (localRes.status === 200) {
|
||||||
localStorage.setItem('lang', lang);
|
localStorage.setItem('lang', lang);
|
||||||
localStorage.setItem('locale', await localRes.text());
|
localStorage.setItem('locale', await localRes.text());
|
||||||
|
@ -77,19 +221,25 @@
|
||||||
|
|
||||||
//#region Script
|
//#region Script
|
||||||
async function importAppScript() {
|
async function importAppScript() {
|
||||||
await import(`/vite/${CLIENT_ENTRY}`)
|
await systemd.start('Load App Script', import(`/vite/${CLIENT_ENTRY}`))
|
||||||
.catch(async e => {
|
.catch(async e => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
renderError('APP_IMPORT', e);
|
renderError('APP_IMPORT', e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cmdlineArray.includes('fail')) {
|
||||||
|
await systemd.startSync('Force Error Service', () => {
|
||||||
|
throw new Error('This error is forced by having fail in command line.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある
|
// タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある
|
||||||
if (document.readyState !== 'loading') {
|
if (document.readyState !== 'loading') {
|
||||||
importAppScript();
|
systemd.start('import App Script', importAppScript());
|
||||||
} else {
|
} else {
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
importAppScript();
|
systemd.start('import App Script', importAppScript());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -97,19 +247,21 @@
|
||||||
//#region Theme
|
//#region Theme
|
||||||
const theme = localStorage.getItem('theme');
|
const theme = localStorage.getItem('theme');
|
||||||
if (theme) {
|
if (theme) {
|
||||||
for (const [k, v] of Object.entries(JSON.parse(theme))) {
|
await systemd.startSync('Apply theme', () => {
|
||||||
document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
|
for (const [k, v] of Object.entries(JSON.parse(theme))) {
|
||||||
|
document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
|
||||||
|
|
||||||
// HTMLの theme-color 適用
|
// HTMLの theme-color 適用
|
||||||
if (k === 'htmlThemeColor') {
|
if (k === 'htmlThemeColor') {
|
||||||
for (const tag of document.head.children) {
|
for (const tag of document.head.children) {
|
||||||
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
|
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
|
||||||
tag.setAttribute('content', v);
|
tag.setAttribute('content', v);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
const colorScheme = localStorage.getItem('colorScheme');
|
const colorScheme = localStorage.getItem('colorScheme');
|
||||||
if (colorScheme) {
|
if (colorScheme) {
|
||||||
|
@ -134,181 +286,22 @@
|
||||||
|
|
||||||
const customCss = localStorage.getItem('customCss');
|
const customCss = localStorage.getItem('customCss');
|
||||||
if (customCss && customCss.length > 0) {
|
if (customCss && customCss.length > 0) {
|
||||||
const style = document.createElement('style');
|
await systemd.startSync('Apply custom CSS', () => {
|
||||||
style.innerHTML = customCss;
|
const style = document.createElement('style');
|
||||||
document.head.appendChild(style);
|
style.innerHTML = customCss;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addStyle(styleText) {
|
async function addStyle(styleText) {
|
||||||
let css = document.createElement('style');
|
await systemd.startSync('Apply custom Style', () => {
|
||||||
css.appendChild(document.createTextNode(styleText));
|
let css = document.createElement('style');
|
||||||
document.head.appendChild(css);
|
css.appendChild(document.createTextNode(styleText));
|
||||||
|
document.head.appendChild(css);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderError(code, details) {
|
async function renderError(code, details) {
|
||||||
// Cannot set property 'innerHTML' of null を回避
|
systemd.emergency_mode(code, details);
|
||||||
if (document.readyState === 'loading') {
|
|
||||||
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
|
|
||||||
}
|
|
||||||
|
|
||||||
let errorsElement = document.getElementById('errors');
|
|
||||||
|
|
||||||
if (!errorsElement) {
|
|
||||||
document.body.innerHTML = `
|
|
||||||
<svg class="icon-warning" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
||||||
<path d="M12 9v2m0 4v.01"></path>
|
|
||||||
<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path>
|
|
||||||
</svg>
|
|
||||||
<h1>Failed to load<br>読み込みに失敗しました</h1>
|
|
||||||
<button class="button-big" onclick="location.reload(true);">
|
|
||||||
<span class="button-label-big">Reload / リロード</span>
|
|
||||||
</button>
|
|
||||||
<p><b>The following actions may solve the problem. / 以下を行うと解決する可能性があります。</b></p>
|
|
||||||
<p>Update your os and browser / ブラウザおよびOSを最新バージョンに更新する</p>
|
|
||||||
<p>Disable an adblocker / アドブロッカーを無効にする</p>
|
|
||||||
<p>Clear the browser cache / ブラウザのキャッシュをクリアする</p>
|
|
||||||
<p>(Tor Browser) Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する</p>
|
|
||||||
<details style="color: #86b300;">
|
|
||||||
<summary>Other options / その他のオプション</summary>
|
|
||||||
<a href="/flush">
|
|
||||||
<button class="button-small">
|
|
||||||
<span class="button-label-small">Clear preferences and cache</span>
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
<br>
|
|
||||||
<a href="/cli">
|
|
||||||
<button class="button-small">
|
|
||||||
<span class="button-label-small">Start the simple client</span>
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
<br>
|
|
||||||
<a href="/bios">
|
|
||||||
<button class="button-small">
|
|
||||||
<span class="button-label-small">Start the repair tool</span>
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
</details>
|
|
||||||
<br>
|
|
||||||
<div id="errors"></div>
|
|
||||||
`;
|
|
||||||
errorsElement = document.getElementById('errors');
|
|
||||||
}
|
|
||||||
const detailsElement = document.createElement('details');
|
|
||||||
detailsElement.id = 'errorInfo';
|
|
||||||
detailsElement.innerHTML = `
|
|
||||||
<br>
|
|
||||||
<summary>
|
|
||||||
<code>ERROR CODE: ${code}</code>
|
|
||||||
</summary>
|
|
||||||
<code>${details.toString()} ${JSON.stringify(details)}</code>`;
|
|
||||||
errorsElement.appendChild(detailsElement);
|
|
||||||
addStyle(`
|
|
||||||
* {
|
|
||||||
font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
#misskey_app,
|
|
||||||
#splash {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body,
|
|
||||||
html {
|
|
||||||
background-color: #222;
|
|
||||||
color: #dfddcc;
|
|
||||||
justify-content: center;
|
|
||||||
margin: auto;
|
|
||||||
padding: 10px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 999px;
|
|
||||||
padding: 0px 12px 0px 12px;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-big {
|
|
||||||
background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
|
|
||||||
line-height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-big:hover {
|
|
||||||
background: rgb(153, 204, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-small {
|
|
||||||
background: #444;
|
|
||||||
line-height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-small:hover {
|
|
||||||
background: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-label-big {
|
|
||||||
color: #222;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2em;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-label-small {
|
|
||||||
color: rgb(153, 204, 0);
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: rgb(134, 179, 0);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
p,
|
|
||||||
li {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-warning {
|
|
||||||
color: #dec340;
|
|
||||||
height: 4rem;
|
|
||||||
padding-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: Fira, FiraCode, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
#errorInfo {
|
|
||||||
background: #333;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
width: 40rem;
|
|
||||||
border-radius: 10px;
|
|
||||||
justify-content: center;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#errorInfo summary {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#errorInfo summary > * {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
|
||||||
#errorInfo {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
}`);
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -9,6 +9,32 @@ html {
|
||||||
color: var(--MI_THEME-fg);
|
color: var(--MI_THEME-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tty {
|
||||||
|
z-index: 10001;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tty > .tty-line {
|
||||||
|
font-family: 'Courier New', Courier, monospace !important;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tty > .tty-line .tty-status-ok {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tty > .tty-line .tty-status-failed {
|
||||||
|
color: darkred;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tty > .tty-line .tty-status-running {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
#splash {
|
#splash {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
|
@ -42,7 +68,7 @@ html {
|
||||||
left: 0;
|
left: 0;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 28px;
|
width: 60px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
transform: translateY(70px);
|
transform: translateY(70px);
|
||||||
color: var(--MI_THEME-accent);
|
color: var(--MI_THEME-accent);
|
||||||
|
@ -60,6 +86,16 @@ html {
|
||||||
stroke-linejoin: round;
|
stroke-linejoin: round;
|
||||||
stroke-miterlimit: 1.5;
|
stroke-miterlimit: 1.5;
|
||||||
}
|
}
|
||||||
|
#splashSpinner > .spinner.bg circle {
|
||||||
|
fill:none;
|
||||||
|
stroke:currentColor;
|
||||||
|
stroke-width:24px;
|
||||||
|
}
|
||||||
|
#splashSpinner > .spinner.fg path {
|
||||||
|
fill:none;
|
||||||
|
stroke:currentColor;
|
||||||
|
stroke-width:24px;
|
||||||
|
}
|
||||||
#splashSpinner > .spinner.bg {
|
#splashSpinner > .spinner.bg {
|
||||||
opacity: 0.275;
|
opacity: 0.275;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ html.embed.noborder #splash {
|
||||||
left: 0;
|
left: 0;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 28px;
|
width: 60px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
transform: translateY(70px);
|
transform: translateY(70px);
|
||||||
color: var(--MI_THEME-accent);
|
color: var(--MI_THEME-accent);
|
||||||
|
@ -82,6 +82,16 @@ html.embed.noborder #splash {
|
||||||
stroke-linejoin: round;
|
stroke-linejoin: round;
|
||||||
stroke-miterlimit: 1.5;
|
stroke-miterlimit: 1.5;
|
||||||
}
|
}
|
||||||
|
#splashSpinner > .spinner.bg circle {
|
||||||
|
fill:none;
|
||||||
|
stroke:currentColor;
|
||||||
|
stroke-width:24px;
|
||||||
|
}
|
||||||
|
#splashSpinner > .spinner.fg path {
|
||||||
|
fill:none;
|
||||||
|
stroke:currentColor;
|
||||||
|
stroke-width:24px;
|
||||||
|
}
|
||||||
#splashSpinner > .spinner.bg {
|
#splashSpinner > .spinner.bg {
|
||||||
opacity: 0.275;
|
opacity: 0.275;
|
||||||
}
|
}
|
||||||
|
|
151
packages/backend/src/server/web/systemd.ts
Normal file
151
packages/backend/src/server/web/systemd.ts
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
export class Systemd {
|
||||||
|
private tty_dom: HTMLDivElement;
|
||||||
|
constructor() {
|
||||||
|
this.tty_dom = document.querySelector('#tty') as HTMLDivElement;
|
||||||
|
|
||||||
|
console.log('Systemd started');
|
||||||
|
}
|
||||||
|
|
||||||
|
async start<T>(id: string, promise: Promise<T>): Promise<T> {
|
||||||
|
|
||||||
|
let state: {
|
||||||
|
state: 'running'
|
||||||
|
} | {
|
||||||
|
state: 'done'
|
||||||
|
} | {
|
||||||
|
state: 'failed'
|
||||||
|
message: string
|
||||||
|
} = { state: 'running' };
|
||||||
|
|
||||||
|
let persistentDom : HTMLDivElement | null = null;
|
||||||
|
|
||||||
|
const started = Date.now();
|
||||||
|
|
||||||
|
const formatRunning = () => {
|
||||||
|
const shiftArray = <T>(arr: T[], n: number): T[] => {
|
||||||
|
return arr.slice(n).concat(arr.slice(0, n));
|
||||||
|
};
|
||||||
|
|
||||||
|
const elapsed_secs = Math.floor((Date.now() - started) / 1000);
|
||||||
|
const stars = shiftArray(['*', '*', '*', ' ', ' ', ' '], elapsed_secs % 6);
|
||||||
|
|
||||||
|
const spanStatus = document.createElement('span');
|
||||||
|
|
||||||
|
spanStatus.innerText = stars.join('');
|
||||||
|
spanStatus.className = 'tty-status-running';
|
||||||
|
|
||||||
|
const spanMessage = document.createElement('span');
|
||||||
|
spanMessage.innerText = `A start job is running for ${id} (${elapsed_secs}s / no limit)`;
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'tty-line';
|
||||||
|
div.innerHTML = '[';
|
||||||
|
div.appendChild(spanStatus);
|
||||||
|
div.innerHTML += '] ';
|
||||||
|
div.appendChild(spanMessage);
|
||||||
|
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDone = () => {
|
||||||
|
const elapsed_secs = Math.floor((Date.now() - started) / 1000);
|
||||||
|
|
||||||
|
const spanStatus = document.createElement('span');
|
||||||
|
spanStatus.innerText = ' OK ';
|
||||||
|
spanStatus.className = 'tty-status-ok';
|
||||||
|
|
||||||
|
const spanMessage = document.createElement('span');
|
||||||
|
spanMessage.innerText = `Finished ${id} in ${elapsed_secs}s`;
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'tty-line';
|
||||||
|
div.innerHTML = '[';
|
||||||
|
div.appendChild(spanStatus);
|
||||||
|
div.innerHTML += '] ';
|
||||||
|
div.appendChild(spanMessage);
|
||||||
|
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatFailed = (message: string) => {
|
||||||
|
const elapsed_secs = Math.floor((Date.now() - started) / 1000);
|
||||||
|
|
||||||
|
const spanStatus = document.createElement('span');
|
||||||
|
spanStatus.innerText = 'FAILED';
|
||||||
|
spanStatus.className = 'tty-status-failed';
|
||||||
|
|
||||||
|
const spanMessage = document.createElement('span');
|
||||||
|
spanMessage.innerText = `Failed ${id} in ${elapsed_secs}s: ${message}`;
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'tty-line';
|
||||||
|
div.innerHTML = '[';
|
||||||
|
div.appendChild(spanStatus);
|
||||||
|
div.innerHTML += '] ';
|
||||||
|
div.appendChild(spanMessage);
|
||||||
|
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
const render = () => {
|
||||||
|
switch (state.state) {
|
||||||
|
case 'running':
|
||||||
|
if (persistentDom === null) {
|
||||||
|
persistentDom = formatRunning();
|
||||||
|
this.tty_dom.appendChild(persistentDom);
|
||||||
|
} else {
|
||||||
|
persistentDom.innerHTML = formatRunning().innerHTML;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'done':
|
||||||
|
if (persistentDom === null) {
|
||||||
|
persistentDom = formatDone();
|
||||||
|
this.tty_dom.appendChild(persistentDom);
|
||||||
|
} else {
|
||||||
|
persistentDom.innerHTML = formatDone().innerHTML;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'failed':
|
||||||
|
if (persistentDom === null) {
|
||||||
|
persistentDom = formatFailed(state.message);
|
||||||
|
this.tty_dom.appendChild(persistentDom);
|
||||||
|
} else {
|
||||||
|
persistentDom.innerHTML = formatFailed(state.message).innerHTML;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render();
|
||||||
|
const interval = setInterval(render, 500);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let res = await promise;
|
||||||
|
state = { state: 'done' };
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
state = { state: 'failed', message: e.message };
|
||||||
|
} else {
|
||||||
|
state = { state: 'failed', message: 'Unknown error' };
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
clearInterval(interval);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async startSync<T>(id: string, func: () => T): Promise<T> {
|
||||||
|
return this.start(id, (async () => {
|
||||||
|
return func();
|
||||||
|
})());
|
||||||
|
}
|
||||||
|
|
||||||
|
public emergency_mode() {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'tty-line';
|
||||||
|
div.innerText = 'You are in emergency mode. Type Ctrl-Shift-I to view logs.';
|
||||||
|
this.tty_dom.appendChild(div);
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,18 +56,8 @@ html(class='embed')
|
||||||
br
|
br
|
||||||
| Please turn on your JavaScript
|
| Please turn on your JavaScript
|
||||||
div#splash
|
div#splash
|
||||||
|
div#tty
|
||||||
img#splashIcon(src= icon || '/static-assets/splash.png')
|
img#splashIcon(src= icon || '/static-assets/splash.png')
|
||||||
div#splashSpinner
|
div#splashSpinner
|
||||||
:dataTag(tagName='img' mimeType='image/svg+xml')
|
<span>Loading...</span>
|
||||||
<svg class="spinner bg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g transform="matrix(1,0,0,1,12,12)">
|
|
||||||
<circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:24px;"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
:dataTag(tagName='img' mimeType='image/svg+xml')
|
|
||||||
<svg class="spinner fg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g transform="matrix(1,0,0,1,12,12)">
|
|
||||||
<path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:24px;"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
block content
|
block content
|
||||||
|
|
|
@ -9,17 +9,6 @@ block loadClientEntry
|
||||||
doctype html
|
doctype html
|
||||||
|
|
||||||
//
|
//
|
||||||
-
|
|
||||||
_____ _ _
|
|
||||||
| |_|___ ___| |_ ___ _ _
|
|
||||||
| | | | |_ -|_ -| '_| -_| | |
|
|
||||||
|_|_|_|_|___|___|_,_|___|_ |
|
|
||||||
|___|
|
|
||||||
Thank you for using Misskey!
|
|
||||||
If you are reading this message... how about joining the development?
|
|
||||||
https://github.com/misskey-dev/misskey
|
|
||||||
|
|
||||||
|
|
||||||
html
|
html
|
||||||
|
|
||||||
head
|
head
|
||||||
|
@ -76,7 +65,6 @@ html
|
||||||
script(type='application/json' id='misskey_clientCtx' data-generated-at=now)
|
script(type='application/json' id='misskey_clientCtx' data-generated-at=now)
|
||||||
!= clientCtx
|
!= clientCtx
|
||||||
|
|
||||||
script(integrity=bootJS.integrity) !{bootJS.content}
|
|
||||||
|
|
||||||
body
|
body
|
||||||
noscript: p
|
noscript: p
|
||||||
|
@ -84,18 +72,11 @@ html
|
||||||
br
|
br
|
||||||
| Please turn on your JavaScript
|
| Please turn on your JavaScript
|
||||||
div#splash
|
div#splash
|
||||||
|
div#tty
|
||||||
img#splashIcon(src= icon || '/static-assets/splash.png')
|
img#splashIcon(src= icon || '/static-assets/splash.png')
|
||||||
div#splashSpinner
|
div#splashSpinner
|
||||||
:dataTag(tagName='img' mimeType='image/svg+xml')
|
<span>Loading...</span>
|
||||||
<svg class="spinner bg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g transform="matrix(1,0,0,1,12,12)">
|
script(integrity=bootJS.integrity) !{bootJS.content}
|
||||||
<circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:24px;"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
:dataTag(tagName='img' mimeType='image/svg+xml')
|
|
||||||
<svg class="spinner fg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g transform="matrix(1,0,0,1,12,12)">
|
|
||||||
<path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:24px;"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
block content
|
block content
|
||||||
|
|
|
@ -1,17 +1,6 @@
|
||||||
doctype html
|
doctype html
|
||||||
|
|
||||||
//
|
//
|
||||||
-
|
|
||||||
_____ _ _
|
|
||||||
| |_|___ ___| |_ ___ _ _
|
|
||||||
| | | | |_ -|_ -| '_| -_| | |
|
|
||||||
|_|_|_|_|___|___|_,_|___|_ |
|
|
||||||
|___|
|
|
||||||
Thank you for using Misskey!
|
|
||||||
If you are reading this message... how about joining the development?
|
|
||||||
https://github.com/misskey-dev/misskey
|
|
||||||
|
|
||||||
|
|
||||||
html
|
html
|
||||||
|
|
||||||
head
|
head
|
||||||
|
|
|
@ -45,7 +45,8 @@ const queryingKey = ref(true);
|
||||||
async function queryKey() {
|
async function queryKey() {
|
||||||
queryingKey.value = true;
|
queryingKey.value = true;
|
||||||
await webAuthnRequest(props.credentialRequest)
|
await webAuthnRequest(props.credentialRequest)
|
||||||
.catch(() => {
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
return Promise.reject(null);
|
return Promise.reject(null);
|
||||||
})
|
})
|
||||||
.then((credential) => {
|
.then((credential) => {
|
||||||
|
@ -53,7 +54,7 @@ async function queryKey() {
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
queryingKey.value = false;
|
queryingKey.value = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -8,12 +8,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div :class="$style.container">
|
<div :class="$style.container">
|
||||||
<svg :class="[$style.spinner, $style.bg]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg">
|
<svg :class="[$style.spinner, $style.bg]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g transform="matrix(1.125,0,0,1.125,12,12)">
|
<g transform="matrix(1.125,0,0,1.125,12,12)">
|
||||||
<circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/>
|
<circle cx="64" cy="64" r="64" :class="[$style.bgcircle]"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<svg :class="[$style.spinner, $style.fg, { [$style.static]: static }]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg">
|
<svg :class="[$style.spinner, $style.fg, { [$style.static]: static }]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g transform="matrix(1.125,0,0,1.125,12,12)">
|
<g transform="matrix(1.125,0,0,1.125,12,12)">
|
||||||
<path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/>
|
<path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" :class="[$style.fgpath]"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
@ -109,4 +109,16 @@ const props = withDefaults(defineProps<{
|
||||||
animation-play-state: paused;
|
animation-play-state: paused;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bgcircle {
|
||||||
|
fill:none;
|
||||||
|
stroke:currentColor;
|
||||||
|
stroke-width:21.33px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fgpath {
|
||||||
|
fill:none;
|
||||||
|
stroke:currentColor;
|
||||||
|
stroke-width:21.33px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<FormLink to="/about-misskey">
|
<FormLink to="/about-misskey">
|
||||||
<template #icon><i class="ti ti-info-circle"></i></template>
|
<template #icon><i class="ti ti-info-circle"></i></template>
|
||||||
{{ i18n.ts.aboutMisskey }}
|
{{ i18n.ts.aboutMisskey }} (Upstream)
|
||||||
</FormLink>
|
</FormLink>
|
||||||
<FormLink v-if="instance.repositoryUrl || instance.providesTarball" :to="instance.repositoryUrl || `/tarball/misskey-${version}.tar.gz`" external>
|
<FormLink v-if="instance.repositoryUrl || instance.providesTarball" :to="instance.repositoryUrl || `/tarball/misskey-${version}.tar.gz`" external>
|
||||||
<template #icon><i class="ti ti-code"></i></template>
|
<template #icon><i class="ti ti-code"></i></template>
|
||||||
|
|
|
@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div><MkSparkle><Mfm :plain="true" :text="user.followedMessage" :author="user"/></MkSparkle></div>
|
<div><MkSparkle><Mfm :plain="true" :text="user.followedMessage" :author="user"/></MkSparkle></div>
|
||||||
</MkFukidashi>
|
</MkFukidashi>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="user.roles.length > 0" class="roles">
|
<div v-if="user.roles && user.roles.length > 0" class="roles">
|
||||||
<span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }">
|
<span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }">
|
||||||
<MkA v-adaptive-bg :to="`/roles/${role.id}`">
|
<MkA v-adaptive-bg :to="`/roles/${role.id}`">
|
||||||
<img v-if="role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="role.iconUrl"/>
|
<img v-if="role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="role.iconUrl"/>
|
||||||
|
@ -96,7 +96,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<dt class="name"><i class="ti ti-cake ti-fw"></i> {{ i18n.ts.birthday }}</dt>
|
<dt class="name"><i class="ti ti-cake ti-fw"></i> {{ i18n.ts.birthday }}</dt>
|
||||||
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ i18n.tsx.yearsOld({ age }) }})</dd>
|
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ i18n.tsx.yearsOld({ age }) }})</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<dl class="field">
|
<dl v-if="user.createdAt" class="field">
|
||||||
<dt class="name"><i class="ti ti-calendar ti-fw"></i> {{ i18n.ts.registeredDate }}</dt>
|
<dt class="name"><i class="ti ti-calendar ti-fw"></i> {{ i18n.ts.registeredDate }}</dt>
|
||||||
<dd class="value">{{ dateString(user.createdAt) }} (<MkTime :time="user.createdAt"/>)</dd>
|
<dd class="value">{{ dateString(user.createdAt) }} (<MkTime :time="user.createdAt"/>)</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
|
@ -5,8 +5,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-app">
|
<div class="mk-app">
|
||||||
<a v-if="isRoot" href="https://github.com/misskey-dev/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--MI_THEME-panel); color:var(--MI_THEME-fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
|
|
||||||
|
|
||||||
<div v-if="!narrow && !isRoot" class="side">
|
<div v-if="!narrow && !isRoot" class="side">
|
||||||
<div class="banner" :style="{ backgroundImage: instance.backgroundImageUrl ? `url(${ instance.backgroundImageUrl })` : 'none' }"></div>
|
<div class="banner" :style="{ backgroundImage: instance.backgroundImageUrl ? `url(${ instance.backgroundImageUrl })` : 'none' }"></div>
|
||||||
<div class="dashboard">
|
<div class="dashboard">
|
||||||
|
|
|
@ -15,7 +15,7 @@ Issueを作成する前に、以下をご確認ください:
|
||||||
- 重複を防ぐため、既に同様の内容のIssueが作成されていないか検索してから新しいIssueを作ってください。
|
- 重複を防ぐため、既に同様の内容のIssueが作成されていないか検索してから新しいIssueを作ってください。
|
||||||
- Issueを質問に使わないでください。
|
- Issueを質問に使わないでください。
|
||||||
- Issueは、要望、提案、問題の報告にのみ使用してください。
|
- Issueは、要望、提案、問題の報告にのみ使用してください。
|
||||||
- 質問は、[GitHub Discussions](https://github.com/misskey-dev/misskey/discussions)や[Discord](https://discord.gg/Wp8gVStHW3)でお願いします。
|
- 質問は、@yume@mi.yumechi.jp / yume@mi.yumechi.jp でお願いします。
|
||||||
|
|
||||||
## PRの作成
|
## PRの作成
|
||||||
PRを作成する前に、以下をご確認ください:
|
PRを作成する前に、以下をご確認ください:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2024.11.0-yumechinokuni.5",
|
"version": "2024.11.0-yumechinokuni.7",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/misskey-dev/misskey.git",
|
"url": "https://forge.yumechi.jp/yume/yumechi-no-kuni.git",
|
||||||
"directory": "packages/misskey-js"
|
"directory": "packages/misskey-js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -12,209 +12,242 @@ import { createEmptyNotification, createNotification } from '@/scripts/create-no
|
||||||
import { swLang } from '@/scripts/lang.js';
|
import { swLang } from '@/scripts/lang.js';
|
||||||
import * as swos from '@/scripts/operations.js';
|
import * as swos from '@/scripts/operations.js';
|
||||||
|
|
||||||
globalThis.addEventListener('install', () => {
|
const STATIC_CACHE_NAME = `misskey-static-${_VERSION_}`;
|
||||||
// ev.waitUntil(globalThis.skipWaiting());
|
const PATHS_TO_CACHE = ['/assets/', '/static-assets/', '/emoji/', '/twemoji/', '/fluent-emoji/', '/vite/'];
|
||||||
|
|
||||||
|
async function cacheWithFallback(cache, paths) {
|
||||||
|
for (const path of paths) {
|
||||||
|
try {
|
||||||
|
await cache.add(new Request(path, { credentials: 'same-origin' }));
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globalThis.addEventListener('install', (ev) => {
|
||||||
|
ev.waitUntil((async () => {
|
||||||
|
const cache = await caches.open(STATIC_CACHE_NAME);
|
||||||
|
await cacheWithFallback(cache, PATHS_TO_CACHE);
|
||||||
|
await globalThis.skipWaiting();
|
||||||
|
})());
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('activate', ev => {
|
globalThis.addEventListener('activate', (ev) => {
|
||||||
ev.waitUntil(
|
ev.waitUntil(
|
||||||
caches.keys()
|
caches.keys()
|
||||||
.then(cacheNames => Promise.all(
|
.then((cacheNames) => Promise.all(
|
||||||
cacheNames
|
cacheNames
|
||||||
.filter((v) => v !== swLang.cacheName)
|
.filter((v) => v !== STATIC_CACHE_NAME && v !== swLang.cacheName)
|
||||||
.map(name => caches.delete(name)),
|
.map((name) => caches.delete(name)),
|
||||||
))
|
))
|
||||||
.then(() => globalThis.clients.claim()),
|
.then(() => globalThis.clients.claim()),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function offlineContentHTML() {
|
async function offlineContentHTML() {
|
||||||
const i18n = await (swLang.i18n ?? swLang.fetchLocale()) as Partial<I18n<Locale>>;
|
const i18n = await (swLang.i18n ?? swLang.fetchLocale()) as Partial<I18n<Locale>>;
|
||||||
const messages = {
|
const messages = {
|
||||||
title: i18n.ts?._offlineScreen.title ?? 'Offline - Could not connect to server',
|
title: i18n.ts?._offlineScreen.title ?? 'Offline - Could not connect to server',
|
||||||
header: i18n.ts?._offlineScreen.header ?? 'Could not connect to server',
|
header: i18n.ts?._offlineScreen.header ?? 'Could not connect to server',
|
||||||
reload: i18n.ts?.reload ?? 'Reload',
|
reload: i18n.ts?.reload ?? 'Reload',
|
||||||
};
|
};
|
||||||
|
|
||||||
return `<!DOCTYPE html><html lang="ja"><head><meta charset="UTF-8"><meta content="width=device-width,initial-scale=1"name="viewport"><title>${messages.title}</title><style>body{background-color:#0c1210;color:#dee7e4;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:24px;box-sizing:border-box}.icon{max-width:120px;width:100%;height:auto;margin-bottom:20px;}.message{text-align:center;font-size:20px;font-weight:700;margin-bottom:20px}.version{text-align:center;font-size:90%;margin-bottom:20px}button{padding:7px 14px;min-width:100px;font-weight:700;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;border-radius:99rem;background-color:#b4e900;color:#192320;border:none;cursor:pointer;-webkit-tap-highlight-color:transparent}button:hover{background-color:#c6ff03}</style></head><body><svg class="icon"fill="none"height="24"stroke="currentColor"stroke-linecap="round"stroke-linejoin="round"stroke-width="2"viewBox="0 0 24 24"width="24"xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z"fill="none"stroke="none"/><path d="M9.58 5.548c.24 -.11 .492 -.207 .752 -.286c1.88 -.572 3.956 -.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.913 0 3.464 1.56 3.464 3.486c0 .957 -.383 1.824 -1.003 2.454m-2.997 1.033h-11.343c-2.572 -.004 -4.657 -2.011 -4.657 -4.487c0 -2.475 2.085 -4.482 4.657 -4.482c.13 -.582 .37 -1.128 .7 -1.62"/><path d="M3 3l18 18"/></svg><div class="message">${messages.header}</div><div class="version">v${_VERSION_}</div><button onclick="reloadPage()">${messages.reload}</button><script>function reloadPage(){location.reload(!0)}</script></body></html>`;
|
return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta content="width=device-width,initial-scale=1" name="viewport"><title>${messages.title}</title><style>body{background-color:#0c1210;color:#dee7e4;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:24px;box-sizing:border-box}.icon{max-width:120px;width:100%;height:auto;margin-bottom:20px;}.message{text-align:center;font-size:20px;font-weight:700;margin-bottom:20px}.version{text-align:center;font-size:90%;margin-bottom:20px}button{padding:7px 14px;min-width:100px;font-weight:700;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;border-radius:99rem;background-color:#ff82ab;color:#192320;border:none;cursor:pointer;-webkit-tap-highlight-color:transparent}button:hover{background-color:#fac5eb}</style></head><body><svg class="icon" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none" stroke="none"/><path d="M9.58 5.548c.24 -.11 .492 -.207 .752 -.286c1.88 -.572 3.956 -.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.913 0 3.464 1.56 3.464 3.486c0 .957 -.383 1.824 -1.003 2.454m-2.997 1.033h-11.343c-2.572 -.004 -4.657 -2.011 -4.657 -4.487c0 -2.475 2.085 -4.482 4.657 -4.482c.13 -.582 .37 -1.128 .7 -1.62"/><path d="M3 3l18 18"/></svg><div class="message">${messages.header}</div><div class="version">v${_VERSION_}</div><button onclick="reloadPage()">${messages.reload}</button><script>function reloadPage(){location.reload(true)}</script></body></html>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
globalThis.addEventListener('fetch', ev => {
|
globalThis.addEventListener('fetch', (ev) => {
|
||||||
let isHTMLRequest = false;
|
const shouldCache = PATHS_TO_CACHE.some((path) => ev.request.url.includes(path));
|
||||||
if (ev.request.headers.get('sec-fetch-dest') === 'document') {
|
|
||||||
isHTMLRequest = true;
|
|
||||||
} else if (ev.request.headers.get('accept')?.includes('/html')) {
|
|
||||||
isHTMLRequest = true;
|
|
||||||
} else if (ev.request.url.endsWith('/')) {
|
|
||||||
isHTMLRequest = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isHTMLRequest) return;
|
if (shouldCache) {
|
||||||
ev.respondWith(
|
ev.respondWith(
|
||||||
fetch(ev.request)
|
caches.match(ev.request)
|
||||||
.catch(async () => {
|
.then((response) => {
|
||||||
const html = await offlineContentHTML();
|
if (response) return response;
|
||||||
return new Response(html, {
|
|
||||||
status: 200,
|
return fetch(ev.request).then((response) => {
|
||||||
headers: {
|
if (!response || response.status !== 200 || response.type !== 'basic') return response;
|
||||||
'content-type': 'text/html',
|
const responseToCache = response.clone();
|
||||||
},
|
caches.open(STATIC_CACHE_NAME)
|
||||||
});
|
.then((cache) => {
|
||||||
}),
|
cache.put(ev.request, responseToCache);
|
||||||
);
|
});
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isHTMLRequest = false;
|
||||||
|
if (ev.request.headers.get('sec-fetch-dest') === 'document') {
|
||||||
|
isHTMLRequest = true;
|
||||||
|
} else if (ev.request.headers.get('accept')?.includes('/html')) {
|
||||||
|
isHTMLRequest = true;
|
||||||
|
} else if (ev.request.url.endsWith('/')) {
|
||||||
|
isHTMLRequest = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isHTMLRequest) return;
|
||||||
|
ev.respondWith(
|
||||||
|
fetch(ev.request)
|
||||||
|
.catch(async () => {
|
||||||
|
const html = await offlineContentHTML();
|
||||||
|
return new Response(html, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'text/html',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('push', ev => {
|
globalThis.addEventListener('push', (ev) => {
|
||||||
// クライアント取得
|
ev.waitUntil(globalThis.clients.matchAll({
|
||||||
ev.waitUntil(globalThis.clients.matchAll({
|
includeUncontrolled: true,
|
||||||
includeUncontrolled: true,
|
type: 'window',
|
||||||
type: 'window',
|
}).then(async () => {
|
||||||
}).then(async () => {
|
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.data?.json();
|
||||||
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.data?.json();
|
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
// case 'driveFileCreated':
|
case 'notification':
|
||||||
case 'notification':
|
case 'unreadAntennaNote':
|
||||||
case 'unreadAntennaNote':
|
if (Date.now() - data.dateTime > 1000 * 60 * 60 * 24) break;
|
||||||
// 1日以上経過している場合は無視
|
|
||||||
if (Date.now() - data.dateTime > 1000 * 60 * 60 * 24) break;
|
|
||||||
|
|
||||||
return createNotification(data);
|
return createNotification(data);
|
||||||
case 'readAllNotifications':
|
case 'readAllNotifications':
|
||||||
await globalThis.registration.getNotifications()
|
await globalThis.registration.getNotifications()
|
||||||
.then(notifications => notifications.forEach(n => n.tag !== 'read_notification' && n.close()));
|
.then((notifications) => notifications.forEach((n) => n.tag !== 'read_notification' && n.close()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
await createEmptyNotification();
|
await createEmptyNotification();
|
||||||
return;
|
return;
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => {
|
globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => {
|
||||||
ev.waitUntil((async (): Promise<void> => {
|
ev.waitUntil((async (): Promise<void> => {
|
||||||
if (_DEV_) {
|
if (_DEV_) {
|
||||||
console.log('notificationclick', ev.action, ev.notification.data);
|
console.log('notificationclick', ev.action, ev.notification.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { action, notification } = ev;
|
const { action, notification } = ev;
|
||||||
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = notification.data ?? {};
|
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = notification.data ?? {};
|
||||||
const { userId: loginId } = data;
|
const { userId: loginId } = data;
|
||||||
let client: WindowClient | null = null;
|
let client: WindowClient | null = null;
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case 'notification':
|
case 'notification':
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'follow':
|
case 'follow':
|
||||||
if ('userId' in data.body) await swos.api('following/create', loginId, { userId: data.body.userId });
|
if ('userId' in data.body) await swos.api('following/create', loginId, { userId: data.body.userId });
|
||||||
break;
|
break;
|
||||||
case 'showUser':
|
case 'showUser':
|
||||||
if ('user' in data.body) client = await swos.openUser(Misskey.acct.toString(data.body.user), loginId);
|
if ('user' in data.body) client = await swos.openUser(Misskey.acct.toString(data.body.user), loginId);
|
||||||
break;
|
break;
|
||||||
case 'reply':
|
case 'reply':
|
||||||
if ('note' in data.body) client = await swos.openPost({ reply: data.body.note }, loginId);
|
if ('note' in data.body) client = await swos.openPost({ reply: data.body.note }, loginId);
|
||||||
break;
|
break;
|
||||||
case 'renote':
|
case 'renote':
|
||||||
if ('note' in data.body) await swos.api('notes/create', loginId, { renoteId: data.body.note.id });
|
if ('note' in data.body) await swos.api('notes/create', loginId, { renoteId: data.body.note.id });
|
||||||
break;
|
break;
|
||||||
case 'accept':
|
case 'accept':
|
||||||
switch (data.body.type) {
|
switch (data.body.type) {
|
||||||
case 'receiveFollowRequest':
|
case 'receiveFollowRequest':
|
||||||
await swos.api('following/requests/accept', loginId, { userId: data.body.userId });
|
await swos.api('following/requests/accept', loginId, { userId: data.body.userId });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'reject':
|
case 'reject':
|
||||||
switch (data.body.type) {
|
switch (data.body.type) {
|
||||||
case 'receiveFollowRequest':
|
case 'receiveFollowRequest':
|
||||||
await swos.api('following/requests/reject', loginId, { userId: data.body.userId });
|
await swos.api('following/requests/reject', loginId, { userId: data.body.userId });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'showFollowRequests':
|
case 'showFollowRequests':
|
||||||
client = await swos.openClient('push', '/my/follow-requests', loginId);
|
client = await swos.openClient('push', '/my/follow-requests', loginId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
switch (data.body.type) {
|
switch (data.body.type) {
|
||||||
case 'receiveFollowRequest':
|
case 'receiveFollowRequest':
|
||||||
client = await swos.openClient('push', '/my/follow-requests', loginId);
|
client = await swos.openClient('push', '/my/follow-requests', loginId);
|
||||||
break;
|
break;
|
||||||
case 'reaction':
|
case 'reaction':
|
||||||
client = await swos.openNote(data.body.note.id, loginId);
|
client = await swos.openNote(data.body.note.id, loginId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if ('note' in data.body) {
|
if ('note' in data.body) {
|
||||||
client = await swos.openNote(data.body.note.id, loginId);
|
client = await swos.openNote(data.body.note.id, loginId);
|
||||||
} else if ('user' in data.body) {
|
} else if ('user' in data.body) {
|
||||||
client = await swos.openUser(Misskey.acct.toString(data.body.user), loginId);
|
client = await swos.openUser(Misskey.acct.toString(data.body.user), loginId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'unreadAntennaNote':
|
case 'unreadAntennaNote':
|
||||||
client = await swos.openAntenna(data.body.antenna.id, loginId);
|
client = await swos.openAntenna(data.body.antenna.id, loginId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'markAllAsRead':
|
case 'markAllAsRead':
|
||||||
await globalThis.registration.getNotifications()
|
await globalThis.registration.getNotifications()
|
||||||
.then(notifications => notifications.forEach(n => n.tag !== 'read_notification' && n.close()));
|
.then((notifications) => notifications.forEach((n) => n.tag !== 'read_notification' && n.close()));
|
||||||
await get<Pick<Misskey.entities.SignupResponse, 'id' | 'token'>[]>('accounts').then(accounts => {
|
await get<Pick<Misskey.entities.SignupResponse, 'id' | 'token'>[]>('accounts').then((accounts) => {
|
||||||
return Promise.all((accounts ?? []).map(async account => {
|
return Promise.all((accounts ?? []).map(async (account) => {
|
||||||
await swos.sendMarkAllAsRead(account.id);
|
await swos.sendMarkAllAsRead(account.id);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'settings':
|
case 'settings':
|
||||||
client = await swos.openClient('push', '/settings/notifications', loginId);
|
client = await swos.openClient('push', '/settings/notifications', loginId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client) {
|
if (client) {
|
||||||
client.focus();
|
client.focus();
|
||||||
}
|
}
|
||||||
if (data.type === 'notification') {
|
if (data.type === 'notification') {
|
||||||
await swos.sendMarkAllAsRead(loginId);
|
await swos.sendMarkAllAsRead(loginId);
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.close();
|
notification.close();
|
||||||
})());
|
})());
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => {
|
globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => {
|
||||||
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.notification.data;
|
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.notification.data;
|
||||||
|
|
||||||
ev.waitUntil((async (): Promise<void> => {
|
ev.waitUntil((async (): Promise<void> => {
|
||||||
if (data.type === 'notification') {
|
if (data.type === 'notification') {
|
||||||
await swos.sendMarkAllAsRead(data.userId);
|
await swos.sendMarkAllAsRead(data.userId);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
})());
|
})());
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => {
|
globalThis.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => {
|
||||||
ev.waitUntil((async (): Promise<void> => {
|
ev.waitUntil((async (): Promise<void> => {
|
||||||
switch (ev.data) {
|
if (ev.data === 'clear') {
|
||||||
case 'clear':
|
await caches.keys()
|
||||||
// Cache Storage全削除
|
.then((cacheNames) => Promise.all(
|
||||||
await caches.keys()
|
cacheNames.map((name) => caches.delete(name)),
|
||||||
.then(cacheNames => Promise.all(
|
));
|
||||||
cacheNames.map(name => caches.delete(name)),
|
return;
|
||||||
));
|
}
|
||||||
return; // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof ev.data === 'object') {
|
if (typeof ev.data === 'object') {
|
||||||
// E.g. '[object Array]' → 'array'
|
const otype = Object.prototype.toString.call(ev.data).slice(8, -1).toLowerCase();
|
||||||
const otype = Object.prototype.toString.call(ev.data).slice(8, -1).toLowerCase();
|
|
||||||
|
|
||||||
if (otype === 'object') {
|
if (otype === 'object') {
|
||||||
if (ev.data.msg === 'initialize') {
|
if (ev.data.msg === 'initialize') {
|
||||||
swLang.setLang(ev.data.lang);
|
swLang.setLang(ev.data.lang);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})());
|
})());
|
||||||
});
|
});
|
||||||
|
|
40
yume-mods/nyuukyou/Cargo.lock
generated
40
yume-mods/nyuukyou/Cargo.lock
generated
|
@ -139,7 +139,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_path_to_error",
|
"serde_path_to_error",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper 1.0.1",
|
"sync_wrapper 1.0.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower 0.5.1",
|
"tower 0.5.1",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
|
@ -162,7 +162,7 @@ dependencies = [
|
||||||
"mime",
|
"mime",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
"sync_wrapper 1.0.1",
|
"sync_wrapper 1.0.2",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -421,7 +421,7 @@ checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fedivet"
|
name = "fedivet"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://forge.yumechi.jp/yume/fedivet?tag=testing-audit%2Brelay%2Bfilter#b1b051dc2f1319a3948d7afcecfd3ac8f92a07de"
|
source = "git+https://forge.yumechi.jp/yume/fedivet?tag=v0.0.1#46456b0a61b449dad7bbe85e0342bdd5e3b6e031"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
|
@ -588,9 +588,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.6"
|
version = "0.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
|
checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-waker",
|
"atomic-waker",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -683,9 +683,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.5.0"
|
version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a"
|
checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
@ -940,9 +940,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.11"
|
version = "1.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
|
@ -1192,9 +1192,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.89"
|
version = "1.0.92"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
@ -1307,7 +1307,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper 1.0.1",
|
"sync_wrapper 1.0.2",
|
||||||
"system-configuration",
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
|
@ -1408,9 +1408,9 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
version = "0.1.26"
|
version = "0.1.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1"
|
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
@ -1564,9 +1564,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.87"
|
version = "2.0.89"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1581,9 +1581,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sync_wrapper"
|
name = "sync_wrapper"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
]
|
]
|
||||||
|
@ -1814,9 +1814,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.13"
|
version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
|
|
|
@ -7,7 +7,7 @@ edition = "2021"
|
||||||
axum = "0.7"
|
axum = "0.7"
|
||||||
clap = { version = "4.5.20", features = ["derive"] }
|
clap = { version = "4.5.20", features = ["derive"] }
|
||||||
env_logger = "0.11.5"
|
env_logger = "0.11.5"
|
||||||
fedivet = { git = "https://forge.yumechi.jp/yume/fedivet", tag = "testing-audit+relay+filter" }
|
fedivet = { git = "https://forge.yumechi.jp/yume/fedivet", tag = "v0.0.1" }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
tokio = { version = "1" }
|
tokio = { version = "1" }
|
||||||
|
|
Loading…
Reference in a new issue