Made the client compatible with the newest webpack stuff
This commit is contained in:
parent
c86b9510a5
commit
75b5192801
2
github
2
github
@ -1 +1 @@
|
|||||||
Subproject commit 171e88fa1f9e4ea67522c1f6bc07b5ca3c8f4b49
|
Subproject commit 6d45c4c883f9b2a53fe1bfeb23fe559b62a61cac
|
@ -121,6 +121,6 @@ function deploy_client() {
|
|||||||
|
|
||||||
#install_npm
|
#install_npm
|
||||||
#compile_scripts
|
#compile_scripts
|
||||||
#compile_native
|
compile_native
|
||||||
#package_client
|
package_client
|
||||||
deploy_client
|
deploy_client
|
||||||
|
@ -8,7 +8,7 @@ const SETTINGS_DIR = path.join(APP_DATA, "settings");
|
|||||||
let _local_storage: {[key: string]: any} = {};
|
let _local_storage: {[key: string]: any} = {};
|
||||||
let _local_storage_save: {[key: string]: boolean} = {};
|
let _local_storage_save: {[key: string]: boolean} = {};
|
||||||
export async function initialize() {
|
export async function initialize() {
|
||||||
await fs.mkdirs(SETTINGS_DIR);
|
await fs.mkdirp(SETTINGS_DIR);
|
||||||
|
|
||||||
const files = await fs.readdir(SETTINGS_DIR);
|
const files = await fs.readdir(SETTINGS_DIR);
|
||||||
for(const file of files) {
|
for(const file of files) {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
window["require_setup"](module);
|
import {createErrorModal} from "tc-shared/ui/elements/Modal";
|
||||||
|
|
||||||
import * as electron from "electron";
|
import * as electron from "electron";
|
||||||
|
import {tr, tra} from "tc-shared/i18n/localize";
|
||||||
|
import {server_connections} from "tc-shared/ui/frames/connection_handlers";
|
||||||
|
import {handle_connect_request} from "tc-shared/main";
|
||||||
|
|
||||||
electron.ipcRenderer.on('connect', (event, url) => handle_native_connect_request(url));
|
electron.ipcRenderer.on('connect', (event, url) => handle_native_connect_request(url));
|
||||||
|
|
||||||
|
@ -1,132 +1,73 @@
|
|||||||
window["require_setup"](module);
|
import * as native from "tc-native/connection";
|
||||||
|
|
||||||
import {audio as naudio} from "teaclient_connection";
|
//FIXME: Native audio initialize handle!
|
||||||
|
export interface Device {
|
||||||
namespace audio.player {
|
device_id: string;
|
||||||
//FIXME: Native audio initialize handle!
|
name: string;
|
||||||
|
|
||||||
export interface Device {
|
|
||||||
device_id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Navigator {
|
|
||||||
mozGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
|
|
||||||
webkitGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
let _initialized_callbacks: (() => any)[] = [];
|
|
||||||
export let _initialized = false;
|
|
||||||
export let _initializing = false;
|
|
||||||
export let _audioContext: AudioContext;
|
|
||||||
export let _processor: ScriptProcessorNode;
|
|
||||||
export let _output_stream: naudio.playback.OwnedAudioOutputStream;
|
|
||||||
export let _current_device: naudio.AudioDevice;
|
|
||||||
|
|
||||||
export function initialized() : boolean {
|
|
||||||
return _initialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function context() : AudioContext {
|
|
||||||
if(!_audioContext) throw "Initialize first!";
|
|
||||||
return _audioContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function destination() : AudioNode {
|
|
||||||
if(!_initialized)
|
|
||||||
throw "Audio player hasn't yet be initialized";
|
|
||||||
return _processor || _audioContext.destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function on_ready(cb: () => any) {
|
|
||||||
if(_initialized)
|
|
||||||
cb();
|
|
||||||
else
|
|
||||||
_initialized_callbacks.push(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initialize() {
|
|
||||||
if(_initializing) return;
|
|
||||||
_initializing = true;
|
|
||||||
|
|
||||||
naudio.initialize(() => {
|
|
||||||
_output_stream = naudio.playback.create_stream();
|
|
||||||
_output_stream.set_buffer_max_latency(0.4);
|
|
||||||
_output_stream.set_buffer_latency(0.02);
|
|
||||||
|
|
||||||
_output_stream.callback_overflow = () => {
|
|
||||||
console.warn("Main audio overflow");
|
|
||||||
_output_stream.clear();
|
|
||||||
};
|
|
||||||
_output_stream.callback_underflow = () => {
|
|
||||||
console.warn("Main audio underflow");
|
|
||||||
};
|
|
||||||
|
|
||||||
_audioContext = new AudioContext({
|
|
||||||
sampleRate: _output_stream.sample_rate
|
|
||||||
});
|
|
||||||
_processor = _audioContext.createScriptProcessor(1024 * 8, _output_stream.channels, _output_stream.channels);
|
|
||||||
|
|
||||||
_processor.onaudioprocess = function(event) {
|
|
||||||
const buffer = event.inputBuffer;
|
|
||||||
//console.log("Received %d channels of %d with a rate of %d", buffer.numberOfChannels, buffer.length, buffer.sampleRate);
|
|
||||||
const target_buffer = new Float32Array(buffer.numberOfChannels * buffer.length);
|
|
||||||
|
|
||||||
for(let channel = 0; channel < buffer.numberOfChannels; channel++) {
|
|
||||||
const channel_data = buffer.getChannelData(channel);
|
|
||||||
target_buffer.set(channel_data, channel * buffer.length);
|
|
||||||
}
|
|
||||||
_output_stream.write_data_rated(target_buffer.buffer, false, buffer.sampleRate);
|
|
||||||
};
|
|
||||||
_processor.connect(_audioContext.destination);
|
|
||||||
|
|
||||||
|
|
||||||
_initialized = true;
|
|
||||||
for(const callback of _initialized_callbacks)
|
|
||||||
callback();
|
|
||||||
_initialized_callbacks = [];
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function available_devices() : Promise<Device[]> {
|
|
||||||
return naudio.available_devices().filter(e => e.output_supported || e.output_default);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function set_device(device_id?: string) : Promise<void> {
|
|
||||||
const dev = naudio.available_devices().filter(e => e.device_id == device_id);
|
|
||||||
if(dev.length == 0) {
|
|
||||||
console.warn("Missing audio device with is %s", device_id);
|
|
||||||
throw "invalid device id";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
naudio.playback.set_device(dev[0].device_id);
|
|
||||||
} catch(error) {
|
|
||||||
if(error instanceof Error)
|
|
||||||
throw error.message;
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
_current_device = dev[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function current_device() : Device {
|
|
||||||
if(_current_device)
|
|
||||||
return _current_device;
|
|
||||||
|
|
||||||
const dev = naudio.available_devices().filter(e => e.output_default);
|
|
||||||
if(dev.length > 0)
|
|
||||||
return dev[0];
|
|
||||||
return {device_id: "default", name: "default"} as Device;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function get_master_volume() : number {
|
|
||||||
return naudio.playback.get_master_volume();
|
|
||||||
}
|
|
||||||
export function set_master_volume(volume: number) {
|
|
||||||
naudio.playback.set_master_volume(volume);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(window["audio"] || (window["audio"] = {} as any), audio);
|
let _initialized_callbacks: (() => any)[] = [];
|
||||||
|
export let _initialized = false;
|
||||||
|
export let _initializing = false;
|
||||||
|
export let _current_device: native.audio.AudioDevice;
|
||||||
|
|
||||||
|
export function initialized() : boolean {
|
||||||
|
return _initialized;
|
||||||
|
}
|
||||||
|
export function on_ready(cb: () => any) {
|
||||||
|
if(_initialized)
|
||||||
|
cb();
|
||||||
|
else
|
||||||
|
_initialized_callbacks.push(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initialize() {
|
||||||
|
if(_initializing) return;
|
||||||
|
_initializing = true;
|
||||||
|
|
||||||
|
native.audio.initialize(() => {
|
||||||
|
_initialized = true;
|
||||||
|
for(const callback of _initialized_callbacks)
|
||||||
|
callback();
|
||||||
|
_initialized_callbacks = [];
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function available_devices() : Promise<Device[]> {
|
||||||
|
return native.audio.available_devices().filter(e => e.output_supported || e.output_default);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function set_device(device_id?: string) : Promise<void> {
|
||||||
|
const dev = native.audio.available_devices().filter(e => e.device_id == device_id);
|
||||||
|
if(dev.length == 0) {
|
||||||
|
console.warn("Missing audio device with is %s", device_id);
|
||||||
|
throw "invalid device id";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
native.audio.playback.set_device(dev[0].device_id);
|
||||||
|
} catch(error) {
|
||||||
|
if(error instanceof Error)
|
||||||
|
throw error.message;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
_current_device = dev[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function current_device() : Device {
|
||||||
|
if(_current_device)
|
||||||
|
return _current_device;
|
||||||
|
|
||||||
|
const dev = native.audio.available_devices().filter(e => e.output_default);
|
||||||
|
if(dev.length > 0)
|
||||||
|
return dev[0];
|
||||||
|
return {device_id: "default", name: "default"} as Device;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get_master_volume() : number {
|
||||||
|
return native.audio.playback.get_master_volume();
|
||||||
|
}
|
||||||
|
export function set_master_volume(volume: number) {
|
||||||
|
native.audio.playback.set_master_volume(volume);
|
||||||
|
}
|
@ -1,498 +1,498 @@
|
|||||||
window["require_setup"](module);
|
import {
|
||||||
|
filter,
|
||||||
|
AbstractInput,
|
||||||
|
InputDevice,
|
||||||
|
InputState,
|
||||||
|
InputConsumer,
|
||||||
|
InputConsumerType, InputStartResult, LevelMeter
|
||||||
|
} from "tc-shared/voice/RecorderBase";
|
||||||
|
import {audio} from "tc-native/connection";
|
||||||
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
|
|
||||||
import {audio as naudio} from "teaclient_connection";
|
interface NativeDevice extends InputDevice {
|
||||||
// <reference types="../imports/import_shared.d.ts" />
|
device_index: number;
|
||||||
/// <reference types="../../modules/renderer/imports/imports_shared.d.ts" />
|
native: any;
|
||||||
|
}
|
||||||
|
|
||||||
export namespace _audio.recorder {
|
let _device_cache: NativeDevice[] = undefined;
|
||||||
import InputDevice = audio.recorder.InputDevice;
|
export function devices() : InputDevice[] {
|
||||||
import AbstractInput = audio.recorder.AbstractInput;
|
//TODO: Handle device updates!
|
||||||
|
if(!audio.initialized()) return [];
|
||||||
|
|
||||||
interface NativeDevice extends InputDevice {
|
return _device_cache || (_device_cache = audio.available_devices().filter(e => e.input_supported || e.input_default).map(e => {
|
||||||
device_index: number;
|
return {
|
||||||
native: any;
|
unique_id: e.device_id,
|
||||||
|
channels: 2, /* TODO */
|
||||||
|
default_input: e.input_default,
|
||||||
|
supported: e.input_supported,
|
||||||
|
name: e.name,
|
||||||
|
driver: e.driver,
|
||||||
|
sample_rate: 48000, /* TODO! */
|
||||||
|
native: e
|
||||||
|
} as NativeDevice
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function device_refresh_available() : boolean { return false; }
|
||||||
|
export function refresh_devices() : Promise<void> { throw "not supported yet!"; }
|
||||||
|
|
||||||
|
export function create_input() : AbstractInput {
|
||||||
|
return new NativeInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace filters {
|
||||||
|
export abstract class NativeFilter implements filter.Filter {
|
||||||
|
type: filter.Type;
|
||||||
|
handle: NativeInput;
|
||||||
|
enabled: boolean = false;
|
||||||
|
|
||||||
|
protected constructor(handle, type) { this.handle = handle; this.type = type; }
|
||||||
|
|
||||||
|
abstract initialize();
|
||||||
|
abstract finalize();
|
||||||
|
|
||||||
|
|
||||||
|
is_enabled(): boolean { return this.enabled; }
|
||||||
}
|
}
|
||||||
|
|
||||||
let _device_cache: NativeDevice[] = undefined;
|
export class NThresholdFilter extends NativeFilter implements filter.ThresholdFilter {
|
||||||
export function devices() : InputDevice[] {
|
private filter: audio.record.ThresholdConsumeFilter;
|
||||||
//TODO: Handle device updates!
|
|
||||||
if(!naudio.initialized()) return [];
|
|
||||||
|
|
||||||
return _device_cache || (_device_cache = naudio.available_devices().filter(e => e.input_supported || e.input_default).map(e => {
|
private _margin_frames: number = 6; /* 120ms */
|
||||||
return {
|
private _threshold: number = 50;
|
||||||
unique_id: e.device_id,
|
private _callback_level: any;
|
||||||
channels: 2, /* TODO */
|
|
||||||
default_input: e.input_default,
|
|
||||||
supported: e.input_supported,
|
|
||||||
name: e.name,
|
|
||||||
driver: e.driver,
|
|
||||||
sample_rate: 48000, /* TODO! */
|
|
||||||
native: e
|
|
||||||
} as NativeDevice
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function device_refresh_available() : boolean { return false; }
|
private _attack_smooth = 0;
|
||||||
export function refresh_devices() : Promise<void> { throw "not supported yet!"; }
|
private _release_smooth = 0;
|
||||||
|
|
||||||
export function create_input() : AbstractInput {
|
callback_level: (level: number) => any;
|
||||||
return new NativeInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace filter {
|
constructor(handle) {
|
||||||
export abstract class NativeFilter implements audio.recorder.filter.Filter {
|
super(handle, filter.Type.THRESHOLD);
|
||||||
type: audio.recorder.filter.Type;
|
|
||||||
handle: NativeInput;
|
|
||||||
enabled: boolean = false;
|
|
||||||
|
|
||||||
protected constructor(handle, type) { this.handle = handle; this.type = type; }
|
Object.defineProperty(this, 'callback_level', {
|
||||||
|
get(): any {
|
||||||
|
return this._callback_level;
|
||||||
|
}, set(v: any): void {
|
||||||
|
if(v === this._callback_level)
|
||||||
|
return;
|
||||||
|
|
||||||
abstract initialize();
|
this._callback_level = v;
|
||||||
abstract finalize();
|
if(this.filter)
|
||||||
|
this.filter.set_analyze_filter(v);
|
||||||
|
},
|
||||||
is_enabled(): boolean { return this.enabled; }
|
enumerable: true,
|
||||||
|
configurable: false,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NThresholdFilter extends NativeFilter implements audio.recorder.filter.ThresholdFilter {
|
get_margin_frames(): number {
|
||||||
private filter: naudio.record.ThresholdConsumeFilter;
|
return this.filter ? this.filter.get_margin_frames() : this._margin_frames;
|
||||||
|
}
|
||||||
|
|
||||||
private _margin_frames: number = 6; /* 120ms */
|
get_threshold(): number {
|
||||||
private _threshold: number = 50;
|
return this.filter ? this.filter.get_threshold() : this._threshold;
|
||||||
private _callback_level: any;
|
}
|
||||||
|
|
||||||
private _attack_smooth = 0;
|
set_margin_frames(value: number) {
|
||||||
private _release_smooth = 0;
|
this._margin_frames = value;
|
||||||
|
if(this.filter)
|
||||||
|
this.filter.set_margin_frames(value);
|
||||||
|
}
|
||||||
|
|
||||||
callback_level: (level: number) => any;
|
get_attack_smooth(): number {
|
||||||
|
return this.filter ? this.filter.get_attack_smooth() : this._attack_smooth;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(handle) {
|
get_release_smooth(): number {
|
||||||
super(handle, audio.recorder.filter.Type.THRESHOLD);
|
return this.filter ? this.filter.get_release_smooth() : this._release_smooth;
|
||||||
|
}
|
||||||
|
|
||||||
Object.defineProperty(this, 'callback_level', {
|
set_attack_smooth(value: number) {
|
||||||
get(): any {
|
this._attack_smooth = value;
|
||||||
return this._callback_level;
|
if(this.filter)
|
||||||
}, set(v: any): void {
|
this.filter.set_attack_smooth(value);
|
||||||
if(v === this._callback_level)
|
}
|
||||||
return;
|
|
||||||
|
|
||||||
this._callback_level = v;
|
set_release_smooth(value: number) {
|
||||||
if(this.filter)
|
this._release_smooth = value;
|
||||||
this.filter.set_analyze_filter(v);
|
if(this.filter)
|
||||||
},
|
this.filter.set_release_smooth(value);
|
||||||
enumerable: true,
|
}
|
||||||
configurable: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
get_margin_frames(): number {
|
set_threshold(value: number): Promise<void> {
|
||||||
return this.filter ? this.filter.get_margin_frames() : this._margin_frames;
|
if(typeof(value) === "string")
|
||||||
}
|
value = parseInt(value); /* yes... this happens */
|
||||||
|
this._threshold = value;
|
||||||
|
if(this.filter)
|
||||||
|
this.filter.set_threshold(value);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
get_threshold(): number {
|
finalize() {
|
||||||
return this.filter ? this.filter.get_threshold() : this._threshold;
|
if(this.filter) {
|
||||||
}
|
if(this.handle.consumer)
|
||||||
|
this.handle.consumer.unregister_filter(this.filter);
|
||||||
set_margin_frames(value: number) {
|
this.filter = undefined;
|
||||||
this._margin_frames = value;
|
|
||||||
if(this.filter)
|
|
||||||
this.filter.set_margin_frames(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
get_attack_smooth(): number {
|
|
||||||
return this.filter ? this.filter.get_attack_smooth() : this._attack_smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
get_release_smooth(): number {
|
|
||||||
return this.filter ? this.filter.get_release_smooth() : this._release_smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_attack_smooth(value: number) {
|
|
||||||
this._attack_smooth = value;
|
|
||||||
if(this.filter)
|
|
||||||
this.filter.set_attack_smooth(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
set_release_smooth(value: number) {
|
|
||||||
this._release_smooth = value;
|
|
||||||
if(this.filter)
|
|
||||||
this.filter.set_release_smooth(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
set_threshold(value: number): Promise<void> {
|
|
||||||
if(typeof(value) === "string")
|
|
||||||
value = parseInt(value); /* yes... this happens */
|
|
||||||
this._threshold = value;
|
|
||||||
if(this.filter)
|
|
||||||
this.filter.set_threshold(value);
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
finalize() {
|
|
||||||
if(this.filter) {
|
|
||||||
if(this.handle.consumer)
|
|
||||||
this.handle.consumer.unregister_filter(this.filter);
|
|
||||||
this.filter = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
if(!this.handle.consumer)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.finalize();
|
|
||||||
this.filter = this.handle.consumer.create_filter_threshold(this._threshold);
|
|
||||||
if(this._callback_level)
|
|
||||||
this.filter.set_analyze_filter(this._callback_level);
|
|
||||||
this.filter.set_margin_frames(this._margin_frames);
|
|
||||||
this.filter.set_attack_smooth(this._attack_smooth);
|
|
||||||
this.filter.set_release_smooth(this._release_smooth);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NStateFilter extends NativeFilter implements audio.recorder.filter.StateFilter {
|
initialize() {
|
||||||
private filter: naudio.record.StateConsumeFilter;
|
if(!this.handle.consumer)
|
||||||
private active = false;
|
|
||||||
|
|
||||||
constructor(handle) {
|
|
||||||
super(handle, audio.recorder.filter.Type.STATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
finalize() {
|
|
||||||
if(this.filter) {
|
|
||||||
if(this.handle.consumer)
|
|
||||||
this.handle.consumer.unregister_filter(this.filter);
|
|
||||||
this.filter = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
if(!this.handle.consumer)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.finalize();
|
|
||||||
this.filter = this.handle.consumer.create_filter_state();
|
|
||||||
this.filter.set_consuming(this.active);
|
|
||||||
}
|
|
||||||
|
|
||||||
is_active(): boolean {
|
|
||||||
return this.active;
|
|
||||||
}
|
|
||||||
|
|
||||||
async set_state(state: boolean): Promise<void> {
|
|
||||||
if(this.active === state)
|
|
||||||
return;
|
|
||||||
this.active = state;
|
|
||||||
if(this.filter)
|
|
||||||
this.filter.set_consuming(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NVoiceLevelFilter extends NativeFilter implements audio.recorder.filter.VoiceLevelFilter {
|
|
||||||
private filter: naudio.record.VADConsumeFilter;
|
|
||||||
private level = 3;
|
|
||||||
private _margin_frames = 5;
|
|
||||||
|
|
||||||
constructor(handle) {
|
|
||||||
super(handle, audio.recorder.filter.Type.VOICE_LEVEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
finalize() {
|
|
||||||
if(this.filter) {
|
|
||||||
if(this.handle.consumer)
|
|
||||||
this.handle.consumer.unregister_filter(this.filter);
|
|
||||||
this.filter = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
if(!this.handle.consumer)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.finalize();
|
|
||||||
this.filter = this.handle.consumer.create_filter_vad(this.level);
|
|
||||||
this.filter.set_margin_frames(this._margin_frames);
|
|
||||||
}
|
|
||||||
|
|
||||||
get_level(): number {
|
|
||||||
return this.level;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_level(value: number) {
|
|
||||||
if(this.level === value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.level = value;
|
|
||||||
if(this.filter) {
|
|
||||||
this.finalize();
|
|
||||||
this.initialize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set_margin_frames(value: number) {
|
|
||||||
this._margin_frames = value;
|
|
||||||
if(this.filter)
|
|
||||||
this.filter.set_margin_frames(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
get_margin_frames(): number {
|
|
||||||
return this.filter ? this.filter.get_margin_frames() : this._margin_frames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NativeInput implements AbstractInput {
|
|
||||||
private handle: naudio.record.AudioRecorder;
|
|
||||||
consumer: naudio.record.AudioConsumer;
|
|
||||||
|
|
||||||
private _current_device: audio.recorder.InputDevice;
|
|
||||||
private _current_state: audio.recorder.InputState = audio.recorder.InputState.PAUSED;
|
|
||||||
|
|
||||||
callback_begin: () => any;
|
|
||||||
callback_end: () => any;
|
|
||||||
|
|
||||||
private filters: filter.NativeFilter[] = [];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.handle = naudio.record.create_recorder();
|
|
||||||
|
|
||||||
this.consumer = this.handle.create_consumer();
|
|
||||||
this.consumer.callback_ended = () => {
|
|
||||||
if(this._current_state !== audio.recorder.InputState.RECORDING)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this._current_state = audio.recorder.InputState.DRY;
|
|
||||||
if(this.callback_end)
|
|
||||||
this.callback_end();
|
|
||||||
};
|
|
||||||
this.consumer.callback_started = () => {
|
|
||||||
if(this._current_state !== audio.recorder.InputState.DRY)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this._current_state = audio.recorder.InputState.RECORDING;
|
|
||||||
if(this.callback_begin)
|
|
||||||
this.callback_begin();
|
|
||||||
};
|
|
||||||
|
|
||||||
this._current_state = audio.recorder.InputState.PAUSED;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: some kind of finalize? */
|
|
||||||
current_consumer(): audio.recorder.InputConsumer | undefined {
|
|
||||||
return {
|
|
||||||
type: audio.recorder.InputConsumerType.NATIVE
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async set_consumer(consumer: audio.recorder.InputConsumer): Promise<void> {
|
|
||||||
if(typeof(consumer) !== "undefined")
|
|
||||||
throw "we only support native consumers!"; /* TODO: May create a general wrapper? */
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
async set_device(_device: audio.recorder.InputDevice | undefined): Promise<void> {
|
|
||||||
if(_device === this._current_device)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._current_device = _device;
|
this.finalize();
|
||||||
try {
|
this.filter = this.handle.consumer.create_filter_threshold(this._threshold);
|
||||||
await new Promise(resolve => this.handle.set_device(this._current_device ? this._current_device.unique_id : undefined, resolve));
|
if(this._callback_level)
|
||||||
if(this._current_state !== audio.recorder.InputState.PAUSED && this._current_device)
|
this.filter.set_analyze_filter(this._callback_level);
|
||||||
await new Promise((resolve, reject) => {
|
this.filter.set_margin_frames(this._margin_frames);
|
||||||
this.handle.start(flag => {
|
this.filter.set_attack_smooth(this._attack_smooth);
|
||||||
if(typeof flag === "boolean" && flag)
|
this.filter.set_release_smooth(this._release_smooth);
|
||||||
resolve();
|
}
|
||||||
else
|
}
|
||||||
reject(typeof flag === "string" ? flag : "failed to start");
|
|
||||||
});
|
export class NStateFilter extends NativeFilter implements filter.StateFilter {
|
||||||
});
|
private filter: audio.record.StateConsumeFilter;
|
||||||
} catch(error) {
|
private active = false;
|
||||||
console.warn(tr("Failed to start playback on new input device (%o)"), error);
|
|
||||||
throw error;
|
constructor(handle) {
|
||||||
|
super(handle, filter.Type.STATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize() {
|
||||||
|
if(this.filter) {
|
||||||
|
if(this.handle.consumer)
|
||||||
|
this.handle.consumer.unregister_filter(this.filter);
|
||||||
|
this.filter = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
current_device(): audio.recorder.InputDevice | undefined {
|
initialize() {
|
||||||
return this._current_device;
|
if(!this.handle.consumer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.finalize();
|
||||||
|
this.filter = this.handle.consumer.create_filter_state();
|
||||||
|
this.filter.set_consuming(this.active);
|
||||||
}
|
}
|
||||||
|
|
||||||
current_state(): audio.recorder.InputState {
|
is_active(): boolean {
|
||||||
return this._current_state;
|
return this.active;
|
||||||
}
|
}
|
||||||
|
|
||||||
disable_filter(type: audio.recorder.filter.Type) {
|
async set_state(state: boolean): Promise<void> {
|
||||||
const filter = this.get_filter(type) as filter.NativeFilter;
|
if(this.active === state)
|
||||||
if(filter.is_enabled())
|
return;
|
||||||
filter.enabled = false;
|
this.active = state;
|
||||||
filter.finalize();
|
if(this.filter)
|
||||||
|
this.filter.set_consuming(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NVoiceLevelFilter extends NativeFilter implements filter.VoiceLevelFilter {
|
||||||
|
private filter: audio.record.VADConsumeFilter;
|
||||||
|
private level = 3;
|
||||||
|
private _margin_frames = 5;
|
||||||
|
|
||||||
|
constructor(handle) {
|
||||||
|
super(handle, filter.Type.VOICE_LEVEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
enable_filter(type: audio.recorder.filter.Type) {
|
finalize() {
|
||||||
const filter = this.get_filter(type) as filter.NativeFilter;
|
if(this.filter) {
|
||||||
if(!filter.is_enabled()) {
|
if(this.handle.consumer)
|
||||||
filter.enabled = true;
|
this.handle.consumer.unregister_filter(this.filter);
|
||||||
filter.initialize();
|
this.filter = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clear_filter() {
|
initialize() {
|
||||||
for(const filter of this.filters) {
|
if(!this.handle.consumer)
|
||||||
filter.enabled = false;
|
return;
|
||||||
filter.finalize();
|
|
||||||
|
this.finalize();
|
||||||
|
this.filter = this.handle.consumer.create_filter_vad(this.level);
|
||||||
|
this.filter.set_margin_frames(this._margin_frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
get_level(): number {
|
||||||
|
return this.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_level(value: number) {
|
||||||
|
if(this.level === value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.level = value;
|
||||||
|
if(this.filter) {
|
||||||
|
this.finalize();
|
||||||
|
this.initialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get_filter(type: audio.recorder.filter.Type): audio.recorder.filter.Filter | undefined {
|
set_margin_frames(value: number) {
|
||||||
for(const filter of this.filters)
|
this._margin_frames = value;
|
||||||
if(filter.type === type)
|
if(this.filter)
|
||||||
return filter;
|
this.filter.set_margin_frames(value);
|
||||||
|
|
||||||
let _filter: filter.NativeFilter;
|
|
||||||
switch (type) {
|
|
||||||
case audio.recorder.filter.Type.THRESHOLD:
|
|
||||||
_filter = new filter.NThresholdFilter(this);
|
|
||||||
break;
|
|
||||||
case audio.recorder.filter.Type.STATE:
|
|
||||||
_filter = new filter.NStateFilter(this);
|
|
||||||
break;
|
|
||||||
case audio.recorder.filter.Type.VOICE_LEVEL:
|
|
||||||
_filter = new filter.NVoiceLevelFilter(this);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw "this filter isn't supported!";
|
|
||||||
}
|
|
||||||
this.filters.push(_filter);
|
|
||||||
return _filter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
supports_filter(type: audio.recorder.filter.Type) : boolean {
|
get_margin_frames(): number {
|
||||||
switch (type) {
|
return this.filter ? this.filter.get_margin_frames() : this._margin_frames;
|
||||||
case audio.recorder.filter.Type.THRESHOLD:
|
|
||||||
case audio.recorder.filter.Type.STATE:
|
|
||||||
case audio.recorder.filter.Type.VOICE_LEVEL:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async start(): Promise<audio.recorder.InputStartResult> {
|
export class NativeInput implements AbstractInput {
|
||||||
try {
|
private handle: audio.record.AudioRecorder;
|
||||||
await this.stop();
|
consumer: audio.record.AudioConsumer;
|
||||||
} catch(error) {
|
|
||||||
console.warn(tr("Failed to stop old record session before start (%o)"), error);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._current_state = audio.recorder.InputState.DRY;
|
private _current_device: InputDevice;
|
||||||
try {
|
private _current_state: InputState = InputState.PAUSED;
|
||||||
if(this._current_device)
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
this.handle.start(flag => {
|
|
||||||
if(flag)
|
|
||||||
resolve();
|
|
||||||
else
|
|
||||||
reject("start failed");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
for(const filter of this.filters)
|
|
||||||
if(filter.is_enabled())
|
|
||||||
filter.initialize();
|
|
||||||
return audio.recorder.InputStartResult.EOK;
|
|
||||||
} catch(error) {
|
|
||||||
this._current_state = audio.recorder.InputState.PAUSED;
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async stop(): Promise<void> {
|
callback_begin: () => any;
|
||||||
this.handle.stop();
|
callback_end: () => any;
|
||||||
for(const filter of this.filters)
|
|
||||||
filter.finalize();
|
private filters: filters.NativeFilter[] = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.handle = audio.record.create_recorder();
|
||||||
|
|
||||||
|
this.consumer = this.handle.create_consumer();
|
||||||
|
this.consumer.callback_ended = () => {
|
||||||
|
if(this._current_state !== InputState.RECORDING)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._current_state = InputState.DRY;
|
||||||
if(this.callback_end)
|
if(this.callback_end)
|
||||||
this.callback_end();
|
this.callback_end();
|
||||||
this._current_state = audio.recorder.InputState.PAUSED;
|
};
|
||||||
}
|
this.consumer.callback_started = () => {
|
||||||
|
if(this._current_state !== InputState.DRY)
|
||||||
|
return;
|
||||||
|
|
||||||
get_volume(): number {
|
this._current_state = InputState.RECORDING;
|
||||||
return this.handle.get_volume();
|
if(this.callback_begin)
|
||||||
}
|
this.callback_begin();
|
||||||
|
};
|
||||||
|
|
||||||
set_volume(volume: number) {
|
this._current_state = InputState.PAUSED;
|
||||||
this.handle.set_volume(volume);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function create_levelmeter(device: InputDevice) : Promise<audio.recorder.LevelMeter> {
|
/* TODO: some kind of finalize? */
|
||||||
const meter = new NativeLevelmenter(device as any);
|
current_consumer(): InputConsumer | undefined {
|
||||||
await meter.initialize();
|
return {
|
||||||
return meter;
|
type: InputConsumerType.NATIVE
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class NativeLevelmenter implements audio.recorder.LevelMeter {
|
async set_consumer(consumer: InputConsumer): Promise<void> {
|
||||||
readonly _device: NativeDevice;
|
if(typeof(consumer) !== "undefined")
|
||||||
|
throw "we only support native consumers!"; /* TODO: May create a general wrapper? */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
private _callback: (num: number) => any;
|
async set_device(_device: InputDevice | undefined): Promise<void> {
|
||||||
private _recorder: naudio.record.AudioRecorder;
|
if(_device === this._current_device)
|
||||||
private _consumer: naudio.record.AudioConsumer;
|
return;
|
||||||
private _filter: naudio.record.ThresholdConsumeFilter;
|
|
||||||
|
|
||||||
constructor(device: NativeDevice) {
|
this._current_device = _device;
|
||||||
this._device = device;
|
try {
|
||||||
}
|
await new Promise(resolve => this.handle.set_device(this._current_device ? this._current_device.unique_id : undefined, resolve));
|
||||||
|
if(this._current_state !== InputState.PAUSED && this._current_device)
|
||||||
async initialize() {
|
|
||||||
try {
|
|
||||||
this._recorder = naudio.record.create_recorder();
|
|
||||||
this._consumer = this._recorder.create_consumer();
|
|
||||||
|
|
||||||
this._filter = this._consumer.create_filter_threshold(.5);
|
|
||||||
this._filter.set_attack_smooth(.75);
|
|
||||||
this._filter.set_release_smooth(.75);
|
|
||||||
|
|
||||||
await new Promise(resolve => this._recorder.set_device(this._device ? this._device.unique_id : undefined, resolve));
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
this._recorder.start(flag => {
|
this.handle.start(flag => {
|
||||||
if(typeof flag === "boolean" && flag)
|
if(typeof flag === "boolean" && flag)
|
||||||
resolve();
|
resolve();
|
||||||
else
|
else
|
||||||
reject(typeof flag === "string" ? flag : "failed to start");
|
reject(typeof flag === "string" ? flag : "failed to start");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
if(typeof(error) === "string")
|
console.warn(tr("Failed to start playback on new input device (%o)"), error);
|
||||||
throw error;
|
throw error;
|
||||||
console.warn(tr("Failed to initialize levelmeter for device %o: %o"), this._device, error);
|
}
|
||||||
throw "initialize failed (lookup console)";
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* references this variable, needs a destory() call, else memory leak */
|
current_device(): InputDevice | undefined {
|
||||||
this._filter.set_analyze_filter(value => {
|
return this._current_device;
|
||||||
(this._callback || (() => {}))(value);
|
}
|
||||||
});
|
|
||||||
|
current_state(): InputState {
|
||||||
|
return this._current_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_filter(type: filter.Type) {
|
||||||
|
const filter = this.get_filter(type) as filters.NativeFilter;
|
||||||
|
if(filter.is_enabled())
|
||||||
|
filter.enabled = false;
|
||||||
|
filter.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
enable_filter(type: filter.Type) {
|
||||||
|
const filter = this.get_filter(type) as filters.NativeFilter;
|
||||||
|
if(!filter.is_enabled()) {
|
||||||
|
filter.enabled = true;
|
||||||
|
filter.initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_filter() {
|
||||||
|
for(const filter of this.filters) {
|
||||||
|
filter.enabled = false;
|
||||||
|
filter.finalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_filter(type: filter.Type): filter.Filter | undefined {
|
||||||
|
for(const filter of this.filters)
|
||||||
|
if(filter.type === type)
|
||||||
|
return filter;
|
||||||
|
|
||||||
|
let _filter: filters.NativeFilter;
|
||||||
|
switch (type) {
|
||||||
|
case filter.Type.THRESHOLD:
|
||||||
|
_filter = new filters.NThresholdFilter(this);
|
||||||
|
break;
|
||||||
|
case filter.Type.STATE:
|
||||||
|
_filter = new filters.NStateFilter(this);
|
||||||
|
break;
|
||||||
|
case filter.Type.VOICE_LEVEL:
|
||||||
|
_filter = new filters.NVoiceLevelFilter(this);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw "this filter isn't supported!";
|
||||||
|
}
|
||||||
|
this.filters.push(_filter);
|
||||||
|
return _filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
supports_filter(type: filter.Type) : boolean {
|
||||||
|
switch (type) {
|
||||||
|
case filter.Type.THRESHOLD:
|
||||||
|
case filter.Type.STATE:
|
||||||
|
case filter.Type.VOICE_LEVEL:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(): Promise<InputStartResult> {
|
||||||
|
try {
|
||||||
|
await this.stop();
|
||||||
|
} catch(error) {
|
||||||
|
console.warn(tr("Failed to stop old record session before start (%o)"), error);
|
||||||
}
|
}
|
||||||
|
|
||||||
destory() {
|
this._current_state = InputState.DRY;
|
||||||
if(this._filter) {
|
try {
|
||||||
this._filter.set_analyze_filter(undefined);
|
if(this._current_device)
|
||||||
this._consumer.unregister_filter(this._filter);
|
await new Promise((resolve, reject) => {
|
||||||
}
|
this.handle.start(flag => {
|
||||||
if(this._consumer)
|
if(flag)
|
||||||
this._recorder.delete_consumer(this._consumer);
|
resolve();
|
||||||
this._recorder.stop();
|
else
|
||||||
this._recorder.set_device(undefined, () => {}); /* -1 := No device */
|
reject("start failed");
|
||||||
this._recorder = undefined;
|
});
|
||||||
this._consumer = undefined;
|
});
|
||||||
this._filter = undefined;
|
for(const filter of this.filters)
|
||||||
|
if(filter.is_enabled())
|
||||||
|
filter.initialize();
|
||||||
|
return InputStartResult.EOK;
|
||||||
|
} catch(error) {
|
||||||
|
this._current_state = InputState.PAUSED;
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
device(): audio.recorder.InputDevice {
|
async stop(): Promise<void> {
|
||||||
return this._device;
|
this.handle.stop();
|
||||||
}
|
for(const filter of this.filters)
|
||||||
|
filter.finalize();
|
||||||
|
if(this.callback_end)
|
||||||
|
this.callback_end();
|
||||||
|
this._current_state = InputState.PAUSED;
|
||||||
|
}
|
||||||
|
|
||||||
set_observer(callback: (value: number) => any) {
|
get_volume(): number {
|
||||||
this._callback = callback;
|
return this.handle.get_volume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_volume(volume: number) {
|
||||||
|
this.handle.set_volume(volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(window["audio"] || (window["audio"] = {} as any), _audio);
|
export async function create_levelmeter(device: InputDevice) : Promise<LevelMeter> {
|
||||||
|
const meter = new NativeLevelmenter(device as any);
|
||||||
|
await meter.initialize();
|
||||||
|
return meter;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NativeLevelmenter implements LevelMeter {
|
||||||
|
readonly _device: NativeDevice;
|
||||||
|
|
||||||
|
private _callback: (num: number) => any;
|
||||||
|
private _recorder: audio.record.AudioRecorder;
|
||||||
|
private _consumer: audio.record.AudioConsumer;
|
||||||
|
private _filter: audio.record.ThresholdConsumeFilter;
|
||||||
|
|
||||||
|
constructor(device: NativeDevice) {
|
||||||
|
this._device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize() {
|
||||||
|
try {
|
||||||
|
this._recorder = audio.record.create_recorder();
|
||||||
|
this._consumer = this._recorder.create_consumer();
|
||||||
|
|
||||||
|
this._filter = this._consumer.create_filter_threshold(.5);
|
||||||
|
this._filter.set_attack_smooth(.75);
|
||||||
|
this._filter.set_release_smooth(.75);
|
||||||
|
|
||||||
|
await new Promise(resolve => this._recorder.set_device(this._device ? this._device.unique_id : undefined, resolve));
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
this._recorder.start(flag => {
|
||||||
|
if (typeof flag === "boolean" && flag)
|
||||||
|
resolve();
|
||||||
|
else
|
||||||
|
reject(typeof flag === "string" ? flag : "failed to start");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (typeof (error) === "string")
|
||||||
|
throw error;
|
||||||
|
console.warn(tr("Failed to initialize levelmeter for device %o: %o"), this._device, error);
|
||||||
|
throw "initialize failed (lookup console)";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* references this variable, needs a destory() call, else memory leak */
|
||||||
|
this._filter.set_analyze_filter(value => {
|
||||||
|
(this._callback || (() => {
|
||||||
|
}))(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
destory() {
|
||||||
|
if (this._filter) {
|
||||||
|
this._filter.set_analyze_filter(undefined);
|
||||||
|
this._consumer.unregister_filter(this._filter);
|
||||||
|
}
|
||||||
|
if (this._consumer)
|
||||||
|
this._recorder.delete_consumer(this._consumer);
|
||||||
|
this._recorder.stop();
|
||||||
|
this._recorder.set_device(undefined, () => {
|
||||||
|
}); /* -1 := No device */
|
||||||
|
this._recorder = undefined;
|
||||||
|
this._consumer = undefined;
|
||||||
|
this._filter = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
device(): InputDevice {
|
||||||
|
return this._device;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_observer(callback: (value: number) => any) {
|
||||||
|
this._callback = callback;
|
||||||
|
}
|
||||||
|
}
|
@ -1,31 +1,24 @@
|
|||||||
window["require_setup"](module);
|
import {audio as naudio} from "tc-native/connection";
|
||||||
// <reference types="../imports/import_shared.d.ts" />
|
|
||||||
/// <reference types="../../modules/renderer/imports/imports_shared.d.ts" />
|
|
||||||
|
|
||||||
import {audio as naudio} from "teaclient_connection";
|
|
||||||
import * as paths from "path";
|
import * as paths from "path";
|
||||||
|
import {SoundFile} from "tc-shared/sound/Sounds";
|
||||||
|
|
||||||
namespace audio.sounds {
|
export async function play_sound(file: SoundFile) : Promise<void> {
|
||||||
export async function play_sound(file: sound.SoundFile) : Promise<void> {
|
await new Promise((resolve, reject) => {
|
||||||
await new Promise((resolve, reject) => {
|
let pathname = paths.dirname(decodeURIComponent(location.pathname));
|
||||||
let pathname = paths.dirname(decodeURIComponent(location.pathname));
|
if(pathname[0] === '/' && pathname[2] === ':') //e.g.: /C:/test...
|
||||||
if(pathname[0] === '/' && pathname[2] === ':') //e.g.: /C:/test...
|
pathname = pathname.substr(1);
|
||||||
pathname = pathname.substr(1);
|
const path = paths.join(pathname, file.path);
|
||||||
const path = paths.join(pathname, file.path);
|
|
||||||
|
|
||||||
console.log(path);
|
console.log(path);
|
||||||
naudio.sounds.playback_sound({
|
naudio.sounds.playback_sound({
|
||||||
callback: (result, message) => {
|
callback: (result, message) => {
|
||||||
if(result == naudio.sounds.PlaybackResult.SUCCEEDED)
|
if(result == naudio.sounds.PlaybackResult.SUCCEEDED)
|
||||||
resolve();
|
resolve();
|
||||||
else
|
else
|
||||||
reject(naudio.sounds.PlaybackResult[result].toLowerCase() + ": " + message);
|
reject(naudio.sounds.PlaybackResult[result].toLowerCase() + ": " + message);
|
||||||
},
|
},
|
||||||
file: path,
|
file: path,
|
||||||
volume: file.volume
|
volume: file.volume
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(window["audio"] || (window["audio"] = {} as any), audio);
|
|
16
modules/renderer/backend-impl/audio/player.ts
Normal file
16
modules/renderer/backend-impl/audio/player.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import * as handler from "../../audio/AudioPlayer";
|
||||||
|
|
||||||
|
export const initialize = handler.initialize;
|
||||||
|
export const initialized = handler.initialized;
|
||||||
|
|
||||||
|
export const context = handler.context;
|
||||||
|
export const get_master_volume = handler.get_master_volume;
|
||||||
|
export const set_master_volume = handler.set_master_volume;
|
||||||
|
|
||||||
|
export const on_ready = handler.on_ready;
|
||||||
|
|
||||||
|
export const available_devices = handler.available_devices;
|
||||||
|
export const set_device = handler.set_device;
|
||||||
|
export const current_device = handler.current_device;
|
||||||
|
|
||||||
|
export const initializeFromGesture = () => {};
|
8
modules/renderer/backend-impl/audio/recorder.ts
Normal file
8
modules/renderer/backend-impl/audio/recorder.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import * as handler from "../../audio/AudioRecorder";
|
||||||
|
|
||||||
|
export const devices = handler.devices;
|
||||||
|
export const device_refresh_available = handler.device_refresh_available;
|
||||||
|
export const refresh_devices = handler.refresh_devices;
|
||||||
|
|
||||||
|
export const create_input = handler.create_input;
|
||||||
|
export const create_levelmeter = handler.create_levelmeter;
|
3
modules/renderer/backend-impl/audio/sounds.ts
Normal file
3
modules/renderer/backend-impl/audio/sounds.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import * as handler from "../../audio/sounds";
|
||||||
|
|
||||||
|
export const play_sound = handler.play_sound;
|
4
modules/renderer/backend-impl/connection.ts
Normal file
4
modules/renderer/backend-impl/connection.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import * as handler from "../connection/ServerConnection";
|
||||||
|
|
||||||
|
export const spawn_server_connection = handler.spawn_server_connection;
|
||||||
|
export const destroy_server_connection = handler.destroy_server_connection;
|
4
modules/renderer/backend-impl/dns.ts
Normal file
4
modules/renderer/backend-impl/dns.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import * as handler from "../dns/dns_resolver";
|
||||||
|
|
||||||
|
export const supported = handler.supported;
|
||||||
|
export const resolve_address = handler.resolve_address;
|
12
modules/renderer/backend-impl/ppt.ts
Normal file
12
modules/renderer/backend-impl/ppt.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import * as handler from "../ppt";
|
||||||
|
|
||||||
|
export const initialize = handler.initialize;
|
||||||
|
export const finalize = handler.finalize;
|
||||||
|
|
||||||
|
export const register_key_listener = handler.register_key_listener;
|
||||||
|
export const unregister_key_listener = handler.unregister_key_listener;
|
||||||
|
|
||||||
|
export const register_key_hook = handler.register_key_hook;
|
||||||
|
export const unregister_key_hook = handler.unregister_key_hook;
|
||||||
|
|
||||||
|
export const key_pressed = handler.key_pressed;
|
@ -1,186 +1,180 @@
|
|||||||
/// <reference path="../imports/imports_shared.d.ts" />
|
import * as native from "tc-native/connection";
|
||||||
|
|
||||||
|
|
||||||
window["require_setup"](module);
|
|
||||||
import * as native from "teaclient_connection";
|
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
import {DownloadKey, DownloadTransfer, UploadKey, UploadTransfer} from "tc-shared/FileManager";
|
||||||
|
import {base64_encode_ab, str2ab8} from "tc-shared/utils/buffers";
|
||||||
|
|
||||||
namespace _transfer {
|
class NativeFileDownload implements DownloadTransfer {
|
||||||
class NativeFileDownload implements transfer.DownloadTransfer {
|
readonly key: DownloadKey;
|
||||||
readonly key: transfer.DownloadKey;
|
private _handle: native.ft.NativeFileTransfer;
|
||||||
private _handle: native.ft.NativeFileTransfer;
|
private _buffer: Uint8Array;
|
||||||
private _buffer: Uint8Array;
|
|
||||||
|
|
||||||
private _result: Promise<void>;
|
private _result: Promise<void>;
|
||||||
private _response: Response;
|
private _response: Response;
|
||||||
|
|
||||||
private _result_success: () => any;
|
private _result_success: () => any;
|
||||||
private _result_error: (error: any) => any;
|
private _result_error: (error: any) => any;
|
||||||
|
|
||||||
constructor(key: transfer.DownloadKey) {
|
constructor(key: DownloadKey) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this._buffer = new Uint8Array(key.total_size);
|
this._buffer = new Uint8Array(key.total_size);
|
||||||
this._handle = native.ft.spawn_connection({
|
this._handle = native.ft.spawn_connection({
|
||||||
client_transfer_id: key.client_transfer_id,
|
client_transfer_id: key.client_transfer_id,
|
||||||
server_transfer_id: key.server_transfer_id,
|
server_transfer_id: key.server_transfer_id,
|
||||||
|
|
||||||
remote_address: key.peer.hosts[0],
|
remote_address: key.peer.hosts[0],
|
||||||
remote_port: key.peer.port,
|
remote_port: key.peer.port,
|
||||||
|
|
||||||
transfer_key: key.key,
|
transfer_key: key.key,
|
||||||
|
|
||||||
object: native.ft.download_transfer_object_from_buffer(this._buffer.buffer)
|
object: native.ft.download_transfer_object_from_buffer(this._buffer.buffer)
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
get_key(): transfer.DownloadKey {
|
|
||||||
return this.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
async request_file(): Promise<Response> {
|
|
||||||
if(this._response)
|
|
||||||
return this._response;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await (this._result || this._start_transfer());
|
|
||||||
} catch(error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this._response)
|
|
||||||
return this._response;
|
|
||||||
|
|
||||||
const buffer = this._buffer.buffer.slice(this._buffer.byteOffset, this._buffer.byteOffset + Math.min(64, this._buffer.byteLength));
|
|
||||||
|
|
||||||
/* may another task has been stepped by and already set the response */
|
|
||||||
return this._response || (this._response = new Response(this._buffer, {
|
|
||||||
status: 200,
|
|
||||||
statusText: "success",
|
|
||||||
headers: {
|
|
||||||
"X-media-bytes": base64_encode_ab(buffer)
|
|
||||||
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
_start_transfer() : Promise<void> {
|
|
||||||
return this._result = new Promise((resolve, reject) => {
|
|
||||||
this._result_error = (error) => {
|
|
||||||
this._result_error = undefined;
|
|
||||||
this._result_success = undefined;
|
|
||||||
reject(error);
|
|
||||||
};
|
|
||||||
this._result_success = () => {
|
|
||||||
this._result_error = undefined;
|
|
||||||
this._result_success = undefined;
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
this._handle.callback_failed = this._result_error;
|
|
||||||
this._handle.callback_finished = aborted => {
|
|
||||||
if(aborted)
|
|
||||||
this._result_error("aborted");
|
|
||||||
else
|
|
||||||
this._result_success();
|
|
||||||
};
|
|
||||||
|
|
||||||
this._handle.start();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class NativeFileUpload implements transfer.UploadTransfer {
|
get_key(): DownloadKey {
|
||||||
readonly transfer_key: transfer.UploadKey;
|
return this.key;
|
||||||
private _handle: native.ft.NativeFileTransfer;
|
|
||||||
|
|
||||||
private _result: Promise<void>;
|
|
||||||
|
|
||||||
private _result_success: () => any;
|
|
||||||
private _result_error: (error: any) => any;
|
|
||||||
|
|
||||||
constructor(key: transfer.UploadKey) {
|
|
||||||
this.transfer_key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
async put_data(data: BlobPart | File) : Promise<void> {
|
|
||||||
if(this._result) {
|
|
||||||
await this._result;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let buffer: native.ft.FileTransferSource;
|
|
||||||
|
|
||||||
if(data instanceof File) {
|
|
||||||
if(data.size != this.transfer_key.total_size)
|
|
||||||
throw "invalid size";
|
|
||||||
|
|
||||||
buffer = native.ft.upload_transfer_object_from_file(path.dirname(data.path), data.name);
|
|
||||||
} else if(typeof(data) === "string") {
|
|
||||||
if(data.length != this.transfer_key.total_size)
|
|
||||||
throw "invalid size";
|
|
||||||
|
|
||||||
buffer = native.ft.upload_transfer_object_from_buffer(str2ab8(data));
|
|
||||||
} else {
|
|
||||||
let buf = <BufferSource>data;
|
|
||||||
if(buf.byteLength != this.transfer_key.total_size)
|
|
||||||
throw "invalid size";
|
|
||||||
|
|
||||||
if(ArrayBuffer.isView(buf))
|
|
||||||
buf = buf.buffer.slice(buf.byteOffset);
|
|
||||||
|
|
||||||
buffer = native.ft.upload_transfer_object_from_buffer(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._handle = native.ft.spawn_connection({
|
|
||||||
client_transfer_id: this.transfer_key.client_transfer_id,
|
|
||||||
server_transfer_id: this.transfer_key.server_transfer_id,
|
|
||||||
|
|
||||||
remote_address: this.transfer_key.peer.hosts[0],
|
|
||||||
remote_port: this.transfer_key.peer.port,
|
|
||||||
|
|
||||||
transfer_key: this.transfer_key.key,
|
|
||||||
|
|
||||||
object: buffer
|
|
||||||
});
|
|
||||||
|
|
||||||
await (this._result = new Promise((resolve, reject) => {
|
|
||||||
this._result_error = (error) => {
|
|
||||||
this._result_error = undefined;
|
|
||||||
this._result_success = undefined;
|
|
||||||
reject(error);
|
|
||||||
};
|
|
||||||
this._result_success = () => {
|
|
||||||
this._result_error = undefined;
|
|
||||||
this._result_success = undefined;
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
this._handle.callback_failed = this._result_error;
|
|
||||||
this._handle.callback_finished = aborted => {
|
|
||||||
if(aborted)
|
|
||||||
this._result_error("aborted");
|
|
||||||
else
|
|
||||||
this._result_success();
|
|
||||||
};
|
|
||||||
|
|
||||||
this._handle.start();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
get_key(): transfer.UploadKey {
|
|
||||||
return this.transfer_key;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async request_file(): Promise<Response> {
|
||||||
|
if(this._response)
|
||||||
|
return this._response;
|
||||||
|
|
||||||
export function spawn_download_transfer(key: transfer.DownloadKey) : transfer.DownloadTransfer {
|
try {
|
||||||
return new NativeFileDownload(key);
|
await (this._result || this._start_transfer());
|
||||||
|
} catch(error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this._response)
|
||||||
|
return this._response;
|
||||||
|
|
||||||
|
const buffer = this._buffer.buffer.slice(this._buffer.byteOffset, this._buffer.byteOffset + Math.min(64, this._buffer.byteLength));
|
||||||
|
|
||||||
|
/* may another task has been stepped by and already set the response */
|
||||||
|
return this._response || (this._response = new Response(this._buffer, {
|
||||||
|
status: 200,
|
||||||
|
statusText: "success",
|
||||||
|
headers: {
|
||||||
|
"X-media-bytes": base64_encode_ab(buffer)
|
||||||
|
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_start_transfer() : Promise<void> {
|
||||||
|
return this._result = new Promise((resolve, reject) => {
|
||||||
|
this._result_error = (error) => {
|
||||||
|
this._result_error = undefined;
|
||||||
|
this._result_success = undefined;
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
this._result_success = () => {
|
||||||
|
this._result_error = undefined;
|
||||||
|
this._result_success = undefined;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
export function spawn_upload_transfer(key: transfer.UploadKey) : transfer.UploadTransfer {
|
this._handle.callback_failed = this._result_error;
|
||||||
return new NativeFileUpload(key);
|
this._handle.callback_finished = aborted => {
|
||||||
|
if(aborted)
|
||||||
|
this._result_error("aborted");
|
||||||
|
else
|
||||||
|
this._result_success();
|
||||||
|
};
|
||||||
|
|
||||||
|
this._handle.start();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(window["transfer"] || (window["transfer"] = {} as any), _transfer);
|
class NativeFileUpload implements UploadTransfer {
|
||||||
|
readonly transfer_key: UploadKey;
|
||||||
|
private _handle: native.ft.NativeFileTransfer;
|
||||||
|
|
||||||
|
private _result: Promise<void>;
|
||||||
|
|
||||||
|
private _result_success: () => any;
|
||||||
|
private _result_error: (error: any) => any;
|
||||||
|
|
||||||
|
constructor(key: UploadKey) {
|
||||||
|
this.transfer_key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
async put_data(data: BlobPart | File) : Promise<void> {
|
||||||
|
if(this._result) {
|
||||||
|
await this._result;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer: native.ft.FileTransferSource;
|
||||||
|
|
||||||
|
if(data instanceof File) {
|
||||||
|
if(data.size != this.transfer_key.total_size)
|
||||||
|
throw "invalid size";
|
||||||
|
|
||||||
|
buffer = native.ft.upload_transfer_object_from_file(path.dirname(data.path), data.name);
|
||||||
|
} else if(typeof(data) === "string") {
|
||||||
|
if(data.length != this.transfer_key.total_size)
|
||||||
|
throw "invalid size";
|
||||||
|
|
||||||
|
buffer = native.ft.upload_transfer_object_from_buffer(str2ab8(data));
|
||||||
|
} else {
|
||||||
|
let buf = <BufferSource>data;
|
||||||
|
if(buf.byteLength != this.transfer_key.total_size)
|
||||||
|
throw "invalid size";
|
||||||
|
|
||||||
|
if(ArrayBuffer.isView(buf))
|
||||||
|
buf = buf.buffer.slice(buf.byteOffset);
|
||||||
|
|
||||||
|
buffer = native.ft.upload_transfer_object_from_buffer(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._handle = native.ft.spawn_connection({
|
||||||
|
client_transfer_id: this.transfer_key.client_transfer_id,
|
||||||
|
server_transfer_id: this.transfer_key.server_transfer_id,
|
||||||
|
|
||||||
|
remote_address: this.transfer_key.peer.hosts[0],
|
||||||
|
remote_port: this.transfer_key.peer.port,
|
||||||
|
|
||||||
|
transfer_key: this.transfer_key.key,
|
||||||
|
|
||||||
|
object: buffer
|
||||||
|
});
|
||||||
|
|
||||||
|
await (this._result = new Promise((resolve, reject) => {
|
||||||
|
this._result_error = (error) => {
|
||||||
|
this._result_error = undefined;
|
||||||
|
this._result_success = undefined;
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
this._result_success = () => {
|
||||||
|
this._result_error = undefined;
|
||||||
|
this._result_success = undefined;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
this._handle.callback_failed = this._result_error;
|
||||||
|
this._handle.callback_finished = aborted => {
|
||||||
|
if(aborted)
|
||||||
|
this._result_error("aborted");
|
||||||
|
else
|
||||||
|
this._result_success();
|
||||||
|
};
|
||||||
|
|
||||||
|
this._handle.start();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
get_key(): UploadKey {
|
||||||
|
return this.transfer_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function spawn_download_transfer(key: DownloadKey) : DownloadTransfer {
|
||||||
|
return new NativeFileDownload(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function spawn_upload_transfer(key: UploadKey) : UploadTransfer {
|
||||||
|
return new NativeFileUpload(key);
|
||||||
|
}
|
@ -1,318 +1,318 @@
|
|||||||
/// <reference path="../imports/imports_shared.d.ts" />
|
import {AbstractCommandHandler, AbstractCommandHandlerBoss} from "tc-shared/connection/AbstractCommandHandler";
|
||||||
|
|
||||||
window["require_setup"](module);
|
|
||||||
import {
|
import {
|
||||||
destroy_server_connection as _destroy_server_connection,
|
AbstractServerConnection, CommandOptionDefaults, CommandOptions,
|
||||||
NativeServerConnection,
|
ConnectionStateListener,
|
||||||
ServerType,
|
ServerCommand,
|
||||||
spawn_server_connection as _spawn_server_connection
|
voice
|
||||||
} from "teaclient_connection";
|
} from "tc-shared/connection/ConnectionBase";
|
||||||
import {_audio} from "./VoiceConnection";
|
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
|
import {ConnectionHandler, ConnectionState, DisconnectReason} from "tc-shared/ConnectionHandler";
|
||||||
|
import {NativeServerConnection, ServerType, spawn_server_connection as spawn_native_server_connection, destroy_server_connection as destroy_native_server_connection} from "tc-native/connection";
|
||||||
|
import {ConnectionCommandHandler} from "tc-shared/connection/CommandHandler";
|
||||||
|
import {HandshakeHandler} from "tc-shared/connection/HandshakeHandler";
|
||||||
|
import {ServerAddress} from "tc-shared/ui/server";
|
||||||
|
import {TeaSpeakHandshakeHandler} from "tc-shared/profiles/identities/TeamSpeakIdentity";
|
||||||
|
import AbstractVoiceConnection = voice.AbstractVoiceConnection;
|
||||||
|
import {VoiceConnection} from "./VoiceConnection";
|
||||||
|
|
||||||
export namespace _connection {
|
class ErrorCommandHandler extends AbstractCommandHandler {
|
||||||
export namespace native {
|
private _handle: ServerConnection;
|
||||||
import VoiceConnection = _audio.native.VoiceConnection;
|
|
||||||
|
|
||||||
class ErrorCommandHandler extends connection.AbstractCommandHandler {
|
constructor(handle: ServerConnection) {
|
||||||
private _handle: ServerConnection;
|
super(handle);
|
||||||
|
this._handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(handle: ServerConnection) {
|
handle_command(command: ServerCommand): boolean {
|
||||||
super(handle);
|
if(command.command === "error") {
|
||||||
this._handle = handle;
|
const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"];
|
||||||
}
|
const data = command.arguments[0];
|
||||||
|
|
||||||
handle_command(command: connection.ServerCommand): boolean {
|
let return_code : string = data["return_code"];
|
||||||
if(command.command === "error") {
|
if(!return_code) {
|
||||||
const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"];
|
const listener = return_listener["last_command"] || return_listener["_clientinit"];
|
||||||
const data = command.arguments[0];
|
if(typeof(listener) === "function") {
|
||||||
|
console.warn(tr("Received error without return code. Using last command (%o)"), listener);
|
||||||
let return_code : string = data["return_code"];
|
listener(new CommandResult(data));
|
||||||
if(!return_code) {
|
delete return_listener["last_command"];
|
||||||
const listener = return_listener["last_command"] || return_listener["_clientinit"];
|
delete return_listener["_clientinit"];
|
||||||
if(typeof(listener) === "function") {
|
} else {
|
||||||
console.warn(tr("Received error without return code. Using last command (%o)"), listener);
|
console.warn(tr("Received error without return code."), data);
|
||||||
listener(new CommandResult(data));
|
|
||||||
delete return_listener["last_command"];
|
|
||||||
delete return_listener["_clientinit"];
|
|
||||||
} else {
|
|
||||||
console.warn(tr("Received error without return code."), data);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(return_listener[return_code]) {
|
|
||||||
return_listener[return_code](new CommandResult(data));
|
|
||||||
} else {
|
|
||||||
console.warn(tr("Error received for no handler! (%o)"), data);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else if(command.command == "initivexpand") {
|
|
||||||
if(command.arguments[0]["teaspeak"] == true) {
|
|
||||||
console.log("Using TeaSpeak identity type");
|
|
||||||
this._handle.handshake_handler().startHandshake();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else if(command.command == "initivexpand2") {
|
|
||||||
/* its TeamSpeak or TeaSpeak with experimental 3.1 and not up2date */
|
|
||||||
this._handle["_do_teamspeak"] = true;
|
|
||||||
} else if(command.command == "initserver") {
|
|
||||||
const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"];
|
|
||||||
|
|
||||||
if(typeof(return_listener["_clientinit"]) === "function") {
|
|
||||||
return_listener["_clientinit"](new CommandResult({id: 0, message: ""}));
|
|
||||||
delete return_listener["_clientinit"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this._handle.onconnectionstatechanged)
|
|
||||||
this._handle.onconnectionstatechanged(ConnectionState.INITIALISING, ConnectionState.CONNECTING);
|
|
||||||
} else if(command.command == "notifyconnectioninforequest") {
|
|
||||||
this._handle.send_command("setconnectioninfo",
|
|
||||||
{
|
|
||||||
//TODO calculate
|
|
||||||
connection_ping: 0.0000,
|
|
||||||
connection_ping_deviation: 0.0,
|
|
||||||
|
|
||||||
connection_packets_sent_speech: 0,
|
|
||||||
connection_packets_sent_keepalive: 0,
|
|
||||||
connection_packets_sent_control: 0,
|
|
||||||
connection_bytes_sent_speech: 0,
|
|
||||||
connection_bytes_sent_keepalive: 0,
|
|
||||||
connection_bytes_sent_control: 0,
|
|
||||||
connection_packets_received_speech: 0,
|
|
||||||
connection_packets_received_keepalive: 0,
|
|
||||||
connection_packets_received_control: 0,
|
|
||||||
connection_bytes_received_speech: 0,
|
|
||||||
connection_bytes_received_keepalive: 0,
|
|
||||||
connection_bytes_received_control: 0,
|
|
||||||
connection_server2client_packetloss_speech: 0.0000,
|
|
||||||
connection_server2client_packetloss_keepalive: 0.0000,
|
|
||||||
connection_server2client_packetloss_control: 0.0000,
|
|
||||||
connection_server2client_packetloss_total: 0.0000,
|
|
||||||
connection_bandwidth_sent_last_second_speech: 0,
|
|
||||||
connection_bandwidth_sent_last_second_keepalive: 0,
|
|
||||||
connection_bandwidth_sent_last_second_control: 0,
|
|
||||||
connection_bandwidth_sent_last_minute_speech: 0,
|
|
||||||
connection_bandwidth_sent_last_minute_keepalive: 0,
|
|
||||||
connection_bandwidth_sent_last_minute_control: 0,
|
|
||||||
connection_bandwidth_received_last_second_speech: 0,
|
|
||||||
connection_bandwidth_received_last_second_keepalive: 0,
|
|
||||||
connection_bandwidth_received_last_second_control: 0,
|
|
||||||
connection_bandwidth_received_last_minute_speech: 0,
|
|
||||||
connection_bandwidth_received_last_minute_keepalive: 0,
|
|
||||||
connection_bandwidth_received_last_minute_control: 0
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export class ServerConnection extends connection.AbstractServerConnection {
|
if(return_listener[return_code]) {
|
||||||
private _native_handle: NativeServerConnection;
|
return_listener[return_code](new CommandResult(data));
|
||||||
private _voice_connection: VoiceConnection;
|
} else {
|
||||||
|
console.warn(tr("Error received for no handler! (%o)"), data);
|
||||||
private _do_teamspeak: boolean;
|
}
|
||||||
private _return_listener: {[key: string]: (result: CommandResult) => any} = {};
|
|
||||||
|
|
||||||
private _command_handler: NativeConnectionCommandBoss;
|
|
||||||
private _command_error_handler: ErrorCommandHandler;
|
|
||||||
private _command_handler_default: connection.ConnectionCommandHandler;
|
|
||||||
|
|
||||||
private _remote_address: ServerAddress;
|
|
||||||
private _handshake_handler: connection.HandshakeHandler;
|
|
||||||
|
|
||||||
private _return_code_index: number = 0;
|
|
||||||
|
|
||||||
onconnectionstatechanged: connection.ConnectionStateListener;
|
|
||||||
|
|
||||||
constructor(props: ConnectionHandler) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this._command_handler = new NativeConnectionCommandBoss(this);
|
|
||||||
this._command_error_handler = new ErrorCommandHandler(this);
|
|
||||||
this._command_handler_default = new connection.ConnectionCommandHandler(this);
|
|
||||||
|
|
||||||
this._command_handler.register_handler(this._command_error_handler);
|
|
||||||
this._command_handler.register_handler(this._command_handler_default);
|
|
||||||
|
|
||||||
this._native_handle = _spawn_server_connection();
|
|
||||||
this._native_handle.callback_disconnect = reason => {
|
|
||||||
this.client.handleDisconnect(DisconnectReason.CONNECTION_CLOSED, {
|
|
||||||
reason: reason,
|
|
||||||
event: event
|
|
||||||
});
|
|
||||||
};
|
|
||||||
this._native_handle.callback_command = (command, args, switches) => {
|
|
||||||
console.log("Received: %o %o %o", command, args, switches);
|
|
||||||
//FIXME catch error
|
|
||||||
|
|
||||||
this._command_handler.invoke_handle({
|
|
||||||
command: command,
|
|
||||||
arguments: args
|
|
||||||
});
|
|
||||||
};
|
|
||||||
this._voice_connection = new VoiceConnection(this, this._native_handle._voice_connection);
|
|
||||||
|
|
||||||
this.command_helper.initialize();
|
|
||||||
this._voice_connection.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
native_handle() : NativeServerConnection {
|
|
||||||
return this._native_handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
finalize() {
|
|
||||||
if(this._native_handle)
|
|
||||||
_destroy_server_connection(this._native_handle);
|
|
||||||
this._native_handle = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(address: ServerAddress, handshake: connection.HandshakeHandler, timeout?: number): Promise<void> {
|
|
||||||
this._remote_address = address;
|
|
||||||
this._handshake_handler = handshake;
|
|
||||||
this._do_teamspeak = false;
|
|
||||||
handshake.setConnection(this);
|
|
||||||
handshake.initialize();
|
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
this._native_handle.connect({
|
|
||||||
remote_host: address.host,
|
|
||||||
remote_port: address.port,
|
|
||||||
|
|
||||||
timeout: typeof(timeout) === "number" ? timeout : -1,
|
|
||||||
|
|
||||||
|
|
||||||
callback: error => {
|
|
||||||
if(error != 0) {
|
|
||||||
/* required to notify the handle, just a promise reject does not work */
|
|
||||||
this.client.handleDisconnect(DisconnectReason.CONNECT_FAILURE, error);
|
|
||||||
reject(this._native_handle.error_message(error));
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Remote server type: %o (%s)", this._native_handle.server_type, ServerType[this._native_handle.server_type]);
|
|
||||||
if(this._native_handle.server_type == ServerType.TEAMSPEAK || this._do_teamspeak) {
|
|
||||||
console.log("Trying to use TeamSpeak's identity system");
|
|
||||||
this.handshake_handler().on_teamspeak();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
identity_key: (handshake.get_identity_handler() as profiles.identities.TeaSpeakHandshakeHandler).identity.private_key,
|
|
||||||
teamspeak: false
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
remote_address(): ServerAddress {
|
|
||||||
return this._remote_address;
|
|
||||||
}
|
|
||||||
|
|
||||||
handshake_handler(): connection.HandshakeHandler {
|
|
||||||
return this._handshake_handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
connected(): boolean {
|
|
||||||
return typeof(this._native_handle) !== "undefined" && this._native_handle.connected();
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect(reason?: string): Promise<void> {
|
|
||||||
console.trace("Disconnect: %s",reason);
|
|
||||||
return new Promise<void>((resolve, reject) => this._native_handle.disconnect(reason || "", error => {
|
|
||||||
if(error == 0)
|
|
||||||
resolve();
|
|
||||||
else
|
|
||||||
reject(this._native_handle.error_message(error));
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
support_voice(): boolean {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
} else if(command.command == "initivexpand") {
|
||||||
|
if(command.arguments[0]["teaspeak"] == true) {
|
||||||
|
console.log("Using TeaSpeak identity type");
|
||||||
|
this._handle.handshake_handler().startHandshake();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if(command.command == "initivexpand2") {
|
||||||
|
/* its TeamSpeak or TeaSpeak with experimental 3.1 and not up2date */
|
||||||
|
this._handle["_do_teamspeak"] = true;
|
||||||
|
} else if(command.command == "initserver") {
|
||||||
|
const return_listener: {[key: string]: (result: CommandResult) => any} = this._handle["_return_listener"];
|
||||||
|
|
||||||
voice_connection(): connection.voice.AbstractVoiceConnection {
|
if(typeof(return_listener["_clientinit"]) === "function") {
|
||||||
return this._voice_connection;
|
return_listener["_clientinit"](new CommandResult({id: 0, message: ""}));
|
||||||
}
|
delete return_listener["_clientinit"];
|
||||||
|
|
||||||
command_handler_boss(): connection.AbstractCommandHandlerBoss {
|
|
||||||
return this._command_handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
private generate_return_code() : string {
|
|
||||||
return (this._return_code_index++).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
send_command(command: string, data?: any, _options?: connection.CommandOptions): Promise<CommandResult> {
|
|
||||||
if(!this.connected()) {
|
|
||||||
console.warn(tr("Tried to send a command without a valid connection."));
|
|
||||||
return Promise.reject(tr("not connected"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: connection.CommandOptions = {};
|
if(this._handle.onconnectionstatechanged)
|
||||||
Object.assign(options, connection.CommandOptionDefaults);
|
this._handle.onconnectionstatechanged(ConnectionState.INITIALISING, ConnectionState.CONNECTING);
|
||||||
Object.assign(options, _options);
|
} else if(command.command == "notifyconnectioninforequest") {
|
||||||
|
this._handle.send_command("setconnectioninfo",
|
||||||
|
{
|
||||||
|
//TODO calculate
|
||||||
|
connection_ping: 0.0000,
|
||||||
|
connection_ping_deviation: 0.0,
|
||||||
|
|
||||||
data = $.isArray(data) ? data : [data || {}];
|
connection_packets_sent_speech: 0,
|
||||||
if(data.length == 0) /* we require min one arg to append return_code */
|
connection_packets_sent_keepalive: 0,
|
||||||
data.push({});
|
connection_packets_sent_control: 0,
|
||||||
|
connection_bytes_sent_speech: 0,
|
||||||
let return_code = data[0]["return_code"] !== undefined ? data[0].return_code : this.generate_return_code();
|
connection_bytes_sent_keepalive: 0,
|
||||||
data[0]["return_code"] = return_code;
|
connection_bytes_sent_control: 0,
|
||||||
|
connection_packets_received_speech: 0,
|
||||||
console.log("Sending %s (%o)", command, data);
|
connection_packets_received_keepalive: 0,
|
||||||
const promise = new Promise<CommandResult>((resolve, reject) => {
|
connection_packets_received_control: 0,
|
||||||
const timeout_id = setTimeout(() => {
|
connection_bytes_received_speech: 0,
|
||||||
delete this._return_listener[return_code];
|
connection_bytes_received_keepalive: 0,
|
||||||
reject("timeout");
|
connection_bytes_received_control: 0,
|
||||||
}, 5000);
|
connection_server2client_packetloss_speech: 0.0000,
|
||||||
|
connection_server2client_packetloss_keepalive: 0.0000,
|
||||||
this._return_listener[return_code] = result => {
|
connection_server2client_packetloss_control: 0.0000,
|
||||||
clearTimeout(timeout_id);
|
connection_server2client_packetloss_total: 0.0000,
|
||||||
delete this._return_listener[return_code];
|
connection_bandwidth_sent_last_second_speech: 0,
|
||||||
|
connection_bandwidth_sent_last_second_keepalive: 0,
|
||||||
(result.success ? resolve : reject)(result);
|
connection_bandwidth_sent_last_second_control: 0,
|
||||||
};
|
connection_bandwidth_sent_last_minute_speech: 0,
|
||||||
|
connection_bandwidth_sent_last_minute_keepalive: 0,
|
||||||
if(command == "clientinit")
|
connection_bandwidth_sent_last_minute_control: 0,
|
||||||
this._return_listener["_clientinit"] = this._return_listener[return_code]; /* fix for TS3 (clientinit does not accept a return code) */
|
connection_bandwidth_received_last_second_speech: 0,
|
||||||
|
connection_bandwidth_received_last_second_keepalive: 0,
|
||||||
try {
|
connection_bandwidth_received_last_second_control: 0,
|
||||||
this._native_handle.send_command(command, data, options.flagset || []);
|
connection_bandwidth_received_last_minute_speech: 0,
|
||||||
} catch(error) {
|
connection_bandwidth_received_last_minute_keepalive: 0,
|
||||||
console.warn(tr("Failed to send command: %o"), error);
|
connection_bandwidth_received_last_minute_control: 0
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
return this._command_handler_default.proxy_command_promise(promise, options);
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ping(): { native: number; javascript?: number } {
|
export class ServerConnection extends AbstractServerConnection {
|
||||||
return {
|
private _native_handle: NativeServerConnection;
|
||||||
native: this._native_handle ? (this._native_handle.current_ping() / 1000) : -2
|
private _voice_connection: VoiceConnection;
|
||||||
};
|
|
||||||
|
private _do_teamspeak: boolean;
|
||||||
|
private _return_listener: {[key: string]: (result: CommandResult) => any} = {};
|
||||||
|
|
||||||
|
private _command_handler: NativeConnectionCommandBoss;
|
||||||
|
private _command_error_handler: ErrorCommandHandler;
|
||||||
|
private _command_handler_default: ConnectionCommandHandler;
|
||||||
|
|
||||||
|
private _remote_address: ServerAddress;
|
||||||
|
private _handshake_handler: HandshakeHandler;
|
||||||
|
|
||||||
|
private _return_code_index: number = 0;
|
||||||
|
|
||||||
|
onconnectionstatechanged: ConnectionStateListener;
|
||||||
|
|
||||||
|
constructor(props: ConnectionHandler) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._command_handler = new NativeConnectionCommandBoss(this);
|
||||||
|
this._command_error_handler = new ErrorCommandHandler(this);
|
||||||
|
this._command_handler_default = new ConnectionCommandHandler(this);
|
||||||
|
|
||||||
|
this._command_handler.register_handler(this._command_error_handler);
|
||||||
|
this._command_handler.register_handler(this._command_handler_default);
|
||||||
|
|
||||||
|
this._native_handle = spawn_native_server_connection();
|
||||||
|
this._native_handle.callback_disconnect = reason => {
|
||||||
|
this.client.handleDisconnect(DisconnectReason.CONNECTION_CLOSED, {
|
||||||
|
reason: reason,
|
||||||
|
event: event
|
||||||
|
});
|
||||||
|
};
|
||||||
|
this._native_handle.callback_command = (command, args, switches) => {
|
||||||
|
console.log("Received: %o %o %o", command, args, switches);
|
||||||
|
//FIXME catch error
|
||||||
|
|
||||||
|
this._command_handler.invoke_handle({
|
||||||
|
command: command,
|
||||||
|
arguments: args
|
||||||
|
});
|
||||||
|
};
|
||||||
|
this._voice_connection = new VoiceConnection(this, this._native_handle._voice_connection);
|
||||||
|
|
||||||
|
this.command_helper.initialize();
|
||||||
|
this._voice_connection.setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
native_handle() : NativeServerConnection {
|
||||||
|
return this._native_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize() {
|
||||||
|
if(this._native_handle)
|
||||||
|
destroy_native_server_connection(this._native_handle);
|
||||||
|
this._native_handle = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(address: ServerAddress, handshake: HandshakeHandler, timeout?: number): Promise<void> {
|
||||||
|
this._remote_address = address;
|
||||||
|
this._handshake_handler = handshake;
|
||||||
|
this._do_teamspeak = false;
|
||||||
|
handshake.setConnection(this);
|
||||||
|
handshake.initialize();
|
||||||
|
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
this._native_handle.connect({
|
||||||
|
remote_host: address.host,
|
||||||
|
remote_port: address.port,
|
||||||
|
|
||||||
|
timeout: typeof(timeout) === "number" ? timeout : -1,
|
||||||
|
|
||||||
|
|
||||||
|
callback: error => {
|
||||||
|
if(error != 0) {
|
||||||
|
/* required to notify the handle, just a promise reject does not work */
|
||||||
|
this.client.handleDisconnect(DisconnectReason.CONNECT_FAILURE, error);
|
||||||
|
reject(this._native_handle.error_message(error));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Remote server type: %o (%s)", this._native_handle.server_type, ServerType[this._native_handle.server_type]);
|
||||||
|
if(this._native_handle.server_type == ServerType.TEAMSPEAK || this._do_teamspeak) {
|
||||||
|
console.log("Trying to use TeamSpeak's identity system");
|
||||||
|
this.handshake_handler().on_teamspeak();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
identity_key: (handshake.get_identity_handler() as TeaSpeakHandshakeHandler).identity.private_key,
|
||||||
|
teamspeak: false
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
remote_address(): ServerAddress {
|
||||||
|
return this._remote_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
handshake_handler(): HandshakeHandler {
|
||||||
|
return this._handshake_handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
connected(): boolean {
|
||||||
|
return typeof(this._native_handle) !== "undefined" && this._native_handle.connected();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect(reason?: string): Promise<void> {
|
||||||
|
console.trace("Disconnect: %s",reason);
|
||||||
|
return new Promise<void>((resolve, reject) => this._native_handle.disconnect(reason || "", error => {
|
||||||
|
if(error == 0)
|
||||||
|
resolve();
|
||||||
|
else
|
||||||
|
reject(this._native_handle.error_message(error));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
support_voice(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
voice_connection(): AbstractVoiceConnection {
|
||||||
|
return this._voice_connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
command_handler_boss(): AbstractCommandHandlerBoss {
|
||||||
|
return this._command_handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
private generate_return_code() : string {
|
||||||
|
return (this._return_code_index++).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
send_command(command: string, data?: any, _options?: CommandOptions): Promise<CommandResult> {
|
||||||
|
if(!this.connected()) {
|
||||||
|
console.warn(tr("Tried to send a command without a valid connection."));
|
||||||
|
return Promise.reject(tr("not connected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: CommandOptions = {};
|
||||||
|
Object.assign(options, CommandOptionDefaults);
|
||||||
|
Object.assign(options, _options);
|
||||||
|
|
||||||
|
data = $.isArray(data) ? data : [data || {}];
|
||||||
|
if(data.length == 0) /* we require min one arg to append return_code */
|
||||||
|
data.push({});
|
||||||
|
|
||||||
|
let return_code = data[0]["return_code"] !== undefined ? data[0].return_code : this.generate_return_code();
|
||||||
|
data[0]["return_code"] = return_code;
|
||||||
|
|
||||||
|
console.log("Sending %s (%o)", command, data);
|
||||||
|
const promise = new Promise<CommandResult>((resolve, reject) => {
|
||||||
|
const timeout_id = setTimeout(() => {
|
||||||
|
delete this._return_listener[return_code];
|
||||||
|
reject("timeout");
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
this._return_listener[return_code] = result => {
|
||||||
|
clearTimeout(timeout_id);
|
||||||
|
delete this._return_listener[return_code];
|
||||||
|
|
||||||
|
(result.success ? resolve : reject)(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
if(command == "clientinit")
|
||||||
|
this._return_listener["_clientinit"] = this._return_listener[return_code]; /* fix for TS3 (clientinit does not accept a return code) */
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._native_handle.send_command(command, data, options.flagset || []);
|
||||||
|
} catch(error) {
|
||||||
|
console.warn(tr("Failed to send command: %o"), error);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
return this._command_handler_default.proxy_command_promise(promise, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NativeConnectionCommandBoss extends connection.AbstractCommandHandlerBoss {
|
ping(): { native: number; javascript?: number } {
|
||||||
constructor(connection: connection.AbstractServerConnection) {
|
return {
|
||||||
super(connection);
|
native: this._native_handle ? (this._native_handle.current_ping() / 1000) : -2
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* override the "normal" connection */
|
|
||||||
export function spawn_server_connection(handle: ConnectionHandler) : connection.AbstractServerConnection {
|
|
||||||
console.log("Spawning native connection");
|
|
||||||
return new native.ServerConnection(handle); /* will be overridden by the client */
|
|
||||||
}
|
|
||||||
|
|
||||||
export function destroy_server_connection(handle: connection.AbstractServerConnection) {
|
|
||||||
if(!(handle instanceof native.ServerConnection))
|
|
||||||
throw "invalid handle";
|
|
||||||
//TODO: Here!
|
|
||||||
console.log("Call to destroy a server connection");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Object.assign(window["connection"] || (window["connection"] = {} as any), _connection);
|
|
||||||
|
export class NativeConnectionCommandBoss extends AbstractCommandHandlerBoss {
|
||||||
|
constructor(connection: AbstractServerConnection) {
|
||||||
|
super(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* override the "normal" connection */
|
||||||
|
export function spawn_server_connection(handle: ConnectionHandler) : AbstractServerConnection {
|
||||||
|
console.log("Spawning native connection");
|
||||||
|
return new ServerConnection(handle); /* will be overridden by the client */
|
||||||
|
}
|
||||||
|
|
||||||
|
export function destroy_server_connection(handle: AbstractServerConnection) {
|
||||||
|
if(!(handle instanceof ServerConnection))
|
||||||
|
throw "invalid handle";
|
||||||
|
//TODO: Here!
|
||||||
|
console.log("Call to destroy a server connection");
|
||||||
|
}
|
@ -1,182 +1,180 @@
|
|||||||
import {_connection} from "./ServerConnection";
|
import {voice} from "tc-shared/connection/ConnectionBase";
|
||||||
import {_audio as _recorder} from "../audio/AudioRecorder";
|
import AbstractVoiceConnection = voice.AbstractVoiceConnection;
|
||||||
|
import {ServerConnection} from "./ServerConnection";
|
||||||
|
import {NativeVoiceConnection} from "tc-native/connection";
|
||||||
|
import {RecorderProfile} from "tc-shared/voice/RecorderProfile";
|
||||||
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
|
import {LogCategory} from "tc-shared/log";
|
||||||
|
import * as log from "tc-shared/log";
|
||||||
|
import VoiceClient = voice.VoiceClient;
|
||||||
|
import LatencySettings = voice.LatencySettings;
|
||||||
|
import {NativeInput} from "../audio/AudioRecorder";
|
||||||
|
|
||||||
import {
|
export class VoiceConnection extends AbstractVoiceConnection {
|
||||||
NativeVoiceConnection,
|
readonly connection: ServerConnection;
|
||||||
NativeVoiceClient
|
readonly handle: NativeVoiceConnection;
|
||||||
} from "teaclient_connection";
|
|
||||||
|
|
||||||
export namespace _audio {
|
private _audio_source: RecorderProfile;
|
||||||
export namespace native {
|
|
||||||
import ServerConnection = _connection.native.ServerConnection;
|
|
||||||
|
|
||||||
export class VoiceConnection extends connection.voice.AbstractVoiceConnection {
|
constructor(connection: ServerConnection, voice: NativeVoiceConnection) {
|
||||||
readonly connection: ServerConnection;
|
super(connection);
|
||||||
readonly handle: NativeVoiceConnection;
|
this.connection = connection;
|
||||||
|
this.handle = voice;
|
||||||
|
}
|
||||||
|
|
||||||
private _audio_source: RecorderProfile;
|
setup() { }
|
||||||
|
|
||||||
constructor(connection: ServerConnection, voice: NativeVoiceConnection) {
|
async acquire_voice_recorder(recorder: RecorderProfile | undefined, enforce?: boolean) {
|
||||||
super(connection);
|
if(this._audio_source === recorder && !enforce)
|
||||||
this.connection = connection;
|
return;
|
||||||
this.handle = voice;
|
|
||||||
}
|
|
||||||
|
|
||||||
setup() { }
|
if(this._audio_source)
|
||||||
|
await this._audio_source.unmount();
|
||||||
|
|
||||||
async acquire_voice_recorder(recorder: RecorderProfile | undefined, enforce?: boolean) {
|
if(recorder) {
|
||||||
if(this._audio_source === recorder && !enforce)
|
if(!(recorder.input instanceof NativeInput))
|
||||||
return;
|
throw "Recorder input must be an instance of NativeInput!";
|
||||||
|
await recorder.unmount();
|
||||||
if(this._audio_source)
|
|
||||||
await this._audio_source.unmount();
|
|
||||||
|
|
||||||
if(recorder) {
|
|
||||||
if(!(recorder.input instanceof _recorder.recorder.NativeInput))
|
|
||||||
throw "Recorder input must be an instance of NativeInput!";
|
|
||||||
await recorder.unmount();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handleVoiceEnded();
|
|
||||||
this._audio_source = recorder;
|
|
||||||
|
|
||||||
if(recorder) {
|
|
||||||
recorder.current_handler = this.connection.client;
|
|
||||||
|
|
||||||
recorder.callback_unmount = () => {
|
|
||||||
this._audio_source = undefined;
|
|
||||||
this.handle.set_audio_source(undefined);
|
|
||||||
this.connection.client.update_voice_status(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
recorder.callback_start = this.on_voice_started.bind(this);
|
|
||||||
recorder.callback_stop = this.handleVoiceEnded.bind(this);
|
|
||||||
|
|
||||||
recorder.callback_support_change = () => {
|
|
||||||
this.connection.client.update_voice_status(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.handle.set_audio_source((recorder.input as _recorder.recorder.NativeInput).consumer);
|
|
||||||
}
|
|
||||||
this.connection.client.update_voice_status(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
voice_playback_support() : boolean {
|
|
||||||
return this.connection.connected();
|
|
||||||
}
|
|
||||||
|
|
||||||
voice_send_support() : boolean {
|
|
||||||
return this.connection.connected();
|
|
||||||
}
|
|
||||||
|
|
||||||
private current_channel_codec() : number {
|
|
||||||
const chandler = this.connection.client;
|
|
||||||
return (chandler.getClient().currentChannel() || {properties: { channel_codec: 4}}).properties.channel_codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleVoiceEnded() {
|
|
||||||
const chandler = this.connection.client;
|
|
||||||
chandler.getClient().speaking = false;
|
|
||||||
|
|
||||||
if(!chandler.connected)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if(chandler.client_status.input_muted)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
console.log(tr("Local voice ended"));
|
|
||||||
//TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
private on_voice_started() {
|
|
||||||
const chandler = this.connection.client;
|
|
||||||
if(chandler.client_status.input_muted) {
|
|
||||||
/* evil hack due to the settings :D */
|
|
||||||
log.warn(LogCategory.VOICE, tr("Received local voice started event, even thou we're muted! Do not send any voice."));
|
|
||||||
if(this.handle) {
|
|
||||||
this.handle.enable_voice_send(false);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info(LogCategory.VOICE, tr("Local voice started"));
|
|
||||||
this.handle.enable_voice_send(true);
|
|
||||||
|
|
||||||
const ch = chandler.getClient();
|
|
||||||
if(ch) ch.speaking = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
connected(): boolean {
|
|
||||||
return true; /* we cant be disconnected at any time! */
|
|
||||||
}
|
|
||||||
|
|
||||||
voice_recorder(): RecorderProfile {
|
|
||||||
return this._audio_source;
|
|
||||||
}
|
|
||||||
|
|
||||||
available_clients(): connection.voice.VoiceClient[] {
|
|
||||||
return this.handle.available_clients().map(e => Object.assign(e, {
|
|
||||||
support_latency_settings() { return true; },
|
|
||||||
reset_latency_settings: function() {
|
|
||||||
const stream = this.get_stream();
|
|
||||||
stream.set_buffer_latency(0.02);
|
|
||||||
stream.set_buffer_max_latency(0.2);
|
|
||||||
return this.latency_settings();
|
|
||||||
},
|
|
||||||
latency_settings: function (settings?: connection.voice.LatencySettings) : connection.voice.LatencySettings {
|
|
||||||
const stream = this.get_stream();
|
|
||||||
if(typeof settings !== "undefined") {
|
|
||||||
stream.set_buffer_latency(settings.min_buffer / 1000);
|
|
||||||
stream.set_buffer_max_latency(settings.max_buffer / 100);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
max_buffer: Math.floor(stream.get_buffer_max_latency() * 1000),
|
|
||||||
min_buffer: Math.floor(stream.get_buffer_latency() * 1000)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
support_flush() { return true; },
|
|
||||||
flush: function () {
|
|
||||||
const stream = this.get_stream();
|
|
||||||
stream.flush_buffer();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
find_client(client_id: number) : connection.voice.VoiceClient | undefined {
|
|
||||||
for(const client of this.available_clients())
|
|
||||||
if(client.client_id === client_id)
|
|
||||||
return client;
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
unregister_client(client: connection.voice.VoiceClient): Promise<void> {
|
|
||||||
this.handle.unregister_client(client.client_id);
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
register_client(client_id: number): connection.voice.VoiceClient {
|
|
||||||
const client = this.handle.register_client(client_id);
|
|
||||||
const c = this.find_client(client_id);
|
|
||||||
c.reset_latency_settings();
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
decoding_supported(codec: number): boolean {
|
|
||||||
return this.handle.decoding_supported(codec);
|
|
||||||
}
|
|
||||||
|
|
||||||
encoding_supported(codec: number): boolean {
|
|
||||||
return this.handle.encoding_supported(codec);
|
|
||||||
}
|
|
||||||
|
|
||||||
get_encoder_codec(): number {
|
|
||||||
return this.handle.get_encoder_codec();
|
|
||||||
}
|
|
||||||
|
|
||||||
set_encoder_codec(codec: number) {
|
|
||||||
return this.handle.set_encoder_codec(codec);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.handleVoiceEnded();
|
||||||
|
this._audio_source = recorder;
|
||||||
|
|
||||||
|
if(recorder) {
|
||||||
|
recorder.current_handler = this.connection.client;
|
||||||
|
|
||||||
|
recorder.callback_unmount = () => {
|
||||||
|
this._audio_source = undefined;
|
||||||
|
this.handle.set_audio_source(undefined);
|
||||||
|
this.connection.client.update_voice_status(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
recorder.callback_start = this.on_voice_started.bind(this);
|
||||||
|
recorder.callback_stop = this.handleVoiceEnded.bind(this);
|
||||||
|
|
||||||
|
recorder.callback_support_change = () => {
|
||||||
|
this.connection.client.update_voice_status(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handle.set_audio_source((recorder.input as NativeInput).consumer);
|
||||||
|
}
|
||||||
|
this.connection.client.update_voice_status(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
voice_playback_support() : boolean {
|
||||||
|
return this.connection.connected();
|
||||||
|
}
|
||||||
|
|
||||||
|
voice_send_support() : boolean {
|
||||||
|
return this.connection.connected();
|
||||||
|
}
|
||||||
|
|
||||||
|
private current_channel_codec() : number {
|
||||||
|
const chandler = this.connection.client;
|
||||||
|
return (chandler.getClient().currentChannel() || {properties: { channel_codec: 4}}).properties.channel_codec;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleVoiceEnded() {
|
||||||
|
const chandler = this.connection.client;
|
||||||
|
chandler.getClient().speaking = false;
|
||||||
|
|
||||||
|
if(!chandler.connected)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(chandler.client_status.input_muted)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
console.log(tr("Local voice ended"));
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
private on_voice_started() {
|
||||||
|
const chandler = this.connection.client;
|
||||||
|
if(chandler.client_status.input_muted) {
|
||||||
|
/* evil hack due to the settings :D */
|
||||||
|
log.warn(LogCategory.VOICE, tr("Received local voice started event, even thou we're muted! Do not send any voice."));
|
||||||
|
if(this.handle) {
|
||||||
|
this.handle.enable_voice_send(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(LogCategory.VOICE, tr("Local voice started"));
|
||||||
|
this.handle.enable_voice_send(true);
|
||||||
|
|
||||||
|
const ch = chandler.getClient();
|
||||||
|
if(ch) ch.speaking = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
connected(): boolean {
|
||||||
|
return true; /* we cant be disconnected at any time! */
|
||||||
|
}
|
||||||
|
|
||||||
|
voice_recorder(): RecorderProfile {
|
||||||
|
return this._audio_source;
|
||||||
|
}
|
||||||
|
|
||||||
|
available_clients(): VoiceClient[] {
|
||||||
|
return this.handle.available_clients().map(e => Object.assign(e, {
|
||||||
|
support_latency_settings() { return true; },
|
||||||
|
reset_latency_settings: function() {
|
||||||
|
const stream = this.get_stream();
|
||||||
|
stream.set_buffer_latency(0.02);
|
||||||
|
stream.set_buffer_max_latency(0.2);
|
||||||
|
return this.latency_settings();
|
||||||
|
},
|
||||||
|
latency_settings: function (settings?: LatencySettings) : LatencySettings {
|
||||||
|
const stream = this.get_stream();
|
||||||
|
if(typeof settings !== "undefined") {
|
||||||
|
stream.set_buffer_latency(settings.min_buffer / 1000);
|
||||||
|
stream.set_buffer_max_latency(settings.max_buffer / 100);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
max_buffer: Math.floor(stream.get_buffer_max_latency() * 1000),
|
||||||
|
min_buffer: Math.floor(stream.get_buffer_latency() * 1000)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
support_flush() { return true; },
|
||||||
|
flush: function () {
|
||||||
|
const stream = this.get_stream();
|
||||||
|
stream.flush_buffer();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
find_client(client_id: number) : VoiceClient | undefined {
|
||||||
|
for(const client of this.available_clients())
|
||||||
|
if(client.client_id === client_id)
|
||||||
|
return client;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
unregister_client(client: VoiceClient): Promise<void> {
|
||||||
|
this.handle.unregister_client(client.client_id);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
register_client(client_id: number): VoiceClient {
|
||||||
|
const client = this.handle.register_client(client_id);
|
||||||
|
const c = this.find_client(client_id);
|
||||||
|
c.reset_latency_settings();
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
decoding_supported(codec: number): boolean {
|
||||||
|
return this.handle.decoding_supported(codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
encoding_supported(codec: number): boolean {
|
||||||
|
return this.handle.encoding_supported(codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
get_encoder_codec(): number {
|
||||||
|
return this.handle.get_encoder_codec();
|
||||||
|
}
|
||||||
|
|
||||||
|
set_encoder_codec(codec: number) {
|
||||||
|
return this.handle.set_encoder_codec(codec);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,5 @@
|
|||||||
import {class_to_image} from "./icon-helper";
|
import {class_to_image} from "./icon-helper";
|
||||||
|
import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
|
||||||
window["require_setup"](module);
|
|
||||||
|
|
||||||
import * as electron from "electron";
|
import * as electron from "electron";
|
||||||
const remote = electron.remote;
|
const remote = electron.remote;
|
||||||
const {Menu, MenuItem} = remote;
|
const {Menu, MenuItem} = remote;
|
||||||
@ -124,5 +122,3 @@ class ElectronContextMenu implements contextmenu.ContextMenuProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
contextmenu.set_provider(new ElectronContextMenu());
|
contextmenu.set_provider(new ElectronContextMenu());
|
||||||
|
|
||||||
export {};
|
|
@ -1,34 +1,32 @@
|
|||||||
/// <reference path="../imports/imports_shared.d.ts" />
|
import {ServerAddress} from "tc-shared/ui/server";
|
||||||
|
import * as loader from "tc-loader";
|
||||||
|
|
||||||
window["require_setup"](module);
|
import {AddressTarget, ResolveOptions} from "tc-shared/dns";
|
||||||
import * as dns_handler from "teaclient_dns";
|
import * as dns_handler from "tc-native/dns";
|
||||||
|
|
||||||
namespace _dns {
|
export function supported() { return true; }
|
||||||
export function supported() { return true; }
|
export async function resolve_address(address: ServerAddress, _options?: ResolveOptions) : Promise<AddressTarget> {
|
||||||
export async function resolve_address(address: ServerAddress, _options?: dns.ResolveOptions) : Promise<dns.AddressTarget> {
|
/* backwards compatibility */
|
||||||
/* backwards compatibility */
|
if(typeof(address) === "string") {
|
||||||
if(typeof(address) === "string") {
|
address = {
|
||||||
address = {
|
host: address,
|
||||||
host: address,
|
port: 9987
|
||||||
port: 9987
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise<dns.AddressTarget>((resolve, reject) => {
|
|
||||||
dns_handler.resolve_cr(address.host, address.port, result => {
|
|
||||||
if(typeof(result) === "string")
|
|
||||||
reject(result);
|
|
||||||
else
|
|
||||||
resolve({
|
|
||||||
target_ip: result.host,
|
|
||||||
target_port: result.port
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new Promise<AddressTarget>((resolve, reject) => {
|
||||||
|
dns_handler.resolve_cr(address.host, address.port, result => {
|
||||||
|
if(typeof(result) === "string")
|
||||||
|
reject(result);
|
||||||
|
else
|
||||||
|
resolve({
|
||||||
|
target_ip: result.host,
|
||||||
|
target_port: result.port
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(window["dns"] || (window["dns"] = {} as any), _dns);
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
name: "Native DNS initialized",
|
name: "Native DNS initialized",
|
||||||
function: async () => {
|
function: async () => {
|
||||||
|
@ -33,7 +33,7 @@ export function class_to_image(klass: string) : NativeImage {
|
|||||||
|
|
||||||
export async function initialize() {
|
export async function initialize() {
|
||||||
if(!_div) {
|
if(!_div) {
|
||||||
_div = $.spawn("div");
|
_div = $(document.createElement("div"));
|
||||||
_div.css('display', 'none');
|
_div.css('display', 'none');
|
||||||
_div.appendTo(document.body);
|
_div.appendTo(document.body);
|
||||||
}
|
}
|
||||||
|
5426
modules/renderer/imports/.copy_imports_shared.d.ts
vendored
5426
modules/renderer/imports/.copy_imports_shared.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@ -1,97 +0,0 @@
|
|||||||
|
|
||||||
/* File: /home/wolverindev/TeaSpeak/Web-Client/shared/loader/loader.ts */
|
|
||||||
declare interface Window {
|
|
||||||
tr(message: string): string;
|
|
||||||
}
|
|
||||||
declare namespace loader {
|
|
||||||
export namespace config {
|
|
||||||
export const loader_groups;
|
|
||||||
export const verbose;
|
|
||||||
export const error;
|
|
||||||
}
|
|
||||||
export type Task = {
|
|
||||||
name: string;
|
|
||||||
priority: number; /* tasks with the same priority will be executed in sync */
|
|
||||||
function: () => Promise<void>;
|
|
||||||
};
|
|
||||||
export enum Stage {
|
|
||||||
/*
|
|
||||||
loading loader required files (incl this)
|
|
||||||
*/
|
|
||||||
INITIALIZING,
|
|
||||||
/*
|
|
||||||
setting up the loading process
|
|
||||||
*/
|
|
||||||
SETUP,
|
|
||||||
/*
|
|
||||||
loading all style sheet files
|
|
||||||
*/
|
|
||||||
STYLE,
|
|
||||||
/*
|
|
||||||
loading all javascript files
|
|
||||||
*/
|
|
||||||
JAVASCRIPT,
|
|
||||||
/*
|
|
||||||
loading all template files
|
|
||||||
*/
|
|
||||||
TEMPLATES,
|
|
||||||
/*
|
|
||||||
initializing static/global stuff
|
|
||||||
*/
|
|
||||||
JAVASCRIPT_INITIALIZING,
|
|
||||||
/*
|
|
||||||
finalizing load process
|
|
||||||
*/
|
|
||||||
FINALIZING,
|
|
||||||
/*
|
|
||||||
invoking main task
|
|
||||||
*/
|
|
||||||
LOADED,
|
|
||||||
DONE
|
|
||||||
}
|
|
||||||
export function get_cache_version();
|
|
||||||
export function finished();
|
|
||||||
export function running();
|
|
||||||
export function register_task(stage: Stage, task: Task);
|
|
||||||
export function execute(): Promise<any>;
|
|
||||||
export function execute_managed();
|
|
||||||
export type DependSource = {
|
|
||||||
url: string;
|
|
||||||
depends: string[];
|
|
||||||
};
|
|
||||||
export type SourcePath = string | DependSource | string[];
|
|
||||||
export class SyntaxError {
|
|
||||||
source: any;
|
|
||||||
constructor(source: any);
|
|
||||||
}
|
|
||||||
export function load_script(path: SourcePath): Promise<void>;
|
|
||||||
export function load_scripts(paths: SourcePath[]): Promise<void>;
|
|
||||||
export function load_style(path: SourcePath): Promise<void>;
|
|
||||||
export function load_styles(paths: SourcePath[]): Promise<void>;
|
|
||||||
export type ErrorHandler = (message: string, detail: string) => void;
|
|
||||||
export function critical_error(message: string, detail?: string);
|
|
||||||
export function critical_error_handler(handler?: ErrorHandler, override?: boolean): ErrorHandler;
|
|
||||||
}
|
|
||||||
declare let _fadeout_warned;
|
|
||||||
declare function fadeoutLoader(duration?, minAge?, ignoreAge?);
|
|
||||||
|
|
||||||
/* File: /home/wolverindev/TeaSpeak/Web-Client/shared/loader/app.ts */
|
|
||||||
declare interface Window {
|
|
||||||
$: JQuery;
|
|
||||||
}
|
|
||||||
declare namespace app {
|
|
||||||
export enum Type {
|
|
||||||
UNKNOWN,
|
|
||||||
CLIENT_RELEASE,
|
|
||||||
CLIENT_DEBUG,
|
|
||||||
WEB_DEBUG,
|
|
||||||
WEB_RELEASE
|
|
||||||
}
|
|
||||||
export let type: Type;
|
|
||||||
export function is_web();
|
|
||||||
export function ui_version();
|
|
||||||
}
|
|
||||||
declare const loader_javascript;
|
|
||||||
declare const loader_webassembly;
|
|
||||||
declare const loader_style;
|
|
||||||
declare function load_templates(): Promise<any>;
|
|
2
modules/renderer/imports/.gitignore
vendored
2
modules/renderer/imports/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
imports_shared.d.ts
|
|
||||||
imports_shared_loader.d.ts
|
|
@ -1,12 +1,7 @@
|
|||||||
/// <reference path="imports/imports_shared.d.ts" />
|
/* --------------- bootstrap --------------- */
|
||||||
|
import * as rh from "./require-handler";
|
||||||
import {Arguments, parse_arguments, process_args} from "../shared/process-arguments";
|
|
||||||
import * as electron from "electron";
|
|
||||||
import {remote} from "electron";
|
|
||||||
import * as crash_handler from "../crash_handler";
|
import * as crash_handler from "../crash_handler";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as os from "os";
|
|
||||||
import ipcRenderer = electron.ipcRenderer;
|
|
||||||
|
|
||||||
/* first of all setup crash handler */
|
/* first of all setup crash handler */
|
||||||
{
|
{
|
||||||
@ -14,266 +9,155 @@ import ipcRenderer = electron.ipcRenderer;
|
|||||||
crash_handler.initialize_handler("renderer", is_electron_run);
|
crash_handler.initialize_handler("renderer", is_electron_run);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Window {
|
/* some decls */
|
||||||
$: any;
|
declare global {
|
||||||
jQuery: any;
|
interface Window {
|
||||||
jsrender: any;
|
$: any;
|
||||||
|
jQuery: any;
|
||||||
|
jsrender: any;
|
||||||
|
|
||||||
impl_display_critical_error: any;
|
impl_display_critical_error: any;
|
||||||
displayCriticalError: any;
|
displayCriticalError: any;
|
||||||
teaclient_initialize: any;
|
teaclient_initialize: any;
|
||||||
|
|
||||||
open_connected_question: () => Promise<boolean>;
|
open_connected_question: () => Promise<boolean>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
rh.initialize(path.join(__dirname, "backend-impl"));
|
||||||
|
/* --------------- main initialize --------------- */
|
||||||
|
import {Arguments, parse_arguments, process_args} from "../shared/process-arguments";
|
||||||
|
import * as electron from "electron";
|
||||||
|
import {remote} from "electron";
|
||||||
|
import * as os from "os";
|
||||||
|
import * as loader from "tc-loader";
|
||||||
|
import ipcRenderer = electron.ipcRenderer;
|
||||||
|
|
||||||
declare const window: Window;
|
|
||||||
|
|
||||||
export const require_native: NodeRequireFunction = id => require(id);
|
/* we use out own jquery resource */
|
||||||
export const initialize = async () => {
|
loader.register_task(loader.Stage.JAVASCRIPT, {
|
||||||
/* we use out own jquery resource */
|
name: "teaclient jquery",
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT, {
|
function: async () => {
|
||||||
name: "teaclient jquery",
|
window.$ = require("jquery");
|
||||||
function: jquery_initialize,
|
window.jQuery = window.$;
|
||||||
priority: 80
|
Object.assign(window.$, window.jsrender = require('jsrender'));
|
||||||
});
|
},
|
||||||
|
priority: 80
|
||||||
|
});
|
||||||
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT, {
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
name: "teaclient general",
|
name: "teaclient initialize persistent storage",
|
||||||
function: load_basic_modules,
|
function: async () => {
|
||||||
priority: 10
|
const storage = require("./PersistentLocalStorage");
|
||||||
});
|
await storage.initialize();
|
||||||
|
},
|
||||||
|
priority: 90
|
||||||
|
});
|
||||||
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
name: "teaclient javascript init",
|
name: "teaclient initialize logging",
|
||||||
function: load_modules,
|
function: async () => {
|
||||||
priority: 50
|
const logger = require("./logger");
|
||||||
});
|
logger.setup();
|
||||||
|
},
|
||||||
|
priority: 80
|
||||||
|
});
|
||||||
|
|
||||||
loader.register_task(loader.Stage.INITIALIZING, {
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
name: "teaclient initialize modules",
|
name: "teaclient initialize error",
|
||||||
function: module_loader_setup,
|
function: async () => {
|
||||||
priority: 60
|
const _impl = message => {
|
||||||
});
|
|
||||||
|
|
||||||
loader.register_task(loader.Stage.INITIALIZING, {
|
|
||||||
name: "teaclient initialize persistent storage",
|
|
||||||
function: async () => {
|
|
||||||
const storage = require("./PersistentLocalStorage");
|
|
||||||
await storage.initialize();
|
|
||||||
},
|
|
||||||
priority: 90
|
|
||||||
});
|
|
||||||
|
|
||||||
loader.register_task(loader.Stage.INITIALIZING, {
|
|
||||||
name: "teaclient initialize logging",
|
|
||||||
function: initialize_logging,
|
|
||||||
priority: 80
|
|
||||||
});
|
|
||||||
|
|
||||||
loader.register_task(loader.Stage.INITIALIZING, {
|
|
||||||
name: "teaclient initialize error",
|
|
||||||
function: initialize_error_handler,
|
|
||||||
priority: 100
|
|
||||||
});
|
|
||||||
|
|
||||||
loader.register_task(loader.Stage.INITIALIZING, {
|
|
||||||
name: "teaclient initialize arguments",
|
|
||||||
function: async () => {
|
|
||||||
parse_arguments();
|
|
||||||
if(process_args.has_value(Arguments.DUMMY_CRASH_RENDERER))
|
|
||||||
crash_handler.handler.crash();
|
|
||||||
if(!process_args.has_flag(Arguments.DEBUG)) {
|
if(!process_args.has_flag(Arguments.DEBUG)) {
|
||||||
window.open_connected_question = () => remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
console.error("Displaying critical error: %o", message);
|
||||||
type: 'question',
|
message = message.replace(/<br>/i, "\n");
|
||||||
buttons: ['Yes', 'No'],
|
|
||||||
title: 'Confirm',
|
|
||||||
message: 'Are you really sure?\nYou\'re still connected!'
|
|
||||||
}).then(result => result.response === 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
priority: 110
|
|
||||||
});
|
|
||||||
|
|
||||||
loader.register_task(loader.Stage.INITIALIZING, {
|
const win = remote.getCurrentWindow();
|
||||||
name: 'gdb-waiter',
|
remote.dialog.showMessageBox({
|
||||||
function: async () => {
|
type: "error",
|
||||||
if(process_args.has_flag(Arguments.DEV_TOOLS_GDB)) {
|
buttons: ["exit"],
|
||||||
console.log("Process ID: %d", process.pid);
|
title: "A critical error happened!",
|
||||||
await new Promise(resolve => {
|
message: message
|
||||||
console.log("Waiting for continue!");
|
|
||||||
|
|
||||||
const listener = () => {
|
|
||||||
console.log("Continue");
|
|
||||||
document.removeEventListener('click', listener);
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
document.addEventListener('click', listener);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
win.close();
|
||||||
|
} else {
|
||||||
|
console.error("Received critical error: %o", message);
|
||||||
|
console.error("Ignoring error due to the debug mode");
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
priority: 100
|
|
||||||
});
|
|
||||||
|
|
||||||
loader.register_task(loader.Stage.LOADED, {
|
if(window.impl_display_critical_error)
|
||||||
name: "argv connect",
|
window.impl_display_critical_error = _impl;
|
||||||
function: async () => {
|
else
|
||||||
ipcRenderer.send('basic-action', "parse-connect-arguments");
|
window.displayCriticalError = _impl;
|
||||||
},
|
},
|
||||||
priority: 0
|
priority: 100
|
||||||
})
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const jquery_initialize = async () => {
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
window.$ = require("jquery");
|
name: "teaclient initialize arguments",
|
||||||
window.jQuery = window.$;
|
function: async () => {
|
||||||
Object.assign(window.$, window.jsrender = require('jsrender'));
|
parse_arguments();
|
||||||
};
|
if(process_args.has_value(Arguments.DUMMY_CRASH_RENDERER))
|
||||||
|
crash_handler.handler.crash();
|
||||||
const initialize_logging = async () => {
|
|
||||||
const logger = require("./logger");
|
|
||||||
logger.setup();
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialize_error_handler = async () => {
|
|
||||||
const _impl = message => {
|
|
||||||
if(!process_args.has_flag(Arguments.DEBUG)) {
|
if(!process_args.has_flag(Arguments.DEBUG)) {
|
||||||
console.error("Displaying critical error: %o", message);
|
window.open_connected_question = () => remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
message = message.replace(/<br>/i, "\n");
|
type: 'question',
|
||||||
|
buttons: ['Yes', 'No'],
|
||||||
|
title: 'Confirm',
|
||||||
|
message: 'Are you really sure?\nYou\'re still connected!'
|
||||||
|
}).then(result => result.response === 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
priority: 110
|
||||||
|
});
|
||||||
|
|
||||||
const win = remote.getCurrentWindow();
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
remote.dialog.showMessageBox({
|
name: 'gdb-waiter',
|
||||||
type: "error",
|
function: async () => {
|
||||||
buttons: ["exit"],
|
if(process_args.has_flag(Arguments.DEV_TOOLS_GDB)) {
|
||||||
title: "A critical error happened!",
|
console.log("Process ID: %d", process.pid);
|
||||||
message: message
|
await new Promise(resolve => {
|
||||||
|
console.log("Waiting for continue!");
|
||||||
|
|
||||||
|
const listener = () => {
|
||||||
|
console.log("Continue");
|
||||||
|
document.removeEventListener('click', listener);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
document.addEventListener('click', listener);
|
||||||
});
|
});
|
||||||
|
|
||||||
win.close();
|
|
||||||
} else {
|
|
||||||
console.error("Received critical error: %o", message);
|
|
||||||
console.error("Ignoring error due to the debug mode");
|
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
priority: 100
|
||||||
|
});
|
||||||
|
|
||||||
if(window.impl_display_critical_error)
|
loader.register_task(loader.Stage.LOADED, {
|
||||||
window.impl_display_critical_error = _impl;
|
name: "argv connect",
|
||||||
else
|
function: async () => {
|
||||||
window.displayCriticalError = _impl;
|
ipcRenderer.send('basic-action', "parse-connect-arguments");
|
||||||
};
|
},
|
||||||
|
priority: 0
|
||||||
|
});
|
||||||
|
|
||||||
const module_loader_setup = async () => {
|
|
||||||
const native_paths = (() => {
|
|
||||||
const app_path = (remote || electron).app.getAppPath();
|
|
||||||
const result = [];
|
|
||||||
result.push(app_path + "/native/build/" + os.platform() + "_" + os.arch() + "/");
|
|
||||||
if(app_path.endsWith(".asar"))
|
|
||||||
result.push(path.join(path.dirname(app_path), "natives"));
|
|
||||||
return result;
|
|
||||||
})();
|
|
||||||
window["require_setup"] = _mod => {
|
|
||||||
if(!_mod || !_mod.paths) return;
|
|
||||||
|
|
||||||
_mod.paths.push(...native_paths);
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
const original_require = _mod.__proto__.require;
|
name: "teaclient load adapters",
|
||||||
if(!_mod.proxied) {
|
function: async () => {
|
||||||
_mod.require = (path: string) => {
|
/* all files which replaces a native driver */
|
||||||
if(path.endsWith("imports/imports_shared")) {
|
|
||||||
console.log("Proxy require for %s. Using 'window' as result.", path);
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
return original_require.apply(_mod, [path]);
|
|
||||||
};
|
|
||||||
_mod.proxied = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const load_basic_modules = async () => {
|
|
||||||
require("./logger");
|
|
||||||
|
|
||||||
require("./audio/AudioPlayer"); /* setup audio */
|
|
||||||
require("./audio/AudioRecorder"); /* setup audio */
|
|
||||||
require("./audio/sounds"); /* setup audio */
|
|
||||||
};
|
|
||||||
|
|
||||||
const load_modules = async () => {
|
|
||||||
window["require_setup"](this);
|
|
||||||
|
|
||||||
console.log(module.paths);
|
|
||||||
console.log("Loading native extensions...");
|
|
||||||
try {
|
|
||||||
try {
|
try {
|
||||||
require("./version");
|
require("./version");
|
||||||
} catch(error) {
|
|
||||||
console.error("Failed to load version extension");
|
|
||||||
console.dir(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
require("./app_backend");
|
|
||||||
} catch(error) {
|
|
||||||
console.error("Failed to load renderer app backend");
|
|
||||||
console.dir(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const helper = require("./icon-helper");
|
|
||||||
await helper.initialize();
|
|
||||||
} catch(error) {
|
|
||||||
console.error("Failed to load the icon helper extension");
|
|
||||||
console.dir(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
require("./ppt");
|
|
||||||
} catch(error) {
|
|
||||||
console.error("Failed to load ppt");
|
|
||||||
console.dir(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
require("./connection/ServerConnection");
|
|
||||||
} catch(error) {
|
|
||||||
console.error("Failed to load server connection extension");
|
|
||||||
console.dir(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
require("./connection/FileTransfer");
|
|
||||||
} catch(error) {
|
|
||||||
console.error("Failed to load file transfer extension");
|
|
||||||
console.dir(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
require("./dns/dns_resolver");
|
|
||||||
} catch(error) {
|
|
||||||
console.error("Failed to load dns extension");
|
|
||||||
console.dir(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
require("./menu");
|
require("./menu");
|
||||||
} catch(error) {
|
|
||||||
console.error("Failed to load menu extension");
|
|
||||||
console.dir(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
require("./context-menu");
|
require("./context-menu");
|
||||||
} catch(error) {
|
require("./app_backend");
|
||||||
console.error("Failed to load context menu extension");
|
require("./icon-helper").initialize();
|
||||||
console.dir(error);
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
window.displayCriticalError("Failed to load native extensions: " + error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
} catch(error){
|
remote.getCurrentWindow().on('focus', () => remote.getCurrentWindow().flashFrame(false));
|
||||||
console.log(error);
|
},
|
||||||
window.displayCriticalError("Failed to load native extensions: " + error);
|
priority: 60
|
||||||
throw error;
|
});
|
||||||
}
|
|
||||||
console.log("Loaded native extensions");
|
|
||||||
|
|
||||||
remote.getCurrentWindow().on('focus', () => remote.getCurrentWindow().flashFrame(false));
|
export async function initialize() { }
|
||||||
// remote.getCurrentWindow().flashFrame(true);
|
|
||||||
};
|
|
@ -1,257 +1,246 @@
|
|||||||
import {class_to_image} from "./icon-helper";
|
import {class_to_image} from "./icon-helper";
|
||||||
|
|
||||||
window["require_setup"](module);
|
|
||||||
|
|
||||||
import * as electron from "electron";
|
import * as electron from "electron";
|
||||||
//import {top_menu as dtop_menu, Icon} from "./imports/imports_shared";
|
import * as mbar from "tc-shared/ui/frames/MenuBar";
|
||||||
|
|
||||||
/// <reference types="./imports/import_shared.d.ts" />
|
|
||||||
import dtop_menu = top_menu;
|
|
||||||
import {Arguments, process_args} from "../shared/process-arguments";
|
import {Arguments, process_args} from "../shared/process-arguments";
|
||||||
|
|
||||||
namespace _top_menu {
|
import ipcRenderer = electron.ipcRenderer;
|
||||||
|
import {Icon} from "tc-shared/FileManager";
|
||||||
|
namespace native {
|
||||||
import ipcRenderer = electron.ipcRenderer;
|
import ipcRenderer = electron.ipcRenderer;
|
||||||
namespace native {
|
let _item_index = 1;
|
||||||
import ipcRenderer = electron.ipcRenderer;
|
|
||||||
let _item_index = 1;
|
|
||||||
|
|
||||||
abstract class NativeMenuBase {
|
abstract class NativeMenuBase {
|
||||||
protected _handle: NativeMenuBar;
|
protected _handle: NativeMenuBar;
|
||||||
protected _click: () => any;
|
protected _click: () => any;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
protected constructor(handle: NativeMenuBar, id?: string) {
|
protected constructor(handle: NativeMenuBar, id?: string) {
|
||||||
this._handle = handle;
|
this._handle = handle;
|
||||||
this.id = id || ("item_" + (_item_index++));
|
this.id = id || ("item_" + (_item_index++));
|
||||||
}
|
|
||||||
|
|
||||||
abstract build() : electron.MenuItemConstructorOptions;
|
|
||||||
abstract items(): (dtop_menu.MenuItem | dtop_menu.HRItem)[];
|
|
||||||
|
|
||||||
trigger_click() {
|
|
||||||
if(this._click)
|
|
||||||
this._click();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class NativeMenuItem extends NativeMenuBase implements dtop_menu.MenuItem {
|
abstract build() : electron.MenuItemConstructorOptions;
|
||||||
private _items: (NativeMenuItem | NativeHrItem)[] = [];
|
abstract items(): (mbar.MenuItem | mbar.HRItem)[];
|
||||||
private _label: string;
|
|
||||||
private _enabled: boolean = true;
|
|
||||||
private _visible: boolean = true;
|
|
||||||
|
|
||||||
private _icon_data: string;
|
trigger_click() {
|
||||||
|
if(this._click)
|
||||||
|
this._click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constructor(handle: NativeMenuBar) {
|
class NativeMenuItem extends NativeMenuBase implements mbar.MenuItem {
|
||||||
super(handle);
|
private _items: (NativeMenuItem | NativeHrItem)[] = [];
|
||||||
|
private _label: string;
|
||||||
|
private _enabled: boolean = true;
|
||||||
|
private _visible: boolean = true;
|
||||||
|
|
||||||
}
|
private _icon_data: string;
|
||||||
|
|
||||||
append_hr(): dtop_menu.HRItem {
|
constructor(handle: NativeMenuBar) {
|
||||||
const item = new NativeHrItem(this._handle);
|
super(handle);
|
||||||
this._items.push(item);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
append_item(label: string): dtop_menu.MenuItem {
|
|
||||||
const item = new NativeMenuItem(this._handle);
|
|
||||||
item.label(label);
|
|
||||||
this._items.push(item);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
click(callback: () => any): this {
|
|
||||||
this._click = callback;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete_item(item: dtop_menu.MenuItem | dtop_menu.HRItem) {
|
|
||||||
const i_index = this._items.indexOf(item as any);
|
|
||||||
if(i_index < 0) return;
|
|
||||||
this._items.splice(i_index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
disabled(value?: boolean): boolean {
|
|
||||||
if(typeof(value) === "boolean")
|
|
||||||
this._enabled = !value;
|
|
||||||
return !this._enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
icon(klass?: string | Promise<Icon> | Icon): string {
|
|
||||||
if(typeof(klass) === "string") {
|
|
||||||
const buffer = class_to_image(klass);
|
|
||||||
if(buffer)
|
|
||||||
this._icon_data = buffer.toDataURL();
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
items(): (dtop_menu.MenuItem | dtop_menu.HRItem)[] {
|
|
||||||
return this._items;
|
|
||||||
}
|
|
||||||
|
|
||||||
label(value?: string): string {
|
|
||||||
if(typeof(value) === "string")
|
|
||||||
this._label = value;
|
|
||||||
return this._label;
|
|
||||||
}
|
|
||||||
|
|
||||||
visible(value?: boolean): boolean {
|
|
||||||
if(typeof(value) === "boolean")
|
|
||||||
this._visible = value;
|
|
||||||
return this._visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
build(): Electron.MenuItemConstructorOptions {
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
|
|
||||||
label: this._label || "",
|
|
||||||
|
|
||||||
submenu: this._items.length > 0 ? this._items.map(e => e.build()) : undefined,
|
|
||||||
enabled: this._enabled,
|
|
||||||
visible: this._visible,
|
|
||||||
|
|
||||||
icon: this._icon_data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class NativeHrItem extends NativeMenuBase implements dtop_menu.HRItem {
|
append_hr(): mbar.HRItem {
|
||||||
constructor(handle: NativeMenuBar) {
|
const item = new NativeHrItem(this._handle);
|
||||||
super(handle);
|
this._items.push(item);
|
||||||
}
|
return item;
|
||||||
|
|
||||||
build(): Electron.MenuItemConstructorOptions {
|
|
||||||
return {
|
|
||||||
type: 'separator',
|
|
||||||
id: this.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
items(): (dtop_menu.MenuItem | dtop_menu.HRItem)[] {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_similar_deep(a, b) {
|
append_item(label: string): mbar.MenuItem {
|
||||||
if(typeof(a) !== typeof(b))
|
const item = new NativeMenuItem(this._handle);
|
||||||
return false;
|
item.label(label);
|
||||||
if(typeof(a) !== "object")
|
this._items.push(item);
|
||||||
return a === b;
|
return item;
|
||||||
|
|
||||||
const aProps = Object.keys(a);
|
|
||||||
const bProps = Object.keys(b);
|
|
||||||
|
|
||||||
if (aProps.length != bProps.length)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (let i = 0; i < aProps.length; i++) {
|
|
||||||
const propName = aProps[i];
|
|
||||||
|
|
||||||
if(!is_similar_deep(a[propName], b[propName]))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
click(callback: () => any): this {
|
||||||
|
this._click = callback;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
export class NativeMenuBar implements dtop_menu.MenuBarDriver {
|
delete_item(item: mbar.MenuItem | mbar.HRItem) {
|
||||||
private static _instance: NativeMenuBar;
|
const i_index = this._items.indexOf(item as any);
|
||||||
|
if(i_index < 0) return;
|
||||||
|
this._items.splice(i_index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
private menu: electron.Menu;
|
disabled(value?: boolean): boolean {
|
||||||
private _items: NativeMenuItem[] = [];
|
if(typeof(value) === "boolean")
|
||||||
private _current_menu: electron.MenuItemConstructorOptions[];
|
this._enabled = !value;
|
||||||
|
return !this._enabled;
|
||||||
|
}
|
||||||
|
|
||||||
public static instance() : NativeMenuBar {
|
icon(klass?: string | Promise<Icon> | Icon): string {
|
||||||
if(!this._instance)
|
if(typeof(klass) === "string") {
|
||||||
this._instance = new NativeMenuBar();
|
const buffer = class_to_image(klass);
|
||||||
return this._instance;
|
if(buffer)
|
||||||
|
this._icon_data = buffer.toDataURL();
|
||||||
}
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
append_item(label: string): dtop_menu.MenuItem {
|
items(): (mbar.MenuItem | mbar.HRItem)[] {
|
||||||
const item = new NativeMenuItem(this);
|
return this._items;
|
||||||
item.label(label);
|
}
|
||||||
this._items.push(item);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete_item(item: dtop_menu.MenuItem) {
|
label(value?: string): string {
|
||||||
const i_index = this._items.indexOf(item as any);
|
if(typeof(value) === "string")
|
||||||
if(i_index < 0) return;
|
this._label = value;
|
||||||
this._items.splice(i_index, 1);
|
return this._label;
|
||||||
}
|
}
|
||||||
|
|
||||||
flush_changes() {
|
visible(value?: boolean): boolean {
|
||||||
const target_menu = this.build_menu();
|
if(typeof(value) === "boolean")
|
||||||
if(is_similar_deep(target_menu, this._current_menu))
|
this._visible = value;
|
||||||
return;
|
return this._visible;
|
||||||
|
}
|
||||||
|
|
||||||
this._current_menu = target_menu;
|
build(): Electron.MenuItemConstructorOptions {
|
||||||
ipcRenderer.send('top-menu', target_menu);
|
return {
|
||||||
}
|
id: this.id,
|
||||||
|
|
||||||
private build_menu() : electron.MenuItemConstructorOptions[] {
|
label: this._label || "",
|
||||||
return this._items.map(e => e.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
items(): dtop_menu.MenuItem[] {
|
submenu: this._items.length > 0 ? this._items.map(e => e.build()) : undefined,
|
||||||
return this._items;
|
enabled: this._enabled,
|
||||||
}
|
visible: this._visible,
|
||||||
|
|
||||||
initialize() {
|
icon: this._icon_data
|
||||||
this.menu = new electron.remote.Menu();
|
|
||||||
ipcRenderer.on('top-menu', (event, clicked_item) => {
|
|
||||||
console.log("Item %o clicked", clicked_item);
|
|
||||||
const check_item = (item: NativeMenuBase) => {
|
|
||||||
if(item.id == clicked_item) {
|
|
||||||
item.trigger_click();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for(const child of item.items())
|
|
||||||
if(check_item(child as NativeMenuBase))
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
for(const item of this._items)
|
|
||||||
if(check_item(item))
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Global variable
|
class NativeHrItem extends NativeMenuBase implements mbar.HRItem {
|
||||||
// @ts-ignore
|
constructor(handle: NativeMenuBar) {
|
||||||
top_menu.set_driver(native.NativeMenuBar.instance());
|
super(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
build(): Electron.MenuItemConstructorOptions {
|
||||||
|
return {
|
||||||
|
type: 'separator',
|
||||||
|
id: this.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(): (mbar.MenuItem | mbar.HRItem)[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_similar_deep(a, b) {
|
||||||
|
if(typeof(a) !== typeof(b))
|
||||||
|
return false;
|
||||||
|
if(typeof(a) !== "object")
|
||||||
|
return a === b;
|
||||||
|
|
||||||
|
const aProps = Object.keys(a);
|
||||||
|
const bProps = Object.keys(b);
|
||||||
|
|
||||||
|
if (aProps.length != bProps.length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (let i = 0; i < aProps.length; i++) {
|
||||||
|
const propName = aProps[i];
|
||||||
|
|
||||||
|
if(!is_similar_deep(a[propName], b[propName]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const call_basic_action = (name: string, ...args: any[]) => ipcRenderer.send('basic-action', name, ...args);
|
export class NativeMenuBar implements mbar.MenuBarDriver {
|
||||||
top_menu.native_actions = {
|
private static _instance: NativeMenuBar;
|
||||||
open_change_log() {
|
|
||||||
call_basic_action("open-changelog");
|
|
||||||
},
|
|
||||||
|
|
||||||
check_native_update() {
|
private menu: electron.Menu;
|
||||||
call_basic_action("check-native-update");
|
private _items: NativeMenuItem[] = [];
|
||||||
},
|
private _current_menu: electron.MenuItemConstructorOptions[];
|
||||||
|
|
||||||
quit() {
|
public static instance() : NativeMenuBar {
|
||||||
call_basic_action("quit");
|
if(!this._instance)
|
||||||
},
|
this._instance = new NativeMenuBar();
|
||||||
|
return this._instance;
|
||||||
|
}
|
||||||
|
|
||||||
open_dev_tools() {
|
append_item(label: string): mbar.MenuItem {
|
||||||
call_basic_action("open-dev-tools");
|
const item = new NativeMenuItem(this);
|
||||||
},
|
item.label(label);
|
||||||
|
this._items.push(item);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
reload_page() {
|
delete_item(item: mbar.MenuItem) {
|
||||||
call_basic_action("reload-window")
|
const i_index = this._items.indexOf(item as any);
|
||||||
},
|
if(i_index < 0) return;
|
||||||
|
this._items.splice(i_index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
show_dev_tools() { return process_args.has_flag(Arguments.DEV_TOOLS); }
|
flush_changes() {
|
||||||
};
|
const target_menu = this.build_menu();
|
||||||
|
if(is_similar_deep(target_menu, this._current_menu))
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._current_menu = target_menu;
|
||||||
|
ipcRenderer.send('top-menu', target_menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private build_menu() : electron.MenuItemConstructorOptions[] {
|
||||||
|
return this._items.map(e => e.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
items(): mbar.MenuItem[] {
|
||||||
|
return this._items;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
this.menu = new electron.remote.Menu();
|
||||||
|
ipcRenderer.on('top-menu', (event, clicked_item) => {
|
||||||
|
console.log("Item %o clicked", clicked_item);
|
||||||
|
const check_item = (item: NativeMenuBase) => {
|
||||||
|
if(item.id == clicked_item) {
|
||||||
|
item.trigger_click();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for(const child of item.items())
|
||||||
|
if(check_item(child as NativeMenuBase))
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
for(const item of this._items)
|
||||||
|
if(check_item(item))
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mbar.set_driver(native.NativeMenuBar.instance());
|
||||||
|
// @ts-ignore
|
||||||
|
mbar.native_actions = {
|
||||||
|
open_change_log() {
|
||||||
|
call_basic_action("open-changelog");
|
||||||
|
},
|
||||||
|
|
||||||
export {};
|
check_native_update() {
|
||||||
|
call_basic_action("check-native-update");
|
||||||
|
},
|
||||||
|
|
||||||
|
quit() {
|
||||||
|
call_basic_action("quit");
|
||||||
|
},
|
||||||
|
|
||||||
|
open_dev_tools() {
|
||||||
|
call_basic_action("open-dev-tools");
|
||||||
|
},
|
||||||
|
|
||||||
|
reload_page() {
|
||||||
|
call_basic_action("reload-window")
|
||||||
|
},
|
||||||
|
|
||||||
|
show_dev_tools() { return process_args.has_flag(Arguments.DEV_TOOLS); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const call_basic_action = (name: string, ...args: any[]) => ipcRenderer.send('basic-action', name, ...args);
|
@ -1,137 +1,141 @@
|
|||||||
window["require_setup"](module);
|
import {KeyEvent as NKeyEvent, RegisterCallback, UnregisterCallback} from "tc-native/ppt";
|
||||||
|
import {EventType, KeyEvent, KeyHook, SpecialKey} from "tc-shared/PPTListener";
|
||||||
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
|
import {LogCategory} from "tc-shared/log";
|
||||||
|
import * as log from "tc-shared/log";
|
||||||
|
|
||||||
import {KeyEvent as NKeyEvent} from "teaclient_ppt";
|
let key_listener: ((_: KeyEvent) => any)[] = [];
|
||||||
namespace _ppt {
|
|
||||||
let key_listener: ((_: ppt.KeyEvent) => any)[] = [];
|
|
||||||
|
|
||||||
let native_ppt;
|
function listener_key(type: EventType, nevent: NKeyEvent) {
|
||||||
function listener_key(type: ppt.EventType, nevent: NKeyEvent) {
|
if(nevent.key_code === 'VoidSymbol' || nevent.key_code === 'error')
|
||||||
if(nevent.key_code === 'VoidSymbol' || nevent.key_code === 'error')
|
nevent.key_code = undefined; /* trigger event for state update */
|
||||||
nevent.key_code = undefined; /* trigger event for state update */
|
|
||||||
|
|
||||||
let event: ppt.KeyEvent = {
|
let event: KeyEvent = {
|
||||||
type: type,
|
type: type,
|
||||||
|
|
||||||
key: nevent.key_code,
|
key: nevent.key_code,
|
||||||
key_code: nevent.key_code,
|
key_code: nevent.key_code,
|
||||||
|
|
||||||
key_ctrl: nevent.key_ctrl,
|
key_ctrl: nevent.key_ctrl,
|
||||||
key_shift: nevent.key_shift,
|
key_shift: nevent.key_shift,
|
||||||
key_alt: nevent.key_alt,
|
key_alt: nevent.key_alt,
|
||||||
key_windows: nevent.key_windows
|
key_windows: nevent.key_windows
|
||||||
} as any;
|
|
||||||
//console.debug("Trigger key event %o", type);
|
|
||||||
|
|
||||||
for (const listener of key_listener)
|
|
||||||
listener && listener(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
function native_keyhook(event: NKeyEvent) {
|
|
||||||
//console.log("Native event!: %o", event);
|
|
||||||
|
|
||||||
if(event.type == 0)
|
|
||||||
listener_key(ppt.EventType.KEY_PRESS, event);
|
|
||||||
else if(event.type == 1)
|
|
||||||
listener_key(ppt.EventType.KEY_RELEASE, event);
|
|
||||||
else if(event.type == 2)
|
|
||||||
listener_key(ppt.EventType.KEY_TYPED, event);
|
|
||||||
else
|
|
||||||
console.warn(tr("Received unknown native event: %o"), event);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function initialize() : Promise<void> {
|
|
||||||
native_ppt = require("teaclient_ppt");
|
|
||||||
|
|
||||||
register_key_listener(listener_hook);
|
|
||||||
native_ppt.RegisterCallback(native_keyhook);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function finalize() {
|
|
||||||
unregister_key_listener(listener_hook);
|
|
||||||
native_ppt.UnregisterCallback(native_keyhook);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function register_key_listener(listener: (_: ppt.KeyEvent) => any) {
|
|
||||||
key_listener.push(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unregister_key_listener(listener: (_: ppt.KeyEvent) => any) {
|
|
||||||
key_listener.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
let key_hooks: ppt.KeyHook[] = [];
|
|
||||||
|
|
||||||
interface CurrentState {
|
|
||||||
keys: {[code: string]:ppt.KeyEvent};
|
|
||||||
special: { [key:number]:boolean };
|
|
||||||
}
|
|
||||||
let current_state: CurrentState = {
|
|
||||||
special: []
|
|
||||||
} as any;
|
} as any;
|
||||||
|
//console.debug("Trigger key event %o", type);
|
||||||
|
|
||||||
let key_hooks_active: ppt.KeyHook[] = [];
|
for (const listener of key_listener)
|
||||||
|
listener && listener(event);
|
||||||
function listener_hook(event: ppt.KeyEvent) {
|
}
|
||||||
if(event.type == ppt.EventType.KEY_TYPED)
|
|
||||||
return;
|
function native_keyhook(event: NKeyEvent) {
|
||||||
|
//console.log("Native event!: %o", event);
|
||||||
let old_hooks = [...key_hooks_active];
|
|
||||||
let new_hooks = [];
|
if(event.type == 0)
|
||||||
|
listener_key(EventType.KEY_PRESS, event);
|
||||||
current_state.special[ppt.SpecialKey.ALT] = event.key_alt;
|
else if(event.type == 1)
|
||||||
current_state.special[ppt.SpecialKey.CTRL] = event.key_ctrl;
|
listener_key(EventType.KEY_RELEASE, event);
|
||||||
current_state.special[ppt.SpecialKey.SHIFT] = event.key_shift;
|
else if(event.type == 2)
|
||||||
current_state.special[ppt.SpecialKey.WINDOWS] = event.key_windows;
|
listener_key(EventType.KEY_TYPED, event);
|
||||||
|
else
|
||||||
current_state[event.key_code] = undefined;
|
console.warn(tr("Received unknown native event: %o"), event);
|
||||||
if(event.type == ppt.EventType.KEY_PRESS) {
|
}
|
||||||
current_state[event.key_code] = event;
|
|
||||||
|
export async function initialize() : Promise<void> {
|
||||||
for(const hook of key_hooks) {
|
register_key_listener(listener_hook);
|
||||||
if(hook.key_code !== event.key_code) continue;
|
RegisterCallback(native_keyhook);
|
||||||
if(hook.key_alt != event.key_alt) continue;
|
}
|
||||||
if(hook.key_ctrl != event.key_ctrl) continue;
|
|
||||||
if(hook.key_shift != event.key_shift) continue;
|
export function finalize() {
|
||||||
if(hook.key_windows != event.key_windows) continue;
|
unregister_key_listener(listener_hook);
|
||||||
|
UnregisterCallback(native_keyhook);
|
||||||
new_hooks.push(hook);
|
}
|
||||||
if(!old_hooks.remove(hook) && hook.callback_press) {
|
|
||||||
hook.callback_press();
|
export function register_key_listener(listener: (_: KeyEvent) => any) {
|
||||||
log.trace(LogCategory.GENERAL, tr("Trigger key press for %o!"), hook);
|
key_listener.push(listener);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
export function unregister_key_listener(listener: (_: KeyEvent) => any) {
|
||||||
|
const index = key_listener.findIndex(e => e === listener);
|
||||||
//We have a new situation
|
if(index !== -1) key_listener.splice(index, 1);
|
||||||
for(const hook of old_hooks) {
|
}
|
||||||
//Do not test for meta key states because they could differ in a key release event
|
|
||||||
if(hook.key_code === event.key_code) {
|
let key_hooks: KeyHook[] = [];
|
||||||
if(hook.callback_release) {
|
|
||||||
hook.callback_release();
|
interface CurrentState {
|
||||||
log.trace(LogCategory.GENERAL, tr("Trigger key release for %o!"), hook);
|
keys: {[code: string]:KeyEvent};
|
||||||
}
|
special: { [key:number]:boolean };
|
||||||
} else {
|
}
|
||||||
new_hooks.push(hook);
|
let current_state: CurrentState = {
|
||||||
}
|
special: []
|
||||||
}
|
} as any;
|
||||||
key_hooks_active = new_hooks;
|
|
||||||
}
|
let key_hooks_active: KeyHook[] = [];
|
||||||
|
|
||||||
|
function listener_hook(event: KeyEvent) {
|
||||||
export function register_key_hook(hook: ppt.KeyHook) {
|
if(event.type == EventType.KEY_TYPED)
|
||||||
key_hooks.push(hook);
|
return;
|
||||||
}
|
|
||||||
|
let old_hooks = [...key_hooks_active];
|
||||||
export function unregister_key_hook(hook: ppt.KeyHook) {
|
let new_hooks = [];
|
||||||
key_hooks.remove(hook);
|
|
||||||
key_hooks_active.remove(hook);
|
current_state.special[SpecialKey.ALT] = event.key_alt;
|
||||||
}
|
current_state.special[SpecialKey.CTRL] = event.key_ctrl;
|
||||||
|
current_state.special[SpecialKey.SHIFT] = event.key_shift;
|
||||||
export function key_pressed(code: string | ppt.SpecialKey) : boolean {
|
current_state.special[SpecialKey.WINDOWS] = event.key_windows;
|
||||||
if(typeof(code) === 'string')
|
|
||||||
return typeof(current_state[code]) === "object";
|
current_state[event.key_code] = undefined;
|
||||||
return current_state.special[code];
|
if(event.type == EventType.KEY_PRESS) {
|
||||||
}
|
current_state[event.key_code] = event;
|
||||||
|
|
||||||
|
for(const hook of key_hooks) {
|
||||||
|
if(hook.key_code !== event.key_code) continue;
|
||||||
|
if(hook.key_alt != event.key_alt) continue;
|
||||||
|
if(hook.key_ctrl != event.key_ctrl) continue;
|
||||||
|
if(hook.key_shift != event.key_shift) continue;
|
||||||
|
if(hook.key_windows != event.key_windows) continue;
|
||||||
|
|
||||||
|
new_hooks.push(hook);
|
||||||
|
const index = old_hooks.findIndex(e => e === hook);
|
||||||
|
if(index !== -1 && hook.callback_press) {
|
||||||
|
old_hooks.splice(index, 1);
|
||||||
|
hook.callback_press();
|
||||||
|
log.trace(LogCategory.GENERAL, tr("Trigger key press for %o!"), hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//We have a new situation
|
||||||
|
for(const hook of old_hooks) {
|
||||||
|
//Do not test for meta key states because they could differ in a key release event
|
||||||
|
if(hook.key_code === event.key_code) {
|
||||||
|
if(hook.callback_release) {
|
||||||
|
hook.callback_release();
|
||||||
|
log.trace(LogCategory.GENERAL, tr("Trigger key release for %o!"), hook);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
new_hooks.push(hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key_hooks_active = new_hooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function register_key_hook(hook: KeyHook) {
|
||||||
|
key_hooks.push(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregister_key_hook(hook: KeyHook) {
|
||||||
|
let index;
|
||||||
|
|
||||||
|
index = key_hooks.findIndex(e => e === hook);
|
||||||
|
if(index !== -1) key_hooks.splice(index, 1);
|
||||||
|
|
||||||
|
index = key_hooks_active.findIndex(e => e === hook);
|
||||||
|
if(index !== -1) key_hooks_active.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function key_pressed(code: string | SpecialKey) : boolean {
|
||||||
|
if(typeof(code) === 'string')
|
||||||
|
return typeof(current_state[code]) === "object";
|
||||||
|
return current_state.special[code];
|
||||||
}
|
}
|
||||||
Object.assign(window["ppt"] || (window["ppt"] = {} as any), _ppt);
|
|
||||||
console.dir(_ppt);
|
|
106
modules/renderer/require-handler.ts
Normal file
106
modules/renderer/require-handler.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import * as path from "path";
|
||||||
|
import {remote} from "electron";
|
||||||
|
import * as electron from "electron";
|
||||||
|
import * as os from "os";
|
||||||
|
|
||||||
|
const Module = require("module");
|
||||||
|
|
||||||
|
interface ModuleOverride {
|
||||||
|
name?: string,
|
||||||
|
test: string | RegExp | ((request: string) => boolean);
|
||||||
|
callback: (this: string, request: string, parent?: NodeJS.Module) => any;
|
||||||
|
}
|
||||||
|
const overrides: ModuleOverride[] = [];
|
||||||
|
|
||||||
|
function proxied_load(request: string, parent?: NodeJS.Module) {
|
||||||
|
for(const override of overrides) {
|
||||||
|
let test_satisfied = false;
|
||||||
|
if(typeof override.test === "string") {
|
||||||
|
test_satisfied = override.test === request;
|
||||||
|
} else if(typeof override.test === "function") {
|
||||||
|
test_satisfied = override.test(request);
|
||||||
|
} else if(typeof override === "object") {
|
||||||
|
if(override.test instanceof RegExp)
|
||||||
|
test_satisfied = !!request.match(override.test);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(test_satisfied) {
|
||||||
|
//console.log("Using override %s for %s", override.name || "unnamed", request);
|
||||||
|
return override.callback.apply(this, arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//console.log("No override found for %s", request);
|
||||||
|
return proxied_load.original_load.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shared_backend_loader(request: string) {
|
||||||
|
if(!request.startsWith("tc-backend/")) throw "invalid target";
|
||||||
|
const target = request.substr(11);
|
||||||
|
|
||||||
|
return require(path.join(backend_root, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace proxied_load {
|
||||||
|
export let original_load: typeof Module.require;
|
||||||
|
}
|
||||||
|
|
||||||
|
let backend_root: string;
|
||||||
|
export function initialize(backend_root_: string) {
|
||||||
|
backend_root = backend_root_;
|
||||||
|
|
||||||
|
proxied_load.original_load = Module._load;
|
||||||
|
Module._load = proxied_load;
|
||||||
|
|
||||||
|
window["backend-loader"] = {
|
||||||
|
require: shared_backend_loader
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
overrides.push({
|
||||||
|
name: "tc-loader",
|
||||||
|
test: "tc-loader",
|
||||||
|
callback: () => window["loader"]
|
||||||
|
});
|
||||||
|
|
||||||
|
overrides.push({
|
||||||
|
name: "native loader",
|
||||||
|
test: /^tc-native\/[a-zA-Z_-]+$/,
|
||||||
|
callback: request => {
|
||||||
|
const name = request.substr(10);
|
||||||
|
|
||||||
|
const file_mapping = {
|
||||||
|
connection: "teaclient_connection.node",
|
||||||
|
ppt: "teaclient_ppt.node",
|
||||||
|
dns: "teaclient_dns.node"
|
||||||
|
};
|
||||||
|
|
||||||
|
if(typeof file_mapping[name] !== "string")
|
||||||
|
throw "unknown native module";
|
||||||
|
|
||||||
|
const app_path = (remote || electron).app.getAppPath();
|
||||||
|
const target_path = path.join(app_path, "native", "build", os.platform() + "_" + os.arch(), file_mapping[name]);
|
||||||
|
return require(target_path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
overrides.push({
|
||||||
|
name: "shared loader",
|
||||||
|
test: /^tc-shared\/.*/,
|
||||||
|
callback: request => {
|
||||||
|
const webpack_path = path.dirname("shared/js/" + request.substr(10)); //FIXME: Get the prefix from a variable!
|
||||||
|
const loader = require("tc-loader");
|
||||||
|
|
||||||
|
const mapping = loader.module_mapping().find(e => e.application === "client-app"); //FIXME: Variable name!
|
||||||
|
if(!mapping) throw "missing mapping";
|
||||||
|
|
||||||
|
const entries = mapping.modules.filter(e => e.context === webpack_path);
|
||||||
|
if(!entries.length) throw "unknown target path";
|
||||||
|
|
||||||
|
const basename = path.basename(request, path.extname(request));
|
||||||
|
const entry = entries.find(e => path.basename(e.resource, path.extname(e.resource)) === basename);
|
||||||
|
if(!entry) throw "unknown import";
|
||||||
|
|
||||||
|
return window["shared-require"](entry.id);
|
||||||
|
}
|
||||||
|
});
|
@ -1,3 +1,4 @@
|
|||||||
|
//FIXME!
|
||||||
namespace native {
|
namespace native {
|
||||||
const remote = require('electron').remote;
|
const remote = require('electron').remote;
|
||||||
export async function client_version() : Promise<string> {
|
export async function client_version() : Promise<string> {
|
||||||
|
2
native/dns/exports/exports.d.ts
vendored
2
native/dns/exports/exports.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
declare module "teaclient_dns" {
|
declare module "tc-native/dns" {
|
||||||
export function resolve_cr(host: string, port: number, callback: (result: string | {host: string, port: number}) => any);
|
export function resolve_cr(host: string, port: number, callback: (result: string | {host: string, port: number}) => any);
|
||||||
export function initialize();
|
export function initialize();
|
||||||
}
|
}
|
2
native/ppt/exports/exports.d.ts
vendored
2
native/ppt/exports/exports.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
declare module "teaclient_ppt" {
|
declare module "tc-native/ppt" {
|
||||||
enum KeyEventType {
|
enum KeyEventType {
|
||||||
PRESS = 0,
|
PRESS = 0,
|
||||||
RELEASE = 1,
|
RELEASE = 1,
|
||||||
|
2
native/serverconnection/exports/exports.d.ts
vendored
2
native/serverconnection/exports/exports.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
declare module "teaclient_connection" {
|
declare module "tc-native/connection" {
|
||||||
export enum ServerType {
|
export enum ServerType {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
TEASPEAK,
|
TEASPEAK,
|
||||||
|
@ -139,6 +139,7 @@ void ProtocolHandler::execute_resend() {
|
|||||||
this->handle->close_connection();
|
this->handle->close_connection();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
log_trace(category::connection, tr("Resended {}"), resended);
|
||||||
|
|
||||||
auto socket = this->handle->get_socket();
|
auto socket = this->handle->get_socket();
|
||||||
if(socket) {
|
if(socket) {
|
||||||
@ -164,6 +165,7 @@ void ProtocolHandler::progress_packet(const pipes::buffer_view &buffer) {
|
|||||||
auto packet_type = packet->type();
|
auto packet_type = packet->type();
|
||||||
auto packet_id = packet->packetId();
|
auto packet_id = packet->packetId();
|
||||||
auto ordered = packet_type.type() == protocol::COMMAND || packet_type.type() == protocol::COMMAND_LOW;
|
auto ordered = packet_type.type() == protocol::COMMAND || packet_type.type() == protocol::COMMAND_LOW;
|
||||||
|
log_trace(category::connection, tr("Received packet {} with id {}"), packet->type().name(), packet->packetId());
|
||||||
|
|
||||||
/* special handling */
|
/* special handling */
|
||||||
if(packet_type.type() == protocol::INIT1) {
|
if(packet_type.type() == protocol::INIT1) {
|
||||||
@ -253,6 +255,7 @@ void ProtocolHandler::progress_packet(const pipes::buffer_view &buffer) {
|
|||||||
unique_lock queue_lock(read_queue.buffer_lock);
|
unique_lock queue_lock(read_queue.buffer_lock);
|
||||||
|
|
||||||
if(ordered) { /* ordered */
|
if(ordered) { /* ordered */
|
||||||
|
log_trace(category::connection, tr("Inserting packet {} with id {}"), packet->type().name(), packet->packetId());
|
||||||
if(!read_queue.insert_index(packet_id, std::forward<shared_ptr<ServerPacket>>(packet))) {
|
if(!read_queue.insert_index(packet_id, std::forward<shared_ptr<ServerPacket>>(packet))) {
|
||||||
log_warn(category::connection, tr("Failed to insert ordered packet into queue. ({} | {} | {})"), packet_type.name(), read_queue.current_index(), packet_id);
|
log_warn(category::connection, tr("Failed to insert ordered packet into queue. ({} | {} | {})"), packet_type.name(), read_queue.current_index(), packet_id);
|
||||||
}
|
}
|
||||||
@ -519,7 +522,7 @@ bool ProtocolHandler::create_datagram_packets(std::vector<pipes::buffer> &result
|
|||||||
void ProtocolHandler::send_command(const ts::Command &cmd, const std::function<void(bool)> &ack_callback) {
|
void ProtocolHandler::send_command(const ts::Command &cmd, const std::function<void(bool)> &ack_callback) {
|
||||||
auto data = cmd.build();
|
auto data = cmd.build();
|
||||||
auto packet = make_shared<ClientPacket>(PacketTypeInfo::Command, pipes::buffer_view{data.data(), data.size()});
|
auto packet = make_shared<ClientPacket>(PacketTypeInfo::Command, pipes::buffer_view{data.data(), data.size()});
|
||||||
if(ack_callback) {
|
if(ack_callback || true) {
|
||||||
auto begin = chrono::system_clock::now();
|
auto begin = chrono::system_clock::now();
|
||||||
packet->setListener(make_unique<threads::Future<bool>>());
|
packet->setListener(make_unique<threads::Future<bool>>());
|
||||||
packet->getListener()->waitAndGetLater([ack_callback, begin](bool f) {
|
packet->getListener()->waitAndGetLater([ack_callback, begin](bool f) {
|
||||||
@ -527,7 +530,7 @@ void ProtocolHandler::send_command(const ts::Command &cmd, const std::function<v
|
|||||||
if(ack_callback)
|
if(ack_callback)
|
||||||
ack_callback(f);
|
ack_callback(f);
|
||||||
|
|
||||||
log_trace(category::connection, tr("Time needed for command: {}"), chrono::duration_cast<chrono::milliseconds>(end - begin).count());
|
log_trace(category::connection, tr("Time needed for command: {}ms. Success: {}"), chrono::duration_cast<chrono::milliseconds>(end - begin).count(), f);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
packet->enable_flag(PacketFlag::NewProtocol);
|
packet->enable_flag(PacketFlag::NewProtocol);
|
||||||
@ -541,6 +544,18 @@ void ProtocolHandler::send_packet(const std::shared_ptr<ts::protocol::ClientPack
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
if(packet->type() == protocol::PacketTypeInfo::Command && this->connection_state == connection_state::CONNECTED && false) {
|
||||||
|
ts::Command cmd{"whoami"};
|
||||||
|
auto data = cmd.build();
|
||||||
|
auto p1 = make_shared<ClientPacket>(PacketTypeInfo::Command, pipes::buffer_view{data.data(), data.size()});
|
||||||
|
if(!this->create_datagram_packets(result, p1))
|
||||||
|
log_error(category::connection, tr("failed to encode trap"));
|
||||||
|
std::reverse(result.begin(), result.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_trace(category::connection, tr("Split up {} {} to {} packets. Ack waiting: {}"), packet->packetId(), packet->type().name(), result.size(), this->acknowledge_handler.awaiting_acknowledge());
|
||||||
auto socket = this->handle->get_socket();
|
auto socket = this->handle->get_socket();
|
||||||
if(!socket) {
|
if(!socket) {
|
||||||
log_error(category::connection, tr("Failed to get socket!"));
|
log_error(category::connection, tr("Failed to get socket!"));
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
#include "ProtocolHandler.h"
|
#include "ProtocolHandler.h"
|
||||||
#include "ServerConnection.h"
|
|
||||||
#include "Socket.h"
|
#include "Socket.h"
|
||||||
#include <protocol/buffers.h>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <tomcrypt.h>
|
#include <tomcrypt.h>
|
||||||
@ -21,7 +19,9 @@ using namespace ts;
|
|||||||
void ProtocolHandler::handlePacketAck(const std::shared_ptr<ts::protocol::ServerPacket> &ack) {
|
void ProtocolHandler::handlePacketAck(const std::shared_ptr<ts::protocol::ServerPacket> &ack) {
|
||||||
string error;
|
string error;
|
||||||
log_trace(category::connection, tr("Handle packet acknowledge for {}"), be2le16(&ack->data()[0]));
|
log_trace(category::connection, tr("Handle packet acknowledge for {}"), be2le16(&ack->data()[0]));
|
||||||
this->acknowledge_handler.process_acknowledge(ack->type().type(), ack->data(), error);
|
if(!this->acknowledge_handler.process_acknowledge(ack->type().type(), ack->data(), error)) {
|
||||||
|
log_warn(category::connection, tr("Failed to handle acknowledge {}: {}"), be2le16(&ack->data()[0]) ,error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProtocolHandler::handlePacketCommand(const std::shared_ptr<ts::protocol::ServerPacket> &packet) {
|
void ProtocolHandler::handlePacketCommand(const std::shared_ptr<ts::protocol::ServerPacket> &packet) {
|
||||||
|
@ -496,6 +496,7 @@ NAN_METHOD(ServerConnection::send_command) {
|
|||||||
cmd[strobf("hwid").string()] = system_uuid(); /* we dont want anybody to patch this out */
|
cmd[strobf("hwid").string()] = system_uuid(); /* we dont want anybody to patch this out */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log_trace(category::audio, tr("Sending data {}"), cmd.command());
|
||||||
this->protocol_handler->send_command(cmd);
|
this->protocol_handler->send_command(cmd);
|
||||||
auto end = chrono::system_clock::now();
|
auto end = chrono::system_clock::now();
|
||||||
}
|
}
|
||||||
|
247
package-lock.json
generated
247
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "TeaClient",
|
"name": "TeaClient",
|
||||||
"version": "1.4.3-2",
|
"version": "1.4.4",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -17,6 +17,18 @@
|
|||||||
"got": "^9.6.0",
|
"got": "^9.6.0",
|
||||||
"sanitize-filename": "^1.6.2",
|
"sanitize-filename": "^1.6.2",
|
||||||
"sumchecker": "^3.0.1"
|
"sumchecker": "^3.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"fs-extra": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||||
|
"requires": {
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"jsonfile": "^4.0.0",
|
||||||
|
"universalify": "^0.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@sindresorhus/is": {
|
"@sindresorhus/is": {
|
||||||
@ -383,6 +395,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||||
},
|
},
|
||||||
|
"at-least-node": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
|
||||||
|
},
|
||||||
"atob": {
|
"atob": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||||
@ -777,23 +794,104 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "2.1.8",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz",
|
||||||
"integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
|
"integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"anymatch": "^2.0.0",
|
"anymatch": "~3.1.1",
|
||||||
"async-each": "^1.0.1",
|
"braces": "~3.0.2",
|
||||||
"braces": "^2.3.2",
|
"fsevents": "~2.1.2",
|
||||||
"fsevents": "^1.2.7",
|
"glob-parent": "~5.1.0",
|
||||||
"glob-parent": "^3.1.0",
|
"is-binary-path": "~2.1.0",
|
||||||
"inherits": "^2.0.3",
|
"is-glob": "~4.0.1",
|
||||||
"is-binary-path": "^1.0.0",
|
"normalize-path": "~3.0.0",
|
||||||
"is-glob": "^4.0.0",
|
"readdirp": "~3.3.0"
|
||||||
"normalize-path": "^3.0.0",
|
},
|
||||||
"path-is-absolute": "^1.0.0",
|
"dependencies": {
|
||||||
"readdirp": "^2.2.1",
|
"anymatch": {
|
||||||
"upath": "^1.1.1"
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"normalize-path": "^3.0.0",
|
||||||
|
"picomatch": "^2.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"binary-extensions": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"braces": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"fill-range": "^7.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fill-range": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"to-regex-range": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fsevents": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"glob-parent": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-glob": "^4.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"is-binary-path": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"binary-extensions": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"is-number": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"readdirp": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"picomatch": "^2.0.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"to-regex-range": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-number": "^7.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chownr": {
|
"chownr": {
|
||||||
@ -1489,9 +1587,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||||
},
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@ -1868,9 +1966,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
@ -2026,9 +2124,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
@ -2089,6 +2187,18 @@
|
|||||||
"ora": "^3.4.0",
|
"ora": "^3.4.0",
|
||||||
"spawn-rx": "^3.0.0",
|
"spawn-rx": "^3.0.0",
|
||||||
"yargs": "^14.2.0"
|
"yargs": "^14.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"fs-extra": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||||
|
"requires": {
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"jsonfile": "^4.0.0",
|
||||||
|
"universalify": "^0.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"electron-winstaller": {
|
"electron-winstaller": {
|
||||||
@ -2188,6 +2298,17 @@
|
|||||||
"uuid": "^3.3.3"
|
"uuid": "^3.3.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"fs-extra": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"jsonfile": "^4.0.0",
|
||||||
|
"universalify": "^0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"klaw": {
|
"klaw": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz",
|
||||||
@ -2553,13 +2674,30 @@
|
|||||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
|
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
|
||||||
},
|
},
|
||||||
"fs-extra": {
|
"fs-extra": {
|
||||||
"version": "8.1.0",
|
"version": "9.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz",
|
||||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
"integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"at-least-node": "^1.0.0",
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
"jsonfile": "^4.0.0",
|
"jsonfile": "^6.0.1",
|
||||||
"universalify": "^0.1.0"
|
"universalify": "^1.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"jsonfile": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==",
|
||||||
|
"requires": {
|
||||||
|
"graceful-fs": "^4.1.6",
|
||||||
|
"universalify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"universalify": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fs-minipass": {
|
"fs-minipass": {
|
||||||
@ -4232,9 +4370,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -4502,6 +4640,26 @@
|
|||||||
"update-notifier": "^2.5.0"
|
"update-notifier": "^2.5.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chokidar": {
|
||||||
|
"version": "2.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
|
||||||
|
"integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"anymatch": "^2.0.0",
|
||||||
|
"async-each": "^1.0.1",
|
||||||
|
"braces": "^2.3.2",
|
||||||
|
"fsevents": "^1.2.7",
|
||||||
|
"glob-parent": "^3.1.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"is-binary-path": "^1.0.0",
|
||||||
|
"is-glob": "^4.0.0",
|
||||||
|
"normalize-path": "^3.0.0",
|
||||||
|
"path-is-absolute": "^1.0.0",
|
||||||
|
"readdirp": "^2.2.1",
|
||||||
|
"upath": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "3.2.6",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||||
@ -4628,9 +4786,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||||
},
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@ -4981,6 +5139,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
||||||
},
|
},
|
||||||
|
"picomatch": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"pify": {
|
"pify": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
|
||||||
@ -5143,9 +5307,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -6607,6 +6771,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||||
},
|
},
|
||||||
|
"v8-callsites": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/v8-callsites/-/v8-callsites-1.2.1.tgz",
|
||||||
|
"integrity": "sha1-PKTi3t9Q60ieNwVu1ksCVele84Y="
|
||||||
|
},
|
||||||
"validate-npm-package-license": {
|
"validate-npm-package-license": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||||
|
11
package.json
11
package.json
@ -38,14 +38,14 @@
|
|||||||
"asar": "^2.0.1",
|
"asar": "^2.0.1",
|
||||||
"cmake-js": "^4.0.1",
|
"cmake-js": "^4.0.1",
|
||||||
"ejs": "^2.7.1",
|
"ejs": "^2.7.1",
|
||||||
"electron-installer-windows": "^1.1.1",
|
"electron-installer-windows": "^1.1.0",
|
||||||
"electron-packager": "8.7.2",
|
"electron-packager": "8.7.2",
|
||||||
"electron-winstaller": "^2.7.0",
|
"electron-winstaller": "^2.7.0",
|
||||||
"electron-wix-msi": "^2.2.0",
|
"electron-wix-msi": "^2.1.1",
|
||||||
"nodemon": "^1.19.4",
|
"nodemon": "^1.19.4",
|
||||||
"platform-dependent-modules": "0.0.14",
|
"platform-dependent-modules": "0.0.14",
|
||||||
"rc": "^1.2.8",
|
"rc": "^1.2.8",
|
||||||
"rcedit": "^1.1.2",
|
"rcedit": "^1.1.1",
|
||||||
"sass": "^1.23.2",
|
"sass": "^1.23.2",
|
||||||
"typescript": "^3.7.2"
|
"typescript": "^3.7.2"
|
||||||
},
|
},
|
||||||
@ -62,7 +62,7 @@
|
|||||||
"electron-rebuild": "^1.8.6",
|
"electron-rebuild": "^1.8.6",
|
||||||
"extend": "^3.0.2",
|
"extend": "^3.0.2",
|
||||||
"extsprintf": "^1.4.0",
|
"extsprintf": "^1.4.0",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^9.0.0",
|
||||||
"http-signature": "^1.3.1",
|
"http-signature": "^1.3.1",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
"json-stringify-safe": "^5.0.1",
|
"json-stringify-safe": "^5.0.1",
|
||||||
@ -80,7 +80,8 @@
|
|||||||
"safer-buffer": "^2.1.2",
|
"safer-buffer": "^2.1.2",
|
||||||
"sshpk": "^1.16.1",
|
"sshpk": "^1.16.1",
|
||||||
"tar-stream": "^2.1.0",
|
"tar-stream": "^2.1.0",
|
||||||
"tough-cookie": "^3.0.1"
|
"tough-cookie": "^3.0.1",
|
||||||
|
"v8-callsites": "latest"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"platformDependentModules": {
|
"platformDependentModules": {
|
||||||
|
@ -6,7 +6,12 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"rootDirs": [
|
"rootDirs": [
|
||||||
"modules"
|
"modules"
|
||||||
]
|
],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"tc-shared/*": ["imports/shared-app/*"],
|
||||||
|
"tc-loader": ["imports/loader"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user