@qrxcode/js 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -16
- package/dist/index.cjs +105 -37
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +103 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,9 +18,14 @@ QRX works with:
|
|
|
18
18
|
A normal QR code still contains a normal URL.
|
|
19
19
|
|
|
20
20
|
QRX-compatible applications resolve the URL,
|
|
21
|
-
discover machine-readable flows
|
|
21
|
+
discover machine-readable flows,
|
|
22
22
|
and let the application decide what to do next.
|
|
23
23
|
|
|
24
|
+
Discovery currently supports:
|
|
25
|
+
|
|
26
|
+
- HTML `<link>` declarations
|
|
27
|
+
- HTTP `Link` response headers
|
|
28
|
+
|
|
24
29
|
This works especially well with podcast websites,
|
|
25
30
|
because many podcast sites already expose one or multiple feeds.
|
|
26
31
|
|
|
@@ -100,7 +105,7 @@ Version `0.4.0` adds a new flow category:
|
|
|
100
105
|
flowType: "qrx"
|
|
101
106
|
```
|
|
102
107
|
|
|
103
|
-
A QRX flow is discovered from an explicit HTML
|
|
108
|
+
A QRX flow is discovered from an explicit HTML declaration:
|
|
104
109
|
|
|
105
110
|
```html
|
|
106
111
|
<link
|
|
@@ -121,7 +126,7 @@ These are valid:
|
|
|
121
126
|
|
|
122
127
|
```html
|
|
123
128
|
<link rel="qrx" type="application/qrx+json" href="/qrx.json">
|
|
124
|
-
<link rel="qrx" type="text/html" href="/demo.html">
|
|
129
|
+
<link rel="qrx" type="text/html" href="/qrx-demo.html">
|
|
125
130
|
<link rel="qrx" type="application/json" href="/manifest.json">
|
|
126
131
|
```
|
|
127
132
|
|
|
@@ -142,6 +147,37 @@ declared flow.
|
|
|
142
147
|
|
|
143
148
|
Applications decide what to do with discovered QRX flows.
|
|
144
149
|
|
|
150
|
+
## New in 0.5.0
|
|
151
|
+
|
|
152
|
+
Version `0.5.0` adds public HTTP `Link` header discovery.
|
|
153
|
+
|
|
154
|
+
QRX can now discover flows from:
|
|
155
|
+
|
|
156
|
+
* HTML `<link>` tags
|
|
157
|
+
* HTTP `Link` response headers
|
|
158
|
+
|
|
159
|
+
Example:
|
|
160
|
+
|
|
161
|
+
```http
|
|
162
|
+
Link: </qrx-demo/>; rel="qrx"; type="text/html"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
HTTP `Link` header discovery lets a server expose QRX flows
|
|
166
|
+
at the HTTP layer,
|
|
167
|
+
without placing the QRX declaration inside the HTML source.
|
|
168
|
+
|
|
169
|
+
HTTP header flows are returned before HTML flows.
|
|
170
|
+
|
|
171
|
+
This is deterministic discovery order only.
|
|
172
|
+
|
|
173
|
+
It is not ranking, priority, override, or deduplication.
|
|
174
|
+
|
|
175
|
+
If the same flow appears in both HTTP headers and HTML,
|
|
176
|
+
both flows are returned.
|
|
177
|
+
|
|
178
|
+
QRX discovers recognized flows.
|
|
179
|
+
Applications decide what to do with them.
|
|
180
|
+
|
|
145
181
|
## Install
|
|
146
182
|
|
|
147
183
|
```bash
|
|
@@ -164,6 +200,12 @@ console.log(result.flows);
|
|
|
164
200
|
|
|
165
201
|
```js
|
|
166
202
|
[
|
|
203
|
+
{
|
|
204
|
+
flowType: "qrx",
|
|
205
|
+
rel: "qrx",
|
|
206
|
+
href: "https://example.com/qrx-demo/",
|
|
207
|
+
type: "text/html"
|
|
208
|
+
},
|
|
167
209
|
{
|
|
168
210
|
flowType: "feed",
|
|
169
211
|
rel: "alternate",
|
|
@@ -233,7 +275,7 @@ const qrxFlows = selectFlowsByFlowType(
|
|
|
233
275
|
|
|
234
276
|
## Supported discovery methods
|
|
235
277
|
|
|
236
|
-
Feed flow:
|
|
278
|
+
Feed flow from HTML:
|
|
237
279
|
|
|
238
280
|
```html
|
|
239
281
|
<link
|
|
@@ -242,21 +284,13 @@ Feed flow:
|
|
|
242
284
|
href="/feed.xml">
|
|
243
285
|
```
|
|
244
286
|
|
|
245
|
-
|
|
246
|
-
<link
|
|
247
|
-
rel="alternate"
|
|
248
|
-
type="application/atom+xml"
|
|
249
|
-
href="/atom.xml">
|
|
250
|
-
```
|
|
287
|
+
Feed flow from HTTP:
|
|
251
288
|
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
rel="alternate"
|
|
255
|
-
type="application/feed+json"
|
|
256
|
-
href="/feed.json">
|
|
289
|
+
```http
|
|
290
|
+
Link: </feed.xml>; rel="alternate"; type="application/rss+xml"
|
|
257
291
|
```
|
|
258
292
|
|
|
259
|
-
QRX flow:
|
|
293
|
+
QRX flow from HTML:
|
|
260
294
|
|
|
261
295
|
```html
|
|
262
296
|
<link
|
|
@@ -265,6 +299,12 @@ QRX flow:
|
|
|
265
299
|
href="/qrx.json">
|
|
266
300
|
```
|
|
267
301
|
|
|
302
|
+
QRX flow from HTTP:
|
|
303
|
+
|
|
304
|
+
```http
|
|
305
|
+
Link: </qrx-demo/>; rel="qrx"; type="text/html"
|
|
306
|
+
```
|
|
307
|
+
|
|
268
308
|
## Philosophy
|
|
269
309
|
|
|
270
310
|
QRX does not change QR codes.
|
package/dist/index.cjs
CHANGED
|
@@ -20,7 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
detectFlowCandidate: () => detectFlowCandidate,
|
|
23
24
|
detectFlows: () => detectFlows,
|
|
25
|
+
detectFlowsFromLinkHeader: () => detectFlowsFromLinkHeader,
|
|
24
26
|
resolveQRX: () => resolveQRX,
|
|
25
27
|
selectFlowsByFlowType: () => selectFlowsByFlowType
|
|
26
28
|
});
|
|
@@ -33,45 +35,101 @@ var FEED_TYPES = /* @__PURE__ */ new Set([
|
|
|
33
35
|
"application/feed+json"
|
|
34
36
|
]);
|
|
35
37
|
function detectFlows(html, sourceUrl) {
|
|
36
|
-
const
|
|
38
|
+
const candidates = extractHtmlLinkCandidates(html);
|
|
39
|
+
return candidates.map(
|
|
40
|
+
(candidate) => detectFlowCandidate(candidate, sourceUrl)
|
|
41
|
+
).filter((flow) => flow !== null);
|
|
42
|
+
}
|
|
43
|
+
function detectFlowsFromLinkHeader(linkHeader, sourceUrl) {
|
|
44
|
+
if (!linkHeader) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const candidates = extractHttpLinkCandidates(linkHeader);
|
|
48
|
+
return candidates.map(
|
|
49
|
+
(candidate) => detectFlowCandidate(candidate, sourceUrl)
|
|
50
|
+
).filter((flow) => flow !== null);
|
|
51
|
+
}
|
|
52
|
+
function detectFlowCandidate(candidate, sourceUrl) {
|
|
53
|
+
const { rel, type, href } = candidate;
|
|
54
|
+
if (!rel || !href) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const normalizedRel = rel.trim();
|
|
58
|
+
if (!normalizedRel) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const resolvedHref = new URL(
|
|
62
|
+
href,
|
|
63
|
+
sourceUrl
|
|
64
|
+
).toString();
|
|
65
|
+
if (normalizedRel === "qrx") {
|
|
66
|
+
if (!type) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
flowType: "qrx",
|
|
71
|
+
rel: normalizedRel,
|
|
72
|
+
href: resolvedHref,
|
|
73
|
+
type: type.trim().toLowerCase()
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (!type) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const normalizedType = type.trim().toLowerCase();
|
|
80
|
+
if (!hasRel(normalizedRel, "alternate")) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if (!FEED_TYPES.has(normalizedType)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
flowType: "feed",
|
|
88
|
+
rel: normalizedRel,
|
|
89
|
+
href: resolvedHref,
|
|
90
|
+
type: normalizedType
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function extractHtmlLinkCandidates(html) {
|
|
37
94
|
const linkTagRegex = /<link\s+[^>]*>/gi;
|
|
38
95
|
const tags = html.match(linkTagRegex) || [];
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
96
|
+
return tags.map((tag) => ({
|
|
97
|
+
rel: getAttribute(tag, "rel"),
|
|
98
|
+
type: getAttribute(tag, "type"),
|
|
99
|
+
href: getAttribute(tag, "href")
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
function extractHttpLinkCandidates(linkHeader) {
|
|
103
|
+
return splitLinkHeader(linkHeader).map(parseHttpLinkValue).filter(
|
|
104
|
+
(candidate) => candidate !== null
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
function parseHttpLinkValue(value) {
|
|
108
|
+
const hrefMatch = value.match(/^\s*<([^>]+)>/);
|
|
109
|
+
if (!hrefMatch) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const candidate = {
|
|
113
|
+
href: hrefMatch[1],
|
|
114
|
+
rel: null,
|
|
115
|
+
type: null
|
|
116
|
+
};
|
|
117
|
+
const paramRegex = /;\s*([a-zA-Z][a-zA-Z0-9_-]*)\s*=\s*"([^"]*)"/g;
|
|
118
|
+
let match;
|
|
119
|
+
while ((match = paramRegex.exec(value)) !== null) {
|
|
120
|
+
const name = match[1].toLowerCase();
|
|
121
|
+
const paramValue = match[2];
|
|
122
|
+
if (name === "rel") {
|
|
123
|
+
candidate.rel = paramValue;
|
|
63
124
|
}
|
|
64
|
-
if (
|
|
65
|
-
|
|
125
|
+
if (name === "type") {
|
|
126
|
+
candidate.type = paramValue;
|
|
66
127
|
}
|
|
67
|
-
flows.push({
|
|
68
|
-
flowType: "feed",
|
|
69
|
-
rel: normalizedRel,
|
|
70
|
-
href: resolvedHref,
|
|
71
|
-
type: normalizedType
|
|
72
|
-
});
|
|
73
128
|
}
|
|
74
|
-
return
|
|
129
|
+
return candidate;
|
|
130
|
+
}
|
|
131
|
+
function splitLinkHeader(linkHeader) {
|
|
132
|
+
return linkHeader.split(/,\s*(?=<)/).map((part) => part.trim()).filter(Boolean);
|
|
75
133
|
}
|
|
76
134
|
function getAttribute(tag, attribute) {
|
|
77
135
|
const regex = new RegExp(
|
|
@@ -88,13 +146,21 @@ function hasRel(rel, expected) {
|
|
|
88
146
|
// src/resolve.ts
|
|
89
147
|
async function resolveQRX(url) {
|
|
90
148
|
const response = await fetch(url);
|
|
149
|
+
const sourceUrl = response.url;
|
|
150
|
+
const headerFlows = detectFlowsFromLinkHeader(
|
|
151
|
+
response.headers.get("link"),
|
|
152
|
+
sourceUrl
|
|
153
|
+
);
|
|
91
154
|
const html = await response.text();
|
|
92
|
-
const
|
|
155
|
+
const htmlFlows = detectFlows(
|
|
93
156
|
html,
|
|
94
|
-
|
|
157
|
+
sourceUrl
|
|
95
158
|
);
|
|
96
159
|
return {
|
|
97
|
-
flows
|
|
160
|
+
flows: [
|
|
161
|
+
...headerFlows,
|
|
162
|
+
...htmlFlows
|
|
163
|
+
]
|
|
98
164
|
};
|
|
99
165
|
}
|
|
100
166
|
|
|
@@ -108,7 +174,9 @@ function selectFlowsByFlowType(flows, preferredFlowTypes) {
|
|
|
108
174
|
}
|
|
109
175
|
// Annotate the CommonJS export names for ESM import in node:
|
|
110
176
|
0 && (module.exports = {
|
|
177
|
+
detectFlowCandidate,
|
|
111
178
|
detectFlows,
|
|
179
|
+
detectFlowsFromLinkHeader,
|
|
112
180
|
resolveQRX,
|
|
113
181
|
selectFlowsByFlowType
|
|
114
182
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -9,10 +9,17 @@ interface QRXResult {
|
|
|
9
9
|
flows: Flow[];
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
interface LinkCandidate {
|
|
13
|
+
rel: string | null;
|
|
14
|
+
href: string | null;
|
|
15
|
+
type: string | null;
|
|
16
|
+
}
|
|
12
17
|
declare function detectFlows(html: string, sourceUrl: string): Flow[];
|
|
18
|
+
declare function detectFlowsFromLinkHeader(linkHeader: string | null, sourceUrl: string): Flow[];
|
|
19
|
+
declare function detectFlowCandidate(candidate: LinkCandidate, sourceUrl: string): Flow | null;
|
|
13
20
|
|
|
14
21
|
declare function resolveQRX(url: string): Promise<QRXResult>;
|
|
15
22
|
|
|
16
23
|
declare function selectFlowsByFlowType(flows: Flow[], preferredFlowTypes: FlowType[]): Flow[];
|
|
17
24
|
|
|
18
|
-
export { type Flow, type FlowType, type QRXResult, detectFlows, resolveQRX, selectFlowsByFlowType };
|
|
25
|
+
export { type Flow, type FlowType, type QRXResult, detectFlowCandidate, detectFlows, detectFlowsFromLinkHeader, resolveQRX, selectFlowsByFlowType };
|
package/dist/index.d.ts
CHANGED
|
@@ -9,10 +9,17 @@ interface QRXResult {
|
|
|
9
9
|
flows: Flow[];
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
interface LinkCandidate {
|
|
13
|
+
rel: string | null;
|
|
14
|
+
href: string | null;
|
|
15
|
+
type: string | null;
|
|
16
|
+
}
|
|
12
17
|
declare function detectFlows(html: string, sourceUrl: string): Flow[];
|
|
18
|
+
declare function detectFlowsFromLinkHeader(linkHeader: string | null, sourceUrl: string): Flow[];
|
|
19
|
+
declare function detectFlowCandidate(candidate: LinkCandidate, sourceUrl: string): Flow | null;
|
|
13
20
|
|
|
14
21
|
declare function resolveQRX(url: string): Promise<QRXResult>;
|
|
15
22
|
|
|
16
23
|
declare function selectFlowsByFlowType(flows: Flow[], preferredFlowTypes: FlowType[]): Flow[];
|
|
17
24
|
|
|
18
|
-
export { type Flow, type FlowType, type QRXResult, detectFlows, resolveQRX, selectFlowsByFlowType };
|
|
25
|
+
export { type Flow, type FlowType, type QRXResult, detectFlowCandidate, detectFlows, detectFlowsFromLinkHeader, resolveQRX, selectFlowsByFlowType };
|
package/dist/index.js
CHANGED
|
@@ -5,45 +5,101 @@ var FEED_TYPES = /* @__PURE__ */ new Set([
|
|
|
5
5
|
"application/feed+json"
|
|
6
6
|
]);
|
|
7
7
|
function detectFlows(html, sourceUrl) {
|
|
8
|
-
const
|
|
8
|
+
const candidates = extractHtmlLinkCandidates(html);
|
|
9
|
+
return candidates.map(
|
|
10
|
+
(candidate) => detectFlowCandidate(candidate, sourceUrl)
|
|
11
|
+
).filter((flow) => flow !== null);
|
|
12
|
+
}
|
|
13
|
+
function detectFlowsFromLinkHeader(linkHeader, sourceUrl) {
|
|
14
|
+
if (!linkHeader) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
const candidates = extractHttpLinkCandidates(linkHeader);
|
|
18
|
+
return candidates.map(
|
|
19
|
+
(candidate) => detectFlowCandidate(candidate, sourceUrl)
|
|
20
|
+
).filter((flow) => flow !== null);
|
|
21
|
+
}
|
|
22
|
+
function detectFlowCandidate(candidate, sourceUrl) {
|
|
23
|
+
const { rel, type, href } = candidate;
|
|
24
|
+
if (!rel || !href) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const normalizedRel = rel.trim();
|
|
28
|
+
if (!normalizedRel) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const resolvedHref = new URL(
|
|
32
|
+
href,
|
|
33
|
+
sourceUrl
|
|
34
|
+
).toString();
|
|
35
|
+
if (normalizedRel === "qrx") {
|
|
36
|
+
if (!type) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
flowType: "qrx",
|
|
41
|
+
rel: normalizedRel,
|
|
42
|
+
href: resolvedHref,
|
|
43
|
+
type: type.trim().toLowerCase()
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (!type) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const normalizedType = type.trim().toLowerCase();
|
|
50
|
+
if (!hasRel(normalizedRel, "alternate")) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
if (!FEED_TYPES.has(normalizedType)) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
flowType: "feed",
|
|
58
|
+
rel: normalizedRel,
|
|
59
|
+
href: resolvedHref,
|
|
60
|
+
type: normalizedType
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function extractHtmlLinkCandidates(html) {
|
|
9
64
|
const linkTagRegex = /<link\s+[^>]*>/gi;
|
|
10
65
|
const tags = html.match(linkTagRegex) || [];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
66
|
+
return tags.map((tag) => ({
|
|
67
|
+
rel: getAttribute(tag, "rel"),
|
|
68
|
+
type: getAttribute(tag, "type"),
|
|
69
|
+
href: getAttribute(tag, "href")
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
function extractHttpLinkCandidates(linkHeader) {
|
|
73
|
+
return splitLinkHeader(linkHeader).map(parseHttpLinkValue).filter(
|
|
74
|
+
(candidate) => candidate !== null
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
function parseHttpLinkValue(value) {
|
|
78
|
+
const hrefMatch = value.match(/^\s*<([^>]+)>/);
|
|
79
|
+
if (!hrefMatch) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const candidate = {
|
|
83
|
+
href: hrefMatch[1],
|
|
84
|
+
rel: null,
|
|
85
|
+
type: null
|
|
86
|
+
};
|
|
87
|
+
const paramRegex = /;\s*([a-zA-Z][a-zA-Z0-9_-]*)\s*=\s*"([^"]*)"/g;
|
|
88
|
+
let match;
|
|
89
|
+
while ((match = paramRegex.exec(value)) !== null) {
|
|
90
|
+
const name = match[1].toLowerCase();
|
|
91
|
+
const paramValue = match[2];
|
|
92
|
+
if (name === "rel") {
|
|
93
|
+
candidate.rel = paramValue;
|
|
35
94
|
}
|
|
36
|
-
if (
|
|
37
|
-
|
|
95
|
+
if (name === "type") {
|
|
96
|
+
candidate.type = paramValue;
|
|
38
97
|
}
|
|
39
|
-
flows.push({
|
|
40
|
-
flowType: "feed",
|
|
41
|
-
rel: normalizedRel,
|
|
42
|
-
href: resolvedHref,
|
|
43
|
-
type: normalizedType
|
|
44
|
-
});
|
|
45
98
|
}
|
|
46
|
-
return
|
|
99
|
+
return candidate;
|
|
100
|
+
}
|
|
101
|
+
function splitLinkHeader(linkHeader) {
|
|
102
|
+
return linkHeader.split(/,\s*(?=<)/).map((part) => part.trim()).filter(Boolean);
|
|
47
103
|
}
|
|
48
104
|
function getAttribute(tag, attribute) {
|
|
49
105
|
const regex = new RegExp(
|
|
@@ -60,13 +116,21 @@ function hasRel(rel, expected) {
|
|
|
60
116
|
// src/resolve.ts
|
|
61
117
|
async function resolveQRX(url) {
|
|
62
118
|
const response = await fetch(url);
|
|
119
|
+
const sourceUrl = response.url;
|
|
120
|
+
const headerFlows = detectFlowsFromLinkHeader(
|
|
121
|
+
response.headers.get("link"),
|
|
122
|
+
sourceUrl
|
|
123
|
+
);
|
|
63
124
|
const html = await response.text();
|
|
64
|
-
const
|
|
125
|
+
const htmlFlows = detectFlows(
|
|
65
126
|
html,
|
|
66
|
-
|
|
127
|
+
sourceUrl
|
|
67
128
|
);
|
|
68
129
|
return {
|
|
69
|
-
flows
|
|
130
|
+
flows: [
|
|
131
|
+
...headerFlows,
|
|
132
|
+
...htmlFlows
|
|
133
|
+
]
|
|
70
134
|
};
|
|
71
135
|
}
|
|
72
136
|
|
|
@@ -79,7 +143,9 @@ function selectFlowsByFlowType(flows, preferredFlowTypes) {
|
|
|
79
143
|
);
|
|
80
144
|
}
|
|
81
145
|
export {
|
|
146
|
+
detectFlowCandidate,
|
|
82
147
|
detectFlows,
|
|
148
|
+
detectFlowsFromLinkHeader,
|
|
83
149
|
resolveQRX,
|
|
84
150
|
selectFlowsByFlowType
|
|
85
151
|
};
|