mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 10:01:08 +08:00
feat: initial devtools package implementation (#10484)
This commit is contained in:
1
packages/devtools/.gitignore
vendored
Normal file
1
packages/devtools/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
dist/
|
15
packages/devtools/package.json
Normal file
15
packages/devtools/package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "@nativescript/devtools",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"files": [
|
||||
"./dist/"
|
||||
],
|
||||
"main": "./dist/index",
|
||||
"scripts": {
|
||||
"prepack": "tsc || echo ok"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nativescript/core": "../core"
|
||||
}
|
||||
}
|
4
packages/devtools/project.json
Normal file
4
packages/devtools/project.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "devtools",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json"
|
||||
}
|
108
packages/devtools/src/devtoolsRuntime.ts
Normal file
108
packages/devtools/src/devtoolsRuntime.ts
Normal file
@ -0,0 +1,108 @@
|
||||
// const domainDispatchers = new Map<string, any>();
|
||||
|
||||
export function DomainDispatcher(domain: string): ClassDecorator {
|
||||
return (klass) => {
|
||||
__registerDomainDispatcher(domain, klass);
|
||||
// if (!domainDispatchers.has(domain)) {
|
||||
// domainDispatchers.set(domain, klass);
|
||||
// } else {
|
||||
// console.trace(`Domain dispatcher for ${domain} already registered!`);
|
||||
// }
|
||||
};
|
||||
}
|
||||
|
||||
interface ProtocolMessage<T = any> {
|
||||
id: number;
|
||||
method: string;
|
||||
params: T;
|
||||
}
|
||||
|
||||
export class ProtocolWrapper {
|
||||
constructor() {
|
||||
return ProtocolWrapper.wrap(this);
|
||||
}
|
||||
|
||||
protected enabled: boolean = false;
|
||||
|
||||
enable() {
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
// private _enabled: boolean = false;
|
||||
|
||||
// get enabled() {
|
||||
// return this._enabled;
|
||||
// }
|
||||
|
||||
// enable() {
|
||||
// console.log(this.constructor.name, 'enable!');
|
||||
// this._enabled = true;
|
||||
// }
|
||||
|
||||
// disable() {
|
||||
// this._enabled = false;
|
||||
// }
|
||||
|
||||
public emit(name: string, params: any) {
|
||||
try {
|
||||
// console.info('[emit]', { method: name, params });
|
||||
__inspectorSendEvent(
|
||||
JSON.stringify({
|
||||
method: name,
|
||||
params,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
public timestamp(): number {
|
||||
return __inspectorTimestamp();
|
||||
}
|
||||
|
||||
private static async sendResponseToDevtools<T = any>(id: number, data: T) {
|
||||
try {
|
||||
const result = await data;
|
||||
if (result) {
|
||||
// console.info('[sendResponseToDevtools]', { id, result });
|
||||
__inspectorSendEvent(
|
||||
JSON.stringify({
|
||||
id,
|
||||
result,
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
private static wrap<T extends Object>(handler: T): T {
|
||||
return new Proxy(handler, {
|
||||
get(target, prop) {
|
||||
if (typeof target[prop] === 'function') {
|
||||
return (params, message: ProtocolMessage) => {
|
||||
try {
|
||||
// console.warn('[incoming]', {
|
||||
// id: message.id,
|
||||
// method: message.method,
|
||||
// params,
|
||||
// message,
|
||||
// });
|
||||
const res = target[prop](params, message);
|
||||
ProtocolWrapper.sendResponseToDevtools(message.id, res);
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
}) as T;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import { ApplicationSettings } from '@nativescript/core';
|
||||
import { KeyValueProvider } from '../providers/keyValueProvider';
|
||||
|
||||
/**
|
||||
* Exposes ApplicationSettings to Chrome Devtools
|
||||
*/
|
||||
export class ApplicationSettingsKeyValueProvider implements KeyValueProvider {
|
||||
getName(): string {
|
||||
return 'ApplicationSettings';
|
||||
}
|
||||
getKeys(): string[] {
|
||||
return ApplicationSettings.getAllKeys();
|
||||
}
|
||||
getValue(key: string) {
|
||||
return ApplicationSettings.getString(key);
|
||||
}
|
||||
setValue(key: string, value: string) {
|
||||
ApplicationSettings.setString(key, value);
|
||||
return true;
|
||||
}
|
||||
deleteKey(key: string) {
|
||||
ApplicationSettings.remove(key);
|
||||
return true;
|
||||
}
|
||||
clear() {
|
||||
ApplicationSettings.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
16
packages/devtools/src/index.ts
Normal file
16
packages/devtools/src/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { ApplicationSettingsKeyValueProvider } from './impl/ApplicationSettingsKeyValueProvider';
|
||||
import { KeyValueProviderRegistry } from './providers/keyValueProvider';
|
||||
|
||||
/**
|
||||
* Initializes the default devtools providers.
|
||||
*/
|
||||
export function initDevtools() {
|
||||
KeyValueProviderRegistry.registerKeyValueProvider(new ApplicationSettingsKeyValueProvider());
|
||||
}
|
||||
|
||||
export { KeyValueProviderRegistry, KeyValueProvider } from './providers/keyValueProvider';
|
||||
// export { SqlDatabaseProviderRegistry, SqlDatabaseProvider, SqlDatabaseResult } from './providers/sqlDatabaseProvider';
|
||||
// export { IndexedDBDatabaseProviderRegistry, IndexedDBDatabaseProvider, IndexedDBDatabaseResult } from './providers/indexedDbDatabaseProvider';
|
||||
// export { NetworkProviderRegistry, NetworkProvider } from './providers/networkProvider';
|
||||
|
||||
export { DomainDispatcher, ProtocolWrapper } from './devtoolsRuntime';
|
161
packages/devtools/src/providers/keyValueProvider.ts
Normal file
161
packages/devtools/src/providers/keyValueProvider.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import { DomainDispatcher, ProtocolWrapper } from '../devtoolsRuntime';
|
||||
|
||||
export interface KeyValueProvider {
|
||||
getName(): string;
|
||||
getKeys(): MaybePromise<string[]>;
|
||||
getValue(key: string): MaybePromise<string | undefined>;
|
||||
setValue(key: string, value: string): MaybePromise<boolean>;
|
||||
deleteKey(key: string): MaybePromise<boolean>;
|
||||
clear(): MaybePromise<boolean>;
|
||||
}
|
||||
|
||||
@DomainDispatcher('Storage')
|
||||
export class KeyValueProviderStorageHandler extends ProtocolWrapper {
|
||||
getStorageKeyForFrame(params) {
|
||||
if (params.frameId.startsWith('KeyValueProvider_')) {
|
||||
return {
|
||||
storageKey: `kv://${params.frameId.substr('KeyValueProvider_'.length)}`,
|
||||
};
|
||||
}
|
||||
|
||||
// return { storageKey: '' };
|
||||
}
|
||||
}
|
||||
|
||||
@DomainDispatcher('DOMStorage')
|
||||
export class KeyValueProviderRegistry extends ProtocolWrapper {
|
||||
private static providers: Map<string, KeyValueProvider> = new Map();
|
||||
|
||||
static registerKeyValueProvider(provider: KeyValueProvider) {
|
||||
this.providers.set(provider.getName(), provider);
|
||||
}
|
||||
|
||||
static getProviderFrames() {
|
||||
return Array.from(this.providers.keys()).map((name) => {
|
||||
console.log('provider', name);
|
||||
return {
|
||||
frame: {
|
||||
id: `KeyValueProvider_${name}`,
|
||||
loaderId: 'NSLoaderIdentifier',
|
||||
url: `kv://KV:${name}`,
|
||||
securityOrigin: '',
|
||||
mimeType: 'text/directory',
|
||||
},
|
||||
resources: [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
enable() {
|
||||
super.enable();
|
||||
console.log('enable KeyValueProviderRegistry');
|
||||
}
|
||||
|
||||
private getProviderFromParams(params) {
|
||||
const { storageKey, isLocalStorage } = params.storageId;
|
||||
if (!isLocalStorage) {
|
||||
return;
|
||||
}
|
||||
|
||||
return KeyValueProviderRegistry.providers.get(storageKey.replace('kv://', ''));
|
||||
}
|
||||
|
||||
async getDOMStorageItems(params) {
|
||||
const provider = this.getProviderFromParams(params);
|
||||
if (!provider) {
|
||||
return { entries: [] };
|
||||
}
|
||||
const keys = await provider.getKeys();
|
||||
const entries = [];
|
||||
for await (const key of keys) {
|
||||
const value = await provider.getValue(key);
|
||||
entries.push([key, value]);
|
||||
}
|
||||
|
||||
return {
|
||||
entries,
|
||||
};
|
||||
}
|
||||
|
||||
async removeDOMStorageItem(params) {
|
||||
const provider = this.getProviderFromParams(params);
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
const res = await provider.deleteKey(params.key);
|
||||
|
||||
if (res) {
|
||||
this.emit('DOMStorage.domStorageItemRemoved', {
|
||||
storageId: params.storageId,
|
||||
key: params.key,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async setDOMStorageItem(params) {
|
||||
const provider = this.getProviderFromParams(params);
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
const oldValue = (await provider.getValue(params.key)) ?? undefined;
|
||||
const res = await provider.setValue(params.key, params.value);
|
||||
if (res) {
|
||||
this.emit(oldValue ? 'DOMStorage.domStorageItemUpdated' : 'DOMStorage.domStorageItemAdded', {
|
||||
storageId: params.storageId,
|
||||
key: params.key,
|
||||
oldValue,
|
||||
newValue: params.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async clear(params) {
|
||||
const provider = this.getProviderFromParams(params);
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await provider.clear();
|
||||
|
||||
if (res) {
|
||||
this.emit('DOMStorage.domStorageItemsCleared', {
|
||||
storageId: params.storageId,
|
||||
});
|
||||
|
||||
// re-emit all remaining values after the clean (system may have added new values)
|
||||
const keys = await provider.getKeys();
|
||||
for await (const key of keys) {
|
||||
const value = await provider.getValue(key);
|
||||
this.emit('DOMStorage.domStorageItemAdded', {
|
||||
storageId: params.storageId,
|
||||
key,
|
||||
newValue: value,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DomainDispatcher('Page')
|
||||
export class KeyValueProviderPageHandler extends ProtocolWrapper {
|
||||
enable(): void {
|
||||
super.enable();
|
||||
KeyValueProviderRegistry.getProviderFrames().forEach(({ frame }) => {
|
||||
this.emit('Page.frameStartedLoading', {
|
||||
frameId: frame.id,
|
||||
});
|
||||
// this.emit('Page.domContentEventFired', {
|
||||
// timestamp: this.timestamp(),
|
||||
// });
|
||||
// this.emit('Page.loadEventFired', {
|
||||
// timestamp: this.timestamp(),
|
||||
// });
|
||||
this.emit('Page.frameNavigated', {
|
||||
frame,
|
||||
});
|
||||
this.emit('Page.frameStoppedLoading', {
|
||||
frameId: frame.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
6
packages/devtools/src/types.d.ts
vendored
Normal file
6
packages/devtools/src/types.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
// Note: these functions are provided by the NativeScript runtimes (@nativescript/ios and @nativescript/android).
|
||||
declare function __registerDomainDispatcher(domain: string, dispatcher: any): void;
|
||||
declare function __inspectorSendEvent(data: string): void;
|
||||
declare function __inspectorTimestamp(): number;
|
||||
|
||||
type MaybePromise<T> = T | Promise<T>;
|
26
packages/devtools/tsconfig.json
Normal file
26
packages/devtools/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"baseUrl": ".",
|
||||
"target": "ES2020",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./dist",
|
||||
"declaration": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["es2017"],
|
||||
"sourceMap": true,
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"diagnostics": true,
|
||||
"paths": {
|
||||
"@nativescript/devtools": ["src"]
|
||||
},
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"stripInternal": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Reference in New Issue
Block a user