swパッケージに

This commit is contained in:
tamaina 2021-11-20 14:44:59 +09:00
parent 8db1585f79
commit 6c2a27756c
30 changed files with 429 additions and 20 deletions

View file

@ -25,6 +25,7 @@ const _dirname = dirname(_filename);
const staticAssets = `${_dirname}/../../../assets/`; const staticAssets = `${_dirname}/../../../assets/`;
const clientAssets = `${_dirname}/../../../../client/assets/`; const clientAssets = `${_dirname}/../../../../client/assets/`;
const assets = `${_dirname}/../../../../../built/_client_dist_/`; const assets = `${_dirname}/../../../../../built/_client_dist_/`;
const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`;
// Init app // Init app
const app = new Koa(); const app = new Koa();
@ -100,7 +101,7 @@ router.get('/twemoji/(.*)', async ctx => {
// ServiceWorker // ServiceWorker
router.get('/sw.js', async ctx => { router.get('/sw.js', async ctx => {
await send(ctx as any, `/sw.${config.version}.js`, { await send(ctx as any, `/sw.${config.version}.js`, {
root: assets root: swAssets
}); });
}); });

View file

@ -1,11 +1,11 @@
import * as misskey from 'misskey-js'; import * as misskey from 'misskey-js';
import { I18n } from '@/scripts/i18n'; import { i18n } from '@/i18n';
/** /**
* 稿 * 稿
* @param {*} note (packされた)稿 * @param {*} note (packされた)稿
*/ */
export const getNoteSummary = (note: misskey.entities.Note, i18n: I18n<any>): string => { export const getNoteSummary = (note: misskey.entities.Note): string => {
if (note.deletedAt) { if (note.deletedAt) {
return `(${i18n.locale.deletedNote})`; return `(${i18n.locale.deletedNote})`;
} }
@ -36,7 +36,7 @@ export const getNoteSummary = (note: misskey.entities.Note, i18n: I18n<any>): st
// 返信のとき // 返信のとき
if (note.replyId) { if (note.replyId) {
if (note.reply) { if (note.reply) {
summary += `\n\nRE: ${getNoteSummary(note.reply, i18n)}`; summary += `\n\nRE: ${getNoteSummary(note.reply)}`;
} else { } else {
summary += '\n\nRE: ...'; summary += '\n\nRE: ...';
} }
@ -45,7 +45,7 @@ export const getNoteSummary = (note: misskey.entities.Note, i18n: I18n<any>): st
// Renoteのとき // Renoteのとき
if (note.renoteId) { if (note.renoteId) {
if (note.renote) { if (note.renote) {
summary += `\n\nRN: ${getNoteSummary(note.renote, i18n)}`; summary += `\n\nRN: ${getNoteSummary(note.renote)}`;
} else { } else {
summary += '\n\nRN: ...'; summary += '\n\nRN: ...';
} }

View file

@ -8,7 +8,7 @@ export async function initializeSw() {
('serviceWorker' in navigator) && ('serviceWorker' in navigator) &&
('PushManager' in window) && ('PushManager' in window) &&
$i && $i.token) { $i && $i.token) {
navigator.serviceWorker.register(`/sw.js`); navigator.serviceWorker.register(`/sw.js`, { scope: '/', type: 'module' });
navigator.serviceWorker.ready.then(registration => { navigator.serviceWorker.ready.then(registration => {
registration.active?.postMessage({ registration.active?.postMessage({

View file

@ -28,8 +28,7 @@
], ],
"lib": [ "lib": [
"esnext", "esnext",
"dom", "dom"
"webworker"
] ]
}, },
"compileOnSave": false, "compileOnSave": false,

View file

@ -37,7 +37,6 @@ const postcss = {
module.exports = { module.exports = {
entry: { entry: {
app: './src/init.ts', app: './src/init.ts',
sw: './src/sw/sw.ts'
}, },
module: { module: {
rules: [{ rules: [{

61
packages/sw/.eslintrc.js Normal file
View file

@ -0,0 +1,61 @@
module.exports = {
root: true,
env: {
"node": false
},
parser: "vue-eslint-parser",
parserOptions: {
"parser": "@typescript-eslint/parser",
tsconfigRootDir: __dirname,
//project: ['./tsconfig.json'],
},
extends: [
//"../shared/.eslintrc.js",
"plugin:vue/vue3-recommended"
],
rules: {
"vue/attributes-order": ["error", {
"alphabetical": false
}],
"vue/no-use-v-if-with-v-for": ["error", {
"allowUsingIterationVar": false
}],
"vue/no-ref-as-operand": "error",
"vue/no-multi-spaces": ["error", {
"ignoreProperties": false
}],
"vue/no-v-html": "error",
"vue/order-in-components": "error",
"vue/html-indent": ["warn", "tab", {
"attribute": 1,
"baseIndent": 0,
"closeBracket": 0,
"alignAttributesVertically": true,
"ignores": []
}],
"vue/html-closing-bracket-spacing": ["warn", {
"startTag": "never",
"endTag": "never",
"selfClosingTag": "never"
}],
"vue/multi-word-component-names": "warn",
"vue/require-v-for-key": "warn",
"vue/no-unused-components": "warn",
"vue/valid-v-for": "warn",
"vue/return-in-computed-property": "warn",
"vue/max-attributes-per-line": "off",
"vue/html-self-closing": "off",
"vue/singleline-html-element-content-newline": "off",
},
globals: {
"require": false,
"_DEV_": false,
"_LANGS_": false,
"_VERSION_": false,
"_ENV_": false,
"_PERF_PREFIX_": false,
"_DATA_TRANSFER_DRIVE_FILE_": false,
"_DATA_TRANSFER_DRIVE_FOLDER_": false,
"_DATA_TRANSFER_DECK_COLUMN_": false
}
}

2
packages/sw/.npmrc Normal file
View file

@ -0,0 +1,2 @@
save-exact = true
package-lock = false

1
packages/sw/.yarnrc Normal file
View file

@ -0,0 +1 @@
network-timeout 600000

14
packages/sw/package.json Normal file
View file

@ -0,0 +1,14 @@
{
"private": true,
"scripts": {
"watch": "webpack --watch",
"build": "webpack",
"lint": "eslint --quiet src/**/*.{ts}"
},
"resolutions": {},
"dependencies": {
"idb-keyval": "6.0.3",
"misskey-js": "0.0.10"
},
"devDependencies": {}
}

View file

@ -3,12 +3,12 @@
*/ */
declare var self: ServiceWorkerGlobalScope; declare var self: ServiceWorkerGlobalScope;
import { swLang } from '@/lang';
import { cli } from '@/operations';
import { pushNotificationDataMap } from '@/types';
import { getNoteSummary } from '@/scripts/get-note-summary'; import { getNoteSummary } from '@/scripts/get-note-summary';
import getUserName from '@/scripts/get-user-name'; import getUserName from '@/scripts/get-user-name';
import { swLang } from '@/sw/lang';
import { I18n } from '@/scripts/i18n'; import { I18n } from '@/scripts/i18n';
import { pushNotificationDataMap } from '@/sw/types';
import { cli } from './operations';
import { getAccountFromId } from '@/scripts/get-account-from-id'; import { getAccountFromId } from '@/scripts/get-account-from-id';
export async function createNotification<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]) { export async function createNotification<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]) {

View file

@ -0,0 +1,14 @@
import * as misskey from 'misskey-js';
import * as Acct from 'misskey-js/built/acct';
export const acct = (user: misskey.Acct) => {
return Acct.toString(user);
};
export const userName = (user: misskey.entities.User) => {
return user.name || user.username;
};
export const userPage = (user: misskey.Acct, path?, absolute = false) => {
return `${absolute ? origin : ''}/@${acct(user)}${(path ? `/${path}` : '')}`;
};

View file

@ -1,8 +1,8 @@
declare var self: ServiceWorkerGlobalScope; declare var self: ServiceWorkerGlobalScope;
import { get } from 'idb-keyval'; import { get } from 'idb-keyval';
import { pushNotificationDataMap } from '@/sw/types'; import { pushNotificationDataMap } from '@/types';
import { api } from '@/sw/operations'; import { api } from '@/operations';
type Accounts = { type Accounts = {
[x: string]: { [x: string]: {

View file

@ -5,7 +5,7 @@
declare var self: ServiceWorkerGlobalScope; declare var self: ServiceWorkerGlobalScope;
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { SwMessage, swMessageOrderType } from '@/sw/types'; import { SwMessage, swMessageOrderType } from '@/types';
import { acct as getAcct } from '@/filters/user'; import { acct as getAcct } from '@/filters/user';
import { getAccountFromId } from '@/scripts/get-account-from-id'; import { getAccountFromId } from '@/scripts/get-account-from-id';
import { getUrlWithLoginId } from '@/scripts/login-id'; import { getUrlWithLoginId } from '@/scripts/login-id';

View file

@ -0,0 +1,7 @@
import { get } from 'idb-keyval';
export async function getAccountFromId(id: string) {
const accounts = await get('accounts') as { token: string; id: string; }[];
if (!accounts) console.log('Accounts are not recorded');
return accounts.find(e => e.id === id);
}

View file

@ -0,0 +1,55 @@
import * as misskey from 'misskey-js';
import { I18n } from '@/scripts/i18n';
/**
* 稿
* @param {*} note (packされた)稿
*/
export const getNoteSummary = (note: misskey.entities.Note, i18n: I18n<any>): string => {
if (note.deletedAt) {
return `(${i18n.locale.deletedNote})`;
}
if (note.isHidden) {
return `(${i18n.locale.invisibleNote})`;
}
let summary = '';
// 本文
if (note.cw != null) {
summary += note.cw;
} else {
summary += note.text ? note.text : '';
}
// ファイルが添付されているとき
if ((note.files || []).length != 0) {
summary += ` (${i18n.t('withNFiles', { n: note.files.length })})`;
}
// 投票が添付されているとき
if (note.poll) {
summary += ` (${i18n.locale.poll})`;
}
// 返信のとき
if (note.replyId) {
if (note.reply) {
summary += `\n\nRE: ${getNoteSummary(note.reply, i18n)}`;
} else {
summary += '\n\nRE: ...';
}
}
// Renoteのとき
if (note.renoteId) {
if (note.renote) {
summary += `\n\nRN: ${getNoteSummary(note.renote, i18n)}`;
} else {
summary += '\n\nRN: ...';
}
}
return summary.trim();
};

View file

@ -0,0 +1,3 @@
export default function(user: { name?: string | null, username: string }): string {
return user.name || user.username;
}

View file

@ -0,0 +1,33 @@
export class I18n<T extends Record<string, any>> {
public locale: T;
constructor(locale: T) {
this.locale = locale;
//#region BIND
this.t = this.t.bind(this);
//#endregion
}
// string にしているのは、ドット区切りでのパス指定を許可するため
// なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
public t(key: string, args?: Record<string, any>): string {
try {
let str = key.split('.').reduce((o, i) => o[i], this.locale as T | any | string);
if (typeof str !== 'string') {
return key;
}
if (args) {
for (const [k, v] of Object.entries(args)) {
str = str.replace(`{${k}}`, v);
}
}
return str;
} catch (e) {
console.warn(`missing localization '${key}'`);
return key;
}
}
}

View file

@ -0,0 +1,11 @@
export function getUrlWithLoginId(url: string, loginId: string) {
const u = new URL(url, origin);
u.searchParams.append('loginId', loginId);
return u.toString();
}
export function getUrlWithoutLoginId(url: string) {
const u = new URL(url);
u.searchParams.delete('loginId');
return u.toString();
}

View file

@ -3,11 +3,11 @@
*/ */
declare var self: ServiceWorkerGlobalScope; declare var self: ServiceWorkerGlobalScope;
import { createEmptyNotification, createNotification } from '@/sw/create-notification'; import { createEmptyNotification, createNotification } from '@/create-notification';
import { swLang } from '@/sw/lang'; import { swLang } from '@/lang';
import { swNotificationRead } from '@/sw/notification-read'; import { swNotificationRead } from '@/notification-read';
import { pushNotificationDataMap } from '@/sw/types'; import { pushNotificationDataMap } from '@/types';
import * as swos from './operations'; import * as swos from '@/operations';
import { acct as getAcct } from '@/filters/user'; import { acct as getAcct } from '@/filters/user';
//#region Lifecycle: Install //#region Lifecycle: Install

39
packages/sw/tsconfig.json Normal file
View file

@ -0,0 +1,39 @@
{
"compilerOptions": {
"allowJs": true,
"noEmitOnError": false,
"noImplicitAny": false,
"noImplicitReturns": true,
"noUnusedParameters": false,
"noUnusedLocals": true,
"noFallthroughCasesInSwitch": true,
"declaration": false,
"sourceMap": false,
"target": "es2017",
"module": "esnext",
"moduleResolution": "node",
"removeComments": false,
"noLib": false,
"strict": true,
"strictNullChecks": true,
"experimentalDecorators": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
},
"typeRoots": [
"node_modules/@types",
"@types",
],
"lib": [
"esnext",
"webworker"
]
},
"compileOnSave": false,
"include": [
"./**/*.ts",
"./**/*.vue"
]
}

View file

@ -0,0 +1,98 @@
/**
* webpack configuration
*/
const fs = require('fs');
const webpack = require('webpack');
const { VueLoaderPlugin } = require('vue-loader');
class WebpackOnBuildPlugin {
constructor(callback) {
this.callback = callback;
}
apply(compiler) {
compiler.hooks.done.tap('WebpackOnBuildPlugin', this.callback);
}
}
const isProduction = process.env.NODE_ENV === 'production';
const locales = require('../../locales');
const meta = require('../../package.json');
const postcss = {
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('cssnano')({
preset: 'default'
})
]
}
},
};
module.exports = {
entry: {
sw: './src/sw.ts'
},
module: {
rules: [{
test: /\.ts$/,
exclude: /node_modules/,
use: [{
loader: 'ts-loader',
options: {
happyPackMode: true,
transpileOnly: true,
configFile: __dirname + '/tsconfig.json',
appendTsSuffixTo: [/\.vue$/]
}
}]
}]
},
plugins: [
new webpack.ProgressPlugin({}),
new webpack.DefinePlugin({
_VERSION_: JSON.stringify(meta.version),
_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])),
_ENV_: JSON.stringify(process.env.NODE_ENV),
_DEV_: process.env.NODE_ENV !== 'production',
_PERF_PREFIX_: JSON.stringify('Misskey:'),
_DATA_TRANSFER_DRIVE_FILE_: JSON.stringify('mk_drive_file'),
_DATA_TRANSFER_DRIVE_FOLDER_: JSON.stringify('mk_drive_folder'),
_DATA_TRANSFER_DECK_COLUMN_: JSON.stringify('mk_deck_column'),
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
}),
new VueLoaderPlugin(),
new WebpackOnBuildPlugin(() => {
fs.mkdirSync(__dirname + '/../../built', { recursive: true });
fs.writeFileSync(__dirname + '/../../built/meta.json', JSON.stringify({ version: meta.version }), 'utf-8');
}),
],
output: {
path: __dirname + '/../../built/_sw_dist_',
filename: `[name].${meta.version}.js`,
publicPath: `/`,
pathinfo: false,
},
resolve: {
extensions: [
'.js', '.ts', '.json'
],
alias: {
'@': __dirname + '/src/',
}
},
resolveLoader: {
modules: ['node_modules']
},
experiments: {
topLevelAwait: true
},
devtool: false, //'source-map',
mode: isProduction ? 'production' : 'development'
};

39
packages/sw/yarn.lock Normal file
View file

@ -0,0 +1,39 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
autobind-decorator@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-2.4.0.tgz#ea9e1c98708cf3b5b356f7cf9f10f265ff18239c"
integrity sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==
eventemitter3@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
idb-keyval@6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.0.3.tgz#e47246a15e55d0fff9fa204fd9ca06f90ff30c52"
integrity sha512-yh8V7CnE6EQMu9YDwQXhRxwZh4nv+8xm/HV4ZqK4IiYFJBWYGjJuykADJbSP+F/GDXUBwCSSNn/14IpGL81TuA==
dependencies:
safari-14-idb-fix "^3.0.0"
misskey-js@0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.10.tgz#f305dd37cecfbaeb7a277d5e0c769ca12c6eb9a6"
integrity sha512-2rdqFrCOwggMKYitsUPyupesqCNpNooqEHQQRfdjttbhiqLbNFJE1UuWQ04ffmiJ08UJt+1ZN2kVAYNEN3HRsg==
dependencies:
autobind-decorator "^2.4.0"
eventemitter3 "^4.0.7"
reconnecting-websocket "^4.4.0"
reconnecting-websocket@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783"
integrity sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==
safari-14-idb-fix@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz#450fc049b996ec7f3fd9ca2f89d32e0761583440"
integrity sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog==

View file

@ -17,6 +17,14 @@ const execa = require('execa');
stderr: process.stderr, stderr: process.stderr,
}); });
console.log('building packages/sw ...');
await execa('npm', ['run', 'build'], {
cwd: __dirname + '/../packages/sw',
stdout: process.stdout,
stderr: process.stderr,
});
console.log('build finishing ...'); console.log('build finishing ...');
await execa('npm', ['run', 'gulp'], { await execa('npm', ['run', 'gulp'], {

View file

@ -7,6 +7,9 @@ const fs = require('fs');
fs.rmSync(__dirname + '/../packages/client/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/client/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/client/node_modules', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/client/node_modules', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/sw/node_modules', { recursive: true, force: true });
fs.rmSync(__dirname + '/../built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../node_modules', { recursive: true, force: true }); fs.rmSync(__dirname + '/../node_modules', { recursive: true, force: true });
})(); })();

View file

@ -3,5 +3,6 @@ const fs = require('fs');
(async () => { (async () => {
fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/client/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/client/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
fs.rmSync(__dirname + '/../built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../built', { recursive: true, force: true });
})(); })();

View file

@ -19,6 +19,12 @@ const execa = require('execa');
stderr: process.stderr, stderr: process.stderr,
}); });
execa('npm', ['run', 'watch'], {
cwd: __dirname + '/../packages/sw',
stdout: process.stdout,
stderr: process.stderr,
});
const start = async () => { const start = async () => {
try { try {
await execa('npm', ['run', 'start'], { await execa('npm', ['run', 'start'], {

View file

@ -16,4 +16,12 @@ const execa = require('execa');
stdout: process.stdout, stdout: process.stdout,
stderr: process.stderr, stderr: process.stderr,
}); });
console.log('installing dependencies of packages/sw ...');
await execa('yarn', ['install'], {
cwd: __dirname + '/../packages/sw',
stdout: process.stdout,
stderr: process.stderr,
});
})(); })();

View file

@ -14,4 +14,11 @@ const execa = require('execa');
stdout: process.stdout, stdout: process.stdout,
stderr: process.stderr, stderr: process.stderr,
}); });
console.log('linting packages/sw ...');
await execa('npm', ['run', 'lint'], {
cwd: __dirname + '/../packages/sw',
stdout: process.stdout,
stderr: process.stderr,
});
})(); })();