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
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export Client from './client';
export User from './user';
export Snippet from './snippet';

import crypto from 'crypto';

Expand Down
79 changes: 79 additions & 0 deletions lib/snippet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { IdentityVerification } from './index';
import htmlencode from 'htmlencode';

export default class Snippet {
constructor(settings) {
this.loggedOut = !settings.user_id && !settings.email;

if (!settings.app_id) {
throw new Error('You must provide an app_id in your Intercom settings');
}
if (!this.loggedOut && !settings.verificationSecret) {
throw new Error('You must provide your verification secret in your Intercom settings');
}

this.settings = settings;
}
create() {
const verificationSecret = this.getVerificationSecret();
const identifier = this.getIdentifier();
this.setUserHash(verificationSecret, identifier);
return this.generateSnippetHTML();
}
getVerificationSecret() {
const { verificationSecret } = this.settings;
delete this.settings.verificationSecret;
return verificationSecret;
}
getIdentifier() {
if (this.settings.user_id) {
return this.settings.user_id.toString();
} else {
return this.settings.email;
}
}
setUserHash(verificationSecret, identifier) {
if (this.loggedOut) {
return;
}

const userHash = IdentityVerification.userHash({
secretKey: verificationSecret,
identifier
});
this.settings.user_hash = userHash;
}
generateSnippetHTML() {
return `
<script>
window.intercomSettings = {
${this.settingsToString(this.settings)}
};
</script>
<script>(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://widget.intercom.io/widget/${this.settings.app_id}';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()</script>
`;
}
settingsToString(settings) {
const intercomSettings = [];
Object.keys(settings).map(key => {
if (typeof settings[key] === 'object' && settings[key] !== null) {
intercomSettings.push(`${key}: { ${this.settingsToString(settings[key])} }`);
} else {
const escapedKey = this.escapeString(key);
const value = this.escapeString(settings[key]);
if (typeof settings[key] === 'string') {
intercomSettings.push(`${escapedKey}: "${value}"`);
} else {
intercomSettings.push(`${escapedKey}: ${value}`);
}
}
});
return intercomSettings.join(', ');
}
escapeString(string) {
if (typeof string === 'string') {
string = htmlencode.htmlEncode(string).replace(/\&quot;/gi, '\\"');
}
return string;
}
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
],
"dependencies": {
"bluebird": "^3.3.4",
"request": "^2.83.0"
"request": "^2.83.0",
"htmlencode": "^0.0.4"
},
"devDependencies": {
"babel-core": "^6.7.4",
Expand All @@ -32,7 +33,7 @@
"gulp-exclude-gitignore": "^1.0.0",
"gulp-istanbul": "^0.10.3",
"gulp-mocha": "^2.0.0",
"gulp-nsp": "^2.3.1",
"gulp-nsp": "^2.4.2",
"gulp-plumber": "^1.1.0",
"nock": "7.5.0"
},
Expand Down
108 changes: 108 additions & 0 deletions test/snippet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import Snippet from '../lib/snippet';
import assert from 'assert';

describe('snippet', () => {
it('should be able to grab the verification secret', () => {
const settings = {
verificationSecret: 'abc123',
app_id: 'xyz789',
user_id: 1
};
const snippet = new Snippet(settings);
assert.equal(snippet.getVerificationSecret(), 'abc123');
});

it('should grab the user_id as the identifier', () => {
const settings = {
verificationSecret: 'abc123',
app_id: 'xyz789',
user_id: 1,
email: 'peter@intercom.io'
};
const snippet = new Snippet(settings);
assert.equal(snippet.getIdentifier(), 1);
});

it('should grab the email as the identifier if no user_id', () => {
const settings = {
verificationSecret: 'abc123',
app_id: 'xyz789',
email: 'peter@intercom.io'
};
const snippet = new Snippet(settings);
assert.equal(snippet.getIdentifier(), 'peter@intercom.io');
});

it('should throw an error if there\'s no verification secret', () => {
const settings = {
app_id: 'xyz789',
user_id: 1
};
assert.throws(() => new Snippet(settings), Error);
});

it('should error if there\'s no app_id', () => {
const settings = {
verificationSecret: 'abc123',
user_id: 1
};
assert.throws(() => new Snippet(settings), Error);
});

it('should return the logged out snippet if no identifier', () => {
const settings = {
app_id: 'xyz789'
};
const snippet = new Snippet(settings);
assert.equal(snippet.settingsToString(settings), 'app_id: "xyz789"');
});

it('should escape bad stuff', () => {
const settings = {
verificationSecret: 'abc123',
app_id: 'xyz789',
email: 'peter@"<script>alert(1)</script>intercom.io'
};
const snippet = new Snippet(settings);
assert.equal(snippet.settingsToString(settings), 'verificationSecret: "abc123", app_id: "xyz789", email: "peter@\\"&lt;script&gt;alert(1)&lt;/script&gt;intercom.io"');
});

it('should not include the verification secret in the snippet html', () => {
const settings = {
verificationSecret: 'abc123',
app_id: 'xyz789',
user_id: 1,
email: 'peter@intercom.io',
name: 'Peter McKenna',
company: {
id: 123,
name: 'Intercom'
}
};
const snippet = new Snippet(settings);
assert.equal(snippet.create().indexOf(settings.verificationSecret), -1);
});

it('should return the snippet html', () => {
const settings = {
verificationSecret: 'abc123',
app_id: 'xyz789',
user_id: 1,
email: 'peter@intercom.io',
name: 'Peter McKenna',
company: {
id: 123,
name: 'Intercom'
}
};
const snippet = new Snippet(settings);
assert.equal(snippet.create(), `
<script>
window.intercomSettings = {
app_id: "xyz789", user_id: 1, email: "peter@intercom.io", name: "Peter McKenna", company: { id: 123, name: "Intercom" }, user_hash: "f02877f24c9dd37542268a28627ebaf2e07d0d114d9482abcdc20f60874b40b3"
};
</script>
<script>(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://widget.intercom.io/widget/xyz789';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()</script>
`);
});
});
Loading