@limrun/ui 0.14.0-rc.2 → 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 (71) 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.d.ts +1 -1
  5. package/dist/app-store-relay/react.js +34 -31
  6. package/dist/apple-credentials/index.cjs +1 -0
  7. package/dist/apple-credentials/index.d.ts +59 -0
  8. package/dist/apple-credentials/index.js +227 -0
  9. package/dist/browser-storage-BnO-Ma-e.mjs +219 -0
  10. package/dist/browser-storage-DLxIqZk5.js +1 -0
  11. package/dist/client-D1drDXxE.js +1 -0
  12. package/dist/{client-CBX-hLnL.mjs → client-DSuNtWDv.mjs} +2 -7
  13. package/dist/core/device-install/operations/operations.d.ts +1 -1
  14. package/dist/core/device-install/storage/browser-storage.d.ts +8 -0
  15. package/dist/core/device-install/storage/index.d.ts +1 -0
  16. package/dist/core/device-install/storage/secret-store.d.ts +101 -0
  17. package/dist/core/device-install/types.d.ts +6 -0
  18. package/dist/core/errors.d.ts +1 -0
  19. package/dist/core/play-publish/google.d.ts +13 -0
  20. package/dist/core/play-publish/publish.d.ts +34 -0
  21. package/dist/device-build/index.cjs +1 -1
  22. package/dist/device-build/index.js +25 -24
  23. package/dist/device-build/react.cjs +1 -1
  24. package/dist/device-build/react.js +42 -43
  25. package/dist/device-install/index.cjs +1 -1
  26. package/dist/device-install/index.js +164 -160
  27. package/dist/device-install/react.cjs +1 -1
  28. package/dist/device-install/react.js +61 -60
  29. package/dist/errors-BSkqS7U-.js +1 -0
  30. package/dist/errors-gy8yQJos.mjs +6 -0
  31. package/dist/gsa-srp-Bme3JLEi.mjs +266 -0
  32. package/dist/gsa-srp-CCIfUz7Y.js +1 -0
  33. package/dist/index-ChhtuQ7e.js +22 -0
  34. package/dist/index-YDu8K9a4.mjs +11279 -0
  35. package/dist/play-publish/index.cjs +1 -0
  36. package/dist/play-publish/index.d.ts +2 -0
  37. package/dist/play-publish/index.js +109 -0
  38. package/dist/play-publish/react.cjs +1 -0
  39. package/dist/play-publish/react.d.ts +31 -0
  40. package/dist/play-publish/react.js +91 -0
  41. package/dist/{provisioning-DKbkBO-p.mjs → provisioning-B_DefBEp.mjs} +9 -9
  42. package/dist/{provisioning-Va0PH4Kk.js → provisioning-BlQMccX_.js} +1 -1
  43. package/package.json +18 -2
  44. package/src/app-store-relay/index.test.ts +6 -6
  45. package/src/app-store-relay/react.ts +6 -5
  46. package/src/apple-credentials/index.test.ts +158 -0
  47. package/src/apple-credentials/index.ts +241 -0
  48. package/src/core/device-install/apple/client.ts +1 -6
  49. package/src/core/device-install/apple/relay.ts +1 -4
  50. package/src/core/device-install/operations/operations.ts +8 -5
  51. package/src/core/device-install/storage/browser-storage.test.ts +65 -0
  52. package/src/core/device-install/storage/browser-storage.ts +33 -0
  53. package/src/core/device-install/storage/index.ts +1 -0
  54. package/src/core/device-install/storage/secret-store.ts +298 -0
  55. package/src/core/device-install/types.ts +6 -0
  56. package/src/core/errors.ts +3 -0
  57. package/src/core/play-publish/google.ts +113 -0
  58. package/src/core/play-publish/publish.test.ts +68 -0
  59. package/src/core/play-publish/publish.ts +89 -0
  60. package/src/device-build/react.ts +1 -4
  61. package/src/device-install/demo/demo.tsx +2 -8
  62. package/src/device-install/react.ts +31 -31
  63. package/src/play-publish/README.md +66 -0
  64. package/src/play-publish/index.ts +12 -0
  65. package/src/play-publish/react.ts +208 -0
  66. package/vite.config.ts +3 -0
  67. package/dist/browser-storage-Cx9eW-bm.mjs +0 -205
  68. package/dist/browser-storage-jJaIht4K.js +0 -1
  69. package/dist/client-BtC4n_qf.js +0 -1
  70. package/dist/index-CMeQfhYy.js +0 -22
  71. package/dist/index-DFdCvi2_.mjs +0 -11543
