Implement basic postMessage widget API components
This implements a 'screenshot' button on the generic widget wrapper, although there's several bugs with the current Riot implementation that prevent it from working and that is why it is commented out. Fixes #155. Even if it doesn't work.
This commit is contained in:
parent
d6b4645cb9
commit
17656e8cf7
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -2268,6 +2268,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"dom-to-image": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-to-image/-/dom-to-image-2.6.0.tgz",
|
||||
"integrity": "sha1-ilA2CAiMh7HCL5A0rgMuGJiVWGc=",
|
||||
"dev": true
|
||||
},
|
||||
"domain-browser": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
"core-js": "^2.5.3",
|
||||
"css-loader": "^0.28.11",
|
||||
"cssnano": "^3.10.0",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"embed-video": "^2.0.0",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "^1.1.11",
|
||||
|
|
|
@ -70,6 +70,7 @@ import { AdminIrcBridgeNetworksComponent } from "./admin/bridges/irc/networks/ne
|
|||
import { AdminIrcBridgeAddSelfhostedComponent } from "./admin/bridges/irc/add-selfhosted/add-selfhosted.component";
|
||||
import { IrcBridgeConfigComponent } from "./configs/bridge/irc/irc.bridge.component";
|
||||
import { IrcApiService } from "./shared/services/integrations/irc-api.service";
|
||||
import { ScreenshotCapableDirective } from "./shared/directives/screenshot-capable.directive";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -132,6 +133,7 @@ import { IrcApiService } from "./shared/services/integrations/irc-api.service";
|
|||
AdminIrcBridgeNetworksComponent,
|
||||
AdminIrcBridgeAddSelfhostedComponent,
|
||||
IrcBridgeConfigComponent,
|
||||
ScreenshotCapableDirective,
|
||||
|
||||
// Vendor
|
||||
],
|
||||
|
|
36
web/app/shared/directives/screenshot-capable.directive.ts
Normal file
36
web/app/shared/directives/screenshot-capable.directive.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { Directive, ElementRef, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
import { ScalarWidgetApi } from "../services/scalar/scalar-widget.api";
|
||||
import { ScalarToWidgetRequest } from "../models/scalar-widget-actions";
|
||||
import * as domtoimage from "dom-to-image";
|
||||
|
||||
@Directive({
|
||||
selector: "[myScreenshotCapable]",
|
||||
})
|
||||
export class ScreenshotCapableDirective implements OnInit, OnDestroy {
|
||||
|
||||
private widgetApiSubscription: Subscription;
|
||||
|
||||
constructor(private el: ElementRef) {
|
||||
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.widgetApiSubscription = ScalarWidgetApi.requestReceived.subscribe(request => {
|
||||
if (request.action === "screenshot") this.takeScreenshot(request);
|
||||
})
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
if (this.widgetApiSubscription) this.widgetApiSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
private takeScreenshot(request: ScalarToWidgetRequest) {
|
||||
domtoimage.toBlob(this.el.nativeElement).then(b => {
|
||||
ScalarWidgetApi.replyScreenshot(request, b);
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
ScalarWidgetApi.replyError(request, error, "Failed to take screenshot");
|
||||
});
|
||||
}
|
||||
}
|
7
web/app/shared/models/scalar-widget-actions.ts
Normal file
7
web/app/shared/models/scalar-widget-actions.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export interface ScalarToWidgetRequest {
|
||||
api: "to_widget";
|
||||
action: "supported_api_versions" | "screenshot" | "capabilities" | "send_event" | "visibility_change" | string;
|
||||
requestId: string;
|
||||
widgetId: string;
|
||||
data?: any;
|
||||
}
|
74
web/app/shared/services/scalar/scalar-widget.api.ts
Normal file
74
web/app/shared/services/scalar/scalar-widget.api.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { ScalarToWidgetRequest } from "../../models/scalar-widget-actions";
|
||||
import { ReplaySubject } from "rxjs/ReplaySubject";
|
||||
import { Subject } from "rxjs/Subject";
|
||||
|
||||
export class ScalarWidgetApi {
|
||||
|
||||
public static requestReceived: Subject<ScalarToWidgetRequest> = new ReplaySubject();
|
||||
private static widgetId: string;
|
||||
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
public static setWidgetId(id: string) {
|
||||
ScalarWidgetApi.widgetId = id;
|
||||
}
|
||||
|
||||
public static test() {
|
||||
ScalarWidgetApi.callAction(null, null);
|
||||
}
|
||||
|
||||
public static replyScreenshot(request: ScalarToWidgetRequest, data: Blob): void {
|
||||
ScalarWidgetApi.replyEvent(request, {screenshot: data});
|
||||
}
|
||||
|
||||
public static replySupportedVersions(request: ScalarToWidgetRequest, versions: string[]): void {
|
||||
ScalarWidgetApi.replyEvent(request, {supported_versions: versions});
|
||||
}
|
||||
|
||||
public static replyCapabilities(request: ScalarToWidgetRequest,
|
||||
capabilities: (
|
||||
"m.text" | "m.image" | "m.sticker" | "m.capability.screenshot" | "m.capability.request_data" |
|
||||
"m.capability.request_messages" | "m.capability.room_membership" | string)[]
|
||||
): void {
|
||||
ScalarWidgetApi.replyEvent(request, {capabilities: capabilities});
|
||||
}
|
||||
|
||||
public static replyError(request: ScalarToWidgetRequest, error: Error, message: string = null): void {
|
||||
ScalarWidgetApi.replyEvent(request, {error: {message: message || error.message, error: error}});
|
||||
}
|
||||
|
||||
private static callAction(action, payload) {
|
||||
if (!window.opener) {
|
||||
return;
|
||||
}
|
||||
|
||||
let request = JSON.parse(JSON.stringify(payload));
|
||||
request["api"] = "fromWidget";
|
||||
request["widgetId"] = ScalarWidgetApi.widgetId;
|
||||
request["action"] = action;
|
||||
|
||||
window.opener.postMessage(request, "*");
|
||||
}
|
||||
|
||||
private static replyEvent(request: ScalarToWidgetRequest, payload) {
|
||||
if (!window.opener) {
|
||||
return;
|
||||
}
|
||||
|
||||
let response = JSON.parse(JSON.stringify(payload));
|
||||
let requestClone = JSON.parse(JSON.stringify(request));
|
||||
requestClone["response"] = response;
|
||||
window.opener.postMessage(requestClone, "*");
|
||||
}
|
||||
}
|
||||
|
||||
// Register the event listener here to ensure it gets created
|
||||
window.addEventListener("message", event => {
|
||||
if (!event.data) return;
|
||||
|
||||
if (event.data.api === "toWidget" && event.data.action) {
|
||||
ScalarWidgetApi.requestReceived.next(event.data);
|
||||
return;
|
||||
}
|
||||
});
|
|
@ -8,6 +8,6 @@
|
|||
<h4>Sorry, this content cannot be embedded</h4>
|
||||
</div>
|
||||
</div>
|
||||
<iframe [src]="embedUrl" *ngIf="!isLoading && canEmbed" frameborder="0" allowfullscreen></iframe>
|
||||
<iframe [src]="embedUrl" *ngIf="!isLoading && canEmbed" frameborder="0" allowfullscreen myScreenshotCapable=""></iframe>
|
||||
<my-fullscreen-button *ngIf="!isLoading && canEmbed" class="toggleFullscreen"></my-fullscreen-button>
|
||||
</div>
|
|
@ -1,22 +1,29 @@
|
|||
import { Component } from "@angular/core";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { DomSanitizer, SafeUrl } from "@angular/platform-browser";
|
||||
import { WidgetApiService } from "../../shared/services/integrations/widget-api.service";
|
||||
import { ScalarWidgetApi } from "../../shared/services/scalar/scalar-widget.api";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
import { ScalarToWidgetRequest } from "../../shared/models/scalar-widget-actions";
|
||||
|
||||
@Component({
|
||||
selector: "my-generic-widget-wrapper",
|
||||
templateUrl: "generic.component.html",
|
||||
styleUrls: ["generic.component.scss"],
|
||||
})
|
||||
export class GenericWidgetWrapperComponent {
|
||||
export class GenericWidgetWrapperComponent implements OnInit, OnDestroy {
|
||||
|
||||
public isLoading = true;
|
||||
public canEmbed = false;
|
||||
public embedUrl: SafeUrl = null;
|
||||
|
||||
private widgetApiSubscription: Subscription;
|
||||
|
||||
constructor(widgetApi: WidgetApiService, activatedRoute: ActivatedRoute, sanitizer: DomSanitizer) {
|
||||
let params: any = activatedRoute.snapshot.queryParams;
|
||||
|
||||
ScalarWidgetApi.setWidgetId("test");
|
||||
|
||||
widgetApi.isEmbeddable(params.url).then(result => {
|
||||
this.canEmbed = result.canEmbed;
|
||||
this.isLoading = false;
|
||||
|
@ -28,4 +35,19 @@ export class GenericWidgetWrapperComponent {
|
|||
});
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.widgetApiSubscription = ScalarWidgetApi.requestReceived.subscribe(e => this.onWidgetRequest(e));
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
if (this.widgetApiSubscription) this.widgetApiSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
private onWidgetRequest(request: ScalarToWidgetRequest) {
|
||||
if (request.action === "capabilities") {
|
||||
// Taking of the screenshot is handled elsewhere
|
||||
// TODO: Re-enable screenshots via a configuration flag when Riot has better support for them (and move it to an abstract class)
|
||||
ScalarWidgetApi.replyCapabilities(request, [/*"m.capability.screenshot"*/]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue