3.0.7 - electron-builder removed
This commit is contained in:
parent
4d8f6c3670
commit
7151bf916e
|
@ -124,7 +124,7 @@
|
|||
core.register('conversations', Conversations());
|
||||
core.connection.onEvent('closed', async(isReconnect) => {
|
||||
if(isReconnect) (<Modal>this.$refs['reconnecting']).show(true);
|
||||
if(this.connected) await core.notifications.playSound('logout');
|
||||
if(this.connected) core.notifications.playSound('logout');
|
||||
this.connected = false;
|
||||
this.connecting = false;
|
||||
document.title = l('title');
|
||||
|
@ -138,7 +138,7 @@
|
|||
this.error = '';
|
||||
this.connecting = false;
|
||||
this.connected = true;
|
||||
await core.notifications.playSound('login');
|
||||
core.notifications.playSound('login');
|
||||
document.title = l('title.connected', core.connection.character);
|
||||
});
|
||||
core.watch(() => core.conversations.hasNew, (hasNew) => {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
@touchend="$refs['userMenu'].handleEvent($event)">
|
||||
<sidebar id="sidebar" :label="l('chat.menu')" icon="fa-bars">
|
||||
<img :src="characterImage(ownCharacter.name)" v-if="showAvatars" style="float:left;margin-right:5px;width:60px"/>
|
||||
<a href="#" target="_blank" :href="ownCharacterLink" class="btn" style="margin-right:5px">{{ownCharacter.name}}</a>
|
||||
<a target="_blank" :href="ownCharacterLink" class="btn" style="margin-right:5px">{{ownCharacter.name}}</a>
|
||||
<a href="#" @click.prevent="logOut" class="btn"><i class="fas fa-sign-out-alt"></i>{{l('chat.logout')}}</a><br/>
|
||||
<div>
|
||||
{{l('chat.status')}}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<span v-show="conversation.channel.id.substr(0, 4) !== 'adh-'" class="fa fa-star" :title="l('channel.official')"
|
||||
style="margin-right:5px;vertical-align:sub"></span>
|
||||
<h5 style="margin:0;display:inline;vertical-align:middle">{{conversation.name}}</h5>
|
||||
<a @click="descriptionExpanded = !descriptionExpanded" class="btn">
|
||||
<a href="#" @click.prevent="descriptionExpanded = !descriptionExpanded" class="btn">
|
||||
<span class="fa" :class="{'fa-chevron-down': !descriptionExpanded, 'fa-chevron-up': descriptionExpanded}"></span>
|
||||
<span class="btn-text">{{l('channel.description')}}</span>
|
||||
</a>
|
||||
|
@ -69,13 +69,13 @@
|
|||
<a class="btn btn-sm btn-light" style="position:absolute;right:5px;top:50%;transform:translateY(-50%);line-height:0;z-index:10"
|
||||
@click="hideSearch"><i class="fas fa-times"></i></a>
|
||||
</div>
|
||||
<div class="border-top messages" :class="'messages-' + conversation.mode" style="flex:1;overflow:auto;margin-top:2px"
|
||||
ref="messages" @scroll="onMessagesScroll">
|
||||
<div class="border-top messages" :class="'messages-' + conversation.mode" ref="messages" @scroll="onMessagesScroll"
|
||||
style="flex:1;overflow:auto;margin-top:2px;position:relative">
|
||||
<template v-for="message in messages">
|
||||
<message-view :message="message" :channel="conversation.channel" :key="message.id"
|
||||
:classes="message == conversation.lastRead ? 'last-read' : ''">
|
||||
</message-view>
|
||||
<span v-if="message.sfc && message.sfc.action == 'report'" :key="message.id">
|
||||
<span v-if="message.sfc && message.sfc.action == 'report'" :key="'r' + message.id">
|
||||
<a :href="'https://www.f-list.net/fchat/getLog.php?log=' + message.sfc.logid"
|
||||
v-if="message.sfc.logid" target="_blank">{{l('events.report.viewLog')}}</a>
|
||||
<span v-else>{{l('events.report.noLog')}}</span>
|
||||
|
@ -174,6 +174,8 @@
|
|||
keypressHandler!: EventListener;
|
||||
scrolledDown = true;
|
||||
scrolledUp = false;
|
||||
adCountdown = 0;
|
||||
adsMode = l('channel.mode.ads');
|
||||
|
||||
mounted(): void {
|
||||
this.extraButtons = [{
|
||||
|
@ -203,6 +205,21 @@
|
|||
this.search = this.searchInput;
|
||||
}, 500);
|
||||
this.messageView = <HTMLElement>this.$refs['messages'];
|
||||
this.$watch('conversation.nextAd', (value: number) => {
|
||||
const setAdCountdown = () => {
|
||||
const diff = ((<Conversation.ChannelConversation>this.conversation).nextAd - Date.now()) / 1000;
|
||||
if(diff <= 0) {
|
||||
if(this.adCountdown !== 0) window.clearInterval(this.adCountdown);
|
||||
this.adCountdown = 0;
|
||||
this.adsMode = l('channel.mode.ads');
|
||||
} else this.adsMode = l('channel.mode.ads.countdown', Math.floor(diff / 60), Math.floor(diff % 60));
|
||||
};
|
||||
if(Date.now() < value) {
|
||||
if(this.adCountdown === 0)
|
||||
this.adCountdown = window.setInterval(setAdCountdown, 1000);
|
||||
setAdCountdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
destroyed(): void {
|
||||
|
@ -252,9 +269,13 @@
|
|||
}
|
||||
|
||||
onMessagesScroll(): void {
|
||||
if(this.messageView.scrollTop < 50 && !this.scrolledUp) {
|
||||
if(this.messageView.scrollTop < 20) {
|
||||
if(!this.scrolledUp) {
|
||||
const firstMessage = this.messageView.firstElementChild;
|
||||
if(this.conversation.loadMore() && firstMessage !== null)
|
||||
this.$nextTick(() => setTimeout(() => this.messageView.scrollTop = (<HTMLElement>firstMessage).offsetTop, 0));
|
||||
}
|
||||
this.scrolledUp = true;
|
||||
this.conversation.loadMore();
|
||||
} else this.scrolledUp = false;
|
||||
this.scrolledDown = this.messageView.scrollTop + this.messageView.offsetHeight >= this.messageView.scrollHeight - 15;
|
||||
}
|
||||
|
@ -313,7 +334,7 @@
|
|||
else if(getKey(e) === Keys.Enter) {
|
||||
if(e.shiftKey === this.settings.enterSend) return;
|
||||
e.preventDefault();
|
||||
await this.conversation.send();
|
||||
setImmediate(async() => this.conversation.send());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -335,13 +356,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
get adsMode(): string | undefined {
|
||||
if(!Conversation.isChannel(this.conversation)) return;
|
||||
if(this.conversation.adCountdown <= 0) return l('channel.mode.ads');
|
||||
else return l('channel.mode.ads.countdown',
|
||||
Math.floor(this.conversation.adCountdown / 60).toString(), (this.conversation.adCountdown % 60).toString());
|
||||
}
|
||||
|
||||
get characterImage(): string {
|
||||
return characterImage(this.conversation.name);
|
||||
}
|
||||
|
@ -390,9 +404,9 @@
|
|||
}
|
||||
|
||||
.chat-info-text {
|
||||
display:flex;
|
||||
align-items:center;
|
||||
flex:1 51%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1 51%;
|
||||
@media (max-width: breakpoint-max(xs)) {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
|
|
@ -246,9 +246,11 @@
|
|||
this.dates[this.dateOffset++]);
|
||||
this.messages = messages.concat(this.messages);
|
||||
const noOverflow = list.offsetHeight === list.scrollHeight;
|
||||
const firstMessage = <HTMLElement>list.firstElementChild!;
|
||||
this.$nextTick(() => {
|
||||
if(list.offsetHeight === list.scrollHeight) return this.onMessagesScroll();
|
||||
else if(noOverflow) list.scrollTop = list.scrollHeight;
|
||||
else if(noOverflow) setTimeout(() => list.scrollTop = list.scrollHeight, 0);
|
||||
else setTimeout(() => list.scrollTop = firstMessage.offsetTop, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="idleTimer">{{l('settings.idleTimer')}}</label>
|
||||
<input id="idleTimer" class="form-control" type="number" v-model="idleTimer"/>
|
||||
<input id="idleTimer" class="form-control" type="number" v-model="idleTimer" min="0" max="1440"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="messageSeparators">
|
||||
|
@ -160,7 +160,7 @@
|
|||
alwaysNotify!: boolean;
|
||||
logMessages!: boolean;
|
||||
logAds!: boolean;
|
||||
fontSize!: number;
|
||||
fontSize!: string;
|
||||
showNeedsReply!: boolean;
|
||||
enterSend!: boolean;
|
||||
colorBookmarks!: boolean;
|
||||
|
@ -192,7 +192,7 @@
|
|||
this.alwaysNotify = settings.alwaysNotify;
|
||||
this.logMessages = settings.logMessages;
|
||||
this.logAds = settings.logAds;
|
||||
this.fontSize = settings.fontSize;
|
||||
this.fontSize = settings.fontSize.toString();
|
||||
this.showNeedsReply = settings.showNeedsReply;
|
||||
this.enterSend = settings.enterSend;
|
||||
this.colorBookmarks = settings.colorBookmarks;
|
||||
|
@ -215,6 +215,8 @@
|
|||
}
|
||||
|
||||
async submit(): Promise<void> {
|
||||
const idleTimer = parseInt(this.idleTimer, 10);
|
||||
const fontSize = parseInt(this.fontSize, 10);
|
||||
core.state.settings = {
|
||||
playSound: this.playSound,
|
||||
clickOpensMessage: this.clickOpensMessage,
|
||||
|
@ -224,14 +226,14 @@
|
|||
highlightWords: this.highlightWords.split(',').map((x) => x.trim()).filter((x) => x.length),
|
||||
showAvatars: this.showAvatars,
|
||||
animatedEicons: this.animatedEicons,
|
||||
idleTimer: this.idleTimer.length > 0 ? parseInt(this.idleTimer, 10) : 0,
|
||||
idleTimer: isNaN(idleTimer) ? 0 : idleTimer < 0 ? 0 : idleTimer > 1440 ? 1440 : idleTimer,
|
||||
messageSeparators: this.messageSeparators,
|
||||
eventMessages: this.eventMessages,
|
||||
joinMessages: this.joinMessages,
|
||||
alwaysNotify: this.alwaysNotify,
|
||||
logMessages: this.logMessages,
|
||||
logAds: this.logAds,
|
||||
fontSize: isNaN(this.fontSize) ? 14 : this.fontSize < 10 ? 10 : this.fontSize > 24 ? 24 : this.fontSize,
|
||||
fontSize: isNaN(fontSize) ? 14 : fontSize < 10 ? 10 : fontSize > 24 ? 24 : fontSize,
|
||||
showNeedsReply: this.showNeedsReply,
|
||||
enterSend: this.enterSend,
|
||||
colorBookmarks: this.colorBookmarks,
|
||||
|
|
|
@ -3,7 +3,6 @@ import {WebSocketConnection} from '../fchat';
|
|||
export default class Socket implements WebSocketConnection {
|
||||
static host = 'wss://chat.f-list.net:9799';
|
||||
private socket: WebSocket;
|
||||
private errorHandler: ((error: Error) => void) | undefined;
|
||||
private lastHandler: Promise<void> = Promise.resolve();
|
||||
|
||||
constructor() {
|
||||
|
@ -16,7 +15,10 @@ export default class Socket implements WebSocketConnection {
|
|||
|
||||
onMessage(handler: (message: string) => void): void {
|
||||
this.socket.addEventListener('message', (e) => {
|
||||
this.lastHandler = this.lastHandler.then(() => handler(<string>e.data), this.errorHandler);
|
||||
this.lastHandler = this.lastHandler.then(() => handler(<string>e.data), (err) => {
|
||||
window.requestAnimationFrame(() => { throw err; });
|
||||
handler(<string>e.data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -29,7 +31,6 @@ export default class Socket implements WebSocketConnection {
|
|||
}
|
||||
|
||||
onError(handler: (error: Error) => void): void {
|
||||
this.errorHandler = handler;
|
||||
this.socket.addEventListener('error', () => handler(new Error()));
|
||||
}
|
||||
|
||||
|
|
|
@ -81,10 +81,11 @@ abstract class Conversation implements Interfaces.Conversation {
|
|||
this.enteredText = this.lastSent;
|
||||
}
|
||||
|
||||
loadMore(): void {
|
||||
if(this.messages.length >= this.allMessages.length) return;
|
||||
loadMore(): boolean {
|
||||
if(this.messages.length >= this.allMessages.length) return false;
|
||||
this.maxMessages += 50;
|
||||
this.messages = this.allMessages.slice(-this.maxMessages);
|
||||
return true;
|
||||
}
|
||||
|
||||
show(): void {
|
||||
|
@ -198,7 +199,7 @@ class ChannelConversation extends Conversation implements Interfaces.ChannelConv
|
|||
readonly context = CommandContext.Channel;
|
||||
readonly name = this.channel.name;
|
||||
isSendingAds = this.channel.mode === 'ads';
|
||||
adCountdown = 0;
|
||||
nextAd = 0;
|
||||
private chat: Interfaces.Message[] = [];
|
||||
private ads: Interfaces.Message[] = [];
|
||||
private both: Interfaces.Message[] = [];
|
||||
|
@ -284,6 +285,13 @@ class ChannelConversation extends Conversation implements Interfaces.ChannelConv
|
|||
this.addModeMessage('both', message);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.messages = [];
|
||||
this.chat.length = 0;
|
||||
this.ads.length = 0;
|
||||
this.both.length = 0;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
core.connection.send('LCH', {channel: this.channel.id});
|
||||
}
|
||||
|
@ -296,17 +304,13 @@ class ChannelConversation extends Conversation implements Interfaces.ChannelConv
|
|||
|
||||
protected async doSend(): Promise<void> {
|
||||
const isAd = this.isSendingAds;
|
||||
if(isAd && this.adCountdown > 0) return;
|
||||
if(isAd && Date.now() < this.nextAd) return;
|
||||
core.connection.send(isAd ? 'LRP' : 'MSG', {channel: this.channel.id, message: this.enteredText});
|
||||
await this.addMessage(
|
||||
createMessage(isAd ? MessageType.Ad : MessageType.Message, core.characters.ownCharacter, this.enteredText, new Date()));
|
||||
if(isAd) {
|
||||
this.adCountdown = core.connection.vars.lfrp_flood;
|
||||
const interval = setInterval(() => {
|
||||
this.adCountdown -= 1;
|
||||
if(this.adCountdown === 0) clearInterval(interval);
|
||||
}, 1000);
|
||||
} else this.enteredText = '';
|
||||
if(isAd)
|
||||
this.nextAd = Date.now() + core.connection.vars.lfrp_flood * 1000;
|
||||
else this.enteredText = '';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ export namespace Conversation {
|
|||
export interface ChannelConversation extends TabConversation {
|
||||
readonly channel: Channel
|
||||
mode: Channel.Mode
|
||||
readonly adCountdown: number
|
||||
readonly nextAd: number
|
||||
isSendingAds: boolean
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ export namespace Conversation {
|
|||
clear(): void
|
||||
loadLastSent(): void
|
||||
show(): void
|
||||
loadMore(): void
|
||||
loadMore(): boolean
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,7 +181,7 @@ export type Settings = Settings.Settings;
|
|||
export interface Notifications {
|
||||
isInBackground: boolean
|
||||
notify(conversation: Conversation, title: string, body: string, icon: string, sound: string): Promise<void>
|
||||
playSound(sound: string): Promise<void>
|
||||
playSound(sound: string): void
|
||||
requestPermission(): Promise<void>
|
||||
initSounds(sounds: ReadonlyArray<string>): Promise<void>
|
||||
}
|
||||
|
|
|
@ -87,6 +87,10 @@ const strings: {[key: string]: string | undefined} = {
|
|||
'logs.selectCharacter': 'Select a character...',
|
||||
'logs.selectConversation': 'Select a conversation...',
|
||||
'logs.allDates': 'Show all',
|
||||
'logs.corruption.desktop': 'Log corruption has been detected. This is usually caused by a crash/force close or power loss mid-write. Please use the "Fix corrupted logs" option for this character to restore proper functionality.',
|
||||
'logs.corruption.mobile': 'Log corruption has been detected. This is usually caused by a crash/force close or power loss mid-write. Will now attempt to fix corrupted logs.',
|
||||
'logs.corruption.mobile.success': 'Your logs have been fixed.',
|
||||
'logs.corruption.mobile.error': 'Unable to fix corrupted logs. Please clear the application data or reinstall the app.',
|
||||
'user.profile': 'Profile',
|
||||
'user.message': 'Open conversation',
|
||||
'user.messageJump': 'View conversation',
|
||||
|
@ -384,6 +388,10 @@ Once this process has started, do not interrupt it or your logs will get corrupt
|
|||
'commands.gop.help': 'Promotes a character to global chat OP.',
|
||||
'commands.gdeop': 'Demote from Chat OP',
|
||||
'commands.gdeop.help': 'Demotes a character from global chat OP.',
|
||||
'commands.scop': 'Promote to Super COP',
|
||||
'commands.scop.help': 'Promotes a character to super channel operator, making them an operator in all public channels.',
|
||||
'commands.scdeop': 'Demote from Super COP',
|
||||
'commands.scdeop.help': 'Demotes a character from super channel operator.',
|
||||
'commands.reloadconfig': 'Reload config',
|
||||
'commands.reloadconfig.help': 'Reload server-side config from disk.',
|
||||
'commands.reloadconfig.param0': 'Save?',
|
||||
|
@ -412,13 +420,13 @@ Any existing FChat 3.0 data for this character will be overwritten.`,
|
|||
'importer.error': 'There was an error importing your settings. The defaults will be used.'
|
||||
};
|
||||
|
||||
export default function l(key: string, ...args: string[]): string {
|
||||
export default function l(key: string, ...args: (string | number)[]): string {
|
||||
let i = args.length;
|
||||
let str = strings[key];
|
||||
if(str === undefined)
|
||||
if(process.env.NODE_ENV !== 'production') throw new Error(`String ${key} does not exist.`);
|
||||
else return '';
|
||||
while(i-- > 0)
|
||||
str = str.replace(new RegExp(`\\{${i}\\}`, 'igm'), args[i]);
|
||||
str = str.replace(new RegExp(`\\{${i}\\}`, 'igm'), args[i].toString());
|
||||
return str;
|
||||
}
|
|
@ -13,17 +13,15 @@ export default class Notifications implements Interface {
|
|||
|
||||
async notify(conversation: Conversation, title: string, body: string, icon: string, sound: string): Promise<void> {
|
||||
if(!this.shouldNotify(conversation)) return;
|
||||
await this.playSound(sound);
|
||||
this.playSound(sound);
|
||||
if(core.state.settings.notifications && (<any>Notification).permission === 'granted') { //tslint:disable-line:no-any
|
||||
const notification = new Notification(title, this.getOptions(conversation, body, icon));
|
||||
notification.onclick = () => {
|
||||
conversation.show();
|
||||
window.focus();
|
||||
notification.close();
|
||||
if('close' in notification) notification.close();
|
||||
};
|
||||
window.setTimeout(() => {
|
||||
notification.close();
|
||||
}, 5000);
|
||||
if('close' in notification) window.setTimeout(() => notification.close(), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,20 +34,22 @@ export default class Notifications implements Interface {
|
|||
};
|
||||
}
|
||||
|
||||
async playSound(sound: string): Promise<void> {
|
||||
playSound(sound: string): void {
|
||||
if(!core.state.settings.playSound) return;
|
||||
const audio = <HTMLAudioElement>document.getElementById(`soundplayer-${sound}`);
|
||||
audio.volume = 1;
|
||||
audio.muted = false;
|
||||
return audio.play();
|
||||
const promise = audio.play();
|
||||
if(promise instanceof Promise) promise.catch((e) => console.error(e));
|
||||
}
|
||||
|
||||
initSounds(sounds: ReadonlyArray<string>): Promise<void> { //tslint:disable-line:promise-function-async
|
||||
async initSounds(sounds: ReadonlyArray<string>): Promise<void> {
|
||||
const promises = [];
|
||||
for(const sound of sounds) {
|
||||
const id = `soundplayer-${sound}`;
|
||||
if(document.getElementById(id) !== null) continue;
|
||||
const audio = document.createElement('audio');
|
||||
audio.preload = 'auto';
|
||||
audio.id = id;
|
||||
for(const name in codecs) {
|
||||
const src = document.createElement('source');
|
||||
|
@ -63,7 +63,7 @@ export default class Notifications implements Interface {
|
|||
audio.muted = true;
|
||||
const promise = audio.play();
|
||||
if(promise instanceof Promise)
|
||||
promises.push(promise);
|
||||
promises.push(promise.catch((e) => console.error(e)));
|
||||
}
|
||||
return <any>Promise.all(promises); //tslint:disable-line:no-any
|
||||
}
|
||||
|
|
|
@ -269,6 +269,16 @@ const commands: {readonly [key: string]: Command | undefined} = {
|
|||
context: CommandContext.Channel,
|
||||
params: [{type: ParamType.Character}]
|
||||
},
|
||||
scop: {
|
||||
exec: (_, character: string) => core.connection.send('SCP', {action: 'add', character}),
|
||||
permission: Permission.Admin,
|
||||
params: [{type: ParamType.Character}]
|
||||
},
|
||||
scdeop: {
|
||||
exec: (_, character: string) => core.connection.send('SCP', {action: 'remove', character}),
|
||||
permission: Permission.Admin,
|
||||
params: [{type: ParamType.Character}]
|
||||
},
|
||||
oplist: {
|
||||
exec: (conv: ChannelConversation) => core.connection.send('COL', {channel: conv.channel.id}),
|
||||
context: CommandContext.Channel
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {RavenStatic} from 'raven-js';
|
||||
import * as Raven from 'raven-js';
|
||||
import Vue from 'vue';
|
||||
|
||||
/*tslint:disable:no-unsafe-any no-any*///hack
|
||||
|
@ -13,7 +13,7 @@ function formatComponentName(vm: any): string {
|
|||
//tslint:enable
|
||||
|
||||
/*tslint:disable:no-unbound-method strict-type-predicates*///hack
|
||||
export default function VueRaven(this: void, raven: RavenStatic): RavenStatic {
|
||||
function VueRaven(this: void, raven: Raven.RavenStatic): Raven.RavenStatic {
|
||||
if(typeof Vue.config !== 'object') return raven;
|
||||
const oldOnError = Vue.config.errorHandler;
|
||||
Vue.config.errorHandler = (error: Error, vm: Vue, info: string): void => {
|
||||
|
@ -44,4 +44,27 @@ export default function VueRaven(this: void, raven: RavenStatic): RavenStatic {
|
|||
|
||||
return raven;
|
||||
}
|
||||
//tslint:enable
|
||||
//tslint:enable
|
||||
|
||||
export function setupRaven(dsn: string, version: string): void {
|
||||
Raven.config(dsn, {
|
||||
release: version,
|
||||
dataCallback: (data: {culprit?: string, exception?: {values: {stacktrace: {frames: {filename: string}[]}}[]}}) => {
|
||||
if(data.culprit !== undefined) {
|
||||
const end = data.culprit.lastIndexOf('?');
|
||||
data.culprit = `~${data.culprit.substring(data.culprit.lastIndexOf('/'), end === -1 ? undefined : end)}`;
|
||||
}
|
||||
if(data.exception !== undefined)
|
||||
for(const ex of data.exception.values)
|
||||
for(const frame of ex.stacktrace.frames) {
|
||||
const index = frame.filename.lastIndexOf('/');
|
||||
const endIndex = frame.filename.lastIndexOf('?');
|
||||
frame.filename =
|
||||
`~${frame.filename.substring(index !== -1 ? index : 0, endIndex === -1 ? undefined : endIndex)}`;
|
||||
}
|
||||
}
|
||||
}).addPlugin(VueRaven, Vue).install();
|
||||
(<Window & {onunhandledrejection(e: PromiseRejectionEvent): void}>window).onunhandledrejection = (e: PromiseRejectionEvent) => {
|
||||
Raven.captureException(<Error>e.reason);
|
||||
};
|
||||
}
|
13
chat/zip.ts
13
chat/zip.ts
|
@ -1,8 +1,10 @@
|
|||
import {getByteLength} from './common';
|
||||
|
||||
let crcTable!: number[];
|
||||
|
||||
export default class Zip {
|
||||
private blob: (object | string)[] = [];
|
||||
private files: {header: object[], offset: number, name: string}[] = [];
|
||||
private blob: BlobPart[] = [];
|
||||
private files: {header: BlobPart[], offset: number, name: string}[] = [];
|
||||
private offset = 0;
|
||||
|
||||
constructor() {
|
||||
|
@ -19,6 +21,7 @@ export default class Zip {
|
|||
addFile(name: string, content: string): void {
|
||||
let crc = -1;
|
||||
let length = 0;
|
||||
const nameLength = getByteLength(name);
|
||||
for(let i = 0, strlen = content.length; i < strlen; ++i) {
|
||||
let c = content.charCodeAt(i);
|
||||
if(c > 0xD800 && c < 0xD8FF) //surrogate pairs
|
||||
|
@ -35,13 +38,13 @@ export default class Zip {
|
|||
}
|
||||
crc = (crc ^ (-1)) >>> 0;
|
||||
const file = {
|
||||
header: [Uint16Array.of(0, 0, 0, 0, 0), Uint32Array.of(crc, length, length), Uint16Array.of(name.length, 0)],
|
||||
header: [Uint16Array.of(0, 0, 0, 0, 0), Uint32Array.of(crc, length, length), Uint16Array.of(nameLength, 0)],
|
||||
offset: this.offset, name
|
||||
};
|
||||
this.blob.push(Uint32Array.of(0x04034B50));
|
||||
this.blob.push(...file.header);
|
||||
this.blob.push(name, content);
|
||||
this.offset += name.length + length + 30;
|
||||
this.offset += nameLength + length + 30;
|
||||
this.files.push(file);
|
||||
}
|
||||
|
||||
|
@ -51,7 +54,7 @@ export default class Zip {
|
|||
this.blob.push(Uint16Array.of(0x4B50, 0x0201, 0));
|
||||
this.blob.push(...file.header);
|
||||
this.blob.push(Uint16Array.of(0, 0, 0, 0, 0), Uint32Array.of(file.offset), file.name);
|
||||
this.offset += file.name.length + 46;
|
||||
this.offset += getByteLength(file.name) + 46;
|
||||
}
|
||||
this.blob.push(Uint16Array.of(0x4B50, 0x0605, 0, 0, this.files.length, this.files.length),
|
||||
Uint32Array.of(this.offset - start, start), Uint16Array.of(0));
|
||||
|
|
|
@ -266,4 +266,9 @@
|
|||
html, body, #page {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
*:not([draggable]), *::after, *::before {
|
||||
-webkit-user-drag: none;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
</style>
|
|
@ -81,6 +81,7 @@
|
|||
l = l;
|
||||
hasUpdate = false;
|
||||
platform = process.platform;
|
||||
lockTab = false;
|
||||
|
||||
mounted(): void {
|
||||
this.addTab();
|
||||
|
@ -193,26 +194,30 @@
|
|||
}
|
||||
|
||||
addTab(): void {
|
||||
if(this.lockTab) return;
|
||||
const tray = new electron.remote.Tray(trayIcon);
|
||||
tray.setToolTip(l('title'));
|
||||
tray.on('click', (_) => this.trayClicked(tab));
|
||||
const view = new electron.remote.BrowserView();
|
||||
view.setAutoResize({width: true, height: true});
|
||||
view.webContents.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'index.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true,
|
||||
query: {settings: JSON.stringify(this.settings)}
|
||||
}));
|
||||
electron.ipcRenderer.send('tab-added', view.webContents.id);
|
||||
const tab = {active: false, view, user: undefined, hasNew: false, tray};
|
||||
tray.setContextMenu(electron.remote.Menu.buildFromTemplate(this.createTrayMenu(tab)));
|
||||
this.tabs.push(tab);
|
||||
this.tabMap[view.webContents.id] = tab;
|
||||
this.show(tab);
|
||||
this.lockTab = true;
|
||||
view.webContents.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'index.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true,
|
||||
query: {settings: JSON.stringify(this.settings)}
|
||||
}));
|
||||
view.webContents.on('did-stop-loading', () => this.lockTab = false);
|
||||
}
|
||||
|
||||
show(tab: Tab): void {
|
||||
if(this.lockTab) return;
|
||||
this.activeTab = tab;
|
||||
browserWindow.setBrowserView(tab.view);
|
||||
tab.view.setBounds(getWindowBounds());
|
||||
|
@ -313,7 +318,7 @@
|
|||
|
||||
#window-tabs {
|
||||
h4 {
|
||||
margin: 0 34px 0 77px;
|
||||
margin: 0 15px 0 77px;
|
||||
}
|
||||
|
||||
.btn, li a {
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"name": "fchat",
|
||||
"version": "3.0.6",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List.net Chat Client",
|
||||
"main": "main.js",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"electron": "^2.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"keytar": "^4.2.1",
|
||||
"spellchecker": "^3.4.4"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 89 KiB |
Binary file not shown.
After Width: | Height: | Size: 267 KiB |
|
@ -32,14 +32,11 @@
|
|||
import Axios from 'axios';
|
||||
import {exec, execSync} from 'child_process';
|
||||
import * as electron from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as qs from 'querystring';
|
||||
import * as Raven from 'raven-js';
|
||||
import Vue from 'vue';
|
||||
import {getKey} from '../chat/common';
|
||||
import l from '../chat/localize';
|
||||
import VueRaven from '../chat/vue-raven';
|
||||
import {setupRaven} from '../chat/vue-raven';
|
||||
import {Keys} from '../keys';
|
||||
import {GeneralSettings, nativeRequire} from './common';
|
||||
import * as SlimcatImporter from './importer';
|
||||
|
@ -67,21 +64,7 @@ const spellchecker = new sc.Spellchecker();
|
|||
Axios.defaults.params = { __fchat: `desktop/${electron.remote.app.getVersion()}` };
|
||||
|
||||
if(process.env.NODE_ENV === 'production') {
|
||||
Raven.config('https://a9239b17b0a14f72ba85e8729b9d1612@sentry.f-list.net/2', {
|
||||
release: electron.remote.app.getVersion(),
|
||||
dataCallback(data: {culprit: string, exception?: {values: {stacktrace: {frames: {filename: string}[]}}[]}}): void {
|
||||
data.culprit = `~${data.culprit.substr(data.culprit.lastIndexOf('/'))}`;
|
||||
if(data.exception !== undefined)
|
||||
for(const ex of data.exception.values)
|
||||
for(const frame of ex.stacktrace.frames) {
|
||||
const index = frame.filename.lastIndexOf('/');
|
||||
frame.filename = index !== -1 ? `~${frame.filename.substr(index)}` : frame.filename;
|
||||
}
|
||||
}
|
||||
}).addPlugin(VueRaven, Vue).install();
|
||||
(<Window & {onunhandledrejection(e: PromiseRejectionEvent): void}>window).onunhandledrejection = (e: PromiseRejectionEvent) => {
|
||||
Raven.captureException(<Error>e.reason);
|
||||
};
|
||||
setupRaven('https://a9239b17b0a14f72ba85e8729b9d1612@sentry.f-list.net/2', electron.remote.app.getVersion());
|
||||
|
||||
electron.remote.getCurrentWebContents().on('devtools-opened', () => {
|
||||
console.log(`%c${l('consoleWarning.head')}`, 'background: red; color: yellow; font-size: 30pt');
|
||||
|
@ -171,12 +154,7 @@ webContents.on('context-menu', (_, props) => {
|
|||
const corrections = spellchecker.getCorrectionsForMisspelling(props.misspelledWord);
|
||||
menuTemplate.unshift({
|
||||
label: l('spellchecker.add'),
|
||||
click: () => {
|
||||
if(customDictionary.indexOf(props.misspelledWord) !== -1) return;
|
||||
spellchecker.add(props.misspelledWord);
|
||||
customDictionary.push(props.misspelledWord);
|
||||
fs.writeFile(customDictionaryPath, JSON.stringify(customDictionary), () => {/**/});
|
||||
}
|
||||
click: () => electron.ipcRenderer.send('dictionary-add', props.misspelledWord)
|
||||
}, {type: 'separator'});
|
||||
if(corrections.length > 0)
|
||||
menuTemplate.unshift(...corrections.map((correction: string) => ({
|
||||
|
@ -184,14 +162,10 @@ webContents.on('context-menu', (_, props) => {
|
|||
click: () => webContents.replaceMisspelling(correction)
|
||||
})));
|
||||
else menuTemplate.unshift({enabled: false, label: l('spellchecker.noCorrections')});
|
||||
} else if(customDictionary.indexOf(props.selectionText) !== -1)
|
||||
} else if(settings.customDictionary.indexOf(props.selectionText) !== -1)
|
||||
menuTemplate.unshift({
|
||||
label: l('spellchecker.remove'),
|
||||
click: () => {
|
||||
spellchecker.remove(props.selectionText);
|
||||
customDictionary.splice(customDictionary.indexOf(props.selectionText), 1);
|
||||
fs.writeFile(customDictionaryPath, JSON.stringify(customDictionary), () => {/**/});
|
||||
}
|
||||
click: () => electron.ipcRenderer.send('dictionary-remove', props.selectionText)
|
||||
}, {type: 'separator'});
|
||||
|
||||
if(menuTemplate.length > 0) electron.remote.Menu.buildFromTemplate(menuTemplate).popup({});
|
||||
|
@ -201,10 +175,14 @@ let dictDir = path.join(electron.remote.app.getPath('userData'), 'spellchecker')
|
|||
if(process.platform === 'win32')
|
||||
exec(`for /d %I in ("${dictDir}") do @echo %~sI`, (_, stdout) => { dictDir = stdout.trim(); });
|
||||
electron.webFrame.setSpellCheckProvider('', false, {spellCheck: (text) => !spellchecker.isMisspelled(text)});
|
||||
electron.ipcRenderer.on('settings', async(_: Event, s: GeneralSettings) => spellchecker.setDictionary(s.spellcheckLang, dictDir));
|
||||
electron.ipcRenderer.on('settings', async(_: Event, s: GeneralSettings) => {
|
||||
settings = s;
|
||||
spellchecker.setDictionary(s.spellcheckLang, dictDir);
|
||||
for(const word of s.customDictionary) spellchecker.add(word);
|
||||
});
|
||||
|
||||
const params = <{[key: string]: string | undefined}>qs.parse(window.location.search.substr(1));
|
||||
const settings = <GeneralSettings>JSON.parse(params['settings']!);
|
||||
let settings = <GeneralSettings>JSON.parse(params['settings']!);
|
||||
if(params['import'] !== undefined)
|
||||
try {
|
||||
if(SlimcatImporter.canImportGeneral() && confirm(l('importer.importGeneral'))) {
|
||||
|
@ -214,11 +192,6 @@ if(params['import'] !== undefined)
|
|||
} catch {
|
||||
alert(l('importer.error'));
|
||||
}
|
||||
spellchecker.setDictionary(settings.spellcheckLang, dictDir);
|
||||
|
||||
const customDictionaryPath = path.join(settings.logDirectory, 'words');
|
||||
const customDictionary = fs.existsSync(customDictionaryPath) ? <string[]>JSON.parse(fs.readFileSync(customDictionaryPath, 'utf8')) : [];
|
||||
for(const word of customDictionary) spellchecker.add(word);
|
||||
|
||||
//tslint:disable-next-line:no-unused-expression
|
||||
new Index({
|
||||
|
|
|
@ -14,6 +14,7 @@ export class GeneralSettings {
|
|||
theme = 'default';
|
||||
version = electron.app.getVersion();
|
||||
beta = false;
|
||||
customDictionary: string[] = [];
|
||||
}
|
||||
|
||||
export function mkdir(dir: string): void {
|
||||
|
|
|
@ -40,7 +40,7 @@ export async function ensureDictionary(lang: string): Promise<void> {
|
|||
const filePath = path.join(dictDir, `${lang}.${type}`);
|
||||
const downloaded = downloadedDictionaries[file.name];
|
||||
if(downloaded === undefined || downloaded.hash !== file.hash || !fs.existsSync(filePath)) {
|
||||
await writeFile(filePath, new Buffer((await Axios.get<string>(`${downloadUrl}${file.name}`, requestConfig)).data));
|
||||
await writeFile(filePath, Buffer.from((await Axios.get<string>(`${downloadUrl}${file.name}`, requestConfig)).data));
|
||||
downloadedDictionaries[file.name] = file;
|
||||
await writeFile(downloadedPath, JSON.stringify(downloadedDictionaries));
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as electron from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {promisify} from 'util';
|
||||
import {Message as MessageImpl} from '../chat/common';
|
||||
import core from '../chat/core';
|
||||
import {Character, Conversation, Logs as Logging, Settings} from '../chat/interfaces';
|
||||
|
@ -14,7 +15,7 @@ declare module '../chat/interfaces' {
|
|||
}
|
||||
|
||||
const dayMs = 86400000;
|
||||
|
||||
const read = promisify(fs.read);
|
||||
const noAssert = process.env.NODE_ENV === 'production';
|
||||
|
||||
function writeFile(p: fs.PathLike | number, data: string | object | number,
|
||||
|
@ -110,43 +111,51 @@ export function fixLogs(character: string): void {
|
|||
const dir = getLogDir(character);
|
||||
const files = fs.readdirSync(dir);
|
||||
const buffer = Buffer.allocUnsafe(50100);
|
||||
for(const file of files)
|
||||
if(file.substr(-4) !== '.idx') {
|
||||
const fd = fs.openSync(path.join(dir, file), 'r+');
|
||||
const indexFd = fs.openSync(path.join(dir, `${file}.idx`), 'r+');
|
||||
fs.readSync(indexFd, buffer, 0, 1, 0);
|
||||
let pos = 0, lastDay = 0;
|
||||
const nameEnd = buffer.readUInt8(0, noAssert) + 1;
|
||||
fs.readSync(indexFd, buffer, 0, nameEnd, null); //tslint:disable-line:no-null-keyword
|
||||
buffer.toString('utf8', 1, nameEnd);
|
||||
fs.ftruncateSync(indexFd, nameEnd);
|
||||
const size = (fs.fstatSync(fd)).size;
|
||||
try {
|
||||
while(pos < size) {
|
||||
buffer.fill(-1);
|
||||
fs.readSync(fd, buffer, 0, 50100, pos);
|
||||
const deserialized = deserializeMessage(buffer, 0, (name) => ({
|
||||
gender: 'None', status: 'online', statusText: '', isFriend: false, isBookmarked: false, isChatOp: false,
|
||||
isIgnored: false, name
|
||||
}), false);
|
||||
const time = deserialized.message.time;
|
||||
const day = Math.floor(time.getTime() / dayMs - time.getTimezoneOffset() / 1440);
|
||||
if(day > lastDay) {
|
||||
buffer.writeUInt16LE(day, 0, noAssert);
|
||||
buffer.writeUIntLE(pos, 2, 5, noAssert);
|
||||
fs.writeSync(indexFd, buffer, 0, 7);
|
||||
lastDay = day;
|
||||
}
|
||||
if(buffer.readUInt16LE(deserialized.size - 2) !== deserialized.size - 2) throw new Error();
|
||||
pos += deserialized.size;
|
||||
}
|
||||
} catch {
|
||||
fs.ftruncateSync(fd, pos);
|
||||
} finally {
|
||||
fs.closeSync(fd);
|
||||
fs.closeSync(indexFd);
|
||||
}
|
||||
for(const file of files) {
|
||||
const full = path.join(dir, file);
|
||||
if(file.substr(-4) === '.idx') {
|
||||
if(!fs.existsSync(full.slice(0, -4))) fs.unlinkSync(full);
|
||||
continue;
|
||||
}
|
||||
const fd = fs.openSync(full, 'r+');
|
||||
const indexPath = path.join(dir, `${file}.idx`);
|
||||
if(!fs.existsSync(indexPath)) {
|
||||
fs.unlinkSync(full);
|
||||
continue;
|
||||
}
|
||||
const indexFd = fs.openSync(indexPath, 'r+');
|
||||
fs.readSync(indexFd, buffer, 0, 1, 0);
|
||||
let pos = 0, lastDay = 0;
|
||||
const nameEnd = buffer.readUInt8(0, noAssert) + 1;
|
||||
fs.ftruncateSync(indexFd, nameEnd);
|
||||
fs.readSync(indexFd, buffer, 0, nameEnd, null); //tslint:disable-line:no-null-keyword
|
||||
const size = (fs.fstatSync(fd)).size;
|
||||
try {
|
||||
while(pos < size) {
|
||||
buffer.fill(-1);
|
||||
fs.readSync(fd, buffer, 0, 50100, pos);
|
||||
const deserialized = deserializeMessage(buffer, 0, (name) => ({
|
||||
gender: 'None', status: 'online', statusText: '', isFriend: false, isBookmarked: false, isChatOp: false,
|
||||
isIgnored: false, name
|
||||
}), false);
|
||||
const time = deserialized.message.time;
|
||||
const day = Math.floor(time.getTime() / dayMs - time.getTimezoneOffset() / 1440);
|
||||
if(day > lastDay) {
|
||||
buffer.writeUInt16LE(day, 0, noAssert);
|
||||
buffer.writeUIntLE(pos, 2, 5, noAssert);
|
||||
fs.writeSync(indexFd, buffer, 0, 7);
|
||||
lastDay = day;
|
||||
}
|
||||
if(buffer.readUInt16LE(deserialized.size - 2) !== deserialized.size - 2) throw new Error();
|
||||
pos += deserialized.size;
|
||||
}
|
||||
} catch {
|
||||
fs.ftruncateSync(fd, pos);
|
||||
} finally {
|
||||
fs.closeSync(fd);
|
||||
fs.closeSync(indexFd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadIndex(name: string): Index {
|
||||
|
@ -155,19 +164,23 @@ function loadIndex(name: string): Index {
|
|||
const files = fs.readdirSync(dir);
|
||||
for(const file of files)
|
||||
if(file.substr(-4) === '.idx') {
|
||||
const content = fs.readFileSync(path.join(dir, file));
|
||||
let offset = content.readUInt8(0, noAssert) + 1;
|
||||
const item: IndexItem = {
|
||||
name: content.toString('utf8', 1, offset),
|
||||
index: {},
|
||||
offsets: new Array(content.length - offset)
|
||||
};
|
||||
for(; offset < content.length; offset += 7) {
|
||||
const key = content.readUInt16LE(offset);
|
||||
item.index[key] = item.offsets.length;
|
||||
item.offsets.push(content.readUIntLE(offset + 2, 5, noAssert));
|
||||
try {
|
||||
const content = fs.readFileSync(path.join(dir, file));
|
||||
let offset = content.readUInt8(0, noAssert) + 1;
|
||||
const item: IndexItem = {
|
||||
name: content.toString('utf8', 1, offset),
|
||||
index: {},
|
||||
offsets: new Array(content.length - offset)
|
||||
};
|
||||
for(; offset < content.length; offset += 7) {
|
||||
const key = content.readUInt16LE(offset);
|
||||
item.index[key] = item.offsets.length;
|
||||
item.offsets.push(content.readUIntLE(offset + 2, 5, noAssert));
|
||||
}
|
||||
index[file.slice(0, -4).toLowerCase()] = item;
|
||||
} catch {
|
||||
alert(l('logs.corruption.desktop'));
|
||||
}
|
||||
index[file.slice(0, -4).toLowerCase()] = item;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
@ -190,18 +203,24 @@ export class Logs implements Logging {
|
|||
let count = 20;
|
||||
let messages = new Array<Conversation.Message>(count);
|
||||
const fd = fs.openSync(file, 'r');
|
||||
let pos = fs.fstatSync(fd).size;
|
||||
const buffer = Buffer.allocUnsafe(65536);
|
||||
while(pos > 0 && count > 0) {
|
||||
fs.readSync(fd, buffer, 0, 2, pos - 2);
|
||||
const length = buffer.readUInt16LE(0);
|
||||
pos = pos - length - 2;
|
||||
fs.readSync(fd, buffer, 0, length, pos);
|
||||
messages[--count] = deserializeMessage(buffer).message;
|
||||
try {
|
||||
let pos = fs.fstatSync(fd).size;
|
||||
const buffer = Buffer.allocUnsafe(65536);
|
||||
while(pos > 0 && count > 0) {
|
||||
fs.readSync(fd, buffer, 0, 2, pos - 2);
|
||||
const length = buffer.readUInt16LE(0);
|
||||
pos = pos - length - 2;
|
||||
fs.readSync(fd, buffer, 0, length, pos);
|
||||
messages[--count] = deserializeMessage(buffer).message;
|
||||
}
|
||||
if(count !== 0) messages = messages.slice(count);
|
||||
return messages;
|
||||
} catch {
|
||||
alert(l('logs.corruption.desktop'));
|
||||
return [];
|
||||
} finally {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
if(count !== 0) messages = messages.slice(count);
|
||||
fs.closeSync(fd);
|
||||
return messages;
|
||||
}
|
||||
|
||||
private getIndex(name: string): Index {
|
||||
|
@ -229,18 +248,25 @@ export class Logs implements Logging {
|
|||
const messages: Conversation.Message[] = [];
|
||||
const pos = index.offsets[dateOffset];
|
||||
const fd = fs.openSync(getLogFile(character, key), 'r');
|
||||
const end = dateOffset + 1 < index.offsets.length ? index.offsets[dateOffset + 1] : (fs.fstatSync(fd)).size;
|
||||
const length = end - pos;
|
||||
const buffer = Buffer.allocUnsafe(length);
|
||||
fs.readSync(fd, buffer, 0, length, pos);
|
||||
fs.closeSync(fd);
|
||||
let offset = 0;
|
||||
while(offset < length) {
|
||||
const deserialized = deserializeMessage(buffer, offset);
|
||||
messages.push(deserialized.message);
|
||||
offset += deserialized.size;
|
||||
try {
|
||||
const end = dateOffset + 1 < index.offsets.length ? index.offsets[dateOffset + 1] : (fs.fstatSync(fd)).size;
|
||||
const length = end - pos;
|
||||
const buffer = Buffer.allocUnsafe(length);
|
||||
await read(fd, buffer, 0, length, pos);
|
||||
fs.closeSync(fd);
|
||||
let offset = 0;
|
||||
while(offset < length) {
|
||||
const deserialized = deserializeMessage(buffer, offset);
|
||||
messages.push(deserialized.message);
|
||||
offset += deserialized.size;
|
||||
}
|
||||
return messages;
|
||||
} catch {
|
||||
alert(l('logs.corruption.desktop'));
|
||||
return [];
|
||||
} finally {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
logMessage(conversation: {key: string, name: string}, message: Message): void {
|
||||
|
@ -262,6 +288,7 @@ export class Logs implements Logging {
|
|||
|
||||
async getAvailableCharacters(): Promise<ReadonlyArray<string>> {
|
||||
const baseDir = core.state.generalSettings!.logDirectory;
|
||||
mkdir(baseDir);
|
||||
return (fs.readdirSync(baseDir)).filter((x) => fs.lstatSync(path.join(baseDir, x)).isDirectory());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
*/
|
||||
import * as electron from 'electron';
|
||||
import log from 'electron-log'; //tslint:disable-line:match-default-export-name
|
||||
import {autoUpdater} from 'electron-updater';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
|
@ -53,12 +52,12 @@ let tabCount = 0;
|
|||
|
||||
const baseDir = app.getPath('userData');
|
||||
mkdir(baseDir);
|
||||
autoUpdater.logger = log;
|
||||
log.transports.file.level = 'debug';
|
||||
log.transports.console.level = 'debug';
|
||||
log.transports.file.maxSize = 5 * 1024 * 1024;
|
||||
log.transports.file.file = path.join(baseDir, 'log.txt');
|
||||
log.info('Starting application.');
|
||||
let shouldImportSettings = false;
|
||||
|
||||
const settingsDir = path.join(baseDir, 'data');
|
||||
mkdir(settingsDir);
|
||||
const settingsFile = path.join(settingsDir, 'settings');
|
||||
const settings = new GeneralSettings();
|
||||
|
||||
async function setDictionary(lang: string | undefined): Promise<void> {
|
||||
if(lang !== undefined) await ensureDictionary(lang);
|
||||
|
@ -66,19 +65,6 @@ async function setDictionary(lang: string | undefined): Promise<void> {
|
|||
setGeneralSettings(settings);
|
||||
}
|
||||
|
||||
const settingsDir = path.join(electron.app.getPath('userData'), 'data');
|
||||
mkdir(settingsDir);
|
||||
const settingsFile = path.join(settingsDir, 'settings');
|
||||
const settings = new GeneralSettings();
|
||||
let shouldImportSettings = false;
|
||||
if(!fs.existsSync(settingsFile)) shouldImportSettings = true;
|
||||
else
|
||||
try {
|
||||
Object.assign(settings, <GeneralSettings>JSON.parse(fs.readFileSync(settingsFile, 'utf8')));
|
||||
} catch(e) {
|
||||
log.error(`Error loading settings: ${e}`);
|
||||
}
|
||||
|
||||
function setGeneralSettings(value: GeneralSettings): void {
|
||||
fs.writeFileSync(path.join(settingsDir, 'settings'), JSON.stringify(value));
|
||||
for(const w of electron.webContents.getAllWebContents()) w.send('settings', settings);
|
||||
|
@ -150,7 +136,21 @@ function showPatchNotes(): void {
|
|||
}
|
||||
|
||||
function onReady(): void {
|
||||
app.setAppUserModelId('net.f-list.f-chat');
|
||||
log.transports.file.level = 'debug';
|
||||
log.transports.console.level = 'debug';
|
||||
log.transports.file.maxSize = 5 * 1024 * 1024;
|
||||
log.transports.file.file = path.join(baseDir, 'log.txt');
|
||||
log.info('Starting application.');
|
||||
|
||||
if(!fs.existsSync(settingsFile)) shouldImportSettings = true;
|
||||
else
|
||||
try {
|
||||
Object.assign(settings, <GeneralSettings>JSON.parse(fs.readFileSync(settingsFile, 'utf8')));
|
||||
} catch(e) {
|
||||
log.error(`Error loading settings: ${e}`);
|
||||
}
|
||||
|
||||
app.setAppUserModelId('com.squirrel.fchat.F-Chat');
|
||||
app.on('open-file', createWindow);
|
||||
|
||||
if(settings.version !== app.getVersion()) {
|
||||
|
@ -159,11 +159,12 @@ function onReady(): void {
|
|||
setGeneralSettings(settings);
|
||||
}
|
||||
|
||||
const updaterUrl = `https://client.f-list.net/${process.platform}`;
|
||||
if(process.env.NODE_ENV === 'production') {
|
||||
autoUpdater.channel = settings.beta ? 'beta' : 'latest';
|
||||
autoUpdater.checkForUpdates(); //tslint:disable-line:no-floating-promises
|
||||
const updateTimer = setInterval(async() => autoUpdater.checkForUpdates(), 3600000);
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
electron.autoUpdater.setFeedURL({url: updaterUrl + (settings.beta ? '?channel=beta' : ''), serverType: 'json'});
|
||||
setTimeout(() => electron.autoUpdater.checkForUpdates(), 10000);
|
||||
const updateTimer = setInterval(() => electron.autoUpdater.checkForUpdates(), 3600000);
|
||||
electron.autoUpdater.on('update-downloaded', () => {
|
||||
clearInterval(updateTimer);
|
||||
const menu = electron.Menu.getApplicationMenu()!;
|
||||
const item = menu.getMenuItemById('update') as MenuItem | null;
|
||||
|
@ -175,7 +176,7 @@ function onReady(): void {
|
|||
label: l('action.update'),
|
||||
click: () => {
|
||||
for(const w of windows) w.webContents.send('quit');
|
||||
autoUpdater.quitAndInstall(false, true);
|
||||
electron.autoUpdater.quitAndInstall();
|
||||
}
|
||||
}, {
|
||||
label: l('help.changelog'),
|
||||
|
@ -186,12 +187,12 @@ function onReady(): void {
|
|||
electron.Menu.setApplicationMenu(menu);
|
||||
for(const w of windows) w.webContents.send('update-available', true);
|
||||
});
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
(<any>autoUpdater).downloadedUpdateHelper.clear(); //tslint:disable-line:no-any no-unsafe-any
|
||||
electron.autoUpdater.on('update-not-available', () => {
|
||||
for(const w of windows) w.webContents.send('update-available', false);
|
||||
const item = electron.Menu.getApplicationMenu()!.getMenuItemById('update') as MenuItem | null;
|
||||
if(item !== null) item.visible = false;
|
||||
});
|
||||
electron.autoUpdater.on('error', (e) => log.error(e));
|
||||
}
|
||||
|
||||
const viewItem = {
|
||||
|
@ -275,8 +276,8 @@ function onReady(): void {
|
|||
click: async(item: Electron.MenuItem) => {
|
||||
settings.beta = item.checked;
|
||||
setGeneralSettings(settings);
|
||||
autoUpdater.channel = item.checked ? 'beta' : 'latest';
|
||||
return autoUpdater.checkForUpdates();
|
||||
electron.autoUpdater.setFeedURL({url: updaterUrl + (item.checked ? '?channel=beta' : ''), serverType: 'json'});
|
||||
return electron.autoUpdater.checkForUpdates();
|
||||
}
|
||||
}, {
|
||||
label: l('fixLogs.action'),
|
||||
|
@ -360,6 +361,15 @@ function onReady(): void {
|
|||
else characters.push(character);
|
||||
e.returnValue = true;
|
||||
});
|
||||
electron.ipcMain.on('dictionary-add', (_: Event, word: string) => {
|
||||
if(settings.customDictionary.indexOf(word) !== -1) return;
|
||||
settings.customDictionary.push(word);
|
||||
setGeneralSettings(settings);
|
||||
});
|
||||
electron.ipcMain.on('dictionary-remove', (_: Event, word: string) => {
|
||||
settings.customDictionary.splice(settings.customDictionary.indexOf(word), 1);
|
||||
setGeneralSettings(settings);
|
||||
});
|
||||
electron.ipcMain.on('disconnect', (_: Event, character: string) => characters.splice(characters.indexOf(character), 1));
|
||||
const emptyBadge = electron.nativeImage.createEmpty();
|
||||
//tslint:disable-next-line:no-require-imports
|
||||
|
@ -372,7 +382,7 @@ function onReady(): void {
|
|||
createWindow();
|
||||
}
|
||||
|
||||
const running = process.env.NODE_ENV === 'production' && app.makeSingleInstance(createWindow);
|
||||
if(running) app.quit();
|
||||
const isSquirrelStart = require('electron-squirrel-startup'); //tslint:disable-line:no-require-imports
|
||||
if(isSquirrelStart || process.env.NODE_ENV === 'production' && app.makeSingleInstance(createWindow)) app.quit();
|
||||
else app.on('ready', onReady);
|
||||
app.on('window-all-closed', () => app.quit());
|
|
@ -9,7 +9,7 @@ const browserWindow = remote.getCurrentWindow();
|
|||
export default class Notifications extends BaseNotifications {
|
||||
async notify(conversation: Conversation, title: string, body: string, icon: string, sound: string): Promise<void> {
|
||||
if(!this.shouldNotify(conversation)) return;
|
||||
await this.playSound(sound);
|
||||
this.playSound(sound);
|
||||
browserWindow.flashFrame(true);
|
||||
if(core.state.settings.notifications) {
|
||||
const notification = new Notification(title, this.getOptions(conversation, body, icon));
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
const path = require('path');
|
||||
const pkg = require(path.join(__dirname, 'package.json'));
|
||||
const fs = require('fs');
|
||||
const child_process = require('child_process');
|
||||
|
||||
function mkdir(dir) {
|
||||
try {
|
||||
fs.mkdirSync(dir);
|
||||
} catch(e) {
|
||||
if(!(e instanceof Error)) throw e;
|
||||
switch(e.code) {
|
||||
case 'ENOENT':
|
||||
const dirname = path.dirname(dir);
|
||||
if(dirname === dir) throw e;
|
||||
mkdir(dirname);
|
||||
mkdir(dir);
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
const stat = fs.statSync(dir);
|
||||
if(stat.isDirectory()) return;
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const distDir = path.join(__dirname, 'dist');
|
||||
const isBeta = pkg.version.indexOf('beta') !== -1;
|
||||
const spellcheckerPath = 'node_modules/spellchecker/build/Release/spellchecker.node',
|
||||
keytarPath = 'node_modules/keytar/build/Release/keytar.node';
|
||||
mkdir(path.dirname(path.join(__dirname, 'app', spellcheckerPath)));
|
||||
mkdir(path.dirname(path.join(__dirname, 'app', keytarPath)));
|
||||
fs.copyFileSync(spellcheckerPath, path.join(__dirname, 'app', spellcheckerPath));
|
||||
fs.copyFileSync(keytarPath, path.join(__dirname, 'app', keytarPath));
|
||||
|
||||
require('electron-packager')({
|
||||
dir: path.join(__dirname, 'app'),
|
||||
out: distDir,
|
||||
overwrite: true,
|
||||
name: 'F-Chat',
|
||||
icon: path.join(__dirname, 'build', 'icon'),
|
||||
ignore: ['\.map$'],
|
||||
osxSign: process.argv.length > 2 ? {identity: process.argv[2]} : false,
|
||||
prune: false
|
||||
}).then((appPaths) => {
|
||||
if(process.platform === 'win32') {
|
||||
console.log('Creating Windows installer');
|
||||
const icon = path.join(__dirname, 'build', 'icon.ico');
|
||||
const setupName = `F-Chat Setup.exe`;
|
||||
if(fs.existsSync(path.join(distDir, setupName))) fs.unlinkSync(path.join(distDir, setupName));
|
||||
const nupkgName = path.join(distDir, `fchat-${pkg.version}-full.nupkg`);
|
||||
const deltaName = path.join(distDir, `fchat-${pkg.version}-delta.nupkg`);
|
||||
if(fs.existsSync(nupkgName)) fs.unlinkSync(nupkgName);
|
||||
if(fs.existsSync(deltaName)) fs.unlinkSync(deltaName);
|
||||
if(process.argv.length <= 3) console.warn('Warning: Creating unsigned installer');
|
||||
require('electron-winstaller').createWindowsInstaller({
|
||||
appDirectory: appPaths[0],
|
||||
outputDirectory: distDir,
|
||||
iconUrl: icon,
|
||||
setupIcon: icon,
|
||||
noMsi: true,
|
||||
exe: 'F-Chat.exe',
|
||||
title: 'F-Chat',
|
||||
setupExe: setupName,
|
||||
remoteReleases: 'https://client.f-list.net/win32/' + (isBeta ? '?channel=beta' : ''),
|
||||
signWithParams: process.argv.length > 3 ? `/a /f ${process.argv[2]} /p ${process.argv[3]} /fd sha256 /tr http://timestamp.digicert.com /td sha256` : undefined
|
||||
}).catch((e) => console.log(`Error while creating installer: ${e.message}`));
|
||||
} else if(process.platform === 'darwin') {
|
||||
console.log('Creating Mac DMG');
|
||||
const target = path.join(distDir, `F-Chat.dmg`);
|
||||
if(fs.existsSync(target)) fs.unlinkSync(target);
|
||||
const appPath = path.join(appPaths[0], 'F-Chat.app');
|
||||
if(process.argv.length <= 2) console.warn('Warning: Creating unsigned DMG');
|
||||
require('appdmg')({
|
||||
basepath: appPaths[0],
|
||||
target,
|
||||
specification: {
|
||||
title: 'F-Chat',
|
||||
icon: path.join(__dirname, 'build', 'icon.png'),
|
||||
background: path.join(__dirname, 'build', 'dmg.png'),
|
||||
contents: [{x: 555, y: 345, type: 'link', path: '/Applications'}, {x: 555, y: 105, type: 'file', path: appPath}],
|
||||
'code-sign': process.argv.length > 2 ? {
|
||||
'signing-identity': process.argv[2]
|
||||
} : undefined
|
||||
}
|
||||
}).on('error', console.error);
|
||||
const zipName = `F-Chat_${pkg.version}.zip`;
|
||||
const zipPath = path.join(distDir, zipName);
|
||||
if(fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
|
||||
const child = child_process.spawn('zip', ['-r', '-y', '-9', zipPath, 'F-Chat.app'], {cwd: appPaths[0]});
|
||||
child.stdout.on('data', () => {});
|
||||
child.stderr.on('data', (data) => console.error(data.toString()));
|
||||
fs.writeFileSync(path.join(distDir, 'updates.json'), JSON.stringify({
|
||||
releases: [{version: pkg.version, updateTo: {url: 'https://client.f-list.net/darwin/' + zipName}}],
|
||||
currentRelease: pkg.version
|
||||
}));
|
||||
} else {
|
||||
console.log('Creating Linux AppImage');
|
||||
fs.renameSync(path.join(appPaths[0], 'F-Chat'), path.join(appPaths[0], 'AppRun'));
|
||||
fs.copyFileSync(path.join(__dirname, 'build', 'icon.png'), path.join(appPaths[0], 'icon.png'));
|
||||
fs.symlinkSync(path.join(appPaths[0], 'icon.png'), path.join(appPaths[0], '.DirIcon'));
|
||||
fs.writeFileSync(path.join(appPaths[0], 'fchat.desktop'), '[Desktop Entry]\nName=F-Chat\nExec=AppRun\nIcon=icon\nType=Application\nCategories=GTK;GNOME;Utility;');
|
||||
require('axios').get('https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage', {responseType: 'stream'}).then((res) => {
|
||||
const downloaded = path.join(distDir, 'appimagetool.AppImage');
|
||||
res.data.pipe(fs.createWriteStream(downloaded));
|
||||
res.data.on('end', () => {
|
||||
const args = [appPaths[0], 'fchat.AppImage', '-u', 'zsync|https://client.f-list.net/fchat.AppImage.zsync'];
|
||||
if(process.argv.length > 2) args.push('-s', '--sign-key', process.argv[2]);
|
||||
else console.warn('Warning: Creating unsigned AppImage');
|
||||
if(process.argv.length > 3) args.push('--sign-args', `--passphrase=${process.argv[3]}`);
|
||||
child_process.spawn(downloaded, ['--appimage-extract'], {cwd: distDir}).on('close', () => {
|
||||
const child = child_process.spawn(path.join(distDir, 'squashfs-root', 'AppRun'), args, {cwd: distDir});
|
||||
child.stdout.on('data', (data) => console.log(data.toString()));
|
||||
child.stderr.on('data', (data) => console.error(data.toString()));
|
||||
});
|
||||
});
|
||||
}, (e) => console.error(`HTTP error: ${e.message}`));
|
||||
}
|
||||
}, (e) => console.log(`Error while packaging: ${e.message}`));
|
|
@ -1,38 +1,16 @@
|
|||
{
|
||||
"name": "fchat",
|
||||
"version": "3.0.0",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List.net Chat Client",
|
||||
"main": "main.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "node ../webpack development",
|
||||
"build:dist": "node ../webpack production",
|
||||
"watch": "node ../webpack watch",
|
||||
"start": "../node_modules/.bin/electron app"
|
||||
},
|
||||
"build": {
|
||||
"appId": "net.f-list.f-chat",
|
||||
"productName": "F-Chat",
|
||||
"files": [
|
||||
"*",
|
||||
"sounds",
|
||||
"themes",
|
||||
"!**/*.map",
|
||||
"!node_modules/",
|
||||
"node_modules/**/*.node"
|
||||
],
|
||||
"asar": false,
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true
|
||||
},
|
||||
"linux": {
|
||||
"category": "Network"
|
||||
},
|
||||
"publish": {
|
||||
"provider": "generic",
|
||||
"url": "https://client.f-list.net/"
|
||||
}
|
||||
}
|
||||
"name": "fchat",
|
||||
"version": "3.0.7",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List.net Chat Client",
|
||||
"main": "main.js",
|
||||
"id": "fchat",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "node ../webpack development",
|
||||
"build:dist": "node ../webpack production",
|
||||
"watch": "node ../webpack watch",
|
||||
"start": "../node_modules/.bin/electron app",
|
||||
"pack": "node ./pack"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
|||
const VueLoaderPlugin = require('vue-loader/lib/plugin');
|
||||
|
||||
const mainConfig = {
|
||||
entry: [path.join(__dirname, 'main.ts'), path.join(__dirname, 'application.json')],
|
||||
entry: [path.join(__dirname, 'main.ts'), path.join(__dirname, 'package.json')],
|
||||
output: {
|
||||
path: __dirname + '/app',
|
||||
filename: 'main.js'
|
||||
|
@ -23,7 +23,7 @@ const mainConfig = {
|
|||
transpileOnly: true
|
||||
}
|
||||
},
|
||||
{test: path.join(__dirname, 'application.json'), loader: 'file-loader?name=package.json', type: 'javascript/auto'},
|
||||
{test: path.join(__dirname, 'package.json'), loader: 'file-loader?name=package.json', type: 'javascript/auto'},
|
||||
{test: /\.(png|html)$/, loader: 'file-loader?name=[name].[ext]'}
|
||||
]
|
||||
},
|
||||
|
@ -45,7 +45,7 @@ const mainConfig = {
|
|||
}, rendererConfig = {
|
||||
entry: {
|
||||
chat: [path.join(__dirname, 'chat.ts'), path.join(__dirname, 'index.html')],
|
||||
window: [path.join(__dirname, 'window.ts'), path.join(__dirname, 'window.html')]
|
||||
window: [path.join(__dirname, 'window.ts'), path.join(__dirname, 'window.html'), path.join(__dirname, 'build', 'tray@2x.png')]
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/app',
|
||||
|
|
|
@ -42,6 +42,7 @@ export namespace Connection {
|
|||
RLL: {channel: string, dice: 'bottle' | string} | {recipient: string, dice: 'bottle' | string},
|
||||
RMO: {channel: string, mode: Channel.Mode},
|
||||
RST: {channel: string, status: 'public' | 'private'},
|
||||
SCP: {action: 'add' | 'remove', character: string}
|
||||
RWD: {character: string},
|
||||
SFC: {action: 'report', report: string, tab?: string, logid: number} | {action: 'confirm', callid: number},
|
||||
STA: {status: Character.Status, statusmsg: string},
|
||||
|
|
|
@ -8,12 +8,12 @@ android {
|
|||
applicationId "net.f_list.fchat"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 27
|
||||
versionCode 17
|
||||
versionName "3.0.6"
|
||||
versionCode 18
|
||||
versionName "3.0.7"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
package net.f_list.fchat
|
||||
|
||||
import android.content.Context
|
||||
import android.util.SparseArray
|
||||
import android.webkit.JavascriptInterface
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONStringer
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.RandomAccessFile
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.CharBuffer
|
||||
import java.util.*
|
||||
|
||||
class Logs(private val ctx: Context) {
|
||||
data class IndexItem(val name: String, val index: MutableMap<Int, Long> = HashMap(), val dates: MutableList<Int> = LinkedList())
|
||||
data class IndexItem(val name: String, val index: MutableMap<Int, Int> = LinkedHashMap(), val offsets: MutableList<Long> = ArrayList())
|
||||
|
||||
private var index: MutableMap<String, IndexItem>? = null
|
||||
private var loadedIndex: MutableMap<String, IndexItem>? = null
|
||||
|
@ -42,8 +44,8 @@ class Logs(private val ctx: Context) {
|
|||
buffer.limit(read)
|
||||
while(buffer.position() < buffer.limit()) {
|
||||
val key = buffer.short.toInt()
|
||||
indexItem.index[key] = buffer.int.toLong() or (buffer.get().toLong() shl 32)
|
||||
indexItem.dates.add(key)
|
||||
indexItem.index[key] = indexItem.offsets.size
|
||||
indexItem.offsets.add(buffer.int.toLong() or (buffer.get().toLong() shl 32))
|
||||
}
|
||||
index[file.nameWithoutExtension] = indexItem
|
||||
}
|
||||
|
@ -60,7 +62,7 @@ class Logs(private val ctx: Context) {
|
|||
loadedIndex = index
|
||||
val json = JSONStringer().`object`()
|
||||
for(item in index!!)
|
||||
json.key(item.key).`object`().key("name").value(item.value.name).key("dates").value(JSONArray(item.value.dates)).endObject()
|
||||
json.key(item.key).`object`().key("name").value(item.value.name).key("dates").value(JSONArray(item.value.index.keys)).endObject()
|
||||
return json.endObject().toString()
|
||||
}
|
||||
|
||||
|
@ -70,7 +72,7 @@ class Logs(private val ctx: Context) {
|
|||
val file = File(baseDir, key)
|
||||
buffer.clear()
|
||||
if(!index!!.containsKey(key)) {
|
||||
index!![key] = IndexItem(conversation, HashMap())
|
||||
index!![key] = IndexItem(conversation)
|
||||
buffer.position(1)
|
||||
encoder.encode(CharBuffer.wrap(conversation), buffer, true)
|
||||
buffer.put(0, (buffer.position() - 1).toByte())
|
||||
|
@ -79,10 +81,9 @@ class Logs(private val ctx: Context) {
|
|||
if(!item.index.containsKey(day)) {
|
||||
buffer.putShort(day.toShort())
|
||||
val size = file.length()
|
||||
item.index[day] = size
|
||||
item.dates.add(day)
|
||||
buffer.putInt((size and 0xffffffffL).toInt())
|
||||
buffer.put((size shr 32).toByte())
|
||||
item.index[day] = item.offsets.size
|
||||
item.offsets.add(size)
|
||||
buffer.putInt((size and 0xffffffffL).toInt()).put((size shr 32).toByte())
|
||||
FileOutputStream(File(baseDir, "$key.idx"), true).use { file ->
|
||||
buffer.flip()
|
||||
file.channel.write(buffer)
|
||||
|
@ -141,20 +142,22 @@ class Logs(private val ctx: Context) {
|
|||
|
||||
@JavascriptInterface
|
||||
fun getLogsN(character: String, key: String, date: Int): String {
|
||||
val offset = loadedIndex!![key]?.index?.get(date) ?: return "[]"
|
||||
val indexItem = loadedIndex!![key] ?: return "[]"
|
||||
val dateKey = indexItem.index[date] ?: return "[]"
|
||||
val json = JSONStringer()
|
||||
json.array()
|
||||
FileInputStream(File(ctx.filesDir, "$character/logs/$key")).use { stream ->
|
||||
val channel = stream.channel
|
||||
channel.position(offset)
|
||||
while(channel.position() < channel.size()) {
|
||||
buffer.clear()
|
||||
val oldPosition = channel.position()
|
||||
channel.read(buffer)
|
||||
buffer.rewind()
|
||||
deserializeMessage(buffer, json, date)
|
||||
if(buffer.position() == 0) break
|
||||
channel.position(oldPosition + buffer.position() + 2)
|
||||
val start = indexItem.offsets[dateKey]
|
||||
val end = if(dateKey >= indexItem.offsets.size - 1) channel.size() else indexItem.offsets[dateKey + 1]
|
||||
channel.position(start)
|
||||
val buffer = ByteBuffer.allocateDirect((end - start).toInt()).order(ByteOrder.LITTLE_ENDIAN)
|
||||
channel.read(buffer)
|
||||
buffer.rewind()
|
||||
while(buffer.position() < buffer.limit()) {
|
||||
deserializeMessage(buffer, json)
|
||||
buffer.limit(buffer.capacity())
|
||||
buffer.position(buffer.position() + 2)
|
||||
}
|
||||
}
|
||||
return json.endArray().toString()
|
||||
|
@ -165,7 +168,7 @@ class Logs(private val ctx: Context) {
|
|||
loadedIndex = if(character == this.character) this.index else this.loadIndex(character)
|
||||
val json = JSONStringer().`object`()
|
||||
for(item in loadedIndex!!)
|
||||
json.key(item.key).`object`().key("name").value(item.value.name).key("dates").value(JSONArray(item.value.dates)).endObject()
|
||||
json.key(item.key).`object`().key("name").value(item.value.name).key("dates").value(JSONArray(item.value.index.keys)).endObject()
|
||||
return json.endObject().toString()
|
||||
}
|
||||
|
||||
|
@ -174,22 +177,83 @@ class Logs(private val ctx: Context) {
|
|||
return JSONArray(ctx.filesDir.listFiles().filter { it.isDirectory }.map { it.name }).toString()
|
||||
}
|
||||
|
||||
private fun deserializeMessage(buffer: ByteBuffer, json: JSONStringer, checkDate: Int = -1) {
|
||||
val date = buffer.int
|
||||
if(checkDate != -1 && date / 86400 != checkDate) return
|
||||
@JavascriptInterface
|
||||
fun repair() {
|
||||
val files = baseDir.listFiles()
|
||||
val indexBuffer = ByteBuffer.allocateDirect(7).order(ByteOrder.LITTLE_ENDIAN)
|
||||
for(entry in files) {
|
||||
if(entry.name.endsWith(".idx")) continue
|
||||
RandomAccessFile("$entry.idx", "rw").use { idx ->
|
||||
buffer.clear()
|
||||
buffer.limit(1)
|
||||
idx.channel.read(buffer)
|
||||
idx.channel.truncate((buffer.get(0) + 1).toLong())
|
||||
idx.channel.position(idx.channel.size())
|
||||
RandomAccessFile(entry, "rw").use { file ->
|
||||
var lastDay = 0
|
||||
val size = file.channel.size()
|
||||
var pos = 0L
|
||||
try {
|
||||
while(file.channel.position() < size) {
|
||||
buffer.clear()
|
||||
pos = file.channel.position()
|
||||
val read = file.channel.read(buffer)
|
||||
var success = false
|
||||
buffer.flip()
|
||||
while(buffer.remaining() > 10) {
|
||||
val offset = buffer.position()
|
||||
val day = buffer.int / 86400
|
||||
buffer.get()
|
||||
val senderLength = buffer.get()
|
||||
if(buffer.remaining() < senderLength + 4) break
|
||||
buffer.limit(buffer.position() + senderLength)
|
||||
decoder.decode(buffer)
|
||||
buffer.limit(read)
|
||||
val textLength = buffer.short.toInt()
|
||||
if(buffer.remaining() < textLength + 2) break
|
||||
buffer.limit(buffer.position() + textLength)
|
||||
decoder.decode(buffer)
|
||||
buffer.limit(read)
|
||||
val messageSize = buffer.position() - offset
|
||||
if(messageSize != buffer.short.toInt()) throw Exception()
|
||||
|
||||
if(day > lastDay) {
|
||||
lastDay = day
|
||||
indexBuffer.position(0)
|
||||
indexBuffer.putShort(day.toShort())
|
||||
indexBuffer.putInt((pos and 0xffffffffL).toInt()).put((pos shr 32).toByte())
|
||||
indexBuffer.position(0)
|
||||
idx.channel.write(indexBuffer)
|
||||
}
|
||||
pos += messageSize + 2
|
||||
success = true
|
||||
}
|
||||
if(!success) throw Exception()
|
||||
file.channel.position(pos)
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
file.channel.truncate(pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deserializeMessage(buffer: ByteBuffer, json: JSONStringer) {
|
||||
val start = buffer.position()
|
||||
json.`object`()
|
||||
json.key("time")
|
||||
json.value(date)
|
||||
json.value(buffer.int)
|
||||
json.key("type")
|
||||
json.value(buffer.get())
|
||||
json.key("sender")
|
||||
val senderLength = buffer.get()
|
||||
buffer.limit(6 + senderLength)
|
||||
buffer.limit(start + 6 + senderLength)
|
||||
json.value(decoder.decode(buffer))
|
||||
buffer.limit(buffer.capacity())
|
||||
val textLength = buffer.short.toInt() and 0xffff
|
||||
json.key("text")
|
||||
buffer.limit(8 + senderLength + textLength)
|
||||
buffer.limit(start + 8 + senderLength + textLength)
|
||||
json.value(decoder.decode(buffer))
|
||||
json.endObject()
|
||||
}
|
||||
|
|
|
@ -30,9 +30,7 @@
|
|||
* @see {@link https://github.com/f-list/exported|GitHub repo}
|
||||
*/
|
||||
import Axios from 'axios';
|
||||
import * as Raven from 'raven-js';
|
||||
import Vue from 'vue';
|
||||
import VueRaven from '../chat/vue-raven';
|
||||
import {setupRaven} from '../chat/vue-raven';
|
||||
import Index from './Index.vue';
|
||||
|
||||
const version = (<{version: string}>require('./package.json')).version; //tslint:disable-line:no-require-imports
|
||||
|
@ -40,23 +38,8 @@ const version = (<{version: string}>require('./package.json')).version; //tslint
|
|||
Axios.defaults.params = { __fchat: `mobile-${platform}/${version}` };
|
||||
};
|
||||
|
||||
if(process.env.NODE_ENV === 'production') {
|
||||
Raven.config('https://a9239b17b0a14f72ba85e8729b9d1612@sentry.f-list.net/2', {
|
||||
release: `mobile-${version}`,
|
||||
dataCallback: (data: {culprit: string, exception?: {values: {stacktrace: {frames: {filename: string}[]}}[]}}) => {
|
||||
data.culprit = `~${data.culprit.substr(data.culprit.lastIndexOf('/'))}`;
|
||||
if(data.exception !== undefined)
|
||||
for(const ex of data.exception.values)
|
||||
for(const frame of ex.stacktrace.frames) {
|
||||
const index = frame.filename.lastIndexOf('/');
|
||||
frame.filename = index !== -1 ? `~${frame.filename.substr(index)}` : frame.filename;
|
||||
}
|
||||
}
|
||||
}).addPlugin(VueRaven, Vue).install();
|
||||
(<Window & {onunhandledrejection(e: PromiseRejectionEvent): void}>window).onunhandledrejection = (e: PromiseRejectionEvent) => {
|
||||
Raven.captureException(<Error>e.reason);
|
||||
};
|
||||
}
|
||||
if(process.env.NODE_ENV === 'production')
|
||||
setupRaven('https://a9239b17b0a14f72ba85e8729b9d1612@sentry.f-list.net/2', `mobile-${version}`);
|
||||
|
||||
new Index({ //tslint:disable-line:no-unused-expression
|
||||
el: '#app'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {Message as MessageImpl} from '../chat/common';
|
||||
import core from '../chat/core';
|
||||
import {Conversation, Logs as Logging, Settings} from '../chat/interfaces';
|
||||
import l from '../chat/localize';
|
||||
|
||||
declare global {
|
||||
const NativeFile: {
|
||||
|
@ -20,6 +21,7 @@ declare global {
|
|||
message: string): Promise<void>;
|
||||
getBacklog(key: string): Promise<ReadonlyArray<NativeMessage>>;
|
||||
getLogs(character: string, key: string, date: number): Promise<ReadonlyArray<NativeMessage>>
|
||||
repair(character: string): Promise<void>
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -41,10 +43,16 @@ export class Logs implements Logging {
|
|||
private index: Index = {};
|
||||
private loadedIndex?: Index;
|
||||
private loadedCharacter?: string;
|
||||
attemptedFix = false;
|
||||
|
||||
constructor() {
|
||||
core.connection.onEvent('connecting', async() => {
|
||||
this.index = await NativeLogs.init(core.connection.character);
|
||||
this.attemptedFix = false;
|
||||
try {
|
||||
this.index = await NativeLogs.init(core.connection.character);
|
||||
} catch {
|
||||
await this.fixLogs(core.connection.character);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -59,20 +67,35 @@ export class Logs implements Logging {
|
|||
}
|
||||
|
||||
async getBacklog(conversation: Conversation): Promise<ReadonlyArray<Conversation.Message>> {
|
||||
return (await NativeLogs.getBacklog(conversation.key))
|
||||
.map((x) => new MessageImpl(x.type, core.characters.get(x.sender), x.text, new Date(x.time * 1000)));
|
||||
try {
|
||||
return (await NativeLogs.getBacklog(conversation.key))
|
||||
.map((x) => new MessageImpl(x.type, core.characters.get(x.sender), x.text, new Date(x.time * 1000)));
|
||||
} catch {
|
||||
await this.fixLogs(this.loadedCharacter!);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private async getIndex(name: string): Promise<Index> {
|
||||
if(this.loadedCharacter === name) return this.loadedIndex!;
|
||||
this.loadedCharacter = name;
|
||||
return this.loadedIndex = name === core.connection.character ? this.index : await NativeLogs.loadIndex(name);
|
||||
try {
|
||||
return this.loadedIndex = name === core.connection.character ? this.index : await NativeLogs.loadIndex(name);
|
||||
} catch {
|
||||
await this.fixLogs(name);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async getLogs(character: string, key: string, date: Date): Promise<ReadonlyArray<Conversation.Message>> {
|
||||
await NativeLogs.loadIndex(character);
|
||||
return (await NativeLogs.getLogs(character, key, Math.floor(date.getTime() / dayMs - date.getTimezoneOffset() / 1440)))
|
||||
.map((x) => new MessageImpl(x.type, core.characters.get(x.sender), x.text, new Date(x.time * 1000)));
|
||||
try {
|
||||
await NativeLogs.loadIndex(character);
|
||||
return (await NativeLogs.getLogs(character, key, Math.floor(date.getTime() / dayMs - date.getTimezoneOffset() / 1440)))
|
||||
.map((x) => new MessageImpl(x.type, core.characters.get(x.sender), x.text, new Date(x.time * 1000)));
|
||||
} catch {
|
||||
await this.fixLogs(character);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getLogDates(character: string, key: string): Promise<ReadonlyArray<Date>> {
|
||||
|
@ -94,6 +117,19 @@ export class Logs implements Logging {
|
|||
async getAvailableCharacters(): Promise<ReadonlyArray<string>> {
|
||||
return NativeLogs.getCharacters();
|
||||
}
|
||||
|
||||
async fixLogs(character: string): Promise<void> {
|
||||
if(this.attemptedFix) return alert(l('logs.corruption.mobile.error'));
|
||||
this.attemptedFix = true;
|
||||
alert(l('logs.corruption.mobile'));
|
||||
try {
|
||||
await NativeLogs.repair(character);
|
||||
this.index = await NativeLogs.init(core.connection.character);
|
||||
alert(l('logs.corruption.mobile.success'));
|
||||
} catch {
|
||||
alert(l('logs.corruption.mobile.error'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getGeneralSettings(): Promise<GeneralSettings | undefined> {
|
||||
|
|
|
@ -3,13 +3,18 @@ import WebKit
|
|||
|
||||
class IndexItem: Encodable {
|
||||
let name: String
|
||||
var index = NSMutableOrderedSet()
|
||||
var dates = [UInt16]()
|
||||
var dates = NSMutableOrderedSet()
|
||||
var offsets = [UInt64]()
|
||||
init(_ name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(name, forKey: .name)
|
||||
try container.encode(dates.array as! [UInt16], forKey: .dates)
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case dates
|
||||
|
@ -19,7 +24,7 @@ class IndexItem: Encodable {
|
|||
class Logs: NSObject, WKScriptMessageHandler {
|
||||
let fm = FileManager.default;
|
||||
let baseDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||
var buffer = UnsafeMutableRawPointer.allocate(bytes: 51000, alignedTo: 1)
|
||||
var buffer = UnsafeMutableRawPointer.allocate(byteCount: 51000, alignment: 1)
|
||||
var logDir: URL!
|
||||
var character: String?
|
||||
var index: [String: IndexItem]!
|
||||
|
@ -43,6 +48,8 @@ class Logs: NSObject, WKScriptMessageHandler {
|
|||
result = try getBacklog(data["key"] as! String)
|
||||
case "getLogs":
|
||||
result = try getLogs(data["character"] as! String, data["key"] as! String, (data["date"] as! NSNumber).uint16Value)
|
||||
case "repair":
|
||||
try repair(data["character"] as! String)
|
||||
default:
|
||||
message.webView!.evaluateJavaScript("nativeError('\(key)',new Error('Unknown message type'))")
|
||||
return
|
||||
|
@ -65,13 +72,13 @@ class Logs: NSObject, WKScriptMessageHandler {
|
|||
let name = String(data: data.subdata(with: NSMakeRange(1, nameLength)), encoding: .utf8)!
|
||||
var offset = nameLength + 1
|
||||
let indexItem = IndexItem(name)
|
||||
if (data.length - offset) % 7 != 0 { throw NSError(domain: "Log corruption", code: 0) }
|
||||
while offset < data.length {
|
||||
var date: UInt16 = 0
|
||||
data.getBytes(&date, range: NSMakeRange(offset, 2))
|
||||
indexItem.dates.append(date)
|
||||
var o: UInt64 = 0
|
||||
data.getBytes(&o, range: NSMakeRange(offset + 2, 5))
|
||||
indexItem.index.add(date)
|
||||
indexItem.dates.add(date)
|
||||
indexItem.offsets.append(o)
|
||||
offset += 7
|
||||
}
|
||||
|
@ -85,6 +92,7 @@ class Logs: NSObject, WKScriptMessageHandler {
|
|||
try fm.createDirectory(at: logDir, withIntermediateDirectories: true, attributes: nil)
|
||||
index = try getIndex(name)
|
||||
loadedIndex = index
|
||||
character = name
|
||||
return String(data: try JSONEncoder().encode(index), encoding: .utf8)!
|
||||
}
|
||||
|
||||
|
@ -104,7 +112,7 @@ class Logs: NSObject, WKScriptMessageHandler {
|
|||
if(indexItem == nil) { fm.createFile(atPath: url.path, contents: nil) }
|
||||
let fd = try FileHandle(forWritingTo: url)
|
||||
fd.seekToEndOfFile()
|
||||
if(!(indexItem?.index.contains(day) ?? false)) {
|
||||
if(!(indexItem?.dates.contains(day) ?? false)) {
|
||||
let indexFile = url.appendingPathExtension("idx")
|
||||
if(indexItem == nil) { fm.createFile(atPath: indexFile.path, contents: nil) }
|
||||
let indexFd = try FileHandle(forWritingTo: indexFile)
|
||||
|
@ -120,9 +128,8 @@ class Logs: NSObject, WKScriptMessageHandler {
|
|||
write(indexFd.fileDescriptor, &day, 2)
|
||||
var offset = fd.offsetInFile
|
||||
write(indexFd.fileDescriptor, &offset, 5)
|
||||
indexItem!.index.add(indexItem!.offsets.count)
|
||||
indexItem!.dates.add(day)
|
||||
indexItem!.offsets.append(offset)
|
||||
indexItem!.dates.append(day)
|
||||
}
|
||||
let start = fd.offsetInFile
|
||||
write(fd.fileDescriptor, &time, 4)
|
||||
|
@ -150,6 +157,7 @@ class Logs: NSObject, WKScriptMessageHandler {
|
|||
file.seek(toFileOffset: file.offsetInFile - 2)
|
||||
read(file.fileDescriptor, buffer, 2)
|
||||
let length = buffer.load(as: UInt16.self)
|
||||
if(length > file.offsetInFile - 2) { throw NSError(domain: "Log corruption", code: 0) }
|
||||
let newOffset = file.offsetInFile - UInt64(length + 2)
|
||||
file.seek(toFileOffset: newOffset)
|
||||
read(file.fileDescriptor, buffer, Int(length))
|
||||
|
@ -161,14 +169,14 @@ class Logs: NSObject, WKScriptMessageHandler {
|
|||
|
||||
func getLogs(_ character: String, _ key: String, _ date: UInt16) throws -> String {
|
||||
let index = loadedIndex![key]
|
||||
guard let indexKey = index?.index.index(of: date) else { return "[]" }
|
||||
guard let indexKey = index?.dates.index(of: date) else { return "[]" }
|
||||
let url = baseDir.appendingPathComponent("\(character)/logs/\(key)", isDirectory: false)
|
||||
let file = try FileHandle(forReadingFrom: url)
|
||||
let start = index!.offsets[indexKey]
|
||||
let end = indexKey >= index!.offsets.count - 1 ? file.seekToEndOfFile() : index!.offsets[indexKey + 1]
|
||||
file.seek(toFileOffset: start)
|
||||
let length = Int(end - start)
|
||||
let buffer = UnsafeMutableRawPointer.allocate(bytes: length, alignedTo: 1)
|
||||
let buffer = UnsafeMutableRawPointer.allocate(byteCount: length, alignment: 1)
|
||||
read(file.fileDescriptor, buffer, length)
|
||||
var json = "["
|
||||
var offset = 0
|
||||
|
@ -185,19 +193,67 @@ class Logs: NSObject, WKScriptMessageHandler {
|
|||
return String(data: try JSONEncoder().encode(loadedIndex), encoding: .utf8)!
|
||||
}
|
||||
|
||||
func decodeString(_ buffer: UnsafeMutableRawPointer, _ offset: Int, _ length: Int) -> String? {
|
||||
return String(bytesNoCopy: buffer.advanced(by: offset), length: length, encoding: .utf8, freeWhenDone: false)
|
||||
}
|
||||
|
||||
func deserializeMessage(_ buffer: UnsafeMutableRawPointer, _ o: Int) throws -> (String, Int) {
|
||||
var offset = o
|
||||
let date = buffer.advanced(by: offset).bindMemory(to: UInt32.self, capacity: 1).pointee
|
||||
let type = buffer.load(fromByteOffset: offset + 4, as: UInt8.self)
|
||||
let senderLength = Int(buffer.load(fromByteOffset: offset + 5, as: UInt8.self))
|
||||
guard let sender = String(bytesNoCopy: buffer.advanced(by: offset + 6), length: senderLength, encoding: .utf8, freeWhenDone: false) else {
|
||||
guard let sender = decodeString(buffer, offset + 6, senderLength) else {
|
||||
throw NSError(domain: "Log corruption", code: 0)
|
||||
}
|
||||
offset += senderLength + 6
|
||||
let textLength = Int(buffer.advanced(by: offset).bindMemory(to: UInt16.self, capacity: 1).pointee)
|
||||
guard let text = String(bytesNoCopy: buffer.advanced(by: offset + 2), length: textLength, encoding: .utf8, freeWhenDone: false) else {
|
||||
guard let text = decodeString(buffer, offset + 2, textLength) else {
|
||||
throw NSError(domain: "Log corruption", code: 0)
|
||||
}
|
||||
return ("{\"time\":\(date),\"type\":\(type),\"sender\":\(File.escape(sender)),\"text\":\(File.escape(text))}", offset + textLength + 2)
|
||||
}
|
||||
|
||||
func repair(_ character: String) throws {
|
||||
let files = try fm.contentsOfDirectory(at: baseDir.appendingPathComponent("\(character)/logs", isDirectory: true), includingPropertiesForKeys: nil, options: [.skipsHiddenFiles])
|
||||
for file in files {
|
||||
if(file.lastPathComponent.hasSuffix(".idx")) { continue }
|
||||
let indexFd = try FileHandle(forUpdating: file.appendingPathExtension("idx"))
|
||||
read(indexFd.fileDescriptor, buffer, 1)
|
||||
indexFd.truncateFile(atOffset: UInt64(buffer.load(as: UInt8.self) + 1))
|
||||
let fd = try FileHandle(forUpdating: file)
|
||||
let size = fd.seekToEndOfFile()
|
||||
fd.seek(toFileOffset: 0)
|
||||
var lastDay = 0, pos = UInt64(0)
|
||||
do {
|
||||
while fd.offsetInFile < size {
|
||||
pos = fd.offsetInFile
|
||||
let max = read(fd.fileDescriptor, buffer, 51000)
|
||||
var offset = 0
|
||||
while offset + 10 < max {
|
||||
let day = buffer.advanced(by: offset).bindMemory(to: UInt32.self, capacity: 1).pointee / 86400
|
||||
let senderLength = Int(buffer.load(fromByteOffset: offset + 5, as: UInt8.self))
|
||||
if offset + senderLength + 10 > max { break }
|
||||
let sender = decodeString(buffer, offset + 6, senderLength)
|
||||
let textLength = Int(buffer.advanced(by: offset + senderLength + 6).bindMemory(to: UInt16.self, capacity: 1).pointee)
|
||||
if(offset + senderLength + textLength + 10 > max) { break }
|
||||
let text = decodeString(buffer, offset + senderLength + 8, textLength)
|
||||
let mark = senderLength + textLength + 8
|
||||
let size = buffer.advanced(by: offset + mark).bindMemory(to: UInt16.self, capacity: 1).pointee
|
||||
if(size != mark || sender == nil || text == nil) { throw NSError(domain: "", code: 0) }
|
||||
if(day > lastDay) {
|
||||
lastDay = Int(day)
|
||||
write(indexFd.fileDescriptor, &lastDay, 2)
|
||||
write(indexFd.fileDescriptor, &pos, 5)
|
||||
}
|
||||
offset = offset + mark + 2
|
||||
pos = pos + UInt64(mark + 2)
|
||||
}
|
||||
if(offset == 0) { throw NSError(domain: "", code: 0) }
|
||||
fd.seek(toFileOffset: pos)
|
||||
}
|
||||
} catch {
|
||||
fd.truncateFile(atOffset: pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -77,5 +77,8 @@ window.NativeLogs = {
|
|||
},
|
||||
getCharacters: function() {
|
||||
return sendMessage('Logs', 'getCharacters', {});
|
||||
},
|
||||
repair: function(character) {
|
||||
return sendMessage('Logs', 'repair', {character: character});
|
||||
}
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "net.f_list.fchat",
|
||||
"version": "3.0.6",
|
||||
"version": "3.0.7",
|
||||
"displayName": "F-Chat",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List.net Chat Client",
|
||||
|
|
96
package.json
96
package.json
|
@ -1,46 +1,54 @@
|
|||
{
|
||||
"name": "flist-exported",
|
||||
"version": "1.0.0",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List Exported",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free-webfonts": "^1.0.6",
|
||||
"@types/node": "^10.3.3",
|
||||
"@types/sortablejs": "^1.3.31",
|
||||
"axios": "^0.18.0",
|
||||
"bootstrap": "^4.1.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"date-fns": "^1.28.5",
|
||||
"electron": "^2.0.2",
|
||||
"electron-builder": "^20.8.1",
|
||||
"electron-log": "^2.2.9",
|
||||
"electron-updater": "^2.21.4",
|
||||
"extract-text-webpack-plugin": "4.0.0-beta.0",
|
||||
"file-loader": "^1.1.10",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.1",
|
||||
"lodash": "^4.16.4",
|
||||
"node-sass": "^4.8.3",
|
||||
"optimize-css-assets-webpack-plugin": "^4.0.0",
|
||||
"qs": "^6.5.1",
|
||||
"raven-js": "^3.24.1",
|
||||
"sass-loader": "^7.0.1",
|
||||
"sortablejs": "^1.6.0",
|
||||
"ts-loader": "^4.2.0",
|
||||
"tslib": "^1.7.1",
|
||||
"tslint": "^5.7.0",
|
||||
"typescript": "^2.8.1",
|
||||
"vue": "^2.5.16",
|
||||
"vue-class-component": "^6.0.0",
|
||||
"vue-loader": "^15.2.4",
|
||||
"vue-property-decorator": "^6.0.0",
|
||||
"vue-template-compiler": "^2.5.16",
|
||||
"webpack": "^4.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "^4.14.107",
|
||||
"keytar": "^4.2.1",
|
||||
"spellchecker": "^3.4.3",
|
||||
"style-loader": "^0.21.0"
|
||||
}
|
||||
"name": "flist-exported",
|
||||
"version": "1.0.0",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List Exported",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free-webfonts": "^1.0.6",
|
||||
"@types/lodash": "^4.14.116",
|
||||
"@types/node": "^10.5.6",
|
||||
"@types/sortablejs": "^1.3.31",
|
||||
"axios": "^0.18.0",
|
||||
"bootstrap": "^4.1.3",
|
||||
"css-loader": "^1.0.0",
|
||||
"date-fns": "^1.28.5",
|
||||
"electron": "2.0.2",
|
||||
"electron-log": "^2.2.16",
|
||||
"electron-packager": "^12.1.0",
|
||||
"electron-rebuild": "^1.8.2",
|
||||
"extract-text-webpack-plugin": "4.0.0-beta.0",
|
||||
"file-loader": "^1.1.10",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.4",
|
||||
"lodash": "^4.16.4",
|
||||
"node-sass": "^4.8.3",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.0",
|
||||
"qs": "^6.5.1",
|
||||
"raven-js": "^3.26.4",
|
||||
"sass-loader": "^7.1.0",
|
||||
"sortablejs": "^1.6.0",
|
||||
"style-loader": "^0.21.0",
|
||||
"ts-loader": "^4.2.0",
|
||||
"tslib": "^1.7.1",
|
||||
"tslint": "^5.7.0",
|
||||
"typescript": "^3.0.1",
|
||||
"vue": "^2.5.17",
|
||||
"vue-class-component": "^6.0.0",
|
||||
"vue-loader": "^15.2.6",
|
||||
"vue-property-decorator": "^7.0.0",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"webpack": "^4.16.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"keytar": "^4.2.1",
|
||||
"spellchecker": "^3.4.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"appdmg": "^0.5.2",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"electron-winstaller": "^2.6.4"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "electron-rebuild -o spellchecker,keytar"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,13 +12,15 @@ All necessary files to build F-Chat 3.0 as an Electron, mobile or web applicatio
|
|||
- To build native Node assets, you will need to install Python 2.7 and the Visual C++ 2015 Build tools. [More information can be found in the node-gyp docs.](https://github.com/nodejs/node-gyp#installation)
|
||||
- Change into the `electron` directory.
|
||||
- Run `yarn build`/`yarn watch` to build assets. They are placed into the `app` directory.
|
||||
- You will probably need to rebuild the native dependencies (`spellchecker` and `keytar`) for electron. To do so, run `npm rebuild {NAME} --target={ELECTRON_VERSION} --arch=x64 --dist-url=https://atom.io/download/electron`. [See the electron documentation for more info.](https://github.com/electron/electron/blob/master/docs/tutorial/using-native-node-modules.md)
|
||||
- Run `yarn start` to start the app in debug mode. Use `Ctrl+Shift+I` to open the Chromium debugger.
|
||||
|
||||
### Packaging
|
||||
See https://electron.atom.io/docs/tutorial/application-distribution/
|
||||
- Run `yarn build:dist` to create a minified production build.
|
||||
- Run `./node_modules/.bin/electron-builder` with [options specifying the platform you want to build for](https://www.electron.build/cli).
|
||||
- Run `yarn pack`. The generated installer is placed into the `dist` directory.
|
||||
- On Windows you can add the path to and password for a code signing certificate as arguments.
|
||||
- On Mac you can add your code signing identity as an argument. `zip` is required to be installed.
|
||||
- On Linux you can add a GPG key for signing as an argument. `mksquashfs` and `zsyncmake` are required to be installed.
|
||||
|
||||
## Building for Mobile
|
||||
- Change into the `mobile` directory.
|
||||
|
|
|
@ -280,4 +280,8 @@ $genders: (
|
|||
border-radius: 100%;
|
||||
line-height: 0;
|
||||
box-shadow: 0 1px 4px #000;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
cursor: pointer;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
$blue-color: #06f;
|
||||
|
||||
.blackText {
|
||||
text-shadow: $gray-600 1px 1px 1px, $gray-600 -1px 1px 1px, $gray-600 1px -1px 1px, $gray-600 -1px -1px 1px;
|
||||
text-shadow: $gray-600 1px 1px, $gray-600 -1px 1px, $gray-600 1px -1px, $gray-600 -1px -1px;
|
||||
}
|
|
@ -59,7 +59,4 @@ $pagination-active-color: $link-color;
|
|||
$text-background-color: $gray-100;
|
||||
$text-background-color-disabled: $gray-200;
|
||||
|
||||
// Dark theme helpers
|
||||
$theme-is-dark: true;
|
||||
|
||||
@import "invert";
|
|
@ -1,9 +1,9 @@
|
|||
.purpleText {
|
||||
text-shadow: #306 1px 1px 1px, #306 -1px 1px 1px, #306 1px -1px 1px, #306 -1px -1px 1px;
|
||||
text-shadow: #306 1px 1px, #306 -1px 1px, #306 1px -1px, #306 -1px -1px;
|
||||
}
|
||||
|
||||
.blackText {
|
||||
text-shadow: $gray-600 1px 1px 1px, $gray-600 -1px 1px 1px, $gray-600 1px -1px 1px, $gray-600 -1px -1px 1px;
|
||||
text-shadow: $gray-600 1px 1px, $gray-600 -1px 1px, $gray-600 1px -1px, $gray-600 -1px -1px;
|
||||
}
|
||||
|
||||
$blue-color: #06f;
|
|
@ -57,7 +57,4 @@ $pagination-active-color: $link-color;
|
|||
$text-background-color: $gray-200;
|
||||
$text-background-color-disabled: $gray-100;
|
||||
|
||||
// Dark theme helpers
|
||||
$theme-is-dark: true;
|
||||
|
||||
@import "invert";
|
|
@ -15,6 +15,5 @@
|
|||
}
|
||||
|
||||
// Alert color levels
|
||||
$alert-bg-level: 7;
|
||||
$alert-border-level: 6;
|
||||
$alert-color-level: -8;
|
||||
$alert-border-level: 4;
|
||||
$theme-is-dark: true;
|
423
scss/yarn.lock
423
scss/yarn.lock
|
@ -3,14 +3,14 @@
|
|||
|
||||
|
||||
"@fortawesome/fontawesome-free-webfonts@^1.0.3":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free-webfonts/-/fontawesome-free-webfonts-1.0.4.tgz#bac5d89755bf3bc2d2b4deee47d92febf641bb1f"
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free-webfonts/-/fontawesome-free-webfonts-1.0.9.tgz#72f2c10453422aba0d338fa6a9cb761b50ba24d5"
|
||||
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
|
||||
ajv@^5.1.0:
|
||||
ajv@^5.1.0, ajv@^5.3.0:
|
||||
version "5.5.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
|
||||
dependencies:
|
||||
|
@ -27,6 +27,10 @@ ansi-regex@^2.0.0:
|
|||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
|
||||
|
||||
ansi-regex@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
||||
|
||||
ansi-styles@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||
|
@ -36,8 +40,8 @@ aproba@^1.0.3:
|
|||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
|
||||
|
||||
are-we-there-yet@~1.1.2:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d"
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
|
||||
dependencies:
|
||||
delegates "^1.0.0"
|
||||
readable-stream "^2.0.6"
|
||||
|
@ -47,17 +51,15 @@ array-find-index@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
|
||||
|
||||
asn1@~0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||
dependencies:
|
||||
safer-buffer "~2.1.0"
|
||||
|
||||
assert-plus@1.0.0, assert-plus@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||
|
||||
assert-plus@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
|
||||
|
||||
async-foreach@^0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
|
||||
|
@ -66,25 +68,21 @@ asynckit@^0.4.0:
|
|||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
|
||||
aws-sign2@~0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
|
||||
|
||||
aws-sign2@~0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||
|
||||
aws4@^1.2.1, aws4@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
|
||||
aws4@^1.6.0, aws4@^1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
|
@ -94,27 +92,9 @@ block-stream@*:
|
|||
dependencies:
|
||||
inherits "~2.0.0"
|
||||
|
||||
boom@2.x.x:
|
||||
version "2.10.1"
|
||||
resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
|
||||
dependencies:
|
||||
hoek "2.x.x"
|
||||
|
||||
boom@4.x.x:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
|
||||
dependencies:
|
||||
hoek "4.x.x"
|
||||
|
||||
boom@5.x.x:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
|
||||
dependencies:
|
||||
hoek "4.x.x"
|
||||
|
||||
bootstrap@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.0.0.tgz#ceb03842c145fcc1b9b4e15da2a05656ba68469a"
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.3.tgz#0eb371af2c8448e8c210411d0cb824a6409a12be"
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
|
@ -142,10 +122,6 @@ camelcase@^3.0.0:
|
|||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
|
||||
|
||||
caseless@~0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
|
||||
|
||||
caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
|
@ -176,16 +152,12 @@ code-point-at@^1.0.0:
|
|||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||
|
||||
combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5:
|
||||
combined-stream@1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818"
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@^2.9.0:
|
||||
version "2.14.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
|
@ -205,18 +177,6 @@ cross-spawn@^3.0.0:
|
|||
lru-cache "^4.0.1"
|
||||
which "^1.2.9"
|
||||
|
||||
cryptiles@2.x.x:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
|
||||
dependencies:
|
||||
boom "2.x.x"
|
||||
|
||||
cryptiles@3.x.x:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
|
||||
dependencies:
|
||||
boom "5.x.x"
|
||||
|
||||
currently-unhandled@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
|
||||
|
@ -242,14 +202,15 @@ delegates@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||
|
||||
ecc-jsbn@~0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||
dependencies:
|
||||
jsbn "~0.1.0"
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
error-ex@^1.2.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
|
||||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
|
@ -257,9 +218,9 @@ escape-string-regexp@^1.0.2:
|
|||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
|
||||
extend@~3.0.0, extend@~3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
|
||||
extend@~3.0.1, extend@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||
|
||||
extsprintf@1.3.0:
|
||||
version "1.3.0"
|
||||
|
@ -288,15 +249,7 @@ forever-agent@~0.6.1:
|
|||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
|
||||
form-data@~2.1.1:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.5"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@~2.3.1:
|
||||
form-data@~2.3.1, form-data@~2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099"
|
||||
dependencies:
|
||||
|
@ -331,24 +284,14 @@ gauge@~2.7.3:
|
|||
wide-align "^1.1.0"
|
||||
|
||||
gaze@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.2.tgz#847224677adb8870d679257ed3388fdb61e40105"
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a"
|
||||
dependencies:
|
||||
globule "^1.0.0"
|
||||
|
||||
generate-function@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74"
|
||||
|
||||
generate-object-property@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
|
||||
dependencies:
|
||||
is-property "^1.0.0"
|
||||
|
||||
get-caller-file@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
|
||||
|
||||
get-stdin@^4.0.1:
|
||||
version "4.0.1"
|
||||
|
@ -382,11 +325,11 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@~7.1.1:
|
|||
path-is-absolute "^1.0.0"
|
||||
|
||||
globule@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09"
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d"
|
||||
dependencies:
|
||||
glob "~7.1.1"
|
||||
lodash "~4.17.4"
|
||||
lodash "~4.17.10"
|
||||
minimatch "~3.0.2"
|
||||
|
||||
graceful-fs@^4.1.2:
|
||||
|
@ -397,15 +340,6 @@ har-schema@^2.0.0:
|
|||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
||||
|
||||
har-validator@~2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d"
|
||||
dependencies:
|
||||
chalk "^1.1.1"
|
||||
commander "^2.9.0"
|
||||
is-my-json-valid "^2.12.4"
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
har-validator@~5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
|
||||
|
@ -413,6 +347,13 @@ har-validator@~5.0.3:
|
|||
ajv "^5.1.0"
|
||||
har-schema "^2.0.0"
|
||||
|
||||
har-validator@~5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29"
|
||||
dependencies:
|
||||
ajv "^5.3.0"
|
||||
har-schema "^2.0.0"
|
||||
|
||||
has-ansi@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
|
||||
|
@ -423,43 +364,9 @@ has-unicode@^2.0.0:
|
|||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
||||
|
||||
hawk@~3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
|
||||
dependencies:
|
||||
boom "2.x.x"
|
||||
cryptiles "2.x.x"
|
||||
hoek "2.x.x"
|
||||
sntp "1.x.x"
|
||||
|
||||
hawk@~6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
|
||||
dependencies:
|
||||
boom "4.x.x"
|
||||
cryptiles "3.x.x"
|
||||
hoek "4.x.x"
|
||||
sntp "2.x.x"
|
||||
|
||||
hoek@2.x.x:
|
||||
version "2.16.3"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
|
||||
|
||||
hoek@4.x.x:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
|
||||
|
||||
http-signature@~1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
|
||||
dependencies:
|
||||
assert-plus "^0.2.0"
|
||||
jsprim "^1.2.2"
|
||||
sshpk "^1.7.0"
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047"
|
||||
|
||||
http-signature@~1.2.0:
|
||||
version "1.2.0"
|
||||
|
@ -516,23 +423,9 @@ is-fullwidth-code-point@^1.0.0:
|
|||
dependencies:
|
||||
number-is-nan "^1.0.0"
|
||||
|
||||
is-my-ip-valid@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824"
|
||||
|
||||
is-my-json-valid@^2.12.4:
|
||||
version "2.17.2"
|
||||
resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c"
|
||||
dependencies:
|
||||
generate-function "^2.0.0"
|
||||
generate-object-property "^1.1.0"
|
||||
is-my-ip-valid "^1.0.0"
|
||||
jsonpointer "^4.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
is-property@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
|
||||
is-fullwidth-code-point@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
|
||||
|
||||
is-typedarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
|
@ -555,8 +448,8 @@ isstream@~0.1.2:
|
|||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
|
||||
js-base64@^2.1.8:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582"
|
||||
version "2.4.8"
|
||||
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.8.tgz#57a9b130888f956834aa40c5b165ba59c758f033"
|
||||
|
||||
jsbn@~0.1.0:
|
||||
version "0.1.1"
|
||||
|
@ -574,10 +467,6 @@ json-stringify-safe@~5.0.1:
|
|||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||
|
||||
jsonpointer@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
|
||||
|
||||
jsprim@^1.2.2:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
||||
|
@ -615,9 +504,9 @@ lodash.mergewith@^4.6.0:
|
|||
version "4.6.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
|
||||
|
||||
lodash@^4.0.0, lodash@~4.17.4:
|
||||
version "4.17.5"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
|
||||
lodash@^4.0.0, lodash@~4.17.10:
|
||||
version "4.17.10"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
|
||||
|
||||
loud-rejection@^1.0.0:
|
||||
version "1.6.0"
|
||||
|
@ -627,8 +516,8 @@ loud-rejection@^1.0.0:
|
|||
signal-exit "^3.0.0"
|
||||
|
||||
lru-cache@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
|
||||
dependencies:
|
||||
pseudomap "^1.0.2"
|
||||
yallist "^2.1.2"
|
||||
|
@ -652,17 +541,17 @@ meow@^3.7.0:
|
|||
redent "^1.0.0"
|
||||
trim-newlines "^1.0.0"
|
||||
|
||||
mime-db@~1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
|
||||
mime-db@~1.35.0:
|
||||
version "1.35.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.35.0.tgz#0569d657466491283709663ad379a99b90d9ab47"
|
||||
|
||||
mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7:
|
||||
version "2.1.18"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
|
||||
mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19:
|
||||
version "2.1.19"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.19.tgz#71e464537a7ef81c15f2db9d97e913fc0ff606f0"
|
||||
dependencies:
|
||||
mime-db "~1.33.0"
|
||||
mime-db "~1.35.0"
|
||||
|
||||
"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
|
||||
"minimatch@2 || 3", minimatch@^3.0.4, minimatch@~3.0.2:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
dependencies:
|
||||
|
@ -682,31 +571,30 @@ minimist@^1.1.3:
|
|||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
nan@^2.3.2:
|
||||
version "2.9.2"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.9.2.tgz#f564d75f5f8f36a6d9456cca7a6c4fe488ab7866"
|
||||
nan@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
|
||||
|
||||
node-gyp@^3.3.1:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60"
|
||||
node-gyp@^3.8.0:
|
||||
version "3.8.0"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
|
||||
dependencies:
|
||||
fstream "^1.0.0"
|
||||
glob "^7.0.3"
|
||||
graceful-fs "^4.1.2"
|
||||
minimatch "^3.0.2"
|
||||
mkdirp "^0.5.0"
|
||||
nopt "2 || 3"
|
||||
npmlog "0 || 1 || 2 || 3 || 4"
|
||||
osenv "0"
|
||||
request "2"
|
||||
request "^2.87.0"
|
||||
rimraf "2"
|
||||
semver "~5.3.0"
|
||||
tar "^2.0.0"
|
||||
which "1"
|
||||
|
||||
node-sass@^4.7.2:
|
||||
version "4.7.2"
|
||||
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e"
|
||||
version "4.9.3"
|
||||
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.3.tgz#f407cf3d66f78308bb1e346b24fa428703196224"
|
||||
dependencies:
|
||||
async-foreach "^0.1.3"
|
||||
chalk "^1.1.1"
|
||||
|
@ -720,10 +608,10 @@ node-sass@^4.7.2:
|
|||
lodash.mergewith "^4.6.0"
|
||||
meow "^3.7.0"
|
||||
mkdirp "^0.5.1"
|
||||
nan "^2.3.2"
|
||||
node-gyp "^3.3.1"
|
||||
nan "^2.10.0"
|
||||
node-gyp "^3.8.0"
|
||||
npmlog "^4.0.0"
|
||||
request "~2.79.0"
|
||||
request "2.87.0"
|
||||
sass-graph "^2.2.4"
|
||||
stdout-stream "^1.4.0"
|
||||
"true-case-path" "^1.0.2"
|
||||
|
@ -756,10 +644,14 @@ number-is-nan@^1.0.0:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
|
||||
oauth-sign@~0.8.1, oauth-sign@~0.8.2:
|
||||
oauth-sign@~0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
|
||||
|
||||
oauth-sign@~0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||
|
||||
object-assign@^4.0.1, object-assign@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
|
@ -841,17 +733,17 @@ pseudomap@^1.0.2:
|
|||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
||||
|
||||
psl@^1.1.24:
|
||||
version "1.1.29"
|
||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67"
|
||||
|
||||
punycode@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
|
||||
qs@~6.3.0:
|
||||
version "6.3.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
|
||||
|
||||
qs@~6.5.1:
|
||||
version "6.5.1"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
|
||||
qs@~6.5.1, qs@~6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
|
||||
read-pkg-up@^1.0.1:
|
||||
version "1.0.1"
|
||||
|
@ -869,15 +761,15 @@ read-pkg@^1.0.0:
|
|||
path-type "^1.0.0"
|
||||
|
||||
readable-stream@^2.0.1, readable-stream@^2.0.6:
|
||||
version "2.3.5"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d"
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.0.3"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
redent@^1.0.0:
|
||||
|
@ -893,9 +785,9 @@ repeating@^2.0.0:
|
|||
dependencies:
|
||||
is-finite "^1.0.0"
|
||||
|
||||
request@2:
|
||||
version "2.83.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
|
||||
request@2.87.0:
|
||||
version "2.87.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e"
|
||||
dependencies:
|
||||
aws-sign2 "~0.7.0"
|
||||
aws4 "^1.6.0"
|
||||
|
@ -905,7 +797,6 @@ request@2:
|
|||
forever-agent "~0.6.1"
|
||||
form-data "~2.3.1"
|
||||
har-validator "~5.0.3"
|
||||
hawk "~6.0.2"
|
||||
http-signature "~1.2.0"
|
||||
is-typedarray "~1.0.0"
|
||||
isstream "~0.1.2"
|
||||
|
@ -915,35 +806,34 @@ request@2:
|
|||
performance-now "^2.1.0"
|
||||
qs "~6.5.1"
|
||||
safe-buffer "^5.1.1"
|
||||
stringstream "~0.0.5"
|
||||
tough-cookie "~2.3.3"
|
||||
tunnel-agent "^0.6.0"
|
||||
uuid "^3.1.0"
|
||||
|
||||
request@~2.79.0:
|
||||
version "2.79.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de"
|
||||
request@^2.87.0:
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
|
||||
dependencies:
|
||||
aws-sign2 "~0.6.0"
|
||||
aws4 "^1.2.1"
|
||||
caseless "~0.11.0"
|
||||
combined-stream "~1.0.5"
|
||||
extend "~3.0.0"
|
||||
aws-sign2 "~0.7.0"
|
||||
aws4 "^1.8.0"
|
||||
caseless "~0.12.0"
|
||||
combined-stream "~1.0.6"
|
||||
extend "~3.0.2"
|
||||
forever-agent "~0.6.1"
|
||||
form-data "~2.1.1"
|
||||
har-validator "~2.0.6"
|
||||
hawk "~3.1.3"
|
||||
http-signature "~1.1.0"
|
||||
form-data "~2.3.2"
|
||||
har-validator "~5.1.0"
|
||||
http-signature "~1.2.0"
|
||||
is-typedarray "~1.0.0"
|
||||
isstream "~0.1.2"
|
||||
json-stringify-safe "~5.0.1"
|
||||
mime-types "~2.1.7"
|
||||
oauth-sign "~0.8.1"
|
||||
qs "~6.3.0"
|
||||
stringstream "~0.0.4"
|
||||
tough-cookie "~2.3.0"
|
||||
tunnel-agent "~0.4.1"
|
||||
uuid "^3.0.0"
|
||||
mime-types "~2.1.19"
|
||||
oauth-sign "~0.9.0"
|
||||
performance-now "^2.1.0"
|
||||
qs "~6.5.2"
|
||||
safe-buffer "^5.1.2"
|
||||
tough-cookie "~2.4.3"
|
||||
tunnel-agent "^0.6.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
require-directory@^2.1.1:
|
||||
version "2.1.1"
|
||||
|
@ -959,9 +849,13 @@ rimraf@2:
|
|||
dependencies:
|
||||
glob "^7.0.5"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
|
||||
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
|
||||
sass-graph@^2.2.4:
|
||||
version "2.2.4"
|
||||
|
@ -995,18 +889,6 @@ signal-exit@^3.0.0:
|
|||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||
|
||||
sntp@1.x.x:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
|
||||
dependencies:
|
||||
hoek "2.x.x"
|
||||
|
||||
sntp@2.x.x:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
|
||||
dependencies:
|
||||
hoek "4.x.x"
|
||||
|
||||
source-map@^0.4.2:
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
|
||||
|
@ -1036,13 +918,14 @@ spdx-license-ids@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87"
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3"
|
||||
version "1.14.2"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98"
|
||||
dependencies:
|
||||
asn1 "~0.2.3"
|
||||
assert-plus "^1.0.0"
|
||||
dashdash "^1.12.0"
|
||||
getpass "^0.1.1"
|
||||
safer-buffer "^2.0.2"
|
||||
optionalDependencies:
|
||||
bcrypt-pbkdf "^1.0.0"
|
||||
ecc-jsbn "~0.1.1"
|
||||
|
@ -1063,22 +946,31 @@ string-width@^1.0.1, string-width@^1.0.2:
|
|||
is-fullwidth-code-point "^1.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
|
||||
string_decoder@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
|
||||
"string-width@^1.0.2 || 2":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
||||
dependencies:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
stringstream@~0.0.4, stringstream@~0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
|
||||
|
||||
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
||||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
strip-ansi@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
|
||||
dependencies:
|
||||
ansi-regex "^3.0.0"
|
||||
|
||||
strip-bom@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
|
||||
|
@ -1103,12 +995,19 @@ tar@^2.0.0:
|
|||
fstream "^1.0.2"
|
||||
inherits "2"
|
||||
|
||||
tough-cookie@~2.3.0, tough-cookie@~2.3.3:
|
||||
tough-cookie@~2.3.3:
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"
|
||||
dependencies:
|
||||
punycode "^1.4.1"
|
||||
|
||||
tough-cookie@~2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
|
||||
dependencies:
|
||||
psl "^1.1.24"
|
||||
punycode "^1.4.1"
|
||||
|
||||
trim-newlines@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
|
||||
|
@ -1125,10 +1024,6 @@ tunnel-agent@^0.6.0:
|
|||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
tunnel-agent@~0.4.1:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
|
||||
|
||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
|
@ -1137,13 +1032,13 @@ util-deprecate@~1.0.1:
|
|||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
|
||||
uuid@^3.0.0, uuid@^3.1.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
|
||||
uuid@^3.1.0, uuid@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||
|
||||
validate-npm-package-license@^3.0.1:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338"
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
|
||||
dependencies:
|
||||
spdx-correct "^3.0.0"
|
||||
spdx-expression-parse "^3.0.0"
|
||||
|
@ -1161,16 +1056,16 @@ which-module@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
|
||||
|
||||
which@1, which@^1.2.9:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
wide-align@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710"
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
|
||||
dependencies:
|
||||
string-width "^1.0.2"
|
||||
string-width "^1.0.2 || 2"
|
||||
|
||||
wrap-ansi@^2.0.0:
|
||||
version "2.1.0"
|
||||
|
@ -1183,10 +1078,6 @@ wrappy@1:
|
|||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
|
||||
xtend@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
|
||||
y18n@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
],
|
||||
"cyclomatic-complexity": false,
|
||||
"eofline": false,
|
||||
"file-name-casing": false,
|
||||
"forin": false,
|
||||
"interface-name": false,
|
||||
"interface-over-type-literal": false,
|
||||
|
|
|
@ -30,12 +30,10 @@
|
|||
* @see {@link https://github.com/f-list/exported|GitHub repo}
|
||||
*/
|
||||
import Axios from 'axios';
|
||||
import * as Raven from 'raven-js';
|
||||
import Vue from 'vue';
|
||||
import Chat from '../chat/Chat.vue';
|
||||
import {init as initCore} from '../chat/core';
|
||||
import l from '../chat/localize';
|
||||
import VueRaven from '../chat/vue-raven';
|
||||
import {setupRaven} from '../chat/vue-raven';
|
||||
import Socket from '../chat/WebSocket';
|
||||
import Connection from '../fchat/connection';
|
||||
import '../scss/fa.scss'; //tslint:disable-line:no-import-side-effect
|
||||
|
@ -49,27 +47,8 @@ if(typeof window.Promise !== 'function' || typeof window.Notification !== 'funct
|
|||
const version = (<{version: string}>require('./package.json')).version; //tslint:disable-line:no-require-imports
|
||||
Axios.defaults.params = { __fchat: `web/${version}` };
|
||||
|
||||
if(process.env.NODE_ENV === 'production') {
|
||||
Raven.config('https://a9239b17b0a14f72ba85e8729b9d1612@sentry.f-list.net/2', {
|
||||
release: `web-${version}`,
|
||||
dataCallback: (data: {culprit?: string, exception?: {values: {stacktrace: {frames: {filename: string}[]}}[]}}) => {
|
||||
if(data.culprit !== undefined) {
|
||||
const end = data.culprit.lastIndexOf('?');
|
||||
data.culprit = `~${data.culprit.substring(data.culprit.lastIndexOf('/'), end === -1 ? undefined : end)}`;
|
||||
}
|
||||
if(data.exception !== undefined)
|
||||
for(const ex of data.exception.values)
|
||||
for(const frame of ex.stacktrace.frames) {
|
||||
const index = frame.filename.lastIndexOf('/');
|
||||
const endIndex = frame.filename.lastIndexOf('?');
|
||||
frame.filename = `~${frame.filename.substring(index !== -1 ? index : 0, endIndex === -1 ? undefined : endIndex)}`;
|
||||
}
|
||||
}
|
||||
}).addPlugin(VueRaven, Vue).install();
|
||||
(<Window & {onunhandledrejection(e: PromiseRejectionEvent): void}>window).onunhandledrejection = (e: PromiseRejectionEvent) => {
|
||||
Raven.captureException(<Error>e.reason);
|
||||
};
|
||||
}
|
||||
if(process.env.NODE_ENV === 'production')
|
||||
setupRaven('https://a9239b17b0a14f72ba85e8729b9d1612@sentry.f-list.net/2', `web-${version}`);
|
||||
|
||||
declare const chatSettings: {account: string, theme: string, characters: ReadonlyArray<string>, defaultCharacter: string | null};
|
||||
|
||||
|
|
|
@ -7,19 +7,17 @@ export default class Notifications extends BaseNotifications {
|
|||
async notify(conversation: Conversation, title: string, body: string, icon: string, sound: string): Promise<void> {
|
||||
if(!this.shouldNotify(conversation)) return;
|
||||
try {
|
||||
return super.notify(conversation, title, body, icon, sound);
|
||||
await super.notify(conversation, title, body, icon, sound);
|
||||
} catch {
|
||||
(async() => { //tslint:disable-line:no-floating-promises
|
||||
//tslint:disable-next-line:no-require-imports no-submodule-imports
|
||||
await navigator.serviceWorker.register(<string>require('file-loader!./sw.js'));
|
||||
const reg = await navigator.serviceWorker.ready;
|
||||
await reg.showNotification(title, this.getOptions(conversation, body, icon));
|
||||
navigator.serviceWorker.onmessage = (e) => {
|
||||
const conv = core.conversations.byKey((<{key: string}>e.data).key);
|
||||
if(conv !== undefined) conv.show();
|
||||
window.focus();
|
||||
};
|
||||
})();
|
||||
//tslint:disable-next-line:no-require-imports no-submodule-imports
|
||||
await navigator.serviceWorker.register(<string>require('file-loader!./sw.js'));
|
||||
const reg = await navigator.serviceWorker.ready;
|
||||
await reg.showNotification(title, this.getOptions(conversation, body, icon));
|
||||
navigator.serviceWorker.onmessage = (e) => {
|
||||
const conv = core.conversations.byKey((<{key: string}>e.data).key);
|
||||
if(conv !== undefined) conv.show();
|
||||
window.focus();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "net.f_list.fchat",
|
||||
"version": "3.0.6",
|
||||
"version": "3.0.7",
|
||||
"displayName": "F-Chat",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List.net Chat Client",
|
||||
|
|
Loading…
Reference in New Issue