1 | var check = require('check-types');
|
2 | var verify = check.verify;
|
3 | var urls = require('url');
|
4 | var crypto = require('crypto');
|
5 | var _ = require('lodash');
|
6 | var param = encodeURIComponent;
|
7 |
|
8 | var events = [
|
9 | 'room_enter',
|
10 | 'room_exit',
|
11 | 'room_message',
|
12 | 'room_notification',
|
13 | 'room_topic_change'
|
14 | ];
|
15 |
|
16 | var self = exports;
|
17 |
|
18 | exports.normalize = function (definition, baseUrl, defaultMountPath, token) {
|
19 | check.map(definition, {
|
20 | event: verify.string,
|
21 | name: verify.maybe.string,
|
22 | url: verify.maybe.string
|
23 | });
|
24 | verify.webUrl(baseUrl);
|
25 | if (events.indexOf(definition.event) === -1) {
|
26 | throw new Error('Unrecognized webhook event: ' + definition.event);
|
27 | }
|
28 | definition = _.extend({}, definition);
|
29 | if (definition.event === 'room_message') {
|
30 | verify.defined(definition.pattern)
|
31 | if (_.isRegExp(definition.pattern)) {
|
32 | definition.pattern = exports.regExpToPattern(definition.pattern);
|
33 | } else {
|
34 | verify.string(definition.pattern);
|
35 | }
|
36 | }
|
37 |
|
38 | if (definition.url) {
|
39 | definition.url = exports.stripNameParam(definition.url);
|
40 | }
|
41 | var parsed = self.parseUrl(definition.url);
|
42 |
|
43 | var fullPath = parsed ? parsed.fullPath : defaultMountPath;
|
44 | definition.url = urls.resolve(baseUrl, fullPath);
|
45 | if (token) {
|
46 | definition.url += '?token=' + param(token);
|
47 | }
|
48 | if (!definition.name) {
|
49 | definition.name = exports.digest(definition);
|
50 | }
|
51 | definition.url += (definition.url.indexOf('?') > 0 ? '&' : '?') + 'name=' + param(definition.name);
|
52 | return definition;
|
53 | };
|
54 |
|
55 | exports.parseUrl = function (url) {
|
56 | var result;
|
57 | if (check.webUrl(url)) {
|
58 |
|
59 | parsed = urls.parse(url);
|
60 | result = {
|
61 | fullPath: parsed.path,
|
62 | mountPath: parsed.pathname
|
63 | };
|
64 | } else if (check.string(url)) {
|
65 |
|
66 | result = {
|
67 | fullPath: url,
|
68 | mountPath: url.split('?')[0]
|
69 | };
|
70 | }
|
71 | return result;
|
72 | };
|
73 |
|
74 | exports.regExpToPattern = function (re) {
|
75 | var pattern = re.toString();
|
76 | pattern = pattern.replace(/\\u([\da-fA-F]{4,4})/g, function ($0, $1) {
|
77 | return '\\x{' + $1 + '}';
|
78 | });
|
79 | var match = /^\/(.*)\/([gimy]+)?/.exec(pattern);
|
80 | if (match) {
|
81 | pattern = match[1];
|
82 | if (match[2]) {
|
83 | pattern = '(?' + match[2] + ')' + pattern;
|
84 | }
|
85 | }
|
86 | return pattern;
|
87 | };
|
88 |
|
89 | exports.patternToRegExp = function (pattern) {
|
90 | var match = /\(\?([gimy])+\)(.*)/.exec(pattern);
|
91 | var flags;
|
92 | if (match) {
|
93 | flags = match[1];
|
94 | pattern = match[2];
|
95 | }
|
96 | pattern = pattern.replace(/\\x\{([\da-fA-F]{4,4})\}/g, function ($0, $1) {
|
97 | return '\\u' + $1 + '';
|
98 | });
|
99 | return new RegExp(pattern, flags);
|
100 | };
|
101 |
|
102 | exports.digest = function (webhook) {
|
103 | webhook = _.extend({}, webhook);
|
104 | if (webhook.url) {
|
105 | webhook.url = exports.stripNameParam(webhook.url);
|
106 | }
|
107 |
|
108 |
|
109 | var fields = ['event', 'url'];
|
110 | if (webhook.event === 'room_message') {
|
111 | verify.defined(webhook.pattern)
|
112 | fields.push('pattern');
|
113 | }
|
114 | var str = fields.filter(function (key) {
|
115 | return !!webhook[key];
|
116 | }).map(function (key) {
|
117 | return key + '=' + encodeURIComponent(webhook[key].toString());
|
118 | }).join('&');
|
119 | return crypto.createHash('sha1').update(str).digest('hex');
|
120 | };
|
121 |
|
122 | exports.stripNameParam = function (url) {
|
123 | return url.replace(/(.*)(\?|&)name=[^&]+&?(.*)/, function ($0, $1, $2, $3) {
|
124 | return $1 + ($3 ? $2 : '') + ($3 || '');
|
125 | });
|
126 | };
|