@absolutejs/absolute 0.20.0-beta.24 → 0.20.0-beta.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -589,6 +589,263 @@ var init_syncSchema = __esm(() => {
589
589
  init_client();
590
590
  });
591
591
 
592
+ // src/mobile/deviceCapabilities.ts
593
+ import { readFileSync as readFileSync4 } from "fs";
594
+ import { extname as extname3, join as join13, relative as relative9, resolve as resolve11 } from "path";
595
+ import ts from "typescript";
596
+ var DEVICES_PACKAGE = "@absolutejs/devices", CAPACITOR_ADAPTER = "@absolutejs/devices-capacitor", SOURCE_GLOB, IGNORED_DIRECTORIES, IDENTIFIER_PATTERN, CAPACITOR_MODULE_PATTERN, CAPACITOR_PACKAGE_PATTERN, ANDROID_PERMISSION_PATTERN, IOS_USAGE_DESCRIPTIONS, IOS_PRIVACY_ACCESSED_API_REASONS, IOS_PRIVACY_ACCESSED_APIS, object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readJson = (path) => {
597
+ const value = JSON.parse(readFileSync4(path, "utf8"));
598
+ if (!object2(value))
599
+ throw new TypeError(`${path} must contain an object.`);
600
+ return value;
601
+ }, text = (value, field) => {
602
+ if (typeof value !== "string" || value.length === 0)
603
+ throw new TypeError(`${field} must be a non-empty string.`);
604
+ return value;
605
+ }, androidPermissions = (value, field) => {
606
+ if (value === undefined)
607
+ return;
608
+ if (!object2(value))
609
+ throw new TypeError(`${field} must be an object.`);
610
+ const { permissions } = value;
611
+ if (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string" && ANDROID_PERMISSION_PATTERN.test(permission)))
612
+ throw new TypeError(`${field}.permissions must contain Android permission names.`);
613
+ return [...permissions];
614
+ }, iosPrivacyAccessedApis = (value, field) => {
615
+ if (value === undefined)
616
+ return;
617
+ if (!object2(value))
618
+ throw new TypeError(`${field} must be an object.`);
619
+ const privacy = {};
620
+ for (const api of IOS_PRIVACY_ACCESSED_APIS) {
621
+ const reasons = value[api];
622
+ if (reasons === undefined)
623
+ continue;
624
+ const supported = IOS_PRIVACY_ACCESSED_API_REASONS[api];
625
+ if (!Array.isArray(reasons) || reasons.length === 0 || !reasons.every((reason) => typeof reason === "string" && supported.has(reason)))
626
+ throw new TypeError(`${field} contains an unsupported API or reason.`);
627
+ privacy[api] = [...reasons];
628
+ }
629
+ if (Object.keys(value).some((api) => !IOS_PRIVACY_ACCESSED_APIS.some((known) => known === api)))
630
+ throw new TypeError(`${field} contains an unsupported API or reason.`);
631
+ return privacy;
632
+ }, iosNativeRequirements = (value, field) => {
633
+ if (value === undefined)
634
+ return;
635
+ if (!object2(value))
636
+ throw new TypeError(`${field} must be an object.`);
637
+ const { privacyAccessedApis, pushNotifications, usageDescriptions } = value;
638
+ if (pushNotifications !== undefined && pushNotifications !== true)
639
+ throw new TypeError(`${field}.pushNotifications must be true.`);
640
+ if (usageDescriptions !== undefined && (!Array.isArray(usageDescriptions) || !usageDescriptions.every((purpose) => typeof purpose === "string" && IOS_USAGE_DESCRIPTIONS.has(purpose))))
641
+ throw new TypeError(`${field}.usageDescriptions contains an unsupported purpose.`);
642
+ const privacy = iosPrivacyAccessedApis(privacyAccessedApis, `${field}.privacyAccessedApis`);
643
+ return {
644
+ ...privacy === undefined ? {} : { privacyAccessedApis: privacy },
645
+ ...pushNotifications === true ? { pushNotifications: true } : {},
646
+ ...usageDescriptions === undefined ? {} : { usageDescriptions: [...usageDescriptions] }
647
+ };
648
+ }, parseProvider = (name, value) => {
649
+ if (!IDENTIFIER_PATTERN.test(name))
650
+ throw new TypeError("Device capability names must be identifiers.");
651
+ if (!object2(value))
652
+ throw new TypeError(`Device capability ${name} must be an object.`);
653
+ const factory = text(value.factory, `${name}.factory`);
654
+ const module = text(value.module, `${name}.module`);
655
+ if (!IDENTIFIER_PATTERN.test(factory))
656
+ throw new TypeError(`${name}.factory must be a JavaScript identifier.`);
657
+ if (!CAPACITOR_MODULE_PATTERN.test(module))
658
+ throw new TypeError(`${name}.module must be an official devices-capacitor subpath.`);
659
+ if (!Array.isArray(value.packages) || !value.packages.every((spec) => typeof spec === "string" && CAPACITOR_PACKAGE_PATTERN.test(spec)))
660
+ throw new TypeError(`${name}.packages must contain exact official Capacitor package versions.`);
661
+ let native;
662
+ const { native: nativeMetadata } = value;
663
+ if (nativeMetadata !== undefined) {
664
+ if (!object2(nativeMetadata))
665
+ throw new TypeError(`${name}.native must be an object.`);
666
+ const { android, ios } = nativeMetadata;
667
+ const permissions = androidPermissions(android, `${name}.native.android`);
668
+ const iosRequirements = iosNativeRequirements(ios, `${name}.native.ios`);
669
+ native = {
670
+ ...permissions === undefined ? {} : { android: { permissions } },
671
+ ...iosRequirements === undefined ? {} : { ios: iosRequirements }
672
+ };
673
+ }
674
+ return {
675
+ factory,
676
+ module,
677
+ ...native === undefined ? {} : { native },
678
+ packages: [...value.packages]
679
+ };
680
+ }, absoluteDeviceNativeRequirements = (plan) => {
681
+ const privacy = plan.capabilities.reduce((requirements, name) => {
682
+ for (const api of IOS_PRIVACY_ACCESSED_APIS) {
683
+ const reasons = plan.providers[name]?.native?.ios?.privacyAccessedApis?.[api] ?? [];
684
+ if (reasons.length === 0)
685
+ continue;
686
+ const current = requirements[api] ?? new Set;
687
+ for (const reason of reasons)
688
+ current.add(reason);
689
+ requirements[api] = current;
690
+ }
691
+ return requirements;
692
+ }, {});
693
+ return {
694
+ androidPermissions: [
695
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.android?.permissions ?? []))
696
+ ].sort(),
697
+ iosPrivacyAccessedApis: IOS_PRIVACY_ACCESSED_APIS.flatMap((api) => {
698
+ const reasons = privacy[api];
699
+ return reasons ? [{ api, reasons: [...reasons].sort() }] : [];
700
+ }),
701
+ iosPushNotifications: plan.capabilities.some((name) => plan.providers[name]?.native?.ios?.pushNotifications === true),
702
+ iosUsageDescriptions: [
703
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.ios?.usageDescriptions ?? []))
704
+ ].sort()
705
+ };
706
+ }, loadAbsoluteDeviceCapabilityProviders = (projectRoot) => {
707
+ const path = join13(resolve11(projectRoot), "node_modules", CAPACITOR_ADAPTER, "package.json");
708
+ const manifest = readJson(path);
709
+ const { absolutejs } = manifest;
710
+ const devices = object2(absolutejs) ? absolutejs.devices : undefined;
711
+ if (!object2(devices) || devices.format !== 1 || devices.provider !== "capacitor" || !object2(devices.capabilities))
712
+ throw new TypeError(`${CAPACITOR_ADAPTER} does not publish supported capability metadata.`);
713
+ const entries = Object.entries(devices.capabilities).map(([name, provider]) => ({
714
+ name,
715
+ provider: parseProvider(name, provider)
716
+ }));
717
+ return Object.fromEntries(entries.sort((left, right) => left.name.localeCompare(right.name)).map(({ name, provider }) => [name, provider]));
718
+ }, isIgnored = (path) => path.split("/").some((segment) => IGNORED_DIRECTORIES.has(segment)), importedCapabilities = (source, file) => {
719
+ const names = new Set;
720
+ const namespaces = new Set;
721
+ const visit = (node) => {
722
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && !node.importClause?.isTypeOnly) {
723
+ const bindings = node.importClause?.namedBindings;
724
+ if (bindings && ts.isNamedImports(bindings)) {
725
+ for (const element of bindings.elements)
726
+ if (!element.isTypeOnly)
727
+ names.add((element.propertyName ?? element.name).text);
728
+ }
729
+ if (bindings && ts.isNamespaceImport(bindings))
730
+ namespaces.add(bindings.name.text);
731
+ }
732
+ if (ts.isExportDeclaration(node) && !node.isTypeOnly && node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && node.exportClause && ts.isNamedExports(node.exportClause)) {
733
+ for (const element of node.exportClause.elements)
734
+ if (!element.isTypeOnly)
735
+ names.add((element.propertyName ?? element.name).text);
736
+ }
737
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && namespaces.has(node.expression.text))
738
+ names.add(node.name.text);
739
+ ts.forEachChild(node, visit);
740
+ };
741
+ const extension = extname3(file).toLowerCase();
742
+ const sources = extension === ".svelte" || extension === ".vue" ? [...source.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script\s*>/giu)].map((match) => match[1]).filter((value) => value !== undefined) : [source];
743
+ for (const [index, script] of sources.entries())
744
+ visit(ts.createSourceFile(`${file}#script-${index}`, script, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX));
745
+ return names;
746
+ }, assertAbsoluteDeviceCapabilityPackages = (projectRoot, plan) => {
747
+ const missing = missingAbsoluteDeviceCapabilityPackages(plan, directAbsoluteProjectPackages(projectRoot));
748
+ const mismatched = plan.requiredPackages.filter((spec) => {
749
+ const separator = spec.lastIndexOf("@");
750
+ const packageName = spec.slice(0, separator);
751
+ if (missing.includes(spec))
752
+ return false;
753
+ try {
754
+ return readJson(join13(resolve11(projectRoot), "node_modules", packageName, "package.json")).version !== spec.slice(separator + 1);
755
+ } catch {
756
+ return true;
757
+ }
758
+ });
759
+ const unmet = [...missing, ...mismatched];
760
+ if (unmet.length > 0)
761
+ throw new TypeError(`Device capabilities ${plan.capabilities.join(", ")} require ${unmet.join(", ")}. Run absolute mobile sync and approve the detected capability plugins.`);
762
+ }, directAbsoluteProjectPackages = (projectRoot) => {
763
+ const manifest = readJson(join13(resolve11(projectRoot), "package.json"));
764
+ const packages = new Set;
765
+ for (const field of ["dependencies", "devDependencies"]) {
766
+ const dependencies = manifest[field];
767
+ if (object2(dependencies))
768
+ for (const name of Object.keys(dependencies))
769
+ packages.add(name);
770
+ }
771
+ return packages;
772
+ }, discoverAbsoluteDeviceCapabilities = (projectRoot, providers = loadAbsoluteDeviceCapabilityProviders(projectRoot)) => {
773
+ const root = resolve11(projectRoot);
774
+ const known = new Set(Object.keys(providers));
775
+ const capabilities = new Set;
776
+ for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
777
+ const portable = relative9(root, resolve11(root, path)).replaceAll("\\", "/");
778
+ if (isIgnored(portable))
779
+ continue;
780
+ const source = readFileSync4(resolve11(root, portable), "utf8");
781
+ for (const name of importedCapabilities(source, portable))
782
+ if (known.has(name))
783
+ capabilities.add(name);
784
+ }
785
+ return [...capabilities].sort();
786
+ }, missingAbsoluteDeviceCapabilityPackages = (plan, directPackages) => plan.requiredPackages.filter((spec) => {
787
+ const packageName = spec.slice(0, spec.lastIndexOf("@"));
788
+ return !directPackages.has(packageName);
789
+ }), projectImportsAbsoluteDeviceCapability = (projectRoot, capability) => {
790
+ const root = resolve11(projectRoot);
791
+ for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
792
+ const portable = relative9(root, resolve11(root, path)).replaceAll("\\", "/");
793
+ if (isIgnored(portable))
794
+ continue;
795
+ const source = readFileSync4(resolve11(root, portable), "utf8");
796
+ if (importedCapabilities(source, portable).has(capability))
797
+ return true;
798
+ }
799
+ return false;
800
+ }, resolveAbsoluteDeviceCapabilityPlan = (projectRoot) => {
801
+ const allProviders = loadAbsoluteDeviceCapabilityProviders(projectRoot);
802
+ const capabilities = discoverAbsoluteDeviceCapabilities(projectRoot, allProviders);
803
+ const providers = {};
804
+ for (const name of capabilities) {
805
+ const provider = allProviders[name];
806
+ if (provider)
807
+ providers[name] = provider;
808
+ }
809
+ return {
810
+ capabilities,
811
+ providers,
812
+ requiredPackages: [
813
+ ...new Set(capabilities.flatMap((name) => providers[name]?.packages ?? []))
814
+ ].sort()
815
+ };
816
+ };
817
+ var init_deviceCapabilities = __esm(() => {
818
+ SOURCE_GLOB = new Bun.Glob("**/*.{js,jsx,ts,tsx,svelte,vue}");
819
+ IGNORED_DIRECTORIES = new Set([
820
+ ".absolutejs",
821
+ ".git",
822
+ ".test-builds",
823
+ ".test-shards",
824
+ "build",
825
+ "dist",
826
+ "node_modules",
827
+ "test",
828
+ "tests"
829
+ ]);
830
+ IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
831
+ CAPACITOR_MODULE_PATTERN = /^@absolutejs\/devices-capacitor\/[a-z][a-z0-9-]*$/u;
832
+ CAPACITOR_PACKAGE_PATTERN = /^@capacitor\/[a-z][a-z0-9-]*@\d+\.\d+\.\d+$/u;
833
+ ANDROID_PERMISSION_PATTERN = /^android\.permission\.[A-Z][A-Z0-9_]*$/u;
834
+ IOS_USAGE_DESCRIPTIONS = new Set([
835
+ "camera",
836
+ "location-always",
837
+ "location-when-in-use",
838
+ "photo-library",
839
+ "photo-library-add"
840
+ ]);
841
+ IOS_PRIVACY_ACCESSED_API_REASONS = {
842
+ NSPrivacyAccessedAPICategoryFileTimestamp: new Set(["C617.1"])
843
+ };
844
+ IOS_PRIVACY_ACCESSED_APIS = [
845
+ "NSPrivacyAccessedAPICategoryFileTimestamp"
846
+ ];
847
+ });
848
+
592
849
  // src/mobile/artifactStore.ts
