Compare commits

..

30 Commits

Author SHA1 Message Date
66c1d25888 v0.9.0 2020-07-28 09:51:56 +08:00
ca8d4778bb feat: optimize plugin event registry
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-28 09:41:10 +08:00
7dcc5ed45a feat: impl bukkit chat api
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-28 09:39:52 +08:00
91d2d836fc feat: add chat api
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-28 09:39:15 +08:00
e5e1bb70a1 revent: qrcode.ts
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-27 01:08:45 +08:00
e93a53aa9f backup: new qrcodeSigned-off-by: MiaoWoo <admin@yumc.pw>
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-27 01:07:35 +08:00
e3bceda06b fix: readSourceMap split error
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-26 21:33:58 +08:00
9367e8c2c9 feat: update plugins
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-25 15:24:02 +08:00
31c7ea2a29 feat: add keyvalue package
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-24 18:45:56 +08:00
2589633069 v0.8.4 2020-07-23 14:22:30 +08:00
bc17e2f376 feat: opitmize plugin manager
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-23 13:44:55 +08:00
20749d3381 feat: add qrcode lib
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-23 13:44:39 +08:00
6261b85413 fix: reflect method error
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-11 15:08:02 +08:00
da9d590374 fix: unregistry controller error
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-06 17:41:50 +08:00
0af3ca8c79 feat: debug print error stack
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-06 17:41:15 +08:00
561d5b60e8 feat: backup plugin
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-06 16:13:18 +08:00
878a5368af feat: add plugin web stage
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-06 16:10:33 +08:00
018b972aea feat: add ServerChecker
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-06 16:07:16 +08:00
4138cf9448 fix: spring tab complate error
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-06 16:07:00 +08:00
646176b43c feat: split plugin ext stage exec
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-06 16:06:47 +08:00
506184a104 fix: reload plugin not effect
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-02 18:30:56 +08:00
392f01d509 v0.8.3 2020-07-02 17:08:20 +08:00
75b892e1c0 feat: enable compilerOptions importHelpers
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-02 16:57:16 +08:00
c2d825eed0 feat: update typescript version
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-02 11:23:14 +08:00
393e63c017 v0.8.2 2020-07-01 14:41:21 +08:00
46d6748f31 feat: update dep version
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-07-01 14:33:16 +08:00
b6803ed01c feat: update plugins
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-06-30 16:02:51 +08:00
bbfc70fdd6 feat: complate @ccms/web package
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-06-30 16:02:39 +08:00
e28af3fbbc feat: optimize process & plugin loader and scanner
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-06-30 15:58:51 +08:00
14eef46e67 fix: boardcast error when socket send packet
Signed-off-by: MiaoWoo <admin@yumc.pw>
2020-06-30 14:02:18 +08:00
84 changed files with 2981 additions and 769 deletions

View File

@@ -21,3 +21,4 @@ wallaby.js
.theia
type_definitions
tsconfig.tsbuildinfo
*.tsbuildinfo

View File

