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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ package-lock.json
demo/package-lock.json
.vscode
owners.txt
test/util.spec.ts
.config/tsaoptions.json
36 changes: 32 additions & 4 deletions dist/powerbi-client.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// powerbi-client v2.22.2
// powerbi-client v2.23.1
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
declare module "config" {
Expand All @@ -12,6 +12,7 @@ declare module "config" {
declare module "errors" {
export const APINotSupportedForRDLError = "This API is currently not supported for RDL reports";
export const EmbedUrlNotSupported = "Embed URL is invalid for this scenario. Please use Power BI REST APIs to get the valid URL";
export const invalidEmbedUrlErrorMessage: string;
}
declare module "util" {
import { HttpPostMessage } from 'http-post-message';
Expand Down Expand Up @@ -124,6 +125,11 @@ declare module "util" {
* @returns {boolean}
*/
export function isCreate(embedType: string): boolean;
/**
* Checks if the embedUrl has an allowed power BI domain
* @hidden
*/
export function validateEmbedUrl(embedUrl: string): boolean;
}
declare module "embed" {
import * as models from 'powerbi-models';
Expand Down Expand Up @@ -151,6 +157,7 @@ declare module "embed" {
export type ITileEmbedConfiguration = models.ITileEmbedConfiguration;
export type IQnaEmbedConfiguration = models.IQnaEmbedConfiguration;
export type IQuickCreateConfiguration = models.IQuickCreateConfiguration;
export type IReportCreateConfiguration = models.IReportCreateConfiguration;
export type ILocaleSettings = models.ILocaleSettings;
export type IQnaSettings = models.IQnaSettings;
export type IEmbedSettings = models.ISettings;
Expand Down Expand Up @@ -597,7 +604,7 @@ declare module "ifilterable" {
}
}
declare module "visualDescriptor" {
import { ExportDataType, FiltersOperations, ICloneVisualRequest, ICloneVisualResponse, IExportDataResult, IFilter, ISlicerState, ISortByVisualRequest, IVisualLayout, VisualContainerDisplayMode } from 'powerbi-models';
import { ExportDataType, FiltersOperations, ICloneVisualRequest, ICloneVisualResponse, IExportDataResult, IFilter, ISlicerState, ISmartNarratives, ISortByVisualRequest, IVisualLayout, VisualContainerDisplayMode } from 'powerbi-models';
import { IHttpPostMessageResponse } from 'http-post-message';
import { IFilterable } from "ifilterable";
import { IPageNode } from "page";
Expand Down Expand Up @@ -794,11 +801,21 @@ declare module "visualDescriptor" {
* @returns {Promise<IHttpPostMessageResponse<void>>}
*/
resizeVisual(width: number, height: number): Promise<IHttpPostMessageResponse<void>>;
/**
* Get insights for single visual
*
* ```javascript
* visual.getSmartNarrativeInsights();
* ```
*
* @returns {Promise<ISmartNarratives>}
*/
getSmartNarrativeInsights(): Promise<ISmartNarratives>;
}
}
declare module "page" {
import { IHttpPostMessageResponse } from 'http-post-message';
import { DisplayOption, FiltersOperations, ICustomPageSize, IFilter, IVisual, LayoutType, PageSizeType, SectionVisibility, VisualContainerDisplayMode, IPageBackground, IPageWallpaper } from 'powerbi-models';
import { DisplayOption, FiltersOperations, ICustomPageSize, IFilter, IVisual, LayoutType, PageSizeType, SectionVisibility, VisualContainerDisplayMode, IPageBackground, IPageWallpaper, ISmartNarratives } from 'powerbi-models';
import { IFilterable } from "ifilterable";
import { IReportNode } from "report";
import { VisualDescriptor } from "visualDescriptor";
Expand Down Expand Up @@ -894,6 +911,16 @@ declare module "page" {
* @hidden
*/
constructor(report: IReportNode, name: string, displayName?: string, isActivePage?: boolean, visibility?: SectionVisibility, defaultSize?: ICustomPageSize, defaultDisplayOption?: DisplayOption, mobileSize?: ICustomPageSize, background?: IPageBackground, wallpaper?: IPageWallpaper);
/**
* Get insights for report page
*
* ```javascript
* page.getSmartNarrativeInsights();
* ```
*
* @returns {Promise<ISmartNarratives>}
*/
getSmartNarrativeInsights(): Promise<ISmartNarratives>;
/**
* Gets all page level filters within the report.
*
Expand Down Expand Up @@ -2940,12 +2967,13 @@ declare module "powerbi-client" {
export { Report } from "report";
export { Dashboard } from "dashboard";
export { Tile } from "tile";
export { IEmbedConfiguration, IQnaEmbedConfiguration, IVisualEmbedConfiguration, IReportEmbedConfiguration, IDashboardEmbedConfiguration, ITileEmbedConfiguration, IQuickCreateConfiguration, Embed, ILocaleSettings, IEmbedSettings, IQnaSettings, } from "embed";
export { IEmbedConfiguration, IQnaEmbedConfiguration, IVisualEmbedConfiguration, IReportEmbedConfiguration, IDashboardEmbedConfiguration, ITileEmbedConfiguration, IQuickCreateConfiguration, IReportCreateConfiguration, Embed, ILocaleSettings, IEmbedSettings, IQnaSettings, } from "embed";
export { Page } from "page";
export { Qna } from "qna";
export { Visual } from "visual";
export { VisualDescriptor } from "visualDescriptor";
export { QuickCreate } from "quickCreate";
export { Create } from "create";
export { BasicFilterBuilder, AdvancedFilterBuilder, TopNFilterBuilder, RelativeDateFilterBuilder, RelativeTimeFilterBuilder } from "FilterBuilders/index";
global {
interface Window {
Expand Down
1,741 changes: 770 additions & 971 deletions dist/powerbi.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/powerbi.min.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "powerbi-client",
"version": "2.22.2",
"version": "2.23.1",
"description": "JavaScript library for embedding Power BI into your apps. Provides service which makes it easy to embed different types of components and an object model which allows easy interaction with these components such as changing pages, applying filters, and responding to data selection.",
"main": "dist/powerbi.js",
"types": "dist/powerbi-client.d.ts",
Expand Down Expand Up @@ -79,9 +79,9 @@
},
"dependencies": {
"http-post-message": "^0.2",
"powerbi-models": "^1.12.3",
"powerbi-models": "^1.14.0",
"powerbi-router": "^0.1",
"window-post-message-proxy": "^0.2"
"window-post-message-proxy": "^0.2.7"
},
"publishConfig": {
"tag": "beta"
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

/** @ignore *//** */
const config = {
version: '2.22.2',
version: '2.23.1',
type: 'js'
};

Expand Down
15 changes: 8 additions & 7 deletions src/embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

import * as models from 'powerbi-models';
import * as sdkConfig from './config';
import { EmbedUrlNotSupported } from './errors';
import { EmbedUrlNotSupported, invalidEmbedUrlErrorMessage } from './errors';
import { ICustomEvent, IEvent, IEventHandler, Service } from './service';
import { addParamToUrl, assign, autoAuthInEmbedUrl, createRandomString, getTimeDiffInMilliseconds, remove, isCreate } from './util';
import { addParamToUrl, assign, autoAuthInEmbedUrl, createRandomString, getTimeDiffInMilliseconds, remove, isCreate, validateEmbedUrl } from './util';

declare global {
interface Document {
Expand Down Expand Up @@ -50,6 +50,8 @@ export type IQnaEmbedConfiguration = models.IQnaEmbedConfiguration;

export type IQuickCreateConfiguration = models.IQuickCreateConfiguration;

export type IReportCreateConfiguration = models.IReportCreateConfiguration;

export type ILocaleSettings = models.ILocaleSettings;

export type IQnaSettings = models.IQnaSettings;
Expand Down Expand Up @@ -571,7 +573,7 @@ export abstract class Embed {

const accessTokenProvider = eventHooks.accessTokenProvider;
if (!!accessTokenProvider) {
if ((['create', 'quickcreate', 'report'].indexOf(this.embedtype.toLowerCase()) === -1) || this.config.tokenType !== models.TokenType.Aad) {
if ((['create', 'quickcreate', 'report'].indexOf(this.embedtype.toLowerCase()) === -1) || this.config.tokenType !== models.TokenType.Aad) {
throw new Error("accessTokenProvider is only supported in report SaaS embed");
}
}
Expand Down Expand Up @@ -632,10 +634,6 @@ export abstract class Embed {
// Trim spaces to fix user mistakes.
hostname = hostname.toLowerCase().trim();

if (hostname.indexOf("http://") === 0) {
throw new Error("HTTP is not allowed. HTTPS is required");
}

if (hostname.indexOf("https://") === 0) {
return `${hostname}/${endpoint}`;
}
Expand Down Expand Up @@ -743,6 +741,9 @@ export abstract class Embed {
if (!this.iframe) {
const iframeContent = document.createElement("iframe");
const embedUrl = this.config.uniqueId ? addParamToUrl(this.config.embedUrl, 'uid', this.config.uniqueId) : this.config.embedUrl;
if (!validateEmbedUrl(embedUrl)) {
throw new Error(invalidEmbedUrlErrorMessage);
}

iframeContent.style.width = '100%';
iframeContent.style.height = '100%';
Expand Down
1 change: 1 addition & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@

export const APINotSupportedForRDLError = "This API is currently not supported for RDL reports";
export const EmbedUrlNotSupported = "Embed URL is invalid for this scenario. Please use Power BI REST APIs to get the valid URL";
export const invalidEmbedUrlErrorMessage: string = "Invalid embed URL detected. Either URL hostname or protocol are invalid. Please use Power BI REST APIs to get the valid URL";

23 changes: 23 additions & 0 deletions src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
VisualContainerDisplayMode,
IPageBackground,
IPageWallpaper,
ISmartNarratives,
} from 'powerbi-models';
import { IFilterable } from './ifilterable';
import { IReportNode, Report } from './report';
Expand Down Expand Up @@ -139,6 +140,28 @@ export class Page implements IPageNode, IFilterable {
this.wallpaper = wallpaper;
}

/**
* Get insights for report page
*
* ```javascript
* page.getSmartNarrativeInsights();
* ```
*
* @returns {Promise<ISmartNarratives>}
*/
async getSmartNarrativeInsights(): Promise<ISmartNarratives > {
if (isRDLEmbed(this.report.config.embedUrl)) {
return Promise.reject(APINotSupportedForRDLError);
}

try {
const response = await this.report.service.hpm.get<ISmartNarratives>(`/report/pages/${this.name}/smartNarrativeInsights`, { uid: this.report.config.uniqueId }, this.report.iframe.contentWindow);
return response.body;
} catch (response) {
throw response.body;
}
}

/**
* Gets all page level filters within the report.
*
Expand Down
4 changes: 4 additions & 0 deletions src/powerbi-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export {
IDashboardEmbedConfiguration,
ITileEmbedConfiguration,
IQuickCreateConfiguration,
IReportCreateConfiguration,
Embed,
ILocaleSettings,
IEmbedSettings,
Expand All @@ -52,6 +53,9 @@ export {
export {
QuickCreate
} from './quickCreate';
export {
Create
} from './create';
export {
BasicFilterBuilder,
AdvancedFilterBuilder,
Expand Down
4 changes: 4 additions & 0 deletions src/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,10 @@ export class Report extends Embed implements IReportNode, IFilterable {
* ```
*/
async refresh(): Promise<void> {
if (isRDLEmbed(this.config.embedUrl)) {
return Promise.reject(APINotSupportedForRDLError);
}

try {
const response = await this.service.hpm.post<void>('/report/refresh', null, { uid: this.config.uniqueId }, this.iframe.contentWindow);
return response.body;
Expand Down
5 changes: 5 additions & 0 deletions src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { Visual } from './visual';
import * as utils from './util';
import { QuickCreate } from './quickCreate';
import * as sdkConfig from './config';
import { invalidEmbedUrlErrorMessage } from './errors';

export interface IEvent<T> {
type: string;
Expand Down Expand Up @@ -667,6 +668,10 @@ export class Service implements IService {
* @param {HTMLElement} [element=undefined]
*/
preload(config: IComponentEmbedConfiguration | IEmbedConfigurationBase, element?: HTMLElement): HTMLIFrameElement {
if (!utils.validateEmbedUrl(config.embedUrl)) {
throw new Error(invalidEmbedUrlErrorMessage);
}

const iframeContent = document.createElement("iframe");
iframeContent.setAttribute("style", "display:none;");
iframeContent.setAttribute("src", config.embedUrl);
Expand Down
36 changes: 35 additions & 1 deletion src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@

import { HttpPostMessage } from 'http-post-message';

/**
* @hidden
*/
const allowedPowerBiHostsRegex =
new RegExp(/(.+\.powerbi\.com$)|(.+\.fabric\.microsoft\.com$)|(.+\.analysis\.windows-int\.net$)|(.+\.analysis-df\.windows\.net$)/);

/**
* @hidden
*/
const allowedPowerBiHostsSovRegex = new RegExp(/^app\.powerbi\.cn$|^app(\.mil\.|\.high\.|\.)powerbigov\.us$|^app\.powerbi\.eaglex\.ic\.gov$|^app\.powerbi\.microsoft\.scloud$/);

/**
* @hidden
*/
const expectedEmbedUrlProtocol: string = "https:";

/**
* Raises a custom event with event data on the specified HTML element.
*
Expand Down Expand Up @@ -194,7 +210,7 @@ export function autoAuthInEmbedUrl(embedUrl: string): boolean {
export function getRandomValue(): number {

// window.msCrypto for IE
const cryptoObj = window.crypto || window.msCrypto;
const cryptoObj = window.crypto || (window as any).msCrypto;
const randomValueArray = new Uint32Array(1);
cryptoObj.getRandomValues(randomValueArray);

Expand Down Expand Up @@ -223,3 +239,21 @@ export function getTimeDiffInMilliseconds(start: Date, end: Date): number {
export function isCreate(embedType: string): boolean {
return embedType === 'create' || embedType === 'quickcreate';
}

/**
* Checks if the embedUrl has an allowed power BI domain
* @hidden
*/
export function validateEmbedUrl(embedUrl: string): boolean {
if (embedUrl) {
let url: URL;
try {
url = new URL(embedUrl.toLowerCase());
} catch(e) {
// invalid URL
return false;
}
return url.protocol === expectedEmbedUrlProtocol &&
(allowedPowerBiHostsRegex.test(url.hostname) || allowedPowerBiHostsSovRegex.test(url.hostname));
}
}
21 changes: 20 additions & 1 deletion src/visualDescriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import {
IExportDataResult,
IFilter,
ISlicerState,
ISmartNarratives,
ISortByVisualRequest,
IUpdateFiltersRequest,
IVisualLayout,
VisualContainerDisplayMode,
VisualLevelFilters
VisualLevelFilters,
} from 'powerbi-models';
import { IHttpPostMessageResponse } from 'http-post-message';
import { IFilterable } from './ifilterable';
Expand Down Expand Up @@ -319,4 +320,22 @@ export class VisualDescriptor implements IVisualNode, IFilterable {

return report.resizeVisual(pageName, visualName, width, height);
}

/**
* Get insights for single visual
*
* ```javascript
* visual.getSmartNarrativeInsights();
* ```
*
* @returns {Promise<ISmartNarratives>}
*/
async getSmartNarrativeInsights(): Promise<ISmartNarratives> {
try {
const response = await this.page.report.service.hpm.get<ISmartNarratives>(`/report/pages/${this.page.name}/visuals/${this.name}/smartNarrativeInsights`, { uid: this.page.report.config.uniqueId }, this.page.report.iframe.contentWindow);
return response.body;
} catch (response) {
throw response.body;
}
}
}
3 changes: 2 additions & 1 deletion test/SDK-to-HPM.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ describe('SDK-to-HPM', function () {
};

spyOn(utils, "getTimeDiffInMilliseconds").and.callFake(() => 700); // Prevent requests from being throttled.
spyOn(utils, 'validateEmbedUrl').and.callFake(() => { return true; });

powerbi = new service.Service(spyHpmFactory, noop, spyRouterFactory, { wpmpName: 'SDK-to-HPM report wpmp' });

sdkSessionId = powerbi.getSdkSessionId();
});

Expand Down
3 changes: 3 additions & 0 deletions test/SDK-to-MockApp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ describe('SDK-to-MockApp', function () {
powerbi = new service.Service(factories.hpmFactory, factories.wpmpFactory, factories.routerFactory, {
wpmpName: 'SDK-to-MockApp HostWpmp'
});

spyOn(utils, 'validateEmbedUrl').and.callFake(() => { return true; });

element = document.createElement('div');
element.id = "reportContainer1";
element.className = 'powerbi-report-container2';
Expand Down
Loading