593
850
  import { createHash as createHash2 } from "crypto";
594
851
  import {
@@ -3680,7 +3937,8 @@ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
3680
3937
  iosVersion: normalizeIosVersion(config.ios?.version),
3681
3938
  nativeProjectDirectory: resolveProjectPath(projectRoot, config.nativeProject?.directory ?? "mobile", "mobile.nativeProject.directory"),
3682
3939
  platforms: normalizePlatforms(config.platforms),
3683
- productionOrigin
3940
+ productionOrigin,
3941
+ pushAndroidGoogleServicesFile: resolveProjectPath(projectRoot, config.pushNotifications?.android?.googleServicesFile ?? "google-services.json", "mobile.pushNotifications.android.googleServicesFile")
3684
3942
  };
3685
3943
  };
3686
3944
 
@@ -4575,6 +4833,12 @@ var shellSyncModule = () => {
4575
4833
  return candidate;
4576
4834
  throw new TypeError("AbsoluteJS mobile Sync shell module is missing.");
4577
4835
  };
4836
+ var shellPushModule = () => {
4837
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellPush.${extension}`)).find(existsSync2);
4838
+ if (candidate)
4839
+ return candidate;
4840
+ throw new TypeError("AbsoluteJS mobile push shell module is missing.");
4841
+ };
4578
4842
  var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
4579
4843
  var indexHtml = (appName) => `<!doctype html>
4580
4844
  <html>
@@ -4627,6 +4891,10 @@ var buildShellBootstrap = async (staging, auth, sync, storagePrefix, deviceCapab
4627
4891
  ` : "";
4628
4892
  const options = auth ? `{ createAuth: createAbsoluteMobileShellAuth${sync ? ", installSync: installAbsoluteMobileShellSync" : ""} }` : "";
4629
4893
  const syncImport = sync ? `import { installAbsoluteMobileShellSync } from ${JSON.stringify(shellSyncModule())};
4894
+ ` : "";
4895
+ const pushIndex = deviceCapabilities.capabilities.indexOf("pushNotifications");
4896
+ const push = pushIndex !== -1;
4897
+ const pushImport = push ? `import { createAbsoluteMobileShellPush } from ${JSON.stringify(shellPushModule())};
4630
4898
  ` : "";
4631
4899
  const capabilityImports = (await Promise.all(deviceCapabilities.capabilities.map(async (name, index) => {
4632
4900
  const provider = deviceCapabilities.providers[name];
@@ -4635,14 +4903,17 @@ var buildShellBootstrap = async (staging, auth, sync, storagePrefix, deviceCapab
4635
4903
  return `import { ${provider.factory} as absoluteDeviceCapability${index} } from ${JSON.stringify(await resolveProjectImport(projectRoot, provider.module))};`;
4636
4904
  }))).join(`
4637
4905
  `);
4638
- const capabilityOptions = deviceCapabilities.capabilities.map((name, index) => `${JSON.stringify(name)}: absoluteDeviceCapability${index}()`).join(", ");
4906
+ const pushSetup = push ? `const absoluteMobilePush = createAbsoluteMobileShellPush();
4907
+ const absoluteMobilePushCapability = absoluteDeviceCapability${pushIndex}(absoluteMobilePush.capabilityOptions);
4908
+ ` : "";
4909
+ const capabilityOptions = deviceCapabilities.capabilities.map((name, index) => `${JSON.stringify(name)}: ${name === "pushNotifications" ? "absoluteMobilePushCapability" : `absoluteDeviceCapability${index}()`}`).join(", ");
4639
4910
  const entryPath = join9(staging, ".absolute-mobile-entry.ts");
4640
4911
  const baseAdapterModule = await resolveProjectImport(projectRoot, "@absolutejs/devices-capacitor");
4641
4912
  await writeFile10(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
4642
4913
  import { installCapacitorDeviceAdapterIfNative } from ${JSON.stringify(baseAdapterModule)};
4643
- ${authImport}${syncImport}${capabilityImports}
4644
- installCapacitorDeviceAdapterIfNative({ storagePrefix: ${JSON.stringify(storagePrefix)}${capabilityOptions ? `, ${capabilityOptions}` : ""} });
4645
- void startAbsoluteMobileShell(${options});
4914
+ ${authImport}${syncImport}${pushImport}${capabilityImports}
4915
+ ${pushSetup}installCapacitorDeviceAdapterIfNative({ storagePrefix: ${JSON.stringify(storagePrefix)}${capabilityOptions ? `, ${capabilityOptions}` : ""} });
4916
+ void startAbsoluteMobileShell(${push ? `{ createAuth: (config, options) => createAbsoluteMobileShellAuth(config, options), beforeSignOut: absoluteMobilePush.beforeSignOut, connectPush: (auth) => absoluteMobilePush.connect(auth, absoluteMobilePushCapability)${sync ? ", installSync: installAbsoluteMobileShellSync" : ""} }` : options});
4646
4917
  `);
4647
4918
  const build = await Bun.build({
4648
4919
  entrypoints: [entryPath],
@@ -5047,265 +5318,7 @@ var serializeAbsoluteMobileAuthEnvironment = (config, auth) => auth === undefine
5047
5318
 
5048
5319
  // src/mobile/buildPipeline.ts
5049
5320
  init_syncSchema();
5050
-
5051
- // src/mobile/deviceCapabilities.ts
5052
- import { readFileSync as readFileSync4 } from "fs";
5053
- import { extname as extname3, join as join13, relative as relative9, resolve as resolve11 } from "path";
5054
- import ts from "typescript";
5055
- var DEVICES_PACKAGE = "@absolutejs/devices";
5056
- var CAPACITOR_ADAPTER = "@absolutejs/devices-capacitor";
5057
- var SOURCE_GLOB = new Bun.Glob("**/*.{js,jsx,ts,tsx,svelte,vue}");
5058
- var IGNORED_DIRECTORIES = new Set([
5059
- ".absolutejs",
5060
- ".git",
5061
- ".test-builds",
5062
- ".test-shards",
5063
- "build",
5064
- "dist",
5065
- "node_modules",
5066
- "test",
5067
- "tests"
5068
- ]);
5069
- var IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
5070
- var CAPACITOR_MODULE_PATTERN = /^@absolutejs\/devices-capacitor\/[a-z][a-z0-9-]*$/u;
5071
- var CAPACITOR_PACKAGE_PATTERN = /^@capacitor\/[a-z][a-z0-9-]*@\d+\.\d+\.\d+$/u;
5072
- var ANDROID_PERMISSION_PATTERN = /^android\.permission\.[A-Z][A-Z0-9_]*$/u;
5073
- var IOS_USAGE_DESCRIPTIONS = new Set([
5074
- "camera",
5075
- "location-always",
5076
- "location-when-in-use",
5077
- "photo-library",
5078
- "photo-library-add"
5079
- ]);
5080
- var IOS_PRIVACY_ACCESSED_API_REASONS = {
5081
- NSPrivacyAccessedAPICategoryFileTimestamp: new Set(["C617.1"])
5082
- };
5083
- var IOS_PRIVACY_ACCESSED_APIS = [
5084
- "NSPrivacyAccessedAPICategoryFileTimestamp"
5085
- ];
5086
- var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5087
- var readJson = (path) => {
5088
- const value = JSON.parse(readFileSync4(path, "utf8"));
5089
- if (!object2(value))
5090
- throw new TypeError(`${path} must contain an object.`);
5091
- return value;
5092
- };
5093
- var text = (value, field) => {
5094
- if (typeof value !== "string" || value.length === 0)
5095
- throw new TypeError(`${field} must be a non-empty string.`);
5096
- return value;
5097
- };
5098
- var androidPermissions = (value, field) => {
5099
- if (value === undefined)
5100
- return;
5101
- if (!object2(value))
5102
- throw new TypeError(`${field} must be an object.`);
5103
- const { permissions } = value;
5104
- if (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string" && ANDROID_PERMISSION_PATTERN.test(permission)))
5105
- throw new TypeError(`${field}.permissions must contain Android permission names.`);
5106
- return [...permissions];
5107
- };
5108
- var iosPrivacyAccessedApis = (value, field) => {
5109
- if (value === undefined)
5110
- return;
5111
- if (!object2(value))
5112
- throw new TypeError(`${field} must be an object.`);
5113
- const privacy = {};
5114
- for (const api of IOS_PRIVACY_ACCESSED_APIS) {
5115
- const reasons = value[api];
5116
- if (reasons === undefined)
5117
- continue;
5118
- const supported = IOS_PRIVACY_ACCESSED_API_REASONS[api];
5119
- if (!Array.isArray(reasons) || reasons.length === 0 || !reasons.every((reason) => typeof reason === "string" && supported.has(reason)))
5120
- throw new TypeError(`${field} contains an unsupported API or reason.`);
5121
- privacy[api] = [...reasons];
5122
- }
5123
- if (Object.keys(value).some((api) => !IOS_PRIVACY_ACCESSED_APIS.some((known) => known === api)))
5124
- throw new TypeError(`${field} contains an unsupported API or reason.`);
5125
- return privacy;
5126
- };
5127
- var iosNativeRequirements = (value, field) => {
5128
- if (value === undefined)
5129
- return;
5130
- if (!object2(value))
5131
- throw new TypeError(`${field} must be an object.`);
5132
- const { privacyAccessedApis, usageDescriptions } = value;
5133
- if (usageDescriptions !== undefined && (!Array.isArray(usageDescriptions) || !usageDescriptions.every((purpose) => typeof purpose === "string" && IOS_USAGE_DESCRIPTIONS.has(purpose))))
5134
- throw new TypeError(`${field}.usageDescriptions contains an unsupported purpose.`);
5135
- const privacy = iosPrivacyAccessedApis(privacyAccessedApis, `${field}.privacyAccessedApis`);
5136
- return {
5137
- ...privacy === undefined ? {} : { privacyAccessedApis: privacy },
5138
- ...usageDescriptions === undefined ? {} : { usageDescriptions: [...usageDescriptions] }
5139
- };
5140
- };
5141
- var parseProvider = (name, value) => {
5142
- if (!IDENTIFIER_PATTERN.test(name))
5143
- throw new TypeError("Device capability names must be identifiers.");
5144
- if (!object2(value))
5145
- throw new TypeError(`Device capability ${name} must be an object.`);
5146
- const factory = text(value.factory, `${name}.factory`);
5147
- const module = text(value.module, `${name}.module`);
5148
- if (!IDENTIFIER_PATTERN.test(factory))
5149
- throw new TypeError(`${name}.factory must be a JavaScript identifier.`);
5150
- if (!CAPACITOR_MODULE_PATTERN.test(module))
5151
- throw new TypeError(`${name}.module must be an official devices-capacitor subpath.`);
5152
- if (!Array.isArray(value.packages) || !value.packages.every((spec) => typeof spec === "string" && CAPACITOR_PACKAGE_PATTERN.test(spec)))
5153
- throw new TypeError(`${name}.packages must contain exact official Capacitor package versions.`);
5154
- let native;
5155
- const { native: nativeMetadata } = value;
5156
- if (nativeMetadata !== undefined) {
5157
- if (!object2(nativeMetadata))
5158
- throw new TypeError(`${name}.native must be an object.`);
5159
- const { android, ios } = nativeMetadata;
5160
- const permissions = androidPermissions(android, `${name}.native.android`);
5161
- const iosRequirements = iosNativeRequirements(ios, `${name}.native.ios`);
5162
- native = {
5163
- ...permissions === undefined ? {} : { android: { permissions } },
5164
- ...iosRequirements === undefined ? {} : { ios: iosRequirements }
5165
- };
5166
- }
5167
- return {
5168
- factory,
5169
- module,
5170
- ...native === undefined ? {} : { native },
5171
- packages: [...value.packages]
5172
- };
5173
- };
5174
- var absoluteDeviceNativeRequirements = (plan) => {
5175
- const privacy = plan.capabilities.reduce((requirements, name) => {
5176
- for (const api of IOS_PRIVACY_ACCESSED_APIS) {
5177
- const reasons = plan.providers[name]?.native?.ios?.privacyAccessedApis?.[api] ?? [];
5178
- if (reasons.length === 0)
5179
- continue;
5180
- const current = requirements[api] ?? new Set;
5181
- for (const reason of reasons)
5182
- current.add(reason);
5183
- requirements[api] = current;
5184
- }
5185
- return requirements;
5186
- }, {});
5187
- return {
5188
- androidPermissions: [
5189
- ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.android?.permissions ?? []))
5190
- ].sort(),
5191
- iosPrivacyAccessedApis: IOS_PRIVACY_ACCESSED_APIS.flatMap((api) => {
5192
- const reasons = privacy[api];
5193
- return reasons ? [{ api, reasons: [...reasons].sort() }] : [];
5194
- }),
5195
- iosUsageDescriptions: [
5196
- ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.ios?.usageDescriptions ?? []))
5197
- ].sort()
5198
- };
5199
- };
5200
- var loadAbsoluteDeviceCapabilityProviders = (projectRoot) => {
5201
- const path = join13(resolve11(projectRoot), "node_modules", CAPACITOR_ADAPTER, "package.json");
5202
- const manifest = readJson(path);
5203
- const { absolutejs } = manifest;
5204
- const devices = object2(absolutejs) ? absolutejs.devices : undefined;
5205
- if (!object2(devices) || devices.format !== 1 || devices.provider !== "capacitor" || !object2(devices.capabilities))
5206
- throw new TypeError(`${CAPACITOR_ADAPTER} does not publish supported capability metadata.`);
5207
- const entries = Object.entries(devices.capabilities).map(([name, provider]) => ({
5208
- name,
5209
- provider: parseProvider(name, provider)
5210
- }));
5211
- return Object.fromEntries(entries.sort((left, right) => left.name.localeCompare(right.name)).map(({ name, provider }) => [name, provider]));
5212
- };
5213
- var isIgnored = (path) => path.split("/").some((segment) => IGNORED_DIRECTORIES.has(segment));
5214
- var importedCapabilities = (source, file) => {
5215
- const names = new Set;
5216
- const namespaces = new Set;
5217
- const visit = (node) => {
5218
- if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && !node.importClause?.isTypeOnly) {
5219
- const bindings = node.importClause?.namedBindings;
5220
- if (bindings && ts.isNamedImports(bindings)) {
5221
- for (const element of bindings.elements)
5222
- if (!element.isTypeOnly)
5223
- names.add((element.propertyName ?? element.name).text);
5224
- }
5225
- if (bindings && ts.isNamespaceImport(bindings))
5226
- namespaces.add(bindings.name.text);
5227
- }
5228
- if (ts.isExportDeclaration(node) && !node.isTypeOnly && node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && node.exportClause && ts.isNamedExports(node.exportClause)) {
5229
- for (const element of node.exportClause.elements)
5230
- if (!element.isTypeOnly)
5231
- names.add((element.propertyName ?? element.name).text);
5232
- }
5233
- if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && namespaces.has(node.expression.text))
5234
- names.add(node.name.text);
5235
- ts.forEachChild(node, visit);
5236
- };
5237
- const extension = extname3(file).toLowerCase();
5238
- const sources = extension === ".svelte" || extension === ".vue" ? [...source.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script\s*>/giu)].map((match) => match[1]).filter((value) => value !== undefined) : [source];
5239
- for (const [index, script] of sources.entries())
5240
- visit(ts.createSourceFile(`${file}#script-${index}`, script, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX));
5241
- return names;
5242
- };
5243
- var assertAbsoluteDeviceCapabilityPackages = (projectRoot, plan) => {
5244
- const missing = missingAbsoluteDeviceCapabilityPackages(plan, directAbsoluteProjectPackages(projectRoot));
5245
- const mismatched = plan.requiredPackages.filter((spec) => {
5246
- const separator = spec.lastIndexOf("@");
5247
- const packageName = spec.slice(0, separator);
5248
- if (missing.includes(spec))
5249
- return false;
5250
- try {
5251
- return readJson(join13(resolve11(projectRoot), "node_modules", packageName, "package.json")).version !== spec.slice(separator + 1);
5252
- } catch {
5253
- return true;
5254
- }
5255
- });
5256
- const unmet = [...missing, ...mismatched];
5257
- if (unmet.length > 0)
5258
- throw new TypeError(`Device capabilities ${plan.capabilities.join(", ")} require ${unmet.join(", ")}. Run absolute mobile sync and approve the detected capability plugins.`);
5259
- };
5260
- var directAbsoluteProjectPackages = (projectRoot) => {
5261
- const manifest = readJson(join13(resolve11(projectRoot), "package.json"));
5262
- const packages = new Set;
5263
- for (const field of ["dependencies", "devDependencies"]) {
5264
- const dependencies = manifest[field];
5265
- if (object2(dependencies))
5266
- for (const name of Object.keys(dependencies))
5267
- packages.add(name);
5268
- }
5269
- return packages;
5270
- };
5271
- var discoverAbsoluteDeviceCapabilities = (projectRoot, providers = loadAbsoluteDeviceCapabilityProviders(projectRoot)) => {
5272
- const root = resolve11(projectRoot);
5273
- const known = new Set(Object.keys(providers));
5274
- const capabilities = new Set;
5275
- for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
5276
- const portable = relative9(root, resolve11(root, path)).replaceAll("\\", "/");
5277
- if (isIgnored(portable))
5278
- continue;
5279
- const source = readFileSync4(resolve11(root, portable), "utf8");
5280
- for (const name of importedCapabilities(source, portable))
5281
- if (known.has(name))
5282
- capabilities.add(name);
5283
- }
5284
- return [...capabilities].sort();
5285
- };
5286
- var missingAbsoluteDeviceCapabilityPackages = (plan, directPackages) => plan.requiredPackages.filter((spec) => {
5287
- const packageName = spec.slice(0, spec.lastIndexOf("@"));
5288
- return !directPackages.has(packageName);
5289
- });
5290
- var resolveAbsoluteDeviceCapabilityPlan = (projectRoot) => {
5291
- const allProviders = loadAbsoluteDeviceCapabilityProviders(projectRoot);
5292
- const capabilities = discoverAbsoluteDeviceCapabilities(projectRoot, allProviders);
5293
- const providers = {};
5294
- for (const name of capabilities) {
5295
- const provider = allProviders[name];
5296
- if (provider)
5297
- providers[name] = provider;
5298
- }
5299
- return {
5300
- capabilities,
5301
- providers,
5302
- requiredPackages: [
5303
- ...new Set(capabilities.flatMap((name) => providers[name]?.packages ?? []))
5304
- ].sort()
5305
- };
5306
- };
5307
-
5308
- // src/mobile/buildPipeline.ts
5321
+ init_deviceCapabilities();
5309
5322
  var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
