@sitepong/fastnode 0.3.0 → 0.3.2
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/README.md +6 -0
- package/dist/actions/index.cjs +81 -0
- package/dist/actions/index.cjs.map +1 -1
- package/dist/actions/index.mjs +82 -1
- package/dist/actions/index.mjs.map +1 -1
- package/dist/{apple-session-jUzBrql5.d.cts → apple-session-D6x7Jo3D.d.cts} +1 -1
- package/dist/{apple-session-jUzBrql5.d.ts → apple-session-D6x7Jo3D.d.ts} +1 -1
- package/dist/cert/index.cjs +92 -51
- package/dist/cert/index.cjs.map +1 -1
- package/dist/cert/index.d.cts +5 -5
- package/dist/cert/index.d.ts +5 -5
- package/dist/cert/index.mjs +92 -51
- package/dist/cert/index.mjs.map +1 -1
- package/dist/{cert-RvXmdMke.d.ts → cert-C-yRB-sO.d.ts} +2 -2
- package/dist/{cert-eOoxGMN1.d.cts → cert-C4hhKH1n.d.cts} +2 -2
- package/dist/cli/index.cjs +151 -47
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +3 -3
- package/dist/cli/index.d.ts +3 -3
- package/dist/cli/index.mjs +152 -48
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.cjs +77 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +3 -3
- package/dist/client/index.d.ts +3 -3
- package/dist/client/index.mjs +72 -1
- package/dist/client/index.mjs.map +1 -1
- package/dist/core/index.d.cts +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/deliver/index.d.cts +4 -4
- package/dist/deliver/index.d.ts +4 -4
- package/dist/{enums-yXNVh3UI.d.cts → enums-BlpayMTx.d.cts} +1 -1
- package/dist/{enums-yXNVh3UI.d.ts → enums-BlpayMTx.d.ts} +1 -1
- package/dist/frameit/index.d.cts +1 -1
- package/dist/frameit/index.d.ts +1 -1
- package/dist/gradle/index.d.cts +1 -1
- package/dist/gradle/index.d.ts +1 -1
- package/dist/gym/index.d.cts +2 -2
- package/dist/gym/index.d.ts +2 -2
- package/dist/{index-DnHQ2uT2.d.cts → index-258t9n7v.d.cts} +2 -2
- package/dist/{index-BvyZ4uiw.d.ts → index-6LKy36sE.d.ts} +4 -4
- package/dist/{index-icIH5e5V.d.cts → index-B74vgav6.d.cts} +4 -4
- package/dist/{index-BoU8qt8r.d.cts → index-B7ERdhh8.d.cts} +1 -1
- package/dist/{index-BoU8qt8r.d.ts → index-B7ERdhh8.d.ts} +1 -1
- package/dist/{index-laE3_wm9.d.cts → index-BIRpj0f2.d.cts} +2 -2
- package/dist/{index-CBf0-NUQ.d.ts → index-BJLHWMwL.d.ts} +3 -3
- package/dist/{index-mEEB7An6.d.ts → index-B_D_01oz.d.ts} +1 -1
- package/dist/{index-D1GHEM3c.d.ts → index-Ba1jHQYg.d.ts} +1 -1
- package/dist/{index-DGkgRnZp.d.cts → index-BawZaGqD.d.cts} +3 -3
- package/dist/{index-8djTPW8O.d.ts → index-BmrOdmNL.d.ts} +2 -2
- package/dist/{index-DjC_gmnL.d.ts → index-C8L03MBu.d.ts} +3 -3
- package/dist/{index-Cp6cjtd-.d.ts → index-CHLDQweM.d.cts} +1 -1
- package/dist/{index-Cp6cjtd-.d.cts → index-CHLDQweM.d.ts} +1 -1
- package/dist/{index-B6-MJpO4.d.cts → index-CRFfLsEG.d.cts} +1 -1
- package/dist/{index-CAEiXVWd.d.ts → index-CV6KnSy1.d.ts} +2 -2
- package/dist/index-CX2hUvvG.d.cts +287 -0
- package/dist/index-CX2hUvvG.d.ts +287 -0
- package/dist/{index-C9mHBJED.d.ts → index-ClhWjR7a.d.ts} +2 -2
- package/dist/{index-BGhV0gS-.d.ts → index-Cq-xo-9x.d.ts} +2 -2
- package/dist/{index-CR3BFPIz.d.ts → index-Cw7ojUY5.d.cts} +1 -1
- package/dist/{index-CR3BFPIz.d.cts → index-Cw7ojUY5.d.ts} +1 -1
- package/dist/{index-C2yG3wXA.d.cts → index-CwD7lG0y.d.cts} +1 -1
- package/dist/{index-C2yG3wXA.d.ts → index-CwD7lG0y.d.ts} +1 -1
- package/dist/{index-DLJso7Vs.d.cts → index-CxrqhkVF.d.cts} +1 -1
- package/dist/{index-BE7bwNff.d.cts → index-D3zyYCYs.d.cts} +2 -2
- package/dist/{index-BaNcf7jt.d.cts → index-DCrSWVMb.d.cts} +3 -3
- package/dist/{index-rTZOdMRE.d.ts → index-DP2f5ZsS.d.ts} +3 -3
- package/dist/{index-DfkIz7i0.d.cts → index-DRjXV9ej.d.cts} +3 -3
- package/dist/{index-Dv-Mek07.d.ts → index-DSXBQeur.d.cts} +1 -1
- package/dist/{index-Dv-Mek07.d.cts → index-DSXBQeur.d.ts} +1 -1
- package/dist/{index-BYonklf3.d.cts → index-Dvi-3DSM.d.cts} +1 -1
- package/dist/{index-BYonklf3.d.ts → index-Dvi-3DSM.d.ts} +1 -1
- package/dist/{index-C01Rkrir.d.ts → index-Dy8EWOnt.d.ts} +2 -2
- package/dist/{index-DrmS802s.d.cts → index-HWWl6HEL.d.cts} +34 -3
- package/dist/{index-DWOLZNlz.d.cts → index-QYgreJV3.d.cts} +2 -2
- package/dist/{index-lsuS2R1T.d.ts → index-SyCMBpNN.d.ts} +34 -3
- package/dist/{index-DFstwgGK.d.cts → index-yro2MWgt.d.cts} +2 -2
- package/dist/index.cjs +1543 -964
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -23
- package/dist/index.d.ts +23 -23
- package/dist/index.mjs +1544 -965
- package/dist/index.mjs.map +1 -1
- package/dist/lane/index.cjs +81 -0
- package/dist/lane/index.cjs.map +1 -1
- package/dist/lane/index.mjs +82 -1
- package/dist/lane/index.mjs.map +1 -1
- package/dist/match/index.d.cts +6 -6
- package/dist/match/index.d.ts +6 -6
- package/dist/match/index.mjs +1 -1
- package/dist/pem/index.d.cts +4 -4
- package/dist/pem/index.d.ts +4 -4
- package/dist/pilot/index.d.cts +4 -4
- package/dist/pilot/index.d.ts +4 -4
- package/dist/playstore/index.cjs +18208 -1
- package/dist/playstore/index.cjs.map +1 -1
- package/dist/playstore/index.d.cts +1 -1
- package/dist/playstore/index.d.ts +1 -1
- package/dist/playstore/index.mjs +18196 -2
- package/dist/playstore/index.mjs.map +1 -1
- package/dist/precheck/index.d.cts +4 -4
- package/dist/precheck/index.d.ts +4 -4
- package/dist/privacy/index.cjs +32 -22
- package/dist/privacy/index.cjs.map +1 -1
- package/dist/privacy/index.d.cts +2 -2
- package/dist/privacy/index.d.ts +2 -2
- package/dist/privacy/index.mjs +32 -22
- package/dist/privacy/index.mjs.map +1 -1
- package/dist/produce/index.d.cts +4 -4
- package/dist/produce/index.d.ts +4 -4
- package/dist/review/index.cjs +32 -22
- package/dist/review/index.cjs.map +1 -1
- package/dist/review/index.d.cts +2 -2
- package/dist/review/index.d.ts +2 -2
- package/dist/review/index.mjs +32 -22
- package/dist/review/index.mjs.map +1 -1
- package/dist/sigh/index.d.cts +4 -4
- package/dist/sigh/index.d.ts +4 -4
- package/dist/snapshot/index.d.cts +1 -1
- package/dist/snapshot/index.d.ts +1 -1
- package/dist/trainer/index.d.cts +1 -1
- package/dist/trainer/index.d.ts +1 -1
- package/dist/xcode/index.d.cts +1 -1
- package/dist/xcode/index.d.ts +1 -1
- package/package.json +2 -2
- package/dist/index-CXJlvmB4.d.cts +0 -63
- package/dist/index-CXJlvmB4.d.ts +0 -63
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/privacy/index.ts","../../src/review/index.ts"],"names":["IRIS_BASE","iris"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * App Privacy details (\"nutrition labels\") for an app in App Store Connect.\n * 1:1 port of Fastlane's `upload_app_privacy_details_to_app_store` action and\n * Spaceship's AppDataUsage / AppDataUsagesPublishState models.\n *\n * The public App Store Connect API has no endpoint for privacy details — an\n * API key cannot set them. Fastlane goes through the iris API that App Store\n * Connect's own web UI uses, authenticated with an Apple ID session, and so\n * does this: pass the AppleSession from `loginWithAppleId` / the App Release\n * sign-in.\n *\n * Usages use Fastlane's JSON format, so an existing `app_privacy_details.json`\n * works unchanged:\n *\n * [\n * { \"category\": \"EMAIL_ADDRESS\", \"purposes\": [\"APP_FUNCTIONALITY\"], \"data_protections\": [\"DATA_LINKED_TO_YOU\"] },\n * { \"data_protections\": [\"DATA_NOT_COLLECTED\"] } // an app that collects nothing\n * ]\n *\n * As in Fastlane, the upload REPLACES everything: existing usages are deleted\n * first, then the new ones are created, then the answers are published.\n */\n\nimport type { AppleSession } from \"../client/apple-session.js\";\n\nconst IRIS_BASE = \"https://appstoreconnect.apple.com/iris/v1\";\nconst OLYMPUS_SESSION = \"https://appstoreconnect.apple.com/olympus/v1/session\";\n\n/** Every category App Store Connect accepts. Anything else is refused before existing answers are deleted. */\nexport const DATA_USAGE_CATEGORIES = [\n \"PAYMENT_INFORMATION\", \"CREDIT_AND_FRAUD\", \"OTHER_FINANCIAL_INFO\",\n \"PRECISE_LOCATION\", \"COARSE_LOCATION\", \"SENSITIVE_INFO\",\n \"PHYSICAL_ADDRESS\", \"EMAIL_ADDRESS\", \"NAME\", \"PHONE_NUMBER\", \"OTHER_CONTACT_INFO\",\n \"CONTACTS\", \"EMAILS_OR_TEXT_MESSAGES\", \"PHOTOS_OR_VIDEOS\", \"AUDIO\", \"GAMEPLAY_CONTENT\",\n \"CUSTOMER_SUPPORT\", \"OTHER_USER_CONTENT\", \"BROWSING_HISTORY\", \"SEARCH_HISTORY\",\n \"USER_ID\", \"DEVICE_ID\", \"PURCHASE_HISTORY\", \"PRODUCT_INTERACTION\", \"ADVERTISING_DATA\",\n \"OTHER_USAGE_DATA\", \"CRASH_DATA\", \"PERFORMANCE_DATA\", \"OTHER_DIAGNOSTIC_DATA\",\n \"OTHER_DATA\", \"HEALTH\", \"FITNESS\",\n] as const;\nexport const DATA_USAGE_PURPOSES = [\n \"THIRD_PARTY_ADVERTISING\", \"DEVELOPERS_ADVERTISING\", \"ANALYTICS\",\n \"PRODUCT_PERSONALIZATION\", \"APP_FUNCTIONALITY\", \"OTHER_PURPOSES\",\n] as const;\nexport const DATA_PROTECTIONS = [\n \"DATA_USED_TO_TRACK_YOU\", \"DATA_LINKED_TO_YOU\", \"DATA_NOT_LINKED_TO_YOU\", \"DATA_NOT_COLLECTED\",\n] as const;\n\nexport type DataUsageCategory =\n | \"PAYMENT_INFORMATION\" | \"CREDIT_AND_FRAUD\" | \"OTHER_FINANCIAL_INFO\"\n | \"PRECISE_LOCATION\" | \"COARSE_LOCATION\" | \"SENSITIVE_INFO\"\n | \"PHYSICAL_ADDRESS\" | \"EMAIL_ADDRESS\" | \"NAME\" | \"PHONE_NUMBER\" | \"OTHER_CONTACT_INFO\"\n | \"CONTACTS\" | \"EMAILS_OR_TEXT_MESSAGES\" | \"PHOTOS_OR_VIDEOS\" | \"AUDIO\" | \"GAMEPLAY_CONTENT\"\n | \"CUSTOMER_SUPPORT\" | \"OTHER_USER_CONTENT\" | \"BROWSING_HISTORY\" | \"SEARCH_HISTORY\"\n | \"USER_ID\" | \"DEVICE_ID\" | \"PURCHASE_HISTORY\" | \"PRODUCT_INTERACTION\" | \"ADVERTISING_DATA\"\n | \"OTHER_USAGE_DATA\" | \"CRASH_DATA\" | \"PERFORMANCE_DATA\" | \"OTHER_DIAGNOSTIC_DATA\"\n | \"OTHER_DATA\" | \"HEALTH\" | \"FITNESS\";\n\nexport type DataUsagePurpose =\n | \"THIRD_PARTY_ADVERTISING\" | \"DEVELOPERS_ADVERTISING\" | \"ANALYTICS\"\n | \"PRODUCT_PERSONALIZATION\" | \"APP_FUNCTIONALITY\" | \"OTHER_PURPOSES\";\n\nexport type DataProtection =\n | \"DATA_USED_TO_TRACK_YOU\" | \"DATA_LINKED_TO_YOU\" | \"DATA_NOT_LINKED_TO_YOU\" | \"DATA_NOT_COLLECTED\";\n\n/** One entry of Fastlane's app_privacy_details.json. */\nexport interface AppPrivacyUsage {\n category?: DataUsageCategory;\n purposes?: DataUsagePurpose[];\n data_protections: DataProtection[];\n}\n\nexport interface UploadAppPrivacyOptions {\n session: AppleSession;\n /** App Store Connect app id (the numeric Apple ID). */\n appId: string;\n usages: AppPrivacyUsage[];\n /** Leave the answers saved but unpublished (Fastlane's skip_publish). */\n skipPublish?: boolean;\n onLog?: (msg: string) => void;\n}\n\nexport interface UploadAppPrivacyResult {\n deleted: number;\n created: number;\n published: boolean;\n}\n\ninterface IrisResponse { status: number; body: any; text: string }\n\nasync function iris(session: AppleSession, method: string, path: string, body?: unknown): Promise<IrisResponse> {\n const res = await fetch(path.startsWith(\"http\") ? path : `${IRIS_BASE}${path}`, {\n method,\n headers: {\n Cookie: cookieHeader(session),\n Accept: \"application/vnd.api+json, application/json\",\n \"Content-Type\": \"application/vnd.api+json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n absorbCookies(session, res);\n const text = await res.text();\n let parsed: any = null;\n try { parsed = text ? JSON.parse(text) : null; } catch { /* not JSON */ }\n return { status: res.status, body: parsed, text };\n}\n\nfunction cookieHeader(session: AppleSession): string {\n return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join(\"; \");\n}\n\nfunction absorbCookies(session: AppleSession, res: Response): void {\n for (const raw of res.headers.getSetCookie?.() ?? []) {\n const m = raw.match(/^([^=]+)=([^;]*)/);\n if (m) session.cookies[m[1]] = m[2];\n }\n}\n\nfunction fail(what: string, r: IrisResponse): never {\n const detail = r.body?.errors?.map((e: any) => e.detail ?? e.title).join(\"; \") || r.text.slice(0, 300);\n throw new Error(`${what} failed (HTTP ${r.status}): ${detail}`);\n}\n\n/**\n * Point the session at the App Store Connect provider (team) that owns the app.\n *\n * An Apple ID in several teams signs in to whichever one it used last, and\n * iris answers 404 for another team's app. Spaceship's `select_team` does the\n * same switch: POST the provider id to the olympus session.\n */\nexport async function selectProviderForApp(session: AppleSession, appId: string, onLog?: (m: string) => void): Promise<void> {\n const owns = async () => (await iris(session, \"GET\", `/apps/${appId}?fields[apps]=name`)).status === 200;\n if (await owns()) return;\n\n const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieHeader(session), Accept: \"application/json\" } });\n absorbCookies(session, res);\n const info: any = await res.json().catch(() => null);\n const providers: Array<{ providerId: number; name: string }> = info?.availableProviders ?? [];\n for (const p of providers) {\n const sw = await fetch(OLYMPUS_SESSION, {\n method: \"POST\",\n headers: { Cookie: cookieHeader(session), \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n body: JSON.stringify({ provider: { providerId: p.providerId } }),\n });\n absorbCookies(session, sw);\n if (sw.ok && (await owns())) {\n onLog?.(`Switched to team \"${p.name}\"`);\n session.teamId = String(p.providerId);\n return;\n }\n }\n throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);\n}\n\n/** Existing privacy answers for an app. */\nexport async function listAppDataUsages(session: AppleSession, appId: string): Promise<Array<{ id: string }>> {\n const all: Array<{ id: string }> = [];\n let next: string | null = `/apps/${appId}/dataUsages?include=category,grouping,purpose,dataProtection&limit=500`;\n while (next) {\n const r = await iris(session, \"GET\", next);\n if (r.status !== 200) fail(\"List app data usages\", r);\n all.push(...(r.body?.data ?? []).map((d: any) => ({ id: d.id })));\n next = r.body?.links?.next ?? null;\n }\n return all;\n}\n\n/** Spaceship AppDataUsage.create — relationships are sent only when given. */\nexport async function createAppDataUsage(\n session: AppleSession,\n input: { appId: string; category?: string | null; purpose?: string | null; dataProtection: string },\n): Promise<string> {\n const relationships: Record<string, unknown> = { app: { data: { type: \"apps\", id: input.appId } } };\n if (input.category) relationships.category = { data: { type: \"appDataUsageCategories\", id: input.category } };\n if (input.purpose) relationships.purpose = { data: { type: \"appDataUsagePurposes\", id: input.purpose } };\n relationships.dataProtection = { data: { type: \"appDataUsageDataProtections\", id: input.dataProtection } };\n const r = await iris(session, \"POST\", \"/appDataUsages\", { data: { type: \"appDataUsages\", relationships } });\n if (r.status !== 201 && r.status !== 200) fail(`Create ${input.category ?? \"\"} ${input.purpose ?? \"\"} ${input.dataProtection}`.replace(/\\s+/g, \" \"), r);\n return r.body?.data?.id;\n}\n\nexport async function deleteAppDataUsage(session: AppleSession, id: string): Promise<void> {\n const r = await iris(session, \"DELETE\", `/appDataUsages/${id}`);\n if (r.status !== 204 && r.status !== 200) fail(`Delete app data usage ${id}`, r);\n}\n\nexport async function getAppDataUsagesPublishState(session: AppleSession, appId: string): Promise<{ id: string; published: boolean }> {\n const r = await iris(session, \"GET\", `/apps/${appId}/dataUsagePublishState`);\n if (r.status !== 200) fail(\"Read privacy publish state\", r);\n return { id: r.body.data.id, published: Boolean(r.body.data.attributes?.published) };\n}\n\nexport async function publishAppDataUsages(session: AppleSession, publishStateId: string): Promise<void> {\n const r = await iris(session, \"PATCH\", `/appDataUsagesPublishState/${publishStateId}`, {\n data: { type: \"appDataUsagesPublishState\", id: publishStateId, attributes: { published: true } },\n });\n if (r.status !== 200) fail(\"Publish privacy details\", r);\n}\n\n/** Fastlane's `upload_app_privacy_details_to_app_store`. Replaces all answers, then publishes. */\nexport async function uploadAppPrivacyDetails(opts: UploadAppPrivacyOptions): Promise<UploadAppPrivacyResult> {\n const { session, appId, usages, skipPublish, onLog = () => {} } = opts;\n validateUsages(usages);\n await selectProviderForApp(session, appId, onLog);\n\n const existing = await listAppDataUsages(session, appId);\n onLog(`Deleting ${existing.length} existing answer${existing.length === 1 ? \"\" : \"s\"}`);\n for (const u of existing) await deleteAppDataUsage(session, u.id);\n\n let created = 0;\n for (const usage of usages) {\n // \"Not collected\" has no purposes but still needs one AppDataUsage, so\n // iterate over [null] — the same trick Fastlane uses.\n const purposes: Array<string | null> = usage.purposes?.length ? usage.purposes : [null];\n for (const purpose of purposes) {\n for (const protection of usage.data_protections) {\n onLog(protection === \"DATA_NOT_COLLECTED\"\n ? `Setting ${protection}`\n : `Setting ${usage.category} and ${purpose} to ${protection}`);\n await createAppDataUsage(session, { appId, category: usage.category ?? null, purpose, dataProtection: protection });\n created++;\n }\n }\n }\n\n if (skipPublish) {\n onLog(\"Skipping publish\");\n return { deleted: existing.length, created, published: false };\n }\n const state = await getAppDataUsagesPublishState(session, appId);\n await publishAppDataUsages(session, state.id);\n onLog(\"Published\");\n return { deleted: existing.length, created, published: true };\n}\n\n/** Catch the answers App Store Connect would refuse, before deleting what is there. */\nexport function validateUsages(usages: AppPrivacyUsage[]): void {\n if (!Array.isArray(usages) || usages.length === 0) throw new Error(\"No privacy usages given.\");\n const notCollected = usages.some((u) => u.data_protections?.includes(\"DATA_NOT_COLLECTED\"));\n if (notCollected && usages.length > 1) {\n throw new Error(\"DATA_NOT_COLLECTED must be the only entry: an app cannot both collect nothing and collect something.\");\n }\n const known = (list: readonly string[], v: string, what: string) => {\n if (!list.includes(v)) throw new Error(`\"${v}\" is not an App Store Connect ${what}. Use one of: ${list.join(\", \")}.`);\n };\n for (const u of usages) {\n if (!u.data_protections?.length) throw new Error(`${u.category ?? \"An entry\"} has no data_protections.`);\n // Checked here, before anything is deleted: App Store Connect refuses an\n // unknown id only when that one answer is created, halfway through.\n if (u.category) known(DATA_USAGE_CATEGORIES, u.category, \"data category\");\n for (const p of u.purposes ?? []) known(DATA_USAGE_PURPOSES, p, \"purpose\");\n for (const d of u.data_protections) known(DATA_PROTECTIONS, d, \"data protection\");\n if (u.data_protections.includes(\"DATA_NOT_COLLECTED\")) continue;\n if (!u.category) throw new Error(\"Every collected entry needs a category.\");\n if (!u.purposes?.length) throw new Error(`${u.category} needs at least one purpose.`);\n const linked = u.data_protections.includes(\"DATA_LINKED_TO_YOU\");\n const unlinked = u.data_protections.includes(\"DATA_NOT_LINKED_TO_YOU\");\n if (linked && unlinked) throw new Error(`${u.category} cannot be both linked and not linked to the user.`);\n }\n}\n","/**\n * App Review rejections: what App Review said, from the Resolution Center.\n * Port of Spaceship's ResolutionCenterThread / ResolutionCenterMessage /\n * ReviewRejection models (connect_api/tunes).\n *\n * Like App Privacy, this is not in the public App Store Connect API — with an\n * API key, /v1/resolutionCenterThreads answers \"resource does not exist\".\n * Spaceship reaches it through its Tunes client, whose host is\n * appstoreconnect.apple.com/iris/, i.e. an Apple ID session. So does this.\n */\n\nimport type { AppleSession } from \"../client/apple-session.js\";\nimport { selectProviderForApp } from \"../privacy/index.js\";\n\nconst IRIS_BASE = \"https://appstoreconnect.apple.com/iris/v1\";\n\nexport interface RejectionReason {\n section?: string;\n description?: string;\n code?: string;\n}\n\nexport interface ResolutionMessage {\n id: string;\n createdDate: string | null;\n from: string | null;\n /** The message as App Review wrote it, HTML reduced to text. */\n body: string;\n reasons: RejectionReason[];\n}\n\nexport interface ResolutionThread {\n id: string;\n threadType: string | null;\n state: string | null;\n createdDate: string | null;\n messages: ResolutionMessage[];\n}\n\nexport interface SubmissionReview {\n submissionId: string;\n state: string;\n submittedDate: string | null;\n threads: ResolutionThread[];\n}\n\nasync function iris(session: AppleSession, path: string): Promise<any> {\n const res = await fetch(`${IRIS_BASE}${path}`, {\n headers: {\n Cookie: Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join(\"; \"),\n Accept: \"application/vnd.api+json, application/json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n },\n });\n for (const raw of res.headers.getSetCookie?.() ?? []) {\n const m = raw.match(/^([^=]+)=([^;]*)/);\n if (m) session.cookies[m[1]] = m[2];\n }\n const text = await res.text();\n let body: any = null;\n try { body = text ? JSON.parse(text) : null; } catch { /* not JSON */ }\n if (!res.ok) {\n const detail = body?.errors?.map((e: any) => e.detail ?? e.title).join(\"; \") || text.slice(0, 300);\n throw new Error(`GET ${path} failed (HTTP ${res.status}): ${detail}`);\n }\n return body;\n}\n\n/** App Review writes HTML; keep paragraphs and list items, drop the markup. */\nexport function htmlToText(html: string | null | undefined): string {\n if (!html) return \"\";\n return html\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<\\/(p|div|li|h\\d)>/gi, \"\\n\")\n .replace(/<li[^>]*>/gi, \"- \")\n .replace(/<[^>]+>/g, \"\")\n .replace(/ /g, \" \").replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\n .replace(/"/g, '\"').replace(/'/g, \"'\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\nfunction reasonsFrom(rejection: any): RejectionReason[] {\n const reasons = rejection?.attributes?.reasons ?? [];\n return (Array.isArray(reasons) ? reasons : []).map((r: any) => ({\n section: r.reasonSection ?? r.section,\n description: r.reasonDescription ?? r.description,\n code: r.reasonCode ?? r.code,\n }));\n}\n\n/** The messages in one thread, each with the guideline reasons App Review attached. */\nexport async function getThreadMessages(session: AppleSession, threadId: string): Promise<ResolutionMessage[]> {\n const body = await iris(session, `/resolutionCenterThreads/${threadId}/resolutionCenterMessages?include=fromActor,rejections`);\n const included = new Map<string, any>((body?.included ?? []).map((i: any) => [`${i.type}:${i.id}`, i]));\n return (body?.data ?? []).map((m: any) => {\n const actor = m.relationships?.fromActor?.data;\n const actorRow = actor ? included.get(`${actor.type}:${actor.id}`) : null;\n const rejections = (m.relationships?.rejections?.data ?? []).map((r: any) => included.get(`${r.type}:${r.id}`)).filter(Boolean);\n return {\n id: m.id,\n createdDate: m.attributes?.createdDate ?? null,\n from: actorRow?.attributes?.actorType ?? actorRow?.attributes?.userFirstName ?? null,\n body: htmlToText(m.attributes?.messageBody),\n reasons: rejections.flatMap(reasonsFrom),\n };\n });\n}\n\n/**\n * Every review submission for the app that App Review has answered, with its\n * Resolution Center threads and messages. `onlyOpen` keeps the ones still\n * waiting on the developer (UNRESOLVED_ISSUES).\n */\nexport async function getReviewFeedback(\n session: AppleSession,\n appId: string,\n opts: { onlyOpen?: boolean; onLog?: (m: string) => void } = {},\n): Promise<SubmissionReview[]> {\n await selectProviderForApp(session, appId, opts.onLog);\n const subs = await iris(session, `/apps/${appId}/reviewSubmissions?limit=10`);\n const out: SubmissionReview[] = [];\n for (const s of subs?.data ?? []) {\n const state = s.attributes?.state ?? \"\";\n if (opts.onlyOpen && state !== \"UNRESOLVED_ISSUES\") continue;\n const threadsBody = await iris(session, `/resolutionCenterThreads?filter[reviewSubmission]=${s.id}&include=reviewSubmission`);\n const threads: ResolutionThread[] = [];\n for (const t of threadsBody?.data ?? []) {\n threads.push({\n id: t.id,\n threadType: t.attributes?.threadType ?? null,\n state: t.attributes?.state ?? null,\n createdDate: t.attributes?.createdDate ?? null,\n messages: await getThreadMessages(session, t.id),\n });\n }\n if (threads.length || !opts.onlyOpen) {\n out.push({ submissionId: s.id, state, submittedDate: s.attributes?.submittedDate ?? null, threads });\n }\n }\n return out;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/client/providers.ts","../../src/privacy/index.ts","../../src/review/index.ts"],"names":["IRIS_BASE","iris"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * App Store Connect \"providers\": the teams an Apple ID belongs to, as App\n * Store Connect sees them. A web session works inside one provider at a time\n * — whichever the person used last — and everything iris creates (API keys,\n * app records) lands in that provider, not the developer-portal team the\n * portal calls were made for. Spaceship's `select_team` is the same switch.\n */\nimport type { AppleSession } from \"./apple-session.js\";\n\nconst OLYMPUS_SESSION = \"https://appstoreconnect.apple.com/olympus/v1/session\";\n\nexport interface AscProvider {\n providerId: number;\n name: string;\n}\n\nexport function cookieHeader(session: AppleSession): string {\n return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join(\"; \");\n}\n\nexport function absorbCookies(session: AppleSession, res: Response): void {\n for (const raw of res.headers.getSetCookie?.() ?? []) {\n const m = raw.match(/^([^=]+)=([^;]*)/);\n if (m) session.cookies[m[1]] = m[2];\n }\n}\n\n/** The provider the session is in now, and every provider it could switch to. */\nexport async function listProviders(session: AppleSession): Promise<{ current: AscProvider | null; providers: AscProvider[] }> {\n const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieHeader(session), Accept: \"application/json\" } });\n absorbCookies(session, res);\n const info: any = await res.json().catch(() => null);\n const pick = (p: any): AscProvider | null =>\n p?.providerId != null ? { providerId: Number(p.providerId), name: String(p.name ?? p.providerId) } : null;\n const providers = ((info?.availableProviders ?? []) as any[]).map(pick).filter((p): p is AscProvider => !!p);\n return { current: pick(info?.provider), providers };\n}\n\n/** Move the session into another provider. True when Apple accepted it. */\nexport async function switchProvider(session: AppleSession, providerId: number): Promise<boolean> {\n const res = await fetch(OLYMPUS_SESSION, {\n method: \"POST\",\n headers: { Cookie: cookieHeader(session), \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n body: JSON.stringify({ provider: { providerId } }),\n });\n absorbCookies(session, res);\n if (res.ok) session.teamId = String(providerId);\n return res.ok;\n}\n","/**\n * App Privacy details (\"nutrition labels\") for an app in App Store Connect.\n * 1:1 port of Fastlane's `upload_app_privacy_details_to_app_store` action and\n * Spaceship's AppDataUsage / AppDataUsagesPublishState models.\n *\n * The public App Store Connect API has no endpoint for privacy details — an\n * API key cannot set them. Fastlane goes through the iris API that App Store\n * Connect's own web UI uses, authenticated with an Apple ID session, and so\n * does this: pass the AppleSession from `loginWithAppleId` / the App Release\n * sign-in.\n *\n * Usages use Fastlane's JSON format, so an existing `app_privacy_details.json`\n * works unchanged:\n *\n * [\n * { \"category\": \"EMAIL_ADDRESS\", \"purposes\": [\"APP_FUNCTIONALITY\"], \"data_protections\": [\"DATA_LINKED_TO_YOU\"] },\n * { \"data_protections\": [\"DATA_NOT_COLLECTED\"] } // an app that collects nothing\n * ]\n *\n * As in Fastlane, the upload REPLACES everything: existing usages are deleted\n * first, then the new ones are created, then the answers are published.\n */\n\nimport type { AppleSession } from \"../client/apple-session.js\";\nimport { absorbCookies, cookieHeader, listProviders, switchProvider } from \"../client/providers.js\";\n\nconst IRIS_BASE = \"https://appstoreconnect.apple.com/iris/v1\";\n\n/** Every category App Store Connect accepts. Anything else is refused before existing answers are deleted. */\nexport const DATA_USAGE_CATEGORIES = [\n \"PAYMENT_INFORMATION\", \"CREDIT_AND_FRAUD\", \"OTHER_FINANCIAL_INFO\",\n \"PRECISE_LOCATION\", \"COARSE_LOCATION\", \"SENSITIVE_INFO\",\n \"PHYSICAL_ADDRESS\", \"EMAIL_ADDRESS\", \"NAME\", \"PHONE_NUMBER\", \"OTHER_CONTACT_INFO\",\n \"CONTACTS\", \"EMAILS_OR_TEXT_MESSAGES\", \"PHOTOS_OR_VIDEOS\", \"AUDIO\", \"GAMEPLAY_CONTENT\",\n \"CUSTOMER_SUPPORT\", \"OTHER_USER_CONTENT\", \"BROWSING_HISTORY\", \"SEARCH_HISTORY\",\n \"USER_ID\", \"DEVICE_ID\", \"PURCHASE_HISTORY\", \"PRODUCT_INTERACTION\", \"ADVERTISING_DATA\",\n \"OTHER_USAGE_DATA\", \"CRASH_DATA\", \"PERFORMANCE_DATA\", \"OTHER_DIAGNOSTIC_DATA\",\n \"OTHER_DATA\", \"HEALTH\", \"FITNESS\",\n] as const;\nexport const DATA_USAGE_PURPOSES = [\n \"THIRD_PARTY_ADVERTISING\", \"DEVELOPERS_ADVERTISING\", \"ANALYTICS\",\n \"PRODUCT_PERSONALIZATION\", \"APP_FUNCTIONALITY\", \"OTHER_PURPOSES\",\n] as const;\nexport const DATA_PROTECTIONS = [\n \"DATA_USED_TO_TRACK_YOU\", \"DATA_LINKED_TO_YOU\", \"DATA_NOT_LINKED_TO_YOU\", \"DATA_NOT_COLLECTED\",\n] as const;\n\nexport type DataUsageCategory =\n | \"PAYMENT_INFORMATION\" | \"CREDIT_AND_FRAUD\" | \"OTHER_FINANCIAL_INFO\"\n | \"PRECISE_LOCATION\" | \"COARSE_LOCATION\" | \"SENSITIVE_INFO\"\n | \"PHYSICAL_ADDRESS\" | \"EMAIL_ADDRESS\" | \"NAME\" | \"PHONE_NUMBER\" | \"OTHER_CONTACT_INFO\"\n | \"CONTACTS\" | \"EMAILS_OR_TEXT_MESSAGES\" | \"PHOTOS_OR_VIDEOS\" | \"AUDIO\" | \"GAMEPLAY_CONTENT\"\n | \"CUSTOMER_SUPPORT\" | \"OTHER_USER_CONTENT\" | \"BROWSING_HISTORY\" | \"SEARCH_HISTORY\"\n | \"USER_ID\" | \"DEVICE_ID\" | \"PURCHASE_HISTORY\" | \"PRODUCT_INTERACTION\" | \"ADVERTISING_DATA\"\n | \"OTHER_USAGE_DATA\" | \"CRASH_DATA\" | \"PERFORMANCE_DATA\" | \"OTHER_DIAGNOSTIC_DATA\"\n | \"OTHER_DATA\" | \"HEALTH\" | \"FITNESS\";\n\nexport type DataUsagePurpose =\n | \"THIRD_PARTY_ADVERTISING\" | \"DEVELOPERS_ADVERTISING\" | \"ANALYTICS\"\n | \"PRODUCT_PERSONALIZATION\" | \"APP_FUNCTIONALITY\" | \"OTHER_PURPOSES\";\n\nexport type DataProtection =\n | \"DATA_USED_TO_TRACK_YOU\" | \"DATA_LINKED_TO_YOU\" | \"DATA_NOT_LINKED_TO_YOU\" | \"DATA_NOT_COLLECTED\";\n\n/** One entry of Fastlane's app_privacy_details.json. */\nexport interface AppPrivacyUsage {\n category?: DataUsageCategory;\n purposes?: DataUsagePurpose[];\n data_protections: DataProtection[];\n}\n\nexport interface UploadAppPrivacyOptions {\n session: AppleSession;\n /** App Store Connect app id (the numeric Apple ID). */\n appId: string;\n usages: AppPrivacyUsage[];\n /** Leave the answers saved but unpublished (Fastlane's skip_publish). */\n skipPublish?: boolean;\n onLog?: (msg: string) => void;\n}\n\nexport interface UploadAppPrivacyResult {\n deleted: number;\n created: number;\n published: boolean;\n}\n\ninterface IrisResponse { status: number; body: any; text: string }\n\nasync function iris(session: AppleSession, method: string, path: string, body?: unknown): Promise<IrisResponse> {\n const res = await fetch(path.startsWith(\"http\") ? path : `${IRIS_BASE}${path}`, {\n method,\n headers: {\n Cookie: cookieHeader(session),\n Accept: \"application/vnd.api+json, application/json\",\n \"Content-Type\": \"application/vnd.api+json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n absorbCookies(session, res);\n const text = await res.text();\n let parsed: any = null;\n try { parsed = text ? JSON.parse(text) : null; } catch { /* not JSON */ }\n return { status: res.status, body: parsed, text };\n}\n\n\nfunction fail(what: string, r: IrisResponse): never {\n const detail = r.body?.errors?.map((e: any) => e.detail ?? e.title).join(\"; \") || r.text.slice(0, 300);\n throw new Error(`${what} failed (HTTP ${r.status}): ${detail}`);\n}\n\n/**\n * Point the session at the App Store Connect provider (team) that owns the app.\n *\n * An Apple ID in several teams signs in to whichever one it used last, and\n * iris answers 404 for another team's app. Spaceship's `select_team` does the\n * same switch: POST the provider id to the olympus session.\n */\nexport async function selectProviderForApp(session: AppleSession, appId: string, onLog?: (m: string) => void): Promise<void> {\n const owns = async () => (await iris(session, \"GET\", `/apps/${appId}?fields[apps]=name`)).status === 200;\n if (await owns()) return;\n\n const { providers } = await listProviders(session);\n for (const p of providers) {\n if ((await switchProvider(session, p.providerId)) && (await owns())) {\n onLog?.(`Switched to team \"${p.name}\"`);\n return;\n }\n }\n throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);\n}\n\n/** Existing privacy answers for an app. */\nexport async function listAppDataUsages(session: AppleSession, appId: string): Promise<Array<{ id: string }>> {\n const all: Array<{ id: string }> = [];\n let next: string | null = `/apps/${appId}/dataUsages?include=category,grouping,purpose,dataProtection&limit=500`;\n while (next) {\n const r = await iris(session, \"GET\", next);\n if (r.status !== 200) fail(\"List app data usages\", r);\n all.push(...(r.body?.data ?? []).map((d: any) => ({ id: d.id })));\n next = r.body?.links?.next ?? null;\n }\n return all;\n}\n\n/** Spaceship AppDataUsage.create — relationships are sent only when given. */\nexport async function createAppDataUsage(\n session: AppleSession,\n input: { appId: string; category?: string | null; purpose?: string | null; dataProtection: string },\n): Promise<string> {\n const relationships: Record<string, unknown> = { app: { data: { type: \"apps\", id: input.appId } } };\n if (input.category) relationships.category = { data: { type: \"appDataUsageCategories\", id: input.category } };\n if (input.purpose) relationships.purpose = { data: { type: \"appDataUsagePurposes\", id: input.purpose } };\n relationships.dataProtection = { data: { type: \"appDataUsageDataProtections\", id: input.dataProtection } };\n const r = await iris(session, \"POST\", \"/appDataUsages\", { data: { type: \"appDataUsages\", relationships } });\n if (r.status !== 201 && r.status !== 200) fail(`Create ${input.category ?? \"\"} ${input.purpose ?? \"\"} ${input.dataProtection}`.replace(/\\s+/g, \" \"), r);\n return r.body?.data?.id;\n}\n\nexport async function deleteAppDataUsage(session: AppleSession, id: string): Promise<void> {\n const r = await iris(session, \"DELETE\", `/appDataUsages/${id}`);\n if (r.status !== 204 && r.status !== 200) fail(`Delete app data usage ${id}`, r);\n}\n\nexport async function getAppDataUsagesPublishState(session: AppleSession, appId: string): Promise<{ id: string; published: boolean }> {\n const r = await iris(session, \"GET\", `/apps/${appId}/dataUsagePublishState`);\n if (r.status !== 200) fail(\"Read privacy publish state\", r);\n return { id: r.body.data.id, published: Boolean(r.body.data.attributes?.published) };\n}\n\nexport async function publishAppDataUsages(session: AppleSession, publishStateId: string): Promise<void> {\n const r = await iris(session, \"PATCH\", `/appDataUsagesPublishState/${publishStateId}`, {\n data: { type: \"appDataUsagesPublishState\", id: publishStateId, attributes: { published: true } },\n });\n if (r.status !== 200) fail(\"Publish privacy details\", r);\n}\n\n/** Fastlane's `upload_app_privacy_details_to_app_store`. Replaces all answers, then publishes. */\nexport async function uploadAppPrivacyDetails(opts: UploadAppPrivacyOptions): Promise<UploadAppPrivacyResult> {\n const { session, appId, usages, skipPublish, onLog = () => {} } = opts;\n validateUsages(usages);\n await selectProviderForApp(session, appId, onLog);\n\n const existing = await listAppDataUsages(session, appId);\n onLog(`Deleting ${existing.length} existing answer${existing.length === 1 ? \"\" : \"s\"}`);\n for (const u of existing) await deleteAppDataUsage(session, u.id);\n\n let created = 0;\n for (const usage of usages) {\n // \"Not collected\" has no purposes but still needs one AppDataUsage, so\n // iterate over [null] — the same trick Fastlane uses.\n const purposes: Array<string | null> = usage.purposes?.length ? usage.purposes : [null];\n for (const purpose of purposes) {\n for (const protection of usage.data_protections) {\n onLog(protection === \"DATA_NOT_COLLECTED\"\n ? `Setting ${protection}`\n : `Setting ${usage.category} and ${purpose} to ${protection}`);\n await createAppDataUsage(session, { appId, category: usage.category ?? null, purpose, dataProtection: protection });\n created++;\n }\n }\n }\n\n if (skipPublish) {\n onLog(\"Skipping publish\");\n return { deleted: existing.length, created, published: false };\n }\n const state = await getAppDataUsagesPublishState(session, appId);\n await publishAppDataUsages(session, state.id);\n onLog(\"Published\");\n return { deleted: existing.length, created, published: true };\n}\n\n/** Catch the answers App Store Connect would refuse, before deleting what is there. */\nexport function validateUsages(usages: AppPrivacyUsage[]): void {\n if (!Array.isArray(usages) || usages.length === 0) throw new Error(\"No privacy usages given.\");\n const notCollected = usages.some((u) => u.data_protections?.includes(\"DATA_NOT_COLLECTED\"));\n if (notCollected && usages.length > 1) {\n throw new Error(\"DATA_NOT_COLLECTED must be the only entry: an app cannot both collect nothing and collect something.\");\n }\n const known = (list: readonly string[], v: string, what: string) => {\n if (!list.includes(v)) throw new Error(`\"${v}\" is not an App Store Connect ${what}. Use one of: ${list.join(\", \")}.`);\n };\n for (const u of usages) {\n if (!u.data_protections?.length) throw new Error(`${u.category ?? \"An entry\"} has no data_protections.`);\n // Checked here, before anything is deleted: App Store Connect refuses an\n // unknown id only when that one answer is created, halfway through.\n if (u.category) known(DATA_USAGE_CATEGORIES, u.category, \"data category\");\n for (const p of u.purposes ?? []) known(DATA_USAGE_PURPOSES, p, \"purpose\");\n for (const d of u.data_protections) known(DATA_PROTECTIONS, d, \"data protection\");\n if (u.data_protections.includes(\"DATA_NOT_COLLECTED\")) continue;\n if (!u.category) throw new Error(\"Every collected entry needs a category.\");\n if (!u.purposes?.length) throw new Error(`${u.category} needs at least one purpose.`);\n const linked = u.data_protections.includes(\"DATA_LINKED_TO_YOU\");\n const unlinked = u.data_protections.includes(\"DATA_NOT_LINKED_TO_YOU\");\n if (linked && unlinked) throw new Error(`${u.category} cannot be both linked and not linked to the user.`);\n }\n}\n","/**\n * App Review rejections: what App Review said, from the Resolution Center.\n * Port of Spaceship's ResolutionCenterThread / ResolutionCenterMessage /\n * ReviewRejection models (connect_api/tunes).\n *\n * Like App Privacy, this is not in the public App Store Connect API — with an\n * API key, /v1/resolutionCenterThreads answers \"resource does not exist\".\n * Spaceship reaches it through its Tunes client, whose host is\n * appstoreconnect.apple.com/iris/, i.e. an Apple ID session. So does this.\n */\n\nimport type { AppleSession } from \"../client/apple-session.js\";\nimport { selectProviderForApp } from \"../privacy/index.js\";\n\nconst IRIS_BASE = \"https://appstoreconnect.apple.com/iris/v1\";\n\nexport interface RejectionReason {\n section?: string;\n description?: string;\n code?: string;\n}\n\nexport interface ResolutionMessage {\n id: string;\n createdDate: string | null;\n from: string | null;\n /** The message as App Review wrote it, HTML reduced to text. */\n body: string;\n reasons: RejectionReason[];\n}\n\nexport interface ResolutionThread {\n id: string;\n threadType: string | null;\n state: string | null;\n createdDate: string | null;\n messages: ResolutionMessage[];\n}\n\nexport interface SubmissionReview {\n submissionId: string;\n state: string;\n submittedDate: string | null;\n threads: ResolutionThread[];\n}\n\nasync function iris(session: AppleSession, path: string): Promise<any> {\n const res = await fetch(`${IRIS_BASE}${path}`, {\n headers: {\n Cookie: Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join(\"; \"),\n Accept: \"application/vnd.api+json, application/json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n },\n });\n for (const raw of res.headers.getSetCookie?.() ?? []) {\n const m = raw.match(/^([^=]+)=([^;]*)/);\n if (m) session.cookies[m[1]] = m[2];\n }\n const text = await res.text();\n let body: any = null;\n try { body = text ? JSON.parse(text) : null; } catch { /* not JSON */ }\n if (!res.ok) {\n const detail = body?.errors?.map((e: any) => e.detail ?? e.title).join(\"; \") || text.slice(0, 300);\n throw new Error(`GET ${path} failed (HTTP ${res.status}): ${detail}`);\n }\n return body;\n}\n\n/** App Review writes HTML; keep paragraphs and list items, drop the markup. */\nexport function htmlToText(html: string | null | undefined): string {\n if (!html) return \"\";\n return html\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<\\/(p|div|li|h\\d)>/gi, \"\\n\")\n .replace(/<li[^>]*>/gi, \"- \")\n .replace(/<[^>]+>/g, \"\")\n .replace(/ /g, \" \").replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\n .replace(/"/g, '\"').replace(/'/g, \"'\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\nfunction reasonsFrom(rejection: any): RejectionReason[] {\n const reasons = rejection?.attributes?.reasons ?? [];\n return (Array.isArray(reasons) ? reasons : []).map((r: any) => ({\n section: r.reasonSection ?? r.section,\n description: r.reasonDescription ?? r.description,\n code: r.reasonCode ?? r.code,\n }));\n}\n\n/** The messages in one thread, each with the guideline reasons App Review attached. */\nexport async function getThreadMessages(session: AppleSession, threadId: string): Promise<ResolutionMessage[]> {\n const body = await iris(session, `/resolutionCenterThreads/${threadId}/resolutionCenterMessages?include=fromActor,rejections`);\n const included = new Map<string, any>((body?.included ?? []).map((i: any) => [`${i.type}:${i.id}`, i]));\n return (body?.data ?? []).map((m: any) => {\n const actor = m.relationships?.fromActor?.data;\n const actorRow = actor ? included.get(`${actor.type}:${actor.id}`) : null;\n const rejections = (m.relationships?.rejections?.data ?? []).map((r: any) => included.get(`${r.type}:${r.id}`)).filter(Boolean);\n return {\n id: m.id,\n createdDate: m.attributes?.createdDate ?? null,\n from: actorRow?.attributes?.actorType ?? actorRow?.attributes?.userFirstName ?? null,\n body: htmlToText(m.attributes?.messageBody),\n reasons: rejections.flatMap(reasonsFrom),\n };\n });\n}\n\n/**\n * Every review submission for the app that App Review has answered, with its\n * Resolution Center threads and messages. `onlyOpen` keeps the ones still\n * waiting on the developer (UNRESOLVED_ISSUES).\n */\nexport async function getReviewFeedback(\n session: AppleSession,\n appId: string,\n opts: { onlyOpen?: boolean; onLog?: (m: string) => void } = {},\n): Promise<SubmissionReview[]> {\n await selectProviderForApp(session, appId, opts.onLog);\n const subs = await iris(session, `/apps/${appId}/reviewSubmissions?limit=10`);\n const out: SubmissionReview[] = [];\n for (const s of subs?.data ?? []) {\n const state = s.attributes?.state ?? \"\";\n if (opts.onlyOpen && state !== \"UNRESOLVED_ISSUES\") continue;\n const threadsBody = await iris(session, `/resolutionCenterThreads?filter[reviewSubmission]=${s.id}&include=reviewSubmission`);\n const threads: ResolutionThread[] = [];\n for (const t of threadsBody?.data ?? []) {\n threads.push({\n id: t.id,\n threadType: t.attributes?.threadType ?? null,\n state: t.attributes?.state ?? null,\n createdDate: t.attributes?.createdDate ?? null,\n messages: await getThreadMessages(session, t.id),\n });\n }\n if (threads.length || !opts.onlyOpen) {\n out.push({ submissionId: s.id, state, submittedDate: s.attributes?.submittedDate ?? null, threads });\n }\n }\n return out;\n}\n"]}
|
package/dist/review/index.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import '../apple-session-
|
|
2
|
-
export { R as RejectionReason, a as ResolutionMessage, b as ResolutionThread, S as SubmissionReview,
|
|
1
|
+
import '../apple-session-D6x7Jo3D.cjs';
|
|
2
|
+
export { R as RejectionReason, a as ResolutionMessage, b as ResolutionThread, S as SubmissionReview, g as getReviewFeedback, c as getThreadMessages, h as htmlToText } from '../index-yro2MWgt.cjs';
|
package/dist/review/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import '../apple-session-
|
|
2
|
-
export { R as RejectionReason, a as ResolutionMessage, b as ResolutionThread, S as SubmissionReview,
|
|
1
|
+
import '../apple-session-D6x7Jo3D.js';
|
|
2
|
+
export { R as RejectionReason, a as ResolutionMessage, b as ResolutionThread, S as SubmissionReview, g as getReviewFeedback, c as getThreadMessages, h as htmlToText } from '../index-Cq-xo-9x.js';
|
package/dist/review/index.mjs
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
|
+
// src/client/providers.ts
|
|
2
|
+
var OLYMPUS_SESSION = "https://appstoreconnect.apple.com/olympus/v1/session";
|
|
3
|
+
function cookieHeader(session) {
|
|
4
|
+
return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
|
|
5
|
+
}
|
|
6
|
+
function absorbCookies(session, res) {
|
|
7
|
+
for (const raw of res.headers.getSetCookie?.() ?? []) {
|
|
8
|
+
const m = raw.match(/^([^=]+)=([^;]*)/);
|
|
9
|
+
if (m) session.cookies[m[1]] = m[2];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
async function listProviders(session) {
|
|
13
|
+
const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieHeader(session), Accept: "application/json" } });
|
|
14
|
+
absorbCookies(session, res);
|
|
15
|
+
const info = await res.json().catch(() => null);
|
|
16
|
+
const pick = (p) => p?.providerId != null ? { providerId: Number(p.providerId), name: String(p.name ?? p.providerId) } : null;
|
|
17
|
+
const providers = (info?.availableProviders ?? []).map(pick).filter((p) => !!p);
|
|
18
|
+
return { current: pick(info?.provider), providers };
|
|
19
|
+
}
|
|
20
|
+
async function switchProvider(session, providerId) {
|
|
21
|
+
const res = await fetch(OLYMPUS_SESSION, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: { Cookie: cookieHeader(session), "Content-Type": "application/json", Accept: "application/json" },
|
|
24
|
+
body: JSON.stringify({ provider: { providerId } })
|
|
25
|
+
});
|
|
26
|
+
absorbCookies(session, res);
|
|
27
|
+
if (res.ok) session.teamId = String(providerId);
|
|
28
|
+
return res.ok;
|
|
29
|
+
}
|
|
30
|
+
|
|
1
31
|
// src/privacy/index.ts
|
|
2
32
|
var IRIS_BASE = "https://appstoreconnect.apple.com/iris/v1";
|
|
3
|
-
var OLYMPUS_SESSION = "https://appstoreconnect.apple.com/olympus/v1/session";
|
|
4
33
|
async function iris(session, method, path, body) {
|
|
5
34
|
const res = await fetch(path.startsWith("http") ? path : `${IRIS_BASE}${path}`, {
|
|
6
35
|
method,
|
|
@@ -21,32 +50,13 @@ async function iris(session, method, path, body) {
|
|
|
21
50
|
}
|
|
22
51
|
return { status: res.status, body: parsed, text };
|
|
23
52
|
}
|
|
24
|
-
function cookieHeader(session) {
|
|
25
|
-
return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join("; ");
|
|
26
|
-
}
|
|
27
|
-
function absorbCookies(session, res) {
|
|
28
|
-
for (const raw of res.headers.getSetCookie?.() ?? []) {
|
|
29
|
-
const m = raw.match(/^([^=]+)=([^;]*)/);
|
|
30
|
-
if (m) session.cookies[m[1]] = m[2];
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
53
|
async function selectProviderForApp(session, appId, onLog) {
|
|
34
54
|
const owns = async () => (await iris(session, "GET", `/apps/${appId}?fields[apps]=name`)).status === 200;
|
|
35
55
|
if (await owns()) return;
|
|
36
|
-
const
|
|
37
|
-
absorbCookies(session, res);
|
|
38
|
-
const info = await res.json().catch(() => null);
|
|
39
|
-
const providers = info?.availableProviders ?? [];
|
|
56
|
+
const { providers } = await listProviders(session);
|
|
40
57
|
for (const p of providers) {
|
|
41
|
-
|
|
42
|
-
method: "POST",
|
|
43
|
-
headers: { Cookie: cookieHeader(session), "Content-Type": "application/json", Accept: "application/json" },
|
|
44
|
-
body: JSON.stringify({ provider: { providerId: p.providerId } })
|
|
45
|
-
});
|
|
46
|
-
absorbCookies(session, sw);
|
|
47
|
-
if (sw.ok && await owns()) {
|
|
58
|
+
if (await switchProvider(session, p.providerId) && await owns()) {
|
|
48
59
|
onLog?.(`Switched to team "${p.name}"`);
|
|
49
|
-
session.teamId = String(p.providerId);
|
|
50
60
|
return;
|
|
51
61
|
}
|
|
52
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/privacy/index.ts","../../src/review/index.ts"],"names":["IRIS_BASE","iris"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * App Privacy details (\"nutrition labels\") for an app in App Store Connect.\n * 1:1 port of Fastlane's `upload_app_privacy_details_to_app_store` action and\n * Spaceship's AppDataUsage / AppDataUsagesPublishState models.\n *\n * The public App Store Connect API has no endpoint for privacy details — an\n * API key cannot set them. Fastlane goes through the iris API that App Store\n * Connect's own web UI uses, authenticated with an Apple ID session, and so\n * does this: pass the AppleSession from `loginWithAppleId` / the App Release\n * sign-in.\n *\n * Usages use Fastlane's JSON format, so an existing `app_privacy_details.json`\n * works unchanged:\n *\n * [\n * { \"category\": \"EMAIL_ADDRESS\", \"purposes\": [\"APP_FUNCTIONALITY\"], \"data_protections\": [\"DATA_LINKED_TO_YOU\"] },\n * { \"data_protections\": [\"DATA_NOT_COLLECTED\"] } // an app that collects nothing\n * ]\n *\n * As in Fastlane, the upload REPLACES everything: existing usages are deleted\n * first, then the new ones are created, then the answers are published.\n */\n\nimport type { AppleSession } from \"../client/apple-session.js\";\n\nconst IRIS_BASE = \"https://appstoreconnect.apple.com/iris/v1\";\nconst OLYMPUS_SESSION = \"https://appstoreconnect.apple.com/olympus/v1/session\";\n\n/** Every category App Store Connect accepts. Anything else is refused before existing answers are deleted. */\nexport const DATA_USAGE_CATEGORIES = [\n \"PAYMENT_INFORMATION\", \"CREDIT_AND_FRAUD\", \"OTHER_FINANCIAL_INFO\",\n \"PRECISE_LOCATION\", \"COARSE_LOCATION\", \"SENSITIVE_INFO\",\n \"PHYSICAL_ADDRESS\", \"EMAIL_ADDRESS\", \"NAME\", \"PHONE_NUMBER\", \"OTHER_CONTACT_INFO\",\n \"CONTACTS\", \"EMAILS_OR_TEXT_MESSAGES\", \"PHOTOS_OR_VIDEOS\", \"AUDIO\", \"GAMEPLAY_CONTENT\",\n \"CUSTOMER_SUPPORT\", \"OTHER_USER_CONTENT\", \"BROWSING_HISTORY\", \"SEARCH_HISTORY\",\n \"USER_ID\", \"DEVICE_ID\", \"PURCHASE_HISTORY\", \"PRODUCT_INTERACTION\", \"ADVERTISING_DATA\",\n \"OTHER_USAGE_DATA\", \"CRASH_DATA\", \"PERFORMANCE_DATA\", \"OTHER_DIAGNOSTIC_DATA\",\n \"OTHER_DATA\", \"HEALTH\", \"FITNESS\",\n] as const;\nexport const DATA_USAGE_PURPOSES = [\n \"THIRD_PARTY_ADVERTISING\", \"DEVELOPERS_ADVERTISING\", \"ANALYTICS\",\n \"PRODUCT_PERSONALIZATION\", \"APP_FUNCTIONALITY\", \"OTHER_PURPOSES\",\n] as const;\nexport const DATA_PROTECTIONS = [\n \"DATA_USED_TO_TRACK_YOU\", \"DATA_LINKED_TO_YOU\", \"DATA_NOT_LINKED_TO_YOU\", \"DATA_NOT_COLLECTED\",\n] as const;\n\nexport type DataUsageCategory =\n | \"PAYMENT_INFORMATION\" | \"CREDIT_AND_FRAUD\" | \"OTHER_FINANCIAL_INFO\"\n | \"PRECISE_LOCATION\" | \"COARSE_LOCATION\" | \"SENSITIVE_INFO\"\n | \"PHYSICAL_ADDRESS\" | \"EMAIL_ADDRESS\" | \"NAME\" | \"PHONE_NUMBER\" | \"OTHER_CONTACT_INFO\"\n | \"CONTACTS\" | \"EMAILS_OR_TEXT_MESSAGES\" | \"PHOTOS_OR_VIDEOS\" | \"AUDIO\" | \"GAMEPLAY_CONTENT\"\n | \"CUSTOMER_SUPPORT\" | \"OTHER_USER_CONTENT\" | \"BROWSING_HISTORY\" | \"SEARCH_HISTORY\"\n | \"USER_ID\" | \"DEVICE_ID\" | \"PURCHASE_HISTORY\" | \"PRODUCT_INTERACTION\" | \"ADVERTISING_DATA\"\n | \"OTHER_USAGE_DATA\" | \"CRASH_DATA\" | \"PERFORMANCE_DATA\" | \"OTHER_DIAGNOSTIC_DATA\"\n | \"OTHER_DATA\" | \"HEALTH\" | \"FITNESS\";\n\nexport type DataUsagePurpose =\n | \"THIRD_PARTY_ADVERTISING\" | \"DEVELOPERS_ADVERTISING\" | \"ANALYTICS\"\n | \"PRODUCT_PERSONALIZATION\" | \"APP_FUNCTIONALITY\" | \"OTHER_PURPOSES\";\n\nexport type DataProtection =\n | \"DATA_USED_TO_TRACK_YOU\" | \"DATA_LINKED_TO_YOU\" | \"DATA_NOT_LINKED_TO_YOU\" | \"DATA_NOT_COLLECTED\";\n\n/** One entry of Fastlane's app_privacy_details.json. */\nexport interface AppPrivacyUsage {\n category?: DataUsageCategory;\n purposes?: DataUsagePurpose[];\n data_protections: DataProtection[];\n}\n\nexport interface UploadAppPrivacyOptions {\n session: AppleSession;\n /** App Store Connect app id (the numeric Apple ID). */\n appId: string;\n usages: AppPrivacyUsage[];\n /** Leave the answers saved but unpublished (Fastlane's skip_publish). */\n skipPublish?: boolean;\n onLog?: (msg: string) => void;\n}\n\nexport interface UploadAppPrivacyResult {\n deleted: number;\n created: number;\n published: boolean;\n}\n\ninterface IrisResponse { status: number; body: any; text: string }\n\nasync function iris(session: AppleSession, method: string, path: string, body?: unknown): Promise<IrisResponse> {\n const res = await fetch(path.startsWith(\"http\") ? path : `${IRIS_BASE}${path}`, {\n method,\n headers: {\n Cookie: cookieHeader(session),\n Accept: \"application/vnd.api+json, application/json\",\n \"Content-Type\": \"application/vnd.api+json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n absorbCookies(session, res);\n const text = await res.text();\n let parsed: any = null;\n try { parsed = text ? JSON.parse(text) : null; } catch { /* not JSON */ }\n return { status: res.status, body: parsed, text };\n}\n\nfunction cookieHeader(session: AppleSession): string {\n return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join(\"; \");\n}\n\nfunction absorbCookies(session: AppleSession, res: Response): void {\n for (const raw of res.headers.getSetCookie?.() ?? []) {\n const m = raw.match(/^([^=]+)=([^;]*)/);\n if (m) session.cookies[m[1]] = m[2];\n }\n}\n\nfunction fail(what: string, r: IrisResponse): never {\n const detail = r.body?.errors?.map((e: any) => e.detail ?? e.title).join(\"; \") || r.text.slice(0, 300);\n throw new Error(`${what} failed (HTTP ${r.status}): ${detail}`);\n}\n\n/**\n * Point the session at the App Store Connect provider (team) that owns the app.\n *\n * An Apple ID in several teams signs in to whichever one it used last, and\n * iris answers 404 for another team's app. Spaceship's `select_team` does the\n * same switch: POST the provider id to the olympus session.\n */\nexport async function selectProviderForApp(session: AppleSession, appId: string, onLog?: (m: string) => void): Promise<void> {\n const owns = async () => (await iris(session, \"GET\", `/apps/${appId}?fields[apps]=name`)).status === 200;\n if (await owns()) return;\n\n const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieHeader(session), Accept: \"application/json\" } });\n absorbCookies(session, res);\n const info: any = await res.json().catch(() => null);\n const providers: Array<{ providerId: number; name: string }> = info?.availableProviders ?? [];\n for (const p of providers) {\n const sw = await fetch(OLYMPUS_SESSION, {\n method: \"POST\",\n headers: { Cookie: cookieHeader(session), \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n body: JSON.stringify({ provider: { providerId: p.providerId } }),\n });\n absorbCookies(session, sw);\n if (sw.ok && (await owns())) {\n onLog?.(`Switched to team \"${p.name}\"`);\n session.teamId = String(p.providerId);\n return;\n }\n }\n throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);\n}\n\n/** Existing privacy answers for an app. */\nexport async function listAppDataUsages(session: AppleSession, appId: string): Promise<Array<{ id: string }>> {\n const all: Array<{ id: string }> = [];\n let next: string | null = `/apps/${appId}/dataUsages?include=category,grouping,purpose,dataProtection&limit=500`;\n while (next) {\n const r = await iris(session, \"GET\", next);\n if (r.status !== 200) fail(\"List app data usages\", r);\n all.push(...(r.body?.data ?? []).map((d: any) => ({ id: d.id })));\n next = r.body?.links?.next ?? null;\n }\n return all;\n}\n\n/** Spaceship AppDataUsage.create — relationships are sent only when given. */\nexport async function createAppDataUsage(\n session: AppleSession,\n input: { appId: string; category?: string | null; purpose?: string | null; dataProtection: string },\n): Promise<string> {\n const relationships: Record<string, unknown> = { app: { data: { type: \"apps\", id: input.appId } } };\n if (input.category) relationships.category = { data: { type: \"appDataUsageCategories\", id: input.category } };\n if (input.purpose) relationships.purpose = { data: { type: \"appDataUsagePurposes\", id: input.purpose } };\n relationships.dataProtection = { data: { type: \"appDataUsageDataProtections\", id: input.dataProtection } };\n const r = await iris(session, \"POST\", \"/appDataUsages\", { data: { type: \"appDataUsages\", relationships } });\n if (r.status !== 201 && r.status !== 200) fail(`Create ${input.category ?? \"\"} ${input.purpose ?? \"\"} ${input.dataProtection}`.replace(/\\s+/g, \" \"), r);\n return r.body?.data?.id;\n}\n\nexport async function deleteAppDataUsage(session: AppleSession, id: string): Promise<void> {\n const r = await iris(session, \"DELETE\", `/appDataUsages/${id}`);\n if (r.status !== 204 && r.status !== 200) fail(`Delete app data usage ${id}`, r);\n}\n\nexport async function getAppDataUsagesPublishState(session: AppleSession, appId: string): Promise<{ id: string; published: boolean }> {\n const r = await iris(session, \"GET\", `/apps/${appId}/dataUsagePublishState`);\n if (r.status !== 200) fail(\"Read privacy publish state\", r);\n return { id: r.body.data.id, published: Boolean(r.body.data.attributes?.published) };\n}\n\nexport async function publishAppDataUsages(session: AppleSession, publishStateId: string): Promise<void> {\n const r = await iris(session, \"PATCH\", `/appDataUsagesPublishState/${publishStateId}`, {\n data: { type: \"appDataUsagesPublishState\", id: publishStateId, attributes: { published: true } },\n });\n if (r.status !== 200) fail(\"Publish privacy details\", r);\n}\n\n/** Fastlane's `upload_app_privacy_details_to_app_store`. Replaces all answers, then publishes. */\nexport async function uploadAppPrivacyDetails(opts: UploadAppPrivacyOptions): Promise<UploadAppPrivacyResult> {\n const { session, appId, usages, skipPublish, onLog = () => {} } = opts;\n validateUsages(usages);\n await selectProviderForApp(session, appId, onLog);\n\n const existing = await listAppDataUsages(session, appId);\n onLog(`Deleting ${existing.length} existing answer${existing.length === 1 ? \"\" : \"s\"}`);\n for (const u of existing) await deleteAppDataUsage(session, u.id);\n\n let created = 0;\n for (const usage of usages) {\n // \"Not collected\" has no purposes but still needs one AppDataUsage, so\n // iterate over [null] — the same trick Fastlane uses.\n const purposes: Array<string | null> = usage.purposes?.length ? usage.purposes : [null];\n for (const purpose of purposes) {\n for (const protection of usage.data_protections) {\n onLog(protection === \"DATA_NOT_COLLECTED\"\n ? `Setting ${protection}`\n : `Setting ${usage.category} and ${purpose} to ${protection}`);\n await createAppDataUsage(session, { appId, category: usage.category ?? null, purpose, dataProtection: protection });\n created++;\n }\n }\n }\n\n if (skipPublish) {\n onLog(\"Skipping publish\");\n return { deleted: existing.length, created, published: false };\n }\n const state = await getAppDataUsagesPublishState(session, appId);\n await publishAppDataUsages(session, state.id);\n onLog(\"Published\");\n return { deleted: existing.length, created, published: true };\n}\n\n/** Catch the answers App Store Connect would refuse, before deleting what is there. */\nexport function validateUsages(usages: AppPrivacyUsage[]): void {\n if (!Array.isArray(usages) || usages.length === 0) throw new Error(\"No privacy usages given.\");\n const notCollected = usages.some((u) => u.data_protections?.includes(\"DATA_NOT_COLLECTED\"));\n if (notCollected && usages.length > 1) {\n throw new Error(\"DATA_NOT_COLLECTED must be the only entry: an app cannot both collect nothing and collect something.\");\n }\n const known = (list: readonly string[], v: string, what: string) => {\n if (!list.includes(v)) throw new Error(`\"${v}\" is not an App Store Connect ${what}. Use one of: ${list.join(\", \")}.`);\n };\n for (const u of usages) {\n if (!u.data_protections?.length) throw new Error(`${u.category ?? \"An entry\"} has no data_protections.`);\n // Checked here, before anything is deleted: App Store Connect refuses an\n // unknown id only when that one answer is created, halfway through.\n if (u.category) known(DATA_USAGE_CATEGORIES, u.category, \"data category\");\n for (const p of u.purposes ?? []) known(DATA_USAGE_PURPOSES, p, \"purpose\");\n for (const d of u.data_protections) known(DATA_PROTECTIONS, d, \"data protection\");\n if (u.data_protections.includes(\"DATA_NOT_COLLECTED\")) continue;\n if (!u.category) throw new Error(\"Every collected entry needs a category.\");\n if (!u.purposes?.length) throw new Error(`${u.category} needs at least one purpose.`);\n const linked = u.data_protections.includes(\"DATA_LINKED_TO_YOU\");\n const unlinked = u.data_protections.includes(\"DATA_NOT_LINKED_TO_YOU\");\n if (linked && unlinked) throw new Error(`${u.category} cannot be both linked and not linked to the user.`);\n }\n}\n","/**\n * App Review rejections: what App Review said, from the Resolution Center.\n * Port of Spaceship's ResolutionCenterThread / ResolutionCenterMessage /\n * ReviewRejection models (connect_api/tunes).\n *\n * Like App Privacy, this is not in the public App Store Connect API — with an\n * API key, /v1/resolutionCenterThreads answers \"resource does not exist\".\n * Spaceship reaches it through its Tunes client, whose host is\n * appstoreconnect.apple.com/iris/, i.e. an Apple ID session. So does this.\n */\n\nimport type { AppleSession } from \"../client/apple-session.js\";\nimport { selectProviderForApp } from \"../privacy/index.js\";\n\nconst IRIS_BASE = \"https://appstoreconnect.apple.com/iris/v1\";\n\nexport interface RejectionReason {\n section?: string;\n description?: string;\n code?: string;\n}\n\nexport interface ResolutionMessage {\n id: string;\n createdDate: string | null;\n from: string | null;\n /** The message as App Review wrote it, HTML reduced to text. */\n body: string;\n reasons: RejectionReason[];\n}\n\nexport interface ResolutionThread {\n id: string;\n threadType: string | null;\n state: string | null;\n createdDate: string | null;\n messages: ResolutionMessage[];\n}\n\nexport interface SubmissionReview {\n submissionId: string;\n state: string;\n submittedDate: string | null;\n threads: ResolutionThread[];\n}\n\nasync function iris(session: AppleSession, path: string): Promise<any> {\n const res = await fetch(`${IRIS_BASE}${path}`, {\n headers: {\n Cookie: Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join(\"; \"),\n Accept: \"application/vnd.api+json, application/json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n },\n });\n for (const raw of res.headers.getSetCookie?.() ?? []) {\n const m = raw.match(/^([^=]+)=([^;]*)/);\n if (m) session.cookies[m[1]] = m[2];\n }\n const text = await res.text();\n let body: any = null;\n try { body = text ? JSON.parse(text) : null; } catch { /* not JSON */ }\n if (!res.ok) {\n const detail = body?.errors?.map((e: any) => e.detail ?? e.title).join(\"; \") || text.slice(0, 300);\n throw new Error(`GET ${path} failed (HTTP ${res.status}): ${detail}`);\n }\n return body;\n}\n\n/** App Review writes HTML; keep paragraphs and list items, drop the markup. */\nexport function htmlToText(html: string | null | undefined): string {\n if (!html) return \"\";\n return html\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<\\/(p|div|li|h\\d)>/gi, \"\\n\")\n .replace(/<li[^>]*>/gi, \"- \")\n .replace(/<[^>]+>/g, \"\")\n .replace(/ /g, \" \").replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\n .replace(/"/g, '\"').replace(/'/g, \"'\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\nfunction reasonsFrom(rejection: any): RejectionReason[] {\n const reasons = rejection?.attributes?.reasons ?? [];\n return (Array.isArray(reasons) ? reasons : []).map((r: any) => ({\n section: r.reasonSection ?? r.section,\n description: r.reasonDescription ?? r.description,\n code: r.reasonCode ?? r.code,\n }));\n}\n\n/** The messages in one thread, each with the guideline reasons App Review attached. */\nexport async function getThreadMessages(session: AppleSession, threadId: string): Promise<ResolutionMessage[]> {\n const body = await iris(session, `/resolutionCenterThreads/${threadId}/resolutionCenterMessages?include=fromActor,rejections`);\n const included = new Map<string, any>((body?.included ?? []).map((i: any) => [`${i.type}:${i.id}`, i]));\n return (body?.data ?? []).map((m: any) => {\n const actor = m.relationships?.fromActor?.data;\n const actorRow = actor ? included.get(`${actor.type}:${actor.id}`) : null;\n const rejections = (m.relationships?.rejections?.data ?? []).map((r: any) => included.get(`${r.type}:${r.id}`)).filter(Boolean);\n return {\n id: m.id,\n createdDate: m.attributes?.createdDate ?? null,\n from: actorRow?.attributes?.actorType ?? actorRow?.attributes?.userFirstName ?? null,\n body: htmlToText(m.attributes?.messageBody),\n reasons: rejections.flatMap(reasonsFrom),\n };\n });\n}\n\n/**\n * Every review submission for the app that App Review has answered, with its\n * Resolution Center threads and messages. `onlyOpen` keeps the ones still\n * waiting on the developer (UNRESOLVED_ISSUES).\n */\nexport async function getReviewFeedback(\n session: AppleSession,\n appId: string,\n opts: { onlyOpen?: boolean; onLog?: (m: string) => void } = {},\n): Promise<SubmissionReview[]> {\n await selectProviderForApp(session, appId, opts.onLog);\n const subs = await iris(session, `/apps/${appId}/reviewSubmissions?limit=10`);\n const out: SubmissionReview[] = [];\n for (const s of subs?.data ?? []) {\n const state = s.attributes?.state ?? \"\";\n if (opts.onlyOpen && state !== \"UNRESOLVED_ISSUES\") continue;\n const threadsBody = await iris(session, `/resolutionCenterThreads?filter[reviewSubmission]=${s.id}&include=reviewSubmission`);\n const threads: ResolutionThread[] = [];\n for (const t of threadsBody?.data ?? []) {\n threads.push({\n id: t.id,\n threadType: t.attributes?.threadType ?? null,\n state: t.attributes?.state ?? null,\n createdDate: t.attributes?.createdDate ?? null,\n messages: await getThreadMessages(session, t.id),\n });\n }\n if (threads.length || !opts.onlyOpen) {\n out.push({ submissionId: s.id, state, submittedDate: s.attributes?.submittedDate ?? null, threads });\n }\n }\n return out;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/client/providers.ts","../../src/privacy/index.ts","../../src/review/index.ts"],"names":["IRIS_BASE","iris"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * App Store Connect \"providers\": the teams an Apple ID belongs to, as App\n * Store Connect sees them. A web session works inside one provider at a time\n * — whichever the person used last — and everything iris creates (API keys,\n * app records) lands in that provider, not the developer-portal team the\n * portal calls were made for. Spaceship's `select_team` is the same switch.\n */\nimport type { AppleSession } from \"./apple-session.js\";\n\nconst OLYMPUS_SESSION = \"https://appstoreconnect.apple.com/olympus/v1/session\";\n\nexport interface AscProvider {\n providerId: number;\n name: string;\n}\n\nexport function cookieHeader(session: AppleSession): string {\n return Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join(\"; \");\n}\n\nexport function absorbCookies(session: AppleSession, res: Response): void {\n for (const raw of res.headers.getSetCookie?.() ?? []) {\n const m = raw.match(/^([^=]+)=([^;]*)/);\n if (m) session.cookies[m[1]] = m[2];\n }\n}\n\n/** The provider the session is in now, and every provider it could switch to. */\nexport async function listProviders(session: AppleSession): Promise<{ current: AscProvider | null; providers: AscProvider[] }> {\n const res = await fetch(OLYMPUS_SESSION, { headers: { Cookie: cookieHeader(session), Accept: \"application/json\" } });\n absorbCookies(session, res);\n const info: any = await res.json().catch(() => null);\n const pick = (p: any): AscProvider | null =>\n p?.providerId != null ? { providerId: Number(p.providerId), name: String(p.name ?? p.providerId) } : null;\n const providers = ((info?.availableProviders ?? []) as any[]).map(pick).filter((p): p is AscProvider => !!p);\n return { current: pick(info?.provider), providers };\n}\n\n/** Move the session into another provider. True when Apple accepted it. */\nexport async function switchProvider(session: AppleSession, providerId: number): Promise<boolean> {\n const res = await fetch(OLYMPUS_SESSION, {\n method: \"POST\",\n headers: { Cookie: cookieHeader(session), \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n body: JSON.stringify({ provider: { providerId } }),\n });\n absorbCookies(session, res);\n if (res.ok) session.teamId = String(providerId);\n return res.ok;\n}\n","/**\n * App Privacy details (\"nutrition labels\") for an app in App Store Connect.\n * 1:1 port of Fastlane's `upload_app_privacy_details_to_app_store` action and\n * Spaceship's AppDataUsage / AppDataUsagesPublishState models.\n *\n * The public App Store Connect API has no endpoint for privacy details — an\n * API key cannot set them. Fastlane goes through the iris API that App Store\n * Connect's own web UI uses, authenticated with an Apple ID session, and so\n * does this: pass the AppleSession from `loginWithAppleId` / the App Release\n * sign-in.\n *\n * Usages use Fastlane's JSON format, so an existing `app_privacy_details.json`\n * works unchanged:\n *\n * [\n * { \"category\": \"EMAIL_ADDRESS\", \"purposes\": [\"APP_FUNCTIONALITY\"], \"data_protections\": [\"DATA_LINKED_TO_YOU\"] },\n * { \"data_protections\": [\"DATA_NOT_COLLECTED\"] } // an app that collects nothing\n * ]\n *\n * As in Fastlane, the upload REPLACES everything: existing usages are deleted\n * first, then the new ones are created, then the answers are published.\n */\n\nimport type { AppleSession } from \"../client/apple-session.js\";\nimport { absorbCookies, cookieHeader, listProviders, switchProvider } from \"../client/providers.js\";\n\nconst IRIS_BASE = \"https://appstoreconnect.apple.com/iris/v1\";\n\n/** Every category App Store Connect accepts. Anything else is refused before existing answers are deleted. */\nexport const DATA_USAGE_CATEGORIES = [\n \"PAYMENT_INFORMATION\", \"CREDIT_AND_FRAUD\", \"OTHER_FINANCIAL_INFO\",\n \"PRECISE_LOCATION\", \"COARSE_LOCATION\", \"SENSITIVE_INFO\",\n \"PHYSICAL_ADDRESS\", \"EMAIL_ADDRESS\", \"NAME\", \"PHONE_NUMBER\", \"OTHER_CONTACT_INFO\",\n \"CONTACTS\", \"EMAILS_OR_TEXT_MESSAGES\", \"PHOTOS_OR_VIDEOS\", \"AUDIO\", \"GAMEPLAY_CONTENT\",\n \"CUSTOMER_SUPPORT\", \"OTHER_USER_CONTENT\", \"BROWSING_HISTORY\", \"SEARCH_HISTORY\",\n \"USER_ID\", \"DEVICE_ID\", \"PURCHASE_HISTORY\", \"PRODUCT_INTERACTION\", \"ADVERTISING_DATA\",\n \"OTHER_USAGE_DATA\", \"CRASH_DATA\", \"PERFORMANCE_DATA\", \"OTHER_DIAGNOSTIC_DATA\",\n \"OTHER_DATA\", \"HEALTH\", \"FITNESS\",\n] as const;\nexport const DATA_USAGE_PURPOSES = [\n \"THIRD_PARTY_ADVERTISING\", \"DEVELOPERS_ADVERTISING\", \"ANALYTICS\",\n \"PRODUCT_PERSONALIZATION\", \"APP_FUNCTIONALITY\", \"OTHER_PURPOSES\",\n] as const;\nexport const DATA_PROTECTIONS = [\n \"DATA_USED_TO_TRACK_YOU\", \"DATA_LINKED_TO_YOU\", \"DATA_NOT_LINKED_TO_YOU\", \"DATA_NOT_COLLECTED\",\n] as const;\n\nexport type DataUsageCategory =\n | \"PAYMENT_INFORMATION\" | \"CREDIT_AND_FRAUD\" | \"OTHER_FINANCIAL_INFO\"\n | \"PRECISE_LOCATION\" | \"COARSE_LOCATION\" | \"SENSITIVE_INFO\"\n | \"PHYSICAL_ADDRESS\" | \"EMAIL_ADDRESS\" | \"NAME\" | \"PHONE_NUMBER\" | \"OTHER_CONTACT_INFO\"\n | \"CONTACTS\" | \"EMAILS_OR_TEXT_MESSAGES\" | \"PHOTOS_OR_VIDEOS\" | \"AUDIO\" | \"GAMEPLAY_CONTENT\"\n | \"CUSTOMER_SUPPORT\" | \"OTHER_USER_CONTENT\" | \"BROWSING_HISTORY\" | \"SEARCH_HISTORY\"\n | \"USER_ID\" | \"DEVICE_ID\" | \"PURCHASE_HISTORY\" | \"PRODUCT_INTERACTION\" | \"ADVERTISING_DATA\"\n | \"OTHER_USAGE_DATA\" | \"CRASH_DATA\" | \"PERFORMANCE_DATA\" | \"OTHER_DIAGNOSTIC_DATA\"\n | \"OTHER_DATA\" | \"HEALTH\" | \"FITNESS\";\n\nexport type DataUsagePurpose =\n | \"THIRD_PARTY_ADVERTISING\" | \"DEVELOPERS_ADVERTISING\" | \"ANALYTICS\"\n | \"PRODUCT_PERSONALIZATION\" | \"APP_FUNCTIONALITY\" | \"OTHER_PURPOSES\";\n\nexport type DataProtection =\n | \"DATA_USED_TO_TRACK_YOU\" | \"DATA_LINKED_TO_YOU\" | \"DATA_NOT_LINKED_TO_YOU\" | \"DATA_NOT_COLLECTED\";\n\n/** One entry of Fastlane's app_privacy_details.json. */\nexport interface AppPrivacyUsage {\n category?: DataUsageCategory;\n purposes?: DataUsagePurpose[];\n data_protections: DataProtection[];\n}\n\nexport interface UploadAppPrivacyOptions {\n session: AppleSession;\n /** App Store Connect app id (the numeric Apple ID). */\n appId: string;\n usages: AppPrivacyUsage[];\n /** Leave the answers saved but unpublished (Fastlane's skip_publish). */\n skipPublish?: boolean;\n onLog?: (msg: string) => void;\n}\n\nexport interface UploadAppPrivacyResult {\n deleted: number;\n created: number;\n published: boolean;\n}\n\ninterface IrisResponse { status: number; body: any; text: string }\n\nasync function iris(session: AppleSession, method: string, path: string, body?: unknown): Promise<IrisResponse> {\n const res = await fetch(path.startsWith(\"http\") ? path : `${IRIS_BASE}${path}`, {\n method,\n headers: {\n Cookie: cookieHeader(session),\n Accept: \"application/vnd.api+json, application/json\",\n \"Content-Type\": \"application/vnd.api+json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n absorbCookies(session, res);\n const text = await res.text();\n let parsed: any = null;\n try { parsed = text ? JSON.parse(text) : null; } catch { /* not JSON */ }\n return { status: res.status, body: parsed, text };\n}\n\n\nfunction fail(what: string, r: IrisResponse): never {\n const detail = r.body?.errors?.map((e: any) => e.detail ?? e.title).join(\"; \") || r.text.slice(0, 300);\n throw new Error(`${what} failed (HTTP ${r.status}): ${detail}`);\n}\n\n/**\n * Point the session at the App Store Connect provider (team) that owns the app.\n *\n * An Apple ID in several teams signs in to whichever one it used last, and\n * iris answers 404 for another team's app. Spaceship's `select_team` does the\n * same switch: POST the provider id to the olympus session.\n */\nexport async function selectProviderForApp(session: AppleSession, appId: string, onLog?: (m: string) => void): Promise<void> {\n const owns = async () => (await iris(session, \"GET\", `/apps/${appId}?fields[apps]=name`)).status === 200;\n if (await owns()) return;\n\n const { providers } = await listProviders(session);\n for (const p of providers) {\n if ((await switchProvider(session, p.providerId)) && (await owns())) {\n onLog?.(`Switched to team \"${p.name}\"`);\n return;\n }\n }\n throw new Error(`None of this Apple ID's ${providers.length} teams can see app ${appId}.`);\n}\n\n/** Existing privacy answers for an app. */\nexport async function listAppDataUsages(session: AppleSession, appId: string): Promise<Array<{ id: string }>> {\n const all: Array<{ id: string }> = [];\n let next: string | null = `/apps/${appId}/dataUsages?include=category,grouping,purpose,dataProtection&limit=500`;\n while (next) {\n const r = await iris(session, \"GET\", next);\n if (r.status !== 200) fail(\"List app data usages\", r);\n all.push(...(r.body?.data ?? []).map((d: any) => ({ id: d.id })));\n next = r.body?.links?.next ?? null;\n }\n return all;\n}\n\n/** Spaceship AppDataUsage.create — relationships are sent only when given. */\nexport async function createAppDataUsage(\n session: AppleSession,\n input: { appId: string; category?: string | null; purpose?: string | null; dataProtection: string },\n): Promise<string> {\n const relationships: Record<string, unknown> = { app: { data: { type: \"apps\", id: input.appId } } };\n if (input.category) relationships.category = { data: { type: \"appDataUsageCategories\", id: input.category } };\n if (input.purpose) relationships.purpose = { data: { type: \"appDataUsagePurposes\", id: input.purpose } };\n relationships.dataProtection = { data: { type: \"appDataUsageDataProtections\", id: input.dataProtection } };\n const r = await iris(session, \"POST\", \"/appDataUsages\", { data: { type: \"appDataUsages\", relationships } });\n if (r.status !== 201 && r.status !== 200) fail(`Create ${input.category ?? \"\"} ${input.purpose ?? \"\"} ${input.dataProtection}`.replace(/\\s+/g, \" \"), r);\n return r.body?.data?.id;\n}\n\nexport async function deleteAppDataUsage(session: AppleSession, id: string): Promise<void> {\n const r = await iris(session, \"DELETE\", `/appDataUsages/${id}`);\n if (r.status !== 204 && r.status !== 200) fail(`Delete app data usage ${id}`, r);\n}\n\nexport async function getAppDataUsagesPublishState(session: AppleSession, appId: string): Promise<{ id: string; published: boolean }> {\n const r = await iris(session, \"GET\", `/apps/${appId}/dataUsagePublishState`);\n if (r.status !== 200) fail(\"Read privacy publish state\", r);\n return { id: r.body.data.id, published: Boolean(r.body.data.attributes?.published) };\n}\n\nexport async function publishAppDataUsages(session: AppleSession, publishStateId: string): Promise<void> {\n const r = await iris(session, \"PATCH\", `/appDataUsagesPublishState/${publishStateId}`, {\n data: { type: \"appDataUsagesPublishState\", id: publishStateId, attributes: { published: true } },\n });\n if (r.status !== 200) fail(\"Publish privacy details\", r);\n}\n\n/** Fastlane's `upload_app_privacy_details_to_app_store`. Replaces all answers, then publishes. */\nexport async function uploadAppPrivacyDetails(opts: UploadAppPrivacyOptions): Promise<UploadAppPrivacyResult> {\n const { session, appId, usages, skipPublish, onLog = () => {} } = opts;\n validateUsages(usages);\n await selectProviderForApp(session, appId, onLog);\n\n const existing = await listAppDataUsages(session, appId);\n onLog(`Deleting ${existing.length} existing answer${existing.length === 1 ? \"\" : \"s\"}`);\n for (const u of existing) await deleteAppDataUsage(session, u.id);\n\n let created = 0;\n for (const usage of usages) {\n // \"Not collected\" has no purposes but still needs one AppDataUsage, so\n // iterate over [null] — the same trick Fastlane uses.\n const purposes: Array<string | null> = usage.purposes?.length ? usage.purposes : [null];\n for (const purpose of purposes) {\n for (const protection of usage.data_protections) {\n onLog(protection === \"DATA_NOT_COLLECTED\"\n ? `Setting ${protection}`\n : `Setting ${usage.category} and ${purpose} to ${protection}`);\n await createAppDataUsage(session, { appId, category: usage.category ?? null, purpose, dataProtection: protection });\n created++;\n }\n }\n }\n\n if (skipPublish) {\n onLog(\"Skipping publish\");\n return { deleted: existing.length, created, published: false };\n }\n const state = await getAppDataUsagesPublishState(session, appId);\n await publishAppDataUsages(session, state.id);\n onLog(\"Published\");\n return { deleted: existing.length, created, published: true };\n}\n\n/** Catch the answers App Store Connect would refuse, before deleting what is there. */\nexport function validateUsages(usages: AppPrivacyUsage[]): void {\n if (!Array.isArray(usages) || usages.length === 0) throw new Error(\"No privacy usages given.\");\n const notCollected = usages.some((u) => u.data_protections?.includes(\"DATA_NOT_COLLECTED\"));\n if (notCollected && usages.length > 1) {\n throw new Error(\"DATA_NOT_COLLECTED must be the only entry: an app cannot both collect nothing and collect something.\");\n }\n const known = (list: readonly string[], v: string, what: string) => {\n if (!list.includes(v)) throw new Error(`\"${v}\" is not an App Store Connect ${what}. Use one of: ${list.join(\", \")}.`);\n };\n for (const u of usages) {\n if (!u.data_protections?.length) throw new Error(`${u.category ?? \"An entry\"} has no data_protections.`);\n // Checked here, before anything is deleted: App Store Connect refuses an\n // unknown id only when that one answer is created, halfway through.\n if (u.category) known(DATA_USAGE_CATEGORIES, u.category, \"data category\");\n for (const p of u.purposes ?? []) known(DATA_USAGE_PURPOSES, p, \"purpose\");\n for (const d of u.data_protections) known(DATA_PROTECTIONS, d, \"data protection\");\n if (u.data_protections.includes(\"DATA_NOT_COLLECTED\")) continue;\n if (!u.category) throw new Error(\"Every collected entry needs a category.\");\n if (!u.purposes?.length) throw new Error(`${u.category} needs at least one purpose.`);\n const linked = u.data_protections.includes(\"DATA_LINKED_TO_YOU\");\n const unlinked = u.data_protections.includes(\"DATA_NOT_LINKED_TO_YOU\");\n if (linked && unlinked) throw new Error(`${u.category} cannot be both linked and not linked to the user.`);\n }\n}\n","/**\n * App Review rejections: what App Review said, from the Resolution Center.\n * Port of Spaceship's ResolutionCenterThread / ResolutionCenterMessage /\n * ReviewRejection models (connect_api/tunes).\n *\n * Like App Privacy, this is not in the public App Store Connect API — with an\n * API key, /v1/resolutionCenterThreads answers \"resource does not exist\".\n * Spaceship reaches it through its Tunes client, whose host is\n * appstoreconnect.apple.com/iris/, i.e. an Apple ID session. So does this.\n */\n\nimport type { AppleSession } from \"../client/apple-session.js\";\nimport { selectProviderForApp } from \"../privacy/index.js\";\n\nconst IRIS_BASE = \"https://appstoreconnect.apple.com/iris/v1\";\n\nexport interface RejectionReason {\n section?: string;\n description?: string;\n code?: string;\n}\n\nexport interface ResolutionMessage {\n id: string;\n createdDate: string | null;\n from: string | null;\n /** The message as App Review wrote it, HTML reduced to text. */\n body: string;\n reasons: RejectionReason[];\n}\n\nexport interface ResolutionThread {\n id: string;\n threadType: string | null;\n state: string | null;\n createdDate: string | null;\n messages: ResolutionMessage[];\n}\n\nexport interface SubmissionReview {\n submissionId: string;\n state: string;\n submittedDate: string | null;\n threads: ResolutionThread[];\n}\n\nasync function iris(session: AppleSession, path: string): Promise<any> {\n const res = await fetch(`${IRIS_BASE}${path}`, {\n headers: {\n Cookie: Object.entries(session.cookies).map(([k, v]) => `${k}=${v}`).join(\"; \"),\n Accept: \"application/vnd.api+json, application/json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n },\n });\n for (const raw of res.headers.getSetCookie?.() ?? []) {\n const m = raw.match(/^([^=]+)=([^;]*)/);\n if (m) session.cookies[m[1]] = m[2];\n }\n const text = await res.text();\n let body: any = null;\n try { body = text ? JSON.parse(text) : null; } catch { /* not JSON */ }\n if (!res.ok) {\n const detail = body?.errors?.map((e: any) => e.detail ?? e.title).join(\"; \") || text.slice(0, 300);\n throw new Error(`GET ${path} failed (HTTP ${res.status}): ${detail}`);\n }\n return body;\n}\n\n/** App Review writes HTML; keep paragraphs and list items, drop the markup. */\nexport function htmlToText(html: string | null | undefined): string {\n if (!html) return \"\";\n return html\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<\\/(p|div|li|h\\d)>/gi, \"\\n\")\n .replace(/<li[^>]*>/gi, \"- \")\n .replace(/<[^>]+>/g, \"\")\n .replace(/ /g, \" \").replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\n .replace(/"/g, '\"').replace(/'/g, \"'\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\nfunction reasonsFrom(rejection: any): RejectionReason[] {\n const reasons = rejection?.attributes?.reasons ?? [];\n return (Array.isArray(reasons) ? reasons : []).map((r: any) => ({\n section: r.reasonSection ?? r.section,\n description: r.reasonDescription ?? r.description,\n code: r.reasonCode ?? r.code,\n }));\n}\n\n/** The messages in one thread, each with the guideline reasons App Review attached. */\nexport async function getThreadMessages(session: AppleSession, threadId: string): Promise<ResolutionMessage[]> {\n const body = await iris(session, `/resolutionCenterThreads/${threadId}/resolutionCenterMessages?include=fromActor,rejections`);\n const included = new Map<string, any>((body?.included ?? []).map((i: any) => [`${i.type}:${i.id}`, i]));\n return (body?.data ?? []).map((m: any) => {\n const actor = m.relationships?.fromActor?.data;\n const actorRow = actor ? included.get(`${actor.type}:${actor.id}`) : null;\n const rejections = (m.relationships?.rejections?.data ?? []).map((r: any) => included.get(`${r.type}:${r.id}`)).filter(Boolean);\n return {\n id: m.id,\n createdDate: m.attributes?.createdDate ?? null,\n from: actorRow?.attributes?.actorType ?? actorRow?.attributes?.userFirstName ?? null,\n body: htmlToText(m.attributes?.messageBody),\n reasons: rejections.flatMap(reasonsFrom),\n };\n });\n}\n\n/**\n * Every review submission for the app that App Review has answered, with its\n * Resolution Center threads and messages. `onlyOpen` keeps the ones still\n * waiting on the developer (UNRESOLVED_ISSUES).\n */\nexport async function getReviewFeedback(\n session: AppleSession,\n appId: string,\n opts: { onlyOpen?: boolean; onLog?: (m: string) => void } = {},\n): Promise<SubmissionReview[]> {\n await selectProviderForApp(session, appId, opts.onLog);\n const subs = await iris(session, `/apps/${appId}/reviewSubmissions?limit=10`);\n const out: SubmissionReview[] = [];\n for (const s of subs?.data ?? []) {\n const state = s.attributes?.state ?? \"\";\n if (opts.onlyOpen && state !== \"UNRESOLVED_ISSUES\") continue;\n const threadsBody = await iris(session, `/resolutionCenterThreads?filter[reviewSubmission]=${s.id}&include=reviewSubmission`);\n const threads: ResolutionThread[] = [];\n for (const t of threadsBody?.data ?? []) {\n threads.push({\n id: t.id,\n threadType: t.attributes?.threadType ?? null,\n state: t.attributes?.state ?? null,\n createdDate: t.attributes?.createdDate ?? null,\n messages: await getThreadMessages(session, t.id),\n });\n }\n if (threads.length || !opts.onlyOpen) {\n out.push({ submissionId: s.id, state, submittedDate: s.attributes?.submittedDate ?? null, threads });\n }\n }\n return out;\n}\n"]}
|
package/dist/sigh/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import '../index-
|
|
2
|
-
import '../enums-
|
|
3
|
-
export { a as SighOptions, S as SighResult, c as createProfile, d as downloadProfile, f as findMatchingProfile, b as installProfile } from '../index-
|
|
4
|
-
import '../apple-session-
|
|
1
|
+
import '../index-HWWl6HEL.cjs';
|
|
2
|
+
import '../enums-BlpayMTx.cjs';
|
|
3
|
+
export { a as SighOptions, S as SighResult, c as createProfile, d as downloadProfile, f as findMatchingProfile, b as installProfile } from '../index-QYgreJV3.cjs';
|
|
4
|
+
import '../apple-session-D6x7Jo3D.cjs';
|
package/dist/sigh/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import '../index-
|
|
2
|
-
import '../enums-
|
|
3
|
-
export { a as SighOptions, S as SighResult, c as createProfile, d as downloadProfile, f as findMatchingProfile, b as installProfile } from '../index-
|
|
4
|
-
import '../apple-session-
|
|
1
|
+
import '../index-SyCMBpNN.js';
|
|
2
|
+
import '../enums-BlpayMTx.js';
|
|
3
|
+
export { a as SighOptions, S as SighResult, c as createProfile, d as downloadProfile, f as findMatchingProfile, b as installProfile } from '../index-Dy8EWOnt.js';
|
|
4
|
+
import '../apple-session-D6x7Jo3D.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { S as ScreenshotResult, a as SimDevice, b as SnapshotOptions, c as bootSimulator, d as captureScreenshots, e as eraseSimulator, f as findDevice, l as listSimulators, g as localizeSimulator, o as overrideStatusBar, s as setInterfaceStyle, h as shutdownAll } from '../index-Cw7ojUY5.cjs';
|
package/dist/snapshot/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { S as ScreenshotResult, a as SimDevice, b as SnapshotOptions, c as bootSimulator, d as captureScreenshots, e as eraseSimulator, f as findDevice, l as listSimulators, g as localizeSimulator, o as overrideStatusBar, s as setInterfaceStyle, h as shutdownAll } from '../index-Cw7ojUY5.js';
|
package/dist/trainer/index.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { J as JUnitResult,
|
|
1
|
+
export { J as JUnitResult, T as TestCase, a as TestSuite, b as TrainerOptions, g as generateJUnitXml, p as parseTestResults } from '../index-B7ERdhh8.cjs';
|
package/dist/trainer/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { J as JUnitResult,
|
|
1
|
+
export { J as JUnitResult, T as TestCase, a as TestSuite, b as TrainerOptions, g as generateJUnitXml, p as parseTestResults } from '../index-B7ERdhh8.js';
|
package/dist/xcode/index.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { K as KeychainHandle,
|
|
1
|
+
export { K as KeychainHandle, c as cleanupKeychain, a as createKeychain, b as importCertificate, d as installProfile, e as isMacOS, r as readBuildSettings, f as requireMacOS, s as setBuildNumber, g as setVersionNumber, u as updateSigningSettings, h as uploadWithAltool } from '../index-CHLDQweM.cjs';
|
package/dist/xcode/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { K as KeychainHandle,
|
|
1
|
+
export { K as KeychainHandle, c as cleanupKeychain, a as createKeychain, b as importCertificate, d as installProfile, e as isMacOS, r as readBuildSettings, f as requireMacOS, s as setBuildNumber, g as setVersionNumber, u as updateSigningSettings, h as uploadWithAltool } from '../index-CHLDQweM.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sitepong/fastnode",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "A complete Node.js/TypeScript port of Fastlane — iOS & Android build automation, code signing, and app distribution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -236,7 +236,7 @@
|
|
|
236
236
|
"commander": "^13.1.0",
|
|
237
237
|
"emoji-regex": "^10.3.0",
|
|
238
238
|
"jose": "^6.0.11",
|
|
239
|
-
"node-forge": "^1.
|
|
239
|
+
"node-forge": "^1.4.0",
|
|
240
240
|
"plist": "^3.1.0"
|
|
241
241
|
},
|
|
242
242
|
"devDependencies": {
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Google Play Developer API v3 uploader — the Android-side equivalent of
|
|
3
|
-
* `pilot` (TestFlight). Uses a service-account JSON to mint an OAuth2 access
|
|
4
|
-
* token (scope androidpublisher), then drives the Edits → upload → track →
|
|
5
|
-
* commit flow against the Android Publisher REST API.
|
|
6
|
-
*/
|
|
7
|
-
type PlayTrack = "internal" | "alpha" | "beta" | "production";
|
|
8
|
-
interface PlayUploadOptions {
|
|
9
|
-
packageName: string;
|
|
10
|
-
/** Raw service-account JSON string (with client_email + private_key). */
|
|
11
|
-
serviceAccountJson: string;
|
|
12
|
-
/** Path to the .aab or .apk artifact to upload. */
|
|
13
|
-
artifactPath: string;
|
|
14
|
-
track: PlayTrack;
|
|
15
|
-
releaseNotes?: string;
|
|
16
|
-
releaseName?: string;
|
|
17
|
-
onProgress?: (msg: string) => void;
|
|
18
|
-
}
|
|
19
|
-
interface PlayUploadResult {
|
|
20
|
-
success: true;
|
|
21
|
-
versionCode: number;
|
|
22
|
-
track: PlayTrack;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Error thrown for any Play upload failure, carrying the HTTP status (if any)
|
|
26
|
-
* so callers/`classifyPlayError` can decide whether to retry.
|
|
27
|
-
*/
|
|
28
|
-
declare class PlayApiError extends Error {
|
|
29
|
-
readonly status?: number;
|
|
30
|
-
constructor(message: string, status?: number);
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Upload an artifact (.aab or .apk) to a Google Play track and roll it out.
|
|
34
|
-
*
|
|
35
|
-
* Flow:
|
|
36
|
-
* 1. Create an edit.
|
|
37
|
-
* 2. Upload the bundle/apk bytes, capturing the returned versionCode.
|
|
38
|
-
* 3. Assign that versionCode to the requested track (status "completed").
|
|
39
|
-
* 4. Commit the edit.
|
|
40
|
-
* 5. On any failure, delete the edit (best effort) and throw.
|
|
41
|
-
*/
|
|
42
|
-
declare function uploadToPlay(options: PlayUploadOptions): Promise<PlayUploadResult>;
|
|
43
|
-
/**
|
|
44
|
-
* Classify a Play upload failure into transient vs. fatal — mirrors
|
|
45
|
-
* pilot/classify.ts. HTTP 429/5xx and network-level errors are transient
|
|
46
|
-
* (safe to retry); 4xx (auth, invalid bundle, version conflict) are fatal.
|
|
47
|
-
*/
|
|
48
|
-
declare function classifyPlayError(err: unknown): {
|
|
49
|
-
transient: boolean;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
type index_PlayApiError = PlayApiError;
|
|
53
|
-
declare const index_PlayApiError: typeof PlayApiError;
|
|
54
|
-
type index_PlayTrack = PlayTrack;
|
|
55
|
-
type index_PlayUploadOptions = PlayUploadOptions;
|
|
56
|
-
type index_PlayUploadResult = PlayUploadResult;
|
|
57
|
-
declare const index_classifyPlayError: typeof classifyPlayError;
|
|
58
|
-
declare const index_uploadToPlay: typeof uploadToPlay;
|
|
59
|
-
declare namespace index {
|
|
60
|
-
export { index_PlayApiError as PlayApiError, type index_PlayTrack as PlayTrack, type index_PlayUploadOptions as PlayUploadOptions, type index_PlayUploadResult as PlayUploadResult, index_classifyPlayError as classifyPlayError, index_uploadToPlay as uploadToPlay };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export { type PlayTrack as P, type PlayUploadOptions as a, type PlayUploadResult as b, PlayApiError as c, classifyPlayError as d, index as i, uploadToPlay as u };
|
package/dist/index-CXJlvmB4.d.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Google Play Developer API v3 uploader — the Android-side equivalent of
|
|
3
|
-
* `pilot` (TestFlight). Uses a service-account JSON to mint an OAuth2 access
|
|
4
|
-
* token (scope androidpublisher), then drives the Edits → upload → track →
|
|
5
|
-
* commit flow against the Android Publisher REST API.
|
|
6
|
-
*/
|
|
7
|
-
type PlayTrack = "internal" | "alpha" | "beta" | "production";
|
|
8
|
-
interface PlayUploadOptions {
|
|
9
|
-
packageName: string;
|
|
10
|
-
/** Raw service-account JSON string (with client_email + private_key). */
|
|
11
|
-
serviceAccountJson: string;
|
|
12
|
-
/** Path to the .aab or .apk artifact to upload. */
|
|
13
|
-
artifactPath: string;
|
|
14
|
-
track: PlayTrack;
|
|
15
|
-
releaseNotes?: string;
|
|
16
|
-
releaseName?: string;
|
|
17
|
-
onProgress?: (msg: string) => void;
|
|
18
|
-
}
|
|
19
|
-
interface PlayUploadResult {
|
|
20
|
-
success: true;
|
|
21
|
-
versionCode: number;
|
|
22
|
-
track: PlayTrack;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Error thrown for any Play upload failure, carrying the HTTP status (if any)
|
|
26
|
-
* so callers/`classifyPlayError` can decide whether to retry.
|
|
27
|
-
*/
|
|
28
|
-
declare class PlayApiError extends Error {
|
|
29
|
-
readonly status?: number;
|
|
30
|
-
constructor(message: string, status?: number);
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Upload an artifact (.aab or .apk) to a Google Play track and roll it out.
|
|
34
|
-
*
|
|
35
|
-
* Flow:
|
|
36
|
-
* 1. Create an edit.
|
|
37
|
-
* 2. Upload the bundle/apk bytes, capturing the returned versionCode.
|
|
38
|
-
* 3. Assign that versionCode to the requested track (status "completed").
|
|
39
|
-
* 4. Commit the edit.
|
|
40
|
-
* 5. On any failure, delete the edit (best effort) and throw.
|
|
41
|
-
*/
|
|
42
|
-
declare function uploadToPlay(options: PlayUploadOptions): Promise<PlayUploadResult>;
|
|
43
|
-
/**
|
|
44
|
-
* Classify a Play upload failure into transient vs. fatal — mirrors
|
|
45
|
-
* pilot/classify.ts. HTTP 429/5xx and network-level errors are transient
|
|
46
|
-
* (safe to retry); 4xx (auth, invalid bundle, version conflict) are fatal.
|
|
47
|
-
*/
|
|
48
|
-
declare function classifyPlayError(err: unknown): {
|
|
49
|
-
transient: boolean;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
type index_PlayApiError = PlayApiError;
|
|
53
|
-
declare const index_PlayApiError: typeof PlayApiError;
|
|
54
|
-
type index_PlayTrack = PlayTrack;
|
|
55
|
-
type index_PlayUploadOptions = PlayUploadOptions;
|
|
56
|
-
type index_PlayUploadResult = PlayUploadResult;
|
|
57
|
-
declare const index_classifyPlayError: typeof classifyPlayError;
|
|
58
|
-
declare const index_uploadToPlay: typeof uploadToPlay;
|
|
59
|
-
declare namespace index {
|
|
60
|
-
export { index_PlayApiError as PlayApiError, type index_PlayTrack as PlayTrack, type index_PlayUploadOptions as PlayUploadOptions, type index_PlayUploadResult as PlayUploadResult, index_classifyPlayError as classifyPlayError, index_uploadToPlay as uploadToPlay };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export { type PlayTrack as P, type PlayUploadOptions as a, type PlayUploadResult as b, PlayApiError as c, classifyPlayError as d, index as i, uploadToPlay as u };
|