@limrun/ui 0.14.0-rc.3 → 0.14.0-rc.4

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.
Files changed (67) hide show
  1. package/dist/app-store-relay/index.cjs +3 -3
  2. package/dist/app-store-relay/index.js +5 -5
  3. package/dist/app-store-relay/react.cjs +1 -1
  4. package/dist/app-store-relay/react.js +32 -33
  5. package/dist/apple-credentials/index.cjs +1 -0
  6. package/dist/apple-credentials/index.d.ts +59 -0
  7. package/dist/apple-credentials/index.js +227 -0
  8. package/dist/browser-storage-BnO-Ma-e.mjs +219 -0
  9. package/dist/browser-storage-DLxIqZk5.js +1 -0
  10. package/dist/client-D1drDXxE.js +1 -0
  11. package/dist/{client-C8ZDEjtD.mjs → client-DSuNtWDv.mjs} +1 -1
  12. package/dist/components/remote-control.d.ts +156 -0
  13. package/dist/core/device-install/storage/browser-storage.d.ts +8 -0
  14. package/dist/core/device-install/storage/index.d.ts +1 -0
  15. package/dist/core/device-install/storage/secret-store.d.ts +101 -0
  16. package/dist/core/device-install/types.d.ts +6 -0
  17. package/dist/core/errors.d.ts +1 -0
  18. package/dist/core/play-publish/google.d.ts +1 -1
  19. package/dist/device-build/index.cjs +1 -1
  20. package/dist/device-build/index.js +21 -20
  21. package/dist/device-build/react.cjs +1 -1
  22. package/dist/device-build/react.js +42 -43
  23. package/dist/device-install/index.cjs +1 -1
  24. package/dist/device-install/index.js +159 -161
  25. package/dist/device-install/react.cjs +1 -1
  26. package/dist/device-install/react.js +47 -49
  27. package/dist/errors-BSkqS7U-.js +1 -0
  28. package/dist/errors-gy8yQJos.mjs +6 -0
  29. package/dist/gsa-srp-Bme3JLEi.mjs +266 -0
  30. package/dist/gsa-srp-CCIfUz7Y.js +1 -0
  31. package/dist/index-ChhtuQ7e.js +22 -0
  32. package/dist/index-YDu8K9a4.mjs +11279 -0
  33. package/dist/index.cjs +1 -1
  34. package/dist/index.css +1 -1
  35. package/dist/index.js +1405 -1125
  36. package/dist/play-publish/index.cjs +1 -1
  37. package/dist/play-publish/index.js +27 -24
  38. package/dist/play-publish/react.cjs +1 -1
  39. package/dist/play-publish/react.d.ts +12 -2
  40. package/dist/play-publish/react.js +70 -60
  41. package/dist/{provisioning-BmsEnF6X.mjs → provisioning-B_DefBEp.mjs} +1 -1
  42. package/dist/{provisioning-Va0PH4Kk.js → provisioning-BlQMccX_.js} +1 -1
  43. package/package.json +6 -1
  44. package/src/app-store-relay/react.ts +1 -4
  45. package/src/apple-credentials/index.test.ts +158 -0
  46. package/src/apple-credentials/index.ts +241 -0
  47. package/src/components/remote-control.css +32 -0
  48. package/src/components/remote-control.tsx +865 -5
  49. package/src/core/device-install/operations/operations.ts +1 -4
  50. package/src/core/device-install/storage/browser-storage.test.ts +65 -0
  51. package/src/core/device-install/storage/browser-storage.ts +33 -0
  52. package/src/core/device-install/storage/index.ts +1 -0
  53. package/src/core/device-install/storage/secret-store.ts +298 -0
  54. package/src/core/device-install/types.ts +6 -0
  55. package/src/core/errors.ts +3 -0
  56. package/src/core/play-publish/google.ts +11 -2
  57. package/src/device-build/react.ts +1 -4
  58. package/src/device-install/demo/demo.tsx +1 -4
  59. package/src/device-install/react.ts +1 -4
  60. package/src/play-publish/README.md +5 -4
  61. package/src/play-publish/react.ts +72 -32
  62. package/vite.config.ts +1 -0
  63. package/dist/browser-storage-Cx9eW-bm.mjs +0 -205
  64. package/dist/browser-storage-jJaIht4K.js +0 -1
  65. package/dist/client-BtC4n_qf.js +0 -1
  66. package/dist/index-CMeQfhYy.js +0 -22
  67. package/dist/index-DFdCvi2_.mjs +0 -11543
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y="https://accounts.google.com/gsi/client",p="https://www.googleapis.com/auth/androidpublisher";function d(){return globalThis.google?.accounts?.oauth2}let c;function w(){return d()?Promise.resolve():typeof document>"u"?Promise.reject(new Error("Google sign-in requires a browser environment.")):(c||(c=new Promise((r,o)=>{const t=document.createElement("script");t.src=y,t.async=!0,t.addEventListener("load",()=>{d()?r():o(new Error("Google sign-in script loaded without the OAuth client."))},{once:!0}),t.addEventListener("error",()=>{t.remove(),o(new Error("Failed to load the Google sign-in script."))},{once:!0}),document.head.appendChild(t)}),c.catch(()=>{c=void 0})),c)}async function f({clientId:r,scope:o=p}){return await w(),new Promise((t,n)=>{d().initTokenClient({client_id:r,scope:o,callback:e=>{if(e.error){n(new Error(e.error_description||e.error));return}if(!e.access_token){n(new Error("Google returned no access token."));return}t(e.access_token)},error_callback:e=>{const a=e?.type==="popup_failed_to_open"?"Google sign-in popup was blocked by the browser.":"Google sign-in was cancelled.";n(new Error(e?.message||a))}}).requestAccessToken()})}class l extends Error{code;status;constructor(o,t={}){super(o),this.name="PlaystorePublishError",this.code=t.code,this.status=t.status}}async function m(r){const{registryApiUrl:o,token:t,organizationId:n,...h}=r;let e;try{e=new URL(o)}catch{throw new l(`Invalid registry URL: ${o}`)}e.pathname=`${e.pathname.replace(/\/$/,"")}/android/playstore/publish`,e.search="",e.hash="";const a={"Content-Type":"application/json"};t&&(a.Authorization=`Bearer ${t}`),n&&(a["X-Limrun-Organization"]=n);let s;try{s=await fetch(e.toString(),{method:"POST",headers:a,body:JSON.stringify(h)})}catch(u){throw new l(`Cannot reach the registry: ${u instanceof Error?u.message:String(u)}`)}const g=await s.text();let i;try{i=g?JSON.parse(g):void 0}catch{i=void 0}if(!s.ok)throw new l(i?.message||`Play publish failed with HTTP ${s.status}`,{code:i?.code,status:s.status});if(typeof i?.versionCode!="number")throw new l("Play publish response is missing versionCode.",{status:s.status});return{versionCode:i.versionCode}}exports.ANDROID_PUBLISHER_SCOPE=p;exports.PlaystorePublishError=l;exports.loadGoogleIdentityServices=w;exports.publishToPlaystore=m;exports.requestGoogleAccessToken=f;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f="https://accounts.google.com/gsi/client",w="https://www.googleapis.com/auth/androidpublisher";function u(){return globalThis.google?.accounts?.oauth2}let c;function y(){return u()?Promise.resolve():typeof document>"u"?Promise.reject(new Error("Google sign-in requires a browser environment.")):(c||(c=new Promise((o,r)=>{const t=document.createElement("script");t.src=f,t.async=!0,t.addEventListener("load",()=>{u()?o():r(new Error("Google sign-in script loaded without the OAuth client."))},{once:!0}),t.addEventListener("error",()=>{t.remove(),r(new Error("Failed to load the Google sign-in script."))},{once:!0}),document.head.appendChild(t)}),c.catch(()=>{c=void 0})),c)}function m(o){return u()?p(o):y().then(()=>p(o))}function p({clientId:o,scope:r=w}){return new Promise((t,n)=>{u().initTokenClient({client_id:o,scope:r,callback:e=>{if(e.error){n(new Error(e.error_description||e.error));return}if(!e.access_token){n(new Error("Google returned no access token."));return}t(e.access_token)},error_callback:e=>{const a=e?.type==="popup_failed_to_open"?"Google sign-in popup was blocked by the browser.":"Google sign-in was cancelled.";n(new Error(e?.message||a))}}).requestAccessToken()})}class l extends Error{code;status;constructor(r,t={}){super(r),this.name="PlaystorePublishError",this.code=t.code,this.status=t.status}}async function b(o){const{registryApiUrl:r,token:t,organizationId:n,...h}=o;let e;try{e=new URL(r)}catch{throw new l(`Invalid registry URL: ${r}`)}e.pathname=`${e.pathname.replace(/\/$/,"")}/android/playstore/publish`,e.search="",e.hash="";const a={"Content-Type":"application/json"};t&&(a.Authorization=`Bearer ${t}`),n&&(a["X-Limrun-Organization"]=n);let s;try{s=await fetch(e.toString(),{method:"POST",headers:a,body:JSON.stringify(h)})}catch(d){throw new l(`Cannot reach the registry: ${d instanceof Error?d.message:String(d)}`)}const g=await s.text();let i;try{i=g?JSON.parse(g):void 0}catch{i=void 0}if(!s.ok)throw new l(i?.message||`Play publish failed with HTTP ${s.status}`,{code:i?.code,status:s.status});if(typeof i?.versionCode!="number")throw new l("Play publish response is missing versionCode.",{status:s.status});return{versionCode:i.versionCode}}exports.ANDROID_PUBLISHER_SCOPE=w;exports.PlaystorePublishError=l;exports.loadGoogleIdentityServices=y;exports.publishToPlaystore=b;exports.requestGoogleAccessToken=m;
@@ -1,21 +1,21 @@
1
- const p = "https://accounts.google.com/gsi/client", w = "https://www.googleapis.com/auth/androidpublisher";
2
- function d() {
1
+ const w = "https://accounts.google.com/gsi/client", f = "https://www.googleapis.com/auth/androidpublisher";
2
+ function u() {
3
3
  return globalThis.google?.accounts?.oauth2;
4
4
  }
5
5
  let c;
6
6
  function y() {
7
- return d() ? Promise.resolve() : typeof document > "u" ? Promise.reject(new Error("Google sign-in requires a browser environment.")) : (c || (c = new Promise((r, o) => {
7
+ return u() ? Promise.resolve() : typeof document > "u" ? Promise.reject(new Error("Google sign-in requires a browser environment.")) : (c || (c = new Promise((o, r) => {
8
8
  const t = document.createElement("script");
9
- t.src = p, t.async = !0, t.addEventListener(
9
+ t.src = w, t.async = !0, t.addEventListener(
10
10
  "load",
11
11
  () => {
12
- d() ? r() : o(new Error("Google sign-in script loaded without the OAuth client."));
12
+ u() ? o() : r(new Error("Google sign-in script loaded without the OAuth client."));
13
13
  },
14
14
  { once: !0 }
15
15
  ), t.addEventListener(
16
16
  "error",
17
17
  () => {
18
- t.remove(), o(new Error("Failed to load the Google sign-in script."));
18
+ t.remove(), r(new Error("Failed to load the Google sign-in script."));
19
19
  },
20
20
  { once: !0 }
21
21
  ), document.head.appendChild(t);
@@ -23,14 +23,17 @@ function y() {
23
23
  c = void 0;
24
24
  })), c);
25
25
  }
26
- async function f({
27
- clientId: r,
28
- scope: o = w
26
+ function m(o) {
27
+ return u() ? p(o) : y().then(() => p(o));
28
+ }
29
+ function p({
30
+ clientId: o,
31
+ scope: r = f
29
32
  }) {
30
- return await y(), new Promise((t, n) => {
31
- d().initTokenClient({
32
- client_id: r,
33
- scope: o,
33
+ return new Promise((t, n) => {
34
+ u().initTokenClient({
35
+ client_id: o,
36
+ scope: r,
34
37
  callback: (e) => {
35
38
  if (e.error) {
36
39
  n(new Error(e.error_description || e.error));
@@ -52,17 +55,17 @@ async function f({
52
55
  class l extends Error {
53
56
  code;
54
57
  status;
55
- constructor(o, t = {}) {
56
- super(o), this.name = "PlaystorePublishError", this.code = t.code, this.status = t.status;
58
+ constructor(r, t = {}) {
59
+ super(r), this.name = "PlaystorePublishError", this.code = t.code, this.status = t.status;
57
60
  }
58
61
  }
59
- async function m(r) {
60
- const { registryApiUrl: o, token: t, organizationId: n, ...h } = r;
62
+ async function b(o) {
63
+ const { registryApiUrl: r, token: t, organizationId: n, ...h } = o;
61
64
  let e;
62
65
  try {
63
- e = new URL(o);
66
+ e = new URL(r);
64
67
  } catch {
65
- throw new l(`Invalid registry URL: ${o}`);
68
+ throw new l(`Invalid registry URL: ${r}`);
66
69
  }
67
70
  e.pathname = `${e.pathname.replace(/\/$/, "")}/android/playstore/publish`, e.search = "", e.hash = "";
68
71
  const a = { "Content-Type": "application/json" };
@@ -74,9 +77,9 @@ async function m(r) {
74
77
  headers: a,
75
78
  body: JSON.stringify(h)
76
79
  });
77
- } catch (u) {
80
+ } catch (d) {
78
81
  throw new l(
79
- `Cannot reach the registry: ${u instanceof Error ? u.message : String(u)}`
82
+ `Cannot reach the registry: ${d instanceof Error ? d.message : String(d)}`
80
83
  );
81
84
  }
82
85
  const g = await s.text();
@@ -98,9 +101,9 @@ async function m(r) {
98
101
  return { versionCode: i.versionCode };
99
102
  }
100
103
  export {
101
- w as ANDROID_PUBLISHER_SCOPE,
104
+ f as ANDROID_PUBLISHER_SCOPE,
102
105
  l as PlaystorePublishError,
103
106
  y as loadGoogleIdentityServices,
104
- m as publishToPlaystore,
105
- f as requestGoogleAccessToken
107
+ b as publishToPlaystore,
108
+ m as requestGoogleAccessToken
106
109
  };
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),l=require("./index.cjs");function q({registryApiUrl:n,token:g,organizationId:v,googleClientId:d}){const[S,r]=e.useState("idle"),[P,R]=e.useState(!1),[k,b]=e.useState(),[C,t]=e.useState(),[p,c]=e.useState(),f=e.useRef(void 0),s=e.useRef(0),o=e.useRef(!1),G=e.useCallback(()=>{l.loadGoogleIdentityServices().catch(()=>{})},[]),T=e.useCallback(async()=>{if(o.current)return!1;if(!d)return t("Google OAuth client ID is not configured."),r("error"),!1;o.current=!0;const a=s.current;r("signing-in"),t(void 0),c(void 0);try{const u=await l.requestGoogleAccessToken({clientId:d});return f.current=u,a===s.current&&(R(!0),r("ready")),!0}catch(u){return a===s.current&&(t(y(u)),r("error")),!1}finally{o.current=!1}},[d]),E=e.useCallback(async a=>{if(o.current)return;if(!n){t("Registry URL is not configured."),r("error");return}const u=f.current;if(!u){t("Sign in with Google before publishing."),r("error");return}o.current=!0;const h=s.current;r("publishing"),t(void 0),c(void 0);try{const i=await l.publishToPlaystore({registryApiUrl:n,token:g,organizationId:v,accessToken:u,...a});return h===s.current&&(b(i.versionCode),r("published")),i}catch(i){h===s.current&&(t(y(i)),i instanceof l.PlaystorePublishError&&c(i.code),r("error"));return}finally{o.current=!1}},[n,g,v]),I=e.useCallback(()=>{s.current+=1,b(void 0),t(void 0),c(void 0),r(f.current?"ready":"idle")},[]);return{status:S,isSignedIn:P,versionCode:k,error:C,errorCode:p,preloadGoogle:G,signInWithGoogle:T,publish:E,reset:I}}function y(n){return n instanceof Error?n.message:String(n)}exports.usePlaystorePublish=q;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),I=require("../errors-BSkqS7U-.js"),d=require("./index.cjs");function N({registryApiUrl:f,token:h,organizationId:y,googleClientId:g}){const[k,t]=e.useState("idle"),[S,M]=e.useState(),[P,l]=e.useState(),[T,n]=e.useState(),[p,i]=e.useState(),v=e.useRef(void 0),s=e.useRef(0),a=e.useRef(!1),C=e.useCallback(()=>d.loadGoogleIdentityServices().then(()=>!0,()=>!1),[]),b=e.useCallback(r=>{n(r),i(void 0),l(void 0),t("error")},[]),R=e.useCallback(async()=>{if(a.current)return!1;if(!g)return b("Google OAuth client ID is not configured."),!1;a.current=!0;const r=s.current;t("signing-in"),n(void 0),i(void 0);try{const o=await d.requestGoogleAccessToken({clientId:g});return v.current=o,M(o),r===s.current?t("ready"):t(u=>u==="idle"?"ready":u),!0}catch(o){return r===s.current&&(n(I.errorMessage(o)),t("error")),!1}finally{a.current=!1}},[g]),G=e.useCallback(async r=>{if(a.current)return;if(!f){b("Registry URL is not configured.");return}const o=v.current;if(!o){b("Sign in with Google before publishing.");return}a.current=!0;const u=s.current;t("publishing"),n(void 0),i(void 0),l(void 0);try{const c=await d.publishToPlaystore({registryApiUrl:f,token:h,organizationId:y,accessToken:o,packageName:r.packageName,assetId:r.assetId,assetName:r.assetName,track:r.track,releaseStatus:r.releaseStatus});return u===s.current&&(l(c.versionCode),t("published")),c}catch(c){u===s.current&&(n(I.errorMessage(c)),c instanceof d.PlaystorePublishError&&i(c.code),t("error"));return}finally{a.current=!1}},[f,h,y]),m=e.useCallback(()=>{s.current+=1,l(void 0),n(void 0),i(void 0),t(v.current?"ready":"idle")},[]);return e.useMemo(()=>({status:k,isSignedIn:S!==void 0,versionCode:P,error:T,errorCode:p,preloadGoogle:C,signInWithGoogle:R,publish:G,reset:m}),[k,S,P,T,p,C,R,G,m])}exports.usePlaystorePublish=N;
@@ -12,10 +12,20 @@ export type UsePlaystorePublishResult = {
12
12
  error?: string;
13
13
  /** Registry error code driving per-cause UX, e.g. versionCodeExists. */
14
14
  errorCode?: string;
15
- /** Warm up the Google sign-in script (e.g. on dialog open) so signInWithGoogle stays synchronous with the click. */
16
- preloadGoogle: () => void;
15
+ /**
16
+ * Warm up the Google sign-in script (e.g. on dialog open) so
17
+ * signInWithGoogle stays synchronous with the click. Resolves true once
18
+ * the script is ready; await it to gate the sign-in button, or ignore
19
+ * the result and rely on the blocked-popup error plus retry.
20
+ */
21
+ preloadGoogle: () => Promise<boolean>;
17
22
  signInWithGoogle: () => Promise<boolean>;
18
23
  publish: (request: PlaystorePublishRequest) => Promise<PlaystorePublishResult | undefined>;
24
+ /**
25
+ * Clears the outcome state (keeps the Google session). Does not cancel an
26
+ * in-flight sign-in or publish: their completion is ignored status-wise
27
+ * and new calls stay refused until the in-flight one settles.
28
+ */
19
29
  reset: () => void;
20
30
  };
21
31
  export declare function usePlaystorePublish({ registryApiUrl, token, organizationId, googleClientId, }: UsePlaystorePublishOptions): UsePlaystorePublishResult;
@@ -1,81 +1,91 @@
1
- import { useState as c, useRef as g, useCallback as f } from "react";
2
- import { loadGoogleIdentityServices as w, requestGoogleAccessToken as x, publishToPlaystore as q, PlaystorePublishError as D } from "./index.js";
3
- function O({
4
- registryApiUrl: t,
5
- token: v,
6
- organizationId: h,
7
- googleClientId: d
1
+ import { useState as u, useRef as y, useCallback as d, useMemo as E } from "react";
2
+ import { e as N } from "../errors-gy8yQJos.mjs";
3
+ import { loadGoogleIdentityServices as x, requestGoogleAccessToken as M, publishToPlaystore as A, PlaystorePublishError as D } from "./index.js";
4
+ function W({
5
+ registryApiUrl: f,
6
+ token: k,
7
+ organizationId: m,
8
+ googleClientId: g
8
9
  }) {
9
- const [S, e] = c("idle"), [G, R] = c(!1), [m, y] = c(), [P, r] = c(), [T, u] = c(), l = g(void 0), n = g(0), o = g(!1), k = f(() => {
10
- w().catch(() => {
11
- });
12
- }, []), C = f(async () => {
13
- if (o.current)
10
+ const [b, r] = u("idle"), [p, w] = u(), [T, l] = u(), [G, s] = u(), [S, c] = u(), v = y(void 0), t = y(0), n = y(!1), R = d(
11
+ () => x().then(
12
+ () => !0,
13
+ () => !1
14
+ ),
15
+ []
16
+ ), h = d((e) => {
17
+ s(e), c(void 0), l(void 0), r("error");
18
+ }, []), P = d(async () => {
19
+ if (n.current)
14
20
  return !1;
15
- if (!d)
16
- return r("Google OAuth client ID is not configured."), e("error"), !1;
17
- o.current = !0;
18
- const a = n.current;
19
- e("signing-in"), r(void 0), u(void 0);
21
+ if (!g)
22
+ return h("Google OAuth client ID is not configured."), !1;
23
+ n.current = !0;
24
+ const e = t.current;
25
+ r("signing-in"), s(void 0), c(void 0);
20
26
  try {
21
- const s = await x({ clientId: d });
22
- return l.current = s, a === n.current && (R(!0), e("ready")), !0;
23
- } catch (s) {
24
- return a === n.current && (r(p(s)), e("error")), !1;
27
+ const o = await M({ clientId: g });
28
+ return v.current = o, w(o), e === t.current ? r("ready") : r((a) => a === "idle" ? "ready" : a), !0;
29
+ } catch (o) {
30
+ return e === t.current && (s(N(o)), r("error")), !1;
25
31
  } finally {
26
- o.current = !1;
32
+ n.current = !1;
27
33
  }
28
- }, [d]), E = f(
29
- async (a) => {
30
- if (o.current)
34
+ }, [g]), C = d(
35
+ async (e) => {
36
+ if (n.current)
31
37
  return;
32
- if (!t) {
33
- r("Registry URL is not configured."), e("error");
38
+ if (!f) {
39
+ h("Registry URL is not configured.");
34
40
  return;
35
41
  }
36
- const s = l.current;
37
- if (!s) {
38
- r("Sign in with Google before publishing."), e("error");
42
+ const o = v.current;
43
+ if (!o) {
44
+ h("Sign in with Google before publishing.");
39
45
  return;
40
46
  }
41
- o.current = !0;
42
- const b = n.current;
43
- e("publishing"), r(void 0), u(void 0);
47
+ n.current = !0;
48
+ const a = t.current;
49
+ r("publishing"), s(void 0), c(void 0), l(void 0);
44
50
  try {
45
- const i = await q({
46
- registryApiUrl: t,
47
- token: v,
48
- organizationId: h,
49
- accessToken: s,
50
- ...a
51
+ const i = await A({
52
+ registryApiUrl: f,
53
+ token: k,
54
+ organizationId: m,
55
+ accessToken: o,
56
+ packageName: e.packageName,
57
+ assetId: e.assetId,
58
+ assetName: e.assetName,
59
+ track: e.track,
60
+ releaseStatus: e.releaseStatus
51
61
  });
52
- return b === n.current && (y(i.versionCode), e("published")), i;
62
+ return a === t.current && (l(i.versionCode), r("published")), i;
53
63
  } catch (i) {
54
- b === n.current && (r(p(i)), i instanceof D && u(i.code), e("error"));
64
+ a === t.current && (s(N(i)), i instanceof D && c(i.code), r("error"));
55
65
  return;
56
66
  } finally {
57
- o.current = !1;
67
+ n.current = !1;
58
68
  }
59
69
  },
60
- [t, v, h]
61
- ), I = f(() => {
62
- n.current += 1, y(void 0), r(void 0), u(void 0), e(l.current ? "ready" : "idle");
70
+ [f, k, m]
71
+ ), I = d(() => {
72
+ t.current += 1, l(void 0), s(void 0), c(void 0), r(v.current ? "ready" : "idle");
63
73
  }, []);
64
- return {
65
- status: S,
66
- isSignedIn: G,
67
- versionCode: m,
68
- error: P,
69
- errorCode: T,
70
- preloadGoogle: k,
71
- signInWithGoogle: C,
72
- publish: E,
73
- reset: I
74
- };
75
- }
76
- function p(t) {
77
- return t instanceof Error ? t.message : String(t);
74
+ return E(
75
+ () => ({
76
+ status: b,
77
+ isSignedIn: p !== void 0,
78
+ versionCode: T,
79
+ error: G,
80
+ errorCode: S,
81
+ preloadGoogle: R,
82
+ signInWithGoogle: P,
83
+ publish: C,
84
+ reset: I
85
+ }),
86
+ [b, p, T, G, S, R, P, C, I]
87
+ );
78
88
  }
79
89
  export {
80
- O as usePlaystorePublish
90
+ W as usePlaystorePublish
81
91
  };
@@ -1,4 +1,4 @@
1
- import { e as c, a as u, n as d, b as f, c as l, i as r } from "./browser-storage-Cx9eW-bm.mjs";
1
+ import { e as c, a as u, n as d, b as f, c as l, i as r } from "./browser-storage-BnO-Ma-e.mjs";
2
2
  function g() {
3
3
  return {
4
4
  method: "POST",
@@ -1 +1 @@
1
- "use strict";const a=require("./browser-storage-jJaIht4K.js");function c(){return{method:"POST",path:"/account/listTeams.action",payload:{}}}function u({bundleID:e,teamID:t=""}){return s("/account/ios/identifiers/listAppIds.action",t,{sort:"name=asc"})}function d({deviceUDID:e,teamID:t=""}){return s("/account/ios/device/listDevices.action",t,{sort:"name=asc",includeRemovedDevices:!1})}function f(e=""){return s("/account/ios/certificate/listCertRequests.action",e,{sort:"name=asc",types:"83Q87W3TGH,5QPB9NHCEI"})}function l(e=""){return s("/account/ios/certificate/listCertRequests.action",e,{sort:"name=asc",types:"WXV89964HE,R58UK2EWSO"})}function p({bundleID:e,teamID:t=""}){return s("/account/ios/profile/listProvisioningProfiles.action",t,{search:e,sort:"name=asc"})}function m({bundleID:e,teamID:t=""}){return s("/account/ios/profile/listProvisioningProfiles.action",t,{search:e,sort:"name=asc",distributionType:"adhoc"})}function R({deviceUDID:e,teamID:t="",name:i="Limrun iPhone"}){return{method:"POST",path:"/account/ios/device/addDevices.action",payload:{teamId:t,deviceNames:i,deviceNumbers:a.normalizeUDID(e),deviceClasses:"iphone",register:"single"}}}function g({bundleID:e,teamID:t="",name:i}){return{method:"POST",path:"/account/ios/identifiers/addAppId.action",payload:{teamId:t,name:i??e,identifier:e,type:"explicit"}}}function q({csrPEM:e,teamID:t=""}){return{method:"POST",path:"/account/ios/certificate/submitCertificateRequest.action",payload:{teamId:t,type:"83Q87W3TGH",csrContent:e}}}function v({csrPEM:e,teamID:t=""}){return{method:"POST",path:"/account/ios/certificate/submitCertificateRequest.action",payload:{teamId:t,type:"WXV89964HE",csrContent:e}}}function P(e,t=""){return{method:"GET",path:"/account/ios/certificate/downloadCertificateContent.action",payload:{teamId:t,certificateId:e,type:"83Q87W3TGH"}}}function D(e,t=""){return{method:"GET",path:"/account/ios/certificate/downloadCertificateContent.action",payload:{teamId:t,certificateId:e,type:"WXV89964HE"}}}function I({bundleID:e,teamID:t="",appIDID:i,certificateID:o,deviceIDs:n,name:r}){return{method:"POST",path:"/account/ios/profile/createProvisioningProfile.action",payload:{teamId:t,provisioningProfileName:r??`Limrun ${e}`,certificateIds:[o],appIdId:i,deviceIds:n,distributionType:"limited",subPlatform:"ios"}}}function h({bundleID:e,teamID:t="",appIDID:i,certificateID:o,deviceIDs:n,name:r}){return{method:"POST",path:"/account/ios/profile/createProvisioningProfile.action",payload:{teamId:t,provisioningProfileName:r??`Limrun Ad Hoc ${e}`,certificateIds:[o],appIdId:i,deviceIds:n,distributionType:"adhoc",subPlatform:"ios"}}}function C(e,t=""){return{method:"GET",path:"/account/ios/profile/downloadProfileContent",payload:{teamId:t,provisioningProfileId:e}}}async function b({bundleID:e,deviceUDID:t,teamID:i,signingMode:o}){const n=await a.getSigningAssets({bundleID:e,deviceUDID:t,signingMode:o});if(!(!n||!S(n,{bundleID:e,deviceUDID:t,teamID:i,signingMode:o})))return n}async function y(e){return a.putSigningAssets({...e,certificateFileName:e.certificateID?`${e.certificateID}.p12`:`apple-${e.signingMode??"development"}.p12`,certificatePassword:e.certificatePassword||void 0,signingMode:e.signingMode,profileFileName:e.profile.uuid?`${e.profile.uuid}.mobileprovision`:`${e.signingMode??"development"}.mobileprovision`})}function S(e,{bundleID:t,deviceUDID:i,teamID:o,signingMode:n="development"}){return(e.signingMode??"development")!==n||!a.profileMatchesBundleID(e.profile,t)||o&&e.teamID&&e.teamID!==o||i&&!a.profileContainsDevice(e.profile,i)||e.profile.expirationDate&&new Date(e.profile.expirationDate).getTime()<=Date.now()?!1:a.normalizeBundleID(e.bundleID)===a.normalizeBundleID(t)}function s(e,t,i={}){const o={pageNumber:1,pageSize:200,...i};return t&&(o.teamId=t),{method:"POST",path:e,payload:o}}exports.createAdHocProfileRequest=h;exports.createBundleIDRequest=g;exports.createDevelopmentProfileRequest=I;exports.downloadCertificateRequest=P;exports.downloadDistributionCertificateRequest=D;exports.downloadProfileRequest=C;exports.findAdHocProfilesRequest=m;exports.findBundleIDRequest=u;exports.findDevelopmentCertificatesRequest=f;exports.findDevelopmentProfilesRequest=p;exports.findDeviceRequest=d;exports.findDistributionCertificatesRequest=l;exports.getReusableAppleSigningAssets=b;exports.listTeamsRequest=c;exports.putAppleGeneratedSigningAssets=y;exports.registerDeviceRequest=R;exports.submitDevelopmentCSRRequest=q;exports.submitDistributionCSRRequest=v;
1
+ "use strict";const a=require("./browser-storage-DLxIqZk5.js");function c(){return{method:"POST",path:"/account/listTeams.action",payload:{}}}function u({bundleID:e,teamID:t=""}){return s("/account/ios/identifiers/listAppIds.action",t,{sort:"name=asc"})}function d({deviceUDID:e,teamID:t=""}){return s("/account/ios/device/listDevices.action",t,{sort:"name=asc",includeRemovedDevices:!1})}function f(e=""){return s("/account/ios/certificate/listCertRequests.action",e,{sort:"name=asc",types:"83Q87W3TGH,5QPB9NHCEI"})}function l(e=""){return s("/account/ios/certificate/listCertRequests.action",e,{sort:"name=asc",types:"WXV89964HE,R58UK2EWSO"})}function p({bundleID:e,teamID:t=""}){return s("/account/ios/profile/listProvisioningProfiles.action",t,{search:e,sort:"name=asc"})}function m({bundleID:e,teamID:t=""}){return s("/account/ios/profile/listProvisioningProfiles.action",t,{search:e,sort:"name=asc",distributionType:"adhoc"})}function R({deviceUDID:e,teamID:t="",name:i="Limrun iPhone"}){return{method:"POST",path:"/account/ios/device/addDevices.action",payload:{teamId:t,deviceNames:i,deviceNumbers:a.normalizeUDID(e),deviceClasses:"iphone",register:"single"}}}function g({bundleID:e,teamID:t="",name:i}){return{method:"POST",path:"/account/ios/identifiers/addAppId.action",payload:{teamId:t,name:i??e,identifier:e,type:"explicit"}}}function q({csrPEM:e,teamID:t=""}){return{method:"POST",path:"/account/ios/certificate/submitCertificateRequest.action",payload:{teamId:t,type:"83Q87W3TGH",csrContent:e}}}function v({csrPEM:e,teamID:t=""}){return{method:"POST",path:"/account/ios/certificate/submitCertificateRequest.action",payload:{teamId:t,type:"WXV89964HE",csrContent:e}}}function P(e,t=""){return{method:"GET",path:"/account/ios/certificate/downloadCertificateContent.action",payload:{teamId:t,certificateId:e,type:"83Q87W3TGH"}}}function D(e,t=""){return{method:"GET",path:"/account/ios/certificate/downloadCertificateContent.action",payload:{teamId:t,certificateId:e,type:"WXV89964HE"}}}function I({bundleID:e,teamID:t="",appIDID:i,certificateID:o,deviceIDs:n,name:r}){return{method:"POST",path:"/account/ios/profile/createProvisioningProfile.action",payload:{teamId:t,provisioningProfileName:r??`Limrun ${e}`,certificateIds:[o],appIdId:i,deviceIds:n,distributionType:"limited",subPlatform:"ios"}}}function h({bundleID:e,teamID:t="",appIDID:i,certificateID:o,deviceIDs:n,name:r}){return{method:"POST",path:"/account/ios/profile/createProvisioningProfile.action",payload:{teamId:t,provisioningProfileName:r??`Limrun Ad Hoc ${e}`,certificateIds:[o],appIdId:i,deviceIds:n,distributionType:"adhoc",subPlatform:"ios"}}}function C(e,t=""){return{method:"GET",path:"/account/ios/profile/downloadProfileContent",payload:{teamId:t,provisioningProfileId:e}}}async function b({bundleID:e,deviceUDID:t,teamID:i,signingMode:o}){const n=await a.getSigningAssets({bundleID:e,deviceUDID:t,signingMode:o});if(!(!n||!S(n,{bundleID:e,deviceUDID:t,teamID:i,signingMode:o})))return n}async function y(e){return a.putSigningAssets({...e,certificateFileName:e.certificateID?`${e.certificateID}.p12`:`apple-${e.signingMode??"development"}.p12`,certificatePassword:e.certificatePassword||void 0,signingMode:e.signingMode,profileFileName:e.profile.uuid?`${e.profile.uuid}.mobileprovision`:`${e.signingMode??"development"}.mobileprovision`})}function S(e,{bundleID:t,deviceUDID:i,teamID:o,signingMode:n="development"}){return(e.signingMode??"development")!==n||!a.profileMatchesBundleID(e.profile,t)||o&&e.teamID&&e.teamID!==o||i&&!a.profileContainsDevice(e.profile,i)||e.profile.expirationDate&&new Date(e.profile.expirationDate).getTime()<=Date.now()?!1:a.normalizeBundleID(e.bundleID)===a.normalizeBundleID(t)}function s(e,t,i={}){const o={pageNumber:1,pageSize:200,...i};return t&&(o.teamId=t),{method:"POST",path:e,payload:o}}exports.createAdHocProfileRequest=h;exports.createBundleIDRequest=g;exports.createDevelopmentProfileRequest=I;exports.downloadCertificateRequest=P;exports.downloadDistributionCertificateRequest=D;exports.downloadProfileRequest=C;exports.findAdHocProfilesRequest=m;exports.findBundleIDRequest=u;exports.findDevelopmentCertificatesRequest=f;exports.findDevelopmentProfilesRequest=p;exports.findDeviceRequest=d;exports.findDistributionCertificatesRequest=l;exports.getReusableAppleSigningAssets=b;exports.listTeamsRequest=c;exports.putAppleGeneratedSigningAssets=y;exports.registerDeviceRequest=R;exports.submitDevelopmentCSRRequest=q;exports.submitDistributionCSRRequest=v;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@limrun/ui",
3
- "version": "0.14.0-rc.3",
3
+ "version": "0.14.0-rc.4",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "tag": "next"
@@ -24,6 +24,11 @@
24
24
  "import": "./dist/app-store-relay/react.js",
25
25
  "require": "./dist/app-store-relay/react.cjs"
26
26
  },
27
+ "./apple-credentials": {
28
+ "types": "./dist/apple-credentials/index.d.ts",
29
+ "import": "./dist/apple-credentials/index.js",
30
+ "require": "./dist/apple-credentials/index.cjs"
31
+ },
27
32
  "./device-build": {
28
33
  "types": "./dist/device-build/index.d.ts",
29
34
  "import": "./dist/device-build/index.js",
@@ -5,6 +5,7 @@ import {
5
5
  type AppleIDLoginResult,
6
6
  type AppleRelayResponse,
7
7
  } from './index';
8
+ import { errorMessage } from '../core/errors';
8
9
 
9
10
  export type UseAppleIDLoginOptions = Pick<AppleIDLoginInput, 'registryApiUrl' | 'token' | 'organizationId'>;
10
11
 
@@ -128,7 +129,3 @@ export function useAppleIDLogin({
128
129
  close,
129
130
  };
130
131
  }
131
-
132
- function errorMessage(error: unknown) {
133
- return error instanceof Error ? error.message : String(error);
134
- }
@@ -0,0 +1,158 @@
1
+ // @vitest-environment node
2
+
3
+ import forge from 'node-forge';
4
+ import { describe, expect, test } from 'vitest';
5
+ import type { AppleRelayWebSocketClient } from '../core/device-install/apple';
6
+ import {
7
+ APPLE_CERTIFICATE_SECRET_TYPE,
8
+ appleCertificateSecretName,
9
+ ensureAppleCertificateSecret,
10
+ type SigningSecret,
11
+ type SigningSecretMetadata,
12
+ type SigningSecretStore,
13
+ type SigningSecretType,
14
+ } from './index';
15
+
16
+ /**
17
+ * A self-signed certificate is enough for exportAppleCertificateP12: the
18
+ * p12 bundling never verifies that the key matches the certificate.
19
+ */
20
+ function selfSignedCertificateBase64() {
21
+ const keys = forge.pki.rsa.generateKeyPair(1024);
22
+ const cert = forge.pki.createCertificate();
23
+ cert.publicKey = keys.publicKey;
24
+ cert.serialNumber = '01';
25
+ cert.validity.notBefore = new Date();
26
+ cert.validity.notAfter = new Date(Date.now() + 24 * 60 * 60 * 1000);
27
+ const attrs = [{ name: 'commonName', value: 'Apple Development Test' }];
28
+ cert.setSubject(attrs);
29
+ cert.setIssuer(attrs);
30
+ cert.sign(keys.privateKey);
31
+ return forge.util.encode64(forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes());
32
+ }
33
+
34
+ function memorySecretStore(options: { failPuts?: () => boolean } = {}) {
35
+ const entries = new Map<string, SigningSecret>();
36
+ const store: SigningSecretStore = {
37
+ async put(type: SigningSecretType, name: string, data: Record<string, string>) {
38
+ if (options.failPuts?.()) {
39
+ throw new Error('secret store unavailable');
40
+ }
41
+ const secret: SigningSecret = { type, name, data, createdAt: new Date().toISOString() };
42
+ entries.set(`${type}:${name}`, secret);
43
+ return secret;
44
+ },
45
+ async get(type: SigningSecretType, name: string) {
46
+ return entries.get(`${type}:${name}`);
47
+ },
48
+ async list() {
49
+ return [...entries.values()].map(({ data: _data, ...metadata }): SigningSecretMetadata => metadata);
50
+ },
51
+ async delete(type: SigningSecretType, name: string) {
52
+ entries.delete(`${type}:${name}`);
53
+ },
54
+ };
55
+ return { store, entries };
56
+ }
57
+
58
+ type PortalState = { mintedCertificateIds: string[]; certificateBase64: string };
59
+
60
+ /**
61
+ * Fakes the relay's provisioning proxy: minting appends to
62
+ * mintedCertificateIds, listing reflects what has been minted so far.
63
+ */
64
+ function fakePortalRelay(state: PortalState): AppleRelayWebSocketClient {
65
+ return {
66
+ async request(_type: string, payload: unknown) {
67
+ const request = payload as { path: string };
68
+ if (request.path.includes('listCertRequests')) {
69
+ return portalOK({
70
+ certRequests: state.mintedCertificateIds.map((certificateId) => ({ certificateId })),
71
+ });
72
+ }
73
+ if (request.path.includes('submitCertificateRequest')) {
74
+ const certificateId = `CERT${state.mintedCertificateIds.length + 1}`;
75
+ state.mintedCertificateIds.push(certificateId);
76
+ return portalOK({
77
+ certRequest: {
78
+ certificateId,
79
+ serialNum: '01',
80
+ expirationDateString: '2027-01-01',
81
+ },
82
+ });
83
+ }
84
+ if (request.path.includes('downloadCertificateContent')) {
85
+ return { status: 200, statusText: 'OK', rawBodyBase64: state.certificateBase64 };
86
+ }
87
+ throw new Error(`unexpected portal path: ${request.path}`);
88
+ },
89
+ } as unknown as AppleRelayWebSocketClient;
90
+ }
91
+
92
+ function portalOK(body: Record<string, unknown>) {
93
+ return { status: 200, statusText: 'OK', body: { resultCode: 0, ...body } };
94
+ }
95
+
96
+ describe('ensureAppleCertificateSecret', () => {
97
+ const teamId = 'TEAM1';
98
+ const secretName = appleCertificateSecretName(teamId, 'DEVELOPMENT');
99
+
100
+ test('surfaces an actionable error when the store write fails after minting', async () => {
101
+ const state: PortalState = {
102
+ mintedCertificateIds: [],
103
+ certificateBase64: selfSignedCertificateBase64(),
104
+ };
105
+ const broken = memorySecretStore({ failPuts: () => true });
106
+
107
+ await expect(
108
+ ensureAppleCertificateSecret({
109
+ relay: fakePortalRelay(state),
110
+ teamId,
111
+ secretStore: broken.store,
112
+ }),
113
+ ).rejects.toThrow(/revoke certificate CERT1/);
114
+ expect(state.mintedCertificateIds).toEqual(['CERT1']);
115
+ });
116
+
117
+ test('mints and stores the certificate with its type in the secret name', async () => {
118
+ const state: PortalState = {
119
+ mintedCertificateIds: [],
120
+ certificateBase64: selfSignedCertificateBase64(),
121
+ };
122
+ const org = memorySecretStore();
123
+
124
+ const result = await ensureAppleCertificateSecret({
125
+ relay: fakePortalRelay(state),
126
+ teamId,
127
+ secretStore: org.store,
128
+ });
129
+ expect(result.created).toBe(true);
130
+ expect(result.certificateId).toBe('CERT1');
131
+ const stored = await org.store.get(APPLE_CERTIFICATE_SECRET_TYPE, secretName);
132
+ expect(stored?.data.certificateID).toBe('CERT1');
133
+ expect(stored?.data.certificateType).toBe('DEVELOPMENT');
134
+ expect(stored?.data.certificateP12Base64).toBeTruthy();
135
+ });
136
+
137
+ test('reuses the stored certificate when it is still on the team', async () => {
138
+ const state: PortalState = {
139
+ mintedCertificateIds: ['CERT1'],
140
+ certificateBase64: selfSignedCertificateBase64(),
141
+ };
142
+ const org = memorySecretStore();
143
+ await org.store.put(APPLE_CERTIFICATE_SECRET_TYPE, secretName, {
144
+ certificateP12Base64: 'cDEy',
145
+ certificateID: 'CERT1',
146
+ teamID: teamId,
147
+ });
148
+
149
+ const result = await ensureAppleCertificateSecret({
150
+ relay: fakePortalRelay(state),
151
+ teamId,
152
+ secretStore: org.store,
153
+ });
154
+ expect(result.created).toBe(false);
155
+ expect(result.certificateId).toBe('CERT1');
156
+ expect(state.mintedCertificateIds).toEqual(['CERT1']);
157
+ });
158
+ });