From 36a8bf9625c629254121406c46ea1f5c0501d34d Mon Sep 17 00:00:00 2001 From: CRT Date: Tue, 13 Oct 2026 04:03:09 +0800 Subject: [PATCH] CLI runs on Node 20+: npx storebound (bunx keeps working) - cli/lib/rt.ts: the runtime layer on node: modules (spawn / spawnSync with Bun's defaults, exit codes and "Executable not found", which with PATHEXT, sleep, serve, Glob, hasher, gunzip, stdin, SQLite, packageRoot, config imports with a type-stripping fallback for Node 20); every Bun.* use in cli/ goes through it - tools/gen-embedded.ts -> cli/lib/embedded.ts: templates/, cli/shots/*.html and Play's Data safety form embedded (replaces Bun text imports; the bundle and the compiled sidecar carry them), checked fresh by bun run ci - package.json: bin dist/storebound.mjs (Node ESM bundle, tools/build-node.ts, prepack), engines node >=20 - bun run ci: node bundle + node smoke (real node: --version, help, check same as Bun, scan, release plan --help, init, MCP handshake + tool call); Forgejo CI sets up Node 20; tests/node-compat.test.ts guards cli/ - install writes npx MCP commands when run with Node; bin/storebound falls back to Node - docs: npx storebound init (Node 20+) or bunx storebound init --- .cursor/rules/storebound.mdc | 2 +- .forgejo/workflows/ci.yml | 4 + .github/copilot-instructions.md | 2 +- .gitignore | 3 + AGENTS.md | 2 +- CHANGELOG.md | 13 + CONTRIBUTING.md | 13 + GEMINI.md | 2 +- README.md | 16 +- bin/storebound | 6 +- cli/ads/client.ts | 3 +- cli/analytics.ts | 5 +- cli/api/client.ts | 3 +- cli/apple/asc.ts | 9 +- cli/apple/build.ts | 5 +- cli/apple/iap-review.ts | 3 +- cli/apple/iap.ts | 3 +- cli/apple/signing.ts | 7 +- cli/apple/status.ts | 3 +- cli/apple/submission.ts | 3 +- cli/apple/testflight.ts | 3 +- cli/apple/upload.ts | 11 +- cli/apps.ts | 3 +- cli/aso.ts | 3 +- cli/console/accounts.ts | 3 +- cli/console/fill.ts | 27 +- cli/console/session.ts | 33 +-- cli/console/verify.ts | 3 +- cli/desktop.ts | 3 +- cli/doctor.ts | 9 +- cli/flow.ts | 7 +- cli/init.ts | 3 +- cli/install.ts | 15 +- cli/lib/config.ts | 3 +- cli/lib/data-safety.ts | 4 +- cli/lib/embedded.ts | 48 ++++ cli/lib/imagehash.ts | 5 +- cli/lib/rt.ts | 465 ++++++++++++++++++++++++++++++++ cli/lib/runner.ts | 13 +- cli/lib/template-files.ts | 69 +---- cli/lib/tools.ts | 7 +- cli/lib/upsert.ts | 3 +- cli/lib/web-assets.ts | 5 +- cli/lib/workspace.ts | 9 +- cli/listing.ts | 3 +- cli/mcp.ts | 3 +- cli/play/build.ts | 3 +- cli/play/client.ts | 5 +- cli/play/data-safety.ts | 3 +- cli/play/google.ts | 3 +- cli/reports.ts | 3 +- cli/revenuecat.ts | 3 +- cli/serve.ts | 5 +- cli/shots/browser.ts | 9 +- cli/shots/capture.ts | 13 +- cli/shots/record.ts | 15 +- cli/shots/render.ts | 20 +- cli/shots/video.ts | 12 +- cli/text-modules.d.ts | 6 - cli/whats-new.ts | 5 +- docs/faq.md | 5 +- docs/getting-started.md | 9 +- package.json | 11 +- site/index.html | 44 +-- tests/do-it-dont-ask.test.ts | 4 +- tests/node-compat.test.ts | 153 +++++++++++ tests/wave1.test.ts | 3 + tools/build-node.ts | 43 +++ tools/ci.ts | 13 + tools/gen-adapters.ts | 2 +- tools/gen-embedded.ts | 60 +++++ tools/gen-flags.ts | 3 +- tools/node-smoke.ts | 124 +++++++++ 73 files changed, 1195 insertions(+), 251 deletions(-) create mode 100644 cli/lib/embedded.ts create mode 100644 cli/lib/rt.ts delete mode 100644 cli/text-modules.d.ts create mode 100644 tests/node-compat.test.ts create mode 100644 tools/build-node.ts create mode 100644 tools/gen-embedded.ts create mode 100644 tools/node-smoke.ts diff --git a/.cursor/rules/storebound.mdc b/.cursor/rules/storebound.mdc index 12a65c7..5b8121d 100644 --- a/.cursor/rules/storebound.mdc +++ b/.cursor/rules/storebound.mdc @@ -71,7 +71,7 @@ need to ...", do it instead, unless it is one of the owner steps in point 2. ## Tools -- CLI: `storebound ` (`storebound help`); config `storebound.config.ts`, credentials in env or `.storebound/`. +- CLI: `storebound ` (`storebound help`; outside the plugin: `npx storebound` on Node 20+ or `bunx storebound` on Bun); config `storebound.config.ts`, credentials in env or `.storebound/`. - MCP: `storebound mcp` (stdio). MCP 2026-07-28 + 2025-11-25. Write tools default to `dry_run: true`; submit / release / cancel / delete / send are not exposed. - Readiness: `storebound scan` (offline rejection risks in the project files), `storebound check` (offline), `storebound validate` (read-only, both stores). - Every endpoint: `storebound api apple|play ` (skill store-api). GETs run; writes are dry unless the owner adds `--yes` in a terminal; MCP has the read-only `api_get`. diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 80ad2e5..5fc7078 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -73,6 +73,10 @@ jobs: # (cli/shots/browser.ts sandboxArgs), Playwright's own launches already pass it - name: Headless Chromium (browser tests, desktop boot check) run: bunx playwright install --with-deps chromium + # the npm bundle runs on Node 20+: tools/ci.ts smoke-tests dist/storebound.mjs with the oldest supported Node + - uses: actions/setup-node@v4 + with: + node-version: '20' # the runner sets CI=true in the job and it wins over the workflow env above: set CI=full for the command # itself, or tools/ci.ts skips the desktop Rust tests and clippy when the push did not touch src-tauri - name: bun run ci diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e158d35..7f734b8 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -71,7 +71,7 @@ need to ...", do it instead, unless it is one of the owner steps in point 2. ## Tools -- CLI: `storebound ` (`storebound help`); config `storebound.config.ts`, credentials in env or `.storebound/`. +- CLI: `storebound ` (`storebound help`; outside the plugin: `npx storebound` on Node 20+ or `bunx storebound` on Bun); config `storebound.config.ts`, credentials in env or `.storebound/`. - MCP: `storebound mcp` (stdio). MCP 2026-07-28 + 2025-11-25. Write tools default to `dry_run: true`; submit / release / cancel / delete / send are not exposed. - Readiness: `storebound scan` (offline rejection risks in the project files), `storebound check` (offline), `storebound validate` (read-only, both stores). - Every endpoint: `storebound api apple|play ` (skill store-api). GETs run; writes are dry unless the owner adds `--yes` in a terminal; MCP has the read-only `api_get`. diff --git a/.gitignore b/.gitignore index 0187f25..a649cc4 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ apps/desktop/.screens/ # scan fixtures: a monorepo whose Capacitor plugins live in apps/native/node_modules !tests/fixtures/scan/monorepo/apps/native/node_modules/ + +# npm bundle (bun tools/build-node.ts; npm pack / publish build it) +/dist/ diff --git a/AGENTS.md b/AGENTS.md index 0185fab..9cea3a8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -72,7 +72,7 @@ need to ...", do it instead, unless it is one of the owner steps in point 2. ## Tools -- CLI: `storebound ` (`storebound help`); config `storebound.config.ts`, credentials in env or `.storebound/`. +- CLI: `storebound ` (`storebound help`; outside the plugin: `npx storebound` on Node 20+ or `bunx storebound` on Bun); config `storebound.config.ts`, credentials in env or `.storebound/`. - MCP: `storebound mcp` (stdio). MCP 2026-07-28 + 2025-11-25. Write tools default to `dry_run: true`; submit / release / cancel / delete / send are not exposed. - Readiness: `storebound scan` (offline rejection risks in the project files), `storebound check` (offline), `storebound validate` (read-only, both stores). - Every endpoint: `storebound api apple|play ` (skill store-api). GETs run; writes are dry unless the owner adds `--yes` in a terminal; MCP has the read-only `api_get`. diff --git a/CHANGELOG.md b/CHANGELOG.md index ec5cddb..6923585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ ## Unreleased +- The CLI runs on Node 20+ (owner decision, 2026-10-13): `npx storebound init` works, `bunx storebound` keeps working. + The npm package's `bin` is one Node ESM bundle, `dist/storebound.mjs` (built by `prepack` with + `bun tools/build-node.ts`, zero runtime dependencies, playwright still an optional peer); `engines` names Node 20. + Everything the CLI needs from its runtime goes through `cli/lib/rt.ts` (spawn / spawnSync with Bun's defaults and + exit codes, which with PATHEXT, sleep, a small HTTP server for shots render / video, glob, hashing, gunzip, stdin, + SQLite, the package folder); templates, the shots pages and Play's Data safety form are embedded by + `tools/gen-embedded.ts` instead of Bun text imports, so the bundle and the compiled desktop sidecar carry them. + `storebound.config.ts` loads on Node too (Node's own type stripping, or a fallback on Node 20). `storebound install` + run with Node writes `npx` MCP commands. `bun run ci` builds the bundle and smoke-tests it with the real `node` + (`--version`, `help`, `check` same as Bun, `scan`, `release plan --help`, `init`, an MCP handshake and tool call); + `tests/node-compat.test.ts` fails on any direct Bun API use in `cli/`. `bin/storebound` falls back to Node when Bun + is not installed. Development, tests and CI stay on Bun. + - `console verify apple-app-privacy` checked against real page text (2026-10-12): the summary sections ("Data Used to Track You" and the others) list category names only and are skipped; a type's lines may come joined by " | "; Apple's comma style ("X, and Y") and the straight apostrophe parse. The tracking radio label of a type that already ends in diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24dd71a..b40a41b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,6 +33,12 @@ Try the screenshot and video pipeline on the demo game without any store account - **Bun + TypeScript strict, zero runtime dependencies.** `fetch` against the official store APIs, `node:` built-ins, external tools (`ffmpeg`, `xcrun`, `adb`) only through argv builders. Playwright is resolved from the user's project. +- **Runs on Node 20+ and Bun.** Development, tests and CI use Bun; users run the npm bundle with Node (`npx`) or Bun + (`bunx`). The CLI never touches the `Bun` global, `bun:` modules, `import.meta.dir` or text imports: everything goes + through `cli/lib/rt.ts` (spawn, which, sleep, serve, glob, hashing, stdin, the package folder, config imports), and + text files the CLI ships are embedded by `bun tools/gen-embedded.ts` (templates/, `cli/shots/*.html`, Play's form + export: run it after changing one). `tests/node-compat.test.ts` fails on a direct Bun use; `bun run ci` builds the + bundle (`tools/build-node.ts`) and runs it with the real `node` (`tools/node-smoke.ts`). - **Pure where possible.** Rules, argv builders, plans and parsers are pure functions with tests in `tests/`; the IO stays in the command's `run`. Tests never need a store account, a simulator or a network. - **Every write command takes `--dry`** and is idempotent. New MCP write tools default to `dry_run: true`. Nothing @@ -65,6 +71,13 @@ Try the screenshot and video pipeline on the demo game without any store account `cli/lib/dry.ts`), not only a count; `tests/pitfalls.test.ts` enforces it. - Plain English in docs and messages, short sentences, no marketing words. +## Releasing the npm package + +`npm pack` and `npm publish` run `prepack`, which builds `dist/storebound.mjs` (the `bin`, `#!/usr/bin/env node`, +gitignored) with `bun tools/build-node.ts`. Before publishing: `bun run ci` green (it smoke-tests the bundle with +Node), `npm pack --dry-run` lists `dist/storebound.mjs`, the privacy scan on the tree and on the bundle, and a run of +the packed tarball in an empty folder (`npx --yes ./storebound-.tgz --version`, then `init`). + ## Pull requests One topic per PR, `bun run ci` green, a line in `CHANGELOG.md` under an "Unreleased" heading, and for anything diff --git a/GEMINI.md b/GEMINI.md index e3dc3b6..6399c67 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -70,7 +70,7 @@ need to ...", do it instead, unless it is one of the owner steps in point 2. ## Tools -- CLI: `storebound ` (`storebound help`); config `storebound.config.ts`, credentials in env or `.storebound/`. +- CLI: `storebound ` (`storebound help`; outside the plugin: `npx storebound` on Node 20+ or `bunx storebound` on Bun); config `storebound.config.ts`, credentials in env or `.storebound/`. - MCP: `storebound mcp` (stdio). MCP 2026-07-28 + 2025-11-25. Write tools default to `dry_run: true`; submit / release / cancel / delete / send are not exposed. - Readiness: `storebound scan` (offline rejection risks in the project files), `storebound check` (offline), `storebound validate` (read-only, both stores). - Every endpoint: `storebound api apple|play ` (skill store-api). GETs run; writes are dry unless the owner adds `--yes` in a terminal; MCP has the read-only `api_get`. diff --git a/README.md b/README.md index 71033f6..2aa298b 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,12 @@ in-app products and prices, the compliance forms, even the consoles that have no [![npm](https://img.shields.io/npm/v/storebound?label=npm&color=cb3837)](https://www.npmjs.com/package/storebound) [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![version 0.6.0](https://img.shields.io/badge/version-0.6.0-informational)](CHANGELOG.md) -[![runtime: Bun](https://img.shields.io/badge/runtime-Bun%201.1%2B-f9f1e1)](https://bun.sh) +[![runtime: Node 20+ or Bun 1.1+](https://img.shields.io/badge/runtime-Node%2020%2B%20%7C%20Bun%201.1%2B-f9f1e1)](#install-and-start) [![runtime dependencies: 0](https://img.shields.io/badge/runtime%20dependencies-0-brightgreen)](package.json) [![Agent Skills: 25](https://img.shields.io/badge/Agent%20Skills-25-8a63d2)](skills) [![MCP: 2026-07-28](https://img.shields.io/badge/MCP-2026--07--28-black)](docs/mcp.md) -**Website: [storebound.dev](https://storebound.dev) · Docs: [docs/](docs/README.md) · `bunx storebound init`** +**Website: [storebound.dev](https://storebound.dev) · Docs: [docs/](docs/README.md) · `npx storebound init`** [![The storebound teaser: from an empty folder to Submit for review in under a minute (opens the video on storebound.dev)](https://git.crt.fyi/CRT/storebound/raw/branch/main/site/img/teaser-poster.webp)](https://storebound.dev/#teaser) @@ -80,16 +80,18 @@ Per-tool setup: [docs/getting-started.md](docs/getting-started.md#install-for-yo ## Install and start -You need [Bun](https://bun.sh) 1.1+ (and `ffmpeg` for icons, screenshots and video). +You need [Node](https://nodejs.org) 20+ or [Bun](https://bun.sh) 1.1+ (and `ffmpeg` for icons, screenshots and video). ```sh cd my-app -bun add -d storebound playwright -bunx storebound init # --profile app (default) | subscription-app | game -bunx storebound install --agent claude # or codex, cursor, gemini, copilot, windsurf, opencode -bunx storebound check # offline: config, limits, Data safety, products, files +npm i -D storebound playwright # or: bun add -d storebound playwright +npx storebound init # --profile app (default) | subscription-app | game (Bun: bunx storebound init) +npx storebound install --agent claude # or codex, cursor, gemini, copilot, windsurf, opencode +npx storebound check # offline: config, limits, Data safety, products, files ``` +Every `npx storebound ...` below works as `bunx storebound ...` too. + Then ask your agent: "take my app to Google Play and the App Store". Claude Code plugin, credentials, coming from fastlane: [docs/getting-started.md](docs/getting-started.md). Desktop app: [storebound.dev/#download](https://storebound.dev/#download). diff --git a/bin/storebound b/bin/storebound index 76275d1..f2c206a 100755 --- a/bin/storebound +++ b/bin/storebound @@ -1,4 +1,8 @@ #!/bin/sh # storebound launcher. On PATH while the Claude Code plugin is enabled; or symlink it anywhere. +# Bun runs the sources; without Bun, Node 20+ runs the npm bundle (dist/storebound.mjs, in the npm package). here=$(cd "$(dirname "$0")" && pwd -P) -exec bun "$here/../cli/index.ts" "$@" +if command -v bun >/dev/null 2>&1; then exec bun "$here/../cli/index.ts" "$@"; fi +if [ -f "$here/../dist/storebound.mjs" ] && command -v node >/dev/null 2>&1; then exec node "$here/../dist/storebound.mjs" "$@"; fi +echo "storebound: needs Bun (https://bun.sh) or Node 20+ with the npm package (npx storebound)" >&2 +exit 127 diff --git a/cli/ads/client.ts b/cli/ads/client.ts index 65561fb..dbe25cc 100644 --- a/cli/ads/client.ts +++ b/cli/ads/client.ts @@ -9,6 +9,7 @@ import { createPrivateKey, type KeyObject } from 'node:crypto'; import { credentialValue, inline } from '../lib/credentials'; import { ADS_API, adsHeaders, type AdsCreds, clientSecret, type J, TOKEN_URL, tokenForm } from '../lib/apple-ads'; import { errorBody } from '../lib/http'; +import * as rt from '../lib/rt'; export const CRED_NAMES = ['APPLE_ADS_CLIENT_ID', 'APPLE_ADS_TEAM_ID', 'APPLE_ADS_KEY_ID', 'APPLE_ADS_PRIVATE_KEY'] as const; @@ -34,7 +35,7 @@ export function adsClient(root: string, o: { sleep?: (ms: number) => Promise Bun.sleep(ms)); + const sleep = o.sleep ?? ((ms: number) => rt.sleep(ms)); let cached = { token: '', until: 0 }; async function token(): Promise { diff --git a/cli/analytics.ts b/cli/analytics.ts index fb7f431..58b139d 100644 --- a/cli/analytics.ts +++ b/cli/analytics.ts @@ -21,6 +21,7 @@ import { STORAGE_READ_SCOPE } from './lib/google-auth'; import { done, log, printJson, warn } from './lib/log'; import { pct, textTable } from './lib/table'; import { googleClient } from './play/google'; +import * as rt from './lib/rt'; const TAG = 'analytics'; const day = (d: Date) => d.toISOString().slice(0, 10); @@ -42,7 +43,7 @@ async function reportRows(c: Asc, report: J, start: string, tag: string): Promis const r = await fetch(seg.attributes.url); if (!r.ok) throw new Error(`segment ${seg.id}: HTTP ${r.status}`); const gz = new Uint8Array(await r.arrayBuffer()); - const raw = gz[0] === 0x1f && gz[1] === 0x8b ? Bun.gunzipSync(gz) : gz; + const raw = gz[0] === 0x1f && gz[1] === 0x8b ? rt.gunzipSync(gz) : gz; // Apple documents an md5 checksum per segment without saying of which bytes: accept the file or its content. if (seg.attributes.checksum && !checksumOk(gz, seg.attributes.checksum) && !checksumOk(raw, seg.attributes.checksum)) throw new Error(`segment ${seg.id}: md5 checksum mismatch`); rows.push(...parseReport(decodeText(raw))); @@ -145,7 +146,7 @@ export async function run(ctx: Ctx): Promise { const series = mergeSeries(snaps); const first = [series.apple[0]?.date, series.play[0]?.date].filter(Boolean).sort()[0] ?? snaps[0]!.window.start; const paths = a.list('paths').length ? a.list('paths') : ['store', 'storebound.config.ts', ...(config.shots?.copy ? [config.shots.copy] : [])]; - const g = Bun.spawnSync(['git', 'log', `--since=${first}`, '--date=short', '--format=%x1e%h%x1f%ad%x1f%s', '--name-only', '--', ...paths], { cwd: root, stdout: 'pipe', stderr: 'pipe' }); + const g = rt.spawnSync(['git', 'log', `--since=${first}`, '--date=short', '--format=%x1e%h%x1f%ad%x1f%s', '--name-only', '--', ...paths], { cwd: root, stdout: 'pipe', stderr: 'pipe' }); if (g.exitCode !== 0) throw new Error(`git log: ${g.stderr.toString().trim()}`); const events = parseGitLog(g.stdout.toString()).reverse(); log(TAG, `${snaps.length} snapshot(s), ${series.apple.length} App Store day(s), ${series.play.length} Play day(s), ${events.length} store change(s) since ${first}`); diff --git a/cli/api/client.ts b/cli/api/client.ts index 8ddd2aa..adda632 100644 --- a/cli/api/client.ts +++ b/cli/api/client.ts @@ -10,6 +10,7 @@ import { PLAY_APIS, PLAY_OPS } from './play.gen'; import type { AppleBody, AppleOp, AppleParams, AppleResponse, ParamValue, PlayBody, PlayOp, PlayParams, PlayResponse } from './types'; import type { Store } from './gate'; import { errorBody } from '../lib/http'; +import * as rt from '../lib/rt'; export interface QueryParam { name: string; required: boolean; repeated: boolean } @@ -203,7 +204,7 @@ const short = (url: string) => url.replace(/^https:\/\/[^/]+/, '').replace(/\?.* export async function send(req: BuiltRequest, auth: Auth, o: SendOptions = {}): Promise { const doFetch = o.fetch ?? ((u: string, i: RequestInit) => fetch(u, i)); - const sleep = o.sleep ?? ((ms: number) => Bun.sleep(ms)); + const sleep = o.sleep ?? ((ms: number) => rt.sleep(ms)); const attempts = o.attempts ?? 5; for (let attempt = 1; ; attempt++) { const headers: Record = { Authorization: await auth(req) }; diff --git a/cli/apple/asc.ts b/cli/apple/asc.ts index e5f71d3..e9202ae 100644 --- a/cli/apple/asc.ts +++ b/cli/apple/asc.ts @@ -5,6 +5,7 @@ import { readFileSync } from 'node:fs'; import { ascKey } from '../lib/credentials'; import { errorBody } from '../lib/http'; import { AmbiguousWriteError, retrySafe } from '../lib/upsert'; +import * as rt from '../lib/rt'; const b64 = (b: Buffer | string): string => Buffer.from(b).toString('base64url'); /** One App Store Connect API call may take at most this long (then retried when safe, else AmbiguousWriteError). */ @@ -72,12 +73,12 @@ export async function ascClient(root: string, bundleId: string, appIdHint?: stri signal: AbortSignal.timeout(ASC_REQUEST_TIMEOUT_MS), // a hung connection is a network error, never a silent wait }); } catch (e) { - if (safe && attempt < 5) { await Bun.sleep(2000 * attempt); return asc(method, path, body, attempt + 1); } + if (safe && attempt < 5) { await rt.sleep(2000 * attempt); return asc(method, path, body, attempt + 1); } if (!safe) throw new AmbiguousWriteError(`${method} ${path} -> no answer (${(e as Error).message})`); throw e; } const t = await r.text(); - if ((r.status === 429 || (r.status >= 500 && safe)) && attempt < 5) { await Bun.sleep(2000 * attempt); return asc(method, path, body, attempt + 1); } + if ((r.status === 429 || (r.status >= 500 && safe)) && attempt < 5) { await rt.sleep(2000 * attempt); return asc(method, path, body, attempt + 1); } if (r.status >= 500 && !safe) throw new AmbiguousWriteError(`${method} ${path} -> ${r.status} ${errorBody(t)}`, r.status); if (r.status === 404 && method === 'DELETE' && attempt > 1) return {}; if (!r.ok) throw new Error(`${method} ${path} -> ${r.status} ${errorBody(t)}`); @@ -89,7 +90,7 @@ export async function ascClient(root: string, bundleId: string, appIdHint?: stri for (let attempt = 1; ; attempt++) { const r = await fetch('https://api.appstoreconnect.apple.com' + path, { headers: { Authorization: 'Bearer ' + jwt(), Accept: accept } }); const t = await r.text(); - if ((r.status >= 500 || r.status === 429) && attempt < 5) { await Bun.sleep(2000 * attempt); continue; } + if ((r.status >= 500 || r.status === 429) && attempt < 5) { await rt.sleep(2000 * attempt); continue; } if (!r.ok) throw new Error(`GET ${path} -> ${r.status} ${errorBody(t, 800)}`); return t ? JSON.parse(t) : {}; } @@ -100,7 +101,7 @@ export async function ascClient(root: string, bundleId: string, appIdHint?: stri async function bytes(path: string, accept = 'application/a-gzip'): Promise { for (let attempt = 1; ; attempt++) { const r = await fetch('https://api.appstoreconnect.apple.com' + path, { headers: { Authorization: 'Bearer ' + jwt(), Accept: accept } }); - if ((r.status >= 500 || r.status === 429) && attempt < 5) { await r.body?.cancel(); await Bun.sleep(2000 * attempt); continue; } + if ((r.status >= 500 || r.status === 429) && attempt < 5) { await r.body?.cancel(); await rt.sleep(2000 * attempt); continue; } if (r.status === 404) { await r.body?.cancel(); return null; } if (!r.ok) throw new Error(`GET ${path.replace(/\?.*$/, '')} -> ${r.status} ${errorBody(await r.text(), 600)}`); return new Uint8Array(await r.arrayBuffer()); diff --git a/cli/apple/build.ts b/cli/apple/build.ts index 5a845a6..c8b096a 100644 --- a/cli/apple/build.ts +++ b/cli/apple/build.ts @@ -26,6 +26,7 @@ import { archiveArgs, type AscAuth, exportArgs, flutterConfigArgs, ipaInfo, pick import { bundleTrackingIssues, ipaManifests } from '../lib/privacy-files'; import { ascClient } from './asc'; import { findBundleId } from './signing'; +import * as rt from '../lib/rt'; const TAG = 'build'; @@ -54,7 +55,7 @@ export function installedProfiles(bundleId: string, teamId: string | null, home async function runLogged(argv: string[], cwd: string, logFile: string, show: string, env?: Record): Promise { log(TAG, `$ ${show}`); log(TAG, `full output: ${logFile}`); - const p = Bun.spawn(argv, { cwd, stdout: 'pipe', stderr: 'pipe', env: { ...process.env, LANG: 'en_US.UTF-8', LC_ALL: 'en_US.UTF-8', ...env } }); + const p = rt.spawn(argv, { cwd, stdout: 'pipe', stderr: 'pipe', env: { ...process.env, LANG: 'en_US.UTF-8', LC_ALL: 'en_US.UTF-8', ...env } }); const chunks: string[] = []; const keep = /(\berror:|\*\* (ARCHIVE|EXPORT|BUILD) (SUCCEEDED|FAILED) \*\*|requires a provisioning profile|No signing certificate|Signing for .* requires|Exported .* to)/; const each = (line: string): void => { chunks.push(line); if (keep.test(line)) log(TAG, line.trim().slice(0, 300)); }; @@ -137,7 +138,7 @@ export async function run(ctx: Ctx): Promise { done(TAG, 'build planned (dry run)'); return; } - if (process.platform !== 'darwin' || !Bun.which('xcodebuild')) throw new Error('apple build needs macOS with Xcode (xcode-select --install is not enough: the full Xcode app)'); + if (process.platform !== 'darwin' || !rt.which('xcodebuild')) throw new Error('apple build needs macOS with Xcode (xcode-select --install is not enough: the full Xcode app)'); const web = await webStep({ ...webOpts, dry: false }); for (const n of target.notes) log(TAG, n); mkdirSync(out, { recursive: true }); diff --git a/cli/apple/iap-review.ts b/cli/apple/iap-review.ts index 12a59d5..a32b296 100644 --- a/cli/apple/iap-review.ts +++ b/cli/apple/iap-review.ts @@ -17,6 +17,7 @@ import { done, log, warn } from '../lib/log'; import { reportMissing } from '../lib/store-lag'; import { IMAGE_SPECS, imageInfo } from '../shots/spec'; import { ascClient, md5 } from './asc'; +import * as rt from '../lib/rt'; /** Every iPhone / iPad screenshot size App Store Connect accepts (portrait and landscape). */ export const REVIEW_SHOT_SIZES: [number, number][] = Object.values(IMAGE_SPECS) @@ -70,7 +71,7 @@ export async function run(ctx: Ctx): Promise { // Apple processes the asset after the commit: read the state back (COMPLETE, or FAILED with the reason) let after: Shot | undefined, state = 'UPLOAD_COMPLETE'; for (let i = 0; i < 10; i++) { - await Bun.sleep(i ? 3000 : 1500); + await rt.sleep(i ? 3000 : 1500); after = await shotOf(p.id); state = stateOf(after); if (state === 'COMPLETE' || state === 'FAILED') break; } diff --git a/cli/apple/iap.ts b/cli/apple/iap.ts index 8d95f08..6a580f6 100644 --- a/cli/apple/iap.ts +++ b/cli/apple/iap.ts @@ -17,6 +17,7 @@ import { LAG_HINT, reportMissing } from '../lib/store-lag'; import { appleTerritory, nearestPrices, samePrice } from '../lib/money'; import { createOnce, findOrCreate, getById } from '../lib/upsert'; import { ascClient, type J } from './asc'; +import * as rt from '../lib/rt'; /** Product type -> InAppPurchaseType (POST /v2/inAppPurchases). */ export const APPLE_IAP_TYPE = { consumable: 'CONSUMABLE', non_consumable: 'NON_CONSUMABLE', non_renewing: 'NON_RENEWING_SUBSCRIPTION' } as const; @@ -104,7 +105,7 @@ export async function run(ctx: Ctx): Promise { return; } if (created.length) { - await Bun.sleep(Number(process.env.STOREBOUND_LIST_SETTLE_MS ?? 3000)); + await rt.sleep(Number(process.env.STOREBOUND_LIST_SETTLE_MS ?? 3000)); const listed = (await c.all(`/v1/apps/${appId}/inAppPurchasesV2?limit=200`)).map((x: J) => x.attributes.productId as string); if (reportMissing('apple', 'new products', created, listed, { fail: false }).length) log('apple', 'wait a minute before apple iap-review, or it cannot see them'); } diff --git a/cli/apple/signing.ts b/cli/apple/signing.ts index 9dced53..c8a5dd0 100644 --- a/cli/apple/signing.ts +++ b/cli/apple/signing.ts @@ -24,6 +24,7 @@ import { textTable } from '../lib/table'; import { type Asc, ascClient, type J } from './asc'; import { describeReq, execute, type Req } from './jsonapi'; import { createOnce, isAmbiguous, upsertTiming } from '../lib/upsert'; +import * as rt from '../lib/rt'; const TAG = 'signing'; const USAGE = 'usage: storebound apple signing status | ensure [--dry] [--keychain [path]] [--no-install]'; @@ -43,8 +44,8 @@ function localKeys(root: string): Map { /** SHA-1s of code signing identities in the keychain search list (macOS; empty elsewhere). Read-only. */ function keychainIdentities(): Set { - if (process.platform !== 'darwin' || !Bun.which('security')) return new Set(); - const r = Bun.spawnSync(['security', 'find-identity', '-v', '-p', 'codesigning'], { stdout: 'pipe', stderr: 'pipe' }); + if (process.platform !== 'darwin' || !rt.which('security')) return new Set(); + const r = rt.spawnSync(['security', 'find-identity', '-v', '-p', 'codesigning'], { stdout: 'pipe', stderr: 'pipe' }); return new Set(parseIdentities(r.stdout.toString()).map(i => i.sha1)); } @@ -77,7 +78,7 @@ function keychainImport(keyFile: string, cerFile: string, keychain: string | nul if (process.platform !== 'darwin') throw new Error('--keychain: macOS only (on other systems keep the key in .storebound/signing)'); const kc = keychain ? ['-k', keychain] : []; for (const cmd of [['security', 'import', keyFile, ...kc, '-t', 'priv', '-f', 'openssl', '-T', '/usr/bin/codesign', '-T', '/usr/bin/productbuild'], ['security', 'import', cerFile, ...kc, '-t', 'cert']]) { - const r = Bun.spawnSync(cmd, { stdout: 'pipe', stderr: 'pipe' }); + const r = rt.spawnSync(cmd, { stdout: 'pipe', stderr: 'pipe' }); const msg = (r.stdout.toString() + r.stderr.toString()).trim(); if (r.exitCode !== 0 && !/already exists/i.test(msg)) throw new Error(`security import failed: ${msg.slice(0, 300)}`); } diff --git a/cli/apple/status.ts b/cli/apple/status.ts index ca87178..4167e18 100644 --- a/cli/apple/status.ts +++ b/cli/apple/status.ts @@ -16,6 +16,7 @@ import { classifyReviewState, nextPollDelay, parseDuration, WAIT_EXIT } from '.. import { EMPTY_DRAFT } from '../lib/review-submit'; import { type Asc, ascClient, type J } from './asc'; import { notCancellable, readItems, type SubItem } from './submission'; +import * as rt from '../lib/rt'; async function waitReview(ctx: Ctx, c: Asc, appId: string): Promise { const want = ctx.args.get('version'); @@ -36,7 +37,7 @@ async function waitReview(ctx: Ctx, c: Asc, appId: string): Promise { delay = nextPollDelay(delay); if (Date.now() + delay > deadline) { log('apple', 'timeout, still in review'); process.exit(WAIT_EXIT.timeout); } log('apple', `next check in ${Math.round(delay / 1000)}s`); - await Bun.sleep(delay); + await rt.sleep(delay); } } diff --git a/cli/apple/submission.ts b/cli/apple/submission.ts index 96d7a5e..f182332 100644 --- a/cli/apple/submission.ts +++ b/cli/apple/submission.ts @@ -7,6 +7,7 @@ import { createOnce, findOrCreate } from '../lib/upsert'; import { EMPTY_DRAFT, resolveHint, type ResolveScope, toResolve } from '../lib/review-submit'; import type { Asc, J } from './asc'; import { one } from './jsonapi'; +import * as rt from '../lib/rt'; /** The draft review submission of the app (READY_FOR_REVIEW, iOS), or null. */ export async function findDraft(c: Asc, appId: string): Promise { @@ -139,7 +140,7 @@ export const submitTiming = { delaysMs: [15_000, 30_000, 60_000, 120_000], timeoutMs: 10 * 60_000, heartbeatMs: 30_000, - sleep: (ms: number): Promise => (ms > 0 ? Bun.sleep(ms) : Promise.resolve()), + sleep: (ms: number): Promise => (ms > 0 ? rt.sleep(ms) : Promise.resolve()), now: (): number => Date.now(), }; diff --git a/cli/apple/testflight.ts b/cli/apple/testflight.ts index 2802809..3adc25e 100644 --- a/cli/apple/testflight.ts +++ b/cli/apple/testflight.ts @@ -31,6 +31,7 @@ import { import { type Asc, ascClient, type J } from './asc'; import { describeReq, execute } from './jsonapi'; import { findOrCreate, getById } from '../lib/upsert'; +import * as rt from '../lib/rt'; const TAG = 'testflight'; const USAGE = 'usage: storebound apple testflight status | groups [sync] | testers | what-to-test --build N | info | distribute --build N --group | wait --build N | expire --keep N | --older-than D'; @@ -68,7 +69,7 @@ export async function waitForBuild(c: Asc, appId: string, number: string, o: { m if (o.once) return result(false); delay = waitDelay(delay); if (Date.now() + delay - started > o.timeoutMs) return result(true); - await Bun.sleep(delay); + await rt.sleep(delay); } } finally { beat?.stop(); } } diff --git a/cli/apple/upload.ts b/cli/apple/upload.ts index 9e3fd9f..873b788 100644 --- a/cli/apple/upload.ts +++ b/cli/apple/upload.ts @@ -29,11 +29,12 @@ import { describeReq } from './jsonapi'; import { createOnce } from '../lib/upsert'; import { elapsed, heartbeat, type HeartbeatOptions, mb, share, withHeartbeat } from '../lib/progress'; import { pump } from '../lib/runner'; +import * as rt from '../lib/rt'; const TAG = 'upload'; function md5File(path: string): string { - const h = new Bun.CryptoHasher('md5'); + const h = rt.hasher('md5'); const fd = openSync(path, 'r'); try { const buf = Buffer.alloc(8 * 1048576); @@ -81,7 +82,7 @@ export function uploadTimeout(sent: number, size: number, partsDone: number, par export async function sendParts(o: SendPartsOptions): Promise { const say = o.say ?? ((m: string) => log(TAG, m)); const doFetch = o.fetch ?? ((u: string, i: RequestInit) => fetch(u, i)); - const sleep = o.sleep ?? Bun.sleep; + const sleep = o.sleep ?? rt.sleep; const now = o.now ?? Date.now; const started = now(); const n = o.ops.length; @@ -159,7 +160,7 @@ async function apiUpload(c: Asc, appId: string, ipa: string, info: ReturnType deadline) throw Object.assign(new Error(`upload still ${st.state ?? 'pending'} at the timeout; the file is sent, App Store Connect is still checking it: storebound apple testflight wait --build ${info.build} --version ${info.version} goes on waiting`), { exit: WAIT_EXIT.timeout }); delay = nextPollDelay(delay, { first: 15_000, max: 60_000 }); log(TAG, `upload ${st.state ?? 'pending'}, next check in ${Math.round(delay / 1000)} s`); - await Bun.sleep(delay); + await rt.sleep(delay); } } @@ -172,14 +173,14 @@ const uploadsOf = (c: Asc, appId: string, info: { version: string; build: string * stopped at the deadline (exit 7 with the hint). */ async function altoolUpload(root: string, ipa: string, deadline: number, o: HeartbeatOptions = {}): Promise { - if (process.platform !== 'darwin' || !Bun.which('xcrun')) throw new Error('--via altool needs macOS with Xcode; use --via api elsewhere'); + if (process.platform !== 'darwin' || !rt.which('xcrun')) throw new Error('--via altool needs macOS with Xcode; use --via api elsewhere'); const k = ascKeyFile(root); try { const argv = altoolArgs(ipa, k); const size = statSync(ipa).size; log(TAG, `$ ${showCmd(argv, k)}`); log(TAG, `uploading ${mb(size)} with altool (it reports only at the end)`); - const p = Bun.spawn(argv, { stdout: 'pipe', stderr: 'pipe' }); + const p = rt.spawn(argv, { stdout: 'pipe', stderr: 'pipe' }); let timedOut = false; const kill = setTimeout(() => { timedOut = true; p.kill(); }, Math.max(0, deadline - Date.now())); const lines: string[] = []; diff --git a/cli/apps.ts b/cli/apps.ts index 9df9679..a5bce31 100644 --- a/cli/apps.ts +++ b/cli/apps.ts @@ -15,6 +15,7 @@ import { appleCheck, type Bytes, dataUrl, type Get, listAppleApps, listPlayApps, import { textTable } from './lib/table'; import { ascClient } from './apple/asc'; import { REPORTING_API } from './play/google'; +import * as rt from './lib/rt'; type Store = 'apple' | 'play'; @@ -48,7 +49,7 @@ function googleGet(token: () => Promise, base: string): Get { for (let attempt = 0; ; attempt++) { const r = await fetch(path.startsWith('http') ? path : base + path, { headers: { Authorization: 'Bearer ' + (await token()) } }); const t = await r.text(); - if ((r.status === 429 || r.status >= 500) && attempt < 3) { await Bun.sleep(1500 * (attempt + 1)); continue; } + if ((r.status === 429 || r.status >= 500) && attempt < 3) { await rt.sleep(1500 * (attempt + 1)); continue; } if (!r.ok) throw new Error(`GET ${path.replace(/^https:\/\/[^/]+/, '')} -> ${r.status} ${errorBody(t, 800)}`); return t ? JSON.parse(t) : {}; } diff --git a/cli/aso.ts b/cli/aso.ts index bd2a165..163432c 100644 --- a/cli/aso.ts +++ b/cli/aso.ts @@ -38,6 +38,7 @@ import type { Ctx } from './lib/ctx'; import { done, log, printJson, warn } from './lib/log'; import { appleSections } from './lib/project-files'; import { textTable } from './lib/table'; +import * as rt from './lib/rt'; const TAG = 'aso'; const SUBS = ['rank', 'competitors', 'suggest', 'audit']; @@ -64,7 +65,7 @@ function snapshots(dir: string): { file: string; data: T }[] { async function searchAll

(plan: P[], limit: number, delay: number, each: (p: P, results: SearchApp[], i: number) => void): Promise { let read = 0; for (const [i, p] of plan.entries()) { - if (i) await Bun.sleep(delay); + if (i) await rt.sleep(delay); const r = await fetch(searchUrl(p.keyword, p.storefront, limit), { headers: { Accept: 'application/json' } }); if (r.status === 403 || r.status === 429) { warn(TAG, `Search API answered ${r.status} (rate limit): stopped after ${read} of ${plan.length}; try later or with a larger --delay`); break; } if (!r.ok) { warn(TAG, `${p.storefront} "${p.keyword}": HTTP ${r.status}`); continue; } diff --git a/cli/console/accounts.ts b/cli/console/accounts.ts index c557509..0e9406b 100644 --- a/cli/console/accounts.ts +++ b/cli/console/accounts.ts @@ -26,6 +26,7 @@ import type { ShipConfig } from '../lib/config'; import type { FormId } from '../lib/console-answers'; import { hasToken, type Name, NAV_BASES, type NavBases, type NavStep, navPlan, playAppIdOf, playDeveloperId, switchAllowed } from '../lib/console-forms'; import { ACTION_MS, accName, findAny, type Locator, navClick, navigate, WAITS, type NavResult, type Page, poll, realRole, safeClick, signInPage, SignInSeen, type Stop, vis } from './fill'; +import * as rt from '../lib/rt'; // ---- the cache ---- @@ -235,7 +236,7 @@ export async function switchTeam(page: Page, name: string, listed: string[], ms: if (!switchAllowed(real, role, listed)) { await closeMenu(page); return false; } await it.loc.click({ timeout: ACTION_MS }); await page.waitForLoadState('domcontentloaded', { timeout: ms }).catch(() => { /* same page, or slow: read what is there */ }); - await Bun.sleep(WAITS.settleMs); // the switched team's page settles + await rt.sleep(WAITS.settleMs); // the switched team's page settles return true; } diff --git a/cli/console/fill.ts b/cli/console/fill.ts index 2b77325..751f017 100644 --- a/cli/console/fill.ts +++ b/cli/console/fill.ts @@ -39,6 +39,7 @@ import { openAcross, withAccountIds } from './accounts'; import { appleAppId, withAppleAppId } from './app-id'; import { bareUrl } from './debug'; import { type ConsoleBrowser, GOOGLE_REJECTED, googleRejected, openConsoleBrowser, parseUseChromeSession } from './session'; +import * as rt from '../lib/rt'; /* eslint-disable @typescript-eslint/no-explicit-any */ export type Page = any; @@ -686,7 +687,7 @@ export async function poll(ms: number, fn: () => Promise, stop?: St try { const r = await fn(); if (r !== null) return r; } catch { /* navigating */ } if (Date.now() >= end) return null; if (page && WAITS.quietMs > 0 && await quietFor(page) >= WAITS.quietMs) return null; - await Bun.sleep(WAITS.pollMs); + await rt.sleep(WAITS.pollMs); } } @@ -989,7 +990,7 @@ export async function fillSession(b: ConsoleBrowser, o: FillSessionOpts): Promis // the type's last step was saved: what comes next is the App Privacy page again const closing = !!t && def.sections.every((x, i) => x.type !== t || done.has(i)); progress(t && !closing ? `the "${t}" dialog step after "${name}"` : `the page after "${t ? `${t}: ` : ''}${name}"`); - if (o.debugEveryStep) { const p = active(); if (p) void Bun.sleep(tick).then(() => dump(p, t && !/^Set Up /.test(name) ? `${t} after ${name}` : `after ${name}`)).catch(() => { /* gone */ }); } + if (o.debugEveryStep) { const p = active(); if (p) void rt.sleep(tick).then(() => dump(p, t && !/^Set Up /.test(name) ? `${t} after ${name}` : `after ${name}`)).catch(() => { /* gone */ }); } }; const meta = formMeta(form, config), res = consoleResult(form, config); const active = (): Page | undefined => b.pages().at(-1); @@ -1056,7 +1057,7 @@ export async function fillSession(b: ConsoleBrowser, o: FillSessionOpts): Promis } } else if (p && reached(p)) { if (++ok >= 2) { if (told) say('signed in'); lastWhy = ''; return true; } } else ok = 0; - await Bun.sleep(tick); + await rt.sleep(tick); } return false; } @@ -1297,13 +1298,13 @@ export async function fillSession(b: ConsoleBrowser, o: FillSessionOpts): Promis while (!b.isClosed() && !finished()) { const p = active(); if (!await watch(p)) break; - if (!p) { await Bun.sleep(tick); continue; } + if (!p) { await rt.sleep(tick); continue; } const w = await signInReason(p).catch(() => ({ rule: 'the page could not be read', url: bareUrl(p.url()) })); if (w) { sayWhy(w); if (!signedOut && plan.length) signInHint(); signedOut = true; - await Bun.sleep(tick); continue; + await rt.sleep(tick); continue; } // sign-in finished while watching: open the form again from step 1 (nothing filled yet) if (signedOut) { @@ -1328,13 +1329,13 @@ export async function fillSession(b: ConsoleBrowser, o: FillSessionOpts): Promis } } let s: FillStep; - try { s = await fillPage(p, def, pageAnswers, done, false, currentType); } catch { await Bun.sleep(tick); continue; } // navigating + try { s = await fillPage(p, def, pageAnswers, done, false, currentType); } catch { await rt.sleep(tick); continue; } // navigating if (s.section !== null) { // a page still loading can miss a field for a moment: only a miss that lasts (3 rounds, at least WAITS.missMs) is a UI change - if (s.fallback && (++misses < 3 || Date.now() - firstMiss < WAITS.missMs)) { if (misses === 1) firstMiss = Date.now(); await Bun.sleep(tick); continue; } + if (s.fallback && (++misses < 3 || Date.now() - firstMiss < WAITS.missMs)) { if (misses === 1) firstMiss = Date.now(); await rt.sleep(tick); continue; } misses = 0; // the miss lasts: fill every field that was found, leave the missing ones to the owner (only all-missing is a UI change) - if (s.fallback) { try { s = await fillPage(p, def, pageAnswers, done, true, currentType); } catch { await Bun.sleep(tick); continue; } } + if (s.fallback) { try { s = await fillPage(p, def, pageAnswers, done, true, currentType); } catch { await rt.sleep(tick); continue; } } if (!s.fallback && s.missing.length) warn(TAG, `${s.title}: could not find ${s.missing.map(id => labels.get(id) ?? id).join(', ')} on the page (the console UI may have changed): filling ${s.missing.length === 1 ? 'it' : 'them'} is left to ${agentPart(form)}`); if (s.fallback) { fallback = true; @@ -1367,7 +1368,7 @@ export async function fillSession(b: ConsoleBrowser, o: FillSessionOpts): Promis continue; // more of the form may be on this page } if (waitFor) { - if (!await answered(waitFor.pending)) { await Bun.sleep(tick); continue; } + if (!await answered(waitFor.pending)) { await rt.sleep(tick); continue; } say(`"${waitFor.title}" answered: storebound goes on`); progress(`the next part after "${waitFor.title}"`); waitFor = null; @@ -1376,7 +1377,7 @@ export async function fillSession(b: ConsoleBrowser, o: FillSessionOpts): Promis pendingAdvance = false; const moved = await advance(p).catch(() => false); if (moved === 'draft') pendingAdvance = true; // Next follows the saved draft - if (moved) { await Bun.sleep(tick); continue; } + if (moved) { await rt.sleep(tick); continue; } } // an App Privacy dialog step with nothing to answer (the "Let's define two important terms" page between linked and // tracking): no inputs, only Back / Cancel / Next -> Next is navigation @@ -1386,10 +1387,10 @@ export async function fillSession(b: ConsoleBrowser, o: FillSessionOpts): Promis if (info && (infoRounds += 1) >= 2) { infoRounds = 0; const next = await controlWhere(info, FLOW_NAMES.next, () => true, dataTypes); - if (next && await flowClick(next, form, dataTypes, say, clicked)) { say('an information-only step of the dialog: storebound pressed Next'); progress('the next step of the dialog'); await Bun.sleep(tick); continue; } + if (next && await flowClick(next, form, dataTypes, say, clicked)) { say('an information-only step of the dialog: storebound pressed Next'); progress('the next step of the dialog'); await rt.sleep(tick); continue; } } if (privacy) await privacyStep(p).catch(() => { /* navigating */ }); - await Bun.sleep(tick); + await rt.sleep(tick); } if (timedOut) { say(`nothing storebound knows happened for ${mins(idleMs)}: it stops here and closes the window${o.debugDir && dumps ? ` (debug: ${o.debugDir})` : ''}. Answers saved as drafts stay; run it again when the page is fixed.`); @@ -1403,7 +1404,7 @@ export async function fillSession(b: ConsoleBrowser, o: FillSessionOpts): Promis // the owner reviews and presses the final button, then closes the window: a reminder every 10 heartbeats, at most finalMs const end = Date.now() + finalMs; while (!b.isClosed() && Date.now() < end) { - await Promise.race([b.closed, Bun.sleep(Math.min(10 * heartbeatMs, Math.max(0, end - Date.now())))]); + await Promise.race([b.closed, rt.sleep(Math.min(10 * heartbeatMs, Math.max(0, end - Date.now())))]); if (!b.isClosed() && Date.now() < end) say('still open: storebound waits for you to review, press the final button and close the window'); } if (!b.isClosed()) { say(`the window has been open for ${mins(finalMs)} after the fill: storebound closes it now`); await b.close(); } diff --git a/cli/console/session.ts b/cli/console/session.ts index bd8e99a..0fd8cea 100644 --- a/cli/console/session.ts +++ b/cli/console/session.ts @@ -43,6 +43,7 @@ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync import { homedir, tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { chromiumFor, installedBrowsers, type LaunchDeps, persistentExecutable, profileHolder, sandboxArgs } from '../shots/browser'; +import * as rt from '../lib/rt'; /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -135,15 +136,15 @@ export function cookieApplies(hostKey: string, hosts: string[] = CONSOLE_HOSTS): /** Deletes every cookie of the copied DB that the consoles never get. Returns counts only (never values or names). */ export async function filterCookies(dbPath: string, hosts: string[] = CONSOLE_HOSTS): Promise<{ kept: number; removed: number }> { - const { Database } = await import('bun:sqlite'); - const db = new Database(dbPath); + // bun:sqlite under Bun, node:sqlite on Node 22.5+; without SQLite (Node 20) this throws and the caller says so + const db = await rt.openSqlite(dbPath); try { - const keys = (db.query('SELECT DISTINCT host_key AS h FROM cookies').all() as { h: string }[]).map(r => r.h); + const keys = db.all<{ h: string }>('SELECT DISTINCT host_key AS h FROM cookies').map(r => r.h); const drop = keys.filter(k => !cookieApplies(k, hosts)); - const count = (db.query('SELECT COUNT(*) AS n FROM cookies').get() as { n: number }).n; - const del = db.prepare('DELETE FROM cookies WHERE host_key = ?'); - db.transaction(() => { for (const k of drop) del.run(k); })(); - const kept = (db.query('SELECT COUNT(*) AS n FROM cookies').get() as { n: number }).n; + const count = Number(db.get<{ n: number }>('SELECT COUNT(*) AS n FROM cookies')!.n); + db.exec('BEGIN'); + try { for (const k of drop) db.run('DELETE FROM cookies WHERE host_key = ?', k); db.exec('COMMIT'); } catch (e) { db.exec('ROLLBACK'); throw e; } + const kept = Number(db.get<{ n: number }>('SELECT COUNT(*) AS n FROM cookies')!.n); db.exec('VACUUM'); return { kept, removed: count - kept }; } finally { db.close(); } @@ -163,7 +164,7 @@ export async function copySession(src: string, opts: { profile?: string; hosts?: const dir = mkdtempSync(join(opts.tmpRoot ?? tmpdir(), 'storebound-chrome-')); const cleanup = () => { // Chrome may still write a file while it exits: retry, and only stop once the folder is gone - for (let i = 0; i < 5 && existsSync(dir); i++) { try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 }); } catch { Bun.sleepSync(100); } } + for (let i = 0; i < 5 && existsSync(dir); i++) { try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 }); } catch { rt.sleepSync(100); } } }; try { for (const [s, d] of files) { mkdirSync(dirname(join(dir, d)), { recursive: true }); copyFileSync(join(src, s), join(dir, d)); } @@ -234,7 +235,7 @@ export interface OpenOpts { /** Runs a graceful close, at most `ms`; errors (already closed) are ignored. */ async function gracefully(fn: () => Promise, ms = 10_000): Promise { - await Promise.race([Promise.resolve().then(fn).catch(() => { /* already closed */ }), Bun.sleep(ms)]); + await Promise.race([Promise.resolve().then(fn).catch(() => { /* already closed */ }), rt.sleep(ms)]); } /** @@ -284,14 +285,14 @@ async function startOwnChrome(root: string, mode: 'copy' | 'profile', exe: { lab const portFile = join(userDataDir, 'DevToolsActivePort'); rmSync(portFile, { force: true }); const args = consoleChromeArgs(userDataDir, { headless: o.headless ?? false }); - const proc = Bun.spawn([exe.path, ...args], { stdin: 'ignore', stdout: 'ignore', stderr: 'ignore' }); + const proc = rt.spawn([exe.path, ...args], { stdin: 'ignore', stdout: 'ignore', stderr: 'ignore' }); let exited = false; const gone = proc.exited.then(() => { exited = true; }); const end = Date.now() + (o.launchTimeoutMs ?? 30_000); let endpoint: string | null = null; - while (!endpoint && !exited && Date.now() < end) { endpoint = devToolsEndpoint(userDataDir); if (!endpoint) await Bun.sleep(100); } + while (!endpoint && !exited && Date.now() < end) { endpoint = devToolsEndpoint(userDataDir); if (!endpoint) await rt.sleep(100); } if (!endpoint) { - if (!exited) { proc.kill('SIGTERM'); await Promise.race([gone, Bun.sleep(5000)]); } + if (!exited) { proc.kill('SIGTERM'); await Promise.race([gone, rt.sleep(5000)]); } throw new Error(`${exe.label} did not start${exited ? ` (exit code ${proc.exitCode})` : ' in time'}`); } let browser: any; @@ -299,18 +300,18 @@ async function startOwnChrome(root: string, mode: 'copy' | 'profile', exe: { lab const pw = o.launchDeps?.pw ?? await chromiumFor(root); browser = await pw.connectOverCDP(endpoint, { timeout: Math.max(5000, end - Date.now()) }); } catch (e) { - proc.kill('SIGTERM'); await Promise.race([gone, Bun.sleep(5000)]); + proc.kill('SIGTERM'); await Promise.race([gone, rt.sleep(5000)]); throw new Error(`${exe.label}: could not attach (${(e as Error).message.split('\n')[0]})`, { cause: e }); } const context = browser.contexts()[0] ?? await browser.newContext(); const close = async () => { if (exited) return; await gracefully(async () => { const s = await browser.newBrowserCDPSession(); await s.send('Browser.close'); }); - await Promise.race([gone, Bun.sleep(10_000)]); - if (!exited) { proc.kill('SIGTERM'); await Promise.race([gone, Bun.sleep(5000)]); } + await Promise.race([gone, rt.sleep(10_000)]); + if (!exited) { proc.kill('SIGTERM'); await Promise.race([gone, rt.sleep(5000)]); } }; let closed = false; - const done = gone.then(async () => { closed = true; cleanup(); await Bun.sleep(300); cleanup(); }); + const done = gone.then(async () => { closed = true; cleanup(); await rt.sleep(300); cleanup(); }); const page = await ownTab(context); const watch = (p: any) => p.on('close', () => { if (!exited && context.pages().length === 0) void close(); }); for (const p of context.pages()) watch(p); diff --git a/cli/console/verify.ts b/cli/console/verify.ts index ce3e2f5..415c61c 100644 --- a/cli/console/verify.ts +++ b/cli/console/verify.ts @@ -12,9 +12,10 @@ import type { Ctx } from '../lib/ctx'; import { isPlanForm, PLAN_FORM_IDS } from '../lib/console-answers'; import { verifyAgeRatingApi, verifyAppRecordApi, type VerifyResult, verifyTable, verifyText } from '../lib/console-verify'; import { printJson } from '../lib/log'; +import * as rt from '../lib/rt'; async function readText(src: string): Promise { - if (src === '-' || src === 'true') return Bun.stdin.text(); + if (src === '-' || src === 'true') return rt.stdinText(); return readFileSync(src, 'utf8'); } diff --git a/cli/desktop.ts b/cli/desktop.ts index a5a1fcd..0ef23ea 100644 --- a/cli/desktop.ts +++ b/cli/desktop.ts @@ -13,6 +13,7 @@ import type { Args } from './lib/args'; import { CONFIG_NAMES } from './lib/config'; import { BUNDLE_ID, DOWNLOAD_LABEL, type Download, type DownloadKey, downloadsDir, downloadsFor, findInstalled, MANIFEST, type Manifest, manifestProblems, openCommand, openFolderLink, PAGE, PUBLIC_KEY } from './lib/desktop-dist'; import { verify } from './lib/minisign'; +import * as rt from './lib/rt'; type Fetch = (url: string) => Promise; @@ -61,7 +62,7 @@ export async function run(args: Args): Promise { const link = isApp ? openFolderLink(dir) : null; const cmd = openCommand(platform, installed, link); if (args.has('json')) { console.log(JSON.stringify({ installed, link, command: cmd }, null, 2)); return; } - const p = Bun.spawn(cmd, { stdio: ['ignore', 'ignore', 'pipe'] }); + const p = rt.spawn(cmd, { stdio: ['ignore', 'ignore', 'pipe'] }); if (platform === 'darwin') { const code = await p.exited; if (code !== 0) throw new Error(`open -b ${BUNDLE_ID} failed: ${(await new Response(p.stderr).text()).trim()}`); diff --git a/cli/doctor.ts b/cli/doctor.ts index 126ff17..b5eb0d6 100644 --- a/cli/doctor.ts +++ b/cli/doctor.ts @@ -10,6 +10,7 @@ import { CONFIG_NAMES } from './lib/config'; import { log } from './lib/log'; import { textTable } from './lib/table'; import { androidSdk, type AndroidSdkLookup, detectTools, findPlaywright, installCommands, type ToolStatus } from './lib/tools'; +import * as rt from './lib/rt'; export interface DoctorRow { id: ToolStatus['id'] | 'playwright'; @@ -24,7 +25,7 @@ export interface DoctorRow { url: string | null; } export interface DoctorResult { - storebound: string; bun: string; platform: string; project: string | null; tools: DoctorRow[]; missing: number; + storebound: string; bun: string | null; runtime: string; platform: string; project: string | null; tools: DoctorRow[]; missing: number; /** The Android SDK `play build` hands to Gradle, where it came from, and every place looked at. */ androidSdk: AndroidSdkLookup; } @@ -32,18 +33,18 @@ export interface DoctorResult { export function doctor(cwd: string, env: Record = process.env): DoctorResult { const tools: DoctorRow[] = detectTools(env).map(t => ({ id: t.id, name: t.name, found: t.found, version: t.version, path: t.path, source: t.source, usedFor: t.usedFor, install: t.found ? [] : installCommands(t.id), url: t.install.url })); - const pw = findPlaywright([cwd, join(import.meta.dir, '..')]); + const pw = findPlaywright([cwd, rt.packageRoot()]); tools.splice(3, 0, { id: 'playwright', name: 'playwright', found: !!pw, version: null, path: pw, source: pw ? 'system' : null, usedFor: 'screenshot renders and web captures from a terminal (the desktop app brings its own)', install: pw ? [] : installCommands('playwright'), url: 'https://playwright.dev/docs/intro' }); const project = CONFIG_NAMES.some(n => existsSync(join(cwd, n))) ? cwd : null; const sdk = androidSdk([join(cwd, 'android'), cwd], env); - return { storebound: pkg.version, bun: Bun.version, platform: `${process.platform}-${process.arch}`, project, tools, missing: tools.filter(t => !t.found).length, androidSdk: sdk }; + return { storebound: pkg.version, bun: rt.bunVersion, runtime: `${rt.runtime.name} ${rt.runtime.version}`, platform: `${process.platform}-${process.arch}`, project, tools, missing: tools.filter(t => !t.found).length, androidSdk: sdk }; } export async function run(args: Args): Promise { const r = doctor(resolve(args.str('dir', '.'))); if (args.has('json')) { console.log(JSON.stringify(r, null, 2)); return; } - log('doctor', `storebound ${r.storebound} · bun ${r.bun} · ${r.platform}${r.project ? ' · project config found' : ''}`); + log('doctor', `storebound ${r.storebound} · ${r.runtime} · ${r.platform}${r.project ? ' · project config found' : ''}`); console.log(textTable(['tool', 'status', 'version', 'used for'], r.tools.map(t => [t.name, t.found ? (t.source === 'app' ? 'ok (app copy)' : 'ok') : 'missing', t.version ?? '', t.usedFor]))); log('doctor', r.androidSdk.path ? `Android SDK: ${r.androidSdk.path} (from ${r.androidSdk.source})` : `Android SDK: not found (play build needs it). Checked: ${r.androidSdk.checked.join('; ')}`); diff --git a/cli/flow.ts b/cli/flow.ts index b4a5ecd..63387e1 100644 --- a/cli/flow.ts +++ b/cli/flow.ts @@ -28,6 +28,7 @@ import { done, log, printJson, step, warn } from './lib/log'; import { childEnv, pump, selfCommand } from './lib/runner'; import { PREBUILD_DONE_ENV, prebuildFor, prebuildsDone } from './lib/web-assets'; import { parseYaml } from './lib/yaml'; +import * as rt from './lib/rt'; const TAG = 'flow'; const EXT = ['.yml', '.yaml', '.json', '.ts']; @@ -41,7 +42,7 @@ export function projectRoot(start = process.cwd()): string { } async function readData(path: string): Promise { - if (path.endsWith('.ts')) { const m = await import(path) as { default?: unknown }; return m.default; } + if (path.endsWith('.ts')) { const m = await rt.importConfigModule(path) as { default?: unknown }; return m.default; } const text = readFileSync(path, 'utf8'); if (path.endsWith('.json')) return JSON.parse(text); return parseYaml(text); @@ -64,7 +65,7 @@ export async function loadFlows(root: string): Promise<{ flows: Flow[]; errors: /** Command names of this storebound (from `storebound help`), for `flow validate`. */ function commandNames(): Set { - const r = Bun.spawnSync([...selfCommand(), 'help'], { stdout: 'pipe', stderr: 'pipe' }); + const r = rt.spawnSync([...selfCommand(), 'help'], { stdout: 'pipe', stderr: 'pipe' }); const names = new Set(['help', 'version']); for (const l of r.stdout.toString().split('\n')) if (/^ {2}[a-z]/.test(l)) { const n = l.slice(2, 27).trim(); if (n && !n.includes(':')) names.add(n); } return names; @@ -106,7 +107,7 @@ function describe(p: PlannedStep): string { /** Runs one storebound step (or shell line) with stdin attached, so the command's own confirmation works; output indented. */ async function exec(p: PlannedStep, root: string, quiet: boolean, extraEnv: Record = {}): Promise<{ code: number; last: string; out: string }> { const cmd = p.shell !== undefined ? ['sh', '-c', p.shell] : [...selfCommand(), ...p.argv!]; - const proc = Bun.spawn(cmd, { cwd: root, stdin: 'inherit', stdout: 'pipe', stderr: 'pipe', env: childEnv(extraEnv) }); + const proc = rt.spawn(cmd, { cwd: root, stdin: 'inherit', stdout: 'pipe', stderr: 'pipe', env: childEnv(extraEnv) }); const lines: string[] = []; let last = ''; const each = (line: string, kind: 'stdout' | 'stderr') => { diff --git a/cli/init.ts b/cli/init.ts index 05e377a..615828b 100644 --- a/cli/init.ts +++ b/cli/init.ts @@ -11,8 +11,9 @@ import type { Args } from './lib/args'; import { type AppProfile, PROFILES } from './lib/config'; import { log } from './lib/log'; import { TEMPLATE_TEXT } from './lib/template-files'; +import * as rt from './lib/rt'; -const T = join(import.meta.dir, '..', 'templates'); +const T = join(rt.packageRoot(), 'templates'); export const FILES: [string, string][] = [ ['storebound.config.ts', 'storebound.config.ts'], ['listing.md', 'store/listing.md'], diff --git a/cli/install.ts b/cli/install.ts index 7db2fb7..3ad841a 100644 --- a/cli/install.ts +++ b/cli/install.ts @@ -3,16 +3,20 @@ // [--no-skills] [--no-mcp] [--command "bunx storebound"] // Skills are copied to the tool's skills folder (our own skill folders are updated, nothing else is touched). The MCP // entry "storebound" is added to the tool's config file; every other entry stays as it is. Re-running is safe. -// MCP command: `bunx storebound mcp` when the project has storebound in node_modules, else this toolchain's -// bin/storebound by absolute path (override with --command). +// MCP command: `bunx storebound mcp` (`npx storebound mcp` when run with Node) when the project has storebound in +// node_modules; else, under Bun, this toolchain's bin/storebound by absolute path, under Node (`npx storebound +// install`: the toolchain sits in npx's cache, which can be cleared) `npx -y storebound@ mcp` +// (override with --command). import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { dirname, join, resolve } from 'node:path'; import type { Args } from './lib/args'; import { AGENTS, type AgentId, isAgent, type McpCommand, mergeCodexToml, mergeJsonServer } from './lib/agents'; import { log, warn } from './lib/log'; +import * as rt from './lib/rt'; +import pkg from '../package.json' with { type: 'json' }; -const TOOLCHAIN = resolve(import.meta.dir, '..'); +const TOOLCHAIN = rt.packageRoot(); /** Every file of a folder, relative path -> content, for comparing skill folders. */ function snapshot(dir: string, base = dir, out = new Map()): Map { @@ -25,9 +29,10 @@ function snapshot(dir: string, base = dir, out = new Map()): Map } const same = (a: Map, b: Map) => a.size === b.size && [...a].every(([k, v]) => b.get(k) === v); -export function mcpCommand(project: string, override?: string): McpCommand { +export function mcpCommand(project: string, override?: string, runtime: 'bun' | 'node' = rt.runtime.name, version = pkg.version): McpCommand { if (override) { const [command, ...args] = override.split(/\s+/).filter(Boolean); return { command: command!, args: [...args, 'mcp'] }; } - if (existsSync(join(project, 'node_modules', 'storebound', 'package.json'))) return { command: 'bunx', args: ['storebound', 'mcp'] }; + if (existsSync(join(project, 'node_modules', 'storebound', 'package.json'))) return { command: runtime === 'bun' ? 'bunx' : 'npx', args: ['storebound', 'mcp'] }; + if (runtime === 'node') return { command: 'npx', args: ['-y', `storebound@${version}`, 'mcp'] }; return { command: join(TOOLCHAIN, 'bin', 'storebound'), args: ['mcp'] }; } diff --git a/cli/lib/config.ts b/cli/lib/config.ts index a4de61c..fe44ad8 100644 --- a/cli/lib/config.ts +++ b/cli/lib/config.ts @@ -2,6 +2,7 @@ // do (see credentials.ts). The file default-exports a plain object; `import type { ShipConfig }` is optional. import { existsSync } from 'node:fs'; import { dirname, isAbsolute, join, resolve } from 'node:path'; +import * as rt from './rt'; import { validateCapture, validateRecord } from '../shots/devices'; import { type AppleDataTypeId, type AppleTypeAnswer, validateAppPrivacyTypes } from './app-privacy'; import { type AppEventDef, type CustomProductPageDef, validateCpp, validateEvents } from './cpp-events'; @@ -1171,7 +1172,7 @@ export async function loadConfig(explicit?: string, start = process.cwd()): Prom } } if (!path || !existsSync(path)) throw new Error('no storebound.config.ts found (run `storebound init` or pass --config)'); - const mod = await import(path); + const mod = await rt.importConfigModule(path); const config = (mod.default ?? mod) as ShipConfig; const errors = validateConfig(config); if (errors.length) throw new Error(`${path}:\n ` + errors.join('\n ')); diff --git a/cli/lib/data-safety.ts b/cli/lib/data-safety.ts index d79d2f3..7464cca 100644 --- a/cli/lib/data-safety.ts +++ b/cli/lib/data-safety.ts @@ -9,7 +9,7 @@ // App content > Data safety > Export to CSV with nothing answered, saved over that file unchanged (docs/pitfalls.md 21). import type { AccountsConfig, DataSafety, PlayPurpose } from './config'; import { csvCell, parseCsv } from './csv'; -import FORM_TEXT from '../data/play-data-safety-form.csv' with { type: 'text' }; +import { PLAY_DATA_SAFETY_FORM as FORM_TEXT } from './embedded'; export { csvCell }; @@ -25,7 +25,7 @@ const pairKey = (q: string, r: string) => `${q}\u0000${r}`; export function playForm(): Map { if (formCache) return formCache; const m = new Map(); - for (const r of parseCsv(FORM_TEXT as unknown as string).slice(1)) { + for (const r of parseCsv(FORM_TEXT).slice(1)) { const [question = '', response = '', , requirement = '', label = ''] = r; m.set(pairKey(question, response), { question, response, requirement, label: label.trim() }); } diff --git a/cli/lib/embedded.ts b/cli/lib/embedded.ts new file mode 100644 index 0000000..d64ab2b --- /dev/null +++ b/cli/lib/embedded.ts @@ -0,0 +1,48 @@ +// GENERATED by tools/gen-embedded.ts from templates/, cli/shots/*.html and cli/data/play-data-safety-form.csv. +// Do not edit: change those files and run `bun tools/gen-embedded.ts` (bun run ci checks it is fresh). +/* eslint-disable */ + +/** Every file under templates/, by its path there (forward slashes). */ +export const TEMPLATE_TEXT: Record = { + "app-privacy.md": "# Example Game · App Store \"App Privacy\" answers\n\nApp Store Connect > your app > App Privacy. **Web UI only, there is no API.** Fill it from this table, then press\n**Publish** at the top of the App Privacy page (saving alone does not publish it, and review checks the published one).\nKeep it consistent with the privacy policy and the Play Data safety answers (`dataSafety` in `storebound.config.ts`).\n\nTypical free-to-play game: analytics SDK + ad mediation (with ATT) + optional server backup, no accounts.\n\n**Do you or your third-party partners collect data from this app?** Yes\n\n**Tracking**: Yes, if any ad network uses the IDFA after the App Tracking Transparency prompt (third-party advertising).\nThen `NSUserTrackingUsageDescription` must be in Info.plist and the prompt must come before the ad SDK starts.\n\n| data type | collected | used to track | linked to the user | purposes |\n|---|---|---|---|---|\n| Identifiers > Device ID (IDFA, analytics random id) | yes | yes (IDFA, with ATT consent) | no | Third-Party Advertising, Analytics, App Functionality |\n| Location > Coarse Location (country from IP) | yes | yes | no | Third-Party Advertising, Analytics |\n| Usage Data > Advertising Data (ads shown, ad taps) | yes | yes | no | Third-Party Advertising, Analytics |\n| Usage Data > Product Interaction (sessions, level events) | yes | no | no | Analytics |\n| User Content > Gameplay Content (save backup, scores) | only with a server backup | no | no | App Functionality |\n| Diagnostics > Other Diagnostic Data (device model, OS version) | yes | no | no | Analytics, Third-Party Advertising |\n| Purchases > Purchase History | only if your server stores receipts | no | yes if tied to an account | App Functionality |\n\n\"Linked to you\" is **No** when there are no accounts and nothing is tied to a name, email or phone number.\nNot collected (typical): Contact Info, Health and Fitness, Financial Info, Precise Location, Sensitive Info, Contacts,\nEmails or Text Messages, Photos or Videos, Audio, Browsing History, Search History, Crash Data (unless you ship a crash\nreporter).\n\nPurposes follow Apple's definitions (https://developer.apple.com/app-store/app-privacy-details/): ads shown by an ad\nSDK are **Third-Party Advertising** only. **Developer's Advertising or Marketing** is your own ads or marketing to your\nusers; tick it only when that is true.\n\nPut the answers into `storebound.config.ts` so `storebound console fill apple-app-privacy` fills them and\n`storebound check` compares them with Info.plist and PrivacyInfo.xcprivacy (all type ids: storebound's\ncli/lib/app-privacy.ts):\n\n```ts\nappPrivacy: { tracking: true, types: {\n DEVICE_ID: { collected: true, linked: false, tracking: true, purposes: ['THIRD_PARTY_ADVERTISING', 'ANALYTICS', 'APP_FUNCTIONALITY'] },\n COARSE_LOCATION: { collected: true, linked: false, tracking: true, purposes: ['THIRD_PARTY_ADVERTISING', 'ANALYTICS'] },\n ADVERTISING_DATA: { collected: true, linked: false, tracking: true, purposes: ['THIRD_PARTY_ADVERTISING', 'ANALYTICS'] },\n PRODUCT_INTERACTION: { collected: true, linked: false, tracking: false, purposes: ['ANALYTICS'] },\n GAMEPLAY_CONTENT: { collected: false },\n} },\n```\n\nPer data type the UI asks, in order: purposes (checkboxes) > \"Is this data linked to the user's identity?\" >\n\"Do you or your third-party partners use this data for tracking purposes?\".\n\nPrivacy policy URL: App Information > Privacy Policy URL (set by `storebound apple listing`). Privacy choices URL:\noptional, leave empty unless you have a page for it.\n", + "appstore.md": "# Example Game · App Store listing copy\n\nSource for `storebound apple listing`. One `## ` section per locale (the first word is the locale code:\n`en-US`, `tr`, `de-DE`, `ja`, `zh-Hans`, `ar-SA`, ...), one fenced block per `### `, field names exactly:\n`name` (optional, defaults to `apple.name`, else `app.name`), `subtitle` (30), `promotionalText` (170), `keywords` (100 bytes, comma\nseparated, no spaces after commas, do not repeat the app name), `description` (4000), `whatsNew` (optional; not allowed\non the first version). Shared values (privacy policy, support and marketing URL) come from `storebound.config.ts`.\n\n---\n\n## en-US · English (U.S.)\n\n### subtitle\n```\nShort hook under the name\n```\n\n### promotionalText\n```\nOne or two sentences you can change any time without a new version.\n```\n\n### keywords\n```\npuzzle,casual,relaxing,offline,brain,match,levels\n```\n\n### description\n```\nFirst paragraph: what the player does and why it feels good.\n\nFEATURES\n• One-thumb controls\n• 100+ levels\n• Play offline, no account needed\n\nFree to play. Contains ads and offers optional in-app purchases.\n```\n\n## tr · Türkçe\n\n### subtitle\n```\nİsmin altındaki kısa kanca\n```\n\n### promotionalText\n```\nYeni sürüm göndermeden istediğin zaman değiştirebileceğin bir iki cümle.\n```\n\n### keywords\n```\nbulmaca,rahatlatıcı,internetsiz,zeka,eşleştirme,bölüm\n```\n\n### description\n```\nİlk paragraf: oyuncu ne yapıyor ve neden keyifli.\n\nÖZELLİKLER\n• Tek parmakla kontrol\n• 100+ bölüm\n• İnternetsiz oyna, hesap gerekmez\n\nÜcretsiz oyun. Reklam içerir ve isteğe bağlı uygulama içi satın alımlar sunar.\n```\n", + "credentials.example.json": "{\n \"PLAY_SERVICE_ACCOUNT\": \"/absolute/path/to/play-service-account.json\",\n \"ASC_KEY_ID\": \"YOUR_KEY_ID\",\n \"ASC_ISSUER_ID\": \"00000000-0000-0000-0000-000000000000\",\n \"ASC_P8\": \"/absolute/path/to/AuthKey_.p8\",\n \"APPLE_ADS_CLIENT_ID\": \"SEARCHADS.00000000-0000-0000-0000-000000000000\",\n \"APPLE_ADS_TEAM_ID\": \"SEARCHADS.00000000-0000-0000-0000-000000000000\",\n \"APPLE_ADS_KEY_ID\": \"00000000-0000-0000-0000-000000000000\",\n \"APPLE_ADS_PRIVATE_KEY\": \"/absolute/path/to/apple-ads-private-key.pem\",\n \"APPLE_ADS_ACCOUNT_ID\": \"\",\n \"REVENUECAT_SECRET_KEY\": \"sk_your_revenuecat_v2_secret_key\",\n \"ANDROID_KEYSTORE\": \"/absolute/path/to/upload.p12\",\n \"ANDROID_KEYSTORE_PASSWORD\": \"\",\n \"ANDROID_KEY_ALIAS\": \"upload\",\n \"ANDROID_KEY_PASSWORD\": \"\",\n \"ASC_VENDOR_NUMBER\": \"\",\n \"reviewContact\": { \"firstName\": \"\", \"lastName\": \"\", \"phone\": \"+1 555 0100\", \"email\": \"review-contact@example.com\" },\n \"reviewDemoAccount\": { \"username\": \"\", \"password\": \"\" }\n}\n", + "data-safety.csv": "Question ID (machine readable),Response ID (machine readable),Response value,Answer requirement,Human-friendly question label\nPSL_DATA_COLLECTION_COLLECTS_PERSONAL_DATA,,TRUE,REQUIRED,Does your app collect or share any of the required user data types?\nPSL_DATA_COLLECTION_ENCRYPTED_IN_TRANSIT,,TRUE,MAYBE_REQUIRED,Is all of the user data collected by your app encrypted in transit?\nPSL_SUPPORTED_ACCOUNT_CREATION_METHODS,PSL_ACM_NONE,TRUE,MULTIPLE_CHOICE,Which of the following methods of account creation does your app support? Select all that apply / My app does not allow users to create an account\nPSL_DATA_TYPES_PERSONAL,PSL_NAME,,MULTIPLE_CHOICE,Personal info / Name\nPSL_DATA_TYPES_PERSONAL,PSL_EMAIL,,MULTIPLE_CHOICE,Personal info / Email address\nPSL_DATA_TYPES_PERSONAL,PSL_USER_ACCOUNT,,MULTIPLE_CHOICE,Personal info / User IDs\nPSL_DATA_TYPES_PERSONAL,PSL_ADDRESS,,MULTIPLE_CHOICE,Personal info / Address\nPSL_DATA_TYPES_PERSONAL,PSL_PHONE,,MULTIPLE_CHOICE,Personal info / Phone number\nPSL_DATA_TYPES_PERSONAL,PSL_RACE_ETHNICITY,,MULTIPLE_CHOICE,Personal info / Race and ethnicity\nPSL_DATA_TYPES_PERSONAL,PSL_POLITICAL_RELIGIOUS,,MULTIPLE_CHOICE,Personal info / Political or religious beliefs\nPSL_DATA_TYPES_PERSONAL,PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY,,MULTIPLE_CHOICE,Personal info / Sexual orientation\nPSL_DATA_TYPES_PERSONAL,PSL_OTHER_PERSONAL,,MULTIPLE_CHOICE,Personal info / Other info\nPSL_DATA_TYPES_FINANCIAL,PSL_PURCHASE_HISTORY,TRUE,MULTIPLE_CHOICE,Financial info / Purchase history\nPSL_DATA_TYPES_FINANCIAL,PSL_CREDIT_SCORE,,MULTIPLE_CHOICE,Financial info / Credit score\nPSL_DATA_TYPES_FINANCIAL,PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER,,MULTIPLE_CHOICE,Financial info / User payment info\nPSL_DATA_TYPES_FINANCIAL,PSL_OTHER,,MULTIPLE_CHOICE,Financial info / Other financial info\nPSL_DATA_TYPES_LOCATION,PSL_APPROX_LOCATION,TRUE,MULTIPLE_CHOICE,Location / Approximate location\nPSL_DATA_TYPES_LOCATION,PSL_PRECISE_LOCATION,,MULTIPLE_CHOICE,Location / Precise location\nPSL_DATA_TYPES_SEARCH_AND_BROWSING,PSL_WEB_BROWSING_HISTORY,,MULTIPLE_CHOICE,Web browsing / Web browsing history\nPSL_DATA_TYPES_EMAIL_AND_TEXT,PSL_EMAILS,,MULTIPLE_CHOICE,Messages / Emails\nPSL_DATA_TYPES_EMAIL_AND_TEXT,PSL_SMS_CALL_LOG,,MULTIPLE_CHOICE,Messages / SMS or MMS\nPSL_DATA_TYPES_EMAIL_AND_TEXT,PSL_OTHER_MESSAGES,,MULTIPLE_CHOICE,Messages / Other in-app messages\nPSL_DATA_TYPES_PHOTOS_AND_VIDEOS,PSL_PHOTOS,,MULTIPLE_CHOICE,Photos and videos / Photos\nPSL_DATA_TYPES_PHOTOS_AND_VIDEOS,PSL_VIDEOS,,MULTIPLE_CHOICE,Photos and videos / Videos\nPSL_DATA_TYPES_AUDIO,PSL_AUDIO,,MULTIPLE_CHOICE,Audio files / Voice or sound recordings\nPSL_DATA_TYPES_AUDIO,PSL_MUSIC,,MULTIPLE_CHOICE,Audio files / Music files\nPSL_DATA_TYPES_AUDIO,PSL_OTHER_AUDIO,,MULTIPLE_CHOICE,Audio files / Other audio files\nPSL_DATA_TYPES_HEALTH_AND_FITNESS,PSL_HEALTH,,MULTIPLE_CHOICE,Health and fitness / Health info\nPSL_DATA_TYPES_HEALTH_AND_FITNESS,PSL_FITNESS,,MULTIPLE_CHOICE,Health and fitness / Fitness info\nPSL_DATA_TYPES_CONTACTS,PSL_CONTACTS,,MULTIPLE_CHOICE,Contacts / Contacts\nPSL_DATA_TYPES_CALENDAR,PSL_CALENDAR,,MULTIPLE_CHOICE,Calendar / Calendar events\nPSL_DATA_TYPES_APP_PERFORMANCE,PSL_CRASH_LOGS,,MULTIPLE_CHOICE,App info and performance / Crash logs\nPSL_DATA_TYPES_APP_PERFORMANCE,PSL_PERFORMANCE_DIAGNOSTICS,TRUE,MULTIPLE_CHOICE,App info and performance / Diagnostics\nPSL_DATA_TYPES_APP_PERFORMANCE,PSL_OTHER_PERFORMANCE,,MULTIPLE_CHOICE,App info and performance / Other app performance data\nPSL_DATA_TYPES_FILES_AND_DOCS,PSL_FILES_AND_DOCS,,MULTIPLE_CHOICE,Files and docs / Files and docs\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_USER_INTERACTION,TRUE,MULTIPLE_CHOICE,App activity / App interactions\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_IN_APP_SEARCH_HISTORY,,MULTIPLE_CHOICE,App activity / In-app search history\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_APPS_ON_DEVICE,,MULTIPLE_CHOICE,App activity / Installed apps\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_USER_GENERATED_CONTENT,,MULTIPLE_CHOICE,App activity / Other user-generated content\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_OTHER_APP_ACTIVITY,TRUE,MULTIPLE_CHOICE,App activity / Other actions\nPSL_DATA_TYPES_IDENTIFIERS,PSL_DEVICE_ID,TRUE,MULTIPLE_CHOICE,Device or other IDs / Device or other IDs\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Name) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Name) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Name) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Name) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Name) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Email address) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Email address) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Email address) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Email address) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Email address) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (User IDs) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (User IDs) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (User IDs) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (User IDs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (User IDs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Address) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Address) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Address) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Address) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Address) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Phone number) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Phone number) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Phone number) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Phone number) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Phone number) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Race and ethnicity) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Political or religious beliefs) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Sexual orientation) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Sexual orientation) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Sexual orientation) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Sexual orientation) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Sexual orientation) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other info) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other info) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other info) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Purchase history) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Purchase history) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (Purchase history) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Purchase history) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,\"Data usage and handling (Purchase history) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Credit score) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Credit score) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Credit score) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Credit score) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Credit score) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (User payment info) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (User payment info) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (User payment info) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (User payment info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (User payment info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other financial info) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other financial info) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other financial info) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other financial info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other financial info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Approximate location) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Approximate location) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (Approximate location) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Approximate location) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,\"Data usage and handling (Approximate location) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Precise location) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Precise location) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Precise location) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Precise location) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Precise location) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Web browsing history) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Web browsing history) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Web browsing history) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Web browsing history) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Web browsing history) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Emails) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Emails) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Emails) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Emails) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Emails) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (SMS or MMS) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (SMS or MMS) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (SMS or MMS) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (SMS or MMS) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (SMS or MMS) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other in-app messages) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other in-app messages) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other in-app messages) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other in-app messages) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other in-app messages) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Photos) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Photos) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Photos) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Photos) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Photos) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Videos) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Videos) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Videos) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Videos) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Videos) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Voice or sound recordings) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Music files) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Music files) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Music files) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Music files) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Music files) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other audio files) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other audio files) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other audio files) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other audio files) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other audio files) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Health info) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Health info) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Health info) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Health info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Health info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Fitness info) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Fitness info) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Fitness info) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Fitness info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Fitness info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Contacts) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Contacts) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Contacts) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Contacts) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Contacts) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Calendar events) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Calendar events) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Calendar events) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Calendar events) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Calendar events) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Crash logs) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Crash logs) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Crash logs) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Crash logs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Crash logs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Diagnostics) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Diagnostics) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (Diagnostics) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Diagnostics) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,\"Data usage and handling (Diagnostics) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other app performance data) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other app performance data) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other app performance data) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other app performance data) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other app performance data) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Files and docs) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Files and docs) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Files and docs) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Files and docs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Files and docs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (App interactions) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (App interactions) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (App interactions) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (App interactions) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,\"Data usage and handling (App interactions) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (In-app search history) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (In-app search history) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (In-app search history) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (In-app search history) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (In-app search history) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Installed apps) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Installed apps) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Installed apps) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Installed apps) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Installed apps) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other user-generated content) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other user-generated content) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other user-generated content) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other user-generated content) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other user-generated content) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Other actions) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other actions) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (Other actions) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other actions) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,\"Data usage and handling (Other actions) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Device or other IDs) / Is this data collected, shared, or both? / Collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Device or other IDs) / Is this data collected, shared, or both? / Shared\"\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_EPHEMERAL,,FALSE,MAYBE_REQUIRED,Data usage and handling (Device or other IDs) / Is this data processed ephemerally?\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Device or other IDs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,TRUE,SINGLE_CHOICE,\"Data usage and handling (Device or other IDs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,TRUE,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Account management\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / App functionality\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,TRUE,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Analytics\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Developer communications\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,TRUE,MULTIPLE_CHOICE,\"Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,TRUE,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Advertising or marketing\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Personalization\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Account management\n", + "declarations.md": "# Example Game · store declarations (answers to copy into the consoles)\n\nAnswers for a typical single-player free-to-play game with ads (mediation) and in-app purchases, no accounts, no chat,\nno user-generated content, general audience 13+. Change what does not match your game. Everything marked **UI** has no\nAPI: Play Console > your app > Policy and programs > **App content** (or Dashboard > \"Set up your app\"), and App Store\nConnect for Apple. Data safety (Play) has an API: `storebound play data-safety`.\n\n## Google Play · App content (UI)\n\n| declaration | answer |\n|---|---|\n| Privacy policy | your policy URL (same one in the listing and inside the app) |\n| App access | All functionality is available without special access (no login) |\n| Ads | Yes, my app contains ads |\n| Content rating | IARC questionnaire, below |\n| Target audience and content | 13-15, 16-17, 18 and over; \"Could your app unintentionally appeal to children?\" No (only if true) |\n| News app | No |\n| COVID-19 contact tracing / status | No |\n| Data safety | `storebound play data-safety` (then check the summary in the Console) |\n| Government apps | No |\n| Financial features | My app doesn't provide any financial features |\n| Health apps | No health features |\n| Advertising ID | Yes, uses it; purposes: Advertising or marketing, Analytics, Fraud prevention / security / compliance |\n| Sign-in details (App access) | No sign-in needed |\n| Store settings | App category Game > (Casual, Puzzle, Arcade ...); tags; contact email + website (website serves app-ads.txt) |\n\nFamilies / Designed for Families: only if you really target children; it changes the ad SDK setup (certified networks,\nno ad id). If you leave children out, the policy and the ad mediation must say \"not directed at children\" too.\n\n## IARC content rating (Play UI; Apple has its own form, `storebound apple appinfo`)\n\nCategory: Game. Typical answers for a non-violent casual game:\n\n| topic | answer |\n|---|---|\n| Violence (realistic / cartoon / fantasy) | No |\n| Blood / gore | No |\n| Fear / horror | No |\n| Sexuality / nudity | No |\n| Language (profanity, crude humour) | No |\n| Controlled substances | No |\n| Gambling: simulated / real money | No / No |\n| Random paid items (loot boxes) | No (fixed contents only; answer Yes if a paid item has random contents) |\n| Users interact or exchange content | No (no chat; OS share sheet does not count) |\n| Shares location with other users | No |\n| Digital purchases | Yes (in-app purchases) |\n| Unrestricted internet / browser | No |\n\nExpected: PEGI 3, ESRB Everyone, USK 0, IARC 3+. Apple age rating 4+ (all content questions None; ads are declared as\n\"advertising: true\", which does not change 4+).\n\n## App Store (UI-only parts)\n\n- App record: App Store Connect > Apps > + > New App (platform, name, primary language, bundle id, SKU). No API.\n- App Privacy: `app-privacy.md`, then **Publish**.\n- Export compliance (export-compliance skill): `ITSAppUsesNonExemptEncryption = NO` in Info.plist only if true (HTTPS / OS encryption only) so\n every build skips the question.\n- First in-app purchases: from the IAP page \"Add for Review\" into the draft submission with the version (see the\n store-iap-setup skill).\n- Guideline 5.1.1: the privacy policy link must also be reachable **inside the app** (Settings > Privacy).\n\n## Ads\n\n- app-ads.txt at the root of the developer website named in both listings (`https://example.com/app-ads.txt`),\n with the mediation's DIRECT line plus each network's lines.\n- iOS: SKAdNetwork ids of every network in Info.plist, ATT prompt before the ad SDK starts.\n- EU / EEA / UK / CH: consent (CMP) or non-personalized ads.\n", + "listing.md": "# Example Game · Google Play listing copy\n\nSource for `storebound play listing` (title, short + full description) and `storebound play release` (release notes).\nRules the parser follows:\n- every `## ...` section with a `` line is a listing; one section can serve several Play codes\n (``)\n- under it, `### ...` headings that contain **title** (optional, defaults to `app.name`), **short**, **full** and\n **what's new**, each followed by one fenced block\n- limits (checked before anything is sent): title 30, short 80, full 4000, what's new 500 characters\nOnly claim what is really in the game. No prices, no \"best / #1\", no other games' names.\n\n---\n\n## English (en-US)\n\n\n### Play short description (max 80)\n```\nShort, punchy promise of the core loop in one line\n```\n\n### Play full description (max 4000)\n```\nFirst paragraph: what the player does and why it feels good, in two or three sentences.\n\nFEATURES\n• One-thumb controls\n• 100+ levels and new ones every update\n• Play offline, no account needed\n\nFree to play. Contains ads and offers optional in-app purchases.\n```\n\n### What's new (max 500)\n```\nFirst release. Have fun!\n```\n\n## Türkçe (tr-TR)\n\n\n### Play kısa açıklama · short (max 80)\n```\nOyunun özünü anlatan kısa ve vurucu bir cümle\n```\n\n### Play tam açıklama · full (max 4000)\n```\nİlk paragraf: oyuncu ne yapıyor ve neden keyifli, iki üç cümlede.\n\nÖZELLİKLER\n• Tek parmakla kontrol\n• 100+ bölüm, her güncellemede yenileri\n• İnternetsiz oyna, hesap gerekmez\n\nÜcretsiz oyun. Reklam içerir ve isteğe bağlı uygulama içi satın alımlar sunar.\n```\n\n### Yenilikler · what's new (max 500)\n```\nİlk sürüm. İyi eğlenceler!\n```\n", + "privacy-policy.html": "\n\n\n\n\nPrivacy Policy · Example Studio\n\n\n\n\n

\n \n\n
\n

Privacy Policy

\n

Example Studio · applies to all our games · last updated 2026-01-01

\n\n

We make small games. We collect as little as we can, never sell personal data, and none of our games needs an\n account. This page explains what each game collects, why, and what you can do about it.

\n\n

What our games use

\n
\n \n \n \n \n
GameServiceWhat and whyYour choice
Example GameAnalytics (e.g. GameAnalytics)Random player id, sessions, level events, device model, OS version, country derived from IP: to improve the game.Settings > Analytics off
Example GameAds (e.g. a mediation SDK and its networks)Advertising id (if you allow it), IP, device info, ad interactions: to show and measure ads.iOS: tracking prompt; Android: OS ad settings; buy \"Remove Ads\"
Example GameApp storesPurchases are handled by Google Play / the App Store; we never see payment details.-
\n\n

Children

\n

Our games are for a general audience and are not directed at children under 13. We do not knowingly collect\n personal data from children. If you believe a child sent us data, contact us and we will delete it.

\n\n

Retention and your rights

\n

Analytics data is kept for up to 24 months. You can ask us for a copy of your data or to delete it by writing\n to privacy@example.com; we answer within 30 days. Include the game name and,\n if you can, the player id shown in Settings.

\n\n

Security

\n

All network traffic of our games uses HTTPS.

\n\n

Changes

\n

When a game starts collecting something new we update this page and its date first.

\n\n

Contact

\n

Example Studio · privacy@example.com

\n
\n\n
\n

Gizlilik Politikası

\n

Example Studio · tüm oyunlarımız için geçerlidir · son güncelleme 2026-01-01

\n

Küçük oyunlar yapıyoruz. Olabildiğince az veri topluyoruz, kişisel verileri asla satmıyoruz ve hiçbir oyunumuz\n hesap istemiyor. Yukarıdaki tablo her oyunun neyi neden topladığını gösterir.

\n

Haklarınız

\n

Verilerinizin bir kopyasını veya silinmesini privacy@example.com adresine\n yazarak isteyebilirsiniz; 30 gün içinde yanıtlıyoruz.

\n
\n
\n\n\n", + "profiles/app/app-privacy.md": "# Example App · App Store \"App Privacy\" answers\n\nApp Store Connect > your app > App Privacy. **Web UI only, there is no API.** Fill it from this table, then press\n**Publish** (saving alone does not publish it). Keep it consistent with the privacy policy and the Play Data safety\nanswers (`dataSafety` in `storebound.config.ts`). **Edit to match your app.**\n\nTypical utility / productivity app: optional account (email), content synced to your backend, an analytics SDK\nacting as your processor, no ads, no tracking.\n\n**Do you or your third-party partners collect data from this app?** Yes\n\n**Tracking**: No (no ad SDK, no data broker, nothing combined with other companies' data for advertising). If that\nchanges, add the App Tracking Transparency prompt first.\n\n| data type | collected | used to track | linked to the user | purposes |\n|---|---|---|---|---|\n| Contact Info > Email Address | yes (account) | no | yes | App Functionality |\n| Identifiers > User ID | yes (account id) | no | yes | App Functionality, Analytics |\n| User Content > Other User Content (lists, notes) | yes (synced) | no | yes | App Functionality |\n| Usage Data > Product Interaction | yes (analytics) | no | yes if tied to the account, else no | Analytics |\n| Diagnostics > Crash Data | yes | no | no | App Functionality, Analytics |\n| Diagnostics > Performance Data | yes | no | no | Analytics |\n\n\"Linked to you\" is **Yes** for what you store under the account. Not collected (typical): Location, Health and\nFitness, Financial Info, Contacts, Photos or Videos (unless people attach them), Browsing and Search History,\nPurchases (until you sell something), Sensitive Info.\n\nPer data type the UI asks, in order: purposes > \"Is this data linked to the user's identity?\" > \"Do you or your\nthird-party partners use this data for tracking purposes?\".\n", + "profiles/app/appstore.md": "# Example App · App Store listing copy\n\nSource for `storebound apple listing`. One `## ` section per locale (the first word is the locale code:\n`en-US`, `tr`, `de-DE`, `ja`, `zh-Hans`, `ar-SA`, ...), one fenced block per `### `, field names exactly:\n`name` (optional, defaults to `apple.name`, else `app.name`), `subtitle` (30), `promotionalText` (170), `keywords` (100 bytes, comma\nseparated, no spaces after commas, do not repeat the app name), `description` (4000), `whatsNew` (optional; not allowed\non the first version). Shared values (privacy policy, support and marketing URL) come from `storebound.config.ts`.\n\n---\n\n## en-US · English (U.S.)\n\n### subtitle\n```\nLists and notes, in sync\n```\n\n### promotionalText\n```\nOne or two sentences you can change any time without a new version.\n```\n\n### keywords\n```\ntodo,checklist,groceries,planner,notes,shared,family,organizer,tasks,sync\n```\n\n### description\n```\nFirst paragraph: the job the app does and why it is easier with it.\n\nFEATURES\n• Lists and notes that sync across your devices\n• Share a list with family or friends\n• Export your data or delete your account in Settings, any time\n\nFree to use. An account is optional and only needed for sync and sharing.\n```\n\n## tr · Türkçe\n\n### subtitle\n```\nListeler ve notlar, eşitli\n```\n\n### promotionalText\n```\nYeni sürüm göndermeden istediğin zaman değiştirebileceğin bir iki cümle.\n```\n\n### keywords\n```\nyapılacaklar,kontrol,market,planlayıcı,not,paylaşım,aile,düzen,görev\n```\n\n### description\n```\nİlk paragraf: uygulamanın gördüğü iş ve onunla neden daha kolay olduğu.\n\nÖZELLİKLER\n• Tüm cihazlarında eşitlenen listeler ve notlar\n• Bir listeyi ailen ya da arkadaşlarınla paylaş\n• Verilerini dışa aktar ya da hesabını Ayarlar'dan istediğin an sil\n\nÜcretsiz. Hesap isteğe bağlıdır; yalnızca eşitleme ve paylaşım için gerekir.\n```\n", + "profiles/app/declarations.md": "# Example App · store declarations (answers to copy into the consoles)\n\nStarting answers for a typical **utility / productivity app**: an optional account (email + password, or sign in\nwith a provider), an analytics SDK acting as your processor, data synced to your own backend, no ads, no chat with\nstrangers, general audience. **Edit every line to match your app**: these are your statements to the stores. Everything\nmarked **UI** has no API: Play Console > your app > Policy and programs > **App content**, and App Store Connect for\nApple. Data safety (Play) has an API: `storebound play data-safety`.\n\n## Google Play · App content (UI)\n\n| declaration | answer |\n|---|---|\n| Privacy policy | your policy URL (same one in the listing and inside the app) |\n| App access | \"All or some functionality is restricted\": add the demo account (username, password) and the steps from `reviewAccess.instructions`; credentials must work at all times, no 2FA / OTP (review-access skill) |\n| Ads | No, my app does not contain ads |\n| Content rating | IARC questionnaire, below |\n| Target audience and content | 18 and over (or 13+ if teens use it); \"Could your app unintentionally appeal to children?\" No (only if true) |\n| News app | No |\n| Data safety | `storebound play data-safety`, then check the summary; with accounts: the delete-account URL (`accounts.deletionUrl`) |\n| Government apps | No |\n| Financial features | My app doesn't provide any financial features |\n| Health apps | No health features |\n| Advertising ID | No (unless an SDK in your app reads it: check the SDK docs and your merged manifest) |\n| Store settings | App, category (Productivity, Tools, ...); contact email + website |\n\n## Account deletion (account-deletion skill)\n\n- In the app: Settings > Account > Delete account deletes the account and its data (not only \"deactivate\").\n- On the web: `accounts.deletionUrl` lets people request deletion without the app; Play asks for it in Data safety.\n- With subscriptions: say that deleting the account does not cancel the store subscription, and how to cancel.\n\n## IARC content rating (Play UI; Apple has its own form, `storebound apple appinfo`)\n\nCategory: not a game (e.g. Utility, Productivity, Communication, or Social Networking if people share content with\nothers). Typical answers for a lists / notes app:\n\n| topic | answer |\n|---|---|\n| Violence, sexuality, language, controlled substances | No |\n| Gambling (simulated / real money) | No |\n| Users interact or exchange content | Yes only if people can share with others outside their own invitations; then also moderation and reporting |\n| Shares location with other users | No |\n| Digital purchases | No (Yes once you sell IAPs or subscriptions) |\n| Unrestricted internet / browser | No |\n\nExpected: PEGI 3, ESRB Everyone, IARC 3+. Apple age rating 4+ (all content questions None) unless users share\ncontent publicly.\n\n## App Store (UI-only parts)\n\n- App record: App Store Connect > Apps > + > New App. No API.\n- App Privacy: `app-privacy.md`, then **Publish**.\n- App Review information: `storebound apple review-details` sends the contact, the demo account (from\n `.storebound/credentials.json`, masked in the log) and the notes.\n- Export compliance (export-compliance skill): `ITSAppUsesNonExemptEncryption = NO` only if true.\n- Guideline 5.1.1: the privacy policy link must also be reachable **inside the app**; 5.1.1(v): account deletion\n inside the app.\n", + "profiles/app/listing.md": "# Example App · Google Play listing copy\n\nSource for `storebound play listing` (title, short + full description) and `storebound play release` (release notes).\nRules the parser follows:\n- every `## ...` section with a `` line is a listing; one section can serve several Play codes\n- under it, `### ...` headings that contain **title** (optional, defaults to `app.name`), **short**, **full** and\n **what's new**, each followed by one fenced block\n- limits (checked before anything is sent): title 30, short 80, full 4000, what's new 500 characters\nOnly claim what the app really does. No prices, no \"best / #1\", no other apps' names.\n\n---\n\n## English (en-US)\n\n\n### Play short description (max 80)\n```\nWhat the app does for people, in one plain line\n```\n\n### Play full description (max 4000)\n```\nFirst paragraph: the job the app does and why it is easier with it, in two or three sentences.\n\nFEATURES\n• Lists and notes that sync across your devices\n• Share a list with family or friends\n• Export your data or delete your account in Settings, any time\n\nFree to use. An account is optional and only needed for sync and sharing.\n```\n\n### What's new (max 500)\n```\nFirst release. Thanks for trying it!\n```\n\n## Türkçe (tr-TR)\n\n\n### Play kısa açıklama · short (max 80)\n```\nUygulamanın insanlar için ne yaptığını anlatan sade bir cümle\n```\n\n### Play tam açıklama · full (max 4000)\n```\nİlk paragraf: uygulamanın gördüğü iş ve onunla neden daha kolay olduğu, iki üç cümlede.\n\nÖZELLİKLER\n• Tüm cihazlarında eşitlenen listeler ve notlar\n• Bir listeyi ailen ya da arkadaşlarınla paylaş\n• Verilerini dışa aktar ya da hesabını Ayarlar'dan istediğin an sil\n\nÜcretsiz. Hesap isteğe bağlıdır; yalnızca eşitleme ve paylaşım için gerekir.\n```\n\n### Yenilikler · what's new (max 500)\n```\nİlk sürüm. Denediğin için teşekkürler!\n```\n", + "profiles/app/privacy-policy.html": "\n\n\n\n\nPrivacy Policy · Example Studio\n\n\n\n\n
\n \n\n
\n

Privacy Policy

\n

Example Studio · applies to all our apps · last updated 2026-01-01

\n\n

We collect what an app needs to work for you, never sell personal data and show no ads. This page explains what\n each app collects, why, and what you can do about it.

\n\n

What our apps use

\n
\n \n \n \n \n \n
AppServiceWhat and whyYour choice
Example AppAccount (our servers)Email address, account id and the lists and notes you create: to sign you in and sync your content between your devices.The account is optional; export or delete it in Settings > Account
Example AppAnalytics (e.g. a privacy-friendly analytics SDK)Random install id, screens used, device model, OS version: to improve the app.Settings > Analytics off
Example AppCrash reportingCrash logs and device model: to fix crashes.-
Example AppApp storesPurchases and subscriptions are handled by Google Play / the App Store; we never see payment details.-
\n\n

Deleting your account

\n

In the app: Settings > Account > Delete account. Without the app: example.com/delete-account.\n We delete your account and its content within 30 days; backups roll over within 90 days. A store subscription is\n not cancelled by deleting the account: cancel it in your Google Play or App Store subscriptions.

\n\n

Children

\n

Our apps are not directed at children under 13. We do not knowingly collect personal data from children. If you\n believe a child sent us data, contact us and we will delete it.

\n\n

Retention and your rights

\n

We keep account data while the account exists and analytics data for up to 24 months. You can ask for a copy or\n the deletion of your data by writing to privacy@example.com; we answer\n within 30 days.

\n\n

Security

\n

All network traffic of our apps uses HTTPS; passwords are stored only as salted hashes.

\n\n

Changes

\n

When an app starts collecting something new we update this page and its date first.

\n\n

Contact

\n

Example Studio · privacy@example.com

\n
\n\n
\n

Gizlilik Politikası

\n

Example Studio · tüm uygulamalarımız için geçerlidir · son güncelleme 2026-01-01

\n

Yalnızca uygulamanın sizin için çalışması gereken verileri topluyoruz, kişisel verileri asla satmıyoruz ve reklam\n göstermiyoruz. Yukarıdaki tablo her uygulamanın neyi neden topladığını gösterir.

\n

Hesabınızı silmek

\n

Uygulamada: Ayarlar > Hesap > Hesabı sil. Uygulama olmadan: example.com/delete-account.\n Mağaza aboneliği hesap silinince iptal olmaz; Google Play veya App Store aboneliklerinizden iptal edin.

\n

Haklarınız

\n

Verilerinizin bir kopyasını veya silinmesini privacy@example.com adresine\n yazarak isteyebilirsiniz; 30 gün içinde yanıtlıyoruz.

\n
\n
\n\n\n", + "profiles/app/shots-copy.json": "{\n \"en\": {\n \"home\": { \"eyebrow\": \"Example App\", \"title\": [\"Everything in order,\", \"in one calm place\"], \"sub\": \"Lists and notes, synced on all your devices.\" },\n \"detail\": { \"eyebrow\": \"Get it done\", \"title\": [\"See what is left\", \"at a glance\"], \"sub\": \"Tap to tick, swipe to reorder.\" },\n \"share\": { \"eyebrow\": \"Together\", \"title\": [\"Share a list,\", \"plan together\"], \"sub\": \"Invite people by link and edit live.\" },\n \"privacy\": { \"eyebrow\": \"Your data\", \"title\": [\"Private by default,\", \"yours to export\"], \"sub\": \"Export your data or delete your account in Settings.\" }\n },\n \"tr\": {\n \"home\": { \"eyebrow\": \"Example App\", \"title\": [\"Her şey düzenli,\", \"tek sakin bir yerde\"], \"sub\": \"Listeler ve notlar, tüm cihazlarında eşitlenir.\" },\n \"detail\": { \"eyebrow\": \"Bitir\", \"title\": [\"Kalanları\", \"bir bakışta gör\"], \"sub\": \"Dokun işaretle, sürükle sırala.\" },\n \"share\": { \"eyebrow\": \"Birlikte\", \"title\": [\"Listeyi paylaş,\", \"birlikte planla\"], \"sub\": \"Bağlantıyla davet et, canlı düzenleyin.\" },\n \"privacy\": { \"eyebrow\": \"Verilerin\", \"title\": [\"Varsayılan olarak gizli,\", \"dışa aktarması senin\"], \"sub\": \"Verilerini dışa aktar ya da hesabını Ayarlar'dan sil.\" }\n }\n}\n", + "profiles/app/storebound.config.ts": "// storebound config for one app (profile \"app\": a utility / productivity app with an optional account and an\n// analytics SDK, no ads). Every answer below is a starting point: edit it to match your app. Credentials NEVER go\n// here (env vars or .storebound/credentials.json, gitignored). Paths are relative to this file.\n// The type import is optional: it only works when storebound is installed in the project (bun add -d storebound).\nimport type { ShipConfig } from 'storebound';\n\nconst config: ShipConfig = {\n app: {\n name: 'Example App',\n profile: 'app',\n copyright: '2026 Example Studio',\n privacyPolicyUrl: 'https://example.com/privacy',\n // termsUrl: 'https://example.com/terms', // Terms of Use; required once you sell subscriptions\n supportUrl: 'https://example.com/support',\n marketingUrl: 'https://example.com',\n website: 'https://example.com',\n contactEmail: 'support@example.com',\n },\n\n // Accounts (account-deletion skill). Remove this block if the app has no sign-up / sign-in at all.\n // Apple 5.1.1(v): deletion must start inside the app. Google Play: in the app AND on a public web page.\n accounts: {\n enabled: true,\n deletionUrl: 'https://example.com/delete-account', // public page, no login needed to request deletion\n inApp: 'Settings > Account > Delete account', // goes into the App Review notes\n // How people sign up: 'username' (a name picked in the app, no password, no email), 'username-password',\n // 'email-password', 'email-link', 'phone', 'oauth', 'other'. `storebound scan` proposes the account data and\n // dataSafety.accountCreation from it; only the 'email-*' methods mean an email address.\n signUp: ['email-password'],\n },\n\n // Sign-in for store reviewers (review-access skill). The demo account itself lives ONLY in\n // .storebound/credentials.json (\"reviewDemoAccount\") or REVIEW_DEMO_USER / REVIEW_DEMO_PASSWORD.\n reviewAccess: {\n signInRequired: true, // false if every feature works without signing in\n instructions: 'Sign in with the demo account. Sample data is already there; nothing needs a second device.',\n },\n\n // id = your folder / copy key; play = Play language code(s); apple = App Store Connect locale\n languages: [\n { id: 'en', play: 'en-US', apple: 'en-US' },\n { id: 'tr', play: 'tr-TR', apple: 'tr' },\n ],\n\n play: {\n packageName: 'com.example.app',\n defaultLanguage: 'en-US',\n listing: 'store/listing.md',\n icon: 'store/icons/play-512.png',\n },\n\n apple: {\n bundleId: 'com.example.app',\n // name: 'Example: Short Tagline', // App Store name (max 30) when app.name is taken there (\"already being used\"); default app.name\n listing: 'store/appstore.md',\n // Export compliance (export-compliance skill): decide it, then uncomment. false ONLY if the app uses no encryption\n // beyond the OS (HTTPS through the system, standard sign-in).\n // usesNonExemptEncryption: false,\n // ipad: true,\n info: {\n categories: { primary: 'PRODUCTIVITY', secondary: 'UTILITIES' },\n ageRating: {}, // every content question NONE / false; change what applies (user-generated content, web access ...)\n contentRights: 'DOES_NOT_USE_THIRD_PARTY_CONTENT',\n free: true,\n reviewNotes: [\n 'Example App keeps lists and notes in sync across devices. An account is optional on the first screen.',\n 'Privacy policy and Terms: Settings > About.',\n ].join('\\n\\n'),\n },\n },\n\n // In-app purchases (optional): a one-time \"Pro\" unlock, for example. Subscriptions: see the subscription-app profile\n // (storebound init --profile subscription-app) or the store-subscriptions skill.\n // products: [{ id: 'com.example.app.pro', type: 'non_consumable', usd: 4.99,\n // listings: { en: { title: 'Pro', description: 'Unlimited lists and themes.' } }, reviewScreenshot: 'store/iap-review/pro.png' }],\n\n // Google Play Data safety. Types not listed = not collected. Typical app with accounts + an analytics SDK acting as\n // your processor (collected, not shared). Edit to match what your app and its SDKs really do.\n dataSafety: {\n collectsData: true,\n encryptedInTransit: true,\n // Sign-up methods. Google does not publish these ids: compare with Play Console > App content > Data safety >\n // Export to CSV before the first import. The delete-account URL (PSL_ACCOUNT_DELETION_URL) comes from\n // accounts.deletionUrl; `storebound check` names any other row Play requires.\n accountCreation: ['PSL_ACM_USER_ID_PASSWORD'],\n // Can people delete their data without deleting the account (a data-only page)? true needs partialDeletionUrl.\n // Play's \"can users request deletion?\" is answered yes by accounts.deletionUrl. Ask the owner.\n partialDeletion: false,\n data: {\n PSL_EMAIL: { shared: false, collect: ['functionality', 'account'] },\n PSL_USER_ACCOUNT: { shared: false, collect: ['functionality', 'account'] },\n PSL_USER_GENERATED_CONTENT: { shared: false, collect: ['functionality'] },\n PSL_USER_INTERACTION: { shared: false, collect: ['analytics'] },\n PSL_CRASH_LOGS: { shared: false, collect: ['analytics'] },\n PSL_PERFORMANCE_DIAGNOSTICS: { shared: false, collect: ['analytics'] },\n PSL_DEVICE_ID: { shared: false, collect: ['analytics'] },\n },\n },\n\n renders: 'store/renders',\n\n shots: {\n style: 'app', // calm gradient, device frame, headline + subline (game: sparkles and badges)\n copy: 'store/shots/copy.json',\n frames: 'store/shots/frames', // /phone/.png, /tablet/.png\n out: 'store/renders',\n // fonts: { title: 'Inter', body: 'Inter' }, // the app style default\n scenes: [\n { id: 'home', palette: 'mist', frames: [{ src: 'home.png' }] },\n { id: 'detail', palette: 'mint', frames: [{ src: 'detail.png' }] },\n { id: 'share', palette: 'sky', frames: [{ src: 'share.png' }] },\n { id: 'privacy', palette: 'slate', frames: [{ src: 'settings.png' }] },\n ],\n feature: { frame: 'home.png', palette: 'mist' },\n // Raw frames from a web build, the iOS Simulator (ios-sim) or an Android emulator (android-emu): sign in to a demo\n // account with realistic sample data, never real user data (store-screenshots skill).\n capture: {\n baseUrl: 'http://127.0.0.1:5173',\n query: 'lang={lang}&demo=1',\n shots: {\n 'home': { path: '/', delayMs: 1000, device: 'both' },\n 'detail': { path: '/lists/1', delayMs: 1000, device: 'both' },\n 'share': { path: '/lists/1/share', delayMs: 1000, device: 'both' },\n 'settings': { path: '/settings', delayMs: 1000, device: 'both' },\n },\n },\n },\n\n icons: {\n composite: 'store/icon/icon.png',\n foreground: 'store/icon/foreground.png',\n background: 'store/icon/background.png',\n out: 'store/icons',\n },\n};\n\nexport default config;\n", + "profiles/app/workflows/beta.yml": "# storebound flow \"beta\": a TestFlight / internal testing build for your testers.\n# storebound flow run beta VERSION=1.2.0 [--apply]\ndescription: Next build to TestFlight and Play internal testing\nparams:\n VERSION: \"1.0.0\"\n GROUP: \"Internal\"\nsteps:\n - id: check\n run: check\n - id: next-build\n if: apple\n run: apple next-build\n capture: BUILD\n - id: build\n if: apple\n run: apple build --version ${VERSION} --build-number ${BUILD}\n - id: upload\n if: apple\n run: apple upload --no-wait\n - id: wait\n if: apple\n run: apple testflight wait --build ${BUILD} --version ${VERSION}\n - id: what-to-test\n if: apple\n run: apple testflight what-to-test --build ${BUILD}\n - id: distribute\n if: apple\n run: apple testflight distribute --build ${BUILD} --group ${GROUP}\n - id: play-build\n if: play\n run: play build --version-name ${VERSION}\n - id: play-upload\n if: play\n run: play upload --track internal\n - id: external\n owner: \"External testers: storebound apple testflight distribute --build ${BUILD} --group --submit-review (Beta App Review is your call)\"\n", + "profiles/app/workflows/release.yml": "# storebound flow \"release\": one version of the app on both stores, up to your Submit click.\n# storebound flow run release VERSION=1.2.0 reads run, writes run as dry runs (nothing changes)\n# storebound flow run release VERSION=1.2.0 --apply writes run for real; each command still asks you first\n# storebound flow run release --resume --apply continue after fixing a failed step\n# Steps are storebound commands (no \"storebound\" prefix). `if: apple` / `if: play` skip a store the config does not\n# have; `owner:` steps are never run, they list what you click. Edit freely: this file is yours.\ndescription: Build, upload and check one version on both stores, then your Submit click\nparams:\n VERSION: \"1.0.0\"\nsteps:\n - id: next-build\n if: apple\n run: apple next-build\n capture: BUILD\n - id: build\n if: apple\n run: apple build --version ${VERSION} --build-number ${BUILD}\n - id: upload\n if: apple\n run: apple upload --no-wait\n - id: wait\n if: apple\n run: apple testflight wait --build ${BUILD} --version ${VERSION}\n - id: play-build\n if: play\n run: play build --version-name ${VERSION}\n - id: play-upload\n if: play\n run: play upload --track internal\n - id: whats-new\n if: exists store/whats-new\n run: whats-new apply\n - id: listing\n if: apple\n run: apple listing --version ${VERSION} --build ${BUILD}\n - id: validate\n run: validate\n - id: scan\n run: scan\n - id: plan\n run: release plan ${VERSION}\n - id: submit\n owner: \"App Store Connect: Submit for Review (version ${VERSION}, build ${BUILD}). Play Console: promote the internal release and Send changes for review\"\n", + "profiles/subscription-app/app-privacy.md": "# Example App · App Store \"App Privacy\" answers\n\nApp Store Connect > your app > App Privacy. **Web UI only, there is no API.** Fill it from this table, then press\n**Publish** (saving alone does not publish it). Keep it consistent with the privacy policy and the Play Data safety\nanswers (`dataSafety` in `storebound.config.ts`). **Edit to match your app.**\n\nTypical subscription app: account (email), content synced to your backend, auto-renewable subscriptions checked by\nyour server, an analytics SDK acting as your processor, no ads, no tracking.\n\n**Do you or your third-party partners collect data from this app?** Yes\n\n**Tracking**: No (no ad SDK, no data broker, nothing combined with other companies' data for advertising). If that\nchanges, add the App Tracking Transparency prompt first.\n\n| data type | collected | used to track | linked to the user | purposes |\n|---|---|---|---|---|\n| Contact Info > Email Address | yes (account) | no | yes | App Functionality |\n| Identifiers > User ID | yes (account id) | no | yes | App Functionality, Analytics |\n| User Content > Other User Content (lists, notes) | yes (synced) | no | yes | App Functionality |\n| Usage Data > Product Interaction | yes (analytics) | no | yes if tied to the account, else no | Analytics |\n| Purchases > Purchase History (subscription status) | yes | no | yes | App Functionality |\n| Diagnostics > Crash Data | yes | no | no | App Functionality, Analytics |\n| Diagnostics > Performance Data | yes | no | no | Analytics |\n\n\"Linked to you\" is **Yes** for what you store under the account. Not collected (typical): Location, Health and\nFitness, Financial Info, Contacts, Photos or Videos (unless people attach them), Browsing and Search History,\nSensitive Info.\n\nPer data type the UI asks, in order: purposes > \"Is this data linked to the user's identity?\" > \"Do you or your\nthird-party partners use this data for tracking purposes?\".\n", + "profiles/subscription-app/appstore.md": "# Example App · App Store listing copy\n\nSource for `storebound apple listing`. One `## ` section per locale (the first word is the locale code:\n`en-US`, `tr`, `de-DE`, `ja`, `zh-Hans`, `ar-SA`, ...), one fenced block per `### `, field names exactly:\n`name` (optional, defaults to `apple.name`, else `app.name`), `subtitle` (30), `promotionalText` (170), `keywords` (100 bytes, comma\nseparated, no spaces after commas, do not repeat the app name), `description` (4000), `whatsNew` (optional; not allowed\non the first version). Shared values (privacy policy, support and marketing URL) come from `storebound.config.ts`.\n\n---\n\n## en-US · English (U.S.)\n\n### subtitle\n```\nLists and notes, in sync\n```\n\n### promotionalText\n```\nOne or two sentences you can change any time without a new version.\n```\n\n### keywords\n```\ntodo,checklist,groceries,planner,notes,shared,family,organizer,tasks,sync\n```\n\n### description\n```\nFirst paragraph: the job the app does and why it is easier with it.\n\nFEATURES\n• Lists and notes that sync across your devices\n• Share a list with family or friends\n• Export your data or delete your account in Settings, any time\n\nFree to use with up to 3 lists. Example Pro unlocks unlimited lists, sharing and themes: yearly with a 1-week free trial, or monthly. Payment is charged to your store account; the subscription renews automatically unless cancelled at least 24 hours before the end of the period, and you can manage or cancel it in your account settings.\n\nTerms of Use: https://example.com/terms\nPrivacy Policy: https://example.com/privacy\n```\n\n## tr · Türkçe\n\n### subtitle\n```\nListeler ve notlar, eşitli\n```\n\n### promotionalText\n```\nYeni sürüm göndermeden istediğin zaman değiştirebileceğin bir iki cümle.\n```\n\n### keywords\n```\nyapılacaklar,kontrol,market,planlayıcı,not,paylaşım,aile,düzen,görev\n```\n\n### description\n```\nİlk paragraf: uygulamanın gördüğü iş ve onunla neden daha kolay olduğu.\n\nÖZELLİKLER\n• Tüm cihazlarında eşitlenen listeler ve notlar\n• Bir listeyi ailen ya da arkadaşlarınla paylaş\n• Verilerini dışa aktar ya da hesabını Ayarlar'dan istediğin an sil\n\n3 listeye kadar ücretsiz. Example Pro sınırsız liste, paylaşım ve tema açar: 1 hafta ücretsiz denemeli yıllık ya da aylık. Ödeme mağaza hesabınızdan alınır; abonelik, dönem bitmeden en az 24 saat önce iptal edilmezse otomatik yenilenir; hesap ayarlarınızdan yönetebilir ya da iptal edebilirsiniz.\n\nKullanım Koşulları: https://example.com/terms\nGizlilik Politikası: https://example.com/privacy\n```\n", + "profiles/subscription-app/declarations.md": "# Example App · store declarations (answers to copy into the consoles)\n\nStarting answers for a typical **subscription app**: auto-renewable subscriptions (free trial on the yearly plan), an account (email + password, or sign in\nwith a provider), an analytics SDK acting as your processor, data synced to your own backend, no ads, no chat with\nstrangers, general audience. **Edit every line to match your app**: these are your statements to the stores. Everything\nmarked **UI** has no API: Play Console > your app > Policy and programs > **App content**, and App Store Connect for\nApple. Data safety (Play) has an API: `storebound play data-safety`.\n\n## Google Play · App content (UI)\n\n| declaration | answer |\n|---|---|\n| Privacy policy | your policy URL (same one in the listing and inside the app) |\n| App access | \"All or some functionality is restricted\": add the demo account (username, password) and the steps from `reviewAccess.instructions`; credentials must work at all times, no 2FA / OTP (review-access skill) |\n| Ads | No, my app does not contain ads |\n| Content rating | IARC questionnaire, below |\n| Target audience and content | 18 and over (or 13+ if teens use it); \"Could your app unintentionally appeal to children?\" No (only if true) |\n| News app | No |\n| Data safety | `storebound play data-safety`, then check the summary; with accounts: the delete-account URL (`accounts.deletionUrl`) |\n| Government apps | No |\n| Financial features | My app doesn't provide any financial features |\n| Health apps | No health features |\n| Advertising ID | No (unless an SDK in your app reads it: check the SDK docs and your merged manifest) |\n| Store settings | App, category (Productivity, Tools, ...); contact email + website |\n\n## Account deletion (account-deletion skill)\n\n- In the app: Settings > Account > Delete account deletes the account and its data (not only \"deactivate\").\n- On the web: `accounts.deletionUrl` lets people request deletion without the app; Play asks for it in Data safety.\n- With subscriptions: say that deleting the account does not cancel the store subscription, and how to cancel.\n\n## IARC content rating (Play UI; Apple has its own form, `storebound apple appinfo`)\n\nCategory: not a game (e.g. Utility, Productivity, Communication, or Social Networking if people share content with\nothers). Typical answers for a lists / notes app:\n\n| topic | answer |\n|---|---|\n| Violence, sexuality, language, controlled substances | No |\n| Gambling (simulated / real money) | No |\n| Users interact or exchange content | Yes only if people can share with others outside their own invitations; then also moderation and reporting |\n| Shares location with other users | No |\n| Digital purchases | Yes (subscriptions) |\n| Unrestricted internet / browser | No |\n\nExpected: PEGI 3, ESRB Everyone, IARC 3+. Apple age rating 4+ (all content questions None) unless users share\ncontent publicly.\n\n## App Store (UI-only parts)\n\n- App record: App Store Connect > Apps > + > New App. No API.\n- App Privacy: `app-privacy.md`, then **Publish**.\n- App Review information: `storebound apple review-details` sends the contact, the demo account (from\n `.storebound/credentials.json`, masked in the log) and the notes.\n- Export compliance (export-compliance skill): `ITSAppUsesNonExemptEncryption = NO` only if true.\n- Guideline 5.1.1: the privacy policy link must also be reachable **inside the app**; 5.1.1(v): account deletion\n inside the app.\n\n## Subscriptions (store-subscriptions skill)\n\n- Purchase screen (both stores): plan name, length, the full renewal price per period (most prominent), what is\n included, free trial length and what is charged after it, how to cancel, Restore Purchases, links to the Terms of Use\n and the Privacy Policy. Apple 3.1.2; Google Play subscriptions policy.\n- Metadata: the App Store description (or a custom EULA in App Information > License Agreement) links the Terms of\n Use; the Privacy Policy URL field is set by `storebound apple listing`.\n- First subscriptions are submitted together with an app version (App Store Connect: add them to the version on its\n page before Submit for Review), like first in-app purchases.\n- App Review notes say where the paywall is and that sandbox purchases work with the demo account.\n- Play: base plans and offers must be active before people can buy; new subscribers see the converted regional prices.\n", + "profiles/subscription-app/listing.md": "# Example App · Google Play listing copy\n\nSource for `storebound play listing` (title, short + full description) and `storebound play release` (release notes).\nRules the parser follows:\n- every `## ...` section with a `` line is a listing; one section can serve several Play codes\n- under it, `### ...` headings that contain **title** (optional, defaults to `app.name`), **short**, **full** and\n **what's new**, each followed by one fenced block\n- limits (checked before anything is sent): title 30, short 80, full 4000, what's new 500 characters\nOnly claim what the app really does. No prices, no \"best / #1\", no other apps' names.\n\n---\n\n## English (en-US)\n\n\n### Play short description (max 80)\n```\nWhat the app does for people, in one plain line\n```\n\n### Play full description (max 4000)\n```\nFirst paragraph: the job the app does and why it is easier with it, in two or three sentences.\n\nFEATURES\n• Lists and notes that sync across your devices\n• Share a list with family or friends\n• Export your data or delete your account in Settings, any time\n\nFree to use with up to 3 lists. Example Pro unlocks unlimited lists, sharing and themes: yearly with a 1-week free trial, or monthly. Payment is charged to your store account; the subscription renews automatically unless cancelled at least 24 hours before the end of the period, and you can manage or cancel it in your account settings.\n\nTerms of Use: https://example.com/terms\nPrivacy Policy: https://example.com/privacy\n```\n\n### What's new (max 500)\n```\nFirst release. Thanks for trying it!\n```\n\n## Türkçe (tr-TR)\n\n\n### Play kısa açıklama · short (max 80)\n```\nUygulamanın insanlar için ne yaptığını anlatan sade bir cümle\n```\n\n### Play tam açıklama · full (max 4000)\n```\nİlk paragraf: uygulamanın gördüğü iş ve onunla neden daha kolay olduğu, iki üç cümlede.\n\nÖZELLİKLER\n• Tüm cihazlarında eşitlenen listeler ve notlar\n• Bir listeyi ailen ya da arkadaşlarınla paylaş\n• Verilerini dışa aktar ya da hesabını Ayarlar'dan istediğin an sil\n\n3 listeye kadar ücretsiz. Example Pro sınırsız liste, paylaşım ve tema açar: 1 hafta ücretsiz denemeli yıllık ya da aylık. Ödeme mağaza hesabınızdan alınır; abonelik, dönem bitmeden en az 24 saat önce iptal edilmezse otomatik yenilenir; hesap ayarlarınızdan yönetebilir ya da iptal edebilirsiniz.\n\nKullanım Koşulları: https://example.com/terms\nGizlilik Politikası: https://example.com/privacy\n```\n\n### Yenilikler · what's new (max 500)\n```\nİlk sürüm. Denediğin için teşekkürler!\n```\n", + "profiles/subscription-app/storebound.config.ts": "// storebound config for one app (profile \"subscription-app\": accounts plus auto-renewable subscriptions, an\n// analytics SDK, no ads). Every answer below is a starting point: edit it to match your app. Credentials NEVER go\n// here (env vars or .storebound/credentials.json, gitignored). Paths are relative to this file.\n// The type import is optional: it only works when storebound is installed in the project (bun add -d storebound).\nimport type { ShipConfig } from 'storebound';\n\nconst config: ShipConfig = {\n app: {\n name: 'Example App',\n profile: 'subscription-app',\n copyright: '2026 Example Studio',\n privacyPolicyUrl: 'https://example.com/privacy',\n termsUrl: 'https://example.com/terms', // Terms of Use (EULA): in the app and the metadata (Apple 3.1.2)\n supportUrl: 'https://example.com/support',\n marketingUrl: 'https://example.com',\n website: 'https://example.com',\n contactEmail: 'support@example.com',\n },\n\n // Accounts (account-deletion skill). Remove this block if the app has no sign-up / sign-in at all.\n // Apple 5.1.1(v): deletion must start inside the app. Google Play: in the app AND on a public web page.\n accounts: {\n enabled: true,\n deletionUrl: 'https://example.com/delete-account', // public page, no login needed to request deletion\n inApp: 'Settings > Account > Delete account', // goes into the App Review notes\n // How people sign up: 'username' (a name picked in the app, no password, no email), 'username-password',\n // 'email-password', 'email-link', 'phone', 'oauth', 'other'. `storebound scan` proposes the account data and\n // dataSafety.accountCreation from it; only the 'email-*' methods mean an email address.\n signUp: ['email-password'],\n },\n\n // Sign-in for store reviewers (review-access skill). The demo account itself lives ONLY in\n // .storebound/credentials.json (\"reviewDemoAccount\") or REVIEW_DEMO_USER / REVIEW_DEMO_PASSWORD.\n reviewAccess: {\n signInRequired: true, // false if every feature works without signing in\n instructions: 'Sign in with the demo account. Sample data is already there; nothing needs a second device.',\n },\n\n // id = your folder / copy key; play = Play language code(s); apple = App Store Connect locale\n languages: [\n { id: 'en', play: 'en-US', apple: 'en-US' },\n { id: 'tr', play: 'tr-TR', apple: 'tr' },\n ],\n\n play: {\n packageName: 'com.example.app',\n defaultLanguage: 'en-US',\n listing: 'store/listing.md',\n icon: 'store/icons/play-512.png',\n },\n\n apple: {\n bundleId: 'com.example.app',\n // name: 'Example: Short Tagline', // App Store name (max 30) when app.name is taken there (\"already being used\"); default app.name\n listing: 'store/appstore.md',\n // Export compliance (export-compliance skill): decide it, then uncomment. false ONLY if the app uses no encryption\n // beyond the OS (HTTPS through the system, standard sign-in).\n // usesNonExemptEncryption: false,\n // ipad: true,\n info: {\n categories: { primary: 'PRODUCTIVITY', secondary: 'UTILITIES' },\n ageRating: {}, // every content question NONE / false; change what applies (user-generated content, web access ...)\n contentRights: 'DOES_NOT_USE_THIRD_PARTY_CONTENT',\n free: true,\n reviewNotes: [\n 'Example App keeps lists and notes in sync across devices. Pro (yearly with a 1-week free trial, or monthly) unlocks unlimited lists, sharing and themes.',\n 'Subscriptions: Settings > Pro (or the paywall after the third list). Restore Purchases is on the paywall and in Settings > Pro.',\n 'Privacy policy and Terms of Use: on the paywall and in Settings > About.',\n ].join('\\n\\n'),\n },\n },\n\n // Auto-renewable subscriptions (store-subscriptions skill): `storebound apple subscriptions`, `storebound play\n // subscriptions`. Apple: one group, levels rank the plans (1 = highest). Play: one subscription per entry with one\n // auto-renewing base plan. Prices: USD base, Apple equalizes from the USA, Play converts per region; overrides set a\n // region by hand. The disclosure (price per period, renewal, how to cancel, Terms + Privacy links) belongs on the\n // purchase screen and in the descriptions.\n subscriptions: [\n {\n name: 'Example Pro',\n listings: { en: { name: 'Example Pro' }, tr: { name: 'Example Pro' } },\n subscriptions: [\n {\n id: 'com.example.app.pro_yearly', period: 'P1Y', level: 1, usd: 29.99,\n listings: {\n en: { title: 'Pro yearly', description: 'Unlimited lists, sharing and themes.', benefits: ['Unlimited lists', 'Shared lists', 'All themes'] },\n tr: { title: 'Pro yıllık', description: 'Sınırsız liste, paylaşım ve tema.', benefits: ['Sınırsız liste', 'Paylaşılan listeler', 'Tüm temalar'] },\n },\n intro: { type: 'free', duration: 'P1W' }, // 1 week free trial for new subscribers\n reviewScreenshot: 'store/iap-review/paywall.png',\n },\n {\n id: 'com.example.app.pro_monthly', period: 'P1M', level: 2, usd: 3.99,\n overrides: [{ region: 'TR', currency: 'TRY', price: 99.99 }],\n listings: {\n en: { title: 'Pro monthly', description: 'Unlimited lists, sharing and themes.', benefits: ['Unlimited lists', 'Shared lists', 'All themes'] },\n tr: { title: 'Pro aylık', description: 'Sınırsız liste, paylaşım ve tema.', benefits: ['Sınırsız liste', 'Paylaşılan listeler', 'Tüm temalar'] },\n },\n reviewScreenshot: 'store/iap-review/paywall.png',\n },\n ],\n },\n ],\n\n // Google Play Data safety. Types not listed = not collected. Typical app with accounts + an analytics SDK acting as\n // your processor (collected, not shared). Edit to match what your app and its SDKs really do.\n dataSafety: {\n collectsData: true,\n encryptedInTransit: true,\n // Sign-up methods. Google does not publish these ids: compare with Play Console > App content > Data safety >\n // Export to CSV before the first import. The delete-account URL (PSL_ACCOUNT_DELETION_URL) comes from\n // accounts.deletionUrl; `storebound check` names any other row Play requires.\n accountCreation: ['PSL_ACM_USER_ID_PASSWORD'],\n // Can people delete their data without deleting the account (a data-only page)? true needs partialDeletionUrl.\n // Play's \"can users request deletion?\" is answered yes by accounts.deletionUrl. Ask the owner.\n partialDeletion: false,\n data: {\n PSL_EMAIL: { shared: false, collect: ['functionality', 'account'] },\n PSL_USER_ACCOUNT: { shared: false, collect: ['functionality', 'account'] },\n PSL_USER_GENERATED_CONTENT: { shared: false, collect: ['functionality'] },\n PSL_USER_INTERACTION: { shared: false, collect: ['analytics'] },\n PSL_CRASH_LOGS: { shared: false, collect: ['analytics'] },\n PSL_PERFORMANCE_DIAGNOSTICS: { shared: false, collect: ['analytics'] },\n PSL_DEVICE_ID: { shared: false, collect: ['analytics'] },\n PSL_PURCHASE_HISTORY: { shared: false, collect: ['functionality'] }, // subscription status tied to the account\n },\n },\n\n renders: 'store/renders',\n\n shots: {\n style: 'app', // calm gradient, device frame, headline + subline (game: sparkles and badges)\n copy: 'store/shots/copy.json',\n frames: 'store/shots/frames', // /phone/.png, /tablet/.png\n out: 'store/renders',\n // fonts: { title: 'Inter', body: 'Inter' }, // the app style default\n scenes: [\n { id: 'home', palette: 'mist', frames: [{ src: 'home.png' }] },\n { id: 'detail', palette: 'mint', frames: [{ src: 'detail.png' }] },\n { id: 'share', palette: 'sky', frames: [{ src: 'share.png' }] },\n { id: 'privacy', palette: 'slate', frames: [{ src: 'settings.png' }] },\n ],\n feature: { frame: 'home.png', palette: 'mist' },\n // Raw frames from a web build, the iOS Simulator (ios-sim) or an Android emulator (android-emu): sign in to a demo\n // account with realistic sample data, never real user data (store-screenshots skill).\n capture: {\n baseUrl: 'http://127.0.0.1:5173',\n query: 'lang={lang}&demo=1',\n shots: {\n 'home': { path: '/', delayMs: 1000, device: 'both' },\n 'detail': { path: '/lists/1', delayMs: 1000, device: 'both' },\n 'share': { path: '/lists/1/share', delayMs: 1000, device: 'both' },\n 'settings': { path: '/settings', delayMs: 1000, device: 'both' },\n },\n },\n },\n\n icons: {\n composite: 'store/icon/icon.png',\n foreground: 'store/icon/foreground.png',\n background: 'store/icon/background.png',\n out: 'store/icons',\n },\n};\n\nexport default config;\n", + "shots-copy.json": "{\n \"en\": {\n \"hero\": { \"title\": [\"Tap. Match.\", \"Repeat!\"], \"badges\": [[\"100+ levels\", \"and more every update\"], [\"Play offline\", \"No account needed\"]] },\n \"play\": { \"eyebrow\": \"Play\", \"title\": [\"Easy to learn,\", \"hard to put down\"], \"sub\": \"One thumb, short rounds, big satisfying moments.\", \"badges\": [[\"Quick rounds\", \"Two minutes each\"], [\"Great!\"]] },\n \"levels\": { \"eyebrow\": \"Worlds\", \"title\": [\"Explore new\", \"worlds\"], \"sub\": \"A fresh look and new rules every ten levels.\", \"badges\": [[\"10 worlds\", \"A new one every 10 levels\"]] },\n \"stars\": { \"eyebrow\": \"Stars\", \"title\": [\"Play smart,\", \"grab 3 stars\"], \"sub\": \"Finish with moves to spare and collect every star.\", \"badges\": [[\"3 stars\"]] }\n },\n \"tr\": {\n \"hero\": { \"title\": [\"Dokun. Eşleştir.\", \"Tekrarla!\"], \"badges\": [[\"100+ bölüm\", \"her güncellemede yenileri\"], [\"İnternetsiz\", \"hesap gerekmez\"]] },\n \"play\": { \"eyebrow\": \"Oyna\", \"title\": [\"Öğrenmesi kolay,\", \"bırakması zor\"], \"sub\": \"Tek parmak, kısa turlar, büyük keyifli anlar.\", \"badges\": [[\"Kısa turlar\", \"Her biri iki dakika\"], [\"Harika!\"]] },\n \"levels\": { \"eyebrow\": \"Dünyalar\", \"title\": [\"Yeni dünyaları\", \"keşfet\"], \"sub\": \"Her on bölümde yeni bir görünüm ve yeni kurallar.\", \"badges\": [[\"10 dünya\", \"Her 10 bölümde yeni biri\"]] },\n \"stars\": { \"eyebrow\": \"Yıldızlar\", \"title\": [\"Akıllı oyna,\", \"3 yıldızı kap\"], \"sub\": \"Hamle artırarak bitir, tüm yıldızları topla.\", \"badges\": [[\"3 yıldız\"]] }\n },\n \"es\": {\n \"hero\": { \"title\": [\"Toca. Combina.\", \"¡Repite!\"], \"badges\": [[\"100+ niveles\", \"y más en cada versión\"], [\"Sin conexión\", \"Sin cuenta\"]] },\n \"play\": { \"eyebrow\": \"Juega\", \"title\": [\"Fácil de aprender,\", \"difícil de soltar\"], \"sub\": \"Un pulgar, rondas cortas, grandes momentos.\", \"badges\": [[\"Rondas cortas\", \"Dos minutos cada una\"], [\"¡Genial!\"]] },\n \"levels\": { \"eyebrow\": \"Mundos\", \"title\": [\"Explora\", \"nuevos mundos\"], \"sub\": \"Un aspecto nuevo y nuevas reglas cada diez niveles.\", \"badges\": [[\"10 mundos\", \"Uno nuevo cada 10 niveles\"]] },\n \"stars\": { \"eyebrow\": \"Estrellas\", \"title\": [\"Juega con cabeza,\", \"logra 3 estrellas\"], \"sub\": \"Termina con movimientos de sobra y consigue todas las estrellas.\", \"badges\": [[\"3 estrellas\"]] }\n },\n \"ja\": {\n \"hero\": { \"title\": [\"タップして、そろえて、\", \"もう一回!\"], \"badges\": [[\"100以上のステージ\", \"アップデートで追加\"], [\"オフラインで遊べる\", \"アカウント不要\"]] },\n \"play\": { \"eyebrow\": \"プレイ\", \"title\": [\"かんたんなのに\", \"やめられない\"], \"sub\": \"片手で遊べる短いラウンド、気持ちいい瞬間がたくさん。\", \"badges\": [[\"短いラウンド\", \"1回2分\"], [\"すごい!\"]] },\n \"levels\": { \"eyebrow\": \"ワールド\", \"title\": [\"新しいワールドを\", \"冒険しよう\"], \"sub\": \"10ステージごとに新しい見た目と新しいルール。\", \"badges\": [[\"10のワールド\", \"10ステージごとに追加\"]] },\n \"stars\": { \"eyebrow\": \"スター\", \"title\": [\"かしこく遊んで\", \"星を3つゲット\"], \"sub\": \"手数を残してクリアし、すべての星を集めよう。\", \"badges\": [[\"星3つ\"]] }\n },\n \"ar\": {\n \"hero\": { \"title\": [\"انقر. طابق.\", \"كرّر!\"], \"badges\": [[\"أكثر من 100 مرحلة\", \"والمزيد مع كل تحديث\"], [\"العب دون اتصال\", \"دون حساب\"]] },\n \"play\": { \"eyebrow\": \"العب\", \"title\": [\"سهلة التعلّم،\", \"صعبة التوقف\"], \"sub\": \"إبهام واحد، جولات قصيرة، ولحظات ممتعة.\", \"badges\": [[\"جولات قصيرة\", \"دقيقتان لكل جولة\"], [\"رائع!\"]] },\n \"levels\": { \"eyebrow\": \"العوالم\", \"title\": [\"استكشف\", \"عوالم جديدة\"], \"sub\": \"شكل جديد وقواعد جديدة كل عشر مراحل.\", \"badges\": [[\"10 عوالم\", \"عالم جديد كل 10 مراحل\"]] },\n \"stars\": { \"eyebrow\": \"النجوم\", \"title\": [\"العب بذكاء،\", \"واجمع 3 نجوم\"], \"sub\": \"أنهِ المرحلة بحركات متبقية واجمع كل النجوم.\", \"badges\": [[\"3 نجوم\"]] }\n }\n}\n", + "storebound.config.ts": "// storebound config for one game. Everything game-specific lives here; credentials NEVER do (they go in env vars or\n// .storebound/credentials.json, which must stay gitignored). Paths are relative to this file.\n// The type import is optional: it only works when storebound is installed in the project (bun add -d ).\nimport type { ShipConfig } from 'storebound';\n\nconst config: ShipConfig = {\n app: {\n name: 'Example Game',\n profile: 'game',\n copyright: '2026 Example Studio',\n privacyPolicyUrl: 'https://example.com/privacy',\n supportUrl: 'https://example.com',\n marketingUrl: 'https://example.com',\n // Developer website on Play: the same domain must serve /app-ads.txt if the game shows ads\n website: 'https://example.com',\n contactEmail: 'support@example.com',\n },\n\n // id = your folder / copy key; play = Play language code(s); apple = App Store Connect locale\n languages: [\n { id: 'en', play: 'en-US', apple: 'en-US' },\n { id: 'tr', play: 'tr-TR', apple: 'tr' },\n { id: 'es', play: ['es-ES', 'es-419'], apple: 'es-ES' },\n { id: 'ja', play: 'ja-JP', apple: 'ja' },\n { id: 'ar', play: 'ar', apple: 'ar-SA', rtl: true },\n ],\n\n play: {\n packageName: 'com.example.game',\n defaultLanguage: 'en-US',\n listing: 'store/listing.md',\n icon: 'store/icons/play-512.png',\n // read by `storebound scan` (fill in what is true for your account and app):\n // accountType: 'personal', accountCreated: 'YYYY-MM-DD', // new personal accounts: 12 testers x 14 days closed test\n // targetAges: ['13-15', '16-17', '18+'], // App content > Target audience; under 13 = Families policy\n },\n\n apple: {\n bundleId: 'com.example.game',\n // name: 'Example: Short Tagline', // App Store name (max 30) when app.name is taken there (\"already being used\"); default app.name\n // appId: '1234567890', // Apple ID of the app record; looked up by bundle id when left out\n listing: 'store/appstore.md',\n reviewShots: 'store/iap-review',\n // Export compliance (export-compliance skill). false ONLY if the game uses no encryption beyond the OS (HTTPS through\n // the system). Left unset on purpose: decide it, then uncomment. Same answer as ITSAppUsesNonExemptEncryption.\n // usesNonExemptEncryption: false,\n // ipad: true, // the app runs on iPad: 13\" iPad screenshots (2064 x 2752, ipad13) are rendered, checked and uploaded\n // iphoneMedium: true, // optional 6.3\" iPhone set (1206 x 2622, iphone-medium); off: Apple scales the 6.5\" set down\n // screenshotTypes: ['APP_IPHONE_67'], // what `storebound validate` requires per locale (default; + APP_IPAD_PRO_3GEN_129 with ipad)\n // TestFlight (`storebound apple testflight`, testflight skill). Tester emails live in .storebound/testers.csv only.\n testflight: {\n groups: [\n { name: 'Team', internal: true, allBuilds: true },\n { name: 'Friends' },\n ],\n description: { en: 'Example Game beta: try the new levels and tell us what feels too hard.' },\n },\n info: {\n categories: { primary: 'GAMES', primarySubOne: 'GAMES_CASUAL', primarySubTwo: 'GAMES_PUZZLE', secondary: 'ENTERTAINMENT' },\n ageRating: { advertising: true }, // everything else NONE / false\n contentRights: 'DOES_NOT_USE_THIRD_PARTY_CONTENT',\n free: true,\n reviewNotes: [\n 'Single-player game. No account or login is needed; there is no user-generated content or chat.',\n 'In-app purchases: Home > Shop. Restore Purchases is in Settings.',\n 'Ads: optional rewarded videos and interstitials between rounds. The App Tracking Transparency prompt is shown before the ad SDK starts.',\n 'Privacy policy: Settings > Privacy.',\n ].join('\\n\\n'),\n },\n },\n\n // Price changes later for many products / regions: storebound prices export, edit store/prices.csv, prices import --dry.\n // prices: { csv: 'store/prices.csv', baseTerritory: 'USA' },\n\n // Same ids on both stores. Prices: USD base; Play converts per region, Apple equalizes from the USA. Overrides set a\n // region by hand (Apple needs an existing price point with exactly that value).\n products: [\n {\n id: 'com.example.game.remove_ads', type: 'non_consumable', usd: 2.99,\n overrides: [{ region: 'TR', currency: 'TRY', price: 99.99 }],\n listings: {\n en: { title: 'Remove Ads', description: 'Removes interstitial ads for good.' },\n tr: { title: 'Reklamları Kaldır', description: 'Ara reklamları kalıcı olarak kaldırır.' },\n },\n reviewScreenshot: 'store/iap-review/shop.png',\n },\n {\n id: 'com.example.game.coins_small', type: 'consumable', usd: 0.99,\n listings: {\n en: { title: '500 Coins', description: 'A small bag of coins.' },\n tr: { title: '500 Altın', description: 'Küçük bir kese altın.' },\n },\n reviewScreenshot: 'store/iap-review/shop.png',\n },\n ],\n\n // Google Play Data safety. Types not listed = not collected. Purposes: functionality, analytics, communications,\n // fraud, advertising, personalization, account. Typical F2P game with an analytics SDK + ad mediation:\n dataSafety: {\n collectsData: true,\n encryptedInTransit: true,\n // No accounts: PSL_ACM_NONE. A guest account with a name picked on the device and a server token (no password, no\n // email): accounts { enabled: true, signUp: ['username'], ... } and ['PSL_ACM_USER_ID_OTHER_AUTH'] here (Play's\n // \"Username and other authentication\"; `storebound scan` proposes it). With accounts Play also requires the\n // delete-account URL (from accounts.deletionUrl). That page also answers \"can users request that their data is\n // deleted?\" with yes (dataDeletion: 'yes' | 'no' | 'auto90' only for apps without one). partialDeletion: true +\n // partialDeletionUrl when a page deletes data without deleting the account. `storebound check` compares every row with Play's\n // current form (cli/data/play-data-safety-form.csv).\n accountCreation: ['PSL_ACM_NONE'],\n data: {\n PSL_APPROX_LOCATION: { shared: true, collect: ['analytics', 'advertising', 'fraud'], share: ['analytics', 'advertising', 'fraud'] },\n PSL_USER_INTERACTION: { shared: true, collect: ['analytics', 'advertising'], share: ['analytics', 'advertising'] },\n PSL_OTHER_APP_ACTIVITY: { shared: false, collect: ['functionality'] },\n PSL_PERFORMANCE_DIAGNOSTICS: { shared: true, collect: ['analytics', 'advertising', 'fraud'], share: ['analytics', 'advertising', 'fraud'] },\n PSL_DEVICE_ID: { shared: true, collect: ['functionality', 'analytics', 'advertising', 'fraud'], share: ['analytics', 'advertising', 'fraud'] },\n PSL_PURCHASE_HISTORY: { shared: false, collect: ['functionality', 'fraud'] },\n },\n },\n\n // Ads: `storebound validate --play` checks /app-ads.txt for these lines (copy them from your mediation\n // dashboard's app-ads.txt page). Placeholder line below; replace it.\n // ads: { appAdsTxt: ['example-network.com, pub-0000000000000000, DIRECT, 0000000000000000'] },\n\n renders: 'store/renders',\n\n shots: {\n copy: 'store/shots/copy.json',\n frames: 'store/shots/frames', // /phone/.png, /tablet/.png\n out: 'store/renders',\n logo: 'store/shots/logo.png', // optional: hero scene + feature graphic\n // Google Fonts; both cover Latin Extended (tr, pl, cs, ro ...) and Vietnamese. `shots fonts` checks every glyph of the\n // copy; a character the font lacks needs a font that has it or fallback: ['Noto Sans Display'] (else shots check fails).\n fonts: { title: 'Paytone One', body: 'Montserrat' },\n // phone: { w: 440, h: 902, dpr: 3 }, // raw frame size in CSS px (screen below a 54 px status bar)\n // tablet: { w: 1032, h: 1352, dpr: 2 }, // default with apple.ipad (13\" iPad below its 24 px bar), else 800 x 1240\n scenes: [\n { id: 'hero', palette: 'fire', hero: true, over: 34, frames: [{ src: 'hero.png', rotate: -4 }],\n badges: [{ y: 0.42, side: 'l', icon: 'flag', iconBg: 'red', rot: -4 }, { y: 0.66, side: 'r', icon: 'offline', iconBg: 'red', rot: 3 }] },\n { id: 'play', palette: 'violet', frames: [{ src: 'gameplay.png', rotate: 3 }],\n badges: [{ y: 0.3, side: 'l', icon: 'bolt', iconBg: 'gold', rot: -3 }, { y: 0.7, side: 'r', hot: true, rot: 4 }] },\n { id: 'levels', palette: 'sky', frames: [{ src: 'level-b.png', scale: 0.82 }, { src: 'level-a.png', x: -31, dy: 10, scale: 0.7, rotate: -8 }, { src: 'level-c.png', x: 31, dy: 10, scale: 0.7, rotate: 8 }],\n badges: [{ y: 0.66, side: 'r', icon: 'map', iconBg: 'blue', rot: 3 }] },\n { id: 'stars', palette: 'gold', frames: [{ src: 'results.png', rotate: -3 }],\n badges: [{ y: 0.42, side: 'l', stars: true, rot: -3 }] },\n ],\n feature: { frame: 'hero.png', palette: 'fire' },\n capture: {\n baseUrl: 'http://127.0.0.1:5173',\n query: 'lang={lang}&noads=1&seed=1',\n shots: {\n 'hero': { path: '/?level=12', waitFor: \"window.game && game.state === 'playing'\", delayMs: 1500, device: 'both' },\n 'gameplay': { path: '/?level=3', delayMs: 2500, device: 'both' },\n 'level-a': { path: '/?level=20', delayMs: 2000 },\n 'level-b': { path: '/?level=40', delayMs: 2000 },\n 'level-c': { path: '/?level=60', delayMs: 2000 },\n 'results': { path: '/?level=5&result=3', delayMs: 2000, device: 'both' },\n },\n },\n },\n\n icons: {\n composite: 'store/icon/icon.png', // 1024 opaque\n foreground: 'store/icon/foreground.png', // 1024 RGBA, subject on transparent\n background: 'store/icon/background.png', // 1024 opaque\n out: 'store/icons',\n web: 'public/icons',\n // capacitorAssets: 'native/assets',\n // capacitorCommand: 'bunx @capacitor/assets generate --assetPath native/assets',\n // androidRes: 'native/android/app/src/main/res',\n },\n};\n\nexport default config;\n", + "testers.csv": "email,first_name,last_name,groups\n", + "web/appstore.svg": "App Store", + "web/googleplay.svg": "Google Play", + "web/landing.html": "\n\n\n\n\n{{APP_NAME}}\n\n\n\n\n\n
\n

{{APP_NAME}}

\n \n
\n\n\n\n", + "workflows/beta.yml": "# storebound flow \"beta\" for a game: a playtest build on TestFlight and Play internal testing.\n# storebound flow run beta VERSION=1.2.0 [--apply]\ndescription: Next game build to TestFlight and Play internal testing\nparams:\n VERSION: \"1.0.0\"\n GROUP: \"Playtesters\"\nsteps:\n - id: check\n run: check\n - id: ads\n if: exists ios\n run: ads scan --ios ios\n continueOnError: true\n - id: next-build\n if: apple\n run: apple next-build\n capture: BUILD\n - id: build\n if: apple\n run: apple build --version ${VERSION} --build-number ${BUILD}\n - id: upload\n if: apple\n run: apple upload --no-wait\n - id: wait\n if: apple\n run: apple testflight wait --build ${BUILD} --version ${VERSION}\n - id: distribute\n if: apple\n run: apple testflight distribute --build ${BUILD} --group ${GROUP}\n - id: play-build\n if: play\n run: play build --version-name ${VERSION}\n - id: play-upload\n if: play\n run: play upload --track internal\n - id: external\n owner: \"External playtesters: storebound apple testflight distribute --build ${BUILD} --group --submit-review (Beta App Review is your call)\"\n", + "workflows/release.yml": "# storebound flow \"release\" for a game: one version on both stores, up to your Submit click.\n# storebound flow run release VERSION=1.2.0 reads run, writes run as dry runs (nothing changes)\n# storebound flow run release VERSION=1.2.0 --apply writes run for real; each command still asks you first\n# storebound flow run release --resume --apply continue after fixing a failed step\n# Steps are storebound commands (no \"storebound\" prefix). `if: apple` / `if: play` skip a store the config does not\n# have; `owner:` steps are never run, they list what you click. `sh:` runs your own build command (only with --apply).\ndescription: Build, upload and check one game version on both stores, then your Submit click\nparams:\n VERSION: \"1.0.0\"\nsteps:\n - id: shots\n if: exists store/renders\n run: shots check\n - id: next-build\n if: apple\n run: apple next-build\n capture: BUILD\n - id: build\n if: apple\n run: apple build --version ${VERSION} --build-number ${BUILD}\n - id: upload\n if: apple\n run: apple upload --no-wait\n - id: wait\n if: apple\n run: apple testflight wait --build ${BUILD} --version ${VERSION}\n - id: play-build\n if: play\n run: play build --version-name ${VERSION}\n - id: play-upload\n if: play\n run: play upload --track internal\n - id: whats-new\n if: exists store/whats-new\n run: whats-new apply\n - id: listing\n if: apple\n run: apple listing --version ${VERSION} --build ${BUILD}\n - id: validate\n run: validate\n - id: scan\n run: scan\n - id: plan\n run: release plan ${VERSION}\n - id: submit\n owner: \"App Store Connect: Submit for Review (version ${VERSION}, build ${BUILD}). Play Console: promote the internal release and Send changes for review\"\n", +}; + +/** The pages shots render / video load (cli/shots/*.html), by file name. */ +export const SHOTS_PAGES: Record = { + "caption.html": "\n\n\n\n\n\n\n\n\n
\n\n\n\n", + "feature-app.html": "\n\n\n\n\n\n\n\n\n
\n
\n
\n
\n\n\n\n", + "feature.html": "\n\n\n\n\n\n\n\n\n
\n
\n
\n
\n\n\n\n", + "template-app.html": "\n\n\n\n\n\n\n\n\n
\n\n\n\n", + "template.html": "\n\n\n\n\n\n\n\n\n
\n\n\n\n", +}; + +/** Play Console's Data safety form export (cli/data/play-data-safety-form.csv). */ +export const PLAY_DATA_SAFETY_FORM = "Question ID (machine readable),Response ID (machine readable),Response value,Answer requirement,Human-friendly question label\r\nPSL_DATA_COLLECTION_COLLECTS_PERSONAL_DATA,,,REQUIRED,Does your app collect or share any of the required user data types?\r\nPSL_DATA_COLLECTION_ENCRYPTED_IN_TRANSIT,,,MAYBE_REQUIRED,Is all of the user data collected by your app encrypted in transit?\r\nPSL_SUPPORTED_ACCOUNT_CREATION_METHODS,PSL_ACM_USER_ID_PASSWORD,,MULTIPLE_CHOICE,Which of the following methods of account creation does your app support? Select all that apply / Username and password\r\nPSL_SUPPORTED_ACCOUNT_CREATION_METHODS,PSL_ACM_USER_ID_OTHER_AUTH,,MULTIPLE_CHOICE,Which of the following methods of account creation does your app support? Select all that apply / Username and other authentication\r\nPSL_SUPPORTED_ACCOUNT_CREATION_METHODS,PSL_ACM_USER_ID_PASSWORD_OTHER_AUTH,,MULTIPLE_CHOICE,\"Which of the following methods of account creation does your app support? Select all that apply / Username, password, and other authentication\"\r\nPSL_SUPPORTED_ACCOUNT_CREATION_METHODS,PSL_ACM_OAUTH,,MULTIPLE_CHOICE,Which of the following methods of account creation does your app support? Select all that apply / OAuth\r\nPSL_SUPPORTED_ACCOUNT_CREATION_METHODS,PSL_ACM_OTHER,,MULTIPLE_CHOICE,Which of the following methods of account creation does your app support? Select all that apply / Other\r\nPSL_SUPPORTED_ACCOUNT_CREATION_METHODS,PSL_ACM_NONE,,MULTIPLE_CHOICE,Which of the following methods of account creation does your app support? Select all that apply / My app does not allow users to create an account\r\nPSL_ACM_SPECIFY,,,MAYBE_REQUIRED,Describe the method of account creation that your app supports\r\nPSL_ACCOUNT_DELETION_URL,,,MAYBE_REQUIRED,Add a link that users can use to request that their account and associated data is deleted \r\nPSL_SUPPORT_DATA_DELETION_BY_USER,DATA_DELETION_YES,,SINGLE_CHOICE,Do you provide a way for users to request that their data is deleted? / Yes\r\nPSL_SUPPORT_DATA_DELETION_BY_USER,DATA_DELETION_NO,,SINGLE_CHOICE,Do you provide a way for users to request that their data is deleted? / No\r\nPSL_SUPPORT_DATA_DELETION_BY_USER,DATA_DELETION_NO_AUTO_DELETED,,SINGLE_CHOICE,\"Do you provide a way for users to request that their data is deleted? / No, but user data is automatically deleted within 90 days\"\r\nPSL_DATA_DELETION_URL,,,MAYBE_REQUIRED,Delete data URL\r\nPSL_DATA_COLLECTION_COMPLIES_FAMILY_POLICY,,,OPTIONAL,\"Only answer this question if you've indicated that your app's target age group includes children, or you've opted into the Designed for Families program. If either of the above is true, you are required to follow the Google Play Families Policy (https://support.google.com/googleplay/android-developer/answer/9893335). Do you want to let users know about this commitment in the Data safety section on your store listing?\"\r\nPSL_INDEPENDENTLY_VALIDATED,,,OPTIONAL,\"Has your app successfully completed an independent security review, according to the Mobile Application Security Assessment (MASA) framework? Only answer \"\"yes\"\" if the review is in good standing.\"\r\nPSL_UPI_BADGE_OPT_IN,,,OPTIONAL,Do you want to show this badge on your store listing?\r\nPSL_HAS_OUTSIDE_APP_ACCOUNTS,,,OPTIONAL,Can users login to your app with accounts created outside of the app?\r\nPSL_OUTSIDE_APP_ACCOUNT_TYPES,PSL_LOGIN_WITH_OUTSIDE_APP_ID,,MULTIPLE_CHOICE,\"How are these accounts created? / Out of app identification (e.g. SIM binding, service subscription)\"\r\nPSL_OUTSIDE_APP_ACCOUNT_TYPES,PSL_LOGIN_THROUGH_EMPLOYMENT_OR_ENTERPRISE_ACCOUNT,,MULTIPLE_CHOICE,\"How are these accounts created? / Through employment, or enterprise accounts\"\r\nPSL_OUTSIDE_APP_ACCOUNT_TYPES,PSL_OUTSIDE_APP_ACCOUNT_TYPE_OTHER,,MULTIPLE_CHOICE,How are these accounts created? / Other\r\nPSL_OUTSIDE_APP_ACCOUNT_TYPE_SPECIFY,,,MAYBE_REQUIRED,Describe how these accounts are created\r\nPSL_DATA_TYPES_PERSONAL,PSL_NAME,,MULTIPLE_CHOICE,Personal info / Name\r\nPSL_DATA_TYPES_PERSONAL,PSL_EMAIL,,MULTIPLE_CHOICE,Personal info / Email address\r\nPSL_DATA_TYPES_PERSONAL,PSL_USER_ACCOUNT,,MULTIPLE_CHOICE,Personal info / User IDs\r\nPSL_DATA_TYPES_PERSONAL,PSL_ADDRESS,,MULTIPLE_CHOICE,Personal info / Address\r\nPSL_DATA_TYPES_PERSONAL,PSL_PHONE,,MULTIPLE_CHOICE,Personal info / Phone number\r\nPSL_DATA_TYPES_PERSONAL,PSL_RACE_ETHNICITY,,MULTIPLE_CHOICE,Personal info / Race and ethnicity\r\nPSL_DATA_TYPES_PERSONAL,PSL_POLITICAL_RELIGIOUS,,MULTIPLE_CHOICE,Personal info / Political or religious beliefs\r\nPSL_DATA_TYPES_PERSONAL,PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY,,MULTIPLE_CHOICE,Personal info / Sexual orientation\r\nPSL_DATA_TYPES_PERSONAL,PSL_OTHER_PERSONAL,,MULTIPLE_CHOICE,Personal info / Other info\r\nPSL_DATA_TYPES_FINANCIAL,PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER,,MULTIPLE_CHOICE,Financial info / User payment info\r\nPSL_DATA_TYPES_FINANCIAL,PSL_PURCHASE_HISTORY,,MULTIPLE_CHOICE,Financial info / Purchase history\r\nPSL_DATA_TYPES_FINANCIAL,PSL_CREDIT_SCORE,,MULTIPLE_CHOICE,Financial info / Credit score\r\nPSL_DATA_TYPES_FINANCIAL,PSL_OTHER,,MULTIPLE_CHOICE,Financial info / Other financial info\r\nPSL_DATA_TYPES_LOCATION,PSL_APPROX_LOCATION,,MULTIPLE_CHOICE,Location / Approximate location\r\nPSL_DATA_TYPES_LOCATION,PSL_PRECISE_LOCATION,,MULTIPLE_CHOICE,Location / Precise location\r\nPSL_DATA_TYPES_SEARCH_AND_BROWSING,PSL_WEB_BROWSING_HISTORY,,MULTIPLE_CHOICE,Web browsing / Web browsing history\r\nPSL_DATA_TYPES_EMAIL_AND_TEXT,PSL_EMAILS,,MULTIPLE_CHOICE,Messages / Emails\r\nPSL_DATA_TYPES_EMAIL_AND_TEXT,PSL_SMS_CALL_LOG,,MULTIPLE_CHOICE,Messages / SMS or MMS\r\nPSL_DATA_TYPES_EMAIL_AND_TEXT,PSL_OTHER_MESSAGES,,MULTIPLE_CHOICE,Messages / Other in-app messages\r\nPSL_DATA_TYPES_PHOTOS_AND_VIDEOS,PSL_PHOTOS,,MULTIPLE_CHOICE,Photos and videos / Photos\r\nPSL_DATA_TYPES_PHOTOS_AND_VIDEOS,PSL_VIDEOS,,MULTIPLE_CHOICE,Photos and videos / Videos\r\nPSL_DATA_TYPES_AUDIO,PSL_AUDIO,,MULTIPLE_CHOICE,Audio files / Voice or sound recordings\r\nPSL_DATA_TYPES_AUDIO,PSL_MUSIC,,MULTIPLE_CHOICE,Audio files / Music files\r\nPSL_DATA_TYPES_AUDIO,PSL_OTHER_AUDIO,,MULTIPLE_CHOICE,Audio files / Other audio files\r\nPSL_DATA_TYPES_HEALTH_AND_FITNESS,PSL_HEALTH,,MULTIPLE_CHOICE,Health and fitness / Health info\r\nPSL_DATA_TYPES_HEALTH_AND_FITNESS,PSL_FITNESS,,MULTIPLE_CHOICE,Health and fitness / Fitness info\r\nPSL_DATA_TYPES_CONTACTS,PSL_CONTACTS,,MULTIPLE_CHOICE,Contacts / Contacts\r\nPSL_DATA_TYPES_CALENDAR,PSL_CALENDAR,,MULTIPLE_CHOICE,Calendar / Calendar events\r\nPSL_DATA_TYPES_APP_PERFORMANCE,PSL_CRASH_LOGS,,MULTIPLE_CHOICE,App info and performance / Crash logs\r\nPSL_DATA_TYPES_APP_PERFORMANCE,PSL_PERFORMANCE_DIAGNOSTICS,,MULTIPLE_CHOICE,App info and performance / Diagnostics\r\nPSL_DATA_TYPES_APP_PERFORMANCE,PSL_OTHER_PERFORMANCE,,MULTIPLE_CHOICE,App info and performance / Other app performance data\r\nPSL_DATA_TYPES_FILES_AND_DOCS,PSL_FILES_AND_DOCS,,MULTIPLE_CHOICE,Files and docs / Files and docs\r\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_USER_INTERACTION,,MULTIPLE_CHOICE,App activity / App interactions\r\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_IN_APP_SEARCH_HISTORY,,MULTIPLE_CHOICE,App activity / In-app search history\r\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_APPS_ON_DEVICE,,MULTIPLE_CHOICE,App activity / Installed apps\r\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_USER_GENERATED_CONTENT,,MULTIPLE_CHOICE,App activity / Other user-generated content\r\nPSL_DATA_TYPES_APP_ACTIVITY,PSL_OTHER_APP_ACTIVITY,,MULTIPLE_CHOICE,App activity / Other actions\r\nPSL_DATA_TYPES_IDENTIFIERS,PSL_DEVICE_ID,,MULTIPLE_CHOICE,Device or other IDs / Device or other IDs\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Name) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Name) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Name) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Name) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Name) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_NAME:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Name) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Email address) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Email address) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Email address) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Email address) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Email address) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAIL:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Email address) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (User IDs) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (User IDs) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (User IDs) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (User IDs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (User IDs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_ACCOUNT:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (User IDs) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Address) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Address) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Address) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Address) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Address) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_ADDRESS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Address) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Phone number) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Phone number) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Phone number) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Phone number) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Phone number) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHONE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Phone number) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Race and ethnicity) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_RACE_ETHNICITY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Race and ethnicity) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Political or religious beliefs) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_POLITICAL_RELIGIOUS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Political or religious beliefs) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Sexual orientation) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Sexual orientation) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Sexual orientation) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Sexual orientation) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Sexual orientation) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Sexual orientation) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other info) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other info) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other info) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERSONAL:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other info) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (User payment info) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (User payment info) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (User payment info) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (User payment info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (User payment info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_DEBIT_BANK_ACCOUNT_NUMBER:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (User payment info) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Purchase history) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Purchase history) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Purchase history) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Purchase history) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Purchase history) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_PURCHASE_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Purchase history) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Credit score) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Credit score) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Credit score) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Credit score) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Credit score) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_CREDIT_SCORE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Credit score) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other financial info) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other financial info) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other financial info) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other financial info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other financial info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other financial info) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Approximate location) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Approximate location) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Approximate location) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Approximate location) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Approximate location) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPROX_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Approximate location) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Precise location) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Precise location) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Precise location) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Precise location) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Precise location) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_PRECISE_LOCATION:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Precise location) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Web browsing history) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Web browsing history) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Web browsing history) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Web browsing history) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Web browsing history) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_WEB_BROWSING_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Web browsing history) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Emails) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Emails) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Emails) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Emails) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Emails) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_EMAILS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Emails) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (SMS or MMS) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (SMS or MMS) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (SMS or MMS) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (SMS or MMS) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (SMS or MMS) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_SMS_CALL_LOG:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (SMS or MMS) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other in-app messages) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other in-app messages) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other in-app messages) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other in-app messages) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other in-app messages) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_MESSAGES:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other in-app messages) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Photos) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Photos) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Photos) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Photos) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Photos) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_PHOTOS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Photos) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Videos) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Videos) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Videos) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Videos) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Videos) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_VIDEOS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Videos) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Voice or sound recordings) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Voice or sound recordings) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Music files) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Music files) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Music files) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Music files) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Music files) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_MUSIC:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Music files) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other audio files) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other audio files) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other audio files) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other audio files) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other audio files) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_AUDIO:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other audio files) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Health info) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Health info) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Health info) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Health info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Health info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_HEALTH:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Health info) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Fitness info) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Fitness info) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Fitness info) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Fitness info) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Fitness info) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_FITNESS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Fitness info) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Contacts) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Contacts) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Contacts) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Contacts) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Contacts) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_CONTACTS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Contacts) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Calendar events) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Calendar events) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Calendar events) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Calendar events) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Calendar events) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_CALENDAR:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Calendar events) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Crash logs) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Crash logs) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Crash logs) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Crash logs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Crash logs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_CRASH_LOGS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Crash logs) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Diagnostics) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Diagnostics) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Diagnostics) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Diagnostics) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Diagnostics) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_PERFORMANCE_DIAGNOSTICS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Diagnostics) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other app performance data) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other app performance data) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other app performance data) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other app performance data) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other app performance data) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_PERFORMANCE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other app performance data) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Files and docs) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Files and docs) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Files and docs) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Files and docs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Files and docs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_FILES_AND_DOCS:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Files and docs) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (App interactions) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (App interactions) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (App interactions) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (App interactions) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (App interactions) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_INTERACTION:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (App interactions) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (In-app search history) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (In-app search history) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (In-app search history) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (In-app search history) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (In-app search history) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_IN_APP_SEARCH_HISTORY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (In-app search history) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Installed apps) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Installed apps) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Installed apps) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Installed apps) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Installed apps) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_APPS_ON_DEVICE:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Installed apps) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other user-generated content) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other user-generated content) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other user-generated content) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other user-generated content) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other user-generated content) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_USER_GENERATED_CONTENT:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other user-generated content) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Other actions) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Other actions) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Other actions) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Other actions) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Other actions) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_OTHER_APP_ACTIVITY:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Other actions) / Why is this user data shared? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_COLLECTED,,MULTIPLE_CHOICE,\"Data usage and handling (Device or other IDs) / Is this data collected, shared, or both? / Collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_COLLECTION_AND_SHARING,PSL_DATA_USAGE_ONLY_SHARED,,MULTIPLE_CHOICE,\"Data usage and handling (Device or other IDs) / Is this data collected, shared, or both? / Shared\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:PSL_DATA_USAGE_EPHEMERAL,,,MAYBE_REQUIRED,Data usage and handling (Device or other IDs) / Is this data processed ephemerally?\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_OPTIONAL,,SINGLE_CHOICE,\"Data usage and handling (Device or other IDs) / Is this data required for your app, or can users choose whether it's collected? / Users can choose whether this data is collected\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_USER_CONTROL,PSL_DATA_USAGE_USER_CONTROL_REQUIRED,,SINGLE_CHOICE,\"Data usage and handling (Device or other IDs) / Is this data required for your app, or can users choose whether it's collected? / Data collection is required (users can't turn off this data collection)\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_COLLECTION_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data collected? Select all that apply. / Account management\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_APP_FUNCTIONALITY,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / App functionality\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_ANALYTICS,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Analytics\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_DEVELOPER_COMMUNICATIONS,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Developer communications\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_FRAUD_PREVENTION_SECURITY,,MULTIPLE_CHOICE,\"Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Fraud prevention, security, and compliance\"\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_ADVERTISING,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Advertising or marketing\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_PERSONALIZATION,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Personalization\r\nPSL_DATA_USAGE_RESPONSES:PSL_DEVICE_ID:DATA_USAGE_SHARING_PURPOSE,PSL_ACCOUNT_MANAGEMENT,,MULTIPLE_CHOICE,Data usage and handling (Device or other IDs) / Why is this user data shared? Select all that apply. / Account management\n"; diff --git a/cli/lib/imagehash.ts b/cli/lib/imagehash.ts index d29b91e..78f7633 100644 --- a/cli/lib/imagehash.ts +++ b/cli/lib/imagehash.ts @@ -4,6 +4,7 @@ // hashes differ in few bits look alike to a person (and to a reviewer comparing store pages). import { readFileSync } from 'node:fs'; import { inflateSync } from 'node:zlib'; +import * as rt from './rt'; export interface Gray { w: number; h: number; px: Float64Array } @@ -128,9 +129,9 @@ export function hashFile(path: string): { hash: ImageHash | null; reason?: strin try { const buf = readFileSync(path); if (buf.length > 8 && buf.readUInt32BE(0) === 0x89504e47) return { hash: hashGray(decodePng(buf)) }; - const ffmpeg = Bun.which('ffmpeg'); + const ffmpeg = rt.which('ffmpeg'); if (!ffmpeg) return { hash: null, reason: 'not a PNG and ffmpeg is not installed' }; - const r = Bun.spawnSync([ffmpeg, '-v', 'error', '-i', path, '-frames:v', '1', '-vf', 'scale=32:32:flags=area,format=gray', '-f', 'rawvideo', '-'], { stdout: 'pipe', stderr: 'pipe', timeout: 20_000 }); + const r = rt.spawnSync([ffmpeg, '-v', 'error', '-i', path, '-frames:v', '1', '-vf', 'scale=32:32:flags=area,format=gray', '-f', 'rawvideo', '-'], { stdout: 'pipe', stderr: 'pipe', timeout: 20_000 }); const px = r.stdout; if (r.exitCode !== 0 || px.length !== 1024) return { hash: null, reason: `ffmpeg could not read it (${r.stderr.toString().trim().split('\n')[0]?.slice(0, 120) ?? r.exitCode})` }; return { hash: hashGray({ w: 32, h: 32, px: Float64Array.from(px) }) }; diff --git a/cli/lib/rt.ts b/cli/lib/rt.ts new file mode 100644 index 0000000..47ed585 --- /dev/null +++ b/cli/lib/rt.ts @@ -0,0 +1,465 @@ +// The runtime layer: everything the CLI needs from its JavaScript runtime, written against Node's own modules so the +// same code runs on Node 20+ (`npx storebound`, the dist/storebound.mjs bundle) and on Bun (`bunx storebound`, the +// sources, the compiled desktop sidecar). The CLI never touches the `Bun` global directly; a test +// (tests/node-compat.test.ts) keeps it that way. The behaviour follows Bun's where callers rely on it: spawn's +// defaults (stdin ignored, stdout piped, stderr inherited), `exited` resolving to the exit code or 128 + the signal +// number, a missing executable throwing "Executable not found in $PATH" right away, spawnSync never limiting output. +import { spawn as nodeSpawn, spawnSync as nodeSpawnSync, type StdioOptions } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'; +import { createServer } from 'node:http'; +import * as nodeModule from 'node:module'; +import { constants } from 'node:os'; +import { delimiter, dirname, isAbsolute, join, resolve } from 'node:path'; +import type { Readable } from 'node:stream'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { gunzipSync as zGunzipSync } from 'node:zlib'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const BUN: any = (globalThis as any).Bun; + +/** True under Bun (sources, bunx, the compiled sidecar); false under Node (the npm bundle). */ +export const isBun = BUN !== undefined; + +/** Runtime name and version, for `doctor` and `system.version`. */ +export const runtime = { name: isBun ? 'bun' : 'node', version: isBun ? String(BUN.version) : process.versions.node } as const; + +/** Bun's version, or null under Node. */ +export const bunVersion: string | null = isBun ? String(BUN.version) : null; + +/** The entry script the runtime started (Bun.main; under Node the script in argv[1]). */ +export const mainPath = (): string => (isBun ? String(BUN.main) : process.argv[1] ?? ''); + +// ---- time ---- + +export const sleep = (ms: number): Promise => new Promise(r => setTimeout(r, Math.max(0, ms))); + +/** Blocks the thread (only for short retries in cleanup code). */ +export function sleepSync(ms: number): void { + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Math.max(0, ms)); +} + +// ---- files and paths ---- + +/** Folder of a module: `here(import.meta.url)` (Bun's import.meta.dir, which Node does not have). */ +export const here = (url: string): string => dirname(fileURLToPath(url)); + +let rootCache: string | null = null; + +/** + * The storebound package folder (package.json, templates/, skills/, cli/): found upwards from this module, which is + * cli/lib/rt.ts in the sources and dist/storebound.mjs in the npm bundle. In the compiled sidecar (no package on + * disk) it is the folder two levels up, as before. + */ +export function packageRoot(): string { + if (rootCache) return rootCache; + const start = here(import.meta.url); + for (let d = start; ; d = dirname(d)) { + try { + const pkg = JSON.parse(readFileSync(join(d, 'package.json'), 'utf8')) as { name?: string }; + if (pkg.name === 'storebound') return (rootCache = d); + } catch { /* keep looking */ } + if (dirname(d) === d) break; + } + return (rootCache = resolve(start, '..', '..')); +} + +/** `require.resolve(spec)` from the folder `dir` (Bun.resolveSync(spec, dir)); throws when it is not found. */ +export function resolveFrom(spec: string, dir: string): string { + return nodeModule.createRequire(join(resolve(dir), '__storebound_resolve__.js')).resolve(spec); +} + +/** Imports a module by file path (absolute paths become file: URLs, which Node needs on Windows). */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const importPath = (path: string): Promise => import(pathToFileURL(path).href); + +/** + * Imports a config module (storebound.config.ts / .js). Bun runs TypeScript itself; so does Node 22.18+ / 23.6+. + * Older Node (20, 22.x) refuses `.ts` (ERR_UNKNOWN_FILE_EXTENSION): the types are stripped (node:module's + * stripTypeScriptTypes when it exists, else the small stripper below, enough for a config file) and the result is + * imported from a data: URL. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function importConfigModule(path: string): Promise { + if (isBun || !/\.[cm]?ts$/.test(path)) return importPath(path); + try { + return await withoutTypeStripWarning(() => importPath(path)); + } catch (e) { + const code = (e as { code?: string }).code; + if (code !== 'ERR_UNKNOWN_FILE_EXTENSION' && code !== 'ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING' && !/Unknown file extension/.test(String(e))) throw e; + } + const src = readFileSync(path, 'utf8'); + let js: string; + const strip = (nodeModule as unknown as { stripTypeScriptTypes?: (s: string) => string }).stripTypeScriptTypes; + try { js = typeof strip === 'function' ? withoutTypeStripWarningSync(() => strip(src)) : stripTypes(src); } catch { js = stripTypes(src); } + if (/^\s*import\s[^'"]*['"]\.{1,2}\//m.test(js)) throw new Error(`${path}: this Node (${process.versions.node}) cannot load a TypeScript config that imports other files; use Node 22.18+ or Bun, or a storebound.config.js`); + return import(`data:text/javascript;base64,${Buffer.from(js).toString('base64')}`); +} + +/** Node 22.18+ prints an ExperimentalWarning the first time it strips types; storebound's output stays clean. */ +async function withoutTypeStripWarning(f: () => Promise): Promise { + const emit = process.emitWarning; + process.emitWarning = ((w: string | Error, ...rest: unknown[]) => { + if (/Type Stripping|type stripping/i.test(String(typeof w === 'string' ? w : w.message))) return; + return (emit as (...a: unknown[]) => void).call(process, w, ...rest); + }) as typeof process.emitWarning; + try { return await f(); } finally { process.emitWarning = emit; } +} + +function withoutTypeStripWarningSync(f: () => T): T { + const emit = process.emitWarning; + process.emitWarning = (() => undefined) as typeof process.emitWarning; + try { return f(); } finally { process.emitWarning = emit; } +} + +/** + * Removes the TypeScript a storebound config uses (type-only imports and exports, `: Type` on a declaration, + * `satisfies T`, `as T`) outside strings and comments. Not a TypeScript compiler: a fallback for Node versions + * without type stripping. + */ +export function stripTypes(src: string): string { + const parts: { code: boolean; text: string }[] = []; + let i = 0, start = 0; + const push = (code: boolean, end: number) => { if (end > start) parts.push({ code, text: src.slice(start, end) }); start = end; }; + while (i < src.length) { + const c = src[i]!, n = src[i + 1]; + if (c === '/' && n === '/') { push(true, i); const e = src.indexOf('\n', i); i = e < 0 ? src.length : e; push(false, i); continue; } + if (c === '/' && n === '*') { push(true, i); const e = src.indexOf('*/', i + 2); i = e < 0 ? src.length : e + 2; push(false, i); continue; } + if (c === '"' || c === "'" || c === '`') { + push(true, i); + for (i++; i < src.length && src[i] !== c; i++) if (src[i] === '\\') i++; + i++; push(false, i); continue; + } + i++; + } + push(true, src.length); + const TYPE = String.raw`[A-Za-z_$][\w$.]*(?:<[^;=(){}]*?>)?(?:\[\])*(?:\s*\|\s*[A-Za-z_$][\w$.]*(?:<[^;=(){}]*?>)?(?:\[\])*)*`; + const out = parts.map(p => !p.code ? p.text : p.text + .replace(new RegExp(String.raw`\b(const|let|var)(\s+[A-Za-z_$][\w$]*)\s*:\s*${TYPE}\s*=`, 'g'), '$1$2 =') + .replace(new RegExp(String.raw`\s+satisfies\s+${TYPE}`, 'g'), '') + .replace(new RegExp(String.raw`\s+as\s+(?:const\b|${TYPE})`, 'g'), '') + .replace(/\{\s*type\s+/g, '{ ') + .replace(/,\s*type\s+(?=[A-Za-z_$])/g, ', ')).join(''); + // type-only imports and exports, whose specifier string sits in its own (non-code) part + return out + .replace(/^\s*import\s+type\s[^;]*?from\s*(['"])[^'"]*\1\s*;?[ \t]*$/gm, '') + .replace(/^\s*export\s+type\s+\{[^}]*\}(\s*from\s*(['"])[^'"]*\2)?\s*;?[ \t]*$/gm, '') + .replace(/^\s*(export\s+)?(type\s+[A-Za-z_$][\w$]*(<[^>]*>)?\s*=[^;]*;|interface\s+[A-Za-z_$][\w$]*[^{]*\{[^}]*\})/gm, ''); +} + +// ---- hashing, compression ---- + +/** Incremental hash (Bun.CryptoHasher): `hasher('md5').update(buf).digest('hex')`. */ +export const hasher = (algorithm: 'md5' | 'sha1' | 'sha256' | 'sha512') => createHash(algorithm); + +export const gunzipSync = (data: Uint8Array): Uint8Array => new Uint8Array(zGunzipSync(data)); + +// ---- PATH lookup ---- + +/** + * Bun.which: the full path of `cmd` on PATH (`opts.PATH` instead of process.env.PATH), or null. A name with a path + * separator is checked as is. On Windows every PATHEXT extension is tried. + */ +export function which(cmd: string, opts: { PATH?: string; cwd?: string } = {}): string | null { + if (!cmd) return null; + const win = process.platform === 'win32'; + const exts = win ? ['', ...(process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean).map(e => e.toLowerCase())] : ['']; + const ok = (p: string): boolean => { + try { + const s = statSync(p); + if (!s.isFile()) return false; + return win || (s.mode & 0o111) !== 0; + } catch { return false; } + }; + const tryExts = (base: string): string | null => { + for (const e of exts) { + if (win && e === '' && !/\.[^\\/.]+$/.test(base)) continue; + const p = base + e; + if (ok(p)) return p; + if (win && e && ok(base + e.toUpperCase())) return base + e.toUpperCase(); + } + return null; + }; + if (/[\\/]/.test(cmd)) return tryExts(isAbsolute(cmd) ? cmd : resolve(opts.cwd ?? process.cwd(), cmd)); + const PATH = opts.PATH ?? process.env.PATH ?? (win ? process.env.Path : undefined) ?? ''; + for (const dir of PATH.split(delimiter)) { + if (!dir) continue; + const hit = tryExts(join(dir.replace(/^"(.*)"$/, '$1'), cmd)); + if (hit) return hit; + } + return null; +} + +// ---- processes ---- + +type Io = 'pipe' | 'inherit' | 'ignore'; + +export interface SpawnOptions { + cwd?: string; + env?: Record; + stdin?: Io | null; + stdout?: Io | null; + stderr?: Io | null; + /** [stdin, stdout, stderr]; wins over the single options. */ + stdio?: [Io | null, Io | null, Io | null]; +} + +/** A running child, shaped like Bun's Subprocess (the parts storebound uses). */ +export interface Subprocess { + readonly pid: number | undefined; + /** The piped output (an empty, closed stream when not piped). */ + readonly stdout: ReadableStream; + readonly stderr: ReadableStream; + /** The exit code; 128 + the signal number when a signal ended it. Never rejects. */ + readonly exited: Promise; + readonly exitCode: number | null; + readonly signalCode: string | null; + readonly killed: boolean; + kill(signal?: NodeJS.Signals | number): void; + /** Lets this process exit while the child runs on. */ + unref(): void; +} + +const notFound = (cmd: string): Error => Object.assign(new Error(`Executable not found in $PATH: "${cmd}"`), { code: 'ENOENT', path: cmd }); + +/** Throws like Bun when the executable does not exist (Node would only emit an asynchronous 'error'). */ +function checkExecutable(cmd: string | undefined, o: { cwd?: string; env?: Record }): string { + if (!cmd) throw new Error('spawn: empty command'); + const PATH = o.env ? (o.env.PATH ?? o.env.Path ?? '') : undefined; + if (!which(cmd, { ...(PATH !== undefined ? { PATH } : {}), ...(o.cwd ? { cwd: o.cwd } : {}) })) throw notFound(cmd); + return cmd; +} + +const io = (v: Io | null | undefined, dflt: Io): Io => (v === null ? 'ignore' : v ?? dflt); + +const envOf = (env: Record | undefined): NodeJS.ProcessEnv | undefined => + env ? Object.fromEntries(Object.entries(env).filter((e): e is [string, string] => e[1] !== undefined)) : undefined; + +/** A node Readable as a web ReadableStream of bytes (or an empty, closed stream for null). */ +function webStream(r: Readable | null | undefined): ReadableStream { + return new ReadableStream({ + start(c) { + if (!r) { c.close(); return; } + let ended = false; + const end = () => { if (!ended) { ended = true; try { c.close(); } catch { /* closed */ } } }; + r.on('data', (d: Buffer | string) => { if (!ended) c.enqueue(typeof d === 'string' ? new TextEncoder().encode(d) : new Uint8Array(d)); }); + r.on('end', end); + r.on('close', end); + r.on('error', () => end()); + }, + }); +} + +const signalNumber = (s: NodeJS.Signals | null): number => (s ? (constants.signals as Record)[s] ?? 1 : 0); + +/** Bun.spawn: argv[0] on PATH (opts.env.PATH when given), Bun's stdio defaults (stdin ignore, stdout pipe, stderr inherit). */ +export function spawn(argv: string[], o: SpawnOptions = {}): Subprocess { + const cmd = checkExecutable(argv[0], o); + const [i0, o0, e0] = o.stdio ?? [o.stdin, o.stdout, o.stderr]; + const stdio: StdioOptions = [io(i0, 'ignore'), io(o0, 'pipe'), io(e0, 'inherit')]; + const child = nodeSpawn(cmd, argv.slice(1), { cwd: o.cwd, env: envOf(o.env) ?? process.env, stdio, windowsHide: true }); + let exitCode: number | null = null, signalCode: string | null = null; + const exited = new Promise(res => { + let done = false; + const finish = (code: number | null, sig: NodeJS.Signals | null) => { + if (done) return; + done = true; + exitCode = code; signalCode = sig; + res(code ?? (sig ? 128 + signalNumber(sig) : 1)); + }; + child.on('exit', finish); + child.on('error', () => { if (child.exitCode === null && child.signalCode === null && child.pid === undefined) finish(1, null); }); + }); + const stdout = webStream(child.stdout), stderr = webStream(child.stderr); + return { + get pid() { return child.pid; }, + stdout, stderr, exited, + get exitCode() { return exitCode; }, + get signalCode() { return signalCode; }, + get killed() { return child.killed; }, + kill(signal?: NodeJS.Signals | number) { try { child.kill(signal ?? 'SIGTERM'); } catch { /* gone */ } }, + unref() { + child.unref(); + for (const s of [child.stdin, child.stdout, child.stderr]) (s as unknown as { unref?: () => void } | null)?.unref?.(); + }, + }; +} + +export interface SpawnSyncOptions { + cwd?: string; + env?: Record; + /** Bytes or text written to the child's stdin (then closed), or an stdio mode. */ + stdin?: Uint8Array | string | Io | null; + stdout?: Io | null; + stderr?: Io | null; + /** Milliseconds; the child gets SIGTERM after it (exitCode null, signalCode 'SIGTERM'). */ + timeout?: number; +} + +export interface SyncResult { exitCode: number | null; signalCode: string | null; success: boolean; stdout: Buffer; stderr: Buffer } + +/** Bun.spawnSync: never throws for a nonzero exit or a timeout, throws when the executable does not exist. */ +export function spawnSync(argv: string[], o: SpawnSyncOptions = {}): SyncResult { + const cmd = checkExecutable(argv[0], o); + const input = typeof o.stdin === 'string' && !['pipe', 'inherit', 'ignore'].includes(o.stdin) ? o.stdin : o.stdin instanceof Uint8Array ? o.stdin : undefined; + const stdinMode: Io = input !== undefined ? 'pipe' : io(o.stdin as Io | null | undefined, 'ignore'); + const r = nodeSpawnSync(cmd, argv.slice(1), { + cwd: o.cwd, env: envOf(o.env) ?? process.env, input, windowsHide: true, + stdio: [stdinMode, io(o.stdout, 'pipe'), io(o.stderr, 'pipe')], maxBuffer: 1 << 30, + ...(o.timeout ? { timeout: o.timeout, killSignal: 'SIGTERM' as const } : {}), + }); + const err = r.error as (Error & { code?: string }) | undefined; + if (err && err.code === 'ENOENT') throw notFound(argv[0]!); + if (err && err.code !== 'ETIMEDOUT' && r.status === null && r.signal === null) throw err; + const buf = (b: Buffer | string | null | undefined): Buffer => (b == null ? Buffer.alloc(0) : typeof b === 'string' ? Buffer.from(b) : b); + return { exitCode: r.status, signalCode: r.signal, success: r.status === 0, stdout: buf(r.stdout), stderr: buf(r.stderr) }; +} + +// ---- stdin ---- + +/** This process's stdin as a web stream (Bun.stdin.stream()). */ +export const stdinStream = (): ReadableStream => webStream(process.stdin); + +/** All of stdin as text (Bun.stdin.text()). */ +export const stdinText = (): Promise => new Response(stdinStream()).text(); + +// ---- a small HTTP server (Bun.serve) ---- + +export interface Server { readonly port: number; readonly hostname: string; stop(closeActive?: boolean): void } + +/** + * Bun.serve on node:http: `fetch(Request) => Response` for local pages (GET and HEAD; the body is read whole). + * Resolves once listening; rejects when the port is taken. + */ +export function serve(o: { port?: number; hostname?: string; fetch(req: Request): Response | Promise }): Promise { + const hostname = o.hostname ?? '127.0.0.1'; + const server = createServer((req, res) => { + void (async () => { + try { + const headers = new Headers(); + for (const [k, v] of Object.entries(req.headers)) if (v !== undefined) headers.set(k, Array.isArray(v) ? v.join(', ') : v); + const r = await o.fetch(new Request(`http://${req.headers.host ?? `${hostname}:${port}`}${req.url ?? '/'}`, { method: req.method ?? 'GET', headers })); + const body = req.method === 'HEAD' ? null : Buffer.from(await r.arrayBuffer()); + const h: Record = {}; + r.headers.forEach((v, k) => { h[k] = v; }); + if (body) h['content-length'] = String(body.length); + res.writeHead(r.status, h); + res.end(body ?? undefined); + } catch (e) { + if (!res.headersSent) res.writeHead(500, { 'content-type': 'text/plain' }); + res.end(String((e as Error).message ?? e)); + } + })(); + }); + let port = o.port ?? 0; + return new Promise((res, rej) => { + server.once('error', rej); + server.listen(port, hostname, () => { + server.off('error', rej); + const a = server.address(); + if (a && typeof a === 'object') port = a.port; + res({ + port, hostname, + stop(closeActive = false) { if (closeActive) server.closeAllConnections?.(); server.close(); }, + }); + }); + }); +} + +/** A file as a Response body, or null when it does not exist (Bun.file + exists()). */ +export function fileResponse(path: string, headers: Record = {}): Response | null { + try { + if (!statSync(path).isFile()) return null; + return new Response(readFileSync(path), { headers }); + } catch { return null; } +} + +// ---- globs (Bun.Glob) ---- + +/** A glob pattern: `*` and `?` within one path segment, `**` across segments, `[abc]`, `{a,b}`. */ +export class Glob { + readonly re: RegExp; + constructor(readonly pattern: string) { this.re = globRegExp(pattern); } + match(path: string): boolean { return this.re.test(path.replace(/\\/g, '/')); } + /** The matching paths under `cwd`, relative with forward slashes; dot entries only with `dot`; symlinked folders are not entered. */ + *scanSync(o: { cwd?: string; onlyFiles?: boolean; dot?: boolean } = {}): Generator { + const cwd = o.cwd ?? process.cwd(); + const onlyFiles = o.onlyFiles ?? true; + const segs = this.pattern.replace(/\\/g, '/').split('/'); + const deep = segs.includes('**') || segs.some(s => s.includes('**')); + // the literal leading segments: no need to walk anything above them + let fixed = 0; + while (fixed < segs.length - 1 && !/[*?[{]/.test(segs[fixed]!)) fixed++; + const prefix = segs.slice(0, fixed).join('/'); + const maxDepth = deep ? Infinity : segs.length - fixed; + const self = this; + function* walk(rel: string, depth: number): Generator { + let entries; + try { entries = readdirSync(join(cwd, rel), { withFileTypes: true }); } catch { return; } + for (const e of entries) { + if (!o.dot && e.name.startsWith('.')) continue; + const p = rel ? `${rel}/${e.name}` : e.name; + const dir = e.isDirectory(); + if ((!onlyFiles || !dir) && self.re.test(p)) yield p; + if (dir && depth + 1 < maxDepth) yield* walk(p, depth + 1); + } + } + if (prefix && !existsSync(join(cwd, prefix))) return; + yield* walk(prefix, 0); + } +} + +export function globRegExp(pattern: string): RegExp { + const p = pattern.replace(/\\/g, '/'); + let re = ''; + let braces = 0; + for (let i = 0; i < p.length; i++) { + const c = p[i]!; + if (c === '*') { + if (p[i + 1] === '*') { + const atStart = i === 0 || p[i - 1] === '/'; + i++; + if (atStart && p[i + 1] === '/') { re += '(?:[^/]*/)*'; i++; } + else re += '.*'; + } else re += '[^/]*'; + } else if (c === '?') re += '[^/]'; + else if (c === '[') { + const end = p.indexOf(']', i + 1); + if (end < 0) { re += '\\['; continue; } + let body = p.slice(i + 1, end); + if (body.startsWith('!')) body = '^' + body.slice(1); + re += `[${body.replace(/\\/g, '\\\\')}]`; + i = end; + } else if (c === '{') { braces++; re += '(?:'; } + else if (c === '}' && braces) { braces--; re += ')'; } + else if (c === ',' && braces) re += '|'; + else re += c.replace(/[.+^$()|\\]/g, '\\$&'); + } + return new RegExp(`^${re}$`); +} + +// ---- SQLite (bun:sqlite under Bun, node:sqlite on Node 22.5+) ---- + +export interface SqliteDb { + all(sql: string, ...params: unknown[]): T[]; + get(sql: string, ...params: unknown[]): T | undefined; + run(sql: string, ...params: unknown[]): void; + exec(sql: string): void; + close(): void; +} + +/** Opens a SQLite file; throws when this runtime has no SQLite (Node before 22.5). */ +export async function openSqlite(path: string): Promise { + const spec = isBun ? 'bun:sqlite' : 'node:sqlite'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const mod: any = await import(spec); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const db: any = isBun ? new mod.Database(path) : new mod.DatabaseSync(path); + return { + all: (sql, ...p) => db.prepare(sql).all(...p), + get: (sql, ...p) => db.prepare(sql).get(...p) ?? undefined, + run: (sql, ...p) => { db.prepare(sql).run(...p); }, + exec: sql => { db.exec(sql); }, + close: () => db.close(), + }; +} diff --git a/cli/lib/runner.ts b/cli/lib/runner.ts index df46be1..ac0fdd6 100644 --- a/cli/lib/runner.ts +++ b/cli/lib/runner.ts @@ -1,16 +1,21 @@ // Runs one storebound command as a child process: the shared core of `storebound mcp` and `storebound serve --stdio`. // Works from source (`bun cli/index.ts ...`) and from a `bun build --compile` binary (the desktop sidecar), where the // binary re-executes itself. Output is streamed line by line (progress) and the `--json` result line is picked out. -import { join } from 'node:path'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; import { RESULT_MARK } from './log'; import { toolPath } from './tools'; +import * as rt from './rt'; /** True inside a `bun build --compile` executable (the entry module lives in Bun's embedded file system). */ -export const compiled = (): boolean => /^(\/\$bunfs\/|[A-Z]:[\\/]~BUN[\\/])/.test(Bun.main); +export const compiled = (): boolean => /^(\/\$bunfs\/|[A-Z]:[\\/]~BUN[\\/])/.test(rt.mainPath()); /** Command prefix that starts this storebound: the compiled binary itself, or bun + cli/index.ts. */ export function selfCommand(): string[] { - return compiled() ? [process.execPath] : [process.execPath, join(import.meta.dir, '..', 'index.ts')]; + if (compiled()) return [process.execPath]; + // from the sources (cli/lib/runner.ts): bun cli/index.ts; from the npm bundle (dist/storebound.mjs): node + const self = fileURLToPath(import.meta.url); + return [process.execPath, /\.ts$/.test(self) ? join(dirname(self), '..', 'index.ts') : self]; } export interface RunOptions { @@ -62,7 +67,7 @@ export async function pump(stream: ReadableStream, kind: 'stdout' | } export function runCommand(argv: string[], o: RunOptions): Running { - const p = Bun.spawn([...selfCommand(), ...argv], { cwd: o.cwd, stdin: 'ignore', stdout: 'pipe', stderr: 'pipe', env: childEnv(o.env, o.machine) }); + const p = rt.spawn([...selfCommand(), ...argv], { cwd: o.cwd, stdin: 'ignore', stdout: 'pipe', stderr: 'pipe', env: childEnv(o.env, o.machine) }); let cancelled = false; const lines: string[] = []; let data: unknown; diff --git a/cli/lib/template-files.ts b/cli/lib/template-files.ts index 7610c67..9d0fe80 100644 --- a/cli/lib/template-files.ts +++ b/cli/lib/template-files.ts @@ -1,65 +1,4 @@ -// Every file under templates/, embedded as text so `storebound init` also works from the compiled desktop sidecar -// (a `bun build --compile` binary has no templates folder next to it). tests/apps.test.ts checks this list against the -// folder: add a line here when a template is added. -import t0 from '../../templates/app-privacy.md' with { type: 'text' }; -import t1 from '../../templates/appstore.md' with { type: 'text' }; -import t2 from '../../templates/credentials.example.json' with { type: 'text' }; -import t3 from '../../templates/data-safety.csv' with { type: 'text' }; -import t4 from '../../templates/declarations.md' with { type: 'text' }; -import t5 from '../../templates/listing.md' with { type: 'text' }; -import t6 from '../../templates/privacy-policy.html' with { type: 'text' }; -import t7 from '../../templates/profiles/app/app-privacy.md' with { type: 'text' }; -import t8 from '../../templates/profiles/app/appstore.md' with { type: 'text' }; -import t9 from '../../templates/profiles/app/declarations.md' with { type: 'text' }; -import t10 from '../../templates/profiles/app/listing.md' with { type: 'text' }; -import t11 from '../../templates/profiles/app/privacy-policy.html' with { type: 'text' }; -import t12 from '../../templates/profiles/app/shots-copy.json' with { type: 'text' }; -import t13 from '../../templates/profiles/app/storebound.config.ts' with { type: 'text' }; -import t14 from '../../templates/profiles/subscription-app/app-privacy.md' with { type: 'text' }; -import t15 from '../../templates/profiles/subscription-app/appstore.md' with { type: 'text' }; -import t16 from '../../templates/profiles/subscription-app/declarations.md' with { type: 'text' }; -import t17 from '../../templates/profiles/subscription-app/listing.md' with { type: 'text' }; -import t18 from '../../templates/profiles/subscription-app/storebound.config.ts' with { type: 'text' }; -import t19 from '../../templates/shots-copy.json' with { type: 'text' }; -import t20 from '../../templates/storebound.config.ts' with { type: 'text' }; -import t21 from '../../templates/testers.csv' with { type: 'text' }; -import tWebAppstore from '../../templates/web/appstore.svg' with { type: 'text' }; -import tWebGoogleplay from '../../templates/web/googleplay.svg' with { type: 'text' }; -import tWebLanding from '../../templates/web/landing.html' with { type: 'text' }; -import tWfAppRelease from '../../templates/profiles/app/workflows/release.yml' with { type: 'text' }; -import tWfAppBeta from '../../templates/profiles/app/workflows/beta.yml' with { type: 'text' }; -import tWfRelease from '../../templates/workflows/release.yml' with { type: 'text' }; -import tWfBeta from '../../templates/workflows/beta.yml' with { type: 'text' }; - -/** Template text by its path under templates/ (forward slashes). */ -export const TEMPLATE_TEXT: Record = { - 'app-privacy.md': t0 as unknown as string, - 'appstore.md': t1 as unknown as string, - 'credentials.example.json': t2 as unknown as string, - 'data-safety.csv': t3 as unknown as string, - 'declarations.md': t4 as unknown as string, - 'listing.md': t5 as unknown as string, - 'privacy-policy.html': t6 as unknown as string, - 'profiles/app/app-privacy.md': t7 as unknown as string, - 'profiles/app/appstore.md': t8 as unknown as string, - 'profiles/app/declarations.md': t9 as unknown as string, - 'profiles/app/listing.md': t10 as unknown as string, - 'profiles/app/privacy-policy.html': t11 as unknown as string, - 'profiles/app/shots-copy.json': t12 as unknown as string, - 'profiles/app/storebound.config.ts': t13 as unknown as string, - 'profiles/subscription-app/app-privacy.md': t14 as unknown as string, - 'profiles/subscription-app/appstore.md': t15 as unknown as string, - 'profiles/subscription-app/declarations.md': t16 as unknown as string, - 'profiles/subscription-app/listing.md': t17 as unknown as string, - 'profiles/subscription-app/storebound.config.ts': t18 as unknown as string, - 'shots-copy.json': t19 as unknown as string, - 'storebound.config.ts': t20 as unknown as string, - 'testers.csv': t21 as unknown as string, - 'web/appstore.svg': tWebAppstore as unknown as string, - 'web/googleplay.svg': tWebGoogleplay as unknown as string, - 'web/landing.html': tWebLanding as unknown as string, - 'profiles/app/workflows/release.yml': tWfAppRelease as unknown as string, - 'profiles/app/workflows/beta.yml': tWfAppBeta as unknown as string, - 'workflows/release.yml': tWfRelease as unknown as string, - 'workflows/beta.yml': tWfBeta as unknown as string, -}; +// Every file under templates/, embedded as text so `storebound init` also works from the npm bundle and the compiled +// desktop sidecar (neither has a templates folder next to its code). The table is generated into lib/embedded.ts by +// tools/gen-embedded.ts (bun run ci checks it is fresh). +export { TEMPLATE_TEXT } from './embedded'; diff --git a/cli/lib/tools.ts b/cli/lib/tools.ts index e0b24ce..3e7217e 100644 --- a/cli/lib/tools.ts +++ b/cli/lib/tools.ts @@ -6,6 +6,7 @@ // Nothing is installed system-wide. Sources and licenses: docs/desktop.md. import { existsSync, readFileSync } from 'node:fs'; import { delimiter, join } from 'node:path'; +import * as rt from './rt'; export type ToolId = 'ffmpeg' | 'ffprobe' | 'chrome' | 'xcode' | 'simctl' | 'adb' | 'git'; @@ -47,7 +48,7 @@ export function toolPath(env: Record = process.env, /** First line of ` ` (version banners), or null. */ function firstLine(cmd: string[], env: Record): string | null { try { - const r = Bun.spawnSync(cmd, { stdout: 'pipe', stderr: 'pipe', env, timeout: 5_000 }); + const r = rt.spawnSync(cmd, { stdout: 'pipe', stderr: 'pipe', env, timeout: 5_000 }); if (r.exitCode !== 0) return null; return (r.stdout.toString() || r.stderr.toString()).trim().split('\n')[0]?.trim() || null; } catch { return null; } @@ -72,7 +73,7 @@ export function detectTools(env: Record = process.en const PATH = toolPath(env); const spawnEnv: Record = { ...Object.fromEntries(Object.entries(env).filter((e): e is [string, string] => e[1] !== undefined)), PATH }; const appDirs = (env.STOREBOUND_TOOL_PATH ?? '').split(delimiter).filter(Boolean); - const which = (n: string): string | null => Bun.which(n, { PATH }); + const which = (n: string): string | null => rt.which(n, { PATH }); const src = (p: string | null): ToolStatus['source'] => (!p ? null : appDirs.some(d => p.startsWith(d)) ? 'app' : 'system'); const bin = (id: ToolId, name: string, usedFor: string, install: ToolStatus['install'], versionArgs: string[]): ToolStatus => { const path = which(id); @@ -206,7 +207,7 @@ export function androidSdkMissing(l: AndroidSdkLookup): string { /** Where playwright resolves from (the project, then the storebound folder), or null. */ export function findPlaywright(bases: string[]): string | null { for (const base of bases) { - try { return Bun.resolveSync('playwright', base); } catch { /* next */ } + try { return rt.resolveFrom('playwright', base); } catch { /* next */ } } return null; } diff --git a/cli/lib/upsert.ts b/cli/lib/upsert.ts index 7b12baa..08a4863 100644 --- a/cli/lib/upsert.ts +++ b/cli/lib/upsert.ts @@ -13,6 +13,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { log, warn } from './log'; +import * as rt from './rt'; /** A write whose outcome is unknown: it may or may not have been applied (timeout, connection reset, 5xx answer to a * POST). The API clients throw this instead of retrying such a request. */ @@ -58,7 +59,7 @@ export function retrySafe(method: string, path: string): boolean { } /** Waits between re-checks (STOREBOUND_UPSERT_WAIT_MS; the test preload sets 0). */ -export const upsertTiming = { waitMs: Number(process.env.STOREBOUND_UPSERT_WAIT_MS ?? 3000), tries: 4, sleep: (ms: number): Promise => (ms > 0 ? Bun.sleep(ms) : Promise.resolve()) }; +export const upsertTiming = { waitMs: Number(process.env.STOREBOUND_UPSERT_WAIT_MS ?? 3000), tries: 4, sleep: (ms: number): Promise => (ms > 0 ? rt.sleep(ms) : Promise.resolve()) }; export interface CreateOnce { /** what is being created, for log lines and errors: `in-app purchase com.example.gems` */ diff --git a/cli/lib/web-assets.ts b/cli/lib/web-assets.ts index 1f27fd6..638b669 100644 --- a/cli/lib/web-assets.ts +++ b/cli/lib/web-assets.ts @@ -18,6 +18,7 @@ import { dirname, join, relative, sep } from 'node:path'; import type { ShipConfig } from './config'; import { log, warn } from './log'; import { pump } from './runner'; +import * as rt from './rt'; export type Store = 'apple' | 'play'; @@ -138,7 +139,7 @@ export function lastSourceCommit(root: string, capDir = ''): number | null { if (capDir) for (const d of ['ios', 'android']) ex.add(`:(exclude)${capDir}/${d}/`); ex.add(':(exclude,glob)**/node_modules/**'); try { - const r = Bun.spawnSync(['git', 'log', '-1', '--format=%ct', '--', '.', ...ex], { cwd: root, stdout: 'pipe', stderr: 'pipe' }); + const r = rt.spawnSync(['git', 'log', '-1', '--format=%ct', '--', '.', ...ex], { cwd: root, stdout: 'pipe', stderr: 'pipe' }); if (r.exitCode !== 0) return null; const s = Number(r.stdout.toString().trim()); return s > 0 ? s * 1000 : null; @@ -179,7 +180,7 @@ export function prebuildsDone(env: Record = process. /** Runs a prebuild command in root with sh -c, streaming its output; throws on a nonzero exit. */ export async function runPrebuild(cmd: string, root: string, tag: string): Promise { log(tag, `prebuild: $ ${cmd}`); - const p = Bun.spawn(['sh', '-c', cmd], { cwd: root, stdin: 'ignore', stdout: 'pipe', stderr: 'pipe', env: process.env }); + const p = rt.spawn(['sh', '-c', cmd], { cwd: root, stdin: 'ignore', stdout: 'pipe', stderr: 'pipe', env: process.env }); const tail: string[] = []; const each = (line: string, kind: 'stdout' | 'stderr'): void => { tail.push(line); if (tail.length > 20) tail.shift(); diff --git a/cli/lib/workspace.ts b/cli/lib/workspace.ts index aa1ff09..bb15288 100644 --- a/cli/lib/workspace.ts +++ b/cli/lib/workspace.ts @@ -21,6 +21,7 @@ import { CONFIG_NAMES } from './config'; import { PROFILE_RE } from './credentials'; import type { StoreApp } from './desktop-protocol'; import { type IdProject, NO_OVERRIDES, type Overrides, type Pair } from './identity'; +import * as rt from './rt'; export const WORKSPACE_NAMES = ['storebound.workspace.json', 'storebound.workspace.ts', 'storebound.workspace.js']; /** Colours a studio can carry: the desktop's set (workspace.rs STUDIO_COLORS). */ @@ -94,7 +95,7 @@ export async function loadWorkspace(from: string, explicit?: string): Promise (ws.studios ?? []).map((s, i) => ({ id: studioId(s), name: s.name, color: s.color ?? STUDIO_COLORS[i % STUDIO_COLORS.length]! })); const isGlob = (p: string): boolean => /[*?[{]/.test(p); -const matchAny = (globs: string[], rel: string): boolean => globs.some(g => new Bun.Glob(g.replace(/\/+$/, '')).match(rel)); +const matchAny = (globs: string[], rel: string): boolean => globs.some(g => new rt.Glob(g.replace(/\/+$/, '')).match(rel)); const hasConfig = (dir: string): boolean => CONFIG_NAMES.some(n => existsSync(join(dir, n))); /** @@ -182,7 +183,7 @@ export function resolveApps(l: LoadedWorkspace): { apps: AppFolder[]; warnings: const base = isAbsolute(p) ? '/' : root; const pattern = isAbsolute(p) ? p.slice(1) : p; let hits = 0; - for (const m of new Bun.Glob(pattern.replace(/\/+$/, '')).scanSync({ cwd: base, onlyFiles: false, dot: false })) { + for (const m of new rt.Glob(pattern.replace(/\/+$/, '')).scanSync({ cwd: base, onlyFiles: false, dot: false })) { if (/(^|\/)(node_modules|\.git)(\/|$)/.test(m)) continue; const dir = resolve(base, m); try { if (!statSync(dir).isDirectory()) continue; } catch { continue; } @@ -249,7 +250,7 @@ export function selectApps(apps: T[], n const picked = new Set(); for (const n of names) { const lc = n.toLowerCase(); - const hits = apps.filter(a => a.rel.toLowerCase() === lc || basename(a.rel).toLowerCase() === lc || a.name.toLowerCase() === lc || (isGlob(n) && new Bun.Glob(n).match(a.rel))); + const hits = apps.filter(a => a.rel.toLowerCase() === lc || basename(a.rel).toLowerCase() === lc || a.name.toLowerCase() === lc || (isGlob(n) && new rt.Glob(n).match(a.rel))); if (!hits.length) unknown.push(n); hits.forEach(h => picked.add(h)); } diff --git a/cli/listing.ts b/cli/listing.ts index 731ef17..331cc30 100644 --- a/cli/listing.ts +++ b/cli/listing.ts @@ -22,6 +22,7 @@ import { } from './lib/listing-translate'; import { textTable } from './lib/table'; import { resolveLanguages } from './lib/whats-new'; +import * as rt from './lib/rt'; const TAG = 'listing'; const storeCode = (l: Language): string => l.apple ?? playCodes(l)[0] ?? l.id; @@ -60,7 +61,7 @@ async function translate(ctx: Ctx): Promise { const dry = a.has('dry'); const rows: Row[] = []; const runHook = (text: string, l: Language, st: 'apple' | 'play', field: string, limit: number): string | null => { - const r = Bun.spawnSync(['sh', '-c', hook!], { stdin: new TextEncoder().encode(text), stdout: 'pipe', stderr: 'pipe', + const r = rt.spawnSync(['sh', '-c', hook!], { stdin: new TextEncoder().encode(text), stdout: 'pipe', stderr: 'pipe', env: { ...process.env, STOREBOUND_SOURCE_LANG: storeCode(src), STOREBOUND_TARGET_LANG: st === 'apple' ? l.apple ?? l.id : playCodes(l)[0] ?? l.id, STOREBOUND_TARGET_LANG_ID: l.id, STOREBOUND_STORE: st, STOREBOUND_FIELD: field, STOREBOUND_LIMIT: String(limit) } }); const out = r.stdout.toString().trim(); diff --git a/cli/mcp.ts b/cli/mcp.ts index adc29d8..6abbfac 100644 --- a/cli/mcp.ts +++ b/cli/mcp.ts @@ -23,6 +23,7 @@ import pkg from '../package.json' with { type: 'json' }; import { runCommand, type Running } from './lib/runner'; import { listTools, TOOLS, toArgv, validateInput } from './mcp-tools'; +import * as rt from './lib/rt'; /** Current (modern, stateless) revision. */ export const PROTOCOL = '2026-07-28'; @@ -132,7 +133,7 @@ export async function serve(): Promise { console.error(`[storebound mcp] ${pkg.version} on stdio (MCP ${PROTOCOL}, legacy ${LEGACY.join(', ')}), project ${process.env.STOREBOUND_PROJECT || process.cwd()}`); let buf = ''; const decoder = new TextDecoder(); - const reader = Bun.stdin.stream().getReader(); + const reader = rt.stdinStream().getReader(); for (;;) { const { value, done } = await reader.read(); if (done) break; diff --git a/cli/play/build.ts b/cli/play/build.ts index 8a87790..a6a8ba5 100644 --- a/cli/play/build.ts +++ b/cli/play/build.ts @@ -26,6 +26,7 @@ import { capacitorProject, webStep } from '../lib/web-assets'; import { androidSdk, androidSdkEnv, androidSdkMissing } from '../lib/tools'; import { playClient } from './client'; import { usedVersionCodes } from './next-version-code'; +import * as rt from '../lib/rt'; const TAG = 'build'; @@ -93,7 +94,7 @@ export async function run(ctx: Ctx): Promise { log(TAG, `$ ${plan.command}`); const started = Date.now(); mkdirSync(out, { recursive: true }); - const p = Bun.spawn(argv, { cwd, stdout: 'pipe', stderr: 'pipe', env: { ...process.env, ...androidSdkEnv(sdk), ...signingEnv(signing) } }); + const p = rt.spawn(argv, { cwd, stdout: 'pipe', stderr: 'pipe', env: { ...process.env, ...androidSdkEnv(sdk), ...signingEnv(signing) } }); const lines: string[] = []; const each = (line: string): void => { lines.push(line); diff --git a/cli/play/client.ts b/cli/play/client.ts index 15f6493..bb5e5a9 100644 --- a/cli/play/client.ts +++ b/cli/play/client.ts @@ -4,6 +4,7 @@ import { tokenSource } from '../lib/google-auth'; import { log, warn } from '../lib/log'; import { errorBody } from '../lib/http'; import { AmbiguousWriteError, isAmbiguous, retrySafe } from '../lib/upsert'; +import * as rt from '../lib/rt'; export type PlayClient = Awaited>; @@ -30,13 +31,13 @@ export async function playClient(root: string, packageName: string) { body: body === undefined ? undefined : bin ? (body as unknown as BodyInit) : JSON.stringify(body), }); } catch (e) { - if (safe && attempt < 4) { await Bun.sleep(2000 * (attempt + 1)); continue; } + if (safe && attempt < 4) { await rt.sleep(2000 * (attempt + 1)); continue; } if (!safe) throw new AmbiguousWriteError(`${method} ${short()} -> no answer (${(e as Error).message})`); throw e; } const text = await r.text(); if (r.ok) return (text ? JSON.parse(text) : {}) as T; - if ((r.status === 429 || (r.status >= 500 && safe)) && attempt < 4) { await Bun.sleep(2000 * (attempt + 1)); continue; } + if ((r.status === 429 || (r.status >= 500 && safe)) && attempt < 4) { await rt.sleep(2000 * (attempt + 1)); continue; } // A wrong path casing answers with an HTML 404 page, not JSON: say so instead of dumping the HTML. const msg = /^\s*= 500 && !safe) throw new AmbiguousWriteError(`${method} ${short()} -> ${r.status} ${msg}`, r.status); diff --git a/cli/play/data-safety.ts b/cli/play/data-safety.ts index e8a8637..66af2fd 100644 --- a/cli/play/data-safety.ts +++ b/cli/play/data-safety.ts @@ -13,11 +13,12 @@ import { dataSafetyCsv, dataSafetyProblem, dataSafetyRows } from '../lib/data-sa import { DATA_SAFETY_UPLOADED } from '../lib/declarations'; import { done, log } from '../lib/log'; import { playClient } from './client'; +import * as rt from '../lib/rt'; export const DATA_SAFETY_HASH = '.storebound/data-safety.sha256'; /** sha256 of the CSV for one package. Pure. */ -export const dataSafetyHash = (pkg: string, csv: string): string => new Bun.CryptoHasher('sha256').update(`${pkg}\n${csv}`).digest('hex'); +export const dataSafetyHash = (pkg: string, csv: string): string => rt.hasher('sha256').update(`${pkg}\n${csv}`).digest('hex'); /** The hash of the last upload of this package, null when none is recorded. */ export function lastUpload(root: string, pkg: string): { sha256: string; at: string } | null { diff --git a/cli/play/google.ts b/cli/play/google.ts index d1fe256..2201858 100644 --- a/cli/play/google.ts +++ b/cli/play/google.ts @@ -3,6 +3,7 @@ import { playServiceAccount } from '../lib/credentials'; import { tokenSource } from '../lib/google-auth'; import { errorBody } from '../lib/http'; +import * as rt from '../lib/rt'; export const REPORTING_API = 'https://playdeveloperreporting.googleapis.com/v1beta1'; @@ -12,7 +13,7 @@ export function googleClient(root: string, scope: string) { for (let attempt = 0; ; attempt++) { const r = await fetch(url, { method, headers: { Authorization: 'Bearer ' + (await token()), ...(body === undefined ? {} : { 'Content-Type': 'application/json' }) }, body: body === undefined ? undefined : JSON.stringify(body) }); - if ((r.status === 429 || r.status >= 500) && attempt < 4) { await r.body?.cancel(); await Bun.sleep(2000 * (attempt + 1)); continue; } + if ((r.status === 429 || r.status >= 500) && attempt < 4) { await r.body?.cancel(); await rt.sleep(2000 * (attempt + 1)); continue; } return r; } } diff --git a/cli/reports.ts b/cli/reports.ts index 7dc678e..e643454 100644 --- a/cli/reports.ts +++ b/cli/reports.ts @@ -26,6 +26,7 @@ import { import { textTable } from './lib/table'; import { unzip } from './lib/zip'; import { googleClient } from './play/google'; +import * as rt from './lib/rt'; const TAG = 'reports'; const USAGE = 'usage: storebound reports apple-sales | apple-finance --month YYYY-MM | play-earnings --month YYYY-MM | play-sales --month YYYY-MM'; @@ -87,7 +88,7 @@ export async function run(ctx: Ctx): Promise { let gz: Uint8Array | null; try { gz = await c.bytes(path); } catch (e) { const hint = appleReportHint((e as Error).message); throw new Error(hint ? `${(e as Error).message}\n${hint}` : (e as Error).message); } if (!gz) { done(TAG, 'no report for that date (no sales then, or not published yet: daily reports come out the next day, monthly ones about a week later)'); if (a.has('json')) printJson({ report: name, empty: true }); return; } - const text = new TextDecoder().decode(gz[0] === 0x1f && gz[1] === 0x8b ? Bun.gunzipSync(gz as Uint8Array) : gz); + const text = new TextDecoder().decode(gz[0] === 0x1f && gz[1] === 0x8b ? rt.gunzipSync(gz as Uint8Array) : gz); const rows = parseTsv(text); const summary = sub === 'apple-sales' ? (a.str('type', 'SALES').toUpperCase() === 'SALES' ? summarizeAppleSales(rows) : []) : summarizeAppleFinance(rows); const files = save(out, name, text, summary, csvOut); diff --git a/cli/revenuecat.ts b/cli/revenuecat.ts index 74ffeee..070be59 100644 --- a/cli/revenuecat.ts +++ b/cli/revenuecat.ts @@ -30,6 +30,7 @@ import { type RcState, type RcStep, resolveApps, wantedRevenueCat, } from './lib/revenuecat'; import { textTable } from './lib/table'; +import * as rt from './lib/rt'; const TAG = 'revenuecat'; const ORIGIN = 'https://api.revenuecat.com'; @@ -63,7 +64,7 @@ export function rcClient(key: string, o: { retries?: number } = {}): RcClient { const s = Math.min(60, Math.max(0, Number(r.headers.get('retry-after') ?? '5') || 0)); warn(TAG, `rate limited, waiting ${s}s`); await r.body?.cancel(); - await Bun.sleep(s * 1000); + await rt.sleep(s * 1000); continue; } const text = await r.text(); diff --git a/cli/serve.ts b/cli/serve.ts index 18225ba..a0ff446 100644 --- a/cli/serve.ts +++ b/cli/serve.ts @@ -27,6 +27,7 @@ import { RENDER_FILE_RE } from './lib/project-files'; import { androidSdk, detectTools } from './lib/tools'; import { compiled, pickCallEnv, runCommand, type RunOptions, type Running } from './lib/runner'; import { inputSchema, type ToolDef, TOOLS, toArgv, validateInput } from './mcp-tools'; +import * as rt from './lib/rt'; type Id = string | number; interface Msg { jsonrpc?: string; id?: Id | null; method?: string; params?: unknown } @@ -388,7 +389,7 @@ export class Session { } if (!this.guard) throw new RpcError(ERR.notInitialized, 'session.hello first'); switch (method) { - case 'system.version': return { version: pkg.version, compiled: compiled(), platform: process.platform, arch: process.arch, bun: Bun.version }; + case 'system.version': return { version: pkg.version, compiled: compiled(), platform: process.platform, arch: process.arch, bun: rt.bunVersion, runtime: rt.runtime }; case 'system.methods': return { methods: describeMethods() }; case 'system.tools': return localTools(); case 'project.inspect': return this.inspect(checkProject(params.project), {}, id); @@ -525,7 +526,7 @@ export async function serveStdio(): Promise { console.error(`[storebound serve] ${pkg.version} on stdio (${DESKTOP_PROTOCOL}), waiting for session.hello`); let buf = ''; const decoder = new TextDecoder(); - const reader = Bun.stdin.stream().getReader(); + const reader = rt.stdinStream().getReader(); for (;;) { const { value, done } = await reader.read(); if (done) break; diff --git a/cli/shots/browser.ts b/cli/shots/browser.ts index 00026ce..71e33a5 100644 --- a/cli/shots/browser.ts +++ b/cli/shots/browser.ts @@ -23,17 +23,18 @@ import { existsSync, readlinkSync } from 'node:fs'; import { hostname } from 'node:os'; import { join } from 'node:path'; import { compiled } from '../lib/runner'; +import * as rt from '../lib/rt'; // eslint-disable-next-line @typescript-eslint/no-explicit-any async function chromium(root: string): Promise { - for (const base of [root, join(import.meta.dir, '../..')]) { + for (const base of [root, rt.packageRoot()]) { try { - const p = Bun.resolveSync('playwright', base); + const p = rt.resolveFrom('playwright', base); // only an installed copy (node_modules): outside a project Bun auto-installs the newest playwright into its global // cache (~/.bun/install/cache), whose browser build was never downloaded ("headless shell: not installed" on // Linux CI, where no Google Chrome hides it); storebound's own copy is next if (!isInstalledPackage(p)) continue; - return (await import(p)).chromium; + return (await rt.importPath(p)).chromium; } catch { /* next */ } } // only reached in the compiled sidecar: the bundled playwright-core (not a runtime dependency of the npm package) @@ -68,7 +69,7 @@ export interface Candidate { } /** - * Switches for a Chromium storebound starts itself (Bun.spawn, not Playwright's launch, which already passes + * Switches for a Chromium storebound starts itself (rt.spawn, not Playwright's launch, which already passes * --no-sandbox unless chromiumSandbox is set): Chromium refuses to run as root on Linux without --no-sandbox * ("Running as root without --no-sandbox is not supported", crbug.com/638180), and inside a Docker / Podman container * its user-namespace sandbox is usually blocked by seccomp, so CI containers (the Forgejo runner runs jobs as root) diff --git a/cli/shots/capture.ts b/cli/shots/capture.ts index d07087a..0b014fc 100644 --- a/cli/shots/capture.ts +++ b/cli/shots/capture.ts @@ -37,6 +37,7 @@ import { adbAppLocaleArgs, adbDemoExitArgs, adbDemoModeArgs, adbForceStopArgs, a import { type CopyFile, copyText, type FrameDims, frameDims } from './lib'; import { captureWeb, defaultConcurrency, hashPaths, hashServed, planWeb, readCache, selectJobs, summaryLines, writeCaptureState } from './capture-web'; import { imageInfo } from './spec'; +import * as rt from '../lib/rt'; type Device = 'phone' | 'tablet'; @@ -56,7 +57,7 @@ export function adbPath(): string { /** Throws the install hint when a tool is neither a path that exists nor on PATH. */ export function requireTool(cmd: string, name = cmd): void { - if (existsSync(cmd) || Bun.which(cmd)) return; + if (existsSync(cmd) || rt.which(cmd)) return; throw new Error(TOOL_HINTS[name] ?? `${name} not found on PATH`); } @@ -150,7 +151,7 @@ export async function run(ctx: Ctx): Promise { if (source === 'ios-sim') { const ios = need(cap.ios, 'shots.capture.ios'); if (!dry) { requireTool('xcrun'); requireTool('ffmpeg'); } - const list = dry && !Bun.which('xcrun') ? '{"devices":{}}' : sh('xcrun', ['simctl', 'list', 'devices', '-j']).out.toString(); + const list = dry && !rt.which('xcrun') ? '{"devices":{}}' : sh('xcrun', ['simctl', 'list', 'devices', '-j']).out.toString(); for (const device of devices) { const want = simTarget(ios, device); const todo = shotsFor(cap, device, only); @@ -171,16 +172,16 @@ export async function run(ctx: Ctx): Promise { if (bootedByUs && warmup > 0) { log('shots', `warming up ${ios.bundleId} (first launch after boot, ${warmup} ms; shots.capture.ios.warmupMs)`); exec('xcrun', simLaunchArgs(sim.udid, ios.bundleId, { lang: langs[0]!, locale: localeOf(config, langs[0]!, 'ios-sim'), shot: '', baseArgs: ios.args })); - if (!dry) await Bun.sleep(warmup); + if (!dry) await rt.sleep(warmup); } for (const lang of langs) for (const [name, s] of todo) { try { const locale = localeOf(config, lang, 'ios-sim'); exec('xcrun', simLaunchArgs(sim.udid, ios.bundleId, { lang, locale, shot: name, baseArgs: ios.args, args: s.args })); - if (s.url) { if (!dry) await Bun.sleep(1500); exec('xcrun', simOpenUrlArgs(sim.udid, fill(s.url, { lang, locale, shot: name }))); } + if (s.url) { if (!dry) await rt.sleep(1500); exec('xcrun', simOpenUrlArgs(sim.udid, fill(s.url, { lang, locale, shot: name }))); } const out = target(lang, device, name); if (dry) { progress(`${lang}/${device}/${name}.png (${sim.name}, would wait ${s.delayMs ?? 2500} ms)`); continue; } - await Bun.sleep(s.delayMs ?? 2500); + await rt.sleep(s.delayMs ?? 2500); const tmp = join(tmpDir, `sim-${name}.png`); sh('xcrun', simScreenshotArgs(sim.udid, tmp)); storeFrame(tmp, out, cap.cropTop, device, dims[device]); @@ -221,7 +222,7 @@ export async function run(ctx: Ctx): Promise { exec(adb, adbStartArgs(and.package, { lang, locale, shot: name, url: s.url, activity: and.activity, extras: s.extras }, serial)); const out = target(lang, device, name); if (dry) { progress(`${lang}/${device}/${name}.png (would wait ${s.delayMs ?? 2500} ms, screencap)`); continue; } - await Bun.sleep(s.delayMs ?? 2500); + await rt.sleep(s.delayMs ?? 2500); const tmp = join(tmpDir, `adb-${name}.png`); writeFileSync(tmp, sh(adb, adbScreencapArgs(serial)).out); storeFrame(tmp, out, cap.cropTop, device, dims[device]); diff --git a/cli/shots/record.ts b/cli/shots/record.ts index c2846c1..0ac1b11 100644 --- a/cli/shots/record.ts +++ b/cli/shots/record.ts @@ -27,6 +27,7 @@ import { ADB_REMOTE_CLIP, adbAppLocaleArgs, adbDemoExitArgs, adbDemoModeArgs, ad simStatusBarArgs, simStatusBarClearArgs, simTarget, SOURCES, type Source, trimArgs, validateRecord, concatArgs, concatList, type ScreencastFrame } from './devices'; import { frameDims } from './lib'; import { CLIP_RE, clipFiles } from './video'; +import * as rt from '../lib/rt'; /** Clips to film, in config order (with their index for the file name), limited by --only. */ export function clipsToRecord(r: VideoRecord, only: string[] = []): [number, string, ClipSpec][] { @@ -137,7 +138,7 @@ export async function run(ctx: Ctx): Promise { if (source === 'ios-sim') { const ios = need(cap?.ios, 'shots.capture.ios'); if (!dry) requireTool('xcrun'); - const list = dry && !Bun.which('xcrun') ? '{"devices":{}}' : sh('xcrun', ['simctl', 'list', 'devices', '-j']).out.toString(); + const list = dry && !rt.which('xcrun') ? '{"devices":{}}' : sh('xcrun', ['simctl', 'list', 'devices', '-j']).out.toString(); const want = simTarget(ios, device); if (!want) throw new Error(`no shots.capture.ios.${device === 'tablet' ? 'devices.tablet' : 'device'}`); const sim = pickSimulator(list, want) ?? (dry ? { udid: want, name: want, state: 'Shutdown' } : null); @@ -151,19 +152,19 @@ export async function run(ctx: Ctx): Promise { if (bootedByUs && warmup > 0) { log('shots', `warming up ${ios.bundleId} (${warmup} ms)`); exec('xcrun', simLaunchArgs(sim.udid, ios.bundleId, { lang: langs[0]!, locale: localeOf(config, langs[0]!, 'ios-sim'), shot: '', baseArgs: ios.args })); - if (!dry) await Bun.sleep(warmup); + if (!dry) await rt.sleep(warmup); } for (const lang of langs) for (const [idx, name, cl] of todo) { try { const locale = localeOf(config, lang, 'ios-sim'); exec('xcrun', simLaunchArgs(sim.udid, ios.bundleId, { lang, locale, shot: name, baseArgs: ios.args, args: cl.args })); - if (cl.url) { if (!dry) await Bun.sleep(1500); exec('xcrun', simOpenUrlArgs(sim.udid, fill(cl.url, { lang, locale, shot: name }))); } + if (cl.url) { if (!dry) await rt.sleep(1500); exec('xcrun', simOpenUrlArgs(sim.udid, fill(cl.url, { lang, locale, shot: name }))); } const out = outFor(lang, idx, name); const raw = join(tmpDir, `sim-${name}.mp4`); if (dry) { log('shots', `would run: xcrun ${simRecordArgs(sim.udid, raw).join(' ')} (${cl.seconds} s, then SIGINT)`); progress(`${lang}/${clipName(idx, name)}`); continue; } - await Bun.sleep(cl.delayMs ?? 2500); - const p = Bun.spawn(['xcrun', ...simRecordArgs(sim.udid, raw)], { stdout: 'ignore', stderr: 'pipe' }); - await Bun.sleep(cl.seconds * 1000 + 700); + await rt.sleep(cl.delayMs ?? 2500); + const p = rt.spawn(['xcrun', ...simRecordArgs(sim.udid, raw)], { stdout: 'ignore', stderr: 'pipe' }); + await rt.sleep(cl.seconds * 1000 + 700); p.kill('SIGINT'); await p.exited; if (!existsSync(raw)) throw new Error(`simctl recordVideo wrote nothing: ${(await new Response(p.stderr).text()).trim().slice(0, 300)}`); @@ -193,7 +194,7 @@ export async function run(ctx: Ctx): Promise { try { exec(adb, adbForceStopArgs(and.package, serial)); exec(adb, adbStartArgs(and.package, { lang, locale, shot: name, url: cl.url, activity: and.activity, extras: cl.extras }, serial)); - if (!dry) await Bun.sleep(cl.delayMs ?? 2500); + if (!dry) await rt.sleep(cl.delayMs ?? 2500); exec(adb, adbRecordArgs(ADB_REMOTE_CLIP, cl.seconds, serial), { timeout: (cl.seconds + 30) * 1000 }); const raw = join(tmpDir, `adb-${name}.mp4`); exec(adb, adbPullArgs(ADB_REMOTE_CLIP, raw, serial), { timeout: 120_000 }); diff --git a/cli/shots/render.ts b/cli/shots/render.ts index a49c69b..a04b23e 100644 --- a/cli/shots/render.ts +++ b/cli/shots/render.ts @@ -13,7 +13,8 @@ // paths) are served from where they are at /fonts/local/... (local-fonts.ts); a file changed since `shots fonts` fails. // Style: shotsStyle() = shots.style, else app for the app / subscription-app profiles (template-app.html, // feature-app.html: calm gradient, device frame, headline + subline), else game (template.html, feature.html). -import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs'; +import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { SHOTS_PAGES } from '../lib/embedded'; import { fontCodepoints } from './cmap'; import { readManifest } from './fonts'; import { type FontManifest, fontUsage, measureFonts, roleTexts, uncoveredChars } from './glyphs'; @@ -27,6 +28,7 @@ import { shotsStyle } from '../lib/config'; import { type CopyFile, SIZES, copyText, defaultSizes, frameDims, mergeCopy, palette, renderDir, scriptFont, sheetLayout, validateCopy, variantScenes } from './lib'; import { faceFilePath, fontsRoute, resolveShotFonts, staleLocalFonts } from './local-fonts'; import { type RenderReport, mergeReport, readReport, resolvedTablets } from './report'; +import * as rt from '../lib/rt'; const TYPES: Record = { html: 'text/html; charset=utf-8', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp', woff2: 'font/woff2', woff: 'font/woff', ttf: 'font/ttf', otf: 'font/otf', css: 'text/css', svg: 'image/svg+xml' }; @@ -113,9 +115,7 @@ export async function run(ctx: Ctx): Promise { for (const lang of langs) for (const f of manifest.langs[lang]?.fallbacks ?? []) (manifest.faces[f.face]?.chosen ? log : warn)('shots', `${lang} ${f.role}: ${[...f.chars].join(' ')} from ${f.family}${manifest.faces[f.face]?.chosen ? '' : ' (automatic fallback: shots check will fail)'}`); } - const tpl = import.meta.dir; - - const server = Bun.serve({ + const server = await rt.serve({ port: a.num('port', 8792), hostname: '127.0.0.1', async fetch(req) { const url = new URL(req.url); @@ -138,11 +138,9 @@ export async function run(ctx: Ctx): Promise { else if (p.startsWith('/frames/')) file = join(framesDir, p.slice(8)); else if (p.startsWith('/fonts/')) file = fontsRoute(p.slice(7), fontsDir, root, manifest) ?? ''; else if (p.startsWith('/out/')) file = join(outDir, p.slice(5)); - else if (/^\/(template|feature)(-app)?\.html$/.test(p)) file = join(tpl, p.slice(1)); + else if (/^\/(template|feature)(-app)?\.html$/.test(p)) return new Response(SHOTS_PAGES[p.slice(1)]!, { headers: { 'content-type': TYPES.html! } }); if (!file || file.includes('..')) return new Response('no', { status: 404 }); - const f = Bun.file(file); - if (!(await f.exists())) return new Response('missing ' + p, { status: 404 }); - return new Response(f, { headers: { 'content-type': TYPES[file.split('.').pop()!.toLowerCase()] ?? 'application/octet-stream' } }); + return rt.fileResponse(file, { 'content-type': TYPES[file.split('.').pop()!.toLowerCase()] ?? 'application/octet-stream' }) ?? new Response('missing ' + p, { status: 404 }); }, }); const base = `http://127.0.0.1:${server.port}`; @@ -182,7 +180,7 @@ export async function run(ctx: Ctx): Promise { } if (overflow.length) warn('shots', `${relative(root, out)}: ${overflow.join('; ')}`); await page.screenshot({ path: tmp, clip: { x: 0, y: 0, width: w, height: h } }); - const ff = Bun.spawn(['ffmpeg', '-v', 'error', '-y', '-i', tmp, '-pix_fmt', 'rgb24', out], { stderr: 'pipe' }); + const ff = rt.spawn(['ffmpeg', '-v', 'error', '-y', '-i', tmp, '-pix_fmt', 'rgb24', out], { stderr: 'pipe' }); const code = await ff.exited; rmSync(tmp, { force: true }); if (code !== 0) throw new Error('ffmpeg: ' + await new Response(ff.stderr).text()); @@ -232,7 +230,7 @@ export async function run(ctx: Ctx): Promise { const lay = sheetLayout(rows); const imgs = lay.tiles.map(t => ``).join(''); const sheet = join(dir, '.sheet.html'); - await Bun.write(sheet, `${imgs} + writeFileSync(sheet, `${imgs} `); await withPage(p => shoot(p, `${base}/out/${relative(outDir, sheet)}`, lay.w, lay.h, join(dir, 'sheet.png'))); rmSync(sheet, { force: true }); @@ -241,7 +239,7 @@ export async function run(ctx: Ctx): Promise { for (const p of pages) await p?.page.context().close().catch(() => {}); await closeBrowser(browser); server.stop(true); - if (Object.keys(report).length) await Bun.write(join(outDir, 'report.json'), JSON.stringify(mergeReport(readReport(outDir), report), null, 1) + '\n'); + if (Object.keys(report).length) writeFileSync(join(outDir, 'report.json'), JSON.stringify(mergeReport(readReport(outDir), report), null, 1) + '\n'); } const flagged = Object.values(report).filter(r => r.overflow.length).length; if (flagged) warn('shots', `${flagged} images with text overflow flags: storebound shots check / shots gallery`); diff --git a/cli/shots/video.ts b/cli/shots/video.ts index 474986f..cf740c3 100644 --- a/cli/shots/video.ts +++ b/cli/shots/video.ts @@ -21,6 +21,8 @@ import { captionsFor, type CopyFile, copyText, palette, renderDir, scriptFont } import { FONT_TYPES, fontsRoute, resolveShotFonts } from './local-fonts'; import { ffprobe } from './report'; import { checkVideo, PREVIEW_RULES, VIDEO_TARGETS } from './spec'; +import { SHOTS_PAGES } from '../lib/embedded'; +import * as rt from '../lib/rt'; export const CLIP_RE = /\.(mp4|mov|m4v|webm)$/i; @@ -145,13 +147,13 @@ export async function run(ctx: Ctx): Promise { if (!dry) mkdirSync(tmp, { recursive: true }); // caption PNGs come from a local page (fonts from `shots fonts`), like the screenshots - let server: ReturnType | null = null, browser: { close(): Promise; newContext(o: unknown): Promise<{ newPage(): Promise }> } | null = null; + let server: rt.Server | null = null, browser: { close(): Promise; newContext(o: unknown): Promise<{ newPage(): Promise }> } | null = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any let page: any = null; if (captions) { const fontsDir = join(root, '.storebound', 'fonts'); if (!existsSync(join(fontsDir, 'fonts.css'))) warn('shots', 'no .storebound/fonts/fonts.css: run `storebound shots fonts` first (captions fall back to system fonts)'); - server = Bun.serve({ port: a.num('port', 8793), hostname: '127.0.0.1', async fetch(req) { + server = await rt.serve({ port: a.num('port', 8793), hostname: '127.0.0.1', async fetch(req) { const url = new URL(req.url); if (url.pathname === '/caption.json') { const lang = url.searchParams.get('lang') ?? ''; @@ -162,11 +164,11 @@ export async function run(ctx: Ctx): Promise { return Response.json({ text: texts[Number(url.searchParams.get('i'))] ?? '', ink: style === 'app' ? '#111827' : pal.ink, rtl: rtl.get(lang), fonts: { title: resolveShotFonts(shots, style, ctx.loaded.resolve, root).title.family, script: script ? `Store ${script.slug}` : null, titleStack: readManifest(fontsDir)?.langs[lang]?.title } }); } - if (url.pathname === '/caption.html') return new Response(Bun.file(join(import.meta.dir, 'caption.html')), { headers: { 'content-type': 'text/html; charset=utf-8' } }); + if (url.pathname === '/caption.html') return new Response(SHOTS_PAGES['caption.html']!, { headers: { 'content-type': 'text/html; charset=utf-8' } }); if (url.pathname.startsWith('/fonts/')) { const file = fontsRoute(decodeURIComponent(url.pathname.slice(7)), fontsDir, root, readManifest(fontsDir)); - const f = file ? Bun.file(file) : null; - if (f && await f.exists()) return new Response(f, { headers: { 'content-type': FONT_TYPES[file!.split('.').pop()!.toLowerCase()] ?? 'font/woff2' } }); + const f = file ? rt.fileResponse(file, { 'content-type': FONT_TYPES[file.split('.').pop()!.toLowerCase()] ?? 'font/woff2' }) : null; + if (f) return f; } return new Response('no', { status: 404 }); } }); diff --git a/cli/text-modules.d.ts b/cli/text-modules.d.ts deleted file mode 100644 index 371dfaa..0000000 --- a/cli/text-modules.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Template files imported as text (lib/template-files.ts, `with { type: 'text' }`). -declare module '*.md' { const text: string; export default text; } -declare module '*.csv' { const text: string; export default text; } -declare module '*.html' { const text: string; export default text; } -declare module '*.svg' { const text: string; export default text; } -declare module '*.yml' { const text: string; export default text; } diff --git a/cli/whats-new.ts b/cli/whats-new.ts index 3c3713e..941a9e8 100644 --- a/cli/whats-new.ts +++ b/cli/whats-new.ts @@ -26,6 +26,7 @@ import { applyLanguageNotes, checkLanguageNotes, checkNotes, formatNotes, type LangCheck, NOTES_LIMITS, type NotesFile, noteItems, notesFileText, parseNotesFile, resolveLanguages, setAppleWhatsNew, setPlayWhatsNew, } from './lib/whats-new'; +import * as rt from './lib/rt'; const TAG = 'whats-new'; const storeCode = (l: Language): string => l.apple ?? playCodes(l)[0] ?? l.id; @@ -62,7 +63,7 @@ async function languagesFlow(ctx: Ctx, draft: { play: string; apple: string }): const cur = existsSync(path) ? parseNotesFile(readFileSync(path, 'utf8'), l.id) : null; if (cur && cur.status === 'translated' && !a.has('force')) { files.push({ l, f: cur, write: false, note: 'kept (translated; --force replaces it)' }); continue; } if (hook && !a.has('dry')) { - const r = Bun.spawnSync(['sh', '-c', hook], { stdin: new TextEncoder().encode(text), stdout: 'pipe', stderr: 'pipe', + const r = rt.spawnSync(['sh', '-c', hook], { stdin: new TextEncoder().encode(text), stdout: 'pipe', stderr: 'pipe', env: { ...process.env, STOREBOUND_SOURCE_LANG: storeCode(src!), STOREBOUND_TARGET_LANG: storeCode(l), STOREBOUND_TARGET_LANG_ID: l.id } }); const out = r.stdout.toString().trim(); if (r.exitCode !== 0 || !out) { warn(TAG, `${l.id}: --translate failed (exit ${r.exitCode}): ${r.stderr.toString().trim().slice(0, 200)}`); files.push({ l, f: { lang: l.id, source: src!.id, status: 'translate', text }, write: true, note: 'to translate (hook failed)' }); continue; } @@ -79,7 +80,7 @@ async function languagesFlow(ctx: Ctx, draft: { play: string; apple: string }): } function git(args: string[], cwd: string): string { - const r = Bun.spawnSync(['git', ...args], { cwd, stdout: 'pipe', stderr: 'pipe' }); + const r = rt.spawnSync(['git', ...args], { cwd, stdout: 'pipe', stderr: 'pipe' }); if (r.exitCode !== 0) throw new Error(`git ${args.join(' ')}: ${r.stderr.toString().trim()}`); return r.stdout.toString(); } diff --git a/docs/faq.md b/docs/faq.md index 92d823c..1be2b57 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -30,8 +30,9 @@ your AAB / build numbers and listing files. password. Store writes are dry runs until you say otherwise, and the public steps (submit, release) stay with you. Use an App Store Connect key with the App Manager role and a Play service account limited to your app. -**Why Bun?** One fast runtime with TypeScript, `fetch`, a test runner and a static server built in: no build step and -zero runtime dependencies. It needs Bun installed (it does not run on plain Node). +**Why Bun, and does it need Bun?** storebound is developed and tested on Bun (TypeScript, `fetch`, a test runner built in), +with zero runtime dependencies. You do not need Bun to use it: the npm package runs on Node 20+ (`npx storebound`), +and `bunx storebound` runs the same CLI on Bun 1.1+. **Does it upload my video to YouTube?** No. Play only takes a YouTube URL, and uploading to YouTube needs your Google account; `shots video` makes the file, you upload it and paste the URL into `play.video`. diff --git a/docs/getting-started.md b/docs/getting-started.md index c1c1449..461473f 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2,12 +2,15 @@ ## 5-minute quick start -You need [Bun](https://bun.sh) 1.1+ (and `ffmpeg` for icons, screenshots and video). +You need [Node](https://nodejs.org) 20+ or [Bun](https://bun.sh) 1.1+ (and `ffmpeg` for icons, screenshots and video). +With Node, run the commands as `npx storebound ...`; with Bun, as `bunx storebound ...` (used below). Both run the +same CLI. ```sh cd my-app -bun add -d storebound playwright # or: bun add -d git+https://git.crt.fyi/CRT/storebound.git playwright -bunx storebound init # --profile app (default) | subscription-app | game: config, store/*.md, declarations, privacy policy, .storebound/ (gitignored) +npx storebound init # Node 20+ (npm i -D storebound playwright to pin it in the project) +bunx storebound init # or Bun (bun add -d storebound playwright, or bun add -d git+https://git.crt.fyi/CRT/storebound.git playwright) + # --profile app (default) | subscription-app | game: config, store/*.md, declarations, privacy policy, .storebound/ (gitignored) # fill in storebound.config.ts and the store/ files bunx storebound check # offline: config, copy limits, Data safety answers, products, files ``` diff --git a/package.json b/package.json index ef0c00e..aa89728 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "storebound", "version": "0.6.0", "desktopVersion": "0.1.0", - "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every store questionnaire (incl. account deletion and App Review demo accounts), privacy, icons, screenshots and previews, TestFlight. After launch: crash vitals, review replies, price sheets, analytics and Product Page Optimization. One config, a Bun CLI, an MCP server and Agent Skills for your AI coding agent.", + "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every store questionnaire (incl. account deletion and App Review demo accounts), privacy, icons, screenshots and previews, TestFlight. After launch: crash vitals, review replies, price sheets, analytics and Product Page Optimization. One config, a CLI (Node 20+ or Bun), an MCP server and Agent Skills for your AI coding agent.", "license": "MIT", "author": "CRTkafa", "type": "module", @@ -26,12 +26,13 @@ "android-vitals" ], "bin": { - "storebound": "cli/index.ts" + "storebound": "dist/storebound.mjs" }, "exports": { ".": "./cli/lib/config.ts" }, "files": [ + "dist/storebound.mjs", "cli", "templates", "skills", @@ -51,6 +52,7 @@ "apps/*" ], "engines": { + "node": ">=20", "bun": ">=1.1" }, "scripts": { @@ -58,9 +60,12 @@ "typecheck": "tsc --noEmit", "adapters": "bun tools/gen-adapters.ts", "scrub": "bun tools/scrub.ts", + "build:node": "bun tools/build-node.ts", + "smoke:node": "bun tools/node-smoke.ts", "ci": "bun tools/ci.ts", "ci:quick": "bun tools/ci.ts --quick", - "check": "bun run typecheck && bun test" + "check": "bun run typecheck && bun test", + "prepack": "bun tools/build-node.ts" }, "peerDependencies": { "playwright": ">=1.45" diff --git a/site/index.html b/site/index.html index ac1f2fe..200c213 100644 --- a/site/index.html +++ b/site/index.html @@ -65,10 +65,10 @@
- bunx storebound init - + npx storebound init +
-

Needs Bun (opens in a new tab) 1.1+. No account, no sign-up, no telemetry.

+

Needs Node 20+ (or Bun (opens in a new tab) 1.1+: bunx storebound init). No account, no sign-up, no telemetry.

@@ -556,47 +556,47 @@ Rejection risks: 2 blocker, 3 high, 0 medium, 0 low
/plugin marketplace add https://git.crt.fyi/CRT/storebound.git
 /plugin install storebound@crtkafa
-

Skills, MCP server and storebound on PATH. Or per project: bunx storebound install --agent claude

+

Skills, MCP server and storebound on PATH. Or per project: npx storebound install --agent claude

OpenAI Codex

-
bunx storebound install --agent codex
+
npx storebound install --agent codex

Skills in .agents/skills, MCP in .codex/config.toml (Codex reads it for trusted projects). The repo is also a Codex plugin, skills only.

Gemini CLI

gemini extensions install https://git.crt.fyi/CRT/storebound.git
-

Skills, MCP server and GEMINI.md. Or bunx storebound install --agent gemini

+

Skills, MCP server and GEMINI.md. Or npx storebound install --agent gemini

Cursor

-
bunx storebound install --agent cursor
+
npx storebound install --agent cursor

Skills in .agents/skills and .cursor/mcp.json. The repo's .cursor/rules/storebound.mdc is an agent-requested rule you can copy.

VS Code / GitHub Copilot

-
bunx storebound install --agent copilot
+
npx storebound install --agent copilot

Skills in .github/skills and .vscode/mcp.json.

Windsurf (Devin Desktop)

-
bunx storebound install --agent windsurf
+
npx storebound install --agent windsurf

Skills in .agents/skills, MCP in your global mcp_config.json.

OpenCode

-
bunx storebound install --agent opencode
+
npx storebound install --agent opencode

Skills in .opencode/skills and opencode.json.

Anything that reads AGENTS.md

-

Copy or link AGENTS.md (rules and skill index) into your project and use the CLI. For an MCP client install does not know: command bunx with args ["storebound", "mcp"].

+

Copy or link AGENTS.md (rules and skill index) into your project and use the CLI. For an MCP client install does not know: command npx (or bunx) with args ["storebound", "mcp"].

install takes several agents at once (--agent codex,cursor), --dry to preview, and only touches its own storebound entry and skill folders. Re-running it is safe.

@@ -702,8 +702,8 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99
-
Terminal
-
$ bunx storebound init --profile app
+
Terminal
+
$ npx storebound init --profile app
Profiles @@ -714,7 +714,7 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99
  • store/listing text, questionnaire answers, screenshot captions
  • .storebound/your keys, gitignored
  • -

    Needs Bun (opens in a new tab) 1.1+, plus ffmpeg for icons, screenshots and video.

    +

    Needs Node 20+ (or Bun (opens in a new tab) 1.1+ with bunx), plus ffmpeg for icons, screenshots and video.

    @@ -738,8 +738,8 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99 };
    -
    Terminal
    -
    $ bunx storebound check
    +
    Terminal
    +
    $ npx storebound check

    Store text lives next to it in store/listing.md and store/appstore.md, one section per language.

    @@ -773,8 +773,8 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99
    -
    Terminal
    -
    $ bunx storebound install --agent claude
    +
    Terminal
    +
    $ npx storebound install --agent claude

    Or codex, cursor, copilot, gemini, windsurf, opencode (per tool). No agent? Every step is a CLI command too.

      @@ -793,8 +793,8 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99
    -
    Terminal
    -
    $ bunx storebound validate
    +
    Terminal
    +
    $ npx storebound validate
    1. Blockers, per store, in fix orderbuild, encryption, metadata, screenshots, review details, prices, products
    2. @@ -840,7 +840,7 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99

      Do I need an AI agent?

      -

      No. Every step is a CLI command too (bunx storebound --help), and the skills are Markdown checklists you can read yourself for the console-only forms. The agent integrations are optional.

      +

      No. Every step is a CLI command too (npx storebound --help), and the skills are Markdown checklists you can read yourself for the console-only forms. The agent integrations are optional.

      Will it press Submit for me?

      @@ -868,7 +868,7 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99

      Does it run on Windows or Linux?

      -

      It runs wherever Bun 1.1+ runs; it does not run on plain Node. The store commands are API calls. Capturing from the iOS Simulator needs a Mac with Xcode; a web build, an Android emulator or a folder of frames works anywhere. Icons, screenshots and video also need ffmpeg.

      +

      It runs on Node 20+ (npx storebound) and on Bun 1.1+ (bunx storebound). The store commands are API calls. Capturing from the iOS Simulator needs a Mac with Xcode; a web build, an Android emulator or a folder of frames works anywhere. Icons, screenshots and video also need ffmpeg.

    diff --git a/tests/do-it-dont-ask.test.ts b/tests/do-it-dont-ask.test.ts index b76b4e1..4b37950 100644 --- a/tests/do-it-dont-ask.test.ts +++ b/tests/do-it-dont-ask.test.ts @@ -78,7 +78,9 @@ describe("first rule: do it, don't ask", () => { /\bto check yourself\b/i, ]; const cliFiles = (dir: string): string[] => readdirSync(join(root, dir), { withFileTypes: true }).flatMap(e => - e.isDirectory() ? (e.name === 'data' ? [] : cliFiles(`${dir}/${e.name}`)) : e.name.endsWith('.ts') ? [`${dir}/${e.name}`] : []); + e.isDirectory() ? (e.name === 'data' ? [] : cliFiles(`${dir}/${e.name}`)) + // lib/embedded.ts is generated from templates/ (the owner's own files, not CLI output), like data/ + : e.name.endsWith('.ts') && `${dir}/${e.name}` !== 'cli/lib/embedded.ts' ? [`${dir}/${e.name}`] : []); test('CLI output never hands agent work to the owner', () => { const hits: string[] = []; diff --git a/tests/node-compat.test.ts b/tests/node-compat.test.ts new file mode 100644 index 0000000..3474594 --- /dev/null +++ b/tests/node-compat.test.ts @@ -0,0 +1,153 @@ +// The CLI runs on Node 20+ (npm bundle) and Bun: cli/ reaches the runtime only through cli/lib/rt.ts, and rt behaves +// like the Bun APIs it replaced (spawn defaults and exit codes, which, Glob, type stripping of a config). +import { describe, expect, test } from 'bun:test'; +import { chmodSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, relative } from 'node:path'; +import * as rt from '../cli/lib/rt'; + +const ROOT = join(import.meta.dir, '..'); + +function cliSources(dir = join(ROOT, 'cli')): string[] { + return readdirSync(dir, { withFileTypes: true }).flatMap(e => { + const p = join(dir, e.name); + if (e.isDirectory()) return cliSources(p); + return e.name.endsWith('.ts') && !e.name.endsWith('.test.ts') ? [p] : []; + }); +} + +describe('cli/ is runtime neutral', () => { + test('no Bun global, bun: module, import.meta.dir or text import outside cli/lib/rt.ts', () => { + const bad: string[] = []; + for (const f of cliSources()) { + const rel = relative(ROOT, f); + if (rel === join('cli', 'lib', 'rt.ts')) continue; + readFileSync(f, 'utf8').split('\n').forEach((line, i) => { + if (/\bBun\.[A-Za-z]|['"]bun:[a-z]|import\.meta\.(dir|dirname|path|file|filename|main)\b|with \{ type: 'text' \}/.test(line)) bad.push(`${rel}:${i + 1}: ${line.trim().slice(0, 120)}`); + }); + } + expect(bad).toEqual([]); + }); + + test('package.json: bin is the Node bundle, engines name Node 20, npm pack builds it', () => { + const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')) as { bin: Record; engines: Record; files: string[]; scripts: Record }; + expect(pkg.bin.storebound).toBe('dist/storebound.mjs'); + expect(pkg.engines.node).toBe('>=20'); + expect(pkg.files).toContain('dist/storebound.mjs'); + expect(pkg.scripts.prepack).toContain('tools/build-node.ts'); + }); +}); + +describe('rt: processes', () => { + test('spawn: Bun defaults, exit code, 128 + signal, streams', async () => { + const p = rt.spawn(['sh', '-c', 'echo out; echo err >&2; exit 3'], { stderr: 'pipe' }); + const [out, err, code] = await Promise.all([new Response(p.stdout).text(), new Response(p.stderr).text(), p.exited]); + expect([out, err, code, p.exitCode]).toEqual(['out\n', 'err\n', 3, 3]); + const s = rt.spawn(['sleep', '10']); + s.kill(); + expect(await s.exited).toBe(143); + expect(s.signalCode).toBe('SIGTERM'); + const i = rt.spawn(['sleep', '10']); + i.kill('SIGINT'); + expect(await i.exited).toBe(130); + }); + + test('a missing executable throws right away, like Bun', () => { + expect(() => rt.spawn(['storebound-no-such-tool'])).toThrow('Executable not found in $PATH: "storebound-no-such-tool"'); + expect(() => rt.spawnSync(['storebound-no-such-tool'])).toThrow('Executable not found in $PATH'); + }); + + test('spawnSync: stdin bytes, env, exit code, timeout without a throw', () => { + const r = rt.spawnSync(['sh', '-c', 'tr a-z A-Z; echo "$X" >&2; exit 2'], { stdin: new TextEncoder().encode('hi'), env: { ...process.env, X: 'x1' }, stdout: 'pipe', stderr: 'pipe' }); + expect([r.stdout.toString(), r.stderr.toString(), r.exitCode, r.success]).toEqual(['HI', 'x1\n', 2, false]); + const t = rt.spawnSync(['sleep', '5'], { timeout: 100 }); + expect([t.exitCode, t.signalCode, t.success]).toEqual([null, 'SIGTERM', false]); + }); + + test('which: PATH lookup with an explicit PATH, executables only', () => { + const dir = mkdtempSync(join(tmpdir(), 'sb-which-')); + try { + writeFileSync(join(dir, 'tool-x'), '#!/bin/sh\n'); + chmodSync(join(dir, 'tool-x'), 0o755); + writeFileSync(join(dir, 'plain-y'), ''); + expect(rt.which('tool-x', { PATH: dir })).toBe(join(dir, 'tool-x')); + expect(rt.which('plain-y', { PATH: dir })).toBeNull(); + expect(rt.which('tool-x', { PATH: '' })).toBeNull(); + expect(rt.which('sh')).toBe(Bun.which('sh')); + } finally { rmSync(dir, { recursive: true, force: true }); } + }); + + test('hasher and gunzip match Bun', () => { + expect(rt.hasher('sha256').update('a').update('b').digest('hex')).toBe(new Bun.CryptoHasher('sha256').update('ab').digest('hex')); + expect(new TextDecoder().decode(rt.gunzipSync(Bun.gzipSync(new TextEncoder().encode('zip'))))).toBe('zip'); + }); +}); + +describe('rt: Glob like Bun.Glob', () => { + const cases: [string, string][] = [ + ['apps/*', 'apps/a'], ['apps/*', 'apps/a/b'], ['apps/**', 'apps/a/b'], ['**/game', 'game'], ['**/game', 'x/y/game'], + ['studio-*/app?', 'studio-1/app2'], ['studio-*/app?', 'studio-1/app22'], ['{a,b}/c', 'b/c'], ['{a,b}/c', 'd/c'], + ['[ab]x', 'bx'], ['[!ab]x', 'cx'], ['[!ab]x', 'ax'], ['*.ts', 'a.ts'], ['*.ts', 'dir/a.ts'], ['a/**/z', 'a/z'], ['a/**/z', 'a/b/c/z'], + ]; + for (const [g, p] of cases) test(`${g} vs ${p}`, () => expect(new rt.Glob(g).match(p)).toBe(new Bun.Glob(g).match(p))); + + test('scanSync finds the same folders, no dot entries', () => { + const dir = mkdtempSync(join(tmpdir(), 'sb-glob-')); + try { + for (const d of ['apps/one', 'apps/two/deep', 'apps/.hidden', 'other/three']) mkdirSync(join(dir, d), { recursive: true }); + writeFileSync(join(dir, 'apps/file.txt'), ''); + for (const g of ['apps/*', 'apps/**', '*/*', 'apps/t*']) { + const mine = [...new rt.Glob(g).scanSync({ cwd: dir, onlyFiles: false, dot: false })].sort(); + const bun = [...new Bun.Glob(g).scanSync({ cwd: dir, onlyFiles: false, dot: false })].sort(); + expect(mine, g).toEqual(bun); + } + } finally { rmSync(dir, { recursive: true, force: true }); } + }); +}); + +describe('rt: configs on Node without type stripping', () => { + test('stripTypes turns every shipped config into JavaScript with the same value', async () => { + const files = ['templates/storebound.config.ts', ...readdirSync(join(ROOT, 'templates/profiles')).map(p => `templates/profiles/${p}/storebound.config.ts`), 'examples/demo-game/storebound.config.ts', 'examples/demo-app/storebound.config.ts'] + .filter(f => { try { readFileSync(join(ROOT, f)); return true; } catch { return false; } }); + expect(files.length).toBeGreaterThan(3); + for (const f of files) { + const js = rt.stripTypes(readFileSync(join(ROOT, f), 'utf8')); + const viaJs = (await import(`data:text/javascript;base64,${Buffer.from(js).toString('base64')}`)).default; + const viaTs = (await import(join(ROOT, f))).default; + expect(JSON.stringify(viaJs), f).toBe(JSON.stringify(viaTs)); + } + }); + + test('stripTypes keeps strings and comments that look like types', () => { + const js = rt.stripTypes("import type { X } from 'x';\n// keep: a as B\nconst c: X = { s: 'a satisfies B', n: 1 as number } satisfies X;\nexport default c as X;\n"); + expect(js).toContain("// keep: a as B"); + expect(js).toContain("'a satisfies B'"); + expect(js).not.toContain('import type'); + expect(js).toContain('const c = {'); + expect(js).toContain('n: 1 }'); + expect(js).toContain('export default c;'); + }); +}); + +describe('rt: serve', () => { + test('fetch handler, file responses, 404', async () => { + const dir = mkdtempSync(join(tmpdir(), 'sb-serve-')); + writeFileSync(join(dir, 'a.txt'), 'file body'); + const s = await rt.serve({ port: 0, hostname: '127.0.0.1', fetch(req) { + const u = new URL(req.url); + if (u.pathname === '/json') return Response.json({ q: u.searchParams.get('q') }); + return rt.fileResponse(join(dir, u.pathname.slice(1)), { 'content-type': 'text/plain' }) ?? new Response('no', { status: 404 }); + } }); + try { + const base = `http://127.0.0.1:${s.port}`; + expect(await (await fetch(`${base}/json?q=1`)).json()).toEqual({ q: '1' }); + const f = await fetch(`${base}/a.txt`); + expect([f.status, f.headers.get('content-type'), await f.text()]).toEqual([200, 'text/plain', 'file body']); + expect((await fetch(`${base}/missing`)).status).toBe(404); + } finally { s.stop(true); rmSync(dir, { recursive: true, force: true }); } + }); + + test('packageRoot is the storebound folder', () => { + expect(rt.packageRoot()).toBe(ROOT); + }); +}); diff --git a/tests/wave1.test.ts b/tests/wave1.test.ts index e3946a2..7926d51 100644 --- a/tests/wave1.test.ts +++ b/tests/wave1.test.ts @@ -432,9 +432,12 @@ describe('install', () => { test('mcp command choice', () => { const dir = tmp(); expect(mcpCommand(dir).command).toMatch(/bin\/storebound$/); + // run with Node (npx storebound install): the toolchain sits in npx's cache, so the pinned package instead + expect(mcpCommand(dir, undefined, 'node', '1.2.3')).toEqual({ command: 'npx', args: ['-y', 'storebound@1.2.3', 'mcp'] }); mkdirSync(join(dir, 'node_modules/storebound'), { recursive: true }); writeFileSync(join(dir, 'node_modules/storebound/package.json'), '{}'); expect(mcpCommand(dir)).toEqual({ command: 'bunx', args: ['storebound', 'mcp'] }); + expect(mcpCommand(dir, undefined, 'node')).toEqual({ command: 'npx', args: ['storebound', 'mcp'] }); expect(mcpCommand(dir, 'npx -y storebound')).toEqual({ command: 'npx', args: ['-y', 'storebound', 'mcp'] }); }); }); diff --git a/tools/build-node.ts b/tools/build-node.ts new file mode 100644 index 0000000..78be7e8 --- /dev/null +++ b/tools/build-node.ts @@ -0,0 +1,43 @@ +#!/usr/bin/env bun +// Builds the npm package's CLI: one Node ESM bundle, dist/storebound.mjs (package.json "bin"), so `npx storebound` +// runs on Node 20+ and `bunx storebound` keeps working. Zero runtime dependencies: playwright stays an optional peer +// resolved from the project at run time (playwright-core only in the compiled sidecar), so it is left out. +// bun tools/build-node.ts write dist/storebound.mjs (npm pack / publish run it: prepack) +// bun tools/build-node.ts --out write elsewhere (tests) +// The bundle is checked for this machine's home and checkout paths (like the sidecar): nothing private may ship. +import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join, relative, resolve } from 'node:path'; +import { privateStrings, ROOT } from './build-sidecars'; + +/** Packages the CLI only imports at run time from the project (or never under Node). */ +export const NODE_EXTERNALS = ['playwright', 'playwright-core', 'electron', 'chromium-bidi']; +export const NODE_BUNDLE = join(ROOT, 'dist', 'storebound.mjs'); + +export function buildNode(out = NODE_BUNDLE): { path: string; bytes: number } { + const dir = mkdtempSync(join(tmpdir(), 'sb-node-')); + try { + const tmp = join(dir, 'storebound.mjs'); + const p = Bun.spawnSync(['bun', 'build', 'cli/index.ts', '--target=node', '--format=esm', ...NODE_EXTERNALS.flatMap(e => ['--external', e]), '--outfile', tmp], + { cwd: ROOT, stdout: 'pipe', stderr: 'pipe' }); + if (p.exitCode !== 0) throw new Error(`bun build failed\n${p.stderr.toString()}${p.stdout.toString()}`); + // the entry's own `#!/usr/bin/env bun` and Bun's `// @bun` marker go; node runs this file + const body = readFileSync(tmp, 'utf8').replace(/^#!.*\n/, '').replace(/^\/\/ @bun[^\n]*\n/, ''); + const js = `#!/usr/bin/env node\n${body}`; + const bunGlobal = /\bBun\.[a-zA-Z]/.exec(js); + if (bunGlobal) throw new Error(`the Node bundle uses the Bun global (${bunGlobal[0]}...): go through cli/lib/rt.ts`); + const leaked = privateStrings().filter(s => js.includes(s)); + if (leaked.length) throw new Error(`the Node bundle contains private paths (${leaked.length}); refusing to write it`); + mkdirSync(dirname(out), { recursive: true }); + writeFileSync(out, js); + chmodSync(out, 0o755); + return { path: out, bytes: statSync(out).size }; + } finally { rmSync(dir, { recursive: true, force: true }); } +} + +if (import.meta.main) { + const i = process.argv.indexOf('--out'); + const t = Date.now(); + const r = buildNode(i >= 0 ? resolve(process.argv[i + 1]!) : NODE_BUNDLE); + console.log(`node bundle -> ${relative(process.cwd(), r.path) || r.path} (${(r.bytes / 1e6).toFixed(2)} MB, ${((Date.now() - t) / 1000).toFixed(1)}s)`); +} diff --git a/tools/ci.ts b/tools/ci.ts index 9d986e1..2a351c9 100644 --- a/tools/ci.ts +++ b/tools/ci.ts @@ -33,6 +33,14 @@ const add = (t: Task) => tasks.push(t); const tsc = (name: string, project: string) => add({ name, cmd: ['bunx', 'tsc', '--noEmit', '--incremental', '-p', project, '--tsBuildInfoFile', join(cache, `${name.replace(/\W+/g, '-')}.tsbuildinfo`)] }); const junitDir = mkdtempSync(join(tmpdir(), 'sb-ci-')); +const node = Bun.which('node'); + +/** The npm bundle (dist/storebound.mjs, Node 20+) and its smoke test with the real `node` (tools/node-smoke.ts). */ +function addNode() { + add({ name: 'node bundle', cmd: ['bun', 'tools/build-node.ts'] }); + if (node) add({ name: 'node smoke', cmd: ['bun', 'tools/node-smoke.ts', '--node', node], after: ['node bundle'] }); + else console.log('ci: no `node` on PATH: the Node smoke test of the npm bundle is skipped'); +} function addTests(files: string[]) { planShards(files).forEach((shard, i, all) => { @@ -53,7 +61,9 @@ if (quick) { console.log(`ci:quick: ${changed === null ? 'no origin/main to diff against: every test' : `${changed.length} changed file(s), ${related.length} related test file(s)`}`); if (far.length) console.log(`ci:quick: left out ${far.length} browser suite(s) reached only through shared modules (bun run ci runs them): ${far.join(' ')}`); add({ name: 'adapters', cmd: ['bun', 'tools/gen-adapters.ts', '--check'] }); + add({ name: 'embedded', cmd: ['bun', 'tools/gen-embedded.ts', '--check'] }); tsc('typecheck', '.'); + if (touches(/^(cli\/|templates\/|tools\/(build-node|node-smoke|gen-embedded)\.ts$|package\.json$)/)) addNode(); if (touches(/^apps\/desktop\//)) tsc('desktop typecheck', 'apps/desktop'); if (related.length) addTests(related); add({ name: 'scrub', cmd: ['bun', 'tools/scrub.ts'] }); @@ -63,6 +73,9 @@ if (quick) { if (!cargo) console.log('ci: cargo not found (or STOREBOUND_CI_RUST=0): desktop Rust tests and clippy skipped'); else if (!rust) console.log('ci: apps/desktop/src-tauri and the sidecar build unchanged against origin/main: desktop Rust tests and clippy skipped (CI=full runs them)'); add({ name: 'adapters', cmd: ['bun', 'tools/gen-adapters.ts', '--check'] }); + // templates/, cli/shots/*.html and Play's form export, embedded in cli/lib/embedded.ts (every runtime and build) + add({ name: 'embedded', cmd: ['bun', 'tools/gen-embedded.ts', '--check'] }); + addNode(); // the App Store Connect / Google Play operation tables, the wrapped-by scan and docs/api-coverage.md match specs/ and cli/ add({ name: 'api tables', cmd: ['bun', 'tools/gen-api.ts', '--check'] }); // one desktop version: package.json desktopVersion = apps/desktop/package.json = tauri.conf.json = Cargo.toml / .lock diff --git a/tools/gen-adapters.ts b/tools/gen-adapters.ts index b07dd4a..875b692 100644 --- a/tools/gen-adapters.ts +++ b/tools/gen-adapters.ts @@ -71,7 +71,7 @@ export function generate(root: string): Map { const cli = [ '## Tools', '', - '- CLI: `storebound ` (`storebound help`); config `storebound.config.ts`, credentials in env or `.storebound/`.', + '- CLI: `storebound ` (`storebound help`; outside the plugin: `npx storebound` on Node 20+ or `bunx storebound` on Bun); config `storebound.config.ts`, credentials in env or `.storebound/`.', '- MCP: `storebound mcp` (stdio). MCP 2026-07-28 + 2025-11-25. Write tools default to `dry_run: true`; submit / release / cancel / delete / send are not exposed.', '- Readiness: `storebound scan` (offline rejection risks in the project files), `storebound check` (offline), `storebound validate` (read-only, both stores).', '- Every endpoint: `storebound api apple|play ` (skill store-api). GETs run; writes are dry unless the owner adds `--yes` in a terminal; MCP has the read-only `api_get`.', diff --git a/tools/gen-embedded.ts b/tools/gen-embedded.ts new file mode 100644 index 0000000..029888e --- /dev/null +++ b/tools/gen-embedded.ts @@ -0,0 +1,60 @@ +#!/usr/bin/env bun +// Writes cli/lib/embedded.ts: the text files the CLI ships inside its code, so they work on every runtime and in every +// build without a files folder next to it: the Node bundle (dist/storebound.mjs), the compiled desktop sidecar +// (`bun build --compile`) and the sources. Bun's `import ... with { type: 'text' }` did this before, but Node cannot +// run that. Embedded: every file under templates/ (storebound init, web init), the shots pages (cli/shots/*.html, +// served by shots render / video) and Play's Data safety form export (cli/data/play-data-safety-form.csv). +// bun tools/gen-embedded.ts write cli/lib/embedded.ts +// bun tools/gen-embedded.ts --check exit 1 when the file is stale (bun run ci) +import { readdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { join, relative, resolve } from 'node:path'; + +const ROOT = resolve(import.meta.dir, '..'); +export const OUT = join(ROOT, 'cli/lib/embedded.ts'); + +function walk(dir: string): string[] { + return readdirSync(dir, { withFileTypes: true }).flatMap(e => { + if (e.name === '.DS_Store') return []; + const p = join(dir, e.name); + return e.isDirectory() ? walk(p) : [p]; + }).sort(); +} + +const rel = (base: string, p: string) => relative(base, p).split('\\').join('/'); +const table = (entries: [string, string][]): string => entries.map(([k, v]) => ` ${JSON.stringify(k)}: ${JSON.stringify(v)},`).join('\n'); + +export function generate(root = ROOT): string { + const templates = walk(join(root, 'templates')).map(p => [rel(join(root, 'templates'), p), readFileSync(p, 'utf8')] as [string, string]); + const shots = readdirSync(join(root, 'cli/shots')).filter(f => f.endsWith('.html')).sort().map(f => [f, readFileSync(join(root, 'cli/shots', f), 'utf8')] as [string, string]); + const form = readFileSync(join(root, 'cli/data/play-data-safety-form.csv'), 'utf8'); + return [ + '// GENERATED by tools/gen-embedded.ts from templates/, cli/shots/*.html and cli/data/play-data-safety-form.csv.', + '// Do not edit: change those files and run `bun tools/gen-embedded.ts` (bun run ci checks it is fresh).', + '/* eslint-disable */', + '', + '/** Every file under templates/, by its path there (forward slashes). */', + 'export const TEMPLATE_TEXT: Record = {', + table(templates), + '};', + '', + '/** The pages shots render / video load (cli/shots/*.html), by file name. */', + 'export const SHOTS_PAGES: Record = {', + table(shots), + '};', + '', + "/** Play Console's Data safety form export (cli/data/play-data-safety-form.csv). */", + `export const PLAY_DATA_SAFETY_FORM = ${JSON.stringify(form)};`, + '', + ].join('\n'); +} + +if (import.meta.main) { + const want = generate(); + let have = ''; + try { have = readFileSync(OUT, 'utf8'); } catch { /* missing */ } + if (process.argv.includes('--check')) { + if (have !== want) { console.error(`stale: ${rel(ROOT, OUT)} (run bun tools/gen-embedded.ts)`); process.exit(1); } + console.log(`${rel(ROOT, OUT)}: fresh`); + } else if (have !== want) { writeFileSync(OUT, want); console.log(`wrote ${rel(ROOT, OUT)}`); } + else console.log(`${rel(ROOT, OUT)}: unchanged`); +} diff --git a/tools/gen-flags.ts b/tools/gen-flags.ts index c32c12e..35034bf 100644 --- a/tools/gen-flags.ts +++ b/tools/gen-flags.ts @@ -49,7 +49,8 @@ function scan(entry: string, errors: string[]): Set { if (seen.has(f)) continue; seen.add(f); const rel = relative(CLI, f); - if (rel === 'index.ts' || rel === 'commands.ts') continue; + // lib/embedded.ts holds file texts (templates, shots pages: their own scripts read URL params), not flag reads + if (rel === 'index.ts' || rel === 'commands.ts' || rel === join('lib', 'embedded.ts')) continue; const src = readFileSync(f, 'utf8'); for (const m of src.matchAll(READ)) if (!NOT_ARGS.test(m[1]!)) flags.add(m[3]!); for (const fl of DYNAMIC[rel] ?? []) flags.add(fl); diff --git a/tools/node-smoke.ts b/tools/node-smoke.ts new file mode 100644 index 0000000..9aeb6b4 --- /dev/null +++ b/tools/node-smoke.ts @@ -0,0 +1,124 @@ +#!/usr/bin/env bun +// Smoke test of the npm bundle (dist/storebound.mjs) with the real `node` binary: the CLI must run on Node 20+. +// bun tools/node-smoke.ts [--bundle dist/storebound.mjs] [--node ] +// Offline only: --version, help, check on examples/demo-game (same exit code and problems as under Bun), scan on the +// teaser fixture, release plan --help, init + check in an empty folder (the TypeScript config the template writes), +// and an MCP initialize handshake over stdio with tools/list and one tools/call (a child run through the bundle). +// Every step has a timeout; on a failure the step's output is printed. +import { mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; +import { ROOT } from './build-sidecars'; +import { NODE_BUNDLE } from './build-node'; + +const arg = (n: string) => { const i = process.argv.indexOf(n); return i >= 0 ? process.argv[i + 1] : undefined; }; +const bundle = resolve(arg('--bundle') ?? NODE_BUNDLE); +const node = arg('--node') ?? Bun.which('node'); +if (!node) { console.error('node-smoke: no `node` on PATH'); process.exit(1); } +const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')) as { version: string; engines?: { node?: string } }; +const env: Record = { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0', STOREBOUND_NO_UPDATE_CHECK: '1' }; + +interface Out { code: number | null; out: string; err: string } +function run(cmd: string[], cwd = ROOT, stdin?: string, timeout = 60_000): Out { + const r = Bun.spawnSync(cmd, { cwd, env, stdout: 'pipe', stderr: 'pipe', timeout, ...(stdin !== undefined ? { stdin: new TextEncoder().encode(stdin) } : {}) }); + return { code: r.exitCode, out: r.stdout.toString(), err: r.stderr.toString() }; +} +const sb = (args: string[], cwd = ROOT, stdin?: string) => run([node, bundle, ...args], cwd, stdin); + +let failed = 0; +function step(name: string, f: () => string | void): void { + const t = Date.now(); + try { + const note = f(); + console.log(`ok ${name}${note ? `: ${note}` : ''} (${Date.now() - t} ms)`); + } catch (e) { + failed++; + console.log(`FAIL ${name}: ${(e as Error).message}`); + } +} +function expectOk(r: Out, what: string, code = 0): void { + if (r.code !== code) throw new Error(`${what}: exit ${r.code} (want ${code})\n--- stdout\n${r.out.slice(-2000)}\n--- stderr\n${r.err.slice(-2000)}`); +} +/** Output without timestamps and absolute paths, to compare a Node and a Bun run. */ +const norm = (s: string) => s.replace(/^\d\d:\d\d:\d\d /gm, '').replace(/\/\S*\/storebound\.config\.ts/g, '').trim(); + +const nodeVersion = run([node, '--version']).out.trim(); +console.log(`node-smoke: ${bundle.replace(ROOT + '/', '')} with node ${nodeVersion} (package engines: node ${pkg.engines?.node ?? '?'})`); + +step('--version', () => { + const r = sb(['--version']); + expectOk(r, '--version'); + if (r.out.trim() !== pkg.version) throw new Error(`printed ${JSON.stringify(r.out.trim())}, package.json says ${pkg.version}`); + return r.out.trim(); +}); + +step('help', () => { + const r = sb(['help']); + expectOk(r, 'help'); + if (!r.out.includes('usage: storebound ')) throw new Error('no usage line'); + return `${r.out.split('\n').length} lines`; +}); + +step('check (examples/demo-game, same result as Bun)', () => { + const cwd = join(ROOT, 'examples/demo-game'); + const n = sb(['check'], cwd); + const b = run(['bun', join(ROOT, 'cli/index.ts'), 'check'], cwd); + if (n.code !== b.code) throw new Error(`node exit ${n.code}, bun exit ${b.code}\n${n.out}\n${n.err}`); + if (norm(n.out + n.err) !== norm(b.out + b.err)) throw new Error(`output differs from Bun's\n--- node\n${norm(n.out + n.err)}\n--- bun\n${norm(b.out + b.err)}`); + return `exit ${n.code}, ${(n.out + n.err).match(/error: .*/)?.[0] ?? 'no problems'}`; +}); + +step('scan (tests/fixtures/scan/teaser)', () => { + const cwd = join(ROOT, 'tests/fixtures/scan/teaser'); + const r = sb(['scan', '--json'], cwd); + const b = run(['bun', join(ROOT, 'cli/index.ts'), 'scan', '--json'], cwd); + if (r.code !== b.code) throw new Error(`node exit ${r.code}, bun exit ${b.code}\n${r.err}`); + const j = JSON.parse(r.out) as { findings?: unknown[] }; + if (JSON.stringify(j) !== JSON.stringify(JSON.parse(b.out))) throw new Error('scan --json differs from Bun\'s'); + return `exit ${r.code}, ${Array.isArray(j.findings) ? `${j.findings.length} findings` : 'json ok'}`; +}); + +step('release plan --help', () => { + const r = sb(['release', 'plan', '--help']); + expectOk(r, 'release plan --help'); + if (!/storebound release/.test(r.out + r.err)) throw new Error('no usage'); +}); + +step('init + inspect in an empty folder (TypeScript config)', () => { + const dir = mkdtempSync(join(tmpdir(), 'sb-node-init-')); + try { + const i = sb(['init'], dir); + expectOk(i, 'init'); + if (!readFileSync(join(dir, 'storebound.config.ts'), 'utf8').includes('ShipConfig')) throw new Error('no storebound.config.ts written'); + const c = sb(['inspect', '--json'], dir); + expectOk(c, 'inspect --json after init'); + return `config loads: ${(JSON.parse(c.out) as { name?: string }).name}`; + } finally { rmSync(dir, { recursive: true, force: true }); } +}); + +step('mcp: initialize, tools/list, tools/call over stdio', () => { + const lines = [ + { jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2025-11-25', capabilities: {}, clientInfo: { name: 'node-smoke', version: '1' } } }, + { jsonrpc: '2.0', method: 'notifications/initialized' }, + { jsonrpc: '2.0', id: 2, method: 'tools/list' }, + { jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: 'check', arguments: {} } }, + ].map(m => JSON.stringify(m)).join('\n') + '\n'; + const prev = env.STOREBOUND_PROJECT; + env.STOREBOUND_PROJECT = join(ROOT, 'examples/demo-game'); + let r: Out; + try { r = sb(['mcp'], ROOT, lines); } finally { if (prev === undefined) delete env.STOREBOUND_PROJECT; else env.STOREBOUND_PROJECT = prev; } + expectOk(r, 'mcp'); + const res = new Map(r.out.trim().split('\n').filter(Boolean).map(l => JSON.parse(l) as { id: number; result?: Record; error?: unknown }).map(m => [m.id, m])); + const init = res.get(1)?.result as { protocolVersion?: string; serverInfo?: { name: string; version: string } } | undefined; + if (init?.serverInfo?.name !== 'storebound' || init.serverInfo.version !== pkg.version) throw new Error(`initialize: ${JSON.stringify(res.get(1))}`); + const tools = (res.get(2)?.result as { tools?: unknown[] } | undefined)?.tools ?? []; + if (tools.length < 10) throw new Error(`tools/list: ${tools.length} tools`); + const call = res.get(3)?.result as { isError?: boolean; content?: { text: string }[] } | undefined; + // demo-game's check reports 2 problems (exit 1): what counts is that the child ran through the bundle and loaded the config + const text = call?.content?.[0]?.text ?? ''; + if (!/config ok/.test(text) || !/\[exit [01]\]/.test(text)) throw new Error(`tools/call check: ${JSON.stringify(res.get(3)).slice(0, 1500)}`); + return `protocol ${init.protocolVersion}, ${tools.length} tools, check ran as a child of the bundle (${text.match(/\[exit \d+\]/)![0]})`; +}); + +if (failed) { console.error(`node-smoke: ${failed} step(s) failed`); process.exit(1); } +console.log('node-smoke: all steps passed');