@tokamakdev/plugin-location 0.1.0-beta.159 → 0.1.0-beta.160

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.
@@ -0,0 +1,115 @@
1
+ // The location plugin's declarations, from its build settings: the Info.plist on Apple platforms
2
+ // and the Android manifest.
3
+ const WHEN_IN_USE = "This app uses your location when you request location features.";
4
+ const ALWAYS = "This app uses your location in the background for location features you turn on.";
5
+ /**
6
+ * The file that declares what `settings` ask for on `platform`, the plugin section key that names
7
+ * it, the Swift compilation conditions of an Apple build, and notices for the build; or the
8
+ * problems with the settings.
9
+ */
10
+ export function declarations(platform, settings) {
11
+ if (settings["always-description"] !== undefined && !settings["background-always"]) {
12
+ return { problems: ["always-description needs background-always, which declares the permission it describes"] };
13
+ }
14
+ if (platform === "android") {
15
+ return { key: "manifest", name: "AndroidManifest.xml", contents: androidManifest(settings), notices: [] };
16
+ }
17
+ const notices = platform === "macos" && settings.precision === "approximate"
18
+ ? ["macOS has no declaration of approximate location, so precision approximate applies to iOS and Android only"]
19
+ : [];
20
+ const keys = appleKeys(platform, settings);
21
+ // Only a build that gives the Always reason compiles the code that asks for Always.
22
+ const always = "NSLocationAlwaysAndWhenInUseUsageDescription" in keys;
23
+ return {
24
+ key: "plist",
25
+ name: "Info.plist",
26
+ contents: plist(keys),
27
+ notices,
28
+ ...(always ? { conditions: ["LOCATION_ALWAYS"] } : {}),
29
+ };
30
+ }
31
+ /** The Info.plist keys of `settings` on the Apple `platform`. */
32
+ function appleKeys(platform, settings) {
33
+ const whenInUse = settings["when-in-use-description"] ?? WHEN_IN_USE;
34
+ const purposes = settings["accuracy-purposes"] ?? {};
35
+ const temporary = Object.keys(purposes).length > 0 ? { NSLocationTemporaryUsageDescriptionDictionary: purposes } : {};
36
+ if (platform === "macos") {
37
+ // macOS reads NSLocationUsageDescription, and its SDK headers name the iOS keys.
38
+ return {
39
+ NSLocationUsageDescription: whenInUse,
40
+ NSLocationWhenInUseUsageDescription: whenInUse,
41
+ NSLocationAlwaysAndWhenInUseUsageDescription: whenInUse,
42
+ ...temporary,
43
+ };
44
+ }
45
+ const keys = { NSLocationWhenInUseUsageDescription: whenInUse };
46
+ if (settings["background-always"])
47
+ keys.NSLocationAlwaysAndWhenInUseUsageDescription = settings["always-description"] ?? ALWAYS;
48
+ if (settings["background-updates"])
49
+ keys.UIBackgroundModes = ["location"];
50
+ if (settings.precision === "approximate")
51
+ keys.NSLocationDefaultAccuracyReduced = true;
52
+ return { ...keys, ...temporary };
53
+ }
54
+ /** The Android manifest of `settings`. */
55
+ function androidManifest(settings) {
56
+ const permissions = ["ACCESS_COARSE_LOCATION", "RECEIVE_BOOT_COMPLETED"];
57
+ if (settings.precision !== "approximate")
58
+ permissions.push("ACCESS_FINE_LOCATION");
59
+ if (settings["background-always"])
60
+ permissions.push("ACCESS_BACKGROUND_LOCATION");
61
+ if (settings["background-updates"])
62
+ permissions.push("FOREGROUND_SERVICE", "FOREGROUND_SERVICE_LOCATION");
63
+ const service = settings["background-updates"]
64
+ ? [
65
+ ' <service android:name="com.tokamak.plugins.location.TokamakLocationService" android:exported="false"',
66
+ ' android:foregroundServiceType="location" />',
67
+ ]
68
+ : [];
69
+ return [
70
+ '<manifest xmlns:android="http://schemas.android.com/apk/res/android">',
71
+ ...permissions.map((name) => ` <uses-permission android:name="android.permission.${name}" />`),
72
+ " <application>",
73
+ ...service,
74
+ ' <receiver android:name="com.tokamak.plugins.location.TokamakLocationReceiver" android:exported="false" />',
75
+ ' <receiver android:name="com.tokamak.plugins.location.TokamakLocationBootReceiver" android:exported="false">',
76
+ " <intent-filter>",
77
+ ' <action android:name="android.intent.action.BOOT_COMPLETED" />',
78
+ ' <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />',
79
+ " </intent-filter>",
80
+ " </receiver>",
81
+ " </application>",
82
+ "</manifest>",
83
+ "",
84
+ ].join("\n");
85
+ }
86
+ /** `keys` as an XML property list. */
87
+ function plist(keys) {
88
+ return [
89
+ '<?xml version="1.0" encoding="UTF-8"?>',
90
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
91
+ '<plist version="1.0">',
92
+ ...element(keys, ""),
93
+ "</plist>",
94
+ "",
95
+ ].join("\n");
96
+ }
97
+ /** The lines of `value` as a property list element, indented by `indent`. */
98
+ function element(value, indent) {
99
+ if (typeof value === "boolean")
100
+ return [`${indent}<${String(value)}/>`];
101
+ if (typeof value === "string")
102
+ return [`${indent}<string>${escaped(value)}</string>`];
103
+ const inner = `${indent}\t`;
104
+ if (Array.isArray(value)) {
105
+ return [`${indent}<array>`, ...value.flatMap((item) => element(item, inner)), `${indent}</array>`];
106
+ }
107
+ const entries = Object.entries(value).flatMap(([key, item]) => [
108
+ `${inner}<key>${escaped(key)}</key>`,
109
+ ...element(item, inner),
110
+ ]);
111
+ return [`${indent}<dict>`, ...entries, `${indent}</dict>`];
112
+ }
113
+ function escaped(text) {
114
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
115
+ }
@@ -3,15 +3,17 @@
3
3
  // and prints the keys that name it and an Apple build's compilation conditions.
