mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 18:12:09 +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