@shopify/shop-minis-react 0.15.0 → 0.15.1
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.
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import { useMemo as f } from "react";
|
|
2
2
|
import { useDeeplink as p } from "../navigation/useDeeplink.js";
|
|
3
3
|
function q() {
|
|
4
|
-
const { queryParams:
|
|
4
|
+
const { queryParams: o } = p();
|
|
5
5
|
return f(() => {
|
|
6
|
-
const
|
|
7
|
-
if (!
|
|
6
|
+
const u = o?.intentQuery;
|
|
7
|
+
if (!u) return { query: null, data: null };
|
|
8
8
|
let e;
|
|
9
9
|
try {
|
|
10
|
-
e = decodeURIComponent(
|
|
10
|
+
e = decodeURIComponent(u);
|
|
11
11
|
} catch {
|
|
12
|
-
e =
|
|
12
|
+
e = u;
|
|
13
13
|
}
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
const c = e.indexOf(":");
|
|
15
|
+
if (c === -1) return { query: null, data: null };
|
|
16
|
+
const s = e.slice(0, c);
|
|
17
|
+
if (!s) return { query: null, data: null };
|
|
18
|
+
const l = e.slice(c + 1), r = l.indexOf(",");
|
|
19
|
+
let a = r === -1 ? l : l.slice(0, r), d = r === -1 ? null : l.slice(r + 1) || null;
|
|
20
|
+
const n = a.match(/^gid:\/\/shopify\/(\w+)(?:\/(.+))?$/);
|
|
21
|
+
if (n && (a = `shopify/${n[1]}`, d = n[2] ? `gid://shopify/${n[1]}/${n[2]}` : null), !a) return { query: null, data: null };
|
|
22
|
+
let y = null;
|
|
23
|
+
const i = o?.intentData;
|
|
22
24
|
if (i) {
|
|
23
25
|
let t;
|
|
24
26
|
try {
|
|
@@ -29,13 +31,13 @@ function q() {
|
|
|
29
31
|
} catch {
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
|
-
t !== null && typeof t == "object" && !Array.isArray(t) && (
|
|
34
|
+
t !== null && typeof t == "object" && !Array.isArray(t) && (y = t);
|
|
33
35
|
}
|
|
34
36
|
return {
|
|
35
|
-
query: { action:
|
|
36
|
-
data:
|
|
37
|
+
query: { action: s, type: a, value: d },
|
|
38
|
+
data: y
|
|
37
39
|
};
|
|
38
|
-
}, [
|
|
40
|
+
}, [o]);
|
|
39
41
|
}
|
|
40
42
|
export {
|
|
41
43
|
q as useIntent
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useIntent.js","sources":["../../../src/hooks/intents/useIntent.ts"],"sourcesContent":["import {useMemo} from 'react'\n\nimport {useDeeplink} from '../navigation/useDeeplink'\n\n/**\n * Structured description of an intent, following the Shopify Intents API\n * URI format: `action:type,value`\n *\n * @see https://shopify.dev/docs/api/admin-extensions/latest/target-apis/utility-apis/intents-api\n */\nexport interface IntentQuery {\n /** Verb describing the operation (e.g., 'try_on', 'create', 'edit') */\n action: string\n /** Resource type identifier (e.g., 'shopify/Product', 'shop/UserImage') */\n type: string\n /** Resource GID (e.g., 'gid://shopify/Product/123') if applicable */\n value: string | null\n}\n\nexport interface UseIntentReturn {\n /** Parsed intent query, or null if no intent was passed */\n query: IntentQuery | null\n /** Additional JSON data passed with the intent, or null */\n data: {[key: string]: unknown} | null\n}\n\n/**\n * Parses an intent passed to this mini via deeplink.\n *\n * Follows the Shopify Intents API URI format (`action:type,value`) with\n * an optional JSON data payload passed separately.\n *\n * @see https://shopify.dev/docs/api/admin-extensions/latest/target-apis/utility-apis/intents-api\n *\n * Deeplink format:\n * ?intentQuery=action:type,value&intentData={\"key\":\"value\"}\n *\n * Examples:\n * ?intentQuery=try_on:shopify/Product,gid://shopify/Product/123\n * ?intentQuery=create:shopify/Product\n * ?intentQuery=edit:shopify/Product,gid://shopify/Product/123&intentData={\"variantId\":\"456\"}\n *\n * Shorthand GID syntax (type inferred from GID):\n * ?intentQuery=edit:gid://shopify/Product/123\n * ?intentQuery=create:gid://shopify/Product\n *\n * Use this hook to receive intents from the host app (Host → Mini direction).\n */\nexport function useIntent(): UseIntentReturn {\n const {queryParams} = useDeeplink()\n\n return useMemo(() => {\n const raw = queryParams?.intentQuery\n\n if (!raw) return {query: null, data: null}\n\n let decoded: string\n try {\n decoded = decodeURIComponent(raw)\n } catch {\n decoded = raw\n }\n\n const colonIdx = decoded.indexOf(':')\n if (colonIdx === -1) return {query: null, data: null}\n\n const action = decoded.slice(0, colonIdx)\n const rest = decoded.slice(colonIdx + 1)\n const commaIdx = rest.indexOf(',')\n\n let type = commaIdx === -1 ? rest : rest.slice(0, commaIdx)\n let value = commaIdx === -1 ? null : rest.slice(commaIdx + 1) || null\n\n // Shorthand GID syntax: edit:gid://shopify/Product/123\n // Infer type from GID and use full GID as value\n const gidMatch = type.match(/^gid:\\/\\/shopify\\/(\\w+)(?:\\/(.+))?$/)\n if (gidMatch) {\n type = `shopify/${gidMatch[1]}`\n value = gidMatch[2] ? `gid://shopify/${gidMatch[1]}/${gidMatch[2]}` : null\n }\n\n if (!type) return {query: null, data: null}\n\n let data: {[key: string]: unknown} | null = null\n const rawData = queryParams?.intentData\n if (rawData) {\n let parsed: unknown\n try {\n parsed = JSON.parse(rawData)\n } catch {\n try {\n parsed = JSON.parse(decodeURIComponent(rawData))\n } catch {\n // malformed JSON — ignore\n }\n }\n if (\n parsed !== null &&\n typeof parsed === 'object' &&\n !Array.isArray(parsed)\n ) {\n data = parsed as {[key: string]: unknown}\n }\n }\n\n return {\n query: {action, type, value},\n data,\n }\n }, [queryParams])\n}\n"],"names":["useIntent","queryParams","useDeeplink","useMemo","raw","decoded","colonIdx","action","rest","commaIdx","type","value","gidMatch","data","rawData","parsed"],"mappings":";;AAgDO,SAASA,IAA6B;AACrC,QAAA,EAAC,aAAAC,EAAW,IAAIC,EAAY;AAElC,SAAOC,EAAQ,MAAM;AACnB,UAAMC,IAAMH,GAAa;AAEzB,QAAI,CAACG,EAAK,QAAO,EAAC,OAAO,MAAM,MAAM,KAAI;AAErC,QAAAC;AACA,QAAA;AACF,MAAAA,IAAU,mBAAmBD,CAAG;AAAA,IAAA,QAC1B;AACI,MAAAC,IAAAD;AAAA,IAAA;AAGN,UAAAE,IAAWD,EAAQ,QAAQ,GAAG;AACpC,QAAIC,MAAa,GAAI,QAAO,EAAC,OAAO,MAAM,MAAM,KAAI;AAEpD,UAAMC,IAASF,EAAQ,MAAM,GAAGC,CAAQ,
|
|
1
|
+
{"version":3,"file":"useIntent.js","sources":["../../../src/hooks/intents/useIntent.ts"],"sourcesContent":["import {useMemo} from 'react'\n\nimport {useDeeplink} from '../navigation/useDeeplink'\n\n/**\n * Structured description of an intent, following the Shopify Intents API\n * URI format: `action:type,value`\n *\n * @see https://shopify.dev/docs/api/admin-extensions/latest/target-apis/utility-apis/intents-api\n */\nexport interface IntentQuery {\n /** Verb describing the operation (e.g., 'try_on', 'create', 'edit') */\n action: string\n /** Resource type identifier (e.g., 'shopify/Product', 'shop/UserImage') */\n type: string\n /** Resource GID (e.g., 'gid://shopify/Product/123') if applicable */\n value: string | null\n}\n\nexport interface UseIntentReturn {\n /** Parsed intent query, or null if no intent was passed */\n query: IntentQuery | null\n /** Additional JSON data passed with the intent, or null */\n data: {[key: string]: unknown} | null\n}\n\n/**\n * Parses an intent passed to this mini via deeplink.\n *\n * Follows the Shopify Intents API URI format (`action:type,value`) with\n * an optional JSON data payload passed separately.\n *\n * @see https://shopify.dev/docs/api/admin-extensions/latest/target-apis/utility-apis/intents-api\n *\n * Deeplink format:\n * ?intentQuery=action:type,value&intentData={\"key\":\"value\"}\n *\n * Examples:\n * ?intentQuery=try_on:shopify/Product,gid://shopify/Product/123\n * ?intentQuery=create:shopify/Product\n * ?intentQuery=edit:shopify/Product,gid://shopify/Product/123&intentData={\"variantId\":\"456\"}\n *\n * Shorthand GID syntax (type inferred from GID):\n * ?intentQuery=edit:gid://shopify/Product/123\n * ?intentQuery=create:gid://shopify/Product\n *\n * Use this hook to receive intents from the host app (Host → Mini direction).\n */\nexport function useIntent(): UseIntentReturn {\n const {queryParams} = useDeeplink()\n\n return useMemo(() => {\n const raw = queryParams?.intentQuery\n\n if (!raw) return {query: null, data: null}\n\n let decoded: string\n try {\n decoded = decodeURIComponent(raw)\n } catch {\n decoded = raw\n }\n\n const colonIdx = decoded.indexOf(':')\n if (colonIdx === -1) return {query: null, data: null}\n\n const action = decoded.slice(0, colonIdx)\n if (!action) return {query: null, data: null}\n\n const rest = decoded.slice(colonIdx + 1)\n const commaIdx = rest.indexOf(',')\n\n let type = commaIdx === -1 ? rest : rest.slice(0, commaIdx)\n let value = commaIdx === -1 ? null : rest.slice(commaIdx + 1) || null\n\n // Shorthand GID syntax: edit:gid://shopify/Product/123\n // Infer type from GID and use full GID as value\n const gidMatch = type.match(/^gid:\\/\\/shopify\\/(\\w+)(?:\\/(.+))?$/)\n if (gidMatch) {\n type = `shopify/${gidMatch[1]}`\n value = gidMatch[2] ? `gid://shopify/${gidMatch[1]}/${gidMatch[2]}` : null\n }\n\n if (!type) return {query: null, data: null}\n\n let data: {[key: string]: unknown} | null = null\n const rawData = queryParams?.intentData\n if (rawData) {\n let parsed: unknown\n try {\n parsed = JSON.parse(rawData)\n } catch {\n try {\n parsed = JSON.parse(decodeURIComponent(rawData))\n } catch {\n // malformed JSON — ignore\n }\n }\n if (\n parsed !== null &&\n typeof parsed === 'object' &&\n !Array.isArray(parsed)\n ) {\n data = parsed as {[key: string]: unknown}\n }\n }\n\n return {\n query: {action, type, value},\n data,\n }\n }, [queryParams])\n}\n"],"names":["useIntent","queryParams","useDeeplink","useMemo","raw","decoded","colonIdx","action","rest","commaIdx","type","value","gidMatch","data","rawData","parsed"],"mappings":";;AAgDO,SAASA,IAA6B;AACrC,QAAA,EAAC,aAAAC,EAAW,IAAIC,EAAY;AAElC,SAAOC,EAAQ,MAAM;AACnB,UAAMC,IAAMH,GAAa;AAEzB,QAAI,CAACG,EAAK,QAAO,EAAC,OAAO,MAAM,MAAM,KAAI;AAErC,QAAAC;AACA,QAAA;AACF,MAAAA,IAAU,mBAAmBD,CAAG;AAAA,IAAA,QAC1B;AACI,MAAAC,IAAAD;AAAA,IAAA;AAGN,UAAAE,IAAWD,EAAQ,QAAQ,GAAG;AACpC,QAAIC,MAAa,GAAI,QAAO,EAAC,OAAO,MAAM,MAAM,KAAI;AAEpD,UAAMC,IAASF,EAAQ,MAAM,GAAGC,CAAQ;AACxC,QAAI,CAACC,EAAQ,QAAO,EAAC,OAAO,MAAM,MAAM,KAAI;AAE5C,UAAMC,IAAOH,EAAQ,MAAMC,IAAW,CAAC,GACjCG,IAAWD,EAAK,QAAQ,GAAG;AAEjC,QAAIE,IAAOD,MAAa,KAAKD,IAAOA,EAAK,MAAM,GAAGC,CAAQ,GACtDE,IAAQF,MAAa,KAAK,OAAOD,EAAK,MAAMC,IAAW,CAAC,KAAK;AAI3D,UAAAG,IAAWF,EAAK,MAAM,qCAAqC;AAMjE,QALIE,MACKF,IAAA,WAAWE,EAAS,CAAC,CAAC,IACrBD,IAAAC,EAAS,CAAC,IAAI,iBAAiBA,EAAS,CAAC,CAAC,IAAIA,EAAS,CAAC,CAAC,KAAK,OAGpE,CAACF,EAAM,QAAO,EAAC,OAAO,MAAM,MAAM,KAAI;AAE1C,QAAIG,IAAwC;AAC5C,UAAMC,IAAUb,GAAa;AAC7B,QAAIa,GAAS;AACP,UAAAC;AACA,UAAA;AACO,QAAAA,IAAA,KAAK,MAAMD,CAAO;AAAA,MAAA,QACrB;AACF,YAAA;AACF,UAAAC,IAAS,KAAK,MAAM,mBAAmBD,CAAO,CAAC;AAAA,QAAA,QACzC;AAAA,QAAA;AAAA,MAER;AAGA,MAAAC,MAAW,QACX,OAAOA,KAAW,YAClB,CAAC,MAAM,QAAQA,CAAM,MAEdF,IAAAE;AAAA,IACT;AAGK,WAAA;AAAA,MACL,OAAO,EAAC,QAAAR,GAAQ,MAAAG,GAAM,OAAAC,EAAK;AAAA,MAC3B,MAAAE;AAAA,IACF;AAAA,EAAA,GACC,CAACZ,CAAW,CAAC;AAClB;"}
|
package/package.json
CHANGED
|
@@ -107,6 +107,20 @@ describe('useIntent', () => {
|
|
|
107
107
|
expect(result.current).toEqual({query: null, data: null})
|
|
108
108
|
})
|
|
109
109
|
|
|
110
|
+
it('returns null query when action is empty', () => {
|
|
111
|
+
vi.mocked(useDeeplink).mockReturnValue({
|
|
112
|
+
path: '/',
|
|
113
|
+
queryParams: {
|
|
114
|
+
intentQuery: ':shopify/Product,gid://shopify/Product/123',
|
|
115
|
+
},
|
|
116
|
+
hash: '',
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const {result} = renderHook(() => useIntent())
|
|
120
|
+
|
|
121
|
+
expect(result.current).toEqual({query: null, data: null})
|
|
122
|
+
})
|
|
123
|
+
|
|
110
124
|
it('returns null value for trailing comma', () => {
|
|
111
125
|
vi.mocked(useDeeplink).mockReturnValue({
|
|
112
126
|
path: '/',
|
|
@@ -65,6 +65,8 @@ export function useIntent(): UseIntentReturn {
|
|
|
65
65
|
if (colonIdx === -1) return {query: null, data: null}
|
|
66
66
|
|
|
67
67
|
const action = decoded.slice(0, colonIdx)
|
|
68
|
+
if (!action) return {query: null, data: null}
|
|
69
|
+
|
|
68
70
|
const rest = decoded.slice(colonIdx + 1)
|
|
69
71
|
const commaIdx = rest.indexOf(',')
|
|
70
72
|
|