@limrun/ui 0.9.0-rc.2 → 0.9.0-rc.5

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 (78) hide show
  1. package/README.md +0 -9
  2. package/dist/components/inspect-overlay.d.ts +33 -0
  3. package/dist/components/remote-control.d.ts +86 -0
  4. package/dist/core/ax-fetcher.d.ts +49 -0
  5. package/dist/core/ax-tree.d.ts +99 -0
  6. package/dist/index.cjs +1 -1
  7. package/dist/index.css +1 -1
  8. package/dist/index.d.ts +3 -2
  9. package/dist/index.js +1491 -777
  10. package/package.json +8 -17
  11. package/src/components/inspect-overlay.css +223 -0
  12. package/src/components/inspect-overlay.tsx +437 -0
  13. package/src/components/remote-control.tsx +547 -9
  14. package/src/core/ax-fetcher.test.ts +418 -0
  15. package/src/core/ax-fetcher.ts +377 -0
  16. package/src/core/ax-tree.test.ts +491 -0
  17. package/src/core/ax-tree.ts +416 -0
  18. package/src/demo.tsx +93 -10
  19. package/src/index.ts +17 -2
  20. package/vite.config.ts +2 -6
  21. package/vitest.config.ts +23 -0
  22. package/dist/components/device-install/device-install-dialog.d.ts +0 -5
  23. package/dist/components/device-install/index.d.ts +0 -2
  24. package/dist/core/device-install/apple/client.d.ts +0 -17
  25. package/dist/core/device-install/apple/crypto.d.ts +0 -20
  26. package/dist/core/device-install/apple/gsa-srp.d.ts +0 -26
  27. package/dist/core/device-install/apple/index.d.ts +0 -5
  28. package/dist/core/device-install/apple/provisioning.d.ts +0 -161
  29. package/dist/core/device-install/apple/relay.d.ts +0 -29
  30. package/dist/core/device-install/index.d.ts +0 -4
  31. package/dist/core/device-install/operations/index.d.ts +0 -6
  32. package/dist/core/device-install/operations/limbuild-client.d.ts +0 -28
  33. package/dist/core/device-install/operations/operations.d.ts +0 -32
  34. package/dist/core/device-install/operations/relay-client.d.ts +0 -25
  35. package/dist/core/device-install/operations/relay-protocol.d.ts +0 -27
  36. package/dist/core/device-install/operations/usbmux.d.ts +0 -32
  37. package/dist/core/device-install/operations/webusb.d.ts +0 -21
  38. package/dist/core/device-install/storage/browser-storage.d.ts +0 -43
  39. package/dist/core/device-install/storage/index.d.ts +0 -1
  40. package/dist/core/device-install/types.d.ts +0 -48
  41. package/dist/device-install/index.cjs +0 -1
  42. package/dist/device-install/index.d.ts +0 -3
  43. package/dist/device-install/index.js +0 -77
  44. package/dist/device-install/react.cjs +0 -1
  45. package/dist/device-install/react.d.ts +0 -1
  46. package/dist/device-install/react.js +0 -4
  47. package/dist/device-install-dialog-CSwQgbBm.js +0 -2
  48. package/dist/device-install-dialog-nThj775b.mjs +0 -395
  49. package/dist/device-install-dialog.css +0 -1
  50. package/dist/hooks/index.d.ts +0 -1
  51. package/dist/hooks/use-device-install.d.ts +0 -70
  52. package/dist/use-device-install-C1uVac59.mjs +0 -13541
  53. package/dist/use-device-install-Ca4jcVKU.js +0 -31
  54. package/src/components/device-install/device-install-dialog.css +0 -244
  55. package/src/components/device-install/device-install-dialog.tsx +0 -432
  56. package/src/components/device-install/index.ts +0 -2
  57. package/src/core/device-install/apple/client.ts +0 -152
  58. package/src/core/device-install/apple/crypto.ts +0 -202
  59. package/src/core/device-install/apple/gsa-srp.ts +0 -127
  60. package/src/core/device-install/apple/index.ts +0 -5
  61. package/src/core/device-install/apple/provisioning.ts +0 -298
  62. package/src/core/device-install/apple/relay.ts +0 -221
  63. package/src/core/device-install/index.ts +0 -4
  64. package/src/core/device-install/operations/index.ts +0 -6
  65. package/src/core/device-install/operations/limbuild-client.ts +0 -104
  66. package/src/core/device-install/operations/operations.ts +0 -217
  67. package/src/core/device-install/operations/relay-client.ts +0 -255
  68. package/src/core/device-install/operations/relay-protocol.ts +0 -71
  69. package/src/core/device-install/operations/usbmux.ts +0 -270
  70. package/src/core/device-install/operations/webusb-dom.d.ts +0 -54
  71. package/src/core/device-install/operations/webusb.ts +0 -105
  72. package/src/core/device-install/storage/browser-storage.ts +0 -251
  73. package/src/core/device-install/storage/index.ts +0 -1
  74. package/src/core/device-install/types.ts +0 -65
  75. package/src/device-install/index.ts +0 -3
  76. package/src/device-install/react.ts +0 -1
  77. package/src/hooks/index.ts +0 -1
  78. package/src/hooks/use-device-install.ts +0 -1067
