@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: a } = p();
4
+ const { queryParams: o } = p();
5
5
  return f(() => {
6
- const c = a?.intentQuery;
7
- if (!c) return { query: null, data: null };
6
+ const u = o?.intentQuery;
7
+ if (!u) return { query: null, data: null };
8
8
  let e;
9
9
  try {
10
- e = decodeURIComponent(c);
10
+ e = decodeURIComponent(u);
11
11
  } catch {
12
- e = c;
12
+ e = u;
13
13
  }
14
- const u = e.indexOf(":");
15
- if (u === -1) return { query: null, data: null };
16
- const y = e.slice(0, u), r = e.slice(u + 1), l = r.indexOf(",");
17
- let o = l === -1 ? r : r.slice(0, l), s = l === -1 ? null : r.slice(l + 1) || null;
18
- const n = o.match(/^gid:\/\/shopify\/(\w+)(?:\/(.+))?$/);
19
- if (n && (o = `shopify/${n[1]}`, s = n[2] ? `gid://shopify/${n[1]}/${n[2]}` : null), !o) return { query: null, data: null };
20
- let d = null;
21
- const i = a?.intentData;
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) && (d = t);
34
+ t !== null && typeof t == "object" && !Array.isArray(t) && (y = t);
33
35
  }
34
36
  return {
35
- query: { action: y, type: o, value: s },
36
- data: d
37
+ query: { action: s, type: a, value: d },
38
+ data: y
37
39
  };
38
- }, [a]);
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,GAClCE,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;"}
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shopify/shop-minis-react",
3
3
  "license": "SEE LICENSE IN LICENSE.txt",
4
- "version": "0.15.0",
4
+ "version": "0.15.1",
5
5
  "sideEffects": false,
6
6
  "type": "module",
7
7
  "engines": {
@@ -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