mirror of
https://github.com/driftywinds/driftywinds.github.io.git
synced 2025-12-19 19:13:33 +00:00
Support AltStore's modern app permissions
This commit is contained in:
26
app.html
26
app.html
@@ -97,13 +97,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="permissions" class="section">
|
<div id="permissions" class="section">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h2>Permissions</h2>
|
<h2>App Permissions</h2>
|
||||||
|
</div>
|
||||||
|
<div id="permission-containers">
|
||||||
|
<div id="privacy" class="permission-container secondary-bg">
|
||||||
|
<div class="permission-container-header">
|
||||||
|
<i class="permission-icon bi-person-fill-check"></i>
|
||||||
|
<p><b>None</b></p>
|
||||||
|
<p class="description">This app's permissions have not been specified by the developer.</p>
|
||||||
|
</div>
|
||||||
|
<div class="permission-items">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="entitlements" class="permission-container secondary-bg">
|
||||||
|
<div class="permission-container-header">
|
||||||
|
<i class="permission-icon bi-key-fill"></i>
|
||||||
|
<p><b>Entitlements</b></p>
|
||||||
|
<p class="description">Entitlements are additional permissions that grant access to certain system services,
|
||||||
|
including potentially sensitive information.</p>
|
||||||
|
</div>
|
||||||
|
<div class="permission-items">
|
||||||
</div>
|
</div>
|
||||||
<div class="permission">
|
|
||||||
<i class="bi-person-fill-check"></i>
|
|
||||||
<div class="text">
|
|
||||||
<p class="title">None</p>
|
|
||||||
<p class="description">The developer has not specified any permissions required by this app.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
57
css/app.css
57
css/app.css
@@ -172,6 +172,63 @@ a {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#permission-containers {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.75em;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-icon {
|
||||||
|
color: var(--app-tint-color) !important;
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-container {
|
||||||
|
border-radius: 1.5em;
|
||||||
|
padding: 1.25em;
|
||||||
|
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-container-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-container p.description {
|
||||||
|
opacity: 0.5;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-container p {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-container b {
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-items {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 49.25% 49.25%;
|
||||||
|
row-gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: fit-content;
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-item i {
|
||||||
|
font-size: 1.5em !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Source */
|
/* Source */
|
||||||
#source {
|
#source {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
|||||||
@@ -12,12 +12,14 @@
|
|||||||
--accent-color: #018084;
|
--accent-color: #018084;
|
||||||
--app-tint-color: #018084;
|
--app-tint-color: #018084;
|
||||||
|
|
||||||
--color-bg: rgba(255, 255, 255, 255);
|
--color-bg: rgba(255, 255, 255, 1);
|
||||||
|
--color-bg-secondary: rgba(235, 235, 235, 1);
|
||||||
--color-separator: rgba(0, 0, 0, 0.15);
|
--color-separator: rgba(0, 0, 0, 0.15);
|
||||||
--color-primary: rgba(0, 122, 254, 255);
|
--color-primary: rgba(0, 122, 254, 1);
|
||||||
--color-transparent: rgba(255, 255, 255, 0.75);
|
--color-transparent: rgba(255, 255, 255, 0.75);
|
||||||
|
|
||||||
--color-bg-dark: rgb(26, 25, 27);
|
--color-bg-dark: rgb(26, 25, 27);
|
||||||
|
--color-bg-dark-secondary: rgb(46, 45, 47);
|
||||||
--color-separator-dark: rgba(255, 255, 255, 0.15);
|
--color-separator-dark: rgba(255, 255, 255, 0.15);
|
||||||
--color-primary-dark: rgba(11, 132, 254, 255);
|
--color-primary-dark: rgba(11, 132, 254, 255);
|
||||||
--color-transparent-dark: rgba(26, 25, 27, 0.25);
|
--color-transparent-dark: rgba(26, 25, 27, 0.25);
|
||||||
@@ -64,6 +66,14 @@
|
|||||||
.screenshot {
|
.screenshot {
|
||||||
border-color: rgba(255, 255, 255, 0.2) !important;
|
border-color: rgba(255, 255, 255, 0.2) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.secondary-bg {
|
||||||
|
background-color: var(--color-bg-dark-secondary) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-bg {
|
||||||
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.loading {
|
body.loading {
|
||||||
|
|||||||
136
js/app.js
136
js/app.js
@@ -6,9 +6,11 @@
|
|||||||
// MIT License.
|
// MIT License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { urlSearchParams, sourceURL } from "./constants.js";
|
import { urlSearchParams, sourceURL, legacyPermissions } from "./constants.js";
|
||||||
import { formatString } from "./utilities.js";
|
import { formatString, insertSpaceInCamelString, insertSpaceInSnakeString } from "./utilities.js";
|
||||||
import { main } from "./main.js";
|
import { main } from "./main.js";
|
||||||
|
import { privacy, entitlements } from "./constants.js";
|
||||||
|
import { AppPermissionItem } from "./components/AppPermissionItem.js";
|
||||||
|
|
||||||
if (!urlSearchParams.has('id')) exit();
|
if (!urlSearchParams.has('id')) exit();
|
||||||
const bundleId = urlSearchParams.get('id');
|
const bundleId = urlSearchParams.get('id');
|
||||||
@@ -160,95 +162,57 @@ main((json) => {
|
|||||||
|
|
||||||
//
|
//
|
||||||
// Permissions
|
// Permissions
|
||||||
const permissions = document.getElementById("permissions");
|
|
||||||
|
|
||||||
// If permissions specified
|
//
|
||||||
if (app.permissions) {
|
// Privacy
|
||||||
// Remove placeholder permission
|
const privacyContainer = document.getElementById("privacy");
|
||||||
permissions.querySelector(".permission").remove();
|
if (app.appPermissions?.privacy?.length || app.permissions) {
|
||||||
|
privacyContainer.querySelector(".permission-icon").classList = "permission-icon bi-person-fill-lock";
|
||||||
app.permissions?.forEach(permission => {
|
privacyContainer.querySelector("b").innerText = "Privacy";
|
||||||
var permissionType, icon;
|
privacyContainer.querySelector(".description").innerText = `"${app.name}" may request to access the following:`;
|
||||||
switch (permission.type) {
|
|
||||||
// AltStore-supported permissions
|
|
||||||
case "background-audio":
|
|
||||||
permissionType = "Background Audio";
|
|
||||||
icon = "volume-up-fill";
|
|
||||||
break;
|
|
||||||
case "background-fetch":
|
|
||||||
permissionType = "Background Fetch";
|
|
||||||
icon = "arrow-repeat"
|
|
||||||
break;
|
|
||||||
case "photos":
|
|
||||||
permissionType = "Photos"
|
|
||||||
icon = "image-fill";
|
|
||||||
break;
|
|
||||||
// Additional permissions
|
|
||||||
case "camera":
|
|
||||||
permissionType = "Camera"
|
|
||||||
icon = "camera-fill";
|
|
||||||
break;
|
|
||||||
case "music":
|
|
||||||
permissionType = "Music Library"
|
|
||||||
icon = "music-note-list";
|
|
||||||
break;
|
|
||||||
case "location":
|
|
||||||
permissionType = "Location"
|
|
||||||
icon = "geo-alt-fill";
|
|
||||||
break;
|
|
||||||
case "microphone":
|
|
||||||
permissionType = "Microphone"
|
|
||||||
icon = "mic-fill";
|
|
||||||
break;
|
|
||||||
case "contacts":
|
|
||||||
permissionType = "Contacts"
|
|
||||||
icon = "people-fill";
|
|
||||||
break;
|
|
||||||
case "bluetooth":
|
|
||||||
permissionType = "Bluetooth"
|
|
||||||
icon = "bluetooth";
|
|
||||||
break;
|
|
||||||
case "faceid":
|
|
||||||
permissionType = "Face ID"
|
|
||||||
icon = "person-bounding-box";
|
|
||||||
break;
|
|
||||||
case "network":
|
|
||||||
permissionType = "Network"
|
|
||||||
icon = "wifi";
|
|
||||||
break;
|
|
||||||
case "calendar":
|
|
||||||
case "calendars":
|
|
||||||
permissionType = "Calendar"
|
|
||||||
icon = "calendar-date";
|
|
||||||
break;
|
|
||||||
case "reminders":
|
|
||||||
permissionType = "Reminders"
|
|
||||||
icon = "list-ul";
|
|
||||||
break;
|
|
||||||
case "siri":
|
|
||||||
permissionType = "Siri"
|
|
||||||
icon = "gear-wide-connected";
|
|
||||||
break;
|
|
||||||
case "speech-recognition":
|
|
||||||
permissionType = "Speech Recognition"
|
|
||||||
icon = "soundwave";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
permissionType = permission.type.replaceAll("-", " ");
|
|
||||||
icon = "gear-wide-connected";
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
permissions.insertAdjacentHTML("beforeend", `
|
app.appPermissions?.privacy?.forEach(privacyPermission => {
|
||||||
<div class="permission">
|
const permission = privacy[privacyPermission.name];
|
||||||
<i class="bi-${icon}" style="color: ${tintColor};"></i>
|
let name = permission?.name ?? insertSpaceInCamelString(privacyPermission.name),
|
||||||
<div class="text">
|
icon;
|
||||||
<p class="title">${permissionType}</p>
|
if (permission?.icon) icon = permission.icon;
|
||||||
<p class="description">${permission.usageDescription ?? "No description provided."}</p>
|
else icon = "gear-wide-connected";
|
||||||
</div>
|
privacyContainer.querySelector(".permission-items").insertAdjacentHTML("beforeend",
|
||||||
</div>`);
|
AppPermissionItem(name, icon, privacyPermission?.usageDescription)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Legacy permissions
|
||||||
|
if (!app.appPermissions?.privacy) {
|
||||||
|
app.permissions?.forEach(appPermission => {
|
||||||
|
const permission = legacyPermissions[appPermission.type];
|
||||||
|
let name = insertSpaceInSnakeString(appPermission.type),
|
||||||
|
icon;
|
||||||
|
if (permission?.icon) icon = permission.icon;
|
||||||
|
else icon = "gear-wide-connected";
|
||||||
|
privacyContainer.querySelector(".permission-items").insertAdjacentHTML("beforeend",
|
||||||
|
AppPermissionItem(name, icon, appPermission?.usageDescription)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Entitlements
|
||||||
|
const entitlementsContainer = document.getElementById("entitlements");
|
||||||
|
console.log(app.appPermissions?.entitlements);
|
||||||
|
if (!app.appPermissions?.entitlements?.length) entitlementsContainer.remove();
|
||||||
|
app.appPermissions?.entitlements.forEach(entitlementPermission => {
|
||||||
|
const permission = entitlements[entitlementPermission.name];
|
||||||
|
let name = permission?.name ?? insertSpaceInSnakeString(entitlementPermission.name),
|
||||||
|
icon;
|
||||||
|
if (permission?.icon) icon = permission.icon;
|
||||||
|
else icon = "gear-wide-connected";;
|
||||||
|
entitlementsContainer.querySelector(".permission-items").insertAdjacentHTML("beforeend",
|
||||||
|
AppPermissionItem(name, icon, permission?.description)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// Source info
|
// Source info
|
||||||
const source = document.getElementById("source");
|
const source = document.getElementById("source");
|
||||||
|
|||||||
15
js/components/AppPermissionItem.js
Normal file
15
js/components/AppPermissionItem.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// AppPermissionItem.js
|
||||||
|
// altsource-viewer (https://github.com/therealFoxster/altsource-viewer)
|
||||||
|
//
|
||||||
|
// Copyright (c) 2023 Foxster.
|
||||||
|
// MIT License.
|
||||||
|
//
|
||||||
|
|
||||||
|
export const AppPermissionItem = (name, icon, details) => `
|
||||||
|
<a class="permission-item"
|
||||||
|
onclick="alert('${details?.replace(/(['"])/g, "\\$1") ?? "altsource-viewer does not have detailed information about this entitlement."}');"
|
||||||
|
>
|
||||||
|
<p><i class="bi-${icon}"></i></p>
|
||||||
|
<p class="title">${name}</p>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
117
js/constants.js
117
js/constants.js
@@ -7,6 +7,123 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
export const urlSearchParams = new URLSearchParams(window.location.search);
|
export const urlSearchParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
export const sourceURL = urlSearchParams.get('source')?.replaceAll("+", "%2B");
|
export const sourceURL = urlSearchParams.get('source')?.replaceAll("+", "%2B");
|
||||||
|
|
||||||
// https://stackoverflow.com/a/8943487
|
// https://stackoverflow.com/a/8943487
|
||||||
export const urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
export const urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
||||||
|
|
||||||
|
export const entitlements = {
|
||||||
|
"com.apple.security.application-groups": {
|
||||||
|
name: "App Groups",
|
||||||
|
description: "Allow app to share files with other apps and app extensions in the same App Group.",
|
||||||
|
icon: "columns-gap"
|
||||||
|
},
|
||||||
|
"com.apple.developer.associated-domains": {
|
||||||
|
name: "Associated Domains",
|
||||||
|
description: "The associated domains for specific services, such as shared web credentials, universal links, and App Clips.",
|
||||||
|
icon: "globe2"
|
||||||
|
},
|
||||||
|
"com.apple.developer.carplay-audio": {
|
||||||
|
name: "CarPlay Audio",
|
||||||
|
description: "Allows the app the provide audio content for CarPlay.",
|
||||||
|
icon: "car-front-fill"
|
||||||
|
},
|
||||||
|
"get-task-allow": {
|
||||||
|
name: "Debuggable",
|
||||||
|
description: "Allow developers to attach a debugger to this app. This permission is required for JIT to work.",
|
||||||
|
icon: "tools"
|
||||||
|
},
|
||||||
|
"com.apple.developer.device-information.user-assigned-device-name": {
|
||||||
|
name: "Device Name",
|
||||||
|
description: "Grants access to the user-assigned device name instead of a generic device name.",
|
||||||
|
icon: "phone-fill"
|
||||||
|
},
|
||||||
|
"keychain-access-groups": {
|
||||||
|
name: "Keychain",
|
||||||
|
description: "Allows app to read and write secure data to the system's keychain.",
|
||||||
|
icon: "key-fill"
|
||||||
|
},
|
||||||
|
"com.apple.developer.networking.multicast": {
|
||||||
|
name: "Multicast",
|
||||||
|
description: "App can send or receive IP multicast traffic.",
|
||||||
|
icon: "globe2"
|
||||||
|
},
|
||||||
|
"aps-environment": {
|
||||||
|
name: "Push Notifications",
|
||||||
|
description: "App can send push notifications.",
|
||||||
|
icon: "app-indicator"
|
||||||
|
},
|
||||||
|
"com.apple.developer.applesignin": {
|
||||||
|
name: "Sign in with Apple",
|
||||||
|
description: "Allows sign in with Apple.",
|
||||||
|
icon: "apple"
|
||||||
|
},
|
||||||
|
"com.apple.developer.siri": {
|
||||||
|
name: "Siri",
|
||||||
|
description: "Allows app to handle Siri requests.",
|
||||||
|
icon: "mic-fill"
|
||||||
|
},
|
||||||
|
"com.apple.developer.networking.wifi-info": {
|
||||||
|
name: "Wi-Fi Information Access",
|
||||||
|
description: "Allows app to access information about the connected Wi-Fi network.",
|
||||||
|
icon: "wifi"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const privacy = {
|
||||||
|
"AppleMusic": {
|
||||||
|
icon: "music-note-beamed"
|
||||||
|
},
|
||||||
|
"BluetoothAlways": {
|
||||||
|
name: "Bluetooth",
|
||||||
|
icon: "bluetooth"
|
||||||
|
},
|
||||||
|
"BluetoothPeripheral": {
|
||||||
|
name: "Bluetooth (Peripherals)",
|
||||||
|
icon: "bluetooth"
|
||||||
|
},
|
||||||
|
"Contacts": {
|
||||||
|
icon: "person-circle"
|
||||||
|
},
|
||||||
|
"Camera": {
|
||||||
|
icon: "camera-fill"
|
||||||
|
},
|
||||||
|
"FaceID": {
|
||||||
|
name: "Face ID",
|
||||||
|
icon: "person-bounding-box"
|
||||||
|
},
|
||||||
|
"LocalNetwork": {
|
||||||
|
icon: "globe2"
|
||||||
|
},
|
||||||
|
"LocationWhenInUse": {
|
||||||
|
name: "Location (When Using)",
|
||||||
|
icon: "cursor-fill"
|
||||||
|
},
|
||||||
|
"Microphone": {
|
||||||
|
icon: "mic-fill"
|
||||||
|
},
|
||||||
|
"PhotoLibrary": {
|
||||||
|
name: "Photos",
|
||||||
|
icon: "images"
|
||||||
|
},
|
||||||
|
"PhotoLibraryAdd": {
|
||||||
|
name: "Photos (Add)",
|
||||||
|
icon: "image"
|
||||||
|
},
|
||||||
|
"UserTracking": {
|
||||||
|
icon: "person-vcard-fill"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const legacyPermissions = {
|
||||||
|
"background-audio": {
|
||||||
|
icon: "volume-up-fill"
|
||||||
|
},
|
||||||
|
"background-fetch": {
|
||||||
|
icon: "arrow-repeat"
|
||||||
|
},
|
||||||
|
"photos": {
|
||||||
|
icon: "images"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,15 @@ import { AltStoreBanner } from "./components/AltStoreBanner.js";
|
|||||||
import { NavigationBar } from "./components/NavigationBar.js";
|
import { NavigationBar } from "./components/NavigationBar.js";
|
||||||
import { urlRegex } from "./constants.js";
|
import { urlRegex } from "./constants.js";
|
||||||
|
|
||||||
|
export function insertSpaceInSnakeString(string) {
|
||||||
|
return string.split(".").slice(-1)[0].split("-").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function insertSpaceInCamelString(string) {
|
||||||
|
// https://stackoverflow.com/a/38388188/19227228
|
||||||
|
return string.match(/[A-Z][a-z]+|[0-9]+/g).join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
export function insertAltStoreBanner(sourceName) {
|
export function insertAltStoreBanner(sourceName) {
|
||||||
document.getElementById("top")?.insertAdjacentHTML("afterbegin", AltStoreBanner(sourceName));
|
document.getElementById("top")?.insertAdjacentHTML("afterbegin", AltStoreBanner(sourceName));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user