5310
5323
  var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
5311
5324
  var serverExportName = (loaded, app) => {
@@ -5380,6 +5393,11 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
5380
5393
  const sync = auth !== undefined && projectUsesAbsoluteSync(options.projectRoot);
5381
5394
  const syncSchema = sync ? discoverAbsoluteSyncSchema(options.projectRoot) : undefined;
5382
5395
  const deviceCapabilities = resolveAbsoluteDeviceCapabilityPlan(options.projectRoot);
5396
+ const usesPush = deviceCapabilities.capabilities.includes("pushNotifications");
5397
+ if (usesPush && !auth)
5398
+ throw new TypeError("Portable push notifications require @absolutejs/auth so provider tokens can be registered without exposing identity controls to page code.");
5399
+ if (usesPush && !loaded.app.routes.some((route) => route.path === "/auth/push" || route.path === "/auth/mobile/push"))
5400
+ throw new TypeError("@absolutejs/devices pushNotifications is used, but Auth push is not configured. Pass a trusted server-side registrar to auth({ push: ... }).");
5383
5401
  assertAbsoluteDeviceCapabilityPackages(options.projectRoot, deviceCapabilities);
5384
5402
  if (auth && !loaded.app.routes.some((route) => route.path === "/.well-known/openid-configuration")) {
5385
5403
  throw new TypeError("@absolutejs/auth is installed, but its OIDC provider is not mounted. Native authentication requires the auth oidc configuration so AbsoluteJS can provision a public PKCE client.");
@@ -5517,6 +5535,10 @@ var installAbsoluteMobileSyncRemediation = (bridge = {
5517
5535
  registry3.installations.splice(index, 1);
5518
5536
  };
5519
5537
  };
5538
+
5539
+ // src/mobile/index.ts
5540
+ init_deviceCapabilities();
5541
+
5520
5542
  // src/mobile/compatibilityDispatcher.ts
5521
5543
  import { Elysia as Elysia2 } from "elysia";
5522
5544
 
@@ -5822,6 +5844,7 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
5822
5844
  };
5823
5845
  };
