@nebulaaa/launcher 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,194 @@
1
+ 'use strict';
2
+
3
+ var cc = require('cc');
4
+ var dict = require('@nebulaaa/dict');
5
+ var env = require('@nebulaaa/env');
6
+ var eventBus = require('@nebulaaa/event-bus');
7
+ var ms = require('@nebulaaa/ms');
8
+ var plugin = require('@nebulaaa/plugin');
9
+ var web = require('@nebulaaa/web');
10
+
11
+ /**
12
+ * 应用构建版本
13
+ */
14
+ exports.Build = void 0;
15
+ (function (Build) {
16
+ /** 内部开发版 */
17
+ Build["Dev"] = "dev";
18
+ /** 内部测试版 */
19
+ Build["Alpha"] = "alpha";
20
+ /** 公开测试版 */
21
+ Build["Beta"] = "beta";
22
+ /** 候选发布版 */
23
+ Build["Candidate"] = "candidate";
24
+ /** 正式发布版 */
25
+ Build["Release"] = "release";
26
+ })(exports.Build || (exports.Build = {}));
27
+
28
+ /** 宏定义 */
29
+ const LAUNCHER_EVENT = {
30
+ /** 应用进入后台 */
31
+ ENTER_BACKGROUND: 'app@enter-background',
32
+ /** 应用进入前台 */
33
+ ENTER_FOREGROUND: 'app@enter-foreground',
34
+ /** 应用退出 */
35
+ QUIT: 'app@quit',
36
+ /** 应用低内存告警 */
37
+ LOW_MEMORY: 'app@low-memory',
38
+ /** 场景启动 */
39
+ STAGE_READY: 'app@stage-ready',
40
+ };
41
+ /** 允许经 URL 覆写的配置键白名单(可经 config.UrlKeys 追加) */
42
+ const LAUNCHER_URL_OVERRIDABLE_KEYS = ['Workspace'];
43
+ /** 保留键(扩展白名单也拒绝注入,防止 URL 伪装构建版本等核心语义) */
44
+ const LAUNCHER_RESERVED_KEYS = ['App', 'Version', 'Build', 'Language'];
45
+
46
+ /**
47
+ * 启动器
48
+ */
49
+ class Launcher extends plugin.Plugin {
50
+ constructor(deps, cfg) {
51
+ var _a;
52
+ dict.Override(cfg !== null && cfg !== void 0 ? cfg : {}, {
53
+ app: 'nebulaaa',
54
+ version: '1.0.0',
55
+ build: exports.Build.Dev,
56
+ language: [cc.sys.Language.CHINESE, cc.sys.Language.ENGLISH],
57
+ env: 'default',
58
+ });
59
+ if ((env.MISC.BROWSER && cfg.Build === exports.Build.Dev) || cfg.Build === exports.Build.Alpha) {
60
+ const qs = web.GetQueryString();
61
+ const whitelist = [...LAUNCHER_URL_OVERRIDABLE_KEYS, ...((_a = cfg.AllowedUrlKeys) !== null && _a !== void 0 ? _a : [])];
62
+ dict.Each(qs, (key, value) => {
63
+ if (LAUNCHER_RESERVED_KEYS.includes(key))
64
+ return; // 保留键拒绝注入
65
+ if (!whitelist.includes(key))
66
+ return; // 白名单外的键拒绝注入
67
+ try {
68
+ cfg[key] = decodeURIComponent(value);
69
+ }
70
+ catch (_a) {
71
+ cfg[key] = value;
72
+ }
73
+ });
74
+ }
75
+ super(deps, cfg);
76
+ this.App = this.Cfg.App;
77
+ this.Version = this.Cfg.Version;
78
+ this.Build = this.Cfg.Build;
79
+ this.IsDev = this.Cfg.Build === exports.Build.Dev;
80
+ this.IsAlpha = this.Cfg.Build === exports.Build.Alpha;
81
+ this.IsBeta = this.Cfg.Build === exports.Build.Beta;
82
+ this.IsCandidate = this.Cfg.Build === exports.Build.Candidate;
83
+ this.IsRelease = this.Cfg.Build === exports.Build.Release;
84
+ this.Workspace = this.Cfg.Workspace;
85
+ this.SupportedLanguages = this.Cfg.Language;
86
+ this._runningLogic = true;
87
+ this._runningInternal = true;
88
+ this._timingPause = 0;
89
+ this._elapsed = 0;
90
+ cc.director.once(cc.Director.EVENT_BEFORE_SCENE_LAUNCH, this.onSceneLaunch, this);
91
+ cc.game.on(cc.Game.EVENT_SHOW, this.internalResume, this);
92
+ cc.game.on(cc.Game.EVENT_HIDE, this.internalPause, this);
93
+ cc.game.on(cc.Game.EVENT_CLOSE, this.internalClose, this);
94
+ cc.game.on(cc.Game.EVENT_LOW_MEMORY, this.internalLowMemory, this);
95
+ this.Logger.Info(`🔥 ${this.App} ${this.Version}_${this.Build}`);
96
+ }
97
+ /** 应用退到后台 */
98
+ internalPause() {
99
+ this._runningInternal = false;
100
+ this._timingPause = ms.Now();
101
+ this.Deps.EventBus.Sys.Notify(eventBus.EventMessage.Void(LAUNCHER_EVENT.ENTER_BACKGROUND));
102
+ }
103
+ /** 应用回到前台 */
104
+ internalResume() {
105
+ this._runningInternal = true;
106
+ if (this._timingPause > 0) {
107
+ this._elapsed = ms.Now() - this._timingPause; // 仅完整的 hide→show 往返计入,首启 SHOW 不产生垃圾值
108
+ this._timingPause = 0;
109
+ }
110
+ this.Deps.EventBus.Sys.Notify(eventBus.EventMessage.Void(LAUNCHER_EVENT.ENTER_FOREGROUND));
111
+ }
112
+ /** 应用退出 */
113
+ internalClose() {
114
+ this.Deps.EventBus.Sys.Notify(eventBus.EventMessage.Void(LAUNCHER_EVENT.QUIT));
115
+ cc.game.targetOff(this);
116
+ cc.director.targetOff(this);
117
+ }
118
+ /** 内存报警 */
119
+ internalLowMemory() {
120
+ // 巡检的节流周期由引擎帧驱动,低内存时须跳过等待立即清扫(此前误传秒值使清扫形同虚设)
121
+ this.Deps.EventBus.Sys.Notify(eventBus.EventMessage.Void(LAUNCHER_EVENT.LOW_MEMORY));
122
+ }
123
+ /** 场景启动 */
124
+ onSceneLaunch(scene) {
125
+ var _a, _b;
126
+ const ctx = this;
127
+ // Scene
128
+ ctx.Scene = scene;
129
+ // Mnt
130
+ ctx.Mnt = scene.getChildByName('mnt');
131
+ if (!ctx.Mnt) {
132
+ ctx.Mnt = new cc.Node('mnt');
133
+ ctx.Mnt.layer = 0;
134
+ ctx.Scene.insertChild(this.Mnt, 0);
135
+ }
136
+ // Root
137
+ ctx.Root = scene.getChildByName('Canvas');
138
+ if (!this.Root) {
139
+ // Canvas 是舞台的挂载前提,缺失时中止装配;仅记录日志,不向引擎事件派发抛出异常
140
+ this.Logger.Error(`启动错误:场景 ${scene.name} 缺少名为 Canvas 的根节点,舞台装配中止`);
141
+ return;
142
+ }
143
+ // Stage
144
+ ctx.Stage = ctx.Root.getChildByName('Stage');
145
+ if (!ctx.Stage) {
146
+ const node = new cc.Node('Stage');
147
+ node.layer = cc.Layers.BitMask.UI_2D;
148
+ const wid = node.addComponent(cc.Widget);
149
+ wid.left = wid.top = wid.right = wid.bottom = 0;
150
+ wid.isAlignLeft = wid.isAlignTop = wid.isAlignBottom = wid.isAlignRight = true;
151
+ wid.alignMode = cc.Widget.AlignMode.ON_WINDOW_RESIZE;
152
+ wid.updateAlignment();
153
+ ctx.Stage = node;
154
+ this.Root.insertChild(this.Stage, 1);
155
+ }
156
+ // Canvas
157
+ ctx.Canvas = ctx.Root.getComponent(cc.Canvas);
158
+ // CameraUi
159
+ ctx.CameraUi = (_b = (_a = ctx.Root.getChildByName('Camera')) === null || _a === void 0 ? void 0 : _a.getComponent(cc.Camera)) !== null && _b !== void 0 ? _b : null;
160
+ if (!this.CameraUi) {
161
+ this.Logger.Warn('启动警告:Canvas 缺少名为 Camera 的子节点,UI 相机不可用');
162
+ }
163
+ this.Deps.EventBus.Sys.Notify(eventBus.EventMessage.Void(LAUNCHER_EVENT.STAGE_READY));
164
+ }
165
+ /** 是否指定构建版本 */
166
+ IsBuildOf(build) {
167
+ return this.Build === build;
168
+ }
169
+ /** 是否支持语言 */
170
+ IsLanguageSupported(language) {
171
+ return this.Cfg.Language.includes(language);
172
+ }
173
+ /** 暂停逻辑 */
174
+ Pause() {
175
+ this._runningLogic = false;
176
+ }
177
+ /** 恢复逻辑 */
178
+ Resume() {
179
+ this._runningLogic = true;
180
+ }
181
+ /** 从后台回到前台经历的时间(ms,未经历完整后台往返时为 0) */
182
+ get Elapsed() {
183
+ return this._elapsed;
184
+ }
185
+ /** 运行状态 */
186
+ get Running() {
187
+ return this._runningInternal && this._runningLogic;
188
+ }
189
+ }
190
+
191
+ exports.LAUNCHER_EVENT = LAUNCHER_EVENT;
192
+ exports.LAUNCHER_RESERVED_KEYS = LAUNCHER_RESERVED_KEYS;
193
+ exports.LAUNCHER_URL_OVERRIDABLE_KEYS = LAUNCHER_URL_OVERRIDABLE_KEYS;
194
+ exports.Launcher = Launcher;
@@ -0,0 +1,169 @@
1
+ import { __private, Scene, Node, Canvas, Camera } from 'cc';
2
+ import { IEventBus } from '@nebulaaa/event-bus';
3
+ import { IPlugin, PluginDeps, PluginCfg, Plugin } from '@nebulaaa/plugin';
4
+
5
+ /** 语言代码 */
6
+ type Language = __private._pal_system_info_enum_type_language__Language;
7
+ /**
8
+ * 应用构建版本
9
+ */
10
+ declare enum Build {
11
+ /** 内部开发版 */
12
+ Dev = "dev",
13
+ /** 内部测试版 */
14
+ Alpha = "alpha",
15
+ /** 公开测试版 */
16
+ Beta = "beta",
17
+ /** 候选发布版 */
18
+ Candidate = "candidate",
19
+ /** 正式发布版 */
20
+ Release = "release"
21
+ }
22
+ /** 启动器插件依赖 */
23
+ interface LauncherDeps extends PluginDeps {
24
+ EventBus: IEventBus;
25
+ }
26
+ /** 启动器插件配置 */
27
+ interface LauncherCfg extends PluginCfg {
28
+ /** 应用名称 */
29
+ App: string;
30
+ /** 应用版本号 */
31
+ Version: string;
32
+ /** 构建版本 */
33
+ Build: Build;
34
+ /** 支持语言 */
35
+ Language: Language[];
36
+ /** 用户空间标识(仅开发时使用,用于区分独立数据的用户空间,默认 `default`) */
37
+ Workspace?: string;
38
+ /** 允许经 URL 覆写的配置键(在默认白名单基础上追加,保留键除外) */
39
+ AllowedUrlKeys?: string[];
40
+ [k: string]: unknown;
41
+ }
42
+ /**
43
+ * 启动器能力接口
44
+ *
45
+ * - 负责管理启动参数
46
+ *
47
+ * @tip 【单场景架构约束】
48
+ * scene/root/mnt/canvas/cameraUi/stage 仅在首个场景启动时装配一次,并随之发出 STAGE_READY 事件。
49
+ */
50
+ interface ILauncher extends IPlugin<LauncherDeps, LauncherCfg> {
51
+ /** 应用名称 */
52
+ readonly App: string;
53
+ /** 应用版本号 */
54
+ readonly Version: string;
55
+ /** 构建版本 */
56
+ readonly Build: Build;
57
+ /** 是否开发版本 */
58
+ readonly IsDev: boolean;
59
+ /** 是否内测版本 */
60
+ readonly IsAlpha: boolean;
61
+ /** 是否公测版本 */
62
+ readonly IsBeta: boolean;
63
+ /** 是否候选版本 */
64
+ readonly IsCandidate: boolean;
65
+ /** 是否正式版本 */
66
+ readonly IsRelease: boolean;
67
+ /** 用户空间标识 */
68
+ readonly Workspace: string;
69
+ /** 获取当前支持的语言列表 */
70
+ readonly SupportedLanguages: Language[];
71
+ /** 是否指定构建版本 */
72
+ IsBuildOf(build: Build): boolean;
73
+ /** 是否支持语言 */
74
+ IsLanguageSupported(language: Language): boolean;
75
+ /** 场景 */
76
+ readonly Scene: Scene;
77
+ /** 根节点 */
78
+ readonly Root: Node;
79
+ /** 挂载节点 */
80
+ readonly Mnt: Node;
81
+ /** 画布 */
82
+ readonly Canvas: Canvas;
83
+ /** UI相机 */
84
+ readonly CameraUi: Camera;
85
+ /** 舞台 */
86
+ readonly Stage: Node;
87
+ /** 暂停逻辑 */
88
+ Pause(): void;
89
+ /** 恢复逻辑 */
90
+ Resume(): void;
91
+ /** 从后台回到前台经历的时间(ms,未经历完整后台往返时为 0) */
92
+ get Elapsed(): number;
93
+ /** 运行状态 */
94
+ get Running(): boolean;
95
+ }
96
+
97
+ /** 宏定义 */
98
+ declare const LAUNCHER_EVENT: {
99
+ /** 应用进入后台 */
100
+ readonly ENTER_BACKGROUND: "app@enter-background";
101
+ /** 应用进入前台 */
102
+ readonly ENTER_FOREGROUND: "app@enter-foreground";
103
+ /** 应用退出 */
104
+ readonly QUIT: "app@quit";
105
+ /** 应用低内存告警 */
106
+ readonly LOW_MEMORY: "app@low-memory";
107
+ /** 场景启动 */
108
+ readonly STAGE_READY: "app@stage-ready";
109
+ };
110
+ /** 允许经 URL 覆写的配置键白名单(可经 config.UrlKeys 追加) */
111
+ declare const LAUNCHER_URL_OVERRIDABLE_KEYS: string[];
112
+ /** 保留键(扩展白名单也拒绝注入,防止 URL 伪装构建版本等核心语义) */
113
+ declare const LAUNCHER_RESERVED_KEYS: string[];
114
+
115
+ /**
116
+ * 启动器
117
+ */
118
+ declare class Launcher extends Plugin<LauncherDeps, LauncherCfg> implements ILauncher {
119
+ readonly App: string;
120
+ readonly Version: string;
121
+ readonly Build: Build;
122
+ readonly IsDev: boolean;
123
+ readonly IsAlpha: boolean;
124
+ readonly IsBeta: boolean;
125
+ readonly IsCandidate: boolean;
126
+ readonly IsRelease: boolean;
127
+ readonly Workspace: string;
128
+ readonly SupportedLanguages: Language[];
129
+ readonly Scene: Scene;
130
+ readonly Root: Node;
131
+ readonly Mnt: Node;
132
+ readonly Canvas: Canvas;
133
+ readonly CameraUi: Camera;
134
+ readonly Stage: Node;
135
+ /** 逻辑运行状态 */
136
+ private _runningLogic;
137
+ /** 内部运行状态 */
138
+ private _runningInternal;
139
+ /** 内部暂停计时点(0 表示不在后台往返中) */
140
+ private _timingPause;
141
+ /** 最近一次后台滞留时长(未经历完整 hide→show 往返时为 0) */
142
+ private _elapsed;
143
+ constructor(deps: LauncherDeps, cfg: LauncherCfg);
144
+ /** 应用退到后台 */
145
+ private internalPause;
146
+ /** 应用回到前台 */
147
+ private internalResume;
148
+ /** 应用退出 */
149
+ private internalClose;
150
+ /** 内存报警 */
151
+ private internalLowMemory;
152
+ /** 场景启动 */
153
+ private onSceneLaunch;
154
+ /** 是否指定构建版本 */
155
+ IsBuildOf(build: Build): boolean;
156
+ /** 是否支持语言 */
157
+ IsLanguageSupported(language: Language): boolean;
158
+ /** 暂停逻辑 */
159
+ Pause(): void;
160
+ /** 恢复逻辑 */
161
+ Resume(): void;
162
+ /** 从后台回到前台经历的时间(ms,未经历完整后台往返时为 0) */
163
+ get Elapsed(): number;
164
+ /** 运行状态 */
165
+ get Running(): boolean;
166
+ }
167
+
168
+ export { Build, LAUNCHER_EVENT, LAUNCHER_RESERVED_KEYS, LAUNCHER_URL_OVERRIDABLE_KEYS, Launcher };
169
+ export type { ILauncher, Language, LauncherCfg, LauncherDeps };
@@ -0,0 +1 @@
1
+ "use strict";var e,t=require("cc"),i=require("@nebulaaa/dict"),s=require("@nebulaaa/env"),n=require("@nebulaaa/event-bus"),a=require("@nebulaaa/ms"),r=require("@nebulaaa/plugin"),o=require("@nebulaaa/web");exports.Build=void 0,(e=exports.Build||(exports.Build={})).Dev="dev",e.Alpha="alpha",e.Beta="beta",e.Candidate="candidate",e.Release="release";const u={ENTER_BACKGROUND:"app@enter-background",ENTER_FOREGROUND:"app@enter-foreground",QUIT:"app@quit",LOW_MEMORY:"app@low-memory",STAGE_READY:"app@stage-ready"},l=["Workspace"],g=["App","Version","Build","Language"];class h extends r.Plugin{constructor(e,n){var a;if(i.Override(null!=n?n:{},{app:"nebulaaa",version:"1.0.0",build:exports.Build.Dev,language:[t.sys.Language.CHINESE,t.sys.Language.ENGLISH],env:"default"}),s.MISC.BROWSER&&n.Build===exports.Build.Dev||n.Build===exports.Build.Alpha){const e=o.GetQueryString(),t=[...l,...null!==(a=n.AllowedUrlKeys)&&void 0!==a?a:[]];i.Each(e,(e,i)=>{if(!g.includes(e)&&t.includes(e))try{n[e]=decodeURIComponent(i)}catch(t){n[e]=i}})}super(e,n),this.App=this.Cfg.App,this.Version=this.Cfg.Version,this.Build=this.Cfg.Build,this.IsDev=this.Cfg.Build===exports.Build.Dev,this.IsAlpha=this.Cfg.Build===exports.Build.Alpha,this.IsBeta=this.Cfg.Build===exports.Build.Beta,this.IsCandidate=this.Cfg.Build===exports.Build.Candidate,this.IsRelease=this.Cfg.Build===exports.Build.Release,this.Workspace=this.Cfg.Workspace,this.SupportedLanguages=this.Cfg.Language,this._runningLogic=!0,this._runningInternal=!0,this._timingPause=0,this._elapsed=0,t.director.once(t.Director.EVENT_BEFORE_SCENE_LAUNCH,this.onSceneLaunch,this),t.game.on(t.Game.EVENT_SHOW,this.internalResume,this),t.game.on(t.Game.EVENT_HIDE,this.internalPause,this),t.game.on(t.Game.EVENT_CLOSE,this.internalClose,this),t.game.on(t.Game.EVENT_LOW_MEMORY,this.internalLowMemory,this),this.Logger.Info(`🔥 ${this.App} ${this.Version}_${this.Build}`)}internalPause(){this._runningInternal=!1,this._timingPause=a.Now(),this.Deps.EventBus.Sys.Notify(n.EventMessage.Void(u.ENTER_BACKGROUND))}internalResume(){this._runningInternal=!0,this._timingPause>0&&(this._elapsed=a.Now()-this._timingPause,this._timingPause=0),this.Deps.EventBus.Sys.Notify(n.EventMessage.Void(u.ENTER_FOREGROUND))}internalClose(){this.Deps.EventBus.Sys.Notify(n.EventMessage.Void(u.QUIT)),t.game.targetOff(this),t.director.targetOff(this)}internalLowMemory(){this.Deps.EventBus.Sys.Notify(n.EventMessage.Void(u.LOW_MEMORY))}onSceneLaunch(e){var i,s;const a=this;if(a.Scene=e,a.Mnt=e.getChildByName("mnt"),a.Mnt||(a.Mnt=new t.Node("mnt"),a.Mnt.layer=0,a.Scene.insertChild(this.Mnt,0)),a.Root=e.getChildByName("Canvas"),this.Root){if(a.Stage=a.Root.getChildByName("Stage"),!a.Stage){const e=new t.Node("Stage");e.layer=t.Layers.BitMask.UI_2D;const i=e.addComponent(t.Widget);i.left=i.top=i.right=i.bottom=0,i.isAlignLeft=i.isAlignTop=i.isAlignBottom=i.isAlignRight=!0,i.alignMode=t.Widget.AlignMode.ON_WINDOW_RESIZE,i.updateAlignment(),a.Stage=e,this.Root.insertChild(this.Stage,1)}a.Canvas=a.Root.getComponent(t.Canvas),a.CameraUi=null!==(s=null===(i=a.Root.getChildByName("Camera"))||void 0===i?void 0:i.getComponent(t.Camera))&&void 0!==s?s:null,this.CameraUi||this.Logger.Warn("启动警告:Canvas 缺少名为 Camera 的子节点,UI 相机不可用"),this.Deps.EventBus.Sys.Notify(n.EventMessage.Void(u.STAGE_READY))}else this.Logger.Error(`启动错误:场景 ${e.name} 缺少名为 Canvas 的根节点,舞台装配中止`)}IsBuildOf(e){return this.Build===e}IsLanguageSupported(e){return this.Cfg.Language.includes(e)}Pause(){this._runningLogic=!1}Resume(){this._runningLogic=!0}get Elapsed(){return this._elapsed}get Running(){return this._runningInternal&&this._runningLogic}}exports.LAUNCHER_EVENT=u,exports.LAUNCHER_RESERVED_KEYS=g,exports.LAUNCHER_URL_OVERRIDABLE_KEYS=l,exports.Launcher=h;
@@ -0,0 +1 @@
1
+ import{sys as t,director as e,Director as i,game as n,Game as s,Node as a,Layers as o,Widget as r,Canvas as l,Camera as h}from"cc";import{Override as g,Each as u}from"@nebulaaa/dict";import{MISC as d}from"@nebulaaa/env";import{EventMessage as p}from"@nebulaaa/event-bus";import{Now as E}from"@nebulaaa/ms";import{Plugin as m}from"@nebulaaa/plugin";import{GetQueryString as C}from"@nebulaaa/web";var f;!function(t){t.Dev="dev",t.Alpha="alpha",t.Beta="beta",t.Candidate="candidate",t.Release="release"}(f||(f={}));const _={ENTER_BACKGROUND:"app@enter-background",ENTER_FOREGROUND:"app@enter-foreground",QUIT:"app@quit",LOW_MEMORY:"app@low-memory",STAGE_READY:"app@stage-ready"},c=["Workspace"],R=["App","Version","Build","Language"];class B extends m{constructor(a,o){var r;if(g(null!=o?o:{},{app:"nebulaaa",version:"1.0.0",build:f.Dev,language:[t.Language.CHINESE,t.Language.ENGLISH],env:"default"}),d.BROWSER&&o.Build===f.Dev||o.Build===f.Alpha){const t=C(),e=[...c,...null!==(r=o.AllowedUrlKeys)&&void 0!==r?r:[]];u(t,(t,i)=>{if(!R.includes(t)&&e.includes(t))try{o[t]=decodeURIComponent(i)}catch(e){o[t]=i}})}super(a,o),this.App=this.Cfg.App,this.Version=this.Cfg.Version,this.Build=this.Cfg.Build,this.IsDev=this.Cfg.Build===f.Dev,this.IsAlpha=this.Cfg.Build===f.Alpha,this.IsBeta=this.Cfg.Build===f.Beta,this.IsCandidate=this.Cfg.Build===f.Candidate,this.IsRelease=this.Cfg.Build===f.Release,this.Workspace=this.Cfg.Workspace,this.SupportedLanguages=this.Cfg.Language,this._runningLogic=!0,this._runningInternal=!0,this._timingPause=0,this._elapsed=0,e.once(i.EVENT_BEFORE_SCENE_LAUNCH,this.onSceneLaunch,this),n.on(s.EVENT_SHOW,this.internalResume,this),n.on(s.EVENT_HIDE,this.internalPause,this),n.on(s.EVENT_CLOSE,this.internalClose,this),n.on(s.EVENT_LOW_MEMORY,this.internalLowMemory,this),this.Logger.Info(`🔥 ${this.App} ${this.Version}_${this.Build}`)}internalPause(){this._runningInternal=!1,this._timingPause=E(),this.Deps.EventBus.Sys.Notify(p.Void(_.ENTER_BACKGROUND))}internalResume(){this._runningInternal=!0,this._timingPause>0&&(this._elapsed=E()-this._timingPause,this._timingPause=0),this.Deps.EventBus.Sys.Notify(p.Void(_.ENTER_FOREGROUND))}internalClose(){this.Deps.EventBus.Sys.Notify(p.Void(_.QUIT)),n.targetOff(this),e.targetOff(this)}internalLowMemory(){this.Deps.EventBus.Sys.Notify(p.Void(_.LOW_MEMORY))}onSceneLaunch(t){var e,i;const n=this;if(n.Scene=t,n.Mnt=t.getChildByName("mnt"),n.Mnt||(n.Mnt=new a("mnt"),n.Mnt.layer=0,n.Scene.insertChild(this.Mnt,0)),n.Root=t.getChildByName("Canvas"),this.Root){if(n.Stage=n.Root.getChildByName("Stage"),!n.Stage){const t=new a("Stage");t.layer=o.BitMask.UI_2D;const e=t.addComponent(r);e.left=e.top=e.right=e.bottom=0,e.isAlignLeft=e.isAlignTop=e.isAlignBottom=e.isAlignRight=!0,e.alignMode=r.AlignMode.ON_WINDOW_RESIZE,e.updateAlignment(),n.Stage=t,this.Root.insertChild(this.Stage,1)}n.Canvas=n.Root.getComponent(l),n.CameraUi=null!==(i=null===(e=n.Root.getChildByName("Camera"))||void 0===e?void 0:e.getComponent(h))&&void 0!==i?i:null,this.CameraUi||this.Logger.Warn("启动警告:Canvas 缺少名为 Camera 的子节点,UI 相机不可用"),this.Deps.EventBus.Sys.Notify(p.Void(_.STAGE_READY))}else this.Logger.Error(`启动错误:场景 ${t.name} 缺少名为 Canvas 的根节点,舞台装配中止`)}IsBuildOf(t){return this.Build===t}IsLanguageSupported(t){return this.Cfg.Language.includes(t)}Pause(){this._runningLogic=!1}Resume(){this._runningLogic=!0}get Elapsed(){return this._elapsed}get Running(){return this._runningInternal&&this._runningLogic}}export{f as Build,_ as LAUNCHER_EVENT,R as LAUNCHER_RESERVED_KEYS,c as LAUNCHER_URL_OVERRIDABLE_KEYS,B as Launcher};
@@ -0,0 +1,189 @@
1
+ import { sys, director, Director, game, Game, Node, Layers, Widget, Canvas, Camera } from 'cc';
2
+ import { Override, Each } from '@nebulaaa/dict';
3
+ import { MISC } from '@nebulaaa/env';
4
+ import { EventMessage } from '@nebulaaa/event-bus';
5
+ import { Now } from '@nebulaaa/ms';
6
+ import { Plugin } from '@nebulaaa/plugin';
7
+ import { GetQueryString } from '@nebulaaa/web';
8
+
9
+ /**
10
+ * 应用构建版本
11
+ */
12
+ var Build;
13
+ (function (Build) {
14
+ /** 内部开发版 */
15
+ Build["Dev"] = "dev";
16
+ /** 内部测试版 */
17
+ Build["Alpha"] = "alpha";
18
+ /** 公开测试版 */
19
+ Build["Beta"] = "beta";
20
+ /** 候选发布版 */
21
+ Build["Candidate"] = "candidate";
22
+ /** 正式发布版 */
23
+ Build["Release"] = "release";
24
+ })(Build || (Build = {}));
25
+
26
+ /** 宏定义 */
27
+ const LAUNCHER_EVENT = {
28
+ /** 应用进入后台 */
29
+ ENTER_BACKGROUND: 'app@enter-background',
30
+ /** 应用进入前台 */
31
+ ENTER_FOREGROUND: 'app@enter-foreground',
32
+ /** 应用退出 */
33
+ QUIT: 'app@quit',
34
+ /** 应用低内存告警 */
35
+ LOW_MEMORY: 'app@low-memory',
36
+ /** 场景启动 */
37
+ STAGE_READY: 'app@stage-ready',
38
+ };
39
+ /** 允许经 URL 覆写的配置键白名单(可经 config.UrlKeys 追加) */
40
+ const LAUNCHER_URL_OVERRIDABLE_KEYS = ['Workspace'];
41
+ /** 保留键(扩展白名单也拒绝注入,防止 URL 伪装构建版本等核心语义) */
42
+ const LAUNCHER_RESERVED_KEYS = ['App', 'Version', 'Build', 'Language'];
43
+
44
+ /**
45
+ * 启动器
46
+ */
47
+ class Launcher extends Plugin {
48
+ constructor(deps, cfg) {
49
+ var _a;
50
+ Override(cfg !== null && cfg !== void 0 ? cfg : {}, {
51
+ app: 'nebulaaa',
52
+ version: '1.0.0',
53
+ build: Build.Dev,
54
+ language: [sys.Language.CHINESE, sys.Language.ENGLISH],
55
+ env: 'default',
56
+ });
57
+ if ((MISC.BROWSER && cfg.Build === Build.Dev) || cfg.Build === Build.Alpha) {
58
+ const qs = GetQueryString();
59
+ const whitelist = [...LAUNCHER_URL_OVERRIDABLE_KEYS, ...((_a = cfg.AllowedUrlKeys) !== null && _a !== void 0 ? _a : [])];
60
+ Each(qs, (key, value) => {
61
+ if (LAUNCHER_RESERVED_KEYS.includes(key))
62
+ return; // 保留键拒绝注入
63
+ if (!whitelist.includes(key))
64
+ return; // 白名单外的键拒绝注入
65
+ try {
66
+ cfg[key] = decodeURIComponent(value);
67
+ }
68
+ catch (_a) {
69
+ cfg[key] = value;
70
+ }
71
+ });
72
+ }
73
+ super(deps, cfg);
74
+ this.App = this.Cfg.App;
75
+ this.Version = this.Cfg.Version;
76
+ this.Build = this.Cfg.Build;
77
+ this.IsDev = this.Cfg.Build === Build.Dev;
78
+ this.IsAlpha = this.Cfg.Build === Build.Alpha;
79
+ this.IsBeta = this.Cfg.Build === Build.Beta;
80
+ this.IsCandidate = this.Cfg.Build === Build.Candidate;
81
+ this.IsRelease = this.Cfg.Build === Build.Release;
82
+ this.Workspace = this.Cfg.Workspace;
83
+ this.SupportedLanguages = this.Cfg.Language;
84
+ this._runningLogic = true;
85
+ this._runningInternal = true;
86
+ this._timingPause = 0;
87
+ this._elapsed = 0;
88
+ director.once(Director.EVENT_BEFORE_SCENE_LAUNCH, this.onSceneLaunch, this);
89
+ game.on(Game.EVENT_SHOW, this.internalResume, this);
90
+ game.on(Game.EVENT_HIDE, this.internalPause, this);
91
+ game.on(Game.EVENT_CLOSE, this.internalClose, this);
92
+ game.on(Game.EVENT_LOW_MEMORY, this.internalLowMemory, this);
93
+ this.Logger.Info(`🔥 ${this.App} ${this.Version}_${this.Build}`);
94
+ }
95
+ /** 应用退到后台 */
96
+ internalPause() {
97
+ this._runningInternal = false;
98
+ this._timingPause = Now();
99
+ this.Deps.EventBus.Sys.Notify(EventMessage.Void(LAUNCHER_EVENT.ENTER_BACKGROUND));
100
+ }
101
+ /** 应用回到前台 */
102
+ internalResume() {
103
+ this._runningInternal = true;
104
+ if (this._timingPause > 0) {
105
+ this._elapsed = Now() - this._timingPause; // 仅完整的 hide→show 往返计入,首启 SHOW 不产生垃圾值
106
+ this._timingPause = 0;
107
+ }
108
+ this.Deps.EventBus.Sys.Notify(EventMessage.Void(LAUNCHER_EVENT.ENTER_FOREGROUND));
109
+ }
110
+ /** 应用退出 */
111
+ internalClose() {
112
+ this.Deps.EventBus.Sys.Notify(EventMessage.Void(LAUNCHER_EVENT.QUIT));
113
+ game.targetOff(this);
114
+ director.targetOff(this);
115
+ }
116
+ /** 内存报警 */
117
+ internalLowMemory() {
118
+ // 巡检的节流周期由引擎帧驱动,低内存时须跳过等待立即清扫(此前误传秒值使清扫形同虚设)
119
+ this.Deps.EventBus.Sys.Notify(EventMessage.Void(LAUNCHER_EVENT.LOW_MEMORY));
120
+ }
121
+ /** 场景启动 */
122
+ onSceneLaunch(scene) {
123
+ var _a, _b;
124
+ const ctx = this;
125
+ // Scene
126
+ ctx.Scene = scene;
127
+ // Mnt
128
+ ctx.Mnt = scene.getChildByName('mnt');
129
+ if (!ctx.Mnt) {
130
+ ctx.Mnt = new Node('mnt');
131
+ ctx.Mnt.layer = 0;
132
+ ctx.Scene.insertChild(this.Mnt, 0);
133
+ }
134
+ // Root
135
+ ctx.Root = scene.getChildByName('Canvas');
136
+ if (!this.Root) {
137
+ // Canvas 是舞台的挂载前提,缺失时中止装配;仅记录日志,不向引擎事件派发抛出异常
138
+ this.Logger.Error(`启动错误:场景 ${scene.name} 缺少名为 Canvas 的根节点,舞台装配中止`);
139
+ return;
140
+ }
141
+ // Stage
142
+ ctx.Stage = ctx.Root.getChildByName('Stage');
143
+ if (!ctx.Stage) {
144
+ const node = new Node('Stage');
145
+ node.layer = Layers.BitMask.UI_2D;
146
+ const wid = node.addComponent(Widget);
147
+ wid.left = wid.top = wid.right = wid.bottom = 0;
148
+ wid.isAlignLeft = wid.isAlignTop = wid.isAlignBottom = wid.isAlignRight = true;
149
+ wid.alignMode = Widget.AlignMode.ON_WINDOW_RESIZE;
150
+ wid.updateAlignment();
151
+ ctx.Stage = node;
152
+ this.Root.insertChild(this.Stage, 1);
153
+ }
154
+ // Canvas
155
+ ctx.Canvas = ctx.Root.getComponent(Canvas);
156
+ // CameraUi
157
+ ctx.CameraUi = (_b = (_a = ctx.Root.getChildByName('Camera')) === null || _a === void 0 ? void 0 : _a.getComponent(Camera)) !== null && _b !== void 0 ? _b : null;
158
+ if (!this.CameraUi) {
159
+ this.Logger.Warn('启动警告:Canvas 缺少名为 Camera 的子节点,UI 相机不可用');
160
+ }
161
+ this.Deps.EventBus.Sys.Notify(EventMessage.Void(LAUNCHER_EVENT.STAGE_READY));
162
+ }
163
+ /** 是否指定构建版本 */
164
+ IsBuildOf(build) {
165
+ return this.Build === build;
166
+ }
167
+ /** 是否支持语言 */
168
+ IsLanguageSupported(language) {
169
+ return this.Cfg.Language.includes(language);
170
+ }
171
+ /** 暂停逻辑 */
172
+ Pause() {
173
+ this._runningLogic = false;
174
+ }
175
+ /** 恢复逻辑 */
176
+ Resume() {
177
+ this._runningLogic = true;
178
+ }
179
+ /** 从后台回到前台经历的时间(ms,未经历完整后台往返时为 0) */
180
+ get Elapsed() {
181
+ return this._elapsed;
182
+ }
183
+ /** 运行状态 */
184
+ get Running() {
185
+ return this._runningInternal && this._runningLogic;
186
+ }
187
+ }
188
+
189
+ export { Build, LAUNCHER_EVENT, LAUNCHER_RESERVED_KEYS, LAUNCHER_URL_OVERRIDABLE_KEYS, Launcher };
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@nebulaaa/launcher",
3
+ "version": "1.0.0",
4
+ "description": "启动器插件",
5
+ "author": "doooreyn",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "launcher",
9
+ "nebulaaa"
10
+ ],
11
+ "engines": {
12
+ "node": ">=16.0.0",
13
+ "npm": ">=10.0.0"
14
+ },
15
+ "main": "./launcher.cjs",
16
+ "module": "./launcher.mjs",
17
+ "types": "./launcher.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./launcher.d.ts",
21
+ "require": "./launcher.cjs",
22
+ "import": "./launcher.mjs",
23
+ "default": "./launcher.cjs"
24
+ },
25
+ "./min": {
26
+ "require": "./launcher.min.cjs",
27
+ "import": "./launcher.min.mjs"
28
+ }
29
+ },
30
+ "publishConfig": {
31
+ "registry": "https://registry.npmjs.org/",
32
+ "access": "public"
33
+ }
34
+ }
package/package.json CHANGED
@@ -1,6 +1,73 @@
1
- {
2
- "name": "@nebulaaa/launcher",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "@nebulaaa/launcher",
3
+ "version": "1.0.0",
4
+ "description": "启动器插件",
5
+ "main": "./dist/launcher.cjs",
6
+ "module": "./dist/launcher.mjs",
7
+ "types": "./dist/launcher.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "require": "./dist/launcher.cjs",
11
+ "import": "./dist/launcher.mjs",
12
+ "types": "./dist/launcher.d.ts",
13
+ "default": "./dist/launcher.cjs"
14
+ },
15
+ "./min": {
16
+ "require": "./dist/launcher.min.cjs",
17
+ "import": "./dist/launcher.min.mjs"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
25
+ "build": "npm run clean && rollup -c rollup.config.mjs && node ../scripts/write-dist-package.mjs launcher",
26
+ "test": "jest"
27
+ },
28
+ "keywords": [
29
+ "launcher",
30
+ "nebulaaa"
31
+ ],
32
+ "author": "doooreyn",
33
+ "license": "MIT",
34
+ "devEngines": {
35
+ "packageManager": {
36
+ "name": "npm",
37
+ "version": ">=10.0.0",
38
+ "onFail": "warn"
39
+ }
40
+ },
41
+ "engines": {
42
+ "node": ">=16.0.0",
43
+ "npm": ">=10.0.0"
44
+ },
45
+ "publishConfig": {
46
+ "registry": "https://registry.npmjs.org/",
47
+ "access": "public"
48
+ },
49
+ "allowScripts": {
50
+ "unrs-resolver@1.12.2": true,
51
+ "@parcel/watcher@2.6.0": true
52
+ },
53
+ "devDependencies": {
54
+ "@rollup/plugin-terser": "^1.0.2",
55
+ "@rollup/plugin-typescript": "^12.1.2",
56
+ "@types/jest": "^30.0.0",
57
+ "@types/node": "^22.10.2",
58
+ "jest": "^30.5.2",
59
+ "rollup": "^4.28.1",
60
+ "rollup-plugin-dts": "^6.1.1",
61
+ "ts-jest": "^29.1.2",
62
+ "ts-node": "^10.9.2",
63
+ "typescript": "5.7.3"
64
+ },
65
+ "dependencies": {
66
+ "@nebulaaa/dict": "^1.1.0",
67
+ "@nebulaaa/env": "^1.0.2",
68
+ "@nebulaaa/event-bus": "^1.0.0",
69
+ "@nebulaaa/ms": "^1.1.0",
70
+ "@nebulaaa/plugin": "^1.0.0",
71
+ "@nebulaaa/web": "^1.1.0"
72
+ }
73
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.