@@ -1,5 +1,5 @@
{
"version": "0.8.1",
"version": "0.9.0",
"useWorkspaces": true,
"npmClient": "yarn",
"packages": [

View File

@@ -20,6 +20,6 @@
"packages/*"
],
"devDependencies": {
"lerna": "^3.21.0"
"lerna": "^3.22.1"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/amqp",
"version": "0.8.1",
"version": "0.9.0",
"description": "MiaoScript amqp package",
"keywords": [
"miaoscript",
@@ -19,14 +19,17 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"@ccms/api": "^0.8.1",
"@ccms/common": "^0.8.0",
"@ccms/container": "^0.8.0",
"@ccms/nashorn": "^0.8.0"
"@ccms/api": "^0.9.0",
"@ccms/common": "^0.9.0",
"@ccms/container": "^0.9.0"
},
"devDependencies": {
"@ccms/nashorn": "^0.9.0",
"@javatypes/amqp-client": "^0.0.2",
"@javatypes/spring-amqp": "^0.0.2",
"@javatypes/spring-rabbit": "^0.0.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/api",
"version": "0.8.1",
"version": "0.9.0",
"description": "MiaoScript api package",
"keywords": [
"miaoscript",
@@ -19,16 +19,16 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"@ccms/common": "^0.8.0",
"@ccms/container": "^0.8.0",
"@ccms/ployfill": "^0.8.1",
"@ccms/common": "^0.9.0",
"@ccms/container": "^0.9.0",
"@ccms/ployfill": "^0.9.0",
"base64-js": "^1.3.1",
"source-map-builder": "^0.0.7"
},
"devDependencies": {
"@types/base64-js": "^1.2.5",
"@types/base64-js": "^1.3.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
}
}

19
packages/api/src/chat.ts Normal file
View File

@@ -0,0 +1,19 @@
export namespace chat {
export abstract class Chat {
sendMessage(sender: any, message: string) {
throw new Error("Method not implemented.")
}
sendActionBar(sender: any, message: string) {
throw new Error("Method not implemented.")
}
clearActionBar(sender: any) {
this.sendActionBar(sender, '')
}
sendTitle(sender: any, title: string, subtitle: string = '', fadeIn: number = 1, time: number = 5, fadeOut: number = 1) {
throw new Error("Method not implemented.")
}
clearTitle(sender: any) {
this.sendTitle(sender, '', '', 0, 0, 0)
}
}
}

View File

@@ -97,10 +97,11 @@ export class MiaoScriptConsole implements Console {
MiaoScriptConsole.sourceMaps[fileName] = null
let sourceLine = base.read(fileName).split('\n')
let lastLine = sourceLine[sourceLine.length - 1]
// lastLine is similar //# sourceMappingURL=data:application/json;base64,
if (lastLine.startsWith('//# sourceMappingURL=')) {
let sourceContent = null
let sourceMappingURL = lastLine.split('sourceMappingURL=', 2)[1]
if (sourceMappingURL.startsWith('data:application/jsonbase64,')) {
if (sourceMappingURL.startsWith('data:application/json;base64,')) {
sourceContent = String.fromCharCode(...Array.from(base64.toByteArray(sourceMappingURL.split(',', 2)[1])))
} else if (sourceMappingURL.startsWith('http')) {
// TODO
@@ -122,6 +123,9 @@ export class MiaoScriptConsole implements Console {
}
} catch (error) {
console.debug('search source map', fileName, 'line', lineNumber, 'error:', error)
if (global.debug) {
console.ex(error)
}
}
return {
fileName,

View File

@@ -4,7 +4,7 @@
import i18n from '@ccms/i18n'
import { injectable, unmanaged } from '@ccms/container'
const Thread = Java.type('java.lang.Thread');
const Thread = Java.type('java.lang.Thread')
export namespace event {
/**
@@ -30,7 +30,7 @@ export namespace event {
protected baseEventDir = '';
constructor(@unmanaged() baseEventDir: string) {
this.baseEventDir = baseEventDir;
this.baseEventDir = baseEventDir
}
/**
@@ -39,74 +39,74 @@ export namespace event {
* org.spongepowered.api.event.game.GameRegistryEvent.Register => gameregistryevent$register
*/
mapEventName() {
if (this.baseEventDir === "") { throw new Error(i18n.translate('ms.api.event.empty.event.dir')); }
let count = 0;
let jar = this.getJarFile(this.baseEventDir);
let entries = jar.entries();
if (this.baseEventDir === "") { throw new Error(i18n.translate('ms.api.event.empty.event.dir')) }
let count = 0
let jar = this.getJarFile(this.baseEventDir)
let entries = jar.entries()
while (entries.hasMoreElements()) {
let entry = entries.nextElement();
let name = entry.name;
let entry = entries.nextElement()
let name = entry.name
if (name.startsWith(this.baseEventDir) && name.endsWith(".class")) {
// replace name to qualifiedName
let qualifiedName = name.replaceAll('/', '.');
let qualifiedName = name.replaceAll('/', '.')
try {
let clazz = base.getClass(qualifiedName.substring(0, qualifiedName.length - 6));
let clazz = base.getClass(qualifiedName.substring(0, qualifiedName.length - 6))
if (this.isValidEvent(clazz)) {
let simpleName = this.class2Name(clazz).toLowerCase();
console.trace(i18n.translate("ms.api.event.mapping", { canonicalName: clazz.canonicalName, simpleName }));
this.mapEvent[simpleName] = clazz;
count++;
let simpleName = this.class2Name(clazz).toLowerCase()
console.trace(i18n.translate("ms.api.event.mapping", { canonicalName: clazz.canonicalName, simpleName }))
this.mapEvent[simpleName] = clazz
count++
}
} catch (ex) {
//ignore already loaded class
}
}
}
return count;
return count
}
getJarFile(resource: string, loader?: any) {
let dirs = (loader || Thread.currentThread().getContextClassLoader()).getResources(resource);
let dirs = (loader || Thread.currentThread().getContextClassLoader()).getResources(resource)
if (dirs.hasMoreElements()) {
let url = dirs.nextElement();
if (url.protocol === "jar") { return url.openConnection().jarFile; }
let url = dirs.nextElement()
if (url.protocol === "jar") { return url.openConnection().jarFile }
}
throw new Error(i18n.translate("ms.api.event.resource.not.found", { resource }))
}
class2Name(clazz: any) {
return clazz.simpleName;
return clazz.simpleName
}
name2Class(name: any, event: string) {
let eventCls = this.mapEvent[event.toLowerCase()] || this.mapEvent[event.toLowerCase() + 'event'];
let eventCls = this.mapEvent[event.toLowerCase()] || this.mapEvent[event.toLowerCase() + 'event']
if (!eventCls) {
try {
eventCls = base.getClass(eventCls);
this.mapEvent[event] = eventCls;
eventCls = base.getClass(eventCls)
this.mapEvent[event] = eventCls
} catch (ex) {
console.i18n("ms.api.event.not.found", { name, event })
return;
return
}
}
return eventCls;
return eventCls
}
execute(name, exec, eventCls) {
return (...args: any[]) => {
try {
let event = args[args.length - 1];
let event = args[args.length - 1]
if (eventCls.isAssignableFrom(event.getClass())) {
let time = Date.now()
exec(event);
let cost = Date.now() - time;
exec(event)
let cost = Date.now() - time
if (cost > 20) {
console.i18n("ms.api.event.execute.slow", { name, event: this.class2Name(eventCls), cost })
}
}
} catch (ex) {
console.i18n("ms.api.event.execute.error", { name, event: this.class2Name(eventCls), ex })
console.ex(ex);
console.ex(ex)
}
}
}
@@ -120,34 +120,34 @@ export namespace event {
* @param ignoreCancel
*/
listen(plugin: any, event: string, exec: (event: any) => void, priority: EventPriority = EventPriority.NORMAL, ignoreCancel = false) {
if (!plugin || !plugin.description || !plugin.description.name) throw new TypeError(i18n.translate("ms.api.event.listen.plugin.name.empty"));
var name = plugin.description.name;
var eventCls = this.name2Class(name, event);
if (!eventCls) { return; }
if (!plugin || !plugin.description || !plugin.description.name) throw new TypeError(i18n.translate("ms.api.event.listen.plugin.name.empty"))
var name = plugin.description.name
var eventCls = this.name2Class(name, event)
if (!eventCls) { return }
if (typeof priority === 'boolean') {
ignoreCancel = priority;
priority = EventPriority.NORMAL;
ignoreCancel = priority
priority = EventPriority.NORMAL
}
priority = priority || EventPriority.NORMAL;
ignoreCancel = ignoreCancel || false;
priority = priority || EventPriority.NORMAL
ignoreCancel = ignoreCancel || false
// noinspection JSUnusedGlobalSymbols
var listener = this.register(eventCls, this.execute(name, exec, eventCls), priority, ignoreCancel);
var listenerMap = this.listenerMap;
var listener = this.register(eventCls, this.execute(name, exec, eventCls), priority, ignoreCancel)
var listenerMap = this.listenerMap
// add to cache Be used for close plugin to close event
if (!listenerMap[name]) listenerMap[name] = [];
if (!listenerMap[name]) listenerMap[name] = []
var offExec = () => {
this.unregister(eventCls, listener);
console.debug(i18n.translate("ms.api.event.unregister", { name, event: this.class2Name(eventCls) }));
};
this.unregister(eventCls, listener)
console.debug(i18n.translate("ms.api.event.unregister", { name, event: this.class2Name(eventCls), exec: exec.name || '[anonymous]' }))
}
var off = {
event: eventCls,
listener: listener,
off: offExec
};
listenerMap[name].push(off);
}
listenerMap[name].push(off)
// noinspection JSUnresolvedVariable
console.debug(i18n.translate("ms.api.event.register", { name, event: this.class2Name(eventCls) }));
return off;
console.debug(i18n.translate("ms.api.event.register", { name, event: this.class2Name(eventCls), exec: exec.name || '[anonymous]' }))
return off
}
/**
@@ -155,10 +155,10 @@ export namespace event {
* @param plugin 插件
*/
disable(plugin: any) {
var eventCache = this.listenerMap[plugin.description.name];
var eventCache = this.listenerMap[plugin.description.name]
if (eventCache) {
eventCache.forEach(t => t.off());
delete this.listenerMap[plugin.description.name];
eventCache.forEach(t => t.off())
delete this.listenerMap[plugin.description.name]
}
}
@@ -166,7 +166,7 @@ export namespace event {
* 判断
* @param clazz 事件类
*/
abstract isValidEvent(clazz: any): boolean;
abstract isValidEvent(clazz: any): boolean
/**
* 注册事件
* @param eventCls 事件类
@@ -174,12 +174,12 @@ export namespace event {
* @param priority 优先级
* @param ignoreCancel 是否忽略已取消的事件
*/
abstract register(eventCls: any, exec: Function, priority: any, ignoreCancel: boolean): any;
abstract register(eventCls: any, exec: Function, priority: any, ignoreCancel: boolean): any
/**
* 取消监听事件
* @param event 事件
* @param listener 监听器
*/
abstract unregister(event: any, listener: any): void;
abstract unregister(event: any, listener: any): void
}
}

View File

@@ -1,4 +1,6 @@
import "@ccms/nashorn"
export * from './chat'
export * from './task'
export * from './event'
export * from './console'

View File

@@ -152,21 +152,21 @@ export namespace plugin {
}
export interface PluginMetadata extends BaseMetadata {
/**
* 插件名称
* 插件名称 不填默认为类名
*/
name: string
name?: string
/**
* 前缀
*/
prefix?: string
/**
* 插件版本
* 插件版本 不填默认为 1.0.0
*/
version: string
version?: string
/**
* 插件版本
* 插件作者 不填默认为 Unknow
*/
author: string | string[]
author?: string | string[]
/**
* 插件源文件 必须指定为 __filename
*/

View File

@@ -1,5 +1,5 @@
import * as reflect from '@ccms/common/dist/reflect'
import { injectable } from '@ccms/container'
import { injectable, inject } from '@ccms/container'
import { NativePluginManager } from './native_plugin'
import { constants } from '../../constants'
@@ -41,6 +41,23 @@ export namespace server {
tabComplete?(sender: string | any, input: string, index?: number): string[]
}
@injectable()
export class ServerChecker {
@inject(ServerType)
private serverType: string
check(servers: string[]) {
// Not set servers -> allow
if (!servers || !servers.length) return true
// include !type -> deny
let denyServers = servers.filter(svr => svr.startsWith("!"))
if (denyServers.length !== 0) {
return !denyServers.includes(`!${this.serverType}`)
} else {
// only include -> allow
return servers.includes(this.serverType)
}
}
}
@injectable()
export abstract class ReflectServer implements server.Server {
protected pipeline: any
protected rootLogger: any

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/bukkit",
"version": "0.8.1",
"version": "0.9.0",
"description": "MiaoScript bukkit package",
"keywords": [
"miaoscript",
@@ -19,13 +19,14 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"devDependencies": {
"@javatypes/spigot-api": "^0.0.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
},
"dependencies": {
"@ccms/api": "^0.8.1",
"@ccms/common": "^0.8.0",
"@ccms/container": "^0.8.0"
"@ccms/api": "^0.9.0",
"@ccms/common": "^0.9.0",
"@ccms/container": "^0.9.0"
}
}

View File

@@ -8,18 +8,30 @@ const Messenger = Bukkit.getMessenger()
@provideSingleton(channel.Channel)
export class BukkitChannel extends channel.Channel {
@inject(plugin.PluginInstance)
private pluginInstance: any;
private pluginInstance: any
send(player: any, channel: string, data: any) {
player.sendPluginMessage(this.pluginInstance, channel, data);
/**
* 给玩家发送通道消息
* @param player 接受消息的玩家
* @param channel 通道
* @param data 数据
*/
send(player: any, channel: string, data: number[]) {
player.sendPluginMessage(this.pluginInstance, channel, data)
}
/**
* 注册通道监听器
* @param channel 通道
* @param listener 监听器
*/
register(channel: string, listener: channel.ChannelListener) {
Messenger.registerIncomingPluginChannel(this.pluginInstance, channel, new PluginMessageListener({
onPluginMessageReceived: (/**String */ channel, /**Player */ player, /**byte[] */data) => {
listener(data, { channel, player, data })
}
}));
Messenger.registerOutgoingPluginChannel(this.pluginInstance, channel);
}))
Messenger.registerOutgoingPluginChannel(this.pluginInstance, channel)
}
unregister(channel: string, listener: any) {
Messenger.unregisterIncomingPluginChannel(this.pluginInstance, channel)

View File

@@ -0,0 +1,16 @@
import { chat } from '@ccms/api'
import { provideSingleton } from '@ccms/container'
import bukkitChat from './enhance/chat'
@provideSingleton(chat.Chat)
export class BukkitChat extends chat.Chat {
sendMessage(sender: any, message: string) {
bukkitChat.send(sender, { text: message }, 0)
}
sendActionBar(sender: any, message: string) {
bukkitChat.send(sender, { text: message }, 2)
}
sendTitle(sender: any, title: string, subtitle: string = '', fadeIn: number = 1, time: number = 5, fadeOut: number = 1) {
sender.sendTitle(title, subtitle, fadeIn, time, fadeOut)
}
}

View File

@@ -1,29 +1,28 @@
/*global Java, base, module, exports, require*/
var nmsChatSerializerClass;
var nmsChatSerializerMethod;
var packetTypeConstructor;
var nmsChatMessageTypeClass;
var chatMessageTypes;
let ChatSerializer: any
let nmsChatSerializerMethodName: string
let PacketPlayOutChat: any
let chatMessageTypes: any
var RemapUtils;
let RemapUtils: any
var playerConnectionFieldName;
var sendPacketMethod;
let playerConnectionFieldName: string
let sendPacketMethodName: string
var downgrade = false;
let downgrade = false
/**
* 获取NMS版本
*/
//@ts-ignore
var nmsVersion = org.bukkit.Bukkit.server.class.name.split('.')[3];
let nmsVersion = org.bukkit.Bukkit.server.class.name.split('.')[3]
/**
* 获取NMS类
*/
function nmsCls(name) {
function nmsCls(name: string) {
return base.getClass(['net.minecraft.server', nmsVersion, name].join('.'))
}
function remapMethod(clazz: any, origin: string, test: string, params) {
function remapMethod(clazz: any, origin: string, test: string, params: any) {
try {
return clazz.getMethod(origin, params)
} catch (ex) {
@@ -49,66 +48,57 @@ function remapFieldName(clazz: any, origin: string, test: string) {
function init() {
try {
RemapUtils = Java.type('catserver.server.remapper.RemapUtils');
RemapUtils = Java.type('catserver.server.remapper.RemapUtils')
} catch (ex) {
}
nmsChatSerializerClass = nmsCls(nmsVersion.split("_")[1] > 7 ? "IChatBaseComponent$ChatSerializer" : "ChatSerializer");
nmsChatSerializerMethod = remapMethod(nmsChatSerializerClass, 'a', 'func_150699_a', base.getClass('java.lang.String'))
var packetTypeClass = nmsCls("PacketPlayOutChat");
Java.from(packetTypeClass.constructors).forEach(function(c) {
let nmsChatSerializerClass = nmsCls(nmsVersion.split("_")[1] > 7 ? "IChatBaseComponent$ChatSerializer" : "ChatSerializer")
let nmsChatSerializerMethod = remapMethod(nmsChatSerializerClass, 'a', 'func_150699_a', base.getClass('java.lang.String'))
nmsChatSerializerMethodName = nmsChatSerializerMethod.getName()
ChatSerializer = Java.type(nmsChatSerializerClass.getName())
let packetTypeClass = nmsCls("PacketPlayOutChat")
PacketPlayOutChat = Java.type(packetTypeClass.getName())
let packetTypeConstructor: { parameterTypes: any[] }
Java.from(packetTypeClass.constructors).forEach(function (c) {
if (c.parameterTypes.length === 2) {
packetTypeConstructor = c
}
});
nmsChatMessageTypeClass = packetTypeConstructor.parameterTypes[1];
})
let nmsChatMessageTypeClass = packetTypeConstructor.parameterTypes[1]
if (nmsChatMessageTypeClass.isEnum()) {
chatMessageTypes = nmsChatMessageTypeClass.getEnumConstants();
} else {
switch (nmsChatMessageTypeClass.name) {
case "int":
//@ts-ignore
nmsChatMessageTypeClass = java.lang.Integer;
break;
case "byte":
//@ts-ignore
nmsChatMessageTypeClass = java.lang.Byte;
break;
}
chatMessageTypes = nmsChatMessageTypeClass.getEnumConstants()
}
var entityPlayerClass = nmsCls('EntityPlayer');
var packetClass = nmsCls('Packet');
var playerConnectionField = remapFieldName(entityPlayerClass, 'playerConnection', 'field_71135_a')
let playerConnectionField = remapFieldName(nmsCls('EntityPlayer'), 'playerConnection', 'field_71135_a')
playerConnectionFieldName = playerConnectionField.getName()
sendPacketMethod = remapMethod(playerConnectionField.getType(), 'sendPacket', 'func_179290_a', packetClass)
sendPacketMethodName = remapMethod(playerConnectionField.getType(), 'sendPacket', 'func_179290_a', nmsCls('Packet')).getName()
}
function json(sender, json) {
function json(sender: { name: string }, json: string) {
if (downgrade) {
return '/tellraw ' + sender.name + ' ' + json
} else {
send(sender, json, 0);
return false;
send(sender, json, 0)
return false
}
}
function send(sender, json, type) {
//@ts-ignore
sendPacket(sender, packetTypeConstructor.newInstance(nmsChatSerializerMethod.invoke(null, json), chatMessageTypes == null ? nmsChatMessageTypeClass.valueOf(java.lang.String.valueOf(type)) : chatMessageTypes[type]))
function send(sender: any, json: any, type: number) {
sendPacket(sender, new PacketPlayOutChat(ChatSerializer[nmsChatSerializerMethodName](json), chatMessageTypes == null ? type : chatMessageTypes[type]))
}
function sendPacket(player, p) {
sendPacketMethod.invoke(player.handle[playerConnectionFieldName], p)
function sendPacket(player: { handle: { [x: string]: { [x: string]: (arg0: any) => void } } }, p: any) {
player.handle[playerConnectionFieldName][sendPacketMethodName](p)
}
try {
init();
init()
} catch (ex) {
org.bukkit.Bukkit.getConsoleSender().sendMessage(`§6[§cMS§6][§bbukkit§6][§achat§6] §cNMS Inject Error §4${ex} §cDowngrade to Command Mode...`)
downgrade = true;
downgrade = true
}
let chat = {
json
json,
send
}
export default chat

View File

@@ -1,18 +1,18 @@
import { server, constants } from '@ccms/api'
import { provideSingleton } from '@ccms/container';
import { provideSingleton } from '@ccms/container'
import * as reflect from '@ccms/common/dist/reflect'
import chat from './enhance/chat'
let Bukkit = org.bukkit.Bukkit;
let Bukkit = org.bukkit.Bukkit
@provideSingleton(server.Server)
export class BukkitServer extends server.ReflectServer {
private pluginsFolder: string;
private pluginsFolder: string
constructor() {
super();
this.pluginsFolder = Bukkit.getUpdateFolderFile().getParentFile().getCanonicalPath();
super()
this.pluginsFolder = Bukkit.getUpdateFolderFile().getParentFile().getCanonicalPath()
}
getPlayer(name: string) {
@@ -40,25 +40,25 @@ export class BukkitServer extends server.ReflectServer {
return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command)
}
getPluginsFolder(): string {
return this.pluginsFolder;
return this.pluginsFolder
}
getNativePluginManager() {
return Bukkit.getPluginManager() as any;
return Bukkit.getPluginManager() as any
}
getDedicatedServer() {
return reflect.on(Bukkit.getServer()).get('console').get()
}
getNettyPipeline() {
return this.pipeline;
return this.pipeline
}
getRootLogger() {
return this.rootLogger;
return this.rootLogger
}
sendJson(sender: string | any, json: object | string): void {
if (typeof sender === "string") {
sender = this.getPlayer(sender)
}
let result = chat.json(sender, json)
let result = chat.json(sender, typeof json == "string" ? json : JSON.stringify(json))
if (result !== false) {
this.dispatchConsoleCommand(result)
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/bungee",
"version": "0.8.1",
"version": "0.9.0",
"description": "MiaoScript bungee package",
"keywords": [
"miaoscript",
@@ -19,13 +19,14 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"devDependencies": {
"@javatypes/bungee-api": "^0.0.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
},
"dependencies": {
"@ccms/api": "^0.8.1",
"@ccms/common": "^0.8.0",
"@ccms/container": "^0.8.0"
"@ccms/api": "^0.9.0",
"@ccms/common": "^0.9.0",
"@ccms/container": "^0.9.0"
}
}

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "@ccms/client",
"version": "0.8.0",
"version": "0.9.0",
"description": "MiaoScript client package",
"keywords": [
"miaoscript",
@@ -22,10 +22,10 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"minecraft-protocol": "^1.11.0"
"minecraft-protocol": "^1.13.0"
},
"devDependencies": {
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/common",
"version": "0.8.0",
"version": "0.9.0",
"description": "MiaoScript api package",
"keywords": [
"miaoscript",
@@ -19,13 +19,11 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"devDependencies": {
"@ccms/nashorn": "^0.9.0",
"@javatypes/jdk": "^0.0.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
},
"dependencies": {
"@ccms/nashorn": "^0.8.0",
"@javatypes/jdk": "^1.14.0"
"typescript": "^3.9.6"
},
"gitHead": "562e2d00175c9d3a99c8b672aa07e6d92706a027"
}

File diff suppressed because it is too large Load Diff

View File

@@ -96,10 +96,7 @@ function types(values: any[], def?: any) {
}
function accessible<T extends java.lang.reflect.AccessibleObject>(accessible: T): T {
if (accessible === null) {
return null
}
if (!accessible.isAccessible()) {
if (!accessible?.isAccessible()) {
accessible.setAccessible(true)
}
return accessible
@@ -143,7 +140,8 @@ function declaredMethod(clazz: java.lang.Class<any>, name: string, ...clazzs: ja
let key = clazz.getName() + '.' + name + ':' + (clazzs || []).join(':')
if (!methodCache[key]) {
try {
methodCache[key] = clazz.getMethod(name, ...clazzs)
// @ts-ignore
methodCache[key] = clazz.getMethod(name, clazzs)
} catch (ex) {
try {
methodCache[key] = clazz.getDeclaredMethod(name, clazzs as any)

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/compile",
"version": "0.8.0",
"version": "0.9.0",
"description": "MiaoScript compile package",
"keywords": [
"miaoscript",
@@ -21,6 +21,6 @@
"devDependencies": {
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/container",
"version": "0.8.0",
"version": "0.9.0",
"description": "MiaoScript container package",
"keywords": [
"miaoscript",
@@ -19,12 +19,12 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"devDependencies": {
"@ccms/nashorn": "^0.9.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
},
"dependencies": {
"@ccms/nashorn": "^0.8.0",
"inversify": "^5.0.1",
"inversify-binding-decorators": "^4.0.0"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/core",
"version": "0.8.1",
"version": "0.9.0",
"description": "MiaoScript api package",
"keywords": [
"miaoscript",
@@ -21,11 +21,11 @@
"devDependencies": {
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
},
"dependencies": {
"@ccms/api": "^0.8.1",
"@ccms/container": "^0.8.0"
"@ccms/api": "^0.9.0",
"@ccms/container": "^0.9.0"
},
"gitHead": "781524f83e52cad26d7c480513e3c525df867121"
}

View File

@@ -4,7 +4,6 @@ import { plugin, server, task, constants } from '@ccms/api'
import { DefaultContainer as container, inject, provideSingleton, ContainerInstance, buildProviderModule } from '@ccms/container'
console.i18n("ms.core.ioc.completed", { scope: global.scope, time: (Date.now() - containerStartTime) / 1000 })
import http from '@ccms/common/dist/http'
import { EventEmitter } from 'events'
@provideSingleton(MiaoScriptCore)
class MiaoScriptCore {
@@ -53,7 +52,6 @@ class MiaoScriptCore {
console.i18n("ms.core.engine.disable")
this.pluginManager.disable(this.pluginManager.getPlugins())
this.taskManager.disable()
process.emit('exit', 0)
process.exit(0)
}
}
@@ -98,6 +96,7 @@ function initialize() {
let type = detectServer()
console.i18n("ms.core.initialize.detect", { scope: global.scope, type })
container.bind(server.ServerType).toConstantValue(type)
container.bind(server.ServerChecker).toSelf().inSingletonScope()
console.i18n("ms.core.package.initialize", { scope: global.scope, type })
require(`${global.scope}/${type}`).default(container)
require(`${global.scope}/plugin`)

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/database",
"version": "0.8.1",
"version": "0.9.0",
"description": "MiaoScript database package",
"keywords": [
"miaoscript",
@@ -19,12 +19,13 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"devDependencies": {
"@javatypes/spring-jdbc": "^0.0.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
},
"dependencies": {
"@ccms/api": "^0.8.1",
"@ccms/container": "^0.8.0"
"@ccms/api": "^0.9.0",
"@ccms/container": "^0.9.0"
}
}

View File

@@ -21,8 +21,8 @@ ms.api.event.not.found: "§6插件 §b{name} §6注册事件 §c{event} §6失
ms.api.event.execute.slow: "§c注意! §6插件 §b{name} §6处理 §d{event} §6事件 §c耗时 §4{cost}ms !"
ms.api.event.execute.error: "§6插件 §b{name} §6处理 §d{event} §6事件时发生异常 §4{ex}"
ms.api.event.listen.plugin.name.empty: "插件名称为空 请检查传入参数!"
ms.api.event.register: "[{name}] 注册事件 {event}"
ms.api.event.unregister: "[{name}] 注销事件 {event}"
ms.api.event.register: "[{name}] 注册事件 {event} => {exec}"
ms.api.event.unregister: "[{name}] 注销事件 {event} => {exec}"
ms.api.command.register.input.error: "CommandExec 必须为一个函数... 输入: {exec}"
ms.api.command.register: "[{plugin}] 注册命令 {name}({cmd})..."
ms.api.command.unregister: "[{plugin}] 注销命令 {name}..."

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/i18n",
"version": "0.8.0",
"version": "0.9.0",
"description": "MiaoScript i18n package",
"keywords": [
"miaoscript",
@@ -19,14 +19,14 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"devDependencies": {
"@types/js-yaml": "^3.12.3",
"@ccms/nashorn": "^0.9.0",
"@types/js-yaml": "^3.12.5",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
},
"dependencies": {
"@ccms/nashorn": "^0.8.0",
"js-yaml": "^3.13.1"
"js-yaml": "^3.14.0"
},
"gitHead": "781524f83e52cad26d7c480513e3c525df867121"
}

View File

@@ -0,0 +1 @@
../../.npmignore

View File

@@ -0,0 +1,36 @@
{
"name": "@ccms/keyvalue",
"version": "0.9.0",
"description": "MiaoScript keyvalue package",
"keywords": [
"miaoscript",
"minecraft",
"bukkit",
"sponge"
],
"author": "MiaoWoo <admin@yumc.pw>",
"homepage": "https://github.com/circlecloud/ms.git",
"license": "ISC",
"main": "dist/index.js",
"scripts": {
"clean": "rimraf dist",
"watch": "tsc --watch",
"build": "yarn clean && tsc",
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"@ccms/api": "^0.9.0",
"@ccms/common": "^0.9.0",
"@ccms/container": "^0.9.0"
},
"devDependencies": {
"@ccms/nashorn": "^0.9.0",
"@javatypes/amqp-client": "^0.0.2",
"@javatypes/spring-amqp": "^0.0.2",
"@javatypes/spring-rabbit": "^0.0.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.6"
},
"gitHead": "2589633069d24f646ac09261b1b2304c21d4ea75"
}

View File

@@ -0,0 +1,17 @@
import { ConnectionFactoryAdapter } from "./connection"
import { RabbitTemplateAdapter } from "./template"
import { JSClass } from "@ccms/container"
export const RabbitAdmin = Java.type('org.springframework.amqp.rabbit.core.RabbitAdmin')
export class RabbitAdminAdapter {
@JSClass('org.springframework.amqp.rabbit.core.RabbitAdmin')
private RabbitAdmin: org.springframework.amqp.rabbit.core.RabbitAdmin
private _Handler: org.springframework.amqp.rabbit.core.RabbitAdmin
constructor(config: RabbitTemplateAdapter | org.springframework.amqp.rabbit.core.RabbitTemplate | ConnectionFactoryAdapter | org.springframework.amqp.rabbit.connection.ConnectionFactory) {
this._Handler = new RabbitAdmin((config instanceof RabbitTemplateAdapter || config instanceof ConnectionFactoryAdapter) ? config.getHandler() : config)
}
getHandler(): org.springframework.amqp.rabbit.core.RabbitAdmin {
return this._Handler
}
}

View File

@@ -0,0 +1,30 @@
export const ConfirmCallback = Java.type('org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback')
export const ReturnCallback = Java.type('org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback')
export abstract class ConfirmCallbackAdapter {
private _Handler: org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback
constructor() {
let ConfirmCallbackImpl = Java.extend(ReturnCallback, {
confirm: (correlationData: org.springframework.amqp.rabbit.connection.CorrelationData, ack: boolean, cause: string) => this.confirm(correlationData, ack, cause)
})
this._Handler = new ConfirmCallbackImpl()
}
abstract confirm(correlationData: org.springframework.amqp.rabbit.connection.CorrelationData, ack: boolean, cause: string)
getHandler() {
return this._Handler
}
}
export abstract class ReturnCallbackAdapter {
private _Handler: org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback
constructor() {
let ReturnCallbackImpl = Java.extend(ReturnCallback, {
returnedMessage: (message: org.springframework.amqp.core.Message, replyCode: number, replyText: string, exchange: string, routingKey: string) => this.returnedMessage(message, replyCode, replyText, exchange, routingKey)
})
this._Handler = new ReturnCallbackImpl()
}
abstract returnedMessage(message: org.springframework.amqp.core.Message, replyCode: number, replyText: string, exchange: string, routingKey: string)
getHandler() {
return this._Handler
}
}

View File

@@ -0,0 +1,31 @@
import threadPool from '@ccms/common/dist/thread-pool'
export const ConnectionFactory = Java.type('org.springframework.amqp.rabbit.connection.ConnectionFactory')
const CachingConnectionFactory = Java.type('org.springframework.amqp.rabbit.connection.CachingConnectionFactory')
interface ConnectionConfig {
url: string,
username?: string,
password?: string,
publisherConfirms?: boolean
cacheSize?: number
}
export class ConnectionFactoryAdapter {
private _Handler: org.springframework.amqp.rabbit.connection.CachingConnectionFactory
constructor(config: ConnectionConfig) {
if (!config.url) { throw new Error('Connection UrI Can\'t be undefiend!') }
config = { publisherConfirms: true, cacheSize: 50, ...config }
this._Handler = new CachingConnectionFactory(new java.net.URI(config.url))
config.username && this._Handler.setUsername(config.username)
config.password && this._Handler.setPassword(config.password)
this._Handler.setPublisherConfirms(config.publisherConfirms)
this._Handler.setExecutor(threadPool.create({
groupName: '@ccms/amqp-rabbit'
}))
this._Handler.setChannelCacheSize(config.cacheSize)
}
getHandler() {
return this._Handler
}
}

View File

@@ -0,0 +1,30 @@
import { ConnectionFactoryAdapter, ConnectionFactory } from "./connection"
import { ChannelAwareMessageListenerAdapter, ChannelAwareMessageListener } from "./listener"
export const SimpleMessageListenerContainer = org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer
export const AcknowledgeMode: org.springframework.amqp.core.AcknowledgeMode = Java.type('org.springframework.amqp.core.AcknowledgeMode')
interface ContainerConfig {
connectionFactory: ConnectionFactoryAdapter | typeof ConnectionFactory
queueNames: string[]
messageListener: ChannelAwareMessageListenerAdapter | typeof ChannelAwareMessageListener
maxConcurrentConsumers?: number
concurrentConsumers?: number
acknowledgeMode?: org.springframework.amqp.core.AcknowledgeMode
}
export class MessageListenerContainerAdapter {
private _Handler: org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer
constructor(config: ContainerConfig) {
config = { concurrentConsumers: 5, maxConcurrentConsumers: 10, acknowledgeMode: AcknowledgeMode.AUTO, ...config }
this._Handler = new SimpleMessageListenerContainer(config.connectionFactory instanceof ConnectionFactoryAdapter ? config.connectionFactory.getHandler() : config.connectionFactory)
//@ts-ignore
this._Handler.setQueueNames(config.queueNames)
this._Handler.setMaxConcurrentConsumers(config.maxConcurrentConsumers)
this._Handler.setConcurrentConsumers(config.concurrentConsumers)
this._Handler.setAcknowledgeMode(config.acknowledgeMode)
this._Handler.setMessageListener(config.messageListener instanceof ChannelAwareMessageListenerAdapter ? config.messageListener.getHandler() : config.messageListener)
}
getHandler() {
return this._Handler
}
}

View File

@@ -0,0 +1,37 @@
const JavaString = Java.type('java.lang.String')
const MessageProperties = org.springframework.amqp.core.MessageProperties
const Message = org.springframework.amqp.core.Message
export const MessageConverter = Java.type('org.springframework.amqp.support.converter.MessageConverter')
export class MessageConverterAdapter {
private _Handler: org.springframework.amqp.support.converter.MessageConverter
constructor() {
var MessageConverterImpl = Java.extend(MessageConverter, {
toMessage: (object: any, messageProperties: org.springframework.amqp.core.MessageProperties) => this.toMessage(object, messageProperties),
fromMessage: (message: org.springframework.amqp.core.Message) => this.fromMessage(message)
})
this._Handler = new MessageConverterImpl()
}
toMessage(object: any, messageProperties: org.springframework.amqp.core.MessageProperties) {
if (typeof object == "string") {
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
return new Message(new JavaString(object).getBytes(), messageProperties)
} else {
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON)
return new Message(new JavaString(JSON.stringify(object)).getBytes(), messageProperties)
}
}
fromMessage(message: org.springframework.amqp.core.Message) {
if (message.getMessageProperties().getContentType() == MessageProperties.CONTENT_TYPE_TEXT_PLAIN) {
return new JavaString(message.getBody(), message.getMessageProperties().getContentEncoding())
}
if (message.getMessageProperties().getContentType() == MessageProperties.CONTENT_TYPE_JSON) {
return JSON.parse(new JavaString(message.getBody(), message.getMessageProperties().getContentEncoding()))
}
return message
}
getHandler() {
return this._Handler
}
}

View File

@@ -0,0 +1,7 @@
export * from './admin'
export * from './callback'
export * from './connection'
export * from './container'
export * from './converter'
export * from './listener'
export * from './template'

View File

@@ -0,0 +1,49 @@
const JavaString = Java.type('java.lang.String')
export const MessageProperties = org.springframework.amqp.core.MessageProperties
export const ChannelAwareMessageListener = Java.type('org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener')
export const AmqpRejectAndDontRequeueException = org.springframework.amqp.AmqpRejectAndDontRequeueException
export const Channel: com.rabbitmq.client.Channel = Java.type('com.rabbitmq.client.Channel')
export const Message = org.springframework.amqp.core.Message
export type MessageHandler<T> = (content: T, message: org.springframework.amqp.core.Message, channel: com.rabbitmq.client.Channel) => any
export abstract class ChannelAwareMessageListenerAdapter<T = any> {
private _Handler: org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener
constructor(manual: boolean = false) {
let ChannelAwareMessageListenerImpl = Java.extend(ChannelAwareMessageListener, {
onMessage: (message: org.springframework.amqp.core.Message, channel: com.rabbitmq.client.Channel) => {
let content = new JavaString(message.getBody(), message.getMessageProperties().getContentEncoding() || "UTF-8")
try {
if (message.getMessageProperties().getContentType() == MessageProperties.CONTENT_TYPE_JSON) {
content = JSON.parse(content)
}
} catch (error) {
if (manual) {
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true)
} else {
throw new AmqpRejectAndDontRequeueException(`${error}`, error)
}
}
manual ? this.manualOnMessage(content, message, channel) : this.onMessage(content, message, channel)
}
})
this._Handler = new ChannelAwareMessageListenerImpl()
}
abstract onMessage(content: T, message: org.springframework.amqp.core.Message, channel: com.rabbitmq.client.Channel): any
onError(error: Error, message: org.springframework.amqp.core.Message, channel: com.rabbitmq.client.Channel): any { }
getHandler() {
return this._Handler
}
private manualOnMessage(content: T, message: org.springframework.amqp.core.Message, channel: com.rabbitmq.client.Channel) {
let deliveryTag = message.getMessageProperties().getDeliveryTag()
try {
if (this.onMessage(content, message, channel)) {
channel.basicAck(deliveryTag, false)
} else if (message.getMessageProperties().getRedelivered()) {
channel.basicReject(deliveryTag, true)
} else {
channel.basicNack(deliveryTag, false, true)
}
} catch (error) {
channel.basicReject(deliveryTag, this.onError(error, message, channel))
}
}
}

View File

@@ -0,0 +1,27 @@
import { MessageConverterAdapter } from "./converter"
import { ConnectionFactoryAdapter } from "./connection"
import { ConfirmCallbackAdapter, ReturnCallbackAdapter } from "./callback"
export const RabbitTemplate = Java.type('org.springframework.amqp.rabbit.core.RabbitTemplate')
interface TemplateConfig {
connectionFactory: ConnectionFactoryAdapter | org.springframework.amqp.rabbit.connection.ConnectionFactory
confirmCallback?: ConfirmCallbackAdapter
returnCallback?: ReturnCallbackAdapter
messageConverter?: MessageConverterAdapter
}
export class RabbitTemplateAdapter {
private _Handler: org.springframework.amqp.rabbit.core.RabbitTemplate
constructor(config: TemplateConfig) {
config = { messageConverter: new MessageConverterAdapter(), ...config }
console.debug(JSON.stringify(config))
this._Handler = new RabbitTemplate(config.connectionFactory instanceof ConnectionFactoryAdapter ? config.connectionFactory.getHandler() : config.connectionFactory)
config.returnCallback && this._Handler.setReturnCallback(config.returnCallback.getHandler())
config.confirmCallback && this._Handler.setConfirmCallback(config.confirmCallback.getHandler())
config.messageConverter && this._Handler.setMessageConverter(config.messageConverter.getHandler())
}
getHandler() {
return this._Handler
}
}

View File

@@ -0,0 +1,89 @@
import { JSClass } from "@ccms/container"
import { RabbitAdminAdapter, MessageListenerContainerAdapter, AcknowledgeMode, MessageHandler, ChannelAwareMessageListenerAdapter } from "./adapter"
export class AmqpAdmin {
@JSClass('org.springframework.amqp.core.TopicExchange')
private TopicExchange: typeof org.springframework.amqp.core.TopicExchange
@JSClass('org.springframework.amqp.core.Queue')
private Queue: typeof org.springframework.amqp.core.Queue
@JSClass('org.springframework.amqp.core.Binding')
private Binding: typeof org.springframework.amqp.core.Binding
@JSClass('org.springframework.amqp.rabbit.core.RabbitAdmin')
private RabbitAdmin: typeof org.springframework.amqp.rabbit.core.RabbitAdmin
@JSClass('org.springframework.amqp.core.Binding.DestinationType')
private DestinationType: org.springframework.amqp.core.Binding.DestinationType
private amqpAdmin: org.springframework.amqp.rabbit.core.RabbitAdmin
constructor(amqpAdmin: org.springframework.amqp.rabbit.core.RabbitAdmin | any) {
if (amqpAdmin instanceof this.RabbitAdmin) {
this.amqpAdmin = amqpAdmin
} else if (amqpAdmin instanceof RabbitAdminAdapter) {
this.amqpAdmin = amqpAdmin.getHandler()
} else {
this.amqpAdmin = new RabbitAdminAdapter(amqpAdmin).getHandler()
}
}
getHandler() {
return this.amqpAdmin
}
getQueueProperties(name: string) {
return this.amqpAdmin.getQueueProperties(name)
}
declareExchange(name: string) {
let exchange = new this.TopicExchange(name, true, false)
this.amqpAdmin.declareExchange(exchange)
return this
}
declareQueue(name: string) {
let queue = new this.Queue(name, true)
this.amqpAdmin.declareQueue(queue)
return this
}
declareBinding(queue: string, exchange: string, routerKey: string, argument: any = null) {
let binding = new this.Binding(queue, this.DestinationType.QUEUE, exchange, routerKey, argument)
this.amqpAdmin.declareBinding(binding)
return this
}
declareQueueAndBindExchange(queue: string, exchange: string, routerKey: string) {
return this.declareQueue(queue).declareExchange(exchange).declareBinding(queue, exchange, routerKey)
}
createContainer<T>(queue: string, listener: MessageHandler<T>, acknowledgeMode = AcknowledgeMode.AUTO) {
let connection = this.amqpAdmin.getRabbitTemplate().getConnectionFactory()
return new MessageListenerContainerAdapter({
connectionFactory: connection,
queueNames: [queue],
messageListener: new SimpleMessageHandler(listener),
acknowledgeMode: acknowledgeMode
}).getHandler()
}
send()
send() {
}
getTemplate() {
return this.amqpAdmin.getRabbitTemplate()
}
close() {
this.getTemplate().stop()
}
}
export class SimpleMessageHandler extends ChannelAwareMessageListenerAdapter {
constructor(private handler: MessageHandler<any>) {
super()
}
onMessage(content: any, message: org.springframework.amqp.core.Message, channel: com.rabbitmq.client.Channel) {
return this.handler(content, message, channel)
}
}

View File

@@ -0,0 +1,3 @@
export const METADATA_KEY = {
}

View File

@@ -0,0 +1,34 @@
/// <reference types="@ccms/nashorn" />
/// <reference types="@javatypes/jdk" />
/// <reference types="@javatypes/spring-amqp" />
/// <reference types="@javatypes/spring-rabbit" />
/// <reference types="@javatypes/amqp-client" />
import { amqp } from '@ccms/api'
import { getContainer, reduceMetadata } from '@ccms/container'
function init() {
const beanFactory = base.getInstance().getAutowireCapableBeanFactory()
getContainer().bind(amqp.rabbit.Template).toDynamicValue((ctx) => {
let metadata = reduceMetadata(ctx)
if (!metadata.named) {
return beanFactory.getBean('rabbitTemplate')
} else {
return beanFactory.getBean(`${metadata.named}-rabbitTemplate`)
}
})
getContainer().bind(amqp.rabbit.Admin).toDynamicValue((ctx) => {
let metadata = reduceMetadata(ctx)
if (!metadata.named) {
return beanFactory.getBean('rabbitAdmin')
} else {
return beanFactory.getBean(`${metadata.named}-rabbitAdmin`)
}
})
}
init()
export * from './admin'
export * from './adapter'
export * from './manager'
export * from './constants'

View File

@@ -0,0 +1,5 @@
export class AmqpListener {
constructor() {
}
}

View File

@@ -0,0 +1,18 @@
import { amqp } from "@ccms/api"
import { provideSingleton } from "@ccms/container"
import { ConnectionFactoryAdapter, RabbitTemplateAdapter, RabbitAdminAdapter } from "./adapter"
import { AmqpAdmin } from "./admin"
@provideSingleton(amqp.Manager)
export class AmqpManager {
createConnection(url: string, username: string, password: string) {
return new ConnectionFactoryAdapter({ url, username, password })
}
createTemplate(adapter: ConnectionFactoryAdapter) {
return new RabbitTemplateAdapter({ connectionFactory: adapter })
}
createAdmin(adapter: RabbitTemplateAdapter | ConnectionFactoryAdapter) {
return new AmqpAdmin(new RabbitAdminAdapter(adapter))
}
}

View File

@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": "src",
"outDir": "dist"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/nashorn",
"version": "0.8.0",
"version": "0.9.0",
"description": "MiaoScript api package",
"keywords": [
"miaoscript",
@@ -22,6 +22,6 @@
"devDependencies": {
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
}
}

View File

@@ -73,6 +73,7 @@ declare global {
interface Console {
ex(err: Error): void
stack(err: Error, color?: boolean): string[]
sender(sender: any, ...args: any): void
sender(...args: any): void
console(...args: any): void
i18n(name: string, ...params: any[]): void

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/nodejs",
"version": "0.8.0",
"version": "0.9.0",
"description": "MiaoScript nodejs package",
"keywords": [
"miaoscript",
@@ -19,12 +19,10 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"devDependencies": {
"@ccms/nashorn": "^0.9.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
},
"dependencies": {
"@ccms/nashorn": "^0.8.0"
"typescript": "^3.9.6"
},
"gitHead": "781524f83e52cad26d7c480513e3c525df867121"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/nukkit",
"version": "0.8.1",
"version": "0.9.0",
"description": "MiaoScript nukkit package",
"keywords": [
"miaoscript",
@@ -19,13 +19,14 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"devDependencies": {
"@javatypes/nukkit-api": "^0.0.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
},
"dependencies": {
"@ccms/api": "^0.8.1",
"@ccms/common": "^0.8.0",
"@ccms/container": "^0.8.0"
"@ccms/api": "^0.9.0",
"@ccms/common": "^0.9.0",
"@ccms/container": "^0.9.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/ployfill",
"version": "0.8.1",
"version": "0.9.0",
"description": "MiaoScript ployfill package",
"author": "MiaoWoo <admin@yumc.pw>",
"homepage": "https://github.com/circlecloud/ms.git",
@@ -14,14 +14,14 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"@ccms/i18n": "^0.8.0",
"@ccms/nashorn": "^0.8.0",
"@ccms/nodejs": "^0.8.0",
"@ccms/i18n": "^0.9.0",
"@ccms/nodejs": "^0.9.0",
"core-js": "^3.6.5"
},
"devDependencies": {
"@ccms/nashorn": "^0.9.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
}
}

View File

@@ -28,8 +28,9 @@ class Process extends EventEmitter {
queueMicrotask(func: Function) {
microTaskPool.execute(func)
}
exit() {
microTaskPool.shutdown();
exit(code: number) {
process.emit('exit', code)
microTaskPool.shutdown()
}
}
global.setGlobal('process', new Process(), {})

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/plugin",
"version": "0.8.1",
"version": "0.9.0",
"description": "MiaoScript api package",
"keywords": [
"miaoscript",
@@ -19,16 +19,16 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"devDependencies": {
"@types/js-yaml": "^3.12.3",
"@types/js-yaml": "^3.12.5",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
},
"dependencies": {
"@ccms/api": "^0.8.1",
"@ccms/common": "^0.8.0",
"@ccms/container": "^0.8.0",
"@ccms/i18n": "^0.8.0",
"js-yaml": "^3.13.1"
"@ccms/api": "^0.9.0",
"@ccms/common": "^0.9.0",
"@ccms/container": "^0.9.0",
"@ccms/i18n": "^0.9.0",
"js-yaml": "^3.14.0"
}
}

View File

@@ -0,0 +1,36 @@
import { command, plugin, server } from '@ccms/api'
import { provideSingleton, postConstruct, inject } from '@ccms/container'
import { getPluginCommandMetadata, getPluginTabCompleterMetadata } from './utils'
@provideSingleton(PluginCommandManager)
export class PluginCommandManager {
@inject(server.ServerChecker)
private ServerChecker: server.ServerChecker
@inject(command.Command)
private CommandManager: command.Command
constructor() {
process.on('plugin.before.enable', (plugin: plugin.Plugin) => this.registryCommand(plugin))
process.on('plugin.after.disable', (plugin: plugin.Plugin) => this.unregistryCommand(plugin))
}
private registryCommand(pluginInstance: plugin.Plugin) {
let cmds = getPluginCommandMetadata(pluginInstance)
let tabs = getPluginTabCompleterMetadata(pluginInstance)
for (const [_, cmd] of cmds) {
let tab = tabs.get(cmd.name)
if (!this.ServerChecker.check(cmd.servers)) { continue }
this.CommandManager.on(pluginInstance, cmd.name, {
cmd: pluginInstance[cmd.executor].bind(pluginInstance),
tab: tab ? pluginInstance[tab.executor].bind(pluginInstance) : undefined
})
}
}
private unregistryCommand(pluginInstance: plugin.Plugin) {
let cmds = getPluginCommandMetadata(pluginInstance)
cmds.forEach(cmd => {
this.CommandManager.off(pluginInstance, cmd.name)
})
}
}

View File

@@ -1,33 +1,75 @@
import * as yaml from 'js-yaml'
import * as fs from '@ccms/common/dist/fs'
import { plugin } from '@ccms/api'
import { getPluginConfigMetadata } from './utils'
export interface PluginConfigLoader {
load(content: string): any;
dump(variable: any): string;
load(content: string): any
dump(variable: any): string
}
export class YamlPluginConfig implements PluginConfigLoader {
load(content: string) {
return yaml.safeLoad(content);
return yaml.safeLoad(content)
}
dump(variable: any): string {
return yaml.safeDump(variable, { skipInvalid: true });
return yaml.safeDump(variable, { skipInvalid: true })
}
}
export class JsonPluginConfig implements PluginConfigLoader {
load(content: string) {
return JSON.parse(content);
return JSON.parse(content)
}
dump(variable: any): string {
return JSON.stringify(variable);
return JSON.stringify(variable)
}
}
const configLoaderMap = new Map<string, PluginConfigLoader>();
const configLoaderMap = new Map<string, PluginConfigLoader>()
export function getConfigLoader(format: string) {
if (!configLoaderMap.has(format)) { throw new Error(`Unsupport config format ${format} !`) }
return configLoaderMap.get(format);
return configLoaderMap.get(format)
}
function loadConfig(plugin: plugin.Plugin) {
let configs = getPluginConfigMetadata(plugin)
for (let [_, config] of configs) {
try {
let configFile = fs.concat(fs.file(plugin.description.loadMetadata.file).parent, plugin.description.name, config.name + '.' + config.format)
let configFactory = getConfigLoader(config.format)
if (!fs.exists(configFile)) {
base.save(configFile, configFactory.dump(plugin[config.variable]))
console.i18n("ms.plugin.manager.config.save.default", { plugin: plugin.description.name, name: config.name, format: config.format })
} else {
plugin[config.variable] = configFactory.load(base.read(configFile))
plugin[config.variable].save = () => {
let result = configFactory.dump(plugin[config.variable])
base.save(configFile, result)
console.debug(`[${plugin.description.name}] Save Config ${config.variable} to file ${configFile} result ${result}`)
}
console.debug(`[${plugin.description.name}] Load Config ${config.variable} from file ${configFile} result ${JSON.stringify(plugin[config.variable])}`)
}
} catch (error) {
console.i18n("ms.plugin.manager.config.load.error", { plugin: plugin.description.name, name: config.name, format: config.format, error })
console.ex(error)
}
}
}
function saveConfig(plugin: plugin.Plugin) {
let configs = getPluginConfigMetadata(plugin)
for (let [_, config] of configs) {
try {
let configFile = fs.concat(fs.file(plugin.description.loadMetadata.file).parent, plugin.description.name, config.name + '.' + config.format)
let configFactory = getConfigLoader(config.format)
if (!config.readonly) { base.save(configFile, configFactory.dump(plugin[config.variable])) }
} catch (error) {
console.i18n("ms.plugin.manager.config.save.error", { plugin: plugin.description.name, name: config.name, format: config.format, error })
console.ex(error)
}
}
}
function init() {
@@ -35,6 +77,8 @@ function init() {
let yaml = new YamlPluginConfig()
configLoaderMap.set("yml", yaml)
configLoaderMap.set("yaml", yaml)
process.on('plugin.before.load', loadConfig)
process.on('plugin.after.load', saveConfig)
}
init()

View File

@@ -8,10 +8,10 @@ import { getPluginMetadatas, getPluginCommandMetadata, getPluginListenerMetadata
* MiaoScript plugin
* @param metadata PluginMetadata
*/
export function plugin(metadata: pluginApi.PluginMetadata) {
export function plugin(metadata: pluginApi.PluginMetadata | any) {
return function (target: any) {
metadata.target = target
metadata.type = "ioc"
if (!metadata.source) metadata = { souece: metadata }
metadata = { name: target.name, version: '1.0.0', author: 'Unknow', target, type: 'ioc', ...metadata }
decorate(injectable(), target)
Reflect.defineMetadata(METADATA_KEY.plugin, metadata, target)
const previousMetadata: Map<string, pluginApi.PluginMetadata> = getPluginMetadatas()
@@ -79,23 +79,16 @@ export function config(metadata: interfaces.ConfigMetadata = {}) {
}
}
function stage(metadata: interfaces.ExecMetadata = {}, stage: string) {
return function (target: any, key: string, value: any) {
metadata.name = metadata.name || key
metadata.executor = key
const previousMetadata: interfaces.ExecMetadata[] = getPluginStageMetadata(target, stage)
Reflect.defineMetadata(METADATA_KEY.stage[stage], [metadata, ...previousMetadata], target.constructor)
function stage(stage: string) {
return (metadata: interfaces.ExecMetadata = {}) => {
return function (target: any, key: string, value: any) {
metadata.name = metadata.name || key
metadata.executor = key
const previousMetadata: interfaces.ExecMetadata[] = getPluginStageMetadata(target, stage)
Reflect.defineMetadata(METADATA_KEY.stage[stage], [metadata, ...previousMetadata], target.constructor)
}
}
}
export function load(metadata: interfaces.ExecMetadata = {}) {
return stage(metadata, 'load')
}
export function enable(metadata: interfaces.ExecMetadata = {}) {
return stage(metadata, 'enable')
}
export function disable(metadata: interfaces.ExecMetadata = {}) {
return stage(metadata, 'disable')
}
export const load = stage('load')
export const enable = stage('enable')
export const disable = stage('disable')

View File

@@ -0,0 +1,30 @@
import { event, plugin, server } from '@ccms/api'
import { provideSingleton, postConstruct, inject } from '@ccms/container'
import { getPluginListenerMetadata } from './utils'
@provideSingleton(PluginEventManager)
export class PluginEventManager {
@inject(server.ServerChecker)
private ServerChecker: server.ServerChecker
@inject(event.Event)
private EventManager: event.Event
constructor() {
process.on('plugin.before.enable', (plugin: plugin.Plugin) => this.registryListener(plugin))
process.on('plugin.after.disable', (plugin: plugin.Plugin) => this.unregistryListener(plugin))
}
private registryListener(pluginInstance: plugin.Plugin) {
let events = getPluginListenerMetadata(pluginInstance)
for (const event of events) {
// ignore space listener
if (!this.ServerChecker.check(event.servers)) { continue }
// here must bind this to pluginInstance
this.EventManager.listen(pluginInstance, event.name, pluginInstance[event.executor].bind(pluginInstance), event.priority, event.ignoreCancel)
}
}
private unregistryListener(pluginInstance: plugin.Plugin) {
this.EventManager.disable(pluginInstance)
}
}

View File

@@ -11,8 +11,8 @@ export class BasicLoader implements plugin.PluginLoader {
this.pluginRequireMap = new Map()
}
require(loadMetadata: plugin.PluginLoadMetadata) {
let metadata = loadMetadata.instance.description
if (metadata && metadata.type == this.type) {
let metadata = loadMetadata.instance?.description
if (metadata?.type == this.type) {
loadMetadata.metadata = metadata
loadMetadata.loaded = true
this.pluginRequireMap.set(metadata.source.toString(), loadMetadata.instance)

View File

@@ -1,11 +1,11 @@
import i18n from '@ccms/i18n'
import { plugin, server, command, event } from '@ccms/api'
import { plugin, server, event } from '@ccms/api'
import { inject, provideSingleton, Container, ContainerInstance } from '@ccms/container'
import * as fs from '@ccms/common/dist/fs'
import './config'
import { interfaces } from './interfaces'
import { getConfigLoader } from './config'
import { getPluginCommandMetadata, getPluginListenerMetadata, getPluginTabCompleterMetadata, getPluginConfigMetadata } from './utils'
import { PluginCommandManager } from './command'
import { PluginEventManager } from './event'
const Thread = Java.type('java.lang.Thread')
@@ -15,15 +15,16 @@ export class PluginManagerImpl implements plugin.PluginManager {
private container: Container
@inject(plugin.PluginInstance)
private pluginInstance: any
@inject(plugin.PluginFolder)
private pluginFolder: string
@inject(server.ServerType)
private serverType: string
@inject(command.Command)
private CommandManager: command.Command
@inject(event.Event)
private EventManager: event.Event
@inject(PluginCommandManager)
private commandManager: PluginCommandManager
@inject(PluginEventManager)
private eventManager: PluginEventManager
private initialized: boolean = false
private sacnnerMap: Map<string, plugin.PluginScanner>
@@ -38,6 +39,10 @@ export class PluginManagerImpl implements plugin.PluginManager {
this.instanceMap = new Map()
this.metadataMap = new Map()
// ignore unused
this.commandManager
this.eventManager
}
initialize() {
@@ -56,6 +61,7 @@ export class PluginManagerImpl implements plugin.PluginManager {
this.loaderMap.set(loader.type, loader)
})
this.initialized = true
process.emit('plugin.initialize')
}
}
@@ -77,39 +83,39 @@ export class PluginManagerImpl implements plugin.PluginManager {
console.ex(error)
}
}
process.emit('plugin.scan', folder)
}
build(): void {
this.buildPlugins()
process.emit('plugin.build')
}
private logStage(plugin: plugin.Plugin, stage: string) {
console.i18n("ms.plugin.manager.stage", { stage, plugin: plugin.description.name, version: plugin.description.version, author: plugin.description.author })
}
private runPluginStage(plugin: plugin.Plugin, stage: string, ext: Function) {
private runPluginStage(plugin: plugin.Plugin, stage: string) {
if (!plugin) { throw new Error(`can't run runPluginStage ${stage} because plugin is ${plugin}`) }
try {
this.logStage(plugin, i18n.translate(`ms.plugin.manager.stage.${stage}`))
ext()
process.emit(`plugin.before.${stage}`, plugin)
this.runCatch(plugin, stage)
this.runCatch(plugin, `${this.serverType}${stage}`)
plugin.description.loadMetadata.loader[stage](plugin)
process.emit(`plugin.after.${stage}`, plugin)
} catch (ex) {
console.i18n("ms.plugin.manager.stage.exec.error", { plugin: plugin.description.name, executor: stage, error: ex })
if (global.debug) { console.ex(ex) }
}
}
private loadPlugin(loadMetadata: plugin.PluginLoadMetadata) {
if (!loadMetadata) { throw new Error('loadMetadata can\'t be undefiend when loadPlugin!') }
if (loadMetadata.loaded) { throw new Error(`Plugin ${loadMetadata.name} is already loaded by ${loadMetadata.loader?.type}!`) }
try {
for (const [, loader] of this.loaderMap) {
if (loader.require(loadMetadata).loaded) {
loadMetadata.loader = loader
let metadata = loadMetadata.metadata
this.metadataMap.set(metadata.name, metadata)
metadata.loadMetadata = loadMetadata
return metadata
}
if (this.loaderRequirePlugin(loadMetadata, loader)?.loaded) return loadMetadata.metadata
}
} catch (error) {
console.i18n("ms.plugin.manager.initialize.error", { name: loadMetadata.file, ex: error })
@@ -118,51 +124,63 @@ export class PluginManagerImpl implements plugin.PluginManager {
console.console(`§6scanner: §b${loadMetadata.scanner.type} §ccan\'t load §6file §b${loadMetadata.file}. §eskip!`)
}
private loaderRequirePlugin(loadMetadata: plugin.PluginLoadMetadata, loader: plugin.PluginLoader) {
try {
if (loader.require(loadMetadata).loaded) {
loadMetadata.loader = loader
let metadata = loadMetadata.metadata
if (this.metadataMap.has(metadata.name)) {
let oldMetadata = this.metadataMap.get(metadata.name)
throw new Error(`Plugin ${oldMetadata.name} is already load from ${oldMetadata.source}...`)
}
this.metadataMap.set(metadata.name, metadata)
metadata.loadMetadata = loadMetadata
}
return loadMetadata
} catch (error) {
if (global.debug) {
console.console(`§6Loader §b${loader.type} §6load §a${loadMetadata.file} §cerror. §4Err: §c${error}`)
console.ex(error)
} else {
console.warn(`Loader ${loader.type} load ${loadMetadata.file} error. Err: ${error}`)
}
}
}
/**
* 从文件加载插件
* @param file java.io.File
*/
loadFromFile(file: string, scanner = this.sacnnerMap.get('file')): plugin.Plugin {
if (!file) { throw new Error('plugin file can\'t be null!') }
if (!scanner) { throw new Error('plugin scanner can\'t be null!') }
let metadata = this.loadPlugin(scanner.read(file))
let plugin = metadata.loadMetadata.loader.build(metadata)
if (!file) { throw new Error('plugin file can\'t be undefiend!') }
if (!scanner) { throw new Error('plugin scanner can\'t be undefiend!') }
let metadata = this.loadPlugin(scanner.load(scanner.read(file)))
let plugin = this.buildPlugin(metadata)
this.load(plugin)
this.enable(plugin)
return plugin
}
load(...args: any[]): void {
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => {
this.runPluginStage(plugin, 'load', () => {
this.loadConfig(plugin)
})
})
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => this.runPluginStage(plugin, 'load'))
}
enable(...args: any[]): void {
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => {
this.runPluginStage(plugin, 'enable', () => {
this.registryCommand(plugin)
this.registryListener(plugin)
})
})
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => this.runPluginStage(plugin, 'enable'))
}
disable(...args: any[]): void {
this.checkAndGet(args[0]).forEach((plugin: plugin.Plugin) => {
this.runPluginStage(plugin, 'disable', () => {
this.saveConfig(plugin)
this.unregistryCommand(plugin)
this.unregistryListener(plugin)
})
this.runPluginStage(plugin, 'disable')
this.metadataMap.delete(plugin.description.name)
this.instanceMap.delete(plugin.description.name)
})
}
reload(...args: any[]): void {
this.checkAndGet(args[0]).forEach((pl: plugin.Plugin) => {
this.disable(pl)
this.loadFromFile(pl.description.source, pl.description.loadMetadata.scanner)
this.loadFromFile(pl.description.loadMetadata.file, pl.description.loadMetadata.scanner)
})
}
@@ -184,109 +202,30 @@ export class PluginManagerImpl implements plugin.PluginManager {
}
private checkAndGet(name: string | plugin.Plugin | undefined | any): Map<string, plugin.Plugin> | plugin.Plugin[] {
if (name === undefined) throw new Error(`checkAndGet Plugin can't be undefiend!`)
if (name == this.instanceMap) { return this.instanceMap }
if (typeof name == 'string' && this.instanceMap.has(name)) { return [this.instanceMap.get(name)] }
if (name instanceof interfaces.Plugin) { return [name as plugin.Plugin] }
if (name.description || name.description.name) { return [name as plugin.Plugin] }
if (name.description?.name) { return [name as plugin.Plugin] }
throw new Error(`Plugin ${JSON.stringify(name)} not exist!`)
}
private allowProcess(servers: string[]) {
// Not set servers -> allow
if (!servers || !servers.length) return true
// include !type -> deny
let denyServers = servers.filter(svr => svr.startsWith("!"))
if (denyServers.length !== 0) {
return !denyServers.includes(`!${this.serverType}`)
} else {
// only include -> allow
return servers.includes(this.serverType)
}
}
private buildPlugins() {
for (const [, metadata] of this.metadataMap) {
let pluginInstance: plugin.Plugin
if (!this.loaderMap.has(metadata.type)) {
console.error(`§4无法加载插件 §c${metadata.name} §4请检查 §c${metadata.type} §4加载器是否正常启用!`)
continue
}
pluginInstance = this.loaderMap.get(metadata.type).build(metadata)
if (!pluginInstance) { console.error(`§4加载器 §c${metadata.type} §4加载插件 §c${metadata.name} §4失败!`); continue }
this.instanceMap.set(metadata.name, pluginInstance)
}
}
private loadConfig(plugin: plugin.Plugin) {
let configs = getPluginConfigMetadata(plugin)
for (let [_, config] of configs) {
this.metadataMap.forEach((metadata) => {
try {
let configFile = fs.concat(root, this.pluginFolder, plugin.description.name, config.name + '.' + config.format)
let configFactory = getConfigLoader(config.format)
if (!fs.exists(configFile)) {
base.save(configFile, configFactory.dump(plugin[config.variable]))
console.i18n("ms.plugin.manager.config.save.default", { plugin: plugin.description.name, name: config.name, format: config.format })
} else {
plugin[config.variable] = configFactory.load(base.read(configFile))
plugin[config.variable].save = () => {
let result = configFactory.dump(plugin[config.variable])
base.save(configFile, result)
console.debug(`[${plugin.description.name}] Save Config ${config.variable} to file ${configFile} result ${result}`)
}
console.debug(`[${plugin.description.name}] Load Config ${config.variable} from file ${configFile} result ${JSON.stringify(plugin[config.variable])}`)
}
this.buildPlugin(metadata)
} catch (error) {
console.i18n("ms.plugin.manager.config.load.error", { plugin: plugin.description.name, name: config.name, format: config.format, error })
console.ex(error)
console.error(error)
}
}
}
private saveConfig(plugin: plugin.Plugin) {
let configs = getPluginConfigMetadata(plugin)
for (let [_, config] of configs) {
try {
let configFile = fs.concat(root, this.pluginFolder, plugin.description.name, config.name + '.' + config.format)
let configFactory = getConfigLoader(config.format)
if (!config.readonly) { base.save(configFile, configFactory.dump(plugin[config.variable])) }
} catch (error) {
console.i18n("ms.plugin.manager.config.save.error", { plugin: plugin.description.name, name: config.name, format: config.format, error })
console.ex(error)
}
}
}
private registryCommand(pluginInstance: plugin.Plugin) {
let cmds = getPluginCommandMetadata(pluginInstance)
let tabs = getPluginTabCompleterMetadata(pluginInstance)
for (const [_, cmd] of cmds) {
let tab = tabs.get(cmd.name)
if (!this.allowProcess(cmd.servers)) { continue }
this.CommandManager.on(pluginInstance, cmd.name, {
cmd: pluginInstance[cmd.executor].bind(pluginInstance),
tab: tab ? pluginInstance[tab.executor].bind(pluginInstance) : undefined
})
}
}
private registryListener(pluginInstance: plugin.Plugin) {
let events = getPluginListenerMetadata(pluginInstance)
for (const event of events) {
// ignore space listener
if (!this.allowProcess(event.servers)) { continue }
// here must bind this to pluginInstance
this.EventManager.listen(pluginInstance, event.name, pluginInstance[event.executor].bind(pluginInstance))
}
}
private unregistryCommand(pluginInstance: plugin.Plugin) {
let cmds = getPluginCommandMetadata(pluginInstance)
cmds.forEach(cmd => {
this.CommandManager.off(pluginInstance, cmd.name)
})
}
private unregistryListener(pluginInstance: plugin.Plugin) {
this.EventManager.disable(pluginInstance)
private buildPlugin(metadata: plugin.PluginMetadata) {
if (!this.loaderMap.has(metadata.type)) { throw new Error(`§4无法加载插件 §c${metadata.name} §4请检查 §c${metadata.type} §4加载器是否正常启用!`) }
let pluginInstance = this.loaderMap.get(metadata.type).build(metadata)
if (!pluginInstance) { throw new Error(`§4加载器 §c${metadata.type} §4加载插件 §c${metadata.name} §4失败!`) }
if (this.instanceMap.has(metadata.name)) { throw new Error(`Plugin ${metadata.name} is already load from ${metadata.source}...`) }
this.instanceMap.set(metadata.name, pluginInstance)
return pluginInstance
}
}

View File

@@ -44,7 +44,7 @@ export class JSFileScanner implements plugin.PluginScanner {
}
private updatePlugin(file: any) {
var update = fs.file(fs.file(file.parentFile, 'update'), file.name)
var update = fs.file(fs.file(fs.file(file).parentFile, 'update'), file.name)
if (update.exists()) {
console.i18n("ms.plugin.manager.build.update", { name: file.name })
fs.move(update, file, true)

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "@ccms/plugins",
"version": "0.8.1",
"version": "0.9.0",
"description": "MiaoScript plugins package",
"keywords": [
"miaoscript",
@@ -20,13 +20,16 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"devDependencies": {
"@javatypes/spring-data-redis": "^0.0.2",
"@javatypes/spring-web": "^0.0.2",
"@javatypes/tomcat": "^0.0.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
},
"dependencies": {
"@ccms/api": "^0.8.1",
"@ccms/container": "^0.8.0",
"@ccms/plugin": "^0.8.1"
"@ccms/api": "^0.9.0",
"@ccms/container": "^0.9.0",
"@ccms/plugin": "^0.9.0"
}
}

View File

@@ -26,7 +26,7 @@ let langMap = {
'main.command.not.exists': '§4未知的子命令: §c{command}',
'main.command.help.tip': '§6请执行 §b/{command} §ahelp §6查看帮助!',
'list.install.header': '§6当前 §bMiaoScript §6已安装下列插件:',
'list.install.body': '§6插件名称: §b{name} §6版本: §a{version} §6作者: §3{author}',
'list.install.body': '§6插件名称: §b{name} §6版本: §a{version} §6作者: §3{author} §6来源: §c{from}',
'list.header': '§6当前 §bMiaoScriptPackageCenter §6中存在下列插件:',
'list.body': '§6插件名称: §b{name} §6版本: §a{version} §6作者: §3{author} §6更新时间: §9{updated_at}',
'plugin.not.exists': '§6插件 §b{name} §c不存在!',
@@ -51,7 +51,7 @@ let langMap = {
let fallbackMap = langMap
@plugin({ name: 'MiaoScriptPackageManager', prefix: 'PM', version: '1.0.1', author: 'MiaoWoo', source: __filename })
@plugin({ name: 'MiaoScriptPackageManager', prefix: 'PM', version: '1.0.2', author: 'MiaoWoo', source: __filename })
export class MiaoScriptPackageManager extends interfaces.Plugin {
@inject(pluginApi.PluginManager)
private pluginManager: pluginApi.PluginManager
@@ -114,7 +114,7 @@ export class MiaoScriptPackageManager extends interfaces.Plugin {
if (type == "i" || type == "install") {
this.i18n(sender, 'list.install.header')
this.pluginManager.getPlugins().forEach((plugin) => {
this.i18n(sender, 'list.install.body', plugin.description)
this.i18n(sender, 'list.install.body', { ...plugin.description, from: plugin.description.loadMetadata.type })
})
} else {
this.i18n(sender, 'list.header')
@@ -137,14 +137,15 @@ export class MiaoScriptPackageManager extends interfaces.Plugin {
}
}
cmdupgrade(sender: any, name: string) {
if (!name) { return this.i18n(sender, 'upgrade.confirm') }
if (name == "comfirm") {
let enginePath = fs.path(fs.file(fs.concat(root, 'node_modules', '@ccms')))
cmdupgrade(sender: any, name: string, confirm: boolean) {
if (name == "system") {
if (!confirm) { return this.i18n(sender, 'upgrade.confirm') }
let enginePath = fs.path(fs.file(root, 'node_modules'))
if (enginePath.startsWith(root)) {
base.delete(enginePath)
this.cmdrestart(sender)
}
return
}
if (this.checkPlugin(sender, name)) {
this.update(sender, name)
@@ -241,7 +242,7 @@ return '§a返回结果: §r'+ eval(${JSON.stringify(code)});`)
name,
author: plugin.description.author,
version: plugin.description.version,
source: base.read(plugin.description.source.toString())
source: base.read((plugin.description.source || plugin.description.loadMetadata.file).toString())
})
this.i18n(sender, result.code == 200 ? 'deploy.success' : 'deploy.fail', { name, msg: result.msg })
}
@@ -263,8 +264,11 @@ return '§a返回结果: §r'+ eval(${JSON.stringify(code)});`)
return ["install", "cloud"]
case "install":
return this.packageNameCache
case "update":
case "upgrade":
if (args.length == 2) return ["system", ...this.pluginManager.getPlugins().keys()]
if (args.length == 3 && args[1] == "system") return ["confirm"]
return []
case "update":
case "load":
case "unload":
case "reload":
@@ -291,7 +295,7 @@ return '§a返回结果: §r'+ eval(${JSON.stringify(code)});`)
http.download(this.packageCache[name].url, pluginFile)
this.i18n(sender, 'download.finish', { name })
if (!update) {
this.pluginManager.loadFromFile(fs.file(pluginFile))
this.pluginManager.loadFromFile(fs.concat(root, this.pluginFolder, name + '.js'))
this.i18n(sender, 'install.finish', { name })
} else {
this.i18n(sender, 'update.finish', { name })

View File

@@ -2,15 +2,50 @@
/// <reference types="@javatypes/spring-web" />
import { constants, database, plugin, web } from "@ccms/api"
import { inject, ContainerInstance, Container, JSClass } from "@ccms/container"
import { inject, ContainerInstance, Container, JSClass, postConstruct } from "@ccms/container"
import { JSPlugin, interfaces, cmd } from "@ccms/plugin"
import { DataBase, DataBaseManager } from '@ccms/database'
import { Server, Context, RequestHandler } from '@ccms/web'
import { Server, Context, RequestHandler, Controllers, Controller, Get, Post, Param, Body } from '@ccms/web'
import * as fs from '@ccms/common/dist/fs'
import * as reflect from '@ccms/common/dist/reflect'
@JSPlugin({ name: 'MiaoSpring', prefix: 'MSpring', version: '1.0.1', author: 'MiaoWoo', servers: [constants.ServerType.Spring], source: __filename })
@Controller()
class PluginController {
@inject(plugin.PluginManager)
private pluginManager: plugin.PluginManager
@inject(database.DataBaseManager)
private databaseManager: DataBaseManager
private mainDB: DataBase
@postConstruct()
initialize() {
this.mainDB = this.databaseManager.getMainDatabase()
}
@Get()
list(@Param('install') install: boolean) {
if (install) {
return { status: 200, data: [...this.pluginManager.getPlugins().values()].map((plugin) => plugin.description), msg: '插件列表获取成功!' }
} else {
return { status: 200, data: this.mainDB.query<Plugin>("SELECT name FROM plugins WHERE deleted = 0") }
}
}
@Post()
deploy(@Body() info: Plugin) {
let plugin = this.mainDB.query<Plugin>("SELECT name FROM plugins WHERE name = ?", info.name)
if (plugin.length == 0) {
this.mainDB.update("INSERT INTO `plugins`(`name`, `source`) VALUES (?, ?)", info.name, info.source)
return { status: 200, msg: `插件 ${info.name} 新增成功!` }
} else {
this.mainDB.update("UPDATE `plugins` SET `source` = ? WHERE id = ?", info.source, plugin[0].id)
return { status: 200, msg: `插件 ${info.name} 更新成功!` }
}
}
}
@JSPlugin({ author: 'MiaoWoo', servers: [constants.ServerType.Spring], source: __filename })
export class MiaoSpring extends interfaces.Plugin {
@inject(ContainerInstance)
private container: Container
@@ -40,6 +75,7 @@ export class MiaoSpring extends interfaces.Plugin {
this.mappings = new Set()
}
@Controllers(PluginController)
enable() {
this.registryDefault()
this.registryPages()
@@ -65,7 +101,7 @@ export class MiaoSpring extends interfaces.Plugin {
this.webServer.registryInterceptor({
name: 'StaticHandle',
preHandle: (ctx: Context) => {
let type = ctx.header['Accept'] || ''
let type = ctx.headers['Accept'] || ''
if (type == '*/*' || type.includes('text/css') || type.includes('text/html')) {
let filePath = fs.concat(root, 'wwwroot', (ctx.request.getRequestURI() == '/' ? 'index.html' : ctx.request.getRequestURI()))
let fileType = 'text/html;charset=utf-8'
@@ -73,33 +109,25 @@ export class MiaoSpring extends interfaces.Plugin {
if (fs.exists(filePath)) {
if (filePath.endsWith('.js')) { fileType = 'application/javascript' }
if (filePath.endsWith('.css')) { fileType = 'text/css' }
// @ts-ignore
return this.ResponseEntity.ok().header('Content-Type', fileType).body(base.read(filePath))
}
}
}
})
this.webServer.registryMapping('/api/eval', (ctx: Context) => {
this.registryMapping('/api/eval', (ctx: Context) => {
try {
return { status: 200, data: this.runCode(ctx.body + ''), msg: '代码执行成功!' }
} catch (error) {
return { status: 500, data: console.stack(error, false), msg: '代码执行异常!' }
}
})
this.webServer.registryMapping('/api/plugin/list', () => {
return { status: 200, data: [...this.pluginManager.getPlugins().values()].map((plugin) => plugin.description), msg: '插件列表获取成功!' }
})
this.webServer.registryMapping('/api/plugin/update', (ctx: Context) => {
if (!ctx.params.name) { return { status: 400, msg: '插件名称不得为空!' } }
})
}
private registryPages() {
this.webServer.registryMapping('/api/page/list', () => {
this.registryMapping('/api/page/list', () => {
return { status: 0, data: { rows: this.mainDatabase.query('SELECT `id`, `type`, `name`, `content` FROM `pages` WHERE `deleted` = 0') } }
})
this.webServer.registryMapping('/api/page/get', (ctx: Context) => {
this.registryMapping('/api/page/get', (ctx: Context) => {
let name = decodeURIComponent(`${ctx.params.name}`)
let varable = undefined
if (!name) { return { status: 400, msg: '名称不能为空!' } }
@@ -119,20 +147,20 @@ export class MiaoSpring extends interfaces.Plugin {
}
return { status: 0, data: JSON.parse(content) }
})
this.webServer.registryMapping('/api/page/add', (ctx: Context) => {
this.registryMapping('/api/page/add', (ctx: Context) => {
let body = ctx.body
if (typeof body.content !== "string") { body.content = JSON.stringify(body.content) }
this.mainDatabase.update("INSERT INTO `pages`(`type`, `name`, `content`) VALUES (?, ?, ?)", body.type || 1, body.name, body.content)
return { status: 0, msg: `${body.name} 新增成功!` }
})
this.webServer.registryMapping('/api/page/update', (ctx: Context) => {
this.registryMapping('/api/page/update', (ctx: Context) => {
if (!ctx.params.id) { return { status: 400, msg: 'ID 不能为空!' } }
const body = ctx.body
if (typeof body.content !== "string") { body.content = JSON.stringify(body.content) }
this.mainDatabase.update("UPDATE `pages` SET `name` = ?, `content` = ? WHERE id = ?", body.name, body.content, ctx.params.id)
return { status: 0, msg: `${body.name} 更新成功!` }
})
this.webServer.registryMapping('/api/page/delete', (ctx: Context) => {
this.registryMapping('/api/page/delete', (ctx: Context) => {
if (!ctx.params.name) { return { status: 400, msg: '页面 名称 不能为空!' } }
this.mainDatabase.update("UPDATE `pages` SET `name` = CONCAT(name, '_deleted'), deleted = 1 WHERE name = ?", ctx.params.name)
return { status: 0, msg: `${ctx.params.name} 删除成功!` }
@@ -142,17 +170,17 @@ export class MiaoSpring extends interfaces.Plugin {
private configTable = "config"
private registryDatabase() {
this.webServer.registryMapping('/api/config/list', (ctx: Context) => {
this.registryMapping('/api/config/list', (ctx: Context) => {
return { status: 0, data: this.mainDatabase.query('SELECT id, name, label, url, driver, username, password FROM `' + this.configTable + '` WHERE deleted = 0 AND type = ?', ctx.params.type || 0) }
})
this.webServer.registryMapping('/api/config/get', (ctx: Context) => {
this.registryMapping('/api/config/get', (ctx: Context) => {
let name = ctx.params.name
if (!name) { return { status: 400, msg: '名称不能为空!' } }
let result = this.mainDatabase.query('SELECT id, name, label, url, driver, username, password FROM `' + this.configTable + '` WHERE `name` = ?', name)
if (!result.length) { return { status: 404, msg: `配置 ${name} 不存在!` } }
return { status: 0, data: result[0] }
})
this.webServer.registryMapping('/api/config/add', (ctx: Context) => {
this.registryMapping('/api/config/add', (ctx: Context) => {
let body = ctx.body
if (!body.name) { return { status: 400, msg: '名称不能为空!' } }
this.mainDatabase.update(
@@ -161,7 +189,7 @@ export class MiaoSpring extends interfaces.Plugin {
)
return { status: 0, msg: `配置 ${body.name} 新增成功!` }
})
this.webServer.registryMapping('/api/config/update', (ctx: Context) => {
this.registryMapping('/api/config/update', (ctx: Context) => {
if (!ctx.params.id) { return { status: 400, msg: 'ID 不能为空!' } }
let body = ctx.body
this.mainDatabase.update(
@@ -181,6 +209,7 @@ export class MiaoSpring extends interfaces.Plugin {
'pluginManager'
]
let params = [
base.getInstance().getAutowireCapableBeanFactory(),
this.mainDatabase,
reflect,
this.container,
@@ -195,5 +224,13 @@ return eval(${JSON.stringify(code)});`)
disable() {
Object.keys(this.mappings).forEach((r) => this.webServer.unregistryMapping(r))
this.webServer.unregistryInterceptor({ name: 'RedirectHandle' })
this.webServer.unregistryInterceptor({ name: 'StaticHandle' })
}
}
class Plugin {
id: number
name: string
source: string
}

View File

@@ -0,0 +1,31 @@
import { constants } from "@ccms/api"
import { plugin, interfaces } from "@ccms/plugin"
import { Controllers, Controller, Get, Body, Post, Cookie, Header, Param } from "@ccms/web"
@Controller()
export class TestController {
@Get()
public get() {
return 'test'
}
@Post()
public post(@Body() body: any,
@Cookie('test') testCookie,
@Header('Accept') testHeader,
@Param("name") param) {
return {
body,
testCookie,
testHeader,
param
}
}
}
@plugin({ author: 'MiaoWoo', servers: [constants.ServerType.Spring], source: __filename })
export class MiaoWeb extends interfaces.Plugin {
@Controllers(TestController)
enable() {
}
}

View File

@@ -1,8 +1,16 @@
/// <reference types="@javatypes/spring-data-redis" />
import { constants, plugin as pluginApi, amqp, server, web } from '@ccms/api'
import { plugin, interfaces, cmd } from '@ccms/plugin'
import { AmqpAdmin, ConnectionFactoryAdapter, AmqpManager } from '@ccms/amqp'
import { inject, Autowired } from '@ccms/container'
import { Server } from '@ccms/web'
import { inject, Autowired, ContainerInstance, Container } from '@ccms/container'
import { Server, Controller, Get, Param, METADATA_KEY } from '@ccms/web'
const SearchRankingAmqpAdmin = Symbol('SearchRanking-AmqpAdmin')
const EXCHANGE_NAME = `search.ranking.${process.env.SPRING_CLOUD_CONFIG_PROFILE || 'dev'}`
const QUEUE_NAME = `search.ranking.${process.env.SPRING_CLOUD_CONFIG_PROFILE || 'dev'}`
const ROUTER_KEY = `search.ranking.${process.env.SPRING_CLOUD_CONFIG_PROFILE || 'dev'}`
@plugin({ name: SearchRanking.name, version: SearchRanking.version, author: SearchRanking.author, servers: SearchRanking.servers, source: __filename })
export class SearchRanking extends interfaces.Plugin {
@@ -10,6 +18,8 @@ export class SearchRanking extends interfaces.Plugin {
public static author = 'MiaoWoo'
public static servers = [constants.ServerType.Spring]
@inject(ContainerInstance)
private container: Container
@inject(pluginApi.PluginManager)
private pluginManager: pluginApi.PluginManager
@inject(amqp.Manager)
@@ -20,17 +30,11 @@ export class SearchRanking extends interfaces.Plugin {
private webServer: Server
@Autowired()
private mongoTemplate: any
@Autowired()
private redisTemplate: any
private redisTemplate: org.springframework.data.redis.core.RedisTemplate<string, any>
private amqpAdmin: AmqpAdmin
private listener: org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer
private readonly exchangeName = 'search.ranking'
private readonly queueName = 'search.ranking'
private readonly routerKey = 'search.ranking'
load() {
let connection = new ConnectionFactoryAdapter({
url: 'amqp://rabbitmq.c.sixi.com:5672',
@@ -42,33 +46,26 @@ export class SearchRanking extends interfaces.Plugin {
}
enable() {
this.webServer.registryMapping('/api/search', (ctx) => {
if (!ctx.params.keyword) { return { status: 400, msg: '查询关键词不得为空!' } }
let keyword = ctx.params.keyword + ''
let type = ctx.params.type == 'sale' ? 'sale' : 'normal'
let time = parseInt(ctx.params.time + '') || 60 * 60 * 24 * 15
return this.cacheAndSearch(keyword, type as any, time * 1000)
})
this.webServer.registryMapping('/api/search/ranking', (ctx) => {
if (!ctx.params.keyword || !ctx.params.shopName) { return { status: 400, msg: '查询关键词不得为空!' } }
let keyword = ctx.params.keyword + ''
let shopName = ctx.params.shopName + ''
let type = ctx.params.type == 'sale' ? 'sale' : 'normal'
let time = parseInt(ctx.params.time + '') || 60 * 60 * 24 * 15
return this.sendSearchRankingCmd(keyword, shopName, type as any, time * 1000)
})
this.amqpAdmin.declareQueueAndBindExchange(this.queueName, this.exchangeName, this.routerKey)
this.amqpAdmin.declareBinding(this.queueName, 'client.topic.exchange', `cmd_res.${this.routerKey}`)
this.listener = this.amqpAdmin.createContainer<string>(this.queueName, (content, message, channel) => {
this.amqpAdmin.declareQueueAndBindExchange(QUEUE_NAME, EXCHANGE_NAME, ROUTER_KEY)
this.amqpAdmin.declareBinding(QUEUE_NAME, 'client.topic.exchange', `cmd_res.${ROUTER_KEY}`)
this.listener = this.amqpAdmin.createContainer<string>(QUEUE_NAME, (content, _message, _channel) => {
let searchResult = JSON.parse(content)
if (searchResult.type == "company") {
this.logger.info(content)
return
}
this.redisTemplate.opsForValue().set(searchResult.reqData.cacheKey, searchResult)
this.logger.sender(this.Server.getConsoleSender(), `§6查询任务完成! §b关键词: §r${searchResult.reqData.keywords}`)
})
this.container.bind(SearchRankingAmqpAdmin).toConstantValue(this.amqpAdmin)
this.listener.start()
this.webServer.registryController(SearchRankingController)
}
disable() {
this.container.unbind(SearchRankingAmqpAdmin)
this.listener.stop()
this.webServer.unregistryController(SearchRankingController)
}
@cmd()
@@ -90,14 +87,55 @@ export class SearchRanking extends interfaces.Plugin {
this.pluginManager.reload(this)
}
cmdsend(sender: any, ...args: string[]) {
}
cmdsearch(sender: any, keyword: string, type: "sale" | "normal" = "sale", time: number) {
this.logger.sender(sender, this.cacheAndSearch(keyword, type, time)?.msg)
this.logger.sender(sender, this.container.getNamed<SearchRankingController>(METADATA_KEY.Controller, SearchRankingController.name).cacheAndSearch(keyword, type, time)?.msg)
}
}
@Controller({ path: '/api', name: SearchRankingController.name })
class SearchRankingController {
@Autowired()
private redisTemplate: org.springframework.data.redis.core.RedisTemplate<string, any>
@inject(SearchRankingAmqpAdmin)
private amqpAdmin: AmqpAdmin
@Get()
public search(
@Param({ name: 'keyword', require: true, message: '查询关键词不得为空!' }) keyword: string,
@Param("type") type: string = 'normal',
@Param('time') time: number = 60 * 60 * 24 * 15
) {
return this.cacheAndSearch(keyword, type as any, time * 1000)
}
private cacheAndSearch(keyword: string, type: string = "sale", time: number = 15 * 24 * 60 * 60 * 1000) {
@Get("/search/ranking")
public searchRanking(
@Param({ name: 'keyword', require: true, message: '查询关键词不得为空!' }) keyword: string,
@Param("shopName") shopName: string = 'normal',
@Param("type") type: string = 'normal',
@Param('time') time: number = 60 * 60 * 24 * 15
) {
return this.sendSearchRankingCmd(keyword, shopName, type as any, time * 1000)
}
@Get('/search/company')
public searchCompany(
@Param('url') url: string
) {
this.amqpAdmin.getTemplate().convertAndSend('client.topic.exchange', 'cmd_req', {
cmd: 'scout._1688CompanyInfo',
data: {
type: 'company',
url: url,
cacheKey: 'SearchRanking:company'
},
resRouteSuffix: ROUTER_KEY,
target: {}
})
}
public cacheAndSearch(keyword: string, type: string = "sale", time: number = 15 * 24 * 60 * 60 * 1000) {
let cacheKey = this.getCacheKey(keyword, type)
if (this.redisTemplate.hasKey(cacheKey)) {
let lastSearchTime = this.redisTemplate.opsForValue().get(cacheKey)
@@ -141,9 +179,10 @@ export class SearchRanking extends interfaces.Plugin {
sortType: 'va_rmdarkgmv30rt',
button_click: 'top'
} : {},
pageTotal: 3,
cacheKey: this.getResultCacheKey(keywords, type, dateCache)
},
resRouteSuffix: this.routerKey,
resRouteSuffix: ROUTER_KEY,
target: {}
})
}
@@ -159,11 +198,10 @@ export class SearchRanking extends interfaces.Plugin {
cacheKey: this.getResultCacheKey(keywords, type, cacheTime),
cacheTime
},
resRouteSuffix: this.routerKey,
resRouteSuffix: ROUTER_KEY,
target: {}
})
}
/**
* 获得关键词+类型的上次查询时间
* @param keywords 关键词
@@ -175,4 +213,4 @@ export class SearchRanking extends interfaces.Plugin {
private getResultCacheKey(keywords: string, type: string, date: number) {
return `SearchRanking:${keywords}:${type}:${date}`
}
}
}

View File

@@ -5,7 +5,9 @@
"outDir": "dist",
"skipLibCheck": true,
"sourceMap": false,
"inlineSourceMap": true
"inlineSourceMap": true,
"declaration": false,
"declarationMap": false
},
"exclude": [
"dist",

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/sponge",
"version": "0.8.1",
"version": "0.9.0",
"description": "MiaoScript api package",
"keywords": [
"miaoscript",
@@ -19,13 +19,14 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"devDependencies": {
"@javatypes/sponge-api": "^0.0.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
},
"dependencies": {
"@ccms/api": "^0.8.1",
"@ccms/common": "^0.8.0",
"@ccms/container": "^0.8.0"
"@ccms/api": "^0.9.0",
"@ccms/common": "^0.9.0",
"@ccms/container": "^0.9.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/spring",
"version": "0.8.1",
"version": "0.9.0",
"description": "MiaoScript spring package",
"keywords": [
"miaoscript",
@@ -21,12 +21,12 @@
"devDependencies": {
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
},
"dependencies": {
"@ccms/api": "^0.8.1",
"@ccms/common": "^0.8.0",
"@ccms/container": "^0.8.0",
"@ccms/database": "^0.8.1"
"@ccms/api": "^0.9.0",
"@ccms/common": "^0.9.0",
"@ccms/container": "^0.9.0",
"@ccms/database": "^0.9.0"
}
}

View File

@@ -7,10 +7,8 @@ import { CommandMap } from './internal/command'
@provideSingleton(command.Command)
export class SpringCommand extends command.Command {
@inject(plugin.PluginInstance)
private pluginInstance: any
@inject(CommandMap)
private commandMap: CommandMap = new CommandMap()
private commandMap: CommandMap
protected create(plugin: any, command: string) {
return this.commandMap.register(plugin, command)
@@ -22,6 +20,6 @@ export class SpringCommand extends command.Command {
command.setExecutor(super.setExecutor(plugin, command, executor))
}
protected onTabComplete(plugin: any, command: any, tabCompleter: Function) {
command.setTabCompleter(super.setExecutor(plugin, command, tabCompleter))
command.setTabCompleter(super.setTabCompleter(plugin, command, tabCompleter))
}
}

View File

@@ -47,7 +47,7 @@ export class CommandMap {
}
tabComplate(sender: any, input: string, index?: number): string[] {
if (index == 0) { return Object.keys(this.commands) }
if (index == 0) { return Object.keys(this.commands).concat('help') }
let [command, ...args] = input.split(' ')
let exists = this.commands[command]
if (exists && exists.tabCompleter) {

View File

@@ -14,6 +14,7 @@ export class MySQLScanner implements plugin.PluginScanner {
type: string = "mysql"
private cacheDir = 'mysql-plugin-cache'
private cacheFileMap: Map<string, MySQLPlugin> = new Map()
private target: string
@inject(database.DataBaseManager)
@@ -25,7 +26,14 @@ export class MySQLScanner implements plugin.PluginScanner {
return plugins.map(p => this.read(p))
}
read(mysqlPlugin: MySQLPlugin): plugin.PluginLoadMetadata {
return { name: mysqlPlugin.name, file: fs.concat(root, this.cacheDir, `${mysqlPlugin.name}.js`), type: this.type, mysqlPlugin, scanner: this }
// if invoke this function from loadFromFile mysqlPlugin is a string need read from cache
if (typeof mysqlPlugin == "string") {
if (!this.cacheFileMap.has(mysqlPlugin)) { throw new Error(`this file ${mysqlPlugin} is not read from mysql-scanner. can't reload from this scanner!`) }
mysqlPlugin = this.cacheFileMap.get(mysqlPlugin)
}
let cacheFile = fs.concat(root, this.cacheDir, `${mysqlPlugin.name}.js`)
this.cacheFileMap.set(cacheFile, mysqlPlugin)
return { name: mysqlPlugin.name, file: cacheFile, type: this.type, mysqlPlugin, scanner: this }
}
load(metadata: plugin.PluginLoadMetadata) {
if (metadata.type !== this.type) { return }

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/web",
"version": "0.8.1",
"version": "0.9.0",
"description": "MiaoScript web package",
"keywords": [
"miaoscript",
@@ -19,12 +19,17 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"devDependencies": {
"@javatypes/jdk": "^0.0.2",
"@javatypes/spring-beans": "^0.0.2",
"@javatypes/spring-core": "^0.0.2",
"@javatypes/spring-web": "^0.0.2",
"@javatypes/tomcat": "^0.0.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
"typescript": "^3.9.6"
},
"dependencies": {
"@ccms/api": "^0.8.1",
"@ccms/container": "^0.8.0"
"@ccms/api": "^0.9.0",
"@ccms/container": "^0.9.0"
}
}

View File

@@ -1,2 +1,16 @@
export const WebProxyBeanName = 'webServerProxy'
export const FilterProxyBeanName = 'webFilterProxy'
export const METADATA_KEY = {
Controller: Symbol("@ccms/web:Controller"),
Action: Symbol("@ccms/web:Action"),
Param: Symbol("@ccms/web:Param"),
Middleware: Symbol("@ccms/web:Middleware"),
}
export enum PARAM_TYPE {
QUERY = 'QUERY',
BODY = 'BODY',
HEADER = 'HEADER',
COOKIE = 'COOKIE',
REQUEST = 'REQUEST',
RESPONSE = 'RESPONSE',
}

View File

@@ -1,30 +1,86 @@
export const Controller = () => {
return <TFunction extends Function>(target: TFunction): ClassDecorator => {
return
import { plugin } from '@ccms/api'
import { decorate, injectable, getContainer } from "@ccms/container"
import { METADATA_KEY, PARAM_TYPE } from '../constants'
import { interfaces } from "../interfaces"
import { addControllerMetadata, addControllerAction, addActionParam, getControllerMetadata } from "./utils"
export const Controllers = (...controllers: any[]) => {
return (target: any, propertyKey: string) => {
for (const controller of controllers) {
addControllerMetadata(getControllerMetadata(controller), target)
}
}
}
export const Post = () => {
return <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): MethodDecorator => {
return
export const Controller = (metadata?: string | interfaces.ControllerMetadata) => {
return (target: any) => {
if (!metadata) { metadata = target.name.toLowerCase().replace('controller', '') }
if (typeof metadata === "string") { metadata = { path: metadata } }
metadata.target = target
metadata.name = metadata.name || target.name
metadata.path = metadata.path ?? `/${metadata}`
metadata.path = metadata.path.startsWith('/') ? metadata.path : `/${metadata.path}`
decorate(injectable(), target)
Reflect.defineMetadata(METADATA_KEY.Controller, metadata, target)
addControllerMetadata(metadata)
}
}
export const Get = () => {
return <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): MethodDecorator => {
return
function action(method: interfaces.Method) {
return (metadata?: string | interfaces.ActionMetadata) => {
return (target: any, propertyKey: string) => {
if (!metadata) { metadata = propertyKey.toLowerCase() }
if (typeof metadata === "string") { metadata = { path: metadata } }
metadata.path = metadata.path ?? `/${propertyKey}`
metadata.path = metadata.path.startsWith('/') ? metadata.path : `/${metadata.path}`
metadata.method = method
metadata.executor = propertyKey
Reflect.defineMetadata(METADATA_KEY.Action, metadata, target[propertyKey])
addControllerAction(target, propertyKey)
}
}
}
export const Header = () => {
return <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): MethodDecorator => {
return
export const Action = action('ALL')
export const Get = action('GET')
export const Post = action('POST')
export const Put = action('PUT')
export const Patch = action('PATCH')
export const Head = action('HEAD')
export const Delete = action('DELETE')
function param(type: PARAM_TYPE) {
return (metadata?: string | interfaces.ParamMetadata) => {
return (target: any, propertyKey: string, index: number) => {
if (!metadata) { metadata = `${propertyKey}-${index}` }
if (typeof metadata === "string") { metadata = { name: metadata } }
metadata.type = type
metadata.index = index
metadata.paramtype = Reflect.getMetadata("design:paramtypes", target, propertyKey)[index]
addActionParam(target, propertyKey, metadata)
}
}
}
export const Param = () => {
return (target: Object, propertyKey: string | symbol, parameterIndex: number): ParameterDecorator => {
return
}
}
export const RequestBody = () => {
return (target: Object, propertyKey: string | symbol, parameterIndex: number): ParameterDecorator => {
return
export const Request = param(PARAM_TYPE.REQUEST)
export const Response = param(PARAM_TYPE.RESPONSE)
export const Header = param(PARAM_TYPE.HEADER)
export const Cookie = param(PARAM_TYPE.COOKIE)
export const Query = param(PARAM_TYPE.QUERY)
export const Param = param(PARAM_TYPE.QUERY)
export const Body = param(PARAM_TYPE.BODY)
function Middleware() {
return (metadata?: string | interfaces.ActionMetadata) => {
return (target: any, propertyKey: string) => {
if (!metadata) { metadata = propertyKey.toLowerCase() }
if (typeof metadata === "string") { metadata = { path: metadata } }
metadata.path = metadata.path ?? `/${propertyKey}`
metadata.path = metadata.path.startsWith('/') ? metadata.path : `/${metadata.path}`
metadata.executor = propertyKey
Reflect.defineMetadata(METADATA_KEY.Action, metadata, target[propertyKey])
addControllerAction(target, propertyKey)
}
}
}
export * from './utils'

View File

@@ -0,0 +1,29 @@
import { interfaces } from '../interfaces'
import { METADATA_KEY } from '../constants'
export function getControllerMetadatas(target: any = Reflect): Map<string, interfaces.ControllerMetadata> {
return Reflect.getMetadata(METADATA_KEY.Controller, target) || new Map<string, interfaces.ControllerMetadata>()
}
export function addControllerMetadata(metadata: interfaces.ControllerMetadata, target: any = Reflect) {
let metadatas = getControllerMetadatas(target)
metadatas.set(metadata.name, metadata)
Reflect.defineMetadata(METADATA_KEY.Controller, metadatas, target)
}
export function getControllerActions(target: any): string[] {
return Reflect.getMetadata(METADATA_KEY.Action, target.constructor) || []
}
export function addControllerAction(target: any, propertyKey: string) {
Reflect.defineMetadata(METADATA_KEY.Action, [propertyKey, ...getControllerActions(target)], target.constructor)
}
export function getControllerMetadata(target: any): interfaces.ControllerMetadata {
return Reflect.getMetadata(METADATA_KEY.Controller, target)
}
export function getActionMetadata(target: any, propertyKey: string): interfaces.ActionMetadata {
return Reflect.getMetadata(METADATA_KEY.Action, target[propertyKey])
}
export function getActionParams(target: any, propertyKey: string): interfaces.ParamMetadata[] {
return Reflect.getMetadata(METADATA_KEY.Param, target[propertyKey]) || []
}
export function addActionParam(target: any, propertyKey: string, metadata: interfaces.ParamMetadata) {
Reflect.defineMetadata(METADATA_KEY.Param, [metadata, ...getActionParams(target, propertyKey)], target[propertyKey])
}

View File

@@ -6,5 +6,6 @@
/// <reference types="@javatypes/spring-beans" />
export * from './server'
export * from './constants'
export * from './decorators'
export * from './interfaces'

View File

@@ -5,14 +5,20 @@ export interface InterceptorAdapter {
postHandle?(ctx: Context): void
}
export type RequestHeader = { [key: string]: string | string[] }
export type RequestParams = { [key: string]: string | string[] }
type StringKeyAndStringValue = { [key: string]: string }
type StringKeyAndStringOrArrayValue = { [key: string]: string | string[] }
export type RequestHeaders = StringKeyAndStringOrArrayValue
export type RequestParams = StringKeyAndStringOrArrayValue
export type RequestCookies = StringKeyAndStringValue
export interface Context {
request?: javax.servlet.http.HttpServletRequest
response?: javax.servlet.http.HttpServletResponse
header?: RequestHeader
handler?: RequestHandler
url?: string
headers?: RequestHeaders
cookies?: RequestCookies
params?: RequestParams
body?: any
result?: any

View File

@@ -1,14 +1,77 @@
export interface BaseMetadata {
/**
* 名称 为空则为对象名称
*/
name?: string
/**
* 支持的服务器列表 为空则代表所有
*/
servers?: string[]
}
import { PARAM_TYPE } from "../constants"
export interface ControllerMetadata extends BaseMetadata {
export namespace interfaces {
export interface BaseMetadata {
/**
* 名称 为空则为对象名称
*/
name?: string
/**
* 支持的服务器列表 为空则代表所有
*/
servers?: string[]
}
export interface WebMetadata extends BaseMetadata {
/**
* 路径
*/
path: string
/**
* 对象
*/
target?: any
}
export interface ControllerMetadata extends WebMetadata {
}
interface Newable<T> {
new(...args: any[]): T
}
interface Abstract<T> {
prototype: T
}
export type ServiceIdentifier<T> = (string | symbol | Newable<T> | Abstract<T>)
export interface MiddlewareMetadata extends BaseMetadata {
/**
* 中间件名称列表
*/
names: Array<ServiceIdentifier<any>>
}
export type Method = 'ALL' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'TRACE'
export interface ActionMetadata extends WebMetadata {
/**
* 请求方法
*/
method?: Method
/**
* 执行器
*/
executor?: string
}
export interface ParamMetadata extends BaseMetadata {
/**
* 参数类型
*/
type?: PARAM_TYPE
/**
* 默认值
*/
default?: any
/**
* 参数位置
*/
index?: number
/**
* 参数对象类型
*/
paramtype?: NewableFunction
/**
* 是否必传
*/
require?: boolean
/**
* 异常消息
*/
message?: string
}
}

View File

@@ -1,10 +1,16 @@
import * as querystring from 'querystring'
import { web } from '@ccms/api'
import { provideSingleton, JSClass, postConstruct } from '@ccms/container'
import { web, plugin } from '@ccms/api'
import { provideSingleton, JSClass, postConstruct, Container, ContainerInstance, inject } from '@ccms/container'
import { WebProxyBeanName, FilterProxyBeanName } from './constants'
import { Context, InterceptorAdapter, RequestHandler } from './interfaces'
import { WebProxyBeanName, FilterProxyBeanName, METADATA_KEY, PARAM_TYPE } from './constants'
import { Context, InterceptorAdapter, RequestHandler, interfaces } from './interfaces'
import { getControllerActions, getActionMetadata, getControllerMetadata, getActionParams, getControllerMetadatas } from './decorators'
const HttpServletRequestWrapper = Java.type('javax.servlet.http.HttpServletRequestWrapper')
const HttpServletResponseWrapper = Java.type('javax.servlet.http.HttpServletResponseWrapper')
const ServletInputStream = Java.type('javax.servlet.ServletInputStream')
const ServletOutputStream = Java.type('javax.servlet.ServletOutputStream')
@provideSingleton(web.Server)
export class Server {
@@ -13,20 +19,27 @@ export class Server {
@JSClass('pw.yumc.MiaoScript.web.WebFilterProxy')
private WebFilterProxy: any
@inject(ContainerInstance)
private container: Container
private StreamUtils = org.springframework.util.StreamUtils
private ResponseEntity = org.springframework.http.ResponseEntity
private controllerActions: Map<string, interfaces.ActionMetadata[]>
private interceptors: Map<string, InterceptorAdapter>
private handlerMapping: Map<string, RequestHandler>
private methodMappings: Map<string, Map<string, RequestHandler>>
private beanFactory: org.springframework.beans.factory.support.DefaultListableBeanFactory
@postConstruct()
initialization() {
this.beanFactory = base.getInstance().getAutowireCapableBeanFactory()
this.controllerActions = new Map()
this.interceptors = new Map()
this.handlerMapping = new Map()
this.methodMappings = new Map()
this.start()
process.on('plugin.after.enable', (plugin: plugin.Plugin) => this.registryPlugin(plugin))
process.on('plugin.after.disable', (plugin: plugin.Plugin) => this.unregistryPlugin(plugin))
}
start() {
@@ -43,13 +56,96 @@ export class Server {
}
}
registryPlugin(plugin: plugin.Plugin) {
let controllers = getControllerMetadatas(plugin).values()
for (const controller of controllers) {
console.debug(`Plugin ${plugin.description.name} Registry Controller ${controller.name}.`)
this.registryController(controller.target)
}
}
unregistryPlugin(plugin: plugin.Plugin) {
let controllers = getControllerMetadatas(plugin).values()
for (const controller of controllers) {
console.debug(`Plugin ${plugin.description.name} Unregistry Controller ${controller.name}.`)
this.unregistryController(controller.target)
}
}
registryController(target: any) {
if (!target) { throw new Error('Controller can\'t be undefiend!') }
let controllerMetadata = getControllerMetadata(target)
if (!controllerMetadata) { throw new Error(`Controller ${target.name} must have @Controller decorator!`) }
target = this.bindController(target)
let actions = getControllerActions(target)
this.controllerActions.set(controllerMetadata.name, [])
for (const action of actions) {
let actionMetadata = getActionMetadata(target, action)
this.controllerActions.get(controllerMetadata.name).push(actionMetadata)
let path = `${controllerMetadata.path || ''}${actionMetadata.path || ''}`
if (!path) throw new Error(`Controller ${controllerMetadata.name} Action ${actionMetadata.name} path is empty!`)
if (!this.methodMappings.has(path)) { this.methodMappings.set(path, new Map()) }
console.debug(`Controller ${controllerMetadata.name} Registry ${path} Action to ${actionMetadata.executor || '<anonymous>'} function.`)
this.methodMappings.get(path).set(actionMetadata.method || 'ALL', (ctx: Context) => {
let args = []
let params = getActionParams(target, action)
for (const index in params) {
let param = params[index]
let paramValue = undefined
switch (param.type) {
case PARAM_TYPE.REQUEST: paramValue = ctx.request; break
case PARAM_TYPE.RESPONSE: paramValue = ctx.response; break
case PARAM_TYPE.QUERY: paramValue = ctx.params[param.name]; break
case PARAM_TYPE.HEADER: paramValue = ctx.headers[param.name]; break
case PARAM_TYPE.BODY: paramValue = ctx.body; break
case PARAM_TYPE.COOKIE: paramValue = ctx.cookies[param.name]; break
}
if (param.require && !paramValue) {
return {
status: 400,
msg: param.message ?? `Param Type ${param.type} require not empty!`,
data: param
}
}
args[param.index] = paramValue ?? param.default
}
return target[actionMetadata.executor].apply(target, args)
})
}
}
private bindController(target: any) {
try {
this.container.rebind(METADATA_KEY.Controller).to(target).inSingletonScope().whenTargetNamed(target.name)
} catch{
this.container.bind(METADATA_KEY.Controller).to(target).inSingletonScope().whenTargetNamed(target.name)
}
return this.container.getNamed(METADATA_KEY.Controller, target.name)
}
unregistryController(target: any) {
if (!target) { throw new Error('Controller can\'t be undefiend!') }
let controllerMetadata = getControllerMetadata(target)
if (!controllerMetadata) { throw new Error(`Controller ${target.name} must have @Controller decorator!`) }
if (!this.controllerActions.has(controllerMetadata.name)) { return console.warn(`Controller ${controllerMetadata.name} not registry!`) }
let actions = this.controllerActions.get(controllerMetadata.name)
for (const actionMetadata of actions) {
let path = `${controllerMetadata.path || ''}${actionMetadata.path || ''}`
if (!this.methodMappings.has(path)) { continue }
this.methodMappings.get(path).delete(actionMetadata.method)
console.debug(`Controller ${controllerMetadata.name} Unregistry ${path} Action.`)
}
this.controllerActions.delete(controllerMetadata.name)
}
registryMapping(path: string, handler: RequestHandler) {
console.debug(`Registry Mapping ${path} to handle ${handler.name || '<anonymous>'} function.`)
this.handlerMapping.set(path, handler)
if (!this.methodMappings.has(path)) { this.methodMappings.set(path, new Map()) }
this.methodMappings.get(path).set("ALL", handler)
}
unregistryMapping(path: string) {
this.handlerMapping.delete(path)
if (this.methodMappings.has(path)) { this.methodMappings.get(path).delete("ALL") }
}
registryInterceptor(interceptor: InterceptorAdapter) {
@@ -57,7 +153,9 @@ export class Server {
this.interceptors.set(interceptor.name, interceptor)
}
unregistryInterceptor(interceptor: InterceptorAdapter) {
unregistryInterceptor(interceptor: string | InterceptorAdapter) {
if (typeof interceptor === "string") { interceptor = { name: interceptor } }
console.debug(`Unregistry ${interceptor.name} Interceptor.`)
this.interceptors.delete(interceptor.name)
}
@@ -65,28 +163,67 @@ export class Server {
try { this.beanFactory.destroySingleton(FilterProxyBeanName) } catch (ex) { }
var WebFilterProxyNashorn = Java.extend(this.WebFilterProxy, {
doFilter: (servletRequest: javax.servlet.http.HttpServletRequest, servletResponse: javax.servlet.http.HttpServletResponse, filterChain: javax.servlet.FilterChain) => {
console.log('WebFilterProxyNashorn', 'doFilter', servletRequest, servletResponse)
filterChain.doFilter(servletRequest, servletResponse)
}
})
this.beanFactory.registerSingleton(FilterProxyBeanName, new WebFilterProxyNashorn())
}
// private getRequestWrapper(servletRequest: javax.servlet.http.HttpServletRequest) {
// var body = org.springframework.util.StreamUtils.copyToByteArray(servletRequest.getInputStream())
// var HttpServletRequestWrapperAdapter = Java.extend(HttpServletRequestWrapper, {
// getInputStream: () => {
// var bais = new java.io.ByteArrayInputStream(body)
// return new ServletInputStream({
// read: () => bais.read(),
// isFinished: () => bais.available() == 0
// })
// }
// })
// var wrapper = new HttpServletRequestWrapperAdapter(servletRequest)
// return wrapper
// }
// private getResponseWrapper(servletResponse: javax.servlet.http.HttpServletResponse) {
// var HttpServletRequestWrapperAdapter = Java.extend(HttpServletRequestWrapper, {
// getOutputStream: () => {
// return new ServletOutputStream({
// })
// }
// })
// var wrapper = new HttpServletRequestWrapperAdapter(servletResponse)
// return wrapper
// }
private notFound(ctx: Context) {
return {
status: 404,
msg: "handlerMapping Not Found!",
method: ctx.request.getMethod(),
path: ctx.request.getRequestURI(),
timestamp: Date.now()
}
}
private registryWebProxy() {
try { this.beanFactory.destroySingleton(WebProxyBeanName) } catch (ex) { }
var WebServerProxyNashorn = Java.extend(this.WebServerProxy, {
process: (req: javax.servlet.http.HttpServletRequest, resp: javax.servlet.http.HttpServletResponse) => {
let ctx: Context = { request: req, response: resp }
let ctx: Context = { request: req, response: resp, params: {}, body: {} }
ctx.url = req.getRequestURI()
// @ts-ignore
ctx.header = { __noSuchProperty__: (name: string) => req.getHeader(name) + '' }
ctx.headers = { __noSuchProperty__: (name: string) => req.getHeader(name) }
ctx.cookies = {}
for (const cookie of (req.getCookies() || [])) {
ctx.cookies[cookie.getName()] = cookie.getValue()
}
if (req.getQueryString()) {
ctx.url += `?${req.getQueryString()}`
ctx.params = querystring.parse(req.getQueryString())
}
if (req.getMethod() == "POST") {
ctx.body = this.StreamUtils.copyToString(req.getInputStream(), java.nio.charset.StandardCharsets.UTF_8)
if ((ctx.header['Content-Type'] || '').includes('application/json')) {
if ((ctx.headers['Content-Type'] || '').includes('application/json')) {
try {
ctx.body = JSON.parse(ctx.body)
} catch (error) {
@@ -133,6 +270,10 @@ export class Server {
}
}
}
let path = ctx.request.getRequestURI()
if (!this.methodMappings.has(path)) return this.notFound(ctx)
let mappings = this.methodMappings.get(ctx.request.getRequestURI())
ctx.handler = mappings.get(ctx.request.getMethod()) || mappings.get("ALL")
ctx.result = this.execRequestHandle(ctx)
for (const [_, interceptor] of this.interceptors) {
if (interceptor.postHandle) {
@@ -158,6 +299,7 @@ export class Server {
===================== MiaoSpring =====================
Request Method : ${ctx.request.getMethod()}
Request URL : ${ctx.url}
Request Body : ${JSON.stringify(ctx.body)}
Response Body : ${JSON.stringify(Java.asJSONCompatible(ctx.result))}
Handle Time : ${Date.now() - startTime}ms
======================================================`)
@@ -165,16 +307,9 @@ Handle Time : ${Date.now() - startTime}ms
}
private execRequestHandle(ctx: Context) {
if (!this.handlerMapping.has(ctx.request.getRequestURI())) {
return {
status: 404,
msg: "handlerMapping Not Found!",
path: ctx.url,
timestamp: Date.now()
}
}
if (!ctx.handler) return this.notFound(ctx)
try {
return this.handlerMapping.get(ctx.request.getRequestURI())(ctx)
return ctx.handler(ctx)
} catch (error) {
return {
status: 500,

View File

@@ -1,6 +1,6 @@
{
"name": "@ccms/websocket",
"version": "0.8.0",
"version": "0.9.0",
"description": "MiaoScript websocket package",
"keywords": [
"miaoscript",
@@ -19,11 +19,10 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"devDependencies": {
"@ccms/nashorn": "^0.9.0",
"@javatypes/tomcat-websocket-api": "^0.0.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typescript": "^3.9.2"
},
"dependencies": {
"@ccms/nashorn": "^0.8.0"
"typescript": "^3.9.6"
}
}

View File

@@ -1,4 +1,5 @@
/// <reference types="@ccms/nashorn" />
/// <reference types="@javatypes/tomcat-websocket-api" />
import { Server, ServerOptions } from './socket-io'
@@ -6,40 +7,40 @@ interface SocketIOStatic {
/**
* Default Server constructor
*/
(): Server;
(): Server
/**
* Creates a new Server
* @param srv The HTTP server that we're going to bind to
* @param opts An optional parameters object
*/
(srv: any, opts?: ServerOptions): Server;
(srv: any, opts?: ServerOptions): Server
/**
* Creates a new Server
* @param port A port to bind to, as a number, or a string
* @param An optional parameters object
*/
(port: string | number, opts?: ServerOptions): Server;
(port: string | number, opts?: ServerOptions): Server
/**
* Creates a new Server
* @param A parameters object
*/
(opts: ServerOptions): Server;
(opts: ServerOptions): Server
/**
* Backwards compatibility
* @see io().listen()
*/
listen?: SocketIOStatic;
listen?: SocketIOStatic
}
type SocketStatic = SocketIOStatic & { Instance?: symbol }
// @ts-ignore
let io: SocketStatic = function (pipeline: any, options: ServerOptions) {
return new Server(pipeline, options)
return new Server(pipeline, options)
}
io.Instance = Symbol("@ccms/websocket")
export default io

View File

@@ -1,18 +1,18 @@
import { EventEmitter } from 'events'
import { ServerEvent } from './constants';
import { Namespace } from './namespace';
import { Client } from './client';
import { ServerEvent } from './constants'
import { Namespace } from './namespace'
import { Client } from './client'
import { SocketIO } from './interfaces'
import { Parser } from './parser'
import { PacketTypes, SubPacketTypes } from './types';
import { Packet } from './packet';
import { Socket } from './socket';
import { Adapter } from './adapter';
import { PacketTypes, SubPacketTypes } from './types'
import { Packet } from './packet'
import { Socket } from './socket'
import { Adapter } from './adapter'
interface ServerOptions extends SocketIO.ServerOptions {
event?: EventEmitter;
root?: string;
event?: EventEmitter
root?: string
}
interface WebSocketServer extends EventEmitter {
@@ -20,87 +20,87 @@ interface WebSocketServer extends EventEmitter {
}
class Server implements SocketIO.Server {
private websocketServer: WebSocketServer;
private allClients: { [key: string]: Client };
private websocketServer: WebSocketServer
private allClients: { [key: string]: Client }
engine: { ws: any; };
nsps: { [namespace: string]: Namespace; };
sockets: Namespace;
json: SocketIO.Server;
volatile: SocketIO.Server;
local: SocketIO.Server;
engine: { ws: any }
nsps: { [namespace: string]: Namespace }
sockets: Namespace
json: SocketIO.Server
volatile: SocketIO.Server
local: SocketIO.Server
parser = new Parser();
_adapter: Adapter;
options: ServerOptions;
_adapter: Adapter
options: ServerOptions
constructor(instance: any, options: ServerOptions) {
if (!instance) { throw new Error('instance can\'t be undefiend!') }
this.allClients = {};
this.nsps = {};
this.sockets = new Namespace('/', this);
this.nsps['/'] = this.sockets;
this.allClients = {}
this.nsps = {}
this.sockets = new Namespace('/', this)
this.nsps['/'] = this.sockets
if (instance.class.name.startsWith('io.netty.channel')) {
let { NettyWebSocketServer } = require("../server")
this.websocketServer = new NettyWebSocketServer(instance, Object.assign({
event: new EventEmitter(),
path: '/socket.io',
root: root + '/wwwroot'
}, options));
}, options))
} else {
let { TomcatWebSocketServer } = require("../tomcat/server")
this.websocketServer = new TomcatWebSocketServer(instance, options);
this.websocketServer = new TomcatWebSocketServer(instance, options)
}
this.initServer()
}
checkRequest(req: any, fn: (err: any, success: boolean) => void): void {
throw new Error("Method not implemented.");
throw new Error("Method not implemented.")
}
serveClient(): boolean;
serveClient(v: boolean): SocketIO.Server;
serveClient(): boolean
serveClient(v: boolean): SocketIO.Server
serveClient(v?: any): boolean | SocketIO.Server {
throw new Error("Method not implemented.");
throw new Error("Method not implemented.")
}
path(): string;
path(v: string): SocketIO.Server;
path(): string
path(v: string): SocketIO.Server
path(v?: any): string | SocketIO.Server {
if (!arguments.length) return this.options.path;
this.options.path = v.replace(/\/$/, '');
return this;
if (!arguments.length) return this.options.path
this.options.path = v.replace(/\/$/, '')
return this
}
adapter(): Adapter;
adapter(v: any): SocketIO.Server;
adapter(): Adapter
adapter(v: any): SocketIO.Server
adapter(v?: any): Adapter | SocketIO.Server {
if (!arguments.length) return this._adapter;
this._adapter = v;
if (!arguments.length) return this._adapter
this._adapter = v
for (var i in this.nsps) {
if (this.nsps.hasOwnProperty(i)) {
this.nsps[i].initAdapter();
this.nsps[i].initAdapter()
}
}
return this;
return this
}
origins(): string | string[];
origins(v: string | string[]): SocketIO.Server;
origins(fn: (origin: string, callback: (error: string, success: boolean) => void) => void): SocketIO.Server;
origins(): string | string[]
origins(v: string | string[]): SocketIO.Server
origins(fn: (origin: string, callback: (error: string, success: boolean) => void) => void): SocketIO.Server
origins(fn?: any): string | string[] | SocketIO.Server {
throw new Error("Method not implemented.");
throw new Error("Method not implemented.")
}
attach(srv: any, opts?: SocketIO.ServerOptions): SocketIO.Server;
attach(port: number, opts?: SocketIO.ServerOptions): SocketIO.Server;
attach(srv: any, opts?: SocketIO.ServerOptions): SocketIO.Server
attach(port: number, opts?: SocketIO.ServerOptions): SocketIO.Server
attach(port: any, opts?: any): SocketIO.Server {
throw new Error("Method not implemented.");
throw new Error("Method not implemented.")
}
listen(srv: any, opts?: SocketIO.ServerOptions): SocketIO.Server;
listen(port: number, opts?: SocketIO.ServerOptions): SocketIO.Server;
listen(srv: any, opts?: SocketIO.ServerOptions): SocketIO.Server
listen(port: number, opts?: SocketIO.ServerOptions): SocketIO.Server
listen(port: any, opts?: any): SocketIO.Server {
throw new Error("Method not implemented.");
throw new Error("Method not implemented.")
}
bind(srv: any): SocketIO.Server {
throw new Error("Method not implemented.");
throw new Error("Method not implemented.")
}
onconnection(socket: Client): SocketIO.Server {
this.allClients[socket.id] = socket;
this.allClients[socket.id] = socket
socket.packet({
type: PacketTypes.OPEN,
data: {
@@ -110,68 +110,75 @@ class Server implements SocketIO.Server {
pingTimeout: 5000
}
})
this.sockets.add(socket);
return this;
this.sockets.add(socket)
return this
}
of(nsp: string): Namespace {
if (!this.nsps[nsp]) {
this.nsps[nsp] = new Namespace(nsp, this);
this.nsps[nsp] = new Namespace(nsp, this)
}
return this.nsps[nsp];
return this.nsps[nsp]
}
close(fn?: () => void): void {
for (let socket in this.sockets.sockets) {
this.sockets.sockets[socket].onclose()
}
this.websocketServer.close();
this.websocketServer.close()
}
on(event: "connection", listener: (socket: SocketIO.Socket) => void): SocketIO.Namespace;
on(event: "connect", listener: (socket: SocketIO.Socket) => void): SocketIO.Namespace;
on(event: string, listener: Function): SocketIO.Namespace;
on(event: "connection", listener: (socket: SocketIO.Socket) => void): SocketIO.Namespace
on(event: "connect", listener: (socket: SocketIO.Socket) => void): SocketIO.Namespace
on(event: string, listener: Function): SocketIO.Namespace
on(event: any, listener: any): SocketIO.Namespace {
return this.sockets.on(event, listener);
return this.sockets.on(event, listener)
}
to(room: string): SocketIO.Namespace {
return this.sockets.to(room);
return this.sockets.to(room)
}
in(room: string): SocketIO.Namespace {
return this.sockets.in(room);
return this.sockets.in(room)
}
use(fn: (socket: SocketIO.Socket, fn: (err?: any) => void) => void): SocketIO.Namespace {
return this.sockets.use(fn);
return this.sockets.use(fn)
}
emit(event: string, ...args: any[]): SocketIO.Namespace {
// @ts-ignore
return this.sockets.emit(event, ...args);
return this.sockets.emit(event, ...args)
}
send(...args: any[]): SocketIO.Namespace {
return this.sockets.send(...args);
return this.sockets.send(...args)
}
write(...args: any[]): SocketIO.Namespace {
return this.sockets.write(...args);
return this.sockets.write(...args)
}
clients(...args: any[]): SocketIO.Namespace {
return this.sockets.clients(args[0]);
return this.sockets.clients(args[0])
}
compress(...args: any[]): SocketIO.Namespace {
return this.sockets.compress(args[0])
}
// ===============================
checkNamespace(name, query, fn) {
fn(false);
fn(false)
};
private initServer() {
this.websocketServer.on(ServerEvent.connect, (socket: SocketIO.EngineSocket) => {
let client = new Client(this, socket);
this.onconnection(client);
let client = new Client(this, socket)
this.onconnection(client)
})
this.websocketServer.on(ServerEvent.message, (socket: SocketIO.EngineSocket, text) => {
this.processPacket(this.parser.decode(text), this.allClients[socket.id]);
this.processPacket(this.parser.decode(text), this.allClients[socket.id])
})
this.websocketServer.on(ServerEvent.disconnect, (socket: SocketIO.EngineSocket, reason) => {
this.allClients[socket.id].onclose(reason)
delete this.allClients[socket.id]
})
this.websocketServer.on(ServerEvent.error, (socket: SocketIO.EngineSocket, cause) => {
console.error(`Client ${socket.id} cause error: ` + cause)
console.ex(cause)
if (socket.listeners(ServerEvent.error).length) {
socket.emit(ServerEvent.error, cause)
} else {
console.error(`client ${socket.id} cause error: ${cause}`)
}
})
}
@@ -182,15 +189,15 @@ class Server implements SocketIO.Server {
type: PacketTypes.PONG,
data: packet.data
})
break;
break
case PacketTypes.UPGRADE:
break;
break
case PacketTypes.MESSAGE:
this.processSubPacket(packet, client);
break;
this.processSubPacket(packet, client)
break
case PacketTypes.CLOSE:
client.onclose()
break;
break
}
}
@@ -201,11 +208,11 @@ class Server implements SocketIO.Server {
type: PacketTypes.MESSAGE,
sub_type: SubPacketTypes.ERROR,
data: 'not support dynamic namespace: ' + packet.nsp
});
client.disconnect();
return;
})
client.disconnect()
return
}
namespace.process(packet, client);
namespace.process(packet, client)
}
}
export {

View File

@@ -1,28 +1,29 @@
import { EventEmitter } from 'events'
import { SocketIO } from "./interfaces";
import { Packet } from './packet';
import { PacketTypes, SubPacketTypes } from './types';
import { Client } from './client';
import { Namespace } from './namespace';
import { SocketIO } from "./interfaces"
import { Packet } from './packet'
import { PacketTypes, SubPacketTypes } from './types'
import { Client } from './client'
import { Namespace } from './namespace'
import * as querystring from 'querystring'
import { ServerEvent } from './constants'
export class Socket extends EventEmitter implements SocketIO.Socket {
nsp: Namespace;
server: SocketIO.Server;
adapter: SocketIO.Adapter;
id: string;
request: any;
client: Client;
conn: SocketIO.EngineSocket;
rooms: { [id: string]: string; };
acks: { [id: string]: Function; };
connected: boolean;
disconnected: boolean;
handshake: SocketIO.Handshake;
fns: any[];
flags: { [key: string]: boolean };
_rooms: string[];
nsp: Namespace
server: SocketIO.Server
adapter: SocketIO.Adapter
id: string
request: any
client: Client
conn: SocketIO.EngineSocket
rooms: { [id: string]: string }
acks: { [id: string]: Function }
connected: boolean
disconnected: boolean
handshake: SocketIO.Handshake
fns: any[]
flags: { [key: string]: boolean }
_rooms: string[]
private events = [
'error',
@@ -34,113 +35,113 @@ export class Socket extends EventEmitter implements SocketIO.Socket {
]
constructor(nsp: Namespace, client: Client, query = {}) {
super();
this.nsp = nsp;
this.server = nsp.server;
this.adapter = this.nsp.adapter;
this.id = nsp.name !== '/' ? nsp.name + '#' + client.id : client.id;
this.client = client;
this.request = client.request;
this.conn = client.conn;
this.rooms = {};
this.acks = {};
this.connected = true;
this.disconnected = false;
this.handshake = this.buildHandshake(query);
this.fns = [];
this.flags = {};
this._rooms = [];
super()
this.nsp = nsp
this.server = nsp.server
this.adapter = this.nsp.adapter
this.id = nsp.name !== '/' ? nsp.name + '#' + client.id : client.id
this.client = client
this.request = client.request
this.conn = client.conn
this.rooms = {}
this.acks = {}
this.connected = true
this.disconnected = false
this.handshake = this.buildHandshake(query)
this.fns = []
this.flags = {}
this._rooms = []
}
get json() {
this.flags.json = true;
this.flags.json = true
return this
}
get volatile() {
this.flags.volatile = true;
this.flags.volatile = true
return this
}
get broadcast() {
this.flags.broadcast = true;
this.flags.broadcast = true
return this
}
get local() {
this.flags.local = true;
this.flags.local = true
return this
}
to(room: string): SocketIO.Socket {
if (!~this._rooms.indexOf(room)) this._rooms.push(room);
return this;
if (!~this._rooms.indexOf(room)) this._rooms.push(room)
return this
}
in(room: string): SocketIO.Socket {
return this.to(room);
return this.to(room)
}
use(fn: (packet: SocketIO.Packet, next: (err?: any) => void) => void): SocketIO.Socket {
throw new Error("Method not implemented.");
throw new Error("Method not implemented.")
}
send(...args: any[]): SocketIO.Socket {
this.emit("message", ...args)
return this;
return this
}
write(...args: any[]): SocketIO.Socket {
return this.send(...args);
return this.send(...args)
}
join(rooms: string | string[], fn?: (err?: any) => void): SocketIO.Socket {
if (!Array.isArray(rooms)) {
rooms = [rooms];
rooms = [rooms]
}
rooms = rooms.filter((room) => {
return !this.rooms.hasOwnProperty(room);
});
return !this.rooms.hasOwnProperty(room)
})
if (!rooms.length) {
fn && fn(null);
return this;
fn && fn(null)
return this
}
this.adapter.addAll(this.id, rooms, (err) => {
if (err) return fn && fn(err);
// debug('joined room %s', rooms);
(rooms as Array<string>).forEach((room) => {
this.rooms[room] = room;
});
fn && fn(null);
});
return this;
this.rooms[room] = room
})
fn && fn(null)
})
return this
}
leave(name: string, fn?: Function): SocketIO.Socket {
delete this.rooms[name];
delete this.rooms[name]
fn && fn(null)
return this;
return this
}
leaveAll(): void {
this.adapter.delAll(this.id);
this.rooms = {};
this.adapter.delAll(this.id)
this.rooms = {}
}
disconnect(close?: boolean): SocketIO.Socket {
if (!this.connected) return this;
if (!this.connected) return this
if (close) {
this.client.disconnect();
this.client.disconnect()
} else {
this.packet({ type: PacketTypes.MESSAGE, sub_type: SubPacketTypes.DISCONNECT });
this.onclose('server namespace disconnect');
this.packet({ type: PacketTypes.MESSAGE, sub_type: SubPacketTypes.DISCONNECT })
this.onclose('server namespace disconnect')
}
return this;
return this
}
compress(compress: boolean): SocketIO.Socket {
throw new Error("Method not implemented.");
throw new Error("Method not implemented.")
}
error(err: any): void {
this.packet({ type: PacketTypes.MESSAGE, sub_type: SubPacketTypes.ERROR, data: err });
this.packet({ type: PacketTypes.MESSAGE, sub_type: SubPacketTypes.ERROR, data: err })
}
// ==========================================
buildHandshake(query): SocketIO.Handshake {
let requestUri = this.request.uri();
let headers = {};
let nativeHeaders = this.request.headers();
let requestUri = this.request.uri()
let headers = {}
let nativeHeaders = this.request.headers()
nativeHeaders.forEach(function (header) {
headers[header.getKey()] = header.getValue();
headers[header.getKey()] = header.getValue()
})
return {
headers: headers,
@@ -155,9 +156,9 @@ export class Socket extends EventEmitter implements SocketIO.Socket {
}
emit(event: string, ...args: any[]): boolean {
if (~this.events.indexOf(event)) {
super.emit(event, ...args);
super.emit(event, ...args)
// @ts-ignore
return this;
return this
}
let packet: Packet = {
@@ -170,44 +171,48 @@ export class Socket extends EventEmitter implements SocketIO.Socket {
// access last argument to see if it's an ACK callback
if (typeof args[args.length - 1] === 'function') {
if (this._rooms.length || this.flags.broadcast) {
throw new Error('Callbacks are not supported when broadcasting');
throw new Error('Callbacks are not supported when broadcasting')
}
// debug('emitting packet with ack id %d', this.nsp.ids);
this.acks[this.nsp.ids] = args.pop();
packet.id = this.nsp.ids++;
this.acks[this.nsp.ids] = args.pop()
packet.id = this.nsp.ids++
}
let rooms = this._rooms.slice(0);
let flags = Object.assign({}, this.flags);
let rooms = this._rooms.slice(0)
let flags = Object.assign({}, this.flags)
// reset flags
this._rooms = [];
this.flags = {};
this._rooms = []
this.flags = {}
if (rooms.length || flags.broadcast) {
this.adapter.broadcast(packet, {
except: [this.id],
rooms: rooms,
flags: flags
});
})
} else {
// dispatch packet
this.packet(packet, flags);
this.packet(packet, flags)
}
// @ts-ignore
return this;
return this
}
packet(packet: Packet, opts: any = { preEncoded: false }) {
if (!opts.preEncoded) {
packet.nsp = this.nsp.name;
opts.compress = false !== opts.compress;
packet.nsp = this.nsp.name
opts.compress = false !== opts.compress
}
try {
this.client.packet(packet, opts)
} catch (error) {
this.onerror(error)
}
this.client.packet(packet, opts);
}
onconnect() {
this.nsp.connected[this.id] = this;
this.client.sockets[this.id] = this;
this.join(this.id);
this.nsp.connected[this.id] = this
this.client.sockets[this.id] = this
this.join(this.id)
// let skip = this.nsp.name === '/' && this.nsp.fns.length === 0;
// if (skip) {
// debug('packet already sent in initial handshake');
@@ -215,54 +220,54 @@ export class Socket extends EventEmitter implements SocketIO.Socket {
this.packet({
type: PacketTypes.MESSAGE,
sub_type: SubPacketTypes.CONNECT
});
})
// }
}
onclose(reason?: string) {
if (!this.connected) return this;
if (!this.connected) return this
// debug('closing socket - reason %s', reason);
this.emit('disconnecting', reason);
this.leaveAll();
this.nsp.remove(this);
this.client.remove(this);
this.connected = false;
this.disconnected = true;
delete this.nsp.connected[this.id];
this.emit('disconnect', reason);
this.emit('disconnecting', reason)
this.leaveAll()
this.nsp.remove(this)
this.client.remove(this)
this.connected = false
this.disconnected = true
delete this.nsp.connected[this.id]
this.emit('disconnect', reason)
}
onpacket(packet: Packet) {
switch (packet.sub_type) {
// 2
case SubPacketTypes.EVENT:
this.onevent(packet);
break;
this.onevent(packet)
break
// 5
case SubPacketTypes.BINARY_EVENT:
this.onevent(packet);
break;
this.onevent(packet)
break
// 3
case SubPacketTypes.ACK:
this.onack(packet);
break;
this.onack(packet)
break
// 6
case SubPacketTypes.BINARY_ACK:
this.onack(packet);
break;
this.onack(packet)
break
// 1
case SubPacketTypes.DISCONNECT:
this.ondisconnect();
break;
this.ondisconnect()
break
// 4
case SubPacketTypes.ERROR:
this.onerror(new Error(packet.data));
this.onerror(new Error(packet.data))
}
}
onerror(err: Error) {
if (this.listeners('error').length) {
this.emit('error', err);
this.emit('error', err)
} else {
console.error('Missing error handler on `socket`.');
console.error(err.stack);
console.error('Missing error handler on `socket`.')
console.error(err.stack)
}
}
ondisconnect() {
@@ -271,39 +276,39 @@ export class Socket extends EventEmitter implements SocketIO.Socket {
onevent(packet: Packet) {
if (null != packet.id) {
// debug('attaching ack callback to event');
this.dispatch(packet, this.ack(packet.id));
this.dispatch(packet, this.ack(packet.id))
} else {
this.dispatch(packet);
this.dispatch(packet)
}
}
ack(id: number) {
let sent = false;
let sent = false
return (...args: any[]) => {
if (sent) return;
if (sent) return
this.packet({
id: id,
type: PacketTypes.MESSAGE,
sub_type: this.hasBin(args) ? SubPacketTypes.BINARY_ACK : SubPacketTypes.ACK,
data: args
});
sent = true;
})
sent = true
}
}
onack(packet: Packet) {
let ack = this.acks[packet.id];
let ack = this.acks[packet.id]
if ('function' == typeof ack) {
// debug('calling ack %s with %j', packet.id, packet.data);
ack.apply(this, packet.data);
delete this.acks[packet.id];
ack.apply(this, packet.data)
delete this.acks[packet.id]
} else {
// debug('bad ack %s', packet.id);
}
}
dispatch(packet: Packet, ack?: Function) {
if (ack) { this.acks[packet.id] = ack; }
if (ack) { this.acks[packet.id] = ack }
super.emit(packet.name, ...packet.data, ack)
}
private hasBin(obj: any) {
return false;
return false
}
}

View File

@@ -1,44 +1,45 @@
import { EventEmitter } from 'events'
import { SocketIO } from '../socket-io/interfaces';
import { SocketIO } from '../socket-io/interfaces'
export class TomcatClient extends EventEmitter implements SocketIO.EngineSocket {
private _id: string;
private session: any
private _id: string
private session: javax.websocket.Session
server: any;
readyState: string;
remoteAddress: string;
upgraded: boolean;
request: any;
transport: any;
server: any
readyState: string
remoteAddress: string
upgraded: boolean
request: any
transport: any
constructor(server: any, session: any) {
super();
this.server = server;
this.readyState = 'open';
constructor(server: any, session: javax.websocket.Session) {
super()
this.server = server
this.readyState = 'open'
this.remoteAddress = session + ''
this.upgraded = true;
this.upgraded = true
this.request = {
uri: () => {
return session.getRequestURI() + ''
},
headers: () => {
return []
}
};
this.transport = null;
uri: () => `${session.getRequestURI()}`,
headers: () => []
}
this.transport = null
this.session = session;
this._id = session.getId();
this.session = session
this._id = session.getId()
}
get id() {
return this._id;
return this._id
}
send(text: string) {
Java.synchronized(() => this.session.getBasicRemote().sendText(text), this.session)()
if (this.readyState == 'open') {
Java.synchronized(() => this.session.getBasicRemote().sendText(text), this.session)()
}
}
close() {
this.session.close();
if (this.readyState == 'open') {
this.readyState = 'close'
this.session.close()
}
}
}

View File

@@ -10,9 +10,7 @@ const WebSocketServerProxy = Java.type("pw.yumc.MiaoScript.websocket.WebSocketPr
const ThreadPoolExecutor = Java.type('java.util.concurrent.ThreadPoolExecutor')
const ThreadPoolTaskExecutor = Java.type('org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor')
interface TomcatWebSocketSession {
getId: () => number
}
type TomcatWebSocketSession = javax.websocket.Session
class TomcatWebSocketServer extends EventEmitter {
private beanFactory: any

View File

@@ -7,6 +7,7 @@
"module": "commonjs",
"strict": true,
"strictNullChecks": false,
"importHelpers": true,
"sourceMap": true,
"declaration": true,
"declarationMap": true,