forked from FoundKeyGang/FoundKey
Compare commits
10 commits
main
...
non-federa
Author | SHA1 | Date | |
---|---|---|---|
0e9ac8d929 | |||
00bc255508 | |||
6793d6accf | |||
a499c364d6 | |||
9c9b5bb44c | |||
b4a83bea38 | |||
78af39a130 | |||
611749eb8d | |||
4c72343d03 | |||
5038eb3ac4 |
45 changed files with 534 additions and 323 deletions
|
@ -190,9 +190,7 @@ charts: "Charts"
|
|||
perHour: "Per Hour"
|
||||
perDay: "Per Day"
|
||||
stopActivityDelivery: "Stop sending activities"
|
||||
stopActivityDeliveryDescription: "Local activities will not be sent to this instance. Receiving activities works as before."
|
||||
blockThisInstance: "Block this instance"
|
||||
blockThisInstanceDescription: "Local activites will not be sent to this instance. Activites from this instance will be discarded."
|
||||
operations: "Operations"
|
||||
software: "Software"
|
||||
version: "Version"
|
||||
|
@ -799,6 +797,8 @@ onlineStatus: "Online status"
|
|||
hideOnlineStatus: "Hide online status"
|
||||
hideOnlineStatusDescription: "Hiding your online status reduces the convenience of\
|
||||
\ some features such as the search."
|
||||
federateBlocks: "Federate blocks"
|
||||
federateBlocksDescription: "If disabled, block activities won't be sent."
|
||||
online: "Online"
|
||||
active: "Active"
|
||||
offline: "Offline"
|
||||
|
|
|
@ -740,6 +740,8 @@ unknown: "不明"
|
|||
onlineStatus: "オンライン状態"
|
||||
hideOnlineStatus: "オンライン状態を隠す"
|
||||
hideOnlineStatusDescription: "オンライン状態を隠すと、検索などの一部機能において利便性が低下することがあります。"
|
||||
federateBlocks: "ブロックを連合に送信"
|
||||
federateBlocksDescription: "オフにするとBlockのActivityは連合に送信しません"
|
||||
online: "オンライン"
|
||||
active: "アクティブ"
|
||||
offline: "オフライン"
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
export class userBlockFederation1631880003000 {
|
||||
name = 'userBlockFederation1631880003000';
|
||||
|
||||
public async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user" ADD "federateBlocks" boolean NOT NULL DEFAULT true`);
|
||||
}
|
||||
|
||||
public async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "federateBlocks"`);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
export class removeAds1657570176749 {
|
||||
name = 'removeAds1657570176749';
|
||||
name = 'removeAds1657570176749'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`DROP TABLE "ad"`);
|
|
@ -158,6 +158,7 @@
|
|||
"@types/sanitize-html": "2.6.2",
|
||||
"@types/semver": "7.3.12",
|
||||
"@types/sharp": "0.30.5",
|
||||
"@types/sinon": "^10.0.13",
|
||||
"@types/sinonjs__fake-timers": "8.1.2",
|
||||
"@types/speakeasy": "2.0.7",
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
|
@ -173,6 +174,7 @@
|
|||
"eslint-plugin-import": "^2.26.0",
|
||||
"execa": "6.1.0",
|
||||
"form-data": "^4.0.0",
|
||||
"sinon": "^14.0.2",
|
||||
"typescript": "^4.8.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,21 +62,22 @@ export function fromHtml(html: string, hashtagNames?: string[]): string {
|
|||
const rel = node.attrs.find(x => x.name === 'rel');
|
||||
const href = node.attrs.find(x => x.name === 'href');
|
||||
|
||||
// hashtags
|
||||
// ハッシュタグ
|
||||
if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) {
|
||||
text += txt;
|
||||
// mentions
|
||||
// メンション
|
||||
} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) {
|
||||
const part = txt.split('@');
|
||||
|
||||
if (part.length === 2 && href) {
|
||||
// restore the host name part
|
||||
//#region ホスト名部分が省略されているので復元する
|
||||
const acct = `${txt}@${(new URL(href.value)).hostname}`;
|
||||
text += acct;
|
||||
//#endregion
|
||||
} else if (part.length === 3) {
|
||||
text += txt;
|
||||
}
|
||||
// other
|
||||
// その他
|
||||
} else {
|
||||
const generateLink = () => {
|
||||
if (!href && !txt) {
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
export class Cache<T> {
|
||||
public cache: Map<string | null, { date: number; value: T; }>;
|
||||
private lifetime: number;
|
||||
public fetcher: (key: string | null) => Promise<T | undefined>;
|
||||
|
||||
constructor(lifetime: number, fetcher: Cache<T>['fetcher']) {
|
||||
constructor(lifetime: Cache<never>['lifetime']) {
|
||||
this.cache = new Map();
|
||||
this.lifetime = lifetime;
|
||||
this.fetcher = fetcher;
|
||||
}
|
||||
|
||||
public set(key: string | null, value: T): void {
|
||||
|
@ -19,13 +17,10 @@ export class Cache<T> {
|
|||
public get(key: string | null): T | undefined {
|
||||
const cached = this.cache.get(key);
|
||||
if (cached == null) return undefined;
|
||||
|
||||
// discard if past the cache lifetime
|
||||
if ((Date.now() - cached.date) > this.lifetime) {
|
||||
this.cache.delete(key);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return cached.value;
|
||||
}
|
||||
|
||||
|
@ -34,22 +29,52 @@ export class Cache<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* If the value is cached, it is returned. Otherwise the fetcher is
|
||||
* run to get the value. If the fetcher returns undefined, it is
|
||||
* returned but not cached.
|
||||
* キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
|
||||
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
|
||||
*/
|
||||
public async fetch(key: string | null): Promise<T | undefined> {
|
||||
const cached = this.get(key);
|
||||
if (cached !== undefined) {
|
||||
return cached;
|
||||
} else {
|
||||
const value = await this.fetcher(key);
|
||||
|
||||
// don't cache undefined
|
||||
if (value !== undefined)
|
||||
this.set(key, value);
|
||||
|
||||
return value;
|
||||
public async fetch(key: string | null, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
|
||||
const cachedValue = this.get(key);
|
||||
if (cachedValue !== undefined) {
|
||||
if (validator) {
|
||||
if (validator(cachedValue)) {
|
||||
// Cache HIT
|
||||
return cachedValue;
|
||||
}
|
||||
} else {
|
||||
// Cache HIT
|
||||
return cachedValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache MISS
|
||||
const value = await fetcher();
|
||||
this.set(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
|
||||
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
|
||||
*/
|
||||
public async fetchMaybe(key: string | null, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
|
||||
const cachedValue = this.get(key);
|
||||
if (cachedValue !== undefined) {
|
||||
if (validator) {
|
||||
if (validator(cachedValue)) {
|
||||
// Cache HIT
|
||||
return cachedValue;
|
||||
}
|
||||
} else {
|
||||
// Cache HIT
|
||||
return cachedValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache MISS
|
||||
const value = await fetcher();
|
||||
if (value !== undefined) {
|
||||
this.set(key, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,26 +3,22 @@ import { Note } from '@/models/entities/note.js';
|
|||
import { User } from '@/models/entities/user.js';
|
||||
import { UserListJoinings, UserGroupJoinings, Blockings } from '@/models/index.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import { MINUTE } from '@/const.js';
|
||||
import { getFullApAccount } from './convert-host.js';
|
||||
import { Packed } from './schema.js';
|
||||
import { Cache } from './cache.js';
|
||||
|
||||
const blockingCache = new Cache<User['id'][]>(
|
||||
5 * MINUTE,
|
||||
(blockerId) => Blockings.findBy({ blockerId }).then(res => res.map(x => x.blockeeId)),
|
||||
);
|
||||
const blockingCache = new Cache<User['id'][]>(1000 * 60 * 5);
|
||||
|
||||
// designation for users you follow, list users and groups is disabled for performance reasons
|
||||
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
|
||||
|
||||
/**
|
||||
* either noteUserFollowers or antennaUserFollowing must be specified
|
||||
* noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい
|
||||
*/
|
||||
export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> {
|
||||
if (note.visibility === 'specified') return false;
|
||||
|
||||
// skip if the antenna creator is blocked by the note author
|
||||
const blockings = await blockingCache.fetch(noteUser.id);
|
||||
// アンテナ作成者がノート作成者にブロックされていたらスキップ
|
||||
const blockings = await blockingCache.fetch(noteUser.id, () => Blockings.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId)));
|
||||
if (blockings.some(blocking => blocking === antenna.userId)) return false;
|
||||
|
||||
if (note.visibility === 'followers') {
|
||||
|
|
|
@ -1,44 +1,44 @@
|
|||
import push from 'web-push';
|
||||
import { db } from '@/db/postgre.js';
|
||||
import { Meta } from '@/models/entities/meta.js';
|
||||
import { getFetchInstanceMetadataLock } from '@/misc/app-lock.js';
|
||||
|
||||
let cache: Meta;
|
||||
|
||||
/**
|
||||
* Performs the primitive database operation to set the server configuration
|
||||
*/
|
||||
export async function setMeta(meta: Meta): Promise<void> {
|
||||
const unlock = await getFetchInstanceMetadataLock('localhost');
|
||||
|
||||
// try to mitigate older bugs where multiple meta entries may have been created
|
||||
db.manager.clear(Meta);
|
||||
db.manager.insert(Meta, meta);
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the primitive database operation to fetch server configuration.
|
||||
* Writes to `cache` instead of returning.
|
||||
*/
|
||||
async function getMeta(): Promise<void> {
|
||||
const unlock = await getFetchInstanceMetadataLock('localhost');
|
||||
|
||||
// new IDs are prioritised because multiple records may have been created due to past bugs
|
||||
cache = db.manager.findOne(Meta, {
|
||||
order: {
|
||||
id: 'DESC',
|
||||
},
|
||||
});
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
export async function fetchMeta(noCache = false): Promise<Meta> {
|
||||
if (!noCache && cache) return cache;
|
||||
|
||||
await getMeta();
|
||||
return await db.transaction(async transactionalEntityManager => {
|
||||
// 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する
|
||||
const metas = await transactionalEntityManager.find(Meta, {
|
||||
order: {
|
||||
id: 'DESC',
|
||||
},
|
||||
});
|
||||
|
||||
return cache;
|
||||
const meta = metas[0];
|
||||
|
||||
if (meta) {
|
||||
cache = meta;
|
||||
return meta;
|
||||
} else {
|
||||
// metaが空のときfetchMetaが同時に呼ばれるとここが同時に呼ばれてしまうことがあるのでフェイルセーフなupsertを使う
|
||||
const saved = await transactionalEntityManager
|
||||
.upsert(
|
||||
Meta,
|
||||
{
|
||||
id: 'x',
|
||||
},
|
||||
['id'],
|
||||
)
|
||||
.then((x) => transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0]));
|
||||
|
||||
cache = saved;
|
||||
return saved;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
fetchMeta(true).then(meta => {
|
||||
cache = meta;
|
||||
});
|
||||
}, 1000 * 10);
|
||||
|
|
|
@ -3,11 +3,8 @@ import { User } from '@/models/entities/user.js';
|
|||
import { UserKeypair } from '@/models/entities/user-keypair.js';
|
||||
import { Cache } from './cache.js';
|
||||
|
||||
const cache = new Cache<UserKeypair>(
|
||||
Infinity,
|
||||
(userId) => UserKeypairs.findOneByOrFail({ userId }),
|
||||
);
|
||||
const cache = new Cache<UserKeypair>(Infinity);
|
||||
|
||||
export async function getUserKeypair(userId: User['id']): Promise<UserKeypair> {
|
||||
return await cache.fetch(userId);
|
||||
return await cache.fetch(userId, () => UserKeypairs.findOneByOrFail({ userId }));
|
||||
}
|
||||
|
|
|
@ -4,27 +4,14 @@ import { Emojis } from '@/models/index.js';
|
|||
import { Emoji } from '@/models/entities/emoji.js';
|
||||
import { Note } from '@/models/entities/note.js';
|
||||
import { query } from '@/prelude/url.js';
|
||||
import { HOUR } from '@/const.js';
|
||||
import { Cache } from './cache.js';
|
||||
import { isSelfHost, toPunyNullable } from './convert-host.js';
|
||||
import { decodeReaction } from './reaction-lib.js';
|
||||
|
||||
/**
|
||||
* composite cache key: `${host ?? ''}:${name}`
|
||||
*/
|
||||
const cache = new Cache<Emoji | null>(
|
||||
12 * HOUR,
|
||||
async (key) => {
|
||||
const [host, name] = key.split(':');
|
||||
return (await Emojis.findOneBy({
|
||||
name,
|
||||
host: host || IsNull(),
|
||||
})) || null;
|
||||
},
|
||||
);
|
||||
const cache = new Cache<Emoji | null>(1000 * 60 * 60 * 12);
|
||||
|
||||
/**
|
||||
* Information needed to attach in ActivityPub
|
||||
* 添付用絵文字情報
|
||||
*/
|
||||
type PopulatedEmoji = {
|
||||
name: string;
|
||||
|
@ -49,22 +36,28 @@ function parseEmojiStr(emojiName: string, noteUserHost: string | null) {
|
|||
|
||||
const name = match[1];
|
||||
|
||||
// ホスト正規化
|
||||
const host = toPunyNullable(normalizeHost(match[2], noteUserHost));
|
||||
|
||||
return { name, host };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve emoji information from ActivityPub attachment.
|
||||
* @param emojiName custom emoji names attached to notes, user profiles or in rections. Colons should not be included. Localhost is denote by @. (see also `decodeReaction`)
|
||||
* @param noteUserHost host that the content is from, to default to
|
||||
* @returns emoji information. `null` means not found.
|
||||
* 添付用絵文字情報を解決する
|
||||
* @param emojiName ノートやユーザープロフィールに添付された、またはリアクションのカスタム絵文字名 (:は含めない, リアクションでローカルホストの場合は@.を付ける (これはdecodeReactionで可能))
|
||||
* @param noteUserHost ノートやユーザープロフィールの所有者のホスト
|
||||
* @returns 絵文字情報, nullは未マッチを意味する
|
||||
*/
|
||||
export async function populateEmoji(emojiName: string, noteUserHost: string | null): Promise<PopulatedEmoji | null> {
|
||||
const { name, host } = parseEmojiStr(emojiName, noteUserHost);
|
||||
if (name == null) return null;
|
||||
|
||||
const emoji = await cache.fetch(`${host ?? ''}:${name}`);
|
||||
const queryOrNull = async () => (await Emojis.findOneBy({
|
||||
name,
|
||||
host: host ?? IsNull(),
|
||||
})) || null;
|
||||
|
||||
const emoji = await cache.fetch(`${name} ${host}`, queryOrNull);
|
||||
|
||||
if (emoji == null) return null;
|
||||
|
||||
|
@ -79,7 +72,7 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieve list of emojis from the cache. Uncached emoji are dropped.
|
||||
* 複数の添付用絵文字情報を解決する (キャシュ付き, 存在しないものは結果から除外される)
|
||||
*/
|
||||
export async function populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise<PopulatedEmoji[]> {
|
||||
const emojis = await Promise.all(emojiNames.map(x => populateEmoji(x, noteUserHost)));
|
||||
|
@ -110,20 +103,11 @@ export function aggregateNoteEmojis(notes: Note[]) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Query list of emojis in bulk and add them to the cache.
|
||||
* 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します
|
||||
*/
|
||||
export async function prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise<void> {
|
||||
const notCachedEmojis = emojis.filter(emoji => {
|
||||
// check if the cache has this emoji
|
||||
return cache.get(`${emoji.host ?? ''}:${emoji.name}`) == null;
|
||||
});
|
||||
|
||||
// check if there even are any uncached emoji to handle
|
||||
if (notCachedEmojis.length === 0) return;
|
||||
|
||||
// query all uncached emoji
|
||||
const notCachedEmojis = emojis.filter(emoji => cache.get(`${emoji.name} ${emoji.host}`) == null);
|
||||
const emojisQuery: any[] = [];
|
||||
// group by hosts to try to reduce query size
|
||||
const hosts = new Set(notCachedEmojis.map(e => e.host));
|
||||
for (const host of hosts) {
|
||||
emojisQuery.push({
|
||||
|
@ -131,14 +115,11 @@ export async function prefetchEmojis(emojis: { name: string; host: string | null
|
|||
host: host ?? IsNull(),
|
||||
});
|
||||
}
|
||||
|
||||
await Emojis.find({
|
||||
const _emojis = emojisQuery.length > 0 ? await Emojis.find({
|
||||
where: emojisQuery,
|
||||
select: ['name', 'host', 'originalUrl', 'publicUrl'],
|
||||
}).then(emojis => {
|
||||
// store all emojis into the cache
|
||||
emojis.forEach(emoji => {
|
||||
cache.set(`${emoji.host ?? ''}:${emoji.name}`, emoji);
|
||||
});
|
||||
});
|
||||
}) : [];
|
||||
for (const emoji of _emojis) {
|
||||
cache.set(`${emoji.name} ${emoji.host}`, emoji);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,8 +33,7 @@ export async function skippedInstances(hosts: Array<Instace['host']>): Array<Ins
|
|||
})
|
||||
.andWhere(new Brackets(qb => { qb
|
||||
.where('instance.isSuspended')
|
||||
.orWhere('instance.lastCommunicatedAt < :deadTime', { deadTime })
|
||||
.orWhere('instance.latestStatus = 410');
|
||||
.orWhere('instance.lastCommunicatedAt < :deadTime', { deadTime });
|
||||
}))
|
||||
.select('host')
|
||||
.getRawMany()
|
||||
|
|
|
@ -7,7 +7,7 @@ export class Instance {
|
|||
public id: string;
|
||||
|
||||
/**
|
||||
* Date and time this instance was first seen.
|
||||
* このインスタンスを捕捉した日時
|
||||
*/
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
|
@ -16,7 +16,7 @@ export class Instance {
|
|||
public caughtAt: Date;
|
||||
|
||||
/**
|
||||
* Hostname
|
||||
* ホスト
|
||||
*/
|
||||
@Index({ unique: true })
|
||||
@Column('varchar', {
|
||||
|
@ -26,7 +26,7 @@ export class Instance {
|
|||
public host: string;
|
||||
|
||||
/**
|
||||
* Number of users on this instance.
|
||||
* インスタンスのユーザー数
|
||||
*/
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
|
@ -35,7 +35,7 @@ export class Instance {
|
|||
public usersCount: number;
|
||||
|
||||
/**
|
||||
* Number of notes on this instance.
|
||||
* インスタンスの投稿数
|
||||
*/
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
|
@ -44,7 +44,7 @@ export class Instance {
|
|||
public notesCount: number;
|
||||
|
||||
/**
|
||||
* Number of local users who are followed by users from this instance.
|
||||
* このインスタンスのユーザーからフォローされている、自インスタンスのユーザーの数
|
||||
*/
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
|
@ -52,7 +52,7 @@ export class Instance {
|
|||
public followingCount: number;
|
||||
|
||||
/**
|
||||
* Number of users from this instance who are followed by local users.
|
||||
* このインスタンスのユーザーをフォローしている、自インスタンスのユーザーの数
|
||||
*/
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
|
@ -60,7 +60,7 @@ export class Instance {
|
|||
public followersCount: number;
|
||||
|
||||
/**
|
||||
* Timestamp of the latest outgoing HTTP request.
|
||||
* 直近のリクエスト送信日時
|
||||
*/
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
|
@ -68,7 +68,7 @@ export class Instance {
|
|||
public latestRequestSentAt: Date | null;
|
||||
|
||||
/**
|
||||
* HTTP status code that was received for the last outgoing HTTP request.
|
||||
* 直近のリクエスト送信時のHTTPステータスコード
|
||||
*/
|
||||
@Column('integer', {
|
||||
nullable: true,
|
||||
|
@ -76,7 +76,7 @@ export class Instance {
|
|||
public latestStatus: number | null;
|
||||
|
||||
/**
|
||||
* Timestamp of the latest incoming HTTP request.
|
||||
* 直近のリクエスト受信日時
|
||||
*/
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
|
@ -84,13 +84,13 @@ export class Instance {
|
|||
public latestRequestReceivedAt: Date | null;
|
||||
|
||||
/**
|
||||
* Timestamp of last communication with this instance (incoming or outgoing).
|
||||
* このインスタンスと最後にやり取りした日時
|
||||
*/
|
||||
@Column('timestamp with time zone')
|
||||
public lastCommunicatedAt: Date;
|
||||
|
||||
/**
|
||||
* Whether this instance seems unresponsive.
|
||||
* このインスタンスと不通かどうか
|
||||
*/
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
|
@ -98,7 +98,7 @@ export class Instance {
|
|||
public isNotResponding: boolean;
|
||||
|
||||
/**
|
||||
* Whether sending activities to this instance has been suspended.
|
||||
* このインスタンスへの配信を停止するか
|
||||
*/
|
||||
@Index()
|
||||
@Column('boolean', {
|
||||
|
|
|
@ -218,6 +218,11 @@ export class User {
|
|||
})
|
||||
public token: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public federateBlocks: boolean;
|
||||
|
||||
constructor(data: Partial<User>) {
|
||||
if (data == null) return;
|
||||
|
||||
|
|
|
@ -6,16 +6,13 @@ import { Packed } from '@/misc/schema.js';
|
|||
import { awaitAll, Promiseable } from '@/prelude/await-all.js';
|
||||
import { populateEmojis } from '@/misc/populate-emojis.js';
|
||||
import { getAntennas } from '@/misc/antenna-cache.js';
|
||||
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD, HOUR } from '@/const.js';
|
||||
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import { db } from '@/db/postgre.js';
|
||||
import { Instance } from '../entities/instance.js';
|
||||
import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, RenoteMutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, AntennaNotes, ChannelFollowings, Instances, DriveFiles } from '../index.js';
|
||||
|
||||
const userInstanceCache = new Cache<Instance | null>(
|
||||
3 * HOUR,
|
||||
(host) => Instances.findOneBy({ host }).then(x => x ?? undefined),
|
||||
);
|
||||
const userInstanceCache = new Cache<Instance | null>(1000 * 60 * 60 * 3);
|
||||
|
||||
type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>;
|
||||
type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> =
|
||||
|
@ -30,7 +27,7 @@ const ajv = new Ajv();
|
|||
const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
|
||||
const passwordSchema = { type: 'string', minLength: 1 } as const;
|
||||
const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
|
||||
const descriptionSchema = { type: 'string', minLength: 1, maxLength: 2048 } as const;
|
||||
const descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const;
|
||||
const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
|
||||
const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const;
|
||||
|
||||
|
@ -312,15 +309,17 @@ export const UserRepository = db.getRepository(User).extend({
|
|||
isModerator: user.isModerator || falsy,
|
||||
isBot: user.isBot || falsy,
|
||||
isCat: user.isCat || falsy,
|
||||
instance: !user.host ? undefined : userInstanceCache.fetch(user.host)
|
||||
.then(instance => !instance ? undefined : {
|
||||
name: instance.name,
|
||||
softwareName: instance.softwareName,
|
||||
softwareVersion: instance.softwareVersion,
|
||||
iconUrl: instance.iconUrl,
|
||||
faviconUrl: instance.faviconUrl,
|
||||
themeColor: instance.themeColor,
|
||||
}),
|
||||
instance: user.host ? userInstanceCache.fetch(user.host,
|
||||
() => Instances.findOneBy({ host: user.host! }),
|
||||
v => v != null,
|
||||
).then(instance => instance ? {
|
||||
name: instance.name,
|
||||
softwareName: instance.softwareName,
|
||||
softwareVersion: instance.softwareVersion,
|
||||
iconUrl: instance.iconUrl,
|
||||
faviconUrl: instance.faviconUrl,
|
||||
themeColor: instance.themeColor,
|
||||
} : undefined) : undefined,
|
||||
emojis: populateEmojis(user.emojis, user.host),
|
||||
onlineStatus: this.getOnlineStatus(user),
|
||||
|
||||
|
@ -336,6 +335,7 @@ export const UserRepository = db.getRepository(User).extend({
|
|||
isLocked: user.isLocked,
|
||||
isSilenced: user.isSilenced || falsy,
|
||||
isSuspended: user.isSuspended || falsy,
|
||||
federateBlocks: user!.federateBlocks,
|
||||
description: profile!.description,
|
||||
location: profile!.location,
|
||||
birthday: profile!.birthday,
|
||||
|
|
|
@ -62,8 +62,8 @@ export default async (job: Bull.Job<DeliverJobData>) => {
|
|||
if (res instanceof StatusError) {
|
||||
// 4xx
|
||||
if (res.isClientError) {
|
||||
// A client error means that something is wrong with the request we are making,
|
||||
// which means that retrying it makes no sense.
|
||||
// HTTPステータスコード4xxはクライアントエラーであり、それはつまり
|
||||
// 何回再送しても成功することはないということなのでエラーにはしないでおく
|
||||
return `${res.statusCode} ${res.statusMessage}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,14 +10,8 @@ import { uriPersonCache, userByIdCache } from '@/services/user-cache.js';
|
|||
import { IObject, getApId } from './type.js';
|
||||
import { resolvePerson } from './models/person.js';
|
||||
|
||||
const publicKeyCache = new Cache<UserPublickey>(
|
||||
Infinity,
|
||||
(keyId) => UserPublickeys.findOneBy({ keyId }).then(x => x ?? undefined),
|
||||
);
|
||||
const publicKeyByUserIdCache = new Cache<UserPublickey>(
|
||||
Infinity,
|
||||
(userId) => UserPublickeys.findOneBy({ userId }).then(x => x ?? undefined),
|
||||
);
|
||||
const publicKeyCache = new Cache<UserPublickey | null>(Infinity);
|
||||
const publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity);
|
||||
|
||||
export type UriParseResult = {
|
||||
/** wether the URI was generated by us */
|
||||
|
@ -105,9 +99,13 @@ export default class DbResolver {
|
|||
if (parsed.local) {
|
||||
if (parsed.type !== 'users') return null;
|
||||
|
||||
return await userByIdCache.fetch(parsed.id) ?? null;
|
||||
return await userByIdCache.fetchMaybe(parsed.id, () => Users.findOneBy({
|
||||
id: parsed.id,
|
||||
}).then(x => x ?? undefined)) ?? null;
|
||||
} else {
|
||||
return await uriPersonCache.fetch(parsed.uri) ?? null;
|
||||
return await uriPersonCache.fetch(parsed.uri, () => Users.findOneBy({
|
||||
uri: parsed.uri,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,12 +116,20 @@ export default class DbResolver {
|
|||
user: CacheableRemoteUser;
|
||||
key: UserPublickey;
|
||||
} | null> {
|
||||
const key = await publicKeyCache.fetch(keyId);
|
||||
const key = await publicKeyCache.fetch(keyId, async () => {
|
||||
const key = await UserPublickeys.findOneBy({
|
||||
keyId,
|
||||
});
|
||||
|
||||
if (key == null) return null;
|
||||
|
||||
return key;
|
||||
}, key => key != null);
|
||||
|
||||
if (key == null) return null;
|
||||
|
||||
return {
|
||||
user: await userByIdCache.fetch(key.userId) as CacheableRemoteUser,
|
||||
user: await userByIdCache.fetch(key.userId, () => Users.findOneByOrFail({ id: key.userId })) as CacheableRemoteUser,
|
||||
key,
|
||||
};
|
||||
}
|
||||
|
@ -139,7 +145,7 @@ export default class DbResolver {
|
|||
|
||||
if (user == null) return null;
|
||||
|
||||
const key = await publicKeyByUserIdCache.fetch(user.id);
|
||||
const key = await publicKeyByUserIdCache.fetch(user.id, () => UserPublickeys.findOneBy({ userId: user.id }), v => v != null);
|
||||
|
||||
return {
|
||||
user,
|
||||
|
|
|
@ -119,18 +119,26 @@ export default class DeliverManager {
|
|||
|
||||
if (this.recipes.some(r => isFollowers(r))) {
|
||||
// followers deliver
|
||||
const followers = await Followings.createQueryBuilder('followings')
|
||||
// return either the shared inbox (if available) or the individual inbox
|
||||
.select('COALESCE(followings.followerSharedInbox, followings.followerInbox)', 'inbox')
|
||||
// so we don't have to make our inboxes Set work as hard
|
||||
.distinct(true)
|
||||
// ...for the specific actors followers
|
||||
.where('followings.followeeId = :actorId', { actorId: this.actor.id })
|
||||
// don't deliver to ourselves
|
||||
.andWhere('followings.followerHost IS NOT NULL')
|
||||
.getRawMany();
|
||||
// TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう
|
||||
// ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう?
|
||||
const followers = await Followings.find({
|
||||
where: {
|
||||
followeeId: this.actor.id,
|
||||
followerHost: Not(IsNull()),
|
||||
},
|
||||
select: {
|
||||
followerSharedInbox: true,
|
||||
followerInbox: true,
|
||||
},
|
||||
}) as {
|
||||
followerSharedInbox: string | null;
|
||||
followerInbox: string;
|
||||
}[];
|
||||
|
||||
followers.forEach(({ inbox }) => inboxes.add(inbox));
|
||||
for (const following of followers) {
|
||||
const inbox = following.followerSharedInbox || following.followerInbox;
|
||||
inboxes.add(inbox);
|
||||
}
|
||||
}
|
||||
|
||||
this.recipes.filter((recipe): recipe is IDirectRecipe =>
|
||||
|
|
|
@ -23,6 +23,8 @@ import Featured from './activitypub/featured.js';
|
|||
// Init router
|
||||
const router = new Router();
|
||||
|
||||
//#region Routing
|
||||
|
||||
function inbox(ctx: Router.RouterContext) {
|
||||
let signature;
|
||||
|
||||
|
@ -43,8 +45,6 @@ const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystr
|
|||
|
||||
function isActivityPubReq(ctx: Router.RouterContext) {
|
||||
ctx.response.vary('Accept');
|
||||
// if no accept header is supplied, koa returns the 1st, so html is used as a dummy
|
||||
// i.e. activitypub requests must be explicit
|
||||
const accepted = ctx.accepts('html', ACTIVITY_JSON, LD_JSON);
|
||||
return typeof accepted === 'string' && !accepted.match(/html/);
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ router.get('/notes/:note', async (ctx, next) => {
|
|||
return;
|
||||
}
|
||||
|
||||
// redirect if remote
|
||||
// リモートだったらリダイレクト
|
||||
if (note.userHost != null) {
|
||||
if (note.uri == null || isSelfHost(note.userHost)) {
|
||||
ctx.status = 500;
|
||||
|
@ -94,15 +94,6 @@ router.get('/notes/:note', async (ctx, next) => {
|
|||
|
||||
// note activity
|
||||
router.get('/notes/:note/activity', async ctx => {
|
||||
if (!isActivityPubReq(ctx)) {
|
||||
/*
|
||||
Redirect to the human readable page. in this case using next is not possible,
|
||||
since there is no human readable page explicitly for the activity.
|
||||
*/
|
||||
ctx.redirect(`/notes/${ctx.params.note}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const note = await Notes.findOneBy({
|
||||
id: ctx.params.note,
|
||||
userHost: IsNull(),
|
||||
|
@ -194,6 +185,7 @@ router.get('/@:user', async (ctx, next) => {
|
|||
|
||||
await userInfo(ctx, user);
|
||||
});
|
||||
//#endregion
|
||||
|
||||
// emoji
|
||||
router.get('/emojis/:emoji', async ctx => {
|
||||
|
|
|
@ -3,13 +3,10 @@ import { Users, AccessTokens, Apps } from '@/models/index.js';
|
|||
import { AccessToken } from '@/models/entities/access-token.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import { App } from '@/models/entities/app.js';
|
||||
import { userByIdCache, localUserByNativeTokenCache } from '@/services/user-cache.js';
|
||||
import { localUserByIdCache, localUserByNativeTokenCache } from '@/services/user-cache.js';
|
||||
import isNativeToken from './common/is-native-token.js';
|
||||
|
||||
const appCache = new Cache<App>(
|
||||
Infinity,
|
||||
(id) => Apps.findOneByOrFail({ id }),
|
||||
);
|
||||
const appCache = new Cache<App>(Infinity);
|
||||
|
||||
export class AuthenticationError extends Error {
|
||||
constructor(message: string) {
|
||||
|
@ -42,7 +39,8 @@ export default async (authorization: string | null | undefined, bodyToken: strin
|
|||
const token: string = maybeToken;
|
||||
|
||||
if (isNativeToken(token)) {
|
||||
const user = await localUserByNativeTokenCache.fetch(token);
|
||||
const user = await localUserByNativeTokenCache.fetch(token,
|
||||
() => Users.findOneBy({ token }) as Promise<ILocalUser | null>);
|
||||
|
||||
if (user == null) {
|
||||
throw new AuthenticationError('unknown token');
|
||||
|
@ -66,13 +64,14 @@ export default async (authorization: string | null | undefined, bodyToken: strin
|
|||
lastUsedAt: new Date(),
|
||||
});
|
||||
|
||||
const user = await userByIdCache.fetch(accessToken.userId);
|
||||
|
||||
// can't authorize remote users
|
||||
if (!Users.isLocalUser(user)) return [null, null];
|
||||
const user = await localUserByIdCache.fetch(accessToken.userId,
|
||||
() => Users.findOneBy({
|
||||
id: accessToken.userId,
|
||||
}) as Promise<ILocalUser>);
|
||||
|
||||
if (accessToken.appId) {
|
||||
const app = await appCache.fetch(accessToken.appId);
|
||||
const app = await appCache.fetch(accessToken.appId,
|
||||
() => Apps.findOneByOrFail({ id: accessToken.appId! }));
|
||||
|
||||
return [user, {
|
||||
id: accessToken.id,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Meta } from '@/models/entities/meta.js';
|
||||
import { insertModerationLog } from '@/services/insert-moderation-log.js';
|
||||
import { fetchMeta, setMeta } from '@/misc/fetch-meta.js';
|
||||
import { db } from '@/db/postgre.js';
|
||||
import define from '../../define.js';
|
||||
|
||||
export const meta = {
|
||||
|
@ -374,10 +375,20 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
set.deeplIsPro = ps.deeplIsPro;
|
||||
}
|
||||
|
||||
const meta = await fetchMeta();
|
||||
await setMeta({
|
||||
...meta,
|
||||
...set,
|
||||
await db.transaction(async transactionalEntityManager => {
|
||||
const metas = await transactionalEntityManager.find(Meta, {
|
||||
order: {
|
||||
id: 'DESC',
|
||||
},
|
||||
});
|
||||
|
||||
const meta = metas[0];
|
||||
|
||||
if (meta) {
|
||||
await transactionalEntityManager.update(Meta, meta.id, set);
|
||||
} else {
|
||||
await transactionalEntityManager.save(Meta, set);
|
||||
}
|
||||
});
|
||||
|
||||
insertModerationLog(me, 'updateMeta');
|
||||
|
|
|
@ -81,6 +81,7 @@ export const paramDef = {
|
|||
emailNotificationTypes: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
federateBlocks: { type: 'boolean' },
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
@ -129,6 +130,7 @@ export default define(meta, paramDef, async (ps, _user, token) => {
|
|||
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
|
||||
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
||||
if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle;
|
||||
if (typeof ps.federateBlocks === 'boolean') updates.federateBlocks = ps.federateBlocks;
|
||||
if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
|
||||
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
||||
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
|
||||
|
|
|
@ -12,7 +12,7 @@ import { perUserFollowingChart } from '@/services/chart/index.js';
|
|||
import { genId } from '@/misc/gen-id.js';
|
||||
import { getActiveWebhooks } from '@/misc/webhook-cache.js';
|
||||
|
||||
export default async function(blocker: User, blockee: User) {
|
||||
export default async function(blocker: User, blockee: User): Promise<void> {
|
||||
await Promise.all([
|
||||
cancelRequest(blocker, blockee),
|
||||
cancelRequest(blockee, blocker),
|
||||
|
@ -32,13 +32,13 @@ export default async function(blocker: User, blockee: User) {
|
|||
|
||||
await Blockings.insert(blocking);
|
||||
|
||||
if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) {
|
||||
if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee) && blocker.federateBlocks) {
|
||||
const content = renderActivity(renderBlock(blocking));
|
||||
deliver(blocker, content, blockee.inbox);
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelRequest(follower: User, followee: User) {
|
||||
async function cancelRequest(follower: User, followee: User): Promise<void> {
|
||||
const request = await FollowRequests.findOneBy({
|
||||
followeeId: followee.id,
|
||||
followerId: follower.id,
|
||||
|
@ -75,20 +75,20 @@ async function cancelRequest(follower: User, followee: User) {
|
|||
});
|
||||
}
|
||||
|
||||
// リモートにフォローリクエストをしていたらUndoFollow送信
|
||||
// Send Undo Follow if followee is remote
|
||||
if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) {
|
||||
const content = renderActivity(renderUndo(renderFollow(follower, followee), follower));
|
||||
deliver(follower, content, followee.inbox);
|
||||
}
|
||||
|
||||
// リモートからフォローリクエストを受けていたらReject送信
|
||||
// Send Reject if follower is remote
|
||||
if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) {
|
||||
const content = renderActivity(renderReject(renderFollow(follower, followee, request.requestId!), followee));
|
||||
deliver(followee, content, follower.inbox);
|
||||
}
|
||||
}
|
||||
|
||||
async function unFollow(follower: User, followee: User) {
|
||||
async function unFollow(follower: User, followee: User): Promise<void> {
|
||||
const following = await Followings.findOneBy({
|
||||
followerId: follower.id,
|
||||
followeeId: followee.id,
|
||||
|
@ -122,14 +122,14 @@ async function unFollow(follower: User, followee: User) {
|
|||
});
|
||||
}
|
||||
|
||||
// リモートにフォローをしていたらUndoFollow送信
|
||||
// Send Undo Follow if follower is remote
|
||||
if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) {
|
||||
const content = renderActivity(renderUndo(renderFollow(follower, followee), follower));
|
||||
deliver(follower, content, followee.inbox);
|
||||
}
|
||||
}
|
||||
|
||||
async function removeFromList(listOwner: User, user: User) {
|
||||
async function removeFromList(listOwner: User, user: User): Promise<void> {
|
||||
const userLists = await UserLists.findBy({
|
||||
userId: listOwner.id,
|
||||
});
|
||||
|
|
|
@ -1,20 +1,28 @@
|
|||
import { IsNull } from 'typeorm';
|
||||
import { ILocalUser } from '@/models/entities/user.js';
|
||||
import { Users } from '@/models/index.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import { createSystemUser } from './create-system-user.js';
|
||||
|
||||
const ACTOR_USERNAME = 'instance.actor' as const;
|
||||
|
||||
let instanceActor = await Users.findOneBy({
|
||||
host: IsNull(),
|
||||
username: ACTOR_USERNAME,
|
||||
}) as ILocalUser | undefined;
|
||||
const cache = new Cache<ILocalUser>(Infinity);
|
||||
|
||||
export async function getInstanceActor(): Promise<ILocalUser> {
|
||||
if (instanceActor) {
|
||||
return instanceActor;
|
||||
const cached = cache.get(null);
|
||||
if (cached) return cached;
|
||||
|
||||
const user = await Users.findOneBy({
|
||||
host: IsNull(),
|
||||
username: ACTOR_USERNAME,
|
||||
}) as ILocalUser | undefined;
|
||||
|
||||
if (user) {
|
||||
cache.set(null, user);
|
||||
return user;
|
||||
} else {
|
||||
instanceActor = await createSystemUser(ACTOR_USERNAME) as ILocalUser;
|
||||
const created = await createSystemUser(ACTOR_USERNAME) as ILocalUser;
|
||||
cache.set(null, created);
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,22 +36,13 @@ import { Cache } from '@/misc/cache.js';
|
|||
import { UserProfile } from '@/models/entities/user-profile.js';
|
||||
import { getActiveWebhooks } from '@/misc/webhook-cache.js';
|
||||
import { IActivity } from '@/remote/activitypub/type.js';
|
||||
import { MINUTE } from '@/const.js';
|
||||
import { updateHashtags } from '../update-hashtag.js';
|
||||
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js';
|
||||
import { createNotification } from '../create-notification.js';
|
||||
import { addNoteToAntenna } from '../add-note-to-antenna.js';
|
||||
import { deliverToRelays } from '../relay.js';
|
||||
|
||||
const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(
|
||||
5 * MINUTE,
|
||||
() => UserProfiles.find({
|
||||
where: {
|
||||
enableWordMute: true,
|
||||
},
|
||||
select: ['userId', 'mutedWords'],
|
||||
}),
|
||||
);
|
||||
const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
|
||||
|
||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
||||
|
||||
|
@ -266,7 +257,12 @@ export default async (user: { id: User['id']; username: User['username']; host:
|
|||
incNotesCountOfUser(user);
|
||||
|
||||
// Word mute
|
||||
mutedWordsCache.fetch(null).then(us => {
|
||||
mutedWordsCache.fetch(null, () => UserProfiles.find({
|
||||
where: {
|
||||
enableWordMute: true,
|
||||
},
|
||||
select: ['userId', 'mutedWords'],
|
||||
})).then(us => {
|
||||
for (const u of us) {
|
||||
checkWordMute(note, { id: u.userId }, u.mutedWords).then(shouldMute => {
|
||||
if (shouldMute) {
|
||||
|
|
|
@ -53,9 +53,11 @@ export default async function(user: { id: User['id']; uri: User['uri']; host: Us
|
|||
deliverToConcerned(user, note, content);
|
||||
}
|
||||
|
||||
// also deliver delete activity to cascaded notes
|
||||
// also deliever delete activity to cascaded notes
|
||||
const cascadingNotes = await findCascadingNotes(note);
|
||||
for (const cascadingNote of cascadingNotes) {
|
||||
if (!cascadingNote.user) continue;
|
||||
if (!Users.isLocalUser(cascadingNote.user)) continue;
|
||||
const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${cascadingNote.id}`), cascadingNote.user));
|
||||
deliverToConcerned(cascadingNote.user, cascadingNote, content);
|
||||
}
|
||||
|
@ -105,9 +107,6 @@ async function findCascadingNotes(note: Note): Promise<Note[]> {
|
|||
});
|
||||
|
||||
await Promise.all(replies.map(reply => {
|
||||
// only add unique notes
|
||||
if (cascadingNotes.find((x) => x.id == reply.id) != null) return;
|
||||
|
||||
cascadingNotes.push(reply);
|
||||
return recursive(reply.id);
|
||||
}));
|
||||
|
|
|
@ -15,21 +15,20 @@ type pushNotificationsTypes = {
|
|||
'readAllMessagingMessagesOfARoom': { userId: string } | { groupId: string };
|
||||
};
|
||||
|
||||
// Reduce the content of the push message because of the character limit
|
||||
// プッシュメッセージサーバーには文字数制限があるため、内容を削減します
|
||||
function truncateNotification(notification: Packed<'Notification'>): any {
|
||||
if (notification.note) {
|
||||
return {
|
||||
...notification,
|
||||
note: {
|
||||
...notification.note,
|
||||
// replace text with getNoteSummary
|
||||
// textをgetNoteSummaryしたものに置き換える
|
||||
text: getNoteSummary(notification.type === 'renote' ? notification.note.renote as Packed<'Note'> : notification.note),
|
||||
|
||||
cw: undefined,
|
||||
reply: undefined,
|
||||
renote: undefined,
|
||||
// unnecessary, since usually the user who is receiving the notification knows who they are
|
||||
user: undefined as any,
|
||||
user: undefined as any, // 通知を受け取ったユーザーである場合が多いのでこれも捨てる
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -42,7 +41,7 @@ export async function pushNotification<T extends keyof pushNotificationsTypes>(u
|
|||
|
||||
if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return;
|
||||
|
||||
// Register key pair information
|
||||
// アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録
|
||||
push.setVapidDetails(config.url,
|
||||
meta.swPublicKey,
|
||||
meta.swPrivateKey);
|
||||
|
@ -66,6 +65,10 @@ export async function pushNotification<T extends keyof pushNotificationsTypes>(u
|
|||
}), {
|
||||
proxy: config.proxy,
|
||||
}).catch((err: any) => {
|
||||
//swLogger.info(err.statusCode);
|
||||
//swLogger.info(err.headers);
|
||||
//swLogger.info(err.body);
|
||||
|
||||
if (err.statusCode === 410) {
|
||||
SwSubscriptions.delete({
|
||||
userId,
|
||||
|
|
|
@ -3,27 +3,29 @@ import { Instances } from '@/models/index.js';
|
|||
import { genId } from '@/misc/gen-id.js';
|
||||
import { toPuny } from '@/misc/convert-host.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import { HOUR } from '@/const.js';
|
||||
|
||||
const cache = new Cache<Instance>(
|
||||
HOUR,
|
||||
(host) => Instances.findOneBy({ host }).then(x => x ?? undefined),
|
||||
);
|
||||
const cache = new Cache<Instance>(1000 * 60 * 60);
|
||||
|
||||
export async function registerOrFetchInstanceDoc(idnHost: string): Promise<Instance> {
|
||||
const host = toPuny(idnHost);
|
||||
|
||||
const cached = cache.fetch(host);
|
||||
const cached = cache.get(host);
|
||||
if (cached) return cached;
|
||||
|
||||
// apparently a new instance
|
||||
const i = await Instances.insert({
|
||||
id: genId(),
|
||||
host,
|
||||
caughtAt: new Date(),
|
||||
lastCommunicatedAt: new Date(),
|
||||
}).then(x => Instances.findOneByOrFail(x.identifiers[0]));
|
||||
const index = await Instances.findOneBy({ host });
|
||||
|
||||
cache.set(host, i);
|
||||
return i;
|
||||
if (index == null) {
|
||||
const i = await Instances.insert({
|
||||
id: genId(),
|
||||
host,
|
||||
caughtAt: new Date(),
|
||||
lastCommunicatedAt: new Date(),
|
||||
}).then(x => Instances.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
cache.set(host, i);
|
||||
return i;
|
||||
} else {
|
||||
cache.set(host, index);
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,21 +8,11 @@ import { Users, Relays } from '@/models/index.js';
|
|||
import { genId } from '@/misc/gen-id.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import { Relay } from '@/models/entities/relay.js';
|
||||
import { MINUTE } from '@/const.js';
|
||||
import { createSystemUser } from './create-system-user.js';
|
||||
|
||||
const ACTOR_USERNAME = 'relay.actor' as const;
|
||||
|
||||
/**
|
||||
* There is only one cache key: null.
|
||||
* A cache is only used here to have expiring storage.
|
||||
*/
|
||||
const relaysCache = new Cache<Relay[]>(
|
||||
10 * MINUTE,
|
||||
() => Relays.findBy({
|
||||
status: 'accepted',
|
||||
}),
|
||||
);
|
||||
const relaysCache = new Cache<Relay[]>(1000 * 60 * 10);
|
||||
|
||||
export async function getRelayActor(): Promise<ILocalUser> {
|
||||
const user = await Users.findOneBy({
|
||||
|
@ -93,7 +83,9 @@ export async function relayRejected(id: string) {
|
|||
export async function deliverToRelays(user: { id: User['id']; host: null; }, activity: any) {
|
||||
if (activity == null) return;
|
||||
|
||||
const relays = await relaysCache.fetch(null);
|
||||
const relays = await relaysCache.fetch(null, () => Relays.findBy({
|
||||
status: 'accepted',
|
||||
}));
|
||||
if (relays.length === 0) return;
|
||||
|
||||
// TODO
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Not, IsNull } from 'typeorm';
|
||||
import renderDelete from '@/remote/activitypub/renderer/delete.js';
|
||||
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||
import DeliverManager from '@/remote/activitypub/deliver-manager.js';
|
||||
import { deliver } from '@/queue/index.js';
|
||||
import config from '@/config/index.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { Users, Followings } from '@/models/index.js';
|
||||
|
@ -11,11 +11,27 @@ export async function doPostSuspend(user: { id: User['id']; host: User['host'] }
|
|||
publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true });
|
||||
|
||||
if (Users.isLocalUser(user)) {
|
||||
// 知り得る全SharedInboxにDelete配信
|
||||
const content = renderActivity(renderDelete(`${config.url}/users/${user.id}`, user));
|
||||
|
||||
// deliver to all of known network
|
||||
const dm = new DeliverManager(user, content);
|
||||
dm.addEveryone();
|
||||
await dm.execute();
|
||||
const queue: string[] = [];
|
||||
|
||||
const followings = await Followings.find({
|
||||
where: [
|
||||
{ followerSharedInbox: Not(IsNull()) },
|
||||
{ followeeSharedInbox: Not(IsNull()) },
|
||||
],
|
||||
select: ['followerSharedInbox', 'followeeSharedInbox'],
|
||||
});
|
||||
|
||||
const inboxes = followings.map(x => x.followerSharedInbox || x.followeeSharedInbox);
|
||||
|
||||
for (const inbox of inboxes) {
|
||||
if (inbox != null && !queue.includes(inbox)) queue.push(inbox);
|
||||
}
|
||||
|
||||
for (const inbox of queue) {
|
||||
deliver(user, content, inbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,10 @@ import { Users } from '@/models/index.js';
|
|||
import { Cache } from '@/misc/cache.js';
|
||||
import { subscriber } from '@/db/redis.js';
|
||||
|
||||
export const userByIdCache = new Cache<CacheableUser>(
|
||||
Infinity,
|
||||
(id) => Users.findOneBy({ id }).then(x => x ?? undefined),
|
||||
);
|
||||
export const localUserByNativeTokenCache = new Cache<CacheableLocalUser>(
|
||||
Infinity,
|
||||
(token) => Users.findOneBy({ token }).then(x => x ?? undefined),
|
||||
);
|
||||
export const uriPersonCache = new Cache<CacheableUser>(
|
||||
Infinity,
|
||||
(uri) => Users.findOneBy({ uri }).then(x => x ?? undefined),
|
||||
);
|
||||
export const userByIdCache = new Cache<CacheableUser>(Infinity);
|
||||
export const localUserByNativeTokenCache = new Cache<CacheableLocalUser | null>(Infinity);
|
||||
export const localUserByIdCache = new Cache<CacheableLocalUser>(Infinity);
|
||||
export const uriPersonCache = new Cache<CacheableUser | null>(Infinity);
|
||||
|
||||
subscriber.on('message', async (_, data) => {
|
||||
const obj = JSON.parse(data);
|
||||
|
@ -35,6 +27,7 @@ subscriber.on('message', async (_, data) => {
|
|||
}
|
||||
if (Users.isLocalUser(user)) {
|
||||
localUserByNativeTokenCache.set(user.token, user);
|
||||
localUserByIdCache.set(user.id, user);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
58
packages/backend/test/services/blocking.ts
Normal file
58
packages/backend/test/services/blocking.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as childProcess from 'child_process';
|
||||
import * as sinon from 'sinon';
|
||||
import { async, signup, startServer, shutdownServer, initTestDb } from '../utils.js';
|
||||
|
||||
describe('Creating a block activity', () => {
|
||||
let p: childProcess.ChildProcess;
|
||||
|
||||
// alice blocks bob
|
||||
let alice: any;
|
||||
let bob: any;
|
||||
let carol: any;
|
||||
|
||||
before(async () => {
|
||||
await initTestDb();
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
carol = await signup({ username: 'carol' });
|
||||
bob.host = 'http://remote';
|
||||
carol.host = 'http://remote';
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
it('Should federate blocks normally', async(async () => {
|
||||
const createBlock = (await import('../../src/services/blocking/create')).default;
|
||||
const deleteBlock = (await import('../../src/services/blocking/delete')).default;
|
||||
|
||||
const queues = await import('../../src/queue/index');
|
||||
const spy = sinon.spy(queues, 'deliver');
|
||||
await createBlock(alice, bob);
|
||||
assert(spy.calledOnce);
|
||||
await deleteBlock(alice, bob);
|
||||
assert(spy.calledTwice);
|
||||
}));
|
||||
|
||||
it('Should not federate blocks if federateBlocks is false', async () => {
|
||||
const createBlock = (await import('../../src/services/blocking/create')).default;
|
||||
const deleteBlock = (await import('../../src/services/blocking/delete')).default;
|
||||
|
||||
alice.federateBlocks = true;
|
||||
|
||||
const queues = await import('../../src/queue/index');
|
||||
const spy = sinon.spy(queues, 'deliver');
|
||||
await createBlock(alice, carol);
|
||||
await deleteBlock(alice, carol);
|
||||
assert(spy.notCalled);
|
||||
});
|
||||
});
|
|
@ -71,6 +71,7 @@ const ok = async () => {
|
|||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'content-type': 'multipart/form-data',
|
||||
authorization: `Bearer ${$i.token}`,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
:spellcheck="spellcheck"
|
||||
:step="step"
|
||||
:list="id"
|
||||
:maxlength="max"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
@keydown="onKeydown($event)"
|
||||
|
@ -59,7 +58,6 @@ const props = defineProps<{
|
|||
manualSave?: boolean;
|
||||
small?: boolean;
|
||||
large?: boolean;
|
||||
max?: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
:pattern="pattern"
|
||||
:autocomplete="autocomplete ? 'on' : 'off'"
|
||||
:spellcheck="spellcheck"
|
||||
:maxlength="max"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
@keydown="onKeydown($event)"
|
||||
|
@ -55,7 +54,6 @@ const props = withDefaults(defineProps<{
|
|||
pre?: boolean;
|
||||
debounce?: boolean;
|
||||
manualSave?: boolean;
|
||||
max?: number;
|
||||
}>(), {
|
||||
pattern: undefined,
|
||||
placeholder: '',
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<div v-if="!totpLogin" class="normal-signin">
|
||||
<MkInput v-model="username" class="_formBlock" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
|
||||
<template #prefix>@</template>
|
||||
<template #suffix>@{{ host }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="i18n.ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password>
|
||||
<template #prefix><i class="fas fa-lock"></i></template>
|
||||
|
@ -54,7 +55,7 @@ import { showSuspendedDialog } from '@/scripts/show-suspended-dialog';
|
|||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import MkInfo from '@/components/ui/info.vue';
|
||||
import { apiUrl } from '@/config';
|
||||
import { apiUrl, host as configHost } from '@/config';
|
||||
import { byteify, hexify } from '@/scripts/2fa';
|
||||
import * as os from '@/os';
|
||||
import { login } from '@/account';
|
||||
|
@ -67,6 +68,7 @@ let user = $ref(null);
|
|||
let username = $ref('');
|
||||
let password = $ref('');
|
||||
let token = $ref('');
|
||||
let host = $ref(toUnicode(configHost));
|
||||
let totpLogin = $ref(false);
|
||||
let challengeData = $ref(null);
|
||||
let queryingKey = $ref(false);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername">
|
||||
<template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template>
|
||||
<template #prefix>@</template>
|
||||
<template #suffix>@{{ host }}</template>
|
||||
<template #caption>
|
||||
<span v-if="usernameState === 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ i18n.ts.checking }}</span>
|
||||
<span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ i18n.ts.available }}</span>
|
||||
|
@ -69,6 +70,7 @@ import MkButton from './ui/button.vue';
|
|||
import MkCaptcha from './captcha.vue';
|
||||
import MkInput from './form/input.vue';
|
||||
import MkSwitch from './form/switch.vue';
|
||||
import * as config from '@/config';
|
||||
import * as os from '@/os';
|
||||
import { login } from '@/account';
|
||||
import { instance } from '@/instance';
|
||||
|
@ -85,6 +87,8 @@ const emit = defineEmits<{
|
|||
(ev: 'signupEmailPending'): void;
|
||||
}>();
|
||||
|
||||
const host = toUnicode(config.host);
|
||||
|
||||
let hcaptcha = $ref();
|
||||
let recaptcha = $ref();
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ export const apiWithDialog = ((
|
|||
promiseDialog(promise, null, (err) => {
|
||||
alert({
|
||||
type: 'error',
|
||||
text: (err.message + '\n' + (err?.endpoint ?? '') + (err?.code ?? '')).trim(),
|
||||
text: err.message + '\n' + (err as any).id,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -141,7 +141,7 @@ export function promiseDialog<T extends Promise<any>>(
|
|||
}
|
||||
});
|
||||
|
||||
// NOTE: dynamic import results in strange behaviour (showing is not reactive)
|
||||
// NOTE: dynamic importすると挙動がおかしくなる(showingの変更が伝播しない)
|
||||
popup(MkWaitingDialog, {
|
||||
success,
|
||||
showing,
|
||||
|
|
|
@ -26,27 +26,8 @@
|
|||
|
||||
<FormSection v-if="iAmModerator">
|
||||
<template #label>Moderation</template>
|
||||
<FormSwitch
|
||||
:model-value="suspended || isBlocked"
|
||||
@update:model-value="newValue => {suspended = newValue; toggleSuspend() }"
|
||||
:disabled="isBlocked"
|
||||
class="_formBlock"
|
||||
>
|
||||
{{ i18n.ts.stopActivityDelivery }}
|
||||
<template #caption>
|
||||
{{ i18n.ts.stopActivityDeliveryDescription }}
|
||||
</template>
|
||||
</FormSwitch>
|
||||
<FormSwitch
|
||||
v-model="isBlocked"
|
||||
@update:modelValue="toggleBlock"
|
||||
class="_formBlock"
|
||||
>
|
||||
{{ i18n.ts.blockThisInstance }}
|
||||
<template #caption>
|
||||
{{ i18n.ts.blockThisInstanceDescription }}
|
||||
</template>
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ i18n.ts.stopActivityDelivery }}</FormSwitch>
|
||||
<FormSwitch v-model="isBlocked" class="_formBlock" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</FormSwitch>
|
||||
<MkButton @click="refreshMetadata"><i class="fas fa-refresh"></i> Refresh metadata</MkButton>
|
||||
</FormSection>
|
||||
|
||||
|
@ -171,7 +152,7 @@ const usersPagination = {
|
|||
offsetMode: true,
|
||||
};
|
||||
|
||||
async function fetch(): Promise<void> {
|
||||
async function fetch() {
|
||||
instance = await os.api('federation/show-instance', {
|
||||
host: props.host,
|
||||
});
|
||||
|
@ -179,21 +160,21 @@ async function fetch(): Promise<void> {
|
|||
isBlocked = instance.isBlocked;
|
||||
}
|
||||
|
||||
async function toggleBlock(): Promise<void> {
|
||||
async function toggleBlock(ev) {
|
||||
if (meta == null) return;
|
||||
await os.api('admin/update-meta', {
|
||||
blockedHosts: isBlocked ? meta.blockedHosts.concat([instance.host]) : meta.blockedHosts.filter(x => x !== instance.host),
|
||||
});
|
||||
}
|
||||
|
||||
async function toggleSuspend(): Promise<void> {
|
||||
async function toggleSuspend(v) {
|
||||
await os.api('admin/federation/update-instance', {
|
||||
host: instance.host,
|
||||
isSuspended: suspended,
|
||||
});
|
||||
}
|
||||
|
||||
function refreshMetadata(): void {
|
||||
function refreshMetadata() {
|
||||
os.api('admin/federation/refresh-remote-instance-metadata', {
|
||||
host: instance.host,
|
||||
});
|
||||
|
|
|
@ -28,6 +28,10 @@
|
|||
{{ i18n.ts.makeExplorable }}
|
||||
<template #caption>{{ i18n.ts.makeExplorableDescription }}</template>
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model="federateBlocks" @update:value="save()">
|
||||
{{ $ts.federateBlocks }}
|
||||
<template #caption>{{ $ts.federateBlocksDescription }}</template>
|
||||
</FormSwitch>
|
||||
|
||||
<FormSection>
|
||||
<FormFolder class="_formBlock">
|
||||
|
@ -69,12 +73,13 @@ let isExplorable = $ref($i.isExplorable);
|
|||
let hideOnlineStatus = $ref($i.hideOnlineStatus);
|
||||
let publicReactions = $ref($i.publicReactions);
|
||||
let ffVisibility = $ref($i.ffVisibility);
|
||||
let federateBlocks = $ref($i.federateBlocks);
|
||||
|
||||
let defaultNoteVisibility = $computed(defaultStore.makeGetterSetter('defaultNoteVisibility'));
|
||||
let defaultNoteLocalOnly = $computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly'));
|
||||
let keepCw = $computed(defaultStore.makeGetterSetter('keepCw'));
|
||||
|
||||
function save() {
|
||||
function save(): void {
|
||||
os.api('i/update', {
|
||||
isLocked: !!isLocked,
|
||||
autoAcceptFollowed: !!autoAcceptFollowed,
|
||||
|
@ -83,6 +88,7 @@ function save() {
|
|||
hideOnlineStatus: !!hideOnlineStatus,
|
||||
publicReactions: !!publicReactions,
|
||||
ffVisibility,
|
||||
federateBlocks,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<template #label>{{ i18n.ts._profile.name }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormTextarea v-model="profile.description" :max="2048" tall manual-save class="_formBlock">
|
||||
<FormTextarea v-model="profile.description" :max="500" tall manual-save class="_formBlock">
|
||||
<template #label>{{ i18n.ts._profile.description }}</template>
|
||||
<template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
|
||||
</FormTextarea>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username class="_formBlock">
|
||||
<template #label>{{ i18n.ts.username }}</template>
|
||||
<template #prefix>@</template>
|
||||
<template #suffix>@{{ host }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="password" type="password" data-cy-admin-password class="_formBlock">
|
||||
<template #label>{{ i18n.ts.password }}</template>
|
||||
|
@ -23,6 +24,7 @@
|
|||
<script lang="ts" setup>
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import { host } from '@/config';
|
||||
import * as os from '@/os';
|
||||
import { login } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
|
|
|
@ -30,6 +30,14 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||
const i18n = await swLang.i18n as I18n<any>;
|
||||
const { t } = i18n;
|
||||
switch (data.type) {
|
||||
/*
|
||||
case 'driveFileCreated': // TODO (Server Side)
|
||||
return [t('_notification.fileUploaded'), {
|
||||
body: body.name,
|
||||
icon: body.url,
|
||||
data
|
||||
}];
|
||||
*/
|
||||
case 'notification':
|
||||
switch (data.body.type) {
|
||||
case 'follow':
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
declare var self: ServiceWorkerGlobalScope;
|
||||
|
||||
import { createNotification } from '@/scripts/create-notification';
|
||||
import { createEmptyNotification, createNotification } from '@/scripts/create-notification';
|
||||
import { swLang } from '@/scripts/lang';
|
||||
import { swNotificationRead } from '@/scripts/notification-read';
|
||||
import { pushNotificationDataMap } from '@/types';
|
||||
|
@ -67,6 +67,8 @@ self.addEventListener('push', ev => {
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return createEmptyNotification();
|
||||
}));
|
||||
});
|
||||
|
||||
|
|
124
yarn.lock
124
yarn.lock
|
@ -1449,7 +1449,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sinonjs/fake-timers@npm:9.1.2":
|
||||
"@sinonjs/commons@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "@sinonjs/commons@npm:2.0.0"
|
||||
dependencies:
|
||||
type-detect: 4.0.8
|
||||
checksum: 5023ba17edf2b85ed58262313b8e9b59e23c6860681a9af0200f239fe939e2b79736d04a260e8270ddd57196851dde3ba754d7230be5c5234e777ae2ca8af137
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sinonjs/fake-timers@npm:9.1.2, @sinonjs/fake-timers@npm:^9.1.2":
|
||||
version: 9.1.2
|
||||
resolution: "@sinonjs/fake-timers@npm:9.1.2"
|
||||
dependencies:
|
||||
|
@ -1458,6 +1467,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sinonjs/fake-timers@npm:^7.0.4":
|
||||
version: 7.1.2
|
||||
resolution: "@sinonjs/fake-timers@npm:7.1.2"
|
||||
dependencies:
|
||||
"@sinonjs/commons": ^1.7.0
|
||||
checksum: c84773d7973edad5511a31d2cc75023447b5cf714a84de9bb50eda45dda88a0d3bd2c30bf6e6e936da50a048d5352e2151c694e13e59b97d187ba1f329e9a00c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sinonjs/fake-timers@npm:^8.0.1":
|
||||
version: 8.1.0
|
||||
resolution: "@sinonjs/fake-timers@npm:8.1.0"
|
||||
|
@ -1467,6 +1485,24 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sinonjs/samsam@npm:^7.0.1":
|
||||
version: 7.0.1
|
||||
resolution: "@sinonjs/samsam@npm:7.0.1"
|
||||
dependencies:
|
||||
"@sinonjs/commons": ^2.0.0
|
||||
lodash.get: ^4.4.2
|
||||
type-detect: ^4.0.8
|
||||
checksum: 291efb158d54c67dee23ddabcb28873d22063449b692aaa3b2a4f1826d2f79d38695574063c92e9c17573cc805cd6acbf0ab0c66c9f3aed7afd0f12a2b905615
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sinonjs/text-encoding@npm:^0.7.1":
|
||||
version: 0.7.2
|
||||
resolution: "@sinonjs/text-encoding@npm:0.7.2"
|
||||
checksum: fe690002a32ba06906cf87e2e8fe84d1590294586f2a7fd180a65355b53660c155c3273d8011a5f2b77209b819aa7306678ae6e4aea0df014bd7ffd4bbbcf1ab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sqltools/formatter@npm:^1.2.2":
|
||||
version: 1.2.3
|
||||
resolution: "@sqltools/formatter@npm:1.2.3"
|
||||
|
@ -2369,6 +2405,22 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/sinon@npm:^10.0.13":
|
||||
version: 10.0.13
|
||||
resolution: "@types/sinon@npm:10.0.13"
|
||||
dependencies:
|
||||
"@types/sinonjs__fake-timers": "*"
|
||||
checksum: 46a14c888db50f0098ec53d451877e0111d878ec4a653b9e9ed7f8e54de386d6beb0e528ddc3e95cd3361a8ab9ad54e4cca33cd88d45b9227b83e9fc8fb6688a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/sinonjs__fake-timers@npm:*, @types/sinonjs__fake-timers@npm:8.1.2":
|
||||
version: 8.1.2
|
||||
resolution: "@types/sinonjs__fake-timers@npm:8.1.2"
|
||||
checksum: bbc73a5ab6c0ec974929392f3d6e1e8db4ebad97ec506d785301e1c3d8a4f98a35b1aa95b97035daef02886fd8efd7788a2fa3ced2ec7105988bfd8dce61eedd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/sinonjs__fake-timers@npm:8.1.1":
|
||||
version: 8.1.1
|
||||
resolution: "@types/sinonjs__fake-timers@npm:8.1.1"
|
||||
|
@ -2376,13 +2428,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/sinonjs__fake-timers@npm:8.1.2":
|
||||
version: 8.1.2
|
||||
resolution: "@types/sinonjs__fake-timers@npm:8.1.2"
|
||||
checksum: bbc73a5ab6c0ec974929392f3d6e1e8db4ebad97ec506d785301e1c3d8a4f98a35b1aa95b97035daef02886fd8efd7788a2fa3ced2ec7105988bfd8dce61eedd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/sizzle@npm:^2.3.2":
|
||||
version: 2.3.3
|
||||
resolution: "@types/sizzle@npm:2.3.3"
|
||||
|
@ -3657,6 +3702,7 @@ __metadata:
|
|||
"@types/sanitize-html": 2.6.2
|
||||
"@types/semver": 7.3.12
|
||||
"@types/sharp": 0.30.5
|
||||
"@types/sinon": ^10.0.13
|
||||
"@types/sinonjs__fake-timers": 8.1.2
|
||||
"@types/speakeasy": 2.0.7
|
||||
"@types/tinycolor2": 1.4.3
|
||||
|
@ -3744,6 +3790,7 @@ __metadata:
|
|||
sanitize-html: 2.7.0
|
||||
semver: 7.3.7
|
||||
sharp: 0.31.2
|
||||
sinon: ^14.0.2
|
||||
speakeasy: 2.0.0
|
||||
strict-event-emitter-types: 2.0.0
|
||||
stringz: 2.1.0
|
||||
|
@ -5917,6 +5964,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"diff@npm:^5.0.0":
|
||||
version: 5.1.0
|
||||
resolution: "diff@npm:5.1.0"
|
||||
checksum: c7bf0df7c9bfbe1cf8a678fd1b2137c4fb11be117a67bc18a0e03ae75105e8533dbfb1cda6b46beb3586ef5aed22143ef9d70713977d5fb1f9114e21455fba90
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dijkstrajs@npm:^1.0.1":
|
||||
version: 1.0.2
|
||||
resolution: "dijkstrajs@npm:1.0.2"
|
||||
|
@ -9764,6 +9818,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isarray@npm:0.0.1":
|
||||
version: 0.0.1
|
||||
resolution: "isarray@npm:0.0.1"
|
||||
checksum: 49191f1425681df4a18c2f0f93db3adb85573bcdd6a4482539d98eac9e705d8961317b01175627e860516a2fc45f8f9302db26e5a380a97a520e272e2a40a8d4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isarray@npm:1.0.0, isarray@npm:^1.0.0, isarray@npm:~1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "isarray@npm:1.0.0"
|
||||
|
@ -10808,6 +10869,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"just-extend@npm:^4.0.2":
|
||||
version: 4.2.1
|
||||
resolution: "just-extend@npm:4.2.1"
|
||||
checksum: ff9fdede240fad313efeeeb68a660b942e5586d99c0058064c78884894a2690dc09bba44c994ad4e077e45d913fef01a9240c14a72c657b53687ac58de53b39c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jwa@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "jwa@npm:2.0.0"
|
||||
|
@ -12262,6 +12330,19 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nise@npm:^5.1.2":
|
||||
version: 5.1.2
|
||||
resolution: "nise@npm:5.1.2"
|
||||
dependencies:
|
||||
"@sinonjs/commons": ^2.0.0
|
||||
"@sinonjs/fake-timers": ^7.0.4
|
||||
"@sinonjs/text-encoding": ^0.7.1
|
||||
just-extend: ^4.0.2
|
||||
path-to-regexp: ^1.7.0
|
||||
checksum: 688c557333dcbc5b41f4f1f1b0ea32fb0f8b424541a8958140bc61074980362c954b2aeb027c282de26b9ddcb4b230656f68ac4206777499e405dd7e716ec1f8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-abi@npm:^3.3.0":
|
||||
version: 3.24.0
|
||||
resolution: "node-abi@npm:3.24.0"
|
||||
|
@ -13133,6 +13214,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-to-regexp@npm:^1.7.0":
|
||||
version: 1.8.0
|
||||
resolution: "path-to-regexp@npm:1.8.0"
|
||||
dependencies:
|
||||
isarray: 0.0.1
|
||||
checksum: 709f6f083c0552514ef4780cb2e7e4cf49b0cc89a97439f2b7cc69a608982b7690fb5d1720a7473a59806508fc2dae0be751ba49f495ecf89fd8fbc62abccbcd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-to-regexp@npm:^6.1.0":
|
||||
version: 6.2.1
|
||||
resolution: "path-to-regexp@npm:6.2.1"
|
||||
|
@ -15353,6 +15443,20 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sinon@npm:^14.0.2":
|
||||
version: 14.0.2
|
||||
resolution: "sinon@npm:14.0.2"
|
||||
dependencies:
|
||||
"@sinonjs/commons": ^2.0.0
|
||||
"@sinonjs/fake-timers": ^9.1.2
|
||||
"@sinonjs/samsam": ^7.0.1
|
||||
diff: ^5.0.0
|
||||
nise: ^5.1.2
|
||||
supports-color: ^7.2.0
|
||||
checksum: de7730cd7785a457e42f9a93e955780c870296036a13816e3c0c5648360afae82fdc748e36c854cf26fb8abd117855a7211aee49265c334fa61439aae17a1b72
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sisteransi@npm:^1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "sisteransi@npm:1.0.5"
|
||||
|
@ -16054,7 +16158,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0":
|
||||
"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0, supports-color@npm:^7.2.0":
|
||||
version: 7.2.0
|
||||
resolution: "supports-color@npm:7.2.0"
|
||||
dependencies:
|
||||
|
@ -16764,7 +16868,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type-detect@npm:4.0.8":
|
||||
"type-detect@npm:4.0.8, type-detect@npm:^4.0.8":
|
||||
version: 4.0.8
|
||||
resolution: "type-detect@npm:4.0.8"
|
||||
checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15
|
||||
|
|
Loading…
Reference in a new issue