chore: rome formatting

This commit is contained in:
Kainoa Kanter 2023-04-07 16:47:04 -07:00
parent 37c0423da6
commit aeb0839da9
15 changed files with 1664 additions and 1032 deletions

View File

@ -1,57 +1,65 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parser: "@typescript-eslint/parser",
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
project: ["./tsconfig.json"],
},
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
plugins: ["@typescript-eslint"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
rules: {
'indent': ['error', 'tab', {
'SwitchCase': 1,
'MemberExpression': 'off',
'flatTernaryExpressions': true,
'ArrayExpression': 'first',
'ObjectExpression': 'first',
}],
'eol-last': ['error', 'always'],
'semi': ['error', 'always'],
'quotes': ['error', 'single'],
'comma-dangle': ['error', 'always-multiline'],
'keyword-spacing': ['error', {
'before': true,
'after': true,
}],
'key-spacing': ['error', {
'beforeColon': false,
'afterColon': true,
}],
'space-infix-ops': ['error'],
'space-before-blocks': ['error', 'always'],
'object-curly-spacing': ['error', 'always'],
'nonblock-statement-body-position': ['error', 'beside'],
'eqeqeq': ['error', 'always', { 'null': 'ignore' }],
'no-multiple-empty-lines': ['error', { 'max': 1 }],
'no-multi-spaces': ['error'],
'no-var': ['error'],
'prefer-arrow-callback': ['error'],
'no-throw-literal': ['error'],
'no-param-reassign': ['warn'],
'no-constant-condition': ['warn'],
'no-empty-pattern': ['warn'],
'@typescript-eslint/no-unnecessary-condition': ['error'],
'@typescript-eslint/no-inferrable-types': ['warn'],
'@typescript-eslint/no-non-null-assertion': ['warn'],
'@typescript-eslint/explicit-function-return-type': ['warn'],
'@typescript-eslint/no-misused-promises': ['error', {
'checksVoidReturn': false,
}],
'@typescript-eslint/consistent-type-imports': 'error',
indent: [
"error",
"tab",
{
SwitchCase: 1,
MemberExpression: "off",
flatTernaryExpressions: true,
ArrayExpression: "first",
ObjectExpression: "first",
},
],
"eol-last": ["error", "always"],
semi: ["error", "always"],
quotes: ["error", "single"],
"comma-dangle": ["error", "always-multiline"],
"keyword-spacing": [
"error",
{
before: true,
after: true,
},
],
"key-spacing": [
"error",
{
beforeColon: false,
afterColon: true,
},
],
"space-infix-ops": ["error"],
"space-before-blocks": ["error", "always"],
"object-curly-spacing": ["error", "always"],
"nonblock-statement-body-position": ["error", "beside"],
eqeqeq: ["error", "always", { null: "ignore" }],
"no-multiple-empty-lines": ["error", { max: 1 }],
"no-multi-spaces": ["error"],
"no-var": ["error"],
"prefer-arrow-callback": ["error"],
"no-throw-literal": ["error"],
"no-param-reassign": ["warn"],
"no-constant-condition": ["warn"],
"no-empty-pattern": ["warn"],
"@typescript-eslint/no-unnecessary-condition": ["error"],
"@typescript-eslint/no-inferrable-types": ["warn"],
"@typescript-eslint/no-non-null-assertion": ["warn"],
"@typescript-eslint/explicit-function-return-type": ["warn"],
"@typescript-eslint/no-misused-promises": [
"error",
{
checksVoidReturn: false,
},
],
"@typescript-eslint/consistent-type-imports": "error",
},
};

View File

