Skip to content

Commit

Permalink
First-draft of Services infrastructure (#2985)
Browse files Browse the repository at this point in the history
* begin services work

* flesh out services impl

* `service` method moved to Particle class

* dump unused ServiceRequestCallback

* better description

* accommodate tslint

* move `waltbird` into repo as particle asset

* enable shorthand service invocation

* do a fandango to appease lint

* fix argument
  • Loading branch information
Scott J. Miles authored May 9, 2019
1 parent 1bef847 commit 7837959
Show file tree
Hide file tree
Showing 18 changed files with 201 additions and 31 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
shells/services/dynamic-import.js
4 changes: 4 additions & 0 deletions particles/Services/Services.recipes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import 'particles/ServiceTest.particle'

recipe ServiceTest
ServiceTest
Binary file added particles/Services/assets/waltbird.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions particles/Services/particles/ServiceTest.particle
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
particle ServiceTest in './js/ServiceTest.js'
consume root
50 changes: 50 additions & 0 deletions particles/Services/particles/js/ServiceTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright (c) 2019 Google Inc. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* Code distributed by Google as part of this project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/

'use strict';

defineParticle(({DomParticle, log, html, resolver}) => {

const template = html`
<div>
<img style="max-width: 240px;" src="{{imageUrl}}"><br>
<div>
<div>Label: </span><span>{{label}}</div>
<div>Confidence: </span><span>{{probability}}</div>
</div>
</div>
`;

const url = resolver(`ServiceTest/../../assets/waltbird.jpg`);

return class extends DomParticle {
get template() {
return template;
}
update({}, state) {
if (!state.classified) {
state.classified = true;
this.classify(url);
}
}
async classify(imageUrl) {
const response = await this.service({call: 'ml5.classifyImage', imageUrl});
this.setState({response});
}
render({}, {response}) {
response = response || {label: '<working>', probability: '<working>'};
return {
label: response.label,
probability: response.probability,
imageUrl: url
};
}
};

});
1 change: 0 additions & 1 deletion particles/TVMaze/particles/js/TVMazeDeduplicate.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/**
* @license
* Copyright (c) 2019 Google Inc. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
Expand Down
1 change: 1 addition & 0 deletions particles/canonical.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'Restaurants/Restaurants.recipes'
import 'Music/Music.recipes'
import 'TVMaze/TVMaze.recipes'
import 'Words/Words.recipes'
import 'Services/Services.recipes'
import 'Demo/Demo.recipes'

// Uncomment to enable tutorial recipes. See Tutorial/tutorial.md for details.
Expand Down
4 changes: 4 additions & 0 deletions shells/configuration/csp.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// TODO: get rid of unsafe-inline for style?
// TODO: construct whitelist dynamically from particle manifests
// OR use service worker to enforce
//
// TODO(sjmiles): explain failure of `script-src 'strict-dynamic'`
// TODO(sjmiles): less-than-ideal unsafe-eval is turned on for ml5 service
//
const httpEquiv = 'Content-Security-Policy';
const content = `
script-src
'self'
'unsafe-eval'
blob:
wss://*.firebase.io wss://*.firebaseio.com
https://*.firebaseio.com
Expand All @@ -43,6 +46,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
https://*.tvmaze.com
https://media.w3.org
https://*.glitch.me
https://unpkg.com
;
font-src
'self'
Expand Down
3 changes: 3 additions & 0 deletions shells/configuration/whitelisted.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ import '../../modalities/dom/components/elements/dom-repeater.js';

// requires app-level firebase configuration
import '../lib/database/firebase-upload.js';

// services for particle use
import '../services/ml5-service.js';
9 changes: 9 additions & 0 deletions shells/services/dynamic-import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Copyright (c) 2019 Google Inc. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* Code distributed by Google as part of this project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
export const dynamicImport = path => import(path);
42 changes: 42 additions & 0 deletions shells/services/ml5-service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2019 Google Inc. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* Code distributed by Google as part of this project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
import {dynamicImport} from './dynamic-import.js';
import {Services} from '../../build/runtime/services.js';

const requireMl5 = async () => {
if (!window.ml5) {
await dynamicImport('https://unpkg.com/[email protected]/dist/ml5.min.js');
}
};

const classifyImage = async ({imageUrl}) => {
console.log('classifying...');
await requireMl5();
const image = await loadImage(imageUrl);
const classifier = await window.ml5.imageClassifier('MobileNet');
const results = await classifier.classify(image);
const result = results.shift();
console.log('classifying done.');
return {
label: result.label,
probability: result.confidence.toFixed(4)
};
};

const loadImage = async url => {
return new Promise((resolve) => {
const image = new Image();
image.src = url;
image.onload = async () => resolve(image);
});
};

Services.register('ml5', {
classifyImage
});
19 changes: 3 additions & 16 deletions shells/single-shell/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ import 'https://$particles/Profile/Sharing.recipe'
`);
console.log(`context [${context.id}]`);

await installSystemUser({userid: 'gomer', context});
console.log('installed SYSTEM_user');

const manifest = await Utils.parse(`import 'https://$particles/${manifestPath || 'Arcs/Login.recipe'}'`);
console.log(`manifest [${manifest.id}]`);

Expand All @@ -22,21 +19,11 @@ import 'https://$particles/Profile/Sharing.recipe'
const plan = await Utils.resolve(arc, recipe);
await arc.instantiate(plan);

console.log(`store [${arc._stores[0].id}]`);
if (arc._stores[0]) {
console.log(`store [${arc._stores[0].id}]`);
}
console.log('serialization:', await arc.serialize());

return arc;
};

async function installSystemUser({userid, context}) {
const store = await context.findStoreById('SYSTEM_user');
if (store) {
const user = {
id: store.generateID(),
rawData: {
id: userid,
}
};
store.set(user);
}
}
12 changes: 6 additions & 6 deletions shells/single-shell/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,20 @@
<div slotid="root"></div>
<div slotid="modal"></div>

<script>window.logLevel = 2;</script>
<script type="module">
import {Env} from '../lib/env/web/env.js';
import {SlotComposer} from '../lib/arcs.js';
import '../services/ml5-service.js';
import {Utils} from '../lib/runtime/utils.js';
import {DomSlotComposer} from '../lib/components/dom-slot-composer.js';
import {App} from './app.js';
import '../configuration/whitelisted.js';

const getUrlParam = name => {
return new URL(document.location.href).searchParams.get(name);
}
const env = new Env('../../');
const utils = Utils.init('../../');

const composer = new SlotComposer({
modalityName: Modality.Name.Dom,
modalityHandler: PlanningModalityHandler.domHandler,
const composer = new DomSlotComposer({
rootContainer: document.body
});

Expand Down
20 changes: 13 additions & 7 deletions src/runtime/api-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ function List(value: MappingType) {
return (target: {}, propertyKey: string, parameterIndex: number) => {
const info: MappingInfo = {type: MappingType.List, value: {type: value}};
set(target.constructor, propertyKey, parameterIndex, info);
};
};
}

function LocalMapped(target: {}, propertyKey: string, parameterIndex: number) {
Expand Down Expand Up @@ -252,7 +252,7 @@ export class APIPort {
function getArgs(func) {
// First match everything inside the function argument parens.
const args = func.toString().match(/.*?\(([^)]*)\)/)[1];

// Split the arguments string into an array comma delimited.
return args.split(',').map((arg) => {
// Ensure no inline comments are parsed and trim the whitespace.
Expand All @@ -262,7 +262,7 @@ function getArgs(func) {
}

// value is covariant with info, and errors will be found
// at start of runtime.
// at start of runtime.
// tslint:disable-next-line: no-any
function convert(info: MappingInfo, value: any, mapper: ThingMapper) {
switch (info.type) {
Expand All @@ -289,7 +289,7 @@ function convert(info: MappingInfo, value: any, mapper: ThingMapper) {
}

// value is covariant with info, and errors will be found
// at start of runtime.
// at start of runtime.
// tslint:disable-next-line: no-any
function unconvert(info: MappingInfo, value: any, mapper: ThingMapper) {
switch (info.type) {
Expand Down Expand Up @@ -331,7 +331,7 @@ function AutoConstruct<S extends {prototype: {}}>(target: S) {
// If this descriptor records that this argument is the identifier, record it
// as the requestedId for mapping below.
const requestedId = descriptor.findIndex(d => d.identifier);

function impl(this: APIPort, ...args) {
const messageBody = {};
for (let i = 0; i < descriptor.length; i++) {
Expand All @@ -342,7 +342,7 @@ function AutoConstruct<S extends {prototype: {}}>(target: S) {
// Process this argument.
messageBody[argNames[i]] = convert(descriptor[i], args[i], this._mapper);
}

// Process the initializer if present.
if (initializer !== -1) {
if (descriptor[initializer].redundant) {
Expand Down Expand Up @@ -403,7 +403,7 @@ function AutoConstruct<S extends {prototype: {}}>(target: S) {
});
}
};

doConstruct(constructor, target);
doConstruct(target, constructor);
};
Expand Down Expand Up @@ -462,6 +462,9 @@ export abstract class PECOuterPort extends APIPort {
abstract onArcLoadRecipe(arc: Arc, recipe: string, callback: number);
abstract onReportExceptionInHost(exception: PropagatedException);

// TODO(sjmiles): experimental `services` impl
abstract onServiceRequest(particle: recipeParticle.Particle, request: {}, callback: number);

// We need an API call to tell the context side that DevTools has been connected, so it can start sending
// stack traces attached to the API calls made from that side.
@NoArgs DevToolsConnected() {}
Expand Down Expand Up @@ -515,6 +518,9 @@ export abstract class PECInnerPort extends APIPort {
ArcMapHandle(@LocalMapped callback: (value: string) => void, @RemoteMapped arc: {}, @Mapped handle: Handle) {}
abstract onMapHandleCallback(callback: (value: string) => void, id: string);

// TODO(sjmiles): experimental `services` impl
ServiceRequest(@Mapped particle: Particle, @Direct content: {}, @LocalMapped callback: Function) {}

ArcCreateSlot(@LocalMapped callback: (value: string) => void, @RemoteMapped arc: {}, @Mapped transformationParticle: Particle, @Direct transformationSlotName: string, @Direct handleId: string) {}
abstract onCreateSlotCallback(callback: (value: string) => void, hostedSlotId: string);
abstract onInnerArcRender(transformationParticle: Particle, transformationSlotName: string, hostedSlotID: string, content: string);
Expand Down
6 changes: 5 additions & 1 deletion src/runtime/particle-execution-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export class ParticleExecutionContext {
// TODO: support multiple handle IDs.
return new Promise((resolve, reject) =>
pec.apiPort.ArcCreateSlot(hostedSlotId => resolve(hostedSlotId), arcId, transformationParticle, transformationSlotName, handleId)
);
);
},
loadRecipe(recipe: string) {
// TODO: do we want to return a promise on completion?
Expand Down Expand Up @@ -191,6 +191,10 @@ export class ParticleExecutionContext {
constructInnerArc: particle => {
return new Promise<InnerArcHandle>((resolve, reject) =>
this.apiPort.ConstructInnerArc(arcId => resolve(this.innerArcHandle(arcId, particle.id)), particle));
},
// TODO(sjmiles): experimental `services` impl
serviceRequest: (particle, args, callback) => {
this.apiPort.ServiceRequest(particle, args, callback);
}
};
}
Expand Down
8 changes: 8 additions & 0 deletions src/runtime/particle-execution-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {SlotComposer} from './slot-composer.js';
import {Content} from './slot-consumer.js';
import {BigCollectionStorageProvider, CollectionStorageProvider, StorageProviderBase, VariableStorageProvider} from './storage/storage-provider-base.js';
import {Type} from './type.js';
import {Services} from './services.js';

export type StartRenderOptions = {
particle: Particle;
Expand Down Expand Up @@ -246,6 +247,13 @@ export class ParticleExecutionHost {
}
reportSystemException(exception);
}

// TODO(sjmiles): experimental `services` impl
async onServiceRequest(particle: Particle, request: {}, callback: number): Promise<void> {
const response = await Services.request(request);
this.SimpleCallback(callback, response);
}

}(port, arc);
}

Expand Down
14 changes: 14 additions & 0 deletions src/runtime/particle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,20 @@ export class Particle {
this.slotProxiesByName.delete(name);
}

/**
* Request (outerPEC) service invocations.
*/
// TODO(sjmiles): experimental services impl
async service(request) {
if (!this.capabilities["serviceRequest"]) {
console.warn(`${this.spec.name} has no service support.`);
return null;
}
return new Promise(resolve => {
this.capabilities["serviceRequest"](this, request, response => resolve(response));
});
}

/**
* Returns the slot with provided name.
*/
Expand Down
Loading

0 comments on commit 7837959

Please sign in to comment.