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
|
push: false
|
||||||
load: true
|
load: true
|
||||||
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/packages:ci-local"
|
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"
|
- name: "Run `backend` checks"
|
||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm backend-ci inv ci
|
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm backend-ci inv ci
|
||||||
- name: "Run `packages` checks"
|
- name: "Run `packages` checks"
|
||||||
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm packages-ci inv ci
|
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"
|
- name: "Clean up"
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"targets": [
|
"targets": [
|
||||||
"backend-management",
|
"backend-management",
|
||||||
"caddy",
|
"caddy",
|
||||||
|
"extension-management",
|
||||||
"keycloak",
|
"keycloak",
|
||||||
"packages-management",
|
"packages-management",
|
||||||
"postgres",
|
"postgres",
|
||||||
|
@ -67,6 +68,28 @@
|
||||||
"type=docker,load=true,push=false"
|
"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": {
|
"caddy": {
|
||||||
"context": "services/",
|
"context": "services/",
|
||||||
"dockerfile": "caddy/Dockerfile",
|
"dockerfile": "caddy/Dockerfile",
|
||||||
|
|
|
@ -13,3 +13,4 @@ services:
|
||||||
include:
|
include:
|
||||||
- path: "./services/backend/docker-compose-ci.yaml"
|
- path: "./services/backend/docker-compose-ci.yaml"
|
||||||
- path: "./services/packages/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: "./docker-compose-cloud.yaml"
|
||||||
- path: "./services/backend/docker-compose.yaml"
|
- path: "./services/backend/docker-compose.yaml"
|
||||||
- path: "./services/packages/docker-compose.yaml"
|
- path: "./services/packages/docker-compose.yaml"
|
||||||
|
- path: "./services/extension/docker-compose.yaml"
|
||||||
|
|
||||||
volumes: {}
|
volumes: {}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
_tmp/
|
_tmp/
|
||||||
|
apple/
|
||||||
backend/node_modules/
|
backend/node_modules/
|
||||||
backend/ops/metal/
|
backend/ops/metal/
|
||||||
backend/hotpocket_backend/playground.py
|
backend/hotpocket_backend/playground.py
|
||||||
|
@ -7,4 +8,6 @@ backend/hotpocket_backend/settings/docker/
|
||||||
backend/hotpocket_backend/secrets/metal/
|
backend/hotpocket_backend/secrets/metal/
|
||||||
backend/hotpocket_backend/settings/metal/
|
backend/hotpocket_backend/settings/metal/
|
||||||
backend/hotpocket_backend/static/
|
backend/hotpocket_backend/static/
|
||||||
|
extension/node_modules/
|
||||||
|
extension/dist/
|
||||||
.envrc*
|
.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',
|
'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
|
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
|
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
|
import enum
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class MessageLevelAlertClass(enum.Enum):
|
class MessageLevelAlertClass(enum.Enum):
|
||||||
debug = 'alert-secondary'
|
debug = 'alert-secondary'
|
||||||
|
@ -15,3 +17,7 @@ class MessageLevelAlertClass(enum.Enum):
|
||||||
class StarUnstarAssociationViewMode(enum.Enum):
|
class StarUnstarAssociationViewMode(enum.Enum):
|
||||||
STAR = 'STAR'
|
STAR = 'STAR'
|
||||||
UNSTAR = 'UNSTAR'
|
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 crispy_forms.layout import Layout, Submit
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.forms import (
|
from django.contrib.auth.forms import (
|
||||||
AuthenticationForm as BaseAuthenticationForm,
|
|
||||||
PasswordChangeForm as BasePasswordChangeForm,
|
PasswordChangeForm as BasePasswordChangeForm,
|
||||||
)
|
)
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .base import Form
|
from hotpocket_backend.apps.ui.forms.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',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileForm(Form):
|
class ProfileForm(Form):
|
||||||
|
@ -131,17 +110,17 @@ class PasswordForm(BasePasswordChangeForm):
|
||||||
|
|
||||||
|
|
||||||
class FederatedPasswordForm(PasswordForm):
|
class FederatedPasswordForm(PasswordForm):
|
||||||
current_password = forms.CharField(
|
old_password = forms.CharField(
|
||||||
label=_('Old password'),
|
label=_('Old password'),
|
||||||
disabled=True,
|
disabled=True,
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
new_password = forms.CharField(
|
new_password1 = forms.CharField(
|
||||||
label=_('New password'),
|
label=_('New password'),
|
||||||
disabled=True,
|
disabled=True,
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
new_password_again = forms.CharField(
|
new_password2 = forms.CharField(
|
||||||
label=_('New password confirmation'),
|
label=_('New password confirmation'),
|
||||||
disabled=True,
|
disabled=True,
|
||||||
required=False,
|
required=False,
|
|
@ -5,19 +5,14 @@ from crispy_forms.layout import Submit
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .base import Form
|
from .base import ConfirmationMixin, Form
|
||||||
|
|
||||||
|
|
||||||
class AssociationForm(Form):
|
class AssociationForm(Form):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ConfirmationForm(AssociationForm):
|
class ConfirmationForm(ConfirmationMixin, AssociationForm):
|
||||||
canhazconfirm = forms.CharField(
|
|
||||||
label='',
|
|
||||||
required=True,
|
|
||||||
widget=forms.HiddenInput,
|
|
||||||
)
|
|
||||||
title = forms.CharField(
|
title = forms.CharField(
|
||||||
label=_('Title'),
|
label=_('Title'),
|
||||||
required=False,
|
required=False,
|
||||||
|
|
|
@ -61,3 +61,11 @@ class Form(forms.Form):
|
||||||
template=self.get_form_actions_template(),
|
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 .associations import UIAssociationsService # noqa: F401
|
||||||
from .imports import UIImportsService # noqa: F401
|
from .imports import UIImportsService # noqa: F401
|
||||||
from .saves import UISavesService # 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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from bthlabs_jsonrpc_core import JSONRPCInternalError
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
import django.db
|
import django.db
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
@ -9,19 +11,19 @@ from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from hotpocket_backend.apps.accounts.types import PAccount
|
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
|
from .base import SaveWorkflow
|
||||||
|
|
||||||
|
|
||||||
class CreateSaveWorkflow(SaveWorkflow):
|
class CreateSaveWorkflow(SaveWorkflow):
|
||||||
def run(self,
|
def create_associate_and_process(self,
|
||||||
*,
|
|
||||||
request: HttpRequest,
|
|
||||||
account: PAccount,
|
account: PAccount,
|
||||||
url: str,
|
url: str,
|
||||||
force_post_save: bool = False,
|
) -> tuple[SaveOut, AssociationOut, AsyncResultOut | None]:
|
||||||
) -> HttpResponse:
|
|
||||||
with django.db.transaction.atomic():
|
with django.db.transaction.atomic():
|
||||||
save = self.create(
|
save = self.create(
|
||||||
account.pk,
|
account.pk,
|
||||||
|
@ -30,6 +32,23 @@ class CreateSaveWorkflow(SaveWorkflow):
|
||||||
|
|
||||||
association = self.associate(account.pk, save)
|
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'))
|
response = redirect(reverse('ui.associations.browse'))
|
||||||
if force_post_save is True or save.is_netloc_banned is True:
|
if force_post_save is True or save.is_netloc_banned is True:
|
||||||
response = redirect(reverse(
|
response = redirect(reverse(
|
||||||
|
@ -46,7 +65,22 @@ class CreateSaveWorkflow(SaveWorkflow):
|
||||||
response.headers['X-HotPocket-Testing-Save-PK'] = save.pk
|
response.headers['X-HotPocket-Testing-Save-PK'] = save.pk
|
||||||
response.headers['X-HotPocket-Testing-Association-PK'] = association.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
|
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 {
|
#navbar .ui-navbar-brand > img {
|
||||||
border-radius: 0.25rem;
|
border-radius: 20%;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
@ -45,11 +45,11 @@ body:not(.ui-js-enabled) .ui-noscript-hide {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-save-card .card-footer .spinner-border {
|
.spinner-border.ui-htmx-indicator {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-save-card .card-footer .spinner-border.htmx-request {
|
.spinner-border.ui-htmx-indicator.htmx-request {
|
||||||
display: block;
|
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;
|
this.app = app;
|
||||||
}
|
}
|
||||||
onLoad = (event) => {
|
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');
|
const button = document.querySelector('#BrowseSavesView .ui-load-more-button');
|
||||||
if (button) {
|
if (button) {
|
||||||
if (event.detail.next_url) {
|
if (event.detail.next_url) {
|
||||||
|
@ -34,7 +39,6 @@
|
||||||
|
|
||||||
htmx.process('#BrowseSavesView .ui-load-more-button');
|
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' %}
|
{% if active_tab == 'profile' %}
|
||||||
<a class="nav-link active" aria-current="page" href="#">
|
<a class="nav-link active" aria-current="page" href="#">
|
||||||
{% else %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% translate 'Profile' %}
|
{% translate 'Profile' %}
|
||||||
</a>
|
</a>
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
{% if active_tab == 'password' %}
|
{% if active_tab == 'password' %}
|
||||||
<a class="nav-link active" aria-current="page" href="#">
|
<a class="nav-link active" aria-current="page" href="#">
|
||||||
{% else %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% translate 'Password' %}
|
{% translate 'Password' %}
|
||||||
</a>
|
</a>
|
||||||
|
@ -23,9 +23,18 @@
|
||||||
{% if active_tab == 'settings' %}
|
{% if active_tab == 'settings' %}
|
||||||
<a class="nav-link active" aria-current="page" href="#">
|
<a class="nav-link active" aria-current="page" href="#">
|
||||||
{% else %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% translate 'Settings' %}
|
{% translate 'Settings' %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<a href="{{ association.target.url }}" target="_blank" rel="noopener noreferer"><small>{{ association.target.url|render_url_domain }}</small></a>
|
<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">
|
<div class="ms-auto flex-shrink-0 d-flex align-items-center">
|
||||||
{% if not association.archived_at %}
|
{% 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>
|
<span class="visually-hidden">{% translate 'Processing' %}</span>
|
||||||
</div>
|
</div>
|
||||||
{% if association.is_starred %}
|
{% if association.is_starred %}
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
<a
|
<a
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
hx-get="{% url 'ui.associations.unstar' pk=association.pk %}"
|
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-swap="innerHTML"
|
||||||
hx-target='[data-association="{{ association.pk }}"]'
|
hx-target='[data-association="{{ association.pk }}"]'
|
||||||
href="{% url 'ui.associations.unstar' pk=association.pk %}"
|
href="{% url 'ui.associations.unstar' pk=association.pk %}"
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
<a
|
<a
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
hx-get="{% url 'ui.associations.star' pk=association.pk %}"
|
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-swap="innerHTML"
|
||||||
hx-target='[data-association="{{ association.pk }}"]'
|
hx-target='[data-association="{{ association.pk }}"]'
|
||||||
href="{% url 'ui.associations.star' pk=association.pk %}"
|
href="{% url 'ui.associations.star' pk=association.pk %}"
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
class="dropdown-item text-warning"
|
class="dropdown-item text-warning"
|
||||||
data-ui-modal="#BrowseSavesView-RefreshModal"
|
data-ui-modal="#BrowseSavesView-RefreshModal"
|
||||||
hx-confirm='CANHAZCONFIRM'
|
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-post="{% url 'ui.associations.refresh' pk=association.pk %}"
|
||||||
hx-swap="none"
|
hx-swap="none"
|
||||||
hx-target='[data-association="{{ association.pk }}"]'
|
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">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle ui-navbar-brand" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<a class="nav-link dropdown-toggle ui-navbar-brand" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
{% spaceless %}
|
{% 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>
|
<span class="ms-2">{% block top_nav_title %}HotPocket{% endblock %}</span>
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
</a>
|
</a>
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link pe-none ui-navbar-brand">
|
<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>
|
<span class="ms-2">{{ SITE_TITLE }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</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.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.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.ViewAssociationView.js' %}" type="text/javascript"></script>
|
||||||
|
<script src="{% static 'ui/js/hotpocket-backend.ui.BrowseAccountAppsView.js' %}" type="text/javascript"></script>
|
||||||
{% block page_scripts %}{% endblock %}
|
{% block page_scripts %}{% endblock %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
(() => {
|
(() => {
|
||||||
|
|
|
@ -8,10 +8,19 @@ import urllib.parse
|
||||||
from django import template
|
from django import template
|
||||||
from django.contrib.messages import Message
|
from django.contrib.messages import Message
|
||||||
from django.urls import reverse_lazy
|
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_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
|
from hotpocket_soa.dto.saves import SaveOut
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -115,3 +124,49 @@ def alert_class(message: Message | None) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
return 'alert-secondary'
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from bthlabs_jsonrpc_django import is_authenticated
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
from hotpocket_backend.apps.core.rpc import JSONRPCView
|
||||||
from hotpocket_backend.apps.ui.constants import StarUnstarAssociationViewMode
|
from hotpocket_backend.apps.ui.constants import StarUnstarAssociationViewMode
|
||||||
|
|
||||||
# isort: off
|
# isort: off
|
||||||
|
@ -20,33 +22,44 @@ from .views import (
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(
|
path(
|
||||||
'accounts/login/',
|
'accounts/login/',
|
||||||
accounts.LoginView.as_view(),
|
accounts.auth.LoginView.as_view(),
|
||||||
name='ui.accounts.login',
|
name='ui.accounts.login',
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
'accounts/post-login/',
|
'accounts/post-login/',
|
||||||
accounts.PostLoginView.as_view(),
|
accounts.auth.PostLoginView.as_view(),
|
||||||
name='ui.accounts.post_login',
|
name='ui.accounts.post_login',
|
||||||
),
|
),
|
||||||
path('accounts/logout/', accounts.logout, name='ui.accounts.logout'),
|
path('accounts/logout/', accounts.auth.logout, name='ui.accounts.logout'),
|
||||||
path('accounts/browse/', accounts.browse, name='ui.accounts.browse'),
|
path('accounts/browse/', accounts.browse.browse, name='ui.accounts.browse'),
|
||||||
path('accounts/settings/', accounts.settings, name='ui.accounts.settings'),
|
path('accounts/settings/', accounts.settings.settings, name='ui.accounts.settings'),
|
||||||
path(
|
path(
|
||||||
'accounts/settings/profile/',
|
'accounts/settings/profile/',
|
||||||
accounts.ProfileView.as_view(),
|
accounts.settings.ProfileView.as_view(),
|
||||||
name='ui.accounts.settings.profile',
|
name='ui.accounts.settings.profile',
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
'accounts/settings/password/',
|
'accounts/settings/password/',
|
||||||
accounts.PasswordView.as_view(),
|
accounts.settings.PasswordView.as_view(),
|
||||||
name='ui.accounts.settings.password',
|
name='ui.accounts.settings.password',
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
'accounts/settings/settings/',
|
'accounts/settings/settings/',
|
||||||
accounts.SettingsView.as_view(),
|
accounts.settings.SettingsView.as_view(),
|
||||||
name='ui.accounts.settings.settings',
|
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(
|
path(
|
||||||
'imports/pocket/',
|
'imports/pocket/',
|
||||||
imports.PocketImportView.as_view(),
|
imports.PocketImportView.as_view(),
|
||||||
|
@ -62,6 +75,16 @@ urlpatterns = [
|
||||||
integrations.android.share_sheet,
|
integrations.android.share_sheet,
|
||||||
name='ui.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(
|
path(
|
||||||
'saves/create/',
|
'saves/create/',
|
||||||
saves.CreateView.as_view(),
|
saves.CreateView.as_view(),
|
||||||
|
@ -107,5 +130,12 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
path('associations/', associations.index, name='ui.associations.index'),
|
path('associations/', associations.index, name='ui.associations.index'),
|
||||||
path('manifest.json', meta.manifest_json, name='ui.meta.manifest_json'),
|
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'),
|
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()
|