5824
5846
  // src/mobile/nativeDeviceCapabilities.ts
5847
+ init_deviceCapabilities();
5825
5848
  import { readFile as readFile15, rename as rename12, writeFile as writeFile13 } from "fs/promises";
5826
5849
  import { join as join16 } from "path";
5827
5850
  var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
@@ -5829,6 +5852,8 @@ var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
5829
5852
  var NOT_FOUND2 = -1;
5830
5853
  var IOS_PRIVACY_FILE_REFERENCE = "A85D0C000000000000000001";
5831
5854
  var IOS_PRIVACY_BUILD_FILE = "A85D0C000000000000000002";
5855
+ var PUSH_START_MARKER = "absolutejs:push-notifications:start";
5856
+ var PUSH_END_MARKER = "absolutejs:push-notifications:end";
5832
5857
  var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
5833
5858
  var writeChangedFile2 = async (path, source) => {
5834
5859
  const current = await readFile15(path, "utf8");
@@ -5839,6 +5864,16 @@ var writeChangedFile2 = async (path, source) => {
5839
5864
  await rename12(temporary, path);
5840
5865
  return true;
5841
5866
  };
5867
+ var writeOptionalChangedFile = async (path, source) => {
5868
+ const current = await optionalSource(path);
5869
+ if (current === source)
5870
+ return false;
5871
+ if (current === null) {
5872
+ await writeFile13(path, source, { flag: "wx" });
5873
+ return true;
5874
+ }
5875
+ return writeChangedFile2(path, source);
5876
+ };
5842
5877
  var optionalSource = async (path) => {
5843
5878
  try {
5844
5879
  return await readFile15(path, "utf8");
@@ -6028,11 +6063,73 @@ ${content}
6028
6063
  const privacyPath = join16(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
6029
6064
  const privacyCurrent = await optionalSource(privacyPath);
6030
6065
  const privacySource = privacyManifestSource(privacyCurrent, requirements);
6031
- const [privacyChanged, projectChanged] = await Promise.all([
6066
+ const [privacyChanged, projectChanged, pushChanged] = await Promise.all([
6032
6067
  writeIosPrivacyManifest(privacyPath, privacyCurrent, privacySource),
6033
- configureIosPrivacyProject(config, requirements)
6068
+ configureIosPrivacyProject(config, requirements),
6069
+ configureIosPushNotifications(config, requirements.iosPushNotifications)
6034
6070
  ]);
6035
- return infoChanged || privacyChanged || projectChanged;
6071
+ return infoChanged || privacyChanged || projectChanged || pushChanged;
6072
+ };
6073
+ var configureIosPushNotifications = async (config, enabled) => {
6074
+ const entitlementsPath = join16(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
6075
+ const entitlements = await optionalSource(entitlementsPath);
6076
+ if (entitlements === null && !enabled)
6077
+ return false;
6078
+ if (entitlements === null)
6079
+ throw new TypeError("AbsoluteJS iOS entitlements are missing. Run native deep-link projection before device capabilities.");
6080
+ const entitlementRegion = enabled ? ` <!-- ${PUSH_START_MARKER} -->
6081
+ <key>aps-environment</key>
6082
+ <string>development</string>
6083
+ <!-- ${PUSH_END_MARKER} -->
6084
+ ` : "";
6085
+ const nextEntitlements = replacePushRegion(entitlements, entitlementRegion, entitlements.lastIndexOf("</dict>"));
6086
+ const delegatePath = join16(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
6087
+ const delegate = await optionalSource(delegatePath);
6088
+ if (delegate === null && !enabled)
6089
+ return false;
6090
+ if (delegate === null)
6091
+ throw new TypeError("Capacitor AppDelegate.swift is missing for iOS push notifications.");
6092
+ const hasSuccess = delegate.includes("didRegisterForRemoteNotificationsWithDeviceToken");
6093
+ const hasFailure = delegate.includes("didFailToRegisterForRemoteNotificationsWithError");
6094
+ if (enabled && hasSuccess !== hasFailure && !delegate.includes(PUSH_START_MARKER))
6095
+ throw new TypeError("iOS AppDelegate has a partial custom remote-notification registration implementation.");
6096
+ const alreadyForwarded = enabled && hasSuccess && hasFailure && delegate.includes("capacitorDidRegisterForRemoteNotifications") && delegate.includes("capacitorDidFailToRegisterForRemoteNotifications");
6097
+ const swiftRegion = enabled && !alreadyForwarded ? `
6098
+ // ${PUSH_START_MARKER}
6099
+ func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
6100
+ NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken)
6101
+ }
6102
+
6103
+ func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
6104
+ NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
6105
+ }
6106
+ // ${PUSH_END_MARKER}
6107
+ ` : "";
6108
+ const nextDelegate = alreadyForwarded ? delegate : replacePushRegion(delegate, swiftRegion, delegate.lastIndexOf("}"));
6109
+ const changed = await Promise.all([
6110
+ writeChangedFile2(entitlementsPath, nextEntitlements),
6111
+ writeChangedFile2(delegatePath, nextDelegate)
6112
+ ]);
6113
+ return changed.some(Boolean);
6114
+ };
6115
+ var replacePushRegion = (source, region, insertion) => {
6116
+ const start = source.indexOf(PUSH_START_MARKER);
6117
+ const end = source.indexOf(PUSH_END_MARKER);
6118
+ if (start < 0 !== end < 0 || start >= 0 && end < start)
6119
+ throw new TypeError("AbsoluteJS push-notification ownership markers are malformed.");
6120
+ if (start >= 0) {
6121
+ const lineStart = source.lastIndexOf(`
6122
+ `, start) + 1;
6123
+ const nextLine = source.indexOf(`
6124
+ `, end + PUSH_END_MARKER.length);
6125
+ const lineEnd = nextLine < 0 ? source.length : nextLine + 1;
6126
+ return `${source.slice(0, lineStart)}${region}${source.slice(lineEnd)}`;
6127
+ }
6128
+ if (!region)
6129
+ return source;
6130
+ if (insertion < 0)
6131
+ throw new TypeError("Could not find a safe native project location for push notifications.");
6132
+ return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
6036
6133
  };
6037
6134
  var configureAndroid2 = async (config, plan) => {
6038
6135
  const path = join16(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
@@ -6047,7 +6144,33 @@ ${content}
6047
6144
  const application = source.indexOf("<application");
6048
6145
  const insertion = application === NOT_FOUND2 ? NOT_FOUND2 : source.lastIndexOf(`
6049
6146
  `, application) + 1;
6050
- return writeChangedFile2(path, managed(source, region, insertion));
6147
+ const nextManifest = managed(source, region, insertion);
6148
+ if (!plan.capabilities.includes("pushNotifications"))
6149
+ return writeChangedFile2(path, nextManifest);
6150
+ const firebaseSource = await optionalSource(config.pushAndroidGoogleServicesFile);
6151
+ if (firebaseSource === null)
6152
+ throw new TypeError(`Push notifications require Firebase config at ${config.pushAndroidGoogleServicesFile}. Set mobile.pushNotifications.android.googleServicesFile to override it.`);
6153
+ let firebase;
6154
+ try {
6155
+ firebase = JSON.parse(firebaseSource);
6156
+ } catch (error) {
6157
+ throw new TypeError("Android google-services.json is invalid JSON.", {
6158
+ cause: error
6159
+ });
6160
+ }
6161
+ const clients = typeof firebase === "object" && firebase !== null ? Reflect.get(firebase, "client") : undefined;
6162
+ const matchesApp = Array.isArray(clients) && clients.some((client) => {
6163
+ const info = typeof client === "object" && client !== null ? Reflect.get(client, "client_info") : undefined;
6164
+ const android = typeof info === "object" && info !== null ? Reflect.get(info, "android_client_info") : undefined;
6165
+ return typeof android === "object" && android !== null && Reflect.get(android, "package_name") === config.appId;
6166
+ });
6167
+ if (!matchesApp)
6168
+ throw new TypeError(`Android google-services.json does not contain package ${config.appId}.`);
6169
+ const [manifestChanged, firebaseChanged] = await Promise.all([
6170
+ writeChangedFile2(path, nextManifest),
6171
+ writeOptionalChangedFile(join16(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
6172
+ ]);
6173
+ return manifestChanged || firebaseChanged;
6051
6174
  };
6052
6175
  var applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platforms = config.platforms, plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot)) => {
6053
6176
  const results = await Promise.all(platforms.map(async (platform) => ({
@@ -6714,6 +6837,7 @@ export {
6714
6837
  publishAbsoluteAndroidRelease,
6715
6838
  projectUsesAbsoluteSync,
6716
6839
  projectUsesAbsoluteAuth,
6840
+ projectImportsAbsoluteDeviceCapability,
6717
6841
  prepareAbsoluteIosRelease,
6718
6842
  prepareAbsoluteIosDevProject,
6719
6843
  prepareAbsoluteAndroidRelease,
@@ -6808,5 +6932,5 @@ export {
6808
6932
  ABSOLUTE_ANDROID_RELEASE_FORMAT
6809
6933
  };
6810
6934
 
6811
- //# debugId=443011A81611F30D64756E2164756E21
6935
+ //# debugId=17F8DCA356CF789D64756E2164756E21
6812
6936
  //# sourceMappingURL=index.js.map