Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/devtools/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
15 changes: 15 additions & 0 deletions packages/devtools/package.json
Original file line number Diff line number Diff line change
@@ -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 changes: 4 additions & 0 deletions packages/devtools/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "devtools",
"$schema": "../../node_modules/nx/schemas/project-schema.json"
}
108 changes: 108 additions & 0 deletions packages/devtools/src/devtoolsRuntime.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
29 changes: 29 additions & 0 deletions packages/devtools/src/impl/ApplicationSettingsKeyValueProvider.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 16 additions & 0 deletions packages/devtools/src/index.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 161 additions & 0 deletions packages/devtools/src/providers/keyValueProvider.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 6 additions & 0 deletions packages/devtools/src/types.d.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 26 additions & 0 deletions packages/devtools/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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"]
}