@ -1,7 +1,7 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/en/configuration.html
*/
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/en/configuration.html
*/
export default {
// All imported modules in your tests should be mocked automatically
@ -117,9 +117,7 @@ export default {
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
roots: [
"<rootDir>"
],
roots: ["<rootDir>"],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
@ -149,7 +147,7 @@ export default {
testMatch: [
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[tj]s?(x)",
"<rootDir>/test/**/*"
"<rootDir>/test/**/*",
],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
@ -174,7 +172,7 @@ export default {
// A map from regular expressions to paths to transformers
transform: {
"^.+\\.(ts|tsx)$": "ts-jest"
"^.+\\.(ts|tsx)$": "ts-jest",
},
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation

View File

@ -4,8 +4,8 @@ export type Acct = {
};
export function parse(acct: string): Acct {
if (acct.startsWith('@')) acct = acct.substr(1);
const split = acct.split('@', 2);
if (acct.startsWith("@")) acct = acct.substr(1);
const split = acct.split("@", 2);
return { username: split[0], host: split[1] || null };
}

View File

@ -1,4 +1,4 @@
import { Endpoints } from './api.types';
import { Endpoints } from "./api.types";
const MK_API_ERROR = Symbol();
@ -6,7 +6,7 @@ export type APIError = {
id: string;
code: string;
message: string;
kind: 'client' | 'server';
kind: "client" | "server";
info: Record<string, any>;
};
@ -14,25 +14,38 @@ export function isAPIError(reason: any): reason is APIError {
return reason[MK_API_ERROR] === true;
}
export type FetchLike = (input: string, init?: {
export type FetchLike = (
input: string,
init?: {
method?: string;
body?: string;
credentials?: RequestCredentials;
cache?: RequestCache;
}) => Promise<{
status: number;
json(): Promise<any>;
}>;
},
) => Promise<{
status: number;
json(): Promise<any>;
}>;
type IsNeverType<T> = [T] extends [never] ? true : false;
type StrictExtract<Union, Cond> = Cond extends Union ? Union : never;
type IsCaseMatched<E extends keyof Endpoints, P extends Endpoints[E]['req'], C extends number> =
IsNeverType<StrictExtract<Endpoints[E]['res']['$switch']['$cases'][C], [P, any]>> extends false ? true : false;
type IsCaseMatched<
E extends keyof Endpoints,
P extends Endpoints[E]["req"],
C extends number,
> = IsNeverType<
StrictExtract<Endpoints[E]["res"]["$switch"]["$cases"][C], [P, any]>
> extends false
? true
: false;
type GetCaseResult<E extends keyof Endpoints, P extends Endpoints[E]['req'], C extends number> =
StrictExtract<Endpoints[E]['res']['$switch']['$cases'][C], [P, any]>[1];
type GetCaseResult<
E extends keyof Endpoints,
P extends Endpoints[E]["req"],
C extends number,
> = StrictExtract<Endpoints[E]["res"]["$switch"]["$cases"][C], [P, any]>[1];
export class APIClient {
public origin: string;
@ -40,9 +53,9 @@ export class APIClient {
public fetch: FetchLike;
constructor(opts: {
origin: APIClient['origin'];
credential?: APIClient['credential'];
fetch?: APIClient['fetch'] | null | undefined;
origin: APIClient["origin"];
credential?: APIClient["credential"];
fetch?: APIClient["fetch"] | null | undefined;
}) {
this.origin = opts.origin;
this.credential = opts.credential;
@ -51,48 +64,64 @@ export class APIClient {
this.fetch = opts.fetch || ((...args) => fetch(...args));
}
public request<E extends keyof Endpoints, P extends Endpoints[E]['req']>(
endpoint: E, params: P = {} as P, credential?: string | null | undefined,
): Promise<Endpoints[E]['res'] extends { $switch: { $cases: [any, any][]; $default: any; }; }
?
IsCaseMatched<E, P, 0> extends true ? GetCaseResult<E, P, 0> :
IsCaseMatched<E, P, 1> extends true ? GetCaseResult<E, P, 1> :
IsCaseMatched<E, P, 2> extends true ? GetCaseResult<E, P, 2> :
IsCaseMatched<E, P, 3> extends true ? GetCaseResult<E, P, 3> :
IsCaseMatched<E, P, 4> extends true ? GetCaseResult<E, P, 4> :
IsCaseMatched<E, P, 5> extends true ? GetCaseResult<E, P, 5> :
IsCaseMatched<E, P, 6> extends true ? GetCaseResult<E, P, 6> :
IsCaseMatched<E, P, 7> extends true ? GetCaseResult<E, P, 7> :
IsCaseMatched<E, P, 8> extends true ? GetCaseResult<E, P, 8> :
IsCaseMatched<E, P, 9> extends true ? GetCaseResult<E, P, 9> :
Endpoints[E]['res']['$switch']['$default']
: Endpoints[E]['res']>
{
public request<E extends keyof Endpoints, P extends Endpoints[E]["req"]>(
endpoint: E,
params: P = {} as P,
credential?: string | null | undefined,
): Promise<
Endpoints[E]["res"] extends {
$switch: { $cases: [any, any][]; $default: any };
}
? IsCaseMatched<E, P, 0> extends true
? GetCaseResult<E, P, 0>
: IsCaseMatched<E, P, 1> extends true
? GetCaseResult<E, P, 1>
: IsCaseMatched<E, P, 2> extends true
? GetCaseResult<E, P, 2>
: IsCaseMatched<E, P, 3> extends true
? GetCaseResult<E, P, 3>
: IsCaseMatched<E, P, 4> extends true
? GetCaseResult<E, P, 4>
: IsCaseMatched<E, P, 5> extends true
? GetCaseResult<E, P, 5>
: IsCaseMatched<E, P, 6> extends true
? GetCaseResult<E, P, 6>
: IsCaseMatched<E, P, 7> extends true
? GetCaseResult<E, P, 7>
: IsCaseMatched<E, P, 8> extends true
? GetCaseResult<E, P, 8>
: IsCaseMatched<E, P, 9> extends true
? GetCaseResult<E, P, 9>
: Endpoints[E]["res"]["$switch"]["$default"]
: Endpoints[E]["res"]
> {
const promise = new Promise((resolve, reject) => {
this.fetch(`${this.origin}/api/${endpoint}`, {
method: 'POST',
method: "POST",
body: JSON.stringify({
...params,
i: credential !== undefined ? credential : this.credential,
}),
credentials: 'omit',
cache: 'no-cache',
}).then(async (res) => {
const body = res.status === 204 ? null : await res.json();
if (res.status === 200) {
resolve(body);
} else if (res.status === 204) {
resolve(null);
} else {
reject({
[MK_API_ERROR]: true,
...body.error,
});
}
}).catch(reject);
credentials: "omit",
cache: "no-cache",
})
.then(async (res) => {
const body = res.status === 204 ? null : await res.json();
if (res.status === 200) {
resolve(body);
} else if (res.status === 204) {
resolve(null);
} else {
reject({
[MK_API_ERROR]: true,
...body.error,
});
}
})
.catch(reject);
});
return promise as any;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,42 +1,60 @@
export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'] as const;
export const notificationTypes = [
"follow",
"mention",
"reply",
"renote",
"quote",
"reaction",
"pollVote",
"pollEnded",
"receiveFollowRequest",
"followRequestAccepted",
"groupInvited",
"app",
] as const;
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
export const noteVisibilities = [
"public",
"home",
"followers",
"specified",
] as const;
export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const;
export const mutedNoteReasons = ["word", "manual", "spam", "other"] as const;
export const ffVisibility = ['public', 'followers', 'private'] as const;
export const ffVisibility = ["public", "followers", "private"] as const;
export const permissions = [
'read:account',
'write:account',
'read:blocks',
'write:blocks',
'read:drive',
'write:drive',
'read:favorites',
'write:favorites',
'read:following',
'write:following',
'read:messaging',
'write:messaging',
'read:mutes',
'write:mutes',
'write:notes',
'read:notifications',
'write:notifications',
'read:reactions',
'write:reactions',
'write:votes',
'read:pages',
'write:pages',
'write:page-likes',
'read:page-likes',
'read:user-groups',
'write:user-groups',
'read:channels',
'write:channels',
'read:gallery',
'write:gallery',
'read:gallery-likes',
'write:gallery-likes',
"read:account",
"write:account",
"read:blocks",
"write:blocks",
"read:drive",
"write:drive",
"read:favorites",
"write:favorites",
"read:following",
"write:following",
"read:messaging",
"write:messaging",
"read:mutes",
"write:mutes",
"write:notes",
"read:notifications",
"write:notifications",
"read:reactions",
"write:reactions",
"write:votes",
"read:pages",
"write:pages",
"write:page-likes",
"read:page-likes",
"read:user-groups",
"write:user-groups",
"read:channels",
"write:channels",
"read:gallery",
"write:gallery",
"read:gallery-likes",
"write:gallery-likes",
];

View File

@ -11,7 +11,7 @@ export type UserLite = {
username: string;
host: string | null;
name: string;
onlineStatus: 'online' | 'active' | 'offline' | 'unknown';
onlineStatus: "online" | "active" | "offline" | "unknown";
avatarUrl: string;
avatarBlurhash: string;
alsoKnownAs: string[];
@ -21,12 +21,12 @@ export type UserLite = {
url: string;
}[];
instance?: {
name: Instance['name'];
softwareName: Instance['softwareName'];
softwareVersion: Instance['softwareVersion'];
iconUrl: Instance['iconUrl'];
faviconUrl: Instance['faviconUrl'];
themeColor: Instance['themeColor'];
name: Instance["name"];
softwareName: Instance["softwareName"];
softwareVersion: Instance["softwareVersion"];
iconUrl: Instance["iconUrl"];
faviconUrl: Instance["faviconUrl"];
themeColor: Instance["themeColor"];
};
};
@ -37,8 +37,8 @@ export type UserDetailed = UserLite & {
birthday: string | null;
createdAt: DateString;
description: string | null;
ffVisibility: 'public' | 'followers' | 'private';
fields: {name: string; value: string}[];
ffVisibility: "public" | "followers" | "private";
fields: { name: string; value: string }[];
followersCount: number;
followingCount: number;
hasPendingFollowRequestFromYou: boolean;
@ -77,12 +77,12 @@ export type UserList = {
id: ID;
createdAt: DateString;
name: string;
userIds: User['id'][];
userIds: User["id"][];
};
export type MeDetailed = UserDetailed & {
avatarId: DriveFile['id'];
bannerId: DriveFile['id'];
avatarId: DriveFile["id"];
bannerId: DriveFile["id"];
autoAcceptFollowed: boolean;
alwaysMarkNsfw: boolean;
carefulBot: boolean;
@ -133,15 +133,15 @@ export type Note = {
text: string | null;
cw: string | null;
user: User;
userId: User['id'];
userId: User["id"];
reply?: Note;
replyId: Note['id'];
replyId: Note["id"];
renote?: Note;
renoteId: Note['id'];
renoteId: Note["id"];
files: DriveFile[];
fileIds: DriveFile['id'][];
visibility: 'public' | 'home' | 'followers' | 'specified';
visibleUserIds?: User['id'][];
fileIds: DriveFile["id"][];
visibility: "public" | "home" | "followers" | "specified";
visibleUserIds?: User["id"][];
localOnly?: boolean;
myReaction?: string;
reactions: Record<string, number>;
@ -176,75 +176,87 @@ export type Notification = {
id: ID;
createdAt: DateString;
isRead: boolean;
} & ({
type: 'reaction';
reaction: string;
user: User;
userId: User['id'];
note: Note;
} | {
type: 'reply';
user: User;
userId: User['id'];
note: Note;
} | {
type: 'renote';
user: User;
userId: User['id'];
note: Note;
} | {
type: 'quote';
user: User;
userId: User['id'];
note: Note;
} | {
type: 'mention';
user: User;
userId: User['id'];
note: Note;
} | {
type: 'pollVote';
user: User;
userId: User['id'];
note: Note;
} | {
type: 'follow';
user: User;
userId: User['id'];
} | {
type: 'followRequestAccepted';
user: User;
userId: User['id'];
} | {
type: 'receiveFollowRequest';
user: User;
userId: User['id'];
} | {
type: 'groupInvited';
invitation: UserGroup;
user: User;
userId: User['id'];
} | {
type: 'app';
header?: string | null;
body: string;
icon?: string | null;
});
} & (
| {
type: "reaction";
reaction: string;
user: User;
userId: User["id"];
note: Note;
}
| {
type: "reply";
user: User;
userId: User["id"];
note: Note;
}
| {
type: "renote";
user: User;
userId: User["id"];
note: Note;
}
| {
type: "quote";
user: User;
userId: User["id"];
note: Note;
}
| {
type: "mention";
user: User;
userId: User["id"];
note: Note;
}
| {
type: "pollVote";
user: User;
userId: User["id"];
note: Note;
}
| {
type: "follow";
user: User;
userId: User["id"];
}
| {
type: "followRequestAccepted";
user: User;
userId: User["id"];
}
| {
type: "receiveFollowRequest";
user: User;
userId: User["id"];
}
| {
type: "groupInvited";
invitation: UserGroup;
user: User;
userId: User["id"];
}
| {
type: "app";
header?: string | null;
body: string;
icon?: string | null;
}
);
export type MessagingMessage = {
id: ID;
createdAt: DateString;
file: DriveFile | null;
fileId: DriveFile['id'] | null;
fileId: DriveFile["id"] | null;
isRead: boolean;
reads: User['id'][];
reads: User["id"][];
text: string | null;
user: User;
userId: User['id'];
userId: User["id"];
recipient?: User | null;
recipientId: User['id'] | null;
recipientId: User["id"] | null;
group?: UserGroup | null;
groupId: UserGroup['id'] | null;
groupId: UserGroup["id"] | null;
};
export type CustomEmoji = {
@ -325,7 +337,7 @@ export type Page = {
id: ID;
createdAt: DateString;
updatedAt: DateString;
userId: User['id'];
userId: User["id"];
user: User;
content: Record<string, any>[];
variables: Record<string, any>[];
@ -336,7 +348,7 @@ export type Page = {
alignCenter: boolean;
font: string;
script: string;
eyeCatchingImageId: DriveFile['id'] | null;
eyeCatchingImageId: DriveFile["id"] | null;
eyeCatchingImage: DriveFile | null;
attachedFiles: any;
likedCount: number;
@ -344,10 +356,10 @@ export type Page = {
};
export type PageEvent = {
pageId: Page['id'];
pageId: Page["id"];
event: string;
var: any;
userId: User['id'];
userId: User["id"];
user: User;
};
@ -367,7 +379,7 @@ export type Antenna = {
name: string;
keywords: string[][]; // TODO
excludeKeywords: string[][]; // TODO
src: 'home' | 'all' | 'users' | 'list' | 'group' | 'instances';
src: "home" | "all" | "users" | "list" | "group" | "instances";
userListId: ID | null; // TODO
userGroupId: ID | null; // TODO
users: string[]; // TODO
@ -394,7 +406,7 @@ export type Clip = TODO;
export type NoteFavorite = {
id: ID;
createdAt: DateString;
noteId: Note['id'];
noteId: Note["id"];
note: Note;
};
@ -412,8 +424,8 @@ export type Channel = {
export type Following = {
id: ID;
createdAt: DateString;
followerId: User['id'];
followeeId: User['id'];
followerId: User["id"];
followeeId: User["id"];
};
export type FollowingFolloweePopulated = Following & {
@ -427,7 +439,7 @@ export type FollowingFollowerPopulated = Following & {
export type Blocking = {
id: ID;
createdAt: DateString;
blockeeId: User['id'];
blockeeId: User["id"];
blockee: UserDetailed;
};
@ -469,10 +481,10 @@ export type Signin = {
};
export type UserSorting =
| '+follower'
| '-follower'
| '+createdAt'
| '-createdAt'
| '+updatedAt'
| '-updatedAt';
export type OriginType = 'combined' | 'local' | 'remote';
| "+follower"
| "-follower"
| "+createdAt"
| "-createdAt"
| "+updatedAt"
| "-updatedAt";
export type OriginType = "combined" | "local" | "remote";

View File

@ -1,16 +1,10 @@
import { Endpoints } from './api.types';
import Stream, { Connection } from './streaming';
import { Channels } from './streaming.types';
import { Acct } from './acct';
import * as consts from './consts';
import { Endpoints } from "./api.types";
import Stream, { Connection } from "./streaming";
import { Channels } from "./streaming.types";
import { Acct } from "./acct";
import * as consts from "./consts";
export {
Endpoints,
Stream,
Connection as ChannelConnection,
Channels,
Acct,
};
export { Endpoints, Stream, Connection as ChannelConnection, Channels, Acct };
export const permissions = consts.permissions;
export const notificationTypes = consts.notificationTypes;
@ -21,6 +15,6 @@ export const ffVisibility = consts.ffVisibility;
// api extractor not supported yet
//export * as api from './api';
//export * as entities from './entities';
import * as api from './api';
import * as entities from './entities';
import * as api from "./api";
import * as entities from "./entities";
export { api, entities };

View File

@ -1,17 +1,22 @@
import autobind from 'autobind-decorator';
import { EventEmitter } from 'eventemitter3';
import ReconnectingWebsocket from 'reconnecting-websocket';
import { BroadcastEvents, Channels } from './streaming.types';
import autobind from "autobind-decorator";
import { EventEmitter } from "eventemitter3";
import ReconnectingWebsocket from "reconnecting-websocket";
import { BroadcastEvents, Channels } from "./streaming.types";
export function urlQuery(obj: Record<string, string | number | boolean | undefined>): string {
export function urlQuery(
obj: Record<string, string | number | boolean | undefined>,
): string {
const params = Object.entries(obj)
.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined)
.filter(([, v]) => (Array.isArray(v) ? v.length : v !== undefined))
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.reduce((a, [k, v]) => (a[k] = v!, a), {} as Record<string, string | number | boolean>);
.reduce(
(a, [k, v]) => ((a[k] = v!), a),
{} as Record<string, string | number | boolean>,
);
return Object.entries(params)
.map((e) => `${e[0]}=${encodeURIComponent(e[1])}`)
.join('&');
.join("&");
}
type AnyOf<T extends Record<any, any>> = T[keyof T];
@ -26,17 +31,21 @@ type StreamEvents = {
*/
export default class Stream extends EventEmitter<StreamEvents> {
private stream: ReconnectingWebsocket;
public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing';
public state: "initializing" | "reconnecting" | "connected" = "initializing";
private sharedConnectionPools: Pool[] = [];
private sharedConnections: SharedConnection[] = [];
private nonSharedConnections: NonSharedConnection[] = [];
private idCounter = 0;
constructor(origin: string, user: { token: string; } | null, options?: {
WebSocket?: any;
}) {
constructor(
origin: string,
user: { token: string } | null,
options?: {
WebSocket?: any;
},
) {
super();
options = options || { };
options = options || {};
const query = urlQuery({
i: user?.token,
@ -45,15 +54,21 @@ export default class Stream extends EventEmitter<StreamEvents> {
_t: Date.now(),
});
const wsOrigin = origin.replace('http://', 'ws://').replace('https://', 'wss://');
const wsOrigin = origin
.replace("http://", "ws://")
.replace("https://", "wss://");
this.stream = new ReconnectingWebsocket(`${wsOrigin}/streaming?${query}`, '', {
minReconnectionDelay: 1, // https://github.com/pladaria/reconnecting-websocket/issues/91
WebSocket: options.WebSocket,
});
this.stream.addEventListener('open', this.onOpen);
this.stream.addEventListener('close', this.onClose);
this.stream.addEventListener('message', this.onMessage);
this.stream = new ReconnectingWebsocket(
`${wsOrigin}/streaming?${query}`,
"",
{
minReconnectionDelay: 1, // https://github.com/pladaria/reconnecting-websocket/issues/91
WebSocket: options.WebSocket,
},
);
this.stream.addEventListener("open", this.onOpen);
this.stream.addEventListener("close", this.onClose);
this.stream.addEventListener("message", this.onMessage);
}
@autobind
@ -62,7 +77,11 @@ export default class Stream extends EventEmitter<StreamEvents> {
}
@autobind
public useChannel<C extends keyof Channels>(channel: C, params?: Channels[C]['params'], name?: string): Connection<Channels[C]> {
public useChannel<C extends keyof Channels>(
channel: C,
params?: Channels[C]["params"],
name?: string,
): Connection<Channels[C]> {
if (params) {
return this.connectToChannel(channel, params);
} else {
@ -71,8 +90,11 @@ export default class Stream extends EventEmitter<StreamEvents> {
}
@autobind
private useSharedConnection<C extends keyof Channels>(channel: C, name?: string): SharedConnection<Channels[C]> {
let pool = this.sharedConnectionPools.find(p => p.channel === channel);
private useSharedConnection<C extends keyof Channels>(
channel: C,
name?: string,
): SharedConnection<Channels[C]> {
let pool = this.sharedConnectionPools.find((p) => p.channel === channel);
if (pool == null) {
pool = new Pool(this, channel, this.genId());
@ -86,24 +108,38 @@ export default class Stream extends EventEmitter<StreamEvents> {
@autobind
public removeSharedConnection(connection: SharedConnection): void {
this.sharedConnections = this.sharedConnections.filter(c => c !== connection);
this.sharedConnections = this.sharedConnections.filter(
(c) => c !== connection,
);
}
@autobind
public removeSharedConnectionPool(pool: Pool): void {
this.sharedConnectionPools = this.sharedConnectionPools.filter(p => p !== pool);
this.sharedConnectionPools = this.sharedConnectionPools.filter(
(p) => p !== pool,
);
}
@autobind
private connectToChannel<C extends keyof Channels>(channel: C, params: Channels[C]['params']): NonSharedConnection<Channels[C]> {
const connection = new NonSharedConnection(this, channel, this.genId(), params);
private connectToChannel<C extends keyof Channels>(
channel: C,
params: Channels[C]["params"],
): NonSharedConnection<Channels[C]> {
const connection = new NonSharedConnection(
this,
channel,
this.genId(),
params,
);
this.nonSharedConnections.push(connection);
return connection;
}
@autobind
public disconnectToChannel(connection: NonSharedConnection): void {
this.nonSharedConnections = this.nonSharedConnections.filter(c => c !== connection);
this.nonSharedConnections = this.nonSharedConnections.filter(
(c) => c !== connection,
);
}
/**
@ -111,10 +147,10 @@ export default class Stream extends EventEmitter<StreamEvents> {
*/
@autobind
private onOpen(): void {
const isReconnect = this.state === 'reconnecting';
const isReconnect = this.state === "reconnecting";
this.state = 'connected';
this.emit('_connected_');
this.state = "connected";
this.emit("_connected_");
// チャンネル再接続
if (isReconnect) {
@ -128,9 +164,9 @@ export default class Stream extends EventEmitter<StreamEvents> {
*/
@autobind
private onClose(): void {
if (this.state === 'connected') {
this.state = 'reconnecting';
this.emit('_disconnected_');
if (this.state === "connected") {
this.state = "reconnecting";
this.emit("_disconnected_");
}
}
@ -138,18 +174,18 @@ export default class Stream extends EventEmitter<StreamEvents> {
* Callback of when received a message from connection
*/
@autobind
private onMessage(message: { data: string; }): void {
private onMessage(message: { data: string }): void {
const { type, body } = JSON.parse(message.data);
if (type === 'channel') {
if (type === "channel") {
const id = body.id;
let connections: Connection[];
connections = this.sharedConnections.filter(c => c.id === id);
connections = this.sharedConnections.filter((c) => c.id === id);
if (connections.length === 0) {
const found = this.nonSharedConnections.find(c => c.id === id);
const found = this.nonSharedConnections.find((c) => c.id === id);
if (found) {
connections = [found];
}
@ -169,10 +205,13 @@ export default class Stream extends EventEmitter<StreamEvents> {
*/
@autobind
public send(typeOrPayload: any, payload?: any): void {
const data = payload === undefined ? typeOrPayload : {
type: typeOrPayload,
body: payload,
};
const data =
payload === undefined
? typeOrPayload
: {
type: typeOrPayload,
body: payload,
};
this.stream.send(JSON.stringify(data));
}
@ -201,7 +240,7 @@ class Pool {
this.stream = stream;
this.id = id;
this.stream.on('_disconnected_', this.onStreamDisconnected);
this.stream.on("_disconnected_", this.onStreamDisconnected);
}
@autobind
@ -242,7 +281,7 @@ class Pool {
public connect(): void {
if (this.isConnected) return;
this.isConnected = true;
this.stream.send('connect', {
this.stream.send("connect", {
channel: this.channel,
id: this.id,
});
@ -250,13 +289,15 @@ class Pool {
@autobind
private disconnect(): void {
this.stream.off('_disconnected_', this.onStreamDisconnected);
this.stream.send('disconnect', { id: this.id });
this.stream.off("_disconnected_", this.onStreamDisconnected);
this.stream.send("disconnect", { id: this.id });
this.stream.removeSharedConnectionPool(this);
}
}
export abstract class Connection<Channel extends AnyOf<Channels> = any> extends EventEmitter<Channel['events']> {
export abstract class Connection<
Channel extends AnyOf<Channels> = any,
> extends EventEmitter<Channel["events"]> {
public channel: string;
protected stream: Stream;
public abstract id: string;
@ -274,8 +315,11 @@ export abstract class Connection<Channel extends AnyOf<Channels> = any> extends
}
@autobind
public send<T extends keyof Channel['receives']>(type: T, body: Channel['receives'][T]): void {
this.stream.send('ch', {
public send<T extends keyof Channel["receives"]>(
type: T,
body: Channel["receives"][T],
): void {
this.stream.send("ch", {
id: this.id,
type: type,
body: body,
@ -287,7 +331,9 @@ export abstract class Connection<Channel extends AnyOf<Channels> = any> extends
public abstract dispose(): void;
}
class SharedConnection<Channel extends AnyOf<Channels> = any> extends Connection<Channel> {
class SharedConnection<
Channel extends AnyOf<Channels> = any,
> extends Connection<Channel> {
private pool: Pool;
public get id(): string {
@ -309,11 +355,18 @@ class SharedConnection<Channel extends AnyOf<Channels> = any> extends Connection
}
}
class NonSharedConnection<Channel extends AnyOf<Channels> = any> extends Connection<Channel> {
class NonSharedConnection<
Channel extends AnyOf<Channels> = any,
> extends Connection<Channel> {
public id: string;
protected params: Channel['params'];
protected params: Channel["params"];
constructor(stream: Stream, channel: string, id: string, params: Channel['params']) {
constructor(
stream: Stream,
channel: string,
id: string,
params: Channel["params"],
) {
super(stream, channel);
this.params = params;
@ -324,7 +377,7 @@ class NonSharedConnection<Channel extends AnyOf<Channels> = any> extends Connect
@autobind
public connect(): void {
this.stream.send('connect', {
this.stream.send("connect", {
channel: this.channel,
id: this.id,
params: this.params,
@ -334,7 +387,7 @@ class NonSharedConnection<Channel extends AnyOf<Channels> = any> extends Connect
@autobind
public dispose(): void {
this.removeAllListeners();
this.stream.send('disconnect', { id: this.id });
this.stream.send("disconnect", { id: this.id });
this.stream.disconnectToChannel(this);
}
}

View File

@ -1,4 +1,15 @@
import { Antenna, CustomEmoji, DriveFile, MeDetailed, MessagingMessage, Note, Notification, PageEvent, User, UserGroup } from './entities';
import {
Antenna,
CustomEmoji,
DriveFile,
MeDetailed,
MessagingMessage,
Note,
Notification,
PageEvent,
User,
UserGroup,
} from "./entities";
type FIXME = any;
@ -15,12 +26,12 @@ export type Channels = {
unfollow: (payload: User) => void; // 自分が他人をフォロー解除したとき
meUpdated: (payload: MeDetailed) => void;
pageEvent: (payload: PageEvent) => void;
urlUploadFinished: (payload: { marker: string; file: DriveFile; }) => void;
urlUploadFinished: (payload: { marker: string; file: DriveFile }) => void;
readAllNotifications: () => void;
unreadNotification: (payload: Notification) => void;
unreadMention: (payload: Note['id']) => void;
unreadMention: (payload: Note["id"]) => void;
readAllUnreadMentions: () => void;
unreadSpecifiedNote: (payload: Note['id']) => void;
unreadSpecifiedNote: (payload: Note["id"]) => void;
readAllUnreadSpecifiedNotes: () => void;
readAllMessagingMessages: () => void;
messagingMessage: (payload: MessagingMessage) => void;
@ -29,7 +40,7 @@ export type Channels = {
unreadAntenna: (payload: Antenna) => void;
readAllAnnouncements: () => void;
readAllChannels: () => void;
unreadChannel: (payload: Note['id']) => void;
unreadChannel: (payload: Note["id"]) => void;
myTokenRegenerated: () => void;
reversiNoInvites: () => void;
reversiInvited: (payload: FIXME) => void;
@ -81,18 +92,18 @@ export type Channels = {
};
messaging: {
params: {
otherparty?: User['id'] | null;
group?: UserGroup['id'] | null;
otherparty?: User["id"] | null;
group?: UserGroup["id"] | null;
};
events: {
message: (payload: MessagingMessage) => void;
deleted: (payload: MessagingMessage['id']) => void;
read: (payload: MessagingMessage['id'][]) => void;
deleted: (payload: MessagingMessage["id"]) => void;
read: (payload: MessagingMessage["id"][]) => void;
typers: (payload: User[]) => void;
};
receives: {
read: {
id: MessagingMessage['id'];
id: MessagingMessage["id"];
};
};
};
@ -122,40 +133,45 @@ export type Channels = {
};
};
export type NoteUpdatedEvent = {
id: Note['id'];
type: 'reacted';
body: {
reaction: string;
userId: User['id'];
};
} | {
id: Note['id'];
type: 'unreacted';
body: {
reaction: string;
userId: User['id'];
};
} | {
id: Note['id'];
type: 'deleted';
body: {
deletedAt: string;
};
} | {
id: Note['id'];
type: 'pollVoted';
body: {
choice: number;
userId: User['id'];
};
} | {
id: Note['id'];
type: 'replied';
body: {
id: Note['id'];
};
};
export type NoteUpdatedEvent =
| {
id: Note["id"];
type: "reacted";
body: {
reaction: string;
userId: User["id"];
};
}
| {
id: Note["id"];
type: "unreacted";
body: {
reaction: string;
userId: User["id"];
};
}
| {
id: Note["id"];
type: "deleted";
body: {
deletedAt: string;
};
}
| {
id: Note["id"];
type: "pollVoted";
body: {
choice: number;
userId: User["id"];
};
}
| {
id: Note["id"];
type: "replied";
body: {
id: Note["id"];
};
};
export type BroadcastEvents = {
noteUpdated: (payload: NoteUpdatedEvent) => void;

View File

@ -1,45 +1,48 @@
import { expectType } from 'tsd';
import * as Misskey from '../src';
import { expectType } from "tsd";
import * as Misskey from "../src";
describe('API', () => {
test('success', async () => {
describe("API", () => {
test("success", async () => {
const cli = new Misskey.api.APIClient({
origin: 'https://misskey.test',
credential: 'TOKEN'
origin: "https://misskey.test",
credential: "TOKEN",
});
const res = await cli.request('meta', { detail: true });
const res = await cli.request("meta", { detail: true });
expectType<Misskey.entities.DetailedInstanceMetadata>(res);
});
test('conditional respose type (meta)', async () => {
test("conditional respose type (meta)", async () => {
const cli = new Misskey.api.APIClient({
origin: 'https://misskey.test',
credential: 'TOKEN'
origin: "https://misskey.test",
credential: "TOKEN",
});
const res = await cli.request('meta', { detail: true });
const res = await cli.request("meta", { detail: true });
expectType<Misskey.entities.DetailedInstanceMetadata>(res);
const res2 = await cli.request('meta', { detail: false });
const res2 = await cli.request("meta", { detail: false });
expectType<Misskey.entities.LiteInstanceMetadata>(res2);
const res3 = await cli.request('meta', { });
const res3 = await cli.request("meta", {});
expectType<Misskey.entities.LiteInstanceMetadata>(res3);
const res4 = await cli.request('meta', { detail: true as boolean });
expectType<Misskey.entities.LiteInstanceMetadata | Misskey.entities.DetailedInstanceMetadata>(res4);
const res4 = await cli.request("meta", { detail: true as boolean });
expectType<
| Misskey.entities.LiteInstanceMetadata
| Misskey.entities.DetailedInstanceMetadata
>(res4);
});
test('conditional respose type (users/show)', async () => {
test("conditional respose type (users/show)", async () => {
const cli = new Misskey.api.APIClient({
origin: 'https://misskey.test',
credential: 'TOKEN'
origin: "https://misskey.test",
credential: "TOKEN",
});
const res = await cli.request('users/show', { userId: 'xxxxxxxx' });
const res = await cli.request("users/show", { userId: "xxxxxxxx" });
expectType<Misskey.entities.UserDetailed>(res);
const res2 = await cli.request('users/show', { userIds: ['xxxxxxxx'] });
const res2 = await cli.request("users/show", { userIds: ["xxxxxxxx"] });
expectType<Misskey.entities.UserDetailed[]>(res2);
});
});

View File

@ -1,25 +1,31 @@
import { expectType } from 'tsd';
import * as Misskey from '../src';
import { expectType } from "tsd";
import * as Misskey from "../src";
describe('Streaming', () => {
test('emit type', async () => {
const stream = new Misskey.Stream('https://misskey.test', { token: 'TOKEN' });
const mainChannel = stream.useChannel('main');
mainChannel.on('notification', notification => {
describe("Streaming", () => {
test("emit type", async () => {
const stream = new Misskey.Stream("https://misskey.test", {
token: "TOKEN",
});
const mainChannel = stream.useChannel("main");
mainChannel.on("notification", (notification) => {
expectType<Misskey.entities.Notification>(notification);
});
});
test('params type', async () => {
const stream = new Misskey.Stream('https://misskey.test', { token: 'TOKEN' });
test("params type", async () => {
const stream = new Misskey.Stream("https://misskey.test", {
token: "TOKEN",
});
// TODO: 「stream.useChannel の第二引数として受け入れる型が
// {
// otherparty?: User['id'] | null;
// group?: UserGroup['id'] | null;
// }
// }
// になっている」というテストを行いたいけどtsdでの書き方がわからない
const messagingChannel = stream.useChannel('messaging', { otherparty: 'aaa' });
messagingChannel.on('message', message => {
const messagingChannel = stream.useChannel("messaging", {
otherparty: "aaa",
});
messagingChannel.on("message", (message) => {
expectType<Misskey.entities.MessagingMessage>(message);
});
});

View File

@ -1,28 +1,28 @@
import { APIClient, isAPIError } from '../src/api';
import { enableFetchMocks } from 'jest-fetch-mock';
import { APIClient, isAPIError } from "../src/api";
import { enableFetchMocks } from "jest-fetch-mock";
enableFetchMocks();
function getFetchCall(call: any[]) {
const { body, method } = call[1];
if (body != null && typeof body != 'string') {
throw new Error('invalid body');
if (body != null && typeof body != "string") {
throw new Error("invalid body");
}
return {
url: call[0],
method: method,
body: JSON.parse(body as any)
body: JSON.parse(body as any),
};
}
describe('API', () => {
test('success', async () => {
describe("API", () => {
test("success", async () => {
fetchMock.resetMocks();
fetchMock.mockResponse(async (req) => {
const body = await req.json();
if (req.method == 'POST' && req.url == 'https://misskey.test/api/i') {
if (body.i === 'TOKEN') {
return JSON.stringify({ id: 'foo' });
if (req.method == "POST" && req.url == "https://misskey.test/api/i") {
if (body.i === "TOKEN") {
return JSON.stringify({ id: "foo" });
} else {
return { status: 400 };
}
@ -32,30 +32,33 @@ describe('API', () => {
});
const cli = new APIClient({
origin: 'https://misskey.test',
credential: 'TOKEN',
origin: "https://misskey.test",
credential: "TOKEN",
});
const res = await cli.request('i');
const res = await cli.request("i");
expect(res).toEqual({
id: 'foo'
id: "foo",
});
expect(getFetchCall(fetchMock.mock.calls[0])).toEqual({
url: 'https://misskey.test/api/i',
method: 'POST',
body: { i: 'TOKEN' }
url: "https://misskey.test/api/i",
method: "POST",
body: { i: "TOKEN" },
});
});
test('with params', async () => {
test("with params", async () => {
fetchMock.resetMocks();
fetchMock.mockResponse(async (req) => {
const body = await req.json();
if (req.method == 'POST' && req.url == 'https://misskey.test/api/notes/show') {
if (body.i === 'TOKEN' && body.noteId === 'aaaaa') {
return JSON.stringify({ id: 'foo' });
if (
req.method == "POST" &&
req.url == "https://misskey.test/api/notes/show"
) {
if (body.i === "TOKEN" && body.noteId === "aaaaa") {
return JSON.stringify({ id: "foo" });
} else {
return { status: 400 };
}
@ -65,27 +68,30 @@ describe('API', () => {
});
const cli = new APIClient({
origin: 'https://misskey.test',
credential: 'TOKEN',
origin: "https://misskey.test",
credential: "TOKEN",
});
const res = await cli.request('notes/show', { noteId: 'aaaaa' });
const res = await cli.request("notes/show", { noteId: "aaaaa" });
expect(res).toEqual({
id: 'foo'
id: "foo",
});
expect(getFetchCall(fetchMock.mock.calls[0])).toEqual({
url: 'https://misskey.test/api/notes/show',
method: 'POST',
body: { i: 'TOKEN', noteId: 'aaaaa' }
url: "https://misskey.test/api/notes/show",
method: "POST",
body: { i: "TOKEN", noteId: "aaaaa" },
});
});
test('204 No Content で null が返る', async () => {
test("204 No Content で null が返る", async () => {
fetchMock.resetMocks();
fetchMock.mockResponse(async (req) => {
if (req.method == 'POST' && req.url == 'https://misskey.test/api/reset-password') {
if (
req.method == "POST" &&
req.url == "https://misskey.test/api/reset-password"
) {
return { status: 204 };
} else {
return { status: 404 };
@ -93,38 +99,41 @@ describe('API', () => {
});
const cli = new APIClient({
origin: 'https://misskey.test',
credential: 'TOKEN',
origin: "https://misskey.test",
credential: "TOKEN",
});
const res = await cli.request('reset-password', { token: 'aaa', password: 'aaa' });
const res = await cli.request("reset-password", {
token: "aaa",
password: "aaa",
});
expect(res).toEqual(null);
expect(getFetchCall(fetchMock.mock.calls[0])).toEqual({
url: 'https://misskey.test/api/reset-password',
method: 'POST',
body: { i: 'TOKEN', token: 'aaa', password: 'aaa' }
url: "https://misskey.test/api/reset-password",
method: "POST",
body: { i: "TOKEN", token: "aaa", password: "aaa" },
});
});
test('インスタンスの credential が指定されていても引数で credential が null ならば null としてリクエストされる', async () => {
test("インスタンスの credential が指定されていても引数で credential が null ならば null としてリクエストされる", async () => {
fetchMock.resetMocks();
fetchMock.mockResponse(async (req) => {
const body = await req.json();
if (req.method == 'POST' && req.url == 'https://misskey.test/api/i') {
if (typeof body.i === 'string') {
return JSON.stringify({ id: 'foo' });
if (req.method == "POST" && req.url == "https://misskey.test/api/i") {
if (typeof body.i === "string") {
return JSON.stringify({ id: "foo" });
} else {
return {
status: 401,
body: JSON.stringify({
error: {
message: 'Credential required.',
code: 'CREDENTIAL_REQUIRED',
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
}
})
message: "Credential required.",
code: "CREDENTIAL_REQUIRED",
id: "1384574d-a912-4b81-8601-c7b1c4085df1",
},
}),
};
}
} else {
@ -134,77 +143,78 @@ describe('API', () => {
try {
const cli = new APIClient({
origin: 'https://misskey.test',
credential: 'TOKEN',
origin: "https://misskey.test",
credential: "TOKEN",
});
await cli.request('i', {}, null);
await cli.request("i", {}, null);
} catch (e) {
expect(isAPIError(e)).toEqual(true);
}
});
test('api error', async () => {
test("api error", async () => {
fetchMock.resetMocks();
fetchMock.mockResponse(async (req) => {
return {
status: 500,
body: JSON.stringify({
error: {
message: 'Internal error occurred. Please contact us if the error persists.',
code: 'INTERNAL_ERROR',
id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac',
kind: 'server',
message:
"Internal error occurred. Please contact us if the error persists.",
code: "INTERNAL_ERROR",
id: "5d37dbcb-891e-41ca-a3d6-e690c97775ac",
kind: "server",
},
})
}),
};
});
try {
const cli = new APIClient({
origin: 'https://misskey.test',
credential: 'TOKEN',
origin: "https://misskey.test",
credential: "TOKEN",
});
await cli.request('i');
await cli.request("i");
} catch (e: any) {
expect(isAPIError(e)).toEqual(true);
expect(e.id).toEqual('5d37dbcb-891e-41ca-a3d6-e690c97775ac');
expect(e.id).toEqual("5d37dbcb-891e-41ca-a3d6-e690c97775ac");
}
});
test('network error', async () => {
test("network error", async () => {
fetchMock.resetMocks();
fetchMock.mockAbort();
try {
const cli = new APIClient({
origin: 'https://misskey.test',
credential: 'TOKEN',
origin: "https://misskey.test",
credential: "TOKEN",
});
await cli.request('i');
await cli.request("i");
} catch (e) {
expect(isAPIError(e)).toEqual(false);
}
});
test('json parse error', async () => {
test("json parse error", async () => {
fetchMock.resetMocks();
fetchMock.mockResponse(async (req) => {
return {
status: 500,
body: '<html>I AM NOT JSON</html>'
body: "<html>I AM NOT JSON</html>",
};
});
try {
const cli = new APIClient({
origin: 'https://misskey.test',
credential: 'TOKEN',
origin: "https://misskey.test",
credential: "TOKEN",
});
await cli.request('i');
await cli.request("i");
} catch (e) {
expect(isAPIError(e)).toEqual(false);
}

View File

@ -1,95 +1,105 @@
import WS from 'jest-websocket-mock';
import Stream from '../src/streaming';
import WS from "jest-websocket-mock";
import Stream from "../src/streaming";
describe('Streaming', () => {
test('useChannel', async () => {
const server = new WS('wss://misskey.test/streaming');
const stream = new Stream('https://misskey.test', { token: 'TOKEN' });
describe("Streaming", () => {
test("useChannel", async () => {
const server = new WS("wss://misskey.test/streaming");
const stream = new Stream("https://misskey.test", { token: "TOKEN" });
const mainChannelReceived: any[] = [];
const main = stream.useChannel('main');
main.on('meUpdated', payload => {
const main = stream.useChannel("main");
main.on("meUpdated", (payload) => {
mainChannelReceived.push(payload);
});
const ws = await server.connected;
expect(new URLSearchParams(new URL(ws.url).search).get('i')).toEqual('TOKEN');
expect(new URLSearchParams(new URL(ws.url).search).get("i")).toEqual(
"TOKEN",
);
const msg = JSON.parse(await server.nextMessage as string);
const msg = JSON.parse((await server.nextMessage) as string);
const mainChannelId = msg.body.id;
expect(msg.type).toEqual('connect');
expect(msg.body.channel).toEqual('main');
expect(msg.type).toEqual("connect");
expect(msg.body.channel).toEqual("main");
expect(mainChannelId != null).toEqual(true);
server.send(JSON.stringify({
type: 'channel',
body: {
id: mainChannelId,
type: 'meUpdated',
server.send(
JSON.stringify({
type: "channel",
body: {
id: 'foo'
}
}
}));
id: mainChannelId,
type: "meUpdated",
body: {
id: "foo",
},
},
}),
);
expect(mainChannelReceived[0]).toEqual({
id: 'foo'
id: "foo",
});
stream.close();
server.close();
});
test('useChannel with parameters', async () => {
const server = new WS('wss://misskey.test/streaming');
const stream = new Stream('https://misskey.test', { token: 'TOKEN' });
test("useChannel with parameters", async () => {
const server = new WS("wss://misskey.test/streaming");
const stream = new Stream("https://misskey.test", { token: "TOKEN" });
const messagingChannelReceived: any[] = [];
const messaging = stream.useChannel('messaging', { otherparty: 'aaa' });
messaging.on('message', payload => {
const messaging = stream.useChannel("messaging", { otherparty: "aaa" });
messaging.on("message", (payload) => {
messagingChannelReceived.push(payload);
});
const ws = await server.connected;
expect(new URLSearchParams(new URL(ws.url).search).get('i')).toEqual('TOKEN');
expect(new URLSearchParams(new URL(ws.url).search).get("i")).toEqual(
"TOKEN",
);
const msg = JSON.parse(await server.nextMessage as string);
const msg = JSON.parse((await server.nextMessage) as string);
const messagingChannelId = msg.body.id;
expect(msg.type).toEqual('connect');
expect(msg.body.channel).toEqual('messaging');
expect(msg.body.params).toEqual({ otherparty: 'aaa' });
expect(msg.type).toEqual("connect");
expect(msg.body.channel).toEqual("messaging");
expect(msg.body.params).toEqual({ otherparty: "aaa" });
expect(messagingChannelId != null).toEqual(true);
server.send(JSON.stringify({
type: 'channel',
body: {
id: messagingChannelId,
type: 'message',
server.send(
JSON.stringify({
type: "channel",
body: {
id: 'foo'
}
}
}));
id: messagingChannelId,
type: "message",
body: {
id: "foo",
},
},
}),
);
expect(messagingChannelReceived[0]).toEqual({
id: 'foo'
id: "foo",
});
stream.close();
server.close();
});
test('ちゃんとチャンネルごとにidが異なる', async () => {
const server = new WS('wss://misskey.test/streaming');
const stream = new Stream('https://misskey.test', { token: 'TOKEN' });
test("ちゃんとチャンネルごとにidが異なる", async () => {
const server = new WS("wss://misskey.test/streaming");
const stream = new Stream("https://misskey.test", { token: "TOKEN" });
stream.useChannel('messaging', { otherparty: 'aaa' });
stream.useChannel('messaging', { otherparty: 'bbb' });
stream.useChannel("messaging", { otherparty: "aaa" });
stream.useChannel("messaging", { otherparty: "bbb" });
const ws = await server.connected;
expect(new URLSearchParams(new URL(ws.url).search).get('i')).toEqual('TOKEN');
expect(new URLSearchParams(new URL(ws.url).search).get("i")).toEqual(
"TOKEN",
);
const msg = JSON.parse(await server.nextMessage as string);
const msg = JSON.parse((await server.nextMessage) as string);
const messagingChannelId = msg.body.id;
const msg2 = JSON.parse(await server.nextMessage as string);
const msg2 = JSON.parse((await server.nextMessage) as string);
const messagingChannelId2 = msg2.body.id;
expect(messagingChannelId != null).toEqual(true);
@ -100,58 +110,64 @@ describe('Streaming', () => {
server.close();
});
test('Connection#send', async () => {
const server = new WS('wss://misskey.test/streaming');
const stream = new Stream('https://misskey.test', { token: 'TOKEN' });
test("Connection#send", async () => {
const server = new WS("wss://misskey.test/streaming");
const stream = new Stream("https://misskey.test", { token: "TOKEN" });
const messaging = stream.useChannel('messaging', { otherparty: 'aaa' });
messaging.send('read', { id: 'aaa' });
const messaging = stream.useChannel("messaging", { otherparty: "aaa" });
messaging.send("read", { id: "aaa" });
const ws = await server.connected;
expect(new URLSearchParams(new URL(ws.url).search).get('i')).toEqual('TOKEN');
expect(new URLSearchParams(new URL(ws.url).search).get("i")).toEqual(
"TOKEN",
);
const connectMsg = JSON.parse(await server.nextMessage as string);
const connectMsg = JSON.parse((await server.nextMessage) as string);
const channelId = connectMsg.body.id;
const msg = JSON.parse(await server.nextMessage as string);
const msg = JSON.parse((await server.nextMessage) as string);
expect(msg.type).toEqual('ch');
expect(msg.type).toEqual("ch");
expect(msg.body.id).toEqual(channelId);
expect(msg.body.type).toEqual('read');
expect(msg.body.body).toEqual({ id: 'aaa' });
expect(msg.body.type).toEqual("read");
expect(msg.body.body).toEqual({ id: "aaa" });
stream.close();
server.close();
});
test('Connection#dispose', async () => {
const server = new WS('wss://misskey.test/streaming');
const stream = new Stream('https://misskey.test', { token: 'TOKEN' });
test("Connection#dispose", async () => {
const server = new WS("wss://misskey.test/streaming");
const stream = new Stream("https://misskey.test", { token: "TOKEN" });
const mainChannelReceived: any[] = [];
const main = stream.useChannel('main');
main.on('meUpdated', payload => {
const main = stream.useChannel("main");
main.on("meUpdated", (payload) => {
mainChannelReceived.push(payload);
});
const ws = await server.connected;
expect(new URLSearchParams(new URL(ws.url).search).get('i')).toEqual('TOKEN');
const msg = JSON.parse(await server.nextMessage as string);
const ws = await server.connected;
expect(new URLSearchParams(new URL(ws.url).search).get("i")).toEqual(
"TOKEN",
);
const msg = JSON.parse((await server.nextMessage) as string);
const mainChannelId = msg.body.id;
expect(msg.type).toEqual('connect');
expect(msg.body.channel).toEqual('main');
expect(msg.type).toEqual("connect");
expect(msg.body.channel).toEqual("main");
expect(mainChannelId != null).toEqual(true);
main.dispose();
server.send(JSON.stringify({
type: 'channel',
body: {
id: mainChannelId,
type: 'meUpdated',
server.send(
JSON.stringify({
type: "channel",
body: {
id: 'foo'
}
}
}));
id: mainChannelId,
type: "meUpdated",
body: {
id: "foo",
},
},
}),
);
expect(mainChannelReceived.length).toEqual(0);

View File

@ -1,7 +1,7 @@
import { markRaw, ref } from "vue";
import { Storage } from "./pizzax";
import { Theme } from "./scripts/theme";
import { deviceKind } from '@/scripts/device-kind';
import { deviceKind } from "@/scripts/device-kind";
export const postFormActions = [];
export const userActions = [];
@ -16,10 +16,10 @@ const menuOptions = [
"explore",
"favorites",
"channels",
"search"
"search",
];
if (deviceKind === 'desktop') menuOptions.push("ui");
if (deviceKind === "desktop") menuOptions.push("ui");
// TODO: それぞれいちいちwhereとかdefaultというキーを付けなきゃいけないの冗長なのでなんとかする(ただ型定義が面倒になりそう)
// あと、現行の定義の仕方なら「whereが何であるかに関わらずキー名の重複不可」という制約を付けられるメリットもあるからそのメリットを引き継ぐ方法も考えないといけない