@@ -1,152 +0,0 @@
1
- import { AppleGsaSrpClient } from './gsa-srp';
2
- import {
3
- createAppleRelaySession,
4
- deleteAppleRelaySession,
5
- fetchAppleAccountSession,
6
- proxyPhoneTwoFactorCode,
7
- proxySrpComplete,
8
- proxySrpInit,
9
- proxyTwoFactorCode,
10
- triggerPhoneTwoFactor,
11
- triggerTrustedDeviceTwoFactor,
12
- type AppleRelayResponse,
13
- } from './relay';
14
-
15
- export type AppleIDLoginInput = {
16
- limbuildApiUrl: string;
17
- accountName: string;
18
- password: string;
19
- token?: string;
20
- };
21
-
22
- export type AppleIDLoginResult = {
23
- appleSessionId: string;
24
- completeResponse: AppleRelayResponse;
25
- twoFactorChallengeResponse?: AppleRelayResponse;
26
- requiresTwoFactor: boolean;
27
- finishTwoFactor: (code: string) => Promise<AppleRelayResponse>;
28
- finalize: () => Promise<AppleRelayResponse>;
29
- close: () => Promise<void>;
30
- };
31
-
32
- type TwoFactorMethod =
33
- | { type: 'trustedDevice' }
34
- | { type: 'phone'; phoneNumberId: number; mode: string };
35
-
36
- export async function startBrowserOwnedAppleIDLogin({
37
- limbuildApiUrl,
38
- accountName,
39
- password,
40
- token,
41
- }: AppleIDLoginInput): Promise<AppleIDLoginResult> {
42
- const { appleSessionId } = await createAppleRelaySession(limbuildApiUrl, token);
43
- try {
44
- const srp = new AppleGsaSrpClient(accountName);
45
- const initResponse = await proxySrpInit(limbuildApiUrl, appleSessionId, await srp.init(), token);
46
- if (initResponse.status < 200 || initResponse.status >= 300) {
47
- throw new Error(`Apple SRP init failed: HTTP ${initResponse.status} ${initResponse.rawBody ?? ''}`.trim());
48
- }
49
- if (!initResponse.body) {
50
- throw new Error('Apple SRP init response did not include a body.');
51
- }
52
- const proof = await srp.complete(password, initResponse.body);
53
- const completeResponse = await proxySrpComplete(
54
- limbuildApiUrl,
55
- appleSessionId,
56
- {
57
- ...proof,
58
- rememberMe: false,
59
- trustTokens: [],
60
- },
61
- token,
62
- );
63
- const requiresTwoFactor = completeResponse.status === 409;
64
- let twoFactorChallengeResponse: AppleRelayResponse | undefined;
65
- let twoFactorMethod: TwoFactorMethod = { type: 'trustedDevice' };
66
- if (requiresTwoFactor) {
67
- twoFactorChallengeResponse = await triggerTrustedDeviceTwoFactor(limbuildApiUrl, appleSessionId, token);
68
- const phone = trustedPhoneNumberFromChallenge(twoFactorChallengeResponse.body);
69
- if (phone) {
70
- twoFactorMethod = {
71
- type: 'phone',
72
- phoneNumberId: phone.id,
73
- mode: phone.pushMode ?? 'sms',
74
- };
75
- }
76
- if (twoFactorChallengeResponse.status === 412) {
77
- if (!phone) {
78
- throw new Error('Apple requested phone verification but did not include a trusted phone number.');
79
- }
80
- twoFactorChallengeResponse = await triggerPhoneTwoFactor(
81
- limbuildApiUrl,
82
- appleSessionId,
83
- phone.id,
84
- phone.pushMode ?? 'sms',
85
- token,
86
- );
87
- }
88
- if (twoFactorChallengeResponse.status < 200 || twoFactorChallengeResponse.status >= 300) {
89
- throw new Error(
90
- `Apple two-factor challenge failed: HTTP ${twoFactorChallengeResponse.status} ${
91
- twoFactorChallengeResponse.rawBody ?? ''
92
- }`.trim(),
93
- );
94
- }
95
- } else if (completeResponse.status < 200 || completeResponse.status >= 300) {
96
- throw new Error(`Apple SRP complete failed: HTTP ${completeResponse.status} ${completeResponse.rawBody ?? ''}`.trim());
97
- }
98
- return {
99
- appleSessionId,
100
- completeResponse,
101
- twoFactorChallengeResponse,
102
- requiresTwoFactor,
103
- finishTwoFactor: async (code) => {
104
- const response =
105
- twoFactorMethod.type === 'phone'
106
- ? await proxyPhoneTwoFactorCode(
107
- limbuildApiUrl,
108
- appleSessionId,
109
- twoFactorMethod.phoneNumberId,
110
- code,
111
- twoFactorMethod.mode,
112
- token,
113
- )
114
- : await proxyTwoFactorCode(limbuildApiUrl, appleSessionId, code, token);
115
- if (response.status < 200 || response.status >= 300) {
116
- throw new Error(`Apple two-factor code failed: HTTP ${response.status} ${response.rawBody ?? ''}`.trim());
117
- }
118
- return response;
119
- },
120
- finalize: async () => fetchAppleAccountSession(limbuildApiUrl, appleSessionId, token),
121
- close: () => deleteAppleRelaySession(limbuildApiUrl, appleSessionId, token),
122
- };
123
- } catch (error) {
124
- await deleteAppleRelaySession(limbuildApiUrl, appleSessionId, token).catch(() => undefined);
125
- throw error;
126
- }
127
- }
128
-
129
- function trustedPhoneNumberFromChallenge(body: unknown) {
130
- if (!isRecord(body)) return undefined;
131
- const verification = isRecord(body.phoneNumberVerification) ? body.phoneNumberVerification : undefined;
132
- const trustedPhoneNumber =
133
- recordValue(verification?.trustedPhoneNumber) ??
134
- recordValue(body.trustedPhoneNumber) ??
135
- recordValue(body.phoneNumber);
136
- if (!trustedPhoneNumber) return undefined;
137
- const id = trustedPhoneNumber.id;
138
- if (typeof id !== 'number') return undefined;
139
- const pushMode =
140
- typeof trustedPhoneNumber.pushMode === 'string' ? trustedPhoneNumber.pushMode
141
- : typeof body.mode === 'string' ? body.mode
142
- : undefined;
143
- return { id, pushMode };
144
- }
145
-
146
- function recordValue(value: unknown) {
147
- return isRecord(value) ? value : undefined;
148
- }
149
-
150
- function isRecord(value: unknown): value is Record<string, unknown> {
151
- return typeof value === 'object' && value !== null && !Array.isArray(value);
152
- }
@@ -1,202 +0,0 @@
1
- import forge from 'node-forge';
2
-
3
- export type AppleSigningKeyMaterial = {
4
- privateKey: CryptoKey;
5
- privateKeyPKCS8Base64: string;
6
- publicKeySPKIBase64: string;
7
- csrPEM: string;
8
- csrBase64: string;
9
- };
10
-
11
- export type AppleCSRInput = {
12
- commonName: string;
13
- emailAddress?: string;
14
- };
15
-
16
- export type ExportP12Input = {
17
- privateKeyPKCS8Base64: string;
18
- certificateBase64?: string;
19
- certificatePEM?: string;
20
- password: string;
21
- friendlyName?: string;
22
- };
23
-
24
- const rsaAlgorithm: RsaHashedKeyGenParams = {
25
- name: 'RSASSA-PKCS1-v1_5',
26
- modulusLength: 2048,
27
- publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
28
- hash: 'SHA-256',
29
- };
30
-
31
- export async function generateAppleSigningKeyAndCSR(input: AppleCSRInput): Promise<AppleSigningKeyMaterial> {
32
- if (!crypto.subtle) {
33
- throw new Error('WebCrypto is not available in this browser.');
34
- }
35
- const keyPair = await crypto.subtle.generateKey(rsaAlgorithm, true, ['sign', 'verify']);
36
- const publicKeySPKI = new Uint8Array(await crypto.subtle.exportKey('spki', keyPair.publicKey));
37
- const certificationRequestInfo = derSequence(
38
- derInteger(0),
39
- derName(input),
40
- publicKeySPKI,
41
- derContext(0, new Uint8Array()),
42
- );
43
- const signature = new Uint8Array(
44
- await crypto.subtle.sign('RSASSA-PKCS1-v1_5', keyPair.privateKey, toArrayBuffer(certificationRequestInfo)),
45
- );
46
- const csrDER = derSequence(
47
- certificationRequestInfo,
48
- derSequence(derOID('1.2.840.113549.1.1.11'), derNull()),
49
- derBitString(signature),
50
- );
51
- const privateKeyPKCS8 = new Uint8Array(await crypto.subtle.exportKey('pkcs8', keyPair.privateKey));
52
- return {
53
- privateKey: keyPair.privateKey,
54
- privateKeyPKCS8Base64: bytesToBase64(privateKeyPKCS8),
55
- publicKeySPKIBase64: bytesToBase64(publicKeySPKI),
56
- csrPEM: pemBlock('CERTIFICATE REQUEST', csrDER),
57
- csrBase64: bytesToBase64(csrDER),
58
- };
59
- }
60
-
61
- export function exportAppleCertificateP12(input: ExportP12Input) {
62
- if (!input.certificateBase64 && !input.certificatePEM) {
63
- throw new Error('certificateBase64 or certificatePEM is required.');
64
- }
65
- const privateKey = forge.pki.privateKeyFromPem(
66
- pemFromBase64('PRIVATE KEY', input.privateKeyPKCS8Base64),
67
- );
68
- const certificate = input.certificatePEM
69
- ? forge.pki.certificateFromPem(input.certificatePEM)
70
- : forge.pki.certificateFromAsn1(
71
- forge.asn1.fromDer(forge.util.createBuffer(base64ToBinary(input.certificateBase64!))),
72
- );
73
- const p12 = forge.pkcs12.toPkcs12Asn1(privateKey, [certificate], input.password, {
74
- algorithm: '3des',
75
- friendlyName: input.friendlyName,
76
- });
77
- const der = forge.asn1.toDer(p12).getBytes();
78
- return binaryToBase64(der);
79
- }
80
-
81
- function derName(input: AppleCSRInput) {
82
- const attributes = [derAttribute('2.5.4.3', derUTF8String(input.commonName))];
83
- if (input.emailAddress) {
84
- attributes.push(derAttribute('1.2.840.113549.1.9.1', derIA5String(input.emailAddress)));
85
- }
86
- return derSequence(...attributes);
87
- }
88
-
89
- function derAttribute(oid: string, value: Uint8Array) {
90
- return derSet(derSequence(derOID(oid), value));
91
- }
92
-
93
- function derSequence(...values: Uint8Array[]) {
94
- return derTLV(0x30, concatBytes(...values));
95
- }
96
-
97
- function derSet(...values: Uint8Array[]) {
98
- return derTLV(0x31, concatBytes(...values));
99
- }
100
-
101
- function derContext(tag: number, value: Uint8Array) {
102
- return derTLV(0xa0 + tag, value);
103
- }
104
-
105
- function derInteger(value: number) {
106
- return derTLV(0x02, new Uint8Array([value]));
107
- }
108
-
109
- function derNull() {
110
- return new Uint8Array([0x05, 0x00]);
111
- }
112
-
113
- function derUTF8String(value: string) {
114
- return derTLV(0x0c, new TextEncoder().encode(value));
115
- }
116
-
117
- function derIA5String(value: string) {
118
- return derTLV(0x16, new TextEncoder().encode(value));
119
- }
120
-
121
- function derBitString(value: Uint8Array) {
122
- return derTLV(0x03, concatBytes(new Uint8Array([0]), value));
123
- }
124
-
125
- function derOID(oid: string) {
126
- const parts = oid.split('.').map((part) => parseInt(part, 10));
127
- if (parts.length < 2 || parts.some((part) => !Number.isFinite(part))) {
128
- throw new Error(`Invalid OID: ${oid}`);
129
- }
130
- const encoded = [parts[0] * 40 + parts[1]];
131
- for (const part of parts.slice(2)) {
132
- const stack = [part & 0x7f];
133
- let value = part >> 7;
134
- while (value > 0) {
135
- stack.unshift((value & 0x7f) | 0x80);
136
- value >>= 7;
137
- }
138
- encoded.push(...stack);
139
- }
140
- return derTLV(0x06, new Uint8Array(encoded));
141
- }
142
-
143
- function derTLV(tag: number, value: Uint8Array) {
144
- return concatBytes(new Uint8Array([tag]), derLength(value.byteLength), value);
145
- }
146
-
147
- function derLength(length: number) {
148
- if (length < 0x80) {
149
- return new Uint8Array([length]);
150
- }
151
- const bytes: number[] = [];
152
- let value = length;
153
- while (value > 0) {
154
- bytes.unshift(value & 0xff);
155
- value >>= 8;
156
- }
157
- return new Uint8Array([0x80 | bytes.length, ...bytes]);
158
- }
159
-
160
- function concatBytes(...chunks: Uint8Array[]) {
161
- const length = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
162
- const out = new Uint8Array(length);
163
- let offset = 0;
164
- for (const chunk of chunks) {
165
- out.set(chunk, offset);
166
- offset += chunk.byteLength;
167
- }
168
- return out;
169
- }
170
-
171
- function pemBlock(label: string, der: Uint8Array) {
172
- const base64 = bytesToBase64(der);
173
- const lines = base64.match(/.{1,64}/g) ?? [];
174
- return `-----BEGIN ${label}-----\n${lines.join('\n')}\n-----END ${label}-----\n`;
175
- }
176
-
177
- function pemFromBase64(label: string, base64: string) {
178
- const lines = base64.match(/.{1,64}/g) ?? [];
179
- return `-----BEGIN ${label}-----\n${lines.join('\n')}\n-----END ${label}-----\n`;
180
- }
181
-
182
- function bytesToBase64(bytes: Uint8Array) {
183
- let binary = '';
184
- for (const byte of bytes) {
185
- binary += String.fromCharCode(byte);
186
- }
187
- return btoa(binary);
188
- }
189
-
190
- function binaryToBase64(binary: string) {
191
- return btoa(binary);
192
- }
193
-
194
- function base64ToBinary(value: string) {
195
- return atob(value);
196
- }
197
-
198
- function toArrayBuffer(bytes: Uint8Array): ArrayBuffer {
199
- const copy = new Uint8Array(bytes.byteLength);
200
- copy.set(bytes);
201
- return copy.buffer;
202
- }
@@ -1,127 +0,0 @@
1
- import { Hash, Mode, Srp, util } from '@foxt/js-srp';
2
-
3
- export type AppleSRPProtocol = 's2k' | 's2k_fo';
4
-
5
- export type AppleSRPInitRequest = {
6
- a: string;
7
- accountName: string;
8
- protocols: AppleSRPProtocol[];
9
- };
10
-
11
- export type AppleSRPInitResponse = {
12
- iteration: number;
13
- salt: string;
14
- protocol: AppleSRPProtocol;
15
- b: string;
16
- c: string;
17
- };
18
-
19
- export type AppleSRPCompleteProof = {
20
- accountName: string;
21
- m1: string;
22
- m2: string;
23
- c: string;
24
- };
25
-
26
- const srp = new Srp(Mode.GSA, Hash.SHA256, 2048);
27
-
28
- export class AppleGsaSrpClient {
29
- private srpClient?: Awaited<ReturnType<typeof srp.newClient>>;
30
-
31
- constructor(private readonly accountName: string) {}
32
-
33
- async init(): Promise<AppleSRPInitRequest> {
34
- if (this.srpClient) {
35
- throw new Error('SRP client is already initialized.');
36
- }
37
- this.srpClient = await srp.newClient(stringToBytes(this.accountName), new Uint8Array());
38
- return {
39
- accountName: this.accountName,
40
- protocols: ['s2k', 's2k_fo'],
41
- a: bytesToBase64(util.bytesFromBigint(this.srpClient.A)),
42
- };
43
- }
44
-
45
- async complete(password: string, serverData: AppleSRPInitResponse): Promise<AppleSRPCompleteProof> {
46
- if (!this.srpClient) {
47
- throw new Error('SRP client is not initialized.');
48
- }
49
- if (serverData.protocol !== 's2k' && serverData.protocol !== 's2k_fo') {
50
- throw new Error(`Unsupported Apple SRP protocol ${serverData.protocol}.`);
51
- }
52
- const salt = base64ToBytes(serverData.salt);
53
- const serverPublicKey = base64ToBytes(serverData.b);
54
- const derivedPassword = await deriveApplePassword(
55
- serverData.protocol,
56
- password,
57
- salt,
58
- serverData.iteration,
59
- );
60
- this.srpClient.p = derivedPassword;
61
- await this.srpClient.generate(salt, serverPublicKey);
62
- const m2 = await this.srpClient.generateM2();
63
- return {
64
- accountName: this.accountName,
65
- c: serverData.c,
66
- m1: bytesToBase64(this.srpClient._M),
67
- m2: bytesToBase64(m2),
68
- };
69
- }
70
- }
71
-
72
- async function deriveApplePassword(
73
- protocol: AppleSRPProtocol,
74
- password: string,
75
- salt: Uint8Array,
76
- iterations: number,
77
- ) {
78
- let passHash = new Uint8Array(await util.hash(srp.h, toArrayBuffer(stringToBytes(password))));
79
- if (protocol === 's2k_fo') {
80
- passHash = stringToBytes(util.toHex(passHash));
81
- }
82
- const imported = await crypto.subtle.importKey(
83
- 'raw',
84
- toArrayBuffer(passHash),
85
- { name: 'PBKDF2' },
86
- false,
87
- ['deriveBits'],
88
- );
89
- const derived = await crypto.subtle.deriveBits(
90
- {
91
- name: 'PBKDF2',
92
- hash: { name: 'SHA-256' },
93
- iterations,
94
- salt: toArrayBuffer(salt),
95
- },
96
- imported,
97
- 256,
98
- );
99
- return new Uint8Array(derived);
100
- }
101
-
102
- function stringToBytes(value: string) {
103
- return new TextEncoder().encode(value);
104
- }
105
-
106
- function bytesToBase64(bytes: Uint8Array) {
107
- let binary = '';
108
- for (const byte of bytes) {
109
- binary += String.fromCharCode(byte);
110
- }
111
- return btoa(binary);
112
- }
113
-
114
- function base64ToBytes(value: string) {
115
- const binary = atob(value);
116
- const bytes = new Uint8Array(binary.length);
117
- for (let index = 0; index < binary.length; index += 1) {
118
- bytes[index] = binary.charCodeAt(index);
119
- }
120
- return bytes;
121
- }
122
-
123
- function toArrayBuffer(bytes: Uint8Array): ArrayBuffer {
124
- const copy = new Uint8Array(bytes.byteLength);
125
- copy.set(bytes);
126
- return copy.buffer;
127
- }
@@ -1,5 +0,0 @@
1
- export * from './client';
2
- export * from './crypto';
3
- export * from './gsa-srp';
4
- export * from './provisioning';
5
- export * from './relay';