4
4
  import { readFileSync, writeFileSync } from "node:fs";
5
5
  import { join } from "node:path";
6
- import { declarations } from "./declarations.mjs";
7
-
6
+ import { declarations } from "./declarations.js";
8
7
  const { platform, settings, output } = JSON.parse(readFileSync(0, "utf8"));
9
- const { key, name, contents, notices, conditions, problems } = declarations(platform, settings);
10
- if (problems) {
11
- console.error(problems.join("\n"));
12
- process.exit(1);
8
+ // tok has checked the settings against the plugin's manifest and applied their defaults.
9
+ const result = declarations(platform, settings);
10
+ if ("problems" in result) {
11
+ console.error(result.problems.join("\n"));
12
+ process.exit(1);
13
13
  }
14
- for (const notice of notices) console.error(notice);
14
+ const { key, name, contents, notices, conditions } = result;
15
+ for (const notice of notices)
16
+ console.error(notice);
15
17
  const path = join(output, name);
16
18
  writeFileSync(path, contents);
17
19
  console.log(JSON.stringify({ [key]: path, "compilation-conditions": conditions }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamakdev/plugin-location",
3
- "version": "0.1.0-beta.159",
3
+ "version": "0.1.0-beta.160",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mantty/tokamak.git",
@@ -14,12 +14,12 @@
14
14
  "./events": "./src/events.ts"
15
15
  },
16
16
  "peerDependencies": {
17
- "@tokamakdev/plugin": "0.1.0-beta.159"
17
+ "@tokamakdev/plugin": "0.1.0-beta.160"
18
18
  },
19
19
  "files": [
20
20
  "android",
21
21
  "apple",
22
- "build",
22
+ "dist",
23
23
  "src",
24
24
  "web",
25
25
  "tokamak-plugin.json",
@@ -27,5 +27,8 @@
27
27
  ],
28
28
  "publishConfig": {
29
29
  "access": "public"
30
+ },
31
+ "scripts": {
32
+ "build": "tsc --project tsconfig.build.json"
30
33
  }
31
34
  }
@@ -35,7 +35,7 @@
35
35
  "description": "Apple only: the reasons for temporary precise location, by purpose key"
36
36
  }
37
37
  },
