Skip to content

Commit

Permalink
✨ Assist.js: Add a shared FrameService to assistjs extension (#32784)
Browse files Browse the repository at this point in the history
* Create the skeleton of the new extension for assist.js: amp-google-assistant-assistjs. It consists of one shared service and multiple custom elements.

* Remove unneeded regenerator-
runtime dependency in package.json

* - Add/modify copyright statement and header comment for each file
- Add appropriate validator rules for the new extension
- Add more detailed description about what this extension does

* Add a shared service(AssistjsConfigService) and a new config element. Also includes some minor refactoring.

The service provides config to other components and the element take prespecified config json.

* Fix styling and remove commented code in AssistjsFrameService.

* Improve config service:
- Ensure config json retrieval
- Move widget iframe url creation into one single place.
- Add assertion to ensure there's only one config element on the page

Minor fixes:
- Replace await usage with then to avoid extra dependencies.
- Update validator rules for the invisible config element

* Fix prettify errors.

* Unit test fix and some minor fixes.

* Fix lint errors.

* Update extensions/amp-google-assistant-assistjs/0.1/assistjs-frame-service.js

Co-authored-by: Justin Ridgewell <justin@ridgewell.name>

* Minor fixes.

* Fix errors caused by not having this.win in a service class(non-AMP.BaseElement).
Also add a config service mock to the unit test.

* ampdoc.getWin() is forbidden. Replace it with "window" directly.

* - Append iframe to the document after frame src is retrieved
- Parse config in config element to save a DOM query
- Delete assistjs-frame-service.js. Add it in next PR with real usage.

* Fix FrameService reference errors and lint errors.

* Add a shared FrameService for all custom elements to handle requests from underlying iframes.
Actual implementation of two endpoints: OpenMic and SendTextQuery is empty now and will be implemented in upcoming PRs.

* Use ampdoc.whenready instead to fix the loading error.
Add sandbox attributes to all iframes.

* Update extensions/amp-google-assistant-assistjs/0.1/assistjs-frame-service.js

Co-authored-by: Justin Ridgewell <justin@ridgewell.name>

* Update extensions/amp-google-assistant-assistjs/0.1/assistjs-frame-service.js

Co-authored-by: Justin Ridgewell <justin@ridgewell.name>

Co-authored-by: Justin Ridgewell <justin@ridgewell.name>
  • Loading branch information
ychsieh and jridgewell committed Feb 25, 2021
1 parent 9597423 commit 476d99c
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 0 deletions.
Expand Up @@ -23,6 +23,7 @@ import {AmpGoogleAssistantInlineSuggestionBar} from './amp-google-assistant-inli
import {AmpGoogleAssistantVoiceBar} from './amp-google-assistant-voice-bar';
import {AmpGoogleAssistantVoiceButton} from './amp-google-assistant-voice-button';
import {AssistjsConfigService} from './assistjs-config-service';
import {AssistjsFrameService} from './assistjs-frame-service';
import {CSS} from '../../../build/amp-google-assistant-assistjs-0.1.css';
import {Services} from '../../../src/services';

Expand All @@ -38,6 +39,7 @@ export class AmpGoogleAssistantAssistjsConfig extends AMP.BaseElement {

AMP.extension('amp-google-assistant-assistjs', '0.1', (AMP) => {
AMP.registerServiceForDoc('assistjs-config-service', AssistjsConfigService);
AMP.registerServiceForDoc('assistjs-frame-service', AssistjsFrameService);
AMP.registerElement(
'amp-google-assistant-assistjs-config',
AmpGoogleAssistantAssistjsConfig,
Expand Down
Expand Up @@ -30,11 +30,15 @@ export class AmpGoogleAssistantInlineSuggestionBar extends AMP.BaseElement {

/** @private {?AssistjsConfigService} */
this.configService_ = null;

/** @private {?AssistjsFrameService} */
this.frameService_ = null;
}

/** @override */
buildCallback() {
this.configService_ = Services.assistjsConfigServiceForDoc(this.element);
this.frameService_ = Services.assistjsFrameServiceForDoc(this.element);
}

/** @override */
Expand All @@ -51,6 +55,7 @@ export class AmpGoogleAssistantInlineSuggestionBar extends AMP.BaseElement {
.then((iframeUrl) => {
addAttributesToElement(iframe, {
src: iframeUrl,
sandbox: 'allow-scripts',
});

// applyFillContent so that frame covers the entire component.
Expand All @@ -59,6 +64,11 @@ export class AmpGoogleAssistantInlineSuggestionBar extends AMP.BaseElement {
this.element.appendChild(iframe);
});

iframe.addEventListener('load', () => {
// TODO: create a channel to receive requests from underlying assist.js iframe.
this.frameService_.sendTextQuery();
});

// Return a load promise for the frame so the runtime knows when the
// component is ready.
return this.loadPromise(iframe);
Expand Down
Expand Up @@ -30,11 +30,15 @@ export class AmpGoogleAssistantVoiceBar extends AMP.BaseElement {

/** @private {?AssistjsConfigService} */
this.configService_ = null;

/** @private {?AssistjsFrameService} */
this.frameService_ = null;
}

/** @override */
buildCallback() {
this.configService_ = Services.assistjsConfigServiceForDoc(this.element);
this.frameService_ = Services.assistjsFrameServiceForDoc(this.element);
}

/** @override */
Expand All @@ -49,6 +53,7 @@ export class AmpGoogleAssistantVoiceBar extends AMP.BaseElement {
this.configService_.getWidgetIframeUrl('voicebar').then((iframeUrl) => {
addAttributesToElement(iframe, {
src: iframeUrl,
sandbox: 'allow-scripts',
});

// applyFillContent so that frame covers the entire component.
Expand All @@ -57,6 +62,12 @@ export class AmpGoogleAssistantVoiceBar extends AMP.BaseElement {
this.element.appendChild(iframe);
});

iframe.addEventListener('load', () => {
// TODO: create a channel to receive requests from underlying assist.js iframe.
this.frameService_.openMic();
this.frameService_.sendTextQuery();
});

// Return a load promise for the frame so the runtime knows when the
// component is ready.
return this.loadPromise(iframe);
Expand Down
Expand Up @@ -30,11 +30,15 @@ export class AmpGoogleAssistantVoiceButton extends AMP.BaseElement {

/** @private {?AssistjsConfigService} */
this.configService_ = null;

/** @private {?AssistjsFrameService} */
this.frameService_ = null;
}

/** @override */
buildCallback() {
this.configService_ = Services.assistjsConfigServiceForDoc(this.element);
this.frameService_ = Services.assistjsFrameServiceForDoc(this.element);
}

/** @override */
Expand All @@ -49,6 +53,7 @@ export class AmpGoogleAssistantVoiceButton extends AMP.BaseElement {
this.configService_.getWidgetIframeUrl('voicebutton').then((iframeUrl) => {
addAttributesToElement(iframe, {
src: iframeUrl,
sandbox: 'allow-scripts',
});

// applyFillContent so that frame covers the entire component.
Expand All @@ -57,6 +62,11 @@ export class AmpGoogleAssistantVoiceButton extends AMP.BaseElement {
this.element.appendChild(iframe);
});

iframe.addEventListener('load', () => {
// TODO: create a channel to receive requests from underlying assist.js iframe.
this.frameService_.openMic();
});

// Return a load promise for the frame so the runtime knows when the
// component is ready.
return this.loadPromise(iframe);
Expand Down
@@ -0,0 +1,71 @@
/**
* Copyright 2021 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @fileoverview The shared service used by all custom elements to talk to Assistant services. It loads an iframe that exports
* endpoints to handle requests from custom elements.
*/

import {Services} from '../../../src/services';
import {addAttributesToElement} from '../../../src/dom';
import {toggle} from '../../../src/style';

export class AssistjsFrameService {
/**
* @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc
*/
constructor(ampdoc) {
/** @private @const {!../../../src/service/ampdoc-impl.AmpDoc} */
this.ampDoc_ = ampdoc;

/** @private {?AssistjsConfigService} */
this.configService_ = null;

/** Create Assistant iframe and append it to the main AMP document. */
this.createAssistantIframe_();
}

/** @private */
createAssistantIframe_() {
this.ampDoc_.whenFirstVisible().then(() => {
this.configService_ = Services.assistjsConfigServiceForDoc(this.ampDoc_);
const iframe = this.ampDoc_.win.document.createElement('iframe');
this.configService_.getWidgetIframeUrl('frame').then((iframeUrl) => {
addAttributesToElement(iframe, {
src: iframeUrl,
allow: 'microphone',
sandbox: 'allow-scripts',
});
toggle(iframe, false);
document.body.appendChild(iframe);
});
});
}

/**
* Activates Assistant microphone on 3P page.
*/
openMic() {
// TODO: add implementation once the channels for iframes are implemented.
}

/**
* Sends text query to Assistant server.
*/
sendTextQuery() {
// TODO: add implementation once the channels for iframes are implemented.
}
}
Expand Up @@ -41,6 +41,11 @@ describes.realWin(
.withExactArgs('voicebutton')
.once();

const frameServiceMock = env.sandbox.mock(
Services.assistjsFrameServiceForDoc(document)
);
frameServiceMock.expects('openMic').once();

configElement = document.createElement(
'amp-google-assistant-assistjs-config'
);
Expand Down

0 comments on commit 476d99c

Please sign in to comment.