// Project signals the console answer sheets use (`console plan`): which ad / attribution SDKs the project has, whether // the Android manifest (source or the merged one of a local build) requests or removes the advertising ID permission, // and code that suggests App Privacy types without a Play Data safety equivalent (customer support, AR scanning). // Pure over the scan facts; null fields mean "not found", never "not scanned" (that is `undefined` signals). import { grep, grepOwn, type ProjectFacts, where } from './scan/project'; import { SIGNALS } from './scan/rules'; /** Sign-in evidence in the project's own code (scan's auth signals: auth SDKs and sign-in calls / fields / endpoints; * import lines and module paths never count): "file:line what" per hit, empty when the code has no sign-in. */ export function authEvidence(p: ProjectFacts): string[] { const sdk = p.sdks.filter(s => s.sdk.category === 'auth').flatMap(s => s.deps.slice(0, 1).map(d => `${d.file}:${d.line} ${s.sdk.name}`)); const code = grepOwn(p, SIGNALS.signIn, 3).hits.map(h => `${h.file}:${h.line}`); return [...sdk, ...code]; } export const AD_ID_PERMISSION = 'com.google.android.gms.permission.AD_ID'; export interface ProjectSignals { /** Ad SDKs (names) found in the dependencies. */ adSdks: string[]; /** Attribution SDKs (names). */ attributionSdks: string[]; /** SDKs whose own manifest merges the AD_ID permission (names). */ adIdSdks: string[]; /** file:line of the AD_ID permission in a source or merged manifest. */ adIdPermission: string | null; /** file:line of . */ adIdRemoved: string | null; /** file:line of a customer support SDK or ticket code (App Privacy "Customer Support"). */ support: string | null; /** file:line of AR / XR, hand or face tracking code (App Privacy Environment Scanning, Hands, Head). */ ar: string | null; } const SUPPORT_DEP = /zendesk|intercom|helpshift|freshchat|freshdesk|crisp-sdk|tawk|gorgias/i; const SUPPORT_CODE = /\b(createTicket|submitTicket|supportTicket|SupportTicket|contactSupport\s*\()/; const AR_CODE = /\bARKit\b|\bARSession\b|ARWorldTrackingConfiguration|ARFaceTrackingConfiguration|ARBodyTrackingConfiguration|VNDetectHumanHandPoseRequest|sceneReconstruction|\bnavigator\.xr\b|@react-three\/xr|ARCore|com\.google\.ar\.core|UnityEngine\.XR\.ARFoundation/; export function projectSignals(p: ProjectFacts): ProjectSignals { const names = (cat: string) => [...new Set(p.sdks.filter(s => s.sdk.category === cat).map(s => s.sdk.name))]; const perm = p.androidInfo.permissions.find(x => x.name === AD_ID_PERMISSION); const removed = p.androidInfo.removedPermissions.find(x => x.name === AD_ID_PERMISSION); const dep = p.deps.find(d => SUPPORT_DEP.test(d.name)); const support = dep ? `${dep.file}:${dep.line}` : grep(p.sources, SUPPORT_CODE, 1).map(h => where(h))[0] ?? null; const arDep = p.deps.find(d => /arcore|arkit|ar-foundation|arfoundation|react-three\/xr|viro/i.test(d.name)); const ar = arDep ? `${arDep.file}:${arDep.line}` : grep(p.sources, AR_CODE, 1).map(h => where(h))[0] ?? null; return { adSdks: names('ads'), attributionSdks: names('attribution'), adIdSdks: [...new Set(p.sdks.filter(s => s.sdk.mergesAdId).map(s => s.sdk.name))], adIdPermission: perm ? where(perm.at) : null, adIdRemoved: removed ? where(removed.at) : null, support, ar, }; }