BTHLABS-50: Safari Web extension
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl> Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
|
@ -61,14 +61,29 @@ jobs:
|
|||
push: false
|
||||
load: true
|
||||
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/packages:ci-local"
|
||||
- name: "Build `extension-ci` image"
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: "services/extension/Dockerfile"
|
||||
context: "services/"
|
||||
target: "ci"
|
||||
push: false
|
||||
load: true
|
||||
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/extension:ci-local"
|
||||
- name: "Run `backend` checks"
|
||||
run: |
|
||||
set -x
|
||||
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm backend-ci inv ci
|
||||
- name: "Run `packages` checks"
|
||||
if: always()
|
||||
run: |
|
||||
set -x
|
||||
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm packages-ci inv ci
|
||||
- name: "Run `extension` checks"
|
||||
if: always()
|
||||
run: |
|
||||
set -x
|
||||
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm extension-ci inv ci
|
||||
- name: "Clean up"
|
||||
if: always()
|
||||
run: |
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"targets": [
|
||||
"backend-management",
|
||||
"caddy",
|
||||
"extension-management",
|
||||
"keycloak",
|
||||
"packages-management",
|
||||
"postgres",
|
||||
|
@ -67,6 +68,28 @@
|
|||
"type=docker,load=true,push=false"
|
||||
]
|
||||
},
|
||||
"extension-management": {
|
||||
"context": "services/",
|
||||
"dockerfile": "extension/Dockerfile",
|
||||
"tags": [
|
||||
"docker-hosted.nexus.bthlabs.pl/hotpocket/extension:local"
|
||||
],
|
||||
"target": "development",
|
||||
"output": [
|
||||
"type=docker,load=true,push=false"
|
||||
]
|
||||
},
|
||||
"extension-ci": {
|
||||
"context": "services/",
|
||||
"dockerfile": "extension/Dockerfile",
|
||||
"tags": [
|
||||
"docker-hosted.nexus.bthlabs.pl/hotpocket/extension:ci-local"
|
||||
],
|
||||
"target": "ci",
|
||||
"output": [
|
||||
"type=docker,load=true,push=false"
|
||||
]
|
||||
},
|
||||
"caddy": {
|
||||
"context": "services/",
|
||||
"dockerfile": "caddy/Dockerfile",
|
||||
|
|
|
@ -13,3 +13,4 @@ services:
|
|||
include:
|
||||
- path: "./services/backend/docker-compose-ci.yaml"
|
||||
- path: "./services/packages/docker-compose-ci.yaml"
|
||||
- path: "./services/extension/docker-compose-ci.yaml"
|
||||
|
|
|
@ -5,6 +5,7 @@ include:
|
|||
- path: "./docker-compose-cloud.yaml"
|
||||
- path: "./services/backend/docker-compose.yaml"
|
||||
- path: "./services/packages/docker-compose.yaml"
|
||||
- path: "./services/extension/docker-compose.yaml"
|
||||
|
||||
volumes: {}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
_tmp/
|
||||
apple/
|
||||
backend/node_modules/
|
||||
backend/ops/metal/
|
||||
backend/hotpocket_backend/playground.py
|
||||
|
@ -7,4 +8,6 @@ backend/hotpocket_backend/settings/docker/
|
|||
backend/hotpocket_backend/secrets/metal/
|
||||
backend/hotpocket_backend/settings/metal/
|
||||
backend/hotpocket_backend/static/
|
||||
extension/node_modules/
|
||||
extension/dist/
|
||||
.envrc*
|
||||
|
|
92
services/apple/.gitignore
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
# Xcode
|
||||
|
||||
## Build generated
|
||||
build/
|
||||
DerivedData/
|
||||
|
||||
## Various settings
|
||||
ExportOptions.plist
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata/
|
||||
|
||||
## Other
|
||||
*.moved-aside
|
||||
*.xccheckout
|
||||
*.xcscmblueprint
|
||||
|
||||
## We don't want any memgrap's to leak ;)
|
||||
**.memgraph
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||
# Packages/
|
||||
# Package.pins
|
||||
# Package.resolved
|
||||
.build/
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
#
|
||||
# Pods/
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||
# *.xcworkspace
|
||||
|
||||
# Carthage
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
# Carthage/Checkouts
|
||||
|
||||
Carthage/Build
|
||||
|
||||
# Accio dependency management
|
||||
Dependencies/
|
||||
.accio/
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||
# screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots/**/*.png
|
||||
fastlane/test_output
|
||||
fastlane/*.env
|
||||
|
||||
# Code Injection
|
||||
#
|
||||
# After new code Injection tools there's a generated folder /iOSInjectionProject
|
||||
# https://github.com/johnno1962/injectionforxcode
|
||||
|
||||
iOSInjectionProject/
|
||||
|
||||
# Extension stuff
|
||||
Shared (Extension)/Resources/_locales/
|
||||
Shared (Extension)/Resources/images/
|
||||
Shared (Extension)/Resources/background-bundle.js
|
||||
Shared (Extension)/Resources/content-bundle.js
|
||||
Shared (Extension)/Resources/manifest.json
|
897
services/apple/HotPocket.xcodeproj/project.pbxproj
Normal file
|
@ -0,0 +1,897 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 77;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
4CABCAD62E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4CABCAD52E56F0C900D8A354 /* HotPocket Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
4CABCAE02E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
4CABCAD72E56F0C900D8A354 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 4CABCA922E56F0C800D8A354 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 4CABCAD42E56F0C900D8A354;
|
||||
remoteInfo = "HotPocket Extension (iOS)";
|
||||
};
|
||||
4CABCAE12E56F0C900D8A354 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 4CABCA922E56F0C800D8A354 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 4CABCADE2E56F0C900D8A354;
|
||||
remoteInfo = "HotPocket Extension (macOS)";
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
4CABCB092E56F0C900D8A354 /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
4CABCAD62E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4CABCB132E56F0C900D8A354 /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
4CABCAE02E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
4CABCAB02E56F0C900D8A354 /* HotPocket.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HotPocket.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4CABCAC62E56F0C900D8A354 /* HotPocket.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HotPocket.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4CABCAD52E56F0C900D8A354 /* HotPocket Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "HotPocket Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "HotPocket Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
4CABCB042E56F0C900D8A354 /* Exceptions for "Shared (App)" folder in "HotPocket (iOS)" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
"/Localized: Resources/Main.html",
|
||||
Assets.xcassets,
|
||||
"Resources/icon-mac-384.png",
|
||||
Resources/Script.js,
|
||||
Resources/Style.css,
|
||||
ViewController.m,
|
||||
);
|
||||
target = 4CABCAAF2E56F0C900D8A354 /* HotPocket (iOS) */;
|
||||
};
|
||||
4CABCB082E56F0C900D8A354 /* Exceptions for "iOS (Extension)" folder in "HotPocket Extension (iOS)" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = 4CABCAD42E56F0C900D8A354 /* HotPocket Extension (iOS) */;
|
||||
};
|
||||
4CABCB0D2E56F0C900D8A354 /* Exceptions for "iOS (App)" folder in "HotPocket (iOS)" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = 4CABCAAF2E56F0C900D8A354 /* HotPocket (iOS) */;
|
||||
};
|
||||
4CABCB0E2E56F0C900D8A354 /* Exceptions for "Shared (App)" folder in "HotPocket (macOS)" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
"/Localized: Resources/Main.html",
|
||||
Assets.xcassets,
|
||||
"Resources/icon-mac-384.png",
|
||||
Resources/Script.js,
|
||||
Resources/Style.css,
|
||||
ViewController.m,
|
||||
);
|
||||
target = 4CABCAC52E56F0C900D8A354 /* HotPocket (macOS) */;
|
||||
};
|
||||
4CABCB122E56F0C900D8A354 /* Exceptions for "macOS (Extension)" folder in "HotPocket Extension (macOS)" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = 4CABCADE2E56F0C900D8A354 /* HotPocket Extension (macOS) */;
|
||||
};
|
||||
4CABCB172E56F0C900D8A354 /* Exceptions for "Shared (Extension)" folder in "HotPocket Extension (iOS)" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Resources/_locales,
|
||||
"Resources/background-bundle.js",
|
||||
"Resources/content-bundle.js",
|
||||
Resources/images,
|
||||
Resources/manifest.json,
|
||||
SafariWebExtensionHandler.m,
|
||||
);
|
||||
target = 4CABCAD42E56F0C900D8A354 /* HotPocket Extension (iOS) */;
|
||||
};
|
||||
4CABCB182E56F0C900D8A354 /* Exceptions for "Shared (Extension)" folder in "HotPocket Extension (macOS)" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Resources/_locales,
|
||||
"Resources/background-bundle.js",
|
||||
"Resources/content-bundle.js",
|
||||
Resources/images,
|
||||
Resources/manifest.json,
|
||||
SafariWebExtensionHandler.m,
|
||||
);
|
||||
target = 4CABCADE2E56F0C900D8A354 /* HotPocket Extension (macOS) */;
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
4CABCA962E56F0C800D8A354 /* Shared (App) */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
4CABCB042E56F0C900D8A354 /* Exceptions for "Shared (App)" folder in "HotPocket (iOS)" target */,
|
||||
4CABCB0E2E56F0C900D8A354 /* Exceptions for "Shared (App)" folder in "HotPocket (macOS)" target */,
|
||||
);
|
||||
path = "Shared (App)";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CABCAA02E56F0C900D8A354 /* Shared (Extension) */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
4CABCB172E56F0C900D8A354 /* Exceptions for "Shared (Extension)" folder in "HotPocket Extension (iOS)" target */,
|
||||
4CABCB182E56F0C900D8A354 /* Exceptions for "Shared (Extension)" folder in "HotPocket Extension (macOS)" target */,
|
||||
);
|
||||
explicitFolders = (
|
||||
Resources/_locales,
|
||||
Resources/images,
|
||||
);
|
||||
path = "Shared (Extension)";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CABCAB22E56F0C900D8A354 /* iOS (App) */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
4CABCB0D2E56F0C900D8A354 /* Exceptions for "iOS (App)" folder in "HotPocket (iOS)" target */,
|
||||
);
|
||||
path = "iOS (App)";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CABCAC72E56F0C900D8A354 /* macOS (App) */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = "macOS (App)";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CABCAD92E56F0C900D8A354 /* iOS (Extension) */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
4CABCB082E56F0C900D8A354 /* Exceptions for "iOS (Extension)" folder in "HotPocket Extension (iOS)" target */,
|
||||
);
|
||||
path = "iOS (Extension)";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CABCAE32E56F0C900D8A354 /* macOS (Extension) */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
4CABCB122E56F0C900D8A354 /* Exceptions for "macOS (Extension)" folder in "HotPocket Extension (macOS)" target */,
|
||||
);
|
||||
path = "macOS (Extension)";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
4CABCAAD2E56F0C900D8A354 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4CABCAC32E56F0C900D8A354 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4CABCAD22E56F0C900D8A354 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4CABCADC2E56F0C900D8A354 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
4CABCA912E56F0C800D8A354 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CABCA962E56F0C800D8A354 /* Shared (App) */,
|
||||
4CABCAA02E56F0C900D8A354 /* Shared (Extension) */,
|
||||
4CABCAB22E56F0C900D8A354 /* iOS (App) */,
|
||||
4CABCAC72E56F0C900D8A354 /* macOS (App) */,
|
||||
4CABCAD92E56F0C900D8A354 /* iOS (Extension) */,
|
||||
4CABCAE32E56F0C900D8A354 /* macOS (Extension) */,
|
||||
4CABCAB12E56F0C900D8A354 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CABCAB12E56F0C900D8A354 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CABCAB02E56F0C900D8A354 /* HotPocket.app */,
|
||||
4CABCAC62E56F0C900D8A354 /* HotPocket.app */,
|
||||
4CABCAD52E56F0C900D8A354 /* HotPocket Extension.appex */,
|
||||
4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
4CABCAAF2E56F0C900D8A354 /* HotPocket (iOS) */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 4CABCB0A2E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket (iOS)" */;
|
||||
buildPhases = (
|
||||
4CABCAAC2E56F0C900D8A354 /* Sources */,
|
||||
4CABCAAD2E56F0C900D8A354 /* Frameworks */,
|
||||
4CABCAAE2E56F0C900D8A354 /* Resources */,
|
||||
4CABCB092E56F0C900D8A354 /* Embed Foundation Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
4CABCAD82E56F0C900D8A354 /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
4CABCAB22E56F0C900D8A354 /* iOS (App) */,
|
||||
);
|
||||
name = "HotPocket (iOS)";
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = "HotPocket (iOS)";
|
||||
productReference = 4CABCAB02E56F0C900D8A354 /* HotPocket.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
4CABCAC52E56F0C900D8A354 /* HotPocket (macOS) */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 4CABCB142E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket (macOS)" */;
|
||||
buildPhases = (
|
||||
4CABCAC22E56F0C900D8A354 /* Sources */,
|
||||
4CABCAC32E56F0C900D8A354 /* Frameworks */,
|
||||
4CABCAC42E56F0C900D8A354 /* Resources */,
|
||||
4CABCB132E56F0C900D8A354 /* Embed Foundation Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
4CABCAE22E56F0C900D8A354 /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
4CABCAC72E56F0C900D8A354 /* macOS (App) */,
|
||||
);
|
||||
name = "HotPocket (macOS)";
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = "HotPocket (macOS)";
|
||||
productReference = 4CABCAC62E56F0C900D8A354 /* HotPocket.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
4CABCAD42E56F0C900D8A354 /* HotPocket Extension (iOS) */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 4CABCB052E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket Extension (iOS)" */;
|
||||
buildPhases = (
|
||||
4CABCAD12E56F0C900D8A354 /* Sources */,
|
||||
4CABCAD22E56F0C900D8A354 /* Frameworks */,
|
||||
4CABCAD32E56F0C900D8A354 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
4CABCAD92E56F0C900D8A354 /* iOS (Extension) */,
|
||||
);
|
||||
name = "HotPocket Extension (iOS)";
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = "HotPocket Extension (iOS)";
|
||||
productReference = 4CABCAD52E56F0C900D8A354 /* HotPocket Extension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
4CABCADE2E56F0C900D8A354 /* HotPocket Extension (macOS) */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 4CABCB0F2E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket Extension (macOS)" */;
|
||||
buildPhases = (
|
||||
4CABCADB2E56F0C900D8A354 /* Sources */,
|
||||
4CABCADC2E56F0C900D8A354 /* Frameworks */,
|
||||
4CABCADD2E56F0C900D8A354 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
4CABCAE32E56F0C900D8A354 /* macOS (Extension) */,
|
||||
);
|
||||
name = "HotPocket Extension (macOS)";
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = "HotPocket Extension (macOS)";
|
||||
productReference = 4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
4CABCA922E56F0C800D8A354 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastUpgradeCheck = 1640;
|
||||
TargetAttributes = {
|
||||
4CABCAAF2E56F0C900D8A354 = {
|
||||
CreatedOnToolsVersion = 16.4;
|
||||
};
|
||||
4CABCAC52E56F0C900D8A354 = {
|
||||
CreatedOnToolsVersion = 16.4;
|
||||
};
|
||||
4CABCAD42E56F0C900D8A354 = {
|
||||
CreatedOnToolsVersion = 16.4;
|
||||
};
|
||||
4CABCADE2E56F0C900D8A354 = {
|
||||
CreatedOnToolsVersion = 16.4;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 4CABCA952E56F0C800D8A354 /* Build configuration list for PBXProject "HotPocket" */;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 4CABCA912E56F0C800D8A354;
|
||||
minimizedProjectReferenceProxies = 1;
|
||||
preferredProjectObjectVersion = 77;
|
||||
productRefGroup = 4CABCAB12E56F0C900D8A354 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
4CABCAAF2E56F0C900D8A354 /* HotPocket (iOS) */,
|
||||
4CABCAC52E56F0C900D8A354 /* HotPocket (macOS) */,
|
||||
4CABCAD42E56F0C900D8A354 /* HotPocket Extension (iOS) */,
|
||||
4CABCADE2E56F0C900D8A354 /* HotPocket Extension (macOS) */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
4CABCAAE2E56F0C900D8A354 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4CABCAC42E56F0C900D8A354 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4CABCAD32E56F0C900D8A354 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4CABCADD2E56F0C900D8A354 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
4CABCAAC2E56F0C900D8A354 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4CABCAC22E56F0C900D8A354 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4CABCAD12E56F0C900D8A354 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4CABCADB2E56F0C900D8A354 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
4CABCAD82E56F0C900D8A354 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 4CABCAD42E56F0C900D8A354 /* HotPocket Extension (iOS) */;
|
||||
targetProxy = 4CABCAD72E56F0C900D8A354 /* PBXContainerItemProxy */;
|
||||
};
|
||||
4CABCAE22E56F0C900D8A354 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 4CABCADE2E56F0C900D8A354 /* HotPocket Extension (macOS) */;
|
||||
targetProxy = 4CABCAE12E56F0C900D8A354 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
4CABCB062E56F0C900D8A354 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.HotPocket.iOS.Extension;
|
||||
PRODUCT_NAME = "HotPocket Extension";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
4CABCB072E56F0C900D8A354 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.HotPocket.iOS.Extension;
|
||||
PRODUCT_NAME = "HotPocket Extension";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
4CABCB0B2E56F0C900D8A354 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "iOS (App)/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
"-framework",
|
||||
WebKit,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.iOS;
|
||||
PRODUCT_NAME = HotPocket;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
4CABCB0C2E56F0C900D8A354 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "iOS (App)/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
"-framework",
|
||||
WebKit,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.iOS;
|
||||
PRODUCT_NAME = HotPocket;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
4CABCB102E56F0C900D8A354 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "macOS (Extension)/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.HotPocket.Extension;
|
||||
PRODUCT_NAME = "HotPocket Extension";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
4CABCB112E56F0C900D8A354 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "macOS (Extension)/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.HotPocket.Extension;
|
||||
PRODUCT_NAME = "HotPocket Extension";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
4CABCB152E56F0C900D8A354 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_NSMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
"-framework",
|
||||
WebKit,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.macOS;
|
||||
PRODUCT_NAME = HotPocket;
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
4CABCB162E56F0C900D8A354 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_NSMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
"-framework",
|
||||
WebKit,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.macOS;
|
||||
PRODUCT_NAME = HotPocket;
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
4CABCB192E56F0C900D8A354 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
4CABCB1A2E56F0C900D8A354 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
4CABCA952E56F0C800D8A354 /* Build configuration list for PBXProject "HotPocket" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
4CABCB192E56F0C900D8A354 /* Debug */,
|
||||
4CABCB1A2E56F0C900D8A354 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
4CABCB052E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket Extension (iOS)" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
4CABCB062E56F0C900D8A354 /* Debug */,
|
||||
4CABCB072E56F0C900D8A354 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
4CABCB0A2E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket (iOS)" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
4CABCB0B2E56F0C900D8A354 /* Debug */,
|
||||
4CABCB0C2E56F0C900D8A354 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
4CABCB0F2E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket Extension (macOS)" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
4CABCB102E56F0C900D8A354 /* Debug */,
|
||||
4CABCB112E56F0C900D8A354 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
4CABCB142E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket (macOS)" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
4CABCB152E56F0C900D8A354 /* Debug */,
|
||||
4CABCB162E56F0C900D8A354 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 4CABCA922E56F0C800D8A354 /* Project object */;
|
||||
}
|
7
services/apple/HotPocket.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xED",
|
||||
"green" : "0xBA",
|
||||
"red" : "0x1C"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon-1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "icon-1024 1.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "tinted"
|
||||
}
|
||||
],
|
||||
"filename" : "icon-1024 2.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-16.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-32 1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-64.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-mac-1024.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 115 KiB |
After Width: | Height: | Size: 115 KiB |
After Width: | Height: | Size: 115 KiB |
After Width: | Height: | Size: 874 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 117 KiB |
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x29",
|
||||
"green" : "0x25",
|
||||
"red" : "0x21"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
23
services/apple/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon-large-128.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-large-128@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-large-128@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
services/apple/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon-large-128.png
vendored
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
services/apple/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon-large-128@2x.png
vendored
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
services/apple/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon-large-128@3x.png
vendored
Normal file
After Width: | Height: | Size: 63 KiB |
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xDD",
|
||||
"green" : "0x82",
|
||||
"red" : "0x00"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
20
services/apple/Shared (App)/Resources/Base.lproj/Main.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
|
||||
<link rel="stylesheet" href="../Style.css">
|
||||
<script src="../Script.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<img src="../icon-mac-384.png" width="128" height="128" alt="HotPocket Icon">
|
||||
<p class="platform-ios">You can turn on Save to Hotpocket Safari extension in Settings.</p>
|
||||
<p class="platform-mac state-unknown">You can turn on Save to Hotpocket extension in Safari Extensions preferences.</p>
|
||||
<p class="platform-mac state-on">Save to Hotpocket extension is currently on. You can turn it off in Safari Extensions preferences.</p>
|
||||
<p class="platform-mac state-off">Save to Hotpocket extension is currently off. You can turn it on in Safari Extensions preferences.</p>
|
||||
<button class="platform-mac open-preferences">Quit and Open Safari Extensions Preferences…</button>
|
||||
</body>
|
||||
</html>
|
24
services/apple/Shared (App)/Resources/Script.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
function show(platform, enabled, useSettingsInsteadOfPreferences) {
|
||||
document.body.classList.add(`platform-${platform}`);
|
||||
|
||||
if (useSettingsInsteadOfPreferences) {
|
||||
document.getElementsByClassName('platform-mac state-on')[0].innerText = "Save to Hotpocket extension is currently on. You can turn it off in the Extensions section of Safari Settings.";
|
||||
document.getElementsByClassName('platform-mac state-off')[0].innerText = "Save to Hotpocket extension is currently off. You can turn it on in the Extensions section of Safari Settings.";
|
||||
document.getElementsByClassName('platform-mac state-unknown')[0].innerText = "You can turn on Save to Hotpocket extension in the Extensions section of Safari Settings.";
|
||||
document.getElementsByClassName('platform-mac open-preferences')[0].innerText = "Quit and Open Safari Settings…";
|
||||
}
|
||||
|
||||
if (typeof enabled === "boolean") {
|
||||
document.body.classList.toggle(`state-on`, enabled);
|
||||
document.body.classList.toggle(`state-off`, !enabled);
|
||||
} else {
|
||||
document.body.classList.remove(`state-on`);
|
||||
document.body.classList.remove(`state-off`);
|
||||
}
|
||||
}
|
||||
|
||||
function openPreferences() {
|
||||
webkit.messageHandlers.controller.postMessage("open-preferences");
|
||||
}
|
||||
|
||||
document.querySelector("button.open-preferences").addEventListener("click", openPreferences);
|
63
services/apple/Shared (App)/Resources/Style.css
Normal file
|
@ -0,0 +1,63 @@
|
|||
* {
|
||||
-webkit-user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
|
||||
--spacing: 20px;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #212529;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
gap: var(--spacing);
|
||||
margin: 0 calc(var(--spacing) * 2);
|
||||
height: 100%;
|
||||
|
||||
font: -apple-system-short-body;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
body:not(.platform-mac, .platform-ios) :is(.platform-mac, .platform-ios) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.platform-ios .platform-mac {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.platform-mac .platform-ios {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.platform-ios .platform-mac {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body:not(.state-on, .state-off) :is(.state-on, .state-off) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.state-on :is(.state-off, .state-unknown) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.state-off :is(.state-on, .state-unknown) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 1em;
|
||||
}
|
BIN
services/apple/Shared (App)/Resources/icon-mac-384.png
Normal file
After Width: | Height: | Size: 38 KiB |
26
services/apple/Shared (App)/ViewController.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// ViewController.h
|
||||
// Shared (App)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
typedef UIViewController PlatformViewController;
|
||||
|
||||
#elif TARGET_OS_OSX
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
typedef NSViewController PlatformViewController;
|
||||
|
||||
#endif
|
||||
|
||||
@interface ViewController : PlatformViewController
|
||||
|
||||
@end
|
76
services/apple/Shared (App)/ViewController.m
Normal file
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// ViewController.m
|
||||
// Shared (App)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import "ViewController.h"
|
||||
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
#import <SafariServices/SafariServices.h>
|
||||
#endif
|
||||
|
||||
static NSString * const extensionBundleIdentifier = @"pl.bthlabs.HotPocket.HotPocket.Extension";
|
||||
|
||||
@interface ViewController () <WKNavigationDelegate, WKScriptMessageHandler>
|
||||
|
||||
@property (nonatomic) IBOutlet WKWebView *webView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
_webView.navigationDelegate = self;
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
_webView.scrollView.scrollEnabled = NO;
|
||||
#endif
|
||||
|
||||
[_webView.configuration.userContentController addScriptMessageHandler:self name:@"controller"];
|
||||
|
||||
[_webView loadFileURL:[NSBundle.mainBundle URLForResource:@"Main" withExtension:@"html"] allowingReadAccessToURL:NSBundle.mainBundle.resourceURL];
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
|
||||
#if TARGET_OS_IOS
|
||||
[webView evaluateJavaScript:@"show('ios')" completionHandler:nil];
|
||||
#elif TARGET_OS_OSX
|
||||
[webView evaluateJavaScript:@"show('mac')" completionHandler:nil];
|
||||
|
||||
[SFSafariExtensionManager getStateOfSafariExtensionWithIdentifier:extensionBundleIdentifier completionHandler:^(SFSafariExtensionState *state, NSError *error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (!state) {
|
||||
// Insert code to inform the user something went wrong.
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *isExtensionEnabledAsString = state.isEnabled ? @"true" : @"false";
|
||||
if (@available(macOS 13, *))
|
||||
[webView evaluateJavaScript:[NSString stringWithFormat:@"show('mac', %@, true)", isExtensionEnabledAsString] completionHandler:nil];
|
||||
else
|
||||
[webView evaluateJavaScript:[NSString stringWithFormat:@"show('mac', %@, false)", isExtensionEnabledAsString] completionHandler:nil];
|
||||
});
|
||||
}];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
|
||||
#if TARGET_OS_OSX
|
||||
if (![message.body isEqualToString:@"open-preferences"])
|
||||
return;
|
||||
|
||||
[SFSafariApplication showPreferencesForExtensionWithIdentifier:extensionBundleIdentifier completionHandler:^(NSError *error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[NSApp terminate:self];
|
||||
});
|
||||
}];
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// SafariWebExtensionHandler.h
|
||||
// Shared (Extension)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface SafariWebExtensionHandler : NSObject <NSExtensionRequestHandling>
|
||||
|
||||
@end
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// SafariWebExtensionHandler.m
|
||||
// Shared (Extension)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import "SafariWebExtensionHandler.h"
|
||||
|
||||
#import <SafariServices/SafariServices.h>
|
||||
|
||||
@implementation SafariWebExtensionHandler
|
||||
|
||||
- (void)beginRequestWithExtensionContext:(NSExtensionContext *)context {
|
||||
NSExtensionItem *request = context.inputItems.firstObject;
|
||||
|
||||
NSUUID *profile;
|
||||
if (@available(iOS 17.0, macOS 14.0, *)) {
|
||||
profile = request.userInfo[SFExtensionProfileKey];
|
||||
} else {
|
||||
profile = request.userInfo[@"profile"];
|
||||
}
|
||||
|
||||
id message;
|
||||
if (@available(iOS 15.0, macOS 11.0, *)) {
|
||||
message = request.userInfo[SFExtensionMessageKey];
|
||||
} else {
|
||||
message = request.userInfo[@"message"];
|
||||
}
|
||||
|
||||
NSLog(@"Received message from browser.runtime.sendNativeMessage: %@ (profile: %@)", message, profile.UUIDString ?: @"none");
|
||||
|
||||
NSExtensionItem *response = [[NSExtensionItem alloc] init];
|
||||
if (@available(iOS 15.0, macOS 11.0, *)) {
|
||||
response.userInfo = @{ SFExtensionMessageKey: @{ @"echo": message } };
|
||||
} else {
|
||||
response.userInfo = @{ @"message": @{ @"echo": message } };
|
||||
}
|
||||
|
||||
[context completeRequestReturningItems:@[ response ] completionHandler:nil];
|
||||
}
|
||||
|
||||
@end
|
12
services/apple/iOS (App)/AppDelegate.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// AppDelegate.h
|
||||
// iOS (App)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
@end
|
21
services/apple/iOS (App)/AppDelegate.m
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// AppDelegate.m
|
||||
// iOS (App)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
// Override point for customization after application launch.
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
|
||||
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
|
||||
}
|
||||
|
||||
@end
|
41
services/apple/iOS (App)/Base.lproj/LaunchScreen.storyboard
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
|
||||
<capability name="Image references" minToolsVersion="12.0"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6HG-Um-bch">
|
||||
<rect key="frame" x="131" y="363" width="128" height="128"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<imageReference key="image" image="LargeIcon"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<color key="backgroundColor" name="BackgroundColor"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LargeIcon" width="128" height="128"/>
|
||||
<namedColor name="BackgroundColor">
|
||||
<color red="0.12941176470588237" green="0.14509803921568629" blue="0.16078431372549021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
</resources>
|
||||
</document>
|
46
services/apple/iOS (App)/Base.lproj/Main.storyboard
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<wkWebView contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RDB-ib-igF">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" name="BackgroundColor"/>
|
||||
<wkWebViewConfiguration key="configuration">
|
||||
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
|
||||
<wkPreferences key="preferences"/>
|
||||
</wkWebViewConfiguration>
|
||||
</wkWebView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<color key="backgroundColor" name="BackgroundColor"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="webView" destination="RDB-ib-igF" id="avx-RC-qRB"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<namedColor name="BackgroundColor">
|
||||
<color red="0.12941176470588237" green="0.14509803921568629" blue="0.16078431372549021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
</resources>
|
||||
</document>
|
25
services/apple/iOS (App)/Info.plist
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
14
services/apple/iOS (App)/SceneDelegate.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// SceneDelegate.h
|
||||
// iOS (App)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
|
||||
|
||||
@property (strong, nonatomic) UIWindow * window;
|
||||
|
||||
@end
|
12
services/apple/iOS (App)/SceneDelegate.m
Normal file
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// SceneDelegate.m
|
||||
// iOS (App)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import "SceneDelegate.h"
|
||||
|
||||
@implementation SceneDelegate
|
||||
|
||||
@end
|
18
services/apple/iOS (App)/main.m
Normal file
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// main.m
|
||||
// iOS (App)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
NSString *appDelegateClassName;
|
||||
@autoreleasepool {
|
||||
// Setup code that might create autoreleased objects goes here.
|
||||
appDelegateClassName = NSStringFromClass([AppDelegate class]);
|
||||
}
|
||||
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
|
||||
}
|
13
services/apple/iOS (Extension)/Info.plist
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.Safari.web-extension</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>SafariWebExtensionHandler</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
12
services/apple/macOS (App)/AppDelegate.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// AppDelegate.h
|
||||
// macOS (App)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||
|
||||
@end
|
20
services/apple/macOS (App)/AppDelegate.m
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// AppDelegate.m
|
||||
// macOS (App)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
|
||||
// Override point for customization after application launch.
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
124
services/apple/macOS (App)/Base.lproj/Main.storyboard
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23727"/>
|
||||
<plugIn identifier="com.apple.WebKit2IBPlugin" version="23727"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Application-->
|
||||
<scene sceneID="JPo-4y-FX3">
|
||||
<objects>
|
||||
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
||||
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
<menuItem title="HotPocket" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="HotPocket" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About HotPocket" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||
<menuItem title="Hide HotPocket" keyEquivalent="h" id="Olw-nP-bQN">
|
||||
<connections>
|
||||
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show All" id="Kd2-mp-pUS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||
<menuItem title="Quit HotPocket" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<connections>
|
||||
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Help" id="wpr-3q-Mcd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
|
||||
<items>
|
||||
<menuItem title="HotPocket Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
||||
<connections>
|
||||
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
||||
</connections>
|
||||
</application>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="76" y="-134"/>
|
||||
</scene>
|
||||
<!--Window Controller-->
|
||||
<scene sceneID="R2V-B0-nI4">
|
||||
<objects>
|
||||
<windowController showSeguePresentationStyle="single" id="B8D-0N-5wS" sceneMemberID="viewController">
|
||||
<window key="window" title="HotPocket" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" animationBehavior="default" id="IQv-IB-iLA">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||
<windowCollectionBehavior key="collectionBehavior" fullScreenNone="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="425" height="325"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="B8D-0N-5wS" id="98r-iN-zZc"/>
|
||||
</connections>
|
||||
</window>
|
||||
<connections>
|
||||
<segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
|
||||
</connections>
|
||||
</windowController>
|
||||
<customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="75" y="250"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="hIz-AP-VOD">
|
||||
<objects>
|
||||
<viewController id="XfG-lQ-9wD" customClass="ViewController" sceneMemberID="viewController">
|
||||
<view key="view" appearanceType="darkAqua" id="m2S-Jp-Qdl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<wkWebView wantsLayer="YES" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="eOr-cG-IQY">
|
||||
<rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<wkWebViewConfiguration key="configuration">
|
||||
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
|
||||
<wkPreferences key="preferences"/>
|
||||
</wkWebViewConfiguration>
|
||||
</wkWebView>
|
||||
</subviews>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="webView" destination="eOr-cG-IQY" id="GFe-mU-dBY"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="75" y="655"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
12
services/apple/macOS (App)/HotPocket.entitlements
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
15
services/apple/macOS (App)/main.m
Normal file
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// main.m
|
||||
// macOS (App)
|
||||
//
|
||||
// Created by Tomek Wójcik on 21/08/2025.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
@autoreleasepool {
|
||||
// Setup code that might create autoreleased objects goes here.
|
||||
}
|
||||
return NSApplicationMain(argc, argv);
|
||||
}
|
10
services/apple/macOS (Extension)/HotPocket.entitlements
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
13
services/apple/macOS (Extension)/Info.plist
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.Safari.web-extension</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>SafariWebExtensionHandler</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -64,4 +64,10 @@ export default defineConfig([
|
|||
'no-invalid-this': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
'hotpocket_backend/apps/**/static/**/*.js',
|
||||
'hotpocket_backend/static/**/*.js',
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
from .access_token import AccessToken # noqa: F401
|
||||
from .account import AccountAdmin # noqa: F401
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from hotpocket_backend.apps.accounts.models import AccessToken
|
||||
|
||||
|
||||
class AccessTokenAdmin(admin.ModelAdmin):
|
||||
list_display = ('pk', 'account_uuid', 'origin', 'created_at', 'is_active')
|
||||
search_fields = ('pk', 'account_uuid', 'key', 'origin')
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return request.user.is_superuser
|
||||
|
||||
|
||||
admin.site.register(AccessToken, AccessTokenAdmin)
|
36
services/backend/hotpocket_backend/apps/accounts/backends.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from django.contrib.auth.backends import ModelBackend, UserModel
|
||||
from django.http import HttpRequest
|
||||
|
||||
from hotpocket_backend.apps.accounts.models import AccessToken, Account
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccessTokenBackend(ModelBackend):
|
||||
def authenticate(self,
|
||||
request: HttpRequest,
|
||||
access_token: AccessToken | None,
|
||||
) -> Account | None:
|
||||
if not access_token:
|
||||
return None
|
||||
|
||||
try:
|
||||
user = UserModel.objects.get(pk=access_token.account_uuid)
|
||||
except UserModel.DoesNotExist as exception:
|
||||
LOGGER.error(
|
||||
'Unhandled exception in AccessToken auth: %s',
|
||||
exception,
|
||||
exc_info=exception,
|
||||
)
|
||||
|
||||
if self.user_can_authenticate(user) is False:
|
||||
return None
|
||||
|
||||
request.access_token = access_token
|
||||
return typing.cast(Account, user)
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from django.contrib import auth
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.http import HttpRequest
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
from hotpocket_backend.apps.accounts.models import AccessToken, Account
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccessTokenMiddleware(MiddlewareMixin):
|
||||
def process_request(self, request: HttpRequest):
|
||||
if not hasattr(request, 'user'):
|
||||
raise ImproperlyConfigured('No `AuthenticationMiddleware`?')
|
||||
|
||||
authorization_header = request.headers.get('Authorization', None)
|
||||
if authorization_header is None:
|
||||
return
|
||||
|
||||
try:
|
||||
scheme, authorization = authorization_header.split(' ', maxsplit=1)
|
||||
assert scheme == 'Bearer', (
|
||||
f'Unsupported authorization scheme: `{scheme}`'
|
||||
)
|
||||
|
||||
access_token = AccessToken.active_objects.get(key=authorization)
|
||||
except (ValueError, AssertionError, AccessToken.DoesNotExist, Account.DoesNotExist) as exception:
|
||||
LOGGER.error(
|
||||
'Unhandled exception in AccessToken middleware: %s',
|
||||
exception,
|
||||
exc_info=exception,
|
||||
)
|
||||
return
|
||||
|
||||
account = auth.authenticate(request, access_token=access_token)
|
||||
if account:
|
||||
request.user = account
|
|
@ -0,0 +1,31 @@
|
|||
# Generated by Django 5.2.3 on 2025-09-04 18:50
|
||||
|
||||
import uuid6
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0004_alter_account_settings_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AccessToken',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid6.uuid7, editable=False, primary_key=True, serialize=False)),
|
||||
('account_uuid', models.UUIDField(db_index=True, default=None)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('deleted_at', models.DateTimeField(blank=True, db_index=True, default=None, editable=False, null=True)),
|
||||
('key', models.CharField(db_index=True, default=None, editable=False, max_length=128, unique=True)),
|
||||
('origin', models.CharField(db_index=True, default=None)),
|
||||
('meta', models.JSONField(blank=True, default=dict, null=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Access Token',
|
||||
'verbose_name_plural': 'Access Tokens',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -1 +1,2 @@
|
|||
from .access_token import AccessToken # noqa: F401
|
||||
from .account import Account # noqa: F401
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from hotpocket_backend.apps.core.models import Model
|
||||
|
||||
|
||||
class ActiveAccessTokensManager(models.Manager):
|
||||
def get_queryset(self) -> models.QuerySet[AccessToken]:
|
||||
return super().get_queryset().filter(
|
||||
deleted_at__isnull=True,
|
||||
)
|
||||
|
||||
|
||||
class AccessToken(Model):
|
||||
key = models.CharField(
|
||||
blank=False,
|
||||
default=None,
|
||||
null=False,
|
||||
max_length=128,
|
||||
db_index=True,
|
||||
unique=True,
|
||||
editable=False,
|
||||
)
|
||||
origin = models.CharField(
|
||||
blank=False, default=None, null=False, db_index=True,
|
||||
)
|
||||
meta = models.JSONField(blank=True, default=dict, null=True)
|
||||
|
||||
objects = models.Manager()
|
||||
active_objects = ActiveAccessTokensManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Access Token')
|
||||
verbose_name_plural = _('Access Tokens')
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'<AccessToken pk={self.pk} account_uuid={self.account_uuid}>'
|
|
@ -0,0 +1 @@
|
|||
from .access_tokens import AccessTokensService # noqa: F401
|
|
@ -0,0 +1,81 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
import uuid6
|
||||
|
||||
from hotpocket_backend.apps.accounts.models import AccessToken
|
||||
from hotpocket_backend.apps.core.conf import settings
|
||||
from hotpocket_soa.dto.accounts import AccessTokensQuery
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccessTokensService:
|
||||
class AccessTokensServiceError(Exception):
|
||||
pass
|
||||
|
||||
class AccessTokenNotFound(AccessTokensServiceError):
|
||||
pass
|
||||
|
||||
def create(self,
|
||||
*,
|
||||
account_uuid: uuid.UUID,
|
||||
origin: str,
|
||||
meta: dict,
|
||||
) -> AccessToken:
|
||||
pk = uuid6.uuid7()
|
||||
key = hmac.new(
|
||||
settings.SECRET_KEY.encode('ascii'),
|
||||
msg=pk.bytes,
|
||||
digestmod=hashlib.sha256,
|
||||
)
|
||||
|
||||
return AccessToken.objects.create(
|
||||
pk=pk,
|
||||
account_uuid=account_uuid,
|
||||
key=key.hexdigest(),
|
||||
origin=origin,
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
def get(self, *, pk: uuid.UUID) -> AccessToken:
|
||||
try:
|
||||
query_set = AccessToken.active_objects
|
||||
|
||||
return query_set.get(pk=pk)
|
||||
except AccessToken.DoesNotExist as exception:
|
||||
raise self.AccessTokenNotFound(
|
||||
f'Access Token not found: pk=`{pk}`',
|
||||
) from exception
|
||||
|
||||
def search(self,
|
||||
*,
|
||||
query: AccessTokensQuery,
|
||||
offset: int = 0,
|
||||
limit: int = 10,
|
||||
order_by: str = '-pk',
|
||||
) -> models.QuerySet[AccessToken]:
|
||||
filters = [
|
||||
models.Q(account_uuid=query.account_uuid),
|
||||
]
|
||||
|
||||
if query.before is not None:
|
||||
filters.append(models.Q(pk__lt=query.before))
|
||||
|
||||
result = AccessToken.active_objects.\
|
||||
filter(*filters).\
|
||||
order_by(order_by)
|
||||
|
||||
return result[offset:offset + limit]
|
||||
|
||||
def delete(self, *, pk: uuid.UUID) -> bool:
|
||||
access_token = self.get(pk=pk)
|
||||
access_token.soft_delete()
|
||||
|
||||
return True
|
51
services/backend/hotpocket_backend/apps/core/rpc.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from bthlabs_jsonrpc_django import (
|
||||
DjangoExecutor,
|
||||
DjangoJSONRPCSerializer,
|
||||
JSONRPCView as BaseJSONRPCView,
|
||||
)
|
||||
from django.core.exceptions import ValidationError
|
||||
import uuid6
|
||||
|
||||
|
||||
class JSONRPCSerializer(DjangoJSONRPCSerializer):
|
||||
STRING_COERCIBLE_TYPES: typing.Any = (
|
||||
*DjangoJSONRPCSerializer.STRING_COERCIBLE_TYPES,
|
||||
uuid6.UUID,
|
||||
)
|
||||
|
||||
def serialize_value(self, value: typing.Any) -> typing.Any:
|
||||
if isinstance(value, ValidationError):
|
||||
result: typing.Any = None
|
||||
|
||||
if hasattr(value, 'error_dict') is True:
|
||||
result = {}
|
||||
for field, errors in value.error_dict.items():
|
||||
result[field] = [
|
||||
error.code
|
||||
for error
|
||||
in errors
|
||||
]
|
||||
elif hasattr(value, 'error_list') is True:
|
||||
result = [
|
||||
error.code
|
||||
for error in value.error_list
|
||||
]
|
||||
else:
|
||||
result = value.code
|
||||
|
||||
return self.serialize_value(result)
|
||||
|
||||
return super().serialize_value(value)
|
||||
|
||||
|
||||
class Executor(DjangoExecutor):
|
||||
serializer = JSONRPCSerializer
|
||||
|
||||
|
||||
class JSONRPCView(BaseJSONRPCView):
|
||||
executor = Executor
|
|
@ -3,6 +3,8 @@ from __future__ import annotations
|
|||
|
||||
import enum
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class MessageLevelAlertClass(enum.Enum):
|
||||
debug = 'alert-secondary'
|
||||
|
@ -15,3 +17,7 @@ class MessageLevelAlertClass(enum.Enum):
|
|||
class StarUnstarAssociationViewMode(enum.Enum):
|
||||
STAR = 'STAR'
|
||||
UNSTAR = 'UNSTAR'
|
||||
|
||||
|
||||
class UIAccessTokenOriginApp(enum.Enum):
|
||||
SAFARI_WEB_EXTENSION = _('Safari Web Extension')
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from .base import BrowseParams as BaseBrowseParams
|
||||
|
||||
|
||||
class AppsBrowseParams(BaseBrowseParams):
|
||||
pass
|
|
@ -0,0 +1,46 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from crispy_forms.layout import Submit
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from hotpocket_backend.apps.ui.forms.base import ConfirmationMixin, Form
|
||||
|
||||
|
||||
class AppForm(Form):
|
||||
pass
|
||||
|
||||
|
||||
class ConfirmationForm(ConfirmationMixin, AppForm):
|
||||
origin_app = forms.CharField(
|
||||
label=_('App'),
|
||||
required=False,
|
||||
disabled=True,
|
||||
show_hidden_initial=True,
|
||||
)
|
||||
platform = forms.CharField(
|
||||
label=_('Platform'),
|
||||
required=False,
|
||||
disabled=True,
|
||||
show_hidden_initial=True,
|
||||
)
|
||||
version = forms.CharField(
|
||||
label=_('Version'),
|
||||
required=False,
|
||||
disabled=True,
|
||||
show_hidden_initial=True,
|
||||
)
|
||||
|
||||
def get_layout_fields(self) -> list[str]:
|
||||
return [
|
||||
'canhazconfirm',
|
||||
'origin_app',
|
||||
'platform',
|
||||
'version',
|
||||
]
|
||||
|
||||
|
||||
class DeleteForm(ConfirmationForm):
|
||||
def get_submit_button(self) -> Submit:
|
||||
return Submit('submit', _('Delete'), css_class='btn btn-danger')
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from crispy_forms.bootstrap import FormActions
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout, Submit
|
||||
from django.contrib.auth.forms import (
|
||||
AuthenticationForm as BaseAuthenticationForm,
|
||||
)
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class LoginForm(BaseAuthenticationForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.helper = FormHelper(self)
|
||||
self.helper.attrs = {
|
||||
'id': self.__class__.__name__,
|
||||
'novalidate': '',
|
||||
}
|
||||
self.helper.layout = Layout(
|
||||
'username',
|
||||
'password',
|
||||
FormActions(
|
||||
Submit('submit', _('Log in'), css_class='btn btn-primary'),
|
||||
template='ui/ui/forms/formactions.html',
|
||||
css_class='mb-0',
|
||||
),
|
||||
)
|
|
@ -6,32 +6,11 @@ from crispy_forms.helper import FormHelper
|
|||
from crispy_forms.layout import Layout, Submit
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import (
|
||||
AuthenticationForm as BaseAuthenticationForm,
|
||||
PasswordChangeForm as BasePasswordChangeForm,
|
||||
)
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .base import Form
|
||||
|
||||
|
||||
class LoginForm(BaseAuthenticationForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.helper = FormHelper(self)
|
||||
self.helper.attrs = {
|
||||
'id': self.__class__.__name__,
|
||||
'novalidate': '',
|
||||
}
|
||||
self.helper.layout = Layout(
|
||||
'username',
|
||||
'password',
|
||||
FormActions(
|
||||
Submit('submit', _('Log in'), css_class='btn btn-primary'),
|
||||
template='ui/ui/forms/formactions.html',
|
||||
css_class='mb-0',
|
||||
),
|
||||
)
|
||||
from hotpocket_backend.apps.ui.forms.base import Form
|
||||
|
||||
|
||||
class ProfileForm(Form):
|
||||
|
@ -131,17 +110,17 @@ class PasswordForm(BasePasswordChangeForm):
|
|||
|
||||
|
||||
class FederatedPasswordForm(PasswordForm):
|
||||
current_password = forms.CharField(
|
||||
old_password = forms.CharField(
|
||||
label=_('Old password'),
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
new_password = forms.CharField(
|
||||
new_password1 = forms.CharField(
|
||||
label=_('New password'),
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
new_password_again = forms.CharField(
|
||||
new_password2 = forms.CharField(
|
||||
label=_('New password confirmation'),
|
||||
disabled=True,
|
||||
required=False,
|
|
@ -5,19 +5,14 @@ from crispy_forms.layout import Submit
|
|||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .base import Form
|
||||
from .base import ConfirmationMixin, Form
|
||||
|
||||
|
||||
class AssociationForm(Form):
|
||||
pass
|
||||
|
||||
|
||||
class ConfirmationForm(AssociationForm):
|
||||
canhazconfirm = forms.CharField(
|
||||
label='',
|
||||
required=True,
|
||||
widget=forms.HiddenInput,
|
||||
)
|
||||
class ConfirmationForm(ConfirmationMixin, AssociationForm):
|
||||
title = forms.CharField(
|
||||
label=_('Title'),
|
||||
required=False,
|
||||
|
|
|
@ -61,3 +61,11 @@ class Form(forms.Form):
|
|||
template=self.get_form_actions_template(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ConfirmationMixin(forms.Form):
|
||||
canhazconfirm = forms.CharField(
|
||||
label='',
|
||||
required=True,
|
||||
widget=forms.HiddenInput,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
from . import accounts # noqa: F401
|
||||
from . import saves # noqa: F401
|
|
@ -0,0 +1,2 @@
|
|||
from . import access_tokens # noqa: F401
|
||||
from . import auth # noqa: F401
|
|
@ -0,0 +1,43 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from bthlabs_jsonrpc_core import register_method
|
||||
from django import db
|
||||
from django.http import HttpRequest
|
||||
|
||||
from hotpocket_soa.services import AccessTokensService
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@register_method('accounts.access_tokens.create')
|
||||
def create(request: HttpRequest,
|
||||
auth_key: str,
|
||||
meta: dict,
|
||||
) -> str:
|
||||
with db.transaction.atomic():
|
||||
try:
|
||||
assert 'extension_auth_key' in request.session, 'Auth key missing'
|
||||
assert request.session['extension_auth_key'] == auth_key, (
|
||||
'Auth key mismatch'
|
||||
)
|
||||
except AssertionError as exception:
|
||||
LOGGER.error(
|
||||
'Unable to issue access token: %s',
|
||||
exception,
|
||||
exc_info=exception,
|
||||
)
|
||||
raise
|
||||
|
||||
access_token = AccessTokensService().create(
|
||||
account_uuid=request.user.pk,
|
||||
origin=request.META['HTTP_ORIGIN'],
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
request.session.pop('extension_auth_key')
|
||||
request.session.save()
|
||||
|
||||
return access_token.key
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from bthlabs_jsonrpc_core import register_method
|
||||
from django.http import HttpRequest
|
||||
|
||||
|
||||
@register_method('accounts.auth.check')
|
||||
def check(request: HttpRequest) -> bool:
|
||||
return request.user.is_anonymous is False
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from bthlabs_jsonrpc_core import register_method
|
||||
from django import db
|
||||
from django.http import HttpRequest
|
||||
|
||||
from hotpocket_backend.apps.ui.services.workflows import CreateSaveWorkflow
|
||||
from hotpocket_soa.dto.associations import AssociationOut
|
||||
|
||||
|
||||
@register_method(method='saves.create')
|
||||
def create(request: HttpRequest, url: str) -> AssociationOut:
|
||||
with db.transaction.atomic():
|
||||
association = CreateSaveWorkflow().run_rpc(
|
||||
request=request,
|
||||
account=request.user,
|
||||
url=url,
|
||||
)
|
||||
|
||||
return association
|
|
@ -1,3 +1,4 @@
|
|||
from .access_tokens import UIAccessTokensService # noqa: F401
|
||||
from .associations import UIAssociationsService # noqa: F401
|
||||
from .imports import UIImportsService # noqa: F401
|
||||
from .saves import UISavesService # noqa: F401
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
|
||||
from hotpocket_soa.dto.accounts import AccessTokenOut
|
||||
from hotpocket_soa.services import AccessTokensService
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UIAccessTokensService:
|
||||
def __init__(self):
|
||||
self.access_tokens_service = AccessTokensService()
|
||||
|
||||
def get_or_404(self,
|
||||
*,
|
||||
account_uuid: uuid.UUID,
|
||||
pk: uuid.UUID,
|
||||
) -> AccessTokenOut:
|
||||
try:
|
||||
return AccessTokensService().get(
|
||||
account_uuid=account_uuid,
|
||||
pk=pk,
|
||||
)
|
||||
except AccessTokensService.AccessTokenNotFound as exception:
|
||||
LOGGER.error(
|
||||
'Access Token not found: account_uuid=`%s` pk=`%s`',
|
||||
account_uuid,
|
||||
pk,
|
||||
exc_info=exception,
|
||||
)
|
||||
raise Http404()
|
||||
except AccessTokensService.AccessTokenAccessDenied as exception:
|
||||
LOGGER.error(
|
||||
'Access Token access denied: account_uuid=`%s` pk=`%s`',
|
||||
account_uuid,
|
||||
pk,
|
||||
exc_info=exception,
|
||||
)
|
||||
raise PermissionDenied()
|
|
@ -1,7 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from bthlabs_jsonrpc_core import JSONRPCInternalError
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import ValidationError
|
||||
import django.db
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
|
@ -9,19 +11,19 @@ from django.urls import reverse
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from hotpocket_backend.apps.accounts.types import PAccount
|
||||
from hotpocket_soa.dto.saves import SaveIn
|
||||
from hotpocket_soa.dto.associations import AssociationOut
|
||||
from hotpocket_soa.dto.celery import AsyncResultOut
|
||||
from hotpocket_soa.dto.saves import SaveIn, SaveOut
|
||||
from hotpocket_soa.services import SavesService
|
||||
|
||||
from .base import SaveWorkflow
|
||||
|
||||
|
||||
class CreateSaveWorkflow(SaveWorkflow):
|
||||
def run(self,
|
||||
*,
|
||||
request: HttpRequest,
|
||||
def create_associate_and_process(self,
|
||||
account: PAccount,
|
||||
url: str,
|
||||
force_post_save: bool = False,
|
||||
) -> HttpResponse:
|
||||
) -> tuple[SaveOut, AssociationOut, AsyncResultOut | None]:
|
||||
with django.db.transaction.atomic():
|
||||
save = self.create(
|
||||
account.pk,
|
||||
|
@ -30,6 +32,23 @@ class CreateSaveWorkflow(SaveWorkflow):
|
|||
|
||||
association = self.associate(account.pk, save)
|
||||
|
||||
processing_result: AsyncResultOut | None = None
|
||||
if save.last_processed_at is None:
|
||||
processing_result = self.schedule_processing(save)
|
||||
|
||||
return (save, association, processing_result)
|
||||
|
||||
def run(self,
|
||||
*,
|
||||
request: HttpRequest,
|
||||
account: PAccount,
|
||||
url: str,
|
||||
force_post_save: bool = False,
|
||||
) -> HttpResponse:
|
||||
save, association, processing_result = self.create_associate_and_process(
|
||||
account, url,
|
||||
)
|
||||
|
||||
response = redirect(reverse('ui.associations.browse'))
|
||||
if force_post_save is True or save.is_netloc_banned is True:
|
||||
response = redirect(reverse(
|
||||
|
@ -46,7 +65,22 @@ class CreateSaveWorkflow(SaveWorkflow):
|
|||
response.headers['X-HotPocket-Testing-Save-PK'] = save.pk
|
||||
response.headers['X-HotPocket-Testing-Association-PK'] = association.pk
|
||||
|
||||
if save.last_processed_at is None:
|
||||
processing_result = self.schedule_processing(save) # noqa: F841
|
||||
|
||||
return response
|
||||
|
||||
def run_rpc(self,
|
||||
*,
|
||||
request: HttpRequest,
|
||||
account: PAccount,
|
||||
url: str,
|
||||
) -> AssociationOut:
|
||||
try:
|
||||
save, association, processing_result = self.create_associate_and_process(
|
||||
account, url,
|
||||
)
|
||||
|
||||
return association
|
||||
except SavesService.SavesServiceError as exception:
|
||||
if isinstance(exception.__cause__, ValidationError) is True:
|
||||
raise JSONRPCInternalError(data=exception.__cause__)
|
||||
|
||||
raise
|
||||
|
|
|
@ -36,7 +36,7 @@ body:not(.ui-js-enabled) .ui-noscript-hide {
|
|||
}
|
||||
|
||||
#navbar .ui-navbar-brand > img {
|
||||
border-radius: 0.25rem;
|
||||
border-radius: 20%;
|
||||
height: 1.5rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
@ -45,11 +45,11 @@ body:not(.ui-js-enabled) .ui-noscript-hide {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.ui-save-card .card-footer .spinner-border {
|
||||
.spinner-border.ui-htmx-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ui-save-card .card-footer .spinner-border.htmx-request {
|
||||
.spinner-border.ui-htmx-indicator.htmx-request {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 874 B |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 51 KiB |
|
@ -0,0 +1,63 @@
|
|||
/*!
|
||||
* HotPocket by BTHLabs (https://hotpocket.app/)
|
||||
* Copyright 2025-present BTHLabs <contact@bthlabs.pl> (https://bthlabs.pl/)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
((HotPocket, htmx) => {
|
||||
class BrowseAccountAppsView {
|
||||
constructor (app) {
|
||||
this.app = app;
|
||||
}
|
||||
onLoad = (event) => {
|
||||
document.addEventListener(
|
||||
'HotPocket:BrowseAccountAppsView:updateLoadMoreButton',
|
||||
this.onUpdateLoadMoreButton,
|
||||
);
|
||||
|
||||
document.addEventListener(
|
||||
'HotPocket:BrowseAccountAppsView:delete',
|
||||
this.onDelete,
|
||||
);
|
||||
};
|
||||
onUpdateLoadMoreButton = (event) => {
|
||||
const button = document.querySelector('#BrowseAccountAppsView .ui-load-more-button');
|
||||
if (button) {
|
||||
if (event.detail.next_url) {
|
||||
button.setAttribute('hx-get', event.detail.next_url);
|
||||
button.classList.remove('disable');
|
||||
button.removeAttribute('disabled', true);
|
||||
} else {
|
||||
button.classList.add('disable');
|
||||
button.setAttribute('disabled', true);
|
||||
}
|
||||
|
||||
htmx.process('#BrowseAccountAppsView .ui-load-more-button');
|
||||
}
|
||||
};
|
||||
onDelete = (event) => {
|
||||
if (event.detail && event.detail.pk) {
|
||||
const elementsToRemove = document.querySelectorAll(
|
||||
`[data-access-token="${event.detail.pk}"]`,
|
||||
);
|
||||
for (let elementToRemove of elementsToRemove) {
|
||||
elementToRemove.remove();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
HotPocket.addPlugin('UI.BrowseAccountAppsView', (app) => {
|
||||
return new BrowseAccountAppsView(app);
|
||||
});
|
||||
})(window.HotPocket, window.htmx);
|
|
@ -20,7 +20,12 @@
|
|||
this.app = app;
|
||||
}
|
||||
onLoad = (event) => {
|
||||
document.addEventListener('HotPocket:BrowseSavesView:updateLoadMoreButton', (event) => {
|
||||
document.addEventListener(
|
||||
'HotPocket:BrowseSavesView:updateLoadMoreButton',
|
||||
this.onUpdateLoadMoreButton,
|
||||
);
|
||||
};
|
||||
onUpdateLoadMoreButton = (event) => {
|
||||
const button = document.querySelector('#BrowseSavesView .ui-load-more-button');
|
||||
if (button) {
|
||||
if (event.detail.next_url) {
|
||||
|
@ -34,7 +39,6 @@
|
|||
|
||||
htmx.process('#BrowseSavesView .ui-load-more-button');
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
{% extends "ui/page.html" %}
|
||||
|
||||
{% load crispy_forms_tags i18n static ui %}
|
||||
|
||||
{% block title %}{% translate 'Apps' %} | {% translate 'Account' %}{% endblock %}
|
||||
|
||||
{% block page_body %}
|
||||
<div id="BrowseAccountAppsView" class="container">
|
||||
<p class="display-6 my-3">{% translate 'Apps' %}</p>
|
||||
|
||||
{% include 'ui/accounts/partials/nav.html' with active_tab='apps' %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{% translate 'App' %}</td>
|
||||
<td>{% translate 'Platform' %}</td>
|
||||
<td>{% translate 'Version' %}</td>
|
||||
<td>{% translate 'Authorized at' %}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="ui-account-apps">
|
||||
{% include 'ui/accounts/partials/apps/apps.html' with access_tokens=access_tokens params=params %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="my-3 text-center {% if not access_tokens and params.before is None %}d-none{% endif %}">
|
||||
<button
|
||||
class="btn btn-primary {% if not before %}disabled{% endif %} ui-noscript-hide ui-load-more-button"
|
||||
hx-get="{{ next_url }}"
|
||||
hx-push-url="true"
|
||||
hx-swap="beforeend"
|
||||
hx-target="#BrowseAccountAppsView .ui-account-apps"
|
||||
>
|
||||
{% translate 'Load more' %}
|
||||
</button>
|
||||
|
||||
<a
|
||||
class="btn btn-primary {% if not before %}disabled{% endif %} ui-noscript-show"
|
||||
href="{{ next_url }}"
|
||||
>
|
||||
{% translate 'Load more' %}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<template id="BrowseAccountAppsView-DeleteModal">
|
||||
<div class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{% translate 'Delete an app authorization?' %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate 'Close' %}"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% include 'ui/accounts/partials/apps/delete_confirmation.html' with alert_class="mb-0" %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% translate 'Cancel' %}</button>
|
||||
<button type="button" class="btn btn-danger" data-ui-modal-action="confirm">
|
||||
{% translate 'Delete' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,17 @@
|
|||
{% extends "ui/page.html" %}
|
||||
|
||||
{% load crispy_forms_tags i18n static ui %}
|
||||
|
||||
{% block title %}{% translate 'Delete an app authorization?' %} | {% translate 'Account' %}{% endblock %}
|
||||
|
||||
{% block page_body %}
|
||||
<div id="BrowseAccountAppsView" class="container">
|
||||
<p class="display-6 my-3">{% translate 'Delete an app authorization?' %}</p>
|
||||
|
||||
{% include 'ui/accounts/partials/nav.html' with active_tab='apps' %}
|
||||
|
||||
{% include 'ui/accounts/partials/apps/delete_confirmation.html' %}
|
||||
|
||||
{% crispy form %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,44 @@
|
|||
{% load i18n static ui %}
|
||||
|
||||
{% for access_token in access_tokens %}
|
||||
<tr data-access-token="{{ access_token.pk }}">
|
||||
<td>{{ access_token|render_access_token_app }}</td>
|
||||
<td>{{ access_token|render_access_token_platform }}</td>
|
||||
<td><code>{{ access_token.meta.version }}</code></td>
|
||||
<td>{{ access_token.created_at }}</td>
|
||||
</tr>
|
||||
<tr data-access-token="{{ access_token.pk }}">
|
||||
<td colspan="4">
|
||||
<div class="d-flex justify-content-end align-items-center">
|
||||
<div class="spinner-border spinner-border-sm ui-htmx-indicator" role="status">
|
||||
<span class="visually-hidden">{% translate 'Processing' %}</span>
|
||||
</div>
|
||||
<a
|
||||
class="btn btn-sm btn-danger ms-2"
|
||||
data-ui-modal="#BrowseAccountAppsView-DeleteModal"
|
||||
hx-confirm='CANHAZCONFIRM'
|
||||
hx-indicator='[data-access-token="{{ access_token.pk }}"] .ui-htmx-indicator'
|
||||
hx-post="{% url 'ui.accounts.apps.delete' pk=access_token.pk %}"
|
||||
hx-swap="delete"
|
||||
hx-target='[data-access-token="{{ access_token.pk }}"]'
|
||||
hx-vars='{"canhazconfirm":true}'
|
||||
href="{% url 'ui.accounts.apps.delete' pk=access_token.pk %}"
|
||||
>
|
||||
<i class="bi bi-trash3-fill"></i> {% translate 'Delete' %}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
{% if not HTMX %}
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
{% if params.before is None %}
|
||||
<strong>{% translate "You haven't authorized any apps yet." %}</strong>
|
||||
{% else %}
|
||||
<span>{% translate "You've reached the end of the line." %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
|
@ -0,0 +1,7 @@
|
|||
{% load i18n static ui %}
|
||||
|
||||
<div class="alert alert-danger {{ alert_class|default_if_none:'' }}">
|
||||
<h4 class="alert-heading">{% translate 'Point of no return' %}</h4>
|
||||
<p class="lead mb-0">{% translate 'Are you sure you want to delete this app authorization?' %}</p>
|
||||
<p class="mb-0"><strong>You'll need to authorize again if you ever change your mind.</strong></p>
|
||||
</div>
|
|
@ -5,7 +5,7 @@
|
|||
{% if active_tab == 'profile' %}
|
||||
<a class="nav-link active" aria-current="page" href="#">
|
||||
{% else %}
|
||||
<a class="nav-link" aria-current="page" href="{% url 'ui.accounts.settings.profile' %}">
|
||||
<a class="nav-link" href="{% url 'ui.accounts.settings.profile' %}">
|
||||
{% endif %}
|
||||
{% translate 'Profile' %}
|
||||
</a>
|
||||
|
@ -14,7 +14,7 @@
|
|||
{% if active_tab == 'password' %}
|
||||
<a class="nav-link active" aria-current="page" href="#">
|
||||
{% else %}
|
||||
<a class="nav-link" aria-current="page" href="{% url 'ui.accounts.settings.password' %}">
|
||||
<a class="nav-link" href="{% url 'ui.accounts.settings.password' %}">
|
||||
{% endif %}
|
||||
{% translate 'Password' %}
|
||||
</a>
|
||||
|
@ -23,9 +23,18 @@
|
|||
{% if active_tab == 'settings' %}
|
||||
<a class="nav-link active" aria-current="page" href="#">
|
||||
{% else %}
|
||||
<a class="nav-link" aria-current="page" href="{% url 'ui.accounts.settings.settings' %}">
|
||||
<a class="nav-link" href="{% url 'ui.accounts.settings.settings' %}">
|
||||
{% endif %}
|
||||
{% translate 'Settings' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
{% if active_tab == 'apps' %}
|
||||
<a class="nav-link active" aria-current="page" href="#">
|
||||
{% else %}
|
||||
<a class="nav-link" href="{% url 'ui.accounts.apps.index' %}">
|
||||
{% endif %}
|
||||
{% translate 'Apps' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<a href="{{ association.target.url }}" target="_blank" rel="noopener noreferer"><small>{{ association.target.url|render_url_domain }}</small></a>
|
||||
<div class="ms-auto flex-shrink-0 d-flex align-items-center">
|
||||
{% if not association.archived_at %}
|
||||
<div class="spinner-border spinner-border-sm" role="status">
|
||||
<div class="spinner-border spinner-border-sm ui-htmx-indicator" role="status">
|
||||
<span class="visually-hidden">{% translate 'Processing' %}</span>
|
||||
</div>
|
||||
{% if association.is_starred %}
|
||||
|
@ -35,7 +35,7 @@
|
|||
<a
|
||||
class="dropdown-item"
|
||||
hx-get="{% url 'ui.associations.unstar' pk=association.pk %}"
|
||||
hx-indicator='[data-association="{{ association.pk }}"] .spinner-border'
|
||||
hx-indicator='[data-association="{{ association.pk }}"] .ui-htmx-indicator'
|
||||
hx-swap="innerHTML"
|
||||
hx-target='[data-association="{{ association.pk }}"]'
|
||||
href="{% url 'ui.associations.unstar' pk=association.pk %}"
|
||||
|
@ -48,7 +48,7 @@
|
|||
<a
|
||||
class="dropdown-item"
|
||||
hx-get="{% url 'ui.associations.star' pk=association.pk %}"
|
||||
hx-indicator='[data-association="{{ association.pk }}"] .spinner-border'
|
||||
hx-indicator='[data-association="{{ association.pk }}"] .ui-htmx-indicator'
|
||||
hx-swap="innerHTML"
|
||||
hx-target='[data-association="{{ association.pk }}"]'
|
||||
href="{% url 'ui.associations.star' pk=association.pk %}"
|
||||
|
@ -70,7 +70,7 @@
|
|||
class="dropdown-item text-warning"
|
||||
data-ui-modal="#BrowseSavesView-RefreshModal"
|
||||
hx-confirm='CANHAZCONFIRM'
|
||||
hx-indicator='[data-association="{{ association.pk }}"] .spinner-border'
|
||||
hx-indicator='[data-association="{{ association.pk }}"] .ui-htmx-indicator'
|
||||
hx-post="{% url 'ui.associations.refresh' pk=association.pk %}"
|
||||
hx-swap="none"
|
||||
hx-target='[data-association="{{ association.pk }}"]'
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "ui/page.html" %}
|
||||
|
||||
{% load i18n static ui %}
|
||||
|
||||
{% block title %}{% translate 'Redirecting back to the extension...' %}{% endblock %}
|
||||
|
||||
{% block button_bar_class %}d-none{% endblock %}
|
||||
|
||||
{% block page_body %}
|
||||
<div class="container">
|
||||
<div class="alert alert-success mt-3" role="alert">
|
||||
<h4 class="alert-heading">{% translate 'Done!' %}</h4>
|
||||
<p class="lead mb-0">
|
||||
{% translate "You've successfully logged in to the extension." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -10,7 +10,7 @@
|
|||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle ui-navbar-brand" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{% spaceless %}
|
||||
<img src="{% static 'ui/img/icon-180.png' %}">
|
||||
<img src="{% static 'ui/img/icon-48.png' %}">
|
||||
<span class="ms-2">{% block top_nav_title %}HotPocket{% endblock %}</span>
|
||||
{% endspaceless %}
|
||||
</a>
|
||||
|
@ -35,7 +35,7 @@
|
|||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link pe-none ui-navbar-brand">
|
||||
<img src="{% static 'ui/img/icon-180.png' %}">
|
||||
<img src="{% static 'ui/img/icon-48.png' %}">
|
||||
<span class="ms-2">{{ SITE_TITLE }}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
@ -146,6 +146,7 @@
|
|||
<script src="{% static 'ui/js/hotpocket-backend.ui.Modal.js' %}" type="text/javascript"></script>
|
||||
<script src="{% static 'ui/js/hotpocket-backend.ui.BrowseSavesView.js' %}" type="text/javascript"></script>
|
||||
<script src="{% static 'ui/js/hotpocket-backend.ui.ViewAssociationView.js' %}" type="text/javascript"></script>
|
||||
<script src="{% static 'ui/js/hotpocket-backend.ui.BrowseAccountAppsView.js' %}" type="text/javascript"></script>
|
||||
{% block page_scripts %}{% endblock %}
|
||||
<script type="text/javascript">
|
||||
(() => {
|
||||
|
|
|
@ -8,10 +8,19 @@ import urllib.parse
|
|||
from django import template
|
||||
from django.contrib.messages import Message
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from hotpocket_backend.apps.ui.constants import MessageLevelAlertClass
|
||||
from hotpocket_backend.apps.ui.constants import (
|
||||
MessageLevelAlertClass,
|
||||
UIAccessTokenOriginApp,
|
||||
)
|
||||
from hotpocket_backend.apps.ui.dto.base import BrowseParams
|
||||
from hotpocket_common.constants import AssociationsSearchMode
|
||||
from hotpocket_common.constants import (
|
||||
AccessTokenOriginApp,
|
||||
AssociationsSearchMode,
|
||||
)
|
||||
from hotpocket_soa.dto.accounts import AccessTokenOut
|
||||
from hotpocket_soa.dto.saves import SaveOut
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
@ -115,3 +124,49 @@ def alert_class(message: Message | None) -> str:
|
|||
)
|
||||
|
||||
return 'alert-secondary'
|
||||
|
||||
|
||||
@register.filter(name='render_access_token_app')
|
||||
def render_access_token_app(access_token: AccessTokenOut) -> str:
|
||||
app: str = access_token.get_origin_app_id()
|
||||
variant = 'secondary'
|
||||
|
||||
origin_app = access_token.get_origin_app()
|
||||
match origin_app:
|
||||
case AccessTokenOriginApp.SAFARI_WEB_EXTENSION:
|
||||
app = UIAccessTokenOriginApp[origin_app.value].value
|
||||
variant = 'info'
|
||||
|
||||
return format_html(
|
||||
'<span class="badge text-bg-{}">{}</span>',
|
||||
variant,
|
||||
app,
|
||||
)
|
||||
|
||||
|
||||
@register.filter(name='render_access_token_platform')
|
||||
def render_access_token_platform(access_token: AccessTokenOut) -> str:
|
||||
match access_token.meta.get('platform', None):
|
||||
case 'MacIntel':
|
||||
return 'macOS'
|
||||
|
||||
case 'iPhone':
|
||||
return 'iOS'
|
||||
|
||||
case 'iPad':
|
||||
return 'iPadOS'
|
||||
|
||||
case 'Win32':
|
||||
return 'Windows'
|
||||
|
||||
case 'Linux x86_64':
|
||||
return 'Linux'
|
||||
|
||||
case 'Linux armv81':
|
||||
return 'Linux'
|
||||
|
||||
case None:
|
||||
return _('Unknown')
|
||||
|
||||
case _:
|
||||
return access_token.meta['platform']
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from bthlabs_jsonrpc_django import is_authenticated
|
||||
from django.urls import path
|
||||
|
||||
from hotpocket_backend.apps.core.rpc import JSONRPCView
|
||||
from hotpocket_backend.apps.ui.constants import StarUnstarAssociationViewMode
|
||||
|
||||
# isort: off
|
||||
|
@ -20,33 +22,44 @@ from .views import (
|
|||
urlpatterns = [
|
||||
path(
|
||||
'accounts/login/',
|
||||
accounts.LoginView.as_view(),
|
||||
accounts.auth.LoginView.as_view(),
|
||||
name='ui.accounts.login',
|
||||
),
|
||||
path(
|
||||
'accounts/post-login/',
|
||||
accounts.PostLoginView.as_view(),
|
||||
accounts.auth.PostLoginView.as_view(),
|
||||
name='ui.accounts.post_login',
|
||||
),
|
||||
path('accounts/logout/', accounts.logout, name='ui.accounts.logout'),
|
||||
path('accounts/browse/', accounts.browse, name='ui.accounts.browse'),
|
||||
path('accounts/settings/', accounts.settings, name='ui.accounts.settings'),
|
||||
path('accounts/logout/', accounts.auth.logout, name='ui.accounts.logout'),
|
||||
path('accounts/browse/', accounts.browse.browse, name='ui.accounts.browse'),
|
||||
path('accounts/settings/', accounts.settings.settings, name='ui.accounts.settings'),
|
||||
path(
|
||||
'accounts/settings/profile/',
|
||||
accounts.ProfileView.as_view(),
|
||||
accounts.settings.ProfileView.as_view(),
|
||||
name='ui.accounts.settings.profile',
|
||||
),
|
||||
path(
|
||||
'accounts/settings/password/',
|
||||
accounts.PasswordView.as_view(),
|
||||
accounts.settings.PasswordView.as_view(),
|
||||
name='ui.accounts.settings.password',
|
||||
),
|
||||
path(
|
||||
'accounts/settings/settings/',
|
||||
accounts.SettingsView.as_view(),
|
||||
accounts.settings.SettingsView.as_view(),
|
||||
name='ui.accounts.settings.settings',
|
||||
),
|
||||
path('accounts/', accounts.index, name='ui.accounts.index'),
|
||||
path('accounts/apps/', accounts.apps.index, name='ui.accounts.apps.index'),
|
||||
path(
|
||||
'accounts/apps/browse/',
|
||||
accounts.apps.browse,
|
||||
name='ui.accounts.apps.browse',
|
||||
),
|
||||
path(
|
||||
'accounts/apps/delete/<str:pk>',
|
||||
accounts.apps.DeleteView.as_view(),
|
||||
name='ui.accounts.apps.delete',
|
||||
),
|
||||
path('accounts/', accounts.index.index, name='ui.accounts.index'),
|
||||
path(
|
||||
'imports/pocket/',
|
||||
imports.PocketImportView.as_view(),
|
||||
|
@ -62,6 +75,16 @@ urlpatterns = [
|
|||
integrations.android.share_sheet,
|
||||
name='ui.integrations.android.share_sheet',
|
||||
),
|
||||
path(
|
||||
'integrations/extension/authenticate/',
|
||||
integrations.extension.authenticate,
|
||||
name='ui.integrations.extension.authenticate',
|
||||
),
|
||||
path(
|
||||
'integrations/extension/post-authenticate/',
|
||||
integrations.extension.post_authenticate,
|
||||
name='ui.integrations.extension.post_authenticate',
|
||||
),
|
||||
path(
|
||||
'saves/create/',
|
||||
saves.CreateView.as_view(),
|
||||
|
@ -107,5 +130,12 @@ urlpatterns = [
|
|||
),
|
||||
path('associations/', associations.index, name='ui.associations.index'),
|
||||
path('manifest.json', meta.manifest_json, name='ui.meta.manifest_json'),
|
||||
path(
|
||||
'rpc/',
|
||||
JSONRPCView.as_view(
|
||||
auth_checks=[is_authenticated],
|
||||
),
|
||||
name='ui.rpc',
|
||||
),
|
||||
path('', index.index, name='ui.index.index'),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
from . import apps # noqaz: F401
|
||||
from . import auth # noqa: F401
|
||||
from . import browse # noqa: F401
|
||||
from . import index # noqa: F401
|
||||
from . import settings # noqa: F401
|
|
@ -0,0 +1,167 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
from django.contrib import messages
|
||||
import django.db
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import FormView
|
||||
from django_htmx.http import trigger_client_event
|
||||
|
||||
from hotpocket_backend.apps.accounts.decorators import account_required
|
||||
from hotpocket_backend.apps.accounts.mixins import AccountRequiredMixin
|
||||
from hotpocket_backend.apps.htmx import messages as htmx_messages
|
||||
from hotpocket_backend.apps.ui.constants import UIAccessTokenOriginApp
|
||||
from hotpocket_backend.apps.ui.dto.accounts import AppsBrowseParams
|
||||
from hotpocket_backend.apps.ui.forms.accounts.apps import DeleteForm
|
||||
from hotpocket_backend.apps.ui.services import UIAccessTokensService
|
||||
from hotpocket_soa.dto.accounts import AccessTokenOut, AccessTokensQuery
|
||||
from hotpocket_soa.services import AccessTokensService
|
||||
|
||||
|
||||
class AccessTokenMixin:
|
||||
def get_access_token(self) -> AccessTokenOut:
|
||||
if hasattr(self, '_access_token') is False:
|
||||
setattr(
|
||||
self,
|
||||
'_access_token',
|
||||
UIAccessTokensService().get_or_404(
|
||||
account_uuid=self.request.user.pk, # type: ignore[attr-defined]
|
||||
pk=self.kwargs['pk'], # type: ignore[attr-defined]
|
||||
),
|
||||
)
|
||||
|
||||
return self._access_token # type: ignore[attr-defined]
|
||||
|
||||
|
||||
class DetailView(AccessTokenMixin, AccountRequiredMixin, FormView):
|
||||
def get_context_data(self, **kwargs) -> dict:
|
||||
result = super().get_context_data(**kwargs)
|
||||
|
||||
result.update({
|
||||
'access_token': self.get_access_token(),
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class ConfirmationView(DetailView):
|
||||
def get_initial(self) -> dict:
|
||||
result = super().get_initial()
|
||||
|
||||
access_token: AccessTokenOut = self.get_access_token()
|
||||
|
||||
origin_app = access_token.get_origin_app()
|
||||
if origin_app is not None:
|
||||
origin_app = UIAccessTokenOriginApp[origin_app.value].value
|
||||
|
||||
result.update({
|
||||
'canhazconfirm': 'hai',
|
||||
'origin_app': origin_app or access_token.get_origin_app_id(),
|
||||
'platform': access_token.meta.get('platform', '-'),
|
||||
'version': access_token.meta.get('version', '-'),
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('ui.accounts.apps.browse')
|
||||
|
||||
|
||||
@account_required
|
||||
def index(request: HttpRequest) -> HttpResponse:
|
||||
return redirect(reverse('ui.accounts.apps.browse'))
|
||||
|
||||
|
||||
@account_required
|
||||
def browse(request: HttpRequest) -> HttpResponse:
|
||||
params = AppsBrowseParams.from_request(request=request)
|
||||
|
||||
access_tokens = AccessTokensService().search(
|
||||
query=AccessTokensQuery.model_validate(dict(
|
||||
account_uuid=request.user.pk,
|
||||
before=params.before,
|
||||
)),
|
||||
limit=params.limit,
|
||||
)
|
||||
|
||||
before: uuid.UUID | None = None
|
||||
if len(access_tokens) == params.limit:
|
||||
before = access_tokens[-1].pk
|
||||
|
||||
next_url: str | None = None
|
||||
if before is not None:
|
||||
next_url = reverse('ui.accounts.apps.browse', query=[
|
||||
('before', before),
|
||||
('limit', params.limit),
|
||||
])
|
||||
|
||||
context = {
|
||||
'access_tokens': access_tokens,
|
||||
'params': params,
|
||||
'before': before,
|
||||
'next_url': next_url,
|
||||
}
|
||||
|
||||
if request.htmx:
|
||||
response = render(
|
||||
request,
|
||||
'ui/accounts/partials/apps/apps.html',
|
||||
context,
|
||||
)
|
||||
|
||||
return trigger_client_event(
|
||||
response,
|
||||
'HotPocket:BrowseAccountAppsView:updateLoadMoreButton',
|
||||
{'next_url': next_url},
|
||||
after='swap',
|
||||
)
|
||||
|
||||
return render(
|
||||
request,
|
||||
'ui/accounts/apps/browse.html',
|
||||
context,
|
||||
)
|
||||
|
||||
|
||||
class DeleteView(ConfirmationView):
|
||||
template_name = 'ui/accounts/apps/delete.html'
|
||||
form_class = DeleteForm
|
||||
|
||||
def form_valid(self, form: DeleteForm) -> HttpResponse:
|
||||
with django.db.transaction.atomic():
|
||||
result = AccessTokensService().delete(
|
||||
access_token=self.get_access_token(),
|
||||
)
|
||||
|
||||
if self.request.htmx:
|
||||
response = JsonResponse({
|
||||
'status': 'ok',
|
||||
'result': result,
|
||||
})
|
||||
|
||||
htmx_messages.add_htmx_message(
|
||||
request=self.request,
|
||||
response=response,
|
||||
level=htmx_messages.SUCCESS,
|
||||
message=_('The app auhtorization has been deleted.'),
|
||||
)
|
||||
|
||||
return trigger_client_event(
|
||||
response,
|
||||
'HotPocket:BrowseAccountAppsView:delete',
|
||||
{'pk': self.kwargs['pk']},
|
||||
after='swap',
|
||||
)
|
||||
|
||||
messages.add_message(
|
||||
self.request,
|
||||
messages.SUCCESS,
|
||||
_('The app auhtorization has been deleted.'),
|
||||
)
|
||||
|
||||
return super().form_valid(form)
|
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from django.contrib.auth import logout as auth_logout
|
||||
from django.contrib.auth.views import LoginView as BaseLoginView
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
from hotpocket_backend.apps.core.conf import settings as django_settings
|
||||
from hotpocket_backend.apps.ui.forms.accounts.auth import LoginForm
|
||||
|
||||
|
||||
class LoginView(BaseLoginView):
|
||||
template_name = 'ui/accounts/login.html'
|
||||
form_class = LoginForm
|
||||
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
request.session['post_login_next_url'] = request.GET.get('next', None)
|
||||
request.session.save()
|
||||
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('ui.accounts.post_login')
|
||||
|
||||
|
||||
class PostLoginView(RedirectView):
|
||||
def get_redirect_url(self, *args, **kwargs) -> str:
|
||||
next_url = self.request.session.pop('post_login_next_url', None)
|
||||
self.request.session.save()
|
||||
|
||||
allowed_hosts = None
|
||||
if len(django_settings.ALLOWED_HOSTS) > 0:
|
||||
allowed_hosts = set(filter(
|
||||
lambda value: value != '*',
|
||||
django_settings.ALLOWED_HOSTS,
|
||||
))
|
||||
|
||||
if next_url is not None:
|
||||
next_url_is_safe = url_has_allowed_host_and_scheme(
|
||||
url=next_url,
|
||||
allowed_hosts=allowed_hosts,
|
||||
require_https=self.request.is_secure(),
|
||||
)
|
||||
|
||||
if next_url_is_safe is False:
|
||||
next_url = None
|
||||
|
||||
return next_url or reverse('ui.index.index')
|
||||
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
if request.user.is_anonymous is True:
|
||||
raise PermissionDenied('NOPE')
|
||||
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
def logout(request: HttpRequest) -> HttpResponse:
|
||||
if request.user.is_authenticated is True:
|
||||
auth_logout(request)
|
||||
|
||||
return render(request, 'ui/accounts/logout.html')
|
|
@ -0,0 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from django.http import Http404, HttpRequest, HttpResponse
|
||||
|
||||
from hotpocket_backend.apps.accounts.decorators import account_required
|
||||
|
||||
|
||||
@account_required
|
||||
def browse(request: HttpRequest) -> HttpResponse:
|
||||
raise Http404()
|