-
Notifications
You must be signed in to change notification settings - Fork 0
/
inject.js
151 lines (134 loc) · 3.84 KB
/
inject.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/* globals chrome */
const util = require('./util');
const stringify = require('./stringify');
/*
* Send the result to the `popup` module via extension messaging.
*/
function sendResult(type, payload) {
chrome.runtime.sendMessage({type, payload});
}
/*
* Wrap a function in an exception handler that sends an error message.
*/
function protect(fn) {
return function() {
try {
fn.apply(this, arguments);
} catch (e) {
sendResult('error', e.stack || e.toString());
window.unravel.requesting = false;
}
};
}
/*
* Receive a `postMessage` from the Twitter Fabric page and forward along the
* payload in a success message.
*/
function receiveMessage(e) {
// Verify message is ours to receive.
if (e.source !== window) {
return;
}
if (!e.data || !e.data.type) {
return;
}
// Process message.
window.unravel.requesting = false;
switch (e.data.type) {
case 'unravel:success':
sendResult('success', stringify.session(e.data.payload));
break;
case 'unravel:error':
throw new Error(
`API returned an error: ${JSON.stringify(e.data.payload)}`
);
default:
throw new Error(
`Unexpected window message type: ${e.data.type}`
);
}
}
/*
* Inject code into the Twitter Fabric page to request a crash session and
* message the results back via `postMessage`.
*/
function requestSession() {
const API_PREFIX = '/api/v3';
// Check for a request in progress.
if (window.unravel.requesting) {
return;
}
window.unravel.requesting = true;
// Install a `postMessage` handler.
if (!window.unravel.installed) {
window.addEventListener('message', protect(receiveMessage));
window.unravel.installed = true;
}
// Parse and confirm that the URL is valid for this browser extension.
const url = window.location.href;
const parsed = util.parseUrl(url);
if (!parsed.validHost || !parsed.validPath) {
throw new Error(
`Extension script was injected for an invalid URL: ${url}`
);
}
// Build the resource URLs.
const resourceUrls = Object.keys(parsed.resourcePaths)
.reduce((urls, key) => {
urls[key] = [API_PREFIX, parsed.resourcePaths[key]].join('');
return urls;
}, {
config: '/api/v2/client_boot/config_data',
});
// Inject a `script` element to fetch the resource and `postMessage` back.
// Yes, this is janky as hell.
function fetch() {
const resourceUrls = '%resourceUrls%';
const configUrl = resourceUrls.config;
delete resourceUrls.config;
function fail(xhr, status, error) {
window.postMessage({
type: 'unravel:error',
payload: {status, error},
}, '*');
}
window.$.get(configUrl)
.done((data) => {
const app = data.current_application || {};
const keys = Object.keys(resourceUrls);
const urls = keys.map((key) => {
return resourceUrls[key].replace(
`/${app.bundle_identifier.toLowerCase()}/`,
`/${app.id}/`
);
});
window.$.when.apply(
window.$,
urls.map((url) => window.$.get(url))
).done((...arr) => {
const payload = keys.reduce((obj, key, i) => {
const data = arr[i];
if (data) {
obj[key] = data[0];
}
return obj;
}, {app});
window.postMessage({
type: 'unravel:success',
payload,
}, '*');
})
.fail(fail);
})
.fail(fail);
}
const scriptText =
`(${fetch.toString()})()`
.replace('"%resourceUrls%"', JSON.stringify(resourceUrls));
const scriptEl = document.createElement('script');
scriptEl.innerHTML = scriptText;
document.body.appendChild(scriptEl);
}
// Immediately attempt to request the current session.
window.unravel = window.unravel || {};
protect(requestSession)();