38
- "build": "build/index.mjs",
38
+ "build": "dist/build/index.js",
39
39
  "platforms": {
40
40
  "macos": {
41
41
  "class": "TokamakLocation.TokamakLocationPlugin"
@@ -1,23 +0,0 @@
1
- /** The location plugin's build settings, with their defaults applied. */
2
- export interface Settings {
3
- "background-always": boolean;
4
- "background-updates": boolean;
5
- precision: "precise" | "approximate";
6
- "when-in-use-description"?: string;
7
- "always-description"?: string;
8
- "accuracy-purposes"?: Record<string, string>;
9
- }
10
-
11
- /**
12
- * The file that declares what the settings ask for, the plugin section key naming it, the Swift
13
- * compilation conditions of an Apple build, and notices.
14
- */
15
- export interface Declarations {
16
- key: "plist" | "manifest";
17
- name: string;
18
- contents: string;
19
- notices: string[];
20
- conditions?: string[];
21
- }
22
-
23
- export function declarations(platform: string, settings: Settings): Declarations | { problems: string[] };
@@ -1,115 +0,0 @@
1
- // The location plugin's declarations, from its build settings: the Info.plist on Apple platforms
2
- // and the Android manifest.
3
-
4
- const WHEN_IN_USE = "This app uses your location when you request location features.";
5
- const ALWAYS = "This app uses your location in the background for location features you turn on.";
6
-
7
- /**
8
- * The file that declares what `settings` ask for on `platform`, the plugin section key that names
9
- * it, the Swift compilation conditions of an Apple build, and notices for the build; or the
10
- * problems with the settings.
11
- */
12
- export function declarations(platform, settings) {
13
- if (settings["always-description"] !== undefined && !settings["background-always"]) {
14
- return { problems: ["always-description needs background-always, which declares the permission it describes"] };
15
- }
16
- if (platform === "android") {
17
- return { key: "manifest", name: "AndroidManifest.xml", contents: androidManifest(settings), notices: [] };
18
- }
19
- const notices =
20
- platform === "macos" && settings.precision === "approximate"
21
- ? ["macOS has no declaration of approximate location, so precision approximate applies to iOS and Android only"]
22
- : [];
23
- const keys = appleKeys(platform, settings);
24
- // Only a build that gives the Always reason compiles the code that asks for Always.
25
- const always = "NSLocationAlwaysAndWhenInUseUsageDescription" in keys;
26
- return {
27
- key: "plist",
28
- name: "Info.plist",
29
- contents: plist(keys),
30
- notices,
31
- ...(always ? { conditions: ["LOCATION_ALWAYS"] } : {}),
32
- };
33
- }
34
-
35
- /** The Info.plist keys of `settings` on the Apple `platform`. */
36
- function appleKeys(platform, settings) {
37
- const whenInUse = settings["when-in-use-description"] ?? WHEN_IN_USE;
38
- const purposes = settings["accuracy-purposes"] ?? {};
39
- const temporary = Object.keys(purposes).length > 0 ? { NSLocationTemporaryUsageDescriptionDictionary: purposes } : {};
40
- if (platform === "macos") {
41
- // macOS reads NSLocationUsageDescription, and its SDK headers name the iOS keys.
42
- return {
43
- NSLocationUsageDescription: whenInUse,
44
- NSLocationWhenInUseUsageDescription: whenInUse,
45
- NSLocationAlwaysAndWhenInUseUsageDescription: whenInUse,
46
- ...temporary,
47
- };
48
- }
49
- const keys = { NSLocationWhenInUseUsageDescription: whenInUse };
50
- if (settings["background-always"]) keys.NSLocationAlwaysAndWhenInUseUsageDescription = settings["always-description"] ?? ALWAYS;
51
- if (settings["background-updates"]) keys.UIBackgroundModes = ["location"];
52
- if (settings.precision === "approximate") keys.NSLocationDefaultAccuracyReduced = true;
53
- return { ...keys, ...temporary };
54
- }
55
-
56
- /** The Android manifest of `settings`. */
57
- function androidManifest(settings) {
58
- const permissions = ["ACCESS_COARSE_LOCATION", "RECEIVE_BOOT_COMPLETED"];
59
- if (settings.precision !== "approximate") permissions.push("ACCESS_FINE_LOCATION");
60
- if (settings["background-always"]) permissions.push("ACCESS_BACKGROUND_LOCATION");
61
- if (settings["background-updates"]) permissions.push("FOREGROUND_SERVICE", "FOREGROUND_SERVICE_LOCATION");
62
- const service = settings["background-updates"]
63
- ? [
64
- ' <service android:name="com.tokamak.plugins.location.TokamakLocationService" android:exported="false"',
65
- ' android:foregroundServiceType="location" />',
66
- ]
67
- : [];
68
- return [
69
- '<manifest xmlns:android="http://schemas.android.com/apk/res/android">',
70
- ...permissions.map((name) => ` <uses-permission android:name="android.permission.${name}" />`),
71
- " <application>",
72
- ...service,
73
- ' <receiver android:name="com.tokamak.plugins.location.TokamakLocationReceiver" android:exported="false" />',
74
- ' <receiver android:name="com.tokamak.plugins.location.TokamakLocationBootReceiver" android:exported="false">',
75
- " <intent-filter>",
76
- ' <action android:name="android.intent.action.BOOT_COMPLETED" />',
77
- ' <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />',
78
- " </intent-filter>",
79
- " </receiver>",
80
- " </application>",
81
- "</manifest>",
82
- "",
83
- ].join("\n");
84
- }
85
-
86
- /** `keys` as an XML property list. */
87
- function plist(keys) {
88
- return [
89
- '<?xml version="1.0" encoding="UTF-8"?>',
90
- '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
91
- '<plist version="1.0">',
92
- ...element(keys, ""),
93
- "</plist>",
94
- "",
95
- ].join("\n");
96
- }
97
-
98
- /** The lines of `value` as a property list element, indented by `indent`. */
99
- function element(value, indent) {
100
- if (typeof value === "boolean") return [`${indent}<${String(value)}/>`];
101
- if (typeof value === "string") return [`${indent}<string>${escaped(value)}</string>`];
102
- const inner = `${indent}\t`;
103
- if (Array.isArray(value)) {
104
- return [`${indent}<array>`, ...value.flatMap((item) => element(item, inner)), `${indent}</array>`];
105
- }
106
- const entries = Object.entries(value).flatMap(([key, item]) => [
107
- `${inner}<key>${escaped(key)}</key>`,
108
- ...element(item, inner),
109
- ]);
110
- return [`${indent}<dict>`, ...entries, `${indent}</dict>`];
111
- }
112
-
113
- function escaped(text) {
114
- return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
115
- }