@looker/embed-sdk
Advanced tools
Comparing version 1.0.0-beta.1 to 1.0.0-beta.2
require('dotenv').config(); | ||
module.exports = { | ||
host: process.env.LOOKER_EMBED_HOST || 'localhost:9999', | ||
domain: process.env.LOOKER_EMBED_DOMAIN || 'localhost:8080', | ||
host: process.env.LOOKER_EMBED_HOST || 'self-signed.looker.com:9999', | ||
secret: process.env.LOOKER_EMBED_SECRET || 'ranger2' | ||
} |
@@ -18,4 +18,3 @@ { | ||
"models": ["powered_by", "thelook"], | ||
"user_attributes": { "locale": "en_US" }, | ||
"access_filters": { "powered_by": { "products.brand": "Allegra K" } } | ||
"user_attributes": { "locale": "en_US" } | ||
} |
@@ -19,2 +19,3 @@ // Karma configuration | ||
{pattern: 'src/**/*.ts'}, | ||
{pattern: 'server_utils/**/*.ts'}, | ||
{pattern: 'tests/**/*.spec.ts'}, | ||
@@ -21,0 +22,0 @@ {pattern: 'tests/**/*.html'} |
{ | ||
"name": "@looker/embed-sdk", | ||
"version": "1.0.0-beta.1", | ||
"version": "1.0.0-beta.2", | ||
"description": "A toolkit for embedding Looker", | ||
@@ -17,6 +17,7 @@ "main": "dist/main.js", | ||
"build_utils": "tsc --build tsconfig-server.json", | ||
"clean": "rm -r lib dist", | ||
"lint": "tslint --project tsconfig.json --format stylish 'src/**/*.ts' 'server_utils/*.ts'", | ||
"lint-fix": "tslint --fix --project tsconfig.json --format stylish 'src/**/*.ts' 'server_utils/*.ts'", | ||
"clean": "rm -rf lib dist", | ||
"lint": "tslint --project tsconfig-lint.json --format stylish", | ||
"lint-fix": "tslint --fix --project tsconfig-lint.json --format stylish", | ||
"start": "npm run build_utils && webpack-dev-server --config webpack-devserver.config.js --hot --inline --open --color --progress", | ||
"python": "webpack --config webpack-devserver.config.js && python demo/demo.py", | ||
"test": "npm run lint && karma start karma.conf.js", | ||
@@ -37,5 +38,7 @@ "test-once": "npm run lint && karma start karma.conf.js --single-run " | ||
"@babel/core": "^7.3.3", | ||
"@types/create-hmac": "^1.1.0", | ||
"@types/jasmine": "^2.8.2", | ||
"@types/node": "^11.12.1", | ||
"babel-loader": "^8.0.5", | ||
"create-hmac": "^1.1.7", | ||
"dotenv": "^6.2.0", | ||
@@ -52,2 +55,3 @@ "jasmine-core": "^2.8.0", | ||
"tslint-config-standard": "^8.0.0", | ||
"tslint-defocus": "^2.0.6", | ||
"tslint-eslint-rules": "^4.1.1", | ||
@@ -54,0 +58,0 @@ "typescript": "^3.3.3", |
@@ -187,2 +187,7 @@ # Looker JavaScript Embed SDK | ||
* Create a file named `.env` in the root of the sdk directory. Add a line to that file: `LOOKER_EMBED_SECRET="YourLookerSecret"` | ||
* Provide your Looker instance host address to the server by either: | ||
* Setting it as `LOOKER_EMBED_HOST` in your shell environment. | ||
* Adding `LOOKER_EMBED_HOST="yourinstance.looker.com:yourport"` to the `.env` file. | ||
* Edit the `demo/demo_config.ts` file to be appropriate for the pages you want to embed. | ||
@@ -255,4 +260,14 @@ | ||
#### Node server | ||
* `npm install` | ||
* `npm start` | ||
* The server will print out what host and port it is running on. If it is different than `http://localhost:8080` then you will need to add that to your Embedded Domain Whitelist. | ||
#### Python server | ||
* `npm install` | ||
* `npm run python` | ||
* The server will print out what host and port it is running on. | ||
You may need to `pip install six` to install the Python 2/3 compatibility layer. |
@@ -26,7 +26,10 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var crypto_1 = require("crypto"); | ||
var createHmac = require("create-hmac"); | ||
function stringify(params) { | ||
var result = []; | ||
for (var key in params) { | ||
result.push(key + "=" + encodeURIComponent(params[key])); | ||
var param = params[key]; | ||
if (typeof param === 'string') { | ||
result.push(key + "=" + encodeURIComponent(param)); | ||
} | ||
} | ||
@@ -39,3 +42,3 @@ return result.join('&'); | ||
function signEmbedUrl(data, secret) { | ||
var stringToSign = [ | ||
var stringsToSign = [ | ||
data.host, | ||
@@ -48,11 +51,15 @@ data.embed_path, | ||
data.permissions, | ||
data.models, | ||
data.group_ids, | ||
data.external_group_id, | ||
data.user_attributes, | ||
data.access_filters | ||
].join('\n'); | ||
return crypto_1.createHmac('sha1', secret).update(forceUnicodeEncoding(stringToSign)).digest('base64').trim(); | ||
data.models | ||
]; | ||
if (data.group_ids) | ||
stringsToSign.push(data.group_ids); | ||
if (data.external_group_id) | ||
stringsToSign.push(data.external_group_id); | ||
if (data.user_attributes) | ||
stringsToSign.push(data.user_attributes); | ||
stringsToSign.push(data.access_filters); | ||
var stringToSign = stringsToSign.join('\n'); | ||
return createHmac('sha1', secret).update(forceUnicodeEncoding(stringToSign)).digest('base64').trim(); | ||
} | ||
function nonce(len) { | ||
function createNonce(len) { | ||
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | ||
@@ -65,5 +72,6 @@ var text = ''; | ||
} | ||
function createSignedUrl(src, user, host, secret) { | ||
; | ||
function createSignedUrl(src, user, host, secret, nonce) { | ||
var jsonTime = JSON.stringify(Math.floor((new Date()).getTime() / 1000)); | ||
var jsonNonce = JSON.stringify(nonce(16)); | ||
var jsonNonce = JSON.stringify(nonce || createNonce(16)); | ||
var params = { | ||
@@ -75,6 +83,7 @@ external_user_id: JSON.stringify(user.external_user_id), | ||
models: JSON.stringify(user.models), | ||
group_ids: JSON.stringify(user.group_ids || []), | ||
group_ids: JSON.stringify(user.group_ids), | ||
user_attributes: JSON.stringify(user.user_attributes), | ||
external_group_id: JSON.stringify(user.external_group_id), | ||
access_filters: JSON.stringify(user.access_filters), | ||
access_filters: JSON.stringify(user.access_filters || {}), | ||
user_timezone: JSON.stringify(user.user_timezone), | ||
force_logout_login: JSON.stringify(user.force_logout_login), | ||
@@ -81,0 +90,0 @@ session_length: JSON.stringify(user.session_length), |
@@ -25,8 +25,11 @@ /* | ||
import { createHmac } from 'crypto' | ||
import * as createHmac from 'create-hmac' | ||
function stringify (params: {[key: string]: string}) { | ||
function stringify (params: {[key: string]: string | undefined}) { | ||
const result = [] | ||
for (const key in params) { | ||
result.push(`${key}=${encodeURIComponent(params[key])}`) | ||
const param = params[key] | ||
if (typeof param === 'string') { | ||
result.push(`${key}=${encodeURIComponent(param)}`) | ||
} | ||
} | ||
@@ -41,3 +44,3 @@ return result.join('&') | ||
function signEmbedUrl (data: {[key: string]: string}, secret: string) { | ||
const stringToSign = [ | ||
const stringsToSign = [ | ||
data.host, | ||
@@ -51,12 +54,14 @@ data.embed_path, | ||
data.permissions, | ||
data.models, | ||
data.group_ids, | ||
data.external_group_id, | ||
data.user_attributes, | ||
data.access_filters | ||
].join('\n') | ||
data.models | ||
] | ||
if (data.group_ids) stringsToSign.push(data.group_ids) | ||
if (data.external_group_id) stringsToSign.push(data.external_group_id) | ||
if (data.user_attributes) stringsToSign.push(data.user_attributes) | ||
stringsToSign.push(data.access_filters) | ||
const stringToSign = stringsToSign.join('\n') | ||
return createHmac('sha1', secret).update(forceUnicodeEncoding(stringToSign)).digest('base64').trim() | ||
} | ||
function nonce (len: number) { | ||
function createNonce (len: number) { | ||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' | ||
@@ -96,3 +101,3 @@ let text = '' | ||
session_length: number | ||
force_logout_login?: boolean, | ||
force_logout_login: boolean, | ||
permissions: LookerUserPermission[] | ||
@@ -103,8 +108,15 @@ models: string[] | ||
user_attributes?: {[key: string]: any} | ||
access_filters: {[key: string]: any} | ||
user_timezone?: string | null | ||
access_filters?: {[key: string]: any} | ||
} | ||
export function createSignedUrl (src: string, user: LookerEmbedUser, host: string, secret: string) { | ||
export function createSignedUrl ( | ||
src: string, | ||
user: LookerEmbedUser, | ||
host: string, | ||
secret: string, | ||
nonce?: string | ||
) { | ||
const jsonTime = JSON.stringify(Math.floor((new Date()).getTime() / 1000)) | ||
const jsonNonce = JSON.stringify(nonce(16)) | ||
const jsonNonce = JSON.stringify(nonce || createNonce(16)) | ||
const params = { | ||
@@ -116,6 +128,7 @@ external_user_id: JSON.stringify(user.external_user_id), | ||
models: JSON.stringify(user.models), | ||
group_ids: JSON.stringify(user.group_ids || []), | ||
group_ids: JSON.stringify(user.group_ids), | ||
user_attributes: JSON.stringify(user.user_attributes), | ||
external_group_id: JSON.stringify(user.external_group_id), | ||
access_filters: JSON.stringify(user.access_filters), | ||
access_filters: JSON.stringify(user.access_filters || {}), | ||
user_timezone: JSON.stringify(user.user_timezone), | ||
@@ -122,0 +135,0 @@ force_logout_login: JSON.stringify(user.force_logout_login), |
@@ -27,2 +27,3 @@ /* | ||
import { EmbedClient } from '../src/embed' | ||
import { EmbedBuilder } from '../src/embed_builder' | ||
import { LookerEmbedExplore } from '../src/explore_client' | ||
@@ -33,4 +34,4 @@ import { LookerEmbedSDK } from '../src/index' | ||
describe('LookerEmbedBuilder', () => { | ||
let builder | ||
let el | ||
let builder: EmbedBuilder<LookerEmbedDashboard> | ||
let el: HTMLDivElement | ||
@@ -149,7 +150,31 @@ beforeEach(() => { | ||
it('should add a second on<action> handler', () => { | ||
const dance = jasmine.createSpy('dance') | ||
const pizza = jasmine.createSpy('pizza') | ||
builder.on('party', dance) | ||
builder.on('party', pizza) | ||
expect(builder.handlers.party).toEqual([dance, pizza]) | ||
}) | ||
it('should add url parameters', () => { | ||
builder.withParams({ alpha: 1, beta: 2 }) | ||
builder.withParams({ alpha: '1', beta: '2' }) | ||
expect(builder.embedUrl).toMatch('alpha=1&beta=2') | ||
}) | ||
it('should allow specifying a theme', () => { | ||
builder.withTheme('Fancy') | ||
expect(builder.embedUrl).toMatch('theme=Fancy') | ||
}) | ||
it('should allow specifying filters for dashboards', () => { | ||
builder.withFilters({ 'State / Region': 'California' }) | ||
expect(builder.embedUrl).toMatch('State%20%2F%20Region=California') | ||
}) | ||
it('should allow specifying filters for looks', () => { | ||
builder = LookerEmbedSDK.createLookWithId(1) | ||
builder.withFilters({ 'State / Region': 'California' }) | ||
expect(builder.embedUrl).toMatch('f%5BState%20%2F%20Region%5D=California') | ||
}) | ||
it('should allow adding sandbox attributes', () => { | ||
@@ -156,0 +181,0 @@ builder.withSandboxAttr('alpha') |
@@ -28,2 +28,3 @@ /* | ||
import mock from 'xhr-mock' | ||
import { EmbedClient } from '../src/embed' | ||
@@ -35,3 +36,3 @@ const testUrl = '/base/tests/test.html' | ||
let el | ||
let client | ||
let client: any | ||
@@ -45,3 +46,3 @@ beforeEach(() => { | ||
describe('with ID', () => { | ||
let fakeDashboardClient | ||
let fakeDashboardClient: any | ||
@@ -69,3 +70,3 @@ beforeEach(() => { | ||
}) | ||
.catch(expect(false)) | ||
.catch(done.fail) | ||
}) | ||
@@ -80,3 +81,3 @@ | ||
.then(() => false) | ||
.catch(expect(false)) | ||
.catch(done.fail) | ||
@@ -88,8 +89,23 @@ client.connect() | ||
}) | ||
.catch(expect(false)) | ||
.catch(done.fail) | ||
}) | ||
it('should handle failures', (done) => { | ||
mock.reset() | ||
mock.get(/\/auth\?src=/, (req, res) => { | ||
expect(req.header('Cache-Control')).toEqual('no-cache') | ||
return res.status(403).statusText('foo') | ||
}) | ||
client.connect() | ||
.then(done.fail) | ||
.catch((error: any) => { | ||
expect(error).toEqual('foo') | ||
done() | ||
}) | ||
}) | ||
}) | ||
describe('with URL', () => { | ||
let fakeDashboardClient | ||
let fakeDashboardClient: any | ||
@@ -114,3 +130,3 @@ beforeEach(() => { | ||
}) | ||
.catch(expect(false)) | ||
.catch(done.fail) | ||
}) | ||
@@ -121,3 +137,3 @@ | ||
.then(() => false) | ||
.catch(expect(false)) | ||
.catch(done.fail) | ||
@@ -129,3 +145,3 @@ client.connect() | ||
}) | ||
.catch(expect(false)) | ||
.catch(done.fail) | ||
}) | ||
@@ -136,4 +152,4 @@ }) | ||
let fakeDashboardClient | ||
let el | ||
let iframe | ||
let el: HTMLDivElement | ||
let iframe: HTMLIFrameElement | ||
@@ -153,3 +169,3 @@ beforeEach(() => { | ||
spyOn(window, 'fetch') | ||
spyOn(ChattyHost.prototype, 'connect').and.callFake(async function () { | ||
spyOn(ChattyHost.prototype, 'connect').and.callFake(async function (this: any) { | ||
iframe = this.iframe | ||
@@ -169,2 +185,3 @@ return Promise.resolve({}) | ||
expect(iframe.classList.toString()).toEqual('classy') | ||
// tslint:disable-next-line:deprecation | ||
expect(iframe.frameBorder).toEqual('0') | ||
@@ -174,5 +191,5 @@ expect(iframe.src).toMatch(testUrl) | ||
}) | ||
.catch(expect(false)) | ||
.catch(done.fail) | ||
}) | ||
}) | ||
}) |
{ | ||
"defaultSeverity": "error", | ||
"extends": [ | ||
"tslint-defocus", | ||
"tslint-config-standard" | ||
@@ -8,2 +9,3 @@ ], | ||
"rules": { | ||
"defocus": true, | ||
"no-namespace": [true, "allow-declarations"], | ||
@@ -10,0 +12,0 @@ "prefer-const": [true, {"destructuring": "all"}], |
@@ -13,3 +13,3 @@ var path = require('path') | ||
filename: "[name].js", | ||
path: path.join(__dirname, "demo", "build") | ||
path: path.join(__dirname, "demo") | ||
}, | ||
@@ -26,3 +26,8 @@ resolve: { | ||
test: /\.ts$/, | ||
loader: "ts-loader" | ||
loader: "ts-loader", | ||
options: { | ||
compilerOptions: { | ||
declaration: false | ||
} | ||
} | ||
} | ||
@@ -34,4 +39,3 @@ ] | ||
contentBase: [ | ||
path.join(__dirname, "demo"), | ||
path.join(__dirname, "demo", "build") | ||
path.join(__dirname, "demo") | ||
], | ||
@@ -38,0 +42,0 @@ watchContentBase: true, |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
144786
44
2563
272
2
25