UNPKG

8.23 kBJavaScriptView Raw
1const _excluded = ["stories", "v"];
2
3function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
4
5function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
6
7function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
8
9import "core-js/modules/es.array.reduce.js";
10import global from 'global';
11import dedent from 'ts-dedent';
12import { transformStoriesRawToStoriesHash, transformStoryIndexToStoriesHash } from '../lib/stories';
13const {
14 location,
15 fetch
16} = global;
17// eslint-disable-next-line no-useless-escape
18const findFilename = /(\/((?:[^\/]+?)\.[^\/]+?)|\/)$/;
19export const getSourceType = (source, refId) => {
20 const {
21 origin: localOrigin,
22 pathname: localPathname
23 } = location;
24 const {
25 origin: sourceOrigin,
26 pathname: sourcePathname
27 } = new URL(source);
28 const localFull = `${localOrigin + localPathname}`.replace(findFilename, '');
29 const sourceFull = `${sourceOrigin + sourcePathname}`.replace(findFilename, '');
30
31 if (localFull === sourceFull) {
32 return ['local', sourceFull];
33 }
34
35 if (refId || source) {
36 return ['external', sourceFull];
37 }
38
39 return [null, null];
40};
41export const defaultStoryMapper = (b, a) => {
42 return Object.assign({}, a, {
43 kind: a.kind.replace('|', '/')
44 });
45};
46
47const addRefIds = (input, ref) => {
48 return Object.entries(input).reduce((acc, [id, item]) => {
49 return Object.assign({}, acc, {
50 [id]: Object.assign({}, item, {
51 refId: ref.id
52 })
53 });
54 }, {});
55};
56
57const handle = async request => {
58 if (request) {
59 return Promise.resolve(request).then(response => response.ok ? response.json() : {}).catch(error => ({
60 error
61 }));
62 }
63
64 return {};
65};
66
67const map = (input, ref, options) => {
68 const {
69 storyMapper
70 } = options;
71
72 if (storyMapper) {
73 return Object.entries(input).reduce((acc, [id, item]) => {
74 return Object.assign({}, acc, {
75 [id]: storyMapper(ref, item)
76 });
77 }, {});
78 }
79
80 return input;
81};
82
83export const init = ({
84 store,
85 provider,
86 singleStory
87}, {
88 runCheck = true
89} = {}) => {
90 const api = {
91 findRef: source => {
92 const refs = api.getRefs();
93 return Object.values(refs).find(({
94 url
95 }) => url.match(source));
96 },
97 changeRefVersion: (id, url) => {
98 const {
99 versions,
100 title
101 } = api.getRefs()[id];
102 const ref = {
103 id,
104 url,
105 versions,
106 title,
107 stories: {}
108 };
109 api.checkRef(ref);
110 },
111 changeRefState: (id, ready) => {
112 const _api$getRefs = api.getRefs(),
113 {
114 [id]: ref
115 } = _api$getRefs,
116 updated = _objectWithoutPropertiesLoose(_api$getRefs, [id].map(_toPropertyKey));
117
118 updated[id] = Object.assign({}, ref, {
119 ready
120 });
121 store.setState({
122 refs: updated
123 });
124 },
125 checkRef: async ref => {
126 const {
127 id,
128 url,
129 version,
130 type
131 } = ref;
132 const isPublic = type === 'server-checked'; // ref's type starts as either 'unknown' or 'server-checked'
133 // "server-checked" happens when we were able to verify the storybook is accessible from node (without cookies)
134 // "unknown" happens if the request was declined of failed (this can happen because the storybook doesn't exists or authentication is required)
135 //
136 // we then make a request for stories.json
137 //
138 // if this request fails when storybook is server-checked we mark the ref as "auto-inject", this is a fallback mechanism for local storybook, legacy storybooks, and storybooks that lack stories.json
139 // if the request fails with type "unknown" we give up and show an error
140 // if the request succeeds we set the ref to 'lazy' type, and show the stories in the sidebar without injecting the iframe first
141 //
142 // then we fetch metadata if the above fetch succeeded
143
144 const loadedData = {};
145 const query = version ? `?version=${version}` : '';
146 const credentials = isPublic ? 'omit' : 'include'; // In theory the `/iframe.html` could be private and the `stories.json` could not exist, but in practice
147 // the only private servers we know about (Chromatic) always include `stories.json`. So we can tell
148 // if the ref actually exists by simply checking `stories.json` w/ credentials.
149
150 const storiesFetch = await fetch(`${url}/stories.json${query}`, {
151 headers: {
152 Accept: 'application/json'
153 },
154 credentials
155 });
156
157 if (!storiesFetch.ok && !isPublic) {
158 loadedData.error = {
159 message: dedent`
160 Error: Loading of ref failed
161 at fetch (lib/api/src/modules/refs.ts)
162
163 URL: ${url}
164
165 We weren't able to load the above URL,
166 it's possible a CORS error happened.
167
168 Please check your dev-tools network tab.
169 `
170 };
171 } else if (storiesFetch.ok) {
172 const [stories, metadata] = await Promise.all([handle(storiesFetch), handle(fetch(`${url}/metadata.json${query}`, {
173 headers: {
174 Accept: 'application/json'
175 },
176 credentials,
177 cache: 'no-cache'
178 }).catch(() => false))]);
179 Object.assign(loadedData, Object.assign({}, stories, metadata));
180 }
181
182 const versions = ref.versions && Object.keys(ref.versions).length ? ref.versions : loadedData.versions;
183 await api.setRef(id, Object.assign({
184 id,
185 url
186 }, loadedData, versions ? {
187 versions
188 } : {}, {
189 error: loadedData.error,
190 type: !loadedData.stories ? 'auto-inject' : 'lazy'
191 }));
192 },
193 getRefs: () => {
194 const {
195 refs = {}
196 } = store.getState();
197 return refs;
198 },
199 setRef: (id, _ref, ready = false) => {
200 let {
201 stories,
202 v
203 } = _ref,
204 rest = _objectWithoutPropertiesLoose(_ref, _excluded);
205
206 if (singleStory) return;
207 const {
208 storyMapper = defaultStoryMapper
209 } = provider.getConfig();
210 const ref = api.getRefs()[id];
211 let storiesHash;
212
213 if (stories) {
214 if (v === 2) {
215 storiesHash = transformStoriesRawToStoriesHash(map(stories, ref, {
216 storyMapper
217 }), {
218 provider
219 });
220 } else if (!v) {
221 throw new Error('Composition: Missing stories.json version');
222 } else {
223 const index = stories;
224 storiesHash = transformStoryIndexToStoriesHash({
225 v,
226 stories: index
227 }, {
228 provider
229 });
230 }
231
232 storiesHash = addRefIds(storiesHash, ref);
233 }
234
235 api.updateRef(id, Object.assign({
236 stories: storiesHash
237 }, rest, {
238 ready
239 }));
240 },
241 updateRef: (id, data) => {
242 const _api$getRefs2 = api.getRefs(),
243 {
244 [id]: ref
245 } = _api$getRefs2,
246 updated = _objectWithoutPropertiesLoose(_api$getRefs2, [id].map(_toPropertyKey));
247
248 updated[id] = Object.assign({}, ref, data);
249 /* eslint-disable no-param-reassign */
250
251 const ordered = Object.keys(initialState).reduce((obj, key) => {
252 obj[key] = updated[key];
253 return obj;
254 }, {});
255 /* eslint-enable no-param-reassign */
256
257 store.setState({
258 refs: ordered
259 });
260 }
261 };
262 const refs = !singleStory && provider.getConfig().refs || {};
263 const initialState = refs;
264
265 if (runCheck) {
266 Object.entries(refs).forEach(([k, v]) => {
267 api.checkRef(v);
268 });
269 }
270
271 return {
272 api,
273 state: {
274 refs: initialState
275 }
276 };
277};
\No newline at end of file