@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.
- package/dist/app-store-relay/index.cjs +3 -3
- package/dist/app-store-relay/index.js +5 -5
- package/dist/app-store-relay/react.cjs +1 -1
- package/dist/app-store-relay/react.js +32 -33
- package/dist/apple-credentials/index.cjs +1 -0
- package/dist/apple-credentials/index.d.ts +59 -0
- package/dist/apple-credentials/index.js +227 -0
- package/dist/browser-storage-BnO-Ma-e.mjs +219 -0
- package/dist/browser-storage-DLxIqZk5.js +1 -0
- package/dist/client-D1drDXxE.js +1 -0
- package/dist/{client-C8ZDEjtD.mjs → client-DSuNtWDv.mjs} +1 -1
- package/dist/components/remote-control.d.ts +156 -0
- package/dist/core/device-install/storage/browser-storage.d.ts +8 -0
- package/dist/core/device-install/storage/index.d.ts +1 -0
- package/dist/core/device-install/storage/secret-store.d.ts +101 -0
- package/dist/core/device-install/types.d.ts +6 -0
- package/dist/core/errors.d.ts +1 -0
- package/dist/core/play-publish/google.d.ts +1 -1
- package/dist/device-build/index.cjs +1 -1
- package/dist/device-build/index.js +21 -20
- package/dist/device-build/react.cjs +1 -1
- package/dist/device-build/react.js +42 -43
- package/dist/device-install/index.cjs +1 -1
- package/dist/device-install/index.js +159 -161
- package/dist/device-install/react.cjs +1 -1
- package/dist/device-install/react.js +47 -49
- package/dist/errors-BSkqS7U-.js +1 -0
- package/dist/errors-gy8yQJos.mjs +6 -0
- package/dist/gsa-srp-Bme3JLEi.mjs +266 -0
- package/dist/gsa-srp-CCIfUz7Y.js +1 -0
- package/dist/index-ChhtuQ7e.js +22 -0
- package/dist/index-YDu8K9a4.mjs +11279 -0
- package/dist/index.cjs +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +1405 -1125
- package/dist/play-publish/index.cjs +1 -1
- package/dist/play-publish/index.js +27 -24
- package/dist/play-publish/react.cjs +1 -1
- package/dist/play-publish/react.d.ts +12 -2
- package/dist/play-publish/react.js +70 -60
- package/dist/{provisioning-BmsEnF6X.mjs → provisioning-B_DefBEp.mjs} +1 -1
- package/dist/{provisioning-Va0PH4Kk.js → provisioning-BlQMccX_.js} +1 -1
- package/package.json +6 -1
- package/src/app-store-relay/react.ts +1 -4
- package/src/apple-credentials/index.test.ts +158 -0
- package/src/apple-credentials/index.ts +241 -0
- package/src/components/remote-control.css +32 -0
- package/src/components/remote-control.tsx +865 -5
- package/src/core/device-install/operations/operations.ts +1 -4
- package/src/core/device-install/storage/browser-storage.test.ts +65 -0
- package/src/core/device-install/storage/browser-storage.ts +33 -0
- package/src/core/device-install/storage/index.ts +1 -0
- package/src/core/device-install/storage/secret-store.ts +298 -0
- package/src/core/device-install/types.ts +6 -0
- package/src/core/errors.ts +3 -0
- package/src/core/play-publish/google.ts +11 -2
- package/src/device-build/react.ts +1 -4
- package/src/device-install/demo/demo.tsx +1 -4
- package/src/device-install/react.ts +1 -4
- package/src/play-publish/README.md +5 -4
- package/src/play-publish/react.ts +72 -32
- package/vite.config.ts +1 -0
- package/dist/browser-storage-Cx9eW-bm.mjs +0 -205
- package/dist/browser-storage-jJaIht4K.js +0 -1
- package/dist/client-BtC4n_qf.js +0 -1
- package/dist/index-CMeQfhYy.js +0 -22
- package/dist/index-DFdCvi2_.mjs +0 -11543
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
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
|
|
2
|
-
function
|
|
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
|
|
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 =
|
|
9
|
+
t.src = w, t.async = !0, t.addEventListener(
|
|
10
10
|
"load",
|
|
11
11
|
() => {
|
|
12
|
-
|
|
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(),
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
31
|
-
|
|
32
|
-
client_id:
|
|
33
|
-
scope:
|
|
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(
|
|
56
|
-
super(
|
|
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
|
|
60
|
-
const { registryApiUrl:
|
|
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(
|
|
66
|
+
e = new URL(r);
|
|
64
67
|
} catch {
|
|
65
|
-
throw new l(`Invalid registry URL: ${
|
|
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 (
|
|
80
|
+
} catch (d) {
|
|
78
81
|
throw new l(
|
|
79
|
-
`Cannot reach the registry: ${
|
|
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
|
-
|
|
104
|
+
f as ANDROID_PUBLISHER_SCOPE,
|
|
102
105
|
l as PlaystorePublishError,
|
|
103
106
|
y as loadGoogleIdentityServices,
|
|
104
|
-
|
|
105
|
-
|
|
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"),
|
|
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
|
-
/**
|
|
16
|
-
|
|
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
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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 [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 (!
|
|
16
|
-
return
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
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
|
|
22
|
-
return
|
|
23
|
-
} catch (
|
|
24
|
-
return
|
|
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
|
-
|
|
32
|
+
n.current = !1;
|
|
27
33
|
}
|
|
28
|
-
}, [
|
|
29
|
-
async (
|
|
30
|
-
if (
|
|
34
|
+
}, [g]), C = d(
|
|
35
|
+
async (e) => {
|
|
36
|
+
if (n.current)
|
|
31
37
|
return;
|
|
32
|
-
if (!
|
|
33
|
-
|
|
38
|
+
if (!f) {
|
|
39
|
+
h("Registry URL is not configured.");
|
|
34
40
|
return;
|
|
35
41
|
}
|
|
36
|
-
const
|
|
37
|
-
if (!
|
|
38
|
-
|
|
42
|
+
const o = v.current;
|
|
43
|
+
if (!o) {
|
|
44
|
+
h("Sign in with Google before publishing.");
|
|
39
45
|
return;
|
|
40
46
|
}
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
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
|
|
46
|
-
registryApiUrl:
|
|
47
|
-
token:
|
|
48
|
-
organizationId:
|
|
49
|
-
accessToken:
|
|
50
|
-
|
|
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
|
|
62
|
+
return a === t.current && (l(i.versionCode), r("published")), i;
|
|
53
63
|
} catch (i) {
|
|
54
|
-
|
|
64
|
+
a === t.current && (s(N(i)), i instanceof D && c(i.code), r("error"));
|
|
55
65
|
return;
|
|
56
66
|
} finally {
|
|
57
|
-
|
|
67
|
+
n.current = !1;
|
|
58
68
|
}
|
|
59
69
|
},
|
|
60
|
-
[
|
|
61
|
-
), I =
|
|
62
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
90
|
+
W as usePlaystorePublish
|
|
81
91
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const a=require("./browser-storage-
|
|
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
|
+
"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
|
+
});
|