@@ -0,0 +1 @@
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;
@@ -0,0 +1,2 @@
1
+ export { ANDROID_PUBLISHER_SCOPE, loadGoogleIdentityServices, requestGoogleAccessToken, type RequestGoogleAccessTokenInput, } from '../core/play-publish/google';
2
+ export { publishToPlaystore, PlaystorePublishError, type PlaystorePublishInput, type PlaystorePublishResult, } from '../core/play-publish/publish';
@@ -0,0 +1,109 @@
1
+ const w = "https://accounts.google.com/gsi/client", f = "https://www.googleapis.com/auth/androidpublisher";
2
+ function u() {
3
+ return globalThis.google?.accounts?.oauth2;
4
+ }
5
+ let c;
6
+ function y() {
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
+ const t = document.createElement("script");
9
+ t.src = w, t.async = !0, t.addEventListener(
10
+ "load",
11
+ () => {
12
+ u() ? o() : r(new Error("Google sign-in script loaded without the OAuth client."));
13
+ },
14
+ { once: !0 }
15
+ ), t.addEventListener(
16
+ "error",
17
+ () => {
18
+ t.remove(), r(new Error("Failed to load the Google sign-in script."));
19
+ },
20
+ { once: !0 }
21
+ ), document.head.appendChild(t);
22
+ }), c.catch(() => {
23
+ c = void 0;
24
+ })), c);
25
+ }
26
+ function m(o) {
27
+ return u() ? p(o) : y().then(() => p(o));
28
+ }
29
+ function p({
30
+ clientId: o,
31
+ scope: r = f
32
+ }) {
33
+ return new Promise((t, n) => {
34
+ u().initTokenClient({
35
+ client_id: o,
36
+ scope: r,
37
+ callback: (e) => {
38
+ if (e.error) {
39
+ n(new Error(e.error_description || e.error));
40
+ return;
41
+ }
42
+ if (!e.access_token) {
43
+ n(new Error("Google returned no access token."));
44
+ return;
45
+ }
46
+ t(e.access_token);
47
+ },
48
+ error_callback: (e) => {
49
+ const a = e?.type === "popup_failed_to_open" ? "Google sign-in popup was blocked by the browser." : "Google sign-in was cancelled.";
50
+ n(new Error(e?.message || a));
51
+ }
52
+ }).requestAccessToken();
53
+ });
54
+ }
55
+ class l extends Error {
56
+ code;
57
+ status;
58
+ constructor(r, t = {}) {
59
+ super(r), this.name = "PlaystorePublishError", this.code = t.code, this.status = t.status;
60
+ }
61
+ }
62
+ async function b(o) {
63
+ const { registryApiUrl: r, token: t, organizationId: n, ...h } = o;
64
+ let e;
65
+ try {
66
+ e = new URL(r);
67
+ } catch {
68
+ throw new l(`Invalid registry URL: ${r}`);
69
+ }
70
+ e.pathname = `${e.pathname.replace(/\/$/, "")}/android/playstore/publish`, e.search = "", e.hash = "";
71
+ const a = { "Content-Type": "application/json" };
72
+ t && (a.Authorization = `Bearer ${t}`), n && (a["X-Limrun-Organization"] = n);
73
+ let s;
74
+ try {
75
+ s = await fetch(e.toString(), {
76
+ method: "POST",
77
+ headers: a,
78
+ body: JSON.stringify(h)
79
+ });
80
+ } catch (d) {
81
+ throw new l(
82
+ `Cannot reach the registry: ${d instanceof Error ? d.message : String(d)}`
83
+ );
84
+ }
85
+ const g = await s.text();
86
+ let i;
87
+ try {
88
+ i = g ? JSON.parse(g) : void 0;
89
+ } catch {
90
+ i = void 0;
91
+ }
92
+ if (!s.ok)
93
+ throw new l(i?.message || `Play publish failed with HTTP ${s.status}`, {
94
+ code: i?.code,
95
+ status: s.status
96
+ });
97
+ if (typeof i?.versionCode != "number")
98
+ throw new l("Play publish response is missing versionCode.", {
99
+ status: s.status
100
+ });
101
+ return { versionCode: i.versionCode };
102
+ }
103
+ export {
104
+ f as ANDROID_PUBLISHER_SCOPE,
105
+ l as PlaystorePublishError,
106
+ y as loadGoogleIdentityServices,
107
+ b as publishToPlaystore,
108
+ m as requestGoogleAccessToken
109
+ };
@@ -0,0 +1 @@
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;
@@ -0,0 +1,31 @@
1
+ import { PlaystorePublishInput, PlaystorePublishResult } from './index';
2
+ export type UsePlaystorePublishOptions = Partial<Pick<PlaystorePublishInput, 'registryApiUrl' | 'token' | 'organizationId'>> & {
3
+ /** Google OAuth Web application client ID used for sign-in. */
4
+ googleClientId?: string;
5
+ };
6
+ export type PlaystorePublishStatus = 'idle' | 'signing-in' | 'ready' | 'publishing' | 'published' | 'error';
7
+ export type PlaystorePublishRequest = Omit<PlaystorePublishInput, 'registryApiUrl' | 'token' | 'organizationId' | 'accessToken'>;
8
+ export type UsePlaystorePublishResult = {
9
+ status: PlaystorePublishStatus;
10
+ isSignedIn: boolean;
11
+ versionCode?: number;
12
+ error?: string;
13
+ /** Registry error code driving per-cause UX, e.g. versionCodeExists. */
14
+ errorCode?: string;
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>;
22
+ signInWithGoogle: () => Promise<boolean>;
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
+ */
29
+ reset: () => void;
30
+ };
31
+ export declare function usePlaystorePublish({ registryApiUrl, token, organizationId, googleClientId, }: UsePlaystorePublishOptions): UsePlaystorePublishResult;
@@ -0,0 +1,91 @@
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
9
+ }) {
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)
20
+ return !1;
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);
26
+ try {
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;
31
+ } finally {
32
+ n.current = !1;
33
+ }
34
+ }, [g]), C = d(
35
+ async (e) => {
36
+ if (n.current)
37
+ return;
38
+ if (!f) {
39
+ h("Registry URL is not configured.");
40
+ return;
41
+ }
42
+ const o = v.current;
43
+ if (!o) {
44
+ h("Sign in with Google before publishing.");
45
+ return;
46
+ }
47
+ n.current = !0;
48
+ const a = t.current;
49
+ r("publishing"), s(void 0), c(void 0), l(void 0);
50
+ try {
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
61
+ });
62
+ return a === t.current && (l(i.versionCode), r("published")), i;
63
+ } catch (i) {
64
+ a === t.current && (s(N(i)), i instanceof D && c(i.code), r("error"));
65
+ return;
66
+ } finally {
67
+ n.current = !1;
68
+ }
69
+ },
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");
73
+ }, []);
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
+ );
88
+ }
89
+ export {
90
+ W as usePlaystorePublish
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",
@@ -224,15 +224,15 @@ export {
224
224
  S as d,
225
225
  v as e,
226
226
  I as f,
227
- q as g,
228
- P as h,
229
- y as i,
230
- R as j,
231
- w as k,
227
+ O as g,
228
+ q as h,
229
+ P as i,
230
+ y as j,
231
+ R as k,
232
232
  g as l,
233
- A as m,
234
- H as n,
235
- O as o,
233
+ w as m,
234
+ A as n,
235
+ H as o,
236
236
  E as p,
237
237
  D as r,
238
238
  C as s
@@ -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,8 +1,9 @@
1
1
  {
2
2
  "name": "@limrun/ui",
3
- "version": "0.14.0-rc.2",
3
+ "version": "0.14.0-rc.4",
4
4
  "publishConfig": {
5
- "access": "public"
5
+ "access": "public",
6
+ "tag": "next"
6
7
  },
7
8
  "main": "./dist/index.js",
8
9
  "module": "./dist/index.js",
@@ -23,6 +24,11 @@
23
24
  "import": "./dist/app-store-relay/react.js",
24
25
  "require": "./dist/app-store-relay/react.cjs"
25
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
+ },
26
32
  "./device-build": {
27
33
  "types": "./dist/device-build/index.d.ts",
28
34
  "import": "./dist/device-build/index.js",
@@ -43,6 +49,16 @@
43
49
  "import": "./dist/device-install/react.js",
44
50
  "require": "./dist/device-install/react.cjs"
45
51
  },
52
+ "./play-publish": {
53
+ "types": "./dist/play-publish/index.d.ts",
54
+ "import": "./dist/play-publish/index.js",
55
+ "require": "./dist/play-publish/index.cjs"
56
+ },
57
+ "./play-publish/react": {
58
+ "types": "./dist/play-publish/react.d.ts",
59
+ "import": "./dist/play-publish/react.js",
60
+ "require": "./dist/play-publish/react.cjs"
61
+ },
46
62
  "./package.json": "./package.json"
47
63
  },
48
64
  "scripts": {
@@ -21,12 +21,12 @@ describe('Registry relay primitives', () => {
21
21
  expect(relay.request.mock.calls[0]).toMatchObject([
22
22
  'provisioning',
23
23
  {
24
- method: 'POST',
25
- path: '/account/ios/profile/deleteProvisioningProfile.action',
26
- payload: {
27
- teamId: 'TEAM',
28
- provisioningProfileId: 'PROFILE',
29
- },
24
+ method: 'POST',
25
+ path: '/account/ios/profile/deleteProvisioningProfile.action',
26
+ payload: {
27
+ teamId: 'TEAM',
28
+ provisioningProfileId: 'PROFILE',
29
+ },
30
30
  },
31
31
  ]);
32
32
  });
@@ -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
 
@@ -29,7 +30,11 @@ export type UseAppleIDLoginResult = {
29
30
  close: () => Promise<void>;
30
31
  };
31
32
 
32
- export function useAppleIDLogin({ registryApiUrl, token, organizationId }: UseAppleIDLoginOptions): UseAppleIDLoginResult {
33
+ export function useAppleIDLogin({
34
+ registryApiUrl,
35
+ token,
36
+ organizationId,
37
+ }: UseAppleIDLoginOptions): UseAppleIDLoginResult {
33
38
  const [status, setStatus] = useState<AppleIDLoginStatus>('idle');
34
39
  const [session, setSession] = useState<AppleIDLoginResult | undefined>();
35
40
  const [completeResponse, setCompleteResponse] = useState<AppleRelayResponse | undefined>();
@@ -124,7 +129,3 @@ export function useAppleIDLogin({ registryApiUrl, token, organizationId }: UseAp
124
129
  close,
125
130
  };
126
131
  }
127
-
128
- function errorMessage(error: unknown) {
129
- return error instanceof Error ? error.message : String(error);
130
- }
@@ -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
+ });