Skip to content

Commit e535751

Browse files
christianalfoniCompuIves
authored andcommitted
add overmind and refactor utils to typescript overmind effect (codesandbox#1824)
* add overmind and refactor utils to typescript overmind effect * initial move of providers and utils to TS
1 parent 74b646b commit e535751

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2372
-18
lines changed

packages/app/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,9 @@
214214
"normalizr": "^3.2.3",
215215
"onigasm": "^2.2.1",
216216
"ot": "^0.0.15",
217+
"overmind": "^17.1.0",
218+
"overmind-devtools": "^18.1.0",
219+
"overmind-react": "^18.1.0",
217220
"phoenix": "^1.3.0",
218221
"postcss": "^6.0.9",
219222
"postcss-selector-parser": "^2.2.3",
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { logError } from '@codesandbox/common/lib/utils/analytics';
2+
import { values } from 'lodash-es';
3+
import { camelizeKeys, decamelizeKeys } from 'humps';
4+
// import { addNotification } from '../factories';
5+
6+
/*
7+
This effect needs to expose a "configure" method where jwt
8+
and error action is passed in
9+
*/
10+
const API_ROOT = '/api/v1';
11+
12+
function createHeaders({ state, jwt }) {
13+
const foundJwt = state.get('jwt') || jwt.get();
14+
15+
return foundJwt
16+
? {
17+
Authorization: `Bearer ${foundJwt}`,
18+
}
19+
: {};
20+
}
21+
22+
const getMessage = (error: Error & { response?: any }) => {
23+
const response = error.response;
24+
25+
if (!response || response.status >= 500) {
26+
logError(error);
27+
}
28+
29+
if (response && response.result) {
30+
if (response.result.errors) {
31+
const errors = values(response.result.errors)[0];
32+
if (Array.isArray(errors)) {
33+
if (errors[0]) {
34+
error.message = errors[0]; // eslint-disable-line no-param-reassign
35+
}
36+
} else {
37+
error.message = errors; // eslint-disable-line no-param-reassign
38+
}
39+
} else if (response.result.error) {
40+
error.message = response.result.error; // eslint-disable-line no-param-reassign
41+
} else if (response.status === 413) {
42+
return 'File too large, upload limit is 5MB.';
43+
}
44+
}
45+
46+
return error.message;
47+
};
48+
49+
const showError = (error, controller) => {
50+
const errorMessage = getMessage(error);
51+
52+
/*
53+
TODO: This needs to be handled differently!
54+
controller.runSignal(
55+
'showNotification',
56+
addNotification(errorMessage, 'error')
57+
);
58+
*/
59+
60+
error.apiMessage = errorMessage; // eslint-disable-line no-param-reassign
61+
};
62+
63+
const handleError = (error, controller) => {
64+
try {
65+
showError(error, controller);
66+
} catch (e) {
67+
console.error(e);
68+
}
69+
70+
throw error;
71+
};
72+
73+
function handleResponse(response, { shouldCamelize = true } = {}) {
74+
const camelizedData = shouldCamelize
75+
? camelizeKeys(response.result)
76+
: response.result;
77+
78+
// Quickfix to prevent underscored dependencies from being camelized.
79+
// Never store data as keys in the future.
80+
if (
81+
camelizedData &&
82+
camelizedData.data &&
83+
camelizedData.data.npmDependencies
84+
) {
85+
camelizedData.data.npmDependencies = response.result.data.npm_dependencies;
86+
}
87+
88+
return camelizedData.data ? camelizedData.data : camelizedData;
89+
}
90+
91+
export default {
92+
get(path, query, options) {
93+
return this.context.http
94+
.get(API_ROOT + path, query, {
95+
headers: createHeaders(this.context),
96+
})
97+
.then(response => handleResponse(response, options))
98+
.catch(e => handleError(e, this.context.controller));
99+
},
100+
post(path, body, options) {
101+
return this.context.http
102+
.post(API_ROOT + path, decamelizeKeys(body), {
103+
headers: createHeaders(this.context),
104+
})
105+
.then(response => handleResponse(response, options))
106+
.catch(e => handleError(e, this.context.controller));
107+
},
108+
patch(path, body, options) {
109+
return this.context.http
110+
.patch(API_ROOT + path, decamelizeKeys(body), {
111+
headers: createHeaders(this.context),
112+
})
113+
.then(response => handleResponse(response, options))
114+
.catch(e => handleError(e, this.context.controller));
115+
},
116+
put(path, body, options) {
117+
return this.context.http
118+
.put(API_ROOT + path, decamelizeKeys(body), {
119+
headers: createHeaders(this.context),
120+
})
121+
.then(response => handleResponse(response, options))
122+
.catch(e => handleError(e, this.context.controller));
123+
},
124+
delete(path, query, options) {
125+
return this.context.http
126+
.delete(API_ROOT + path, query, {
127+
headers: createHeaders(this.context),
128+
})
129+
.then(response => handleResponse(response, options))
130+
.catch(e => handleError(e, this.context.controller));
131+
},
132+
request(options) {
133+
return this.context.http
134+
.request(
135+
Object.assign(options, {
136+
url: API_ROOT + options.url,
137+
body: options.body ? camelizeKeys(options.body) : null,
138+
headers: createHeaders(this.context),
139+
})
140+
)
141+
.then(response => handleResponse(response, options))
142+
.catch(e => handleError(e, this.context.controller));
143+
},
144+
};
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
function getPopupOffset({ width, height }) {
2+
const wLeft = window.screenLeft ? window.screenLeft : window.screenX;
3+
const wTop = window.screenTop ? window.screenTop : window.screenY;
4+
5+
const left = wLeft + window.innerWidth / 2 - width / 2;
6+
const top = wTop + window.innerHeight / 2 - height / 2;
7+
8+
return { top, left };
9+
}
10+
11+
function getPopupSize() {
12+
return { width: 1020, height: 618 };
13+
}
14+
15+
function getPopupDimensions() {
16+
const { width, height } = getPopupSize();
17+
const { top, left } = getPopupOffset({ width, height });
18+
19+
return `width=${width},height=${height},top=${top},left=${left}`;
20+
}
21+
22+
export default {
23+
setTitle(title) {
24+
document.title = title;
25+
},
26+
alert(message) {
27+
return alert(message); // eslint-disable-line no-alert
28+
},
29+
confirm(message) {
30+
return confirm(message); // eslint-disable-line no-alert
31+
},
32+
onUnload(cb) {
33+
window.onbeforeunload = cb;
34+
},
35+
openWindow(url) {
36+
window.open(url, '_blank');
37+
},
38+
openPopup(url, name) {
39+
const popup = window.open(
40+
url,
41+
name,
42+
`scrollbars=no,toolbar=no,location=no,titlebar=no,directories=no,status=no,menubar=no, ${getPopupDimensions()}`
43+
);
44+
return {
45+
close: () => popup.close(),
46+
};
47+
},
48+
waitForMessage(type) {
49+
return new Promise(resolve => {
50+
window.addEventListener('message', function onMessage(event) {
51+
if (event.data.type === type) {
52+
window.removeEventListener('message', onMessage);
53+
resolve(event.data.data);
54+
}
55+
});
56+
});
57+
},
58+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import addListener from '@codesandbox/common/lib/connection-manager';
2+
3+
const listeners = {};
4+
5+
export default {
6+
addListener(signalPath) {
7+
const listener = connection =>
8+
this.context.controller.getSignal(signalPath)({ connection });
9+
10+
listeners[signalPath] = addListener(listener);
11+
},
12+
removeListener(signalPath) {
13+
listeners[signalPath]();
14+
15+
delete listeners[signalPath];
16+
},
17+
};
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { getAbsoluteDependencies } from '@codesandbox/common/lib/utils/dependencies';
2+
import { protocolAndHost } from '@codesandbox/common/lib/utils/url-generator';
3+
4+
import { getGlobal } from '@codesandbox/common/lib/utils/global';
5+
6+
const global = getGlobal() as Window & { BrowserFS: any };
7+
8+
const fs = global.BrowserFS.BFSRequire('fs');
9+
const SERVICE_URL = 'https://ata-fetcher.cloud/api/v5/typings';
10+
11+
let fileInterval;
12+
let lastMTime = new Date(0);
13+
14+
function sendTypes() {
15+
global.postMessage(
16+
{
17+
$broadcast: true,
18+
$type: 'typings-sync',
19+
$data: types,
20+
},
21+
protocolAndHost()
22+
);
23+
}
24+
25+
let typeInfoPromise;
26+
let types;
27+
28+
/**
29+
* Gets all entries of dependencies -> @types/ version
30+
*/
31+
function getTypesInfo() {
32+
if (typeInfoPromise) {
33+
return typeInfoPromise;
34+
}
35+
36+
typeInfoPromise = fetch('https://unpkg.com/types-registry@latest/index.json')
37+
.then(x => x.json())
38+
.then(x => x.entries);
39+
40+
return typeInfoPromise;
41+
}
42+
43+
async function syncDependencyTypings(
44+
packageJSON: string,
45+
autoInstallTypes: boolean
46+
) {
47+
try {
48+
types = {};
49+
const { dependencies = {}, devDependencies = {} } = JSON.parse(packageJSON);
50+
51+
const totalDependencies = {
52+
'@types/jest': 'latest',
53+
...dependencies,
54+
...devDependencies,
55+
};
56+
57+
if (autoInstallTypes) {
58+
const typeInfo = await getTypesInfo();
59+
Object.keys(totalDependencies).forEach(async dep => {
60+
if (
61+
!dep.startsWith('@types/') &&
62+
!totalDependencies[`@types/${dep}`] &&
63+
typeInfo[dep]
64+
) {
65+
totalDependencies[`@types/${dep}`] = typeInfo[dep].latest;
66+
}
67+
});
68+
}
69+
70+
const absoluteDependencies = await getAbsoluteDependencies(
71+
totalDependencies
72+
);
73+
74+
return Promise.all(
75+
Object.keys(absoluteDependencies).map(async depName => {
76+
const depVersion = absoluteDependencies[depName];
77+
78+
try {
79+
const fetchRequest = await fetch(
80+
`${SERVICE_URL}/${depName}@${depVersion}.json`
81+
);
82+
83+
if (!fetchRequest.ok) {
84+
throw new Error('Fetch error');
85+
}
86+
87+
const { files } = await fetchRequest.json();
88+
types = { ...types, ...files };
89+
sendTypes();
90+
} catch (e) {
91+
if (process.env.NODE_ENV === 'development') {
92+
console.warn('Trouble fetching types for ' + depName);
93+
}
94+
return {};
95+
}
96+
})
97+
);
98+
} catch (e) {
99+
/* ignore */
100+
return Promise.resolve({});
101+
}
102+
}
103+
104+
export default {
105+
syncCurrentSandbox() {
106+
if (fileInterval) {
107+
clearInterval(fileInterval);
108+
}
109+
110+
const sendFiles = () => {
111+
if (this.context.controller.getState().editor.currentId) {
112+
const { modulesByPath } = this.context.controller.getState().editor;
113+
114+
global.postMessage(
115+
{
116+
$broadcast: true,
117+
$type: 'file-sync',
118+
$data: modulesByPath,
119+
},
120+
protocolAndHost()
121+
);
122+
}
123+
};
124+
125+
fileInterval = setInterval(() => {
126+
sendFiles();
127+
128+
try {
129+
fs.stat('/sandbox/package.json', (e, stat) => {
130+
if (e) {
131+
return;
132+
}
133+
134+
if (stat.mtime.toString() !== lastMTime.toString()) {
135+
lastMTime = stat.mtime;
136+
137+
fs.readFile('/sandbox/package.json', async (err, rv) => {
138+
if (e) {
139+
console.error(e);
140+
return;
141+
}
142+
143+
fs.stat('/sandbox/tsconfig.json', (err, result) => {
144+
// If tsconfig exists we want to sync the types
145+
syncDependencyTypings(rv.toString(), !!err || !result);
146+
});
147+
});
148+
}
149+
});
150+
} catch (e) {}
151+
}, 1000);
152+
153+
self.addEventListener('message', evt => {
154+
if (evt.data.$type === 'request-data') {
155+
sendTypes();
156+
sendFiles();
157+
}
158+
});
159+
},
160+
};

0 commit comments

Comments
 (0)